@nowcrew/daemon 0.6.90 → 0.6.91-beta.10

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 (54) hide show
  1. package/dist/agent-ability/types.js +1 -1
  2. package/dist/console.js +5 -4
  3. package/dist/daemon-update-controller.js +16 -8
  4. package/dist/daemon-updater.js +10 -22
  5. package/dist/execution-concurrency-telemetry.js +1 -0
  6. package/dist/execution-journal.js +3 -1
  7. package/dist/execution-protocol.js +2 -2
  8. package/dist/execution-runner.js +6 -3
  9. package/dist/external-output.js +2 -1
  10. package/dist/host-execution-coordinator.js +12 -6
  11. package/dist/i18n.js +4 -3
  12. package/dist/local-execution-prompt.js +8 -3
  13. package/dist/local-executor.js +16 -37
  14. package/dist/local-memory/execution-workspace.js +36 -0
  15. package/dist/local-memory/full-index-search.js +47 -0
  16. package/dist/local-memory/index-budget.js +9 -3
  17. package/dist/local-memory/migration-access.js +118 -0
  18. package/dist/local-memory/migration-cli.js +54 -0
  19. package/dist/local-memory/migration-io.js +199 -0
  20. package/dist/local-memory/migration-plan.js +283 -0
  21. package/dist/local-memory/migration-schema.js +55 -0
  22. package/dist/local-memory/migration-store.js +424 -0
  23. package/dist/local-memory/migration-tool-entry.js +21 -0
  24. package/dist/local-memory/prompt.js +20 -4
  25. package/dist/local-memory/read-cli.js +36 -1
  26. package/dist/local-memory/rollout.js +53 -8
  27. package/dist/local-memory/runtime.js +5 -2
  28. package/dist/local-memory/storage-format.js +152 -0
  29. package/dist/local-memory/write.js +108 -17
  30. package/dist/machine-environment-cache.js +18 -4
  31. package/dist/machine-environment-contract.js +3 -1
  32. package/dist/machine-environment-process.js +21 -1
  33. package/dist/machine-info.js +3 -0
  34. package/dist/marketplace-serve.js +25 -1
  35. package/dist/memory-migration-controller.js +84 -0
  36. package/dist/memory-migration-inspect.js +74 -0
  37. package/dist/memory-migration-journal.js +14 -0
  38. package/dist/memory-migration-protocol.js +14 -0
  39. package/dist/memory-migration-runtime.js +305 -0
  40. package/dist/message-file-preview.js +11 -1
  41. package/dist/normalize.js +9 -5
  42. package/dist/pre-admission-rejection.js +5 -4
  43. package/dist/prompt.js +5 -1
  44. package/dist/runner.js +1 -1
  45. package/dist/runtime-capabilities.js +2 -1
  46. package/dist/runtime-startup-gate.js +1 -0
  47. package/dist/runtimes/pi-runner.js +28 -0
  48. package/dist/runtimes/pi.js +17 -0
  49. package/dist/serve.js +37 -6
  50. package/dist/shared-execution-slots.js +2 -0
  51. package/dist/supervised-runtime.js +20 -0
  52. package/dist/token.js +1 -0
  53. package/dist/workspace-control-handler.js +16 -3
  54. package/package.json +1 -1
@@ -2,7 +2,7 @@ import { ExecutionCompletedSchema, ExecutionRejectedSchema } from "./execution-p
2
2
  import { hashExecutionSpec } from "./execution-runner.js";
3
3
  import { dslog } from "./slog.js";
4
4
  /** Queue rejection is admission work too: own the ID before any asynchronous journal write. */
