@vibedeckx/linux-x64 0.3.29 → 0.3.31
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 +414 -65
- 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",
|
|
@@ -188476,7 +188477,7 @@ var createWorkflowRunRepos = (kdb) => ({
|
|
|
188476
188477
|
...opts,
|
|
188477
188478
|
reviewer_session_id: opts.reviewer_session_id ?? null,
|
|
188478
188479
|
review_span: opts.review_span ?? "this_turn",
|
|
188479
|
-
status: "waiting_reviewer"
|
|
188480
|
+
status: opts.status ?? "waiting_reviewer"
|
|
188480
188481
|
}).execute();
|
|
188481
188482
|
const row = await kdb.selectFrom("workflow_runs").selectAll().where("id", "=", opts.id).executeTakeFirstOrThrow();
|
|
188482
188483
|
return asRun(row);
|
|
@@ -188504,6 +188505,10 @@ var createWorkflowRunRepos = (kdb) => ({
|
|
|
188504
188505
|
const row = await kdb.selectFrom("workflow_runs").selectAll().where("source_session_id", "=", sourceSessionId).where("status", "=", "completed").where("reviewer_session_id", "is not", null).orderBy("created_at", "desc").orderBy(sql`rowid`, "desc").executeTakeFirst();
|
|
188505
188506
|
return row ? asRun(row) : void 0;
|
|
188506
188507
|
},
|
|
188508
|
+
listReviewedSourceSessions: async (projectId, branch) => {
|
|
188509
|
+
const rows = await kdb.selectFrom("workflow_runs").select("source_session_id").distinct().where("project_id", "=", projectId).where("branch", "is", branch).where("status", "=", "completed").where("reviewer_session_id", "is not", null).execute();
|
|
188510
|
+
return rows.map((r) => r.source_session_id);
|
|
188511
|
+
},
|
|
188507
188512
|
update: async (id, patch) => {
|
|
188508
188513
|
if (Object.keys(patch).length > 0) {
|
|
188509
188514
|
await kdb.updateTable("workflow_runs").set({ ...patch, updated_at: sql`datetime('now')` }).where("id", "=", id).execute();
|
|
@@ -205223,6 +205228,12 @@ var initializeSchema = (db) => {
|
|
|
205223
205228
|
FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE
|
|
205224
205229
|
);
|
|
205225
205230
|
|
|
205231
|
+
-- getActive (panel poll, every 5s per open workspace) and
|
|
205232
|
+
-- listReviewedSourceSessions (same request) both scope by project+branch
|
|
205233
|
+
-- and differ only in the status filter.
|
|
205234
|
+
CREATE INDEX IF NOT EXISTS idx_workflow_runs_project_branch_status
|
|
205235
|
+
ON workflow_runs(project_id, branch, status);
|
|
205236
|
+
|
|
205226
205237
|
CREATE TABLE IF NOT EXISTS turn_snapshots (
|
|
205227
205238
|
session_id TEXT NOT NULL,
|
|
205228
205239
|
turn_end_index INTEGER NOT NULL,
|
|
@@ -233410,22 +233421,32 @@ async function createRemoteWorkflowReviewer(deps, params) {
|
|
|
233410
233421
|
});
|
|
233411
233422
|
let registeredLocalSessionId = null;
|
|
233412
233423
|
try {
|
|
233413
|
-
const
|
|
233424
|
+
const sharedBody = {
|
|
233425
|
+
sourceSessionId: params.sourceRemoteSessionId,
|
|
233426
|
+
reviewFocus: params.reviewFocus,
|
|
233427
|
+
sourceTurnEndIndex: params.sourceTurnEndIndex,
|
|
233428
|
+
reviewSpan: params.reviewSpan,
|
|
233429
|
+
reviewerAgentType: params.reviewerAgentType,
|
|
233430
|
+
runId: remoteRunId,
|
|
233431
|
+
newReviewerSessionId: remoteReviewerSessionId
|
|
233432
|
+
};
|
|
233433
|
+
const proxyOpts = { reverseConnectManager: deps.reverseConnectManager ?? void 0 };
|
|
233434
|
+
const result = params.phase === "prepare" ? await proxyToRemoteAuto(
|
|
233435
|
+
params.agentMode,
|
|
233436
|
+
"POST",
|
|
233437
|
+
"/api/path/workflow-runs/prepare",
|
|
233438
|
+
sharedBody,
|
|
233439
|
+
proxyOpts
|
|
233440
|
+
) : await proxyToRemoteAuto(
|
|
233414
233441
|
params.agentMode,
|
|
233415
233442
|
"POST",
|
|
233416
233443
|
"/api/path/workflow-runs",
|
|
233417
233444
|
{
|
|
233418
|
-
|
|
233419
|
-
reviewFocus: params.reviewFocus,
|
|
233420
|
-
sourceTurnEndIndex: params.sourceTurnEndIndex,
|
|
233421
|
-
reviewSpan: params.reviewSpan,
|
|
233445
|
+
...sharedBody,
|
|
233422
233446
|
reviewContextMode: params.reviewContextMode,
|
|
233423
|
-
|
|
233424
|
-
intentBrief: params.intentBrief,
|
|
233425
|
-
runId: remoteRunId,
|
|
233426
|
-
newReviewerSessionId: remoteReviewerSessionId
|
|
233447
|
+
intentBrief: params.intentBrief
|
|
233427
233448
|
},
|
|
233428
|
-
|
|
233449
|
+
proxyOpts
|
|
233429
233450
|
);
|
|
233430
233451
|
if (!result.ok) {
|
|
233431
233452
|
const uncertain = result.errorCode === "network_error" || result.errorCode === "timeout";
|
|
@@ -239802,6 +239823,7 @@ var FINAL_VERDICT_PROMPT = [
|
|
|
239802
239823
|
...VERDICT_INSTRUCTIONS
|
|
239803
239824
|
].join("\n");
|
|
239804
239825
|
var REVIEWER_AGENT_TYPES = /* @__PURE__ */ new Set(["claude-code", "codex"]);
|
|
239826
|
+
var PREPARE_TIMEOUT_MS = 10 * 6e4;
|
|
239805
239827
|
var WorkflowEngine = class {
|
|
239806
239828
|
constructor(storage2, agentOps) {
|
|
239807
239829
|
this.storage = storage2;
|
|
@@ -239812,6 +239834,12 @@ var WorkflowEngine = class {
|
|
|
239812
239834
|
eventBus;
|
|
239813
239835
|
/** sessionId → participation in an active run (rebuilt on boot). */
|
|
239814
239836
|
participants = /* @__PURE__ */ new Map();
|
|
239837
|
+
/** runId → prompt inputs captured at prepare time (see PendingActivation). */
|
|
239838
|
+
pendingActivations = /* @__PURE__ */ new Map();
|
|
239839
|
+
/** runId → armed preparation-timeout timer. */
|
|
239840
|
+
prepareTimers = /* @__PURE__ */ new Map();
|
|
239841
|
+
/** runId → in-flight activation, so concurrent calls join instead of double-sending. */
|
|
239842
|
+
activationFlights = /* @__PURE__ */ new Map();
|
|
239815
239843
|
setEventBus(bus) {
|
|
239816
239844
|
this.eventBus = bus;
|
|
239817
239845
|
bus.subscribe((event) => {
|
|
@@ -239836,6 +239864,12 @@ var WorkflowEngine = class {
|
|
|
239836
239864
|
await this.storage.workflowRuns.update(run2.id, {
|
|
239837
239865
|
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"
|
|
239838
239866
|
});
|
|
239867
|
+
} else if (run2.status === "preparing") {
|
|
239868
|
+
const createdAt = Date.parse(
|
|
239869
|
+
run2.created_at.includes("T") ? run2.created_at : run2.created_at.replace(" ", "T") + "Z"
|
|
239870
|
+
);
|
|
239871
|
+
const elapsed = Number.isFinite(createdAt) ? Date.now() - createdAt : PREPARE_TIMEOUT_MS;
|
|
239872
|
+
this.armPrepareTimeout(run2.id, PREPARE_TIMEOUT_MS - elapsed);
|
|
239839
239873
|
}
|
|
239840
239874
|
this.trackParticipants(run2);
|
|
239841
239875
|
}
|
|
@@ -239850,6 +239884,37 @@ var WorkflowEngine = class {
|
|
|
239850
239884
|
for (const [sid, p2] of this.participants) {
|
|
239851
239885
|
if (p2.runId === run2.id) this.participants.delete(sid);
|
|
239852
239886
|
}
|
|
239887
|
+
this.clearPendingActivation(run2.id);
|
|
239888
|
+
}
|
|
239889
|
+
clearPendingActivation(runId) {
|
|
239890
|
+
this.pendingActivations.delete(runId);
|
|
239891
|
+
const timer = this.prepareTimers.get(runId);
|
|
239892
|
+
if (timer) {
|
|
239893
|
+
clearTimeout(timer);
|
|
239894
|
+
this.prepareTimers.delete(runId);
|
|
239895
|
+
}
|
|
239896
|
+
}
|
|
239897
|
+
/**
|
|
239898
|
+
* Backstop for a run stuck in `preparing` (see PREPARE_TIMEOUT_MS). A timed
|
|
239899
|
+
* out preparation becomes a normal failed run — visible in the UI and as a
|
|
239900
|
+
* workflow_failed milestone — instead of a placeholder that spins forever.
|
|
239901
|
+
* `unref` so an armed timer never holds the process open.
|
|
239902
|
+
*/
|
|
239903
|
+
armPrepareTimeout(runId, delayMs) {
|
|
239904
|
+
const existing = this.prepareTimers.get(runId);
|
|
239905
|
+
if (existing) clearTimeout(existing);
|
|
239906
|
+
const timer = setTimeout(() => {
|
|
239907
|
+
this.prepareTimers.delete(runId);
|
|
239908
|
+
void (async () => {
|
|
239909
|
+
const run2 = await this.storage.workflowRuns.getById(runId);
|
|
239910
|
+
if (run2?.status === "preparing") {
|
|
239911
|
+
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");
|
|
239912
|
+
}
|
|
239913
|
+
this.pendingActivations.delete(runId);
|
|
239914
|
+
})().catch((err) => console.error("[WorkflowEngine] prepare-timeout sweep failed:", err));
|
|
239915
|
+
}, Math.max(0, delayMs));
|
|
239916
|
+
timer.unref?.();
|
|
239917
|
+
this.prepareTimers.set(runId, timer);
|
|
239853
239918
|
}
|
|
239854
239919
|
releaseReservations(runId) {
|
|
239855
239920
|
for (const [sid, participant] of this.participants) {
|
|
@@ -239890,7 +239955,10 @@ var WorkflowEngine = class {
|
|
|
239890
239955
|
);
|
|
239891
239956
|
if (!ok) return;
|
|
239892
239957
|
const failed = await this.storage.workflowRuns.getById(run2.id);
|
|
239893
|
-
if (failed)
|
|
239958
|
+
if (failed) {
|
|
239959
|
+
this.untrackRun(failed);
|
|
239960
|
+
this.emitRunUpdated(failed);
|
|
239961
|
+
}
|
|
239894
239962
|
this.onMilestoneCreated?.();
|
|
239895
239963
|
}
|
|
239896
239964
|
/** Test seam for the failure path (see failRun). */
|
|
@@ -239959,7 +240027,29 @@ var WorkflowEngine = class {
|
|
|
239959
240027
|
reason: null
|
|
239960
240028
|
};
|
|
239961
240029
|
}
|
|
240030
|
+
/**
|
|
240031
|
+
* Single-shot start, preserved for old callers and durable-intent replays:
|
|
240032
|
+
* prepare + activate back to back. The reuse-reviewer path delivers its
|
|
240033
|
+
* prompt inside prepare (no preparing state — nothing distills for a
|
|
240034
|
+
* re-review); a fresh reviewer comes back `preparing` and is activated
|
|
240035
|
+
* inline, which also completes a replayed run that a previous caller
|
|
240036
|
+
* prepared but never activated.
|
|
240037
|
+
*/
|
|
239962
240038
|
async startAdhocReview(opts) {
|
|
240039
|
+
const run2 = await this.prepareAdhocReview(opts);
|
|
240040
|
+
if (opts.reviewerSessionId || run2.status !== "preparing") return run2;
|
|
240041
|
+
return this.activateAdhocReview(run2.id, { intentBrief: opts.intentBrief, blind: opts.blind });
|
|
240042
|
+
}
|
|
240043
|
+
/**
|
|
240044
|
+
* Phase 1 of the two-phase adhoc review: validate, reserve participants,
|
|
240045
|
+
* create the run row and (for a fresh review) the reviewer session — titled
|
|
240046
|
+
* and visible in the sidebar — WITHOUT sending the reviewer prompt. Fast
|
|
240047
|
+
* (no model calls), so the route can respond immediately and let the
|
|
240048
|
+
* intent-brief distillation happen after the user has moved on. The run
|
|
240049
|
+
* stays `preparing` until activateAdhocReview delivers the first message,
|
|
240050
|
+
* bounded by PREPARE_TIMEOUT_MS.
|
|
240051
|
+
*/
|
|
240052
|
+
async prepareAdhocReview(opts) {
|
|
239963
240053
|
if (opts.reviewerSessionId === opts.sourceSessionId) {
|
|
239964
240054
|
throw new WorkflowError("reviewer-unavailable", "reviewer session \u4E0D\u80FD\u4E0E source session \u76F8\u540C");
|
|
239965
240055
|
}
|
|
@@ -239982,7 +240072,7 @@ var WorkflowEngine = class {
|
|
|
239982
240072
|
}
|
|
239983
240073
|
return existingRun;
|
|
239984
240074
|
}
|
|
239985
|
-
if (existingRun.status !== "waiting_reviewer") {
|
|
240075
|
+
if (existingRun.status !== "waiting_reviewer" && existingRun.status !== "preparing") {
|
|
239986
240076
|
throw new WorkflowError("bad-state", "workflow run \u5DF2\u7EC8\u6B62\uFF0C\u4E0D\u80FD\u4F5C\u4E3A\u672A\u77E5\u521B\u5EFA\u7ED3\u679C\u91CD\u653E");
|
|
239987
240077
|
}
|
|
239988
240078
|
}
|
|
@@ -240060,9 +240150,17 @@ var WorkflowEngine = class {
|
|
|
240060
240150
|
review_focus: opts.reviewFocus ?? null,
|
|
240061
240151
|
review_target: JSON.stringify(target),
|
|
240062
240152
|
reviewer_session_id: opts.reviewerSessionId ?? null,
|
|
240063
|
-
review_span: opts.reviewSpan ?? "this_turn"
|
|
240153
|
+
review_span: opts.reviewSpan ?? "this_turn",
|
|
240154
|
+
// Reuse skips the preparing state entirely: its prompt is delivered
|
|
240155
|
+
// below, before this method returns.
|
|
240156
|
+
status: opts.reviewerSessionId ? "waiting_reviewer" : "preparing"
|
|
240064
240157
|
});
|
|
240065
240158
|
this.trackParticipants(run2);
|
|
240159
|
+
if (existingRun && !existingRun.reviewer_session_id && !opts.reviewerSessionId && run2.status === "waiting_reviewer") {
|
|
240160
|
+
const flipped = await this.storage.workflowRuns.transition(run2.id, "waiting_reviewer", "preparing");
|
|
240161
|
+
if (!flipped) throw new WorkflowError("bad-state", "run \u5728\u91CD\u653E\u671F\u95F4\u5DF2\u88AB\u53D6\u6D88\u6216\u5931\u8D25");
|
|
240162
|
+
run2.status = "preparing";
|
|
240163
|
+
}
|
|
240066
240164
|
if (opts.reviewerSessionId && reviewerSession) {
|
|
240067
240165
|
if (reviewerSession.permission_mode !== "plan") {
|
|
240068
240166
|
let switched = false;
|
|
@@ -240113,7 +240211,13 @@ var WorkflowEngine = class {
|
|
|
240113
240211
|
false,
|
|
240114
240212
|
"plan",
|
|
240115
240213
|
opts.reviewerAgentType ?? "claude-code",
|
|
240116
|
-
|
|
240214
|
+
// announceRunning=false — deliberate change from the single-shot
|
|
240215
|
+
// start: the preparing reviewer must appear in the sidebar (the
|
|
240216
|
+
// spawn's unconditional session:process emit covers that) WITHOUT
|
|
240217
|
+
// the session:status "running" emit that auto-surfaces it into the
|
|
240218
|
+
// open agent window. The user stays on the source session and opens
|
|
240219
|
+
// the reviewer from the sidebar when they choose to.
|
|
240220
|
+
false,
|
|
240117
240221
|
false,
|
|
240118
240222
|
{
|
|
240119
240223
|
startSnapshot: endSnap,
|
|
@@ -240125,27 +240229,25 @@ var WorkflowEngine = class {
|
|
|
240125
240229
|
reviewerId,
|
|
240126
240230
|
`Review - ${sourceSession?.title || (taskContext ? snippetTitle(taskContext) : null) || "Conversation"}`
|
|
240127
240231
|
).catch((err) => console.warn(`[WorkflowEngine] failed to set reviewer title for ${reviewerId}:`, err));
|
|
240128
|
-
|
|
240232
|
+
this.pendingActivations.set(run2.id, {
|
|
240233
|
+
scope,
|
|
240129
240234
|
taskContext,
|
|
240130
240235
|
originalIntent: extractFirstUserMessage(entries),
|
|
240131
|
-
authorSelfReport: extractAuthorSelfReport(entries, turnEndIndex)
|
|
240132
|
-
|
|
240133
|
-
|
|
240134
|
-
|
|
240135
|
-
|
|
240136
|
-
|
|
240236
|
+
authorSelfReport: extractAuthorSelfReport(entries, turnEndIndex)
|
|
240237
|
+
});
|
|
240238
|
+
const updated = await this.storage.workflowRuns.update(run2.id, {
|
|
240239
|
+
reviewer_session_id: reviewerId,
|
|
240240
|
+
// Legacy replay backfill: a durable run interrupted before reviewer
|
|
240241
|
+
// binding may carry no stored target. Activation builds the prompt
|
|
240242
|
+
// from the stored one, so persist the target just captured (same
|
|
240243
|
+
// worktree, cutoff verified above to match the stored run).
|
|
240244
|
+
...run2.review_target ? {} : { review_target: JSON.stringify(target) }
|
|
240137
240245
|
});
|
|
240138
|
-
const sent = await this.agentOps.sendUserMessage(reviewerId, prompt, opts.project.path, void 0, REVIEWER_TURN);
|
|
240139
|
-
if (!sent) {
|
|
240140
|
-
await this.failRun({ ...run2, reviewer_session_id: reviewerId }, "\u5411 reviewer \u6295\u9012\u4EFB\u52A1\u5931\u8D25");
|
|
240141
|
-
throw new WorkflowError("spawn-failed", "\u5411 reviewer \u6295\u9012\u4EFB\u52A1\u5931\u8D25");
|
|
240142
|
-
}
|
|
240143
|
-
const updated = await this.storage.workflowRuns.update(run2.id, { reviewer_session_id: reviewerId });
|
|
240144
240246
|
this.trackParticipants(updated);
|
|
240247
|
+
this.armPrepareTimeout(run2.id, PREPARE_TIMEOUT_MS);
|
|
240145
240248
|
this.emitRunUpdated(updated);
|
|
240146
240249
|
return updated;
|
|
240147
240250
|
} catch (err) {
|
|
240148
|
-
if (err instanceof WorkflowError && err.code === "spawn-failed") throw err;
|
|
240149
240251
|
await this.failRun(run2, `\u521B\u5EFA reviewer \u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}`);
|
|
240150
240252
|
throw new WorkflowError("spawn-failed", "\u521B\u5EFA reviewer session \u5931\u8D25");
|
|
240151
240253
|
}
|
|
@@ -240154,6 +240256,73 @@ var WorkflowEngine = class {
|
|
|
240154
240256
|
throw err;
|
|
240155
240257
|
}
|
|
240156
240258
|
}
|
|
240259
|
+
/**
|
|
240260
|
+
* Phase 2 of the two-phase adhoc review: build the reviewer prompt and send
|
|
240261
|
+
* the first message. Called after the intent brief finished distilling —
|
|
240262
|
+
* inline for single-shot starts, in the background (or over the tunnel, for
|
|
240263
|
+
* remote reviews) after the route already responded. Idempotent: replaying
|
|
240264
|
+
* an already-activated run returns it unchanged, which the durable-intent
|
|
240265
|
+
* replay path relies on.
|
|
240266
|
+
*/
|
|
240267
|
+
async activateAdhocReview(runId, opts = {}) {
|
|
240268
|
+
const existing = this.activationFlights.get(runId);
|
|
240269
|
+
if (existing) return existing;
|
|
240270
|
+
const flight = this.runActivation(runId, opts);
|
|
240271
|
+
this.activationFlights.set(runId, flight);
|
|
240272
|
+
const clear = () => {
|
|
240273
|
+
if (this.activationFlights.get(runId) === flight) this.activationFlights.delete(runId);
|
|
240274
|
+
};
|
|
240275
|
+
flight.then(clear, clear);
|
|
240276
|
+
return flight;
|
|
240277
|
+
}
|
|
240278
|
+
async runActivation(runId, opts) {
|
|
240279
|
+
const run2 = await this.storage.workflowRuns.getById(runId);
|
|
240280
|
+
if (!run2) throw new WorkflowError("bad-state", "run \u4E0D\u5B58\u5728\uFF0C\u65E0\u6CD5\u6FC0\u6D3B");
|
|
240281
|
+
if (run2.status !== "preparing") {
|
|
240282
|
+
if (TERMINAL_STATUSES.has(run2.status)) {
|
|
240283
|
+
throw new WorkflowError("bad-state", "run \u5DF2\u7ED3\u675F\uFF0C\u65E0\u6CD5\u6FC0\u6D3B");
|
|
240284
|
+
}
|
|
240285
|
+
return run2;
|
|
240286
|
+
}
|
|
240287
|
+
if (!run2.reviewer_session_id) {
|
|
240288
|
+
throw new WorkflowError("bad-state", "run \u8FD8\u6CA1\u6709 reviewer session\uFF0C\u65E0\u6CD5\u6FC0\u6D3B");
|
|
240289
|
+
}
|
|
240290
|
+
if (!run2.review_target) {
|
|
240291
|
+
throw new WorkflowError("bad-state", "run \u7F3A\u5C11 review target\uFF0C\u65E0\u6CD5\u6FC0\u6D3B");
|
|
240292
|
+
}
|
|
240293
|
+
const pending = this.pendingActivations.get(runId);
|
|
240294
|
+
try {
|
|
240295
|
+
const entries = pending ? null : this.agentOps.getRawMessages(run2.source_session_id);
|
|
240296
|
+
const prompt = buildReviewerPrompt({
|
|
240297
|
+
taskContext: pending ? pending.taskContext : extractTaskContextBefore(entries, run2.source_turn_end_index),
|
|
240298
|
+
originalIntent: pending ? pending.originalIntent : extractFirstUserMessage(entries),
|
|
240299
|
+
authorSelfReport: pending ? pending.authorSelfReport : extractAuthorSelfReport(entries, run2.source_turn_end_index),
|
|
240300
|
+
intentBrief: opts.intentBrief ?? null,
|
|
240301
|
+
blind: opts.blind,
|
|
240302
|
+
reviewFocus: run2.review_focus,
|
|
240303
|
+
target: JSON.parse(run2.review_target),
|
|
240304
|
+
scope: pending?.scope ?? null
|
|
240305
|
+
});
|
|
240306
|
+
const project = await this.storage.projects.getById(run2.project_id);
|
|
240307
|
+
const sent = await this.agentOps.sendUserMessage(run2.reviewer_session_id, prompt, project?.path ?? void 0, void 0, REVIEWER_TURN).catch(() => false);
|
|
240308
|
+
if (!sent) {
|
|
240309
|
+
await this.failRun(run2, "\u5411 reviewer \u6295\u9012\u4EFB\u52A1\u5931\u8D25");
|
|
240310
|
+
throw new WorkflowError("spawn-failed", "\u5411 reviewer \u6295\u9012\u4EFB\u52A1\u5931\u8D25");
|
|
240311
|
+
}
|
|
240312
|
+
} catch (err) {
|
|
240313
|
+
if (err instanceof WorkflowError && err.code === "spawn-failed") throw err;
|
|
240314
|
+
await this.failRun(run2, `\u6FC0\u6D3B reviewer \u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}`);
|
|
240315
|
+
throw new WorkflowError("spawn-failed", "\u6FC0\u6D3B reviewer session \u5931\u8D25");
|
|
240316
|
+
}
|
|
240317
|
+
const claimed = await this.storage.workflowRuns.transition(runId, "preparing", "waiting_reviewer");
|
|
240318
|
+
if (!claimed) {
|
|
240319
|
+
throw new WorkflowError("bad-state", "run \u5728\u51C6\u5907\u671F\u95F4\u5DF2\u88AB\u53D6\u6D88\u6216\u5931\u8D25");
|
|
240320
|
+
}
|
|
240321
|
+
this.clearPendingActivation(runId);
|
|
240322
|
+
const updated = await this.storage.workflowRuns.getById(runId);
|
|
240323
|
+
this.emitRunUpdated(updated);
|
|
240324
|
+
return updated;
|
|
240325
|
+
}
|
|
240157
240326
|
async handleTaskCompleted(event) {
|
|
240158
240327
|
const p2 = this.participants.get(event.sessionId);
|
|
240159
240328
|
if (!p2 || p2.role !== "reviewer") return;
|
|
@@ -240271,7 +240440,7 @@ var WorkflowEngine = class {
|
|
|
240271
240440
|
if (!run2) return void 0;
|
|
240272
240441
|
if (TERMINAL_STATUSES.has(run2.status)) return run2;
|
|
240273
240442
|
const patch = reason ? { error: reason } : void 0;
|
|
240274
|
-
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);
|
|
240443
|
+
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);
|
|
240275
240444
|
if (!cancelled) {
|
|
240276
240445
|
const current = await this.storage.workflowRuns.getById(runId);
|
|
240277
240446
|
if (current?.status === "sending_feedback") {
|
|
@@ -250685,6 +250854,34 @@ async function routes20(fastify2) {
|
|
|
250685
250854
|
const proxyAuto = (info, method, apiPath, body) => proxyToRemoteAuto(info.remoteServerId, method, apiPath, body, {
|
|
250686
250855
|
reverseConnectManager: fastify2.reverseConnectManager
|
|
250687
250856
|
});
|
|
250857
|
+
const TWO_PHASE_REVIEW_CAPABILITIES = [
|
|
250858
|
+
"http:POST /api/path/workflow-runs/prepare",
|
|
250859
|
+
"http:POST /api/path/workflow-runs/:param/activate"
|
|
250860
|
+
];
|
|
250861
|
+
const activateRemoteReview = async (opts) => {
|
|
250862
|
+
const blind = opts.reviewContextMode === "blind";
|
|
250863
|
+
let intentBrief = opts.clientBrief;
|
|
250864
|
+
if (!opts.clientProvidedBrief && !blind) {
|
|
250865
|
+
intentBrief = await distillIntentBrief(opts.userId, opts.sourceSessionId);
|
|
250866
|
+
}
|
|
250867
|
+
const body = { intentBrief, reviewContextMode: opts.reviewContextMode };
|
|
250868
|
+
for (let attempt = 1; ; attempt++) {
|
|
250869
|
+
const result = await proxyAuto(
|
|
250870
|
+
opts,
|
|
250871
|
+
"POST",
|
|
250872
|
+
`/api/path/workflow-runs/${opts.bareRunId}/activate`,
|
|
250873
|
+
body
|
|
250874
|
+
);
|
|
250875
|
+
if (result.ok) return;
|
|
250876
|
+
if (result.status !== 0 || attempt >= 3) {
|
|
250877
|
+
console.warn(
|
|
250878
|
+
`[WorkflowRuns] remote activation failed for run ${opts.bareRunId}: status=${result.status} code=${result.errorCode ?? "n/a"}`
|
|
250879
|
+
);
|
|
250880
|
+
return;
|
|
250881
|
+
}
|
|
250882
|
+
await new Promise((resolve3) => setTimeout(resolve3, attempt * 5e3));
|
|
250883
|
+
}
|
|
250884
|
+
};
|
|
250688
250885
|
const sendProxyFailure = (reply, result) => reply.code(proxyStatus(result)).send(
|
|
250689
250886
|
result.status === 0 ? { error: `Remote proxy failed: ${result.errorCode || "unknown"}` } : result.data
|
|
250690
250887
|
);
|
|
@@ -250767,10 +250964,6 @@ async function routes20(fastify2) {
|
|
|
250767
250964
|
}
|
|
250768
250965
|
bareReviewerSessionId = reviewerInfo.remoteSessionId;
|
|
250769
250966
|
}
|
|
250770
|
-
let intentBrief2 = clientBrief;
|
|
250771
|
-
if (!clientProvidedBrief && !bareReviewerSessionId && !blind) {
|
|
250772
|
-
intentBrief2 = await distillIntentBrief(userId, sourceSessionId);
|
|
250773
|
-
}
|
|
250774
250967
|
if (reviewerSessionId && !await fastify2.remoteNotificationSync.prepareForNewTurn(reviewerSessionId)) {
|
|
250775
250968
|
return reply.code(502).send({
|
|
250776
250969
|
error: "Could not reach the remote server to prepare notification delivery",
|
|
@@ -250779,6 +250972,7 @@ async function routes20(fastify2) {
|
|
|
250779
250972
|
}
|
|
250780
250973
|
const reviewerActivityAt = Date.now();
|
|
250781
250974
|
let bareRun;
|
|
250975
|
+
let twoPhase = false;
|
|
250782
250976
|
if (bareReviewerSessionId) {
|
|
250783
250977
|
const result = await proxyAuto(remoteInfo, "POST", "/api/path/workflow-runs", {
|
|
250784
250978
|
sourceSessionId: remoteInfo.remoteSessionId,
|
|
@@ -250786,7 +250980,7 @@ async function routes20(fastify2) {
|
|
|
250786
250980
|
sourceTurnEndIndex,
|
|
250787
250981
|
reviewSpan,
|
|
250788
250982
|
reviewerSessionId: bareReviewerSessionId,
|
|
250789
|
-
intentBrief:
|
|
250983
|
+
intentBrief: clientBrief
|
|
250790
250984
|
});
|
|
250791
250985
|
if (!result.ok) return sendProxyFailure(reply, result);
|
|
250792
250986
|
bareRun = result.data.run;
|
|
@@ -250796,6 +250990,14 @@ async function routes20(fastify2) {
|
|
|
250796
250990
|
remoteInfo.remoteServerId
|
|
250797
250991
|
);
|
|
250798
250992
|
if (!remoteConfig) return reply.code(404).send({ error: "Remote project configuration not found" });
|
|
250993
|
+
const server = await fastify2.storage.remoteServers.getById(remoteInfo.remoteServerId);
|
|
250994
|
+
twoPhase = TWO_PHASE_REVIEW_CAPABILITIES.every(
|
|
250995
|
+
(capability) => server?.worker_capabilities?.includes(capability)
|
|
250996
|
+
);
|
|
250997
|
+
let intentBrief = clientBrief;
|
|
250998
|
+
if (!twoPhase && !clientProvidedBrief && !blind) {
|
|
250999
|
+
intentBrief = await distillIntentBrief(userId, sourceSessionId);
|
|
251000
|
+
}
|
|
250799
251001
|
const result = await createRemoteWorkflowReviewer({
|
|
250800
251002
|
remoteSessionMap: fastify2.remoteSessionMap,
|
|
250801
251003
|
remoteSessionMappings: fastify2.storage.remoteSessionMappings,
|
|
@@ -250817,8 +251019,9 @@ async function routes20(fastify2) {
|
|
|
250817
251019
|
// trailing "(review context: …)" line records what actually ran.
|
|
250818
251020
|
reviewContextMode,
|
|
250819
251021
|
reviewerAgentType: reviewerAgentType ?? "claude-code",
|
|
250820
|
-
intentBrief
|
|
250821
|
-
userId
|
|
251022
|
+
intentBrief,
|
|
251023
|
+
userId,
|
|
251024
|
+
...twoPhase ? { phase: "prepare" } : {}
|
|
250822
251025
|
});
|
|
250823
251026
|
if (!result.ok) return reply.code(proxyStatus(result)).send(result.data);
|
|
250824
251027
|
bareRun = result.remoteRun;
|
|
@@ -250889,18 +251092,34 @@ async function routes20(fastify2) {
|
|
|
250889
251092
|
sessionId: localRun.reviewer_session_id,
|
|
250890
251093
|
alive: true
|
|
250891
251094
|
});
|
|
250892
|
-
|
|
250893
|
-
|
|
250894
|
-
|
|
250895
|
-
|
|
250896
|
-
|
|
250897
|
-
|
|
250898
|
-
|
|
251095
|
+
if (!twoPhase) {
|
|
251096
|
+
fastify2.eventBus.emit({
|
|
251097
|
+
type: "session:status",
|
|
251098
|
+
projectId,
|
|
251099
|
+
branch: bareRun.branch,
|
|
251100
|
+
sessionId: localRun.reviewer_session_id,
|
|
251101
|
+
status: "running"
|
|
251102
|
+
});
|
|
251103
|
+
}
|
|
250899
251104
|
}
|
|
250900
251105
|
fastify2.agentSessionManager.markTitleResolved(localRun.reviewer_session_id);
|
|
250901
251106
|
await fastify2.storage.remoteSessionMappings.markTitleResolved(localRun.reviewer_session_id);
|
|
250902
251107
|
}
|
|
250903
251108
|
fastify2.eventBus.emit({ type: "workflow:run-updated", projectId, branch: bareRun.branch, run: localRun });
|
|
251109
|
+
if (twoPhase) {
|
|
251110
|
+
const bareRunId = bareRun.id;
|
|
251111
|
+
void activateRemoteReview({
|
|
251112
|
+
remoteServerId: remoteInfo.remoteServerId,
|
|
251113
|
+
bareRunId,
|
|
251114
|
+
sourceSessionId,
|
|
251115
|
+
userId,
|
|
251116
|
+
reviewContextMode,
|
|
251117
|
+
clientBrief,
|
|
251118
|
+
clientProvidedBrief
|
|
251119
|
+
}).catch((err) => {
|
|
251120
|
+
console.warn(`[WorkflowRuns] remote activation task failed for run ${bareRunId}:`, err);
|
|
251121
|
+
});
|
|
251122
|
+
}
|
|
250904
251123
|
return reply.code(201).send({ run: localRun });
|
|
250905
251124
|
}
|
|
250906
251125
|
const project = await fastify2.storage.projects.getById(projectId, userId);
|
|
@@ -250915,9 +251134,33 @@ async function routes20(fastify2) {
|
|
|
250915
251134
|
if (branch !== void 0 && (branch || null) !== runBranch) {
|
|
250916
251135
|
return reply.code(400).send({ error: "branch does not match source session" });
|
|
250917
251136
|
}
|
|
250918
|
-
|
|
250919
|
-
|
|
250920
|
-
|
|
251137
|
+
if (!reviewerSessionId) {
|
|
251138
|
+
let run2;
|
|
251139
|
+
try {
|
|
251140
|
+
run2 = await fastify2.workflowEngine.prepareAdhocReview({
|
|
251141
|
+
project: { id: project.id, path: project.path },
|
|
251142
|
+
branch: runBranch,
|
|
251143
|
+
sourceSessionId,
|
|
251144
|
+
reviewFocus,
|
|
251145
|
+
sourceTurnEndIndex,
|
|
251146
|
+
reviewSpan,
|
|
251147
|
+
reviewerAgentType
|
|
251148
|
+
});
|
|
251149
|
+
} catch (err) {
|
|
251150
|
+
const status = errStatus(err);
|
|
251151
|
+
if (status) return reply.code(status).send({ error: err.message });
|
|
251152
|
+
throw err;
|
|
251153
|
+
}
|
|
251154
|
+
void (async () => {
|
|
251155
|
+
let intentBrief = clientBrief;
|
|
251156
|
+
if (!clientProvidedBrief && !blind) {
|
|
251157
|
+
intentBrief = await distillIntentBrief(userId, sourceSessionId);
|
|
251158
|
+
}
|
|
251159
|
+
await fastify2.workflowEngine.activateAdhocReview(run2.id, { intentBrief, blind });
|
|
251160
|
+
})().catch((err) => {
|
|
251161
|
+
console.warn(`[WorkflowRuns] background activation failed for run ${run2.id}:`, err);
|
|
251162
|
+
});
|
|
251163
|
+
return reply.code(201).send({ run: run2 });
|
|
250921
251164
|
}
|
|
250922
251165
|
try {
|
|
250923
251166
|
const run2 = await fastify2.workflowEngine.startAdhocReview({
|
|
@@ -250927,9 +251170,8 @@ async function routes20(fastify2) {
|
|
|
250927
251170
|
reviewFocus,
|
|
250928
251171
|
sourceTurnEndIndex,
|
|
250929
251172
|
reviewSpan,
|
|
250930
|
-
reviewerAgentType,
|
|
250931
251173
|
reviewerSessionId,
|
|
250932
|
-
intentBrief,
|
|
251174
|
+
intentBrief: clientBrief,
|
|
250933
251175
|
blind
|
|
250934
251176
|
});
|
|
250935
251177
|
return reply.code(201).send({ run: run2 });
|
|
@@ -251040,19 +251282,27 @@ async function routes20(fastify2) {
|
|
|
251040
251282
|
};
|
|
251041
251283
|
const result = await proxyAuto(info, "GET", `/api/path/workflow-runs?${q}`);
|
|
251042
251284
|
if (!result.ok) return sendProxyFailure(reply, result);
|
|
251043
|
-
const
|
|
251285
|
+
const data = result.data;
|
|
251286
|
+
const bareRuns = data.runs ?? [];
|
|
251044
251287
|
const runs2 = bareRuns.map((r) => {
|
|
251045
251288
|
const mapped = mapRemoteRun(r, info.remoteServerId, projectId);
|
|
251046
251289
|
trackRemoteRun(mapped, { ...info, bareRunId: r.id, projectId });
|
|
251047
251290
|
return mapped;
|
|
251048
251291
|
});
|
|
251049
251292
|
logRead(runs2.length, `remote:${info.remoteServerId}`);
|
|
251050
|
-
|
|
251293
|
+
const prefix = `remote-${info.remoteServerId}-${projectId}-`;
|
|
251294
|
+
return reply.send({
|
|
251295
|
+
runs: runs2,
|
|
251296
|
+
...data.reviewedSessionIds ? { reviewedSessionIds: data.reviewedSessionIds.map((id) => prefix + id) } : {}
|
|
251297
|
+
});
|
|
251051
251298
|
}
|
|
251052
251299
|
}
|
|
251053
|
-
const runs = await
|
|
251300
|
+
const [runs, reviewedSessionIds] = await Promise.all([
|
|
251301
|
+
fastify2.storage.workflowRuns.getActive(projectId, branch ?? null),
|
|
251302
|
+
fastify2.storage.workflowRuns.listReviewedSourceSessions(projectId, branch ?? null)
|
|
251303
|
+
]);
|
|
251054
251304
|
logRead(runs.length, "local");
|
|
251055
|
-
return reply.send({ runs });
|
|
251305
|
+
return reply.send({ runs, reviewedSessionIds });
|
|
251056
251306
|
}
|
|
251057
251307
|
);
|
|
251058
251308
|
fastify2.get("/api/workflow-runs/:id", async (req, reply) => {
|
|
@@ -251215,6 +251465,76 @@ async function routes20(fastify2) {
|
|
|
251215
251465
|
throw err;
|
|
251216
251466
|
}
|
|
251217
251467
|
});
|
|
251468
|
+
fastify2.post("/api/path/workflow-runs/prepare", async (req, reply) => {
|
|
251469
|
+
const userId = requireAuth(req, reply);
|
|
251470
|
+
if (userId === null) return;
|
|
251471
|
+
const { sourceSessionId, reviewFocus, sourceTurnEndIndex } = req.body ?? {};
|
|
251472
|
+
if (!sourceSessionId) return reply.code(400).send({ error: "sourceSessionId is required" });
|
|
251473
|
+
const reviewSpan = parseReviewSpan(req.body?.reviewSpan);
|
|
251474
|
+
if (reviewSpan === null) return reply.code(400).send({ error: "reviewSpan must be one of: this_turn, session_start" });
|
|
251475
|
+
const reviewerAgentType = parseReviewerAgentType(req.body?.reviewerAgentType);
|
|
251476
|
+
if (reviewerAgentType === null) return reply.code(400).send({ error: "reviewerAgentType must be one of: claude-code, codex" });
|
|
251477
|
+
const runId = typeof req.body?.runId === "string" ? req.body.runId.trim() : "";
|
|
251478
|
+
const newReviewerSessionId = typeof req.body?.newReviewerSessionId === "string" ? req.body.newReviewerSessionId.trim() : "";
|
|
251479
|
+
if (!runId || !newReviewerSessionId) {
|
|
251480
|
+
return reply.code(400).send({ error: "runId and newReviewerSessionId are required" });
|
|
251481
|
+
}
|
|
251482
|
+
const sourceSession = await fastify2.storage.agentSessions.getById(sourceSessionId);
|
|
251483
|
+
if (!sourceSession) return reply.code(404).send({ error: "Session not found" });
|
|
251484
|
+
const sourceProjection = await projectLocalSession(sourceSession);
|
|
251485
|
+
if (!sourceProjection) return reply.code(409).send({ error: "Session workspace binding is unavailable" });
|
|
251486
|
+
const project = await fastify2.storage.projects.getById(sourceProjection.projectId);
|
|
251487
|
+
if (!project) return reply.code(404).send({ error: "Session not found" });
|
|
251488
|
+
if (!project.path) return reply.code(400).send({ error: "Project has no local path" });
|
|
251489
|
+
const projectPath = project.path;
|
|
251490
|
+
try {
|
|
251491
|
+
let flight = durableReviewFlights.get(runId);
|
|
251492
|
+
if (!flight) {
|
|
251493
|
+
flight = fastify2.workflowEngine.prepareAdhocReview({
|
|
251494
|
+
project: { id: project.id, path: projectPath },
|
|
251495
|
+
branch: sourceProjection.branch,
|
|
251496
|
+
sourceSessionId,
|
|
251497
|
+
reviewFocus,
|
|
251498
|
+
sourceTurnEndIndex,
|
|
251499
|
+
reviewSpan,
|
|
251500
|
+
reviewerAgentType,
|
|
251501
|
+
runId,
|
|
251502
|
+
newReviewerSessionId
|
|
251503
|
+
});
|
|
251504
|
+
durableReviewFlights.set(runId, flight);
|
|
251505
|
+
const clear = () => {
|
|
251506
|
+
if (durableReviewFlights.get(runId) === flight) durableReviewFlights.delete(runId);
|
|
251507
|
+
};
|
|
251508
|
+
void flight.then(clear, clear);
|
|
251509
|
+
}
|
|
251510
|
+
const run2 = await flight;
|
|
251511
|
+
return reply.code(201).send({ run: run2 });
|
|
251512
|
+
} catch (err) {
|
|
251513
|
+
const status = errStatus(err);
|
|
251514
|
+
if (status) return reply.code(status).send({ error: err.message });
|
|
251515
|
+
throw err;
|
|
251516
|
+
}
|
|
251517
|
+
});
|
|
251518
|
+
fastify2.post("/api/path/workflow-runs/:id/activate", async (req, reply) => {
|
|
251519
|
+
const userId = requireAuth(req, reply);
|
|
251520
|
+
if (userId === null) return;
|
|
251521
|
+
const reviewContextMode = parseReviewContextMode(req.body?.reviewContextMode);
|
|
251522
|
+
if (reviewContextMode === null) return reply.code(400).send({ error: "reviewContextMode must be one of: briefed, blind" });
|
|
251523
|
+
const blind = reviewContextMode === "blind";
|
|
251524
|
+
const intentBriefRaw = req.body?.intentBrief;
|
|
251525
|
+
if (intentBriefRaw !== void 0 && typeof intentBriefRaw !== "string") {
|
|
251526
|
+
return reply.code(400).send({ error: "intentBrief must be a string" });
|
|
251527
|
+
}
|
|
251528
|
+
const intentBrief = blind ? void 0 : normalizeIntentBrief(intentBriefRaw);
|
|
251529
|
+
try {
|
|
251530
|
+
const run2 = await fastify2.workflowEngine.activateAdhocReview(req.params.id, { intentBrief, blind });
|
|
251531
|
+
return reply.send({ run: run2 });
|
|
251532
|
+
} catch (err) {
|
|
251533
|
+
const status = errStatus(err);
|
|
251534
|
+
if (status) return reply.code(status).send({ error: err.message });
|
|
251535
|
+
throw err;
|
|
251536
|
+
}
|
|
251537
|
+
});
|
|
251218
251538
|
fastify2.get("/api/path/workflow-runs/reviewer-candidate", async (req, reply) => {
|
|
251219
251539
|
const userId = requireAuth(req, reply);
|
|
251220
251540
|
if (userId === null) return;
|
|
@@ -251234,9 +251554,12 @@ async function routes20(fastify2) {
|
|
|
251234
251554
|
const { path: projectPath, branch } = req.query;
|
|
251235
251555
|
if (!projectPath) return reply.code(400).send({ error: "path is required" });
|
|
251236
251556
|
const project = await fastify2.storage.projects.getByPath(projectPath) ?? await fastify2.storage.projects.getById(`path:${projectPath}`);
|
|
251237
|
-
if (!project) return reply.send({ runs: [] });
|
|
251238
|
-
const runs = await
|
|
251239
|
-
|
|
251557
|
+
if (!project) return reply.send({ runs: [], reviewedSessionIds: [] });
|
|
251558
|
+
const [runs, reviewedSessionIds] = await Promise.all([
|
|
251559
|
+
fastify2.storage.workflowRuns.getActive(project.id, branch || null),
|
|
251560
|
+
fastify2.storage.workflowRuns.listReviewedSourceSessions(project.id, branch || null)
|
|
251561
|
+
]);
|
|
251562
|
+
return reply.send({ runs, reviewedSessionIds });
|
|
251240
251563
|
});
|
|
251241
251564
|
}
|
|
251242
251565
|
var workflow_run_routes_default = (0, import_fastify_plugin21.default)(routes20, { name: "workflow-run-routes" });
|
|
@@ -252137,6 +252460,16 @@ var routes23 = async (fastify2) => {
|
|
|
252137
252460
|
const stopHeartbeat = attachWsHeartbeat(socket, { label: "ExecutorMux" });
|
|
252138
252461
|
const subs = /* @__PURE__ */ new Map();
|
|
252139
252462
|
const handleInputMap = /* @__PURE__ */ new Map();
|
|
252463
|
+
const wanted = /* @__PURE__ */ new Set();
|
|
252464
|
+
const dropSubscription = (processId) => {
|
|
252465
|
+
const sub = subs.get(processId);
|
|
252466
|
+
if (sub) {
|
|
252467
|
+
sub.detach();
|
|
252468
|
+
sub.cleanup();
|
|
252469
|
+
subs.delete(processId);
|
|
252470
|
+
}
|
|
252471
|
+
handleInputMap.delete(processId);
|
|
252472
|
+
};
|
|
252140
252473
|
const subscribeProcess = async (processId) => {
|
|
252141
252474
|
if (subs.has(processId)) return;
|
|
252142
252475
|
const ownerUserId = processOwnerScope(principal);
|
|
@@ -252149,25 +252482,31 @@ var routes23 = async (fastify2) => {
|
|
|
252149
252482
|
return;
|
|
252150
252483
|
}
|
|
252151
252484
|
if (subs.has(processId)) return;
|
|
252485
|
+
if (!wanted.has(processId)) return;
|
|
252486
|
+
let detached = false;
|
|
252152
252487
|
const send = (msg) => {
|
|
252488
|
+
if (detached) return;
|
|
252153
252489
|
try {
|
|
252154
252490
|
socket.send(JSON.stringify({ processId, ...msg }));
|
|
252155
252491
|
} catch {
|
|
252156
252492
|
}
|
|
252157
252493
|
};
|
|
252158
252494
|
let terminated = false;
|
|
252495
|
+
let self2 = null;
|
|
252159
252496
|
const onTerminal = () => {
|
|
252160
252497
|
terminated = true;
|
|
252161
|
-
|
|
252162
|
-
if (c) {
|
|
252163
|
-
c();
|
|
252498
|
+
if (self2 && subs.get(processId) === self2) {
|
|
252164
252499
|
subs.delete(processId);
|
|
252500
|
+
handleInputMap.delete(processId);
|
|
252165
252501
|
}
|
|
252166
|
-
|
|
252502
|
+
self2?.cleanup();
|
|
252167
252503
|
};
|
|
252168
252504
|
const handle = processId.startsWith("remote-") ? attachRemoteProcessStream(fastify2, processId, send, onTerminal) : attachLocalProcessStream(fastify2, processId, send, onTerminal);
|
|
252169
252505
|
if (!terminated) {
|
|
252170
|
-
|
|
252506
|
+
self2 = { cleanup: handle.cleanup, detach: () => {
|
|
252507
|
+
detached = true;
|
|
252508
|
+
} };
|
|
252509
|
+
subs.set(processId, self2);
|
|
252171
252510
|
handleInputMap.set(processId, handle.handleInput);
|
|
252172
252511
|
}
|
|
252173
252512
|
};
|
|
@@ -252175,13 +252514,13 @@ var routes23 = async (fastify2) => {
|
|
|
252175
252514
|
try {
|
|
252176
252515
|
const msg = JSON.parse(data.toString());
|
|
252177
252516
|
if (msg.type === "subscribe") {
|
|
252517
|
+
wanted.add(msg.processId);
|
|
252178
252518
|
subscribeProcess(msg.processId).catch((err) => {
|
|
252179
252519
|
console.error(`[ExecutorMux] Failed to subscribe to ${msg.processId}:`, err);
|
|
252180
252520
|
});
|
|
252181
252521
|
} else if (msg.type === "unsubscribe") {
|
|
252182
|
-
|
|
252183
|
-
|
|
252184
|
-
handleInputMap.delete(msg.processId);
|
|
252522
|
+
wanted.delete(msg.processId);
|
|
252523
|
+
dropSubscription(msg.processId);
|
|
252185
252524
|
} else if (msg.type === "input") {
|
|
252186
252525
|
handleInputMap.get(msg.processId)?.({ type: "input", data: msg.data });
|
|
252187
252526
|
} else if (msg.type === "resize") {
|
|
@@ -252197,7 +252536,11 @@ var routes23 = async (fastify2) => {
|
|
|
252197
252536
|
socket.on("close", () => {
|
|
252198
252537
|
console.log(`[ExecutorMux] Client disconnected; cleaning ${subs.size} subscriptions`);
|
|
252199
252538
|
stopHeartbeat();
|
|
252200
|
-
|
|
252539
|
+
wanted.clear();
|
|
252540
|
+
for (const sub of subs.values()) {
|
|
252541
|
+
sub.detach();
|
|
252542
|
+
sub.cleanup();
|
|
252543
|
+
}
|
|
252201
252544
|
subs.clear();
|
|
252202
252545
|
handleInputMap.clear();
|
|
252203
252546
|
});
|
|
@@ -260900,6 +261243,12 @@ var WORKER_CAPABILITIES = {
|
|
|
260900
261243
|
// --- Workflow runs ---
|
|
260901
261244
|
// Empirically bisected via cross-version e2e: 0.2.4 → 404, 0.2.5 → serves.
|
|
260902
261245
|
"http:POST /api/path/workflow-runs": { since: "0.2.5", summary: "\u521B\u5EFA workflow run" },
|
|
261246
|
+
// Both additive, checked as a pair (TWO_PHASE_REVIEW_CAPABILITIES in
|
|
261247
|
+
// workflow-run-routes.ts): a worker missing either gets the original
|
|
261248
|
+
// single-shot create — the hub distills inline and the submit blocks on it,
|
|
261249
|
+
// exactly the pre-two-phase behavior.
|
|
261250
|
+
"http:POST /api/path/workflow-runs/prepare": { since: "0.3.30", summary: "\u4E24\u6BB5\u5F0F review:\u51C6\u5907(\u5360\u4F4D run + reviewer session)" },
|
|
261251
|
+
"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)" },
|
|
260903
261252
|
"http:GET /api/path/workflow-runs": { since: "0.2.5", summary: "workflow run \u5217\u8868" },
|
|
260904
261253
|
"http:GET /api/path/workflow-runs/reviewer-candidate": { since: "0.2.5", summary: "reviewer \u5019\u9009\u67E5\u8BE2" },
|
|
260905
261254
|
"http:GET /api/workflow-runs/:param": { since: "0.2.5", summary: "\u8BFB workflow run" },
|