jishushell 0.4.17 → 0.4.24-beta.2
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/Dockerfile.hermes-slim +193 -0
- package/apps/hermes-container.yaml +35 -0
- package/apps/ollama-binary.yaml +164 -0
- package/apps/ollama-cpu-container.yaml +37 -0
- package/apps/ollama-with-hollama-binary.yaml +159 -0
- package/apps/openclaw-binary.yaml +69 -0
- package/apps/openclaw-container.yaml +37 -0
- package/apps/openclaw-with-ollama-container.yaml +42 -0
- package/apps/openclaw-with-searxng-container.yaml +136 -0
- package/apps/openwebui-container.yaml +53 -0
- package/apps/playwright-container.yaml +120 -0
- package/apps/searxng-container.yaml +115 -0
- package/dist/auth.d.ts +1 -0
- package/dist/auth.js +15 -14
- package/dist/auth.js.map +1 -1
- package/dist/cli/app.d.ts +1 -0
- package/dist/cli/app.js +770 -52
- package/dist/cli/app.js.map +1 -1
- package/dist/cli/backup.d.ts +3 -0
- package/dist/cli/backup.js +434 -0
- package/dist/cli/backup.js.map +1 -0
- package/dist/cli/doctor.d.ts +1 -0
- package/dist/cli/doctor.js +61 -35
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/job.d.ts +1 -0
- package/dist/cli/job.js +37 -99
- package/dist/cli/job.js.map +1 -1
- package/dist/cli/llm.d.ts +1 -0
- package/dist/cli/llm.js +20 -14
- package/dist/cli/llm.js.map +1 -1
- package/dist/cli/managed-list.d.ts +30 -0
- package/dist/cli/managed-list.js +129 -0
- package/dist/cli/managed-list.js.map +1 -0
- package/dist/cli/panel.d.ts +4 -3
- package/dist/cli/panel.js +94 -24
- package/dist/cli/panel.js.map +1 -1
- package/dist/cli/version.d.ts +1 -0
- package/dist/cli/version.js +12 -0
- package/dist/cli/version.js.map +1 -0
- package/dist/cli.js +47 -516
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +68 -0
- package/dist/config.js +266 -12
- package/dist/config.js.map +1 -1
- package/dist/control.d.ts +10 -6
- package/dist/control.js +87 -6
- package/dist/control.js.map +1 -1
- package/dist/install.d.ts +16 -0
- package/dist/install.js +75 -26
- package/dist/install.js.map +1 -1
- package/dist/routes/agent-apps.d.ts +15 -0
- package/dist/routes/agent-apps.js +78 -0
- package/dist/routes/agent-apps.js.map +1 -0
- package/dist/routes/apps.js +186 -7
- package/dist/routes/apps.js.map +1 -1
- package/dist/routes/backup.js +3 -3
- package/dist/routes/backup.js.map +1 -1
- package/dist/routes/instances.d.ts +6 -0
- package/dist/routes/instances.js +862 -879
- package/dist/routes/instances.js.map +1 -1
- package/dist/routes/llm.js +9 -8
- package/dist/routes/llm.js.map +1 -1
- package/dist/routes/runtime.d.ts +15 -0
- package/dist/routes/runtime.js +69 -0
- package/dist/routes/runtime.js.map +1 -0
- package/dist/routes/setup.js +103 -8
- package/dist/routes/setup.js.map +1 -1
- package/dist/routes/system.js +25 -3
- package/dist/routes/system.js.map +1 -1
- package/dist/server.js +71 -7
- package/dist/server.js.map +1 -1
- package/dist/services/agent-apps/catalog.d.ts +30 -0
- package/dist/services/agent-apps/catalog.js +60 -0
- package/dist/services/agent-apps/catalog.js.map +1 -0
- package/dist/services/agent-apps/index.d.ts +36 -0
- package/dist/services/agent-apps/index.js +171 -0
- package/dist/services/agent-apps/index.js.map +1 -0
- package/dist/services/agent-apps/installers/adapter-probes.d.ts +49 -0
- package/dist/services/agent-apps/installers/adapter-probes.js +223 -0
- package/dist/services/agent-apps/installers/adapter-probes.js.map +1 -0
- package/dist/services/agent-apps/installers/adapter.d.ts +30 -0
- package/dist/services/agent-apps/installers/adapter.js +171 -0
- package/dist/services/agent-apps/installers/adapter.js.map +1 -0
- package/dist/services/agent-apps/installers/registry-probe.d.ts +38 -0
- package/dist/services/agent-apps/installers/registry-probe.js +183 -0
- package/dist/services/agent-apps/installers/registry-probe.js.map +1 -0
- package/dist/services/agent-apps/installers/shell-script.d.ts +47 -0
- package/dist/services/agent-apps/installers/shell-script.js +471 -0
- package/dist/services/agent-apps/installers/shell-script.js.map +1 -0
- package/dist/services/agent-apps/types.d.ts +125 -0
- package/dist/services/agent-apps/types.js +17 -0
- package/dist/services/agent-apps/types.js.map +1 -0
- package/dist/services/{app-compiler.d.ts → app/app-compiler.d.ts} +3 -3
- package/dist/services/{app-compiler.js → app/app-compiler.js} +10 -7
- package/dist/services/app/app-compiler.js.map +1 -0
- package/dist/services/app/app-manager.d.ts +142 -0
- package/dist/services/app/app-manager.js +2148 -0
- package/dist/services/app/app-manager.js.map +1 -0
- package/dist/services/app/custom-manager.d.ts +27 -0
- package/dist/services/app/custom-manager.js +285 -0
- package/dist/services/app/custom-manager.js.map +1 -0
- package/dist/services/app/hermes-agent-manager.d.ts +20 -0
- package/dist/services/app/hermes-agent-manager.js +289 -0
- package/dist/services/app/hermes-agent-manager.js.map +1 -0
- package/dist/services/app/id-normalizer.d.ts +27 -0
- package/dist/services/app/id-normalizer.js +77 -0
- package/dist/services/app/id-normalizer.js.map +1 -0
- package/dist/services/app/ollama-manager.d.ts +18 -0
- package/dist/services/app/ollama-manager.js +207 -0
- package/dist/services/app/ollama-manager.js.map +1 -0
- package/dist/services/app/openclaw-manager.d.ts +63 -0
- package/dist/services/app/openclaw-manager.js +1178 -0
- package/dist/services/app/openclaw-manager.js.map +1 -0
- package/dist/services/app/paths.d.ts +47 -0
- package/dist/services/app/paths.js +68 -0
- package/dist/services/app/paths.js.map +1 -0
- package/dist/services/app/registry.d.ts +17 -0
- package/dist/services/app/registry.js +31 -0
- package/dist/services/app/registry.js.map +1 -0
- package/dist/services/app/remote-spec.d.ts +14 -0
- package/dist/services/app/remote-spec.js +58 -0
- package/dist/services/app/remote-spec.js.map +1 -0
- package/dist/services/app/terminal-session-manager.d.ts +27 -0
- package/dist/services/app/terminal-session-manager.js +157 -0
- package/dist/services/app/terminal-session-manager.js.map +1 -0
- package/dist/services/app/types.d.ts +72 -0
- package/dist/services/app/types.js +16 -0
- package/dist/services/app/types.js.map +1 -0
- package/dist/services/backup-manager.js +60 -22
- package/dist/services/backup-manager.js.map +1 -1
- package/dist/services/instance-manager.d.ts +82 -39
- package/dist/services/instance-manager.js +575 -1142
- package/dist/services/instance-manager.js.map +1 -1
- package/dist/services/llm-proxy/circuit-breaker.js +10 -2
- package/dist/services/llm-proxy/circuit-breaker.js.map +1 -1
- package/dist/services/llm-proxy/index.d.ts +14 -1
- package/dist/services/llm-proxy/index.js +51 -6
- package/dist/services/llm-proxy/index.js.map +1 -1
- package/dist/services/nomad-manager.d.ts +260 -3
- package/dist/services/nomad-manager.js +2866 -449
- package/dist/services/nomad-manager.js.map +1 -1
- package/dist/services/panel-manager.d.ts +10 -0
- package/dist/services/panel-manager.js +97 -0
- package/dist/services/panel-manager.js.map +1 -1
- package/dist/services/plugin-installer.js +28 -2
- package/dist/services/plugin-installer.js.map +1 -1
- package/dist/services/process-manager.js +22 -0
- package/dist/services/process-manager.js.map +1 -1
- package/dist/services/runtime/adapters/custom.d.ts +20 -0
- package/dist/services/runtime/adapters/custom.js +90 -0
- package/dist/services/runtime/adapters/custom.js.map +1 -0
- package/dist/services/runtime/adapters/hermes.d.ts +174 -0
- package/dist/services/runtime/adapters/hermes.js +1316 -0
- package/dist/services/runtime/adapters/hermes.js.map +1 -0
- package/dist/services/runtime/adapters/openclaw-routes.d.ts +17 -0
- package/dist/services/runtime/adapters/openclaw-routes.js +946 -0
- package/dist/services/runtime/adapters/openclaw-routes.js.map +1 -0
- package/dist/services/runtime/adapters/openclaw.d.ts +188 -0
- package/dist/services/runtime/adapters/openclaw.js +2195 -0
- package/dist/services/runtime/adapters/openclaw.js.map +1 -0
- package/dist/services/runtime/errors.d.ts +28 -0
- package/dist/services/runtime/errors.js +31 -0
- package/dist/services/runtime/errors.js.map +1 -0
- package/dist/services/runtime/index.d.ts +34 -0
- package/dist/services/runtime/index.js +51 -0
- package/dist/services/runtime/index.js.map +1 -0
- package/dist/services/runtime/instance.d.ts +24 -0
- package/dist/services/runtime/instance.js +143 -0
- package/dist/services/runtime/instance.js.map +1 -0
- package/dist/services/runtime/migrations.d.ts +15 -0
- package/dist/services/runtime/migrations.js +25 -0
- package/dist/services/runtime/migrations.js.map +1 -0
- package/dist/services/runtime/registry.d.ts +13 -0
- package/dist/services/runtime/registry.js +32 -0
- package/dist/services/runtime/registry.js.map +1 -0
- package/dist/services/runtime/types.d.ts +545 -0
- package/dist/services/runtime/types.js +14 -0
- package/dist/services/runtime/types.js.map +1 -0
- package/dist/services/setup-manager.d.ts +70 -29
- package/dist/services/setup-manager.js +278 -597
- package/dist/services/setup-manager.js.map +1 -1
- package/dist/services/task-registry.d.ts +44 -0
- package/dist/services/task-registry.js +74 -0
- package/dist/services/task-registry.js.map +1 -0
- package/dist/services/telemetry/heartbeat.d.ts +6 -6
- package/dist/services/telemetry/heartbeat.js +29 -30
- package/dist/services/telemetry/heartbeat.js.map +1 -1
- package/dist/types.d.ts +164 -2
- package/dist/utils/docker-host.d.ts +15 -0
- package/dist/utils/docker-host.js +64 -0
- package/dist/utils/docker-host.js.map +1 -0
- package/install/jishu-install.sh +25 -2
- package/package.json +14 -4
- package/public/assets/Dashboard-rh9qpYRR.js +1 -0
- package/public/assets/HermesChatPanel-D6JI6lLY.js +1 -0
- package/public/assets/HermesConfigForm-DcbSemaj.js +4 -0
- package/public/assets/InitPassword-CFTKsED4.js +1 -0
- package/public/assets/InstanceDetail-BhNIKA6Z.js +91 -0
- package/public/assets/{Login-D1Bt-Lyk.js → Login-KB9qrtM0.js} +1 -1
- package/public/assets/NewInstance-CxkO8Hlq.js +1 -0
- package/public/assets/Settings-BVWJvOkU.js +1 -0
- package/public/assets/Setup-X-lzuaUT.js +1 -0
- package/public/assets/WeixinLoginPanel-gca0QTic.js +9 -0
- package/public/assets/index-C8B0cFJM.js +19 -0
- package/public/assets/index-CPhVFEsx.css +1 -0
- package/public/assets/input-paste-CrNVAyOy.js +1 -0
- package/public/assets/registry-fVUSujib.js +2 -0
- package/public/assets/{usePolling-CK0DfI4h.js → usePolling-Do5Erqm_.js} +1 -1
- package/public/assets/vendor-i18n-ucpM0OR0.js +9 -0
- package/public/assets/{vendor-react-B1-3Yrt-.js → vendor-react-Bk1hRGiY.js} +1 -1
- package/public/favicon.png +0 -0
- package/public/index.html +9 -4
- package/public/logos/hermes.png +0 -0
- package/public/logos/ollama.png +0 -0
- package/public/logos/openclaw.svg +60 -0
- package/scripts/build-hermes-image.sh +21 -0
- package/scripts/build-local.sh +54 -0
- package/scripts/check-adapter-isolation.ts +293 -0
- package/scripts/fixtures/instances/hermes-sample/instance.json +37 -0
- package/scripts/fixtures/instances/legacy-openclaw-sample/instance.json +7 -0
- package/scripts/smoke/hermes-bootstrap.sh +195 -0
- package/templates/hermes-entrypoint.sh +154 -0
- package/dist/cli/openclaw.d.ts +0 -12
- package/dist/cli/openclaw.js +0 -156
- package/dist/cli/openclaw.js.map +0 -1
- package/dist/services/app-compiler.js.map +0 -1
- package/dist/services/app-manager.d.ts +0 -17
- package/dist/services/app-manager.js +0 -168
- package/dist/services/app-manager.js.map +0 -1
- package/dist/services/job-manager.d.ts +0 -22
- package/dist/services/job-manager.js +0 -102
- package/dist/services/job-manager.js.map +0 -1
- package/public/assets/Dashboard-CQsp1Mr9.js +0 -1
- package/public/assets/InitPassword-BEC8SE4A.js +0 -1
- package/public/assets/InstanceDetail-B5wTgNEg.js +0 -17
- package/public/assets/NewInstance-GQzm3K9D.js +0 -1
- package/public/assets/Settings-ByjGlqhP.js +0 -1
- package/public/assets/Setup-cMF21Y-8.js +0 -1
- package/public/assets/index-B6qQP4mH.css +0 -1
- package/public/assets/index-BuTQtuNy.js +0 -16
- package/public/assets/vendor-i18n-CfW0RvgE.js +0 -9
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
#!/usr/bin/env -S node --experimental-strip-types
|
|
2
|
+
/**
|
|
3
|
+
* check-adapter-isolation — §32.2.1 static enforcement + §30.3 fixtures.
|
|
4
|
+
*
|
|
5
|
+
* Two invariants:
|
|
6
|
+
*
|
|
7
|
+
* 1. Core framework files never import from
|
|
8
|
+
* `src/services/runtime/adapters/*`. Adapters load exclusively through
|
|
9
|
+
* `src/services/runtime/index.ts` side-effect imports, and framework
|
|
10
|
+
* code reaches adapters through `getAdapter(kind)` only.
|
|
11
|
+
*
|
|
12
|
+
* 2. Fixture `instance.json` samples under
|
|
13
|
+
* `scripts/fixtures/instances/<name>/` satisfy the minimum meta
|
|
14
|
+
* contract: `id`, `name`, resolvable agent discriminator, and
|
|
15
|
+
* (for Hermes) the required `paths.agentHome` / `paths.primaryConfig`
|
|
16
|
+
* / `paths.secretEnv` entries. Legacy openclaw samples round-trip
|
|
17
|
+
* through `backfillInstanceMeta` to `agentType="openclaw"`.
|
|
18
|
+
*
|
|
19
|
+
* Run via `npm run check:contracts`. Exits non-zero on violation.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
23
|
+
import { resolve } from "node:path";
|
|
24
|
+
|
|
25
|
+
const REPO_ROOT = resolve(import.meta.dirname ?? ".", "..");
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Files that must not import any specific adapter directly. Matches the
|
|
29
|
+
* list in docs/multi-agent-runtime-generalization-plan.md §32.2.1.
|
|
30
|
+
*/
|
|
31
|
+
const CORE_FILES = [
|
|
32
|
+
"src/services/instance-manager.ts",
|
|
33
|
+
"src/services/nomad-manager.ts",
|
|
34
|
+
"src/services/setup-manager.ts",
|
|
35
|
+
"src/services/backup-manager.ts",
|
|
36
|
+
"src/services/llm-proxy/index.ts",
|
|
37
|
+
"src/routes/instances.ts",
|
|
38
|
+
"src/routes/setup.ts",
|
|
39
|
+
"src/server.ts",
|
|
40
|
+
] as const;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Regexes for forbidden adapter imports. Covers both static imports and
|
|
44
|
+
* dynamic `import()`. Note: we deliberately allow references to the
|
|
45
|
+
* registry / runtime/index (`./runtime/index.js`, `./runtime/registry.js`,
|
|
46
|
+
* `./runtime/types.js`, `./runtime/instance.js`) because those contain
|
|
47
|
+
* the framework surface, not adapter-specific logic.
|
|
48
|
+
*/
|
|
49
|
+
const FORBIDDEN_IMPORT_PATTERNS = [
|
|
50
|
+
/from\s+["'][^"']*\/runtime\/adapters\/[^"']+["']/g,
|
|
51
|
+
/import\s*\(\s*["'][^"']*\/runtime\/adapters\/[^"']+["']\s*\)/g,
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Literal strings that indicate kind-specific bleed-through into
|
|
56
|
+
* framework files. These are advisory only for now: existing OpenClaw
|
|
57
|
+
* legacy code may still contain OPENCLAW_HOME env references etc. We log
|
|
58
|
+
* them but don't fail the build — the physical migration cleans them up
|
|
59
|
+
* in a follow-up PR. When MIGRATION_STRICT=1 is set, they become errors.
|
|
60
|
+
*/
|
|
61
|
+
const KIND_LITERAL_PATTERNS: Array<{ pattern: RegExp; description: string }> = [
|
|
62
|
+
{
|
|
63
|
+
pattern: /JOB_PREFIX\s*=\s*["']openclaw-/,
|
|
64
|
+
description: `JOB_PREFIX literal "openclaw-" — §32.2.7 wants panel.nomadJobPrefix`,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
pattern: /OPENCLAW_HOME|OPENCLAW_INSTANCE_ID|OPENCLAW_GATEWAY_PORT/,
|
|
68
|
+
description: `OpenClaw-specific env var literal — should live in OpenClawAdapter`,
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
pattern: /isHermesInstance\s*\(/,
|
|
72
|
+
description: `isHermesInstance() — runtime-specific branch, should dispatch via getAdapter`,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
pattern: /agentType\s*===\s*["']hermes["']|agentType\s*===\s*["']openclaw["']/,
|
|
76
|
+
description: `agentType literal comparison — dispatch through adapter instead`,
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
const STRICT = process.env.MIGRATION_STRICT === "1";
|
|
81
|
+
|
|
82
|
+
type Violation = {
|
|
83
|
+
file: string;
|
|
84
|
+
line: number;
|
|
85
|
+
column: number;
|
|
86
|
+
text: string;
|
|
87
|
+
rule: string;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
function isCommentLine(lineText: string): boolean {
|
|
91
|
+
const t = lineText.trimStart();
|
|
92
|
+
return t.startsWith("//") || t.startsWith("*") || t.startsWith("/*");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function findViolations(filepath: string, content: string): Violation[] {
|
|
96
|
+
const violations: Violation[] = [];
|
|
97
|
+
|
|
98
|
+
// 1. Forbidden adapter imports (HARD errors)
|
|
99
|
+
for (const pattern of FORBIDDEN_IMPORT_PATTERNS) {
|
|
100
|
+
let match: RegExpExecArray | null;
|
|
101
|
+
const re = new RegExp(pattern.source, "g");
|
|
102
|
+
while ((match = re.exec(content)) !== null) {
|
|
103
|
+
const before = content.slice(0, match.index);
|
|
104
|
+
const line = before.split("\n").length;
|
|
105
|
+
const column = match.index - before.lastIndexOf("\n");
|
|
106
|
+
violations.push({
|
|
107
|
+
file: filepath,
|
|
108
|
+
line,
|
|
109
|
+
column,
|
|
110
|
+
text: match[0],
|
|
111
|
+
rule: "adapter-import-forbidden",
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 2. Kind-specific literals (SOFT warnings unless MIGRATION_STRICT=1).
|
|
117
|
+
// Comment lines are skipped — references in docstrings or `§32`
|
|
118
|
+
// migration notes are not actual code bleed-through.
|
|
119
|
+
if (STRICT) {
|
|
120
|
+
const lines = content.split("\n");
|
|
121
|
+
for (let i = 0; i < lines.length; i++) {
|
|
122
|
+
const lineText = lines[i];
|
|
123
|
+
if (isCommentLine(lineText)) continue;
|
|
124
|
+
for (const { pattern, description } of KIND_LITERAL_PATTERNS) {
|
|
125
|
+
if (pattern.test(lineText)) {
|
|
126
|
+
violations.push({
|
|
127
|
+
file: filepath,
|
|
128
|
+
line: i + 1,
|
|
129
|
+
column: 1,
|
|
130
|
+
text: lineText.trim().slice(0, 120),
|
|
131
|
+
rule: `kind-literal: ${description}`,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return violations;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ── Fixture schema checks (plan §30.3) ───────────────────────────────
|
|
142
|
+
//
|
|
143
|
+
// Keep the schema rules inline: we only validate a handful of fields and
|
|
144
|
+
// pulling in ajv for a single static script adds disproportionate churn.
|
|
145
|
+
// The check mirrors the runtime contract in `runtime/migrations.ts`:
|
|
146
|
+
// - every meta must carry `id` / `name`
|
|
147
|
+
// - `agentType` resolves (missing → defaults to "openclaw")
|
|
148
|
+
// - Hermes fixtures must declare `paths.agentHome` + `paths.primaryConfig`
|
|
149
|
+
// - legacy openclaw fixtures must backfill to `agentType === "openclaw"`
|
|
150
|
+
|
|
151
|
+
type FixtureViolation = { fixture: string; rule: string; detail: string };
|
|
152
|
+
|
|
153
|
+
function resolveFixtureAgentType(meta: Record<string, unknown>): string {
|
|
154
|
+
const raw = typeof meta.agentType === "string" ? meta.agentType : "";
|
|
155
|
+
return raw.trim() ? raw.trim() : "openclaw";
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function checkFixture(fixtureName: string, meta: Record<string, unknown>): FixtureViolation[] {
|
|
159
|
+
const out: FixtureViolation[] = [];
|
|
160
|
+
const agentType = resolveFixtureAgentType(meta);
|
|
161
|
+
if (typeof meta.id !== "string" || !meta.id) {
|
|
162
|
+
out.push({ fixture: fixtureName, rule: "missing-id", detail: "instance.json must carry a non-empty string id" });
|
|
163
|
+
}
|
|
164
|
+
if (typeof meta.name !== "string" || !meta.name) {
|
|
165
|
+
out.push({ fixture: fixtureName, rule: "missing-name", detail: "instance.json must carry a non-empty string name" });
|
|
166
|
+
}
|
|
167
|
+
if (agentType === "hermes") {
|
|
168
|
+
const paths = (meta.paths ?? {}) as Record<string, unknown>;
|
|
169
|
+
for (const key of ["agentHome", "primaryConfig", "secretEnv"] as const) {
|
|
170
|
+
if (typeof paths[key] !== "string" || !paths[key]) {
|
|
171
|
+
out.push({
|
|
172
|
+
fixture: fixtureName,
|
|
173
|
+
rule: `hermes-paths-${key}`,
|
|
174
|
+
detail: `hermes instance.json requires paths.${key}`,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (fixtureName.startsWith("legacy-openclaw") && agentType !== "openclaw") {
|
|
180
|
+
out.push({
|
|
181
|
+
fixture: fixtureName,
|
|
182
|
+
rule: "legacy-backfill",
|
|
183
|
+
detail: `legacy fixture must backfill to agentType="openclaw", got "${agentType}"`,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
return out;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function loadFixtures(): FixtureViolation[] {
|
|
190
|
+
const fixturesRoot = resolve(REPO_ROOT, "scripts/fixtures/instances");
|
|
191
|
+
let entries: string[];
|
|
192
|
+
try {
|
|
193
|
+
entries = readdirSync(fixturesRoot);
|
|
194
|
+
} catch {
|
|
195
|
+
return []; // directory optional during early migrations
|
|
196
|
+
}
|
|
197
|
+
const violations: FixtureViolation[] = [];
|
|
198
|
+
for (const entry of entries) {
|
|
199
|
+
const fullPath = resolve(fixturesRoot, entry);
|
|
200
|
+
try {
|
|
201
|
+
if (!statSync(fullPath).isDirectory()) continue;
|
|
202
|
+
} catch {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
const metaPath = resolve(fullPath, "instance.json");
|
|
206
|
+
let raw: string;
|
|
207
|
+
try {
|
|
208
|
+
raw = readFileSync(metaPath, "utf-8");
|
|
209
|
+
} catch {
|
|
210
|
+
violations.push({ fixture: entry, rule: "missing-instance-json", detail: metaPath });
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
const meta = JSON.parse(raw) as Record<string, unknown>;
|
|
215
|
+
violations.push(...checkFixture(entry, meta));
|
|
216
|
+
} catch (e: any) {
|
|
217
|
+
violations.push({ fixture: entry, rule: "invalid-json", detail: e?.message ?? String(e) });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return violations;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function main(): void {
|
|
224
|
+
const allViolations: Violation[] = [];
|
|
225
|
+
const softFindings: Array<{ file: string; line: number; text: string; description: string }> = [];
|
|
226
|
+
|
|
227
|
+
for (const relPath of CORE_FILES) {
|
|
228
|
+
const abs = resolve(REPO_ROOT, relPath);
|
|
229
|
+
let content: string;
|
|
230
|
+
try {
|
|
231
|
+
content = readFileSync(abs, "utf-8");
|
|
232
|
+
} catch {
|
|
233
|
+
console.warn(`[check-adapter-isolation] skip (not found): ${relPath}`);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
allViolations.push(...findViolations(relPath, content));
|
|
237
|
+
|
|
238
|
+
// Collect soft findings separately so non-strict runs can still report them.
|
|
239
|
+
if (!STRICT) {
|
|
240
|
+
const lines = content.split("\n");
|
|
241
|
+
for (let i = 0; i < lines.length; i++) {
|
|
242
|
+
const lineText = lines[i];
|
|
243
|
+
if (isCommentLine(lineText)) continue;
|
|
244
|
+
for (const { pattern, description } of KIND_LITERAL_PATTERNS) {
|
|
245
|
+
if (pattern.test(lineText)) {
|
|
246
|
+
softFindings.push({
|
|
247
|
+
file: relPath,
|
|
248
|
+
line: i + 1,
|
|
249
|
+
text: lineText.trim().slice(0, 120),
|
|
250
|
+
description,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const fixtureViolations = loadFixtures();
|
|
259
|
+
|
|
260
|
+
if (allViolations.length === 0 && softFindings.length === 0 && fixtureViolations.length === 0) {
|
|
261
|
+
console.log("[check-adapter-isolation] OK — core files are fully decoupled from specific adapters");
|
|
262
|
+
process.exit(0);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (allViolations.length > 0) {
|
|
266
|
+
console.error(`\n[check-adapter-isolation] ${allViolations.length} HARD violation(s):\n`);
|
|
267
|
+
for (const v of allViolations) {
|
|
268
|
+
console.error(` ${v.file}:${v.line}:${v.column} [${v.rule}]`);
|
|
269
|
+
console.error(` ${v.text}`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (fixtureViolations.length > 0) {
|
|
274
|
+
console.error(`\n[check-adapter-isolation] ${fixtureViolations.length} fixture violation(s):\n`);
|
|
275
|
+
for (const v of fixtureViolations) {
|
|
276
|
+
console.error(` fixtures/${v.fixture} [${v.rule}] ${v.detail}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (softFindings.length > 0) {
|
|
281
|
+
console.warn(
|
|
282
|
+
`\n[check-adapter-isolation] ${softFindings.length} soft finding(s) (remaining framework bleed-through — migrate in follow-up PRs; run with MIGRATION_STRICT=1 to fail on these):\n`,
|
|
283
|
+
);
|
|
284
|
+
for (const f of softFindings) {
|
|
285
|
+
console.warn(` ${f.file}:${f.line} ${f.description}`);
|
|
286
|
+
console.warn(` ${f.text}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
process.exit(allViolations.length > 0 || fixtureViolations.length > 0 ? 1 : 0);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
main();
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "hermes-sample",
|
|
3
|
+
"name": "Hermes Sample",
|
|
4
|
+
"description": "Fixture for plan §30.3 contract check",
|
|
5
|
+
"agentType": "hermes",
|
|
6
|
+
"paths": {
|
|
7
|
+
"instanceDir": "/tmp/instances/hermes-sample",
|
|
8
|
+
"agentHome": "/tmp/instances/hermes-sample/agent-home",
|
|
9
|
+
"primaryConfig": "/tmp/instances/hermes-sample/agent-home/config.yaml",
|
|
10
|
+
"secretEnv": "/tmp/instances/hermes-sample/agent-home/.env"
|
|
11
|
+
},
|
|
12
|
+
"runtime": {
|
|
13
|
+
"driver": "docker",
|
|
14
|
+
"image": "ghcr.io/x-aijishu/hermes-runtime:latest",
|
|
15
|
+
"envFiles": ["/tmp/instances/hermes-sample/agent-home/.env"],
|
|
16
|
+
"ports": [
|
|
17
|
+
{ "name": "gateway", "containerPort": 8642, "hostPort": 8642 }
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"x-jishushell": {
|
|
21
|
+
"proxy": {
|
|
22
|
+
"upstream": {
|
|
23
|
+
"providerId": "openai",
|
|
24
|
+
"baseUrl": "https://api.openai.com",
|
|
25
|
+
"api": "openai-completions",
|
|
26
|
+
"authHeader": false,
|
|
27
|
+
"headers": {},
|
|
28
|
+
"models": [{ "id": "gpt-4o-mini", "name": "gpt-4o-mini", "contextWindow": 128000 }],
|
|
29
|
+
"selectedModelId": "gpt-4o-mini",
|
|
30
|
+
"apiKey": "",
|
|
31
|
+
"hasApiKey": false,
|
|
32
|
+
"clearApiKey": false
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"created_at": "2026-04-20T00:00:00.000Z"
|
|
37
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "legacy-openclaw-sample",
|
|
3
|
+
"name": "Legacy OpenClaw",
|
|
4
|
+
"description": "Pre-§32.2 instance: no agentType, only openclaw_home. Must backfill to agentType=openclaw.",
|
|
5
|
+
"openclaw_home": "/tmp/instances/legacy-openclaw-sample/openclaw-home",
|
|
6
|
+
"created_at": "2025-11-01T00:00:00.000Z"
|
|
7
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# End-to-end smoke for Hermes default onboarding.
|
|
4
|
+
#
|
|
5
|
+
# Drives the complete lifecycle against a running jishushell panel:
|
|
6
|
+
# 1. install Hermes runtime (docker pull + shim + panel.json catalog)
|
|
7
|
+
# 2. create a Hermes instance via POST /api/instances {"agentType":"hermes"}
|
|
8
|
+
# 3. start the instance
|
|
9
|
+
# 4. poll /health until 200 OK (or timeout)
|
|
10
|
+
# 5. stop + delete the instance
|
|
11
|
+
# 6. verify instance dir is cleaned up
|
|
12
|
+
#
|
|
13
|
+
# Usage:
|
|
14
|
+
# JISHUSHELL_URL=http://pi2:8090 JISHUSHELL_TOKEN=... ./scripts/smoke/hermes-bootstrap.sh
|
|
15
|
+
# (or run on the same host with defaults)
|
|
16
|
+
#
|
|
17
|
+
# Per docs/feedback_validate_before_commit.md, this script MUST be run on a
|
|
18
|
+
# real Pi2 box before the Hermes onboarding work is considered complete.
|
|
19
|
+
|
|
20
|
+
set -eo pipefail
|
|
21
|
+
|
|
22
|
+
PANEL_URL="${JISHUSHELL_URL:-http://127.0.0.1:8090}"
|
|
23
|
+
AUTH_TOKEN="${JISHUSHELL_TOKEN:-}"
|
|
24
|
+
INSTANCE_ID="${INSTANCE_ID:-hermes-smoke}"
|
|
25
|
+
INSTANCE_NAME="${INSTANCE_NAME:-Hermes Smoke Test}"
|
|
26
|
+
HEALTH_TIMEOUT="${HEALTH_TIMEOUT:-180}" # seconds; first docker pull can take a while
|
|
27
|
+
INSTALL_TIMEOUT="${INSTALL_TIMEOUT:-1800}" # 30 min for 2.4 GB image on slow links
|
|
28
|
+
|
|
29
|
+
red() { printf '\033[31m%s\033[0m\n' "$*"; }
|
|
30
|
+
green() { printf '\033[32m%s\033[0m\n' "$*"; }
|
|
31
|
+
blue() { printf '\033[34m%s\033[0m\n' "$*"; }
|
|
32
|
+
yellow(){ printf '\033[33m%s\033[0m\n' "$*"; }
|
|
33
|
+
|
|
34
|
+
log() { blue "[hermes-smoke] $*"; }
|
|
35
|
+
ok() { green "[hermes-smoke] ✔ $*"; }
|
|
36
|
+
fail() { red "[hermes-smoke] ✘ $*"; exit 1; }
|
|
37
|
+
|
|
38
|
+
CURL_AUTH=()
|
|
39
|
+
if [ -n "$AUTH_TOKEN" ]; then
|
|
40
|
+
CURL_AUTH=(-H "Authorization: Bearer $AUTH_TOKEN")
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
api() {
|
|
44
|
+
local method="$1"; shift
|
|
45
|
+
local path="$1"; shift
|
|
46
|
+
curl -fsS -X "$method" "${PANEL_URL}${path}" \
|
|
47
|
+
"${CURL_AUTH[@]}" \
|
|
48
|
+
-H "Content-Type: application/json" \
|
|
49
|
+
"$@"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# ── 0. Preflight ──────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
log "panel: $PANEL_URL"
|
|
55
|
+
log "instance: $INSTANCE_ID"
|
|
56
|
+
|
|
57
|
+
if ! curl -fsS "$PANEL_URL/api/setup/status" "${CURL_AUTH[@]}" >/dev/null 2>&1; then
|
|
58
|
+
fail "panel not reachable at $PANEL_URL"
|
|
59
|
+
fi
|
|
60
|
+
ok "panel reachable"
|
|
61
|
+
|
|
62
|
+
# Ensure any stale instance from a prior run is gone
|
|
63
|
+
if curl -fsS "$PANEL_URL/api/instances/$INSTANCE_ID" "${CURL_AUTH[@]}" >/dev/null 2>&1; then
|
|
64
|
+
yellow "stale $INSTANCE_ID found, cleaning up first"
|
|
65
|
+
api DELETE "/api/instances/$INSTANCE_ID?purge_backups=true" >/dev/null || true
|
|
66
|
+
sleep 2
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
# ── 1. Install Hermes runtime ────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
log "installing Hermes runtime (this pulls ~2.4 GB on first run)..."
|
|
72
|
+
INSTALL_RESP=$(api POST "/api/setup/install/hermes" -d '{}')
|
|
73
|
+
TASK_ID=$(printf '%s' "$INSTALL_RESP" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('taskId',''))" 2>/dev/null || echo "")
|
|
74
|
+
if [ -z "$TASK_ID" ]; then
|
|
75
|
+
fail "installHermes did not return a taskId. Response: $INSTALL_RESP"
|
|
76
|
+
fi
|
|
77
|
+
log "install task: $TASK_ID — polling..."
|
|
78
|
+
|
|
79
|
+
START_TS=$(date +%s)
|
|
80
|
+
while : ; do
|
|
81
|
+
STATUS_RESP=$(curl -fsS "$PANEL_URL/api/setup/tasks/$TASK_ID" "${CURL_AUTH[@]}")
|
|
82
|
+
STATUS=$(printf '%s' "$STATUS_RESP" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('status',''))" 2>/dev/null || echo "")
|
|
83
|
+
LAST_MSG=$(printf '%s' "$STATUS_RESP" | python3 -c "import json,sys; d=json.load(sys.stdin); ev=d.get('events') or []; print(ev[-1].get('message','')) if ev else print('')" 2>/dev/null || echo "")
|
|
84
|
+
ELAPSED=$(( $(date +%s) - START_TS ))
|
|
85
|
+
printf '\r [%ds] %-60s' "$ELAPSED" "$LAST_MSG"
|
|
86
|
+
if [ "$STATUS" = "done" ]; then
|
|
87
|
+
printf '\n'
|
|
88
|
+
ok "Hermes installed"
|
|
89
|
+
break
|
|
90
|
+
fi
|
|
91
|
+
if [ "$STATUS" = "error" ]; then
|
|
92
|
+
printf '\n'
|
|
93
|
+
fail "Hermes install failed: $LAST_MSG"
|
|
94
|
+
fi
|
|
95
|
+
if [ "$ELAPSED" -gt "$INSTALL_TIMEOUT" ]; then
|
|
96
|
+
printf '\n'
|
|
97
|
+
fail "Hermes install timeout after ${INSTALL_TIMEOUT}s"
|
|
98
|
+
fi
|
|
99
|
+
sleep 3
|
|
100
|
+
done
|
|
101
|
+
|
|
102
|
+
# ── 2. Create Hermes instance ────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
log "creating instance $INSTANCE_ID..."
|
|
105
|
+
CREATE_BODY=$(printf '{"id":"%s","name":"%s","agentType":"hermes"}' "$INSTANCE_ID" "$INSTANCE_NAME")
|
|
106
|
+
CREATE_RESP=$(api POST "/api/instances" -d "$CREATE_BODY")
|
|
107
|
+
AGENT_TYPE=$(printf '%s' "$CREATE_RESP" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('agentType',''))" 2>/dev/null || echo "")
|
|
108
|
+
if [ "$AGENT_TYPE" != "hermes" ]; then
|
|
109
|
+
fail "created instance has wrong agentType: expected 'hermes', got '$AGENT_TYPE'. Response: $CREATE_RESP"
|
|
110
|
+
fi
|
|
111
|
+
ok "instance created"
|
|
112
|
+
|
|
113
|
+
# ── 3. Start (may already be auto-started if default_provider is set) ─
|
|
114
|
+
|
|
115
|
+
log "starting instance..."
|
|
116
|
+
api POST "/api/instances/$INSTANCE_ID/service/start" -d '{}' >/dev/null || yellow "service/start returned non-2xx (may already be running)"
|
|
117
|
+
|
|
118
|
+
# ── 4. Poll /health ──────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
log "resolving allocated host port..."
|
|
121
|
+
sleep 3
|
|
122
|
+
GW_INFO=$(api GET "/api/instances/$INSTANCE_ID/gateway-launch" 2>/dev/null || echo "")
|
|
123
|
+
GW_PORT=$(printf '%s' "$GW_INFO" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('port',''))" 2>/dev/null || echo "")
|
|
124
|
+
if [ -z "$GW_PORT" ]; then
|
|
125
|
+
# Fallback: read port from instance.json via /api/instances/:id
|
|
126
|
+
INST_INFO=$(api GET "/api/instances/$INSTANCE_ID")
|
|
127
|
+
GW_PORT=$(printf '%s' "$INST_INFO" | python3 -c "
|
|
128
|
+
import json, sys
|
|
129
|
+
d = json.load(sys.stdin)
|
|
130
|
+
rt = d.get('runtime', {}) or {}
|
|
131
|
+
ports = rt.get('ports') or []
|
|
132
|
+
for p in ports:
|
|
133
|
+
if p.get('name') == 'gateway':
|
|
134
|
+
print(p.get('hostPort', ''))
|
|
135
|
+
break
|
|
136
|
+
else:
|
|
137
|
+
print('')
|
|
138
|
+
" 2>/dev/null || echo "")
|
|
139
|
+
fi
|
|
140
|
+
if [ -z "$GW_PORT" ] || [ "$GW_PORT" = "0" ]; then
|
|
141
|
+
fail "could not resolve allocated gateway port"
|
|
142
|
+
fi
|
|
143
|
+
log "gateway port: $GW_PORT"
|
|
144
|
+
|
|
145
|
+
# Determine health target — assume panel host for Pi deploy
|
|
146
|
+
HEALTH_HOST=$(printf '%s' "$PANEL_URL" | sed -E 's#https?://([^:/]+).*#\1#')
|
|
147
|
+
HEALTH_URL="http://${HEALTH_HOST}:${GW_PORT}/health"
|
|
148
|
+
log "probing $HEALTH_URL (timeout ${HEALTH_TIMEOUT}s)..."
|
|
149
|
+
|
|
150
|
+
START_TS=$(date +%s)
|
|
151
|
+
while : ; do
|
|
152
|
+
if BODY=$(curl -fsS --max-time 5 "$HEALTH_URL" 2>/dev/null); then
|
|
153
|
+
STATUS_FIELD=$(printf '%s' "$BODY" | python3 -c "import json,sys; print(json.load(sys.stdin).get('status',''))" 2>/dev/null || echo "")
|
|
154
|
+
PLATFORM=$(printf '%s' "$BODY" | python3 -c "import json,sys; print(json.load(sys.stdin).get('platform',''))" 2>/dev/null || echo "")
|
|
155
|
+
if [ "$STATUS_FIELD" = "ok" ] && [ "$PLATFORM" = "hermes-agent" ]; then
|
|
156
|
+
ok "health OK — ${BODY}"
|
|
157
|
+
break
|
|
158
|
+
fi
|
|
159
|
+
fi
|
|
160
|
+
ELAPSED=$(( $(date +%s) - START_TS ))
|
|
161
|
+
if [ "$ELAPSED" -gt "$HEALTH_TIMEOUT" ]; then
|
|
162
|
+
red " last body: $BODY"
|
|
163
|
+
fail "health check timeout after ${HEALTH_TIMEOUT}s"
|
|
164
|
+
fi
|
|
165
|
+
printf '\r [%ds] waiting for /health ... ' "$ELAPSED"
|
|
166
|
+
sleep 3
|
|
167
|
+
done
|
|
168
|
+
|
|
169
|
+
# ── 5. Stop + delete ─────────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
log "stopping instance..."
|
|
172
|
+
api POST "/api/instances/$INSTANCE_ID/service/stop" -d '{}' >/dev/null
|
|
173
|
+
ok "stopped"
|
|
174
|
+
|
|
175
|
+
log "deleting instance..."
|
|
176
|
+
api DELETE "/api/instances/$INSTANCE_ID?purge_backups=true" >/dev/null
|
|
177
|
+
ok "deleted"
|
|
178
|
+
|
|
179
|
+
# ── 6. Verify cleanup ────────────────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
# Via API — should 404
|
|
182
|
+
if curl -fsS "$PANEL_URL/api/instances/$INSTANCE_ID" "${CURL_AUTH[@]}" >/dev/null 2>&1; then
|
|
183
|
+
fail "instance still exists after delete"
|
|
184
|
+
fi
|
|
185
|
+
ok "instance removed from panel"
|
|
186
|
+
|
|
187
|
+
# Via filesystem — optional local verification (works when running on same host)
|
|
188
|
+
if [ -d "$HOME/.jishushell/instances/$INSTANCE_ID" ]; then
|
|
189
|
+
fail "instance directory still exists at $HOME/.jishushell/instances/$INSTANCE_ID"
|
|
190
|
+
fi
|
|
191
|
+
|
|
192
|
+
green ""
|
|
193
|
+
green "════════════════════════════════════════════"
|
|
194
|
+
green " Hermes default onboarding smoke: PASSED"
|
|
195
|
+
green "════════════════════════════════════════════"
|