5
- export async function rejectQueuedAdmission(context, spec, executionClass, facts) {
5
+ export async function rejectQueuedAdmission(context, spec, executionClass, facts, rejectionReason = "machine_queue_full") {
6
6
  const id = spec.executionId;
7
7
  const hash = hashExecutionSpec(spec);
8
8
  if (context.knownExecutionHashes.has(id) || context.executionRuns.has(id)) {
@@ -16,10 +16,11 @@ export async function rejectQueuedAdmission(context, spec, executionClass, facts
16
16
  context.knownExecutionHashes.set(id, hash);
17
17
  const rejected = ExecutionRejectedSchema.parse({
18
18
  type: "execution:rejected", protocolVersion: 1, executionId: id,
19
- reason: "resource_limit", message: "Local machine execution queue is full", at: new Date().toISOString(),
19
+ reason: "resource_limit", message: rejectionReason === "maintenance" ? "Local machine is temporarily unavailable for maintenance"
20
+ : rejectionReason === "agent_queue_full" ? "Local agent execution queue is full" : "Local machine execution queue is full", at: new Date().toISOString(),
20
21
  });
21
- dslog("execution.machine_queue_rejected", "机器执行队列已满", {
22
- level: "WARN", execution_id: id, agent_handle: spec.agent.handle, execution_class: executionClass, ...facts,
22
+ dslog("execution.machine_queue_rejected", rejectionReason === "maintenance" ? "机器维护期间暂停接收执行" : "执行等待队列已满", {
23
+ level: "WARN", execution_id: id, agent_handle: spec.agent.handle, execution_class: executionClass, admission_reason: rejectionReason, ...facts,
23
24
  });
24
25
  const rejecting = persistPreAdmissionRejection(context.journal, spec, rejected)
25
26
  .then(async (entry) => {
package/dist/prompt.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * claim-before-work、freshness/draft、"做完所有事再停"、inbox notice 语义、
6
6
  * 分层记忆与压缩安全、协作礼仪。措辞为本项目原创。
7
7
  */
8
+ import { detectLocalMemoryStorageVersion, selectLocalMemoryPreview } from "./local-memory/storage-format.js";
8
9
  /** work-log 注入上限(字符):防冷启动注入随文件膨胀。超限保头(目标/结论)+尾(最近进展),
9
10
  * 截中段并提示读全文——全文仍在盘上,只限"默认推送量"不限"可获取量"。纯函数。 */
10
11
  export const WORKLOG_INJECT_CAP = 8000;
@@ -67,7 +68,10 @@ export function capWorkLogForInject(workLog, cap = WORKLOG_INJECT_CAP) {
67
68
  const tail = workLog.slice(-WORKLOG_TAIL);
68
69
  return `${head}\n…(work-log 过长已截去中段,全文用 Read 读 $CREW_TASK_LOG)\n${tail}`;
69
70
  }
70
- export function capMemoryForInject(memory, cap = MEMORY_INJECT_CAP, maxBytes = Number.POSITIVE_INFINITY) {
71
+ export function capMemoryForInject(memory, cap = MEMORY_INJECT_CAP, maxBytes = Number.POSITIVE_INFINITY, query = "") {
72
+ if (detectLocalMemoryStorageVersion(memory) === 2) {
73
+ return selectLocalMemoryPreview(memory, query, { maxCodePoints: cap, maxBytes }).memory;
74
+ }
71
75
  if (memory.length <= cap && Buffer.byteLength(memory, "utf8") <= maxBytes)
72
76
  return memory;
73
77
  const notice = "[MEMORY.md preview truncated; use crew-memory search/read for more.]\n";
package/dist/runner.js CHANGED
@@ -80,7 +80,7 @@ async function runAgentWithMachineEnvironment(config, input, onActivity = defaul
80
80
  ? { scheduledExternalNotificationPolicy: input.scheduled.externalNotificationPolicy }
81
81
  : {}),
82
82
  ...(input.wakeOrigin ? { wakeOrigin: input.wakeOrigin } : {}),
83
- memory: capMemoryForInject(workspace.memory),
83
+ memory: capMemoryForInject(workspace.memory, undefined, undefined, baseWake),
84
84
  ...(resuming ? {} : { workLog: capWorkLogForInject(workspace.workLog) }),
85
85
  collaborationToolsEnabled: config.codexCollaborationTools && runtime === "codex",
86
86
  localMemoryV2Enabled: workspace.localMemoryV2Enabled ?? localMemoryV2Enabled,
@@ -1,6 +1,6 @@
1
1
  import { ATTACHMENT_NATIVE_IMAGE_MAX_BYTES } from "./attachment-limits.js";
2
2
  export const LOCAL_EXECUTION_RUNTIMES = [
3
- "claude", "codex", "kimi", "hermes", "opencode", "deepseek-harness",
3
+ "claude", "codex", "kimi", "hermes", "opencode", "deepseek-harness", "pi",
4
4
  ];
5
5
  export const LOCAL_RUNTIME_CAPABILITIES = Object.freeze({
6
6
  claude: Object.freeze({
@@ -33,6 +33,7 @@ export const LOCAL_RUNTIME_CAPABILITIES = Object.freeze({
33
33
  nativeResume: false,
34
34
  systemPromptTransport: "protocol",
35
35
  }),
36
+ pi: Object.freeze({ transport: "pi-json", nativeResume: false, systemPromptTransport: "protocol" }),
36
37
  });
37
38
  export function runtimeCapability(runtime) {
38
39
  return LOCAL_RUNTIME_CAPABILITIES[runtime];
@@ -95,6 +95,7 @@ export function createRuntimeStartupGate(limits, now = Date.now) {
95
95
  kimi: activeByRuntimeAndLane.get(activeKey("kimi", "foreground")) ?? 0,
96
96
  hermes: activeByRuntimeAndLane.get(activeKey("hermes", "foreground")) ?? 0,
97
97
  opencode: activeByRuntimeAndLane.get(activeKey("opencode", "foreground")) ?? 0,
98
+ pi: activeByRuntimeAndLane.get(activeKey("pi", "foreground")) ?? 0,
98
99
  "deepseek-harness": activeByRuntimeAndLane.get(activeKey("deepseek-harness", "foreground")) ?? 0,
99
100
  },
100
101
  }),
@@ -0,0 +1,28 @@
1
+ import { once } from "node:events";
2
+ import { createInterface } from "node:readline";
3
+ import { parseArgs } from "node:util";
4
+ import { pathToFileURL } from "node:url";
5
+ import spawn from "cross-spawn";
6
+ import { augmentedPath } from "../runtime-path.js";
7
+ import { buildPiArgs, createPiEventDecoder } from "./pi.js";
8
+ async function readInput() { process.stdin.setEncoding("utf8"); let text = ""; for await (const chunk of process.stdin)
9
+ text += String(chunk); return JSON.parse(text); }
10
+ async function emit(event) { if (process.stdout.write(`${JSON.stringify(event)}\n`))
11
+ return; await once(process.stdout, "drain"); }
12
+ export async function runPi(bin) { const input = await readInput(); const child = spawn(bin, buildPiArgs(input), { cwd: process.cwd(), env: { ...process.env, PATH: augmentedPath(), PWD: process.cwd() }, stdio: ["ignore", "pipe", "pipe"] }); if (!child.stdout || !child.stderr)
13
+ throw new Error("Pi process did not expose stdio"); child.stderr.pipe(process.stderr, { end: false }); const exit = once(child, "close"); const decoder = createPiEventDecoder(); for await (const line of createInterface({ input: child.stdout })) {
14
+ try {
15
+ for (const event of decoder.push(JSON.parse(line)))
16
+ await emit(event);
17
+ }
18
+ catch { }
19
+ } const [code, signal] = await exit; const result = decoder.finish(); if (!result.ok) {
20
+ process.stderr.write(`${result.error}\n`);
21
+ return 1;
22
+ } return code === 0 ? 0 : (code ?? (signal ? 128 : 1)); }
23
+ const { values } = parseArgs({ args: process.argv.slice(2), options: { bin: { type: "string" } } });
24
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
25
+ if (!values.bin)
26
+ throw new Error("--bin is required");
27
+ runPi(values.bin).then(code => { process.exitCode = code; }).catch(error => { process.stderr.write(`Pi runner failed: ${String(error)}\n`); process.exitCode = 1; });
28
+ }
@@ -0,0 +1,17 @@
1
+ export function buildPiArgs(options) { return ["--mode", "json", "--print", "--no-session", "--system-prompt", options.systemPrompt, ...(options.model ? ["--model", options.model] : []), ...(options.reasoning && options.reasoning !== "default" ? ["--thinking", options.reasoning] : []), options.wakePrompt]; }
2
+ export function createPiEventDecoder() { let sessionId = null; let finalText = ""; let error = null; let sawTurnEnd = false; let usage; return { push(raw) { const e = (raw ?? {}); const out = []; if (e.type === "session" && typeof e.id === "string") {
3
+ sessionId = e.id;
4
+ out.push({ type: "thread.started", thread_id: e.id });
5
+ } const ae = e.assistantMessageEvent; if (e.type === "message_update" && ae?.type === "text_delta" && typeof ae.delta === "string") {
6
+ finalText += ae.delta;
7
+ out.push({ type: "pi.text_delta", text: ae.delta });
8
+ } if (e.type === "message_update" && typeof ae?.type === "string" && /tool(call|_call)/iu.test(ae.type))
9
+ out.push({ type: "pi.tool_call", ...(ae.name ? { title: ae.name } : {}) }); if (e.type === "message_end" && e.message?.role === "assistant")
10
+ usage = e.message.usage; if (e.type === "turn_end") {
11
+ sawTurnEnd = true;
12
+ const u = usage;
13
+ out.push({ type: "turn.completed", ...(u ? { usage: { input_tokens: u.input ?? 0, output_tokens: u.output ?? 0, cache_read_input_tokens: u.cacheRead ?? 0, cache_creation_input_tokens: u.cacheWrite ?? 0 } } : {}) });
14
+ } if (e.type === "error" || e.type === "agent_error") {
15
+ error = typeof e.error === "string" ? e.error : (e.error?.message ?? "Pi error");
16
+ out.push({ type: "pi.error", message: error });
17
+ } return out; }, finish() { return { ok: error === null && sawTurnEnd, ...(error === null && sawTurnEnd ? {} : { error: error ?? "Pi stream ended without turn completion" }), finalText, sessionId, usage }; } }; }
package/dist/serve.js CHANGED
@@ -14,6 +14,7 @@ import { reconcileExecutionJournal } from "./execution-recovery.js";
14
14
  import { initReleaseSlog, reportDaemonConnectionClose, reportReleaseError } from "./release-channel-guard.js";
15
15
  import { handleWorkspaceControlRequest } from "./workspace-control-handler.js";
16
16
  import { inspectRaftWorkspace, importRaftWorkspace } from "./workspace-import.js";
17
+ import { withMemoryAccess } from "./local-memory/migration-access.js";
17
18
  import { normalizeScheduledContext } from "./scheduled-report.js";
18
19
  import { formatDaemonLogLine } from "./log-format.js";
19
20
  import { reportAgentRunComplete } from "./scheduled-run-report.js";
@@ -50,6 +51,8 @@ import { probeRuntimeHealth } from "./runtime-health.js";
50
51
  import { createServeMarketplace } from "./marketplace-serve.js";
51
52
  import { buildControlPlaneUrl } from "./control-plane-url.js";
52
53
  import { resolveDaemonLocalStateRootFromEnv } from "./daemon-local-home.js";
54
+ import { createMemoryMigrationController } from "./memory-migration-controller.js";
55
+ import { createMemoryMigrationJournal, replayExecutionJournals } from "./memory-migration-journal.js";
53
56
  import { bindStorageServerIdentity, createStorageIdentityAdmission, } from "./storage-serve-adapter.js";
54
57
  export { buildControlPlaneUrl } from "./control-plane-url.js";
55
58
  // normalize.ts 的活动种类 → activity 枚举
@@ -71,6 +74,8 @@ export function serve(config, opts = {}) {
71
74
  let reconnectTimer = null;
72
75
  let stopPromise = null;
73
76
  let executionJournal;
77
+ let migrationExecutionJournal;
78
+ let migrationJournalReady = false;
74
79
  const injectedExecutionJournal = opts.execution?.journal;
75
80
  const agentMemory = opts.execution?.dependencies?.agentMemory
76
81
  ?? (config.agentMemory === null ? undefined : createAgentMemoryBridge(config.agentMemory));
@@ -85,6 +90,15 @@ export function serve(config, opts = {}) {
85
90
  startupGapMs: config.executionLimits.startupGapMs,
86
91
  });
87
92
  const localSlots = createSharedSlotManager(config.executionLimits);
93
+ const memoryMigrations = createMemoryMigrationController({ config, coordinator: hostCoordinator,
94
+ isAdmissionClosed: () => stopped || storageIdentityAdmission.isAdmissionClosed(),
95
+ journal: () => { if (!migrationJournalReady)
96
+ throw new Error("memory_migration_journal_unavailable"); return migrationExecutionJournal; }, send: frame => {
97
+ if (frame.type === "execution:completed")
98
+ completionRetransmitter.track(frame);
99
+ else if (ws?.readyState === WebSocket.OPEN)
100
+ ws.send(JSON.stringify(frame));
101
+ } });
88
102
  const sharedSlots = hostCoordinatedSlotManager(localSlots, hostCoordinator);
89
103
  const concurrencyTelemetry = createExecutionConcurrencyTelemetry(sharedSlots, {
90
104
  ...(opts.execution?.concurrencySnapshotIntervalMs === undefined
@@ -105,11 +119,9 @@ export function serve(config, opts = {}) {
105
119
  install: opts.update?.install ?? ((input) => installExactDaemonUpdate({
106
120
  ...input,
107
121
  localSlots,
108
- hostCoordinator,
109
122
  })),
110
123
  prepareRestart: opts.update?.prepareRestart ?? (() => prepareDaemonRestart({
111
124
  localSlots,
112
- hostCoordinator,
113
125
  })),
114
126
  scheduleRestart: opts.update?.scheduleRestart ?? scheduleServiceRestart,
115
127
  sendStatus: (frame) => {
@@ -234,7 +246,7 @@ export function serve(config, opts = {}) {
234
246
  return null;
235
247
  };
236
248
  const sendSnapshot = async (reqId) => {
237
- const entries = (await executionJournal.replay()).map(snapshotEntry).filter((entry) => entry !== null);
249
+ const entries = (await replayExecutionJournals(executionJournal, migrationExecutionJournal)).map(snapshotEntry).filter((entry) => entry !== null);
238
250
  safeExecutionSend(ExecutionSnapshotSchema.parse({
239
251
  type: "execution:snapshot", protocolVersion: 1, reqId, entries,
240
252
  }));
@@ -315,6 +327,12 @@ export function serve(config, opts = {}) {
315
327
  machineRuntime.open(openedSocket, { capabilities: capabilities.machineHello, beforePublish: Promise.resolve(), connectionId });
316
328
  opts.onOpen?.(ws);
317
329
  void sendSnapshot(`reconnect-${randomUUID()}`);
330
+ // 普通回执不等待维护日志IO;两类记录分别重放到同一发送器。
331
+ void migrationExecutionJournal.replay().then(entries => {
332
+ for (const entry of entries)
333
+ if (entry.completion && !entry.completionAcknowledged)
334
+ completionRetransmitter.track(entry.completion);
335
+ }).catch(() => dslog("memory_migration.journal_unavailable", "维护回执重放失败,普通回执继续", { level: "ERROR" }));
318
336
  void executionJournal.replay()
319
337
  .then((entries) => {
320
338
  for (const entry of entries) {
@@ -389,7 +407,8 @@ export function serve(config, opts = {}) {
389
407
  return;
390
408
  if (frame.type === "execution:completion-ack") {
391
409
  try {
392
- await executionJournal.acknowledgeCompletion(frame.executionId);
410
+ const journal = await executionJournal.get(frame.executionId) ? executionJournal : migrationExecutionJournal;
411
+ await journal.acknowledgeCompletion(frame.executionId);
393
412
  const attempts = completionRetransmitter.acknowledge(frame.executionId);
394
413
  dslog("execution.completion_acknowledged", "execution completion ACK 已持久化", {
395
414
  execution_id: frame.executionId,
@@ -415,6 +434,8 @@ export function serve(config, opts = {}) {
415
434
  return;
416
435
  }
417
436
  if (frame.type === "execution:cancel") {
437
+ if (memoryMigrations.cancel(frame.executionId))
438
+ return;
418
439
  if (!knownExecutionHashes.has(frame.executionId)
419
440
  && !executionRuns.has(frame.executionId)
420
441
  && await executionJournal.get(frame.executionId) === null)
@@ -490,7 +511,7 @@ export function serve(config, opts = {}) {
490
511
  let reservation = sharedSlots.reserve(spec.agent.handle, "execution", executionClass);
491
512
  if (!reservation.accepted) {
492
513
  await rejectQueuedAdmission({ journal: executionJournal, knownExecutionHashes, executionRuns,
493
- report: reportExecutionFrame, replay: sendJournalStatus }, spec, executionClass, reservation.facts);
514
+ report: reportExecutionFrame, replay: sendJournalStatus }, spec, executionClass, reservation.facts, reservation.rejectionReason);
494
515
  return;
495
516
  }
496
517
  knownExecutionHashes.set(spec.executionId, hash);
@@ -737,6 +758,8 @@ export function serve(config, opts = {}) {
737
758
  // ready 帧带 server 视角的 machineId/workspaceId → 作为后续所有日志的默认关联键
738
759
  const r = msg;
739
760
  setSlogDefaults({ machine_id: r.machineId, workspace_id: r.workspaceId });
761
+ if (r.workspaceId && r.machineId)
762
+ memoryMigrations.setIdentity({ workspaceId: r.workspaceId, machineId: r.machineId });
740
763
  if (opts.storageRuntime !== undefined && typeof r.workspaceId === "string" && typeof r.machineId === "string") {
741
764
  const bindingSocket = ws;
742
765
  bindStorageServerIdentity(opts.storageRuntime, { workspaceId: r.workspaceId, machineId: r.machineId }, {
@@ -803,6 +826,8 @@ export function serve(config, opts = {}) {
803
826
  }
804
827
  catch { /* server 会按请求超时处理 */ } }))
805
828
  return;
829
+ if (await memoryMigrations.handle(msg))
830
+ return;
806
831
  if (await agentAbilityRuntime.tryHandleControlMessage(msg, serverCapabilities, (frame) => ws?.send(JSON.stringify(frame))))
807
832
  return;
808
833
  // 导入 raft agent 工作区:inspect 反填 name/description;import 复制用户内容
@@ -817,7 +842,7 @@ export function serve(config, opts = {}) {
817
842
  try {
818
843
  const data2 = req.type === "raft:inspect"
819
844
  ? await inspectRaftWorkspace(req.path)
820
- : await importRaftWorkspace(req.path, join(config.agentsRoot, req.handle));
845
+ : await withMemoryAccess(join(config.agentsRoot, req.handle), () => importRaftWorkspace(req.path, join(config.agentsRoot, req.handle)), { create: true });
821
846
  if (req.type === "raft:import") {
822
847
  log(`📦 已导入 raft 工作区 → ${req.handle}: 复制 ${data2.copied.length} 项`);
823
848
  }
@@ -1261,6 +1286,9 @@ export function serve(config, opts = {}) {
1261
1286
  writeStderr: (line) => process.stderr.write(line),
1262
1287
  });
1263
1288
  await runProjectSkillStartupCleanup(config.agentsRoot, executionJournal, dslog);
1289
+ migrationExecutionJournal = createMemoryMigrationJournal(config.agentsRoot);
1290
+ await migrationExecutionJournal.reconcileAfterRestart().then(() => { migrationJournalReady = true; })
1291
+ .catch(() => dslog("memory_migration.journal_unavailable", "迁移日志恢复失败,普通执行继续,迁移请求单独报错", { level: "ERROR" }));
1264
1292
  await connect();
1265
1293
  })();
1266
1294
  const stop = () => {
@@ -1278,6 +1306,7 @@ export function serve(config, opts = {}) {
1278
1306
  const webSocketClosed = closeWebSocketWithinDeadline(ws, deadline.signal);
1279
1307
  const pending = [
1280
1308
  machineRuntime.stop(),
1309
+ memoryMigrations.stop(),
1281
1310
  webSocketClosed,
1282
1311
  updateController.drain(),
1283
1312
  marketplace.stop(),
@@ -1296,6 +1325,8 @@ export function serve(config, opts = {}) {
1296
1325
  if (executionJournal !== undefined) {
1297
1326
  await executionJournal.close({ signal: deadline.signal });
1298
1327
  }
1328
+ if (migrationExecutionJournal !== undefined)
1329
+ await migrationExecutionJournal.close({ signal: deadline.signal });
1299
1330
  }
1300
1331
  finally {
1301
1332
  deadline.dispose();
@@ -59,6 +59,7 @@ export function createSharedSlotManager(limits) {
59
59
  if (exclusive) {
60
60
  return {
61
61
  accepted: false,
62
+ rejectionReason: "maintenance",
62
63
  facts,
63
64
  ready: Promise.resolve(),
64
65
  isQueued: () => false,
@@ -77,6 +78,7 @@ export function createSharedSlotManager(limits) {
77
78
  || laneQueuedTotal >= limits.maxQueuedTotal)) {
78
79
  return {
79
80
  accepted: false,
81
+ rejectionReason: queuedForAgent >= limits.maxQueuedPerAgent ? "agent_queue_full" : "machine_queue_full",
80
82
  facts,
81
83
  ready: Promise.resolve(),
82
84
  isQueued: () => false,
@@ -61,6 +61,26 @@ export function supervisorLaunch(request) {
61
61
  }),
62
62
  };
63
63
  }
64
+ if (request.runtime === "pi") {
65
+ if (request.effectivePermission !== "full_access") {
66
+ throw new Error(`Pi cannot enforce ${request.effectivePermission} permission`);
67
+ }
68
+ return {
69
+ command: process.execPath,
70
+ args: [
71
+ fileURLToPath(new URL("./runtimes/pi-runner.js", import.meta.url)),
72
+ "--bin", request.bin,
73
+ ],
74
+ cwd: request.cwd,
75
+ env: request.env,
76
+ stdinText: JSON.stringify({
77
+ systemPrompt: request.systemPrompt,
78
+ wakePrompt: request.wakePrompt,
79
+ ...(request.model === undefined ? {} : { model: request.model }),
80
+ ...(request.reasoning === undefined ? {} : { reasoning: request.reasoning }),
81
+ }),
82
+ };
83
+ }
64
84
  if (request.runtime === "opencode") {
65
85
  if (request.effectivePermission !== "full_access") {
66
86
  throw new Error(`OpenCode cannot enforce ${request.effectivePermission} permission`);
package/dist/token.js CHANGED
@@ -7,6 +7,7 @@ export async function mintAgentToken(serverUrl, machineToken, handle, displayNam
7
7
  },
8
8
  // 把本轮唤醒的线程根钉到 per-launch 令牌:服务端据此让 task/消息默认绑/回当前线程。
9
9
  body: JSON.stringify({
10
+ memoryStateGuardVersion: 1,
10
11
  handle,
11
12
  ...(displayName ? { displayName } : {}),
12
13
  ...(options.wakeThreadRoot ? { wakeThreadRoot: options.wakeThreadRoot } : {}),
@@ -1,9 +1,22 @@
1
1
  /** 控制面文件/技能查看的按需处理,独立于执行生命周期。 */
2
- import { join } from "node:path";
2
+ import { lstat } from "node:fs/promises";
3
+ import { join, resolve } from "node:path";
3
4
  import { readSharedMessageFile } from "./message-file-preview.js";
4
5
  import { listWorkspace, readWorkspaceFile } from "./workspace-fs.js";
5
6
  import { listSkills } from "./skills.js";
6
7
  import { listRuntimeModels } from "./list-models.js";
8
+ import { withMemoryAccess } from "./local-memory/migration-access.js";
9
+ async function listProtectedWorkspace(root, path) {
10
+ try {
11
+ await lstat(root);
12
+ }
13
+ catch (error) {
14
+ if (error.code !== "ENOENT")
15
+ throw error;
16
+ return { root: resolve(root), path: path.replace(/^\/+/, ""), entries: [] };
17
+ }
18
+ return withMemoryAccess(root, () => listWorkspace(root, path));
19
+ }
7
20
  export async function handleWorkspaceControlRequest(raw, agentsRoot, registry, models = listRuntimeModels) {
8
21
  if (!raw || typeof raw !== "object")
9
22
  return null;
@@ -15,8 +28,8 @@ export async function handleWorkspaceControlRequest(raw, agentsRoot, registry, m
15
28
  try {
16
29
  const root = join(agentsRoot, request.handle);
17
30
  const data = request.type === "fs:read-shared" ? await readSharedMessageFile(agentsRoot, raw, registry)
18
- : request.type === "fs:list" ? await listWorkspace(root, request.path)
19
- : request.type === "fs:read" ? await readWorkspaceFile(root, request.path)
31
+ : request.type === "fs:list" ? await listProtectedWorkspace(root, request.path)
32
+ : request.type === "fs:read" ? await withMemoryAccess(root, () => readWorkspaceFile(root, request.path))
20
33
  : request.type === "skills:list" ? await listSkills(request.runtime)
21
34
  : { models: await models(request.runtime ?? "") };
22
35
  return { type: "fs:result", reqId: request.reqId, ok: true, data };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nowcrew/daemon",
3
- "version": "0.6.90",
3
+ "version": "0.6.91-beta.10",
4
4
  "type": "module",
5
5
  "description": "crew daemon — 运行在用户机器:拉起/管理 agent 进程,注入 crew CLI,归一化 runtime 事件",
6
6
  "license": "Apache-2.0",