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