agent-relay 13.2.0 → 13.4.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.
Files changed (55) hide show
  1. package/README.md +60 -0
  2. package/dist/cli/agent-relay-mcp.d.ts.map +1 -1
  3. package/dist/cli/agent-relay-mcp.js +131 -10
  4. package/dist/cli/agent-relay-mcp.js.map +1 -1
  5. package/dist/cli/commands/agent.d.ts.map +1 -1
  6. package/dist/cli/commands/agent.js +24 -19
  7. package/dist/cli/commands/agent.js.map +1 -1
  8. package/dist/cli/commands/fleet.d.ts.map +1 -1
  9. package/dist/cli/commands/fleet.js +183 -39
  10. package/dist/cli/commands/fleet.js.map +1 -1
  11. package/dist/cli/commands/integration.d.ts.map +1 -1
  12. package/dist/cli/commands/integration.js +81 -7
  13. package/dist/cli/commands/integration.js.map +1 -1
  14. package/dist/cli/commands/local-agent.d.ts.map +1 -1
  15. package/dist/cli/commands/local-agent.js +22 -3
  16. package/dist/cli/commands/local-agent.js.map +1 -1
  17. package/dist/cli/commands/message.d.ts.map +1 -1
  18. package/dist/cli/commands/message.js +111 -29
  19. package/dist/cli/commands/message.js.map +1 -1
  20. package/dist/cli/lib/agent-registration.d.ts +2 -0
  21. package/dist/cli/lib/agent-registration.d.ts.map +1 -1
  22. package/dist/cli/lib/agent-registration.js +2 -2
  23. package/dist/cli/lib/agent-registration.js.map +1 -1
  24. package/dist/cli/lib/agent-removal.d.ts +30 -0
  25. package/dist/cli/lib/agent-removal.d.ts.map +1 -0
  26. package/dist/cli/lib/agent-removal.js +73 -0
  27. package/dist/cli/lib/agent-removal.js.map +1 -0
  28. package/dist/cli/lib/attachments.d.ts +31 -0
  29. package/dist/cli/lib/attachments.d.ts.map +1 -0
  30. package/dist/cli/lib/attachments.js +239 -0
  31. package/dist/cli/lib/attachments.js.map +1 -0
  32. package/dist/cli/lib/fleet-live-agents.d.ts +17 -0
  33. package/dist/cli/lib/fleet-live-agents.d.ts.map +1 -1
  34. package/dist/cli/lib/fleet-live-agents.js +23 -0
  35. package/dist/cli/lib/fleet-live-agents.js.map +1 -1
  36. package/dist/cli/lib/sdk-command.d.ts.map +1 -1
  37. package/dist/cli/lib/sdk-command.js +8 -2
  38. package/dist/cli/lib/sdk-command.js.map +1 -1
  39. package/dist/cli/lib/spawn-liveness.d.ts +50 -0
  40. package/dist/cli/lib/spawn-liveness.d.ts.map +1 -0
  41. package/dist/cli/lib/spawn-liveness.js +184 -0
  42. package/dist/cli/lib/spawn-liveness.js.map +1 -0
  43. package/dist/cli/lib/task-input.d.ts +26 -0
  44. package/dist/cli/lib/task-input.d.ts.map +1 -0
  45. package/dist/cli/lib/task-input.js +102 -0
  46. package/dist/cli/lib/task-input.js.map +1 -0
  47. package/dist/cli/mcp/messaging-tools.d.ts.map +1 -1
  48. package/dist/cli/mcp/messaging-tools.js +136 -34
  49. package/dist/cli/mcp/messaging-tools.js.map +1 -1
  50. package/dist/cli/mcp/request-replay.d.ts +21 -9
  51. package/dist/cli/mcp/request-replay.d.ts.map +1 -1
  52. package/dist/cli/mcp/request-replay.js +58 -9
  53. package/dist/cli/mcp/request-replay.js.map +1 -1
  54. package/dist/index.cjs +200 -6
  55. package/package.json +10 -10
@@ -1,15 +1,7 @@
1
1
  import { addSdkOptions, printJson, runSdk, sdkOptionsFromOpts, withSdkDefaults, } from '../lib/sdk-command.js';
