agent-relay 3.0.2 → 3.1.0
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/README.md +8 -0
- 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 +273 -56
- package/dist/src/cli/commands/core.d.ts +2 -0
- package/dist/src/cli/commands/core.d.ts.map +1 -1
- package/dist/src/cli/commands/core.js +9 -2
- package/dist/src/cli/commands/core.js.map +1 -1
- package/dist/src/cli/lib/broker-lifecycle.d.ts.map +1 -1
- package/dist/src/cli/lib/broker-lifecycle.js +87 -28
- package/dist/src/cli/lib/broker-lifecycle.js.map +1 -1
- package/package.json +8 -8
- package/packages/acp-bridge/README.md +50 -67
- 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/policy/package.json +2 -2
- package/packages/sdk/README.md +169 -64
- package/packages/sdk/dist/__tests__/contract-fixtures.test.js +76 -9
- package/packages/sdk/dist/__tests__/contract-fixtures.test.js.map +1 -1
- package/packages/sdk/dist/__tests__/integration.test.js +5 -4
- package/packages/sdk/dist/__tests__/integration.test.js.map +1 -1
- package/packages/sdk/dist/client.d.ts +34 -3
- package/packages/sdk/dist/client.d.ts.map +1 -1
- package/packages/sdk/dist/client.js +120 -10
- package/packages/sdk/dist/client.js.map +1 -1
- package/packages/sdk/dist/protocol.d.ts +7 -1
- package/packages/sdk/dist/protocol.d.ts.map +1 -1
- package/packages/sdk/dist/relay.d.ts +47 -11
- package/packages/sdk/dist/relay.d.ts.map +1 -1
- package/packages/sdk/dist/relay.js +114 -23
- package/packages/sdk/dist/relay.js.map +1 -1
- package/packages/sdk/dist/workflows/runner.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/runner.js +71 -36
- package/packages/sdk/dist/workflows/runner.js.map +1 -1
- package/packages/sdk/dist/workflows/types.d.ts +1 -1
- package/packages/sdk/dist/workflows/types.d.ts.map +1 -1
- package/packages/sdk/package.json +2 -2
- package/packages/sdk/src/__tests__/contract-fixtures.test.ts +88 -9
- package/packages/sdk/src/__tests__/error-scenarios.test.ts +1 -1
- package/packages/sdk/src/__tests__/idle-nudge.test.ts +205 -257
- package/packages/sdk/src/__tests__/integration.test.ts +5 -4
- package/packages/sdk/src/__tests__/orchestration-upgrades.test.ts +277 -13
- package/packages/sdk/src/__tests__/swarm-coordinator.test.ts +1 -0
- package/packages/sdk/src/__tests__/workflow-runner.test.ts +67 -7
- package/packages/sdk/src/__tests__/workflow-trajectory.test.ts +4 -5
- package/packages/sdk/src/client.ts +171 -14
- package/packages/sdk/src/examples/workflows/runner-idle-refactor.yaml +306 -0
- package/packages/sdk/src/protocol.ts +7 -2
- package/packages/sdk/src/relay.ts +196 -34
- package/packages/sdk/src/workflows/runner.ts +73 -42
- package/packages/sdk/src/workflows/schema.json +1 -1
- package/packages/sdk/src/workflows/types.ts +1 -1
- package/packages/sdk/vitest.config.ts +1 -0
- package/packages/sdk-py/README.md +89 -102
- package/packages/sdk-py/agent_relay/__init__.py +16 -19
- package/packages/sdk-py/pyproject.toml +5 -1
- package/packages/sdk-py/src/agent_relay/__init__.py +35 -1
- package/packages/sdk-py/src/agent_relay/client.py +776 -0
- package/packages/sdk-py/src/agent_relay/models.py +27 -0
- package/packages/sdk-py/src/agent_relay/protocol.py +114 -0
- package/packages/sdk-py/src/agent_relay/relay.py +860 -0
- package/packages/sdk-py/tests/test_relay_lifecycle_hooks.py +250 -0
- 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/packages/sdk/.trajectories/active/traj_1771875803391_84ca57b2.json +0 -50
- package/packages/sdk/.trajectories/active/traj_1771891934534_06504121.json +0 -50
- package/packages/sdk/.trajectories/active/traj_1771891957929_211afc4e.json +0 -50
- package/packages/sdk/.trajectories/active/traj_1771891982509_38c84638.json +0 -50
- package/packages/sdk/.trajectories/completed/traj_1771875803188_cd6d181c.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771875803204_f2aeb8c8.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771875803210_d65f3f1a.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771875803218_e454a25d.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771875803223_d7a64815.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771875803227_7e56da5b.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771875803235_4fbf93b4.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771875803243_47931c71.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771875803258_3816f3fe.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771875803268_8061140e.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771875803326_ae6f9c78.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771875808396_cbde0a6c.json +0 -91
- package/packages/sdk/.trajectories/completed/traj_1771875812026_aa2442bb.json +0 -91
- package/packages/sdk/.trajectories/completed/traj_1771875815431_c2c656c5.json +0 -91
- package/packages/sdk/.trajectories/completed/traj_1771875818645_3a4dbf02.json +0 -91
- package/packages/sdk/.trajectories/completed/traj_1771891934403_24923c03.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891934421_dca16e24.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891934430_057706f7.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891934442_faf97382.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891934454_5542ecd5.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891934464_12202a08.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891934487_94378275.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891934503_ca728c13.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891934519_100af69a.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891934536_62ad39d9.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891934553_d6798a52.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891939537_541c8096.json +0 -91
- package/packages/sdk/.trajectories/completed/traj_1771891942985_36ab9a4d.json +0 -91
- package/packages/sdk/.trajectories/completed/traj_1771891946453_e8a6e05f.json +0 -91
- package/packages/sdk/.trajectories/completed/traj_1771891949838_5de0de84.json +0 -91
- package/packages/sdk/.trajectories/completed/traj_1771891957807_0ecfb4f4.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891957827_c4539239.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891957836_91168b48.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891957848_8c5cad0b.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891957857_0986b293.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891957872_8a3113af.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891957884_0bb85208.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891957892_86c75e2e.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891957907_98ca0e6f.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891957918_d9091231.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891957931_dcaf77ed.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891962931_eb1fdee2.json +0 -91
- package/packages/sdk/.trajectories/completed/traj_1771891966262_9061a93f.json +0 -91
- package/packages/sdk/.trajectories/completed/traj_1771891969915_1adaba19.json +0 -91
- package/packages/sdk/.trajectories/completed/traj_1771891973588_f08b79e9.json +0 -91
- package/packages/sdk/.trajectories/completed/traj_1771891982421_f1985bce.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891982432_e7a84163.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891982447_369b842a.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891982469_5fc45199.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891982495_454c7cb3.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891982514_08098e03.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891982526_b351d778.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891982533_fa542d83.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891982540_18ab24dc.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891982544_5b4fa163.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891982548_c13f089a.json +0 -80
- package/packages/sdk/.trajectories/completed/traj_1771891987510_23f6da1f.json +0 -91
- package/packages/sdk/.trajectories/completed/traj_1771891991466_912c2e04.json +0 -91
- package/packages/sdk/.trajectories/completed/traj_1771891994891_60604be2.json +0 -91
- package/packages/sdk/.trajectories/completed/traj_1771891998370_cfaf9b8b.json +0 -91
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { once } from 'node:events';
|
|
2
|
-
import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process';
|
|
2
|
+
import { execSync, spawn, type ChildProcessWithoutNullStreams } from 'node:child_process';
|
|
3
3
|
import { createInterface, type Interface as ReadlineInterface } from 'node:readline';
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import os from 'node:os';
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
type BrokerStats,
|
|
15
15
|
type BrokerStatus,
|
|
16
16
|
type CrashInsightsResponse,
|
|
17
|
+
type HeadlessProvider,
|
|
17
18
|
type ProtocolEnvelope,
|
|
18
19
|
type ProtocolError,
|
|
19
20
|
type RestartPolicy,
|
|
@@ -51,13 +52,33 @@ export interface SpawnPtyInput {
|
|
|
51
52
|
continueFrom?: string;
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
export interface
|
|
55
|
+
export interface SpawnHeadlessInput {
|
|
55
56
|
name: string;
|
|
57
|
+
provider: HeadlessProvider;
|
|
56
58
|
args?: string[];
|
|
57
59
|
channels?: string[];
|
|
58
60
|
task?: string;
|
|
59
61
|
}
|
|
60
62
|
|
|
63
|
+
export type AgentTransport = 'pty' | 'headless';
|
|
64
|
+
|
|
65
|
+
export interface SpawnProviderInput {
|
|
66
|
+
name: string;
|
|
67
|
+
provider: string;
|
|
68
|
+
transport?: AgentTransport;
|
|
69
|
+
args?: string[];
|
|
70
|
+
channels?: string[];
|
|
71
|
+
task?: string;
|
|
72
|
+
model?: string;
|
|
73
|
+
cwd?: string;
|
|
74
|
+
team?: string;
|
|
75
|
+
shadowOf?: string;
|
|
76
|
+
shadowMode?: string;
|
|
77
|
+
idleThresholdSecs?: number;
|
|
78
|
+
restartPolicy?: RestartPolicy;
|
|
79
|
+
continueFrom?: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
61
82
|
export interface SendMessageInput {
|
|
62
83
|
to: string;
|
|
63
84
|
text: string;
|
|
@@ -70,6 +91,7 @@ export interface SendMessageInput {
|
|
|
70
91
|
export interface ListAgent {
|
|
71
92
|
name: string;
|
|
72
93
|
runtime: AgentRuntime;
|
|
94
|
+
provider?: HeadlessProvider;
|
|
73
95
|
cli?: string;
|
|
74
96
|
model?: string;
|
|
75
97
|
team?: string;
|
|
@@ -113,6 +135,10 @@ export class AgentRelayProcessError extends Error {
|
|
|
113
135
|
}
|
|
114
136
|
}
|
|
115
137
|
|
|
138
|
+
function isHeadlessProvider(value: string): value is HeadlessProvider {
|
|
139
|
+
return value === 'claude' || value === 'opencode';
|
|
140
|
+
}
|
|
141
|
+
|
|
116
142
|
export class AgentRelayClient {
|
|
117
143
|
private readonly options: Required<AgentRelayClientOptions>;
|
|
118
144
|
private child?: ChildProcessWithoutNullStreams;
|
|
@@ -250,13 +276,12 @@ export class AgentRelayClient {
|
|
|
250
276
|
return result;
|
|
251
277
|
}
|
|
252
278
|
|
|
253
|
-
async
|
|
254
|
-
input: SpawnHeadlessClaudeInput
|
|
255
|
-
): Promise<{ name: string; runtime: AgentRuntime }> {
|
|
279
|
+
async spawnHeadless(input: SpawnHeadlessInput): Promise<{ name: string; runtime: AgentRuntime }> {
|
|
256
280
|
await this.start();
|
|
257
281
|
const agent: AgentSpec = {
|
|
258
282
|
name: input.name,
|
|
259
|
-
runtime: '
|
|
283
|
+
runtime: 'headless',
|
|
284
|
+
provider: input.provider,
|
|
260
285
|
args: input.args ?? [],
|
|
261
286
|
channels: input.channels ?? [],
|
|
262
287
|
};
|
|
@@ -267,6 +292,52 @@ export class AgentRelayClient {
|
|
|
267
292
|
return result;
|
|
268
293
|
}
|
|
269
294
|
|
|
295
|
+
async spawnProvider(input: SpawnProviderInput): Promise<{ name: string; runtime: AgentRuntime }> {
|
|
296
|
+
const transport = input.transport ?? (input.provider === 'opencode' ? 'headless' : 'pty');
|
|
297
|
+
if (transport === 'headless') {
|
|
298
|
+
if (!isHeadlessProvider(input.provider)) {
|
|
299
|
+
throw new AgentRelayProcessError(
|
|
300
|
+
`provider '${input.provider}' does not support headless transport (supported: claude, opencode)`
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
return this.spawnHeadless({
|
|
304
|
+
name: input.name,
|
|
305
|
+
provider: input.provider,
|
|
306
|
+
args: input.args,
|
|
307
|
+
channels: input.channels,
|
|
308
|
+
task: input.task,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return this.spawnPty({
|
|
313
|
+
name: input.name,
|
|
314
|
+
cli: input.provider,
|
|
315
|
+
args: input.args,
|
|
316
|
+
channels: input.channels,
|
|
317
|
+
task: input.task,
|
|
318
|
+
model: input.model,
|
|
319
|
+
cwd: input.cwd,
|
|
320
|
+
team: input.team,
|
|
321
|
+
shadowOf: input.shadowOf,
|
|
322
|
+
shadowMode: input.shadowMode,
|
|
323
|
+
idleThresholdSecs: input.idleThresholdSecs,
|
|
324
|
+
restartPolicy: input.restartPolicy,
|
|
325
|
+
continueFrom: input.continueFrom,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async spawnClaude(
|
|
330
|
+
input: Omit<SpawnProviderInput, 'provider'>
|
|
331
|
+
): Promise<{ name: string; runtime: AgentRuntime }> {
|
|
332
|
+
return this.spawnProvider({ ...input, provider: 'claude' });
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async spawnOpencode(
|
|
336
|
+
input: Omit<SpawnProviderInput, 'provider'>
|
|
337
|
+
): Promise<{ name: string; runtime: AgentRuntime }> {
|
|
338
|
+
return this.spawnProvider({ ...input, provider: 'opencode' });
|
|
339
|
+
}
|
|
340
|
+
|
|
270
341
|
async release(name: string, reason?: string): Promise<{ name: string }> {
|
|
271
342
|
await this.start();
|
|
272
343
|
return this.requestOk<{ name: string }>('release_agent', { name, reason });
|
|
@@ -533,7 +604,11 @@ export class AgentRelayClient {
|
|
|
533
604
|
pending.resolve(envelope);
|
|
534
605
|
}
|
|
535
606
|
|
|
536
|
-
private async requestHello(): Promise<{
|
|
607
|
+
private async requestHello(): Promise<{
|
|
608
|
+
broker_version: string;
|
|
609
|
+
protocol_version: number;
|
|
610
|
+
workspace_key?: string;
|
|
611
|
+
}> {
|
|
537
612
|
const payload = {
|
|
538
613
|
client_name: this.options.clientName,
|
|
539
614
|
client_version: this.options.clientVersion,
|
|
@@ -644,6 +719,92 @@ function isExplicitPath(binaryPath: string): boolean {
|
|
|
644
719
|
);
|
|
645
720
|
}
|
|
646
721
|
|
|
722
|
+
function detectPlatformSuffix(): string | null {
|
|
723
|
+
const platformMap: Record<string, Record<string, string>> = {
|
|
724
|
+
darwin: { arm64: 'darwin-arm64', x64: 'darwin-x64' },
|
|
725
|
+
linux: { arm64: 'linux-arm64', x64: 'linux-x64' },
|
|
726
|
+
};
|
|
727
|
+
return platformMap[process.platform]?.[process.arch] ?? null;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function getLatestVersionSync(): string | null {
|
|
731
|
+
try {
|
|
732
|
+
const result = execSync('curl -fsSL https://api.github.com/repos/AgentWorkforce/relay/releases/latest', {
|
|
733
|
+
timeout: 15_000,
|
|
734
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
735
|
+
}).toString();
|
|
736
|
+
const match = result.match(/"tag_name"\s*:\s*"v?([^"]+)"/);
|
|
737
|
+
return match?.[1] ?? null;
|
|
738
|
+
} catch {
|
|
739
|
+
return null;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function installBrokerBinary(): string {
|
|
744
|
+
const suffix = detectPlatformSuffix();
|
|
745
|
+
if (!suffix) {
|
|
746
|
+
throw new AgentRelayProcessError(`Unsupported platform: ${process.platform}-${process.arch}`);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || '';
|
|
750
|
+
const installDir = path.join(homeDir, '.agent-relay', 'bin');
|
|
751
|
+
const brokerExe = process.platform === 'win32' ? 'agent-relay-broker.exe' : 'agent-relay-broker';
|
|
752
|
+
const targetPath = path.join(installDir, brokerExe);
|
|
753
|
+
|
|
754
|
+
console.log(`[agent-relay] Broker binary not found, installing for ${suffix}...`);
|
|
755
|
+
|
|
756
|
+
const version = getLatestVersionSync();
|
|
757
|
+
if (!version) {
|
|
758
|
+
throw new AgentRelayProcessError(
|
|
759
|
+
'Failed to fetch latest agent-relay version from GitHub.\n' +
|
|
760
|
+
'Install manually: curl -fsSL https://raw.githubusercontent.com/AgentWorkforce/relay/main/install.sh | bash'
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
const binaryName = `agent-relay-broker-${suffix}`;
|
|
765
|
+
const downloadUrl = `https://github.com/AgentWorkforce/relay/releases/download/v${version}/${binaryName}`;
|
|
766
|
+
|
|
767
|
+
console.log(`[agent-relay] Downloading v${version} from ${downloadUrl}`);
|
|
768
|
+
|
|
769
|
+
try {
|
|
770
|
+
fs.mkdirSync(installDir, { recursive: true });
|
|
771
|
+
execSync(`curl -fsSL "${downloadUrl}" -o "${targetPath}"`, {
|
|
772
|
+
timeout: 60_000,
|
|
773
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
774
|
+
});
|
|
775
|
+
fs.chmodSync(targetPath, 0o755);
|
|
776
|
+
|
|
777
|
+
// macOS: re-sign to avoid Gatekeeper issues
|
|
778
|
+
if (process.platform === 'darwin') {
|
|
779
|
+
try {
|
|
780
|
+
execSync(`codesign --force --sign - "${targetPath}"`, {
|
|
781
|
+
timeout: 10_000,
|
|
782
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
783
|
+
});
|
|
784
|
+
} catch {
|
|
785
|
+
// Non-fatal
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// Verify
|
|
790
|
+
execSync(`"${targetPath}" --help`, { timeout: 10_000, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
791
|
+
} catch (err) {
|
|
792
|
+
try {
|
|
793
|
+
fs.unlinkSync(targetPath);
|
|
794
|
+
} catch {
|
|
795
|
+
/* ignore */
|
|
796
|
+
}
|
|
797
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
798
|
+
throw new AgentRelayProcessError(
|
|
799
|
+
`Failed to install broker binary: ${message}\n` +
|
|
800
|
+
'Install manually: curl -fsSL https://raw.githubusercontent.com/AgentWorkforce/relay/main/install.sh | bash'
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
console.log(`[agent-relay] Broker installed to ${targetPath}`);
|
|
805
|
+
return targetPath;
|
|
806
|
+
}
|
|
807
|
+
|
|
647
808
|
function resolveDefaultBinaryPath(): string {
|
|
648
809
|
const brokerExe = process.platform === 'win32' ? 'agent-relay-broker.exe' : 'agent-relay-broker';
|
|
649
810
|
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -659,11 +820,7 @@ function resolveDefaultBinaryPath(): string {
|
|
|
659
820
|
// Try platform-specific name first (CI publishes per-platform binaries),
|
|
660
821
|
// then fall back to the generic name (local dev / postinstall copy).
|
|
661
822
|
const binDir = path.resolve(moduleDir, '..', 'bin');
|
|
662
|
-
const
|
|
663
|
-
darwin: { arm64: 'darwin-arm64', x64: 'darwin-x64' },
|
|
664
|
-
linux: { arm64: 'linux-arm64', x64: 'linux-x64' },
|
|
665
|
-
};
|
|
666
|
-
const suffix = platformMap[process.platform]?.[process.arch];
|
|
823
|
+
const suffix = detectPlatformSuffix();
|
|
667
824
|
if (suffix) {
|
|
668
825
|
const platformBinary = path.join(binDir, `agent-relay-broker-${suffix}`);
|
|
669
826
|
if (fs.existsSync(platformBinary)) {
|
|
@@ -682,6 +839,6 @@ function resolveDefaultBinaryPath(): string {
|
|
|
682
839
|
return standaloneBroker;
|
|
683
840
|
}
|
|
684
841
|
|
|
685
|
-
// 4.
|
|
686
|
-
return
|
|
842
|
+
// 4. Auto-install from GitHub releases
|
|
843
|
+
return installBrokerBinary();
|
|
687
844
|
}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
version: '1.0'
|
|
2
|
+
name: runner-idle-refactor
|
|
3
|
+
description: >
|
|
4
|
+
Refactors WorkflowRunner with two changes:
|
|
5
|
+
1. Remove agent pre-registration (preflightAgents) — agents spawn only when their step runs.
|
|
6
|
+
2. Idle = done — when no idleNudge config is set, race waitForExit vs waitForIdle;
|
|
7
|
+
if idle fires first the step completes immediately.
|
|
8
|
+
|
|
9
|
+
Workflow: read context → implement → update tests → type-check → run tests
|
|
10
|
+
→ fix if broken → final test run → review.
|
|
11
|
+
|
|
12
|
+
swarm:
|
|
13
|
+
pattern: pipeline
|
|
14
|
+
maxConcurrency: 3
|
|
15
|
+
timeoutMs: 1800000 # 30 min
|
|
16
|
+
channel: wf-runner-refactor
|
|
17
|
+
|
|
18
|
+
# No idleNudge — each agent is spawned per step and exits when done.
|
|
19
|
+
# Non-interactive preset for all agents (pure code changes, no relay tools needed).
|
|
20
|
+
|
|
21
|
+
agents:
|
|
22
|
+
- name: implementer
|
|
23
|
+
cli: codex
|
|
24
|
+
preset: worker
|
|
25
|
+
role: 'Makes the two targeted edits to packages/sdk/src/workflows/runner.ts.'
|
|
26
|
+
constraints:
|
|
27
|
+
model: gpt-5.3-codex
|
|
28
|
+
|
|
29
|
+
- name: test-writer
|
|
30
|
+
cli: codex
|
|
31
|
+
preset: worker
|
|
32
|
+
role: 'Adds new test cases to idle-nudge.test.ts covering the idle=done behavior.'
|
|
33
|
+
constraints:
|
|
34
|
+
model: gpt-5.3-codex
|
|
35
|
+
|
|
36
|
+
- name: fixer
|
|
37
|
+
cli: codex
|
|
38
|
+
preset: worker
|
|
39
|
+
role: 'Fixes TypeScript errors or failing tests found in the test run.'
|
|
40
|
+
constraints:
|
|
41
|
+
model: gpt-5.3-codex
|
|
42
|
+
|
|
43
|
+
- name: reviewer
|
|
44
|
+
cli: claude
|
|
45
|
+
preset: reviewer
|
|
46
|
+
role: 'Reviews the diff for correctness, edge cases, and backwards compatibility.'
|
|
47
|
+
constraints:
|
|
48
|
+
model: sonnet
|
|
49
|
+
|
|
50
|
+
workflows:
|
|
51
|
+
- name: default
|
|
52
|
+
onError: continue
|
|
53
|
+
|
|
54
|
+
steps:
|
|
55
|
+
# ── Phase 1: Capture context for agents ───────────────────────────────
|
|
56
|
+
|
|
57
|
+
- name: read-prespawn-block
|
|
58
|
+
type: deterministic
|
|
59
|
+
command: >
|
|
60
|
+
grep -n "Pre-register all interactive\|preflightAgents\|Agent pre-registration"
|
|
61
|
+
packages/sdk/src/workflows/runner.ts | head -10 &&
|
|
62
|
+
echo "---" &&
|
|
63
|
+
awk '/Pre-register all interactive agent steps/,/Agent pre-registration complete/{print NR": "$0}'
|
|
64
|
+
packages/sdk/src/workflows/runner.ts
|
|
65
|
+
captureOutput: true
|
|
66
|
+
failOnError: false
|
|
67
|
+
|
|
68
|
+
- name: read-spawn-comment
|
|
69
|
+
type: deterministic
|
|
70
|
+
command: >
|
|
71
|
+
grep -n "cache.*hit\|preflightAgents\|token cache"
|
|
72
|
+
packages/sdk/src/workflows/runner.ts | head -10
|
|
73
|
+
captureOutput: true
|
|
74
|
+
failOnError: false
|
|
75
|
+
|
|
76
|
+
- name: read-idle-method
|
|
77
|
+
type: deterministic
|
|
78
|
+
command: >
|
|
79
|
+
awk '/private async waitForExitWithIdleNudging/,/^ \}$/{print NR": "$0}'
|
|
80
|
+
packages/sdk/src/workflows/runner.ts | head -80
|
|
81
|
+
captureOutput: true
|
|
82
|
+
failOnError: false
|
|
83
|
+
|
|
84
|
+
- name: read-test-file
|
|
85
|
+
type: deterministic
|
|
86
|
+
dependsOn: [read-prespawn-block]
|
|
87
|
+
command: cat packages/sdk/src/__tests__/idle-nudge.test.ts
|
|
88
|
+
captureOutput: true
|
|
89
|
+
failOnError: false
|
|
90
|
+
|
|
91
|
+
# ── Phase 2: Implement runner.ts changes ──────────────────────────────
|
|
92
|
+
|
|
93
|
+
- name: implement
|
|
94
|
+
type: agent
|
|
95
|
+
agent: implementer
|
|
96
|
+
dependsOn: [read-prespawn-block, read-spawn-comment, read-idle-method]
|
|
97
|
+
task: |
|
|
98
|
+
Make exactly two changes to `packages/sdk/src/workflows/runner.ts`:
|
|
99
|
+
|
|
100
|
+
── Change 1: Remove agent pre-registration block ──
|
|
101
|
+
|
|
102
|
+
Find the block that starts with this comment and delete it entirely:
|
|
103
|
+
// Pre-register all interactive agent steps with Relaycast before execution.
|
|
104
|
+
|
|
105
|
+
The block ends with:
|
|
106
|
+
this.log('Agent pre-registration complete');
|
|
107
|
+
followed by the closing `}` of the if statement.
|
|
108
|
+
|
|
109
|
+
Here is what the block looks like (with line numbers for reference):
|
|
110
|
+
{{steps.read-prespawn-block.output}}
|
|
111
|
+
|
|
112
|
+
Also find and update the stale comment inside `spawnAndWait` that says something
|
|
113
|
+
about "token cache" / "cache hits" / "preflightAgents" — those references are now
|
|
114
|
+
stale. Keep just: "Deterministic name: step name + first 8 chars of run ID."
|
|
115
|
+
|
|
116
|
+
Stale comment location:
|
|
117
|
+
{{steps.read-spawn-comment.output}}
|
|
118
|
+
|
|
119
|
+
── Change 2: Idle = done in waitForExitWithIdleNudging ──
|
|
120
|
+
|
|
121
|
+
Find the `waitForExitWithIdleNudging` method. In the branch where `nudgeConfig` is
|
|
122
|
+
absent, replace the simple `return agent.waitForExit(timeoutMs)` with a race between
|
|
123
|
+
`waitForExit` and `waitForIdle`. If idle wins, release the agent and return 'released'.
|
|
124
|
+
|
|
125
|
+
Current method (with line numbers):
|
|
126
|
+
{{steps.read-idle-method.output}}
|
|
127
|
+
|
|
128
|
+
Replace the no-nudge-config branch with:
|
|
129
|
+
```typescript
|
|
130
|
+
if (!nudgeConfig) {
|
|
131
|
+
// Idle = done: race exit against idle. Whichever fires first completes the step.
|
|
132
|
+
const result = await Promise.race([
|
|
133
|
+
agent.waitForExit(timeoutMs).then((r) => ({ kind: 'exit' as const, result: r })),
|
|
134
|
+
agent.waitForIdle(timeoutMs).then((r) => ({ kind: 'idle' as const, result: r })),
|
|
135
|
+
]);
|
|
136
|
+
if (result.kind === 'idle' && result.result === 'idle') {
|
|
137
|
+
this.log(`[${step.name}] Agent "${agent.name}" went idle — treating as complete`);
|
|
138
|
+
this.postToChannel(`**[${step.name}]** Agent \`${agent.name}\` idle — treating as complete`);
|
|
139
|
+
await agent.release();
|
|
140
|
+
return 'released';
|
|
141
|
+
}
|
|
142
|
+
// Exit won the race, or idle returned 'exited'/'timeout' — pass through.
|
|
143
|
+
return result.result as 'exited' | 'timeout' | 'released';
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Only modify these two things. Do not change anything else.
|
|
148
|
+
verification:
|
|
149
|
+
type: exit_code
|
|
150
|
+
|
|
151
|
+
# ── Phase 3: Update tests ──────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
- name: update-tests
|
|
154
|
+
type: agent
|
|
155
|
+
agent: test-writer
|
|
156
|
+
dependsOn: [implement, read-test-file]
|
|
157
|
+
task: |
|
|
158
|
+
Add new test cases to `packages/sdk/src/__tests__/idle-nudge.test.ts` for the
|
|
159
|
+
new idle=done behavior. Do NOT modify any existing tests — only append new ones.
|
|
160
|
+
|
|
161
|
+
Current test file:
|
|
162
|
+
{{steps.read-test-file.output}}
|
|
163
|
+
|
|
164
|
+
Background on the change:
|
|
165
|
+
- When `idleNudge` config is absent in the swarm config, `waitForExitWithIdleNudging`
|
|
166
|
+
now races `waitForExit` vs `waitForIdle`.
|
|
167
|
+
- If `waitForIdle` resolves with `'idle'` first → `agent.release()` is called and
|
|
168
|
+
the method returns `'released'`.
|
|
169
|
+
- If `waitForExit` resolves first → that result is returned as-is.
|
|
170
|
+
- If `waitForIdle` resolves with `'exited'` or `'timeout'` → exit result wins.
|
|
171
|
+
|
|
172
|
+
The mock infrastructure already has `waitForExitFn` and `waitForIdleFn` that you
|
|
173
|
+
can control. Add a new describe block: `'Idle = done (no idleNudge config)'` with:
|
|
174
|
+
|
|
175
|
+
Test 1 — idle fires first:
|
|
176
|
+
waitForIdleFn resolves 'idle' immediately
|
|
177
|
+
waitForExitFn never resolves (returns a never-settling promise or very long timeout)
|
|
178
|
+
→ workflow run should complete (step succeeds, agent.release() is called)
|
|
179
|
+
|
|
180
|
+
Test 2 — exit fires first:
|
|
181
|
+
waitForExitFn resolves 'exited' immediately
|
|
182
|
+
waitForIdleFn resolves 'timeout' (or never fires before exit)
|
|
183
|
+
→ workflow run should complete, agent.release() NOT called by idle logic
|
|
184
|
+
|
|
185
|
+
Test 3 — both timeout:
|
|
186
|
+
waitForExitFn resolves 'timeout'
|
|
187
|
+
waitForIdleFn resolves 'timeout'
|
|
188
|
+
→ step should fail with a timeout error
|
|
189
|
+
|
|
190
|
+
Use the existing `makeConfig()` and `makeDb()` helpers. Use the existing
|
|
191
|
+
`WorkflowRunner` import pattern already in the file.
|
|
192
|
+
verification:
|
|
193
|
+
type: exit_code
|
|
194
|
+
|
|
195
|
+
# ── Phase 4: Type-check ────────────────────────────────────────────────
|
|
196
|
+
|
|
197
|
+
- name: type-check
|
|
198
|
+
type: deterministic
|
|
199
|
+
dependsOn: [implement, update-tests]
|
|
200
|
+
command: >
|
|
201
|
+
cd packages/sdk &&
|
|
202
|
+
npx tsc --noEmit 2>&1 | tail -30 &&
|
|
203
|
+
echo "TYPE_CHECK_PASSED" || echo "TYPE_CHECK_FAILED"
|
|
204
|
+
captureOutput: true
|
|
205
|
+
failOnError: false
|
|
206
|
+
|
|
207
|
+
# ── Phase 5: Run vitest ────────────────────────────────────────────────
|
|
208
|
+
|
|
209
|
+
- name: run-tests
|
|
210
|
+
type: deterministic
|
|
211
|
+
dependsOn: [type-check]
|
|
212
|
+
command: >-
|
|
213
|
+
cd packages/sdk && { npx vitest run 2>&1; echo "EXIT:$?"; } | tail -80
|
|
214
|
+
captureOutput: true
|
|
215
|
+
failOnError: false
|
|
216
|
+
|
|
217
|
+
# ── Phase 6: Fix failures ──────────────────────────────────────────────
|
|
218
|
+
|
|
219
|
+
- name: fix-if-broken
|
|
220
|
+
type: agent
|
|
221
|
+
agent: fixer
|
|
222
|
+
dependsOn: [run-tests, type-check]
|
|
223
|
+
task: |
|
|
224
|
+
Review the type-check and test results. Fix any failures.
|
|
225
|
+
|
|
226
|
+
Type-check:
|
|
227
|
+
{{steps.type-check.output}}
|
|
228
|
+
|
|
229
|
+
Test run:
|
|
230
|
+
{{steps.run-tests.output}}
|
|
231
|
+
|
|
232
|
+
If both show PASSED / ALL_TESTS_PASSED, output: FIX_DONE:none
|
|
233
|
+
|
|
234
|
+
Otherwise:
|
|
235
|
+
- For TypeScript errors: fix packages/sdk/src/workflows/runner.ts
|
|
236
|
+
- For failing tests: fix packages/sdk/src/__tests__/idle-nudge.test.ts
|
|
237
|
+
- Do NOT change the intended behavior — only fix syntax/type/mock issues
|
|
238
|
+
|
|
239
|
+
verification:
|
|
240
|
+
type: exit_code
|
|
241
|
+
maxIterations: 2
|
|
242
|
+
|
|
243
|
+
# ── Phase 7: Final test run ────────────────────────────────────────────
|
|
244
|
+
|
|
245
|
+
- name: final-tests
|
|
246
|
+
type: deterministic
|
|
247
|
+
dependsOn: [fix-if-broken]
|
|
248
|
+
command: >-
|
|
249
|
+
cd packages/sdk && { npx vitest run 2>&1; echo "EXIT:$?"; } | tail -60
|
|
250
|
+
captureOutput: true
|
|
251
|
+
failOnError: false
|
|
252
|
+
|
|
253
|
+
# ── Phase 8: Capture diff for review ──────────────────────────────────
|
|
254
|
+
|
|
255
|
+
- name: capture-diff
|
|
256
|
+
type: deterministic
|
|
257
|
+
dependsOn: [final-tests]
|
|
258
|
+
command: >
|
|
259
|
+
git diff packages/sdk/src/workflows/runner.ts
|
|
260
|
+
packages/sdk/src/__tests__/idle-nudge.test.ts
|
|
261
|
+
captureOutput: true
|
|
262
|
+
failOnError: false
|
|
263
|
+
|
|
264
|
+
# ── Phase 9: Code review ───────────────────────────────────────────────
|
|
265
|
+
|
|
266
|
+
- name: review
|
|
267
|
+
type: agent
|
|
268
|
+
agent: reviewer
|
|
269
|
+
dependsOn: [capture-diff, final-tests]
|
|
270
|
+
task: |
|
|
271
|
+
Review these changes to WorkflowRunner. Be precise and thorough.
|
|
272
|
+
|
|
273
|
+
Final test result:
|
|
274
|
+
{{steps.final-tests.output}}
|
|
275
|
+
|
|
276
|
+
Diff:
|
|
277
|
+
{{steps.capture-diff.output}}
|
|
278
|
+
|
|
279
|
+
Review checklist:
|
|
280
|
+
1. Pre-registration block is fully gone — no leftover `preflightAgents` calls or
|
|
281
|
+
stale comments referencing "token cache" / "cache hits"
|
|
282
|
+
2. Race logic in `waitForExitWithIdleNudging`: both promises created before awaiting?
|
|
283
|
+
No floating promise leak if exit wins before idle settles?
|
|
284
|
+
3. Edge case: `waitForIdle` returns 'exited' (agent already gone) — does the cast
|
|
285
|
+
`result.result as 'exited' | 'timeout' | 'released'` hold? Check the union types.
|
|
286
|
+
4. Edge case: `waitForIdle` returns 'timeout' and exit won — correct fallthrough?
|
|
287
|
+
5. New tests: do they actually cover all three cases with proper mock wiring?
|
|
288
|
+
6. No unintended changes to the nudge path (idleNudge config present) — that code
|
|
289
|
+
should be unchanged.
|
|
290
|
+
|
|
291
|
+
verification:
|
|
292
|
+
type: exit_code
|
|
293
|
+
|
|
294
|
+
errorHandling:
|
|
295
|
+
strategy: continue # best-effort — don't abort if an optional step fails
|
|
296
|
+
maxRetries: 1
|
|
297
|
+
retryDelayMs: 3000
|
|
298
|
+
notifyChannel: wf-runner-refactor
|
|
299
|
+
|
|
300
|
+
state:
|
|
301
|
+
backend: memory
|
|
302
|
+
ttlMs: 7200000 # 2 hours
|
|
303
|
+
|
|
304
|
+
trajectories:
|
|
305
|
+
enabled: true
|
|
306
|
+
autoDecisions: true
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export const PROTOCOL_VERSION = 1 as const;
|
|
2
2
|
|
|
3
|
-
export type AgentRuntime = 'pty' | '
|
|
3
|
+
export type AgentRuntime = 'pty' | 'headless';
|
|
4
|
+
export type HeadlessProvider = 'claude' | 'opencode';
|
|
4
5
|
|
|
5
6
|
export interface RestartPolicy {
|
|
6
7
|
enabled?: boolean;
|
|
@@ -12,6 +13,7 @@ export interface RestartPolicy {
|
|
|
12
13
|
export interface AgentSpec {
|
|
13
14
|
name: string;
|
|
14
15
|
runtime: AgentRuntime;
|
|
16
|
+
provider?: HeadlessProvider;
|
|
15
17
|
cli?: string;
|
|
16
18
|
args?: string[];
|
|
17
19
|
channels?: string[];
|
|
@@ -112,6 +114,7 @@ export interface BrokerStatus {
|
|
|
112
114
|
agents: Array<{
|
|
113
115
|
name: string;
|
|
114
116
|
runtime: AgentRuntime;
|
|
117
|
+
provider?: HeadlessProvider;
|
|
115
118
|
cli?: string;
|
|
116
119
|
model?: string;
|
|
117
120
|
team?: string;
|
|
@@ -180,6 +183,7 @@ export type BrokerEvent =
|
|
|
180
183
|
kind: 'agent_spawned';
|
|
181
184
|
name: string;
|
|
182
185
|
runtime: AgentRuntime;
|
|
186
|
+
provider?: HeadlessProvider;
|
|
183
187
|
cli?: string;
|
|
184
188
|
model?: string;
|
|
185
189
|
parent?: string;
|
|
@@ -271,6 +275,7 @@ export type BrokerEvent =
|
|
|
271
275
|
kind: 'worker_ready';
|
|
272
276
|
name: string;
|
|
273
277
|
runtime: AgentRuntime;
|
|
278
|
+
provider?: HeadlessProvider;
|
|
274
279
|
cli?: string;
|
|
275
280
|
model?: string;
|
|
276
281
|
}
|
|
@@ -361,7 +366,7 @@ export type BrokerToWorker =
|
|
|
361
366
|
export type WorkerToBroker =
|
|
362
367
|
| {
|
|
363
368
|
type: 'worker_ready';
|
|
364
|
-
payload: { name: string; runtime: AgentRuntime };
|
|
369
|
+
payload: { name: string; runtime: AgentRuntime; provider?: HeadlessProvider };
|
|
365
370
|
}
|
|
366
371
|
| {
|
|
367
372
|
type: 'delivery_ack';
|