@nowcrew/daemon 0.6.71 → 0.6.73

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.
@@ -14,6 +14,8 @@ const execFileP = (bin, args) => execFileRaw(bin, args, {
14
14
  });
15
15
  export async function listRuntimeModels(runtime, options = {}) {
16
16
  switch (runtime) {
17
+ case "claude":
18
+ return listClaudeModels();
17
19
  case "codex":
18
20
  return parseCodexModels((await execFileP("codex", ["debug", "models"])).stdout);
19
21
  case "cursor":
@@ -28,6 +30,23 @@ export async function listRuntimeModels(runtime, options = {}) {
28
30
  return null;
29
31
  }
30
32
  }
33
+ /** claude CLI 没有模型枚举命令,但 `--model` 的族别名(fable/opus/sonnet/haiku)恒指
34
+ * 本机已装 CLI 版本的最新对应模型(2.1.252 帮助与二进制文案实证)——上报别名即
35
+ * 「上报本机 claude 实际可用的模型」,CLI 升级零维护、永不过期。
36
+ * `--version` 只作安装探测(未安装/不可执行时抛错,由上层回落到 server 静态兜底),
37
+ * 版本号拼进展示名让用户看得到清单来自哪台机器的哪个 CLI。
38
+ * 思考强度不在此上报:claude 的 --effort 档位由 web 端 REASONING_CATALOG 静态维护。 */
39
+ async function listClaudeModels() {
40
+ const rawVersion = (await execFileP("claude", ["--version"])).stdout.trim();
41
+ const version = rawVersion.replace(/\s*\(Claude Code\)\s*$/, "").trim();
42
+ const suffix = version ? ` · Claude Code ${version}` : "";
43
+ return [
44
+ { id: "fable", label: `Fable (latest)${suffix}` },
45
+ { id: "sonnet", label: `Sonnet (latest)${suffix}`, default: true },
46
+ { id: "opus", label: `Opus (latest)${suffix}` },
47
+ { id: "haiku", label: `Haiku (latest)${suffix}` },
48
+ ];
49
+ }
31
50
  function parseCodexModels(stdout) {
32
51
  try {
33
52
  const parsed = JSON.parse(stdout);
package/dist/main.js CHANGED
File without changes
@@ -42,7 +42,7 @@ export function createStorageControlController(input) {
42
42
  ...extra,
43
43
  });
44
44
  const failure = (identity, errorCode, error, reopenAdmission = false, extra = {}) => {
45
- input.sendStatus(frame(identity, "failed", {
45
+ reportStatus(frame(identity, "failed", {
46
46
  errorCode,
47
47
  ...extra,
48
48
  ...(error === undefined ? {} : { errorMessage: safeError(error) }),
@@ -51,6 +51,14 @@ export function createStorageControlController(input) {
51
51
  : {}),
52
52
  }));
53
53
  };
54
+ const reportStatus = (statusFrame) => {
55
+ try {
56
+ input.sendStatus(statusFrame);
57
+ }
58
+ catch (error) {
59
+ input.onStatusError?.(error, statusFrame);
60
+ }
61
+ };
54
62
  const matchesIdentity = (identity) => identity.environment === environment && identity.workspaceId === workspaceId;
55
63
  const preflightKey = (identity) => `${identity.migrationId}\0${identity.environment}\0${identity.workspaceId}`;
56
64
  const runPreflight = (identity, failureFallback = "doctor_failed") => {
@@ -61,7 +69,7 @@ export function createStorageControlController(input) {
61
69
  const controller = new AbortController();
62
70
  let scannedEntries = 0;
63
71
  let timedOut = false;
64
- input.sendStatus(frame(identity, "preflight_running", {
72
+ reportStatus(frame(identity, "preflight_running", {
65
73
  progress: { state: "running", scannedEntries: 0 },
66
74
  }));
67
75
  const doctorPromise = Promise.resolve().then(() => input.runDoctor(identity.migrationId, {
@@ -70,7 +78,7 @@ export function createStorageControlController(input) {
70
78
  if (timedOut)
71
79
  return;
72
80
  scannedEntries = progress.scannedEntries;
73
- input.sendStatus(frame(identity, "preflight_running", {
81
+ reportStatus(frame(identity, "preflight_running", {
74
82
  progress: { state: "running", scannedEntries },
75
83
  }));
76
84
  },
@@ -101,7 +109,7 @@ export function createStorageControlController(input) {
101
109
  return null;
102
110
  }
103
111
  preflights.set(identity.migrationId, doctor);
104
- input.sendStatus(frame(identity, "preflight_passed", {
112
+ reportStatus(frame(identity, "preflight_passed", {
105
113
  sourceStorageFingerprint,
106
114
  targetStorageFingerprint,
107
115
  manifestDigest: doctor.manifestDigest,
@@ -177,7 +185,7 @@ export function createStorageControlController(input) {
177
185
  try {
178
186
  await input.runMigration(doctor, (status) => {
179
187
  if (StatusSchema.safeParse(status).success)
180
- input.sendStatus(frame(identity, status));
188
+ reportStatus(frame(identity, status));
181
189
  });
182
190
  appliedMigrationIds.add(identity.migrationId);
183
191
  }
@@ -324,6 +324,15 @@ async function* scanSourceEntries(input) {
324
324
  type = "file";
325
325
  else if (stat.isSymbolicLink())
326
326
  type = "symlink";
327
+ // Host-local runtime and credential trees are intentionally excluded from
328
+ // the migration. Do not descend into them: their contents are volatile,
329
+ // never copied, and would otherwise make the source digest drift between
330
+ // preflight and apply.
331
+ if (stat.isDirectory()
332
+ && transformation === undefined
333
+ && (classifiedReason === "local_runtime" || classifiedReason === "credential_material")) {
334
+ continue;
335
+ }
327
336
  if (stat.isDirectory())
328
337
  pending.push(path);
329
338
  if (exclusion !== undefined) {
@@ -121,6 +121,10 @@ function loadOutbox(path) {
121
121
  if (storageStatusEventId(delivered) !== delivered.eventId) {
122
122
  throw new Error("storage status outbox event identity mismatch");
123
123
  }
124
+ // Progress is telemetry, not a durable obligation. Older versions could
125
+ // leave hundreds of progress frames behind and starve terminal statuses.
126
+ if (delivered.status === "preflight_running")
127
+ continue;
124
128
  pending.set(delivered.eventId, delivered);
125
129
  }
126
130
  return pending;
@@ -157,6 +161,13 @@ export function createStorageStatusDelivery(input) {
157
161
  const eventId = frame.eventId ?? storageStatusEventId(frame);
158
162
  EventIdSchema.parse(eventId);
159
163
  const delivered = deliveredFrame({ ...frame, eventId });
164
+ // A large manifest can emit thousands of progress updates. Send the
165
+ // latest telemetry opportunistically without putting it in the bounded
166
+ // durable outbox; terminal states remain durable and replayable.
167
+ if (delivered.status === "preflight_running") {
168
+ input.transmit(delivered);
169
+ return;
170
+ }
160
171
  const existing = pending.get(eventId);
161
172
  if (existing === undefined && pending.size >= 256) {
162
173
  throw new Error("storage migration status outbox limit exceeded");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nowcrew/daemon",
3
- "version": "0.6.71",
3
+ "version": "0.6.73",
4
4
  "type": "module",
5
5
  "description": "crew daemon — 运行在用户机器:拉起/管理 agent 进程,注入 crew CLI,归一化 runtime 事件",
6
6
  "license": "Apache-2.0",
@@ -16,14 +16,6 @@
16
16
  "publishConfig": {
17
17
  "access": "public"
18
18
  },
19
- "scripts": {
20
- "daemon": "pnpm --filter @nowcrew/cli build && tsx src/main.ts",
21
- "build": "tsc -p tsconfig.json",
22
- "codex-home:migrate": "tsx src/runtimes/codex-home-migration-cli.ts",
23
- "prepublishOnly": "pnpm build && node ../scripts/daemon-release-artifact.mjs --strict-registry",
24
- "test": "vitest run",
25
- "typecheck": "tsc --noEmit"
26
- },
27
19
  "dependencies": {
28
20
  "@agentclientprotocol/sdk": "1.2.1",
29
21
  "@nowcrew/cli": "^0.4.13",
@@ -42,5 +34,12 @@
42
34
  "tsx": "^4.19.0",
43
35
  "typescript": "^5.6.0",
44
36
  "vitest": "^2.1.0"
37
+ },
38
+ "scripts": {
39
+ "daemon": "pnpm --filter @nowcrew/cli build && tsx src/main.ts",
40
+ "build": "tsc -p tsconfig.json",
41
+ "codex-home:migrate": "tsx src/runtimes/codex-home-migration-cli.ts",
42
+ "test": "vitest run",
43
+ "typecheck": "tsc --noEmit"
45
44
  }
46
- }
45
+ }