jishushell 0.5.15 → 0.5.22

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 (97) hide show
  1. package/Dockerfile.hermes-slim +2 -5
  2. package/apps/filebrowser-container.yaml +1 -0
  3. package/apps/ollama-binary.yaml +44 -0
  4. package/apps/ollama-with-hollama-binary.yaml +45 -1
  5. package/dist/cli/doctor.js +144 -16
  6. package/dist/cli/doctor.js.map +1 -1
  7. package/dist/install.js +1 -1
  8. package/dist/install.js.map +1 -1
  9. package/dist/routes/instances.js +42 -5
  10. package/dist/routes/instances.js.map +1 -1
  11. package/dist/routes/llm.js +29 -0
  12. package/dist/routes/llm.js.map +1 -1
  13. package/dist/server.js +18 -4
  14. package/dist/server.js.map +1 -1
  15. package/dist/services/agent-apps/catalog.d.ts +3 -0
  16. package/dist/services/agent-apps/catalog.js +40 -13
  17. package/dist/services/agent-apps/catalog.js.map +1 -1
  18. package/dist/services/agent-apps/installers/shell-script.d.ts +1 -1
  19. package/dist/services/agent-apps/installers/shell-script.js +19 -2
  20. package/dist/services/agent-apps/installers/shell-script.js.map +1 -1
  21. package/dist/services/agent-apps/types.d.ts +3 -0
  22. package/dist/services/app/app-manager.d.ts +8 -0
  23. package/dist/services/app/app-manager.js +77 -3
  24. package/dist/services/app/app-manager.js.map +1 -1
  25. package/dist/services/app/openclaw-manager.js +17 -2
  26. package/dist/services/app/openclaw-manager.js.map +1 -1
  27. package/dist/services/backup-manager.js +43 -4
  28. package/dist/services/backup-manager.js.map +1 -1
  29. package/dist/services/capability-endpoint-validator.js +26 -7
  30. package/dist/services/capability-endpoint-validator.js.map +1 -1
  31. package/dist/services/instance-manager.js +89 -9
  32. package/dist/services/instance-manager.js.map +1 -1
  33. package/dist/services/llm-proxy/index.d.ts +28 -0
  34. package/dist/services/llm-proxy/index.js +76 -3
  35. package/dist/services/llm-proxy/index.js.map +1 -1
  36. package/dist/services/llm-proxy/validate-key.d.ts +41 -0
  37. package/dist/services/llm-proxy/validate-key.js +672 -0
  38. package/dist/services/llm-proxy/validate-key.js.map +1 -0
  39. package/dist/services/macos-launchd.d.ts +89 -0
  40. package/dist/services/macos-launchd.js +273 -0
  41. package/dist/services/macos-launchd.js.map +1 -0
  42. package/dist/services/nomad-manager.d.ts +7 -0
  43. package/dist/services/nomad-manager.js +290 -79
  44. package/dist/services/nomad-manager.js.map +1 -1
  45. package/dist/services/panel-manager.js +20 -10
  46. package/dist/services/panel-manager.js.map +1 -1
  47. package/dist/services/runtime/adapters/custom.js +56 -0
  48. package/dist/services/runtime/adapters/custom.js.map +1 -1
  49. package/dist/services/runtime/adapters/hermes.d.ts +4 -3
  50. package/dist/services/runtime/adapters/hermes.js +165 -63
  51. package/dist/services/runtime/adapters/hermes.js.map +1 -1
  52. package/dist/services/runtime/adapters/openclaw.d.ts +28 -0
  53. package/dist/services/runtime/adapters/openclaw.js +502 -4
  54. package/dist/services/runtime/adapters/openclaw.js.map +1 -1
  55. package/dist/services/setup-manager.js +97 -50
  56. package/dist/services/setup-manager.js.map +1 -1
  57. package/dist/services/update-manager.js +32 -14
  58. package/dist/services/update-manager.js.map +1 -1
  59. package/dist/types.d.ts +1 -0
  60. package/install/jishu-install.sh +247 -35
  61. package/install/jishu-uninstall.sh +45 -5
  62. package/package.json +5 -2
  63. package/public/assets/ApiKeyField-CvyAOcJS.js +1 -0
  64. package/public/assets/Dashboard-AuJESBlJ.js +1 -0
  65. package/public/assets/{HermesChatPanel-B_2HlVBQ.js → HermesChatPanel-CByPREwb.js} +1 -1
  66. package/public/assets/HermesConfigForm-DRda8FKX.js +4 -0
  67. package/public/assets/InitPassword-ka4wNpM5.js +1 -0
  68. package/public/assets/InstanceDetail-Cg1nS8HX.js +92 -0
  69. package/public/assets/Login-aPajuQzf.js +1 -0
  70. package/public/assets/NewInstance-Dd1ebNIx.js +1 -0
  71. package/public/assets/ProviderRecommendations-DFmADQ7V.js +1 -0
  72. package/public/assets/Settings-BYQnbLYL.js +1 -0
  73. package/public/assets/Setup-D05lwDOV.js +1 -0
  74. package/public/assets/WeixinLoginPanel-D89kdhP4.js +9 -0
  75. package/public/assets/index-HSXCsceK.css +1 -0
  76. package/public/assets/{index-BZc5zH7u.js → index-bnBu0nlQ.js} +7 -7
  77. package/public/assets/registry-C_qeFTkZ.js +2 -0
  78. package/public/assets/usePolling-Bn93fe7M.js +1 -0
  79. package/public/assets/{vendor-i18n-y9V7Sfuu.js → vendor-i18n-flxcMVeP.js} +2 -2
  80. package/public/assets/{vendor-react-BWrEVJVb.js → vendor-react-ZC5T_huj.js} +1 -1
  81. package/public/index.html +4 -4
  82. package/scripts/check-colima-launchd.mjs +230 -0
  83. package/public/assets/Dashboard-BdWPtroF.js +0 -1
  84. package/public/assets/HermesConfigForm-DVlhg3WV.js +0 -4
  85. package/public/assets/InitPassword-D7glTExX.js +0 -1
  86. package/public/assets/InstanceDetail-CxSy2cpe.js +0 -92
  87. package/public/assets/Login-Cfr5c2sv.js +0 -1
  88. package/public/assets/NewInstance-BIYDmJis.js +0 -1
  89. package/public/assets/ProviderRecommendations-BuRnvRcI.js +0 -1
  90. package/public/assets/Settings-Cc-tYBil.js +0 -1
  91. package/public/assets/Setup-lGZEk5jq.js +0 -1
  92. package/public/assets/WeixinLoginPanel-CoGqzxeV.js +0 -9
  93. package/public/assets/index-87IJXG-w.css +0 -1
  94. package/public/assets/input-paste-CrNVAyOy.js +0 -1
  95. package/public/assets/providers-DtNXh9JD.js +0 -1
  96. package/public/assets/registry-BWnkJgZ1.js +0 -2
  97. package/public/assets/usePolling-CwwT9KrC.js +0 -1
