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.
Files changed (167) hide show
  1. package/INSTALL-NOTICE +11 -0
  2. package/apps/browserless-chromium-container.yaml +78 -0
  3. package/apps/hermes-container.yaml +36 -2
  4. package/apps/ollama-binary.yaml +91 -90
  5. package/apps/ollama-cpu-container.yaml +8 -1
  6. package/apps/ollama-with-hollama-binary.yaml +91 -90
  7. package/apps/openclaw-binary.yaml +30 -1
  8. package/apps/openclaw-container.yaml +37 -2
  9. package/apps/openclaw-with-ollama-container.yaml +11 -2
  10. package/apps/openclaw-with-searxng-container.yaml +22 -2
  11. package/apps/openwebui-container.yaml +45 -1
  12. package/apps/playwright-container.yaml +7 -1
  13. package/apps/searxng-container.yaml +54 -4
  14. package/dist/cli/app.js +79 -9
  15. package/dist/cli/app.js.map +1 -1
  16. package/dist/cli/doctor.d.ts +12 -12
  17. package/dist/cli/doctor.js +242 -55
  18. package/dist/cli/doctor.js.map +1 -1
  19. package/dist/cli/llm.d.ts +4 -3
  20. package/dist/cli/llm.js +4 -3
  21. package/dist/cli/llm.js.map +1 -1
  22. package/dist/cli/panel.d.ts +6 -5
  23. package/dist/cli/panel.js +10 -9
  24. package/dist/cli/panel.js.map +1 -1
  25. package/dist/control.d.ts +7 -6
  26. package/dist/control.js +7 -6
  27. package/dist/control.js.map +1 -1
  28. package/dist/routes/agent-apps.d.ts +1 -1
  29. package/dist/routes/agent-apps.js +1 -1
  30. package/dist/routes/apps.js +44 -11
  31. package/dist/routes/apps.js.map +1 -1
  32. package/dist/routes/auth.js +3 -0
  33. package/dist/routes/auth.js.map +1 -1
  34. package/dist/routes/instances.js +787 -16
  35. package/dist/routes/instances.js.map +1 -1
  36. package/dist/routes/llm.js +24 -35
  37. package/dist/routes/llm.js.map +1 -1
  38. package/dist/routes/setup.js +1 -1
  39. package/dist/routes/setup.js.map +1 -1
  40. package/dist/server.d.ts +9 -0
  41. package/dist/server.js +410 -17
  42. package/dist/server.js.map +1 -1
  43. package/dist/services/agent-apps/catalog.js +4 -3
  44. package/dist/services/agent-apps/catalog.js.map +1 -1
  45. package/dist/services/agent-apps/index.d.ts +1 -1
  46. package/dist/services/agent-apps/index.js +1 -1
  47. package/dist/services/agent-apps/installers/adapter.d.ts +1 -1
  48. package/dist/services/agent-apps/installers/adapter.js +1 -1
  49. package/dist/services/agent-apps/installers/shell-script.d.ts +1 -1
  50. package/dist/services/agent-apps/installers/shell-script.js +3 -3
  51. package/dist/services/agent-apps/installers/shell-script.js.map +1 -1
  52. package/dist/services/agent-apps/types.d.ts +2 -2
  53. package/dist/services/agent-apps/types.js +1 -1
  54. package/dist/services/app/app-manager.d.ts +24 -1
  55. package/dist/services/app/app-manager.js +664 -116
  56. package/dist/services/app/app-manager.js.map +1 -1
  57. package/dist/services/app/hermes-agent-manager.js +6 -4
  58. package/dist/services/app/hermes-agent-manager.js.map +1 -1
  59. package/dist/services/app/provide-resolver.d.ts +29 -0
  60. package/dist/services/app/provide-resolver.js +112 -0
  61. package/dist/services/app/provide-resolver.js.map +1 -0
  62. package/dist/services/capability-endpoint-validator.d.ts +41 -0
  63. package/dist/services/capability-endpoint-validator.js +104 -0
  64. package/dist/services/capability-endpoint-validator.js.map +1 -0
  65. package/dist/services/capability-health.d.ts +16 -0
  66. package/dist/services/capability-health.js +121 -0
  67. package/dist/services/capability-health.js.map +1 -0
  68. package/dist/services/capability-registry.d.ts +106 -0
  69. package/dist/services/capability-registry.js +313 -0
  70. package/dist/services/capability-registry.js.map +1 -0
  71. package/dist/services/connection-apply.d.ts +89 -0
  72. package/dist/services/connection-apply.js +421 -0
  73. package/dist/services/connection-apply.js.map +1 -0
  74. package/dist/services/connection-resolver.d.ts +65 -0
  75. package/dist/services/connection-resolver.js +281 -0
  76. package/dist/services/connection-resolver.js.map +1 -0
  77. package/dist/services/connection-transactor.d.ts +37 -0
  78. package/dist/services/connection-transactor.js +341 -0
  79. package/dist/services/connection-transactor.js.map +1 -0
  80. package/dist/services/instance-manager.d.ts +13 -0
  81. package/dist/services/instance-manager.js +137 -23
  82. package/dist/services/instance-manager.js.map +1 -1
  83. package/dist/services/llm-proxy/index.d.ts +16 -2
  84. package/dist/services/llm-proxy/index.js +48 -44
  85. package/dist/services/llm-proxy/index.js.map +1 -1
  86. package/dist/services/llm-proxy/probe.d.ts +6 -0
  87. package/dist/services/llm-proxy/probe.js +85 -0
  88. package/dist/services/llm-proxy/probe.js.map +1 -0
  89. package/dist/services/llm-proxy/ssrf.d.ts +1 -0
  90. package/dist/services/llm-proxy/ssrf.js +18 -7
  91. package/dist/services/llm-proxy/ssrf.js.map +1 -1
  92. package/dist/services/nomad-manager.js +375 -16
  93. package/dist/services/nomad-manager.js.map +1 -1
  94. package/dist/services/process-manager.js +1 -1
  95. package/dist/services/process-manager.js.map +1 -1
  96. package/dist/services/runtime/adapters/hermes.d.ts +30 -1
  97. package/dist/services/runtime/adapters/hermes.js +218 -5
  98. package/dist/services/runtime/adapters/hermes.js.map +1 -1
  99. package/dist/services/runtime/adapters/openclaw-mcporter.d.ts +45 -0
  100. package/dist/services/runtime/adapters/openclaw-mcporter.js +108 -0
  101. package/dist/services/runtime/adapters/openclaw-mcporter.js.map +1 -0
  102. package/dist/services/runtime/adapters/openclaw.d.ts +87 -0
  103. package/dist/services/runtime/adapters/openclaw.js +250 -2
  104. package/dist/services/runtime/adapters/openclaw.js.map +1 -1
  105. package/dist/services/runtime/mcp-shims/firewall.d.ts +26 -0
  106. package/dist/services/runtime/mcp-shims/firewall.js +129 -0
  107. package/dist/services/runtime/mcp-shims/firewall.js.map +1 -0
  108. package/dist/services/runtime/mcp-shims/searxng-shim.d.ts +27 -0
  109. package/dist/services/runtime/mcp-shims/searxng-shim.js +125 -0
  110. package/dist/services/runtime/mcp-shims/searxng-shim.js.map +1 -0
  111. package/dist/services/runtime/mcp-shims/write-mcp-entry.d.ts +83 -0
  112. package/dist/services/runtime/mcp-shims/write-mcp-entry.js +127 -0
  113. package/dist/services/runtime/mcp-shims/write-mcp-entry.js.map +1 -0
  114. package/dist/services/runtime/migrations.d.ts +8 -0
  115. package/dist/services/runtime/migrations.js +100 -0
  116. package/dist/services/runtime/migrations.js.map +1 -1
  117. package/dist/services/runtime/types.d.ts +15 -0
  118. package/dist/services/setup-manager.js +6 -6
  119. package/dist/services/setup-manager.js.map +1 -1
  120. package/dist/services/suggestions.d.ts +27 -0
  121. package/dist/services/suggestions.js +133 -0
  122. package/dist/services/suggestions.js.map +1 -0
  123. package/dist/services/task-registry.js +4 -2
  124. package/dist/services/task-registry.js.map +1 -1
  125. package/dist/services/telemetry/device-fingerprint.d.ts +1 -1
  126. package/dist/services/telemetry/device-fingerprint.js +1 -1
  127. package/dist/services/types-shim.d.ts +16 -0
  128. package/dist/services/types-shim.js +2 -0
  129. package/dist/services/types-shim.js.map +1 -0
  130. package/dist/types.d.ts +171 -1
  131. package/dist/utils/instance-lock.d.ts +22 -0
  132. package/dist/utils/instance-lock.js +48 -0
  133. package/dist/utils/instance-lock.js.map +1 -0
  134. package/dist/utils/safe-json.js +55 -22
  135. package/dist/utils/safe-json.js.map +1 -1
  136. package/install/jishu-install.sh +323 -27
  137. package/install/jishu-uninstall.sh +353 -20
  138. package/package.json +3 -1
  139. package/public/assets/Dashboard-rkWp-CXd.js +1 -0
  140. package/public/assets/{HermesChatPanel-mFSureyc.js → HermesChatPanel-_GHoklgo.js} +1 -1
  141. package/public/assets/HermesConfigForm-anDnwUp_.js +4 -0
  142. package/public/assets/{InitPassword-CVA8wQA6.js → InitPassword-ZU9_-hDr.js} +1 -1
  143. package/public/assets/InstanceDetail-CN0FH1aw.js +92 -0
  144. package/public/assets/{Login-BWsZH2mu.js → Login-BItXqYAJ.js} +1 -1
  145. package/public/assets/NewInstance-BousE6kY.js +1 -0
  146. package/public/assets/ProviderRecommendations-DFYj7Fb6.js +1 -0
  147. package/public/assets/Settings-Bttc6QmM.js +1 -0
  148. package/public/assets/Setup-Bsxx1zgj.js +1 -0
  149. package/public/assets/{WeixinLoginPanel-CnjR8xMu.js → WeixinLoginPanel-DPZpAKgO.js} +2 -2
  150. package/public/assets/index-8xZy1z5k.css +1 -0
  151. package/public/assets/index-Dw3HhUYE.js +19 -0
  152. package/public/assets/providers-DtNXh9JD.js +1 -0
  153. package/public/assets/registry-5s2UB6is.js +2 -0
  154. package/public/index.html +2 -2
  155. package/scripts/check-app-spec.mjs +443 -0
  156. package/scripts/check-i18n.mjs +154 -0
  157. package/scripts/run.sh +4 -4
  158. package/public/assets/Dashboard-B-JoOjBQ.js +0 -1
  159. package/public/assets/HermesConfigForm-DvR05LK1.js +0 -4
  160. package/public/assets/InstanceDetail-DcZW2QGO.js +0 -91
  161. package/public/assets/NewInstance-BCIrAd86.js +0 -1
  162. package/public/assets/Settings-xkDcduFz.js +0 -1
  163. package/public/assets/Setup-Cfuwj4gV.js +0 -1
  164. package/public/assets/index-CPhVFEsx.css +0 -1
  165. package/public/assets/index-DQsM6Joa.js +0 -19
  166. package/public/assets/providers-V-vwrExZ.js +0 -1
  167. package/public/assets/registry-B4UFJdpA.js +0 -2
