Repository navigation
Conversation
The interim review and the phase judge shared one function for a single attempt on the report keys, kept inside the large gemini module. Another caller of the same attempt is coming, and it should not have to reach into that module's private helpers. Move the function to gemini/side_calls.rs unchanged. Behavior stays the same; the existing review and judge tests cover it. Change-Id: I84e397d70ff117debd3dd6c703ec541c5de0b5f5
The editor, line numbers, indent guides, bracket handling, history and run results were wired directly into interview.js. A second page needs the same editor, and copying 600 lines of it would let the two drift. Move it into web/workspace.js behind mountWorkspace, and have the interview page mount it. The editor behaves as before; the browser tests that read the old handler and paint code now read the new functions. Change-Id: Iff887ada21e5ebedee3be31079f1e7c90f324488
Practice interviews today need an AI interviewer. Add an opt-in mode, CODETRIAL_PEER_ENABLED, where two signed-in members interview each other instead. Members are identified by their GitHub login and a nickname, and are matched through a LiveKit lobby or a private invite. A session has two rounds with swapped roles. The candidate and the interviewer share one code buffer behind a writer lease, the server holds the problem, tests and guide, and each member submits written feedback that stays private until both have. A silent AI observer joins the room, never speaks, and produces advice for each member. Transcript and audio capture are independent choices. Both are kept apart from the AI interview reports, readable only by the two members, and deleted on request or after 24 hours. Room capacity counts waiting rooms and active sessions together, and a confirmed lobby pair keeps its reservation until it is cancelled. The server refuses to start in this mode without GitHub sign-in and the observer's Gemini key, and caps a session at two hours so the observer's capture stays bounded. Change-Id: Ie2a568c015088e6fcdff055151a9c50b8286987f
The peer service has no browser half yet. Add a lobby page where a signed-in member sets a nickname, publishes availability, answers pairing requests or follows an invite, and an interview page for the two rounds: the shared workspace, the problem and guide, the writer lease, the mute toggle, feedback, and the transcript, audio and advice the observer produced. Both pages are plain modules with no build step. The home page links to the lobby only for a verified member of a deployment that turned the mode on, and the shared results renderer can now leave out the interview-only hint. Change-Id: I02db219c4737e5a310c7b6910826c504305eb95f
The new mode changes what a deployment stores and who can read it, so the contracts that describe that need to say so. Add docs/peer-interviews.md for configuration, the session and round lifecycle, the observer, the transcript and audio retention, and a staging check. Extend the recording contract, the observable-delivery policy and the provider-cost note with how peer capture differs from an AI interview, and link the new page from the README. Change-Id: I31971093011d80ff450520fb40a4bbdc2027b1c0
Reciprocal practice was the one feature an operator had to switch on with CODETRIAL_PEER_ENABLED, and startup refused to run when that flag was set without GitHub sign-in or the observer's Google keys. Treat it like the other optional features instead. It is offered whenever GitHub OAuth credentials and a Google key for the silent observer are present, and is otherwise left out, which the session reply already reports as peerEnabled false. The CODETRIAL_PEER_* limits are validated either way, and an observer cap above two hours still fails startup. Change-Id: I39156d0c52e82952e7712f369cad4ab17332d05a
Verified each automated review comment against the code and fixed the ones that held. Security and limits: reject web-root symlinks that reach the audio directory, classify the snapshot rate limit on the decoded path, check the invite version, and size the snapshot ceiling from its schema. Require both rounds to fit the hard limit and give LiveKit a departure timeout that follows the configured reconnect grace. Retention and data: prune finalizing sessions and paired availability rows once their room is gone, index the audio sweep on its predicate, and drop the unreliable transcript wipe. Advice evidence needs a real excerpt and the verbatim check matches whole words. Pages: order previews with a sequence number, keep polling when an invite or session load fails, drop a rejected invite token, handle expired transcripts and a disconnected room, and report every error the server can send. Tests: cover the previously untested paths and bound the waits. Change-Id: Ie76b7e2ca665bb04b3499ef1a67d6c506da58824
There was a problem hiding this comment.
3 issues found across 50 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="web/peer-lib.js">
<violation number="1" location="web/peer-lib.js:58">
P2: The new `lease_lost` text says to reload, but the page's status line says to recover access or download unsaved code. Reloading can drop unsaved edits in this tab. Reword the message to match the recovery path, for example "Edit access moved to another tab or expired. Use Recover access to continue."</violation>
</file>
<file name="src/accounts/peer.rs">
<violation number="1" location="src/accounts/peer.rs:453">
P2: This check caps each round at half the hard maximum, but the lobby input still advertises `max="90"`. Members who enter 46 to 90 minutes will get `invalid_settings` at submit. Update the input's `max` to follow the configured limit, or have the lobby state the effective ceiling.</violation>
</file>
<file name="web/peer-artifacts.js">
<violation number="1" location="web/peer-artifacts.js:101">
P2: A transcript `not_found` also means the partner withdrew the transcript, not only that the entire practice expired. Calling `expire()` then hides still-available advice and audio and reports a false expiration; distinguish transcript withdrawal from retention expiry before clearing unrelated artifacts.</violation>
</file>
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
| request_limit: "Too many requests. Wait a moment and try again.", | ||
| feedback_required: "Submit your feedback before moving on.", | ||
| lease_lost: | ||
| "Edit access moved to another tab or expired. Reload to edit again.", |
There was a problem hiding this comment.
P2: The new lease_lost text says to reload, but the page's status line says to recover access or download unsaved code. Reloading can drop unsaved edits in this tab. Reword the message to match the recovery path, for example "Edit access moved to another tab or expired. Use Recover access to continue."
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At web/peer-lib.js, line 58:
<comment>The new `lease_lost` text says to reload, but the page's status line says to recover access or download unsaved code. Reloading can drop unsaved edits in this tab. Reword the message to match the recovery path, for example "Edit access moved to another tab or expired. Use Recover access to continue."</comment>
<file context>
@@ -52,7 +54,10 @@ const PEER_ERRORS = {
feedback_required: "Submit your feedback before moving on.",
- lease_lost: "Your partner took over the editor.",
+ lease_lost:
+ "Edit access moved to another tab or expired. Reload to edit again.",
+ round_sealed: "This round has ended.",
+ round_unavailable: "This round is not available.",
</file context>
| "Edit access moved to another tab or expired. Reload to edit again.", | |
| "Edit access moved to another tab or expired. Use Recover access to continue.", |
| || !["easy", "medium", "hard"].contains(&settings.difficulty.to_ascii_lowercase().as_str()) | ||
| || !["python", "javascript", "cpp", "c", "java"].contains(&settings.language.as_str()) | ||
| || settings.round_minutes == 0 | ||
| || settings.round_minutes.saturating_mul(2) > config.hard_max_minutes |
There was a problem hiding this comment.
P2: This check caps each round at half the hard maximum, but the lobby input still advertises max="90". Members who enter 46 to 90 minutes will get invalid_settings at submit. Update the input's max to follow the configured limit, or have the lobby state the effective ceiling.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/accounts/peer.rs, line 453:
<comment>This check caps each round at half the hard maximum, but the lobby input still advertises `max="90"`. Members who enter 46 to 90 minutes will get `invalid_settings` at submit. Update the input's `max` to follow the configured limit, or have the lobby state the effective ceiling.</comment>
<file context>
@@ -450,7 +450,7 @@ pub(super) fn valid_settings(settings: &Settings, config: &crate::config::PeerCo
|| !["python", "javascript", "cpp", "c", "java"].contains(&settings.language.as_str())
|| settings.round_minutes == 0
- || settings.round_minutes > config.hard_max_minutes
+ || settings.round_minutes.saturating_mul(2) > config.hard_max_minutes
|| settings.topic.len() > 80
|| rounds::eligible_problems(settings).len() < 2)
</file context>
| pending = ["processing", "observing"].includes(answer.status); | ||
| } catch (error) { | ||
| if (error.code === "not_found") { | ||
| expire(); |
There was a problem hiding this comment.
P2: A transcript not_found also means the partner withdrew the transcript, not only that the entire practice expired. Calling expire() then hides still-available advice and audio and reports a false expiration; distinguish transcript withdrawal from retention expiry before clearing unrelated artifacts.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At web/peer-artifacts.js, line 101:
<comment>A transcript `not_found` also means the partner withdrew the transcript, not only that the entire practice expired. Calling `expire()` then hides still-available advice and audio and reports a false expiration; distinguish transcript withdrawal from retention expiry before clearing unrelated artifacts.</comment>
<file context>
@@ -85,15 +98,7 @@ export function mountPeerArtifacts({
- nodes.withdrawTranscript.hidden = true;
- nodes.audioNote.textContent = "";
- nodes.transcriptNote.textContent = "";
+ expire();
refreshing = false;
return;
</file context>
Adds reciprocal peer mock interviews, so two signed-in members can practice interviewing each other. It is offered like the other features once GitHub sign-in and a Google key for the silent observer are configured, with no separate enable flag. Members are identified by GitHub login plus a nickname, matched through a LiveKit lobby or a private invite, and play two rounds with swapped roles, sharing code, tests and a hint guide. Each round ends with written human feedback, and a silent AI observer adds suggestions after the second.
Transcripts and audio are saved only if a member chooses, kept separately from the AI interview reports, readable by the two members alone, and removed on request or after 24 hours. Closed sessions, code snapshots and feedback are deleted 30 days after creation by an hourly worker.
The Gemini side-call attempt and the code workspace are moved out of the existing interview code so both modes share them; the browser half stays plain web/*.js with no build step. The commits go in that order: shared pieces, service, pages, docs, then the flag removal.
Verified with ./scripts/test.sh. The last full run failed only clippy on an unused import, since fixed; clippy and the format check pass again. Desktop clippy and cargo-deny were not run. Left out: a per-account lobby limit, observer reconnect (documented as a limitation) and problem shuffling.