@@ -178,11 +178,8 @@ RUN mkdir -p /opt/data && \
178
178
  COPY templates/hermes-entrypoint.sh /usr/local/bin/jishushell-hermes-entry.sh
179
179
  RUN chmod 0755 /usr/local/bin/jishushell-hermes-entry.sh
180
180
 
181
- # Advertise the protocol contract this image satisfies. The panel reads
182
- # this label via `docker inspect` before launching an instance; a panel
183
- # that only knows protocol version N refuses to start an image that
184
- # declares N+1, which is how we gate rolling upgrades without silent
185
- # behavioral drift.
181
+ # Retained as build metadata for operators and registry inspection. Runtime
182
+ # readiness is determined from the official ghcr.io RepoDigest, not this label.
186
183
  LABEL runtime.protocol.version="1"
187
184
 
188
185
  # Zombie reaping is handled by Nomad's `init: true` flag (docker-init
@@ -78,6 +78,7 @@ provides:
78
78
  task: "filebrowser"
79
79
  port: 18088
80
80
  path: "/"
81
+ embedded: "proxy"
81
82
  protocol: "http"
82
83
  visibility: "external"
83
84
  description: "Filebrowser Web UI;panel 反代到 /apps/filebrowser/*"
@@ -106,6 +106,28 @@ lifecycle:
106
106
  owner_marker="$HOME/.jishushell/.ollama-installed-by-jishushell-${app_id}";