2
- import { RelayError } from '@agent-relay/sdk';
2
+ import { AgentRemovalPendingError, isNotFoundError, parseRemovalWaitTimeout, waitForAgentRemoval, } from '../lib/agent-removal.js';
3
3
  import { withAgentRegistrationDeadline, withDeadline } from '../lib/agent-registration.js';
4
4
  import { attributableReleaseReason } from '../lib/release-reason.js';
5
- function isNotFoundError(error) {
6
- if (error instanceof RelayError)
7
- return error.code === 'not_found' || error.statusCode === 404;
8
- const statusCode = error && typeof error === 'object'
9
- ? (error.statusCode ?? error.status)
10
- : undefined;
11
- return Number(statusCode) === 404;
12
- }
13
5
  function withAgentDefaults(overrides = {}) {
14
6
  return {
15
7
  ...withSdkDefaults(overrides),
@@ -95,21 +87,34 @@ export function registerAgentCommands(program, overrides = {}) {
95
87
  .command('remove')
96
88
  .description('Remove an agent while preserving attributed message history')
97
89
  .argument('<name>', 'Agent name')
98
- .option('--reason <reason>', 'Removal reason')).action(async (name, opts) => {
90
+ .option('--reason <reason>', 'Removal reason')
91
+ .option('--wait', 'Wait until the registration is cleared', false)
92
+ .option('--no-wait', 'Return after the removal is accepted')
93
+ .option('--wait-timeout <ms>', 'Registration clearance timeout', '30000')).action(async (name, opts) => {
99
94
  await runSdk(deps, async () => {
100
95
  const relay = deps.createWorkspaceRelay(sdkOptionsFromOpts(opts));
101
96
  const reason = attributableReleaseReason(opts.reason, process.env.RELAY_AGENT_NAME ?? 'agent-relay CLI', 'agent removed');
97
+ const waitTimeoutMs = opts.wait === true ? parseRemovalWaitTimeout(opts.waitTimeout) : undefined;
102
98
  const result = await relay.workspace.release({ name, reason, deleteAgent: true });
103
- // The release endpoint acknowledges an async action invocation — a
104
- // resolved promise means the request was accepted, not that the
105
- // deletion has finished. Only claim "Removed" once the invocation
106
- // itself reports completion; otherwise say what actually happened.
107
- if (result.status === 'completed') {
108
- deps.log(`Removed agent ${name}.`);
109
- }
110
- else {
111
- deps.log(`Removal of agent ${name} was initiated (status: ${result.status ?? 'pending'}) and is processed asynchronously.`);
99
+ if (waitTimeoutMs !== undefined) {
100
+ const removal = await waitForAgentRemoval({
101
+ name,
102
+ getAgent: (name) => relay.agents.get(name),
103
+ listAgents: () => relay.agents.list(),
104
+ timeoutMs: waitTimeoutMs,
105
+ });
106
+ if (removal.cleared) {
107
+ deps.log(`Removed agent ${name}.`);
108
+ return;
109
+ }
110
+ if (removal.observedPresent)
111
+ throw new AgentRemovalPendingError(name);
112
+ // Unverifiable clearance keeps the asynchronous acknowledgement.
113
+ deps.error(`Could not verify removal: ${removal.readError ?? 'registration unavailable'}`);
114
+ deps.log(`Removal of agent ${name} was initiated (status: ${result.status ?? 'pending'}) and is processed asynchronously; registration clearance could not be verified.`);
115
+ return;
112
116
  }
117
+ deps.log(`Removal of agent ${name} was initiated (status: ${result.status ?? 'pending'}) and is processed asynchronously. Use --wait to verify the name is reusable.`);
113
118
  });
114
119
  });
115
120
  }
@@ -1 +1 @@
1
- {"version":3,"file":"agent.js","sourceRoot":"","sources":["../../../src/cli/commands/agent.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../../../src/cli/commands/agent.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"fleet.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/fleet.ts"],"names":[],"mappings":"AAGA,OAAO,EAAwB,KAAK,OAAO,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAIL,uBAAuB,EACvB,uBAAuB,EACvB,mCAAmC,EACnC,qBAAqB,EAKtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAGL,KAAK,wBAAwB,EAE9B,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAkBhE,OAAO,EAAE,8BAA8B,EAAE,MAAM,kBAAkB,CAAC;AAClE,OAAO,EAAE,wBAAwB,EAAmC,MAAM,wBAAwB,CAAC;AAEnG,OAAO,EAEL,yBAAyB,EACzB,+BAA+B,EAG/B,KAAK,gBAAgB,EACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAML,KAAK,cAAc,EACpB,MAAM,uBAAuB,CAAC;AA8K/B,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,gBAAgB,CAAC;IACvB,GAAG,EAAE,cAAc,CAAC;IACpB,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAClE,0BAA0B,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,wBAAwB,CAAC;IACpF,yBAAyB,EAAE,OAAO,yBAAyB,CAAC;IAC5D,wBAAwB,EAAE,OAAO,wBAAwB,CAAC;IAC1D,eAAe,EAAE,OAAO,eAAe,CAAC;IACxC,qBAAqB,EAAE,OAAO,qBAAqB,CAAC;IACpD,+BAA+B,EAAE,OAAO,+BAA+B,CAAC;IACxE,uBAAuB,EAAE,OAAO,uBAAuB,CAAC;IACxD,mCAAmC,EAAE,OAAO,mCAAmC,CAAC;IAChF,uBAAuB,EAAE,OAAO,uBAAuB,CAAC;IACxD,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IAClC,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IACnC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IACpC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK,CAAC;IAC9B,mBAAmB,EAAE,OAAO,8BAA8B,CAAC;CAC5D;AAqID,6EAA6E;AAC7E,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,SAAS,GAAE,OAAO,CAAC,wBAAwB,CAAM,GAChD,IAAI,CAm8BN"}
1
+ {"version":3,"file":"fleet.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/fleet.ts"],"names":[],"mappings":"AAIA,OAAO,EAAwB,KAAK,OAAO,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAIL,uBAAuB,EACvB,uBAAuB,EACvB,mCAAmC,EACnC,qBAAqB,EAKtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAGL,KAAK,wBAAwB,EAE9B,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAC;AA+BhE,OAAO,EAAE,8BAA8B,EAAE,MAAM,kBAAkB,CAAC;AAClE,OAAO,EAAE,wBAAwB,EAAmC,MAAM,wBAAwB,CAAC;AAEnG,OAAO,EAEL,yBAAyB,EACzB,+BAA+B,EAG/B,KAAK,gBAAgB,EACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAML,KAAK,cAAc,EACpB,MAAM,uBAAuB,CAAC;AAkM/B,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,gBAAgB,CAAC;IACvB,GAAG,EAAE,cAAc,CAAC;IACpB,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAClE,0BAA0B,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,wBAAwB,CAAC;IACpF,yBAAyB,EAAE,OAAO,yBAAyB,CAAC;IAC5D,wBAAwB,EAAE,OAAO,wBAAwB,CAAC;IAC1D,eAAe,EAAE,OAAO,eAAe,CAAC;IACxC,qBAAqB,EAAE,OAAO,qBAAqB,CAAC;IACpD,+BAA+B,EAAE,OAAO,+BAA+B,CAAC;IACxE,uBAAuB,EAAE,OAAO,uBAAuB,CAAC;IACxD,mCAAmC,EAAE,OAAO,mCAAmC,CAAC;IAChF,uBAAuB,EAAE,OAAO,uBAAuB,CAAC;IACxD,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IAClC,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IACnC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IACpC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK,CAAC;IAC9B,mBAAmB,EAAE,OAAO,8BAA8B,CAAC;CAC5D;AAqID,6EAA6E;AAC7E,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,SAAS,GAAE,OAAO,CAAC,wBAAwB,CAAM,GAChD,IAAI,CAwjCN"}
@@ -1,3 +1,4 @@
1
+ import { readTaskInput, validateTaskSize } from '../lib/task-input.js';
1
2
  import { randomUUID } from 'node:crypto';
2
3
  import path from 'node:path';
3
4
  import { InvalidArgumentError } from 'commander';
@@ -11,13 +12,15 @@ import { readBrokerConnection } from '../lib/broker-lifecycle.js';
11
12
  import { spawnAgentWithClient } from '../lib/client-factory.js';
12
13
  import { formatRelativeTime, sanitizeForTerminalLine } from '../lib/formatting.js';
13
14
  import { connectProjectBrokerClient } from '../lib/project-broker-client.js';
14
- import { isAvailableFleetNode } from '../lib/fleet-live-agents.js';
15
+ import { AgentRemovalPendingError, parseRemovalWaitTimeout, waitForAgentRemoval, } from '../lib/agent-removal.js';
16
+ import { classifySpawnFailure, mayStillBeRunning, nameTakenSpawnError, pendingSpawnError, probeSpawnLiveness, terminalSpawnOutcome, } from '../lib/spawn-liveness.js';
17
+ import { heartbeatAgeMs, MAX_LIVE_HEARTBEAT_AGE_MS, isAvailableFleetNode } from '../lib/fleet-live-agents.js';
15
18
  import { declaredWorkforceMetadata } from '../lib/registration-metadata.js';
16
19
  import { redactSecrets } from '../lib/redact.js';
17
20
  import { attributableReleaseReason } from '../lib/release-reason.js';
18
21
  import { retireOwnedIntegrationBindings } from './integration.js';
19
22
  import { resolveSandboxRepository } from '../lib/sandbox-repo.js';
20
- import { spawnPlacementReceipt } from '../lib/spawn-lifecycle.js';
23
+ import { sanitizedSpawnReceipt, spawnPlacementReceipt } from '../lib/spawn-lifecycle.js';
21
24
  import { resolveAgentToken, resolveWorkspaceSelection, persistWorkspaceRelaycastTarget, resolveWorkspaceKeyWithSource, resolveWorkspaceTransport, } from '../lib/sdk-client.js';
22
25
  import { addSdkOptions, printJson, runSdk, sdkOptionsFromOpts, withSdkDefaults, } from '../lib/sdk-command.js';
23
26
  const FLEET_NODES_ALWAYS_ON_MESSAGE = 'Fleet nodes need no per-workspace enablement; this command is a no-op.';
@@ -134,12 +137,12 @@ function spawnInvocationWithMergedPlacement(invocation) {
134
137
  ...invocation,
135
138
  placement: {
136
139
  ...(sdkPlacement ?? {}),
137
- dispatchState: receipt.dispatchState,
140
+ dispatchState: sdkPlacement?.dispatchState ?? receipt.dispatchState,
138
141
  ...(invocationId ? { invocationId } : {}),
139
142
  },
140
143
  };
141
144
  }
142
- function throwForTerminalSpawnFailure(invocation) {
145
+ function throwForTerminalSpawnFailure(invocation, name) {
143
146
  const placement = spawnPlacementReceipt(invocation);
144
147
  if (placement.state !== 'failed')
145
148
  return;
@@ -147,7 +150,7 @@ function throwForTerminalSpawnFailure(invocation) {
147
150
  const message = typeof invocation.error === 'string' && invocation.error.trim()
148
151
  ? invocation.error
149
152
  : 'Fleet spawn invocation reported a terminal failure.';
150
- throw new RelayPlacementError('spawn_failed', message, {
153
+ const error = new RelayPlacementError('spawn_failed', message, {
151
154
  capability: typeof invocation.capability === 'string' ? invocation.capability : 'spawn',
152
155
  attempts: 1,
153
156
  ...(invocationId ? { invocationId } : {}),
@@ -155,6 +158,26 @@ function throwForTerminalSpawnFailure(invocation) {
155
158
  dispatchState: placement.dispatchState,
156
159
  receipt: invocation,
157
160
  });
161
+ throw classifySpawnFailure(message) ? nameTakenSpawnError(name, error) : error;
162
+ }
163
+ // Keep placement codes, receipts and cleanup behavior intact while explaining
164
+ // why releasing a process alone does not make its Relaycast name reusable.
165
+ async function runFleetSpawn(deps, fn) {
166
+ await runSdk(deps, async () => {
167
+ try {
168
+ await fn();
169
+ }
170
+ catch (error) {
171
+ if (error instanceof Error && /\bagent_already_exists\b/.test(error.message)) {
172
+ error.message +=
173
+ ' Fleet release keeps the Relaycast identity unless --delete-agent is passed.' +
174
+ ' To free a name you own, run `agent-relay fleet release <name> --delete-agent --wait`' +
175
+ ' in the same workspace, then retry spawning once it reports clearance. This permanently' +
176
+ ' deletes the identity and retires its provider bindings.';
177
+ }
178
+ throw error;
179
+ }
180
+ });
158
181
  }
159
182
  function withFleetDefaults(overrides = {}) {
160
183
  const core = overrides.core ?? withDefaults();
@@ -306,12 +329,65 @@ export function registerFleetCommands(program, overrides = {}) {
306
329
  .option('--all', 'Include offline/history nodes the way `fleet nodes --all` does')).action(async (options) => {
307
330
  await runFleetAgentList(deps, options);
308
331
  });
332
+ addSdkOptions(group
333
+ .command('spawn-status')
334
+ .description('Read an existing spawn dispatch without launching another agent')
335
+ .argument('<invocation-id>', 'Invocation ID returned by fleet spawn')).action(async (invocationId, options) => {
336
+ await runSdk(deps.sdk, async () => {
337
+ const clientOptions = sdkOptionsFromOpts(options);
338
+ let token = resolveAgentToken(clientOptions);
339
+ let launcherName;
340
+ let workspace;
341
+ try {
342
+ if (!token) {
343
+ workspace = deps.sdk.createWorkspaceRelay(clientOptions);
344
+ const name = `fleet-status-launcher-${randomUUID().slice(0, 8)}`;
345
+ const launcher = await workspace.workspace.register({ name, metadata: { purpose: 'fleet-spawn-status' } }, { strict: true });
346
+ launcherName = name;
347
+ token = launcher.token;
348
+ if (!token)
349
+ throw new Error('The temporary fleet status launcher did not receive an agent token.');
350
+ }
351
+ // A minted launcher token must read from the gateway that minted it, and
352
+ // never alongside the workspace key that minted it (createAgentRelay
353
+ // rejects that pair), matching the spawn path's minted-token client.
354
+ const relay = deps.sdk.createAgentRelay(launcherName
355
+ ? { token, baseUrl: resolveWorkspaceTransport(clientOptions).baseUrl }
356
+ : { ...clientOptions, token });
357
+ const invocation = await relay.messaging.commands.getInvocation('spawn', invocationId);
358
+ // Keep this read-only surface on the existing safe receipt projection.
359
+ const receipt = sanitizedSpawnReceipt(invocation);
360
+ const result = spawnInvocationWithPlacement(receipt);
361
+ const output = receipt.output;
362
+ // --no-confirm dispatches may legitimately report launch without readiness.
363
+ // Preserve that evidence instead of claiming the launched worker failed.
364
+ if (['completed', 'success', 'succeeded'].includes(String(receipt.status).toLowerCase()) &&
365
+ output?.spawned === true &&
366
+ output.ready === false) {
367
+ result.placement = { ...spawnPlacementReceipt(receipt), state: 'accepted' };
368
+ }
369
+ printJson(deps.sdk, { invocation: result });
370
+ }
371
+ finally {
372
+ if (launcherName && workspace) {
373
+ await workspace.workspace
374
+ .release({
375
+ name: launcherName,
376
+ reason: 'Temporary fleet spawn status launcher completed',
377
+ deleteAgent: true,
378
+ })
379
+ .catch((error) => deps.warn(redactSecrets(String(error))));
380
+ }
381
+ }
382
+ });
383
+ });
309
384
  addSdkOptions(group
310
385
  .command('spawn')
311
386
  .description('Spawn locally by default, or select a fleet node or Cloud sandbox explicitly')
312
387
  .argument('<cli>', 'AI CLI to launch', parseFleetCli)
313
388
  .requiredOption('--name <name>', 'Worker agent name')
314
- .requiredOption('--task <text>', 'Initial task instructions')
389
+ .option('--task <text>', 'Initial task instructions')
390
+ .option('--task-file <path>', 'Read the initial task from a local UTF-8 file')
315
391
  .option('--auto-place', 'Request automatic eligible-node placement in the Relay workspace')
316
392
  .option('--node <name>', 'Target a specific fleet node')
317
393
  .option('--target-node <name>', 'Alias for --node')
@@ -334,11 +410,11 @@ export function registerFleetCommands(program, overrides = {}) {
334
410
  .option('--objective <objective>', 'Declared objective (defaults to --task when omitted)')
335
411
  .option('--session-ref <reference>', 'Session reference for a resumable targeted spawn')
336
412
  .option('--no-confirm', 'Report a targeted spawn as soon as the node accepts it, without waiting for the node to confirm the agent actually launched')
337
- .option('--confirm-timeout <ms>', `How long a targeted spawn waits for harness readiness (minimum ${MIN_VERIFIED_CONFIRM_TIMEOUT_MS}ms)`, '120000')).action(async (cli, options) => {
338
- await runSdk(deps.sdk, async () => {
413
+ .option('--confirm-timeout <ms>', `How long a targeted spawn waits for harness readiness (minimum ${MIN_VERIFIED_CONFIRM_TIMEOUT_MS}ms)`, '360000')).action(async (cli, options) => {
414
+ await runFleetSpawn(deps.sdk, async () => {
339
415
  const clientOptions = sdkOptionsFromOpts(options);
340
416
  const name = requiredText(options.name, 'Worker name');
341
- const task = requiredText(options.task, 'Task');
417
+ const task = (await readTaskInput(options.task, options.taskFile, true, { cli }));
342
418
  let targetNode = optionalText(options.targetNode, 'Target node') ?? optionalText(options.node, 'Node');
343
419
  const useSandbox = options.sandbox === true;
344
420
  // Explicit hosted credentials/transport and personas retain their legacy
@@ -414,7 +490,7 @@ export function registerFleetCommands(program, overrides = {}) {
414
490
  const objective = optionalText(options.objective, 'Objective');
415
491
  const sessionRef = optionalText(options.sessionRef, 'Session reference');
416
492
  const registrationMetadata = declaredWorkforceMetadata({ organization, project, workstream, role, objective }, task);
417
- const confirmTimeoutText = optionalText(options.confirmTimeout, 'Confirm timeout') ?? '120000';
493
+ const confirmTimeoutText = optionalText(options.confirmTimeout, 'Confirm timeout') ?? '360000';
418
494
  const confirmTimeoutMs = Number(confirmTimeoutText);
419
495
  if (!Number.isFinite(confirmTimeoutMs) || confirmTimeoutMs <= 0) {
420
496
  throw new Error('--confirm-timeout must be a positive number of milliseconds.');
@@ -838,31 +914,73 @@ export function registerFleetCommands(program, overrides = {}) {
838
914
  'The Relayfile daemon synchronizes this tree; it intentionally has no .git directory.',
839
915
  ].join(' ')
840
916
  : undefined;
841
- const invocation = await relay.messaging.placement.spawn({
842
- capability: `spawn:${cli}`,
843
- node: targetNode,
844
- failFast: true,
845
- confirm,
846
- ...(confirm ? { confirmTimeoutMs: confirmTimeoutMs } : {}),
847
- input: {
917
+ const spawnTask = sandbox &&
918
+ checkoutRepository &&
919
+ sandboxRepository &&
920
+ mountSandboxRelayfile &&
921
+ sandbox.outcome === 'provisioned'
922
+ ? `${task}\n\nAgent Relay sandbox context: Relayfile records are available at ${sandbox.relayfileMountPath ?? '/workspace'}. The source checkout is separate; use ${workerCwd ?? 'the worker checkout'} for repository files and the mount for Relayfile records.`
923
+ : liveSandboxContext
924
+ ? `${task}\n\n${liveSandboxContext}`
925
+ : task;
926
+ // The sandbox context is appended after readTaskInput accepted the
927
+ // task, so the composed task is what the worker must receive.
928
+ validateTaskSize(spawnTask, { cli });
929
+ let invocation;
930
+ try {
931
+ invocation = await relay.messaging.placement.spawn({
932
+ capability: `spawn:${cli}`,
933
+ node: targetNode,
934
+ failFast: true,
935
+ confirm,
936
+ ...(confirm ? { confirmTimeoutMs: confirmTimeoutMs } : {}),
937
+ input: {
938
+ name,
939
+ cli,
940
+ task: spawnTask,
941
+ ...(channel ? { channels: [channel] } : {}),
942
+ ...(model ? { model } : {}),
943
+ ...(workerCwd ? { worker_cwd: workerCwd } : {}),
944
+ ...registrationMetadata,
945
+ ...(sessionRef ? { session_ref: sessionRef } : {}),
946
+ },
947
+ });
948
+ }
949
+ catch (error) {
950
+ if (!(error instanceof RelayPlacementError))
951
+ throw error;
952
+ if (error.code === 'spawn_failed' && classifySpawnFailure(error.message))
953
+ throw nameTakenSpawnError(name, error);
954
+ if (error.code !== 'spawn_unconfirmed')
955
+ throw error;
956
+ const liveness = await probeSpawnLiveness({
848
957
  name,
849
- cli,
850
- task: sandbox &&
851
- checkoutRepository &&
852
- sandboxRepository &&
853
- mountSandboxRelayfile &&
854
- sandbox.outcome === 'provisioned'
855
- ? `${task}\n\nAgent Relay sandbox context: Relayfile records are available at ${sandbox.relayfileMountPath ?? '/workspace'}. The source checkout is separate; use ${workerCwd ?? 'the worker checkout'} for repository files and the mount for Relayfile records.`
856
- : liveSandboxContext
857
- ? `${task}\n\n${liveSandboxContext}`
858
- : task,
859
- ...(channel ? { channels: [channel] } : {}),
860
- ...(model ? { model } : {}),
861
- ...(workerCwd ? { worker_cwd: workerCwd } : {}),
862
- ...registrationMetadata,
863
- ...(sessionRef ? { session_ref: sessionRef } : {}),
864
- },
865
- });
958
+ targetNode,
959
+ ...(error.invocationId
960
+ ? {
961
+ getInvocation: () => relay.messaging.commands.getInvocation('spawn', error.invocationId),
962
+ }
963
+ : {}),
964
+ createClient: () => {
965
+ const workspace = deps.sdk.createWorkspaceRelay(relaycastClientOptions);
966
+ return { nodes: workspace.nodes, agents: workspace.agents };
967
+ },
968
+ });
969
+ if (liveness.evidence === 'invocation_terminal') {
970
+ try {
971
+ invocation = terminalSpawnOutcome(liveness.invocation, error, confirm);
972
+ }
973
+ catch (terminalError) {
974
+ if (terminalError instanceof RelayPlacementError &&
975
+ classifySpawnFailure(terminalError.message))
976
+ throw nameTakenSpawnError(name, terminalError);
977
+ throw terminalError;
978
+ }
979
+ }
980
+ else {
981
+ throw pendingSpawnError(name, error, liveness, error.message);
982
+ }
983
+ }
866
984
  const printableSandbox = (sandbox?.outcome === 'provisioned' || sandbox?.outcome === 'reused') && sandbox.relaycastTarget
867
985
  ? {
868
986
  ...sandbox,
@@ -884,9 +1002,7 @@ export function registerFleetCommands(program, overrides = {}) {
884
1002
  });
885
1003
  }
886
1004
  catch (error) {
887
- if (shouldCleanupSandbox &&
888
- sandbox?.outcome === 'provisioned' &&
889
- !(error instanceof RelayPlacementError && error.state === 'unconfirmed_may_be_running')) {
1005
+ if (shouldCleanupSandbox && sandbox?.outcome === 'provisioned' && !mayStillBeRunning(error)) {
890
1006
  await deps
891
1007
  .deleteCloudFleetSandbox({
892
1008
  cloudWorkspaceId: sandbox.cloudWorkspaceId,
@@ -960,7 +1076,7 @@ export function registerFleetCommands(program, overrides = {}) {
960
1076
  }
961
1077
  : {}),
962
1078
  });
963
- throwForTerminalSpawnFailure(invocation);
1079
+ throwForTerminalSpawnFailure(invocation, name);
964
1080
  printJson(deps.sdk, { invocation: spawnInvocationWithPlacement(invocation) });
965
1081
  });
966
1082
  });
@@ -969,12 +1085,16 @@ export function registerFleetCommands(program, overrides = {}) {
969
1085
  .description('Release a spawned fleet agent')
970
1086
  .argument('<name>', 'Worker agent name')
971
1087
  .option('--reason <reason>', 'Release reason')
972
- .option('--delete-agent', 'Permanently delete the agent after release')
1088
+ .option('--delete-agent', 'Permanently delete the agent after release so its name can be reused')
1089
+ .option('--wait', 'With --delete-agent, wait for registration clearance', false)
1090
+ .option('--no-wait', 'Return after release is accepted')
1091
+ .option('--wait-timeout <ms>', 'Registration clearance timeout', '30000')
973
1092
  .option('--unsubscribe-bindings', 'Retire provider bindings owned by this identity (implied by --delete-agent)')).action(async (name, options) => {
974
1093
  await runSdk(deps.sdk, async () => {
975
1094
  warnIfInferredFromProjectSession(options, deps.warn);
976
1095
  const workerName = requiredText(name, 'Worker name');
977
1096
  const deleteAgent = options.deleteAgent === true;
1097
+ const waitTimeoutMs = deleteAgent && options.wait === true ? parseRemovalWaitTimeout(options.waitTimeout) : undefined;
978
1098
  const sdkOpts = sdkOptionsFromOpts(options);
979
1099
  // One transport for both cleanup and release. Cleanup otherwise
980
1100
  // substitutes the local broker session whenever --workspace-key is
@@ -1018,6 +1138,21 @@ export function registerFleetCommands(program, overrides = {}) {
1018
1138
  reason,
1019
1139
  deleteAgent: true,
1020
1140
  });
1141
+ if (waitTimeoutMs !== undefined) {
1142
+ // Construct/read on the same selected transport; unavailable credentials are uncertainty.
1143
+ const removal = await waitForAgentRemoval({
1144
+ name: workerName,
1145
+ getAgent: (name) => deps.sdk.createWorkspaceRelay(transport).agents.get(name),
1146
+ listAgents: () => deps.sdk.createWorkspaceRelay(transport).agents.list(),
1147
+ timeoutMs: waitTimeoutMs,
1148
+ });
1149
+ printJson(deps.sdk, { ...deleted, removal });
1150
+ if (!removal.cleared && removal.observedPresent)
1151
+ throw new AgentRemovalPendingError(workerName);
1152
+ if (!removal.cleared)
1153
+ deps.warn(`Removal was initiated asynchronously; could not verify clearance: ${removal.readError}`);
1154
+ return;
1155
+ }
1021
1156
  printJson(deps.sdk, deleted);
1022
1157
  return;
1023
1158
  }
@@ -1302,12 +1437,21 @@ async function runFleetAgentList(deps, options) {
1302
1437
  }
1303
1438
  const now = new Date();
1304
1439
  const output = buildRows({ contributions, roster }, now);
1440
+ const heartbeatNodes = contributions.map(({ node }) => ({
1441
+ name: node.name,
1442
+ lastHeartbeatAt: node.lastHeartbeatAt ?? null,
1443
+ heartbeatAgeMs: heartbeatAgeMs(node, now.getTime()),
1444
+ }));
1445
+ if (heartbeatNodes.some((node) => node.heartbeatAgeMs !== null && node.heartbeatAgeMs > MAX_LIVE_HEARTBEAT_AGE_MS)) {
1446
+ deps.warn('Some node heartbeats are older than 36 seconds. Listings are heartbeat snapshots and can differ within one heartbeat window.');
1447
+ }
1305
1448
  if (options.pretty === true) {
1306
1449
  deps.log(formatPretty(output));
1307
1450
  return;
1308
1451
  }
1309
1452
  printJson(deps.sdk, {
1310
1453
  generatedAt: now.toISOString(),
1454
+ nodes: heartbeatNodes,
1311
1455
  localNode: localNodeName ?? null,
1312
1456
  perNode: output.perNode,
1313
1457
  unplacedRoster: output.unplacedRoster,