agent-relay 3.0.2 → 3.1.1
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-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 +9 -9
- 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/openclaw/README.md +78 -0
- package/packages/openclaw/bin/relay-openclaw.mjs +2 -0
- package/packages/openclaw/bridge/bridge.mjs +305 -0
- package/packages/openclaw/dist/__tests__/gateway-threads.test.d.ts +2 -0
- package/packages/openclaw/dist/__tests__/gateway-threads.test.d.ts.map +1 -0
- package/packages/openclaw/dist/__tests__/gateway-threads.test.js +320 -0
- package/packages/openclaw/dist/__tests__/gateway-threads.test.js.map +1 -0
- package/packages/openclaw/dist/__tests__/naming.test.d.ts +2 -0
- package/packages/openclaw/dist/__tests__/naming.test.d.ts.map +1 -0
- package/packages/openclaw/dist/__tests__/naming.test.js +21 -0
- package/packages/openclaw/dist/__tests__/naming.test.js.map +1 -0
- package/packages/openclaw/dist/__tests__/spawn-manager.test.d.ts +2 -0
- package/packages/openclaw/dist/__tests__/spawn-manager.test.d.ts.map +1 -0
- package/packages/openclaw/dist/__tests__/spawn-manager.test.js +126 -0
- package/packages/openclaw/dist/__tests__/spawn-manager.test.js.map +1 -0
- package/packages/openclaw/dist/auth/converter.d.ts +28 -0
- package/packages/openclaw/dist/auth/converter.d.ts.map +1 -0
- package/packages/openclaw/dist/auth/converter.js +64 -0
- package/packages/openclaw/dist/auth/converter.js.map +1 -0
- package/packages/openclaw/dist/cli.d.ts +2 -0
- package/packages/openclaw/dist/cli.d.ts.map +1 -0
- package/packages/openclaw/dist/cli.js +230 -0
- package/packages/openclaw/dist/cli.js.map +1 -0
- package/packages/openclaw/dist/config.d.ts +27 -0
- package/packages/openclaw/dist/config.d.ts.map +1 -0
- package/packages/openclaw/dist/config.js +97 -0
- package/packages/openclaw/dist/config.js.map +1 -0
- package/packages/openclaw/dist/control.d.ts +22 -0
- package/packages/openclaw/dist/control.d.ts.map +1 -0
- package/packages/openclaw/dist/control.js +58 -0
- package/packages/openclaw/dist/control.js.map +1 -0
- package/packages/openclaw/dist/gateway.d.ts +71 -0
- package/packages/openclaw/dist/gateway.d.ts.map +1 -0
- package/packages/openclaw/dist/gateway.js +785 -0
- package/packages/openclaw/dist/gateway.js.map +1 -0
- package/packages/openclaw/dist/identity/contract.d.ts +11 -0
- package/packages/openclaw/dist/identity/contract.d.ts.map +1 -0
- package/packages/openclaw/dist/identity/contract.js +40 -0
- package/packages/openclaw/dist/identity/contract.js.map +1 -0
- package/packages/openclaw/dist/identity/files.d.ts +33 -0
- package/packages/openclaw/dist/identity/files.d.ts.map +1 -0
- package/packages/openclaw/dist/identity/files.js +145 -0
- package/packages/openclaw/dist/identity/files.js.map +1 -0
- package/packages/openclaw/dist/identity/model.d.ts +11 -0
- package/packages/openclaw/dist/identity/model.d.ts.map +1 -0
- package/packages/openclaw/dist/identity/model.js +28 -0
- package/packages/openclaw/dist/identity/model.js.map +1 -0
- package/packages/openclaw/dist/identity/naming.d.ts +5 -0
- package/packages/openclaw/dist/identity/naming.d.ts.map +1 -0
- package/packages/openclaw/dist/identity/naming.js +7 -0
- package/packages/openclaw/dist/identity/naming.js.map +1 -0
- package/packages/openclaw/dist/index.d.ts +20 -0
- package/packages/openclaw/dist/index.d.ts.map +1 -0
- package/packages/openclaw/dist/index.js +27 -0
- package/packages/openclaw/dist/index.js.map +1 -0
- package/packages/openclaw/dist/inject.d.ts +14 -0
- package/packages/openclaw/dist/inject.d.ts.map +1 -0
- package/packages/openclaw/dist/inject.js +66 -0
- package/packages/openclaw/dist/inject.js.map +1 -0
- package/packages/openclaw/dist/mcp/server.d.ts +8 -0
- package/packages/openclaw/dist/mcp/server.d.ts.map +1 -0
- package/packages/openclaw/dist/mcp/server.js +105 -0
- package/packages/openclaw/dist/mcp/server.js.map +1 -0
- package/packages/openclaw/dist/mcp/tools.d.ts +17 -0
- package/packages/openclaw/dist/mcp/tools.d.ts.map +1 -0
- package/packages/openclaw/dist/mcp/tools.js +145 -0
- package/packages/openclaw/dist/mcp/tools.js.map +1 -0
- package/packages/openclaw/dist/runtime/openclaw-config.d.ts +20 -0
- package/packages/openclaw/dist/runtime/openclaw-config.d.ts.map +1 -0
- package/packages/openclaw/dist/runtime/openclaw-config.js +50 -0
- package/packages/openclaw/dist/runtime/openclaw-config.js.map +1 -0
- package/packages/openclaw/dist/runtime/patch.d.ts +24 -0
- package/packages/openclaw/dist/runtime/patch.d.ts.map +1 -0
- package/packages/openclaw/dist/runtime/patch.js +92 -0
- package/packages/openclaw/dist/runtime/patch.js.map +1 -0
- package/packages/openclaw/dist/runtime/setup.d.ts +26 -0
- package/packages/openclaw/dist/runtime/setup.d.ts.map +1 -0
- package/packages/openclaw/dist/runtime/setup.js +58 -0
- package/packages/openclaw/dist/runtime/setup.js.map +1 -0
- package/packages/openclaw/dist/setup.d.ts +29 -0
- package/packages/openclaw/dist/setup.d.ts.map +1 -0
- package/packages/openclaw/dist/setup.js +300 -0
- package/packages/openclaw/dist/setup.js.map +1 -0
- package/packages/openclaw/dist/spawn/docker.d.ts +58 -0
- package/packages/openclaw/dist/spawn/docker.d.ts.map +1 -0
- package/packages/openclaw/dist/spawn/docker.js +222 -0
- package/packages/openclaw/dist/spawn/docker.js.map +1 -0
- package/packages/openclaw/dist/spawn/manager.d.ts +45 -0
- package/packages/openclaw/dist/spawn/manager.d.ts.map +1 -0
- package/packages/openclaw/dist/spawn/manager.js +140 -0
- package/packages/openclaw/dist/spawn/manager.js.map +1 -0
- package/packages/openclaw/dist/spawn/process.d.ts +16 -0
- package/packages/openclaw/dist/spawn/process.d.ts.map +1 -0
- package/packages/openclaw/dist/spawn/process.js +241 -0
- package/packages/openclaw/dist/spawn/process.js.map +1 -0
- package/packages/openclaw/dist/spawn/types.d.ts +42 -0
- package/packages/openclaw/dist/spawn/types.d.ts.map +1 -0
- package/packages/openclaw/dist/spawn/types.js +2 -0
- package/packages/openclaw/dist/spawn/types.js.map +1 -0
- package/packages/openclaw/dist/types.d.ts +37 -0
- package/packages/openclaw/dist/types.d.ts.map +1 -0
- package/packages/openclaw/dist/types.js +2 -0
- package/packages/openclaw/dist/types.js.map +1 -0
- package/packages/openclaw/package.json +63 -0
- package/packages/openclaw/skill/SKILL.md +194 -0
- package/packages/openclaw/src/__tests__/gateway-threads.test.ts +384 -0
- package/packages/openclaw/src/__tests__/naming.test.ts +24 -0
- package/packages/openclaw/src/__tests__/spawn-manager.test.ts +152 -0
- package/packages/openclaw/src/auth/converter.ts +90 -0
- package/packages/openclaw/src/cli.ts +269 -0
- package/packages/openclaw/src/config.ts +124 -0
- package/packages/openclaw/src/control.ts +100 -0
- package/packages/openclaw/src/gateway.ts +941 -0
- package/packages/openclaw/src/identity/contract.ts +44 -0
- package/packages/openclaw/src/identity/files.ts +198 -0
- package/packages/openclaw/src/identity/model.ts +27 -0
- package/packages/openclaw/src/identity/naming.ts +6 -0
- package/packages/openclaw/src/index.ts +59 -0
- package/packages/openclaw/src/inject.ts +77 -0
- package/packages/openclaw/src/mcp/server.ts +121 -0
- package/packages/openclaw/src/mcp/tools.ts +174 -0
- package/packages/openclaw/src/runtime/openclaw-config.ts +64 -0
- package/packages/openclaw/src/runtime/patch.ts +103 -0
- package/packages/openclaw/src/runtime/setup.ts +89 -0
- package/packages/openclaw/src/setup.ts +336 -0
- package/packages/openclaw/src/spawn/docker.ts +261 -0
- package/packages/openclaw/src/spawn/manager.ts +181 -0
- package/packages/openclaw/src/spawn/process.ts +272 -0
- package/packages/openclaw/src/spawn/types.ts +43 -0
- package/packages/openclaw/src/types.ts +38 -0
- package/packages/openclaw/templates/SOUL.md.template +34 -0
- package/packages/openclaw/tsconfig.json +12 -0
- 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/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/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,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Idle nudge detection and escalation tests.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Covers both modes:
|
|
5
|
+
* - No idleNudge config: idle is treated as completion.
|
|
6
|
+
* - idleNudge config enabled: waitForExit timeout drives nudges/escalation.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
@@ -18,7 +19,7 @@ const mockFetch = vi.fn().mockResolvedValue({
|
|
|
18
19
|
});
|
|
19
20
|
vi.stubGlobal('fetch', mockFetch);
|
|
20
21
|
|
|
21
|
-
// ── Mock RelayCast SDK
|
|
22
|
+
// ── Mock RelayCast SDK ────────────────────────────────────────────────────────
|
|
22
23
|
|
|
23
24
|
const mockRelaycastAgent = {
|
|
24
25
|
send: vi.fn().mockResolvedValue(undefined),
|
|
@@ -52,9 +53,8 @@ vi.mock('@relaycast/sdk', () => ({
|
|
|
52
53
|
RelayError: MockRelayError,
|
|
53
54
|
}));
|
|
54
55
|
|
|
55
|
-
// ── Mock AgentRelay
|
|
56
|
+
// ── Mock AgentRelay ───────────────────────────────────────────────────────────
|
|
56
57
|
|
|
57
|
-
/** Control how waitForExit / waitForIdle resolve in each test. */
|
|
58
58
|
let waitForExitFn: (ms?: number) => Promise<'exited' | 'timeout' | 'released'>;
|
|
59
59
|
let waitForIdleFn: (ms?: number) => Promise<'idle' | 'timeout' | 'exited'>;
|
|
60
60
|
|
|
@@ -84,15 +84,15 @@ vi.mock('../relay.js', () => ({
|
|
|
84
84
|
spawnPty: vi.fn().mockResolvedValue(mockAgent),
|
|
85
85
|
human: vi.fn().mockReturnValue(mockHuman),
|
|
86
86
|
shutdown: vi.fn().mockResolvedValue(undefined),
|
|
87
|
+
onBrokerStderr: vi.fn().mockReturnValue(() => {}),
|
|
87
88
|
onWorkerOutput: null,
|
|
88
89
|
listAgentsRaw: vi.fn().mockResolvedValue([]),
|
|
89
90
|
})),
|
|
90
91
|
}));
|
|
91
92
|
|
|
92
|
-
// Import after mocking
|
|
93
93
|
const { WorkflowRunner } = await import('../workflows/runner.js');
|
|
94
94
|
|
|
95
|
-
// ── Test fixtures
|
|
95
|
+
// ── Test fixtures ─────────────────────────────────────────────────────────────
|
|
96
96
|
|
|
97
97
|
function makeDb(): WorkflowDb {
|
|
98
98
|
const runs = new Map<string, WorkflowRunRow>();
|
|
@@ -135,11 +135,14 @@ function makeConfig(overrides: Partial<RelayYamlConfig> = {}): RelayYamlConfig {
|
|
|
135
135
|
steps: [{ name: 'step-1', agent: 'agent-a', task: 'Do step 1' }],
|
|
136
136
|
},
|
|
137
137
|
],
|
|
138
|
+
trajectories: false,
|
|
138
139
|
...overrides,
|
|
139
140
|
};
|
|
140
141
|
}
|
|
141
142
|
|
|
142
|
-
|
|
143
|
+
function never<T>(): Promise<T> {
|
|
144
|
+
return new Promise(() => {});
|
|
145
|
+
}
|
|
143
146
|
|
|
144
147
|
describe('Idle Nudge Detection', () => {
|
|
145
148
|
let db: WorkflowDb;
|
|
@@ -150,289 +153,234 @@ describe('Idle Nudge Detection', () => {
|
|
|
150
153
|
db = makeDb();
|
|
151
154
|
runner = new WorkflowRunner({ db, workspaceId: 'ws-test' });
|
|
152
155
|
|
|
153
|
-
// Default: agent exits immediately (no idle)
|
|
154
156
|
waitForExitFn = vi.fn().mockResolvedValue('exited');
|
|
155
157
|
waitForIdleFn = vi.fn().mockResolvedValue('timeout');
|
|
156
158
|
});
|
|
157
159
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
160
|
+
describe('idleNudge enabled', () => {
|
|
161
|
+
it('sends direct nudge then completes when exit follows', async () => {
|
|
162
|
+
let exitCallCount = 0;
|
|
163
|
+
waitForExitFn = vi.fn().mockImplementation(() => {
|
|
164
|
+
exitCallCount++;
|
|
165
|
+
return Promise.resolve(exitCallCount === 1 ? 'timeout' : 'exited');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const run = await runner.execute(
|
|
169
|
+
makeConfig({
|
|
170
|
+
swarm: {
|
|
171
|
+
pattern: 'mesh',
|
|
172
|
+
idleNudge: { nudgeAfterMs: 100, escalateAfterMs: 100, maxNudges: 1 },
|
|
173
|
+
},
|
|
174
|
+
}),
|
|
175
|
+
'default'
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
expect(run.status).toBe('completed');
|
|
179
|
+
expect(mockHumanSendMessage).toHaveBeenCalledTimes(1);
|
|
180
|
+
expect(mockHumanSendMessage).toHaveBeenCalledWith(
|
|
181
|
+
expect.objectContaining({
|
|
182
|
+
to: 'test-agent-abc',
|
|
183
|
+
text: expect.stringContaining('/exit'),
|
|
184
|
+
})
|
|
185
|
+
);
|
|
186
|
+
expect(mockRelease).not.toHaveBeenCalled();
|
|
187
|
+
expect(waitForIdleFn).not.toHaveBeenCalled();
|
|
185
188
|
});
|
|
186
189
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
190
|
+
it('uses hub fallback behavior without failing when hub is not active', async () => {
|
|
191
|
+
let exitCallCount = 0;
|
|
192
|
+
waitForExitFn = vi.fn().mockImplementation(() => {
|
|
193
|
+
exitCallCount++;
|
|
194
|
+
return Promise.resolve(exitCallCount === 1 ? 'timeout' : 'exited');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const run = await runner.execute(
|
|
198
|
+
makeConfig({
|
|
199
|
+
swarm: {
|
|
200
|
+
pattern: 'hub-spoke',
|
|
201
|
+
idleNudge: { nudgeAfterMs: 100, escalateAfterMs: 100, maxNudges: 1 },
|
|
202
|
+
},
|
|
203
|
+
agents: [
|
|
204
|
+
{ name: 'lead', cli: 'claude', role: 'Lead coordinator' },
|
|
205
|
+
{ name: 'worker', cli: 'claude' },
|
|
206
|
+
],
|
|
207
|
+
workflows: [
|
|
208
|
+
{
|
|
209
|
+
name: 'default',
|
|
210
|
+
steps: [{ name: 'step-1', agent: 'worker', task: 'Do work' }],
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
}),
|
|
214
|
+
'default'
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
expect(run.status).toBe('completed');
|
|
218
|
+
expect(mockHumanSendMessage).toHaveBeenCalledTimes(1);
|
|
192
219
|
});
|
|
193
220
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (idleCallCount === 1) return Promise.resolve('idle');
|
|
212
|
-
return Promise.resolve('exited');
|
|
213
|
-
});
|
|
214
|
-
let exitCallCount = 0;
|
|
215
|
-
waitForExitFn = vi.fn().mockImplementation(() => {
|
|
216
|
-
exitCallCount++;
|
|
217
|
-
if (exitCallCount === 1) return new Promise(() => {});
|
|
218
|
-
return Promise.resolve('exited');
|
|
221
|
+
it('force-releases after maxNudges is exceeded', async () => {
|
|
222
|
+
waitForExitFn = vi.fn().mockResolvedValue('timeout');
|
|
223
|
+
|
|
224
|
+
const run = await runner.execute(
|
|
225
|
+
makeConfig({
|
|
226
|
+
swarm: {
|
|
227
|
+
pattern: 'dag',
|
|
228
|
+
idleNudge: { nudgeAfterMs: 50, escalateAfterMs: 50, maxNudges: 1 },
|
|
229
|
+
},
|
|
230
|
+
}),
|
|
231
|
+
'default'
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
expect(run.status).toBe('completed');
|
|
235
|
+
expect(mockHumanSendMessage).toHaveBeenCalledTimes(1);
|
|
236
|
+
expect(mockRelease).toHaveBeenCalledTimes(1);
|
|
237
|
+
expect(waitForIdleFn).not.toHaveBeenCalled();
|
|
219
238
|
});
|
|
220
239
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
240
|
+
it('force-releases after multiple nudges', async () => {
|
|
241
|
+
waitForExitFn = vi.fn().mockResolvedValue('timeout');
|
|
242
|
+
|
|
243
|
+
const run = await runner.execute(
|
|
244
|
+
makeConfig({
|
|
245
|
+
swarm: {
|
|
246
|
+
pattern: 'dag',
|
|
247
|
+
idleNudge: { nudgeAfterMs: 50, escalateAfterMs: 50, maxNudges: 3 },
|
|
248
|
+
},
|
|
249
|
+
}),
|
|
250
|
+
'default'
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
expect(run.status).toBe('completed');
|
|
254
|
+
expect(mockHumanSendMessage).toHaveBeenCalledTimes(3);
|
|
255
|
+
expect(mockRelease).toHaveBeenCalledTimes(1);
|
|
236
256
|
});
|
|
237
257
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
const config = makeConfig({
|
|
260
|
-
swarm: {
|
|
261
|
-
pattern: 'hub-spoke',
|
|
262
|
-
idleNudge: { nudgeAfterMs: 100, escalateAfterMs: 100, maxNudges: 1 },
|
|
263
|
-
},
|
|
264
|
-
agents: [{ name: 'lead', cli: 'claude', role: 'Lead coordinator' }],
|
|
265
|
-
workflows: [
|
|
266
|
-
{
|
|
267
|
-
name: 'default',
|
|
268
|
-
steps: [{ name: 'step-1', agent: 'lead', task: 'Coordinate work' }],
|
|
269
|
-
},
|
|
270
|
-
],
|
|
258
|
+
it('emits step:nudged event', async () => {
|
|
259
|
+
let exitCallCount = 0;
|
|
260
|
+
waitForExitFn = vi.fn().mockImplementation(() => {
|
|
261
|
+
exitCallCount++;
|
|
262
|
+
return Promise.resolve(exitCallCount === 1 ? 'timeout' : 'exited');
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
const events: Array<{ type: string }> = [];
|
|
266
|
+
runner.on((event) => events.push(event));
|
|
267
|
+
|
|
268
|
+
await runner.execute(
|
|
269
|
+
makeConfig({
|
|
270
|
+
swarm: {
|
|
271
|
+
pattern: 'dag',
|
|
272
|
+
idleNudge: { nudgeAfterMs: 50, escalateAfterMs: 50, maxNudges: 1 },
|
|
273
|
+
},
|
|
274
|
+
}),
|
|
275
|
+
'default'
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
expect(events.filter((e) => e.type === 'step:nudged')).toHaveLength(1);
|
|
271
279
|
});
|
|
272
280
|
|
|
273
|
-
|
|
281
|
+
it('emits step:force-released event on escalation', async () => {
|
|
282
|
+
waitForExitFn = vi.fn().mockResolvedValue('timeout');
|
|
274
283
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
expect(mockHumanSendMessage).toHaveBeenCalledTimes(1);
|
|
278
|
-
});
|
|
284
|
+
const events: Array<{ type: string }> = [];
|
|
285
|
+
runner.on((event) => events.push(event));
|
|
279
286
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
287
|
+
await runner.execute(
|
|
288
|
+
makeConfig({
|
|
289
|
+
swarm: {
|
|
290
|
+
pattern: 'dag',
|
|
291
|
+
idleNudge: { nudgeAfterMs: 50, escalateAfterMs: 50, maxNudges: 1 },
|
|
292
|
+
},
|
|
293
|
+
}),
|
|
294
|
+
'default'
|
|
295
|
+
);
|
|
284
296
|
|
|
285
|
-
|
|
286
|
-
swarm: {
|
|
287
|
-
pattern: 'dag',
|
|
288
|
-
idleNudge: { nudgeAfterMs: 50, escalateAfterMs: 50, maxNudges: 1 },
|
|
289
|
-
},
|
|
297
|
+
expect(events.filter((e) => e.type === 'step:force-released')).toHaveLength(1);
|
|
290
298
|
});
|
|
291
299
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
300
|
+
it('uses defaults when idleNudge is empty object', async () => {
|
|
301
|
+
waitForExitFn = vi.fn().mockResolvedValue('timeout');
|
|
302
|
+
|
|
303
|
+
const run = await runner.execute(
|
|
304
|
+
makeConfig({
|
|
305
|
+
swarm: {
|
|
306
|
+
pattern: 'dag',
|
|
307
|
+
idleNudge: {},
|
|
308
|
+
},
|
|
309
|
+
}),
|
|
310
|
+
'default'
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
expect(run.status).toBe('completed');
|
|
314
|
+
// default maxNudges is 1
|
|
315
|
+
expect(mockHumanSendMessage).toHaveBeenCalledTimes(1);
|
|
316
|
+
expect(mockRelease).toHaveBeenCalledTimes(1);
|
|
309
317
|
});
|
|
310
318
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
319
|
+
it('respects overall timeout during nudge loop', async () => {
|
|
320
|
+
// Each waitForExit call takes 100ms (real timer), but the overall timeout
|
|
321
|
+
// is only 80ms. After the first call (~100ms elapsed), the loop detects
|
|
322
|
+
// that remaining time is exhausted and returns 'timeout'.
|
|
323
|
+
waitForExitFn = vi
|
|
324
|
+
.fn()
|
|
325
|
+
.mockImplementation(
|
|
326
|
+
() => new Promise<'timeout'>((resolve) => setTimeout(() => resolve('timeout'), 100))
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
const run = await runner.execute(
|
|
330
|
+
makeConfig({
|
|
331
|
+
swarm: {
|
|
332
|
+
pattern: 'dag',
|
|
333
|
+
idleNudge: { nudgeAfterMs: 10, escalateAfterMs: 10, maxNudges: 10 },
|
|
334
|
+
},
|
|
335
|
+
agents: [{ name: 'agent-a', cli: 'claude', constraints: { timeoutMs: 80 } }],
|
|
336
|
+
}),
|
|
337
|
+
'default'
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
expect(run.status).toBe('failed');
|
|
341
|
+
expect(run.error).toContain('timed out');
|
|
331
342
|
});
|
|
332
|
-
|
|
333
|
-
// The step has a short timeout — should not loop forever
|
|
334
|
-
const run = await runner.execute(config, 'default');
|
|
335
|
-
// Either completed (force-released) or failed (timeout) — either is acceptable
|
|
336
|
-
expect(['completed', 'failed']).toContain(run.status);
|
|
337
343
|
});
|
|
338
344
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
if (idleCallCount === 1) return Promise.resolve('idle');
|
|
344
|
-
return Promise.resolve('exited');
|
|
345
|
-
});
|
|
346
|
-
let exitCallCount = 0;
|
|
347
|
-
waitForExitFn = vi.fn().mockImplementation(() => {
|
|
348
|
-
exitCallCount++;
|
|
349
|
-
if (exitCallCount === 1) return new Promise(() => {});
|
|
350
|
-
return Promise.resolve('exited');
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
const events: Array<{ type: string }> = [];
|
|
354
|
-
const config = makeConfig({
|
|
355
|
-
swarm: {
|
|
356
|
-
pattern: 'dag',
|
|
357
|
-
idleNudge: { nudgeAfterMs: 50, escalateAfterMs: 50, maxNudges: 1 },
|
|
358
|
-
},
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
runner.on((event) => events.push(event));
|
|
362
|
-
await runner.execute(config, 'default');
|
|
363
|
-
|
|
364
|
-
const nudgeEvents = events.filter((e) => e.type === 'step:nudged');
|
|
365
|
-
expect(nudgeEvents).toHaveLength(1);
|
|
366
|
-
});
|
|
345
|
+
describe('Idle = done (no idleNudge config)', () => {
|
|
346
|
+
it('idle fires first: releases agent and completes step', async () => {
|
|
347
|
+
waitForIdleFn = vi.fn().mockResolvedValue('idle');
|
|
348
|
+
waitForExitFn = vi.fn().mockImplementation(() => never());
|
|
367
349
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
waitForExitFn = vi.fn().mockImplementation(() => new Promise(() => {}));
|
|
350
|
+
const run = await runner.execute(makeConfig(), 'default');
|
|
351
|
+
const steps = await db.getStepsByRunId(run.id);
|
|
371
352
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
idleNudge: { nudgeAfterMs: 50, escalateAfterMs: 50, maxNudges: 1 },
|
|
377
|
-
},
|
|
353
|
+
expect(run.status).toBe('completed');
|
|
354
|
+
expect(steps).toHaveLength(1);
|
|
355
|
+
expect(steps[0]?.status).toBe('completed');
|
|
356
|
+
expect(mockRelease).toHaveBeenCalledTimes(1);
|
|
378
357
|
});
|
|
379
358
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
const forceReleasedEvents = events.filter((e) => e.type === 'step:force-released');
|
|
384
|
-
expect(forceReleasedEvents).toHaveLength(1);
|
|
385
|
-
});
|
|
359
|
+
it('exit fires first: completes without idle-based release', async () => {
|
|
360
|
+
waitForExitFn = vi.fn().mockResolvedValue('exited');
|
|
361
|
+
waitForIdleFn = vi.fn().mockResolvedValue('timeout');
|
|
386
362
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
// First idle → nudge. Second idle → escalate (maxNudges: 1)
|
|
390
|
-
waitForIdleFn = vi.fn().mockImplementation(() => {
|
|
391
|
-
idleCallCount++;
|
|
392
|
-
return Promise.resolve('idle');
|
|
393
|
-
});
|
|
394
|
-
waitForExitFn = vi.fn().mockImplementation(() => new Promise(() => {}));
|
|
363
|
+
const run = await runner.execute(makeConfig(), 'default');
|
|
364
|
+
const steps = await db.getStepsByRunId(run.id);
|
|
395
365
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
},
|
|
366
|
+
expect(run.status).toBe('completed');
|
|
367
|
+
expect(steps).toHaveLength(1);
|
|
368
|
+
expect(steps[0]?.status).toBe('completed');
|
|
369
|
+
expect(mockRelease).not.toHaveBeenCalled();
|
|
401
370
|
});
|
|
402
371
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
// 1 nudge sent, then force-released
|
|
407
|
-
expect(mockHumanSendMessage).toHaveBeenCalledTimes(1);
|
|
408
|
-
expect(mockRelease).toHaveBeenCalledTimes(1);
|
|
409
|
-
});
|
|
372
|
+
it('both timeout: fails step with timeout error', async () => {
|
|
373
|
+
waitForExitFn = vi.fn().mockResolvedValue('timeout');
|
|
374
|
+
waitForIdleFn = vi.fn().mockResolvedValue('timeout');
|
|
410
375
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
waitForIdleFn = vi.fn().mockImplementation(() => {
|
|
414
|
-
idleCallCount++;
|
|
415
|
-
if (idleCallCount === 1) return Promise.resolve('idle');
|
|
416
|
-
return Promise.resolve('exited');
|
|
417
|
-
});
|
|
418
|
-
let exitCallCount = 0;
|
|
419
|
-
waitForExitFn = vi.fn().mockImplementation(() => {
|
|
420
|
-
exitCallCount++;
|
|
421
|
-
if (exitCallCount === 1) return new Promise(() => {});
|
|
422
|
-
return Promise.resolve('exited');
|
|
423
|
-
});
|
|
376
|
+
const run = await runner.execute(makeConfig(), 'default');
|
|
377
|
+
const steps = await db.getStepsByRunId(run.id);
|
|
424
378
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
379
|
+
expect(run.status).toBe('failed');
|
|
380
|
+
expect(run.error).toContain('timed out');
|
|
381
|
+
expect(steps).toHaveLength(1);
|
|
382
|
+
expect(steps[0]?.status).toBe('failed');
|
|
383
|
+
expect(steps[0]?.error).toContain('timed out');
|
|
430
384
|
});
|
|
431
|
-
|
|
432
|
-
const run = await runner.execute(config, 'default');
|
|
433
|
-
|
|
434
|
-
expect(run.status).toBe('completed');
|
|
435
|
-
// Default maxNudges: 1, so one nudge should have been sent
|
|
436
|
-
expect(mockHumanSendMessage).toHaveBeenCalledTimes(1);
|
|
437
385
|
});
|
|
438
386
|
});
|
|
@@ -103,7 +103,7 @@ test('sdk can start broker and manage agent lifecycle', async (t) => {
|
|
|
103
103
|
}
|
|
104
104
|
});
|
|
105
105
|
|
|
106
|
-
test('sdk can spawn and release
|
|
106
|
+
test('sdk can spawn and release provider worker with transport override', async (t) => {
|
|
107
107
|
const binaryPath = resolveBinaryPath();
|
|
108
108
|
if (!fs.existsSync(binaryPath)) {
|
|
109
109
|
t.skip(`agent-relay-broker binary not found at ${binaryPath}`);
|
|
@@ -124,17 +124,18 @@ test('sdk can spawn and release headless claude worker', async (t) => {
|
|
|
124
124
|
});
|
|
125
125
|
|
|
126
126
|
try {
|
|
127
|
-
const spawned = await client.
|
|
127
|
+
const spawned = await client.spawnClaude({
|
|
128
128
|
name: spawnedName,
|
|
129
|
+
transport: 'headless',
|
|
129
130
|
channels: ['general'],
|
|
130
131
|
});
|
|
131
132
|
assert.equal(spawned.name, spawnedName);
|
|
132
|
-
assert.equal(spawned.runtime, '
|
|
133
|
+
assert.equal(spawned.runtime, 'headless');
|
|
133
134
|
|
|
134
135
|
const agentsAfterSpawn = await client.listAgents();
|
|
135
136
|
const spawnedAgent = agentsAfterSpawn.find((agent) => agent.name === spawnedName);
|
|
136
137
|
assert.ok(spawnedAgent, 'spawned headless agent should be present in listAgents()');
|
|
137
|
-
assert.equal(spawnedAgent?.runtime, '
|
|
138
|
+
assert.equal(spawnedAgent?.runtime, 'headless');
|
|
138
139
|
|
|
139
140
|
const released = await client.release(spawnedName);
|
|
140
141
|
assert.equal(released.name, spawnedName);
|