107
107
  legacy_owner_marker="$HOME/.jishushell/apps/${app_id}/.ollama-installed-by-jishushell";
108
108
  system_install_marker="$HOME/.jishushell/.ollama-system-install-${app_id}";
109
+ if [ "$(uname -s)" = "Linux" ] && ! command -v zstd >/dev/null 2>&1; then
110
+ echo "未检测到 zstd;Ollama v0.24+ 安装包仅提供 .tar.zst,需先安装 zstd..." >&2;
111
+ pm_rc=1;
112
+ if command -v apt-get >/dev/null 2>&1; then
113
+ DEBIAN_FRONTEND=noninteractive apt-get update -qq || true;
114
+ DEBIAN_FRONTEND=noninteractive apt-get install -y zstd && pm_rc=0;
115
+ elif command -v dnf >/dev/null 2>&1; then
116
+ dnf install -y zstd && pm_rc=0;
117
+ elif command -v yum >/dev/null 2>&1; then
118
+ yum install -y zstd && pm_rc=0;
119
+ elif command -v apk >/dev/null 2>&1; then
120
+ apk add --no-cache zstd && pm_rc=0;
121
+ elif command -v pacman >/dev/null 2>&1; then
122
+ pacman -Sy --noconfirm zstd && pm_rc=0;
123
+ else
124
+ echo "ERROR: 未找到受支持的包管理器(apt/dnf/yum/apk/pacman)来安装 zstd" >&2;
125
+ fi;
126
+ if [ "$pm_rc" != "0" ] || ! command -v zstd >/dev/null 2>&1; then
127
+ echo "ERROR: 自动安装 zstd 失败;请手动安装后重试。" >&2;
128
+ exit 1;
129
+ fi;
130
+ fi;
109
131
  for BINDIR in /usr/local/bin /usr/bin /bin; do
110
132
  echo "$PATH" | grep -q "$BINDIR" && break || continue;
111
133
  done;
@@ -117,6 +139,10 @@ lifecycle:
117
139
  ( rm -rf "$old_dir" >/dev/null 2>&1 || true ) &
118
140
  fi;
119
141
  curl -fsSL https://ollama.com/install.sh | sh;
142
+ if ! command -v ollama >/dev/null 2>&1; then
143
+ echo "ERROR: 安装脚本结束后仍未找到 ollama 命令;请检查网络、磁盘空间或 zstd 是否可用。" >&2;
144
+ exit 1;
145
+ fi;
120
146
  rm -f "$install_marker";
121
147
  rm -f "$legacy_owner_marker";
122
148
  rm -f "$system_install_marker";
@@ -138,6 +164,18 @@ lifecycle:
138
164
  : > "$owner_marker";
139
165
  fi
140
166
  ifFileExists: "~/.jishushell/apps/${app_id}/.ollama-install-needed"
167
+ - run: >-
168
+ if [ -e /usr/local/bin/ollama ]; then
169
+ exit 0;
170
+ fi;
171
+ if ! command -v ollama >/dev/null 2>&1; then
172
+ exit 0;
173
+ fi;
174
+ real_ollama="$(command -v ollama)";
175
+ echo "检测到 ollama 位于 $real_ollama 但 /usr/local/bin/ollama 不存在;建立兼容符号链接(用于 Apple Silicon brew 等场景)..." >&2;
176
+ mkdir -p /usr/local/bin;
177
+ ln -sf "$real_ollama" /usr/local/bin/ollama
178
+ sudo: true
141
179
  - run: >-
142
180
  service_active_marker="$HOME/.jishushell/apps/${app_id}/.ollama-service-active";
143
181
  rm -f "$service_active_marker";
@@ -198,4 +236,10 @@ lifecycle:
198
236
  rm -f "$owner_marker" "$legacy_owner_marker" "$system_install_marker" "$uninstall_marker"
199
237
  sudo: true
