@vibedeckx/linux-x64 0.3.29 → 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 +386 -58
- 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);
|
|
@@ -233410,22 +233411,32 @@ async function createRemoteWorkflowReviewer(deps, params) {
|
|
|
233410
233411
|
});
|
|
233411
233412
|
let registeredLocalSessionId = null;
|
|
233412
233413
|
try {
|
|
233413
|
-
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(
|
|
233414
233431
|
params.agentMode,
|
|
233415
233432
|
"POST",
|
|
233416
233433
|
"/api/path/workflow-runs",
|
|
233417
233434
|
{
|
|
233418
|
-
|
|
233419
|
-
reviewFocus: params.reviewFocus,
|
|
233420
|
-
sourceTurnEndIndex: params.sourceTurnEndIndex,
|
|
233421
|
-
reviewSpan: params.reviewSpan,
|
|
233435
|
+
...sharedBody,
|
|
233422
233436
|
reviewContextMode: params.reviewContextMode,
|
|
233423
|
-
|
|
233424
|
-
intentBrief: params.intentBrief,
|
|
233425
|
-
runId: remoteRunId,
|
|
233426
|
-
newReviewerSessionId: remoteReviewerSessionId
|
|
233437
|
+
intentBrief: params.intentBrief
|
|
233427
233438
|
},
|
|
233428
|
-
|
|
233439
|
+
proxyOpts
|
|
233429
233440
|
);
|
|
233430
233441
|
if (!result.ok) {
|
|
233431
233442
|
const uncertain = result.errorCode === "network_error" || result.errorCode === "timeout";
|
|
@@ -239802,6 +239813,7 @@ var FINAL_VERDICT_PROMPT = [
|
|
|
239802
239813
|
...VERDICT_INSTRUCTIONS
|
|
239803
239814
|
].join("\n");
|
|
239804
239815
|
var REVIEWER_AGENT_TYPES = /* @__PURE__ */ new Set(["claude-code", "codex"]);
|
|
239816
|
+
var PREPARE_TIMEOUT_MS = 10 * 6e4;
|
|
239805
239817
|
var WorkflowEngine = class {
|
|
239806
239818
|
constructor(storage2, agentOps) {
|
|
239807
239819
|
this.storage = storage2;
|
|
@@ -239812,6 +239824,12 @@ var WorkflowEngine = class {
|
|
|
239812
239824
|
eventBus;
|
|
239813
239825
|
/** sessionId → participation in an active run (rebuilt on boot). */
|
|
239814
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();
|
|
239815
239833
|
setEventBus(bus) {
|
|
239816
239834
|
this.eventBus = bus;
|
|
239817
239835
|
bus.subscribe((event) => {
|
|
@@ -239836,6 +239854,12 @@ var WorkflowEngine = class {
|
|
|
239836
239854
|
await this.storage.workflowRuns.update(run2.id, {
|
|
239837
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"
|
|
239838
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);
|
|
239839
239863
|
}
|
|
239840
239864
|
this.trackParticipants(run2);
|
|
239841
239865
|
}
|
|
@@ -239850,6 +239874,37 @@ var WorkflowEngine = class {
|
|
|
239850
239874
|
for (const [sid, p2] of this.participants) {
|
|
239851
239875
|
if (p2.runId === run2.id) this.participants.delete(sid);
|
|
239852
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);
|
|
239853
239908
|
}
|
|
239854
239909
|
releaseReservations(runId) {
|
|
239855
239910
|
for (const [sid, participant] of this.participants) {
|
|
@@ -239890,7 +239945,10 @@ var WorkflowEngine = class {
|
|
|
239890
239945
|
);
|
|
239891
239946
|
if (!ok) return;
|
|
239892
239947
|
const failed = await this.storage.workflowRuns.getById(run2.id);
|
|
239893
|
-
if (failed)
|
|
239948
|
+
if (failed) {
|
|
239949
|
+
this.untrackRun(failed);
|
|
239950
|
+
this.emitRunUpdated(failed);
|
|
239951
|
+
}
|
|
239894
239952
|
this.onMilestoneCreated?.();
|
|
239895
239953
|
}
|
|
239896
239954
|
/** Test seam for the failure path (see failRun). */
|
|
@@ -239959,7 +240017,29 @@ var WorkflowEngine = class {
|
|
|
239959
240017
|
reason: null
|
|
239960
240018
|
};
|
|
239961
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
|
+
*/
|
|
239962
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) {
|
|
239963
240043
|
if (opts.reviewerSessionId === opts.sourceSessionId) {
|
|
239964
240044
|
throw new WorkflowError("reviewer-unavailable", "reviewer session \u4E0D\u80FD\u4E0E source session \u76F8\u540C");
|
|
239965
240045
|
}
|
|
@@ -239982,7 +240062,7 @@ var WorkflowEngine = class {
|
|
|
239982
240062
|
}
|
|
239983
240063
|
return existingRun;
|
|
239984
240064
|
}
|
|
239985
|
-
if (existingRun.status !== "waiting_reviewer") {
|
|
240065
|
+
if (existingRun.status !== "waiting_reviewer" && existingRun.status !== "preparing") {
|
|
239986
240066
|
throw new WorkflowError("bad-state", "workflow run \u5DF2\u7EC8\u6B62\uFF0C\u4E0D\u80FD\u4F5C\u4E3A\u672A\u77E5\u521B\u5EFA\u7ED3\u679C\u91CD\u653E");
|
|
239987
240067
|
}
|
|
239988
240068
|
}
|
|
@@ -240060,9 +240140,17 @@ var WorkflowEngine = class {
|
|
|
240060
240140
|
review_focus: opts.reviewFocus ?? null,
|
|
240061
240141
|
review_target: JSON.stringify(target),
|
|
240062
240142
|
reviewer_session_id: opts.reviewerSessionId ?? null,
|
|
240063
|
-
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"
|
|
240064
240147
|
});
|
|
240065
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
|
+
}
|
|
240066
240154
|
if (opts.reviewerSessionId && reviewerSession) {
|
|
240067
240155
|
if (reviewerSession.permission_mode !== "plan") {
|
|
240068
240156
|
let switched = false;
|
|
@@ -240113,7 +240201,13 @@ var WorkflowEngine = class {
|
|
|
240113
240201
|
false,
|
|
240114
240202
|
"plan",
|
|
240115
240203
|
opts.reviewerAgentType ?? "claude-code",
|
|
240116
|
-
|
|
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,
|
|
240117
240211
|
false,
|
|
240118
240212
|
{
|
|
240119
240213
|
startSnapshot: endSnap,
|
|
@@ -240125,27 +240219,25 @@ var WorkflowEngine = class {
|
|
|
240125
240219
|
reviewerId,
|
|
240126
240220
|
`Review - ${sourceSession?.title || (taskContext ? snippetTitle(taskContext) : null) || "Conversation"}`
|
|
240127
240221
|
).catch((err) => console.warn(`[WorkflowEngine] failed to set reviewer title for ${reviewerId}:`, err));
|
|
240128
|
-
|
|
240222
|
+
this.pendingActivations.set(run2.id, {
|
|
240223
|
+
scope,
|
|
240129
240224
|
taskContext,
|
|
240130
240225
|
originalIntent: extractFirstUserMessage(entries),
|
|
240131
|
-
authorSelfReport: extractAuthorSelfReport(entries, turnEndIndex)
|
|
240132
|
-
|
|
240133
|
-
|
|
240134
|
-
|
|
240135
|
-
|
|
240136
|
-
|
|
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) }
|
|
240137
240235
|
});
|
|
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
240236
|
this.trackParticipants(updated);
|
|
240237
|
+
this.armPrepareTimeout(run2.id, PREPARE_TIMEOUT_MS);
|
|
240145
240238
|
this.emitRunUpdated(updated);
|
|
240146
240239
|
return updated;
|
|
240147
240240
|
} catch (err) {
|
|
240148
|
-
if (err instanceof WorkflowError && err.code === "spawn-failed") throw err;
|
|
240149
240241
|
await this.failRun(run2, `\u521B\u5EFA reviewer \u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}`);
|
|
240150
240242
|
throw new WorkflowError("spawn-failed", "\u521B\u5EFA reviewer session \u5931\u8D25");
|
|
240151
240243
|
}
|
|
@@ -240154,6 +240246,73 @@ var WorkflowEngine = class {
|
|
|
240154
240246
|
throw err;
|
|
240155
240247
|
}
|
|
240156
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
|
+
}
|
|
240157
240316
|
async handleTaskCompleted(event) {
|
|
240158
240317
|
const p2 = this.participants.get(event.sessionId);
|
|
240159
240318
|
if (!p2 || p2.role !== "reviewer") return;
|
|
@@ -240271,7 +240430,7 @@ var WorkflowEngine = class {
|
|
|
240271
240430
|
if (!run2) return void 0;
|
|
240272
240431
|
if (TERMINAL_STATUSES.has(run2.status)) return run2;
|
|
240273
240432
|
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);
|
|
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);
|
|
240275
240434
|
if (!cancelled) {
|
|
240276
240435
|
const current = await this.storage.workflowRuns.getById(runId);
|
|
240277
240436
|
if (current?.status === "sending_feedback") {
|
|
@@ -250685,6 +250844,34 @@ async function routes20(fastify2) {
|
|
|
250685
250844
|
const proxyAuto = (info, method, apiPath, body) => proxyToRemoteAuto(info.remoteServerId, method, apiPath, body, {
|
|
250686
250845
|
reverseConnectManager: fastify2.reverseConnectManager
|
|
250687
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
|
+
};
|
|
250688
250875
|
const sendProxyFailure = (reply, result) => reply.code(proxyStatus(result)).send(
|
|
250689
250876
|
result.status === 0 ? { error: `Remote proxy failed: ${result.errorCode || "unknown"}` } : result.data
|
|
250690
250877
|
);
|
|
@@ -250767,10 +250954,6 @@ async function routes20(fastify2) {
|
|
|
250767
250954
|
}
|
|
250768
250955
|
bareReviewerSessionId = reviewerInfo.remoteSessionId;
|
|
250769
250956
|
}
|
|
250770
|
-
let intentBrief2 = clientBrief;
|
|
250771
|
-
if (!clientProvidedBrief && !bareReviewerSessionId && !blind) {
|
|
250772
|
-
intentBrief2 = await distillIntentBrief(userId, sourceSessionId);
|
|
250773
|
-
}
|
|
250774
250957
|
if (reviewerSessionId && !await fastify2.remoteNotificationSync.prepareForNewTurn(reviewerSessionId)) {
|
|
250775
250958
|
return reply.code(502).send({
|
|
250776
250959
|
error: "Could not reach the remote server to prepare notification delivery",
|
|
@@ -250779,6 +250962,7 @@ async function routes20(fastify2) {
|
|
|
250779
250962
|
}
|
|
250780
250963
|
const reviewerActivityAt = Date.now();
|
|
250781
250964
|
let bareRun;
|
|
250965
|
+
let twoPhase = false;
|
|
250782
250966
|
if (bareReviewerSessionId) {
|
|
250783
250967
|
const result = await proxyAuto(remoteInfo, "POST", "/api/path/workflow-runs", {
|
|
250784
250968
|
sourceSessionId: remoteInfo.remoteSessionId,
|
|
@@ -250786,7 +250970,7 @@ async function routes20(fastify2) {
|
|
|
250786
250970
|
sourceTurnEndIndex,
|
|
250787
250971
|
reviewSpan,
|
|
250788
250972
|
reviewerSessionId: bareReviewerSessionId,
|
|
250789
|
-
intentBrief:
|
|
250973
|
+
intentBrief: clientBrief
|
|
250790
250974
|
});
|
|
250791
250975
|
if (!result.ok) return sendProxyFailure(reply, result);
|
|
250792
250976
|
bareRun = result.data.run;
|
|
@@ -250796,6 +250980,14 @@ async function routes20(fastify2) {
|
|
|
250796
250980
|
remoteInfo.remoteServerId
|
|
250797
250981
|
);
|
|
250798
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
|
+
}
|
|
250799
250991
|
const result = await createRemoteWorkflowReviewer({
|
|
250800
250992
|
remoteSessionMap: fastify2.remoteSessionMap,
|
|
250801
250993
|
remoteSessionMappings: fastify2.storage.remoteSessionMappings,
|
|
@@ -250817,8 +251009,9 @@ async function routes20(fastify2) {
|
|
|
250817
251009
|
// trailing "(review context: …)" line records what actually ran.
|
|
250818
251010
|
reviewContextMode,
|
|
250819
251011
|
reviewerAgentType: reviewerAgentType ?? "claude-code",
|
|
250820
|
-
intentBrief
|
|
250821
|
-
userId
|
|
251012
|
+
intentBrief,
|
|
251013
|
+
userId,
|
|
251014
|
+
...twoPhase ? { phase: "prepare" } : {}
|
|
250822
251015
|
});
|
|
250823
251016
|
if (!result.ok) return reply.code(proxyStatus(result)).send(result.data);
|
|
250824
251017
|
bareRun = result.remoteRun;
|
|
@@ -250889,18 +251082,34 @@ async function routes20(fastify2) {
|
|
|
250889
251082
|
sessionId: localRun.reviewer_session_id,
|
|
250890
251083
|
alive: true
|
|
250891
251084
|
});
|
|
250892
|
-
|
|
250893
|
-
|
|
250894
|
-
|
|
250895
|
-
|
|
250896
|
-
|
|
250897
|
-
|
|
250898
|
-
|
|
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
|
+
}
|
|
250899
251094
|
}
|
|
250900
251095
|
fastify2.agentSessionManager.markTitleResolved(localRun.reviewer_session_id);
|
|
250901
251096
|
await fastify2.storage.remoteSessionMappings.markTitleResolved(localRun.reviewer_session_id);
|
|
250902
251097
|
}
|
|
250903
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
|
+
}
|
|
250904
251113
|
return reply.code(201).send({ run: localRun });
|
|
250905
251114
|
}
|
|
250906
251115
|
const project = await fastify2.storage.projects.getById(projectId, userId);
|
|
@@ -250915,9 +251124,33 @@ async function routes20(fastify2) {
|
|
|
250915
251124
|
if (branch !== void 0 && (branch || null) !== runBranch) {
|
|
250916
251125
|
return reply.code(400).send({ error: "branch does not match source session" });
|
|
250917
251126
|
}
|
|
250918
|
-
|
|
250919
|
-
|
|
250920
|
-
|
|
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 });
|
|
250921
251154
|
}
|
|
250922
251155
|
try {
|
|
250923
251156
|
const run2 = await fastify2.workflowEngine.startAdhocReview({
|
|
@@ -250927,9 +251160,8 @@ async function routes20(fastify2) {
|
|
|
250927
251160
|
reviewFocus,
|
|
250928
251161
|
sourceTurnEndIndex,
|
|
250929
251162
|
reviewSpan,
|
|
250930
|
-
reviewerAgentType,
|
|
250931
251163
|
reviewerSessionId,
|
|
250932
|
-
intentBrief,
|
|
251164
|
+
intentBrief: clientBrief,
|
|
250933
251165
|
blind
|
|
250934
251166
|
});
|
|
250935
251167
|
return reply.code(201).send({ run: run2 });
|
|
@@ -251215,6 +251447,76 @@ async function routes20(fastify2) {
|
|
|
251215
251447
|
throw err;
|
|
251216
251448
|
}
|
|
251217
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
|
+
});
|
|
251218
251520
|
fastify2.get("/api/path/workflow-runs/reviewer-candidate", async (req, reply) => {
|
|
251219
251521
|
const userId = requireAuth(req, reply);
|
|
251220
251522
|
if (userId === null) return;
|
|
@@ -252137,6 +252439,16 @@ var routes23 = async (fastify2) => {
|
|
|
252137
252439
|
const stopHeartbeat = attachWsHeartbeat(socket, { label: "ExecutorMux" });
|
|
252138
252440
|
const subs = /* @__PURE__ */ new Map();
|
|
252139
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
|
+
};
|
|
252140
252452
|
const subscribeProcess = async (processId) => {
|
|
252141
252453
|
if (subs.has(processId)) return;
|
|
252142
252454
|
const ownerUserId = processOwnerScope(principal);
|
|
@@ -252149,25 +252461,31 @@ var routes23 = async (fastify2) => {
|
|
|
252149
252461
|
return;
|
|
252150
252462
|
}
|
|
252151
252463
|
if (subs.has(processId)) return;
|
|
252464
|
+
if (!wanted.has(processId)) return;
|
|
252465
|
+
let detached = false;
|
|
252152
252466
|
const send = (msg) => {
|
|
252467
|
+
if (detached) return;
|
|
252153
252468
|
try {
|
|
252154
252469
|
socket.send(JSON.stringify({ processId, ...msg }));
|
|
252155
252470
|
} catch {
|
|
252156
252471
|
}
|
|
252157
252472
|
};
|
|
252158
252473
|
let terminated = false;
|
|
252474
|
+
let self2 = null;
|
|
252159
252475
|
const onTerminal = () => {
|
|
252160
252476
|
terminated = true;
|
|
252161
|
-
|
|
252162
|
-
if (c) {
|
|
252163
|
-
c();
|
|
252477
|
+
if (self2 && subs.get(processId) === self2) {
|
|
252164
252478
|
subs.delete(processId);
|
|
252479
|
+
handleInputMap.delete(processId);
|
|
252165
252480
|
}
|
|
252166
|
-
|
|
252481
|
+
self2?.cleanup();
|
|
252167
252482
|
};
|
|
252168
252483
|
const handle = processId.startsWith("remote-") ? attachRemoteProcessStream(fastify2, processId, send, onTerminal) : attachLocalProcessStream(fastify2, processId, send, onTerminal);
|
|
252169
252484
|
if (!terminated) {
|
|
252170
|
-
|
|
252485
|
+
self2 = { cleanup: handle.cleanup, detach: () => {
|
|
252486
|
+
detached = true;
|
|
252487
|
+
} };
|
|
252488
|
+
subs.set(processId, self2);
|
|
252171
252489
|
handleInputMap.set(processId, handle.handleInput);
|
|
252172
252490
|
}
|
|
252173
252491
|
};
|
|
@@ -252175,13 +252493,13 @@ var routes23 = async (fastify2) => {
|
|
|
252175
252493
|
try {
|
|
252176
252494
|
const msg = JSON.parse(data.toString());
|
|
252177
252495
|
if (msg.type === "subscribe") {
|
|
252496
|
+
wanted.add(msg.processId);
|
|
252178
252497
|
subscribeProcess(msg.processId).catch((err) => {
|
|
252179
252498
|
console.error(`[ExecutorMux] Failed to subscribe to ${msg.processId}:`, err);
|
|
252180
252499
|
});
|
|
252181
252500
|
} else if (msg.type === "unsubscribe") {
|
|
252182
|
-
|
|
252183
|
-
|
|
252184
|
-
handleInputMap.delete(msg.processId);
|
|
252501
|
+
wanted.delete(msg.processId);
|
|
252502
|
+
dropSubscription(msg.processId);
|
|
252185
252503
|
} else if (msg.type === "input") {
|
|
252186
252504
|
handleInputMap.get(msg.processId)?.({ type: "input", data: msg.data });
|
|
252187
252505
|
} else if (msg.type === "resize") {
|
|
@@ -252197,7 +252515,11 @@ var routes23 = async (fastify2) => {
|
|
|
252197
252515
|
socket.on("close", () => {
|
|
252198
252516
|
console.log(`[ExecutorMux] Client disconnected; cleaning ${subs.size} subscriptions`);
|
|
252199
252517
|
stopHeartbeat();
|
|
252200
|
-
|
|
252518
|
+
wanted.clear();
|
|
252519
|
+
for (const sub of subs.values()) {
|
|
252520
|
+
sub.detach();
|
|
252521
|
+
sub.cleanup();
|
|
252522
|
+
}
|
|
252201
252523
|
subs.clear();
|
|
252202
252524
|
handleInputMap.clear();
|
|
252203
252525
|
});
|
|
@@ -260900,6 +261222,12 @@ var WORKER_CAPABILITIES = {
|
|
|
260900
261222
|
// --- Workflow runs ---
|
|
260901
261223
|
// Empirically bisected via cross-version e2e: 0.2.4 → 404, 0.2.5 → serves.
|
|
260902
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)" },
|
|
260903
261231
|
"http:GET /api/path/workflow-runs": { since: "0.2.5", summary: "workflow run \u5217\u8868" },
|
|
260904
261232
|
"http:GET /api/path/workflow-runs/reviewer-candidate": { since: "0.2.5", summary: "reviewer \u5019\u9009\u67E5\u8BE2" },
|
|
260905
261233
|
"http:GET /api/workflow-runs/:param": { since: "0.2.5", summary: "\u8BFB workflow run" },
|