@@ -0,0 +1,45 @@
1
+ export type McporterSource = {
2
+ kind: "user";
3
+ } | {
4
+ kind: "connection";
5
+ slot: string;
6
+ consumerInstanceId: string;
7
+ };
8
+ export interface McporterServerEntry {
9
+ command?: string;
10
+ args?: string[];
11
+ url?: string;
12
+ transport?: "sse" | "http" | "stdio";
13
+ env?: Record<string, string>;
14
+ __source?: McporterSource;
15
+ [key: string]: unknown;
16
+ }
17
+ interface McporterConfig {
18
+ mcpServers: Record<string, McporterServerEntry>;
19
+ imports?: any[];
20
+ [key: string]: unknown;
21
+ }
22
+ export declare function readMcporterConfig(instanceId: string): McporterConfig;
23
+ export declare function writeMcporterConfig(instanceId: string, cfg: McporterConfig): void;
24
+ /**
25
+ * Merge `servers` into the consumer's mcporter.json. Existing entries with
26
+ * the same name and matching `__source` get overwritten; entries with a
27
+ * different source (e.g. user-managed) are preserved untouched and the new
28
+ * entry's name is suffixed with a numeric tag (`-2`, `-3`, …) so the merge
29
+ * never silently overwrites user-managed servers.
30
+ *
31
+ * When user-managed and connection-managed entries collide on name, the
32
+ * conflict surfaces by having the connection-managed entry land under a
33
+ * suffixed key. Routes that later inspect the file can detect the
34
+ * suffix and surface a UI warning if needed.
35
+ */
36
+ export declare function mergeMcporterServers(instanceId: string, servers: Record<string, McporterServerEntry>): void;
37
+ /**
38
+ * Remove all entries whose `__source` matches the given filter. If
39
+ * filter.source is omitted, removes every connection-managed entry
40
+ * (everything except `kind: "user"` / no marker).
41
+ */
42
+ export declare function removeMcporterServers(instanceId: string, filter?: {
43
+ source?: McporterSource;
44
+ }): void;
45
+ export {};
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Mcporter helper — extracted from `openclaw-routes.ts` so connection
3
+ * apply hooks (PR 5) and the existing /mcporter/add route share a single
4
+ * read/merge/write code path.
5
+ *
6
+ * Connection-merged servers carry a `__source` object so we can later
7
+ * (un)bind precisely without touching user-managed entries:
8
+ *
9
+ * { __source: { kind: "connection", slot, consumerInstanceId } }
10
+ *
11
+ * `__source: { kind: "user" }` (or no marker) means "user-managed via
12
+ * Mcporter UI"; never touched by connection apply hooks.
13
+ */
14
+ import { existsSync, readFileSync } from "fs";
15
+ import { join } from "path";
16
+ import * as instanceManager from "../../instance-manager.js";
17
+ import { ensureDirHost } from "../../../utils/fs.js";
18
+ import { safeWriteJson } from "../../../utils/safe-json.js";
19
+ const PROTO_KEYS = new Set(["__proto__", "constructor", "prototype"]);
20
+ function mcporterPath(instanceId) {
21
+ const home = instanceManager.getOpenclawHome(instanceId);
22
+ return join(home, ".openclaw", "workspace", "config", "mcporter.json");
23
+ }
24
+ export function readMcporterConfig(instanceId) {
25
+ const path = mcporterPath(instanceId);
26
+ if (!existsSync(path))
27
+ return { mcpServers: {}, imports: [] };
28
+ try {
29
+ const raw = JSON.parse(readFileSync(path, "utf-8"));
30
+ if (!raw || typeof raw !== "object")
31
+ return { mcpServers: {}, imports: [] };
32
+ if (!raw.mcpServers || typeof raw.mcpServers !== "object") {
33
+ raw.mcpServers = {};
34
+ }
35
+ return raw;
36
+ }
37
+ catch {
38
+ return { mcpServers: {}, imports: [] };
39
+ }
40
+ }
41
+ export function writeMcporterConfig(instanceId, cfg) {
42
+ const path = mcporterPath(instanceId);
43
+ ensureDirHost(join(path, ".."));
44
+ safeWriteJson(path, cfg);
45
+ }
46
+ /**
47
+ * Merge `servers` into the consumer's mcporter.json. Existing entries with
48
+ * the same name and matching `__source` get overwritten; entries with a
49
+ * different source (e.g. user-managed) are preserved untouched and the new
50
+ * entry's name is suffixed with a numeric tag (`-2`, `-3`, …) so the merge
51
+ * never silently overwrites user-managed servers.
52
+ *
53
+ * When user-managed and connection-managed entries collide on name, the
54
+ * conflict surfaces by having the connection-managed entry land under a
55
+ * suffixed key. Routes that later inspect the file can detect the
56
+ * suffix and surface a UI warning if needed.
57
+ */
58
+ export function mergeMcporterServers(instanceId, servers) {
59
+ const cfg = readMcporterConfig(instanceId);
60
+ cfg.mcpServers = cfg.mcpServers ?? {};
61
+ for (const [rawName, entry] of Object.entries(servers)) {
62
+ if (PROTO_KEYS.has(rawName))
63
+ continue;
64
+ let name = rawName;
65
+ const existing = cfg.mcpServers[name];
66
+ if (existing && !sourceMatches(existing.__source, entry.__source)) {
67
+ // user-managed vs connection-managed collision — pick a suffixed key.
68
+ let i = 2;
69
+ while (cfg.mcpServers[`${rawName}-${i}`])
70
+ i++;
71
+ name = `${rawName}-${i}`;
72
+ }
73
+ cfg.mcpServers[name] = entry;
74
+ }
75
+ writeMcporterConfig(instanceId, cfg);
76
+ }
77
+ /**
78
+ * Remove all entries whose `__source` matches the given filter. If
79
+ * filter.source is omitted, removes every connection-managed entry
80
+ * (everything except `kind: "user"` / no marker).
81
+ */
82
+ export function removeMcporterServers(instanceId, filter = {}) {
83
+ const cfg = readMcporterConfig(instanceId);
84
+ if (!cfg.mcpServers)
85
+ return;
86
+ const keep = {};
87
+ for (const [name, entry] of Object.entries(cfg.mcpServers)) {
88
+ const src = entry.__source;
89
+ const matches = filter.source
90
+ ? sourceMatches(src, filter.source)
91
+ : src && src.kind === "connection";
92
+ if (!matches)
93
+ keep[name] = entry;
94
+ }
95
+ cfg.mcpServers = keep;
96
+ writeMcporterConfig(instanceId, cfg);
97
+ }
98
+ function sourceMatches(a, b) {
99
+ if (!a || !b)
100
+ return false;
101
+ if (a.kind !== b.kind)
102
+ return false;
103
+ if (a.kind === "connection" && b.kind === "connection") {
104
+ return a.slot === b.slot && a.consumerInstanceId === b.consumerInstanceId;
105
+ }
106
+ return true;
107
+ }
108
+ //# sourceMappingURL=openclaw-mcporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openclaw-mcporter.js","sourceRoot":"","sources":["../../../../src/services/runtime/adapters/openclaw-mcporter.ts"],"names":[],"mappings":"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"}
@@ -36,6 +36,63 @@
36
36
  import { type InstallResult } from "../../setup-manager.js";
