@openclaw-cloud/agent-controller 3.0.0-beta.2 → 3.0.0-beta.20
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 +56 -20
- package/dist/config-file.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.js +12 -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 +87 -119
- package/dist/index.js.map +1 -1
- package/dist/lockfile.js +2 -1
- package/dist/lockfile.js.map +1 -1
- 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 +11 -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 +295 -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
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export type ReconcileSource = 'bootstrap' | 'startup';
|
|
2
|
+
export interface ReconcileResult {
|
|
3
|
+
declared: string[];
|
|
4
|
+
missing: string[];
|
|
5
|
+
installed: string[];
|
|
6
|
+
failed: Array<{
|
|
7
|
+
id: string;
|
|
8
|
+
error: string;
|
|
9
|
+
}>;
|
|
10
|
+
/** Set when reconcile could not run — nothing was installed. */
|
|
11
|
+
skippedReason?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The plugin ids openclaw.json says must be present.
|
|
15
|
+
*
|
|
16
|
+
* `plugins.allow` is the primary source: the backend's config generator writes
|
|
17
|
+
* exactly the external npm plugins it expects installed (today
|
|
18
|
+
* `['@openclaw-cloud/mcp-warmup']`).
|
|
19
|
+
*
|
|
20
|
+
* `plugins.entries` is also consulted, but ONLY for scoped ids (`@scope/name`).
|
|
21
|
+
* That block additionally holds openclaw's BUNDLED plugins — `telegram`,
|
|
22
|
+
* `discord`, `slack`, `whatsapp` — which ship inside the runtime and are not
|
|
23
|
+
* npm-installable; handing those to `openclaw plugins install` would fail on
|
|
24
|
+
* every boot. Bundled ids are bare words, external ones are scoped, so the
|
|
25
|
+
* `@scope/` shape is the discriminator. An entry explicitly `{enabled: false}`
|
|
26
|
+
* is skipped.
|
|
27
|
+
*
|
|
28
|
+
* Everything is finally filtered through `isSafePackageName` — these ids reach
|
|
29
|
+
* a shell via `openclaw plugins install <id>@<version>`.
|
|
30
|
+
*/
|
|
31
|
+
export declare function declaredPluginIds(openclawConfig: unknown): string[];
|
|
32
|
+
/**
|
|
33
|
+
* Install every declared-but-absent plugin. NEVER throws — bootstrap and
|
|
34
|
+
* startup both call it on their happy path and a plugin problem must not take
|
|
35
|
+
* down the boot; the next controller start reconciles again.
|
|
36
|
+
*
|
|
37
|
+
* Cheap when nothing is missing: one `openclaw plugins list --json` (no npm
|
|
38
|
+
* traffic, no backend call). The backend/npm work only happens once something
|
|
39
|
+
* is actually absent.
|
|
40
|
+
*
|
|
41
|
+
* Installs go through `runOpenclawStackUpdate`, the one owner of
|
|
42
|
+
* "install then restart the gateway once if anything changed" — so a plugin
|
|
43
|
+
* installed while the gateway is already running is picked up immediately
|
|
44
|
+
* instead of waiting for the next restart.
|
|
45
|
+
*/
|
|
46
|
+
export declare function reconcilePlugins(source: ReconcileSource): Promise<ReconcileResult>;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin reconciliation — agent-controller is the SINGLE OWNER of "the plugins
|
|
3
|
+
* openclaw.json declares are actually installed on disk".
|
|
4
|
+
*
|
|
5
|
+
* Background (incident 2026-08-03): the EC2 user-data used to install
|
|
6
|
+
* `@openclaw-cloud/mcp-warmup` itself with
|
|
7
|
+
* su - ubuntu -c "openclaw plugins install ...@latest --force" || echo "…non-fatal"
|
|
8
|
+
* On a whole cohort of VMs that step died with `openclaw: command not found`
|
|
9
|
+
* (the login shell has no npm-global bin on PATH that early in first boot) and
|
|
10
|
+
* the `|| echo` swallowed it. The agents then ran for months with
|
|
11
|
+
* `plugins.allow=["@openclaw-cloud/mcp-warmup"]` in openclaw.json and no
|
|
12
|
+
* package on disk — openclaw logged "stale config entry ignored", no MCP warmup
|
|
13
|
+
* session ever started, and board tasks were undeliverable. Nothing noticed.
|
|
14
|
+
*
|
|
15
|
+
* So: the declared set lives in openclaw.json (written by the backend
|
|
16
|
+
* bootstrap), and this module makes the disk match it — at `agent-controller
|
|
17
|
+
* bootstrap` and again on every controller start, which is what turns a reboot
|
|
18
|
+
* into a self-heal instead of a permanent silent failure.
|
|
19
|
+
*
|
|
20
|
+
* SCOPE — missing-entirely only. A plugin that is installed at the "wrong"
|
|
21
|
+
* version is NOT touched here: version upgrades remain self-update's job
|
|
22
|
+
* (`runExplicitSelfUpdate` / the `agent_update` + `plugin_update` handlers,
|
|
23
|
+
* driven by backend targets). Reconcile answers one question — "is it there at
|
|
24
|
+
* all?" — so it can stay cheap enough to run on every boot.
|
|
25
|
+
*/
|
|
26
|
+
import fs from 'node:fs/promises';
|
|
27
|
+
import { getOpenclawJsonPath } from './apply-config.js';
|
|
28
|
+
import { listInstalledPlugins } from './openclaw-ops.js';
|
|
29
|
+
import { runOpenclawStackUpdate } from './openclaw-stack-update.js';
|
|
30
|
+
import { isSafePackageName } from './package-install.js';
|
|
31
|
+
import { getReleaseChannel } from './release-channel.js';
|
|
32
|
+
// The runtime-targets fetch has one implementation, in the self-update CLI —
|
|
33
|
+
// reused rather than re-spelled so reconcile resolves plugin versions exactly
|
|
34
|
+
// the way self-update does.
|
|
35
|
+
import { fetchRuntimeTargets } from '../commands/self-update.js';
|
|
36
|
+
import { logCollector } from '../connection.js';
|
|
37
|
+
import { toErrorMessage } from './response.js';
|
|
38
|
+
const LOG_PREFIX = '[plugin-reconcile]';
|
|
39
|
+
/**
|
|
40
|
+
* The plugin ids openclaw.json says must be present.
|
|
41
|
+
*
|
|
42
|
+
* `plugins.allow` is the primary source: the backend's config generator writes
|
|
43
|
+
* exactly the external npm plugins it expects installed (today
|
|
44
|
+
* `['@openclaw-cloud/mcp-warmup']`).
|
|
45
|
+
*
|
|
46
|
+
* `plugins.entries` is also consulted, but ONLY for scoped ids (`@scope/name`).
|
|
47
|
+
* That block additionally holds openclaw's BUNDLED plugins — `telegram`,
|
|
48
|
+
* `discord`, `slack`, `whatsapp` — which ship inside the runtime and are not
|
|
49
|
+
* npm-installable; handing those to `openclaw plugins install` would fail on
|
|
50
|
+
* every boot. Bundled ids are bare words, external ones are scoped, so the
|
|
51
|
+
* `@scope/` shape is the discriminator. An entry explicitly `{enabled: false}`
|
|
52
|
+
* is skipped.
|
|
53
|
+
*
|
|
54
|
+
* Everything is finally filtered through `isSafePackageName` — these ids reach
|
|
55
|
+
* a shell via `openclaw plugins install <id>@<version>`.
|
|
56
|
+
*/
|
|
57
|
+
export function declaredPluginIds(openclawConfig) {
|
|
58
|
+
const plugins = openclawConfig?.plugins;
|
|
59
|
+
if (!plugins || typeof plugins !== 'object')
|
|
60
|
+
return [];
|
|
61
|
+
const ids = new Set();
|
|
62
|
+
const allow = plugins.allow;
|
|
63
|
+
if (Array.isArray(allow)) {
|
|
64
|
+
for (const id of allow) {
|
|
65
|
+
if (typeof id === 'string' && id.trim())
|
|
66
|
+
ids.add(id.trim());
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const entries = plugins.entries;
|
|
70
|
+
if (entries && typeof entries === 'object' && !Array.isArray(entries)) {
|
|
71
|
+
for (const [rawId, entry] of Object.entries(entries)) {
|
|
72
|
+
const id = rawId.trim();
|
|
73
|
+
if (!id.startsWith('@') || !id.includes('/'))
|
|
74
|
+
continue; // bundled runtime plugin
|
|
75
|
+
const enabled = entry?.enabled;
|
|
76
|
+
if (enabled === false)
|
|
77
|
+
continue;
|
|
78
|
+
ids.add(id);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return [...ids].filter(isSafePackageName);
|
|
82
|
+
}
|
|
83
|
+
async function readDeclaredPlugins() {
|
|
84
|
+
const raw = await fs.readFile(getOpenclawJsonPath(), 'utf-8').catch(() => null);
|
|
85
|
+
if (raw === null)
|
|
86
|
+
return [];
|
|
87
|
+
try {
|
|
88
|
+
return declaredPluginIds(JSON.parse(raw));
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
console.warn(`${LOG_PREFIX} openclaw.json is not valid JSON:`, toErrorMessage(err));
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Version to install a missing plugin at — resolved exactly the way
|
|
97
|
+
* self-update does:
|
|
98
|
+
* 1. the backend's runtime targets for this agent
|
|
99
|
+
* (`GET /api/agent/runtime-targets` → `openclaw.plugins[]`), which is what
|
|
100
|
+
* `runSelfUpdateCli` prefers, then
|
|
101
|
+
* 2. the AC_RELEASE_CHANNEL dist-tag — `beta` on the beta channel, `latest`
|
|
102
|
+
* on stable (the same mapping as `selfUpdate()`), which is also what the
|
|
103
|
+
* old user-data line used.
|
|
104
|
+
*/
|
|
105
|
+
async function resolvePluginVersions(ids) {
|
|
106
|
+
const channel = getReleaseChannel();
|
|
107
|
+
const distTag = channel === 'stable' ? 'latest' : channel;
|
|
108
|
+
// Best-effort: fetchRuntimeTargets() already swallows every failure into null.
|
|
109
|
+
const targets = await fetchRuntimeTargets();
|
|
110
|
+
const fromBackend = new Map();
|
|
111
|
+
for (const p of targets?.openclaw?.plugins ?? []) {
|
|
112
|
+
if (p && typeof p.id === 'string' && typeof p.version === 'string' && p.version) {
|
|
113
|
+
fromBackend.set(p.id, p.version);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return new Map(ids.map((id) => [id, fromBackend.get(id) ?? distTag]));
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Install every declared-but-absent plugin. NEVER throws — bootstrap and
|
|
120
|
+
* startup both call it on their happy path and a plugin problem must not take
|
|
121
|
+
* down the boot; the next controller start reconciles again.
|
|
122
|
+
*
|
|
123
|
+
* Cheap when nothing is missing: one `openclaw plugins list --json` (no npm
|
|
124
|
+
* traffic, no backend call). The backend/npm work only happens once something
|
|
125
|
+
* is actually absent.
|
|
126
|
+
*
|
|
127
|
+
* Installs go through `runOpenclawStackUpdate`, the one owner of
|
|
128
|
+
* "install then restart the gateway once if anything changed" — so a plugin
|
|
129
|
+
* installed while the gateway is already running is picked up immediately
|
|
130
|
+
* instead of waiting for the next restart.
|
|
131
|
+
*/
|
|
132
|
+
export async function reconcilePlugins(source) {
|
|
133
|
+
const empty = { declared: [], missing: [], installed: [], failed: [] };
|
|
134
|
+
try {
|
|
135
|
+
const declared = await readDeclaredPlugins();
|
|
136
|
+
if (declared.length === 0) {
|
|
137
|
+
console.log(`${LOG_PREFIX} no plugins declared in openclaw.json — nothing to reconcile`);
|
|
138
|
+
return empty;
|
|
139
|
+
}
|
|
140
|
+
// `openclaw plugins list --json` is the source of truth, NOT the
|
|
141
|
+
// `plugins.installs` block of openclaw.json: the incident was precisely a
|
|
142
|
+
// config entry with no package behind it, so a config-derived inventory
|
|
143
|
+
// would report the missing plugin as present.
|
|
144
|
+
let installedIds;
|
|
145
|
+
try {
|
|
146
|
+
installedIds = new Set((await listInstalledPlugins()).map((p) => p.id));
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
const error = toErrorMessage(err);
|
|
150
|
+
// Loud: without an inventory we cannot tell present from absent, and
|
|
151
|
+
// blind-installing on every boot would hit npm forever.
|
|
152
|
+
console.error(`${LOG_PREFIX} cannot list installed plugins — skipping reconcile: ${error}`);
|
|
153
|
+
logCollector?.push('plugin_reconcile_probe_failed', 'error', `Plugin reconcile could not read the installed plugin list: ${error}`, { source, declared, error });
|
|
154
|
+
return { ...empty, declared, skippedReason: error };
|
|
155
|
+
}
|
|
156
|
+
const missing = declared.filter((id) => !installedIds.has(id));
|
|
157
|
+
if (missing.length === 0) {
|
|
158
|
+
console.log(`${LOG_PREFIX} all ${declared.length} declared plugin(s) present`);
|
|
159
|
+
return { ...empty, declared };
|
|
160
|
+
}
|
|
161
|
+
console.log(`${LOG_PREFIX} missing plugin(s): ${missing.join(', ')} — installing`);
|
|
162
|
+
logCollector?.push('plugin_reconcile_missing', 'warn', `Declared plugin(s) not installed: ${missing.join(', ')}`, { source, declared, missing });
|
|
163
|
+
const versions = await resolvePluginVersions(missing);
|
|
164
|
+
const plugins = missing.map((id) => ({ id, version: versions.get(id) ?? 'latest' }));
|
|
165
|
+
const stack = await runOpenclawStackUpdate({
|
|
166
|
+
plugins,
|
|
167
|
+
eventPrefix: 'plugin_reconcile',
|
|
168
|
+
meta: { source },
|
|
169
|
+
});
|
|
170
|
+
const summary = stack.pluginSummary ?? { installed: [], skipped: [], failed: [] };
|
|
171
|
+
if (summary.installed.length > 0) {
|
|
172
|
+
console.log(`${LOG_PREFIX} installed: ${summary.installed.join(', ')}`);
|
|
173
|
+
logCollector?.push('plugin_reconcile_installed', 'info', `Installed ${summary.installed.length} missing plugin(s): ${summary.installed.join(', ')}`, { source, installed: summary.installed, gatewayRestarted: stack.gatewayRestarted });
|
|
174
|
+
}
|
|
175
|
+
if (stack.restartError) {
|
|
176
|
+
console.error(`${LOG_PREFIX} gateway restart failed: ${stack.restartError}`);
|
|
177
|
+
}
|
|
178
|
+
if (summary.failed.length > 0) {
|
|
179
|
+
// LOUD. A swallowed plugin install is what caused the incident; this is
|
|
180
|
+
// the line an operator greps for, and the backend `agent_log` row an
|
|
181
|
+
// alert can fire on. (In the `bootstrap` CLI process `logCollector` is
|
|
182
|
+
// undefined — no WS connection — so there the console line is all we
|
|
183
|
+
// have; the startup reconcile re-reports it to the backend.)
|
|
184
|
+
const detail = summary.failed.map((f) => `${f.id}: ${f.error}`).join('; ');
|
|
185
|
+
console.error(`${LOG_PREFIX} install FAILED for ${summary.failed.length} plugin(s): ${detail}`);
|
|
186
|
+
logCollector?.push('plugin_reconcile_failed', 'error', `Plugin install failed during reconcile: ${detail}`, { source, failed: summary.failed });
|
|
187
|
+
}
|
|
188
|
+
return { declared, missing, installed: summary.installed, failed: summary.failed };
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
// Anything unexpected (fs, backend client construction, …). Never fatal.
|
|
192
|
+
const error = toErrorMessage(err);
|
|
193
|
+
console.error(`${LOG_PREFIX} reconcile failed: ${error}`);
|
|
194
|
+
logCollector?.push('plugin_reconcile_failed', 'error', `Plugin reconcile failed: ${error}`, {
|
|
195
|
+
source,
|
|
196
|
+
error,
|
|
197
|
+
});
|
|
198
|
+
return { ...empty, skippedReason: error };
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=plugin-reconcile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-reconcile.js","sourceRoot":"","sources":["../../src/utils/plugin-reconcile.ts"],"names":[],"mappings":"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"}
|
package/dist/utils/response.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"response.js","sourceRoot":"","sources":["../../src/utils/response.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,cAAc,CAAC,GAAY;IACzC,OAAO,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAAqB,EAAE,GAAY;IACjE,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;AAC5F,CAAC"}
|
|
1
|
+
{"version":3,"file":"response.js","sourceRoot":"","sources":["../../src/utils/response.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,cAAc,CAAC,GAAY;IACzC,kFAAkF;IAClF,OAAO,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAAqB,EAAE,GAAY;IACjE,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;AAC5F,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Write platform-managed files into the agent user's HOME directory.
|
|
3
|
+
*
|
|
4
|
+
* These live OUTSIDE the openclaw workspace on purpose: the workspace is
|
|
5
|
+
* indexed and pushed back to the backend by knowledge sync, and these files are
|
|
6
|
+
* credentials (first consumer: `.secrets/SECRETS.txt`). Keys are paths RELATIVE
|
|
7
|
+
* to `os.homedir()`; values are the full contents.
|
|
8
|
+
*
|
|
9
|
+
* `os.homedir()` is the right anchor because on a VM/pod agent-controller runs
|
|
10
|
+
* as the agent user itself — the same resolution `writeClaudeEnv()`
|
|
11
|
+
* (`~/.claude`) and `config.stateDir` (`~/.openclaw`) already rely on. On
|
|
12
|
+
* systemd that means the unit's `HOME` must be the agent user's home; it
|
|
13
|
+
* already must be, or the controller would not find `~/.openclaw` either
|
|
14
|
+
* (docs/DRAGONS.md#systemd-home).
|
|
15
|
+
*
|
|
16
|
+
* Semantics:
|
|
17
|
+
* - Parent dirs created with mode 0700, files written 0600, always overwritten.
|
|
18
|
+
* - Absolute keys and keys escaping HOME are logged and skipped — the same
|
|
19
|
+
* segment-aware sandbox `writeWorkspaceFiles` uses, anchored at HOME.
|
|
20
|
+
* - Nothing here throws: one bad entry must never fail the whole config apply.
|
|
21
|
+
* - File CONTENTS are never logged — only the relative path and a byte count.
|
|
22
|
+
*
|
|
23
|
+
* ponytail: no deletion / directory GC. The platform overwrites the files it
|
|
24
|
+
* knows about; a key dropped from a later payload leaves the file on disk. Add
|
|
25
|
+
* a manifest + prune only if revocation-by-removal ever becomes a requirement.
|
|
26
|
+
*/
|
|
27
|
+
export declare function writeHomeFiles(files: Record<string, string> | undefined): Promise<number>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { resolveInsideWorkspace } from '../workspace.js';
|
|
5
|
+
import { getResolvedDeployType, isManagedDeploy } from './deploy-type.js';
|
|
6
|
+
import { toErrorMessage } from './response.js';
|
|
7
|
+
/**
|
|
8
|
+
* Write platform-managed files into the agent user's HOME directory.
|
|
9
|
+
*
|
|
10
|
+
* These live OUTSIDE the openclaw workspace on purpose: the workspace is
|
|
11
|
+
* indexed and pushed back to the backend by knowledge sync, and these files are
|
|
12
|
+
* credentials (first consumer: `.secrets/SECRETS.txt`). Keys are paths RELATIVE
|
|
13
|
+
* to `os.homedir()`; values are the full contents.
|
|
14
|
+
*
|
|
15
|
+
* `os.homedir()` is the right anchor because on a VM/pod agent-controller runs
|
|
16
|
+
* as the agent user itself — the same resolution `writeClaudeEnv()`
|
|
17
|
+
* (`~/.claude`) and `config.stateDir` (`~/.openclaw`) already rely on. On
|
|
18
|
+
* systemd that means the unit's `HOME` must be the agent user's home; it
|
|
19
|
+
* already must be, or the controller would not find `~/.openclaw` either
|
|
20
|
+
* (docs/DRAGONS.md#systemd-home).
|
|
21
|
+
*
|
|
22
|
+
* Semantics:
|
|
23
|
+
* - Parent dirs created with mode 0700, files written 0600, always overwritten.
|
|
24
|
+
* - Absolute keys and keys escaping HOME are logged and skipped — the same
|
|
25
|
+
* segment-aware sandbox `writeWorkspaceFiles` uses, anchored at HOME.
|
|
26
|
+
* - Nothing here throws: one bad entry must never fail the whole config apply.
|
|
27
|
+
* - File CONTENTS are never logged — only the relative path and a byte count.
|
|
28
|
+
*
|
|
29
|
+
* ponytail: no deletion / directory GC. The platform overwrites the files it
|
|
30
|
+
* knows about; a key dropped from a later payload leaves the file on disk. Add
|
|
31
|
+
* a manifest + prune only if revocation-by-removal ever becomes a requirement.
|
|
32
|
+
*/
|
|
33
|
+
export async function writeHomeFiles(files) {
|
|
34
|
+
if (!files || Object.keys(files).length === 0)
|
|
35
|
+
return 0;
|
|
36
|
+
// Same guard as writeClaudeEnv(), for the same reason: this writes into a
|
|
37
|
+
// real user's HOME. `agent-controller bootstrap` on a developer laptop must
|
|
38
|
+
// never do that. Defense in depth — the only caller is already managed-only.
|
|
39
|
+
if (!isManagedDeploy()) {
|
|
40
|
+
console.log(`[home-files] skipped: not a managed install (deployType=${getResolvedDeployType() ?? 'unknown'})`);
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
const homeDir = os.homedir();
|
|
44
|
+
let written = 0;
|
|
45
|
+
for (const [relPath, content] of Object.entries(files)) {
|
|
46
|
+
if (typeof content !== 'string') {
|
|
47
|
+
console.warn(`[home-files] ✗ ${relPath}: rejected (content is not a string)`);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const fullPath = resolveInsideWorkspace(homeDir, relPath);
|
|
51
|
+
if (!fullPath) {
|
|
52
|
+
console.warn(`[home-files] ✗ ${relPath}: rejected (path escapes home directory)`);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
// `mode` only applies to what these calls CREATE: a pre-existing dir
|
|
57
|
+
// keeps its permissions, and writeFile's mode is ignored when the file
|
|
58
|
+
// already exists — hence the explicit chmod on every write.
|
|
59
|
+
await fs.mkdir(path.dirname(fullPath), { recursive: true, mode: 0o700 });
|
|
60
|
+
await fs.writeFile(fullPath, content, { mode: 0o600 });
|
|
61
|
+
await fs.chmod(fullPath, 0o600);
|
|
62
|
+
written++;
|
|
63
|
+
// Credentials: path + size only, never the value.
|
|
64
|
+
console.log(`[home-files] ✓ ${relPath} (${Buffer.byteLength(content, 'utf8')} bytes)`);
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
console.error(`[home-files] ✗ ${relPath}: ${toErrorMessage(err)}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return written;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=write-home-files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write-home-files.js","sourceRoot":"","sources":["../../src/utils/write-home-files.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,KAAyC;IAC5E,IAAI,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAExD,0EAA0E;IAC1E,4EAA4E;IAC5E,6EAA6E;IAC7E,IAAI,CAAC,eAAe,EAAE,EAAE,CAAC;QACvB,OAAO,CAAC,GAAG,CACT,2DAA2D,qBAAqB,EAAE,IAAI,SAAS,GAAG,CACnG,CAAC;QACF,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,OAAO,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC;IAC7B,IAAI,OAAO,GAAG,CAAC,CAAC;IAEhB,KAAK,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACvD,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;YAChC,OAAO,CAAC,IAAI,CAAC,kBAAkB,OAAO,sCAAsC,CAAC,CAAC;YAC9E,SAAS;QACX,CAAC;QAED,MAAM,QAAQ,GAAG,sBAAsB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC1D,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,OAAO,CAAC,IAAI,CAAC,kBAAkB,OAAO,0CAA0C,CAAC,CAAC;YAClF,SAAS;QACX,CAAC;QAED,IAAI,CAAC;YACH,qEAAqE;YACrE,uEAAuE;YACvE,4DAA4D;YAC5D,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YACzE,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YACvD,MAAM,EAAE,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;YAChC,OAAO,EAAE,CAAC;YACV,kDAAkD;YAClD,OAAO,CAAC,GAAG,CAAC,kBAAkB,OAAO,KAAK,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;QACzF,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,kBAAkB,OAAO,KAAK,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACrE,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -12,4 +12,4 @@
|
|
|
12
12
|
* command path in `applyBootstrapConfig` (propagates shared-file updates from
|
|
13
13
|
* the backend without requiring a restart).
|
|
14
14
|
*/
|
|
15
|
-
export declare function writeWorkspaceFiles(files: Record<string, string>, workspaceDir: string): Promise<
|
|
15
|
+
export declare function writeWorkspaceFiles(files: Record<string, string>, workspaceDir: string): Promise<number>;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { resolveInsideWorkspace } from '../workspace.js';
|
|
4
|
+
import { toErrorMessage } from './response.js';
|
|
3
5
|
/**
|
|
4
6
|
* Write a map of workspace-relative paths → content into `workspaceDir`.
|
|
5
7
|
*
|
|
@@ -17,32 +19,42 @@ import path from 'node:path';
|
|
|
17
19
|
export async function writeWorkspaceFiles(files, workspaceDir) {
|
|
18
20
|
const fileNames = Object.keys(files);
|
|
19
21
|
if (fileNames.length === 0)
|
|
20
|
-
return;
|
|
22
|
+
return 0;
|
|
21
23
|
await fs.mkdir(workspaceDir, { recursive: true });
|
|
24
|
+
const written = [];
|
|
22
25
|
for (const [filePath, content] of Object.entries(files)) {
|
|
23
|
-
|
|
26
|
+
// Reject any key that escapes the workspace — a backend-supplied key like
|
|
27
|
+
// "../../etc/openclaw/agent.env" or an absolute path must not write outside
|
|
28
|
+
// it. Same sandbox helper as the file_write / file_delete handlers.
|
|
29
|
+
const fullPath = resolveInsideWorkspace(workspaceDir, filePath);
|
|
30
|
+
if (!fullPath) {
|
|
31
|
+
console.error(` ✗ ${filePath}: rejected (path escapes workspace)`);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
24
34
|
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
25
35
|
try {
|
|
26
36
|
await fs.writeFile(fullPath, content);
|
|
37
|
+
written.push(filePath);
|
|
27
38
|
console.log(` ✓ ${filePath}`);
|
|
28
39
|
}
|
|
29
40
|
catch (err) {
|
|
30
|
-
console.error(` ✗ ${filePath}: ${err
|
|
41
|
+
console.error(` ✗ ${filePath}: ${toErrorMessage(err)}`);
|
|
31
42
|
}
|
|
32
43
|
}
|
|
33
|
-
// Make .sh files executable
|
|
34
|
-
for (const filePath of
|
|
44
|
+
// Make .sh files executable (only the ones we actually wrote)
|
|
45
|
+
for (const filePath of written) {
|
|
35
46
|
if (filePath.endsWith('.sh')) {
|
|
36
47
|
const fullPath = path.join(workspaceDir, filePath);
|
|
37
48
|
await fs.chmod(fullPath, 0o755);
|
|
38
49
|
}
|
|
39
50
|
}
|
|
40
51
|
// Summary for deployed skills (under .agents/skills/<name>/...)
|
|
41
|
-
const skillFiles =
|
|
52
|
+
const skillFiles = written.filter((f) => f.startsWith('.agents/skills/'));
|
|
42
53
|
if (skillFiles.length > 0) {
|
|
43
54
|
const skills = [...new Set(skillFiles.map((f) => f.split('/')[2]))];
|
|
44
55
|
console.log(` ✓ skills deployed: ${skills.join(', ')}`);
|
|
45
56
|
}
|
|
46
|
-
console.log(` wrote ${
|
|
57
|
+
console.log(` wrote ${written.length} workspace files`);
|
|
58
|
+
return written.length;
|
|
47
59
|
}
|
|
48
60
|
//# sourceMappingURL=write-workspace-files.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"write-workspace-files.js","sourceRoot":"","sources":["../../src/utils/write-workspace-files.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"write-workspace-files.js","sourceRoot":"","sources":["../../src/utils/write-workspace-files.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,KAA6B,EAC7B,YAAoB;IAEpB,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrC,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAErC,MAAM,EAAE,CAAC,KAAK,CAAC,YAAY,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAElD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACxD,0EAA0E;QAC1E,4EAA4E;QAC5E,oEAAoE;QACpE,MAAM,QAAQ,GAAG,sBAAsB,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;QAChE,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,OAAO,CAAC,KAAK,CAAC,OAAO,QAAQ,qCAAqC,CAAC,CAAC;YACpE,SAAS;QACX,CAAC;QACD,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5D,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YACtC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACvB,OAAO,CAAC,GAAG,CAAC,OAAO,QAAQ,EAAE,CAAC,CAAC;QACjC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,OAAO,QAAQ,KAAK,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC;IAED,8DAA8D;IAC9D,KAAK,MAAM,QAAQ,IAAI,OAAO,EAAE,CAAC;QAC/B,IAAI,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAC7B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;YACnD,MAAM,EAAE,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IAED,gEAAgE;IAChE,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC;IAC1E,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACpE,OAAO,CAAC,GAAG,CAAC,wBAAwB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,WAAW,OAAO,CAAC,MAAM,kBAAkB,CAAC,CAAC;IACzD,OAAO,OAAO,CAAC,MAAM,CAAC;AACxB,CAAC"}
|
package/dist/workspace.d.ts
CHANGED
|
@@ -10,3 +10,14 @@
|
|
|
10
10
|
* (which used WORKSPACE_DIR) and backup/knowledge_sync (which used OPENCLAW_STATE_DIR).
|
|
11
11
|
*/
|
|
12
12
|
export declare function getWorkspaceDir(): string;
|
|
13
|
+
/**
|
|
14
|
+
* Resolve a backend-supplied relative path inside `workspaceDir`, or null if it
|
|
15
|
+
* escapes. The ONE sandbox for every backend-supplied path — `file_write`,
|
|
16
|
+
* `file_delete` and `writeWorkspaceFiles` all route through it.
|
|
17
|
+
*
|
|
18
|
+
* Containment is checked on path *segments* (via `path.relative`), not by
|
|
19
|
+
* substring. The old `p.includes('..') || p.startsWith('/')` copies in the two
|
|
20
|
+
* handlers rejected innocent names like `notes..v2.md` and missed Windows
|
|
21
|
+
* absolute forms such as `C:\x` and `\\server\share`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveInsideWorkspace(workspaceDir: string, relPath: string): string | null;
|
package/dist/workspace.js
CHANGED
|
@@ -16,4 +16,24 @@ export function getWorkspaceDir() {
|
|
|
16
16
|
return config.workspaceDir;
|
|
17
17
|
return path.join(config.stateDir, 'workspace');
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolve a backend-supplied relative path inside `workspaceDir`, or null if it
|
|
21
|
+
* escapes. The ONE sandbox for every backend-supplied path — `file_write`,
|
|
22
|
+
* `file_delete` and `writeWorkspaceFiles` all route through it.
|
|
23
|
+
*
|
|
24
|
+
* Containment is checked on path *segments* (via `path.relative`), not by
|
|
25
|
+
* substring. The old `p.includes('..') || p.startsWith('/')` copies in the two
|
|
26
|
+
* handlers rejected innocent names like `notes..v2.md` and missed Windows
|
|
27
|
+
* absolute forms such as `C:\x` and `\\server\share`.
|
|
28
|
+
*/
|
|
29
|
+
export function resolveInsideWorkspace(workspaceDir, relPath) {
|
|
30
|
+
if (!relPath || path.isAbsolute(relPath) || path.win32.isAbsolute(relPath))
|
|
31
|
+
return null;
|
|
32
|
+
const fullPath = path.resolve(workspaceDir, relPath);
|
|
33
|
+
const rel = path.relative(workspaceDir, fullPath);
|
|
34
|
+
if (rel === '' || rel === '..' || rel.startsWith('..' + path.sep) || path.isAbsolute(rel)) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return fullPath;
|
|
38
|
+
}
|
|
19
39
|
//# sourceMappingURL=workspace.js.map
|
package/dist/workspace.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace.js","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe;IAC7B,IAAI,MAAM,CAAC,YAAY;QAAE,OAAO,MAAM,CAAC,YAAY,CAAC;IACpD,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;AACjD,CAAC"}
|
|
1
|
+
{"version":3,"file":"workspace.js","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe;IAC7B,IAAI,MAAM,CAAC,YAAY;QAAE,OAAO,MAAM,CAAC,YAAY,CAAC;IACpD,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;AACjD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,sBAAsB,CAAC,YAAoB,EAAE,OAAe;IAC1E,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IACxF,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IACrD,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;IAClD,IAAI,GAAG,KAAK,EAAE,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1F,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw-cloud/agent-controller",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.20",
|
|
4
|
+
"packageManager": "pnpm@11.5.2",
|
|
4
5
|
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=22"
|
|
8
|
+
},
|
|
5
9
|
"main": "dist/index.js",
|
|
6
10
|
"bin": {
|
|
7
11
|
"agent-controller": "bin/agent-controller.js"
|
|
8
12
|
},
|
|
9
13
|
"scripts": {
|
|
10
14
|
"build": "tsc",
|
|
11
|
-
"prepublishOnly": "
|
|
12
|
-
"test": "
|
|
15
|
+
"prepublishOnly": "pnpm run build",
|
|
16
|
+
"test": "NODE_OPTIONS=--experimental-vm-modules jest --forceExit",
|
|
13
17
|
"start": "node bin/agent-controller.js",
|
|
14
|
-
"prepare": "[ -
|
|
18
|
+
"prepare": "[ -e .git ] && husky || true",
|
|
15
19
|
"lint": "eslint --fix ./src"
|
|
16
20
|
},
|
|
17
21
|
"files": [
|
|
@@ -25,6 +29,7 @@
|
|
|
25
29
|
"zod": "^4.3.6"
|
|
26
30
|
},
|
|
27
31
|
"devDependencies": {
|
|
32
|
+
"@jest/globals": "^30.0.0",
|
|
28
33
|
"@semantic-release/git": "^10.0.1",
|
|
29
34
|
"@semantic-release/npm": "^13.1.5",
|
|
30
35
|
"@types/jest": "^30.0.0",
|