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.
- package/README.md +60 -0
- package/dist/cli/agent-relay-mcp.d.ts.map +1 -1
- package/dist/cli/agent-relay-mcp.js +131 -10
- package/dist/cli/agent-relay-mcp.js.map +1 -1
- package/dist/cli/commands/agent.d.ts.map +1 -1
- package/dist/cli/commands/agent.js +24 -19
- package/dist/cli/commands/agent.js.map +1 -1
- package/dist/cli/commands/fleet.d.ts.map +1 -1
- package/dist/cli/commands/fleet.js +183 -39
- package/dist/cli/commands/fleet.js.map +1 -1
- package/dist/cli/commands/integration.d.ts.map +1 -1
- package/dist/cli/commands/integration.js +81 -7
- package/dist/cli/commands/integration.js.map +1 -1
- package/dist/cli/commands/local-agent.d.ts.map +1 -1
- package/dist/cli/commands/local-agent.js +22 -3
- package/dist/cli/commands/local-agent.js.map +1 -1
- package/dist/cli/commands/message.d.ts.map +1 -1
- package/dist/cli/commands/message.js +111 -29
- package/dist/cli/commands/message.js.map +1 -1
- package/dist/cli/lib/agent-registration.d.ts +2 -0
- package/dist/cli/lib/agent-registration.d.ts.map +1 -1
- package/dist/cli/lib/agent-registration.js +2 -2
- package/dist/cli/lib/agent-registration.js.map +1 -1
- package/dist/cli/lib/agent-removal.d.ts +30 -0
- package/dist/cli/lib/agent-removal.d.ts.map +1 -0
- package/dist/cli/lib/agent-removal.js +73 -0
- package/dist/cli/lib/agent-removal.js.map +1 -0
- package/dist/cli/lib/attachments.d.ts +31 -0
- package/dist/cli/lib/attachments.d.ts.map +1 -0
- package/dist/cli/lib/attachments.js +239 -0
- package/dist/cli/lib/attachments.js.map +1 -0
- package/dist/cli/lib/fleet-live-agents.d.ts +17 -0
- package/dist/cli/lib/fleet-live-agents.d.ts.map +1 -1
- package/dist/cli/lib/fleet-live-agents.js +23 -0
- package/dist/cli/lib/fleet-live-agents.js.map +1 -1
- package/dist/cli/lib/sdk-command.d.ts.map +1 -1
- package/dist/cli/lib/sdk-command.js +8 -2
- package/dist/cli/lib/sdk-command.js.map +1 -1
- package/dist/cli/lib/spawn-liveness.d.ts +50 -0
- package/dist/cli/lib/spawn-liveness.d.ts.map +1 -0
- package/dist/cli/lib/spawn-liveness.js +184 -0
- package/dist/cli/lib/spawn-liveness.js.map +1 -0
- package/dist/cli/lib/task-input.d.ts +26 -0
- package/dist/cli/lib/task-input.d.ts.map +1 -0
- package/dist/cli/lib/task-input.js +102 -0
- package/dist/cli/lib/task-input.js.map +1 -0
- package/dist/cli/mcp/messaging-tools.d.ts.map +1 -1
- package/dist/cli/mcp/messaging-tools.js +136 -34
- package/dist/cli/mcp/messaging-tools.js.map +1 -1
- package/dist/cli/mcp/request-replay.d.ts +21 -9
- package/dist/cli/mcp/request-replay.d.ts.map +1 -1
- package/dist/cli/mcp/request-replay.js +58 -9
- package/dist/cli/mcp/request-replay.js.map +1 -1
- package/dist/index.cjs +200 -6
- package/package.json +10 -10
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
import { addSdkOptions, printJson, runSdk, sdkOptionsFromOpts, withSdkDefaults, } from '../lib/sdk-command.js';
|
|
2
|
-
import {
|
|
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')
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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":"AAEA,OAAO,EACL,aAAa,EACb,SAAS,EACT,MAAM,EACN,kBAAkB,EAClB,eAAe,GAEhB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,
|
|
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":"
|
|
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 {
|
|
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
|
-
|
|
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
|
-
.
|
|
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)`, '
|
|
338
|
-
await
|
|
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 =
|
|
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') ?? '
|
|
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
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
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
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
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,
|