@openclaw-cloud/agent-controller 3.0.0-beta.2 → 3.0.0-beta.21
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/bin/agent-controller.js +5 -9
- package/dist/api.d.ts +16 -0
- package/dist/api.js.map +1 -1
- package/dist/commands/agent-update-cli.js +23 -38
- package/dist/commands/agent-update-cli.js.map +1 -1
- package/dist/commands/backup-cli.js +6 -4
- package/dist/commands/backup-cli.js.map +1 -1
- package/dist/commands/bootstrap.d.ts +10 -1
- package/dist/commands/bootstrap.js +33 -73
- package/dist/commands/bootstrap.js.map +1 -1
- package/dist/commands/channel-server.js +4 -3
- package/dist/commands/channel-server.js.map +1 -1
- package/dist/commands/heartbeat-cli.js +2 -6
- package/dist/commands/heartbeat-cli.js.map +1 -1
- package/dist/commands/install-deps.d.ts +10 -0
- package/dist/commands/install-deps.js +33 -43
- package/dist/commands/install-deps.js.map +1 -1
- package/dist/commands/knowledge-sync-cli.js +6 -4
- package/dist/commands/knowledge-sync-cli.js.map +1 -1
- package/dist/commands/plugin-update-cli.js +4 -8
- package/dist/commands/plugin-update-cli.js.map +1 -1
- package/dist/commands/self-update.d.ts +1 -0
- package/dist/commands/self-update.js +76 -37
- package/dist/commands/self-update.js.map +1 -1
- package/dist/commands/update-skills.js +7 -6
- package/dist/commands/update-skills.js.map +1 -1
- package/dist/config-file.d.ts +24 -0
- package/dist/config-file.js +59 -20
- package/dist/config-file.js.map +1 -1
- package/dist/config.d.ts +10 -0
- package/dist/config.js +28 -0
- package/dist/config.js.map +1 -1
- package/dist/connection.d.ts +4 -0
- package/dist/connection.js +50 -5
- package/dist/connection.js.map +1 -1
- package/dist/handlers/agent-update.d.ts +4 -0
- package/dist/handlers/agent-update.js +16 -44
- package/dist/handlers/agent-update.js.map +1 -1
- package/dist/handlers/deploy.d.ts +1 -2
- package/dist/handlers/deploy.js +4 -20
- package/dist/handlers/deploy.js.map +1 -1
- package/dist/handlers/diagnostics.js +0 -12
- package/dist/handlers/diagnostics.js.map +1 -1
- package/dist/handlers/file-delete.js +4 -5
- package/dist/handlers/file-delete.js.map +1 -1
- package/dist/handlers/file-write.js +4 -4
- package/dist/handlers/file-write.js.map +1 -1
- package/dist/handlers/knowledge-sync.js +2 -14
- package/dist/handlers/knowledge-sync.js.map +1 -1
- package/dist/handlers/memory.d.ts +11 -1
- package/dist/handlers/memory.js.map +1 -1
- package/dist/handlers/onboarding.d.ts +1 -2
- package/dist/handlers/onboarding.js +3 -20
- package/dist/handlers/onboarding.js.map +1 -1
- package/dist/handlers/package-install.js +1 -1
- package/dist/handlers/package-install.js.map +1 -1
- package/dist/handlers/pair.js +0 -12
- package/dist/handlers/pair.js.map +1 -1
- package/dist/handlers/restart.d.ts +1 -2
- package/dist/handlers/restart.js +3 -8
- package/dist/handlers/restart.js.map +1 -1
- package/dist/handlers/slack-event.d.ts +13 -0
- package/dist/handlers/slack-event.js +40 -0
- package/dist/handlers/slack-event.js.map +1 -0
- package/dist/handlers/stop.js +0 -7
- package/dist/handlers/stop.js.map +1 -1
- package/dist/handlers/telegram-webhook.js +3 -22
- package/dist/handlers/telegram-webhook.js.map +1 -1
- package/dist/handlers/update-config.js +4 -13
- package/dist/handlers/update-config.js.map +1 -1
- package/dist/heartbeat.d.ts +7 -1
- package/dist/heartbeat.js +35 -6
- package/dist/heartbeat.js.map +1 -1
- package/dist/index.d.ts +0 -8
- package/dist/index.js +96 -119
- package/dist/index.js.map +1 -1
- package/dist/lockfile.js +2 -1
- package/dist/lockfile.js.map +1 -1
- package/dist/mock-board-worker.d.ts +31 -0
- package/dist/mock-board-worker.js +125 -0
- package/dist/mock-board-worker.js.map +1 -0
- package/dist/platform/linux.js +6 -5
- package/dist/platform/linux.js.map +1 -1
- package/dist/platform/macos.js +3 -2
- package/dist/platform/macos.js.map +1 -1
- package/dist/platform/windows.js +3 -2
- package/dist/platform/windows.js.map +1 -1
- package/dist/providers/claude-code/channel-server.js +2 -1
- package/dist/providers/claude-code/channel-server.js.map +1 -1
- package/dist/providers/claude-code/settings-writer.d.ts +3 -2
- package/dist/providers/claude-code/settings-writer.js +6 -15
- package/dist/providers/claude-code/settings-writer.js.map +1 -1
- package/dist/providers/index.d.ts +13 -0
- package/dist/providers/index.js +25 -0
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/openclaw/gateway-adapter.js +2 -0
- package/dist/providers/openclaw/gateway-adapter.js.map +1 -1
- package/dist/providers/openclaw/gateway-client.js +6 -1
- package/dist/providers/openclaw/gateway-client.js.map +1 -1
- package/dist/providers/openclaw/index.js +34 -13
- package/dist/providers/openclaw/index.js.map +1 -1
- package/dist/slack-relay-server.d.ts +48 -0
- package/dist/slack-relay-server.js +197 -0
- package/dist/slack-relay-server.js.map +1 -0
- package/dist/types.d.ts +16 -1
- package/dist/utils/anthropic-auth.d.ts +14 -0
- package/dist/utils/anthropic-auth.js +24 -0
- package/dist/utils/anthropic-auth.js.map +1 -1
- package/dist/utils/apply-config.d.ts +65 -12
- package/dist/utils/apply-config.js +308 -100
- package/dist/utils/apply-config.js.map +1 -1
- package/dist/utils/claude-env.d.ts +6 -0
- package/dist/utils/claude-env.js +29 -28
- package/dist/utils/claude-env.js.map +1 -1
- package/dist/utils/config-merge-paths.d.ts +15 -1
- package/dist/utils/config-merge-paths.js +17 -3
- package/dist/utils/config-merge-paths.js.map +1 -1
- package/dist/utils/deploy-type.d.ts +16 -0
- package/dist/utils/deploy-type.js +34 -0
- package/dist/utils/deploy-type.js.map +1 -0
- package/dist/utils/env.d.ts +0 -1
- package/dist/utils/env.js +0 -8
- package/dist/utils/env.js.map +1 -1
- package/dist/utils/exec.d.ts +11 -0
- package/dist/utils/exec.js +47 -1
- package/dist/utils/exec.js.map +1 -1
- package/dist/utils/gateway-restart-handler.d.ts +6 -2
- package/dist/utils/gateway-restart-handler.js +11 -2
- package/dist/utils/gateway-restart-handler.js.map +1 -1
- package/dist/utils/gateway-restart.d.ts +7 -0
- package/dist/utils/gateway-restart.js +12 -4
- package/dist/utils/gateway-restart.js.map +1 -1
- package/dist/utils/knowledge-graph.d.ts +13 -1
- package/dist/utils/knowledge-graph.js +75 -29
- package/dist/utils/knowledge-graph.js.map +1 -1
- package/dist/utils/legacy-mcp-servers.d.ts +12 -0
- package/dist/utils/legacy-mcp-servers.js +37 -0
- package/dist/utils/legacy-mcp-servers.js.map +1 -0
- package/dist/utils/log-collector.js +2 -1
- package/dist/utils/log-collector.js.map +1 -1
- package/dist/utils/openclaw-ops.d.ts +45 -3
- package/dist/utils/openclaw-ops.js +94 -15
- package/dist/utils/openclaw-ops.js.map +1 -1
- package/dist/utils/openclaw-stack-update.d.ts +31 -0
- package/dist/utils/openclaw-stack-update.js +50 -0
- package/dist/utils/openclaw-stack-update.js.map +1 -0
- package/dist/utils/package-install.js +4 -12
- package/dist/utils/package-install.js.map +1 -1
- package/dist/utils/package-version.d.ts +10 -0
- package/dist/utils/package-version.js +18 -0
- package/dist/utils/package-version.js.map +1 -0
- package/dist/utils/parse-id-at-version.d.ts +15 -0
- package/dist/utils/parse-id-at-version.js +22 -0
- package/dist/utils/parse-id-at-version.js.map +1 -0
- package/dist/utils/plugin-reconcile.d.ts +46 -0
- package/dist/utils/plugin-reconcile.js +201 -0
- package/dist/utils/plugin-reconcile.js.map +1 -0
- package/dist/utils/response.js +1 -0
- package/dist/utils/response.js.map +1 -1
- package/dist/utils/write-home-files.d.ts +27 -0
- package/dist/utils/write-home-files.js +72 -0
- package/dist/utils/write-home-files.js.map +1 -0
- package/dist/utils/write-workspace-files.d.ts +1 -1
- package/dist/utils/write-workspace-files.js +19 -7
- package/dist/utils/write-workspace-files.js.map +1 -1
- package/dist/workspace.d.ts +11 -0
- package/dist/workspace.js +20 -0
- package/dist/workspace.js.map +1 -1
- package/package.json +9 -4
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Write ~/.claude/settings.json, ~/.claude/cc-notify.sh, and auth-profiles.json
|
|
3
3
|
* from a flat env vars dict (ANTHROPIC_API_KEY, ANTHROPIC_BASE_URL).
|
|
4
|
+
*
|
|
5
|
+
* This is a WHOLESALE rewrite of the user's Claude settings, so it is gated on
|
|
6
|
+
* the install being managed ('vm'/'k8s'). The guard lives here rather than in
|
|
7
|
+
* the callers deliberately: it is defense in depth that holds for every current
|
|
8
|
+
* and future caller. `agent-controller bootstrap` on a developer laptop once
|
|
9
|
+
* blew away ~/.claude/settings.json — that must not be re-reachable.
|
|
4
10
|
*/
|
|
5
11
|
export declare function writeClaudeEnv(envVars: Record<string, string>): Promise<void>;
|
package/dist/utils/claude-env.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import os from 'node:os';
|
|
4
|
-
import { classifyAnthropicAuth } from './anthropic-auth.js';
|
|
4
|
+
import { buildClaudeSettings, classifyAnthropicAuth, CLAUDE_PERMISSIONS, } from './anthropic-auth.js';
|
|
5
|
+
import { getResolvedDeployType, isManagedDeploy } from './deploy-type.js';
|
|
5
6
|
const CC_NOTIFY_SH = `#!/bin/bash
|
|
6
7
|
# Claude Code hook: notify OpenClaw on Stop/PermissionRequest
|
|
7
8
|
EVENT_TYPE="\${CC_HOOK_EVENT:-unknown}"
|
|
@@ -41,8 +42,18 @@ exit 0
|
|
|
41
42
|
/**
|
|
42
43
|
* Write ~/.claude/settings.json, ~/.claude/cc-notify.sh, and auth-profiles.json
|
|
43
44
|
* from a flat env vars dict (ANTHROPIC_API_KEY, ANTHROPIC_BASE_URL).
|
|
45
|
+
*
|
|
46
|
+
* This is a WHOLESALE rewrite of the user's Claude settings, so it is gated on
|
|
47
|
+
* the install being managed ('vm'/'k8s'). The guard lives here rather than in
|
|
48
|
+
* the callers deliberately: it is defense in depth that holds for every current
|
|
49
|
+
* and future caller. `agent-controller bootstrap` on a developer laptop once
|
|
50
|
+
* blew away ~/.claude/settings.json — that must not be re-reachable.
|
|
44
51
|
*/
|
|
45
52
|
export async function writeClaudeEnv(envVars) {
|
|
53
|
+
if (!isManagedDeploy()) {
|
|
54
|
+
console.log(`[claude-env] skipped: not a managed install (deployType=${getResolvedDeployType() ?? 'unknown'})`);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
46
57
|
const claudeDir = path.join(os.homedir(), '.claude');
|
|
47
58
|
await fs.mkdir(claudeDir, { recursive: true });
|
|
48
59
|
// Write cc-notify.sh
|
|
@@ -50,10 +61,6 @@ export async function writeClaudeEnv(envVars) {
|
|
|
50
61
|
await fs.writeFile(notifyPath, CC_NOTIFY_SH);
|
|
51
62
|
await fs.chmod(notifyPath, 0o755);
|
|
52
63
|
const auth = classifyAnthropicAuth({ anthropicApiKey: envVars.ANTHROPIC_API_KEY });
|
|
53
|
-
// This flow only handles the OAuth-vs-API-key split (no explicit
|
|
54
|
-
// anthropicAuthToken input); an `auth_token` classification here always
|
|
55
|
-
// means "the API key was in fact an OAuth token".
|
|
56
|
-
const isOAuthToken = auth.kind === 'auth_token';
|
|
57
64
|
const hooks = {
|
|
58
65
|
PreToolUse: [{ matcher: 'Bash', hooks: [] }],
|
|
59
66
|
PostToolUse: [{ matcher: 'Write|Edit', hooks: [] }],
|
|
@@ -69,29 +76,23 @@ export async function writeClaudeEnv(envVars) {
|
|
|
69
76
|
},
|
|
70
77
|
],
|
|
71
78
|
};
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
env
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
...(envVars.ANTHROPIC_BASE_URL
|
|
90
|
-
? { env: { ANTHROPIC_BASE_URL: envVars.ANTHROPIC_BASE_URL } }
|
|
91
|
-
: {}),
|
|
92
|
-
hooks,
|
|
93
|
-
};
|
|
94
|
-
}
|
|
79
|
+
// This flow only handles the OAuth-vs-API-key split (no explicit
|
|
80
|
+
// anthropicAuthToken input); an `auth_token` classification here always
|
|
81
|
+
// means "the API key was in fact an OAuth token".
|
|
82
|
+
const settings = auth.kind === 'auth_token'
|
|
83
|
+
? // OAuth token flow: ANTHROPIC_AUTH_TOKEN, no apiKeyHelper, no ANTHROPIC_BASE_URL
|
|
84
|
+
{ ...buildClaudeSettings(auth), hooks }
|
|
85
|
+
: // API key flow (default): the key is NOT embedded — apiKeyHelper reads it
|
|
86
|
+
// out of the VM .env at runtime — so this branch cannot use the shared
|
|
87
|
+
// `env` variant. Optional ANTHROPIC_BASE_URL rides along.
|
|
88
|
+
{
|
|
89
|
+
permissions: CLAUDE_PERMISSIONS,
|
|
90
|
+
apiKeyHelper: "bash -c 'source /opt/openclaw-cloud/agent-controller/.env && echo $ANTHROPIC_API_KEY'",
|
|
91
|
+
...(envVars.ANTHROPIC_BASE_URL
|
|
92
|
+
? { env: { ANTHROPIC_BASE_URL: envVars.ANTHROPIC_BASE_URL } }
|
|
93
|
+
: {}),
|
|
94
|
+
hooks,
|
|
95
|
+
};
|
|
95
96
|
// Contains ANTHROPIC_AUTH_TOKEN on OAuth flow — chmod 0o600.
|
|
96
97
|
await fs.writeFile(path.join(claudeDir, 'settings.json'), JSON.stringify(settings, null, 2) + '\n', { mode: 0o600 });
|
|
97
98
|
// auth-profiles.json is now written by applyBootstrapConfig from openclaw.json
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-env.js","sourceRoot":"","sources":["../../src/utils/claude-env.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,qBAAqB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"claude-env.js","sourceRoot":"","sources":["../../src/utils/claude-env.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAE1E,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmCpB,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAA+B;IAClE,IAAI,CAAC,eAAe,EAAE,EAAE,CAAC;QACvB,OAAO,CAAC,GAAG,CACT,2DAA2D,qBAAqB,EAAE,IAAI,SAAS,GAAG,CACnG,CAAC;QACF,OAAO;IACT,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;IACrD,MAAM,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE/C,qBAAqB;IACrB,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;IACxD,MAAM,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;IAC7C,MAAM,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAElC,MAAM,IAAI,GAAG,qBAAqB,CAAC,EAAE,eAAe,EAAE,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAC;IAEnF,MAAM,KAAK,GAAG;QACZ,UAAU,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;QAC5C,WAAW,EAAE,CAAC,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;QACnD,IAAI,EAAE;YACJ;gBACE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,2CAA2C,EAAE,CAAC;aACnF;SACF;QACD,YAAY,EAAE;YACZ;gBACE,OAAO,EAAE,mBAAmB;gBAC5B,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,mDAAmD,EAAE,CAAC;aAC3F;SACF;KACF,CAAC;IAEF,iEAAiE;IACjE,wEAAwE;IACxE,kDAAkD;IAClD,MAAM,QAAQ,GACZ,IAAI,CAAC,IAAI,KAAK,YAAY;QACxB,CAAC,CAAC,iFAAiF;YACjF,EAAE,GAAG,mBAAmB,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE;QACzC,CAAC,CAAC,0EAA0E;YAC1E,uEAAuE;YACvE,0DAA0D;YAC1D;gBACE,WAAW,EAAE,kBAAkB;gBAC/B,YAAY,EACV,uFAAuF;gBACzF,GAAG,CAAC,OAAO,CAAC,kBAAkB;oBAC5B,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,kBAAkB,EAAE,OAAO,CAAC,kBAAkB,EAAE,EAAE;oBAC7D,CAAC,CAAC,EAAE,CAAC;gBACP,KAAK;aACN,CAAC;IAER,6DAA6D;IAC7D,MAAM,EAAE,CAAC,SAAS,CAChB,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,eAAe,CAAC,EACrC,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EACxC,EAAE,IAAI,EAAE,KAAK,EAAE,CAChB,CAAC;IAEF,+EAA+E;AACjF,CAAC"}
|
|
@@ -19,12 +19,26 @@
|
|
|
19
19
|
* To extend: add the path here. Keep it tight — every entry is an implicit
|
|
20
20
|
* promise that merging it won't clobber user work.
|
|
21
21
|
*/
|
|
22
|
+
/**
|
|
23
|
+
* The one backend-managed section of openclaw.json. Merged REMOTE-wins per
|
|
24
|
+
* server name (see `applyMergeableConfig`) and the only section whose change
|
|
25
|
+
* forces a gateway restart on standalone — openclaw does not hot-reload it.
|
|
26
|
+
*/
|
|
27
|
+
export declare const MCP_SERVERS_PATH = "mcp.servers";
|
|
22
28
|
export declare const MERGEABLE_CONFIG_PATHS: readonly string[];
|
|
23
29
|
/**
|
|
24
30
|
* Paths within ~/.claude/settings.json that are safe to merge from a backend
|
|
25
|
-
*
|
|
31
|
+
* bootstrap payload. Everything else (apiKeyHelper, permissions, hooks, `env`,
|
|
26
32
|
* user-owned keys) is left untouched.
|
|
27
33
|
*
|
|
34
|
+
* `env` is DELIBERATELY EXCLUDED and must never be added back. The backend's
|
|
35
|
+
* `envVars` carry `ANTHROPIC_API_KEY` / `ANTHROPIC_BASE_URL`; merging them into
|
|
36
|
+
* a standalone user's ~/.claude/settings.json points ALL of that user's own
|
|
37
|
+
* Claude Code sessions at the platform proxy — which, when it is not running,
|
|
38
|
+
* breaks Claude Code on the machine outright. That incident happened twice
|
|
39
|
+
* (latest 2026-08-02) before the path was removed. Managed installs ('vm'/'k8s')
|
|
40
|
+
* still get their credentials, via `writeClaudeEnv()` — not via this merge.
|
|
41
|
+
*
|
|
28
42
|
* NOTE: `mcp.servers` is a DOTTED path — Claude Code's modern settings.json
|
|
29
43
|
* schema nests MCP servers under `mcp.servers`, not the legacy flat
|
|
30
44
|
* `mcpServers` key. Agent-controller migrates any pre-existing flat
|
|
@@ -19,22 +19,36 @@
|
|
|
19
19
|
* To extend: add the path here. Keep it tight — every entry is an implicit
|
|
20
20
|
* promise that merging it won't clobber user work.
|
|
21
21
|
*/
|
|
22
|
+
/**
|
|
23
|
+
* The one backend-managed section of openclaw.json. Merged REMOTE-wins per
|
|
24
|
+
* server name (see `applyMergeableConfig`) and the only section whose change
|
|
25
|
+
* forces a gateway restart on standalone — openclaw does not hot-reload it.
|
|
26
|
+
*/
|
|
27
|
+
export const MCP_SERVERS_PATH = 'mcp.servers';
|
|
22
28
|
export const MERGEABLE_CONFIG_PATHS = [
|
|
23
|
-
|
|
29
|
+
MCP_SERVERS_PATH,
|
|
24
30
|
'tools.media.audio',
|
|
25
31
|
'tools.media.voice',
|
|
26
32
|
'tools.transcription',
|
|
27
33
|
];
|
|
28
34
|
/**
|
|
29
35
|
* Paths within ~/.claude/settings.json that are safe to merge from a backend
|
|
30
|
-
*
|
|
36
|
+
* bootstrap payload. Everything else (apiKeyHelper, permissions, hooks, `env`,
|
|
31
37
|
* user-owned keys) is left untouched.
|
|
32
38
|
*
|
|
39
|
+
* `env` is DELIBERATELY EXCLUDED and must never be added back. The backend's
|
|
40
|
+
* `envVars` carry `ANTHROPIC_API_KEY` / `ANTHROPIC_BASE_URL`; merging them into
|
|
41
|
+
* a standalone user's ~/.claude/settings.json points ALL of that user's own
|
|
42
|
+
* Claude Code sessions at the platform proxy — which, when it is not running,
|
|
43
|
+
* breaks Claude Code on the machine outright. That incident happened twice
|
|
44
|
+
* (latest 2026-08-02) before the path was removed. Managed installs ('vm'/'k8s')
|
|
45
|
+
* still get their credentials, via `writeClaudeEnv()` — not via this merge.
|
|
46
|
+
*
|
|
33
47
|
* NOTE: `mcp.servers` is a DOTTED path — Claude Code's modern settings.json
|
|
34
48
|
* schema nests MCP servers under `mcp.servers`, not the legacy flat
|
|
35
49
|
* `mcpServers` key. Agent-controller migrates any pre-existing flat
|
|
36
50
|
* `mcpServers` into `mcp.servers` on first merge and deletes the flat key
|
|
37
51
|
* (see `applyMergeableConfig` in apply-config.ts).
|
|
38
52
|
*/
|
|
39
|
-
export const MERGEABLE_SETTINGS_PATHS = ['mcp.servers'
|
|
53
|
+
export const MERGEABLE_SETTINGS_PATHS = ['mcp.servers'];
|
|
40
54
|
//# sourceMappingURL=config-merge-paths.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-merge-paths.js","sourceRoot":"","sources":["../../src/utils/config-merge-paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAsB;IACvD,
|
|
1
|
+
{"version":3,"file":"config-merge-paths.js","sourceRoot":"","sources":["../../src/utils/config-merge-paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,aAAa,CAAC;AAE9C,MAAM,CAAC,MAAM,sBAAsB,GAAsB;IACvD,gBAAgB;IAChB,mBAAmB;IACnB,mBAAmB;IACnB,qBAAqB;CACb,CAAC;AAEX;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAsB,CAAC,aAAa,CAAU,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Record the deployment type reported by a bootstrap payload. Ignores empty
|
|
3
|
+
* values so an older backend that omits the field cannot clear a known type.
|
|
4
|
+
*/
|
|
5
|
+
export declare function setResolvedDeployType(t?: string): void;
|
|
6
|
+
/**
|
|
7
|
+
* The payload-reported deployment type if one has arrived, otherwise the
|
|
8
|
+
* `AGENT_DEPLOY_TYPE` env var. `undefined` means "we do not know".
|
|
9
|
+
*/
|
|
10
|
+
export declare function getResolvedDeployType(): string | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* True only for managed installs ('vm' / 'k8s'). Unknown deployment types
|
|
13
|
+
* deliberately resolve to false — fail safe, because the expensive mistake is
|
|
14
|
+
* rewriting a developer's ~/.claude/settings.json, not skipping a full apply.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isManagedDeploy(): boolean;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { config } from '../config.js';
|
|
2
|
+
/**
|
|
3
|
+
* Deployment types describing a machine our platform provisioned end to end.
|
|
4
|
+
* Everything else — 'standalone', 'external', or an unknown/absent type — is a
|
|
5
|
+
* third-party machine whose config files we are only allowed to merge into.
|
|
6
|
+
*/
|
|
7
|
+
const MANAGED_TYPES = new Set(['vm', 'k8s']);
|
|
8
|
+
/** Set from the bootstrap payload; survives for the life of the process. */
|
|
9
|
+
let resolvedDeployType;
|
|
10
|
+
/**
|
|
11
|
+
* Record the deployment type reported by a bootstrap payload. Ignores empty
|
|
12
|
+
* values so an older backend that omits the field cannot clear a known type.
|
|
13
|
+
*/
|
|
14
|
+
export function setResolvedDeployType(t) {
|
|
15
|
+
if (t)
|
|
16
|
+
resolvedDeployType = t;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The payload-reported deployment type if one has arrived, otherwise the
|
|
20
|
+
* `AGENT_DEPLOY_TYPE` env var. `undefined` means "we do not know".
|
|
21
|
+
*/
|
|
22
|
+
export function getResolvedDeployType() {
|
|
23
|
+
return resolvedDeployType ?? config.agentDeployType;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* True only for managed installs ('vm' / 'k8s'). Unknown deployment types
|
|
27
|
+
* deliberately resolve to false — fail safe, because the expensive mistake is
|
|
28
|
+
* rewriting a developer's ~/.claude/settings.json, not skipping a full apply.
|
|
29
|
+
*/
|
|
30
|
+
export function isManagedDeploy() {
|
|
31
|
+
const type = getResolvedDeployType();
|
|
32
|
+
return type !== undefined && MANAGED_TYPES.has(type);
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=deploy-type.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deploy-type.js","sourceRoot":"","sources":["../../src/utils/deploy-type.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC;;;;GAIG;AACH,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;AAE7C,4EAA4E;AAC5E,IAAI,kBAAsC,CAAC;AAE3C;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,CAAU;IAC9C,IAAI,CAAC;QAAE,kBAAkB,GAAG,CAAC,CAAC;AAChC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB;IACnC,OAAO,kBAAkB,IAAI,MAAM,CAAC,eAAe,CAAC;AACtD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe;IAC7B,MAAM,IAAI,GAAG,qBAAqB,EAAE,CAAC;IACrC,OAAO,IAAI,KAAK,SAAS,IAAI,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACvD,CAAC"}
|
package/dist/utils/env.d.ts
CHANGED
package/dist/utils/env.js
CHANGED
|
@@ -1,12 +1,4 @@
|
|
|
1
1
|
import { config, requireConfig } from '../config.js';
|
|
2
|
-
export function requireEnv(name) {
|
|
3
|
-
const value = process.env[name];
|
|
4
|
-
if (!value) {
|
|
5
|
-
console.error(`Missing required environment variable: ${name}`);
|
|
6
|
-
process.exit(1);
|
|
7
|
-
}
|
|
8
|
-
return value;
|
|
9
|
-
}
|
|
10
2
|
export function getBackendUrl() {
|
|
11
3
|
return config.backendUrl ?? requireConfig('backendUrl');
|
|
12
4
|
}
|
package/dist/utils/env.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/utils/env.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAErD,MAAM,UAAU,
|
|
1
|
+
{"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/utils/env.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAErD,MAAM,UAAU,aAAa;IAC3B,OAAO,MAAM,CAAC,UAAU,IAAI,aAAa,CAAC,YAAY,CAAC,CAAC;AAC1D,CAAC"}
|
package/dist/utils/exec.d.ts
CHANGED
|
@@ -1 +1,12 @@
|
|
|
1
|
+
/** PATH handed to child processes: the inherited one plus {@link extraPathDirs}. */
|
|
2
|
+
export declare function execPath(): string;
|
|
3
|
+
/**
|
|
4
|
+
* Run a command through a shell and resolve its trimmed stdout.
|
|
5
|
+
*
|
|
6
|
+
* `timeout` defaults to 0 (no timeout) to preserve the behaviour of existing
|
|
7
|
+
* no-timeout callers (fast systemctl/launchctl/which calls); long-running
|
|
8
|
+
* callers already pass an explicit timeout. The raised maxBuffer prevents
|
|
9
|
+
* verbose output (e.g. `npm install -g`) from rejecting with
|
|
10
|
+
* ERR_CHILD_PROCESS_STDOUT_MAXBUFFER.
|
|
11
|
+
*/
|
|
1
12
|
export declare function execAsync(cmd: string, timeout?: number): Promise<string>;
|
package/dist/utils/exec.js
CHANGED
|
@@ -1,7 +1,53 @@
|
|
|
1
1
|
import { exec } from 'node:child_process';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { config } from '../config.js';
|
|
4
|
+
// 16 MB — avoid ERR_CHILD_PROCESS_STDOUT_MAXBUFFER on chatty commands like `npm install -g`.
|
|
5
|
+
const DEFAULT_MAX_BUFFER = 16 * 1024 * 1024;
|
|
6
|
+
/**
|
|
7
|
+
* Directories APPENDED to PATH for every child process we spawn.
|
|
8
|
+
*
|
|
9
|
+
* Why: the binaries we shell out to (`openclaw`, `npm`) live in a global npm
|
|
10
|
+
* prefix that is not on the ambient PATH in every context we run from, and a
|
|
11
|
+
* missing PATH entry has cost us the fleet twice:
|
|
12
|
+
* - VM first boot: the user-data step `su - ubuntu -c "openclaw plugins
|
|
13
|
+
* install @openclaw-cloud/mcp-warmup@latest"` died with `openclaw: command
|
|
14
|
+
* not found` on a whole cohort (~/.profile exports the npm-global bin only
|
|
15
|
+
* later in the bootstrap), and the `|| echo` swallowed it.
|
|
16
|
+
* - macOS launchd: the service PATH lacks /opt/homebrew/bin, so the
|
|
17
|
+
* best-effort `openclaw gateway restart` after a config merge always failed
|
|
18
|
+
* with `/bin/sh: openclaw: command not found`.
|
|
19
|
+
*
|
|
20
|
+
* APPENDED, never prepended — an operator's own PATH entry still wins, so this
|
|
21
|
+
* cannot hijack a deliberately pinned binary. Non-existent dirs are harmless.
|
|
22
|
+
*/
|
|
23
|
+
function extraPathDirs() {
|
|
24
|
+
return [
|
|
25
|
+
path.join(config.home, '.npm-global', 'bin'), // npm prefix used by the VM provisioner
|
|
26
|
+
'/usr/local/bin',
|
|
27
|
+
'/opt/homebrew/bin', // Homebrew on Apple silicon
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
/** PATH handed to child processes: the inherited one plus {@link extraPathDirs}. */
|
|
31
|
+
export function execPath() {
|
|
32
|
+
const parts = (process.env.PATH ?? '').split(path.delimiter).filter(Boolean);
|
|
33
|
+
for (const dir of extraPathDirs()) {
|
|
34
|
+
if (!parts.includes(dir))
|
|
35
|
+
parts.push(dir);
|
|
36
|
+
}
|
|
37
|
+
return parts.join(path.delimiter);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Run a command through a shell and resolve its trimmed stdout.
|
|
41
|
+
*
|
|
42
|
+
* `timeout` defaults to 0 (no timeout) to preserve the behaviour of existing
|
|
43
|
+
* no-timeout callers (fast systemctl/launchctl/which calls); long-running
|
|
44
|
+
* callers already pass an explicit timeout. The raised maxBuffer prevents
|
|
45
|
+
* verbose output (e.g. `npm install -g`) from rejecting with
|
|
46
|
+
* ERR_CHILD_PROCESS_STDOUT_MAXBUFFER.
|
|
47
|
+
*/
|
|
2
48
|
export function execAsync(cmd, timeout = 0) {
|
|
3
49
|
return new Promise((resolve, reject) => {
|
|
4
|
-
exec(cmd, { timeout }, (err, stdout) => {
|
|
50
|
+
exec(cmd, { timeout, maxBuffer: DEFAULT_MAX_BUFFER, env: { ...process.env, PATH: execPath() } }, (err, stdout) => {
|
|
5
51
|
if (err)
|
|
6
52
|
reject(err);
|
|
7
53
|
else
|
package/dist/utils/exec.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exec.js","sourceRoot":"","sources":["../../src/utils/exec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"exec.js","sourceRoot":"","sources":["../../src/utils/exec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC1C,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC,6FAA6F;AAC7F,MAAM,kBAAkB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAE5C;;;;;;;;;;;;;;;;GAgBG;AACH,SAAS,aAAa;IACpB,OAAO;QACL,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,aAAa,EAAE,KAAK,CAAC,EAAE,wCAAwC;QACtF,gBAAgB;QAChB,mBAAmB,EAAE,4BAA4B;KAClD,CAAC;AACJ,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,QAAQ;IACtB,MAAM,KAAK,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC7E,KAAK,MAAM,GAAG,IAAI,aAAa,EAAE,EAAE,CAAC;QAClC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AACpC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,SAAS,CAAC,GAAW,EAAE,OAAO,GAAG,CAAC;IAChD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,IAAI,CACF,GAAG,EACH,EAAE,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,EACrF,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;YACd,IAAI,GAAG;gBAAE,MAAM,CAAC,GAAG,CAAC,CAAC;;gBAChB,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;QACzC,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { AgentCommand, AgentResponse } from '../types.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* The handler behind every command whose whole job is "restart the OpenClaw
|
|
4
|
+
* gateway": `restart`, `deploy` and `onboarding_complete`. They were three
|
|
5
|
+
* byte-identical copies of this wrapper.
|
|
6
|
+
*
|
|
7
|
+
* Emits only the domain event (`gateway_restart`) — the command lifecycle
|
|
8
|
+
* events are owned by the dispatch loop in connection.ts.
|
|
5
9
|
*/
|
|
6
10
|
export declare function handleGatewayRestartCommand(command: AgentCommand): Promise<AgentResponse>;
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import { restartGateway } from './gateway-restart.js';
|
|
2
|
+
import { logCollector } from '../connection.js';
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
4
|
+
* The handler behind every command whose whole job is "restart the OpenClaw
|
|
5
|
+
* gateway": `restart`, `deploy` and `onboarding_complete`. They were three
|
|
6
|
+
* byte-identical copies of this wrapper.
|
|
7
|
+
*
|
|
8
|
+
* Emits only the domain event (`gateway_restart`) — the command lifecycle
|
|
9
|
+
* events are owned by the dispatch loop in connection.ts.
|
|
5
10
|
*/
|
|
6
11
|
export async function handleGatewayRestartCommand(command) {
|
|
12
|
+
logCollector?.push('gateway_restart', 'info', 'Gateway restart requested', {
|
|
13
|
+
commandId: command.id,
|
|
14
|
+
type: command.type,
|
|
15
|
+
});
|
|
7
16
|
try {
|
|
8
17
|
const { stdout, stderr } = await restartGateway();
|
|
9
18
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gateway-restart-handler.js","sourceRoot":"","sources":["../../src/utils/gateway-restart-handler.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"gateway-restart-handler.js","sourceRoot":"","sources":["../../src/utils/gateway-restart-handler.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEhD;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAAC,OAAqB;IACrE,YAAY,EAAE,IAAI,CAAC,iBAAiB,EAAE,MAAM,EAAE,2BAA2B,EAAE;QACzE,SAAS,EAAE,OAAO,CAAC,EAAE;QACrB,IAAI,EAAE,OAAO,CAAC,IAAI;KACnB,CAAC,CAAC;IACH,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,cAAc,EAAE,CAAC;QAClD,OAAO;YACL,EAAE,EAAE,OAAO,CAAC,EAAE;YACd,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,OAAO,EAAE,IAAI;YACb,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE;SACzB,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,GAAG,GAAmD,CAAC;QAC9D,OAAO;YACL,EAAE,EAAE,OAAO,CAAC,EAAE;YACd,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,OAAO,EAAE,KAAK;YACd,IAAI,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,EAAE,EAAE;YACxD,KAAK,EAAE,CAAC,CAAC,OAAO;SACjB,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -5,6 +5,13 @@ export interface GatewayRestartResult {
|
|
|
5
5
|
/**
|
|
6
6
|
* Restart the gateway via the configured agent provider.
|
|
7
7
|
* Returns { stdout: '', stderr: '' } for interface compatibility.
|
|
8
|
+
*
|
|
9
|
+
* `configureProvider()` only ever runs inside the daemon's `main()`, so in
|
|
10
|
+
* EVERY CLI invocation `getProvider()` is null. This used to warn and return
|
|
11
|
+
* success, which made `agent-controller restart` (and the restart tail of
|
|
12
|
+
* `deploy` / `agent-update` / `self-update`) a silent no-op that still exited
|
|
13
|
+
* 0. We now build a throwaway provider from config instead, and throw when
|
|
14
|
+
* even that is impossible so the caller can exit non-zero.
|
|
8
15
|
*/
|
|
9
16
|
export declare function restartGateway(): Promise<GatewayRestartResult>;
|
|
10
17
|
/**
|
|
@@ -1,13 +1,21 @@
|
|
|
1
|
-
import { getProvider } from '../providers/index.js';
|
|
1
|
+
import { createProviderFromConfig, getProvider } from '../providers/index.js';
|
|
2
|
+
import { config } from '../config.js';
|
|
2
3
|
/**
|
|
3
4
|
* Restart the gateway via the configured agent provider.
|
|
4
5
|
* Returns { stdout: '', stderr: '' } for interface compatibility.
|
|
6
|
+
*
|
|
7
|
+
* `configureProvider()` only ever runs inside the daemon's `main()`, so in
|
|
8
|
+
* EVERY CLI invocation `getProvider()` is null. This used to warn and return
|
|
9
|
+
* success, which made `agent-controller restart` (and the restart tail of
|
|
10
|
+
* `deploy` / `agent-update` / `self-update`) a silent no-op that still exited
|
|
11
|
+
* 0. We now build a throwaway provider from config instead, and throw when
|
|
12
|
+
* even that is impossible so the caller can exit non-zero.
|
|
5
13
|
*/
|
|
6
14
|
export async function restartGateway() {
|
|
7
|
-
const provider = getProvider();
|
|
15
|
+
const provider = getProvider() ?? createProviderFromConfig();
|
|
8
16
|
if (!provider) {
|
|
9
|
-
|
|
10
|
-
|
|
17
|
+
throw new Error(`Cannot restart gateway: unknown agent provider '${config.agentProvider}' ` +
|
|
18
|
+
'(set AGENT_PROVIDER to openclaw, claude-code or mock)');
|
|
11
19
|
}
|
|
12
20
|
await provider.restart();
|
|
13
21
|
return { stdout: '', stderr: '' };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gateway-restart.js","sourceRoot":"","sources":["../../src/utils/gateway-restart.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"gateway-restart.js","sourceRoot":"","sources":["../../src/utils/gateway-restart.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAC9E,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAOtC;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,QAAQ,GAAG,WAAW,EAAE,IAAI,wBAAwB,EAAE,CAAC;IAC7D,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CACb,mDAAmD,MAAM,CAAC,aAAa,IAAI;YACzE,uDAAuD,CAC1D,CAAC;IACJ,CAAC;IACD,MAAM,QAAQ,CAAC,OAAO,EAAE,CAAC;IACzB,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;AACpC,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,cAAc,CAAC"}
|
|
@@ -48,8 +48,20 @@ export declare class KnowledgeGraphManager extends EventEmitter {
|
|
|
48
48
|
/** Load graph from disk into memory. Call once at startup. */
|
|
49
49
|
init(): Promise<void>;
|
|
50
50
|
private readGraphFromDisk;
|
|
51
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* Serialized persist: enqueues writes so concurrent calls never interleave.
|
|
53
|
+
* The queue tail swallows rejections so persistQueue is always fulfilled — a
|
|
54
|
+
* failed write never poisons the chain (the next write still runs) — while the
|
|
55
|
+
* caller of THIS write still observes its real success/failure.
|
|
56
|
+
*/
|
|
52
57
|
private persist;
|
|
58
|
+
/**
|
|
59
|
+
* Persist after an in-memory mutation, rolling the graph back to `snapshot`
|
|
60
|
+
* if the write fails. This keeps memory consistent with disk and with the
|
|
61
|
+
* backend (which is told the mutation failed and receives no delta) instead
|
|
62
|
+
* of leaving the failed mutation in memory to land on the next write.
|
|
63
|
+
*/
|
|
64
|
+
private persistWithRollback;
|
|
53
65
|
/** Atomic persist: write to .tmp, then fs.rename(). */
|
|
54
66
|
private doPersist;
|
|
55
67
|
/** Emit delta event after mutation (for sync to backend). */
|
|
@@ -33,24 +33,35 @@ export class KnowledgeGraphManager extends EventEmitter {
|
|
|
33
33
|
try {
|
|
34
34
|
const data = await fs.readFile(this.memoryFilePath, 'utf-8');
|
|
35
35
|
const lines = data.split('\n').filter((line) => line.trim() !== '');
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
const graph = { entities: [], relations: [] };
|
|
37
|
+
for (const line of lines) {
|
|
38
|
+
let item;
|
|
39
|
+
try {
|
|
40
|
+
item = JSON.parse(line);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Skip a single corrupt/truncated line instead of discarding the
|
|
44
|
+
// entire graph — recover every well-formed record around it.
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (item.type === 'entity' && typeof item.name === 'string') {
|
|
39
48
|
graph.entities.push({
|
|
40
49
|
name: item.name,
|
|
41
|
-
entityType: item.entityType,
|
|
42
|
-
observations: item.observations,
|
|
50
|
+
entityType: item.entityType ?? '',
|
|
51
|
+
observations: item.observations ?? [],
|
|
43
52
|
});
|
|
44
53
|
}
|
|
45
|
-
if (item.type === 'relation'
|
|
54
|
+
else if (item.type === 'relation' &&
|
|
55
|
+
typeof item.from === 'string' &&
|
|
56
|
+
typeof item.to === 'string') {
|
|
46
57
|
graph.relations.push({
|
|
47
58
|
from: item.from,
|
|
48
59
|
to: item.to,
|
|
49
|
-
relationType: item.relationType,
|
|
60
|
+
relationType: item.relationType ?? '',
|
|
50
61
|
});
|
|
51
62
|
}
|
|
52
|
-
|
|
53
|
-
|
|
63
|
+
}
|
|
64
|
+
return graph;
|
|
54
65
|
}
|
|
55
66
|
catch (error) {
|
|
56
67
|
if (error?.code === 'ENOENT') {
|
|
@@ -59,10 +70,31 @@ export class KnowledgeGraphManager extends EventEmitter {
|
|
|
59
70
|
throw error;
|
|
60
71
|
}
|
|
61
72
|
}
|
|
62
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* Serialized persist: enqueues writes so concurrent calls never interleave.
|
|
75
|
+
* The queue tail swallows rejections so persistQueue is always fulfilled — a
|
|
76
|
+
* failed write never poisons the chain (the next write still runs) — while the
|
|
77
|
+
* caller of THIS write still observes its real success/failure.
|
|
78
|
+
*/
|
|
63
79
|
persist() {
|
|
64
|
-
|
|
65
|
-
|
|
80
|
+
const run = this.persistQueue.then(() => this.doPersist());
|
|
81
|
+
this.persistQueue = run.then(() => undefined, () => undefined);
|
|
82
|
+
return run;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Persist after an in-memory mutation, rolling the graph back to `snapshot`
|
|
86
|
+
* if the write fails. This keeps memory consistent with disk and with the
|
|
87
|
+
* backend (which is told the mutation failed and receives no delta) instead
|
|
88
|
+
* of leaving the failed mutation in memory to land on the next write.
|
|
89
|
+
*/
|
|
90
|
+
async persistWithRollback(snapshot) {
|
|
91
|
+
try {
|
|
92
|
+
await this.persist();
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
this.graph = snapshot;
|
|
96
|
+
throw err;
|
|
97
|
+
}
|
|
66
98
|
}
|
|
67
99
|
/** Atomic persist: write to .tmp, then fs.rename(). */
|
|
68
100
|
async doPersist() {
|
|
@@ -89,8 +121,9 @@ export class KnowledgeGraphManager extends EventEmitter {
|
|
|
89
121
|
// -------------------------------------------------------------------------
|
|
90
122
|
async createEntities(entities) {
|
|
91
123
|
const newEntities = entities.filter((e) => !this.graph.entities.some((existing) => existing.name === e.name));
|
|
124
|
+
const snapshot = structuredClone(this.graph);
|
|
92
125
|
this.graph.entities.push(...newEntities);
|
|
93
|
-
await this.
|
|
126
|
+
await this.persistWithRollback(snapshot);
|
|
94
127
|
this.emitDelta('entities_created', newEntities);
|
|
95
128
|
return newEntities;
|
|
96
129
|
}
|
|
@@ -98,44 +131,57 @@ export class KnowledgeGraphManager extends EventEmitter {
|
|
|
98
131
|
const newRelations = relations.filter((r) => !this.graph.relations.some((existing) => existing.from === r.from &&
|
|
99
132
|
existing.to === r.to &&
|
|
100
133
|
existing.relationType === r.relationType));
|
|
134
|
+
const snapshot = structuredClone(this.graph);
|
|
101
135
|
this.graph.relations.push(...newRelations);
|
|
102
|
-
await this.
|
|
136
|
+
await this.persistWithRollback(snapshot);
|
|
103
137
|
this.emitDelta('relations_created', newRelations);
|
|
104
138
|
return newRelations;
|
|
105
139
|
}
|
|
106
140
|
async addObservations(observations) {
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
141
|
+
const snapshot = structuredClone(this.graph);
|
|
142
|
+
try {
|
|
143
|
+
const results = observations.map((o) => {
|
|
144
|
+
const entity = this.graph.entities.find((e) => e.name === o.entityName);
|
|
145
|
+
if (!entity) {
|
|
146
|
+
throw new Error(`Entity with name ${o.entityName} not found`);
|
|
147
|
+
}
|
|
148
|
+
const newObservations = o.contents.filter((content) => !entity.observations.includes(content));
|
|
149
|
+
entity.observations.push(...newObservations);
|
|
150
|
+
return { entityName: o.entityName, addedObservations: newObservations };
|
|
151
|
+
});
|
|
152
|
+
await this.persistWithRollback(snapshot);
|
|
153
|
+
this.emitDelta('observations_added', results);
|
|
154
|
+
return results;
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
// Roll back any partial in-memory mutation (a missing entity throws
|
|
158
|
+
// mid-map after earlier entities were already mutated).
|
|
159
|
+
this.graph = snapshot;
|
|
160
|
+
throw err;
|
|
161
|
+
}
|
|
119
162
|
}
|
|
120
163
|
async deleteEntities(names) {
|
|
164
|
+
const snapshot = structuredClone(this.graph);
|
|
121
165
|
this.graph.entities = this.graph.entities.filter((e) => !names.includes(e.name));
|
|
122
166
|
this.graph.relations = this.graph.relations.filter((r) => !names.includes(r.from) && !names.includes(r.to));
|
|
123
|
-
await this.
|
|
167
|
+
await this.persistWithRollback(snapshot);
|
|
124
168
|
this.emitDelta('entities_deleted', names);
|
|
125
169
|
}
|
|
126
170
|
async deleteObservations(deletions) {
|
|
171
|
+
const snapshot = structuredClone(this.graph);
|
|
127
172
|
deletions.forEach((d) => {
|
|
128
173
|
const entity = this.graph.entities.find((e) => e.name === d.entityName);
|
|
129
174
|
if (entity) {
|
|
130
175
|
entity.observations = entity.observations.filter((o) => !d.observations.includes(o));
|
|
131
176
|
}
|
|
132
177
|
});
|
|
133
|
-
await this.
|
|
178
|
+
await this.persistWithRollback(snapshot);
|
|
134
179
|
this.emitDelta('observations_deleted', deletions);
|
|
135
180
|
}
|
|
136
181
|
async deleteRelations(relations) {
|
|
182
|
+
const snapshot = structuredClone(this.graph);
|
|
137
183
|
this.graph.relations = this.graph.relations.filter((r) => !relations.some((del) => r.from === del.from && r.to === del.to && r.relationType === del.relationType));
|
|
138
|
-
await this.
|
|
184
|
+
await this.persistWithRollback(snapshot);
|
|
139
185
|
this.emitDelta('relations_deleted', relations);
|
|
140
186
|
}
|
|
141
187
|
// -------------------------------------------------------------------------
|