200
238
  ifFileExists: "~/.jishushell/.ollama-uninstall-needed-${app_id}"
239
+ - run: >-
240
+ if [ -L /usr/local/bin/ollama ]; then
241
+ echo "清理由 JishuShell 建立的兼容性符号链接 /usr/local/bin/ollama" >&2;
242
+ rm -f /usr/local/bin/ollama;
243
+ fi
244
+ sudo: true
201
245
 
@@ -102,6 +102,28 @@ lifecycle:
102
102
  owner_marker="$HOME/.jishushell/.ollama-installed-by-jishushell-${app_id}";
103
103
  legacy_owner_marker="$HOME/.jishushell/apps/${app_id}/.ollama-installed-by-jishushell";
104
104
  system_install_marker="$HOME/.jishushell/.ollama-system-install-${app_id}";
105
+ if [ "$(uname -s)" = "Linux" ] && ! command -v zstd >/dev/null 2>&1; then
106
+ echo "未检测到 zstd;Ollama v0.24+ 安装包仅提供 .tar.zst,需先安装 zstd..." >&2;
107
+ pm_rc=1;
108
+ if command -v apt-get >/dev/null 2>&1; then
109
+ DEBIAN_FRONTEND=noninteractive apt-get update -qq || true;
110
+ DEBIAN_FRONTEND=noninteractive apt-get install -y zstd && pm_rc=0;
111
+ elif command -v dnf >/dev/null 2>&1; then
112
+ dnf install -y zstd && pm_rc=0;
113
+ elif command -v yum >/dev/null 2>&1; then
114
+ yum install -y zstd && pm_rc=0;
115
+ elif command -v apk >/dev/null 2>&1; then
116
+ apk add --no-cache zstd && pm_rc=0;
117
+ elif command -v pacman >/dev/null 2>&1; then
118
+ pacman -Sy --noconfirm zstd && pm_rc=0;
119
+ else
120
+ echo "ERROR: 未找到受支持的包管理器(apt/dnf/yum/apk/pacman)来安装 zstd" >&2;
121
+ fi;
122
+ if [ "$pm_rc" != "0" ] || ! command -v zstd >/dev/null 2>&1; then
123
+ echo "ERROR: 自动安装 zstd 失败;请手动安装后重试。" >&2;
124
+ exit 1;
125
+ fi;
126
+ fi;
105
127
  for BINDIR in /usr/local/bin /usr/bin /bin; do
106
128
  echo "$PATH" | grep -q "$BINDIR" && break || continue;
107
129
  done;
@@ -113,6 +135,10 @@ lifecycle:
113
135
  ( rm -rf "$old_dir" >/dev/null 2>&1 || true ) &
114
136
  fi;
115
137
  curl -fsSL https://ollama.com/install.sh | sh;
138
+ if ! command -v ollama >/dev/null 2>&1; then
139
+ echo "ERROR: 安装脚本结束后仍未找到 ollama 命令;请检查网络、磁盘空间或 zstd 是否可用。" >&2;
140
+ exit 1;
141
+ fi;
116
142
  rm -f "$install_marker";
117
143
  rm -f "$legacy_owner_marker";
118
144
  rm -f "$system_install_marker";
@@ -134,6 +160,18 @@ lifecycle:
134
160
  : > "$owner_marker";
135
161
  fi
136
162
  ifFileExists: "~/.jishushell/apps/${app_id}/.ollama-install-needed"
163
+ - run: >-
164
+ if [ -e /usr/local/bin/ollama ]; then
165
+ exit 0;
166
+ fi;
167
+ if ! command -v ollama >/dev/null 2>&1; then
168
+ exit 0;
169
+ fi;
170
+ real_ollama="$(command -v ollama)";
171
+ echo "检测到 ollama 位于 $real_ollama 但 /usr/local/bin/ollama 不存在;建立兼容符号链接(用于 Apple Silicon brew 等场景)..." >&2;
172
+ mkdir -p /usr/local/bin;
173
+ ln -sf "$real_ollama" /usr/local/bin/ollama
174
+ sudo: true
137
175
  - run: >-
138
176
  service_active_marker="$HOME/.jishushell/apps/${app_id}/.ollama-service-active";
