jishushell 0.4.10 → 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.
Files changed (248) hide show
  1. package/Dockerfile.hermes-slim +193 -0
  2. package/INSTALL-NOTICE +10 -12
  3. package/apps/hermes-container.yaml +35 -0
  4. package/apps/ollama-binary.yaml +164 -0
  5. package/apps/ollama-cpu-container.yaml +37 -0
  6. package/apps/ollama-with-hollama-binary.yaml +159 -0
  7. package/apps/openclaw-binary.yaml +69 -0
  8. package/apps/openclaw-container.yaml +37 -0
  9. package/apps/openclaw-with-ollama-container.yaml +42 -0
  10. package/apps/openclaw-with-searxng-container.yaml +136 -0
  11. package/apps/openwebui-container.yaml +53 -0
  12. package/apps/playwright-container.yaml +120 -0
  13. package/apps/searxng-container.yaml +115 -0
  14. package/dist/auth.d.ts +1 -0
  15. package/dist/auth.js +15 -14
  16. package/dist/auth.js.map +1 -1
  17. package/dist/cli/app.d.ts +4 -0
  18. package/dist/cli/app.js +874 -0
  19. package/dist/cli/app.js.map +1 -0
  20. package/dist/cli/backup.d.ts +3 -0
  21. package/dist/cli/backup.js +434 -0
  22. package/dist/cli/backup.js.map +1 -0
  23. package/dist/{doctor.d.ts → cli/doctor.d.ts} +7 -1
  24. package/dist/{doctor.js → cli/doctor.js} +377 -22
  25. package/dist/cli/doctor.js.map +1 -0
  26. package/dist/cli/helpers.d.ts +4 -0
  27. package/dist/cli/helpers.js +32 -0
  28. package/dist/cli/helpers.js.map +1 -0
  29. package/dist/cli/job.d.ts +4 -0
  30. package/dist/cli/job.js +198 -0
  31. package/dist/cli/job.js.map +1 -0
  32. package/dist/cli/llm.d.ts +25 -0
  33. package/dist/cli/llm.js +599 -0
  34. package/dist/cli/llm.js.map +1 -0
  35. package/dist/cli/managed-list.d.ts +30 -0
  36. package/dist/cli/managed-list.js +129 -0
  37. package/dist/cli/managed-list.js.map +1 -0
  38. package/dist/cli/panel.d.ts +26 -0
  39. package/dist/cli/panel.js +804 -0
  40. package/dist/cli/panel.js.map +1 -0
  41. package/dist/cli/version.d.ts +1 -0
  42. package/dist/cli/version.js +12 -0
  43. package/dist/cli/version.js.map +1 -0
  44. package/dist/cli.js +48 -776
  45. package/dist/cli.js.map +1 -1
  46. package/dist/config.d.ts +69 -0
  47. package/dist/config.js +268 -7
  48. package/dist/config.js.map +1 -1
  49. package/dist/control.d.ts +17 -41
  50. package/dist/control.js +61 -1323
  51. package/dist/control.js.map +1 -1
  52. package/dist/install.d.ts +16 -0
  53. package/dist/install.js +75 -26
  54. package/dist/install.js.map +1 -1
  55. package/dist/routes/agent-apps.d.ts +15 -0
  56. package/dist/routes/agent-apps.js +78 -0
  57. package/dist/routes/agent-apps.js.map +1 -0
  58. package/dist/routes/apps.d.ts +3 -0
  59. package/dist/routes/apps.js +278 -0
  60. package/dist/routes/apps.js.map +1 -0
  61. package/dist/routes/backup.js +3 -3
  62. package/dist/routes/backup.js.map +1 -1
  63. package/dist/routes/instances.d.ts +6 -0
  64. package/dist/routes/instances.js +863 -874
  65. package/dist/routes/instances.js.map +1 -1
  66. package/dist/routes/llm.d.ts +15 -0
  67. package/dist/routes/llm.js +247 -0
  68. package/dist/routes/llm.js.map +1 -0
  69. package/dist/routes/runtime.d.ts +15 -0
  70. package/dist/routes/runtime.js +69 -0
  71. package/dist/routes/runtime.js.map +1 -0
  72. package/dist/routes/setup.js +131 -9
  73. package/dist/routes/setup.js.map +1 -1
  74. package/dist/routes/system.js +56 -9
  75. package/dist/routes/system.js.map +1 -1
  76. package/dist/server.js +107 -7
  77. package/dist/server.js.map +1 -1
  78. package/dist/services/agent-apps/catalog.d.ts +30 -0
  79. package/dist/services/agent-apps/catalog.js +60 -0
  80. package/dist/services/agent-apps/catalog.js.map +1 -0
  81. package/dist/services/agent-apps/index.d.ts +36 -0
  82. package/dist/services/agent-apps/index.js +171 -0
  83. package/dist/services/agent-apps/index.js.map +1 -0
  84. package/dist/services/agent-apps/installers/adapter-probes.d.ts +49 -0
  85. package/dist/services/agent-apps/installers/adapter-probes.js +223 -0
  86. package/dist/services/agent-apps/installers/adapter-probes.js.map +1 -0
  87. package/dist/services/agent-apps/installers/adapter.d.ts +30 -0
  88. package/dist/services/agent-apps/installers/adapter.js +171 -0
  89. package/dist/services/agent-apps/installers/adapter.js.map +1 -0
  90. package/dist/services/agent-apps/installers/registry-probe.d.ts +38 -0
  91. package/dist/services/agent-apps/installers/registry-probe.js +183 -0
  92. package/dist/services/agent-apps/installers/registry-probe.js.map +1 -0
  93. package/dist/services/agent-apps/installers/shell-script.d.ts +47 -0
  94. package/dist/services/agent-apps/installers/shell-script.js +471 -0
  95. package/dist/services/agent-apps/installers/shell-script.js.map +1 -0
  96. package/dist/services/agent-apps/types.d.ts +125 -0
  97. package/dist/services/agent-apps/types.js +17 -0
  98. package/dist/services/agent-apps/types.js.map +1 -0
  99. package/dist/services/app/app-compiler.d.ts +15 -0
  100. package/dist/services/app/app-compiler.js +172 -0
  101. package/dist/services/app/app-compiler.js.map +1 -0
  102. package/dist/services/app/app-manager.d.ts +142 -0
  103. package/dist/services/app/app-manager.js +2148 -0
  104. package/dist/services/app/app-manager.js.map +1 -0
  105. package/dist/services/app/custom-manager.d.ts +27 -0
  106. package/dist/services/app/custom-manager.js +285 -0
  107. package/dist/services/app/custom-manager.js.map +1 -0
  108. package/dist/services/app/hermes-agent-manager.d.ts +20 -0
  109. package/dist/services/app/hermes-agent-manager.js +289 -0
  110. package/dist/services/app/hermes-agent-manager.js.map +1 -0
  111. package/dist/services/app/id-normalizer.d.ts +27 -0
  112. package/dist/services/app/id-normalizer.js +77 -0
  113. package/dist/services/app/id-normalizer.js.map +1 -0
  114. package/dist/services/app/ollama-manager.d.ts +18 -0
  115. package/dist/services/app/ollama-manager.js +207 -0
  116. package/dist/services/app/ollama-manager.js.map +1 -0
  117. package/dist/services/app/openclaw-manager.d.ts +63 -0
  118. package/dist/services/app/openclaw-manager.js +1178 -0
  119. package/dist/services/app/openclaw-manager.js.map +1 -0
  120. package/dist/services/app/paths.d.ts +47 -0
  121. package/dist/services/app/paths.js +68 -0
  122. package/dist/services/app/paths.js.map +1 -0
  123. package/dist/services/app/registry.d.ts +17 -0
  124. package/dist/services/app/registry.js +31 -0
  125. package/dist/services/app/registry.js.map +1 -0
  126. package/dist/services/app/remote-spec.d.ts +14 -0
  127. package/dist/services/app/remote-spec.js +58 -0
  128. package/dist/services/app/remote-spec.js.map +1 -0
  129. package/dist/services/app/terminal-session-manager.d.ts +27 -0
  130. package/dist/services/app/terminal-session-manager.js +157 -0
  131. package/dist/services/app/terminal-session-manager.js.map +1 -0
  132. package/dist/services/app/types.d.ts +72 -0
  133. package/dist/services/app/types.js +16 -0
  134. package/dist/services/app/types.js.map +1 -0
  135. package/dist/services/backup-manager.js +60 -22
  136. package/dist/services/backup-manager.js.map +1 -1
  137. package/dist/services/instance-manager.d.ts +125 -34
  138. package/dist/services/instance-manager.js +679 -1043
  139. package/dist/services/instance-manager.js.map +1 -1
  140. package/dist/services/llm-proxy/adapters.js +5 -1
  141. package/dist/services/llm-proxy/adapters.js.map +1 -1
  142. package/dist/services/llm-proxy/circuit-breaker.js +10 -2
  143. package/dist/services/llm-proxy/circuit-breaker.js.map +1 -1
  144. package/dist/services/llm-proxy/index.d.ts +43 -0
  145. package/dist/services/llm-proxy/index.js +120 -5
  146. package/dist/services/llm-proxy/index.js.map +1 -1
  147. package/dist/services/llm-proxy/ssrf.js +1 -1
  148. package/dist/services/llm-proxy/ssrf.js.map +1 -1
  149. package/dist/services/nomad-manager.d.ts +260 -3
  150. package/dist/services/nomad-manager.js +2921 -341
  151. package/dist/services/nomad-manager.js.map +1 -1
  152. package/dist/services/panel-manager.d.ts +50 -0
  153. package/dist/services/panel-manager.js +443 -0
  154. package/dist/services/panel-manager.js.map +1 -0
  155. package/dist/services/plugin-installer.js +28 -2
  156. package/dist/services/plugin-installer.js.map +1 -1
  157. package/dist/services/process-manager.js +42 -7
  158. package/dist/services/process-manager.js.map +1 -1
  159. package/dist/services/runtime/adapters/custom.d.ts +20 -0
  160. package/dist/services/runtime/adapters/custom.js +90 -0
  161. package/dist/services/runtime/adapters/custom.js.map +1 -0
  162. package/dist/services/runtime/adapters/hermes.d.ts +174 -0
  163. package/dist/services/runtime/adapters/hermes.js +1316 -0
  164. package/dist/services/runtime/adapters/hermes.js.map +1 -0
  165. package/dist/services/runtime/adapters/openclaw-routes.d.ts +17 -0
  166. package/dist/services/runtime/adapters/openclaw-routes.js +946 -0
  167. package/dist/services/runtime/adapters/openclaw-routes.js.map +1 -0
  168. package/dist/services/runtime/adapters/openclaw.d.ts +188 -0
  169. package/dist/services/runtime/adapters/openclaw.js +2195 -0
  170. package/dist/services/runtime/adapters/openclaw.js.map +1 -0
  171. package/dist/services/runtime/errors.d.ts +28 -0
  172. package/dist/services/runtime/errors.js +31 -0
  173. package/dist/services/runtime/errors.js.map +1 -0
  174. package/dist/services/runtime/index.d.ts +34 -0
  175. package/dist/services/runtime/index.js +51 -0
  176. package/dist/services/runtime/index.js.map +1 -0
  177. package/dist/services/runtime/instance.d.ts +24 -0
  178. package/dist/services/runtime/instance.js +143 -0
  179. package/dist/services/runtime/instance.js.map +1 -0
  180. package/dist/services/runtime/migrations.d.ts +15 -0
  181. package/dist/services/runtime/migrations.js +25 -0
  182. package/dist/services/runtime/migrations.js.map +1 -0
  183. package/dist/services/runtime/registry.d.ts +13 -0
  184. package/dist/services/runtime/registry.js +32 -0
  185. package/dist/services/runtime/registry.js.map +1 -0
  186. package/dist/services/runtime/types.d.ts +545 -0
  187. package/dist/services/runtime/types.js +14 -0
  188. package/dist/services/runtime/types.js.map +1 -0
  189. package/dist/services/setup-manager.d.ts +70 -29
  190. package/dist/services/setup-manager.js +591 -625
  191. package/dist/services/setup-manager.js.map +1 -1
  192. package/dist/services/task-registry.d.ts +44 -0
  193. package/dist/services/task-registry.js +74 -0
  194. package/dist/services/task-registry.js.map +1 -0
  195. package/dist/services/telemetry/heartbeat.d.ts +6 -6
  196. package/dist/services/telemetry/heartbeat.js +29 -30
  197. package/dist/services/telemetry/heartbeat.js.map +1 -1
  198. package/dist/services/update-manager.d.ts +47 -0
  199. package/dist/services/update-manager.js +305 -0
  200. package/dist/services/update-manager.js.map +1 -0
  201. package/dist/types.d.ts +224 -0
  202. package/dist/utils/docker-host.d.ts +15 -0
  203. package/dist/utils/docker-host.js +64 -0
  204. package/dist/utils/docker-host.js.map +1 -0
  205. package/install/jishu-install.sh +303 -38
  206. package/install/post-install.sh +64 -5
  207. package/package.json +19 -5
  208. package/public/assets/Dashboard-rh9qpYRR.js +1 -0
  209. package/public/assets/HermesChatPanel-D6JI6lLY.js +1 -0
  210. package/public/assets/HermesConfigForm-DcbSemaj.js +4 -0
  211. package/public/assets/InitPassword-CFTKsED4.js +1 -0
  212. package/public/assets/InstanceDetail-BhNIKA6Z.js +91 -0
  213. package/public/assets/{Login-CUoEZOWR.js → Login-KB9qrtM0.js} +1 -1
  214. package/public/assets/NewInstance-CxkO8Hlq.js +1 -0
  215. package/public/assets/Settings-BVWJvOkU.js +1 -0
  216. package/public/assets/Setup-X-lzuaUT.js +1 -0
  217. package/public/assets/WeixinLoginPanel-gca0QTic.js +9 -0
  218. package/public/assets/index-C8B0cFJM.js +19 -0
  219. package/public/assets/index-CPhVFEsx.css +1 -0
  220. package/public/assets/input-paste-CrNVAyOy.js +1 -0
  221. package/public/assets/{providers-lBSOjUWy.js → providers-V-vwrExZ.js} +1 -1
  222. package/public/assets/registry-fVUSujib.js +2 -0
  223. package/public/assets/{usePolling-CK0DfI4h.js → usePolling-Do5Erqm_.js} +1 -1
  224. package/public/assets/vendor-i18n-ucpM0OR0.js +9 -0
  225. package/public/assets/{vendor-react-B1-3Yrt-.js → vendor-react-Bk1hRGiY.js} +1 -1
  226. package/public/favicon.png +0 -0
  227. package/public/index.html +9 -4
  228. package/public/logos/hermes.png +0 -0
  229. package/public/logos/ollama.png +0 -0
  230. package/public/logos/openclaw.svg +60 -0
  231. package/scripts/build-hermes-image.sh +21 -0
  232. package/scripts/build-local.sh +54 -0
  233. package/scripts/check-adapter-isolation.ts +293 -0
  234. package/scripts/fixtures/instances/hermes-sample/instance.json +37 -0
  235. package/scripts/fixtures/instances/legacy-openclaw-sample/instance.json +7 -0
  236. package/scripts/smoke/hermes-bootstrap.sh +195 -0
  237. package/templates/hermes-entrypoint.sh +154 -0
  238. package/dist/doctor.js.map +0 -1
  239. package/install/jishu-install-china.sh +0 -3092
  240. package/public/assets/Dashboard-DhsrzJ4F.js +0 -1
  241. package/public/assets/InitPassword-BjubiVdd.js +0 -1
  242. package/public/assets/InstanceDetail-DMcywsof.js +0 -17
  243. package/public/assets/NewInstance-Bk0G4EiJ.js +0 -1
  244. package/public/assets/Settings-D5tHL_h5.js +0 -1
  245. package/public/assets/Setup-4t6E3Rut.js +0 -1
  246. package/public/assets/index-BJ47MWpF.css +0 -1
  247. package/public/assets/index-DbX85irc.js +0 -16
  248. 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 "════════════════════════════════════════════"