37
37
  import type { AdapterHooks, AdapterManifest, AgentType, CapabilityProfile, ConfigDocument, CreateInstanceArgs, CreateInstanceContext, InstanceConfigMeta, InstancePaths, PairingApproveInput, RuntimeAdapter, RuntimeSpec, RuntimeVersionInfo } from "../types.js";
38
38
  export declare const OPENCLAW_DEFAULT_GATEWAY_PORT = 18789;
39
+ /**
40
+ * Ensure the `llm-agent` capability advertised by openclaw-*.yaml works for
41
+ * every instance, including ones created before this code shipped: enable
42
+ * OpenClaw's OpenAI-compatible endpoints in `openclaw.json` on every start.
43
+ *
44
+ * Runs idempotently next to `patchDockerBridgeGatewayBind`. The OpenAI
45
+ * routes (`/v1/chat/completions`, `/v1/responses`, `/v1/models`,
46
+ * `/v1/embeddings`) live in openclaw's bundled server.impl and are gated
47
+ * behind `gateway.http.endpoints.{chatCompletions,responses}.enabled` —
48
+ * default false, so a fresh OpenClaw install would 404 those paths even
49
+ * though OpenWebUI binds it through the connections page.
50
+ */
51
+ export declare function patchOpenAIEndpointsEnabled(configPath: string): void;
52
+ /**
53
+ * Deep-merge a SearXNG connection into an OpenClaw config file at `configPath`.
54
+ *
55
+ * Writes both halves of the wiring required for the `web_search` tool to use
56
+ * the registry-resolved SearXNG instance:
57
+ *
58
+ * plugins.entries.searxng.enabled = true
59
+ * plugins.entries.searxng.config.webSearch.baseUrl = baseUrl
60
+ * tools.web.search.provider = "searxng"
61
+ *
62
+ * The provider selector is required because OpenClaw's built-in default is
63
+ * `"brave"`; without it the tool fails with `missing_brave_api_key` even when
64
+ * the searxng plugin is otherwise correctly configured (verified on Pi
65
+ * 2026-04-29: claw11 had the plugin block right but the selector unset).
66
+ *
67
+ * Why a partial deep-merge instead of `saveNativeConfig`: the latter is
68
+ * destructive — it replaces top-level keys wholesale and only preserves a
69
+ * hardcoded subset (`plugins.installs`, `plugins.entries` per-key, partial
70
+ * `channels` merge). A partial patch through it would wipe `models.providers`
71
+ * / `agents.defaults` / etc., bricking the instance with `No API key found
72
+ * for provider "openai"` on the next chat. So we read, merge, write back.
73
+ *
74
+ * No-op when the config file is absent (instance not yet started).
75
+ */
76
+ export declare function applySearxngConnectionToConfig(configPath: string, baseUrl: string): void;
77
+ /**
78
+ * Counterpart to `applySearxngConnectionToConfig` — invoked when the user
79
+ * unbinds the SEARCH slot in the Connections tab. Without this, unbinding
80
+ * left the plugin entry enabled with a baseUrl pointing at the now-gone
81
+ * searxng provider, and `tools.web.search.provider="searxng"` kept routing
82
+ * the agent's `web_search` tool through that dead URL on every chat.
83
+ *
84
+ * Conservative cleanup so a future re-bind (or user-customized plugin
85
+ * settings) survives:
86
+ * - flip the searxng plugin to enabled:false (don't delete the entry —
87
+ * user may have hand-tuned it and we want re-bind to re-enable cheaply)
88
+ * - drop the stale `webSearch.baseUrl` so nothing reads it as still-live
89
+ * - clear `tools.web.search.provider` so the runtime falls back to its
90
+ * built-in default (which today errors out with `missing_brave_api_key`
91
+ * — the right outcome: search was unbound, web_search shouldn't work)
92
+ *
93
+ * No-op when the config file is absent.
94
+ */
95
+ export declare function clearSearxngConnectionFromConfig(configPath: string): void;
39
96
  declare class OpenClawAdapter implements RuntimeAdapter {
40
97
  readonly agentType: AgentType;
41
98
  readonly displayName = "OpenClaw";
@@ -95,6 +152,36 @@ declare class OpenClawAdapter implements RuntimeAdapter {
95
152
  isChannelPluginInstalled(instanceId: string, channelId: string): boolean;
96
153
  installChannelPlugin(instanceId: string, channelId: string): Promise<void>;
97
154
  saveNativeConfig(instanceId: string, config: Record<string, any>): boolean;
155
+ /**
156
+ * Connection-apply hook (§7 of app-interconnect-design): translate
157
+ * resolved `inject_as` env vars into OpenClaw-native plugin config and
158
+ * persist them into `openclaw.json`. Without this, the runtime env
159
+ * injected by `nomad-manager.injectConnectionsRuntimeEnv` (PR 8) sits
160
+ * in the container unused — the OpenClaw agent reads tool config from
161
+ * `openclaw.json`, not from environment variables.
162
+ *
163
+ * Currently wired:
164
+ * SEARCH_API_BASE_URL → plugins.entries.searxng.config.webSearch.baseUrl
165
+ * SEARCH_API_BASE_URL → tools.web.search.provider = "searxng"
166
+ *
167
+ * The provider selector at `tools.web.search.provider` is required: without
168
+ * it the `web_search` tool defaults to Brave and fails with
169
+ * `missing_brave_api_key` even when the searxng plugin is fully configured.
170
+ *
171
+ * Browser / LLM / MCP wiring lands in PR 9b — they need their own
172
+ * config-shape mapping (browser → tools.browser, llm → models.providers,
173
+ * mcp already wired via openclaw-mcporter).
174
+ *
175
+ * The write goes through `saveNativeConfig` which deep-merges with the
176
+ * existing on-disk config, so user-managed plugin entries (e.g.
177
+ * openclaw-lark) are preserved. Plugin auto-enable then promotes the
178
+ * searxng entry into `plugins.allow` automatically on next start
179
+ * because we set `enabled:true` and provide config (the openclaw
180
+ * runtime treats the presence of `config.webSearch.baseUrl` as a
181
+ * "configured" signal — see `plugin-auto-enable` in the openclaw
182
+ * dist bundle).
183
+ */
184
+ applyConnectionEnv(instanceId: string, env: Record<string, string>): Promise<void>;
98
185
  resolveBin(): string;
99
186
  resolveAgentHome(instanceId: string): string;
100
187
  /** Env vars OpenClaw's CLI needs when backup-manager runs it as a subprocess. */
@@ -265,6 +265,20 @@ function patchJsproxyBaseUrl(configPath) {
265
265
  * container loopback. Normalize default/loopback gateway binds to `lan` so
266
266
  * Nomad's published host port can reach the gateway.
267
267
  */
268
+ // Mirrors hermes.ts:nomadHasExternalHostNetwork. When the host nomad.hcl
269
+ // declares `host_network "external" { ... }`, the legacy openclaw job
270
+ // attaches it to its ReservedPorts so docker publishes the gateway to
271
+ // the LAN address instead of 127.0.0.1. Without this, OpenWebUI in a
272
+ // sibling container can't reach openclaw at all.
273
+ function nomadHasExternalHostNetwork() {
274
+ const path = join(JISHUSHELL_HOME, "nomad", "nomad.hcl");
275
+ try {
276
+ return /host_network\s+"external"\s*\{/.test(readFileSync(path, "utf-8"));
277
+ }
278
+ catch {
279
+ return false;
280
+ }
281
+ }
268
282
  function patchDockerBridgeGatewayBind(configPath) {
269
283
  try {
270
284
  const raw = readFileSync(configPath, "utf-8");
@@ -290,6 +304,137 @@ function patchDockerBridgeGatewayBind(configPath) {
290
304
  console.warn(`[openclaw] Failed to patch gateway.bind in ${configPath}: ${e.message}`);
291
305
  }
292
306
  }
307
+ /**
308
+ * Ensure the `llm-agent` capability advertised by openclaw-*.yaml works for
309
+ * every instance, including ones created before this code shipped: enable
310
+ * OpenClaw's OpenAI-compatible endpoints in `openclaw.json` on every start.
311
+ *
312
+ * Runs idempotently next to `patchDockerBridgeGatewayBind`. The OpenAI
313
+ * routes (`/v1/chat/completions`, `/v1/responses`, `/v1/models`,
314
+ * `/v1/embeddings`) live in openclaw's bundled server.impl and are gated
315
+ * behind `gateway.http.endpoints.{chatCompletions,responses}.enabled` —
316
+ * default false, so a fresh OpenClaw install would 404 those paths even
317
+ * though OpenWebUI binds it through the connections page.
318
+ */
319
+ export function patchOpenAIEndpointsEnabled(configPath) {
320
+ try {
321
+ const raw = readFileSync(configPath, "utf-8");
322
+ const parsed = JSON.parse(raw);
323
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
324
+ return;
325
+ const root = parsed;
326
+ const gateway = root.gateway && typeof root.gateway === "object" && !Array.isArray(root.gateway)
327
+ ? root.gateway
328
+ : (root.gateway = {});
329
+ const http = gateway.http && typeof gateway.http === "object" && !Array.isArray(gateway.http)
330
+ ? gateway.http
331
+ : (gateway.http = {});
332
+ const endpoints = http.endpoints && typeof http.endpoints === "object" && !Array.isArray(http.endpoints)
333
+ ? http.endpoints
334
+ : (http.endpoints = {});
335
+ let changed = false;
336
+ for (const key of ["chatCompletions", "responses"]) {
337
+ const ep = endpoints[key] && typeof endpoints[key] === "object" && !Array.isArray(endpoints[key])
338
+ ? endpoints[key]
339
+ : (endpoints[key] = {});
340
+ if (ep.enabled !== true) {
341
+ ep.enabled = true;
342
+ changed = true;
343
+ }
344
+ }
345
+ if (!changed)
346
+ return;
347
+ const next = JSON.stringify(parsed, null, 2);
348
+ const output = raw.endsWith("\n") ? `${next}\n` : next;
349
+ writeConfigFile(configPath, output);
350
+ console.log(`[openclaw] Enabled gateway.http.endpoints.{chatCompletions,responses} in ${configPath} for llm-agent capability`);
351
+ }
352
+ catch (e) {
353
+ console.warn(`[openclaw] Failed to patch OpenAI endpoints in ${configPath}: ${e.message}`);
354
+ }
355
+ }
356
+ /**
357
+ * Deep-merge a SearXNG connection into an OpenClaw config file at `configPath`.
358
+ *
359
+ * Writes both halves of the wiring required for the `web_search` tool to use
360
+ * the registry-resolved SearXNG instance:
361
+ *
362
+ * plugins.entries.searxng.enabled = true
363
+ * plugins.entries.searxng.config.webSearch.baseUrl = baseUrl
364
+ * tools.web.search.provider = "searxng"
365
+ *
366
+ * The provider selector is required because OpenClaw's built-in default is
367
+ * `"brave"`; without it the tool fails with `missing_brave_api_key` even when
368
+ * the searxng plugin is otherwise correctly configured (verified on Pi
369
+ * 2026-04-29: claw11 had the plugin block right but the selector unset).
370
+ *
371
+ * Why a partial deep-merge instead of `saveNativeConfig`: the latter is
372
+ * destructive — it replaces top-level keys wholesale and only preserves a
373
+ * hardcoded subset (`plugins.installs`, `plugins.entries` per-key, partial
374
+ * `channels` merge). A partial patch through it would wipe `models.providers`
375
+ * / `agents.defaults` / etc., bricking the instance with `No API key found
376
+ * for provider "openai"` on the next chat. So we read, merge, write back.
377
+ *
378
+ * No-op when the config file is absent (instance not yet started).
379
+ */
380
+ export function applySearxngConnectionToConfig(configPath, baseUrl) {
381
+ if (!existsSync(configPath))
382
+ return;
383
+ const existing = JSON.parse(readFileSync(configPath, "utf-8"));
384
+ existing.plugins = existing.plugins ?? {};
385
+ existing.plugins.entries = existing.plugins.entries ?? {};
386
+ const prior = existing.plugins.entries.searxng ?? {};
387
+ existing.plugins.entries.searxng = {
388
+ ...prior,
389
+ enabled: true,
390
+ config: {
391
+ ...(prior.config ?? {}),
392
+ webSearch: {
393
+ ...((prior.config ?? {}).webSearch ?? {}),
394
+ baseUrl,
395
+ },
396
+ },
397
+ };
398
+ existing.tools = existing.tools ?? {};
399
+ existing.tools.web = existing.tools.web ?? {};
400
+ existing.tools.web.search = existing.tools.web.search ?? {};
401
+ existing.tools.web.search.provider = "searxng";
402
+ safeWriteJson(configPath, existing);
403
+ }
404
+ /**
405
+ * Counterpart to `applySearxngConnectionToConfig` — invoked when the user
406
+ * unbinds the SEARCH slot in the Connections tab. Without this, unbinding
407
+ * left the plugin entry enabled with a baseUrl pointing at the now-gone
408
+ * searxng provider, and `tools.web.search.provider="searxng"` kept routing
409
+ * the agent's `web_search` tool through that dead URL on every chat.
410
+ *
411
+ * Conservative cleanup so a future re-bind (or user-customized plugin
412
+ * settings) survives:
413
+ * - flip the searxng plugin to enabled:false (don't delete the entry —
414
+ * user may have hand-tuned it and we want re-bind to re-enable cheaply)
415
+ * - drop the stale `webSearch.baseUrl` so nothing reads it as still-live
416
+ * - clear `tools.web.search.provider` so the runtime falls back to its
417
+ * built-in default (which today errors out with `missing_brave_api_key`
418
+ * — the right outcome: search was unbound, web_search shouldn't work)
419
+ *
420
+ * No-op when the config file is absent.
421
+ */
422
+ export function clearSearxngConnectionFromConfig(configPath) {
423
+ if (!existsSync(configPath))
424
+ return;
425
+ const existing = JSON.parse(readFileSync(configPath, "utf-8"));
426
+ const sx = existing?.plugins?.entries?.searxng;
427
+ if (sx && typeof sx === "object") {
428
+ sx.enabled = false;
429
+ if (sx.config?.webSearch && typeof sx.config.webSearch === "object") {
430
+ delete sx.config.webSearch.baseUrl;
431
+ }
432
+ }
433
+ if (existing?.tools?.web?.search && typeof existing.tools.web.search === "object") {
434
+ delete existing.tools.web.search.provider;
435
+ }
436
+ safeWriteJson(configPath, existing);
437
+ }
293
438
  /**
294
439
  * Pre-seed the per-instance npm global prefix with a symlink to the image's
295
440
  * baked openclaw package so OpenClaw's in-gateway "Update now" handler can
@@ -513,7 +658,7 @@ async function pullOrBuildOpenclawImageWithTask(task, tag) {
513
658
  const invocation = resolveDockerInvocation();
514
659
  // Always attempt pull — when the image is already local and in sync
515
660
  // with upstream, docker returns within seconds after a digest check.
516
- // The "skip if image present" early exit was making "重新安装" feel
661
+ // The "skip if image present" early exit was making "reinstall" feel
517
662
  // like a no-op; explicit re-pull matches user intent better. On pull
518
663
  // failure we still fall back to local build below.
519
664
  emitTask(task, { type: "progress", message: `正在拉取镜像: ${targetTag} ...`, progress: 10 });
@@ -683,6 +828,11 @@ class OpenClawAdapter {
683
828
  patchDockerBridgeGatewayBind(configPath);
684
829
  patchJsproxyBaseUrl(configPath);
685
830
  }
831
+ // Driver-agnostic: enable the OpenAI-compatible endpoints on every
832
+ // start so the `llm-agent` capability advertised by openclaw-*.yaml
833
+ // works for both fresh installs and instances created before this
834
+ // patcher shipped. Idempotent — bails out fast if already enabled.
835
+ patchOpenAIEndpointsEnabled(configPath);
686
836
  // 4. npm update-seed — use local resolver
687
837
  try {
688
838
  const home = openclawAdapter.resolveAgentHome(instanceId);
@@ -1220,6 +1370,12 @@ class OpenClawAdapter {
1220
1370
  const safeJobId = `${this.nomadJobPrefix}${instanceId}`;
1221
1371
  assertSafeTemplateId(safeJobId);
1222
1372
  const normalizedResources = normalizeDockerResources(instanceId, rawResources);
1373
+ // Same rationale as hermes.ts:nomadHasExternalHostNetwork — without
1374
+ // HostNetwork, Nomad's docker driver publishes the gateway port to
1375
+ // 127.0.0.1, breaking cross-container consumers (e.g. OpenWebUI
1376
+ // binding openclaw via the llm-agent capability). Bring the legacy
1377
+ // openclaw job in line with the unified app-spec path.
1378
+ const hostNetwork = nomadHasExternalHostNetwork() ? "external" : undefined;
1223
1379
  return {
1224
1380
  Name: "gateway",
1225
1381
  Driver: "docker",
@@ -1227,9 +1383,17 @@ class OpenClawAdapter {
1227
1383
  Config: {
1228
1384
  image,
1229
1385
  force_pull: false,
1386
+ // Match nomad-manager.ts:buildAppTask — default 5-minute pull
1387
+ // timeout is too short for Pi-class networks pulling a 1+ GiB
1388
+ // openclaw runtime image; bump to 15 minutes.
1389
+ image_pull_timeout: "15m",
1230
1390
  args,
1231
1391
  work_dir: openclawHome,
1232
1392
  volumes: [`${openclawHome}:${openclawHome}:rw`],
1393
+ // Tell the docker driver to publish the labeled "gateway" port so
1394
+ // it routes via the host_network IP rather than the 127.0.0.1
1395
+ // default.
1396
+ ports: ["gateway"],
1233
1397
  extra_hosts: ["host.docker.internal:host-gateway"],
1234
1398
  cap_drop: ["ALL"],
1235
1399
  security_opt: ["no-new-privileges"],
@@ -1244,7 +1408,18 @@ class OpenClawAdapter {
1244
1408
  Env: containerEnv,
1245
1409
  Resources: {
1246
1410
  ...normalizedResources,
1247
- Networks: [{ ReservedPorts: [{ Label: "gateway", Value: gatewayPort }] }],
1411
+ Networks: [
1412
+ {
1413
+ ReservedPorts: [
1414
+ {
1415
+ Label: "gateway",
1416
+ Value: gatewayPort,
1417
+ To: gatewayPort,
1418
+ ...(hostNetwork ? { HostNetwork: hostNetwork } : {}),
1419
+ },
1420
+ ],
1421
+ },
1422
+ ],
1248
1423
  },
1249
1424
  LogConfig: { MaxFiles: 3, MaxFileSizeMB: 10 },
1250
1425
  Templates: [
@@ -1378,6 +1553,79 @@ class OpenClawAdapter {
1378
1553
  saveNativeConfig(instanceId, config) {
1379
1554
  return saveNativeConfigImpl(instanceId, config);
1380
1555
  }
1556
+ /**
1557
+ * Connection-apply hook (§7 of app-interconnect-design): translate
1558
+ * resolved `inject_as` env vars into OpenClaw-native plugin config and
1559
+ * persist them into `openclaw.json`. Without this, the runtime env
1560
+ * injected by `nomad-manager.injectConnectionsRuntimeEnv` (PR 8) sits
1561
+ * in the container unused — the OpenClaw agent reads tool config from
1562
+ * `openclaw.json`, not from environment variables.
1563
+ *
1564
+ * Currently wired:
1565
+ * SEARCH_API_BASE_URL → plugins.entries.searxng.config.webSearch.baseUrl
1566
+ * SEARCH_API_BASE_URL → tools.web.search.provider = "searxng"
1567
+ *
1568
+ * The provider selector at `tools.web.search.provider` is required: without
1569
+ * it the `web_search` tool defaults to Brave and fails with
1570
+ * `missing_brave_api_key` even when the searxng plugin is fully configured.
1571
+ *
1572
+ * Browser / LLM / MCP wiring lands in PR 9b — they need their own
1573
+ * config-shape mapping (browser → tools.browser, llm → models.providers,
1574
+ * mcp already wired via openclaw-mcporter).
1575
+ *
1576
+ * The write goes through `saveNativeConfig` which deep-merges with the
1577
+ * existing on-disk config, so user-managed plugin entries (e.g.
1578
+ * openclaw-lark) are preserved. Plugin auto-enable then promotes the
1579
+ * searxng entry into `plugins.allow` automatically on next start
1580
+ * because we set `enabled:true` and provide config (the openclaw
1581
+ * runtime treats the presence of `config.webSearch.baseUrl` as a
1582
+ * "configured" signal — see `plugin-auto-enable` in the openclaw
1583
+ * dist bundle).
1584
+ */
1585
+ async applyConnectionEnv(instanceId, env) {
1586
+ const searchUrl = env.SEARCH_API_BASE_URL;
1587
+ if (typeof searchUrl !== "string")
1588
+ return;
1589
+ if (searchUrl === "") {
1590
+ // Empty value — connection-transactor's UNPERSIST_HOOKS uses this as
1591
+ // the "unbind" signal. Clear the searxng plugin config so the next
1592
+ // start doesn't keep routing web_search through a now-disconnected
1593
+ // provider.
1594
+ try {
1595
+ clearSearxngConnectionFromConfig(openclawConfigPath(instanceId));
1596
+ }
1597
+ catch (e) {
1598
+ console.warn(`[openclaw] applyConnectionEnv unbind failed for ${instanceId}: ${e.message}`);
1599
+ }
1600
+ return;
1601
+ }
1602
+ // SEARCH_API_BASE_URL points at "<base>/search" (the SearXNG search
1603
+ // endpoint). The plugin's webSearch.baseUrl wants the bare origin —
1604
+ // strip the trailing "/search" path segment if present.
1605
+ // baseUrl stays at the registry-resolved host:port snapshot from
1606
+ // when the user PUT /connections; the framework re-runs this hook
1607
+ // on every instance start (PR 9 phaseRefreshConnections), so host
1608
+ // IP changes propagate automatically on next agent restart.
1609
+ let baseUrl = searchUrl;
1610
+ try {
1611
+ const u = new URL(searchUrl);
1612
+ if (u.pathname === "/search" || u.pathname === "/search/") {
1613
+ u.pathname = "";
1614
+ baseUrl = u.toString().replace(/\/$/, "");
1615
+ }
1616
+ }
1617
+ catch {
1618
+ // not a URL — abort, the openclaw plugin would silently break with
1619
+ // a non-URL baseUrl and the instance still starts but search fails.
1620
+ return;
1621
+ }
1622
+ try {
1623
+ applySearxngConnectionToConfig(openclawConfigPath(instanceId), baseUrl);
1624
+ }
1625
+ catch (e) {
1626
+ console.warn(`[openclaw] applyConnectionEnv merge failed for ${instanceId}: ${e.message}`);
1627
+ }
1628
+ }
1381
1629
  // ── Path resolvers (physically migrated) ───────────────────────────
1382
1630
  resolveBin() {
1383
1631
  return resolveOpenclawBin();