jishushell 0.4.24 → 0.4.30
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/INSTALL-NOTICE +11 -0
- package/apps/browserless-chromium-container.yaml +78 -0
- package/apps/hermes-container.yaml +36 -2
- package/apps/ollama-binary.yaml +91 -90
- package/apps/ollama-cpu-container.yaml +8 -1
- package/apps/ollama-with-hollama-binary.yaml +91 -90
- package/apps/openclaw-binary.yaml +30 -1
- package/apps/openclaw-container.yaml +37 -2
- package/apps/openclaw-with-ollama-container.yaml +11 -2
- package/apps/openclaw-with-searxng-container.yaml +22 -2
- package/apps/openwebui-container.yaml +45 -1
- package/apps/playwright-container.yaml +7 -1
- package/apps/searxng-container.yaml +54 -4
- package/dist/cli/app.js +79 -9
- package/dist/cli/app.js.map +1 -1
- package/dist/cli/doctor.d.ts +12 -12
- package/dist/cli/doctor.js +242 -55
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/llm.d.ts +4 -3
- package/dist/cli/llm.js +4 -3
- package/dist/cli/llm.js.map +1 -1
- package/dist/cli/panel.d.ts +6 -5
- package/dist/cli/panel.js +10 -9
- package/dist/cli/panel.js.map +1 -1
- package/dist/control.d.ts +7 -6
- package/dist/control.js +7 -6
- package/dist/control.js.map +1 -1
- package/dist/routes/agent-apps.d.ts +1 -1
- package/dist/routes/agent-apps.js +1 -1
- package/dist/routes/apps.js +44 -11
- package/dist/routes/apps.js.map +1 -1
- package/dist/routes/auth.js +3 -0
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/instances.js +787 -16
- package/dist/routes/instances.js.map +1 -1
- package/dist/routes/llm.js +24 -35
- package/dist/routes/llm.js.map +1 -1
- package/dist/routes/setup.js +1 -1
- package/dist/routes/setup.js.map +1 -1
- package/dist/server.d.ts +9 -0
- package/dist/server.js +410 -17
- package/dist/server.js.map +1 -1
- package/dist/services/agent-apps/catalog.js +4 -3
- package/dist/services/agent-apps/catalog.js.map +1 -1
- package/dist/services/agent-apps/index.d.ts +1 -1
- package/dist/services/agent-apps/index.js +1 -1
- package/dist/services/agent-apps/installers/adapter.d.ts +1 -1
- package/dist/services/agent-apps/installers/adapter.js +1 -1
- package/dist/services/agent-apps/installers/shell-script.d.ts +1 -1
- package/dist/services/agent-apps/installers/shell-script.js +3 -3
- package/dist/services/agent-apps/installers/shell-script.js.map +1 -1
- package/dist/services/agent-apps/types.d.ts +2 -2
- package/dist/services/agent-apps/types.js +1 -1
- package/dist/services/app/app-manager.d.ts +24 -1
- package/dist/services/app/app-manager.js +664 -116
- package/dist/services/app/app-manager.js.map +1 -1
- package/dist/services/app/hermes-agent-manager.js +6 -4
- package/dist/services/app/hermes-agent-manager.js.map +1 -1
- package/dist/services/app/provide-resolver.d.ts +29 -0
- package/dist/services/app/provide-resolver.js +112 -0
- package/dist/services/app/provide-resolver.js.map +1 -0
- package/dist/services/capability-endpoint-validator.d.ts +41 -0
- package/dist/services/capability-endpoint-validator.js +104 -0
- package/dist/services/capability-endpoint-validator.js.map +1 -0
- package/dist/services/capability-health.d.ts +16 -0
- package/dist/services/capability-health.js +121 -0
- package/dist/services/capability-health.js.map +1 -0
- package/dist/services/capability-registry.d.ts +106 -0
- package/dist/services/capability-registry.js +313 -0
- package/dist/services/capability-registry.js.map +1 -0
- package/dist/services/connection-apply.d.ts +89 -0
- package/dist/services/connection-apply.js +421 -0
- package/dist/services/connection-apply.js.map +1 -0
- package/dist/services/connection-resolver.d.ts +65 -0
- package/dist/services/connection-resolver.js +281 -0
- package/dist/services/connection-resolver.js.map +1 -0
- package/dist/services/connection-transactor.d.ts +37 -0
- package/dist/services/connection-transactor.js +341 -0
- package/dist/services/connection-transactor.js.map +1 -0
- package/dist/services/instance-manager.d.ts +13 -0
- package/dist/services/instance-manager.js +137 -23
- package/dist/services/instance-manager.js.map +1 -1
- package/dist/services/llm-proxy/index.d.ts +16 -2
- package/dist/services/llm-proxy/index.js +48 -44
- package/dist/services/llm-proxy/index.js.map +1 -1
- package/dist/services/llm-proxy/probe.d.ts +6 -0
- package/dist/services/llm-proxy/probe.js +85 -0
- package/dist/services/llm-proxy/probe.js.map +1 -0
- package/dist/services/llm-proxy/ssrf.d.ts +1 -0
- package/dist/services/llm-proxy/ssrf.js +18 -7
- package/dist/services/llm-proxy/ssrf.js.map +1 -1
- package/dist/services/nomad-manager.js +375 -16
- package/dist/services/nomad-manager.js.map +1 -1
- package/dist/services/process-manager.js +1 -1
- package/dist/services/process-manager.js.map +1 -1
- package/dist/services/runtime/adapters/hermes.d.ts +30 -1
- package/dist/services/runtime/adapters/hermes.js +218 -5
- package/dist/services/runtime/adapters/hermes.js.map +1 -1
- package/dist/services/runtime/adapters/openclaw-mcporter.d.ts +45 -0
- package/dist/services/runtime/adapters/openclaw-mcporter.js +108 -0
- package/dist/services/runtime/adapters/openclaw-mcporter.js.map +1 -0
- package/dist/services/runtime/adapters/openclaw.d.ts +87 -0
- package/dist/services/runtime/adapters/openclaw.js +250 -2
- package/dist/services/runtime/adapters/openclaw.js.map +1 -1
- package/dist/services/runtime/mcp-shims/firewall.d.ts +26 -0
- package/dist/services/runtime/mcp-shims/firewall.js +129 -0
- package/dist/services/runtime/mcp-shims/firewall.js.map +1 -0
- package/dist/services/runtime/mcp-shims/searxng-shim.d.ts +27 -0
- package/dist/services/runtime/mcp-shims/searxng-shim.js +125 -0
- package/dist/services/runtime/mcp-shims/searxng-shim.js.map +1 -0
- package/dist/services/runtime/mcp-shims/write-mcp-entry.d.ts +83 -0
- package/dist/services/runtime/mcp-shims/write-mcp-entry.js +127 -0
- package/dist/services/runtime/mcp-shims/write-mcp-entry.js.map +1 -0
- package/dist/services/runtime/migrations.d.ts +8 -0
- package/dist/services/runtime/migrations.js +100 -0
- package/dist/services/runtime/migrations.js.map +1 -1
- package/dist/services/runtime/types.d.ts +15 -0
- package/dist/services/setup-manager.js +6 -6
- package/dist/services/setup-manager.js.map +1 -1
- package/dist/services/suggestions.d.ts +27 -0
- package/dist/services/suggestions.js +133 -0
- package/dist/services/suggestions.js.map +1 -0
- package/dist/services/task-registry.js +4 -2
- package/dist/services/task-registry.js.map +1 -1
- package/dist/services/telemetry/device-fingerprint.d.ts +1 -1
- package/dist/services/telemetry/device-fingerprint.js +1 -1
- package/dist/services/types-shim.d.ts +16 -0
- package/dist/services/types-shim.js +2 -0
- package/dist/services/types-shim.js.map +1 -0
- package/dist/types.d.ts +171 -1
- package/dist/utils/instance-lock.d.ts +22 -0
- package/dist/utils/instance-lock.js +48 -0
- package/dist/utils/instance-lock.js.map +1 -0
- package/dist/utils/safe-json.js +55 -22
- package/dist/utils/safe-json.js.map +1 -1
- package/install/jishu-install.sh +323 -27
- package/install/jishu-uninstall.sh +353 -20
- package/package.json +3 -1
- package/public/assets/Dashboard-rkWp-CXd.js +1 -0
- package/public/assets/{HermesChatPanel-mFSureyc.js → HermesChatPanel-_GHoklgo.js} +1 -1
- package/public/assets/HermesConfigForm-anDnwUp_.js +4 -0
- package/public/assets/{InitPassword-CVA8wQA6.js → InitPassword-ZU9_-hDr.js} +1 -1
- package/public/assets/InstanceDetail-CN0FH1aw.js +92 -0
- package/public/assets/{Login-BWsZH2mu.js → Login-BItXqYAJ.js} +1 -1
- package/public/assets/NewInstance-BousE6kY.js +1 -0
- package/public/assets/ProviderRecommendations-DFYj7Fb6.js +1 -0
- package/public/assets/Settings-Bttc6QmM.js +1 -0
- package/public/assets/Setup-Bsxx1zgj.js +1 -0
- package/public/assets/{WeixinLoginPanel-CnjR8xMu.js → WeixinLoginPanel-DPZpAKgO.js} +2 -2
- package/public/assets/index-8xZy1z5k.css +1 -0
- package/public/assets/index-Dw3HhUYE.js +19 -0
- package/public/assets/providers-DtNXh9JD.js +1 -0
- package/public/assets/registry-5s2UB6is.js +2 -0
- package/public/index.html +2 -2
- package/scripts/check-app-spec.mjs +443 -0
- package/scripts/check-i18n.mjs +154 -0
- package/scripts/run.sh +4 -4
- package/public/assets/Dashboard-B-JoOjBQ.js +0 -1
- package/public/assets/HermesConfigForm-DvR05LK1.js +0 -4
- package/public/assets/InstanceDetail-DcZW2QGO.js +0 -91
- package/public/assets/NewInstance-BCIrAd86.js +0 -1
- package/public/assets/Settings-xkDcduFz.js +0 -1
- package/public/assets/Setup-Cfuwj4gV.js +0 -1
- package/public/assets/index-CPhVFEsx.css +0 -1
- package/public/assets/index-DQsM6Joa.js +0 -19
- package/public/assets/providers-V-vwrExZ.js +0 -1
- package/public/assets/registry-B4UFJdpA.js +0 -2
package/dist/types.d.ts
CHANGED
|
@@ -26,6 +26,12 @@ export interface ServiceResult {
|
|
|
26
26
|
eval_id?: string;
|
|
27
27
|
/** Port allocation metadata from adapters (process-manager / nomad-manager). */
|
|
28
28
|
port_allocation?: unknown;
|
|
29
|
+
/**
|
|
30
|
+
* Stable error code for callers that prefer machine-readable tags over
|
|
31
|
+
* regex-matching localized error messages. Populated by PR 3
|
|
32
|
+
* resolveConnections (`MISSING_REQUIRED_CONNECTION` / `AMBIGUOUS_CONNECTION` / etc).
|
|
33
|
+
*/
|
|
34
|
+
code?: string;
|
|
29
35
|
}
|
|
30
36
|
export interface ExecResult {
|
|
31
37
|
stdout: string;
|
|
@@ -157,6 +163,16 @@ export interface AppTask {
|
|
|
157
163
|
after?: string[];
|
|
158
164
|
/** Volumes: string format "src:dest[:ro]" or object { source, target, readonly? } */
|
|
159
165
|
volumes?: Array<string | AppTaskVolume>;
|
|
166
|
+
/**
|
|
167
|
+
* Container task user spec. Format: "uid:gid" (numeric) or "uid". Special
|
|
168
|
+
* value "host" resolves to the panel process's `process.getuid()`:`getgid()`
|
|
169
|
+
* at job-build time — keeps bind-mounted data dirs writable by the panel
|
|
170
|
+
* user (typically `pi`) without forcing the container to run as root and
|
|
171
|
+
* without needing `chown`/`CAP_DAC_OVERRIDE` gymnastics. Mirrors how the
|
|
172
|
+
* hermes / openclaw adapters set `User`. When omitted on container tasks
|
|
173
|
+
* the unified builder falls back to "host" by default.
|
|
174
|
+
*/
|
|
175
|
+
user?: string;
|
|
160
176
|
}
|
|
161
177
|
export interface AppTerminalCommandPreset {
|
|
162
178
|
cmd: string;
|
|
@@ -173,25 +189,165 @@ export interface AppTerminalConfig {
|
|
|
173
189
|
timeout_ms?: number;
|
|
174
190
|
commands?: Record<string, Array<string | AppTerminalCommandPreset>>;
|
|
175
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* Health-probe spec attached to a provide. When omitted, the prober derives
|
|
194
|
+
* a default strategy from `protocol` (§5.6: http→HEAD, ws/tcp→connect,
|
|
195
|
+
* mcp→initialize, terminal→no-op).
|
|
196
|
+
*/
|
|
197
|
+
export interface ProvideHealthSpec {
|
|
198
|
+
kind?: "http" | "tcp" | "mcp" | "none";
|
|
199
|
+
/** HTTP path to probe (defaults to "/"). Ignored for non-http kinds. */
|
|
200
|
+
path?: string;
|
|
201
|
+
/** Custom port to probe; defaults to the provide's resolved hostPort. */
|
|
202
|
+
port?: number;
|
|
203
|
+
}
|
|
176
204
|
export interface AppProvide {
|
|
177
205
|
capability: string;
|
|
206
|
+
/** Task that exposes this capability — required for resolveProvideEndpoint
|
|
207
|
+
* (§5.5) to pick the right port when an app has multiple service tasks. */
|
|
208
|
+
task?: string;
|
|
209
|
+
/** Optional port-name disambiguator when multiple ports share a number. */
|
|
210
|
+
portName?: string;
|
|
178
211
|
port?: number;
|
|
179
212
|
path?: string;
|
|
180
213
|
url?: string;
|
|
181
|
-
protocol?: "http" | "https" | "tcp" | "udp" | (string & {});
|
|
214
|
+
protocol?: "http" | "https" | "tcp" | "udp" | "ws" | "wss" | "sse" | "mcp" | "terminal" | (string & {});
|
|
182
215
|
visibility?: string;
|
|
183
216
|
description?: string;
|
|
184
217
|
terminal?: AppTerminalConfig;
|
|
218
|
+
/** Optional explicit health probe; defaults derived from `protocol` (§5.6). */
|
|
219
|
+
health?: ProvideHealthSpec;
|
|
220
|
+
/**
|
|
221
|
+
* §17 (PR 8) — canonical MCP tool surface. Filled when this capability
|
|
222
|
+
* is meant to be re-exposed to an LLM agent through an MCP server. The
|
|
223
|
+
* jishushell MCP firewall uses these fields verbatim — upstream
|
|
224
|
+
* `tools/list` descriptions are discarded — to prevent third-party
|
|
225
|
+
* packages from prompt-injecting the LLM via their own tool wording.
|
|
226
|
+
* When absent, adapters fall back to the legacy per-capability path
|
|
227
|
+
* (PR 7 in-tree shim or direct `npx <pkg>` wiring).
|
|
228
|
+
*/
|
|
229
|
+
tool_schema?: ToolSchema;
|
|
230
|
+
/**
|
|
231
|
+
* §6 — describes how a consumer that binds this `provides[]` capability
|
|
232
|
+
* should authenticate when calling the endpoint. Optional. Absent = no auth.
|
|
233
|
+
*/
|
|
234
|
+
auth?: AppProvideAuth;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* §6 — describes how a consumer that binds this `provides[]` capability
|
|
238
|
+
* should authenticate when calling the endpoint. Optional. Absent = no auth.
|
|
239
|
+
*
|
|
240
|
+
* `tokenSource` is a small DSL the apply hook resolves at apply time:
|
|
241
|
+
* "instance.env.<NAME>" — read from the provider instance's runtime env
|
|
242
|
+
* "instance.config.<path>" — read from the provider's openclaw.json (json-pointer)
|
|
243
|
+
* "proxy.token" — read from the embedded llm-proxy's per-instance token
|
|
244
|
+
*
|
|
245
|
+
* Validation rules (PR A): `tokenSource` is required when kind !== "none".
|
|
246
|
+
* At the type level we keep it optional because the validator catches violations.
|
|
247
|
+
*/
|
|
248
|
+
export interface AppProvideAuth {
|
|
249
|
+
kind: "none" | "bearer" | "header" | "query";
|
|
250
|
+
tokenSource?: string;
|
|
251
|
+
headerName?: string;
|
|
252
|
+
tokenPrefix?: string;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* §17.3.1 — what the LLM sees for an MCP-class capability.
|
|
256
|
+
*
|
|
257
|
+
* `name` / `description` / `parameters` are forwarded verbatim by the
|
|
258
|
+
* MCP firewall's `tools/list` response, replacing whatever the upstream
|
|
259
|
+
* package would have advertised. `upstream` tells the firewall how to
|
|
260
|
+
* spawn the actual implementation.
|
|
261
|
+
*/
|
|
262
|
+
export interface ToolSchema {
|
|
263
|
+
/** MCP tool name. Must match `^[a-z][a-z0-9_]*$`. */
|
|
264
|
+
name: string;
|
|
265
|
+
/**
|
|
266
|
+
* Human-language description shown to the LLM. Keep minimal —
|
|
267
|
+
* "news / recent events / today" style suggestions invite query
|
|
268
|
+
* rewriting in some models (see §17.1).
|
|
269
|
+
*/
|
|
270
|
+
description: string;
|
|
271
|
+
/**
|
|
272
|
+
* MCP `inputSchema`. Stored under the `parameters` key in yaml for
|
|
273
|
+
* brevity; the firewall publishes it as `inputSchema` on the wire.
|
|
274
|
+
*/
|
|
275
|
+
parameters: {
|
|
276
|
+
type: "object";
|
|
277
|
+
properties: Record<string, Record<string, any>>;
|
|
278
|
+
required?: string[];
|
|
279
|
+
};
|
|
280
|
+
/**
|
|
281
|
+
* How the firewall spawns the actual upstream MCP server. Args are
|
|
282
|
+
* passed through unchanged. `env_template` values may reference
|
|
283
|
+
* `${SLOT}` placeholders that the writeMcpEntry helper resolves
|
|
284
|
+
* against the connection-resolver's env output before serializing
|
|
285
|
+
* into firewall config.
|
|
286
|
+
*/
|
|
287
|
+
upstream: {
|
|
288
|
+
command: string;
|
|
289
|
+
args?: string[];
|
|
290
|
+
env_template?: Record<string, string>;
|
|
291
|
+
};
|
|
292
|
+
/**
|
|
293
|
+
* When true (default), wrap each `tools/call` result in
|
|
294
|
+
* `{untrusted: true, source: <capability_id>, content: ...}` so the
|
|
295
|
+
* LLM treats output as untrusted data, not instructions. Mirrors
|
|
296
|
+
* OpenClaw's `wrapWebContent` defense.
|
|
297
|
+
*/
|
|
298
|
+
wrap_outputs?: boolean;
|
|
185
299
|
}
|
|
186
300
|
export interface AppRequire {
|
|
187
301
|
capability: string;
|
|
188
302
|
inject_as: string;
|
|
189
303
|
required?: boolean;
|
|
304
|
+
/**
|
|
305
|
+
* Multi-candidate semantics. `"one"` (default) lets the user pick exactly
|
|
306
|
+
* one provider; `"many"` allows multiple providers (e.g. several MCP
|
|
307
|
+
* servers merged together).
|
|
308
|
+
*/
|
|
309
|
+
cardinality?: "one" | "many";
|
|
310
|
+
/**
|
|
311
|
+
* LLM apply mode (§7.1.2). Only meaningful when `capability` resolves to
|
|
312
|
+
* the `llm` category. `"proxy-upstream"` writes the consumer instance's
|
|
313
|
+
* `x-jishushell.proxy.upstream` (OpenClaw / Hermes default); `"openai-env"`
|
|
314
|
+
* injects `OPENAI_API_BASE_URL` / `OPENAI_API_KEY` directly into runtime
|
|
315
|
+
* env (Open WebUI / generic OpenAI clients). Omit to fall back to the
|
|
316
|
+
* adapter-type default.
|
|
317
|
+
*/
|
|
318
|
+
apply?: "proxy-upstream" | "openai-env";
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Persisted binding state. Stored under `instance.json.connections[slot]`.
|
|
322
|
+
* `null` denotes user-explicit disconnect (do not auto-fall-back).
|
|
323
|
+
* Missing slot = "never set" (legacy single-candidate fallback may apply).
|
|
324
|
+
*/
|
|
325
|
+
export interface InstanceConnectionSingle {
|
|
326
|
+
kind: "single";
|
|
327
|
+
providerId: string;
|
|
328
|
+
capability: string;
|
|
329
|
+
/** LLM-only: which model the user picked. */
|
|
330
|
+
selectedModelId?: string;
|
|
331
|
+
}
|
|
332
|
+
export interface InstanceConnectionMany {
|
|
333
|
+
kind: "many";
|
|
334
|
+
providers: Array<{
|
|
335
|
+
providerId: string;
|
|
336
|
+
capability: string;
|
|
337
|
+
}>;
|
|
338
|
+
}
|
|
339
|
+
export type InstanceConnection = InstanceConnectionSingle | InstanceConnectionMany | null;
|
|
340
|
+
export type InstanceConnections = Record<string, InstanceConnection>;
|
|
341
|
+
export interface DismissedSuggestion {
|
|
342
|
+
slot: string;
|
|
343
|
+
until: string;
|
|
190
344
|
}
|
|
191
345
|
export type AppLifecycleStep = {
|
|
192
346
|
run: string;
|
|
193
347
|
timeout_ms?: number;
|
|
194
348
|
successIfCommandExists?: string;
|
|
349
|
+
sudo?: boolean;
|
|
350
|
+
ifFileExists?: string;
|
|
195
351
|
} | {
|
|
196
352
|
downloadImage: string;
|
|
197
353
|
timeout_ms?: number;
|
|
@@ -207,6 +363,12 @@ export type AppLifecycleStep = {
|
|
|
207
363
|
deleteBinary: string;
|
|
208
364
|
} | {
|
|
209
365
|
mkdir: string;
|
|
366
|
+
} | {
|
|
367
|
+
chown: {
|
|
368
|
+
path: string;
|
|
369
|
+
owner: string;
|
|
370
|
+
recursive?: boolean;
|
|
371
|
+
};
|
|
210
372
|
} | {
|
|
211
373
|
deleteDir: string;
|
|
212
374
|
} | {
|
|
@@ -217,6 +379,14 @@ export interface AppLifecycle {
|
|
|
217
379
|
pre_install?: AppLifecycleStep[];
|
|
218
380
|
/** Steps run during `app install`, before the job is created. */
|
|
219
381
|
install?: AppLifecycleStep[];
|
|
382
|
+
/**
|
|
383
|
+
* Steps run on every app start, immediately before the Nomad job is
|
|
384
|
+
* submitted. Use for idempotent invariants the runtime depends on but
|
|
385
|
+
* that may drift between starts (e.g. chowning a bind-mount to match
|
|
386
|
+
* the container's runtime user when `cap_drop: ALL` strips
|
|
387
|
+
* CAP_DAC_OVERRIDE).
|
|
388
|
+
*/
|
|
389
|
+
pre_start?: AppLifecycleStep[];
|
|
220
390
|
/** Steps run during `app uninstall`, before files are removed. */
|
|
221
391
|
uninstall?: AppLifecycleStep[];
|
|
222
392
|
/** Steps run after the Nomad job transitions to `running`. */
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-instance promise-chain mutex for serializing operations that mutate
|
|
3
|
+
* a single instance's durable state across multiple files (instance.json,
|
|
4
|
+
* provider.env, openclaw.json, mcporter.json, …).
|
|
5
|
+
*
|
|
6
|
+
* Mirrors the existing pattern in `llm-proxy/index.ts:_configSaveLocks`:
|
|
7
|
+
* each `withInstanceLock(id, fn)` call chains onto whatever promise was
|
|
8
|
+
* last queued for that id, so concurrent callers see strict FIFO ordering.
|
|
9
|
+
* The map self-cleans entries when the chain settles to keep memory flat.
|
|
10
|
+
*
|
|
11
|
+
* Use this for any code path that performs more than one file write on a
|
|
12
|
+
* given instance id (PUT /connections transactor, startApp, stopApp,
|
|
13
|
+
* restartApp). Operations on different instance ids never block each other.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Run `fn` exclusively against `instanceId`. Returns `fn`'s value (or
|
|
17
|
+
* propagates its rejection). Pending callers form a FIFO chain — each
|
|
18
|
+
* waits for prior ones to settle, even if they reject.
|
|
19
|
+
*/
|
|
20
|
+
export declare function withInstanceLock<T>(instanceId: string, fn: () => Promise<T>): Promise<T>;
|
|
21
|
+
/** Test-only helper for verifying the chain is empty between cases. */
|
|
22
|
+
export declare function __instanceLockSize(): number;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-instance promise-chain mutex for serializing operations that mutate
|
|
3
|
+
* a single instance's durable state across multiple files (instance.json,
|
|
4
|
+
* provider.env, openclaw.json, mcporter.json, …).
|
|
5
|
+
*
|
|
6
|
+
* Mirrors the existing pattern in `llm-proxy/index.ts:_configSaveLocks`:
|
|
7
|
+
* each `withInstanceLock(id, fn)` call chains onto whatever promise was
|
|
8
|
+
* last queued for that id, so concurrent callers see strict FIFO ordering.
|
|
9
|
+
* The map self-cleans entries when the chain settles to keep memory flat.
|
|
10
|
+
*
|
|
11
|
+
* Use this for any code path that performs more than one file write on a
|
|
12
|
+
* given instance id (PUT /connections transactor, startApp, stopApp,
|
|
13
|
+
* restartApp). Operations on different instance ids never block each other.
|
|
14
|
+
*/
|
|
15
|
+
const _instanceLocks = new Map();
|
|
16
|
+
/**
|
|
17
|
+
* Run `fn` exclusively against `instanceId`. Returns `fn`'s value (or
|
|
18
|
+
* propagates its rejection). Pending callers form a FIFO chain — each
|
|
19
|
+
* waits for prior ones to settle, even if they reject.
|
|
20
|
+
*/
|
|
21
|
+
export async function withInstanceLock(instanceId, fn) {
|
|
22
|
+
const prev = _instanceLocks.get(instanceId) ?? Promise.resolve();
|
|
23
|
+
// Swallow prior rejections so the chain doesn't break — each caller still
|
|
24
|
+
// gets its own promise's result. The map only tracks ordering, not health.
|
|
25
|
+
const current = prev.catch(() => undefined).then(() => fn());
|
|
26
|
+
// Identity check uses the same reference we store; .finally() returns a
|
|
27
|
+
// new promise, so we must store `current` and attach the cleanup as a
|
|
28
|
+
// side-effect, not store the .finally() result. The trailing .catch()
|
|
29
|
+
// swallows the cleanup-promise's rejection — without it Node logs an
|
|
30
|
+
// "unhandled rejection" each time `fn()` throws even though the caller
|
|
31
|
+
// already handled it via the returned `current`.
|
|
32
|
+
current
|
|
33
|
+
.finally(() => {
|
|
34
|
+
if (_instanceLocks.get(instanceId) === current) {
|
|
35
|
+
_instanceLocks.delete(instanceId);
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
.catch(() => {
|
|
39
|
+
// intentional no-op — caller handles the original rejection
|
|
40
|
+
});
|
|
41
|
+
_instanceLocks.set(instanceId, current);
|
|
42
|
+
return current;
|
|
43
|
+
}
|
|
44
|
+
/** Test-only helper for verifying the chain is empty between cases. */
|
|
45
|
+
export function __instanceLockSize() {
|
|
46
|
+
return _instanceLocks.size;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=instance-lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instance-lock.js","sourceRoot":"","sources":["../../src/utils/instance-lock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,cAAc,GAAG,IAAI,GAAG,EAA4B,CAAC;AAE3D;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,UAAkB,EAClB,EAAoB;IAEpB,MAAM,IAAI,GAAG,cAAc,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IACjE,0EAA0E;IAC1E,2EAA2E;IAC3E,MAAM,OAAO,GAAe,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;IACzE,wEAAwE;IACxE,sEAAsE;IACtE,sEAAsE;IACtE,qEAAqE;IACrE,uEAAuE;IACvE,iDAAiD;IACjD,OAAO;SACJ,OAAO,CAAC,GAAG,EAAE;QACZ,IAAI,cAAc,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,OAAO,EAAE,CAAC;YAC/C,cAAc,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QACpC,CAAC;IACH,CAAC,CAAC;SACD,KAAK,CAAC,GAAG,EAAE;QACV,4DAA4D;IAC9D,CAAC,CAAC,CAAC;IACL,cAAc,CAAC,GAAG,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IACxC,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,kBAAkB;IAChC,OAAO,cAAc,CAAC,IAAI,CAAC;AAC7B,CAAC"}
|
package/dist/utils/safe-json.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { chmodSync, existsSync, readFileSync, writeFileSync, renameSync, copyFileSync, openSync, fdatasyncSync, closeSync, mkdirSync } from "fs";
|
|
1
|
+
import { chmodSync, existsSync, readFileSync, writeFileSync, renameSync, copyFileSync, openSync, fdatasyncSync, closeSync, mkdirSync, rmSync } from "fs";
|
|
2
2
|
import { dirname } from "path";
|
|
3
3
|
const BACKUP_SUFFIXES = [".bak", ".bak.1", ".bak.2"];
|
|
4
4
|
function recoverJson(path, label, mode) {
|
|
@@ -48,34 +48,67 @@ function withWriteLock(path, fn) {
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
function atomicWriteJson(path, data, fsync, mode) {
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
const dir = dirname(path);
|
|
52
|
+
mkdirSync(dir, { recursive: true });
|
|
53
|
+
const tmp = `${path}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`;
|
|
53
54
|
const json = typeof data === "string" ? data : JSON.stringify(data);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
try {
|
|
56
|
+
writeFileSync(tmp, json, { mode });
|
|
57
|
+
chmodSync(tmp, mode);
|
|
58
|
+
JSON.parse(readFileSync(tmp, "utf-8"));
|
|
59
|
+
if (fsync) {
|
|
60
|
+
let fd = null;
|
|
61
|
+
try {
|
|
62
|
+
fd = openSync(tmp, "r");
|
|
63
|
+
fdatasyncSync(fd);
|
|
64
|
+
}
|
|
65
|
+
catch { }
|
|
66
|
+
finally {
|
|
67
|
+
if (fd !== null)
|
|
68
|
+
try {
|
|
69
|
+
closeSync(fd);
|
|
70
|
+
}
|
|
71
|
+
catch { }
|
|
72
|
+
}
|
|
62
73
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
74
|
+
if (existsSync(path)) {
|
|
75
|
+
try {
|
|
76
|
+
if (existsSync(path + ".bak.1"))
|
|
77
|
+
copyFileSync(path + ".bak.1", path + ".bak.2");
|
|
78
|
+
}
|
|
79
|
+
catch { }
|
|
80
|
+
try {
|
|
81
|
+
if (existsSync(path + ".bak"))
|
|
82
|
+
copyFileSync(path + ".bak", path + ".bak.1");
|
|
83
|
+
}
|
|
84
|
+
catch { }
|
|
85
|
+
copyFileSync(path, path + ".bak");
|
|
69
86
|
}
|
|
70
|
-
|
|
87
|
+
renameSync(tmp, path);
|
|
88
|
+
chmodSync(path, mode);
|
|
89
|
+
if (fsync) {
|
|
90
|
+
let dirFd = null;
|
|
91
|
+
try {
|
|
92
|
+
dirFd = openSync(dir, "r");
|
|
93
|
+
fdatasyncSync(dirFd);
|
|
94
|
+
}
|
|
95
|
+
catch { }
|
|
96
|
+
finally {
|
|
97
|
+
if (dirFd !== null)
|
|
98
|
+
try {
|
|
99
|
+
closeSync(dirFd);
|
|
100
|
+
}
|
|
101
|
+
catch { }
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
71
106
|
try {
|
|
72
|
-
|
|
73
|
-
copyFileSync(path + ".bak", path + ".bak.1");
|
|
107
|
+
rmSync(tmp, { force: true });
|
|
74
108
|
}
|
|
75
109
|
catch { }
|
|
76
|
-
|
|
110
|
+
throw err;
|
|
77
111
|
}
|
|
78
|
-
renameSync(tmp, path);
|
|
79
112
|
}
|
|
80
113
|
export function safeWriteJson(path, data, fsync = false) {
|
|
81
114
|
withWriteLock(path, () => atomicWriteJson(path, data, fsync, 0o644));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"safe-json.js","sourceRoot":"","sources":["../../src/utils/safe-json.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"safe-json.js","sourceRoot":"","sources":["../../src/utils/safe-json.ts"],"names":[],"mappings":"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"}
|