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
|
@@ -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":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,eAAe,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAsB5D,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,aAAa,EAAE,WAAW,CAAC,CAAC,CAAC;AAEtE,SAAS,YAAY,CAAC,UAAkB;IACtC,MAAM,IAAI,GAAG,eAAe,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;IACzD,OAAO,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,eAAe,CAAC,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,UAAkB;IACnD,MAAM,IAAI,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC;IACtC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC9D,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;QACpD,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;QAC5E,IAAI,CAAC,GAAG,CAAC,UAAU,IAAI,OAAO,GAAG,CAAC,UAAU,KAAK,QAAQ,EAAE,CAAC;YAC1D,GAAG,CAAC,UAAU,GAAG,EAAE,CAAC;QACtB,CAAC;QACD,OAAO,GAAqB,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACzC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,UAAkB,EAClB,GAAmB;IAEnB,MAAM,IAAI,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC;IACtC,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IAChC,aAAa,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AAC3B,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,oBAAoB,CAClC,UAAkB,EAClB,OAA4C;IAE5C,MAAM,GAAG,GAAG,kBAAkB,CAAC,UAAU,CAAC,CAAC;IAC3C,GAAG,CAAC,UAAU,GAAG,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC;IACtC,KAAK,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACvD,IAAI,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,SAAS;QACtC,IAAI,IAAI,GAAG,OAAO,CAAC;QACnB,MAAM,QAAQ,GAAG,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,QAAQ,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;YAClE,sEAAsE;YACtE,IAAI,CAAC,GAAG,CAAC,CAAC;YACV,OAAO,GAAG,CAAC,UAAU,CAAC,GAAG,OAAO,IAAI,CAAC,EAAE,CAAC;gBAAE,CAAC,EAAE,CAAC;YAC9C,IAAI,GAAG,GAAG,OAAO,IAAI,CAAC,EAAE,CAAC;QAC3B,CAAC;QACD,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;IAC/B,CAAC;IACD,mBAAmB,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;AACvC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CACnC,UAAkB,EAClB,SAAsC,EAAE;IAExC,MAAM,GAAG,GAAG,kBAAkB,CAAC,UAAU,CAAC,CAAC;IAC3C,IAAI,CAAC,GAAG,CAAC,UAAU;QAAE,OAAO;IAC5B,MAAM,IAAI,GAAwC,EAAE,CAAC;IACrD,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3D,MAAM,GAAG,GAAG,KAAK,CAAC,QAAQ,CAAC;QAC3B,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM;YAC3B,CAAC,CAAC,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC;YACnC,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,CAAC;QACrC,IAAI,CAAC,OAAO;YAAE,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;IACnC,CAAC;IACD,GAAG,CAAC,UAAU,GAAG,IAAI,CAAC;IACtB,mBAAmB,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;AACvC,CAAC;AAED,SAAS,aAAa,CAAC,CAAkB,EAAE,CAAkB;IAC3D,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAC3B,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACpC,IAAI,CAAC,CAAC,IAAI,KAAK,YAAY,IAAI,CAAC,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;QACvD,OAAO,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,kBAAkB,KAAK,CAAC,CAAC,kBAAkB,CAAC;IAC5E,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -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 "
|
|
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: [
|
|
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();
|