139
177
  rm -f "$service_active_marker";
@@ -193,4 +231,10 @@ lifecycle:
193
231
  rm -rf /Applications/Ollama.app "$HOME/Applications/Ollama.app" 2>/dev/null || true;
194
232
  rm -f "$owner_marker" "$legacy_owner_marker" "$system_install_marker" "$uninstall_marker"
195
233
  sudo: true
196
- ifFileExists: "~/.jishushell/.ollama-uninstall-needed-${app_id}"
234
+ ifFileExists: "~/.jishushell/.ollama-uninstall-needed-${app_id}"
235
+ - run: >-
236
+ if [ -L /usr/local/bin/ollama ]; then
237
+ echo "清理由 JishuShell 建立的兼容性符号链接 /usr/local/bin/ollama" >&2;
238
+ rm -f /usr/local/bin/ollama;
239
+ fi
240
+ sudo: true
@@ -20,7 +20,7 @@ import { randomBytes } from "crypto";
20
20
  import { existsSync, readFileSync, unlinkSync, } from "fs";
21
21
  import * as http from "http";
22
22
  import { homedir } from "os";
23
- import { join } from "path";
23
+ import { dirname, join } from "path";
24
24
  import { AUTH_FILE, DEFAULT_OPENCLAW_DOCKER_IMAGE, INSTANCES_DIR, JISHUSHELL_HOME, PANEL_CONFIG_FILE, ensureDirs, getNomadToken, getPanelConfig, } from "../config.js";
25
25
  import { buildDockerClientEnv, managedColimaDockerHost } from "../utils/docker-host.js";
26
26
  import { loadNomadToken } from "../services/setup-manager.js";
@@ -61,6 +61,37 @@ function detectNomadExternalInterface(loopbackIface) {
61
61
  }
62
62
  }
63
63
  // ── Internal helpers ────────────────────────────────────────────────────────
