agent-relay 3.1.16 → 3.1.18
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/bin/agent-relay-broker-darwin-arm64 +0 -0
- package/bin/agent-relay-broker-darwin-x64 +0 -0
- package/bin/agent-relay-broker-linux-arm64 +0 -0
- package/bin/agent-relay-broker-linux-x64 +0 -0
- package/dist/index.cjs +573 -32
- package/package.json +8 -8
- package/packages/acp-bridge/package.json +2 -2
- package/packages/config/package.json +1 -1
- package/packages/hooks/package.json +4 -4
- package/packages/memory/package.json +2 -2
- package/packages/openclaw/package.json +2 -2
- package/packages/policy/package.json +2 -2
- package/packages/sdk/dist/__tests__/e2e-owner-review.test.d.ts +16 -0
- package/packages/sdk/dist/__tests__/e2e-owner-review.test.d.ts.map +1 -0
- package/packages/sdk/dist/__tests__/e2e-owner-review.test.js +640 -0
- package/packages/sdk/dist/__tests__/e2e-owner-review.test.js.map +1 -0
- package/packages/sdk/dist/client.d.ts +2 -0
- package/packages/sdk/dist/client.d.ts.map +1 -1
- package/packages/sdk/dist/client.js +2 -0
- package/packages/sdk/dist/client.js.map +1 -1
- package/packages/sdk/dist/protocol.d.ts +4 -0
- package/packages/sdk/dist/protocol.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/cli.js +10 -0
- package/packages/sdk/dist/workflows/cli.js.map +1 -1
- package/packages/sdk/dist/workflows/runner.d.ts +31 -0
- package/packages/sdk/dist/workflows/runner.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/runner.js +542 -31
- package/packages/sdk/dist/workflows/runner.js.map +1 -1
- package/packages/sdk/dist/workflows/trajectory.d.ts +22 -1
- package/packages/sdk/dist/workflows/trajectory.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/trajectory.js +55 -8
- package/packages/sdk/dist/workflows/trajectory.js.map +1 -1
- package/packages/sdk/dist/workflows/validator.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/validator.js +29 -0
- package/packages/sdk/dist/workflows/validator.js.map +1 -1
- package/packages/sdk/package.json +2 -2
- package/packages/sdk/src/__tests__/e2e-owner-review.test.ts +778 -0
- package/packages/sdk/src/__tests__/workflow-runner.test.ts +484 -9
- package/packages/sdk/src/client.ts +4 -0
- package/packages/sdk/src/protocol.ts +4 -0
- package/packages/sdk/src/workflows/README.md +11 -0
- package/packages/sdk/src/workflows/cli.ts +10 -0
- package/packages/sdk/src/workflows/runner.ts +714 -33
- package/packages/sdk/src/workflows/trajectory.ts +89 -8
- package/packages/sdk/src/workflows/validator.ts +29 -0
- package/packages/sdk-py/pyproject.toml +1 -1
- package/packages/telemetry/package.json +1 -1
- package/packages/trajectory/package.json +2 -2
- package/packages/user-directory/package.json +2 -2
- package/packages/utils/package.json +2 -2
- package/relay-snippets/agent-relay-snippet.md +12 -0
package/dist/index.cjs
CHANGED
|
@@ -9189,6 +9189,8 @@ var AgentRelayClient = class _AgentRelayClient {
|
|
|
9189
9189
|
text: input.text,
|
|
9190
9190
|
from: input.from,
|
|
9191
9191
|
thread_id: input.threadId,
|
|
9192
|
+
workspace_id: input.workspaceId,
|
|
9193
|
+
workspace_alias: input.workspaceAlias,
|
|
9192
9194
|
priority: input.priority,
|
|
9193
9195
|
data: input.data
|
|
9194
9196
|
});
|
|
@@ -47272,18 +47274,66 @@ var WorkflowTrajectory = class {
|
|
|
47272
47274
|
}
|
|
47273
47275
|
// ── Step events ────────────────────────────────────────────────────────────
|
|
47274
47276
|
/** Record step started — captures intent, not just assignment. */
|
|
47275
|
-
async stepStarted(step, agent) {
|
|
47277
|
+
async stepStarted(step, agent, participants) {
|
|
47276
47278
|
if (!this.enabled || !this.trajectory)
|
|
47277
47279
|
return;
|
|
47278
|
-
|
|
47280
|
+
await this.registerAgent(agent, participants?.role ?? step.agent ?? "deterministic");
|
|
47281
|
+
if (participants?.owner && participants.owner !== agent) {
|
|
47282
|
+
await this.registerAgent(participants.owner, "owner");
|
|
47283
|
+
}
|
|
47284
|
+
if (participants?.specialist) {
|
|
47285
|
+
await this.registerAgent(participants.specialist, "specialist");
|
|
47286
|
+
}
|
|
47287
|
+
if (participants?.reviewer) {
|
|
47288
|
+
await this.registerAgent(participants.reviewer, "reviewer");
|
|
47289
|
+
}
|
|
47290
|
+
const intent = step.task ? step.task.trim().split(/\n|\.(?=\s)/)[0].trim().slice(0, 120) : `${step.type ?? "deterministic"} step`;
|
|
47291
|
+
this.addEvent("note", `"${step.name}": ${intent}`, void 0, { agent });
|
|
47292
|
+
await this.flush();
|
|
47293
|
+
}
|
|
47294
|
+
async registerAgent(name, role) {
|
|
47295
|
+
if (!this.enabled || !this.trajectory)
|
|
47296
|
+
return;
|
|
47297
|
+
if (!this.trajectory.agents.some((a) => a.name === name)) {
|
|
47279
47298
|
this.trajectory.agents.push({
|
|
47280
|
-
name
|
|
47281
|
-
role
|
|
47299
|
+
name,
|
|
47300
|
+
role,
|
|
47282
47301
|
joinedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
47283
47302
|
});
|
|
47303
|
+
await this.flush();
|
|
47284
47304
|
}
|
|
47285
|
-
|
|
47286
|
-
|
|
47305
|
+
}
|
|
47306
|
+
async stepSupervisionAssigned(step, supervised) {
|
|
47307
|
+
if (!this.enabled || !this.trajectory)
|
|
47308
|
+
return;
|
|
47309
|
+
await this.registerAgent(supervised.owner.name, "owner");
|
|
47310
|
+
await this.registerAgent(supervised.specialist.name, "specialist");
|
|
47311
|
+
if (supervised.reviewer?.name) {
|
|
47312
|
+
await this.registerAgent(supervised.reviewer.name, "reviewer");
|
|
47313
|
+
}
|
|
47314
|
+
const reviewerNote = supervised.reviewer?.name ? `, reviewer=${supervised.reviewer.name}` : "";
|
|
47315
|
+
this.addEvent("decision", `"${step.name}" supervision assigned \u2192 owner=${supervised.owner.name}, specialist=${supervised.specialist.name}${reviewerNote}`, "medium", {
|
|
47316
|
+
owner: supervised.owner.name,
|
|
47317
|
+
specialist: supervised.specialist.name,
|
|
47318
|
+
reviewer: supervised.reviewer?.name
|
|
47319
|
+
});
|
|
47320
|
+
await this.flush();
|
|
47321
|
+
}
|
|
47322
|
+
async ownerMonitoringEvent(stepName, owner, detail, raw) {
|
|
47323
|
+
if (!this.enabled || !this.trajectory)
|
|
47324
|
+
return;
|
|
47325
|
+
this.addEvent("note", `"${stepName}" owner ${owner}: ${detail}`, "medium", raw ? { owner, ...raw } : { owner });
|
|
47326
|
+
await this.flush();
|
|
47327
|
+
}
|
|
47328
|
+
async reviewCompleted(stepName, reviewerName, decision, reason) {
|
|
47329
|
+
if (!this.enabled || !this.trajectory)
|
|
47330
|
+
return;
|
|
47331
|
+
this.addEvent("review-completed", `"${stepName}" review ${decision} by ${reviewerName}`, "medium", {
|
|
47332
|
+
stepName,
|
|
47333
|
+
reviewer: reviewerName,
|
|
47334
|
+
decision,
|
|
47335
|
+
reason
|
|
47336
|
+
});
|
|
47287
47337
|
await this.flush();
|
|
47288
47338
|
}
|
|
47289
47339
|
/** Record step completed — captures what was accomplished. */
|
|
@@ -47613,6 +47663,8 @@ var WorkflowRunner = class _WorkflowRunner {
|
|
|
47613
47663
|
lastIdleLog = /* @__PURE__ */ new Map();
|
|
47614
47664
|
/** Tracks last logged activity type per agent to avoid duplicate status lines. */
|
|
47615
47665
|
lastActivity = /* @__PURE__ */ new Map();
|
|
47666
|
+
/** Runtime-name lookup for agents participating in supervised owner flows. */
|
|
47667
|
+
supervisedRuntimeAgents = /* @__PURE__ */ new Map();
|
|
47616
47668
|
/** Resolved named paths from the top-level `paths` config, keyed by name → absolute directory. */
|
|
47617
47669
|
resolvedPaths = /* @__PURE__ */ new Map();
|
|
47618
47670
|
constructor(options = {}) {
|
|
@@ -48523,6 +48575,10 @@ ${err.suggestion}`);
|
|
|
48523
48575
|
const fromShort = msg.from.replace(/-[a-f0-9]{6,}$/, "");
|
|
48524
48576
|
const toShort = msg.to.replace(/-[a-f0-9]{6,}$/, "");
|
|
48525
48577
|
this.log(`[msg] ${fromShort} \u2192 ${toShort}: ${body}`);
|
|
48578
|
+
const supervision = this.supervisedRuntimeAgents.get(msg.from);
|
|
48579
|
+
if (supervision?.role === "owner") {
|
|
48580
|
+
void this.trajectory?.ownerMonitoringEvent(supervision.stepName, supervision.logicalName, `Messaged ${msg.to}: ${msg.text.slice(0, 120)}`, { to: msg.to, text: msg.text });
|
|
48581
|
+
}
|
|
48526
48582
|
};
|
|
48527
48583
|
this.relay.onAgentSpawned = (agent) => {
|
|
48528
48584
|
if (!this.activeAgentHandles.has(agent.name)) {
|
|
@@ -48625,6 +48681,7 @@ ${err.suggestion}`);
|
|
|
48625
48681
|
}
|
|
48626
48682
|
this.lastIdleLog.clear();
|
|
48627
48683
|
this.lastActivity.clear();
|
|
48684
|
+
this.supervisedRuntimeAgents.clear();
|
|
48628
48685
|
this.log("Shutting down broker...");
|
|
48629
48686
|
await this.relay?.shutdown();
|
|
48630
48687
|
this.relay = void 0;
|
|
@@ -49131,10 +49188,19 @@ ${trimmedOutput.slice(0, 200)}`);
|
|
|
49131
49188
|
if (!rawAgentDef) {
|
|
49132
49189
|
throw new Error(`Agent "${agentName}" not found in config`);
|
|
49133
49190
|
}
|
|
49134
|
-
const
|
|
49135
|
-
const
|
|
49191
|
+
const specialistDef = _WorkflowRunner.resolveAgentDef(rawAgentDef);
|
|
49192
|
+
const usesOwnerFlow = specialistDef.interactive !== false;
|
|
49193
|
+
const ownerDef = usesOwnerFlow ? this.resolveAutoStepOwner(specialistDef, agentMap) : specialistDef;
|
|
49194
|
+
const reviewDef = usesOwnerFlow ? this.resolveAutoReviewAgent(ownerDef, agentMap) : void 0;
|
|
49195
|
+
const supervised = {
|
|
49196
|
+
specialist: specialistDef,
|
|
49197
|
+
owner: ownerDef,
|
|
49198
|
+
reviewer: reviewDef
|
|
49199
|
+
};
|
|
49200
|
+
const usesDedicatedOwner = usesOwnerFlow && ownerDef.name !== specialistDef.name;
|
|
49201
|
+
const maxRetries = step.retries ?? ownerDef.constraints?.retries ?? specialistDef.constraints?.retries ?? errorHandling?.maxRetries ?? 0;
|
|
49136
49202
|
const retryDelay = errorHandling?.retryDelayMs ?? 1e3;
|
|
49137
|
-
const timeoutMs = step.timeoutMs ??
|
|
49203
|
+
const timeoutMs = step.timeoutMs ?? ownerDef.constraints?.timeoutMs ?? specialistDef.constraints?.timeoutMs ?? this.currentConfig?.swarm?.timeoutMs;
|
|
49138
49204
|
let lastError;
|
|
49139
49205
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
49140
49206
|
this.checkAborted();
|
|
@@ -49158,48 +49224,95 @@ ${trimmedOutput.slice(0, 200)}`);
|
|
|
49158
49224
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
49159
49225
|
});
|
|
49160
49226
|
this.emit({ type: "step:started", runId, stepName: step.name });
|
|
49161
|
-
this.postToChannel(`**[${step.name}]** Started (
|
|
49162
|
-
await this.trajectory?.stepStarted(step,
|
|
49227
|
+
this.postToChannel(`**[${step.name}]** Started (owner: ${ownerDef.name}, specialist: ${specialistDef.name})`);
|
|
49228
|
+
await this.trajectory?.stepStarted(step, ownerDef.name, {
|
|
49229
|
+
role: usesDedicatedOwner ? "owner" : "specialist",
|
|
49230
|
+
owner: ownerDef.name,
|
|
49231
|
+
specialist: specialistDef.name,
|
|
49232
|
+
reviewer: reviewDef?.name
|
|
49233
|
+
});
|
|
49234
|
+
if (usesDedicatedOwner) {
|
|
49235
|
+
await this.trajectory?.stepSupervisionAssigned(step, supervised);
|
|
49236
|
+
}
|
|
49237
|
+
this.emit({
|
|
49238
|
+
type: "step:owner-assigned",
|
|
49239
|
+
runId,
|
|
49240
|
+
stepName: step.name,
|
|
49241
|
+
ownerName: ownerDef.name,
|
|
49242
|
+
specialistName: specialistDef.name
|
|
49243
|
+
});
|
|
49163
49244
|
const stepOutputContext = this.buildStepOutputContext(stepStates, runId);
|
|
49164
49245
|
let resolvedTask = this.interpolateStepTask(step.task ?? "", stepOutputContext);
|
|
49165
|
-
if (
|
|
49246
|
+
if (specialistDef.interactive !== false || ownerDef.interactive !== false) {
|
|
49166
49247
|
const nonInteractiveInfo = this.buildNonInteractiveAwareness(agentMap, stepStates);
|
|
49167
49248
|
if (nonInteractiveInfo) {
|
|
49168
49249
|
resolvedTask += nonInteractiveInfo;
|
|
49169
49250
|
}
|
|
49170
49251
|
}
|
|
49171
|
-
|
|
49172
|
-
|
|
49173
|
-
|
|
49174
|
-
|
|
49175
|
-
|
|
49252
|
+
const applyStepWorkdir = (def) => {
|
|
49253
|
+
if (step.workdir) {
|
|
49254
|
+
const stepWorkdir = this.resolveStepWorkdir(step);
|
|
49255
|
+
if (stepWorkdir) {
|
|
49256
|
+
return { ...def, cwd: stepWorkdir, workdir: void 0 };
|
|
49257
|
+
}
|
|
49176
49258
|
}
|
|
49259
|
+
return def;
|
|
49260
|
+
};
|
|
49261
|
+
const effectiveSpecialist = applyStepWorkdir(specialistDef);
|
|
49262
|
+
const effectiveOwner = applyStepWorkdir(ownerDef);
|
|
49263
|
+
let specialistOutput;
|
|
49264
|
+
let ownerOutput;
|
|
49265
|
+
let ownerElapsed;
|
|
49266
|
+
if (usesDedicatedOwner) {
|
|
49267
|
+
const result = await this.executeSupervisedAgentStep(step, { specialist: effectiveSpecialist, owner: effectiveOwner, reviewer: reviewDef }, resolvedTask, timeoutMs);
|
|
49268
|
+
specialistOutput = result.specialistOutput;
|
|
49269
|
+
ownerOutput = result.ownerOutput;
|
|
49270
|
+
ownerElapsed = result.ownerElapsed;
|
|
49271
|
+
} else {
|
|
49272
|
+
const ownerTask = this.injectStepOwnerContract(step, resolvedTask, effectiveOwner, effectiveSpecialist);
|
|
49273
|
+
this.log(`[${step.name}] Spawning owner "${effectiveOwner.name}" (cli: ${effectiveOwner.cli})${step.workdir ? ` [workdir: ${step.workdir}]` : ""}`);
|
|
49274
|
+
const resolvedStep = { ...step, task: ownerTask };
|
|
49275
|
+
const ownerStartTime = Date.now();
|
|
49276
|
+
const output = this.executor ? await this.executor.executeAgentStep(resolvedStep, effectiveOwner, ownerTask, timeoutMs) : await this.spawnAndWait(effectiveOwner, resolvedStep, timeoutMs);
|
|
49277
|
+
ownerElapsed = Date.now() - ownerStartTime;
|
|
49278
|
+
this.log(`[${step.name}] Owner "${effectiveOwner.name}" exited`);
|
|
49279
|
+
if (usesOwnerFlow) {
|
|
49280
|
+
this.assertOwnerCompletionMarker(step, output, ownerTask);
|
|
49281
|
+
}
|
|
49282
|
+
specialistOutput = output;
|
|
49283
|
+
ownerOutput = output;
|
|
49177
49284
|
}
|
|
49178
|
-
this.log(`[${step.name}] Spawning agent "${effectiveAgentDef.name}" (cli: ${effectiveAgentDef.cli})${step.workdir ? ` [workdir: ${step.workdir}]` : ""}`);
|
|
49179
|
-
const resolvedStep = { ...step, task: resolvedTask };
|
|
49180
|
-
const output = this.executor ? await this.executor.executeAgentStep(resolvedStep, effectiveAgentDef, resolvedTask, timeoutMs) : await this.spawnAndWait(effectiveAgentDef, resolvedStep, timeoutMs);
|
|
49181
|
-
this.log(`[${step.name}] Agent "${agentDef.name}" exited`);
|
|
49182
49285
|
if (step.verification) {
|
|
49183
|
-
this.runVerification(step.verification,
|
|
49286
|
+
this.runVerification(step.verification, specialistOutput, step.name, resolvedTask);
|
|
49287
|
+
}
|
|
49288
|
+
let combinedOutput = specialistOutput;
|
|
49289
|
+
if (usesOwnerFlow && reviewDef) {
|
|
49290
|
+
const remainingMs = timeoutMs ? Math.max(0, timeoutMs - ownerElapsed) : void 0;
|
|
49291
|
+
const reviewOutput = await this.runStepReviewGate(step, resolvedTask, specialistOutput, ownerOutput, ownerDef, reviewDef, remainingMs);
|
|
49292
|
+
combinedOutput = this.combineStepAndReviewOutput(specialistOutput, reviewOutput);
|
|
49184
49293
|
}
|
|
49185
49294
|
state.row.status = "completed";
|
|
49186
|
-
state.row.output =
|
|
49295
|
+
state.row.output = combinedOutput;
|
|
49187
49296
|
state.row.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
49188
49297
|
await this.db.updateStep(state.row.id, {
|
|
49189
49298
|
status: "completed",
|
|
49190
|
-
output,
|
|
49299
|
+
output: combinedOutput,
|
|
49191
49300
|
completedAt: state.row.completedAt,
|
|
49192
49301
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
49193
49302
|
});
|
|
49194
|
-
await this.persistStepOutput(runId, step.name,
|
|
49195
|
-
this.emit({ type: "step:completed", runId, stepName: step.name, output });
|
|
49196
|
-
await this.trajectory?.stepCompleted(step,
|
|
49303
|
+
await this.persistStepOutput(runId, step.name, combinedOutput);
|
|
49304
|
+
this.emit({ type: "step:completed", runId, stepName: step.name, output: combinedOutput });
|
|
49305
|
+
await this.trajectory?.stepCompleted(step, combinedOutput, attempt + 1);
|
|
49197
49306
|
return;
|
|
49198
49307
|
} catch (err) {
|
|
49199
49308
|
lastError = err instanceof Error ? err.message : String(err);
|
|
49309
|
+
const ownerTimedOut = usesDedicatedOwner ? /\bowner timed out\b/i.test(lastError) : /\btimed out\b/i.test(lastError) && !lastError.includes(`${step.name}-review`);
|
|
49310
|
+
if (ownerTimedOut) {
|
|
49311
|
+
this.emit({ type: "step:owner-timeout", runId, stepName: step.name, ownerName: ownerDef.name });
|
|
49312
|
+
}
|
|
49200
49313
|
}
|
|
49201
49314
|
}
|
|
49202
|
-
const nonInteractive =
|
|
49315
|
+
const nonInteractive = ownerDef.interactive === false || ["worker", "reviewer", "analyst"].includes(ownerDef.preset ?? "");
|
|
49203
49316
|
const verificationValue = typeof step.verification === "object" && "value" in step.verification ? String(step.verification.value) : void 0;
|
|
49204
49317
|
await this.trajectory?.stepFailed(step, lastError ?? "Unknown error", maxRetries + 1, maxRetries, {
|
|
49205
49318
|
agent: agentName,
|
|
@@ -49210,6 +49323,429 @@ ${trimmedOutput.slice(0, 200)}`);
|
|
|
49210
49323
|
await this.markStepFailed(state, lastError ?? "Unknown error", runId);
|
|
49211
49324
|
throw new Error(`Step "${step.name}" failed after ${maxRetries} retries: ${lastError ?? "Unknown error"}`);
|
|
49212
49325
|
}
|
|
49326
|
+
injectStepOwnerContract(step, resolvedTask, ownerDef, specialistDef) {
|
|
49327
|
+
if (ownerDef.interactive === false)
|
|
49328
|
+
return resolvedTask;
|
|
49329
|
+
const specialistNote = ownerDef.name === specialistDef.name ? "" : `Specialist intended for this step: "${specialistDef.name}" (${specialistDef.role ?? specialistDef.cli}).`;
|
|
49330
|
+
return resolvedTask + `
|
|
49331
|
+
|
|
49332
|
+
---
|
|
49333
|
+
STEP OWNER CONTRACT:
|
|
49334
|
+
- You are the accountable owner for step "${step.name}".
|
|
49335
|
+
` + (specialistNote ? `- ${specialistNote}
|
|
49336
|
+
` : "") + `- If you delegate, you must still verify completion yourself.
|
|
49337
|
+
- Before exiting, provide an explicit completion line: STEP_COMPLETE:${step.name}
|
|
49338
|
+
- Then self-terminate immediately with /exit.`;
|
|
49339
|
+
}
|
|
49340
|
+
buildOwnerSupervisorTask(step, originalTask, supervised, workerRuntimeName) {
|
|
49341
|
+
const verificationGuide = this.buildSupervisorVerificationGuide(step.verification);
|
|
49342
|
+
const channelLine = this.channel ? `#${this.channel}` : "(workflow channel unavailable)";
|
|
49343
|
+
return `You are the step owner/supervisor for step "${step.name}".
|
|
49344
|
+
|
|
49345
|
+
Worker: ${supervised.specialist.name} (runtime: ${workerRuntimeName}) on ${channelLine}
|
|
49346
|
+
Task: ${originalTask}
|
|
49347
|
+
|
|
49348
|
+
Your job: Monitor the worker and determine when the task is complete.
|
|
49349
|
+
|
|
49350
|
+
How to verify completion:
|
|
49351
|
+
- Watch ${channelLine} for the worker's progress messages and mirrored PTY output
|
|
49352
|
+
- Check file changes: run \`git diff --stat\` or inspect expected files directly
|
|
49353
|
+
- Ask the worker directly on ${channelLine} if you need a status update
|
|
49354
|
+
` + verificationGuide + `
|
|
49355
|
+
When you're satisfied the work is done correctly:
|
|
49356
|
+
Output exactly: STEP_COMPLETE:${step.name}`;
|
|
49357
|
+
}
|
|
49358
|
+
buildSupervisorVerificationGuide(verification) {
|
|
49359
|
+
if (!verification)
|
|
49360
|
+
return "";
|
|
49361
|
+
switch (verification.type) {
|
|
49362
|
+
case "output_contains":
|
|
49363
|
+
return `- Verification gate: confirm the worker output contains ${JSON.stringify(verification.value)}
|
|
49364
|
+
`;
|
|
49365
|
+
case "file_exists":
|
|
49366
|
+
return `- Verification gate: confirm the file exists at ${JSON.stringify(verification.value)}
|
|
49367
|
+
`;
|
|
49368
|
+
case "exit_code":
|
|
49369
|
+
return `- Verification gate: confirm the worker exits with code ${JSON.stringify(verification.value)}
|
|
49370
|
+
`;
|
|
49371
|
+
case "custom":
|
|
49372
|
+
return `- Verification gate: apply the custom verification rule ${JSON.stringify(verification.value)}
|
|
49373
|
+
`;
|
|
49374
|
+
default:
|
|
49375
|
+
return "";
|
|
49376
|
+
}
|
|
49377
|
+
}
|
|
49378
|
+
async executeSupervisedAgentStep(step, supervised, resolvedTask, timeoutMs) {
|
|
49379
|
+
if (this.executor) {
|
|
49380
|
+
const supervisorTask2 = this.buildOwnerSupervisorTask(step, resolvedTask, supervised, supervised.specialist.name);
|
|
49381
|
+
const specialistStep2 = { ...step, task: resolvedTask };
|
|
49382
|
+
const ownerStep2 = {
|
|
49383
|
+
...step,
|
|
49384
|
+
name: `${step.name}-owner`,
|
|
49385
|
+
agent: supervised.owner.name,
|
|
49386
|
+
task: supervisorTask2
|
|
49387
|
+
};
|
|
49388
|
+
this.log(`[${step.name}] Spawning specialist "${supervised.specialist.name}" and owner "${supervised.owner.name}"`);
|
|
49389
|
+
const specialistPromise = this.executor.executeAgentStep(specialistStep2, supervised.specialist, resolvedTask, timeoutMs);
|
|
49390
|
+
const specialistSettled = specialistPromise.catch(() => void 0);
|
|
49391
|
+
try {
|
|
49392
|
+
const ownerStartTime2 = Date.now();
|
|
49393
|
+
const ownerOutput = await this.executor.executeAgentStep(ownerStep2, supervised.owner, supervisorTask2, timeoutMs);
|
|
49394
|
+
const ownerElapsed = Date.now() - ownerStartTime2;
|
|
49395
|
+
this.assertOwnerCompletionMarker(step, ownerOutput, supervisorTask2);
|
|
49396
|
+
const specialistOutput = await specialistPromise;
|
|
49397
|
+
return { specialistOutput, ownerOutput, ownerElapsed };
|
|
49398
|
+
} catch (error95) {
|
|
49399
|
+
await specialistSettled;
|
|
49400
|
+
throw error95;
|
|
49401
|
+
}
|
|
49402
|
+
}
|
|
49403
|
+
let workerHandle;
|
|
49404
|
+
let workerRuntimeName = supervised.specialist.name;
|
|
49405
|
+
let workerSpawned = false;
|
|
49406
|
+
let workerReleased = false;
|
|
49407
|
+
let resolveWorkerSpawn;
|
|
49408
|
+
let rejectWorkerSpawn;
|
|
49409
|
+
const workerReady = new Promise((resolve3, reject) => {
|
|
49410
|
+
resolveWorkerSpawn = resolve3;
|
|
49411
|
+
rejectWorkerSpawn = reject;
|
|
49412
|
+
});
|
|
49413
|
+
const specialistStep = { ...step, task: resolvedTask };
|
|
49414
|
+
this.log(`[${step.name}] Spawning specialist "${supervised.specialist.name}" (cli: ${supervised.specialist.cli})`);
|
|
49415
|
+
const workerPromise = this.spawnAndWait(supervised.specialist, specialistStep, timeoutMs, {
|
|
49416
|
+
agentNameSuffix: "worker",
|
|
49417
|
+
onSpawned: ({ actualName, agent }) => {
|
|
49418
|
+
workerHandle = agent;
|
|
49419
|
+
workerRuntimeName = actualName;
|
|
49420
|
+
this.supervisedRuntimeAgents.set(actualName, {
|
|
49421
|
+
stepName: step.name,
|
|
49422
|
+
role: "specialist",
|
|
49423
|
+
logicalName: supervised.specialist.name
|
|
49424
|
+
});
|
|
49425
|
+
if (!workerSpawned) {
|
|
49426
|
+
workerSpawned = true;
|
|
49427
|
+
resolveWorkerSpawn();
|
|
49428
|
+
}
|
|
49429
|
+
},
|
|
49430
|
+
onChunk: ({ agentName, chunk }) => {
|
|
49431
|
+
this.forwardAgentChunkToChannel(step.name, "Worker", agentName, chunk);
|
|
49432
|
+
}
|
|
49433
|
+
}).catch((error95) => {
|
|
49434
|
+
if (!workerSpawned) {
|
|
49435
|
+
workerSpawned = true;
|
|
49436
|
+
rejectWorkerSpawn(error95);
|
|
49437
|
+
}
|
|
49438
|
+
throw error95;
|
|
49439
|
+
});
|
|
49440
|
+
const workerSettled = workerPromise.catch(() => void 0);
|
|
49441
|
+
workerPromise.then((output) => {
|
|
49442
|
+
workerReleased = true;
|
|
49443
|
+
this.postToChannel(`**[${step.name}]** Worker \`${workerRuntimeName}\` exited`);
|
|
49444
|
+
if (step.verification?.type === "output_contains" && output.includes(step.verification.value)) {
|
|
49445
|
+
this.postToChannel(`**[${step.name}]** Verification gate observed: output contains ${JSON.stringify(step.verification.value)}`);
|
|
49446
|
+
}
|
|
49447
|
+
}).catch((error95) => {
|
|
49448
|
+
const message = error95 instanceof Error ? error95.message : String(error95);
|
|
49449
|
+
this.postToChannel(`**[${step.name}]** Worker \`${workerRuntimeName}\` exited with error: ${message}`);
|
|
49450
|
+
});
|
|
49451
|
+
await workerReady;
|
|
49452
|
+
const supervisorTask = this.buildOwnerSupervisorTask(step, resolvedTask, supervised, workerRuntimeName);
|
|
49453
|
+
const ownerStep = {
|
|
49454
|
+
...step,
|
|
49455
|
+
name: `${step.name}-owner`,
|
|
49456
|
+
agent: supervised.owner.name,
|
|
49457
|
+
task: supervisorTask
|
|
49458
|
+
};
|
|
49459
|
+
this.log(`[${step.name}] Spawning owner "${supervised.owner.name}" (cli: ${supervised.owner.cli})`);
|
|
49460
|
+
const ownerStartTime = Date.now();
|
|
49461
|
+
try {
|
|
49462
|
+
const ownerOutput = await this.spawnAndWait(supervised.owner, ownerStep, timeoutMs, {
|
|
49463
|
+
agentNameSuffix: "owner",
|
|
49464
|
+
onSpawned: ({ actualName }) => {
|
|
49465
|
+
this.supervisedRuntimeAgents.set(actualName, {
|
|
49466
|
+
stepName: step.name,
|
|
49467
|
+
role: "owner",
|
|
49468
|
+
logicalName: supervised.owner.name
|
|
49469
|
+
});
|
|
49470
|
+
},
|
|
49471
|
+
onChunk: ({ chunk }) => {
|
|
49472
|
+
void this.recordOwnerMonitoringChunk(step, supervised.owner, chunk);
|
|
49473
|
+
}
|
|
49474
|
+
});
|
|
49475
|
+
const ownerElapsed = Date.now() - ownerStartTime;
|
|
49476
|
+
this.log(`[${step.name}] Owner "${supervised.owner.name}" exited`);
|
|
49477
|
+
this.assertOwnerCompletionMarker(step, ownerOutput, supervisorTask);
|
|
49478
|
+
const specialistOutput = await workerPromise;
|
|
49479
|
+
return { specialistOutput, ownerOutput, ownerElapsed };
|
|
49480
|
+
} catch (error95) {
|
|
49481
|
+
const message = error95 instanceof Error ? error95.message : String(error95);
|
|
49482
|
+
if (!workerReleased && workerHandle) {
|
|
49483
|
+
await workerHandle.release().catch(() => void 0);
|
|
49484
|
+
}
|
|
49485
|
+
await workerSettled;
|
|
49486
|
+
if (/\btimed out\b/i.test(message)) {
|
|
49487
|
+
throw new Error(`Step "${step.name}" owner timed out after ${timeoutMs ?? "unknown"}ms`);
|
|
49488
|
+
}
|
|
49489
|
+
throw error95;
|
|
49490
|
+
}
|
|
49491
|
+
}
|
|
49492
|
+
forwardAgentChunkToChannel(stepName, roleLabel, agentName, chunk) {
|
|
49493
|
+
const lines = _WorkflowRunner.stripAnsi(chunk).split("\n").map((line) => line.trim()).filter(Boolean).slice(0, 3);
|
|
49494
|
+
for (const line of lines) {
|
|
49495
|
+
this.postToChannel(`**[${stepName}]** ${roleLabel} \`${agentName}\`: ${line.slice(0, 280)}`);
|
|
49496
|
+
}
|
|
49497
|
+
}
|
|
49498
|
+
async recordOwnerMonitoringChunk(step, ownerDef, chunk) {
|
|
49499
|
+
const stripped = _WorkflowRunner.stripAnsi(chunk);
|
|
49500
|
+
const details = [];
|
|
49501
|
+
if (/git diff --stat/i.test(stripped))
|
|
49502
|
+
details.push("Checked git diff stats");
|
|
49503
|
+
if (/\bls -la\b/i.test(stripped))
|
|
49504
|
+
details.push("Listed files for verification");
|
|
49505
|
+
if (/status update\?/i.test(stripped))
|
|
49506
|
+
details.push("Asked the worker for a status update");
|
|
49507
|
+
if (/STEP_COMPLETE:/i.test(stripped))
|
|
49508
|
+
details.push("Declared the step complete");
|
|
49509
|
+
for (const detail of details) {
|
|
49510
|
+
await this.trajectory?.ownerMonitoringEvent(step.name, ownerDef.name, detail, {
|
|
49511
|
+
output: stripped.slice(0, 240)
|
|
49512
|
+
});
|
|
49513
|
+
}
|
|
49514
|
+
}
|
|
49515
|
+
resolveAutoStepOwner(specialistDef, agentMap) {
|
|
49516
|
+
if (specialistDef.interactive === false)
|
|
49517
|
+
return specialistDef;
|
|
49518
|
+
const allDefs = [...agentMap.values()].map((d) => _WorkflowRunner.resolveAgentDef(d));
|
|
49519
|
+
const candidates = allDefs.filter((d) => d.interactive !== false);
|
|
49520
|
+
const matchesHubRole = (text) => [..._WorkflowRunner.HUB_ROLES].some((r) => new RegExp(`\\b${r}\\b`, "i").test(text));
|
|
49521
|
+
const ownerish = (def) => {
|
|
49522
|
+
const nameLC = def.name.toLowerCase();
|
|
49523
|
+
const roleLC = def.role?.toLowerCase() ?? "";
|
|
49524
|
+
return matchesHubRole(nameLC) || matchesHubRole(roleLC);
|
|
49525
|
+
};
|
|
49526
|
+
const ownerPriority = (def) => {
|
|
49527
|
+
const roleLC = def.role?.toLowerCase() ?? "";
|
|
49528
|
+
const nameLC = def.name.toLowerCase();
|
|
49529
|
+
if (/\blead\b/.test(roleLC) || /\blead\b/.test(nameLC))
|
|
49530
|
+
return 6;
|
|
49531
|
+
if (/\bcoordinator\b/.test(roleLC) || /\bcoordinator\b/.test(nameLC))
|
|
49532
|
+
return 5;
|
|
49533
|
+
if (/\bsupervisor\b/.test(roleLC) || /\bsupervisor\b/.test(nameLC))
|
|
49534
|
+
return 4;
|
|
49535
|
+
if (/\borchestrator\b/.test(roleLC) || /\borchestrator\b/.test(nameLC))
|
|
49536
|
+
return 3;
|
|
49537
|
+
if (/\bhub\b/.test(roleLC) || /\bhub\b/.test(nameLC))
|
|
49538
|
+
return 2;
|
|
49539
|
+
return ownerish(def) ? 1 : 0;
|
|
49540
|
+
};
|
|
49541
|
+
const dedicatedOwner = candidates.filter((d) => d.name !== specialistDef.name && ownerish(d)).sort((a, b) => ownerPriority(b) - ownerPriority(a) || a.name.localeCompare(b.name))[0];
|
|
49542
|
+
if (dedicatedOwner)
|
|
49543
|
+
return dedicatedOwner;
|
|
49544
|
+
return specialistDef;
|
|
49545
|
+
}
|
|
49546
|
+
resolveAutoReviewAgent(ownerDef, agentMap) {
|
|
49547
|
+
const allDefs = [...agentMap.values()].map((d) => _WorkflowRunner.resolveAgentDef(d));
|
|
49548
|
+
const isReviewer = (def) => {
|
|
49549
|
+
const roleLC = def.role?.toLowerCase() ?? "";
|
|
49550
|
+
const nameLC = def.name.toLowerCase();
|
|
49551
|
+
return def.preset === "reviewer" || roleLC.includes("review") || roleLC.includes("critic") || roleLC.includes("verifier") || roleLC.includes("qa") || nameLC.includes("review");
|
|
49552
|
+
};
|
|
49553
|
+
const reviewerPriority = (def) => {
|
|
49554
|
+
if (def.preset === "reviewer")
|
|
49555
|
+
return 5;
|
|
49556
|
+
const roleLC = def.role?.toLowerCase() ?? "";
|
|
49557
|
+
const nameLC = def.name.toLowerCase();
|
|
49558
|
+
if (roleLC.includes("review") || nameLC.includes("review"))
|
|
49559
|
+
return 4;
|
|
49560
|
+
if (roleLC.includes("verifier") || roleLC.includes("qa"))
|
|
49561
|
+
return 3;
|
|
49562
|
+
if (roleLC.includes("critic"))
|
|
49563
|
+
return 2;
|
|
49564
|
+
return isReviewer(def) ? 1 : 0;
|
|
49565
|
+
};
|
|
49566
|
+
const dedicated = allDefs.filter((d) => d.name !== ownerDef.name && isReviewer(d)).sort((a, b) => reviewerPriority(b) - reviewerPriority(a) || a.name.localeCompare(b.name))[0];
|
|
49567
|
+
if (dedicated)
|
|
49568
|
+
return dedicated;
|
|
49569
|
+
const alternate = allDefs.find((d) => d.name !== ownerDef.name && d.interactive !== false);
|
|
49570
|
+
if (alternate)
|
|
49571
|
+
return alternate;
|
|
49572
|
+
return ownerDef;
|
|
49573
|
+
}
|
|
49574
|
+
assertOwnerCompletionMarker(step, output, injectedTaskText) {
|
|
49575
|
+
const marker = `STEP_COMPLETE:${step.name}`;
|
|
49576
|
+
const taskHasMarker = injectedTaskText.includes(marker);
|
|
49577
|
+
const first = output.indexOf(marker);
|
|
49578
|
+
if (first === -1) {
|
|
49579
|
+
throw new Error(`Step "${step.name}" owner completion marker missing: "${marker}"`);
|
|
49580
|
+
}
|
|
49581
|
+
const outputLikelyContainsInjectedPrompt = output.includes("STEP OWNER CONTRACT") || output.includes("Output exactly: STEP_COMPLETE:");
|
|
49582
|
+
if (taskHasMarker && outputLikelyContainsInjectedPrompt) {
|
|
49583
|
+
const hasSecond = output.includes(marker, first + marker.length);
|
|
49584
|
+
if (!hasSecond) {
|
|
49585
|
+
throw new Error(`Step "${step.name}" owner completion marker missing in agent response: "${marker}"`);
|
|
49586
|
+
}
|
|
49587
|
+
}
|
|
49588
|
+
}
|
|
49589
|
+
async runStepReviewGate(step, resolvedTask, specialistOutput, ownerOutput, ownerDef, reviewerDef, timeoutMs) {
|
|
49590
|
+
const reviewSnippetMax = 12e3;
|
|
49591
|
+
let specialistSnippet = specialistOutput;
|
|
49592
|
+
if (specialistOutput.length > reviewSnippetMax) {
|
|
49593
|
+
const head = Math.floor(reviewSnippetMax / 2);
|
|
49594
|
+
const tail = reviewSnippetMax - head;
|
|
49595
|
+
const omitted = specialistOutput.length - head - tail;
|
|
49596
|
+
specialistSnippet = `${specialistOutput.slice(0, head)}
|
|
49597
|
+
...[truncated ${omitted} chars for review]...
|
|
49598
|
+
${specialistOutput.slice(specialistOutput.length - tail)}`;
|
|
49599
|
+
}
|
|
49600
|
+
let ownerSnippet = ownerOutput;
|
|
49601
|
+
if (ownerOutput.length > reviewSnippetMax) {
|
|
49602
|
+
const head = Math.floor(reviewSnippetMax / 2);
|
|
49603
|
+
const tail = reviewSnippetMax - head;
|
|
49604
|
+
const omitted = ownerOutput.length - head - tail;
|
|
49605
|
+
ownerSnippet = `${ownerOutput.slice(0, head)}
|
|
49606
|
+
...[truncated ${omitted} chars for review]...
|
|
49607
|
+
${ownerOutput.slice(ownerOutput.length - tail)}`;
|
|
49608
|
+
}
|
|
49609
|
+
const reviewTask = `Review workflow step "${step.name}" for completion and safe handoff.
|
|
49610
|
+
Step owner: ${ownerDef.name}
|
|
49611
|
+
Original objective:
|
|
49612
|
+
${resolvedTask}
|
|
49613
|
+
|
|
49614
|
+
Specialist output:
|
|
49615
|
+
${specialistSnippet}
|
|
49616
|
+
|
|
49617
|
+
Owner verification notes:
|
|
49618
|
+
${ownerSnippet}
|
|
49619
|
+
|
|
49620
|
+
Return exactly:
|
|
49621
|
+
REVIEW_DECISION: APPROVE or REJECT
|
|
49622
|
+
REVIEW_REASON: <one sentence>
|
|
49623
|
+
Then output /exit.`;
|
|
49624
|
+
const safetyTimeoutMs = timeoutMs ?? 6e5;
|
|
49625
|
+
const reviewStep = {
|
|
49626
|
+
name: `${step.name}-review`,
|
|
49627
|
+
type: "agent",
|
|
49628
|
+
agent: reviewerDef.name,
|
|
49629
|
+
task: reviewTask
|
|
49630
|
+
};
|
|
49631
|
+
await this.trajectory?.registerAgent(reviewerDef.name, "reviewer");
|
|
49632
|
+
this.postToChannel(`**[${step.name}]** Review started (reviewer: ${reviewerDef.name})`);
|
|
49633
|
+
const emitReviewCompleted = async (decision, reason) => {
|
|
49634
|
+
await this.trajectory?.reviewCompleted(step.name, reviewerDef.name, decision, reason);
|
|
49635
|
+
this.emit({
|
|
49636
|
+
type: "step:review-completed",
|
|
49637
|
+
runId: this.currentRunId ?? "",
|
|
49638
|
+
stepName: step.name,
|
|
49639
|
+
reviewerName: reviewerDef.name,
|
|
49640
|
+
decision
|
|
49641
|
+
});
|
|
49642
|
+
};
|
|
49643
|
+
if (this.executor) {
|
|
49644
|
+
const reviewOutput2 = await this.executor.executeAgentStep(reviewStep, reviewerDef, reviewTask, safetyTimeoutMs);
|
|
49645
|
+
const parsed = this.parseReviewDecision(reviewOutput2);
|
|
49646
|
+
if (!parsed) {
|
|
49647
|
+
throw new Error(`Step "${step.name}" review response malformed from "${reviewerDef.name}" (missing REVIEW_DECISION)`);
|
|
49648
|
+
}
|
|
49649
|
+
await emitReviewCompleted(parsed.decision, parsed.reason);
|
|
49650
|
+
if (parsed.decision === "rejected") {
|
|
49651
|
+
throw new Error(`Step "${step.name}" review rejected by "${reviewerDef.name}"`);
|
|
49652
|
+
}
|
|
49653
|
+
this.postToChannel(`**[${step.name}]** Review approved by \`${reviewerDef.name}\``);
|
|
49654
|
+
return reviewOutput2;
|
|
49655
|
+
}
|
|
49656
|
+
let reviewerHandle;
|
|
49657
|
+
let reviewerReleased = false;
|
|
49658
|
+
let reviewOutput = "";
|
|
49659
|
+
let completedReview;
|
|
49660
|
+
let reviewCompletionPromise;
|
|
49661
|
+
const reviewCompletionStarted = { value: false };
|
|
49662
|
+
const startReviewCompletion = (parsed) => {
|
|
49663
|
+
if (reviewCompletionStarted.value)
|
|
49664
|
+
return;
|
|
49665
|
+
reviewCompletionStarted.value = true;
|
|
49666
|
+
completedReview = parsed;
|
|
49667
|
+
reviewCompletionPromise = (async () => {
|
|
49668
|
+
await emitReviewCompleted(parsed.decision, parsed.reason);
|
|
49669
|
+
if (reviewerHandle && !reviewerReleased) {
|
|
49670
|
+
reviewerReleased = true;
|
|
49671
|
+
await reviewerHandle.release().catch(() => void 0);
|
|
49672
|
+
}
|
|
49673
|
+
})();
|
|
49674
|
+
};
|
|
49675
|
+
try {
|
|
49676
|
+
reviewOutput = await this.spawnAndWait(reviewerDef, reviewStep, safetyTimeoutMs, {
|
|
49677
|
+
onSpawned: ({ agent }) => {
|
|
49678
|
+
reviewerHandle = agent;
|
|
49679
|
+
},
|
|
49680
|
+
onChunk: ({ chunk }) => {
|
|
49681
|
+
const nextOutput = reviewOutput + _WorkflowRunner.stripAnsi(chunk);
|
|
49682
|
+
reviewOutput = nextOutput;
|
|
49683
|
+
const parsed = this.parseReviewDecision(nextOutput);
|
|
49684
|
+
if (parsed) {
|
|
49685
|
+
startReviewCompletion(parsed);
|
|
49686
|
+
}
|
|
49687
|
+
}
|
|
49688
|
+
});
|
|
49689
|
+
await reviewCompletionPromise;
|
|
49690
|
+
} catch (error95) {
|
|
49691
|
+
const message = error95 instanceof Error ? error95.message : String(error95);
|
|
49692
|
+
if (/\btimed out\b/i.test(message)) {
|
|
49693
|
+
this.log(`[${step.name}] Review safety backstop timeout fired after ${safetyTimeoutMs}ms`);
|
|
49694
|
+
throw new Error(`Step "${step.name}" review safety backstop timed out after ${safetyTimeoutMs}ms`);
|
|
49695
|
+
}
|
|
49696
|
+
throw error95;
|
|
49697
|
+
}
|
|
49698
|
+
if (!completedReview) {
|
|
49699
|
+
const parsed = this.parseReviewDecision(reviewOutput);
|
|
49700
|
+
if (!parsed) {
|
|
49701
|
+
throw new Error(`Step "${step.name}" review response malformed from "${reviewerDef.name}" (missing REVIEW_DECISION)`);
|
|
49702
|
+
}
|
|
49703
|
+
completedReview = parsed;
|
|
49704
|
+
await emitReviewCompleted(parsed.decision, parsed.reason);
|
|
49705
|
+
}
|
|
49706
|
+
if (completedReview.decision === "rejected") {
|
|
49707
|
+
throw new Error(`Step "${step.name}" review rejected by "${reviewerDef.name}"`);
|
|
49708
|
+
}
|
|
49709
|
+
this.postToChannel(`**[${step.name}]** Review approved by \`${reviewerDef.name}\``);
|
|
49710
|
+
return reviewOutput;
|
|
49711
|
+
}
|
|
49712
|
+
parseReviewDecision(reviewOutput) {
|
|
49713
|
+
const decisionPattern = /REVIEW_DECISION:\s*(APPROVE|REJECT)/gi;
|
|
49714
|
+
const decisionMatches = [...reviewOutput.matchAll(decisionPattern)];
|
|
49715
|
+
if (decisionMatches.length === 0) {
|
|
49716
|
+
return null;
|
|
49717
|
+
}
|
|
49718
|
+
const outputLikelyContainsEchoedPrompt = reviewOutput.includes("Return exactly") || reviewOutput.includes("REVIEW_DECISION: APPROVE or REJECT");
|
|
49719
|
+
const decisionMatch = outputLikelyContainsEchoedPrompt && decisionMatches.length > 1 ? decisionMatches[decisionMatches.length - 1] : decisionMatches[0];
|
|
49720
|
+
const decision = decisionMatch?.[1]?.toUpperCase();
|
|
49721
|
+
if (decision !== "APPROVE" && decision !== "REJECT") {
|
|
49722
|
+
return null;
|
|
49723
|
+
}
|
|
49724
|
+
const reasonPattern = /REVIEW_REASON:\s*(.+)/gi;
|
|
49725
|
+
const reasonMatches = [...reviewOutput.matchAll(reasonPattern)];
|
|
49726
|
+
const reasonMatch = outputLikelyContainsEchoedPrompt && reasonMatches.length > 1 ? reasonMatches[reasonMatches.length - 1] : reasonMatches[0];
|
|
49727
|
+
const reason = reasonMatch?.[1]?.trim();
|
|
49728
|
+
return {
|
|
49729
|
+
decision: decision === "APPROVE" ? "approved" : "rejected",
|
|
49730
|
+
reason: reason && reason !== "<one sentence>" ? reason : void 0
|
|
49731
|
+
};
|
|
49732
|
+
}
|
|
49733
|
+
combineStepAndReviewOutput(stepOutput, reviewOutput) {
|
|
49734
|
+
const primary = stepOutput.trimEnd();
|
|
49735
|
+
const review = reviewOutput.trim();
|
|
49736
|
+
if (!review)
|
|
49737
|
+
return primary;
|
|
49738
|
+
if (!primary)
|
|
49739
|
+
return `REVIEW_OUTPUT
|
|
49740
|
+
${review}
|
|
49741
|
+
`;
|
|
49742
|
+
return `${primary}
|
|
49743
|
+
|
|
49744
|
+
---
|
|
49745
|
+
REVIEW_OUTPUT
|
|
49746
|
+
${review}
|
|
49747
|
+
`;
|
|
49748
|
+
}
|
|
49213
49749
|
/**
|
|
49214
49750
|
* Build the CLI command and arguments for a non-interactive agent execution.
|
|
49215
49751
|
* Each CLI has a specific flag for one-shot prompt mode.
|
|
@@ -49403,17 +49939,18 @@ DO NOT:
|
|
|
49403
49939
|
this.unregisterWorker(agentName);
|
|
49404
49940
|
}
|
|
49405
49941
|
}
|
|
49406
|
-
async spawnAndWait(agentDef, step, timeoutMs) {
|
|
49942
|
+
async spawnAndWait(agentDef, step, timeoutMs, options = {}) {
|
|
49407
49943
|
if (agentDef.interactive === false) {
|
|
49408
49944
|
return this.execNonInteractive(agentDef, step, timeoutMs);
|
|
49409
49945
|
}
|
|
49410
49946
|
if (!this.relay) {
|
|
49411
49947
|
throw new Error("AgentRelay not initialized");
|
|
49412
49948
|
}
|
|
49413
|
-
|
|
49949
|
+
const requestedName = `${step.name}${options.agentNameSuffix ? `-${options.agentNameSuffix}` : ""}-${(this.currentRunId ?? this.generateShortId()).slice(0, 8)}`;
|
|
49950
|
+
let agentName = requestedName;
|
|
49414
49951
|
const role = agentDef.role?.toLowerCase() ?? "";
|
|
49415
49952
|
const nameLC = agentDef.name.toLowerCase();
|
|
49416
|
-
const isHub = _WorkflowRunner.HUB_ROLES.has(nameLC) || [..._WorkflowRunner.HUB_ROLES].some((r) =>
|
|
49953
|
+
const isHub = _WorkflowRunner.HUB_ROLES.has(nameLC) || [..._WorkflowRunner.HUB_ROLES].some((r) => new RegExp(`\\b${r}\\b`).test(role));
|
|
49417
49954
|
const pattern = this.currentConfig?.swarm.pattern;
|
|
49418
49955
|
const isHubPattern = pattern && _WorkflowRunner.HUB_PATTERNS.has(pattern);
|
|
49419
49956
|
const delegationGuidance = isHub || !isHubPattern ? this.buildDelegationGuidance(agentDef.cli, timeoutMs) : "";
|
|
@@ -49427,6 +49964,7 @@ DO NOT:
|
|
|
49427
49964
|
const stripped = _WorkflowRunner.stripAnsi(chunk);
|
|
49428
49965
|
this.ptyOutputBuffers.get(agentName)?.push(stripped);
|
|
49429
49966
|
logStream.write(chunk);
|
|
49967
|
+
options.onChunk?.({ agentName, chunk });
|
|
49430
49968
|
});
|
|
49431
49969
|
const agentChannels = this.channel ? [this.channel] : agentDef.channels;
|
|
49432
49970
|
let agent;
|
|
@@ -49469,10 +50007,12 @@ DO NOT:
|
|
|
49469
50007
|
const stripped = _WorkflowRunner.stripAnsi(chunk);
|
|
49470
50008
|
this.ptyOutputBuffers.get(agent.name)?.push(stripped);
|
|
49471
50009
|
newLogStream.write(chunk);
|
|
50010
|
+
options.onChunk?.({ agentName: agent.name, chunk });
|
|
49472
50011
|
});
|
|
49473
50012
|
}
|
|
49474
50013
|
agentName = agent.name;
|
|
49475
50014
|
}
|
|
50015
|
+
await options.onSpawned?.({ requestedName, actualName: agent.name, agent });
|
|
49476
50016
|
let workerPid;
|
|
49477
50017
|
try {
|
|
49478
50018
|
const rawAgents = await this.relay.listAgentsRaw();
|
|
@@ -49525,6 +50065,7 @@ DO NOT:
|
|
|
49525
50065
|
this.ptyLogStreams.delete(agentName);
|
|
49526
50066
|
}
|
|
49527
50067
|
this.unregisterWorker(agentName);
|
|
50068
|
+
this.supervisedRuntimeAgents.delete(agentName);
|
|
49528
50069
|
}
|
|
49529
50070
|
let output;
|
|
49530
50071
|
if (ptyChunks.length > 0) {
|
|
@@ -49650,7 +50191,7 @@ DO NOT:
|
|
|
49650
50191
|
continue;
|
|
49651
50192
|
const role = agentDef.role?.toLowerCase() ?? "";
|
|
49652
50193
|
const nameLC = agentDef.name.toLowerCase();
|
|
49653
|
-
if (_WorkflowRunner.HUB_ROLES.has(nameLC) || [..._WorkflowRunner.HUB_ROLES].some((r) =>
|
|
50194
|
+
if (_WorkflowRunner.HUB_ROLES.has(nameLC) || [..._WorkflowRunner.HUB_ROLES].some((r) => new RegExp(`\\b${r}\\b`).test(role))) {
|
|
49654
50195
|
const handle = this.activeAgentHandles.get(agentDef.name);
|
|
49655
50196
|
if (handle)
|
|
49656
50197
|
return handle;
|