@vibedeckx/linux-x64 0.3.28 → 0.3.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +512 -90
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -186767,6 +186767,7 @@ function getWorkspaceBindingReadMetrics() {
|
|
|
186767
186767
|
|
|
186768
186768
|
// src/storage/workflow-run-status.ts
|
|
186769
186769
|
var WORKFLOW_ACTIVE_STATUSES = [
|
|
186770
|
+
"preparing",
|
|
186770
186771
|
"waiting_reviewer",
|
|
186771
186772
|
"waiting_feedback",
|
|
186772
186773
|
"discussing",
|
|
@@ -186844,7 +186845,9 @@ var mapAgentSession = (row) => ({
|
|
|
186844
186845
|
last_completed_at: row.last_completed_at,
|
|
186845
186846
|
favorited_at: row.favorited_at,
|
|
186846
186847
|
native_session_id: row.native_session_id,
|
|
186847
|
-
history_epoch: row.history_epoch
|
|
186848
|
+
history_epoch: row.history_epoch,
|
|
186849
|
+
branched_from_session_id: row.branched_from_session_id,
|
|
186850
|
+
branched_from_entry_index: row.branched_from_entry_index
|
|
186848
186851
|
});
|
|
186849
186852
|
var parseActivityTimestamp = (value) => {
|
|
186850
186853
|
const explicitZone = /(?:Z|[+-]\d\d:\d\d)$/i.test(value);
|
|
@@ -187007,6 +187010,7 @@ var mapRemoteCreationIntent = (row) => ({
|
|
|
187007
187010
|
var mapRemoteReviewerCreationIntent = (row) => ({
|
|
187008
187011
|
...row,
|
|
187009
187012
|
review_span: row.review_span,
|
|
187013
|
+
review_context_mode: row.review_context_mode ?? null,
|
|
187010
187014
|
status: row.status
|
|
187011
187015
|
});
|
|
187012
187016
|
var nowActivityAt = () => sql`cast((julianday('now') - 2440587.5) * 86400000 as integer)`;
|
|
@@ -187231,6 +187235,9 @@ var createAgentSessionRepos = (kdb, h) => ({
|
|
|
187231
187235
|
// Toggle favorite without touching updated_at — favoriting is a passive
|
|
187232
187236
|
// bookmark, not a "this session was active" signal, so it must not
|
|
187233
187237
|
// disturb the dropdown's recency ordering.
|
|
187238
|
+
setBranchedFrom: async (id, sourceSessionId, entryIndex) => {
|
|
187239
|
+
await kdb.updateTable("agent_sessions").set({ branched_from_session_id: sourceSessionId, branched_from_entry_index: entryIndex }).where("id", "=", id).execute();
|
|
187240
|
+
},
|
|
187234
187241
|
setFavorited: async (id, favorited) => {
|
|
187235
187242
|
await kdb.updateTable("agent_sessions").set({ favorited_at: favorited ? Date.now() : null }).where("id", "=", id).execute();
|
|
187236
187243
|
},
|
|
@@ -187549,6 +187556,7 @@ var createAgentSessionRepos = (kdb, h) => ({
|
|
|
187549
187556
|
review_focus: intent.reviewFocus ?? null,
|
|
187550
187557
|
source_turn_end_index: intent.sourceTurnEndIndex ?? null,
|
|
187551
187558
|
review_span: intent.reviewSpan,
|
|
187559
|
+
review_context_mode: intent.reviewContextMode ?? null,
|
|
187552
187560
|
agent_type: intent.agentType,
|
|
187553
187561
|
intent_brief: intent.intentBrief ?? null,
|
|
187554
187562
|
user_id: intent.userId ?? null,
|
|
@@ -187558,7 +187566,7 @@ var createAgentSessionRepos = (kdb, h) => ({
|
|
|
187558
187566
|
updated_at: h.nowMs()
|
|
187559
187567
|
}).onConflict((oc) => oc.column("local_reviewer_session_id").doNothing()).execute();
|
|
187560
187568
|
const row = await trx.selectFrom("remote_reviewer_creation_intents").selectAll().where("local_reviewer_session_id", "=", intent.localReviewerSessionId).executeTakeFirstOrThrow();
|
|
187561
|
-
const sameIdentity = row.remote_reviewer_session_id === intent.remoteReviewerSessionId && row.remote_run_id === intent.remoteRunId && row.project_id === intent.projectId && row.remote_server_id === intent.remoteServerId && (row.branch ?? "") === (intent.branch ?? "") && row.remote_path === intent.remotePath && row.source_remote_session_id === intent.sourceRemoteSessionId && row.review_focus === (intent.reviewFocus ?? null) && row.source_turn_end_index === (intent.sourceTurnEndIndex ?? null) && row.review_span === intent.reviewSpan && row.agent_type === intent.agentType && row.intent_brief === (intent.intentBrief ?? null) && row.user_id === (intent.userId ?? null);
|
|
187569
|
+
const sameIdentity = row.remote_reviewer_session_id === intent.remoteReviewerSessionId && row.remote_run_id === intent.remoteRunId && row.project_id === intent.projectId && row.remote_server_id === intent.remoteServerId && (row.branch ?? "") === (intent.branch ?? "") && row.remote_path === intent.remotePath && row.source_remote_session_id === intent.sourceRemoteSessionId && row.review_focus === (intent.reviewFocus ?? null) && row.source_turn_end_index === (intent.sourceTurnEndIndex ?? null) && row.review_span === intent.reviewSpan && (row.review_context_mode ?? null) === (intent.reviewContextMode ?? null) && row.agent_type === intent.agentType && row.intent_brief === (intent.intentBrief ?? null) && row.user_id === (intent.userId ?? null);
|
|
187562
187570
|
if (!sameIdentity) {
|
|
187563
187571
|
throw new Error(`Remote reviewer creation intent ${intent.localReviewerSessionId} has conflicting identity`);
|
|
187564
187572
|
}
|
|
@@ -188469,7 +188477,7 @@ var createWorkflowRunRepos = (kdb) => ({
|
|
|
188469
188477
|
...opts,
|
|
188470
188478
|
reviewer_session_id: opts.reviewer_session_id ?? null,
|
|
188471
188479
|
review_span: opts.review_span ?? "this_turn",
|
|
188472
|
-
status: "waiting_reviewer"
|
|
188480
|
+
status: opts.status ?? "waiting_reviewer"
|
|
188473
188481
|
}).execute();
|
|
188474
188482
|
const row = await kdb.selectFrom("workflow_runs").selectAll().where("id", "=", opts.id).executeTakeFirstOrThrow();
|
|
188475
188483
|
return asRun(row);
|
|
@@ -203758,6 +203766,8 @@ var tightenWorkspaceCheckoutForeignKeys = (db) => {
|
|
|
203758
203766
|
favorited_at INTEGER DEFAULT NULL,
|
|
203759
203767
|
native_session_id TEXT DEFAULT NULL,
|
|
203760
203768
|
history_epoch INTEGER NOT NULL DEFAULT 0,
|
|
203769
|
+
branched_from_session_id TEXT DEFAULT NULL,
|
|
203770
|
+
branched_from_entry_index INTEGER DEFAULT NULL,
|
|
203761
203771
|
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
|
|
203762
203772
|
FOREIGN KEY (workspace_checkout_id) REFERENCES workspace_checkouts(id)
|
|
203763
203773
|
DEFERRABLE INITIALLY DEFERRED
|
|
@@ -203765,10 +203775,12 @@ var tightenWorkspaceCheckoutForeignKeys = (db) => {
|
|
|
203765
203775
|
INSERT INTO agent_sessions_fk_new
|
|
203766
203776
|
(id, project_id, branch, workspace_checkout_id, status, permission_mode, agent_type,
|
|
203767
203777
|
title, model, created_at, updated_at, activity_at, last_user_message_at,
|
|
203768
|
-
last_completed_at, favorited_at, native_session_id, history_epoch
|
|
203778
|
+
last_completed_at, favorited_at, native_session_id, history_epoch,
|
|
203779
|
+
branched_from_session_id, branched_from_entry_index)
|
|
203769
203780
|
SELECT id, project_id, branch, workspace_checkout_id, status, permission_mode, agent_type,
|
|
203770
203781
|
title, model, created_at, updated_at, activity_at, last_user_message_at,
|
|
203771
|
-
last_completed_at, favorited_at, native_session_id, history_epoch
|
|
203782
|
+
last_completed_at, favorited_at, native_session_id, history_epoch,
|
|
203783
|
+
branched_from_session_id, branched_from_entry_index
|
|
203772
203784
|
FROM agent_sessions;
|
|
203773
203785
|
DROP TABLE agent_sessions;
|
|
203774
203786
|
ALTER TABLE agent_sessions_fk_new RENAME TO agent_sessions;
|
|
@@ -204179,6 +204191,7 @@ var initializeSchema = (db) => {
|
|
|
204179
204191
|
review_focus TEXT,
|
|
204180
204192
|
source_turn_end_index INTEGER,
|
|
204181
204193
|
review_span TEXT NOT NULL CHECK (review_span IN ('this_turn', 'session_start')),
|
|
204194
|
+
review_context_mode TEXT CHECK (review_context_mode IN ('briefed', 'blind')),
|
|
204182
204195
|
agent_type TEXT NOT NULL,
|
|
204183
204196
|
intent_brief TEXT,
|
|
204184
204197
|
user_id TEXT,
|
|
@@ -204595,6 +204608,11 @@ var initializeSchema = (db) => {
|
|
|
204595
204608
|
coalesce(cast((julianday(created_at) - 2440587.5) * 86400000 as integer), 0)
|
|
204596
204609
|
)`);
|
|
204597
204610
|
}
|
|
204611
|
+
const sessionBranchedFromInfo = db.prepare("PRAGMA table_info(agent_sessions)").all();
|
|
204612
|
+
if (!sessionBranchedFromInfo.some((col) => col.name === "branched_from_session_id")) {
|
|
204613
|
+
db.exec("ALTER TABLE agent_sessions ADD COLUMN branched_from_session_id TEXT DEFAULT NULL");
|
|
204614
|
+
db.exec("ALTER TABLE agent_sessions ADD COLUMN branched_from_entry_index INTEGER DEFAULT NULL");
|
|
204615
|
+
}
|
|
204598
204616
|
db.exec(`
|
|
204599
204617
|
CREATE INDEX IF NOT EXISTS idx_agent_sessions_project_branch
|
|
204600
204618
|
ON agent_sessions(project_id, branch);
|
|
@@ -205344,6 +205362,10 @@ var initializeSchema = (db) => {
|
|
|
205344
205362
|
if (!workflowRunsInfo.some((col) => col.name === "review_span")) {
|
|
205345
205363
|
db.exec("ALTER TABLE workflow_runs ADD COLUMN review_span TEXT NOT NULL DEFAULT 'this_turn'");
|
|
205346
205364
|
}
|
|
205365
|
+
const reviewerIntentsInfo = db.prepare("PRAGMA table_info(remote_reviewer_creation_intents)").all();
|
|
205366
|
+
if (!reviewerIntentsInfo.some((col) => col.name === "review_context_mode")) {
|
|
205367
|
+
db.exec("ALTER TABLE remote_reviewer_creation_intents ADD COLUMN review_context_mode TEXT CHECK (review_context_mode IN ('briefed', 'blind'))");
|
|
205368
|
+
}
|
|
205347
205369
|
db.exec(`
|
|
205348
205370
|
CREATE TABLE IF NOT EXISTS user_settings (
|
|
205349
205371
|
user_id TEXT NOT NULL,
|
|
@@ -231165,6 +231187,7 @@ var AgentSessionManager = class {
|
|
|
231165
231187
|
reset: opts.historyEpoch !== void 0 && opts.historyEpoch !== session.historyEpoch
|
|
231166
231188
|
}
|
|
231167
231189
|
}));
|
|
231190
|
+
ws.send(JSON.stringify(this.backgroundTasksMessage(session)));
|
|
231168
231191
|
for (const patch of session.store.patches) {
|
|
231169
231192
|
const entryIndices = patch.flatMap((op) => {
|
|
231170
231193
|
const match2 = op.path.match(/^\/entries\/(\d+)$/);
|
|
@@ -231177,8 +231200,6 @@ var AgentSessionManager = class {
|
|
|
231177
231200
|
ws.send(JSON.stringify({ Ready: true, historyEpoch: session.historyEpoch }));
|
|
231178
231201
|
const statusPatch = ConversationPatch.updateStatus(session.status);
|
|
231179
231202
|
ws.send(JSON.stringify({ JsonPatch: statusPatch }));
|
|
231180
|
-
const tasksMsg = this.backgroundTasksMessage(session);
|
|
231181
|
-
ws.send(JSON.stringify(tasksMsg));
|
|
231182
231203
|
return () => {
|
|
231183
231204
|
session.subscribers.delete(ws);
|
|
231184
231205
|
};
|
|
@@ -231977,6 +231998,8 @@ var AgentSessionManager = class {
|
|
|
231977
231998
|
permissionMode,
|
|
231978
231999
|
agentType: dbSession.agent_type || "claude-code",
|
|
231979
232000
|
model: dbSession.model ?? null,
|
|
232001
|
+
branchedFromSessionId: dbSession.branched_from_session_id ?? null,
|
|
232002
|
+
branchedFromEntryIndex: dbSession.branched_from_entry_index ?? null,
|
|
231980
232003
|
completion: new TurnCompletionLedger(this.parkTimeoutMs),
|
|
231981
232004
|
graceTimer: null,
|
|
231982
232005
|
parkTimer: null,
|
|
@@ -232103,6 +232126,14 @@ var AgentSessionManager = class {
|
|
|
232103
232126
|
if (existingRuntime && opts.crossRemoteMcp) {
|
|
232104
232127
|
existingRuntime.crossRemoteMcp = opts.crossRemoteMcp;
|
|
232105
232128
|
}
|
|
232129
|
+
if (!existingBranch.branched_from_session_id) {
|
|
232130
|
+
const repairedEntryIndex = entryRows[entryRows.length - 1].entry_index;
|
|
232131
|
+
await this.storage.agentSessions.setBranchedFrom(newId, sourceSessionId, repairedEntryIndex);
|
|
232132
|
+
if (existingRuntime) {
|
|
232133
|
+
existingRuntime.branchedFromSessionId = sourceSessionId;
|
|
232134
|
+
existingRuntime.branchedFromEntryIndex = repairedEntryIndex;
|
|
232135
|
+
}
|
|
232136
|
+
}
|
|
232106
232137
|
return { ok: true, sessionId: newId };
|
|
232107
232138
|
}
|
|
232108
232139
|
}
|
|
@@ -232136,6 +232167,8 @@ var AgentSessionManager = class {
|
|
|
232136
232167
|
for (const row of entryRows) {
|
|
232137
232168
|
await this.storage.agentSessions.upsertEntry(newId, row.entry_index, row.data);
|
|
232138
232169
|
}
|
|
232170
|
+
const branchedFromEntryIndex = opts.upToEntryIndex ?? entryRows[entryRows.length - 1].entry_index;
|
|
232171
|
+
await this.storage.agentSessions.setBranchedFrom(newId, sourceSessionId, branchedFromEntryIndex);
|
|
232139
232172
|
let baseTitle = sourceRow?.title ?? null;
|
|
232140
232173
|
if (!baseTitle) {
|
|
232141
232174
|
for (const row of entryRows) {
|
|
@@ -232179,7 +232212,9 @@ var AgentSessionManager = class {
|
|
|
232179
232212
|
lastActiveAt: Date.now(),
|
|
232180
232213
|
turnOpenSince: null,
|
|
232181
232214
|
turnDisposition: null,
|
|
232182
|
-
crossRemoteMcp: opts.crossRemoteMcp
|
|
232215
|
+
crossRemoteMcp: opts.crossRemoteMcp,
|
|
232216
|
+
branchedFromSessionId: sourceSessionId,
|
|
232217
|
+
branchedFromEntryIndex
|
|
232183
232218
|
};
|
|
232184
232219
|
this.sessions.set(newId, branched);
|
|
232185
232220
|
await this.emitDerivedBranchActivity(projectId, branch);
|
|
@@ -233369,27 +233404,39 @@ async function createRemoteWorkflowReviewer(deps, params) {
|
|
|
233369
233404
|
reviewFocus: params.reviewFocus ?? null,
|
|
233370
233405
|
sourceTurnEndIndex: params.sourceTurnEndIndex ?? null,
|
|
233371
233406
|
reviewSpan: params.reviewSpan,
|
|
233407
|
+
reviewContextMode: params.reviewContextMode ?? null,
|
|
233372
233408
|
agentType: params.reviewerAgentType,
|
|
233373
233409
|
intentBrief: params.intentBrief ?? null,
|
|
233374
233410
|
userId: params.userId ?? null
|
|
233375
233411
|
});
|
|
233376
233412
|
let registeredLocalSessionId = null;
|
|
233377
233413
|
try {
|
|
233378
|
-
const
|
|
233414
|
+
const sharedBody = {
|
|
233415
|
+
sourceSessionId: params.sourceRemoteSessionId,
|
|
233416
|
+
reviewFocus: params.reviewFocus,
|
|
233417
|
+
sourceTurnEndIndex: params.sourceTurnEndIndex,
|
|
233418
|
+
reviewSpan: params.reviewSpan,
|
|
233419
|
+
reviewerAgentType: params.reviewerAgentType,
|
|
233420
|
+
runId: remoteRunId,
|
|
233421
|
+
newReviewerSessionId: remoteReviewerSessionId
|
|
233422
|
+
};
|
|
233423
|
+
const proxyOpts = { reverseConnectManager: deps.reverseConnectManager ?? void 0 };
|
|
233424
|
+
const result = params.phase === "prepare" ? await proxyToRemoteAuto(
|
|
233425
|
+
params.agentMode,
|
|
233426
|
+
"POST",
|
|
233427
|
+
"/api/path/workflow-runs/prepare",
|
|
233428
|
+
sharedBody,
|
|
233429
|
+
proxyOpts
|
|
233430
|
+
) : await proxyToRemoteAuto(
|
|
233379
233431
|
params.agentMode,
|
|
233380
233432
|
"POST",
|
|
233381
233433
|
"/api/path/workflow-runs",
|
|
233382
233434
|
{
|
|
233383
|
-
|
|
233384
|
-
|
|
233385
|
-
|
|
233386
|
-
reviewSpan: params.reviewSpan,
|
|
233387
|
-
reviewerAgentType: params.reviewerAgentType,
|
|
233388
|
-
intentBrief: params.intentBrief,
|
|
233389
|
-
runId: remoteRunId,
|
|
233390
|
-
newReviewerSessionId: remoteReviewerSessionId
|
|
233435
|
+
...sharedBody,
|
|
233436
|
+
reviewContextMode: params.reviewContextMode,
|
|
233437
|
+
intentBrief: params.intentBrief
|
|
233391
233438
|
},
|
|
233392
|
-
|
|
233439
|
+
proxyOpts
|
|
233393
233440
|
);
|
|
233394
233441
|
if (!result.ok) {
|
|
233395
233442
|
const uncertain = result.errorCode === "network_error" || result.errorCode === "timeout";
|
|
@@ -233575,6 +233622,7 @@ function recoverPendingRemoteReviewerOnce(deps, intent) {
|
|
|
233575
233622
|
reviewFocus: intent.review_focus ?? void 0,
|
|
233576
233623
|
sourceTurnEndIndex: intent.source_turn_end_index ?? void 0,
|
|
233577
233624
|
reviewSpan: intent.review_span,
|
|
233625
|
+
reviewContextMode: intent.review_context_mode ?? void 0,
|
|
233578
233626
|
reviewerAgentType: intent.agent_type,
|
|
233579
233627
|
intentBrief: intent.intent_brief ?? void 0,
|
|
233580
233628
|
userId: intent.user_id ?? void 0,
|
|
@@ -239666,13 +239714,17 @@ var VERDICT_INSTRUCTIONS = [
|
|
|
239666
239714
|
"3. Non-blocking notes \u2014 style and polish, briefly, clearly separated from the blocking list."
|
|
239667
239715
|
];
|
|
239668
239716
|
function buildReviewerPrompt(opts) {
|
|
239669
|
-
const
|
|
239670
|
-
const
|
|
239671
|
-
const
|
|
239717
|
+
const blind = opts.blind === true;
|
|
239718
|
+
const intent = !blind && opts.originalIntent !== opts.taskContext ? opts.originalIntent : null;
|
|
239719
|
+
const brief = blind ? null : opts.intentBrief || null;
|
|
239720
|
+
const taskContext = blind ? null : opts.taskContext;
|
|
239721
|
+
const selfReport = blind ? null : opts.authorSelfReport;
|
|
239722
|
+
const hasExcerpt = Boolean(intent || taskContext || selfReport);
|
|
239672
239723
|
const scope = opts.scope && opts.scope.changedFiles.length > 0 ? opts.scope : null;
|
|
239673
|
-
const noDiffWithAnalysis = Boolean(
|
|
239724
|
+
const noDiffWithAnalysis = Boolean(selfReport) && selfReport.trim().length >= SELF_REPORT_MIN_CHARS;
|
|
239674
239725
|
return [
|
|
239675
239726
|
"You are a code reviewer agent. Another agent just completed work in this workspace; review it critically and independently.",
|
|
239727
|
+
blind ? '\n## Independent review\nBy design you have been given no context from the conversation that produced this work \u2014 no task statement, no author summary. Infer the intent from the change itself, the repository, and its history, and open your verdict message by stating that inferred intent in one or two sentences. Do not assume any agreement, exemption, or constraint that is not evidenced in the repository; if a behavior looks wrong but could plausibly be intentional, report it marked "possibly intended \u2014 needs author confirmation" rather than staying silent.' : null,
|
|
239676
239728
|
brief ? `
|
|
239677
239729
|
## Intent brief (distilled from the source conversation)
|
|
239678
239730
|
${brief}` : null,
|
|
@@ -239682,10 +239734,10 @@ ${intent}` : null,
|
|
|
239682
239734
|
// Deliberately not titled "Original task": in confirmation-style
|
|
239683
239735
|
// conversations the latest message is often just "ok" — informative as
|
|
239684
239736
|
// the user's last word, misleading as a statement of the task.
|
|
239685
|
-
!brief &&
|
|
239737
|
+
!brief && taskContext ? `
|
|
239686
239738
|
## Latest user message (verbatim)
|
|
239687
|
-
${
|
|
239688
|
-
selfReportSection(
|
|
239739
|
+
${taskContext}` : null,
|
|
239740
|
+
selfReportSection(selfReport),
|
|
239689
239741
|
opts.reviewFocus ? `
|
|
239690
239742
|
## Review focus (from the user)
|
|
239691
239743
|
${opts.reviewFocus}` : null,
|
|
@@ -239702,14 +239754,17 @@ Confine your review to these files and changes. Other uncommitted or pre-existin
|
|
|
239702
239754
|
"- Do NOT modify any files \u2014 you are in read-only review mode.",
|
|
239703
239755
|
"- Inspect the actual workspace state yourself: read the relevant files, run `git diff`, `git status` and `git log`.",
|
|
239704
239756
|
reviewTargetPromptLine(opts.target),
|
|
239705
|
-
noDiffWithAnalysis ? "- Judge correctness and completeness against the task. For this analysis/plan turn the work under review is the reasoning and the proposal, not code quality of a diff. Be specific: reference files and lines." : "- Judge correctness, completeness against the task, and code quality. Be specific: reference files and lines.",
|
|
239757
|
+
blind ? "- Judge correctness and code quality on the change's own evidence \u2014 there is no task statement to judge completeness against. Be specific: reference files and lines." : noDiffWithAnalysis ? "- Judge correctness and completeness against the task. For this analysis/plan turn the work under review is the reasoning and the proposal, not code quality of a diff. Be specific: reference files and lines." : "- Judge correctness, completeness against the task, and code quality. Be specific: reference files and lines.",
|
|
239706
239758
|
// These two only make sense against a distilled brief: tier 2 has no
|
|
239707
239759
|
// [settled]/[tentative] marks and no stated scope, and implying it does
|
|
239708
239760
|
// would suppress findings on the strength of data that doesn't exist.
|
|
239709
239761
|
brief ? "- Where the brief marks a decision, non-goal, or accepted limitation as [settled], do not re-raise the choice itself as a finding; DO report concrete consequences it causes \u2014 failure of the core goal, or a correctness, security, or data loss problem. Items marked [tentative] (or unmarked) get normal review. A violated hard constraint is always blocking." : null,
|
|
239710
239762
|
brief ? "- Do not propose enhancements beyond the brief's stated scope \u2014 scope expansion is a product decision, not a review finding." : null,
|
|
239711
239763
|
...VERDICT_INSTRUCTIONS,
|
|
239712
|
-
|
|
239764
|
+
// "deliberately withheld" vs the tier-3 "was unavailable": both are
|
|
239765
|
+
// workspace-only prompts, but post-hoc attribution must be able to tell a
|
|
239766
|
+
// user choice from a degradation.
|
|
239767
|
+
blind ? "\n(review context: independent review \u2014 session context deliberately withheld; live workspace only)" : brief ? opts.authorSelfReport ? "\n(review context: distilled intent brief + author self-report + live workspace)" : "\n(review context: distilled intent brief + live workspace)" : hasExcerpt ? "\n(review context: deterministic excerpt of the source conversation + live workspace)" : "\n(review context: live workspace only \u2014 the source conversation was unavailable)"
|
|
239713
239768
|
].filter((l) => l !== null).join("\n");
|
|
239714
239769
|
}
|
|
239715
239770
|
function reviewTargetPromptLine(target) {
|
|
@@ -239758,6 +239813,7 @@ var FINAL_VERDICT_PROMPT = [
|
|
|
239758
239813
|
...VERDICT_INSTRUCTIONS
|
|
239759
239814
|
].join("\n");
|
|
239760
239815
|
var REVIEWER_AGENT_TYPES = /* @__PURE__ */ new Set(["claude-code", "codex"]);
|
|
239816
|
+
var PREPARE_TIMEOUT_MS = 10 * 6e4;
|
|
239761
239817
|
var WorkflowEngine = class {
|
|
239762
239818
|
constructor(storage2, agentOps) {
|
|
239763
239819
|
this.storage = storage2;
|
|
@@ -239768,6 +239824,12 @@ var WorkflowEngine = class {
|
|
|
239768
239824
|
eventBus;
|
|
239769
239825
|
/** sessionId → participation in an active run (rebuilt on boot). */
|
|
239770
239826
|
participants = /* @__PURE__ */ new Map();
|
|
239827
|
+
/** runId → prompt inputs captured at prepare time (see PendingActivation). */
|
|
239828
|
+
pendingActivations = /* @__PURE__ */ new Map();
|
|
239829
|
+
/** runId → armed preparation-timeout timer. */
|
|
239830
|
+
prepareTimers = /* @__PURE__ */ new Map();
|
|
239831
|
+
/** runId → in-flight activation, so concurrent calls join instead of double-sending. */
|
|
239832
|
+
activationFlights = /* @__PURE__ */ new Map();
|
|
239771
239833
|
setEventBus(bus) {
|
|
239772
239834
|
this.eventBus = bus;
|
|
239773
239835
|
bus.subscribe((event) => {
|
|
@@ -239792,6 +239854,12 @@ var WorkflowEngine = class {
|
|
|
239792
239854
|
await this.storage.workflowRuns.update(run2.id, {
|
|
239793
239855
|
error: "\u670D\u52A1\u91CD\u542F\uFF0C\u53EF\u80FD\u9519\u8FC7 reviewer \u5B8C\u6210\u4E8B\u4EF6\u3002\u82E5 reviewer \u5DF2\u5B8C\u6210\uFF0C\u8BF7\u6253\u5F00\u5176\u7A97\u53E3\u67E5\u770B\uFF0C\u6216\u7ED3\u675F\u672C\u6B21 review\u3002"
|
|
239794
239856
|
});
|
|
239857
|
+
} else if (run2.status === "preparing") {
|
|
239858
|
+
const createdAt = Date.parse(
|
|
239859
|
+
run2.created_at.includes("T") ? run2.created_at : run2.created_at.replace(" ", "T") + "Z"
|
|
239860
|
+
);
|
|
239861
|
+
const elapsed = Number.isFinite(createdAt) ? Date.now() - createdAt : PREPARE_TIMEOUT_MS;
|
|
239862
|
+
this.armPrepareTimeout(run2.id, PREPARE_TIMEOUT_MS - elapsed);
|
|
239795
239863
|
}
|
|
239796
239864
|
this.trackParticipants(run2);
|
|
239797
239865
|
}
|
|
@@ -239806,6 +239874,37 @@ var WorkflowEngine = class {
|
|
|
239806
239874
|
for (const [sid, p2] of this.participants) {
|
|
239807
239875
|
if (p2.runId === run2.id) this.participants.delete(sid);
|
|
239808
239876
|
}
|
|
239877
|
+
this.clearPendingActivation(run2.id);
|
|
239878
|
+
}
|
|
239879
|
+
clearPendingActivation(runId) {
|
|
239880
|
+
this.pendingActivations.delete(runId);
|
|
239881
|
+
const timer = this.prepareTimers.get(runId);
|
|
239882
|
+
if (timer) {
|
|
239883
|
+
clearTimeout(timer);
|
|
239884
|
+
this.prepareTimers.delete(runId);
|
|
239885
|
+
}
|
|
239886
|
+
}
|
|
239887
|
+
/**
|
|
239888
|
+
* Backstop for a run stuck in `preparing` (see PREPARE_TIMEOUT_MS). A timed
|
|
239889
|
+
* out preparation becomes a normal failed run — visible in the UI and as a
|
|
239890
|
+
* workflow_failed milestone — instead of a placeholder that spins forever.
|
|
239891
|
+
* `unref` so an armed timer never holds the process open.
|
|
239892
|
+
*/
|
|
239893
|
+
armPrepareTimeout(runId, delayMs) {
|
|
239894
|
+
const existing = this.prepareTimers.get(runId);
|
|
239895
|
+
if (existing) clearTimeout(existing);
|
|
239896
|
+
const timer = setTimeout(() => {
|
|
239897
|
+
this.prepareTimers.delete(runId);
|
|
239898
|
+
void (async () => {
|
|
239899
|
+
const run2 = await this.storage.workflowRuns.getById(runId);
|
|
239900
|
+
if (run2?.status === "preparing") {
|
|
239901
|
+
await this.failRun(run2, "review \u51C6\u5907\u8D85\u65F6\uFF1Aintent brief \u7684\u751F\u6210\u65B9\u6CA1\u6709\u56DE\u6765\u6FC0\u6D3B reviewer\u3002\u53EF\u7ED3\u675F\u540E\u91CD\u65B0\u53D1\u8D77\u3002");
|
|
239902
|
+
}
|
|
239903
|
+
this.pendingActivations.delete(runId);
|
|
239904
|
+
})().catch((err) => console.error("[WorkflowEngine] prepare-timeout sweep failed:", err));
|
|
239905
|
+
}, Math.max(0, delayMs));
|
|
239906
|
+
timer.unref?.();
|
|
239907
|
+
this.prepareTimers.set(runId, timer);
|
|
239809
239908
|
}
|
|
239810
239909
|
releaseReservations(runId) {
|
|
239811
239910
|
for (const [sid, participant] of this.participants) {
|
|
@@ -239846,7 +239945,10 @@ var WorkflowEngine = class {
|
|
|
239846
239945
|
);
|
|
239847
239946
|
if (!ok) return;
|
|
239848
239947
|
const failed = await this.storage.workflowRuns.getById(run2.id);
|
|
239849
|
-
if (failed)
|
|
239948
|
+
if (failed) {
|
|
239949
|
+
this.untrackRun(failed);
|
|
239950
|
+
this.emitRunUpdated(failed);
|
|
239951
|
+
}
|
|
239850
239952
|
this.onMilestoneCreated?.();
|
|
239851
239953
|
}
|
|
239852
239954
|
/** Test seam for the failure path (see failRun). */
|
|
@@ -239878,6 +239980,7 @@ var WorkflowEngine = class {
|
|
|
239878
239980
|
sessionId: null,
|
|
239879
239981
|
title: null,
|
|
239880
239982
|
agentType: null,
|
|
239983
|
+
lastActiveAt: null,
|
|
239881
239984
|
reason
|
|
239882
239985
|
});
|
|
239883
239986
|
const source = await this.storage.agentSessions.getById(sourceSessionId);
|
|
@@ -239910,16 +240013,42 @@ var WorkflowEngine = class {
|
|
|
239910
240013
|
sessionId: reviewer.id,
|
|
239911
240014
|
title: reviewer.title ?? null,
|
|
239912
240015
|
agentType: reviewer.agent_type,
|
|
240016
|
+
lastActiveAt: reviewerProjection.lastActiveAt,
|
|
239913
240017
|
reason: null
|
|
239914
240018
|
};
|
|
239915
240019
|
}
|
|
240020
|
+
/**
|
|
240021
|
+
* Single-shot start, preserved for old callers and durable-intent replays:
|
|
240022
|
+
* prepare + activate back to back. The reuse-reviewer path delivers its
|
|
240023
|
+
* prompt inside prepare (no preparing state — nothing distills for a
|
|
240024
|
+
* re-review); a fresh reviewer comes back `preparing` and is activated
|
|
240025
|
+
* inline, which also completes a replayed run that a previous caller
|
|
240026
|
+
* prepared but never activated.
|
|
240027
|
+
*/
|
|
239916
240028
|
async startAdhocReview(opts) {
|
|
240029
|
+
const run2 = await this.prepareAdhocReview(opts);
|
|
240030
|
+
if (opts.reviewerSessionId || run2.status !== "preparing") return run2;
|
|
240031
|
+
return this.activateAdhocReview(run2.id, { intentBrief: opts.intentBrief, blind: opts.blind });
|
|
240032
|
+
}
|
|
240033
|
+
/**
|
|
240034
|
+
* Phase 1 of the two-phase adhoc review: validate, reserve participants,
|
|
240035
|
+
* create the run row and (for a fresh review) the reviewer session — titled
|
|
240036
|
+
* and visible in the sidebar — WITHOUT sending the reviewer prompt. Fast
|
|
240037
|
+
* (no model calls), so the route can respond immediately and let the
|
|
240038
|
+
* intent-brief distillation happen after the user has moved on. The run
|
|
240039
|
+
* stays `preparing` until activateAdhocReview delivers the first message,
|
|
240040
|
+
* bounded by PREPARE_TIMEOUT_MS.
|
|
240041
|
+
*/
|
|
240042
|
+
async prepareAdhocReview(opts) {
|
|
239917
240043
|
if (opts.reviewerSessionId === opts.sourceSessionId) {
|
|
239918
240044
|
throw new WorkflowError("reviewer-unavailable", "reviewer session \u4E0D\u80FD\u4E0E source session \u76F8\u540C");
|
|
239919
240045
|
}
|
|
239920
240046
|
if (opts.reviewerSessionId && opts.newReviewerSessionId) {
|
|
239921
240047
|
throw new WorkflowError("reviewer-unavailable", "\u4E0D\u80FD\u540C\u65F6\u590D\u7528\u548C\u65B0\u5EFA reviewer session");
|
|
239922
240048
|
}
|
|
240049
|
+
if (opts.blind && opts.reviewerSessionId) {
|
|
240050
|
+
throw new WorkflowError("reviewer-unavailable", "blind review \u4E0D\u80FD\u590D\u7528\u5DF2\u6709 reviewer session");
|
|
240051
|
+
}
|
|
239923
240052
|
const runId = opts.runId ?? randomUUID6();
|
|
239924
240053
|
const existingRun = opts.runId ? await this.storage.workflowRuns.getById(runId) : void 0;
|
|
239925
240054
|
if (existingRun) {
|
|
@@ -239933,7 +240062,7 @@ var WorkflowEngine = class {
|
|
|
239933
240062
|
}
|
|
239934
240063
|
return existingRun;
|
|
239935
240064
|
}
|
|
239936
|
-
if (existingRun.status !== "waiting_reviewer") {
|
|
240065
|
+
if (existingRun.status !== "waiting_reviewer" && existingRun.status !== "preparing") {
|
|
239937
240066
|
throw new WorkflowError("bad-state", "workflow run \u5DF2\u7EC8\u6B62\uFF0C\u4E0D\u80FD\u4F5C\u4E3A\u672A\u77E5\u521B\u5EFA\u7ED3\u679C\u91CD\u653E");
|
|
239938
240067
|
}
|
|
239939
240068
|
}
|
|
@@ -240011,9 +240140,17 @@ var WorkflowEngine = class {
|
|
|
240011
240140
|
review_focus: opts.reviewFocus ?? null,
|
|
240012
240141
|
review_target: JSON.stringify(target),
|
|
240013
240142
|
reviewer_session_id: opts.reviewerSessionId ?? null,
|
|
240014
|
-
review_span: opts.reviewSpan ?? "this_turn"
|
|
240143
|
+
review_span: opts.reviewSpan ?? "this_turn",
|
|
240144
|
+
// Reuse skips the preparing state entirely: its prompt is delivered
|
|
240145
|
+
// below, before this method returns.
|
|
240146
|
+
status: opts.reviewerSessionId ? "waiting_reviewer" : "preparing"
|
|
240015
240147
|
});
|
|
240016
240148
|
this.trackParticipants(run2);
|
|
240149
|
+
if (existingRun && !existingRun.reviewer_session_id && !opts.reviewerSessionId && run2.status === "waiting_reviewer") {
|
|
240150
|
+
const flipped = await this.storage.workflowRuns.transition(run2.id, "waiting_reviewer", "preparing");
|
|
240151
|
+
if (!flipped) throw new WorkflowError("bad-state", "run \u5728\u91CD\u653E\u671F\u95F4\u5DF2\u88AB\u53D6\u6D88\u6216\u5931\u8D25");
|
|
240152
|
+
run2.status = "preparing";
|
|
240153
|
+
}
|
|
240017
240154
|
if (opts.reviewerSessionId && reviewerSession) {
|
|
240018
240155
|
if (reviewerSession.permission_mode !== "plan") {
|
|
240019
240156
|
let switched = false;
|
|
@@ -240064,7 +240201,13 @@ var WorkflowEngine = class {
|
|
|
240064
240201
|
false,
|
|
240065
240202
|
"plan",
|
|
240066
240203
|
opts.reviewerAgentType ?? "claude-code",
|
|
240067
|
-
|
|
240204
|
+
// announceRunning=false — deliberate change from the single-shot
|
|
240205
|
+
// start: the preparing reviewer must appear in the sidebar (the
|
|
240206
|
+
// spawn's unconditional session:process emit covers that) WITHOUT
|
|
240207
|
+
// the session:status "running" emit that auto-surfaces it into the
|
|
240208
|
+
// open agent window. The user stays on the source session and opens
|
|
240209
|
+
// the reviewer from the sidebar when they choose to.
|
|
240210
|
+
false,
|
|
240068
240211
|
false,
|
|
240069
240212
|
{
|
|
240070
240213
|
startSnapshot: endSnap,
|
|
@@ -240076,26 +240219,25 @@ var WorkflowEngine = class {
|
|
|
240076
240219
|
reviewerId,
|
|
240077
240220
|
`Review - ${sourceSession?.title || (taskContext ? snippetTitle(taskContext) : null) || "Conversation"}`
|
|
240078
240221
|
).catch((err) => console.warn(`[WorkflowEngine] failed to set reviewer title for ${reviewerId}:`, err));
|
|
240079
|
-
|
|
240222
|
+
this.pendingActivations.set(run2.id, {
|
|
240223
|
+
scope,
|
|
240080
240224
|
taskContext,
|
|
240081
240225
|
originalIntent: extractFirstUserMessage(entries),
|
|
240082
|
-
authorSelfReport: extractAuthorSelfReport(entries, turnEndIndex)
|
|
240083
|
-
|
|
240084
|
-
|
|
240085
|
-
|
|
240086
|
-
|
|
240226
|
+
authorSelfReport: extractAuthorSelfReport(entries, turnEndIndex)
|
|
240227
|
+
});
|
|
240228
|
+
const updated = await this.storage.workflowRuns.update(run2.id, {
|
|
240229
|
+
reviewer_session_id: reviewerId,
|
|
240230
|
+
// Legacy replay backfill: a durable run interrupted before reviewer
|
|
240231
|
+
// binding may carry no stored target. Activation builds the prompt
|
|
240232
|
+
// from the stored one, so persist the target just captured (same
|
|
240233
|
+
// worktree, cutoff verified above to match the stored run).
|
|
240234
|
+
...run2.review_target ? {} : { review_target: JSON.stringify(target) }
|
|
240087
240235
|
});
|
|
240088
|
-
const sent = await this.agentOps.sendUserMessage(reviewerId, prompt, opts.project.path, void 0, REVIEWER_TURN);
|
|
240089
|
-
if (!sent) {
|
|
240090
|
-
await this.failRun({ ...run2, reviewer_session_id: reviewerId }, "\u5411 reviewer \u6295\u9012\u4EFB\u52A1\u5931\u8D25");
|
|
240091
|
-
throw new WorkflowError("spawn-failed", "\u5411 reviewer \u6295\u9012\u4EFB\u52A1\u5931\u8D25");
|
|
240092
|
-
}
|
|
240093
|
-
const updated = await this.storage.workflowRuns.update(run2.id, { reviewer_session_id: reviewerId });
|
|
240094
240236
|
this.trackParticipants(updated);
|
|
240237
|
+
this.armPrepareTimeout(run2.id, PREPARE_TIMEOUT_MS);
|
|
240095
240238
|
this.emitRunUpdated(updated);
|
|
240096
240239
|
return updated;
|
|
240097
240240
|
} catch (err) {
|
|
240098
|
-
if (err instanceof WorkflowError && err.code === "spawn-failed") throw err;
|
|
240099
240241
|
await this.failRun(run2, `\u521B\u5EFA reviewer \u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}`);
|
|
240100
240242
|
throw new WorkflowError("spawn-failed", "\u521B\u5EFA reviewer session \u5931\u8D25");
|
|
240101
240243
|
}
|
|
@@ -240104,6 +240246,73 @@ var WorkflowEngine = class {
|
|
|
240104
240246
|
throw err;
|
|
240105
240247
|
}
|
|
240106
240248
|
}
|
|
240249
|
+
/**
|
|
240250
|
+
* Phase 2 of the two-phase adhoc review: build the reviewer prompt and send
|
|
240251
|
+
* the first message. Called after the intent brief finished distilling —
|
|
240252
|
+
* inline for single-shot starts, in the background (or over the tunnel, for
|
|
240253
|
+
* remote reviews) after the route already responded. Idempotent: replaying
|
|
240254
|
+
* an already-activated run returns it unchanged, which the durable-intent
|
|
240255
|
+
* replay path relies on.
|
|
240256
|
+
*/
|
|
240257
|
+
async activateAdhocReview(runId, opts = {}) {
|
|
240258
|
+
const existing = this.activationFlights.get(runId);
|
|
240259
|
+
if (existing) return existing;
|
|
240260
|
+
const flight = this.runActivation(runId, opts);
|
|
240261
|
+
this.activationFlights.set(runId, flight);
|
|
240262
|
+
const clear = () => {
|
|
240263
|
+
if (this.activationFlights.get(runId) === flight) this.activationFlights.delete(runId);
|
|
240264
|
+
};
|
|
240265
|
+
flight.then(clear, clear);
|
|
240266
|
+
return flight;
|
|
240267
|
+
}
|
|
240268
|
+
async runActivation(runId, opts) {
|
|
240269
|
+
const run2 = await this.storage.workflowRuns.getById(runId);
|
|
240270
|
+
if (!run2) throw new WorkflowError("bad-state", "run \u4E0D\u5B58\u5728\uFF0C\u65E0\u6CD5\u6FC0\u6D3B");
|
|
240271
|
+
if (run2.status !== "preparing") {
|
|
240272
|
+
if (TERMINAL_STATUSES.has(run2.status)) {
|
|
240273
|
+
throw new WorkflowError("bad-state", "run \u5DF2\u7ED3\u675F\uFF0C\u65E0\u6CD5\u6FC0\u6D3B");
|
|
240274
|
+
}
|
|
240275
|
+
return run2;
|
|
240276
|
+
}
|
|
240277
|
+
if (!run2.reviewer_session_id) {
|
|
240278
|
+
throw new WorkflowError("bad-state", "run \u8FD8\u6CA1\u6709 reviewer session\uFF0C\u65E0\u6CD5\u6FC0\u6D3B");
|
|
240279
|
+
}
|
|
240280
|
+
if (!run2.review_target) {
|
|
240281
|
+
throw new WorkflowError("bad-state", "run \u7F3A\u5C11 review target\uFF0C\u65E0\u6CD5\u6FC0\u6D3B");
|
|
240282
|
+
}
|
|
240283
|
+
const pending = this.pendingActivations.get(runId);
|
|
240284
|
+
try {
|
|
240285
|
+
const entries = pending ? null : this.agentOps.getRawMessages(run2.source_session_id);
|
|
240286
|
+
const prompt = buildReviewerPrompt({
|
|
240287
|
+
taskContext: pending ? pending.taskContext : extractTaskContextBefore(entries, run2.source_turn_end_index),
|
|
240288
|
+
originalIntent: pending ? pending.originalIntent : extractFirstUserMessage(entries),
|
|
240289
|
+
authorSelfReport: pending ? pending.authorSelfReport : extractAuthorSelfReport(entries, run2.source_turn_end_index),
|
|
240290
|
+
intentBrief: opts.intentBrief ?? null,
|
|
240291
|
+
blind: opts.blind,
|
|
240292
|
+
reviewFocus: run2.review_focus,
|
|
240293
|
+
target: JSON.parse(run2.review_target),
|
|
240294
|
+
scope: pending?.scope ?? null
|
|
240295
|
+
});
|
|
240296
|
+
const project = await this.storage.projects.getById(run2.project_id);
|
|
240297
|
+
const sent = await this.agentOps.sendUserMessage(run2.reviewer_session_id, prompt, project?.path ?? void 0, void 0, REVIEWER_TURN).catch(() => false);
|
|
240298
|
+
if (!sent) {
|
|
240299
|
+
await this.failRun(run2, "\u5411 reviewer \u6295\u9012\u4EFB\u52A1\u5931\u8D25");
|
|
240300
|
+
throw new WorkflowError("spawn-failed", "\u5411 reviewer \u6295\u9012\u4EFB\u52A1\u5931\u8D25");
|
|
240301
|
+
}
|
|
240302
|
+
} catch (err) {
|
|
240303
|
+
if (err instanceof WorkflowError && err.code === "spawn-failed") throw err;
|
|
240304
|
+
await this.failRun(run2, `\u6FC0\u6D3B reviewer \u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}`);
|
|
240305
|
+
throw new WorkflowError("spawn-failed", "\u6FC0\u6D3B reviewer session \u5931\u8D25");
|
|
240306
|
+
}
|
|
240307
|
+
const claimed = await this.storage.workflowRuns.transition(runId, "preparing", "waiting_reviewer");
|
|
240308
|
+
if (!claimed) {
|
|
240309
|
+
throw new WorkflowError("bad-state", "run \u5728\u51C6\u5907\u671F\u95F4\u5DF2\u88AB\u53D6\u6D88\u6216\u5931\u8D25");
|
|
240310
|
+
}
|
|
240311
|
+
this.clearPendingActivation(runId);
|
|
240312
|
+
const updated = await this.storage.workflowRuns.getById(runId);
|
|
240313
|
+
this.emitRunUpdated(updated);
|
|
240314
|
+
return updated;
|
|
240315
|
+
}
|
|
240107
240316
|
async handleTaskCompleted(event) {
|
|
240108
240317
|
const p2 = this.participants.get(event.sessionId);
|
|
240109
240318
|
if (!p2 || p2.role !== "reviewer") return;
|
|
@@ -240221,7 +240430,7 @@ var WorkflowEngine = class {
|
|
|
240221
240430
|
if (!run2) return void 0;
|
|
240222
240431
|
if (TERMINAL_STATUSES.has(run2.status)) return run2;
|
|
240223
240432
|
const patch = reason ? { error: reason } : void 0;
|
|
240224
|
-
const cancelled = await this.storage.workflowRuns.transition(runId, "waiting_reviewer", "cancelled", patch) || await this.storage.workflowRuns.transition(runId, "waiting_feedback", "cancelled", patch) || await this.storage.workflowRuns.transition(runId, "discussing", "cancelled", patch);
|
|
240433
|
+
const cancelled = await this.storage.workflowRuns.transition(runId, "preparing", "cancelled", patch) || await this.storage.workflowRuns.transition(runId, "waiting_reviewer", "cancelled", patch) || await this.storage.workflowRuns.transition(runId, "waiting_feedback", "cancelled", patch) || await this.storage.workflowRuns.transition(runId, "discussing", "cancelled", patch);
|
|
240225
240434
|
if (!cancelled) {
|
|
240226
240435
|
const current = await this.storage.workflowRuns.getById(runId);
|
|
240227
240436
|
if (current?.status === "sending_feedback") {
|
|
@@ -247598,6 +247807,25 @@ var routes11 = async (fastify2) => {
|
|
|
247598
247807
|
}
|
|
247599
247808
|
return reader(session.id, "runtime");
|
|
247600
247809
|
}
|
|
247810
|
+
function sendBackFields(session) {
|
|
247811
|
+
const parentId = session.branchedFromSessionId;
|
|
247812
|
+
if (!parentId) return {};
|
|
247813
|
+
return {
|
|
247814
|
+
branchedFromSessionId: parentId,
|
|
247815
|
+
branchedFromAvailable: fastify2.agentSessionManager.getSession(parentId) != null,
|
|
247816
|
+
// Entry indices survive the branch copy unchanged, so this tells the UI
|
|
247817
|
+
// which dividers are inherited history (≤) vs the branch's own turns (>)
|
|
247818
|
+
// — send-back only makes sense on the latter.
|
|
247819
|
+
...session.branchedFromEntryIndex != null ? { branchedFromEntryIndex: session.branchedFromEntryIndex } : {}
|
|
247820
|
+
};
|
|
247821
|
+
}
|
|
247822
|
+
async function mapRemoteSendBackFields(remoteServerId, session) {
|
|
247823
|
+
const { branchedFromSessionId: workerParentId, branchedFromAvailable, branchedFromEntryIndex, ...rest } = session;
|
|
247824
|
+
if (typeof workerParentId !== "string") return rest;
|
|
247825
|
+
const mapping = await fastify2.storage.remoteSessionMappings.getByRemote?.(remoteServerId, workerParentId);
|
|
247826
|
+
if (!mapping) return rest;
|
|
247827
|
+
return { ...rest, branchedFromSessionId: mapping.local_session_id, branchedFromAvailable, branchedFromEntryIndex };
|
|
247828
|
+
}
|
|
247601
247829
|
async function performLocalBranch(sourceSessionId, userId, opts) {
|
|
247602
247830
|
const sourceRow = await fastify2.storage.agentSessions.getById(sourceSessionId);
|
|
247603
247831
|
const sourceProjection = sourceRow ? await projectLocalSessionIdentity(sourceRow) : void 0;
|
|
@@ -247633,7 +247861,8 @@ var routes11 = async (fastify2) => {
|
|
|
247633
247861
|
permissionMode: session?.permissionMode || "edit",
|
|
247634
247862
|
agentType: session?.agentType || "claude-code",
|
|
247635
247863
|
model: session?.model ?? null,
|
|
247636
|
-
title: dbRow?.title ?? null
|
|
247864
|
+
title: dbRow?.title ?? null,
|
|
247865
|
+
...session ? sendBackFields(session) : {}
|
|
247637
247866
|
},
|
|
247638
247867
|
messages
|
|
247639
247868
|
}
|
|
@@ -247721,7 +247950,8 @@ var routes11 = async (fastify2) => {
|
|
|
247721
247950
|
workspaceCheckoutId: session?.workspaceCheckoutId ?? null,
|
|
247722
247951
|
worktreePath: projection?.worktreePath ?? session?.checkoutPath ?? null,
|
|
247723
247952
|
checkoutDeletedAt: projection?.checkoutDeletedAt ?? null,
|
|
247724
|
-
processAlive: session ? fastify2.agentSessionManager.getSessionProcessAlive(sessionId) : false
|
|
247953
|
+
processAlive: session ? fastify2.agentSessionManager.getSessionProcessAlive(sessionId) : false,
|
|
247954
|
+
...session ? sendBackFields(session) : {}
|
|
247725
247955
|
},
|
|
247726
247956
|
messages: historyWindow ? historyWindow.entries.map((entry) => entry.message) : messages,
|
|
247727
247957
|
...historyWindow ? { historyWindow } : {}
|
|
@@ -247871,7 +248101,8 @@ var routes11 = async (fastify2) => {
|
|
|
247871
248101
|
model: active.model ?? null,
|
|
247872
248102
|
workspaceCheckoutId: active.workspaceCheckoutId,
|
|
247873
248103
|
worktreePath: active.checkoutPath,
|
|
247874
|
-
processAlive: fastify2.agentSessionManager.getSessionProcessAlive(sessionId)
|
|
248104
|
+
processAlive: fastify2.agentSessionManager.getSessionProcessAlive(sessionId),
|
|
248105
|
+
...sendBackFields(active)
|
|
247875
248106
|
},
|
|
247876
248107
|
messages: fastify2.agentSessionManager.getMessages(sessionId)
|
|
247877
248108
|
});
|
|
@@ -248195,7 +248426,7 @@ var routes11 = async (fastify2) => {
|
|
|
248195
248426
|
}
|
|
248196
248427
|
return reply.code(200).send({
|
|
248197
248428
|
session: {
|
|
248198
|
-
...remoteData.session,
|
|
248429
|
+
...await mapRemoteSendBackFields(agentMode, remoteData.session),
|
|
248199
248430
|
id: localSessionId,
|
|
248200
248431
|
projectId: req.params.projectId
|
|
248201
248432
|
},
|
|
@@ -248237,7 +248468,8 @@ var routes11 = async (fastify2) => {
|
|
|
248237
248468
|
permissionMode: session?.permissionMode || "edit",
|
|
248238
248469
|
agentType: session?.agentType || "claude-code",
|
|
248239
248470
|
model: session?.model ?? null,
|
|
248240
|
-
processAlive: session ? fastify2.agentSessionManager.getSessionProcessAlive(sessionId) : false
|
|
248471
|
+
processAlive: session ? fastify2.agentSessionManager.getSessionProcessAlive(sessionId) : false,
|
|
248472
|
+
...session ? sendBackFields(session) : {}
|
|
248241
248473
|
},
|
|
248242
248474
|
messages: historyWindow ? historyWindow.entries.map((entry) => entry.message) : messages,
|
|
248243
248475
|
...historyWindow ? { historyWindow } : {}
|
|
@@ -248374,7 +248606,7 @@ var routes11 = async (fastify2) => {
|
|
|
248374
248606
|
return reply.code(200).send({
|
|
248375
248607
|
...remoteData,
|
|
248376
248608
|
session: {
|
|
248377
|
-
...remoteData.session,
|
|
248609
|
+
...await mapRemoteSendBackFields(remoteInfo.remoteServerId, remoteData.session),
|
|
248378
248610
|
id: req.params.sessionId,
|
|
248379
248611
|
projectId: registered?.workspace.project_id ?? mapping?.project_id,
|
|
248380
248612
|
branch: registered ? registered.workspace.branch === "" ? null : registered.workspace.branch : mapping?.branch,
|
|
@@ -248419,7 +248651,8 @@ var routes11 = async (fastify2) => {
|
|
|
248419
248651
|
permissionMode: session.permissionMode,
|
|
248420
248652
|
agentType: session.agentType || "claude-code",
|
|
248421
248653
|
model: session.model ?? null,
|
|
248422
|
-
processAlive: fastify2.agentSessionManager.getSessionProcessAlive(session.id)
|
|
248654
|
+
processAlive: fastify2.agentSessionManager.getSessionProcessAlive(session.id),
|
|
248655
|
+
...sendBackFields(session)
|
|
248423
248656
|
},
|
|
248424
248657
|
messages
|
|
248425
248658
|
});
|
|
@@ -248450,7 +248683,7 @@ var routes11 = async (fastify2) => {
|
|
|
248450
248683
|
return reply.code(200).send({
|
|
248451
248684
|
...data,
|
|
248452
248685
|
session: data.session ? {
|
|
248453
|
-
...data.session,
|
|
248686
|
+
...await mapRemoteSendBackFields(remoteInfo.remoteServerId, data.session),
|
|
248454
248687
|
id: req.params.sessionId,
|
|
248455
248688
|
projectId: projectIdFromRemoteSessionId(req.params.sessionId, remoteInfo),
|
|
248456
248689
|
branch: remoteInfo.branch ?? null
|
|
@@ -248488,7 +248721,8 @@ var routes11 = async (fastify2) => {
|
|
|
248488
248721
|
permissionMode: session.permissionMode,
|
|
248489
248722
|
agentType: session.agentType,
|
|
248490
248723
|
model: session.model,
|
|
248491
|
-
processAlive: fastify2.agentSessionManager.getSessionProcessAlive(session.id)
|
|
248724
|
+
processAlive: fastify2.agentSessionManager.getSessionProcessAlive(session.id),
|
|
248725
|
+
...sendBackFields(session)
|
|
248492
248726
|
}
|
|
248493
248727
|
});
|
|
248494
248728
|
});
|
|
@@ -250548,6 +250782,10 @@ function parseReviewSpan(raw) {
|
|
|
250548
250782
|
if (raw === void 0) return "this_turn";
|
|
250549
250783
|
return raw === "this_turn" || raw === "session_start" ? raw : null;
|
|
250550
250784
|
}
|
|
250785
|
+
function parseReviewContextMode(raw) {
|
|
250786
|
+
if (raw === void 0) return "briefed";
|
|
250787
|
+
return raw === "briefed" || raw === "blind" ? raw : null;
|
|
250788
|
+
}
|
|
250551
250789
|
function normalizeIntentBrief(raw) {
|
|
250552
250790
|
if (!raw?.trim()) return void 0;
|
|
250553
250791
|
return raw.length > 8e3 ? raw.slice(0, 8e3) + "\u2026" : raw;
|
|
@@ -250606,6 +250844,34 @@ async function routes20(fastify2) {
|
|
|
250606
250844
|
const proxyAuto = (info, method, apiPath, body) => proxyToRemoteAuto(info.remoteServerId, method, apiPath, body, {
|
|
250607
250845
|
reverseConnectManager: fastify2.reverseConnectManager
|
|
250608
250846
|
});
|
|
250847
|
+
const TWO_PHASE_REVIEW_CAPABILITIES = [
|
|
250848
|
+
"http:POST /api/path/workflow-runs/prepare",
|
|
250849
|
+
"http:POST /api/path/workflow-runs/:param/activate"
|
|
250850
|
+
];
|
|
250851
|
+
const activateRemoteReview = async (opts) => {
|
|
250852
|
+
const blind = opts.reviewContextMode === "blind";
|
|
250853
|
+
let intentBrief = opts.clientBrief;
|
|
250854
|
+
if (!opts.clientProvidedBrief && !blind) {
|
|
250855
|
+
intentBrief = await distillIntentBrief(opts.userId, opts.sourceSessionId);
|
|
250856
|
+
}
|
|
250857
|
+
const body = { intentBrief, reviewContextMode: opts.reviewContextMode };
|
|
250858
|
+
for (let attempt = 1; ; attempt++) {
|
|
250859
|
+
const result = await proxyAuto(
|
|
250860
|
+
opts,
|
|
250861
|
+
"POST",
|
|
250862
|
+
`/api/path/workflow-runs/${opts.bareRunId}/activate`,
|
|
250863
|
+
body
|
|
250864
|
+
);
|
|
250865
|
+
if (result.ok) return;
|
|
250866
|
+
if (result.status !== 0 || attempt >= 3) {
|
|
250867
|
+
console.warn(
|
|
250868
|
+
`[WorkflowRuns] remote activation failed for run ${opts.bareRunId}: status=${result.status} code=${result.errorCode ?? "n/a"}`
|
|
250869
|
+
);
|
|
250870
|
+
return;
|
|
250871
|
+
}
|
|
250872
|
+
await new Promise((resolve3) => setTimeout(resolve3, attempt * 5e3));
|
|
250873
|
+
}
|
|
250874
|
+
};
|
|
250609
250875
|
const sendProxyFailure = (reply, result) => reply.code(proxyStatus(result)).send(
|
|
250610
250876
|
result.status === 0 ? { error: `Remote proxy failed: ${result.errorCode || "unknown"}` } : result.data
|
|
250611
250877
|
);
|
|
@@ -250655,12 +250921,18 @@ async function routes20(fastify2) {
|
|
|
250655
250921
|
return reply.code(400).send({ error: "reviewerSessionId and reviewerAgentType are mutually exclusive" });
|
|
250656
250922
|
}
|
|
250657
250923
|
const reviewerSessionId = reviewerSessionIdRaw?.trim();
|
|
250924
|
+
const reviewContextMode = parseReviewContextMode(req.body?.reviewContextMode);
|
|
250925
|
+
if (reviewContextMode === null) return reply.code(400).send({ error: "reviewContextMode must be one of: briefed, blind" });
|
|
250926
|
+
const blind = reviewContextMode === "blind";
|
|
250927
|
+
if (blind && reviewerSessionId) {
|
|
250928
|
+
return reply.code(400).send({ error: "blind review requires a new reviewer session" });
|
|
250929
|
+
}
|
|
250658
250930
|
const intentBriefRaw = req.body?.intentBrief;
|
|
250659
250931
|
if (intentBriefRaw !== void 0 && typeof intentBriefRaw !== "string") {
|
|
250660
250932
|
return reply.code(400).send({ error: "intentBrief must be a string" });
|
|
250661
250933
|
}
|
|
250662
250934
|
const clientProvidedBrief = intentBriefRaw !== void 0;
|
|
250663
|
-
const clientBrief = normalizeIntentBrief(intentBriefRaw);
|
|
250935
|
+
const clientBrief = blind ? void 0 : normalizeIntentBrief(intentBriefRaw);
|
|
250664
250936
|
if (sourceSessionId.startsWith("remote-")) {
|
|
250665
250937
|
const remoteInfo = fastify2.remoteSessionMap.get(sourceSessionId);
|
|
250666
250938
|
if (!remoteInfo) return reply.code(404).send({ error: "Session not found" });
|
|
@@ -250682,10 +250954,6 @@ async function routes20(fastify2) {
|
|
|
250682
250954
|
}
|
|
250683
250955
|
bareReviewerSessionId = reviewerInfo.remoteSessionId;
|
|
250684
250956
|
}
|
|
250685
|
-
let intentBrief2 = clientBrief;
|
|
250686
|
-
if (!clientProvidedBrief && !bareReviewerSessionId) {
|
|
250687
|
-
intentBrief2 = await distillIntentBrief(userId, sourceSessionId);
|
|
250688
|
-
}
|
|
250689
250957
|
if (reviewerSessionId && !await fastify2.remoteNotificationSync.prepareForNewTurn(reviewerSessionId)) {
|
|
250690
250958
|
return reply.code(502).send({
|
|
250691
250959
|
error: "Could not reach the remote server to prepare notification delivery",
|
|
@@ -250694,6 +250962,7 @@ async function routes20(fastify2) {
|
|
|
250694
250962
|
}
|
|
250695
250963
|
const reviewerActivityAt = Date.now();
|
|
250696
250964
|
let bareRun;
|
|
250965
|
+
let twoPhase = false;
|
|
250697
250966
|
if (bareReviewerSessionId) {
|
|
250698
250967
|
const result = await proxyAuto(remoteInfo, "POST", "/api/path/workflow-runs", {
|
|
250699
250968
|
sourceSessionId: remoteInfo.remoteSessionId,
|
|
@@ -250701,7 +250970,7 @@ async function routes20(fastify2) {
|
|
|
250701
250970
|
sourceTurnEndIndex,
|
|
250702
250971
|
reviewSpan,
|
|
250703
250972
|
reviewerSessionId: bareReviewerSessionId,
|
|
250704
|
-
intentBrief:
|
|
250973
|
+
intentBrief: clientBrief
|
|
250705
250974
|
});
|
|
250706
250975
|
if (!result.ok) return sendProxyFailure(reply, result);
|
|
250707
250976
|
bareRun = result.data.run;
|
|
@@ -250711,6 +250980,14 @@ async function routes20(fastify2) {
|
|
|
250711
250980
|
remoteInfo.remoteServerId
|
|
250712
250981
|
);
|
|
250713
250982
|
if (!remoteConfig) return reply.code(404).send({ error: "Remote project configuration not found" });
|
|
250983
|
+
const server = await fastify2.storage.remoteServers.getById(remoteInfo.remoteServerId);
|
|
250984
|
+
twoPhase = TWO_PHASE_REVIEW_CAPABILITIES.every(
|
|
250985
|
+
(capability) => server?.worker_capabilities?.includes(capability)
|
|
250986
|
+
);
|
|
250987
|
+
let intentBrief = clientBrief;
|
|
250988
|
+
if (!twoPhase && !clientProvidedBrief && !blind) {
|
|
250989
|
+
intentBrief = await distillIntentBrief(userId, sourceSessionId);
|
|
250990
|
+
}
|
|
250714
250991
|
const result = await createRemoteWorkflowReviewer({
|
|
250715
250992
|
remoteSessionMap: fastify2.remoteSessionMap,
|
|
250716
250993
|
remoteSessionMappings: fastify2.storage.remoteSessionMappings,
|
|
@@ -250727,9 +251004,14 @@ async function routes20(fastify2) {
|
|
|
250727
251004
|
reviewFocus,
|
|
250728
251005
|
sourceTurnEndIndex,
|
|
250729
251006
|
reviewSpan,
|
|
251007
|
+
// Additive tunnel field: a worker that predates it ignores the flag
|
|
251008
|
+
// and runs a briefed (tier-2) review — the reviewer prompt's
|
|
251009
|
+
// trailing "(review context: …)" line records what actually ran.
|
|
251010
|
+
reviewContextMode,
|
|
250730
251011
|
reviewerAgentType: reviewerAgentType ?? "claude-code",
|
|
250731
|
-
intentBrief
|
|
250732
|
-
userId
|
|
251012
|
+
intentBrief,
|
|
251013
|
+
userId,
|
|
251014
|
+
...twoPhase ? { phase: "prepare" } : {}
|
|
250733
251015
|
});
|
|
250734
251016
|
if (!result.ok) return reply.code(proxyStatus(result)).send(result.data);
|
|
250735
251017
|
bareRun = result.remoteRun;
|
|
@@ -250800,18 +251082,34 @@ async function routes20(fastify2) {
|
|
|
250800
251082
|
sessionId: localRun.reviewer_session_id,
|
|
250801
251083
|
alive: true
|
|
250802
251084
|
});
|
|
250803
|
-
|
|
250804
|
-
|
|
250805
|
-
|
|
250806
|
-
|
|
250807
|
-
|
|
250808
|
-
|
|
250809
|
-
|
|
251085
|
+
if (!twoPhase) {
|
|
251086
|
+
fastify2.eventBus.emit({
|
|
251087
|
+
type: "session:status",
|
|
251088
|
+
projectId,
|
|
251089
|
+
branch: bareRun.branch,
|
|
251090
|
+
sessionId: localRun.reviewer_session_id,
|
|
251091
|
+
status: "running"
|
|
251092
|
+
});
|
|
251093
|
+
}
|
|
250810
251094
|
}
|
|
250811
251095
|
fastify2.agentSessionManager.markTitleResolved(localRun.reviewer_session_id);
|
|
250812
251096
|
await fastify2.storage.remoteSessionMappings.markTitleResolved(localRun.reviewer_session_id);
|
|
250813
251097
|
}
|
|
250814
251098
|
fastify2.eventBus.emit({ type: "workflow:run-updated", projectId, branch: bareRun.branch, run: localRun });
|
|
251099
|
+
if (twoPhase) {
|
|
251100
|
+
const bareRunId = bareRun.id;
|
|
251101
|
+
void activateRemoteReview({
|
|
251102
|
+
remoteServerId: remoteInfo.remoteServerId,
|
|
251103
|
+
bareRunId,
|
|
251104
|
+
sourceSessionId,
|
|
251105
|
+
userId,
|
|
251106
|
+
reviewContextMode,
|
|
251107
|
+
clientBrief,
|
|
251108
|
+
clientProvidedBrief
|
|
251109
|
+
}).catch((err) => {
|
|
251110
|
+
console.warn(`[WorkflowRuns] remote activation task failed for run ${bareRunId}:`, err);
|
|
251111
|
+
});
|
|
251112
|
+
}
|
|
250815
251113
|
return reply.code(201).send({ run: localRun });
|
|
250816
251114
|
}
|
|
250817
251115
|
const project = await fastify2.storage.projects.getById(projectId, userId);
|
|
@@ -250826,9 +251124,33 @@ async function routes20(fastify2) {
|
|
|
250826
251124
|
if (branch !== void 0 && (branch || null) !== runBranch) {
|
|
250827
251125
|
return reply.code(400).send({ error: "branch does not match source session" });
|
|
250828
251126
|
}
|
|
250829
|
-
|
|
250830
|
-
|
|
250831
|
-
|
|
251127
|
+
if (!reviewerSessionId) {
|
|
251128
|
+
let run2;
|
|
251129
|
+
try {
|
|
251130
|
+
run2 = await fastify2.workflowEngine.prepareAdhocReview({
|
|
251131
|
+
project: { id: project.id, path: project.path },
|
|
251132
|
+
branch: runBranch,
|
|
251133
|
+
sourceSessionId,
|
|
251134
|
+
reviewFocus,
|
|
251135
|
+
sourceTurnEndIndex,
|
|
251136
|
+
reviewSpan,
|
|
251137
|
+
reviewerAgentType
|
|
251138
|
+
});
|
|
251139
|
+
} catch (err) {
|
|
251140
|
+
const status = errStatus(err);
|
|
251141
|
+
if (status) return reply.code(status).send({ error: err.message });
|
|
251142
|
+
throw err;
|
|
251143
|
+
}
|
|
251144
|
+
void (async () => {
|
|
251145
|
+
let intentBrief = clientBrief;
|
|
251146
|
+
if (!clientProvidedBrief && !blind) {
|
|
251147
|
+
intentBrief = await distillIntentBrief(userId, sourceSessionId);
|
|
251148
|
+
}
|
|
251149
|
+
await fastify2.workflowEngine.activateAdhocReview(run2.id, { intentBrief, blind });
|
|
251150
|
+
})().catch((err) => {
|
|
251151
|
+
console.warn(`[WorkflowRuns] background activation failed for run ${run2.id}:`, err);
|
|
251152
|
+
});
|
|
251153
|
+
return reply.code(201).send({ run: run2 });
|
|
250832
251154
|
}
|
|
250833
251155
|
try {
|
|
250834
251156
|
const run2 = await fastify2.workflowEngine.startAdhocReview({
|
|
@@ -250838,9 +251160,9 @@ async function routes20(fastify2) {
|
|
|
250838
251160
|
reviewFocus,
|
|
250839
251161
|
sourceTurnEndIndex,
|
|
250840
251162
|
reviewSpan,
|
|
250841
|
-
reviewerAgentType,
|
|
250842
251163
|
reviewerSessionId,
|
|
250843
|
-
intentBrief
|
|
251164
|
+
intentBrief: clientBrief,
|
|
251165
|
+
blind
|
|
250844
251166
|
});
|
|
250845
251167
|
return reply.code(201).send({ run: run2 });
|
|
250846
251168
|
} catch (err) {
|
|
@@ -251057,11 +251379,14 @@ async function routes20(fastify2) {
|
|
|
251057
251379
|
if (!sourceSessionId) return reply.code(400).send({ error: "sourceSessionId is required" });
|
|
251058
251380
|
const reviewSpan = parseReviewSpan(req.body?.reviewSpan);
|
|
251059
251381
|
if (reviewSpan === null) return reply.code(400).send({ error: "reviewSpan must be one of: this_turn, session_start" });
|
|
251382
|
+
const reviewContextMode = parseReviewContextMode(req.body?.reviewContextMode);
|
|
251383
|
+
if (reviewContextMode === null) return reply.code(400).send({ error: "reviewContextMode must be one of: briefed, blind" });
|
|
251384
|
+
const blind = reviewContextMode === "blind";
|
|
251060
251385
|
const intentBriefRaw = req.body?.intentBrief;
|
|
251061
251386
|
if (intentBriefRaw !== void 0 && typeof intentBriefRaw !== "string") {
|
|
251062
251387
|
return reply.code(400).send({ error: "intentBrief must be a string" });
|
|
251063
251388
|
}
|
|
251064
|
-
const intentBrief = normalizeIntentBrief(intentBriefRaw);
|
|
251389
|
+
const intentBrief = blind ? void 0 : normalizeIntentBrief(intentBriefRaw);
|
|
251065
251390
|
const reviewerAgentType = parseReviewerAgentType(req.body?.reviewerAgentType);
|
|
251066
251391
|
if (reviewerAgentType === null) return reply.code(400).send({ error: "reviewerAgentType must be one of: claude-code, codex" });
|
|
251067
251392
|
const reviewerSessionIdRaw = req.body?.reviewerSessionId;
|
|
@@ -251099,6 +251424,7 @@ async function routes20(fastify2) {
|
|
|
251099
251424
|
reviewerAgentType,
|
|
251100
251425
|
reviewerSessionId,
|
|
251101
251426
|
intentBrief,
|
|
251427
|
+
blind,
|
|
251102
251428
|
runId: runId || void 0,
|
|
251103
251429
|
newReviewerSessionId: newReviewerSessionId || void 0
|
|
251104
251430
|
});
|
|
@@ -251121,6 +251447,76 @@ async function routes20(fastify2) {
|
|
|
251121
251447
|
throw err;
|
|
251122
251448
|
}
|
|
251123
251449
|
});
|
|
251450
|
+
fastify2.post("/api/path/workflow-runs/prepare", async (req, reply) => {
|
|
251451
|
+
const userId = requireAuth(req, reply);
|
|
251452
|
+
if (userId === null) return;
|
|
251453
|
+
const { sourceSessionId, reviewFocus, sourceTurnEndIndex } = req.body ?? {};
|
|
251454
|
+
if (!sourceSessionId) return reply.code(400).send({ error: "sourceSessionId is required" });
|
|
251455
|
+
const reviewSpan = parseReviewSpan(req.body?.reviewSpan);
|
|
251456
|
+
if (reviewSpan === null) return reply.code(400).send({ error: "reviewSpan must be one of: this_turn, session_start" });
|
|
251457
|
+
const reviewerAgentType = parseReviewerAgentType(req.body?.reviewerAgentType);
|
|
251458
|
+
if (reviewerAgentType === null) return reply.code(400).send({ error: "reviewerAgentType must be one of: claude-code, codex" });
|
|
251459
|
+
const runId = typeof req.body?.runId === "string" ? req.body.runId.trim() : "";
|
|
251460
|
+
const newReviewerSessionId = typeof req.body?.newReviewerSessionId === "string" ? req.body.newReviewerSessionId.trim() : "";
|
|
251461
|
+
if (!runId || !newReviewerSessionId) {
|
|
251462
|
+
return reply.code(400).send({ error: "runId and newReviewerSessionId are required" });
|
|
251463
|
+
}
|
|
251464
|
+
const sourceSession = await fastify2.storage.agentSessions.getById(sourceSessionId);
|
|
251465
|
+
if (!sourceSession) return reply.code(404).send({ error: "Session not found" });
|
|
251466
|
+
const sourceProjection = await projectLocalSession(sourceSession);
|
|
251467
|
+
if (!sourceProjection) return reply.code(409).send({ error: "Session workspace binding is unavailable" });
|
|
251468
|
+
const project = await fastify2.storage.projects.getById(sourceProjection.projectId);
|
|
251469
|
+
if (!project) return reply.code(404).send({ error: "Session not found" });
|
|
251470
|
+
if (!project.path) return reply.code(400).send({ error: "Project has no local path" });
|
|
251471
|
+
const projectPath = project.path;
|
|
251472
|
+
try {
|
|
251473
|
+
let flight = durableReviewFlights.get(runId);
|
|
251474
|
+
if (!flight) {
|
|
251475
|
+
flight = fastify2.workflowEngine.prepareAdhocReview({
|
|
251476
|
+
project: { id: project.id, path: projectPath },
|
|
251477
|
+
branch: sourceProjection.branch,
|
|
251478
|
+
sourceSessionId,
|
|
251479
|
+
reviewFocus,
|
|
251480
|
+
sourceTurnEndIndex,
|
|
251481
|
+
reviewSpan,
|
|
251482
|
+
reviewerAgentType,
|
|
251483
|
+
runId,
|
|
251484
|
+
newReviewerSessionId
|
|
251485
|
+
});
|
|
251486
|
+
durableReviewFlights.set(runId, flight);
|
|
251487
|
+
const clear = () => {
|
|
251488
|
+
if (durableReviewFlights.get(runId) === flight) durableReviewFlights.delete(runId);
|
|
251489
|
+
};
|
|
251490
|
+
void flight.then(clear, clear);
|
|
251491
|
+
}
|
|
251492
|
+
const run2 = await flight;
|
|
251493
|
+
return reply.code(201).send({ run: run2 });
|
|
251494
|
+
} catch (err) {
|
|
251495
|
+
const status = errStatus(err);
|
|
251496
|
+
if (status) return reply.code(status).send({ error: err.message });
|
|
251497
|
+
throw err;
|
|
251498
|
+
}
|
|
251499
|
+
});
|
|
251500
|
+
fastify2.post("/api/path/workflow-runs/:id/activate", async (req, reply) => {
|
|
251501
|
+
const userId = requireAuth(req, reply);
|
|
251502
|
+
if (userId === null) return;
|
|
251503
|
+
const reviewContextMode = parseReviewContextMode(req.body?.reviewContextMode);
|
|
251504
|
+
if (reviewContextMode === null) return reply.code(400).send({ error: "reviewContextMode must be one of: briefed, blind" });
|
|
251505
|
+
const blind = reviewContextMode === "blind";
|
|
251506
|
+
const intentBriefRaw = req.body?.intentBrief;
|
|
251507
|
+
if (intentBriefRaw !== void 0 && typeof intentBriefRaw !== "string") {
|
|
251508
|
+
return reply.code(400).send({ error: "intentBrief must be a string" });
|
|
251509
|
+
}
|
|
251510
|
+
const intentBrief = blind ? void 0 : normalizeIntentBrief(intentBriefRaw);
|
|
251511
|
+
try {
|
|
251512
|
+
const run2 = await fastify2.workflowEngine.activateAdhocReview(req.params.id, { intentBrief, blind });
|
|
251513
|
+
return reply.send({ run: run2 });
|
|
251514
|
+
} catch (err) {
|
|
251515
|
+
const status = errStatus(err);
|
|
251516
|
+
if (status) return reply.code(status).send({ error: err.message });
|
|
251517
|
+
throw err;
|
|
251518
|
+
}
|
|
251519
|
+
});
|
|
251124
251520
|
fastify2.get("/api/path/workflow-runs/reviewer-candidate", async (req, reply) => {
|
|
251125
251521
|
const userId = requireAuth(req, reply);
|
|
251126
251522
|
if (userId === null) return;
|
|
@@ -252043,6 +252439,16 @@ var routes23 = async (fastify2) => {
|
|
|
252043
252439
|
const stopHeartbeat = attachWsHeartbeat(socket, { label: "ExecutorMux" });
|
|
252044
252440
|
const subs = /* @__PURE__ */ new Map();
|
|
252045
252441
|
const handleInputMap = /* @__PURE__ */ new Map();
|
|
252442
|
+
const wanted = /* @__PURE__ */ new Set();
|
|
252443
|
+
const dropSubscription = (processId) => {
|
|
252444
|
+
const sub = subs.get(processId);
|
|
252445
|
+
if (sub) {
|
|
252446
|
+
sub.detach();
|
|
252447
|
+
sub.cleanup();
|
|
252448
|
+
subs.delete(processId);
|
|
252449
|
+
}
|
|
252450
|
+
handleInputMap.delete(processId);
|
|
252451
|
+
};
|
|
252046
252452
|
const subscribeProcess = async (processId) => {
|
|
252047
252453
|
if (subs.has(processId)) return;
|
|
252048
252454
|
const ownerUserId = processOwnerScope(principal);
|
|
@@ -252055,25 +252461,31 @@ var routes23 = async (fastify2) => {
|
|
|
252055
252461
|
return;
|
|
252056
252462
|
}
|
|
252057
252463
|
if (subs.has(processId)) return;
|
|
252464
|
+
if (!wanted.has(processId)) return;
|
|
252465
|
+
let detached = false;
|
|
252058
252466
|
const send = (msg) => {
|
|
252467
|
+
if (detached) return;
|
|
252059
252468
|
try {
|
|
252060
252469
|
socket.send(JSON.stringify({ processId, ...msg }));
|
|
252061
252470
|
} catch {
|
|
252062
252471
|
}
|
|
252063
252472
|
};
|
|
252064
252473
|
let terminated = false;
|
|
252474
|
+
let self2 = null;
|
|
252065
252475
|
const onTerminal = () => {
|
|
252066
252476
|
terminated = true;
|
|
252067
|
-
|
|
252068
|
-
if (c) {
|
|
252069
|
-
c();
|
|
252477
|
+
if (self2 && subs.get(processId) === self2) {
|
|
252070
252478
|
subs.delete(processId);
|
|
252479
|
+
handleInputMap.delete(processId);
|
|
252071
252480
|
}
|
|
252072
|
-
|
|
252481
|
+
self2?.cleanup();
|
|
252073
252482
|
};
|
|
252074
252483
|
const handle = processId.startsWith("remote-") ? attachRemoteProcessStream(fastify2, processId, send, onTerminal) : attachLocalProcessStream(fastify2, processId, send, onTerminal);
|
|
252075
252484
|
if (!terminated) {
|
|
252076
|
-
|
|
252485
|
+
self2 = { cleanup: handle.cleanup, detach: () => {
|
|
252486
|
+
detached = true;
|
|
252487
|
+
} };
|
|
252488
|
+
subs.set(processId, self2);
|
|
252077
252489
|
handleInputMap.set(processId, handle.handleInput);
|
|
252078
252490
|
}
|
|
252079
252491
|
};
|
|
@@ -252081,13 +252493,13 @@ var routes23 = async (fastify2) => {
|
|
|
252081
252493
|
try {
|
|
252082
252494
|
const msg = JSON.parse(data.toString());
|
|
252083
252495
|
if (msg.type === "subscribe") {
|
|
252496
|
+
wanted.add(msg.processId);
|
|
252084
252497
|
subscribeProcess(msg.processId).catch((err) => {
|
|
252085
252498
|
console.error(`[ExecutorMux] Failed to subscribe to ${msg.processId}:`, err);
|
|
252086
252499
|
});
|
|
252087
252500
|
} else if (msg.type === "unsubscribe") {
|
|
252088
|
-
|
|
252089
|
-
|
|
252090
|
-
handleInputMap.delete(msg.processId);
|
|
252501
|
+
wanted.delete(msg.processId);
|
|
252502
|
+
dropSubscription(msg.processId);
|
|
252091
252503
|
} else if (msg.type === "input") {
|
|
252092
252504
|
handleInputMap.get(msg.processId)?.({ type: "input", data: msg.data });
|
|
252093
252505
|
} else if (msg.type === "resize") {
|
|
@@ -252103,7 +252515,11 @@ var routes23 = async (fastify2) => {
|
|
|
252103
252515
|
socket.on("close", () => {
|
|
252104
252516
|
console.log(`[ExecutorMux] Client disconnected; cleaning ${subs.size} subscriptions`);
|
|
252105
252517
|
stopHeartbeat();
|
|
252106
|
-
|
|
252518
|
+
wanted.clear();
|
|
252519
|
+
for (const sub of subs.values()) {
|
|
252520
|
+
sub.detach();
|
|
252521
|
+
sub.cleanup();
|
|
252522
|
+
}
|
|
252107
252523
|
subs.clear();
|
|
252108
252524
|
handleInputMap.clear();
|
|
252109
252525
|
});
|
|
@@ -252185,6 +252601,12 @@ var routes23 = async (fastify2) => {
|
|
|
252185
252601
|
}));
|
|
252186
252602
|
} catch {
|
|
252187
252603
|
}
|
|
252604
|
+
if (cacheEntry.backgroundTasks !== null) {
|
|
252605
|
+
try {
|
|
252606
|
+
socket.send(cacheEntry.backgroundTasks);
|
|
252607
|
+
} catch {
|
|
252608
|
+
}
|
|
252609
|
+
}
|
|
252188
252610
|
for (const raw of cacheEntry.messages) {
|
|
252189
252611
|
if (replayAfter >= 0) {
|
|
252190
252612
|
try {
|
|
@@ -252208,12 +252630,6 @@ var routes23 = async (fastify2) => {
|
|
|
252208
252630
|
socket.send(JSON.stringify({ Ready: true, historyEpoch: cacheEntry.historyEpoch ?? void 0 }));
|
|
252209
252631
|
} catch {
|
|
252210
252632
|
}
|
|
252211
|
-
if (cacheEntry.backgroundTasks !== null) {
|
|
252212
|
-
try {
|
|
252213
|
-
socket.send(cacheEntry.backgroundTasks);
|
|
252214
|
-
} catch {
|
|
252215
|
-
}
|
|
252216
|
-
}
|
|
252217
252633
|
if (cacheEntry.finished) {
|
|
252218
252634
|
try {
|
|
252219
252635
|
socket.send(JSON.stringify({ finished: true }));
|
|
@@ -260806,6 +261222,12 @@ var WORKER_CAPABILITIES = {
|
|
|
260806
261222
|
// --- Workflow runs ---
|
|
260807
261223
|
// Empirically bisected via cross-version e2e: 0.2.4 → 404, 0.2.5 → serves.
|
|
260808
261224
|
"http:POST /api/path/workflow-runs": { since: "0.2.5", summary: "\u521B\u5EFA workflow run" },
|
|
261225
|
+
// Both additive, checked as a pair (TWO_PHASE_REVIEW_CAPABILITIES in
|
|
261226
|
+
// workflow-run-routes.ts): a worker missing either gets the original
|
|
261227
|
+
// single-shot create — the hub distills inline and the submit blocks on it,
|
|
261228
|
+
// exactly the pre-two-phase behavior.
|
|
261229
|
+
"http:POST /api/path/workflow-runs/prepare": { since: "0.3.30", summary: "\u4E24\u6BB5\u5F0F review:\u51C6\u5907(\u5360\u4F4D run + reviewer session)" },
|
|
261230
|
+
"http:POST /api/path/workflow-runs/:param/activate": { since: "0.3.30", summary: "\u4E24\u6BB5\u5F0F review:\u6FC0\u6D3B(\u6295\u9012 reviewer \u9996\u6761\u6D88\u606F)" },
|
|
260809
261231
|
"http:GET /api/path/workflow-runs": { since: "0.2.5", summary: "workflow run \u5217\u8868" },
|
|
260810
261232
|
"http:GET /api/path/workflow-runs/reviewer-candidate": { since: "0.2.5", summary: "reviewer \u5019\u9009\u67E5\u8BE2" },
|
|
260811
261233
|
"http:GET /api/workflow-runs/:param": { since: "0.2.5", summary: "\u8BFB workflow run" },
|