64
+ const MACOS_EXEC_PATH_ENTRIES = [
65
+ "/opt/homebrew/bin",
66
+ "/opt/homebrew/sbin",
67
+ "/usr/local/bin",
68
+ "/usr/local/sbin",
69
+ "/usr/bin",
70
+ "/bin",
71
+ "/usr/sbin",
72
+ "/sbin",
73
+ ];
74
+ function buildDeterministicPath(basePath, extraEntries = []) {
75
+ return [...extraEntries, basePath ?? "", ...MACOS_EXEC_PATH_ENTRIES]
76
+ .flatMap((entry) => entry.split(":"))
77
+ .map((entry) => entry.trim())
78
+ .filter(Boolean)
79
+ .filter((entry, index, entries) => entries.indexOf(entry) === index)
80
+ .join(":");
81
+ }
82
+ function buildMacosColimaEnv(colimaHome) {
83
+ return {
84
+ ...process.env,
85
+ COLIMA_HOME: colimaHome,
86
+ PATH: buildDeterministicPath(process.env.PATH, [dirname(process.execPath)]),
87
+ };
88
+ }
89
+ function buildMacosDockerEnv() {
90
+ return {
91
+ ...process.env,
92
+ PATH: buildDeterministicPath(process.env.PATH, [dirname(process.execPath)]),
93
+ };
94
+ }
64
95
  function httpGet(url, timeoutMs = 3000) {
65
96
  return new Promise((resolve, reject) => {
66
97
  const req = http.get(url, { timeout: timeoutMs }, (res) => {
@@ -93,15 +124,25 @@ function httpGetWithHeaders(url, headers, timeoutMs = 3000) {
93
124
  req.end();
94
125
  });
95
126
  }
96
- function commandExists(cmd) {
127
+ function commandExists(cmd, env = process.env) {
97
128
  try {
98
- execFileSync("which", [cmd], { stdio: "ignore", timeout: 2000 });
129
+ execFileSync("which", [cmd], { stdio: "ignore", timeout: 2000, env });
99
130
  return true;
100
131
  }
101
132
  catch {
102
133
  return false;
103
134
  }
104
135
  }
136
+ function buildDoctorDockerEnv() {
137
+ const dockerEnv = buildDockerClientEnv({ jishuHome: JISHUSHELL_HOME });
138
+ if (process.platform !== "darwin")
139
+ return dockerEnv;
140
+ return {
141
+ ...buildMacosDockerEnv(),
142
+ ...dockerEnv,
143
+ PATH: buildDeterministicPath(process.env.PATH, [dirname(process.execPath)]),
144
+ };
145
+ }
105
146
  function isPortListening(port) {
106
147
  try {
107
148
  if (process.platform === "darwin") {
@@ -150,7 +191,8 @@ const checkNode = {
150
191
  const checkDockerBin = {
151
192
  id: "docker-bin", label: "Docker 已安装", category: "系统环境", severity: "error",
152
193
  async check() {
153
- if (commandExists("docker"))
194
+ const env = process.platform === "darwin" ? buildMacosDockerEnv() : process.env;
195
+ if (commandExists("docker", env))
154
196
  return { ok: true, detail: "docker 命令可用" };
155
197
  return { ok: false, detail: "未找到 docker 命令", hint: "curl -fsSL https://get.docker.com | sh", fixable: false };
156
198
  },
@@ -158,7 +200,7 @@ const checkDockerBin = {
158
200
  const checkDockerDaemon = {
159
201
  id: "docker-daemon", label: "Docker 守护进程", category: "系统环境", severity: "error",
160
202
  async check() {
161
- const env = buildDockerClientEnv({ jishuHome: JISHUSHELL_HOME });
203
+ const env = buildDoctorDockerEnv();
162
204
  try {
163
205
  execFileSync("docker", ["info"], { stdio: "ignore", timeout: 6000, env });
164
206
  const ver = execSync("docker --version 2>/dev/null", { encoding: "utf-8", timeout: 3000, env }).trim();
@@ -172,7 +214,13 @@ const checkDockerDaemon = {
172
214
  catch { }
173
215
  if (process.platform === "darwin") {
174
216
  const colimaDir = join(JISHUSHELL_HOME, "colima");
175
- return { ok: false, detail: "Docker 守护进程未运行", hint: `启动 Docker Desktop,或执行 COLIMA_HOME=${colimaDir} colima start jishushell`, fixable: true };
217
+ const colimaPath = buildDeterministicPath(process.env.PATH, [dirname(process.execPath)]);
218
+ return {
219
+ ok: false,
220
+ detail: "Docker 守护进程未运行",
221
+ hint: `启动 Docker Desktop,或执行 PATH=${colimaPath} COLIMA_HOME=${colimaDir} colima start jishushell`,
222
+ fixable: true,
223
+ };
176
224
  }
177
225
  return { ok: false, detail: "Docker 守护进程未运行", hint: "sudo systemctl start docker", fixable: true };
178
226
  },
@@ -180,8 +228,11 @@ const checkDockerDaemon = {
180
228
  if (process.platform === "darwin") {
181
229
  try {
182
230
  const colimaDir = join(JISHUSHELL_HOME, "colima");
183
- const colimaEnv = { ...process.env, COLIMA_HOME: colimaDir };
184
- const dockerEnv = { ...process.env, DOCKER_HOST: managedColimaDockerHost(JISHUSHELL_HOME) };
231
+ const colimaEnv = buildMacosColimaEnv(colimaDir);
232
+ const dockerEnv = {
233
+ ...buildMacosDockerEnv(),
234
+ DOCKER_HOST: managedColimaDockerHost(JISHUSHELL_HOME),
235
+ };
185
236
  execFileSync("mkdir", ["-p", colimaDir], { timeout: 5000 });
186
237
  // `--network-host-addresses` (added upstream as b8e7dd5) is the
187
238
  // load-bearing flag on macOS: without it Colima won't expose Mac
@@ -231,10 +282,11 @@ const checkDockerDaemon = {
231
282
  const checkDockerGroup = {
232
283
  id: "docker-group", label: "Docker 用户权限", category: "系统环境", severity: "warning",
233
284
  async check() {
234
- if (!commandExists("docker"))
285
+ const env = process.platform === "darwin" ? buildDoctorDockerEnv() : process.env;
286
+ if (!commandExists("docker", env))
235
287
  return { ok: true, detail: "跳过(docker 未安装)" };
236
288
  try {
237
- execFileSync("docker", ["info"], { stdio: "ignore", timeout: 4000 });
289
+ execFileSync("docker", ["info"], { stdio: "ignore", timeout: 4000, env });
238
290
  return { ok: true, detail: "无需 sudo 即可使用 Docker" };
239
291
  }
240
292
  catch { }
@@ -652,6 +704,9 @@ ${externalHostNetworkBlock}
652
704
  plugin "docker" {
653
705
  config {
654
706
  disable_log_collection = true
707
+ gc {
708
+ image = false
709
+ }
655
710
  volumes {
656
711
  enabled = true
657
712
  }
@@ -900,12 +955,34 @@ const checkNomadJobs = {
900
955
  const jobs = JSON.parse(res.body);
901
956
  if (jobs.length === 0)
902
957
  return { ok: true, detail: "暂无 Job(集群为空)" };
903
- const unhealthy = jobs.filter((j) => j.Status !== "running");
904
- const extras = jobs.map((j, idx) => {
905
- const branch = idx === jobs.length - 1 ? "└─" : "├─";
906
- const icon = j.Status === "running" ? c.green("✓") : c.yellow("!");
907
- const status = j.Status === "running" ? c.green(j.Status) : c.yellow(j.Status);
908
- return c.dim(` ${branch} ${icon} ${j.ID.padEnd(26)}`) + " " + status + c.dim(` [${j.Type}]`);
958
+ const runtimeJobs = await Promise.all(jobs.map(async (job) => {
959
+ try {
960
+ const allocRes = token
961
+ ? await httpGetWithHeaders(`${nomadAddr}/v1/job/${encodeURIComponent(job.ID)}/allocations`, { "X-Nomad-Token": token }, 5000)
962
+ : await httpGet(`${nomadAddr}/v1/job/${encodeURIComponent(job.ID)}/allocations`, 5000);
963
+ if (allocRes.status !== 200) {
964
+ return { ...job, runtimeStatus: job.Status, runtimeHint: `alloc HTTP ${allocRes.status}` };
965
+ }
966
+ const allocs = JSON.parse(allocRes.body);
967
+ const running = allocs.find((alloc) => alloc.ClientStatus === "running");
968
+ if (running)
969
+ return { ...job, runtimeStatus: "running", runtimeHint: "" };
970
+ const latest = [...allocs].sort((a, b) => ((b.ModifyIndex ?? b.CreateIndex ?? 0) - (a.ModifyIndex ?? a.CreateIndex ?? 0)))[0];
971
+ const runtimeStatus = latest?.ClientStatus || job.Status || "unknown";
972
+ const runtimeHint = runtimeStatus !== job.Status ? `job=${job.Status}` : "";
973
+ return { ...job, runtimeStatus, runtimeHint };
974
+ }
975
+ catch (e) {
976
+ return { ...job, runtimeStatus: job.Status || "unknown", runtimeHint: e?.message || "alloc query failed" };
977
+ }
978
+ }));
979
+ const unhealthy = runtimeJobs.filter((j) => j.runtimeStatus !== "running");
980
+ const extras = runtimeJobs.map((j, idx) => {
981
+ const branch = idx === runtimeJobs.length - 1 ? "└─" : "├─";
982
+ const icon = j.runtimeStatus === "running" ? c.green("✓") : c.yellow("!");
983
+ const status = j.runtimeStatus === "running" ? c.green(j.runtimeStatus) : c.yellow(j.runtimeStatus);
984
+ const hint = j.runtimeHint ? `, ${j.runtimeHint}` : "";
985
+ return c.dim(` ${branch} ${icon} ${j.ID.padEnd(26)}`) + " " + status + c.dim(` [${j.Type}${hint}]`);
909
986
  });
910
987
  if (unhealthy.length > 0)
911
988
  return { ok: false, detail: `${jobs.length} 个 job,${unhealthy.length} 个非 running 状态`, extras };
@@ -1083,6 +1160,54 @@ const checkNetwork = {
1083
1160
  return { ok: false, detail: "无法连通外网(ping 及 DNS 均失败)", hint: "检查网络: ip route && cat /etc/resolv.conf", fixable: false };
1084
1161
  },
1085
1162
  };
1163
+ const COLIMA_WRAPPER = join(JISHUSHELL_HOME, "bin", "colima-launchd-wrapper.sh");
1164
+ const COLIMA_PLIST = join(homedir(), "Library/LaunchAgents/com.jishushell.colima.plist");
1165
+ const checkColimaLaunchd = {
1166
+ id: "colima-launchd", label: "Colima 自启 Agent", category: "Nomad", severity: "warning",
1167
+ async check() {
1168
+ if (process.platform !== "darwin")
1169
+ return { ok: true, detail: "非 macOS(已跳过)" };
1170
+ if (!existsSync(COLIMA_PLIST))
1171
+ return { ok: false, detail: "com.jishushell.colima.plist 不存在", hint: "bash jishu-install.sh --run 3", fixable: false };
1172
+ const body = readFileSync(COLIMA_PLIST, "utf-8");
1173
+ const oldStyle = body.includes("<string>start</string>") || body.includes("--arch</string><string>aarch64");
1174
+ if (oldStyle)
1175
+ return { ok: false, detail: "旧式 plist(无 wrapper / 写死 arch)", hint: "bash jishu-install.sh --run 3 重新生成", fixable: false };
1176
+ return { ok: true, detail: "自重试 wrapper 形式" };
1177
+ },
1178
+ };
1179
+ const checkColimaWrapper = {
1180
+ id: "colima-wrapper", label: "Colima wrapper 脚本", category: "Nomad", severity: "warning",
1181
+ async check() {
1182
+ if (process.platform !== "darwin")
1183
+ return { ok: true, detail: "非 macOS(已跳过)" };
1184
+ if (!existsSync(COLIMA_WRAPPER))
1185
+ return { ok: false, detail: "colima-launchd-wrapper.sh 缺失", hint: "bash jishu-install.sh --run 3", fixable: false };
1186
+ return { ok: true, detail: COLIMA_WRAPPER };
1187
+ },
1188
+ };
1189
+ const checkMacosAutologin = {
1190
+ id: "macos-autologin", label: "macOS 自动登录", category: "Nomad", severity: "warning",
1191
+ async check() {
1192
+ if (process.platform !== "darwin")
1193
+ return { ok: true, detail: "非 macOS(已跳过)" };
1194
+ let fvOn = false;
1195
+ try {
1196
+ fvOn = execFileSync("fdesetup", ["status"], { encoding: "utf-8", timeout: 4000 }).includes("FileVault is On");
1197
+ }
1198
+ catch { /* treat as off */ }
1199
+ if (fvOn)
1200
+ return { ok: true, detail: "FileVault 开启(auto-login 不适用,已跳过)" };
1201
+ let user = "";
1202
+ try {
1203
+ user = execFileSync("defaults", ["read", "/Library/Preferences/com.apple.loginwindow", "autoLoginUser"], { encoding: "utf-8", timeout: 4000 }).trim();
1204
+ }
1205
+ catch { /* not set */ }
1206
+ if (!user)
1207
+ return { ok: false, detail: "未配置(headless 重启不会触发 LaunchAgent)", hint: "bash jishu-install.sh --run 3 --enable-autologin", fixable: false };
1208
+ return { ok: true, detail: `autoLoginUser=${user}` };
1209
+ },
1210
+ };
1086
1211
  // ════════════════════════════════════════════════════════════════════════════
1087
1212
  // All check items (in execution order).
1088
1213
  // ════════════════════════════════════════════════════════════════════════════
@@ -1108,6 +1233,9 @@ export const ALL_CHECKS = [
1108
1233
  checkNomadConfig,
1109
1234
  checkNomadAgent,
1110
1235
  checkNomadDockerDriver,
1236
+ checkColimaLaunchd,
1237
+ checkColimaWrapper,
1238
+ checkMacosAutologin,
1111
1239
  checkNomadSystemd,
1112
1240
  checkNomadJobs,
1113
1241
  // AI components