@agent-relay/sdk 4.0.4 → 4.0.6
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/broker-path.d.ts +6 -4
- package/dist/broker-path.d.ts.map +1 -1
- package/dist/broker-path.js +34 -8
- package/dist/broker-path.js.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +57 -9
- package/dist/client.js.map +1 -1
- package/dist/relay.d.ts +1 -0
- package/dist/relay.d.ts.map +1 -1
- package/dist/relay.js +5 -1
- package/dist/relay.js.map +1 -1
- package/dist/workflows/__tests__/permissions-integration.test.js +42 -0
- package/dist/workflows/__tests__/permissions-integration.test.js.map +1 -1
- package/dist/workflows/cli.js +4 -2
- package/dist/workflows/cli.js.map +1 -1
- package/dist/workflows/listr-renderer.d.ts.map +1 -1
- package/dist/workflows/listr-renderer.js +2 -4
- package/dist/workflows/listr-renderer.js.map +1 -1
- package/dist/workflows/runner.d.ts +2 -0
- package/dist/workflows/runner.d.ts.map +1 -1
- package/dist/workflows/runner.js +97 -23
- package/dist/workflows/runner.js.map +1 -1
- package/package.json +2 -2
package/dist/workflows/runner.js
CHANGED
|
@@ -103,6 +103,20 @@ function resolveCursorCli() {
|
|
|
103
103
|
const resolved = resolveCliSync('cursor');
|
|
104
104
|
return resolved?.binary ?? 'agent';
|
|
105
105
|
}
|
|
106
|
+
function getWorkflowSdkSpawner(relay, cli) {
|
|
107
|
+
switch (cli) {
|
|
108
|
+
case 'claude':
|
|
109
|
+
return relay.claude;
|
|
110
|
+
case 'codex':
|
|
111
|
+
return relay.codex;
|
|
112
|
+
case 'gemini':
|
|
113
|
+
return relay.gemini;
|
|
114
|
+
case 'opencode':
|
|
115
|
+
return relay.opencode;
|
|
116
|
+
default:
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
106
120
|
// ── WorkflowRunner ──────────────────────────────────────────────────────────
|
|
107
121
|
export class WorkflowRunner {
|
|
108
122
|
db;
|
|
@@ -1070,12 +1084,13 @@ export class WorkflowRunner {
|
|
|
1070
1084
|
});
|
|
1071
1085
|
}
|
|
1072
1086
|
getRelayEnv() {
|
|
1073
|
-
if (!this.relayApiKey) {
|
|
1074
|
-
return
|
|
1087
|
+
if (!this.relayApiKey && !this.relayOptions.env) {
|
|
1088
|
+
return undefined;
|
|
1075
1089
|
}
|
|
1076
1090
|
return {
|
|
1077
|
-
...
|
|
1078
|
-
|
|
1091
|
+
...process.env,
|
|
1092
|
+
...(this.relayOptions.env ?? {}),
|
|
1093
|
+
...(this.relayApiKey ? { RELAY_API_KEY: this.relayApiKey } : {}),
|
|
1079
1094
|
};
|
|
1080
1095
|
}
|
|
1081
1096
|
async provisionAgents(config) {
|
|
@@ -2048,7 +2063,7 @@ export class WorkflowRunner {
|
|
|
2048
2063
|
this.log('API key resolved');
|
|
2049
2064
|
if (this.relayApiKeyAutoCreated && this.relayApiKey) {
|
|
2050
2065
|
this.log(`Workspace created — follow this run in Relaycast:`);
|
|
2051
|
-
this.log(` Observer: https://agentrelay.
|
|
2066
|
+
this.log(` Observer: https://agentrelay.com/observer?key=${this.relayApiKey}`);
|
|
2052
2067
|
this.log(` Channel: ${channel}`);
|
|
2053
2068
|
}
|
|
2054
2069
|
}
|
|
@@ -3115,7 +3130,7 @@ export class WorkflowRunner {
|
|
|
3115
3130
|
let completionReason;
|
|
3116
3131
|
let promptTaskText;
|
|
3117
3132
|
if (usesDedicatedOwner) {
|
|
3118
|
-
const result = await this.executeSupervisedAgentStep(step, { specialist: effectiveSpecialist, owner: effectiveOwner, reviewer: reviewDef }, resolvedTask, timeoutMs);
|
|
3133
|
+
const result = await this.executeSupervisedAgentStep(step, { specialist: effectiveSpecialist, owner: effectiveOwner, reviewer: reviewDef }, resolvedTask, timeoutMs, attempt);
|
|
3119
3134
|
specialistOutput = result.specialistOutput;
|
|
3120
3135
|
ownerOutput = result.ownerOutput;
|
|
3121
3136
|
ownerElapsed = result.ownerElapsed;
|
|
@@ -3133,6 +3148,7 @@ export class WorkflowRunner {
|
|
|
3133
3148
|
const spawnResult = this.executor
|
|
3134
3149
|
? await this.executor.executeAgentStep(resolvedStep, effectiveOwner, ownerTask, timeoutMs)
|
|
3135
3150
|
: await this.spawnAndWait(effectiveOwner, resolvedStep, timeoutMs, {
|
|
3151
|
+
retryAttempt: attempt,
|
|
3136
3152
|
evidenceStepName: step.name,
|
|
3137
3153
|
evidenceRole: usesOwnerFlow ? 'owner' : 'specialist',
|
|
3138
3154
|
preserveOnIdle: !isHubPattern || !this.isLeadLikeAgent(effectiveOwner) ? false : undefined,
|
|
@@ -3361,6 +3377,34 @@ export class WorkflowRunner {
|
|
|
3361
3377
|
`- Do not rely on terminal output alone for handoff; use the workflow group chat signal above.\n` +
|
|
3362
3378
|
`- After posting your handoff signal, self-terminate with /exit unless the owner asks for follow-up.`);
|
|
3363
3379
|
}
|
|
3380
|
+
buildWorkflowRuntimeAgentBaseName(stepName, options) {
|
|
3381
|
+
return `${stepName}${options.agentNameSuffix ? `-${options.agentNameSuffix}` : ''}-${(this.currentRunId ?? this.generateShortId()).slice(0, 8)}`;
|
|
3382
|
+
}
|
|
3383
|
+
async releaseStaleRetryAgents(baseRequestedName, stepName) {
|
|
3384
|
+
if (!this.relay) {
|
|
3385
|
+
return;
|
|
3386
|
+
}
|
|
3387
|
+
const staleAgents = (await this.relay.listAgents()).filter((agent) => agent.name === baseRequestedName || agent.name.startsWith(`${baseRequestedName}-r`));
|
|
3388
|
+
if (staleAgents.length === 0) {
|
|
3389
|
+
return;
|
|
3390
|
+
}
|
|
3391
|
+
const staleNames = [...new Set(staleAgents.map((agent) => agent.name))].sort();
|
|
3392
|
+
this.log(`[${stepName}] Releasing stale retry agent(s): ${staleNames.join(', ')}`);
|
|
3393
|
+
for (const agent of staleAgents) {
|
|
3394
|
+
await agent.release(`workflow retry cleanup for step "${stepName}"`);
|
|
3395
|
+
}
|
|
3396
|
+
const deadline = Date.now() + 5_000;
|
|
3397
|
+
while (Date.now() < deadline) {
|
|
3398
|
+
const remaining = (await this.relay.listAgentsRaw())
|
|
3399
|
+
.map((agent) => agent.name)
|
|
3400
|
+
.filter((name) => staleNames.includes(name));
|
|
3401
|
+
if (remaining.length === 0) {
|
|
3402
|
+
return;
|
|
3403
|
+
}
|
|
3404
|
+
await this.delay(100);
|
|
3405
|
+
}
|
|
3406
|
+
throw new Error(`Failed to clear stale retry agent(s) before respawn: ${staleNames.join(', ')}`);
|
|
3407
|
+
}
|
|
3364
3408
|
buildSupervisorVerificationGuide(verification) {
|
|
3365
3409
|
if (!verification)
|
|
3366
3410
|
return '';
|
|
@@ -3377,7 +3421,7 @@ export class WorkflowRunner {
|
|
|
3377
3421
|
return '';
|
|
3378
3422
|
}
|
|
3379
3423
|
}
|
|
3380
|
-
async executeSupervisedAgentStep(step, supervised, resolvedTask, timeoutMs) {
|
|
3424
|
+
async executeSupervisedAgentStep(step, supervised, resolvedTask, timeoutMs, retryAttempt = 0) {
|
|
3381
3425
|
if (this.executor) {
|
|
3382
3426
|
const specialistTask = this.buildWorkerHandoffTask(step, resolvedTask, supervised);
|
|
3383
3427
|
const supervisorTask = this.buildOwnerSupervisorTask(step, resolvedTask, supervised, supervised.specialist.name);
|
|
@@ -3425,6 +3469,7 @@ export class WorkflowRunner {
|
|
|
3425
3469
|
this.log(`[${step.name}] Spawning specialist "${supervised.specialist.name}" (cli: ${supervised.specialist.cli})`);
|
|
3426
3470
|
const workerPromise = this.spawnAndWait(supervised.specialist, specialistStep, timeoutMs, {
|
|
3427
3471
|
agentNameSuffix: 'worker',
|
|
3472
|
+
retryAttempt,
|
|
3428
3473
|
evidenceStepName: step.name,
|
|
3429
3474
|
evidenceRole: 'worker',
|
|
3430
3475
|
logicalName: supervised.specialist.name,
|
|
@@ -3488,6 +3533,7 @@ export class WorkflowRunner {
|
|
|
3488
3533
|
try {
|
|
3489
3534
|
const ownerResultObj = await this.spawnAndWait(supervised.owner, ownerStep, timeoutMs, {
|
|
3490
3535
|
agentNameSuffix: 'owner',
|
|
3536
|
+
retryAttempt,
|
|
3491
3537
|
evidenceStepName: step.name,
|
|
3492
3538
|
evidenceRole: 'owner',
|
|
3493
3539
|
logicalName: supervised.owner.name,
|
|
@@ -4411,9 +4457,14 @@ export class WorkflowRunner {
|
|
|
4411
4457
|
throw new Error('AgentRelay not initialized');
|
|
4412
4458
|
}
|
|
4413
4459
|
const evidenceStepName = options.evidenceStepName ?? step.name;
|
|
4414
|
-
|
|
4415
|
-
const requestedName =
|
|
4460
|
+
const baseRequestedName = this.buildWorkflowRuntimeAgentBaseName(step.name, options);
|
|
4461
|
+
const requestedName = (options.retryAttempt ?? 0) > 0
|
|
4462
|
+
? `${baseRequestedName}-r${(options.retryAttempt ?? 0) + 1}`
|
|
4463
|
+
: baseRequestedName;
|
|
4416
4464
|
let agentName = requestedName;
|
|
4465
|
+
if ((options.retryAttempt ?? 0) > 0) {
|
|
4466
|
+
await this.releaseStaleRetryAgents(baseRequestedName, step.name);
|
|
4467
|
+
}
|
|
4417
4468
|
// Only inject delegation guidance for lead/coordinator agents, not spokes/workers.
|
|
4418
4469
|
// In non-hub patterns (pipeline, dag, etc.) every agent is autonomous so they all get it.
|
|
4419
4470
|
const role = agentDef.role?.toLowerCase() ?? '';
|
|
@@ -4422,20 +4473,28 @@ export class WorkflowRunner {
|
|
|
4422
4473
|
[...WorkflowRunner.HUB_ROLES].some((r) => new RegExp(`\\b${r}\\b`).test(role));
|
|
4423
4474
|
const pattern = this.currentConfig?.swarm.pattern;
|
|
4424
4475
|
const isHubPattern = pattern && WorkflowRunner.HUB_PATTERNS.has(pattern);
|
|
4425
|
-
const
|
|
4476
|
+
const usesHeadlessWorkflowSpawner = agentDef.cli === 'opencode';
|
|
4477
|
+
const delegationGuidance = usesHeadlessWorkflowSpawner || (!isHub && isHubPattern)
|
|
4478
|
+
? ''
|
|
4479
|
+
: this.buildDelegationGuidance(agentDef.cli, timeoutMs);
|
|
4426
4480
|
// Non-claude CLIs (codex, gemini, etc.) don't auto-register with Relaycast
|
|
4427
4481
|
// via the MCP system prompt the way claude does. Inject an explicit preamble
|
|
4428
4482
|
// so they call register() before any other relay tool.
|
|
4429
|
-
const relayRegistrationNote =
|
|
4430
|
-
|
|
4431
|
-
(
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4483
|
+
const relayRegistrationNote = usesHeadlessWorkflowSpawner
|
|
4484
|
+
? ''
|
|
4485
|
+
: this.buildRelayRegistrationNote(agentDef.cli, agentName);
|
|
4486
|
+
const interactiveTaskBase = step.task ?? '';
|
|
4487
|
+
const taskWithExit = usesHeadlessWorkflowSpawner
|
|
4488
|
+
? interactiveTaskBase
|
|
4489
|
+
: interactiveTaskBase +
|
|
4490
|
+
(relayRegistrationNote ? '\n\n' + relayRegistrationNote : '') +
|
|
4491
|
+
(delegationGuidance ? '\n\n' + delegationGuidance + '\n' : '') +
|
|
4492
|
+
'\n\n---\n' +
|
|
4493
|
+
'IMPORTANT: When you have fully completed this task, you MUST self-terminate by either: ' +
|
|
4494
|
+
'(a) calling remove_agent(name: "<your-agent-name>", reason: "task completed") — preferred, or ' +
|
|
4495
|
+
'(b) outputting the exact text "/exit" on its own line as a fallback. ' +
|
|
4496
|
+
'Do not wait for further input — terminate immediately after finishing. ' +
|
|
4497
|
+
'Do NOT spawn sub-agents unless the task explicitly requires it.';
|
|
4439
4498
|
const preparedTask = this.prepareInteractiveSpawnTask(agentName, taskWithExit);
|
|
4440
4499
|
// Register PTY output listener before spawning so we capture everything
|
|
4441
4500
|
this.ptyOutputBuffers.set(agentName, []);
|
|
@@ -4464,9 +4523,8 @@ export class WorkflowRunner {
|
|
|
4464
4523
|
RELAY_API_KEY: this.relayApiKey ?? 'workflow-runner',
|
|
4465
4524
|
AGENT_CHANNELS: (agentChannels ?? []).join(','),
|
|
4466
4525
|
});
|
|
4467
|
-
|
|
4526
|
+
const spawnOptions = {
|
|
4468
4527
|
name: agentName,
|
|
4469
|
-
cli: agentDef.cli,
|
|
4470
4528
|
model: agentDef.constraints?.model,
|
|
4471
4529
|
args: interactiveSpawnPolicy.args,
|
|
4472
4530
|
channels: agentChannels,
|
|
@@ -4474,7 +4532,19 @@ export class WorkflowRunner {
|
|
|
4474
4532
|
idleThresholdSecs: agentDef.constraints?.idleThresholdSecs,
|
|
4475
4533
|
cwd: agentCwd,
|
|
4476
4534
|
agentToken: this.agentTokens.get(agentDef.name),
|
|
4477
|
-
}
|
|
4535
|
+
};
|
|
4536
|
+
const sdkSpawner = getWorkflowSdkSpawner(this.relay, agentDef.cli);
|
|
4537
|
+
if (sdkSpawner) {
|
|
4538
|
+
this.log(`[${step.name}] Using SDK spawner for ${agentDef.cli} (requested runtime: ${agentDef.cli === 'opencode' ? 'headless' : 'pty'})`);
|
|
4539
|
+
agent = await sdkSpawner.spawn(spawnOptions);
|
|
4540
|
+
}
|
|
4541
|
+
else {
|
|
4542
|
+
this.log(`[${step.name}] Using PTY fallback for ${agentDef.cli}`);
|
|
4543
|
+
agent = await this.relay.spawnPty({
|
|
4544
|
+
...spawnOptions,
|
|
4545
|
+
cli: agentDef.cli,
|
|
4546
|
+
});
|
|
4547
|
+
}
|
|
4478
4548
|
// Re-key PTY maps if broker assigned a different name than requested
|
|
4479
4549
|
if (agent.name !== agentName) {
|
|
4480
4550
|
const oldName = agentName;
|
|
@@ -5351,6 +5421,10 @@ export class WorkflowRunner {
|
|
|
5351
5421
|
}
|
|
5352
5422
|
const maxMsg = 2000;
|
|
5353
5423
|
const preview = scrubbed.length > maxMsg ? scrubbed.slice(-maxMsg) : scrubbed;
|
|
5424
|
+
// Surface the final output preview in the local workflow log immediately.
|
|
5425
|
+
// Some deterministic wrappers grep stdout/stderr for completion sentinels,
|
|
5426
|
+
// and fire-and-forget channel delivery can arrive too late for single-step runs.
|
|
5427
|
+
this.log(`[${stepName}] Output:\n\`\`\`\n${preview}\n\`\`\``);
|
|
5354
5428
|
this.postToChannel(`**[${stepName}] Output:**\n\`\`\`\n${preview}\n\`\`\``, { stepName });
|
|
5355
5429
|
}
|
|
5356
5430
|
async persistAgentReport(runId, stepName, report) {
|