@hypercli.com/sdk 2026.7.31 → 2026.8.11
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 +113 -11
- package/dist/agent-slots.d.ts +17 -0
- package/dist/agent-slots.d.ts.map +1 -0
- package/dist/agent-slots.js +18 -0
- package/dist/agent-slots.js.map +1 -0
- package/dist/agent.d.ts +48 -10
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +196 -28
- package/dist/agent.js.map +1 -1
- package/dist/agents.d.ts +168 -51
- package/dist/agents.d.ts.map +1 -1
- package/dist/agents.js +801 -211
- package/dist/agents.js.map +1 -1
- package/dist/browser.d.ts +2 -2
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +1 -1
- package/dist/browser.js.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +5 -1
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +2 -2
- package/dist/config.js.map +1 -1
- package/dist/errors.d.ts +4 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +9 -2
- package/dist/errors.js.map +1 -1
- package/dist/hermes/gateway.d.ts +194 -0
- package/dist/hermes/gateway.d.ts.map +1 -0
- package/dist/hermes/gateway.js +209 -0
- package/dist/hermes/gateway.js.map +1 -0
- package/dist/http.d.ts +6 -0
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +84 -25
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/job/base.d.ts +6 -0
- package/dist/job/base.d.ts.map +1 -1
- package/dist/job/base.js +7 -1
- package/dist/job/base.js.map +1 -1
- package/dist/keys.d.ts +15 -4
- package/dist/keys.d.ts.map +1 -1
- package/dist/keys.js +55 -3
- package/dist/keys.js.map +1 -1
- package/dist/openclaw/gateway.d.ts +32 -1
- package/dist/openclaw/gateway.d.ts.map +1 -1
- package/dist/openclaw/gateway.js +101 -21
- package/dist/openclaw/gateway.js.map +1 -1
- package/package.json +6 -2
package/dist/agents.js
CHANGED
|
@@ -2,11 +2,15 @@
|
|
|
2
2
|
* HyperClaw agents API - typed agent lifecycle, files, exec, and OpenClaw access.
|
|
3
3
|
*/
|
|
4
4
|
import { randomFillSync } from 'node:crypto';
|
|
5
|
+
import NodeWebSocket from 'ws';
|
|
6
|
+
import { agentSlotFromDict, } from './agent-slots.js';
|
|
7
|
+
export { agentSlotFromDict, } from './agent-slots.js';
|
|
5
8
|
import { getAgentsApiBaseUrl, getConfigValue } from './config.js';
|
|
6
9
|
import { APIError } from './errors.js';
|
|
7
10
|
import { HTTPClient } from './http.js';
|
|
8
11
|
import { GatewayClient, } from './openclaw/gateway.js';
|
|
9
12
|
import { normalizeSlackRelayBaseUrl } from './channels.js';
|
|
13
|
+
import { HermesApiClient } from './hermes/gateway.js';
|
|
10
14
|
const AGENTS_API_BASE = 'https://api.hypercli.com/agents';
|
|
11
15
|
const DEV_AGENTS_API_BASE = 'https://api.dev.hypercli.com/agents';
|
|
12
16
|
const DEPLOYMENTS_API_PREFIX = '/deployments';
|
|
@@ -14,37 +18,68 @@ const AGENTS_WS_URL = 'wss://api.agents.hypercli.com/ws';
|
|
|
14
18
|
const DEV_AGENTS_WS_URL = 'wss://api.agents.dev.hypercli.com/ws';
|
|
15
19
|
export const DEFAULT_OPENCLAW_IMAGE = 'ghcr.io/hypercli/hypercli-openclaw:pro-latest';
|
|
16
20
|
export const DEFAULT_OPENCLAW_PRO_IMAGE = 'ghcr.io/hypercli/hypercli-openclaw:pro-latest';
|
|
21
|
+
export const DEFAULT_HERMES_AGENT_IMAGE = 'ghcr.io/hypercli/hypercli-hermes-agent:latest';
|
|
17
22
|
export const DEFAULT_OPENCODE_IMAGE = 'ghcr.io/hypercli/hypercli-opencode:latest';
|
|
18
23
|
export const DEFAULT_CODEX_IMAGE = 'ghcr.io/hypercli/hypercli-codex:latest';
|
|
19
24
|
export const DEFAULT_CLAUDE_CODE_IMAGE = 'ghcr.io/hypercli/hypercli-claude-code:latest';
|
|
20
25
|
export const DEFAULT_GOOSE_IMAGE = 'ghcr.io/hypercli/hypercli-goose:latest';
|
|
21
26
|
export const DEFAULT_KIMI_CODE_IMAGE = 'ghcr.io/hypercli/hypercli-kimi-code:latest';
|
|
27
|
+
export const DEFAULT_BUZZ_AGENT_IMAGE = 'ghcr.io/hypercli/hypercli-buzz-agent:latest';
|
|
22
28
|
export const DEFAULT_BUZZ_OPENCODE_IMAGE = 'ghcr.io/hypercli/hypercli-buzz-opencode:latest';
|
|
23
29
|
export const DEFAULT_BUZZ_CODEX_IMAGE = 'ghcr.io/hypercli/hypercli-buzz-codex:latest';
|
|
24
30
|
export const DEFAULT_BUZZ_CLAUDE_CODE_IMAGE = 'ghcr.io/hypercli/hypercli-buzz-claude:latest';
|
|
25
31
|
export const DEFAULT_BUZZ_GOOSE_IMAGE = 'ghcr.io/hypercli/hypercli-buzz-goose:latest';
|
|
26
32
|
export const DEFAULT_BUZZ_KIMI_CODE_IMAGE = 'ghcr.io/hypercli/hypercli-buzz-kimi-code:latest';
|
|
33
|
+
export const DEFAULT_AGENT_RUNTIME_SCOPES = Object.freeze([
|
|
34
|
+
'agents:none',
|
|
35
|
+
'files:*',
|
|
36
|
+
'flows:*',
|
|
37
|
+
'models:*',
|
|
38
|
+
'voice:*',
|
|
39
|
+
'web:*',
|
|
40
|
+
'workspaces:*',
|
|
41
|
+
]);
|
|
27
42
|
export const DEFAULT_CODING_AGENT_SYNC_ROOT = '/home/node';
|
|
28
43
|
export const DEFAULT_CODING_AGENT_IMAGES = {
|
|
44
|
+
'buzz-agent': DEFAULT_BUZZ_AGENT_IMAGE,
|
|
29
45
|
opencode: DEFAULT_OPENCODE_IMAGE,
|
|
30
46
|
codex: DEFAULT_CODEX_IMAGE,
|
|
31
47
|
'claude-code': DEFAULT_CLAUDE_CODE_IMAGE,
|
|
32
48
|
goose: DEFAULT_GOOSE_IMAGE,
|
|
33
49
|
'kimi-code': DEFAULT_KIMI_CODE_IMAGE,
|
|
34
50
|
};
|
|
51
|
+
export const DEFAULT_CODING_AGENT_SYNC_INCLUDES = {
|
|
52
|
+
'buzz-agent': [],
|
|
53
|
+
opencode: [
|
|
54
|
+
'.config/opencode',
|
|
55
|
+
'.local/share/opencode',
|
|
56
|
+
'.local/state/opencode',
|
|
57
|
+
'.cache/opencode',
|
|
58
|
+
],
|
|
59
|
+
codex: ['.codex'],
|
|
60
|
+
'claude-code': ['.claude', '.claude.json'],
|
|
61
|
+
goose: ['.goose'],
|
|
62
|
+
'kimi-code': ['.kimi-code'],
|
|
63
|
+
};
|
|
35
64
|
export const DEFAULT_BUZZ_CODING_AGENT_IMAGES = {
|
|
65
|
+
'buzz-agent': DEFAULT_BUZZ_AGENT_IMAGE,
|
|
36
66
|
opencode: DEFAULT_BUZZ_OPENCODE_IMAGE,
|
|
37
67
|
codex: DEFAULT_BUZZ_CODEX_IMAGE,
|
|
38
68
|
'claude-code': DEFAULT_BUZZ_CLAUDE_CODE_IMAGE,
|
|
39
69
|
goose: DEFAULT_BUZZ_GOOSE_IMAGE,
|
|
40
70
|
'kimi-code': DEFAULT_BUZZ_KIMI_CODE_IMAGE,
|
|
41
71
|
};
|
|
42
|
-
const CODING_AGENT_RUNTIMES = new Set(['opencode', 'codex', 'claude-code', 'goose', 'kimi-code']);
|
|
72
|
+
const CODING_AGENT_RUNTIMES = new Set(['buzz-agent', 'opencode', 'codex', 'claude-code', 'goose', 'kimi-code']);
|
|
43
73
|
const BUZZ_RUNTIME_COMMANDS = {
|
|
74
|
+
'buzz-agent': {
|
|
75
|
+
command: '/usr/local/bin/buzz-agent',
|
|
76
|
+
args: [],
|
|
77
|
+
mcpCommand: '/usr/local/bin/buzz-dev-mcp',
|
|
78
|
+
},
|
|
44
79
|
opencode: {
|
|
45
80
|
command: '/usr/local/bin/opencode',
|
|
46
81
|
args: ['acp'],
|
|
47
|
-
mcpCommand: '
|
|
82
|
+
mcpCommand: '',
|
|
48
83
|
},
|
|
49
84
|
codex: {
|
|
50
85
|
command: '/usr/local/bin/codex-acp',
|
|
@@ -82,6 +117,10 @@ const BUZZ_RESERVED_ENV_KEYS = new Set([
|
|
|
82
117
|
'BUZZ_ACP_MCP_COMMAND',
|
|
83
118
|
'BUZZ_ACP_LAZY_POOL',
|
|
84
119
|
'BUZZ_ACP_RELAY_OBSERVER',
|
|
120
|
+
'BUZZ_ACP_DISPLAY_NAME',
|
|
121
|
+
'BUZZ_ACP_TEXT_MENTIONS',
|
|
122
|
+
'BUZZ_ACP_REQUIRE_REPLY',
|
|
123
|
+
'CLAUDE_CODE_EXECUTABLE',
|
|
85
124
|
'BUZZ_ACP_SESSION_TITLE',
|
|
86
125
|
'BUZZ_ACP_SYSTEM_PROMPT',
|
|
87
126
|
'BUZZ_ACP_MODEL',
|
|
@@ -106,28 +145,64 @@ export const OPENCLAW_MEMORY_SEARCH_ENV_DEFAULTS = {
|
|
|
106
145
|
};
|
|
107
146
|
export const OPENCLAW_WORKSPACES_SYNC_ENV_DEFAULTS = {
|
|
108
147
|
HYPER_WORKSPACES_BOOT_SYNC: '1',
|
|
109
|
-
HYPER_WORKSPACES_DIR: '/home/node/
|
|
148
|
+
HYPER_WORKSPACES_DIR: '/home/node/shared',
|
|
110
149
|
HYPER_WORKSPACES_SYNC_READY_ONLY: '1',
|
|
111
150
|
};
|
|
151
|
+
const DEFAULT_OPENCLAW_SYNC_EXCLUDE = [
|
|
152
|
+
'shared/**',
|
|
153
|
+
'.openclaw/npm/**/node_modules/**',
|
|
154
|
+
'.openclaw/agents/**/agent/*.sqlite.memory-reindex-*',
|
|
155
|
+
'.openclaw/agents/**/agent/*.sqlite.reindex-lock.sqlite*',
|
|
156
|
+
'.openclaw/browser/**/Code Cache/**',
|
|
157
|
+
'.openclaw/browser/**/GPUCache/**',
|
|
158
|
+
'.openclaw/browser/**/ShaderCache/**',
|
|
159
|
+
'.openclaw/browser/**/GrShaderCache/**',
|
|
160
|
+
'.openclaw/browser/**/optimization_guide_model_store/**',
|
|
161
|
+
];
|
|
112
162
|
const LAUNCH_CONFIG_KEYS = new Set([
|
|
113
163
|
'image',
|
|
114
164
|
'env',
|
|
165
|
+
'secrets',
|
|
115
166
|
'routes',
|
|
116
|
-
'ports',
|
|
117
167
|
'command',
|
|
118
168
|
'entrypoint',
|
|
119
169
|
'sync_root',
|
|
120
|
-
'
|
|
170
|
+
'sync_include',
|
|
171
|
+
'sync_exclude',
|
|
121
172
|
'sync_uid',
|
|
122
173
|
'sync_gid',
|
|
123
174
|
'registry_url',
|
|
124
175
|
'registry_auth',
|
|
125
176
|
'restart',
|
|
177
|
+
'runtime_scopes',
|
|
126
178
|
]);
|
|
127
179
|
const DEFAULT_OPENCLAW_SYNC_ROOT = '/home/node';
|
|
180
|
+
export const DEFAULT_HERMES_AGENT_SYNC_ROOT = '/opt/data';
|
|
181
|
+
export const DEFAULT_HERMES_AGENT_SYNC_UID = 10000;
|
|
182
|
+
export const DEFAULT_HERMES_AGENT_SYNC_GID = 10000;
|
|
128
183
|
export const AGENT_FILE_MAX_BYTES = 250 * 1024 * 1024;
|
|
129
184
|
export const AGENT_FILE_TRANSFER_CHUNK_BYTES = 64 * 1024;
|
|
130
185
|
export const AGENT_FILE_OPERATION_TIMEOUT_MS = 300_000;
|
|
186
|
+
function stringifyOpenClawOperationsFailure(reason) {
|
|
187
|
+
if (reason instanceof Error)
|
|
188
|
+
return reason.message || reason.name || 'Error';
|
|
189
|
+
if (typeof reason === 'string')
|
|
190
|
+
return reason;
|
|
191
|
+
try {
|
|
192
|
+
const serialized = JSON.stringify(reason);
|
|
193
|
+
if (serialized !== undefined)
|
|
194
|
+
return serialized;
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
// Fall back to String for non-serializable rejection values.
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
return String(reason);
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return 'Unknown error';
|
|
204
|
+
}
|
|
205
|
+
}
|
|
131
206
|
function shellAbortError() {
|
|
132
207
|
const error = new Error('Shell connection cancelled');
|
|
133
208
|
error.name = 'AbortError';
|
|
@@ -185,9 +260,8 @@ function buildBuzzLaunchEnv(runtime, buzz, defaultSessionTitle) {
|
|
|
185
260
|
}
|
|
186
261
|
const harness = BUZZ_RUNTIME_COMMANDS[runtime];
|
|
187
262
|
const env = {
|
|
188
|
-
BUZZ_PRIVATE_KEY: buzz.privateKeyNsec,
|
|
189
|
-
NOSTR_PRIVATE_KEY: buzz.privateKeyNsec,
|
|
190
263
|
BUZZ_RELAY_URL: buzz.relayUrl,
|
|
264
|
+
BUZZ_MANAGED_AGENT_START_NONCE: randomHexToken(16),
|
|
191
265
|
BUZZ_ACP_AGENT_COMMAND: harness.command,
|
|
192
266
|
BUZZ_ACP_AGENT_ARGS: harness.args.join(','),
|
|
193
267
|
BUZZ_ACP_MCP_COMMAND: harness.mcpCommand,
|
|
@@ -197,10 +271,14 @@ function buildBuzzLaunchEnv(runtime, buzz, defaultSessionTitle) {
|
|
|
197
271
|
BUZZ_ACP_MULTIPLE_EVENT_HANDLING: 'steer',
|
|
198
272
|
BUZZ_ACP_DEDUP: 'queue',
|
|
199
273
|
};
|
|
274
|
+
if (runtime === 'claude-code') {
|
|
275
|
+
env.CLAUDE_CODE_EXECUTABLE = '/usr/local/bin/claude';
|
|
276
|
+
}
|
|
200
277
|
if (buzz.rustLog)
|
|
201
278
|
env.RUST_LOG = buzz.rustLog;
|
|
202
279
|
const optional = {
|
|
203
280
|
BUZZ_AUTH_TAG: buzz.authTag,
|
|
281
|
+
BUZZ_ACP_DISPLAY_NAME: buzz.displayName,
|
|
204
282
|
BUZZ_ACP_SESSION_TITLE: buzz.sessionTitle || defaultSessionTitle,
|
|
205
283
|
BUZZ_ACP_SYSTEM_PROMPT: buzz.systemPrompt,
|
|
206
284
|
BUZZ_ACP_MODEL: buzz.model,
|
|
@@ -219,8 +297,20 @@ function buildBuzzLaunchEnv(runtime, buzz, defaultSessionTitle) {
|
|
|
219
297
|
if (value)
|
|
220
298
|
env[key] = value;
|
|
221
299
|
}
|
|
300
|
+
if (buzz.textMentions)
|
|
301
|
+
env.BUZZ_ACP_TEXT_MENTIONS = 'true';
|
|
302
|
+
if (buzz.requireReply)
|
|
303
|
+
env.BUZZ_ACP_REQUIRE_REPLY = 'true';
|
|
222
304
|
return env;
|
|
223
305
|
}
|
|
306
|
+
function buildBuzzLaunchSecrets(buzz) {
|
|
307
|
+
if (!buzz.privateKeyNsec.trim())
|
|
308
|
+
throw new Error('buzz.privateKeyNsec is required');
|
|
309
|
+
return {
|
|
310
|
+
BUZZ_PRIVATE_KEY: buzz.privateKeyNsec,
|
|
311
|
+
NOSTR_PRIVATE_KEY: buzz.privateKeyNsec,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
224
314
|
/** The agent's sync root and workspace, owned by the SDK so paths are unambiguous. */
|
|
225
315
|
export const OPENCLAW_SYNC_ROOT = '/home/node';
|
|
226
316
|
export const OPENCLAW_WORKSPACE_PREFIX = '.openclaw/workspace';
|
|
@@ -287,6 +377,16 @@ function isDirectAgentIdRef(value) {
|
|
|
287
377
|
const raw = value.trim();
|
|
288
378
|
return isUuidRef(raw) || /^[0-9a-f]{6,}$/i.test(raw) || /^(agent|external)[-_:]/i.test(raw);
|
|
289
379
|
}
|
|
380
|
+
function isSelfAgentRef(value) {
|
|
381
|
+
return value.trim().toLowerCase() === 'self';
|
|
382
|
+
}
|
|
383
|
+
function agentRoutesStateFromData(data) {
|
|
384
|
+
return {
|
|
385
|
+
agentId: String(data.agent_id ?? ''),
|
|
386
|
+
routes: structuredClone(data.routes ?? {}),
|
|
387
|
+
routeStatuses: structuredClone(data.route_statuses ?? {}),
|
|
388
|
+
};
|
|
389
|
+
}
|
|
290
390
|
/** Strip a workspace prefix so gateway (name-addressed) sees the bare name. */
|
|
291
391
|
function resolveGatewayFileName(path) {
|
|
292
392
|
if (path.startsWith('/')) {
|
|
@@ -404,6 +504,39 @@ export class AgentFiles {
|
|
|
404
504
|
});
|
|
405
505
|
}
|
|
406
506
|
}
|
|
507
|
+
/** Canonical states understood by this SDK. AgentState remains forward-open. */
|
|
508
|
+
export const CANONICAL_AGENT_STATES = [
|
|
509
|
+
'CREATING',
|
|
510
|
+
'STARTING',
|
|
511
|
+
'RESTORING',
|
|
512
|
+
'RUNNING',
|
|
513
|
+
'STOPPING',
|
|
514
|
+
'STOPPED',
|
|
515
|
+
'ARCHIVING',
|
|
516
|
+
'ARCHIVED',
|
|
517
|
+
'FAILED',
|
|
518
|
+
'DELETED',
|
|
519
|
+
];
|
|
520
|
+
export const AGENT_TRANSITIONAL_STATES = new Set([
|
|
521
|
+
'CREATING',
|
|
522
|
+
'STARTING',
|
|
523
|
+
'RESTORING',
|
|
524
|
+
'STOPPING',
|
|
525
|
+
'ARCHIVING',
|
|
526
|
+
]);
|
|
527
|
+
export const AGENT_RUNTIME_INACTIVE_STATES = new Set([
|
|
528
|
+
'STOPPED',
|
|
529
|
+
'ARCHIVING',
|
|
530
|
+
'ARCHIVED',
|
|
531
|
+
'FAILED',
|
|
532
|
+
'DELETED',
|
|
533
|
+
]);
|
|
534
|
+
export function isAgentTransitionalState(state) {
|
|
535
|
+
return AGENT_TRANSITIONAL_STATES.has(state.toUpperCase());
|
|
536
|
+
}
|
|
537
|
+
export function isAgentRuntimeInactiveState(state) {
|
|
538
|
+
return AGENT_RUNTIME_INACTIVE_STATES.has(state.toUpperCase());
|
|
539
|
+
}
|
|
407
540
|
function parseDate(value) {
|
|
408
541
|
if (typeof value !== 'string' || !value)
|
|
409
542
|
return null;
|
|
@@ -510,24 +643,18 @@ export function routesHaveDesktop(routes) {
|
|
|
510
643
|
return true;
|
|
511
644
|
return Object.values(routes).some((route) => isPlainRecord(route) && route.prefix === 'desktop');
|
|
512
645
|
}
|
|
513
|
-
export function portsHaveDesktop(ports) {
|
|
514
|
-
return Array.isArray(ports) && ports.some((port) => isPlainRecord(port) && Number(port.port) === 3000);
|
|
515
|
-
}
|
|
516
646
|
export function launchConfigHasDesktop(launchConfig) {
|
|
517
647
|
if (!isPlainRecord(launchConfig))
|
|
518
648
|
return false;
|
|
519
649
|
if (isTruthyEnv(getLaunchConfigValue(launchConfig, 'env.OPENCLAW_DESKTOP_ENABLED')))
|
|
520
650
|
return true;
|
|
521
|
-
|
|
522
|
-
return true;
|
|
523
|
-
return portsHaveDesktop(getLaunchConfigValue(launchConfig, 'ports'));
|
|
651
|
+
return routesHaveDesktop(getLaunchConfigValue(launchConfig, 'routes'));
|
|
524
652
|
}
|
|
525
653
|
export function agentConfigHasDesktop(source) {
|
|
526
654
|
if (!source)
|
|
527
655
|
return false;
|
|
528
656
|
return (launchConfigHasDesktop(source.launchConfig ?? source.launch_config) ||
|
|
529
|
-
routesHaveDesktop(source.routes)
|
|
530
|
-
portsHaveDesktop(source.ports));
|
|
657
|
+
routesHaveDesktop(source.routes));
|
|
531
658
|
}
|
|
532
659
|
function browserDesktopRedirectPath(options = {}) {
|
|
533
660
|
const redirect = (options.redirect ?? 'vnc.html').trim() || 'vnc.html';
|
|
@@ -744,11 +871,13 @@ function execResultFromDict(data) {
|
|
|
744
871
|
};
|
|
745
872
|
}
|
|
746
873
|
function agentStateFromDict(data) {
|
|
874
|
+
const launchConfig = isPlainRecord(data.launch_config) ? structuredClone(data.launch_config) : null;
|
|
875
|
+
if (launchConfig) {
|
|
876
|
+
delete launchConfig.secrets;
|
|
877
|
+
}
|
|
747
878
|
return {
|
|
748
879
|
id: data.id ?? '',
|
|
749
880
|
userId: data.user_id ?? '',
|
|
750
|
-
podId: data.pod_id ?? '',
|
|
751
|
-
podName: data.pod_name ?? '',
|
|
752
881
|
state: data.state ?? 'unknown',
|
|
753
882
|
name: data.name ?? null,
|
|
754
883
|
handle: data.handle ?? null,
|
|
@@ -769,15 +898,18 @@ function agentStateFromDict(data) {
|
|
|
769
898
|
jwtExpiresAt: parseDate(data.jwt_expires_at),
|
|
770
899
|
startedAt: parseDate(data.started_at),
|
|
771
900
|
stoppedAt: parseDate(data.stopped_at),
|
|
772
|
-
|
|
901
|
+
archivedAt: parseDate(data.archived_at),
|
|
902
|
+
clusterId: data.cluster_id ?? null,
|
|
903
|
+
launchEpoch: data.launch_epoch ?? 0,
|
|
773
904
|
createdAt: parseDate(data.created_at),
|
|
774
905
|
updatedAt: parseDate(data.updated_at),
|
|
775
|
-
launchConfig
|
|
906
|
+
launchConfig,
|
|
776
907
|
meta: data.meta?.ui ? { ui: structuredClone(data.meta.ui) } : null,
|
|
777
|
-
routes: data.routes ??
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
908
|
+
routes: data.routes ?? (isPlainRecord(launchConfig?.routes)
|
|
909
|
+
? launchConfig.routes
|
|
910
|
+
: {}),
|
|
911
|
+
command: data.command ?? (Array.isArray(launchConfig?.command) ? launchConfig.command : []),
|
|
912
|
+
entrypoint: data.entrypoint ?? (Array.isArray(launchConfig?.entrypoint) ? launchConfig.entrypoint : []),
|
|
781
913
|
dryRun: Boolean(data.dry_run),
|
|
782
914
|
};
|
|
783
915
|
}
|
|
@@ -802,13 +934,20 @@ export function buildAgentConfig(config = {}, options = {}) {
|
|
|
802
934
|
preparedConfig.agents = agentsConfig;
|
|
803
935
|
}
|
|
804
936
|
const env = { ...(options.env ?? {}) };
|
|
805
|
-
|
|
937
|
+
const secrets = { ...(options.secrets ?? {}) };
|
|
938
|
+
if (Object.prototype.hasOwnProperty.call(env, 'OPENCLAW_GATEWAY_TOKEN')) {
|
|
939
|
+
throw new Error('OPENCLAW_GATEWAY_TOKEN is a Secret; pass it through secrets or gatewayToken, not env');
|
|
940
|
+
}
|
|
941
|
+
let gatewayToken = null;
|
|
806
942
|
if (options.injectGatewayToken !== false) {
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
943
|
+
const explicitGatewayToken = options.gatewayToken?.trim() || null;
|
|
944
|
+
const secretGatewayToken = secrets.OPENCLAW_GATEWAY_TOKEN?.trim() || null;
|
|
945
|
+
if (explicitGatewayToken && secretGatewayToken && explicitGatewayToken !== secretGatewayToken) {
|
|
946
|
+
throw new Error('gatewayToken conflicts with secrets.OPENCLAW_GATEWAY_TOKEN');
|
|
947
|
+
}
|
|
948
|
+
gatewayToken = explicitGatewayToken ?? secretGatewayToken;
|
|
949
|
+
if (gatewayToken)
|
|
950
|
+
secrets.OPENCLAW_GATEWAY_TOKEN = gatewayToken;
|
|
812
951
|
if (options.controlUiOriginLock !== false && !env.OPENCLAW_CONTROL_UI_ALLOWED_ORIGIN?.trim()) {
|
|
813
952
|
const controlUiOrigin = defaultControlUiAllowedOrigin();
|
|
814
953
|
if (controlUiOrigin) {
|
|
@@ -816,13 +955,27 @@ export function buildAgentConfig(config = {}, options = {}) {
|
|
|
816
955
|
}
|
|
817
956
|
}
|
|
818
957
|
}
|
|
958
|
+
const collidingKeys = Object.keys(env).filter((key) => Object.prototype.hasOwnProperty.call(secrets, key));
|
|
959
|
+
if (collidingKeys.length > 0) {
|
|
960
|
+
throw new Error(`Launch keys cannot appear in both env and secrets: ${collidingKeys.join(', ')}`);
|
|
961
|
+
}
|
|
962
|
+
const normalizeSyncOwner = (value, field) => {
|
|
963
|
+
if (value === undefined || value === null)
|
|
964
|
+
return undefined;
|
|
965
|
+
if (!Number.isSafeInteger(value) || value < 0 || value > 4_294_967_294) {
|
|
966
|
+
throw new Error(`${field} must be an integer between 0 and 4294967294`);
|
|
967
|
+
}
|
|
968
|
+
return value;
|
|
969
|
+
};
|
|
970
|
+
const syncUid = normalizeSyncOwner(options.syncUid, 'syncUid');
|
|
971
|
+
const syncGid = normalizeSyncOwner(options.syncGid, 'syncGid');
|
|
819
972
|
const prepared = {};
|
|
820
973
|
if (Object.keys(preparedConfig).length > 0)
|
|
821
974
|
prepared.config = preparedConfig;
|
|
822
975
|
if (Object.keys(env).length > 0)
|
|
823
976
|
prepared.env = env;
|
|
824
|
-
if (
|
|
825
|
-
prepared.
|
|
977
|
+
if (Object.keys(secrets).length > 0)
|
|
978
|
+
prepared.secrets = secrets;
|
|
826
979
|
if (options.routes !== undefined && options.routes !== null)
|
|
827
980
|
prepared.routes = options.routes;
|
|
828
981
|
if (options.command !== undefined && options.command !== null)
|
|
@@ -833,18 +986,25 @@ export function buildAgentConfig(config = {}, options = {}) {
|
|
|
833
986
|
prepared.image = options.image;
|
|
834
987
|
if (options.syncRoot !== undefined && options.syncRoot !== null)
|
|
835
988
|
prepared.sync_root = options.syncRoot;
|
|
836
|
-
if (options.
|
|
837
|
-
prepared.
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
989
|
+
if (options.syncInclude !== undefined) {
|
|
990
|
+
prepared.sync_include = options.syncInclude === null ? null : [...options.syncInclude];
|
|
991
|
+
}
|
|
992
|
+
if (options.syncInclude === undefined && options.syncExclude !== undefined) {
|
|
993
|
+
prepared.sync_exclude = options.syncExclude === null ? null : [...options.syncExclude];
|
|
994
|
+
}
|
|
995
|
+
if (syncUid !== undefined)
|
|
996
|
+
prepared.sync_uid = syncUid;
|
|
997
|
+
if (syncGid !== undefined)
|
|
998
|
+
prepared.sync_gid = syncGid;
|
|
842
999
|
if (options.registryUrl !== undefined && options.registryUrl !== null)
|
|
843
1000
|
prepared.registry_url = options.registryUrl;
|
|
844
1001
|
if (options.registryAuth !== undefined && options.registryAuth !== null)
|
|
845
1002
|
prepared.registry_auth = options.registryAuth;
|
|
846
1003
|
if (options.restart !== undefined && options.restart !== null)
|
|
847
1004
|
prepared.restart = options.restart;
|
|
1005
|
+
if (options.runtimeScopes !== undefined && options.runtimeScopes !== null) {
|
|
1006
|
+
prepared.runtime_scopes = [...options.runtimeScopes];
|
|
1007
|
+
}
|
|
848
1008
|
return { config: prepared, gatewayToken };
|
|
849
1009
|
}
|
|
850
1010
|
function defaultOpenClawImage(image) {
|
|
@@ -857,10 +1017,17 @@ function defaultOpenClawProImage(image) {
|
|
|
857
1017
|
return image;
|
|
858
1018
|
return DEFAULT_OPENCLAW_PRO_IMAGE;
|
|
859
1019
|
}
|
|
860
|
-
function
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
return
|
|
1020
|
+
function defaultHermesAgentImage(image) {
|
|
1021
|
+
if (image !== undefined && image !== null)
|
|
1022
|
+
return image;
|
|
1023
|
+
return DEFAULT_HERMES_AGENT_IMAGE;
|
|
1024
|
+
}
|
|
1025
|
+
function resolveHermesApiServerKey(explicit, env) {
|
|
1026
|
+
const supplied = explicit?.trim() || env?.API_SERVER_KEY?.trim() || '';
|
|
1027
|
+
const key = supplied || randomHexToken(32);
|
|
1028
|
+
if (key.length < 32)
|
|
1029
|
+
throw new Error('Hermes API_SERVER_KEY must be at least 32 characters');
|
|
1030
|
+
return key;
|
|
864
1031
|
}
|
|
865
1032
|
export async function startSlackOAuth(options) {
|
|
866
1033
|
const relayBaseUrl = normalizeSlackRelayBaseUrl(options.relayBaseUrl);
|
|
@@ -1072,6 +1239,15 @@ export function buildOpenClawRoutes(options = {}) {
|
|
|
1072
1239
|
}
|
|
1073
1240
|
return routes;
|
|
1074
1241
|
}
|
|
1242
|
+
export function buildHermesAgentRoutes(options = {}) {
|
|
1243
|
+
return {
|
|
1244
|
+
hermes: {
|
|
1245
|
+
port: options.port ?? 8642,
|
|
1246
|
+
auth: options.auth ?? false,
|
|
1247
|
+
prefix: options.prefix ?? '',
|
|
1248
|
+
},
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1075
1251
|
function envBool(value) {
|
|
1076
1252
|
return value ? '1' : '0';
|
|
1077
1253
|
}
|
|
@@ -1116,9 +1292,6 @@ export function buildOpenClawWorkspacesSyncEnv(workspacesSync = null) {
|
|
|
1116
1292
|
if (options.enabled !== undefined && options.enabled !== null) {
|
|
1117
1293
|
env.HYPER_WORKSPACES_BOOT_SYNC = envBool(options.enabled);
|
|
1118
1294
|
}
|
|
1119
|
-
if (options.outputDir) {
|
|
1120
|
-
env.HYPER_WORKSPACES_DIR = options.outputDir;
|
|
1121
|
-
}
|
|
1122
1295
|
if (options.readyOnly !== undefined && options.readyOnly !== null) {
|
|
1123
1296
|
env.HYPER_WORKSPACES_SYNC_READY_ONLY = envBool(options.readyOnly);
|
|
1124
1297
|
}
|
|
@@ -1137,8 +1310,6 @@ function bindAgent(agent, deployments) {
|
|
|
1137
1310
|
export class Agent {
|
|
1138
1311
|
id;
|
|
1139
1312
|
userId;
|
|
1140
|
-
podId;
|
|
1141
|
-
podName;
|
|
1142
1313
|
state;
|
|
1143
1314
|
name;
|
|
1144
1315
|
handle;
|
|
@@ -1159,7 +1330,9 @@ export class Agent {
|
|
|
1159
1330
|
jwtExpiresAt;
|
|
1160
1331
|
startedAt;
|
|
1161
1332
|
stoppedAt;
|
|
1162
|
-
|
|
1333
|
+
archivedAt;
|
|
1334
|
+
clusterId;
|
|
1335
|
+
launchEpoch;
|
|
1163
1336
|
createdAt;
|
|
1164
1337
|
updatedAt;
|
|
1165
1338
|
launchConfig;
|
|
@@ -1167,14 +1340,11 @@ export class Agent {
|
|
|
1167
1340
|
routes;
|
|
1168
1341
|
command;
|
|
1169
1342
|
entrypoint;
|
|
1170
|
-
ports;
|
|
1171
1343
|
dryRun;
|
|
1172
1344
|
_deployments = null;
|
|
1173
1345
|
constructor(fields) {
|
|
1174
1346
|
this.id = fields.id;
|
|
1175
1347
|
this.userId = fields.userId;
|
|
1176
|
-
this.podId = fields.podId;
|
|
1177
|
-
this.podName = fields.podName;
|
|
1178
1348
|
this.state = fields.state;
|
|
1179
1349
|
this.name = fields.name ?? null;
|
|
1180
1350
|
this.handle = fields.handle ?? null;
|
|
@@ -1195,7 +1365,9 @@ export class Agent {
|
|
|
1195
1365
|
this.jwtExpiresAt = fields.jwtExpiresAt ?? null;
|
|
1196
1366
|
this.startedAt = fields.startedAt ?? null;
|
|
1197
1367
|
this.stoppedAt = fields.stoppedAt ?? null;
|
|
1198
|
-
this.
|
|
1368
|
+
this.archivedAt = fields.archivedAt ?? null;
|
|
1369
|
+
this.clusterId = fields.clusterId ?? null;
|
|
1370
|
+
this.launchEpoch = fields.launchEpoch ?? 0;
|
|
1199
1371
|
this.createdAt = fields.createdAt ?? null;
|
|
1200
1372
|
this.updatedAt = fields.updatedAt ?? null;
|
|
1201
1373
|
this.launchConfig = fields.launchConfig ?? null;
|
|
@@ -1203,7 +1375,6 @@ export class Agent {
|
|
|
1203
1375
|
this.routes = { ...fields.routes };
|
|
1204
1376
|
this.command = [...fields.command];
|
|
1205
1377
|
this.entrypoint = [...fields.entrypoint];
|
|
1206
|
-
this.ports = [...fields.ports];
|
|
1207
1378
|
this.dryRun = fields.dryRun;
|
|
1208
1379
|
}
|
|
1209
1380
|
static fromDict(data) {
|
|
@@ -1242,17 +1413,24 @@ export class Agent {
|
|
|
1242
1413
|
get shellUrl() {
|
|
1243
1414
|
return this.routeUrl('shell');
|
|
1244
1415
|
}
|
|
1245
|
-
get executorUrl() {
|
|
1246
|
-
return this.shellUrl;
|
|
1247
|
-
}
|
|
1248
1416
|
get isRunning() {
|
|
1249
1417
|
return this.state.toLowerCase() === 'running';
|
|
1250
1418
|
}
|
|
1419
|
+
get isTransitioning() {
|
|
1420
|
+
return isAgentTransitionalState(this.state);
|
|
1421
|
+
}
|
|
1422
|
+
/** True when the agent is cold-restorable from its verified archive. */
|
|
1423
|
+
get isArchived() {
|
|
1424
|
+
return this.state.toUpperCase() === 'ARCHIVED';
|
|
1425
|
+
}
|
|
1426
|
+
/** True only for an explicitly included deletion tombstone. */
|
|
1427
|
+
get isDeleted() {
|
|
1428
|
+
return this.state.toUpperCase() === 'DELETED';
|
|
1429
|
+
}
|
|
1251
1430
|
get hasDesktop() {
|
|
1252
1431
|
return agentConfigHasDesktop({
|
|
1253
1432
|
launchConfig: this.launchConfig,
|
|
1254
1433
|
routes: this.routes,
|
|
1255
|
-
ports: this.ports,
|
|
1256
1434
|
});
|
|
1257
1435
|
}
|
|
1258
1436
|
requireDeployments() {
|
|
@@ -1275,7 +1453,7 @@ export class Agent {
|
|
|
1275
1453
|
return data;
|
|
1276
1454
|
}
|
|
1277
1455
|
async waitRunning(timeoutMs = 300_000, pollIntervalMs = 5_000) {
|
|
1278
|
-
return this.requireDeployments().waitRunning(this.id, timeoutMs, pollIntervalMs);
|
|
1456
|
+
return this.requireDeployments().waitRunning(this.id, timeoutMs, pollIntervalMs, this.launchEpoch > 0 ? this.launchEpoch : undefined);
|
|
1279
1457
|
}
|
|
1280
1458
|
async update(options) {
|
|
1281
1459
|
return this.requireDeployments().update(this.id, options);
|
|
@@ -1284,15 +1462,29 @@ export class Agent {
|
|
|
1284
1462
|
return this.requireDeployments().resize(this.id, options);
|
|
1285
1463
|
}
|
|
1286
1464
|
async env() {
|
|
1287
|
-
const
|
|
1288
|
-
|
|
1465
|
+
const response = await this.requireDeployments().env(this.id);
|
|
1466
|
+
if (response.launch_epoch < this.launchEpoch) {
|
|
1467
|
+
throw new Error('agent env belongs to an older launch epoch');
|
|
1468
|
+
}
|
|
1469
|
+
return response.env;
|
|
1470
|
+
}
|
|
1471
|
+
async secretNames() {
|
|
1472
|
+
const response = await this.requireDeployments().secretNames(this.id);
|
|
1473
|
+
if (response.launch_epoch < this.launchEpoch) {
|
|
1474
|
+
throw new Error('agent Secret names belong to an older launch epoch');
|
|
1475
|
+
}
|
|
1476
|
+
return response.names;
|
|
1477
|
+
}
|
|
1478
|
+
async secret(key) {
|
|
1479
|
+
const response = await this.requireDeployments().secret(this.id, key);
|
|
1480
|
+
if (response.launch_epoch < this.launchEpoch) {
|
|
1481
|
+
throw new Error('agent Secret belongs to an older launch epoch');
|
|
1482
|
+
}
|
|
1483
|
+
return response.value;
|
|
1289
1484
|
}
|
|
1290
1485
|
async exec(command, options = {}) {
|
|
1291
1486
|
return this.requireDeployments().exec(this, command, options);
|
|
1292
1487
|
}
|
|
1293
|
-
async health() {
|
|
1294
|
-
return this.requireDeployments().health(this);
|
|
1295
|
-
}
|
|
1296
1488
|
/**
|
|
1297
1489
|
* The gateway file writer a subclass supplies — the seam that enables the
|
|
1298
1490
|
* `gateway` source. The base agent has none (gateway calls throw); an agent
|
|
@@ -1344,6 +1536,12 @@ export class Agent {
|
|
|
1344
1536
|
}
|
|
1345
1537
|
}
|
|
1346
1538
|
const RUNTIME_AUTH_CONFIG = {
|
|
1539
|
+
'buzz-agent': {
|
|
1540
|
+
agentCommand: ['buzz-agent'],
|
|
1541
|
+
statusCommand: ['buzz-acp', 'models', '--agent-command', 'buzz-agent', '--json'],
|
|
1542
|
+
logoutCommand: null,
|
|
1543
|
+
nativeMethods: [],
|
|
1544
|
+
},
|
|
1347
1545
|
opencode: {
|
|
1348
1546
|
agentCommand: ['opencode', 'acp'],
|
|
1349
1547
|
statusCommand: ['buzz-acp', 'models', '--agent-command', 'opencode', '--agent-args', 'acp', '--json'],
|
|
@@ -1442,19 +1640,22 @@ async function websocketMessageText(data) {
|
|
|
1442
1640
|
export class RuntimeLoginSession {
|
|
1443
1641
|
authClient;
|
|
1444
1642
|
socket;
|
|
1643
|
+
requiresDeviceChallenge;
|
|
1445
1644
|
output = '';
|
|
1446
1645
|
verificationUrl = null;
|
|
1447
1646
|
userCode = null;
|
|
1448
1647
|
interactiveRequired = false;
|
|
1648
|
+
rawOutput = '';
|
|
1449
1649
|
exitCode = null;
|
|
1450
1650
|
marker;
|
|
1451
1651
|
completion;
|
|
1452
1652
|
ready;
|
|
1453
1653
|
complete;
|
|
1454
1654
|
markReady;
|
|
1455
|
-
constructor(authClient, socket, command) {
|
|
1655
|
+
constructor(authClient, socket, command, requiresDeviceChallenge) {
|
|
1456
1656
|
this.authClient = authClient;
|
|
1457
1657
|
this.socket = socket;
|
|
1658
|
+
this.requiresDeviceChallenge = requiresDeviceChallenge;
|
|
1458
1659
|
this.marker = `__HYPERCLI_AUTH_EXIT_${randomHexToken(12)}__=`;
|
|
1459
1660
|
this.completion = new Promise((resolve) => { this.complete = resolve; });
|
|
1460
1661
|
this.ready = new Promise((resolve) => { this.markReady = resolve; });
|
|
@@ -1467,9 +1668,9 @@ export class RuntimeLoginSession {
|
|
|
1467
1668
|
};
|
|
1468
1669
|
socket.send(`${commandString(command)}; _hypercli_auth_rc=$?; printf '\\n${this.marker}%s\\n' "$_hypercli_auth_rc"\n`);
|
|
1469
1670
|
}
|
|
1470
|
-
static async start(authClient, command, challengeTimeoutMs = 45_000) {
|
|
1671
|
+
static async start(authClient, command, challengeTimeoutMs = 45_000, requiresDeviceChallenge = false) {
|
|
1471
1672
|
const socket = await authClient.agent.shellConnect();
|
|
1472
|
-
const session = new RuntimeLoginSession(authClient, socket, command);
|
|
1673
|
+
const session = new RuntimeLoginSession(authClient, socket, command, requiresDeviceChallenge);
|
|
1473
1674
|
let timer;
|
|
1474
1675
|
try {
|
|
1475
1676
|
await Promise.race([
|
|
@@ -1490,7 +1691,8 @@ export class RuntimeLoginSession {
|
|
|
1490
1691
|
}
|
|
1491
1692
|
}
|
|
1492
1693
|
consume(chunk) {
|
|
1493
|
-
this.
|
|
1694
|
+
this.rawOutput += chunk;
|
|
1695
|
+
this.output = stripTerminalCodes(this.rawOutput);
|
|
1494
1696
|
const markerIndex = this.output.lastIndexOf(this.marker);
|
|
1495
1697
|
if (markerIndex >= 0) {
|
|
1496
1698
|
const match = this.output.slice(markerIndex + this.marker.length).match(/^(-?\d+)/);
|
|
@@ -1500,10 +1702,13 @@ export class RuntimeLoginSession {
|
|
|
1500
1702
|
this.complete();
|
|
1501
1703
|
}
|
|
1502
1704
|
}
|
|
1503
|
-
this.verificationUrl ??= this.output.match(/https?:\/\/[^\s"'<>]
|
|
1504
|
-
this.userCode ??= this.output.match(
|
|
1705
|
+
this.verificationUrl ??= this.output.match(/https?:\/\/[^\s"'<>]+(?=[\s"'<>])/)?.[0] ?? null;
|
|
1706
|
+
this.userCode ??= this.output.match(/\b(?:user|device|verification|one[- ]time)\s+code\b\s*(?:is|:)?\s*(?:\([^\r\n)]*\)\s*)*((?!authorization\b)[A-Z0-9](?:[A-Z0-9-]*[A-Z0-9])?)(?=[\s.,;:)\]])/i)?.[1] ?? null;
|
|
1505
1707
|
this.interactiveRequired ||= /\b(select|choose)\b.*\b(provider|login method)\b/i.test(this.output);
|
|
1506
|
-
|
|
1708
|
+
const challengeReady = this.requiresDeviceChallenge
|
|
1709
|
+
? Boolean(this.verificationUrl && this.userCode)
|
|
1710
|
+
: Boolean(this.verificationUrl || this.userCode);
|
|
1711
|
+
if (challengeReady || this.interactiveRequired)
|
|
1507
1712
|
this.markReady();
|
|
1508
1713
|
}
|
|
1509
1714
|
send(text) {
|
|
@@ -1519,6 +1724,10 @@ export class RuntimeLoginSession {
|
|
|
1519
1724
|
}),
|
|
1520
1725
|
]);
|
|
1521
1726
|
}
|
|
1727
|
+
catch (error) {
|
|
1728
|
+
this.cancel();
|
|
1729
|
+
throw error;
|
|
1730
|
+
}
|
|
1522
1731
|
finally {
|
|
1523
1732
|
if (timer)
|
|
1524
1733
|
clearTimeout(timer);
|
|
@@ -1529,7 +1738,9 @@ export class RuntimeLoginSession {
|
|
|
1529
1738
|
return this.authClient.status();
|
|
1530
1739
|
}
|
|
1531
1740
|
cancel() {
|
|
1532
|
-
|
|
1741
|
+
// The SDK supports Node runtimes where the socket is supplied by `ws` and
|
|
1742
|
+
// no global WebSocket constructor exists. OPEN is the protocol state 1.
|
|
1743
|
+
if (this.socket.readyState === 1)
|
|
1533
1744
|
this.socket.send('\x03');
|
|
1534
1745
|
this.socket.close();
|
|
1535
1746
|
}
|
|
@@ -1632,7 +1843,10 @@ export class RuntimeAuthClient {
|
|
|
1632
1843
|
}
|
|
1633
1844
|
if (this.agent.runtime === 'claude-code' && options.email)
|
|
1634
1845
|
command.push('--email', options.email);
|
|
1635
|
-
|
|
1846
|
+
const requiresDeviceChallenge = method.kind === 'device'
|
|
1847
|
+
|| method.id === 'device'
|
|
1848
|
+
|| command.some((part) => part.toLowerCase().includes('device-auth'));
|
|
1849
|
+
return RuntimeLoginSession.start(this, command, options.challengeTimeoutMs, requiresDeviceChallenge);
|
|
1636
1850
|
}
|
|
1637
1851
|
async logout(provider) {
|
|
1638
1852
|
if (this.config.logoutCommand === null) {
|
|
@@ -1652,35 +1866,101 @@ export class RuntimeAuthClient {
|
|
|
1652
1866
|
}
|
|
1653
1867
|
}
|
|
1654
1868
|
export class CodingAgent extends Agent {
|
|
1869
|
+
static defaultSyncInclude = null;
|
|
1655
1870
|
get auth() {
|
|
1656
1871
|
return new RuntimeAuthClient(this);
|
|
1657
1872
|
}
|
|
1658
1873
|
}
|
|
1874
|
+
export class BuzzAgent extends CodingAgent {
|
|
1875
|
+
static defaultSyncInclude = DEFAULT_CODING_AGENT_SYNC_INCLUDES['buzz-agent'];
|
|
1876
|
+
static fromDict(data) {
|
|
1877
|
+
return new BuzzAgent(agentStateFromDict(data));
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1659
1880
|
export class OpenCodeAgent extends CodingAgent {
|
|
1881
|
+
static defaultSyncInclude = DEFAULT_CODING_AGENT_SYNC_INCLUDES.opencode;
|
|
1660
1882
|
static fromDict(data) {
|
|
1661
1883
|
return new OpenCodeAgent(agentStateFromDict(data));
|
|
1662
1884
|
}
|
|
1663
1885
|
}
|
|
1664
1886
|
export class CodexAgent extends CodingAgent {
|
|
1887
|
+
static defaultSyncInclude = DEFAULT_CODING_AGENT_SYNC_INCLUDES.codex;
|
|
1665
1888
|
static fromDict(data) {
|
|
1666
1889
|
return new CodexAgent(agentStateFromDict(data));
|
|
1667
1890
|
}
|
|
1668
1891
|
}
|
|
1669
1892
|
export class ClaudeCodeAgent extends CodingAgent {
|
|
1893
|
+
static defaultSyncInclude = DEFAULT_CODING_AGENT_SYNC_INCLUDES['claude-code'];
|
|
1670
1894
|
static fromDict(data) {
|
|
1671
1895
|
return new ClaudeCodeAgent(agentStateFromDict(data));
|
|
1672
1896
|
}
|
|
1673
1897
|
}
|
|
1674
1898
|
export class GooseAgent extends CodingAgent {
|
|
1899
|
+
static defaultSyncInclude = DEFAULT_CODING_AGENT_SYNC_INCLUDES.goose;
|
|
1675
1900
|
static fromDict(data) {
|
|
1676
1901
|
return new GooseAgent(agentStateFromDict(data));
|
|
1677
1902
|
}
|
|
1678
1903
|
}
|
|
1679
1904
|
export class KimiCodeAgent extends CodingAgent {
|
|
1905
|
+
static defaultSyncInclude = DEFAULT_CODING_AGENT_SYNC_INCLUDES['kimi-code'];
|
|
1680
1906
|
static fromDict(data) {
|
|
1681
1907
|
return new KimiCodeAgent(agentStateFromDict(data));
|
|
1682
1908
|
}
|
|
1683
1909
|
}
|
|
1910
|
+
const CODING_AGENT_CLASSES = {
|
|
1911
|
+
'buzz-agent': BuzzAgent,
|
|
1912
|
+
opencode: OpenCodeAgent,
|
|
1913
|
+
codex: CodexAgent,
|
|
1914
|
+
'claude-code': ClaudeCodeAgent,
|
|
1915
|
+
goose: GooseAgent,
|
|
1916
|
+
'kimi-code': KimiCodeAgent,
|
|
1917
|
+
};
|
|
1918
|
+
export class HermesAgent extends Agent {
|
|
1919
|
+
apiUrl;
|
|
1920
|
+
openaiBaseUrl;
|
|
1921
|
+
/** Available only on the instance returned by createHermesAgent/startHermesAgent. */
|
|
1922
|
+
apiServerKey;
|
|
1923
|
+
constructor(fields) {
|
|
1924
|
+
super(fields);
|
|
1925
|
+
this.apiUrl = fields.apiUrl ?? this.routeUrl('hermes', '');
|
|
1926
|
+
this.openaiBaseUrl = this.apiUrl ? `${this.apiUrl.replace(/\/+$/, '')}/v1` : null;
|
|
1927
|
+
this.apiServerKey = fields.apiServerKey ?? null;
|
|
1928
|
+
}
|
|
1929
|
+
static fromDict(data) {
|
|
1930
|
+
const fields = agentStateFromDict(data);
|
|
1931
|
+
if (fields.launchConfig?.env && typeof fields.launchConfig.env === 'object') {
|
|
1932
|
+
fields.launchConfig = structuredClone(fields.launchConfig);
|
|
1933
|
+
delete fields.launchConfig.env.API_SERVER_KEY;
|
|
1934
|
+
}
|
|
1935
|
+
return new HermesAgent({
|
|
1936
|
+
...fields,
|
|
1937
|
+
// Never recover API_SERVER_KEY from launch_config or process env.
|
|
1938
|
+
apiServerKey: null,
|
|
1939
|
+
});
|
|
1940
|
+
}
|
|
1941
|
+
/** Create a client for this agent's authenticated Hermes API. */
|
|
1942
|
+
get api() {
|
|
1943
|
+
if (!this.apiUrl || !this.apiServerKey)
|
|
1944
|
+
return null;
|
|
1945
|
+
return new HermesApiClient(this.apiUrl, { apiKey: this.apiServerKey });
|
|
1946
|
+
}
|
|
1947
|
+
/** Require a ready URL and the one-time SDK-retained API server key. */
|
|
1948
|
+
apiClient() {
|
|
1949
|
+
const client = this.api;
|
|
1950
|
+
if (!client) {
|
|
1951
|
+
throw new Error('Hermes API context is unavailable; use the HermesAgent returned by createHermesAgent/startHermesAgent after its hostname is attached');
|
|
1952
|
+
}
|
|
1953
|
+
return client;
|
|
1954
|
+
}
|
|
1955
|
+
async waitRunning(timeoutMs = 300_000, pollIntervalMs = 5_000) {
|
|
1956
|
+
const ready = await super.waitRunning(timeoutMs, pollIntervalMs);
|
|
1957
|
+
if (!(ready instanceof HermesAgent)) {
|
|
1958
|
+
throw new Error("Running deployment did not identify runtime 'hermes-agent'");
|
|
1959
|
+
}
|
|
1960
|
+
ready.apiServerKey = this.apiServerKey;
|
|
1961
|
+
return ready;
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1684
1964
|
export class OpenClawAgent extends Agent {
|
|
1685
1965
|
gatewayUrl;
|
|
1686
1966
|
gatewayToken;
|
|
@@ -1693,33 +1973,14 @@ export class OpenClawAgent extends Agent {
|
|
|
1693
1973
|
return new OpenClawAgent({
|
|
1694
1974
|
...agentStateFromDict(data),
|
|
1695
1975
|
gatewayUrl: this.gatewayUrlFromHostname(data.hostname),
|
|
1696
|
-
gatewayToken:
|
|
1976
|
+
gatewayToken: null,
|
|
1697
1977
|
});
|
|
1698
1978
|
}
|
|
1699
1979
|
static gatewayUrlFromHostname(hostname) {
|
|
1700
1980
|
const trimmed = String(hostname ?? '').trim();
|
|
1701
1981
|
return trimmed ? `wss://${trimmed}` : null;
|
|
1702
1982
|
}
|
|
1703
|
-
|
|
1704
|
-
if (this.hostname)
|
|
1705
|
-
return this.hostname;
|
|
1706
|
-
if (!this.gatewayUrl)
|
|
1707
|
-
return null;
|
|
1708
|
-
try {
|
|
1709
|
-
return new URL(this.gatewayUrl).hostname || null;
|
|
1710
|
-
}
|
|
1711
|
-
catch {
|
|
1712
|
-
return this.gatewayUrl.replace(/^wss?:\/\//, '').split('/')[0] || null;
|
|
1713
|
-
}
|
|
1714
|
-
}
|
|
1715
|
-
/**
|
|
1716
|
-
* Resolve gateway context through the deployment record plus `/env`.
|
|
1717
|
-
*
|
|
1718
|
-
* Agent startup is eventually consistent: the deployment record may lag
|
|
1719
|
-
* behind hostname attachment, and runtime env can lag behind both. The SDK
|
|
1720
|
-
* derives the gateway URL from the attached hostname and reads the gateway
|
|
1721
|
-
* token from `OPENCLAW_GATEWAY_TOKEN` in the agent env route.
|
|
1722
|
-
*/
|
|
1983
|
+
/** Resolve OpenClaw connection context from the generic Agent and Secret APIs. */
|
|
1723
1984
|
async waitForGatewayContext(options = {}) {
|
|
1724
1985
|
const callerAbortError = () => {
|
|
1725
1986
|
if (options.signal?.reason instanceof Error)
|
|
@@ -1730,13 +1991,6 @@ export class OpenClawAgent extends Agent {
|
|
|
1730
1991
|
};
|
|
1731
1992
|
if (options.signal?.aborted)
|
|
1732
1993
|
throw callerAbortError();
|
|
1733
|
-
if (this.gatewayToken && this.gatewayUrl) {
|
|
1734
|
-
return {
|
|
1735
|
-
agent_id: this.id,
|
|
1736
|
-
hostname: this.currentGatewayHostname(),
|
|
1737
|
-
gateway_token: this.gatewayToken,
|
|
1738
|
-
};
|
|
1739
|
-
}
|
|
1740
1994
|
const timeoutMs = options.timeoutMs ?? 30_000;
|
|
1741
1995
|
const retryIntervalMs = options.retryIntervalMs ?? 1_000;
|
|
1742
1996
|
const deployments = this.requireDeployments();
|
|
@@ -1799,31 +2053,41 @@ export class OpenClawAgent extends Agent {
|
|
|
1799
2053
|
signal: controller.signal,
|
|
1800
2054
|
timeout: Math.max(1, remainingMs),
|
|
1801
2055
|
};
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
2056
|
+
try {
|
|
2057
|
+
const refreshed = await runWithAbort(deployments.get(this.id, requestOptions));
|
|
2058
|
+
if (refreshed.launchEpoch < this.launchEpoch) {
|
|
2059
|
+
throw new Error('gateway Agent belongs to an older launch epoch');
|
|
2060
|
+
}
|
|
2061
|
+
if (refreshed.state.toUpperCase() !== 'RUNNING') {
|
|
2062
|
+
throw new Error(`gateway Agent is ${refreshed.state}, not RUNNING`);
|
|
2063
|
+
}
|
|
2064
|
+
const secret = await runWithAbort(deployments.secret(this.id, 'OPENCLAW_GATEWAY_TOKEN', requestOptions));
|
|
2065
|
+
if (secret.launch_epoch !== refreshed.launchEpoch) {
|
|
2066
|
+
throw new Error('gateway Secret and Agent belong to different launch epochs');
|
|
2067
|
+
}
|
|
2068
|
+
const gatewayToken = secret.value.trim();
|
|
2069
|
+
if (!gatewayToken)
|
|
2070
|
+
throw new Error('OPENCLAW_GATEWAY_TOKEN is empty');
|
|
2071
|
+
const confirmed = await runWithAbort(deployments.get(this.id, requestOptions));
|
|
2072
|
+
if (confirmed.launchEpoch !== refreshed.launchEpoch
|
|
2073
|
+
|| confirmed.state.toUpperCase() !== 'RUNNING') {
|
|
2074
|
+
throw new Error('gateway Agent changed while its Secret was resolved');
|
|
2075
|
+
}
|
|
2076
|
+
const gatewayUrl = OpenClawAgent.gatewayUrlFromHostname(confirmed.hostname);
|
|
2077
|
+
if (!gatewayUrl)
|
|
2078
|
+
throw new Error('gateway Agent has no hostname');
|
|
2079
|
+
this.gatewayUrl = gatewayUrl;
|
|
2080
|
+
this.gatewayToken = gatewayToken;
|
|
1820
2081
|
return {
|
|
1821
2082
|
agent_id: this.id,
|
|
1822
|
-
|
|
1823
|
-
gateway_token:
|
|
2083
|
+
gateway_url: gatewayUrl,
|
|
2084
|
+
gateway_token: gatewayToken,
|
|
2085
|
+
launch_epoch: refreshed.launchEpoch,
|
|
1824
2086
|
};
|
|
1825
2087
|
}
|
|
1826
|
-
|
|
2088
|
+
catch (error) {
|
|
2089
|
+
lastError = error;
|
|
2090
|
+
}
|
|
1827
2091
|
const remainingAfterRequestMs = deadline - Date.now();
|
|
1828
2092
|
if (remainingAfterRequestMs <= 0)
|
|
1829
2093
|
throw timeoutError();
|
|
@@ -2063,6 +2327,35 @@ export class OpenClawAgent extends Agent {
|
|
|
2063
2327
|
client.close();
|
|
2064
2328
|
}
|
|
2065
2329
|
}
|
|
2330
|
+
async operationsSnapshot(options = {}) {
|
|
2331
|
+
if (!this.gatewayUrl || (!this.gatewayToken && !options.gatewayToken)) {
|
|
2332
|
+
await this.waitForGatewayContext({ timeoutMs: options.timeout });
|
|
2333
|
+
}
|
|
2334
|
+
const client = this.gateway(options);
|
|
2335
|
+
try {
|
|
2336
|
+
await client.connect();
|
|
2337
|
+
const [sessionsResult, cronResult] = await Promise.allSettled([
|
|
2338
|
+
client.sessionsListResult(),
|
|
2339
|
+
client.cronList(),
|
|
2340
|
+
]);
|
|
2341
|
+
const failures = {};
|
|
2342
|
+
if (sessionsResult.status === 'rejected') {
|
|
2343
|
+
failures.sessions = stringifyOpenClawOperationsFailure(sessionsResult.reason);
|
|
2344
|
+
}
|
|
2345
|
+
if (cronResult.status === 'rejected') {
|
|
2346
|
+
failures.cron = stringifyOpenClawOperationsFailure(cronResult.reason);
|
|
2347
|
+
}
|
|
2348
|
+
return {
|
|
2349
|
+
sessions: sessionsResult.status === 'fulfilled' ? sessionsResult.value : null,
|
|
2350
|
+
cronJobs: cronResult.status === 'fulfilled' ? cronResult.value : null,
|
|
2351
|
+
failures,
|
|
2352
|
+
capturedAt: Date.now(),
|
|
2353
|
+
};
|
|
2354
|
+
}
|
|
2355
|
+
finally {
|
|
2356
|
+
client.close();
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2066
2359
|
async *chatSend(message, sessionKey, options = {}) {
|
|
2067
2360
|
const client = await this.connect(options);
|
|
2068
2361
|
try {
|
|
@@ -2407,7 +2700,7 @@ export class OpenClawProAgent extends OpenClawAgent {
|
|
|
2407
2700
|
return new OpenClawProAgent({
|
|
2408
2701
|
...agentStateFromDict(data),
|
|
2409
2702
|
gatewayUrl: this.gatewayUrlFromHostname(data.hostname),
|
|
2410
|
-
gatewayToken:
|
|
2703
|
+
gatewayToken: null,
|
|
2411
2704
|
});
|
|
2412
2705
|
}
|
|
2413
2706
|
}
|
|
@@ -2433,7 +2726,13 @@ export class Deployments {
|
|
|
2433
2726
|
}
|
|
2434
2727
|
hydrateAgent(data) {
|
|
2435
2728
|
let agent;
|
|
2436
|
-
if (data.runtime === '
|
|
2729
|
+
if (data.runtime === 'hermes-agent') {
|
|
2730
|
+
agent = HermesAgent.fromDict(data);
|
|
2731
|
+
}
|
|
2732
|
+
else if (data.runtime === 'buzz-agent') {
|
|
2733
|
+
agent = BuzzAgent.fromDict(data);
|
|
2734
|
+
}
|
|
2735
|
+
else if (data.runtime === 'opencode') {
|
|
2437
2736
|
agent = OpenCodeAgent.fromDict(data);
|
|
2438
2737
|
}
|
|
2439
2738
|
else if (data.runtime === 'codex') {
|
|
@@ -2476,7 +2775,7 @@ export class Deployments {
|
|
|
2476
2775
|
}
|
|
2477
2776
|
const matches = [];
|
|
2478
2777
|
for (const agent of await this.list(requestOptions)) {
|
|
2479
|
-
const values = [agent.id, agent.name, agent.handle, agent.
|
|
2778
|
+
const values = [agent.id, agent.name, agent.handle, agent.hostname];
|
|
2480
2779
|
if (values.some((value) => String(value || '') === raw)) {
|
|
2481
2780
|
matches.push(agent);
|
|
2482
2781
|
continue;
|
|
@@ -2493,11 +2792,16 @@ export class Deployments {
|
|
|
2493
2792
|
}
|
|
2494
2793
|
return this.getById(matches[0].id, requestOptions);
|
|
2495
2794
|
}
|
|
2496
|
-
async resolveAgentId(agentIdOrName, requestOptions = {}) {
|
|
2795
|
+
async resolveAgentId(agentIdOrName, requestOptions = {}, allowSelf = false) {
|
|
2497
2796
|
const raw = String(agentIdOrName || '').trim();
|
|
2498
2797
|
if (!raw) {
|
|
2499
2798
|
throw new Error('agentIdOrName is required');
|
|
2500
2799
|
}
|
|
2800
|
+
if (isSelfAgentRef(raw)) {
|
|
2801
|
+
if (allowSelf)
|
|
2802
|
+
return 'self';
|
|
2803
|
+
throw new Error('self is only supported for status, start, stop, and routes');
|
|
2804
|
+
}
|
|
2501
2805
|
if (isDirectAgentIdRef(raw)) {
|
|
2502
2806
|
return raw;
|
|
2503
2807
|
}
|
|
@@ -2533,7 +2837,7 @@ export class Deployments {
|
|
|
2533
2837
|
return response;
|
|
2534
2838
|
}
|
|
2535
2839
|
async create(options = {}) {
|
|
2536
|
-
const { config
|
|
2840
|
+
const { config } = buildAgentConfig(options.config ?? {}, {
|
|
2537
2841
|
...options,
|
|
2538
2842
|
injectGatewayToken: options.injectGatewayToken ?? !isCodingAgentRuntime(options.runtime),
|
|
2539
2843
|
});
|
|
@@ -2552,40 +2856,71 @@ export class Deployments {
|
|
|
2552
2856
|
body.tags = [...options.tags];
|
|
2553
2857
|
if (options.runtime)
|
|
2554
2858
|
body.runtime = options.runtime;
|
|
2555
|
-
const data = await this.agentHttp.post(DEPLOYMENTS_API_PREFIX, body);
|
|
2556
|
-
|
|
2557
|
-
if (agent instanceof OpenClawAgent) {
|
|
2558
|
-
agent.gatewayToken = gatewayToken;
|
|
2559
|
-
}
|
|
2560
|
-
agent.launchConfig = config;
|
|
2561
|
-
agent.command = [...(config.command ?? [])];
|
|
2562
|
-
agent.entrypoint = [...(config.entrypoint ?? [])];
|
|
2563
|
-
return agent;
|
|
2859
|
+
const data = await this.agentHttp.post(DEPLOYMENTS_API_PREFIX, body, { retries: 1 });
|
|
2860
|
+
return this.hydrateAgent(data);
|
|
2564
2861
|
}
|
|
2565
2862
|
async createOpenClaw(options = {}) {
|
|
2566
2863
|
const effectiveOptions = { ...options, runtime: options.runtime ?? 'openclaw' };
|
|
2567
2864
|
effectiveOptions.env = {
|
|
2568
|
-
HYPER_API_BASE: productApiBaseFromAgentsApiBase(this.apiBase),
|
|
2569
2865
|
...buildOpenClawWorkspacesSyncEnv(options.workspacesSync ?? null),
|
|
2570
2866
|
...buildOpenClawMemoryIndexEnv(options.memoryIndex),
|
|
2571
2867
|
...(options.env ?? {}),
|
|
2572
2868
|
};
|
|
2869
|
+
if (effectiveOptions.env.HYPER_WORKSPACES_BOOT_SYNC !== '0') {
|
|
2870
|
+
effectiveOptions.env.HYPER_WORKSPACES_DIR = '/home/node/shared';
|
|
2871
|
+
}
|
|
2573
2872
|
if (options.routes === undefined) {
|
|
2574
2873
|
effectiveOptions.routes = buildOpenClawRoutes(options.openClawRoutes ?? {});
|
|
2575
2874
|
}
|
|
2576
2875
|
effectiveOptions.image = defaultOpenClawImage(options.image);
|
|
2577
2876
|
if (effectiveOptions.syncRoot === undefined)
|
|
2578
2877
|
effectiveOptions.syncRoot = DEFAULT_OPENCLAW_SYNC_ROOT;
|
|
2579
|
-
if (
|
|
2580
|
-
effectiveOptions.
|
|
2878
|
+
if (options.syncInclude === undefined && options.syncExclude === undefined) {
|
|
2879
|
+
effectiveOptions.syncExclude = DEFAULT_OPENCLAW_SYNC_EXCLUDE;
|
|
2880
|
+
}
|
|
2581
2881
|
return this.create(effectiveOptions);
|
|
2582
2882
|
}
|
|
2883
|
+
async createHermesAgent(options = {}) {
|
|
2884
|
+
const apiServerKey = resolveHermesApiServerKey(options.apiServerKey, options.env);
|
|
2885
|
+
const env = {
|
|
2886
|
+
...(options.env ?? {}),
|
|
2887
|
+
API_SERVER_ENABLED: 'true',
|
|
2888
|
+
API_SERVER_HOST: '0.0.0.0',
|
|
2889
|
+
API_SERVER_KEY: apiServerKey,
|
|
2890
|
+
};
|
|
2891
|
+
delete env.OPENCLAW_GATEWAY_TOKEN;
|
|
2892
|
+
const effectiveOptions = {
|
|
2893
|
+
...options,
|
|
2894
|
+
runtime: 'hermes-agent',
|
|
2895
|
+
env,
|
|
2896
|
+
image: defaultHermesAgentImage(options.image),
|
|
2897
|
+
runtimeScopes: options.runtimeScopes ?? DEFAULT_AGENT_RUNTIME_SCOPES,
|
|
2898
|
+
injectGatewayToken: false,
|
|
2899
|
+
syncRoot: options.syncRoot ?? DEFAULT_HERMES_AGENT_SYNC_ROOT,
|
|
2900
|
+
syncUid: options.syncUid ?? DEFAULT_HERMES_AGENT_SYNC_UID,
|
|
2901
|
+
syncGid: options.syncGid ?? DEFAULT_HERMES_AGENT_SYNC_GID,
|
|
2902
|
+
routes: options.routes === undefined
|
|
2903
|
+
? buildHermesAgentRoutes(options.hermesRoute ?? {})
|
|
2904
|
+
: options.routes,
|
|
2905
|
+
};
|
|
2906
|
+
const agent = await this.create(effectiveOptions);
|
|
2907
|
+
if (!(agent instanceof HermesAgent)) {
|
|
2908
|
+
throw new Error("Hermes deployment response did not identify runtime 'hermes-agent'");
|
|
2909
|
+
}
|
|
2910
|
+
agent.apiServerKey = apiServerKey;
|
|
2911
|
+
if (agent.launchConfig?.env && typeof agent.launchConfig.env === 'object') {
|
|
2912
|
+
agent.launchConfig = structuredClone(agent.launchConfig);
|
|
2913
|
+
delete agent.launchConfig.env.API_SERVER_KEY;
|
|
2914
|
+
}
|
|
2915
|
+
return agent;
|
|
2916
|
+
}
|
|
2583
2917
|
async createOpenClawPro(options = {}) {
|
|
2584
2918
|
return this.createOpenClaw({
|
|
2585
2919
|
...options,
|
|
2586
2920
|
runtime: 'openclaw-pro',
|
|
2587
2921
|
env: { OPENCLAW_DESKTOP_ENABLED: '1', ...(options.env ?? {}) },
|
|
2588
2922
|
image: defaultOpenClawProImage(options.image),
|
|
2923
|
+
runtimeScopes: options.runtimeScopes ?? DEFAULT_AGENT_RUNTIME_SCOPES,
|
|
2589
2924
|
openClawRoutes: { includeDesktop: true, ...(options.openClawRoutes ?? {}) },
|
|
2590
2925
|
});
|
|
2591
2926
|
}
|
|
@@ -2601,24 +2936,57 @@ export class Deployments {
|
|
|
2601
2936
|
throw new Error("Buzz coding agents require size='large'");
|
|
2602
2937
|
}
|
|
2603
2938
|
const effectiveEnv = {
|
|
2604
|
-
HYPER_API_BASE: productApiBaseFromAgentsApiBase(this.apiBase),
|
|
2605
2939
|
...buildOpenClawWorkspacesSyncEnv(options.workspacesSync ?? null),
|
|
2606
2940
|
...(options.env ?? {}),
|
|
2607
2941
|
};
|
|
2942
|
+
const effectiveSecrets = { ...(options.secrets ?? {}) };
|
|
2943
|
+
for (const key of ['BUZZ_PRIVATE_KEY', 'NOSTR_PRIVATE_KEY']) {
|
|
2944
|
+
const value = effectiveEnv[key];
|
|
2945
|
+
if (value === undefined)
|
|
2946
|
+
continue;
|
|
2947
|
+
delete effectiveEnv[key];
|
|
2948
|
+
if (effectiveSecrets[key] !== undefined && effectiveSecrets[key] !== value) {
|
|
2949
|
+
throw new Error(`${key} conflicts between env and secrets`);
|
|
2950
|
+
}
|
|
2951
|
+
effectiveSecrets[key] = value;
|
|
2952
|
+
}
|
|
2953
|
+
if (effectiveEnv.HYPER_WORKSPACES_BOOT_SYNC !== '0') {
|
|
2954
|
+
effectiveEnv.HYPER_WORKSPACES_DIR = '/home/node/shared';
|
|
2955
|
+
}
|
|
2608
2956
|
if (options.buzz) {
|
|
2609
2957
|
for (const key of BUZZ_RESERVED_ENV_KEYS)
|
|
2610
2958
|
delete effectiveEnv[key];
|
|
2611
2959
|
Object.assign(effectiveEnv, buildBuzzLaunchEnv(runtime, options.buzz, options.name));
|
|
2960
|
+
Object.assign(effectiveSecrets, buildBuzzLaunchSecrets(options.buzz));
|
|
2612
2961
|
}
|
|
2613
2962
|
if (buzzLaunch) {
|
|
2614
2963
|
effectiveEnv.RUST_LOG ??= DEFAULT_BUZZ_RUST_LOG;
|
|
2615
2964
|
}
|
|
2965
|
+
let syncInclude;
|
|
2966
|
+
let syncExclude;
|
|
2967
|
+
if (options.syncInclude !== undefined && options.syncInclude !== null) {
|
|
2968
|
+
syncInclude = options.syncInclude;
|
|
2969
|
+
syncExclude = undefined;
|
|
2970
|
+
}
|
|
2971
|
+
else if (options.syncExclude !== undefined) {
|
|
2972
|
+
syncInclude = undefined;
|
|
2973
|
+
syncExclude = options.syncExclude ?? undefined;
|
|
2974
|
+
}
|
|
2975
|
+
else if (options.syncInclude === null) {
|
|
2976
|
+
syncInclude = undefined;
|
|
2977
|
+
syncExclude = undefined;
|
|
2978
|
+
}
|
|
2979
|
+
else {
|
|
2980
|
+
syncInclude = CODING_AGENT_CLASSES[runtime].defaultSyncInclude;
|
|
2981
|
+
syncExclude = undefined;
|
|
2982
|
+
}
|
|
2616
2983
|
const effectiveOptions = {
|
|
2617
2984
|
...options,
|
|
2618
2985
|
runtime,
|
|
2619
2986
|
size: buzzLaunch ? 'large' : options.size,
|
|
2620
2987
|
injectGatewayToken: false,
|
|
2621
2988
|
env: effectiveEnv,
|
|
2989
|
+
secrets: effectiveSecrets,
|
|
2622
2990
|
routes: options.routes ?? {},
|
|
2623
2991
|
image: options.image ?? (buzzLaunch
|
|
2624
2992
|
? DEFAULT_BUZZ_CODING_AGENT_IMAGES[runtime]
|
|
@@ -2627,18 +2995,23 @@ export class Deployments {
|
|
|
2627
2995
|
? ['/usr/local/bin/buzz-acp']
|
|
2628
2996
|
: options.command,
|
|
2629
2997
|
syncRoot: options.syncRoot ?? DEFAULT_CODING_AGENT_SYNC_ROOT,
|
|
2630
|
-
|
|
2998
|
+
syncInclude,
|
|
2999
|
+
syncExclude,
|
|
2631
3000
|
syncUid: options.syncUid ?? 1000,
|
|
2632
3001
|
syncGid: options.syncGid ?? 1000,
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
3002
|
+
// Hosted Buzz shutdown is process-driven; generic launch options cannot
|
|
3003
|
+
// opt it back into automatic restart.
|
|
3004
|
+
restart: buzzLaunch ? false : options.restart,
|
|
3005
|
+
runtimeScopes: options.runtimeScopes ?? DEFAULT_AGENT_RUNTIME_SCOPES,
|
|
2636
3006
|
};
|
|
2637
3007
|
return await this.create(effectiveOptions);
|
|
2638
3008
|
}
|
|
2639
3009
|
async createOpenCode(options = {}) {
|
|
2640
3010
|
return await this.createCodingAgent('opencode', options);
|
|
2641
3011
|
}
|
|
3012
|
+
async createBuzzAgent(options = {}) {
|
|
3013
|
+
return await this.createCodingAgent('buzz-agent', options);
|
|
3014
|
+
}
|
|
2642
3015
|
async createCodex(options = {}) {
|
|
2643
3016
|
return await this.createCodingAgent('codex', options);
|
|
2644
3017
|
}
|
|
@@ -2665,6 +3038,9 @@ export class Deployments {
|
|
|
2665
3038
|
return this.agentHttp.get(`${DEPLOYMENTS_API_PREFIX}/${agentId}/metrics`);
|
|
2666
3039
|
}
|
|
2667
3040
|
async list(options = {}) {
|
|
3041
|
+
return (await this.listWithCapacity(options)).items;
|
|
3042
|
+
}
|
|
3043
|
+
async listWithCapacity(options = {}) {
|
|
2668
3044
|
const params = {};
|
|
2669
3045
|
if (options.state)
|
|
2670
3046
|
params.state = options.state;
|
|
@@ -2692,13 +3068,33 @@ export class Deployments {
|
|
|
2692
3068
|
}
|
|
2693
3069
|
}
|
|
2694
3070
|
const data = await this.agentHttp.get(DEPLOYMENTS_API_PREFIX, Object.keys(params).length ? params : undefined, Object.keys(cleanRequestOptions).length ? cleanRequestOptions : undefined);
|
|
2695
|
-
const
|
|
2696
|
-
|
|
3071
|
+
const payload = Array.isArray(data) ? { items: data } : data;
|
|
3072
|
+
const items = (payload.items ?? []).map((item) => this.hydrateAgent(item));
|
|
3073
|
+
const runningFallback = items.filter((agent) => !isAgentRuntimeInactiveState(agent.state)).length;
|
|
3074
|
+
return {
|
|
3075
|
+
items,
|
|
3076
|
+
totalAgents: Number(payload.total_agents ?? items.length),
|
|
3077
|
+
maxAgentsPerAccount: Number(payload.max_agents_per_account ?? 0),
|
|
3078
|
+
runningAgents: Number(payload.running_agents ?? runningFallback),
|
|
3079
|
+
slots: Object.fromEntries(Object.entries(payload.slots ?? {}).map(([size, raw]) => {
|
|
3080
|
+
const inventory = raw;
|
|
3081
|
+
return [size, {
|
|
3082
|
+
granted: Number(inventory.granted ?? 0),
|
|
3083
|
+
used: Number(inventory.used ?? inventory.occupied ?? 0),
|
|
3084
|
+
available: Number(inventory.available ?? 0),
|
|
3085
|
+
}];
|
|
3086
|
+
})),
|
|
3087
|
+
agentSlots: (payload.agent_slots ?? []).map(agentSlotFromDict),
|
|
3088
|
+
pooledTpd: Number(payload.pooled_tpd ?? 0),
|
|
3089
|
+
};
|
|
2697
3090
|
}
|
|
2698
3091
|
async get(agentIdOrName, requestOptions = {}) {
|
|
2699
3092
|
const raw = String(agentIdOrName || '').trim();
|
|
2700
3093
|
if (!raw)
|
|
2701
3094
|
throw new Error('agentIdOrName is required');
|
|
3095
|
+
if (isSelfAgentRef(raw)) {
|
|
3096
|
+
return this.getById('self', requestOptions);
|
|
3097
|
+
}
|
|
2702
3098
|
if (!isDirectAgentIdRef(raw)) {
|
|
2703
3099
|
return this.resolveAgent(raw, requestOptions);
|
|
2704
3100
|
}
|
|
@@ -2767,77 +3163,240 @@ export class Deployments {
|
|
|
2767
3163
|
agentId,
|
|
2768
3164
|
});
|
|
2769
3165
|
}
|
|
2770
|
-
async
|
|
3166
|
+
async subscribe(handler, options = {}) {
|
|
3167
|
+
let retryDelay = 250;
|
|
3168
|
+
const waitBeforeReconnect = async () => {
|
|
3169
|
+
let abortRetry = () => { };
|
|
3170
|
+
const aborted = new Promise((resolve) => {
|
|
3171
|
+
if (options.signal?.aborted)
|
|
3172
|
+
resolve();
|
|
3173
|
+
else {
|
|
3174
|
+
abortRetry = resolve;
|
|
3175
|
+
options.signal?.addEventListener('abort', abortRetry, { once: true });
|
|
3176
|
+
}
|
|
3177
|
+
});
|
|
3178
|
+
try {
|
|
3179
|
+
await Promise.race([sleep(retryDelay), aborted]);
|
|
3180
|
+
}
|
|
3181
|
+
finally {
|
|
3182
|
+
options.signal?.removeEventListener('abort', abortRetry);
|
|
3183
|
+
}
|
|
3184
|
+
retryDelay = Math.min(retryDelay * 2, 5_000);
|
|
3185
|
+
};
|
|
3186
|
+
while (!options.signal?.aborted) {
|
|
3187
|
+
try {
|
|
3188
|
+
const token = await this.agentHttp.post(`${DEPLOYMENTS_API_PREFIX}/events/token`, undefined, { signal: options.signal });
|
|
3189
|
+
const WebSocketImpl = globalThis.WebSocket ?? NodeWebSocket;
|
|
3190
|
+
const ws = new WebSocketImpl(token.ws_url);
|
|
3191
|
+
await new Promise((resolve, reject) => {
|
|
3192
|
+
let opened = false;
|
|
3193
|
+
let ready = false;
|
|
3194
|
+
let processing = Promise.resolve();
|
|
3195
|
+
const readyTimer = setTimeout(() => {
|
|
3196
|
+
ws.close(4002, 'Deployment event ready timed out');
|
|
3197
|
+
reject(new Error('Deployment event ready timed out'));
|
|
3198
|
+
}, 10_000);
|
|
3199
|
+
const abort = () => ws.close(1000, 'Subscription cancelled');
|
|
3200
|
+
options.signal?.addEventListener('abort', abort, { once: true });
|
|
3201
|
+
ws.addEventListener('open', () => {
|
|
3202
|
+
opened = true;
|
|
3203
|
+
ws.send(JSON.stringify({ type: 'auth', token: token.token }));
|
|
3204
|
+
});
|
|
3205
|
+
ws.addEventListener('message', (message) => {
|
|
3206
|
+
processing = processing.then(async () => {
|
|
3207
|
+
const frame = JSON.parse(await websocketMessageText(message.data));
|
|
3208
|
+
if (!ready) {
|
|
3209
|
+
if (frame.type !== 'ready') {
|
|
3210
|
+
throw new Error('Deployment event socket did not send ready');
|
|
3211
|
+
}
|
|
3212
|
+
ready = true;
|
|
3213
|
+
clearTimeout(readyTimer);
|
|
3214
|
+
await options.onReady?.();
|
|
3215
|
+
retryDelay = 250;
|
|
3216
|
+
return;
|
|
3217
|
+
}
|
|
3218
|
+
if (frame.type === 'deployment.transition'
|
|
3219
|
+
&& typeof frame.agent_id === 'string'
|
|
3220
|
+
&& frame.agent_id.length > 0) {
|
|
3221
|
+
await handler(frame);
|
|
3222
|
+
}
|
|
3223
|
+
}).catch((error) => {
|
|
3224
|
+
ws.close(4002, 'Invalid deployment event');
|
|
3225
|
+
reject(error);
|
|
3226
|
+
});
|
|
3227
|
+
});
|
|
3228
|
+
ws.addEventListener('error', () => reject(new Error('Deployment event WebSocket failed')));
|
|
3229
|
+
ws.addEventListener('close', () => {
|
|
3230
|
+
clearTimeout(readyTimer);
|
|
3231
|
+
options.signal?.removeEventListener('abort', abort);
|
|
3232
|
+
processing.then(resolve, reject);
|
|
3233
|
+
});
|
|
3234
|
+
if (options.signal?.aborted)
|
|
3235
|
+
abort();
|
|
3236
|
+
void opened;
|
|
3237
|
+
});
|
|
3238
|
+
if (!options.signal?.aborted)
|
|
3239
|
+
await waitBeforeReconnect();
|
|
3240
|
+
}
|
|
3241
|
+
catch (error) {
|
|
3242
|
+
if (options.signal?.aborted)
|
|
3243
|
+
break;
|
|
3244
|
+
if (error instanceof APIError && [401, 403].includes(error.statusCode))
|
|
3245
|
+
throw error;
|
|
3246
|
+
await waitBeforeReconnect();
|
|
3247
|
+
void error;
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
async waitForState(agentIdOrName, states, timeoutMs = 300_000, failureStates = [], minimumLaunchEpoch, pollIntervalMs = 5_000) {
|
|
3252
|
+
if (!states.length)
|
|
3253
|
+
throw new Error('states must not be empty');
|
|
3254
|
+
if (minimumLaunchEpoch !== undefined && minimumLaunchEpoch < 0) {
|
|
3255
|
+
throw new Error('minimumLaunchEpoch must be non-negative');
|
|
3256
|
+
}
|
|
2771
3257
|
const agentId = await this.resolveAgentId(agentIdOrName);
|
|
2772
3258
|
const deadline = Date.now() + timeoutMs;
|
|
2773
3259
|
let lastState = '';
|
|
2774
|
-
let
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
details.push(`updatedAt=${agent.updatedAt.toISOString()}`);
|
|
2783
|
-
}
|
|
2784
|
-
return details.length ? `, ${details.join(', ')}` : '';
|
|
2785
|
-
};
|
|
2786
|
-
while (Date.now() < deadline) {
|
|
3260
|
+
let wakePending = true;
|
|
3261
|
+
let wake = null;
|
|
3262
|
+
const controller = new AbortController();
|
|
3263
|
+
const desired = new Set(states.map((state) => state.toLowerCase()));
|
|
3264
|
+
const failures = new Set(failureStates.map((state) => state.toLowerCase()));
|
|
3265
|
+
const effectivePollIntervalMs = Math.max(1, pollIntervalMs);
|
|
3266
|
+
const stateLabel = states.join(', ');
|
|
3267
|
+
const refresh = async () => {
|
|
2787
3268
|
const agent = await this.get(agentId);
|
|
2788
|
-
lastAgent = agent;
|
|
2789
3269
|
lastState = String(agent.state || '');
|
|
2790
|
-
if (
|
|
3270
|
+
if (minimumLaunchEpoch !== undefined
|
|
3271
|
+
&& agent.launchEpoch < minimumLaunchEpoch)
|
|
3272
|
+
return null;
|
|
3273
|
+
if (desired.has(lastState.toLowerCase()))
|
|
2791
3274
|
return agent;
|
|
3275
|
+
if (failures.has(lastState.toLowerCase())) {
|
|
3276
|
+
throw new Error(`Agent entered ${lastState} while waiting for ${stateLabel}`);
|
|
3277
|
+
}
|
|
3278
|
+
return null;
|
|
3279
|
+
};
|
|
3280
|
+
const subscription = this.subscribe((event) => {
|
|
3281
|
+
if (event.type === 'deployment.transition' && event.agent_id === agentId) {
|
|
3282
|
+
wakePending = true;
|
|
3283
|
+
wake?.();
|
|
2792
3284
|
}
|
|
2793
|
-
|
|
2794
|
-
|
|
3285
|
+
}, {
|
|
3286
|
+
signal: controller.signal,
|
|
3287
|
+
onReady: () => {
|
|
3288
|
+
wakePending = true;
|
|
3289
|
+
wake?.();
|
|
3290
|
+
},
|
|
3291
|
+
}).catch(() => {
|
|
3292
|
+
// Lifecycle events reduce latency, but REST polling remains authoritative.
|
|
3293
|
+
wakePending = true;
|
|
3294
|
+
wake?.();
|
|
3295
|
+
});
|
|
3296
|
+
try {
|
|
3297
|
+
while (Date.now() < deadline) {
|
|
3298
|
+
if (wakePending) {
|
|
3299
|
+
wakePending = false;
|
|
3300
|
+
const agent = await refresh();
|
|
3301
|
+
if (agent)
|
|
3302
|
+
return agent;
|
|
3303
|
+
continue;
|
|
3304
|
+
}
|
|
3305
|
+
const remaining = deadline - Date.now();
|
|
3306
|
+
if (remaining <= 0)
|
|
3307
|
+
break;
|
|
3308
|
+
await Promise.race([
|
|
3309
|
+
new Promise((resolve) => { wake = resolve; }),
|
|
3310
|
+
sleep(Math.min(remaining, effectivePollIntervalMs)).then(() => {
|
|
3311
|
+
wakePending = true;
|
|
3312
|
+
}),
|
|
3313
|
+
]);
|
|
3314
|
+
wake = null;
|
|
2795
3315
|
}
|
|
2796
|
-
await
|
|
3316
|
+
const finalAgent = await refresh();
|
|
3317
|
+
if (finalAgent)
|
|
3318
|
+
return finalAgent;
|
|
2797
3319
|
}
|
|
2798
|
-
|
|
3320
|
+
finally {
|
|
3321
|
+
controller.abort();
|
|
3322
|
+
await subscription;
|
|
3323
|
+
}
|
|
3324
|
+
throw new Error(`Timed out waiting for agent ${agentId} to reach ${stateLabel} (last=${lastState || 'unknown'})`);
|
|
3325
|
+
}
|
|
3326
|
+
async waitRunning(agentIdOrName, timeoutMs = 300_000, pollIntervalMs = 5_000, minimumLaunchEpoch) {
|
|
3327
|
+
return this.waitForState(agentIdOrName, ['RUNNING'], timeoutMs, ['STOPPED', 'ARCHIVED', 'DELETED', 'FAILED'], minimumLaunchEpoch, pollIntervalMs);
|
|
2799
3328
|
}
|
|
2800
3329
|
async start(agentIdOrName, options = {}) {
|
|
2801
|
-
|
|
3330
|
+
if (isSelfAgentRef(agentIdOrName)) {
|
|
3331
|
+
const provided = Object.keys(options).filter((key) => options[key] !== undefined);
|
|
3332
|
+
if (provided.length > 0) {
|
|
3333
|
+
throw new Error(`start self uses the backend-stored launch configuration and does not accept launch overrides: ${provided.join(', ')}`);
|
|
3334
|
+
}
|
|
3335
|
+
const data = await this.agentHttp.post(`${DEPLOYMENTS_API_PREFIX}/self/start`, {}, { retries: 1 });
|
|
3336
|
+
return this.hydrateAgent(data);
|
|
3337
|
+
}
|
|
3338
|
+
const provided = Object.keys(options).filter((key) => options[key] !== undefined);
|
|
3339
|
+
const agentId = await this.resolveAgentId(agentIdOrName, {}, true);
|
|
3340
|
+
if (provided.length === 0) {
|
|
3341
|
+
const data = await this.agentHttp.post(`${DEPLOYMENTS_API_PREFIX}/${agentId}/start`, {}, { retries: 1 });
|
|
3342
|
+
return this.hydrateAgent(data);
|
|
3343
|
+
}
|
|
3344
|
+
const { config } = buildAgentConfig(options.config ?? {}, options);
|
|
2802
3345
|
const body = { ...config };
|
|
2803
3346
|
if (options.dryRun)
|
|
2804
3347
|
body.dry_run = true;
|
|
2805
|
-
const
|
|
2806
|
-
|
|
2807
|
-
const agent = this.hydrateAgent(data);
|
|
2808
|
-
if (agent instanceof OpenClawAgent) {
|
|
2809
|
-
agent.gatewayToken = gatewayToken;
|
|
2810
|
-
}
|
|
2811
|
-
agent.launchConfig = config;
|
|
2812
|
-
agent.command = [...(config.command ?? [])];
|
|
2813
|
-
agent.entrypoint = [...(config.entrypoint ?? [])];
|
|
2814
|
-
return agent;
|
|
3348
|
+
const data = await this.agentHttp.post(`${DEPLOYMENTS_API_PREFIX}/${agentId}/start`, body, { retries: 1 });
|
|
3349
|
+
return this.hydrateAgent(data);
|
|
2815
3350
|
}
|
|
2816
3351
|
async startOpenClaw(agentIdOrName, options = {}) {
|
|
2817
3352
|
const effectiveOptions = { ...options };
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
3353
|
+
if (options.workspacesSync !== undefined || options.memoryIndex !== undefined) {
|
|
3354
|
+
effectiveOptions.env = {
|
|
3355
|
+
...(options.workspacesSync !== undefined
|
|
3356
|
+
? buildOpenClawWorkspacesSyncEnv(options.workspacesSync ?? null)
|
|
3357
|
+
: {}),
|
|
3358
|
+
...(options.memoryIndex !== undefined
|
|
3359
|
+
? buildOpenClawMemoryIndexEnv(options.memoryIndex)
|
|
3360
|
+
: {}),
|
|
3361
|
+
...(options.env ?? {}),
|
|
3362
|
+
};
|
|
3363
|
+
}
|
|
3364
|
+
if (options.routes === undefined && options.openClawRoutes !== undefined) {
|
|
3365
|
+
effectiveOptions.routes = buildOpenClawRoutes(options.openClawRoutes ?? undefined);
|
|
3366
|
+
}
|
|
3367
|
+
return this.start(agentIdOrName, effectiveOptions);
|
|
3368
|
+
}
|
|
3369
|
+
async startHermesAgent(agentIdOrName, options = {}) {
|
|
3370
|
+
const apiServerKey = resolveHermesApiServerKey(options.apiServerKey, options.env);
|
|
3371
|
+
const env = {
|
|
2822
3372
|
...(options.env ?? {}),
|
|
3373
|
+
API_SERVER_ENABLED: 'true',
|
|
3374
|
+
API_SERVER_HOST: '0.0.0.0',
|
|
3375
|
+
API_SERVER_KEY: apiServerKey,
|
|
2823
3376
|
};
|
|
2824
|
-
|
|
2825
|
-
|
|
3377
|
+
delete env.OPENCLAW_GATEWAY_TOKEN;
|
|
3378
|
+
const effectiveOptions = {
|
|
3379
|
+
...options,
|
|
3380
|
+
env,
|
|
3381
|
+
image: defaultHermesAgentImage(options.image),
|
|
3382
|
+
runtimeScopes: options.runtimeScopes ?? DEFAULT_AGENT_RUNTIME_SCOPES,
|
|
3383
|
+
injectGatewayToken: false,
|
|
3384
|
+
syncRoot: options.syncRoot ?? DEFAULT_HERMES_AGENT_SYNC_ROOT,
|
|
3385
|
+
syncUid: options.syncUid ?? DEFAULT_HERMES_AGENT_SYNC_UID,
|
|
3386
|
+
syncGid: options.syncGid ?? DEFAULT_HERMES_AGENT_SYNC_GID,
|
|
3387
|
+
routes: options.routes === undefined
|
|
3388
|
+
? buildHermesAgentRoutes(options.hermesRoute ?? {})
|
|
3389
|
+
: options.routes,
|
|
3390
|
+
};
|
|
3391
|
+
const agent = await this.start(agentIdOrName, effectiveOptions);
|
|
3392
|
+
if (!(agent instanceof HermesAgent)) {
|
|
3393
|
+
throw new Error("Hermes deployment response did not identify runtime 'hermes-agent'");
|
|
2826
3394
|
}
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
effectiveOptions.syncRoot = DEFAULT_OPENCLAW_SYNC_ROOT;
|
|
2830
|
-
if (effectiveOptions.syncEnabled === undefined)
|
|
2831
|
-
effectiveOptions.syncEnabled = true;
|
|
2832
|
-
return this.start(agentIdOrName, effectiveOptions);
|
|
3395
|
+
agent.apiServerKey = apiServerKey;
|
|
3396
|
+
return agent;
|
|
2833
3397
|
}
|
|
2834
3398
|
async startOpenClawPro(agentIdOrName, options = {}) {
|
|
2835
|
-
return this.startOpenClaw(agentIdOrName,
|
|
2836
|
-
...options,
|
|
2837
|
-
env: { OPENCLAW_DESKTOP_ENABLED: '1', ...(options.env ?? {}) },
|
|
2838
|
-
image: defaultOpenClawProImage(options.image),
|
|
2839
|
-
openClawRoutes: { includeDesktop: true, ...(options.openClawRoutes ?? {}) },
|
|
2840
|
-
});
|
|
3399
|
+
return this.startOpenClaw(agentIdOrName, options);
|
|
2841
3400
|
}
|
|
2842
3401
|
async update(agentIdOrName, options = {}) {
|
|
2843
3402
|
const body = {};
|
|
@@ -2851,8 +3410,8 @@ export class Deployments {
|
|
|
2851
3410
|
body.launch_config = options.launchConfig;
|
|
2852
3411
|
if (options.refreshFromLagoon !== undefined)
|
|
2853
3412
|
body.refresh_from_lagoon = options.refreshFromLagoon;
|
|
2854
|
-
if (options.
|
|
2855
|
-
body.
|
|
3413
|
+
if (options.error !== undefined)
|
|
3414
|
+
body.error = options.error;
|
|
2856
3415
|
const agentId = await this.resolveAgentId(agentIdOrName);
|
|
2857
3416
|
const data = await this.agentHttp.patch(`${DEPLOYMENTS_API_PREFIX}/${agentId}`, body);
|
|
2858
3417
|
return this.hydrateAgent(data);
|
|
@@ -2877,10 +3436,42 @@ export class Deployments {
|
|
|
2877
3436
|
* deployment slot as released.
|
|
2878
3437
|
*/
|
|
2879
3438
|
async stop(agentIdOrName) {
|
|
3439
|
+
const agentId = await this.resolveAgentId(agentIdOrName, {}, true);
|
|
3440
|
+
const data = await this.agentHttp.post(`${DEPLOYMENTS_API_PREFIX}/${agentId}/stop`, undefined, { retries: 1 });
|
|
3441
|
+
return this.hydrateAgent(data);
|
|
3442
|
+
}
|
|
3443
|
+
/** Restore durable storage. The accepted snapshot is `RESTORING`; completion is `STOPPED`. */
|
|
3444
|
+
async restore(agentIdOrName) {
|
|
2880
3445
|
const agentId = await this.resolveAgentId(agentIdOrName);
|
|
2881
|
-
const data = await this.agentHttp.post(`${DEPLOYMENTS_API_PREFIX}/${agentId}/
|
|
3446
|
+
const data = await this.agentHttp.post(`${DEPLOYMENTS_API_PREFIX}/${agentId}/restore`, undefined, { retries: 1 });
|
|
2882
3447
|
return this.hydrateAgent(data);
|
|
2883
3448
|
}
|
|
3449
|
+
async getRoutes(agentIdOrName) {
|
|
3450
|
+
const agentId = await this.resolveAgentId(agentIdOrName, {}, true);
|
|
3451
|
+
const data = await this.agentHttp.get(`${DEPLOYMENTS_API_PREFIX}/${agentId}/routes`);
|
|
3452
|
+
return agentRoutesStateFromData(data);
|
|
3453
|
+
}
|
|
3454
|
+
async setRoutes(agentIdOrName, routes) {
|
|
3455
|
+
const agentId = await this.resolveAgentId(agentIdOrName, {}, true);
|
|
3456
|
+
const body = { routes: structuredClone(routes) };
|
|
3457
|
+
const data = await this.agentHttp.put(`${DEPLOYMENTS_API_PREFIX}/${agentId}/routes`, body);
|
|
3458
|
+
return agentRoutesStateFromData(data);
|
|
3459
|
+
}
|
|
3460
|
+
async setRoute(agentIdOrName, name, route) {
|
|
3461
|
+
const agentId = await this.resolveAgentId(agentIdOrName, {}, true);
|
|
3462
|
+
const body = { port: route.port };
|
|
3463
|
+
if (route.auth !== undefined)
|
|
3464
|
+
body.auth = route.auth;
|
|
3465
|
+
if (route.prefix !== undefined)
|
|
3466
|
+
body.prefix = route.prefix;
|
|
3467
|
+
const data = await this.agentHttp.put(`${DEPLOYMENTS_API_PREFIX}/${agentId}/routes/${encodeURIComponent(name)}`, body);
|
|
3468
|
+
return agentRoutesStateFromData(data);
|
|
3469
|
+
}
|
|
3470
|
+
async removeRoute(agentIdOrName, name) {
|
|
3471
|
+
const agentId = await this.resolveAgentId(agentIdOrName, {}, true);
|
|
3472
|
+
const data = await this.agentHttp.delete(`${DEPLOYMENTS_API_PREFIX}/${agentId}/routes/${encodeURIComponent(name)}`);
|
|
3473
|
+
return agentRoutesStateFromData(data);
|
|
3474
|
+
}
|
|
2884
3475
|
async delete(agentIdOrName) {
|
|
2885
3476
|
const agentId = await this.resolveAgentId(agentIdOrName);
|
|
2886
3477
|
return this.agentHttp.delete(`${DEPLOYMENTS_API_PREFIX}/${agentId}`);
|
|
@@ -2927,6 +3518,20 @@ export class Deployments {
|
|
|
2927
3518
|
? this.agentHttp.get(path)
|
|
2928
3519
|
: this.agentHttp.get(path, undefined, requestOptions);
|
|
2929
3520
|
}
|
|
3521
|
+
async secretNames(agentIdOrName, requestOptions = {}) {
|
|
3522
|
+
const agentId = await this.resolveAgentId(agentIdOrName, requestOptions);
|
|
3523
|
+
const path = `${DEPLOYMENTS_API_PREFIX}/${agentId}/secrets`;
|
|
3524
|
+
return Object.keys(requestOptions).length === 0
|
|
3525
|
+
? this.agentHttp.get(path)
|
|
3526
|
+
: this.agentHttp.get(path, undefined, requestOptions);
|
|
3527
|
+
}
|
|
3528
|
+
async secret(agentIdOrName, key, requestOptions = {}) {
|
|
3529
|
+
const agentId = await this.resolveAgentId(agentIdOrName, requestOptions);
|
|
3530
|
+
const path = `${DEPLOYMENTS_API_PREFIX}/${agentId}/secrets/${encodeURIComponent(key)}`;
|
|
3531
|
+
return Object.keys(requestOptions).length === 0
|
|
3532
|
+
? this.agentHttp.get(path)
|
|
3533
|
+
: this.agentHttp.get(path, undefined, requestOptions);
|
|
3534
|
+
}
|
|
2930
3535
|
async exec(target, command, options = {}) {
|
|
2931
3536
|
const agentId = await this.agentIdFor(target);
|
|
2932
3537
|
const payload = {
|
|
@@ -2938,21 +3543,6 @@ export class Deployments {
|
|
|
2938
3543
|
const data = await this.agentHttp.post(`${DEPLOYMENTS_API_PREFIX}/${agentId}/exec`, payload);
|
|
2939
3544
|
return execResultFromDict(data);
|
|
2940
3545
|
}
|
|
2941
|
-
async health(target) {
|
|
2942
|
-
if (!target.executorUrl) {
|
|
2943
|
-
throw new Error('Agent has no executor URL');
|
|
2944
|
-
}
|
|
2945
|
-
const headers = {};
|
|
2946
|
-
if (target.jwtToken) {
|
|
2947
|
-
headers.Authorization = `Bearer ${target.jwtToken}`;
|
|
2948
|
-
headers.Cookie = `${target.podName}-token=${target.jwtToken}`;
|
|
2949
|
-
}
|
|
2950
|
-
const response = await fetch(`${target.executorUrl}/health`, { headers });
|
|
2951
|
-
if (!response.ok) {
|
|
2952
|
-
throw new Error(`Agent health failed: ${response.status} ${response.statusText}`);
|
|
2953
|
-
}
|
|
2954
|
-
return (await response.json());
|
|
2955
|
-
}
|
|
2956
3546
|
async filesList(target, path = '', source = 'auto') {
|
|
2957
3547
|
const { suffix, params } = deploymentFileReadRequest(path, source);
|
|
2958
3548
|
const agentId = await this.agentIdFor(target);
|