@nowcrew/daemon 0.5.14 → 0.5.16

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.
@@ -0,0 +1,264 @@
1
+ import { once } from "node:events";
2
+ import { parseArgs } from "node:util";
3
+ import { Readable, Writable } from "node:stream";
4
+ import { pathToFileURL } from "node:url";
5
+ import spawn from "cross-spawn";
6
+ import { PROTOCOL_VERSION, client, methods, ndJsonStream, } from "@agentclientprotocol/sdk";
7
+ const ERROR_MESSAGE_CAP = 2_000;
8
+ const PROBE_TIMEOUT_MS = 5_000;
9
+ function jsonLine(event) {
10
+ if (process.stdout.write(`${JSON.stringify(event)}\n`))
11
+ return Promise.resolve();
12
+ return once(process.stdout, "drain").then(() => undefined);
13
+ }
14
+ function textContent(content) {
15
+ if (typeof content === "string")
16
+ return content;
17
+ if (!Array.isArray(content))
18
+ return "";
19
+ return content.flatMap((part) => {
20
+ if (part && typeof part === "object" && "text" in part && typeof part.text === "string") {
21
+ return [part.text];
22
+ }
23
+ if (part && typeof part === "object" && "content" in part
24
+ && part.content && typeof part.content === "object"
25
+ && "type" in part.content && part.content.type === "text"
26
+ && "text" in part.content && typeof part.content.text === "string") {
27
+ return [part.content.text];
28
+ }
29
+ return [];
30
+ }).join("\n");
31
+ }
32
+ /** Translate stable ACP updates into daemon-owned NDJSON, without exposing thought chunks. */
33
+ export function mapKimiAcpUpdate(update) {
34
+ if (update.sessionUpdate === "agent_message_chunk" && update.content.type === "text") {
35
+ return [{ type: "kimi.acp.text_delta", text: update.content.text }];
36
+ }
37
+ if (update.sessionUpdate === "tool_call") {
38
+ return [{
39
+ type: "kimi.acp.tool_call",
40
+ id: update.toolCallId,
41
+ title: update.title,
42
+ ...(update.kind === undefined ? {} : { kind: update.kind }),
43
+ ...(update.status === undefined ? {} : { status: update.status }),
44
+ ...(update.rawInput === undefined ? {} : { input: update.rawInput }),
45
+ }];
46
+ }
47
+ if (update.sessionUpdate === "tool_call_update") {
48
+ const output = textContent(update.content);
49
+ return [{
50
+ type: "kimi.acp.tool_result",
51
+ id: update.toolCallId,
52
+ ...(update.status === undefined ? {} : { status: update.status }),
53
+ ...(output ? { content: output } : {}),
54
+ }];
55
+ }
56
+ return [];
57
+ }
58
+ function safeErrorMessage(error, prompt) {
59
+ const raw = error instanceof Error ? error.message : String(error);
60
+ const redacted = prompt && raw.includes(prompt) ? raw.replaceAll(prompt, "[prompt redacted]") : raw;
61
+ return redacted.slice(0, ERROR_MESSAGE_CAP);
62
+ }
63
+ /** Full access may approve an operation, but it must never fabricate an answer to an agent question. */
64
+ export function selectKimiPermission(params) {
65
+ const allowOnce = params.options.filter((option) => option.kind === "allow_once");
66
+ if (params.toolCall.title === "AskUserQuestion" || allowOnce.length > 1) {
67
+ return { outcome: { outcome: "cancelled" } };
68
+ }
69
+ const allowed = allowOnce[0]
70
+ ?? (params.options.filter((option) => option.kind === "allow_always").length === 1
71
+ ? params.options.find((option) => option.kind === "allow_always")
72
+ : undefined);
73
+ return allowed === undefined
74
+ ? { outcome: { outcome: "cancelled" } }
75
+ : { outcome: { outcome: "selected", optionId: allowed.optionId } };
76
+ }
77
+ async function readPrompt() {
78
+ process.stdin.setEncoding("utf8");
79
+ let prompt = "";
80
+ for await (const chunk of process.stdin)
81
+ prompt += String(chunk);
82
+ if (!prompt)
83
+ throw new Error("Kimi ACP prompt is empty");
84
+ return prompt;
85
+ }
86
+ async function stopChild(child) {
87
+ if (child.exitCode !== null || child.signalCode !== null)
88
+ return;
89
+ const closed = once(child, "close").then(() => undefined);
90
+ child.kill("SIGTERM");
91
+ let timer;
92
+ const graceful = await Promise.race([
93
+ closed.then(() => true),
94
+ new Promise((resolve) => { timer = setTimeout(() => resolve(false), 1_000); }),
95
+ ]);
96
+ if (timer !== undefined)
97
+ clearTimeout(timer);
98
+ if (!graceful && child.exitCode === null && child.signalCode === null) {
99
+ child.kill("SIGKILL");
100
+ await closed;
101
+ }
102
+ }
103
+ /** A help banner is insufficient: Kimi's authenticate RPC re-checks its token without creating a session. */
104
+ export async function probeKimiAcp(options, spawnProcess = spawn) {
105
+ const child = spawnProcess(options.bin, ["acp"], {
106
+ cwd: process.cwd(),
107
+ env: process.env,
108
+ stdio: ["pipe", "pipe", "pipe"],
109
+ });
110
+ if (child.stdin === null || child.stdout === null || child.stderr === null)
111
+ return false;
112
+ child.stderr.resume();
113
+ const app = client({ name: "nowcrew-daemon-kimi-probe" })
114
+ .onRequest(methods.client.session.requestPermission, () => ({
115
+ outcome: { outcome: "cancelled" },
116
+ }))
117
+ .onNotification(methods.client.session.update, () => undefined);
118
+ let timeout;
119
+ try {
120
+ const stream = ndJsonStream(Writable.toWeb(child.stdin), Readable.toWeb(child.stdout));
121
+ const connected = app.connectWith(stream, async (context) => {
122
+ const initialized = await context.request(methods.agent.initialize, {
123
+ protocolVersion: PROTOCOL_VERSION,
124
+ clientCapabilities: {},
125
+ clientInfo: { name: "nowcrew-daemon", version: "1" },
126
+ });
127
+ const authMethod = initialized.authMethods?.[0];
128
+ if (authMethod !== undefined) {
129
+ await context.request(methods.agent.authenticate, { methodId: authMethod.id });
130
+ }
131
+ return true;
132
+ });
133
+ const result = await Promise.race([
134
+ connected,
135
+ new Promise((resolve) => {
136
+ timeout = setTimeout(() => {
137
+ void stopChild(child);
138
+ resolve(false);
139
+ }, PROBE_TIMEOUT_MS);
140
+ }),
141
+ ]);
142
+ return result;
143
+ }
144
+ catch {
145
+ return false;
146
+ }
147
+ finally {
148
+ if (timeout !== undefined)
149
+ clearTimeout(timeout);
150
+ await stopChild(child);
151
+ }
152
+ }
153
+ export async function runKimiAcp(options) {
154
+ const prompt = await readPrompt();
155
+ const child = spawn(options.bin, ["acp"], {
156
+ cwd: process.cwd(),
157
+ env: process.env,
158
+ stdio: ["pipe", "pipe", "pipe"],
159
+ });
160
+ if (child.stdin === null || child.stdout === null || child.stderr === null) {
161
+ throw new Error("Kimi ACP process did not expose stdio");
162
+ }
163
+ child.stderr.pipe(process.stderr, { end: false });
164
+ let context = null;
165
+ let sessionId = null;
166
+ let cancelling = false;
167
+ const cancel = async () => {
168
+ if (cancelling)
169
+ return;
170
+ cancelling = true;
171
+ if (context !== null && sessionId !== null) {
172
+ await context.notify(methods.agent.session.cancel, { sessionId }).catch(() => undefined);
173
+ }
174
+ await stopChild(child);
175
+ };
176
+ const onSignal = () => {
177
+ void cancel().finally(() => process.exit(130));
178
+ };
179
+ process.once("SIGTERM", onSignal);
180
+ process.once("SIGINT", onSignal);
181
+ const app = client({ name: "nowcrew-daemon-kimi" })
182
+ .onRequest(methods.client.session.requestPermission, ({ params }) => selectKimiPermission(params))
183
+ .onNotification(methods.client.session.update, async ({ params }) => {
184
+ for (const event of mapKimiAcpUpdate(params.update))
185
+ await jsonLine(event);
186
+ });
187
+ try {
188
+ const stream = ndJsonStream(Writable.toWeb(child.stdin), Readable.toWeb(child.stdout));
189
+ const result = await app.connectWith(stream, async (nextContext) => {
190
+ context = nextContext;
191
+ const initialized = await nextContext.request(methods.agent.initialize, {
192
+ protocolVersion: PROTOCOL_VERSION,
193
+ clientCapabilities: {},
194
+ clientInfo: { name: "nowcrew-daemon", version: "1" },
195
+ });
196
+ if (process.env.CREW_KIMI_ACP_DEBUG === "1") {
197
+ process.stderr.write(`Kimi ACP auth methods: ${JSON.stringify(initialized.authMethods ?? [])}\n`);
198
+ }
199
+ const authMethod = initialized.authMethods?.[0];
200
+ if (authMethod !== undefined) {
201
+ await nextContext.request(methods.agent.authenticate, { methodId: authMethod.id });
202
+ }
203
+ const session = await nextContext.request(methods.agent.session.new, {
204
+ cwd: process.cwd(),
205
+ mcpServers: [],
206
+ });
207
+ sessionId = session.sessionId;
208
+ if (options.model) {
209
+ await nextContext.request(methods.agent.session.setConfigOption, {
210
+ sessionId,
211
+ configId: "model",
212
+ value: options.model,
213
+ });
214
+ }
215
+ return nextContext.request(methods.agent.session.prompt, {
216
+ sessionId,
217
+ prompt: [{ type: "text", text: prompt }],
218
+ });
219
+ });
220
+ const usage = result.usage;
221
+ await jsonLine({
222
+ type: "turn.completed",
223
+ stop_reason: result.stopReason,
224
+ ...(usage === undefined || usage === null ? {} : {
225
+ usage: {
226
+ input_tokens: usage.inputTokens,
227
+ output_tokens: usage.outputTokens,
228
+ cache_read_input_tokens: usage.cachedReadTokens ?? 0,
229
+ cache_creation_input_tokens: usage.cachedWriteTokens ?? 0,
230
+ },
231
+ }),
232
+ });
233
+ return result.stopReason === "end_turn" ? 0 : result.stopReason === "cancelled" ? 130 : 1;
234
+ }
235
+ catch (error) {
236
+ process.stderr.write(`Kimi ACP execution failed: ${safeErrorMessage(error, prompt)}\n`);
237
+ return 1;
238
+ }
239
+ finally {
240
+ process.off("SIGTERM", onSignal);
241
+ process.off("SIGINT", onSignal);
242
+ await stopChild(child);
243
+ }
244
+ }
245
+ function optionsFromArgv(argv) {
246
+ const { values } = parseArgs({
247
+ args: [...argv],
248
+ options: {
249
+ bin: { type: "string" },
250
+ model: { type: "string" },
251
+ },
252
+ });
253
+ if (!values.bin)
254
+ throw new Error("--bin is required");
255
+ return { bin: values.bin, ...(values.model ? { model: values.model } : {}) };
256
+ }
257
+ if (process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href) {
258
+ runKimiAcp(optionsFromArgv(process.argv.slice(2)))
259
+ .then((code) => { process.exitCode = code; })
260
+ .catch((error) => {
261
+ process.stderr.write(`Kimi ACP runner failed: ${safeErrorMessage(error, "")}\n`);
262
+ process.exitCode = 1;
263
+ });
264
+ }
@@ -10,6 +10,15 @@
10
10
  */
11
11
  // cross-spawn:win32 上 npm CLI 是 .cmd shim,node 原生 spawn 不带 shell 无法执行(ENOENT/EINVAL)
12
12
  import spawn from "cross-spawn";
13
+ // Windows CreateProcess receives one UTF-16 command line. Reserve room for the executable, flags,
14
+ // model and cmd shim quoting instead of relying on the theoretical 32767-character ceiling.
15
+ export const KIMI_LEGACY_PROMPT_MAX_UTF16 = 28_000;
16
+ export function assertKimiLegacyPromptFits(prompt, platform = process.platform) {
17
+ if (platform !== "win32" || prompt.length <= KIMI_LEGACY_PROMPT_MAX_UTF16)
18
+ return;
19
+ throw new Error(`Kimi legacy prompt exceeds the Windows argv limit (${prompt.length}/${KIMI_LEGACY_PROMPT_MAX_UTF16}); `
20
+ + "shorten the prompt (protocol-v1 remains disabled on Windows until Job Object ownership is available)");
21
+ }
13
22
  // Kimi Code 思考强度档位(kimi-code 0.23.0 实测+源码):无 CLI 参数,
14
23
  // 由 runner 经 KIMI_MODEL_THINKING_EFFORT env 注入;白名单外的值不注。
15
24
  export const KIMI_EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
@@ -24,6 +33,7 @@ export function buildKimiArgs(input) {
24
33
  return args;
25
34
  }
26
35
  export function spawnKimi(input) {
36
+ assertKimiLegacyPromptFits(input.wakePrompt);
27
37
  // stdio 固定 ignore/pipe/pipe,stdout/stderr 必为 Readable;cross-spawn 类型不带该细化,断言之
28
38
  return spawn(input.bin, buildKimiArgs(input), {
29
39
  cwd: input.cwd,
@@ -1,12 +1,16 @@
1
1
  export function normalizeScheduledPolicy(value) {
2
2
  return value === "always_report" ? "always_report" : "silent_unless_report";
3
3
  }
4
+ export function normalizeExternalNotificationPolicy(value) {
5
+ return value === "agent_decides" ? "agent_decides" : "disabled";
6
+ }
4
7
  export function normalizeScheduledContext(input) {
5
8
  return {
6
9
  jobId: input.jobId,
7
10
  runId: input.runId,
8
11
  title: input.title?.trim() || "Scheduled job",
9
12
  outputPolicy: normalizeScheduledPolicy(input.outputPolicy),
13
+ externalNotificationPolicy: normalizeExternalNotificationPolicy(input.externalNotificationPolicy),
10
14
  };
11
15
  }
12
16
  export async function deliverScheduledReport(input) {
@@ -27,6 +27,7 @@ export function reportAgentRunComplete(socket, input, log) {
27
27
  exitCode: input.exitCode,
28
28
  ...(input.wakeOrigin ? { wakeOrigin: input.wakeOrigin } : {}),
29
29
  ...(input.originDecision ? { originDecision: input.originDecision } : {}),
30
+ ...(input.contextUpToSeq !== undefined ? { contextUpToSeq: input.contextUpToSeq } : {}),
30
31
  ...(input.runtime ? { runtime: input.runtime } : {}),
31
32
  ...(input.model !== undefined ? { model: input.model } : {}),
32
33
  ...(input.resumed !== undefined ? { resumed: input.resumed } : {}),
package/dist/serve.js CHANGED
@@ -8,7 +8,7 @@ import { randomUUID } from "node:crypto";
8
8
  import { initSlog, dslog, setSlogDefaults, drainSpool, flushSlog } from "./slog.js";
9
9
  import { mergeRunAgentResults, reportScheduledStartFailure, runAgent } from "./runner.js";
10
10
  import { buildOriginDecisionRetryPrompt, buildScheduledPrompt } from "./prompt.js";
11
- import { collectMachineHello, DAEMON_CAPABILITIES, EXECUTION_PROTOCOL } from "./machine-info.js";
11
+ import { collectMachineHello, DAEMON_CAPABILITIES, EXECUTION_PROTOCOL, } from "./machine-info.js";
12
12
  import { listWorkspace, readWorkspaceFile } from "./workspace-fs.js";
13
13
  import { listSkills } from "./skills.js";
14
14
  import { inspectRaftWorkspace, importRaftWorkspace } from "./workspace-import.js";
@@ -18,8 +18,10 @@ import { formatDaemonLogLine } from "./log-format.js";
18
18
  import { reportAgentRunComplete } from "./scheduled-run-report.js";
19
19
  import { runWithOriginDecisionGuard } from "./origin-decision.js";
20
20
  import { createExecutionJournal } from "./execution-journal.js";
21
+ import { createExecutionTelemetryJournal } from "./execution-telemetry-journal.js";
21
22
  import { ExecutionRejectedSchema, ExecutionSnapshotSchema, LegacyAgentStartSchema, ServerToDaemonExecutionFrameSchema, } from "./execution-protocol.js";
22
23
  import { hashExecutionSpec, runExecution, } from "./execution-runner.js";
24
+ import { executionBackendCapability } from "./execution-backend.js";
23
25
  // normalize.ts 的活动种类 → activity 枚举
24
26
  const ACTIVITY_MAP = {
25
27
  init: "working", text: "thinking", reading: "reading", sending: "sending",
@@ -28,7 +30,7 @@ const ACTIVITY_MAP = {
28
30
  };
29
31
  export function buildControlPlaneUrl(serverUrl, machineToken, runtimePlatform = process.platform) {
30
32
  const query = new URLSearchParams({ key: machineToken });
31
- if (runtimePlatform !== "win32") {
33
+ if (executionBackendCapability(runtimePlatform).supported) {
32
34
  query.set("execution_min", String(EXECUTION_PROTOCOL.min));
33
35
  query.set("execution_max", String(EXECUTION_PROTOCOL.max));
34
36
  }
@@ -43,8 +45,9 @@ export function serve(config, opts = {}) {
43
45
  let backoff = 1000;
44
46
  const maxBackoff = opts.maxBackoffMs ?? 30_000;
45
47
  const executionJournal = opts.execution?.journal ?? createExecutionJournal(config.agentsRoot);
48
+ const executionTelemetry = createExecutionTelemetryJournal(config.agentsRoot);
46
49
  const executeProtocol = opts.execution?.runExecution ?? runExecution;
47
- let detectedRuntimes = [];
50
+ let detectedExecutionRuntimes = [];
48
51
  let runtimeFacts = null;
49
52
  const executionReady = executionJournal.reconcileAfterRestart().then(() => true, (error) => {
50
53
  dslog("execution.recovery_failed", "execution journal 恢复失败", {
@@ -66,6 +69,26 @@ export function serve(config, opts = {}) {
66
69
  }
67
70
  catch { /* reconnect/sync replays durable lifecycle */ }
68
71
  };
72
+ const reportExecutionFrame = async (frame) => {
73
+ if (frame.type === "execution:activity" || frame.type === "execution:console") {
74
+ try {
75
+ await executionTelemetry.append(frame);
76
+ }
77
+ catch (error) {
78
+ dslog("execution.telemetry_journal_failed", "execution telemetry 持久化失败", {
79
+ level: "ERROR", execution_id: frame.executionId,
80
+ error_message: error.message,
81
+ });
82
+ throw error;
83
+ }
84
+ }
85
+ safeExecutionSend(frame);
86
+ };
87
+ const replayExecutionTelemetry = async () => {
88
+ const frames = await executionTelemetry.replay();
89
+ for (const frame of frames)
90
+ safeExecutionSend(frame);
91
+ };
69
92
  const snapshotEntry = (entry) => {
70
93
  if ((entry.state === "completed" || entry.state === "interrupted") && entry.completion !== null) {
71
94
  return { executionId: entry.executionId, state: entry.state, completion: entry.completion, updatedAt: entry.updatedAt };
@@ -238,7 +261,7 @@ export function serve(config, opts = {}) {
238
261
  void drainSpool();
239
262
  // 上报本机信息 (hostname/os/daemon 版本/已装 runtimes)
240
263
  const helloPromise = collectMachineHello(config.agentsRoot, config.executionLimits);
241
- runtimeFacts = helloPromise.then((hello) => hello.runtimes, (error) => {
264
+ runtimeFacts = helloPromise.then((hello) => hello.executionRuntimes, (error) => {
242
265
  dslog("execution.runtime_detection_failed", "runtime 探测失败", {
243
266
  level: "ERROR", error_message: error.message,
244
267
  });
@@ -246,16 +269,21 @@ export function serve(config, opts = {}) {
246
269
  });
247
270
  void helloPromise
248
271
  .then((hello) => {
249
- detectedRuntimes = hello.runtimes;
272
+ detectedExecutionRuntimes = hello.executionRuntimes;
250
273
  try {
251
274
  ws?.send(JSON.stringify(hello));
252
- log(`📤 已上报机器信息: ${hello.hostname} · ${hello.os} · runtimes=[${hello.runtimes.join(",")}] · agents=[${hello.agentHandles.join(",")}]`);
275
+ log(`📤 已上报机器信息: ${hello.hostname} · ${hello.os} · installed=[${hello.runtimes.join(",")}] · executable=[${hello.executionRuntimes.join(",")}] · agents=[${hello.agentHandles.join(",")}]`);
253
276
  }
254
277
  catch { /* 非 OPEN,忽略 */ }
255
278
  })
256
279
  .catch(() => { });
257
280
  opts.onOpen?.(ws);
258
281
  void executionReady.then((ready) => ready ? sendSnapshot(`reconnect-${randomUUID()}`) : undefined);
282
+ void replayExecutionTelemetry().catch((error) => {
283
+ dslog("execution.telemetry_replay_failed", "execution telemetry 重放失败", {
284
+ level: "ERROR", error_message: error.message,
285
+ });
286
+ });
259
287
  });
260
288
  ws.on("message", async (data) => {
261
289
  let decoded;
@@ -292,6 +320,15 @@ export function serve(config, opts = {}) {
292
320
  await executionJournal.acknowledgeCompletion(frame.executionId).catch(() => { });
293
321
  return;
294
322
  }
323
+ if (frame.type === "execution:event-ack") {
324
+ await executionTelemetry.acknowledge(frame.executionId, frame.kind, frame.seq).catch((error) => {
325
+ dslog("execution.telemetry_ack_failed", "execution telemetry ACK 处理失败", {
326
+ level: "ERROR", execution_id: frame.executionId,
327
+ error_message: error.message,
328
+ });
329
+ });
330
+ return;
331
+ }
295
332
  if (frame.type === "execution:cancel") {
296
333
  if (!knownExecutionHashes.has(frame.executionId)
297
334
  && !executionRuns.has(frame.executionId)
@@ -352,7 +389,7 @@ export function serve(config, opts = {}) {
352
389
  let availableRuntimes;
353
390
  try {
354
391
  availableRuntimes = opts.execution?.availableRuntimes?.()
355
- ?? await (runtimeFacts ?? Promise.resolve(detectedRuntimes));
392
+ ?? await (runtimeFacts ?? Promise.resolve(detectedExecutionRuntimes));
356
393
  }
357
394
  catch (error) {
358
395
  reservation.release();
@@ -373,7 +410,7 @@ export function serve(config, opts = {}) {
373
410
  availableRuntimes,
374
411
  ...reservation.facts,
375
412
  },
376
- report: safeExecutionSend,
413
+ report: reportExecutionFrame,
377
414
  ...(reservation.state === undefined ? {} : {
378
415
  slot: { state: reservation.state, ready: reservation.ready },
379
416
  }),
@@ -478,6 +515,9 @@ export function serve(config, opts = {}) {
478
515
  ...(msg.scheduledRun.outputPolicy !== undefined
479
516
  ? { outputPolicy: msg.scheduledRun.outputPolicy }
480
517
  : {}),
518
+ ...(msg.scheduledRun.externalNotificationPolicy !== undefined
519
+ ? { externalNotificationPolicy: msg.scheduledRun.externalNotificationPolicy }
520
+ : {}),
481
521
  })
482
522
  : null;
483
523
  const threadId = msg.wake?.threadId;
@@ -582,7 +622,7 @@ export function serve(config, opts = {}) {
582
622
  ? `crew thread read`
583
623
  : `crew message read --channel ${msg.channelId}`;
584
624
  const wakeText = scheduled
585
- ? buildScheduledPrompt(msg.channelId, msg.wake?.content ?? "", scheduled.outputPolicy)
625
+ ? buildScheduledPrompt(msg.channelId, msg.wake?.content ?? "", scheduled.outputPolicy, scheduled.externalNotificationPolicy)
586
626
  : (msg.wake?.content
587
627
  ? `你被唤醒(${msg.reason}): ${msg.wake.content}\n用 ${readCmd} 读${threadId ? "本线程" : "频道"}后按需处理。${reasonHint}${ackHint}${threadHint}${attHint}`
588
628
  : undefined);
@@ -602,7 +642,11 @@ export function serve(config, opts = {}) {
602
642
  taskKey, // 每任务隔离 cwd + work-log(并行不冲突)
603
643
  runId, // 贯穿 SLS 日志的单轮关联键
604
644
  ...(scheduled ? {
605
- scheduled: { title: scheduled.title, outputPolicy: scheduled.outputPolicy },
645
+ scheduled: {
646
+ title: scheduled.title,
647
+ outputPolicy: scheduled.outputPolicy,
648
+ externalNotificationPolicy: scheduled.externalNotificationPolicy,
649
+ },
606
650
  } : {}),
607
651
  ...(wakeOrigin ? { wakeOrigin, originDecisionAttempt: attempt } : {}),
608
652
  // 唤醒锚点是具体消息(非纯频道唤醒)时,把它透传下去,供 `crew task create` 锚定到该消息。
@@ -648,6 +692,7 @@ export function serve(config, opts = {}) {
648
692
  ...(wakeOrigin ? {
649
693
  wakeOrigin,
650
694
  originDecision: result.originDecision ?? "missing",
695
+ ...(msg.wake?.seq !== undefined ? { contextUpToSeq: msg.wake.seq } : {}),
651
696
  } : {}),
652
697
  ...(scheduled ? { scheduledRunId: scheduled.runId } : {}),
653
698
  ...(result.errorMessage ? { errorMessage: result.errorMessage } : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nowcrew/daemon",
3
- "version": "0.5.14",
3
+ "version": "0.5.16",
4
4
  "type": "module",
5
5
  "description": "crew daemon — 运行在用户机器:拉起/管理 agent 进程,注入 crew CLI,归一化 runtime 事件",
6
6
  "license": "Apache-2.0",
@@ -17,10 +17,11 @@
17
17
  "access": "public"
18
18
  },
19
19
  "dependencies": {
20
+ "@agentclientprotocol/sdk": "1.2.1",
20
21
  "cross-spawn": "^7.0.6",
21
22
  "ws": "^8",
22
23
  "zod": "^3.23.0",
23
- "@nowcrew/cli": "^0.4.6"
24
+ "@nowcrew/cli": "^0.4.8"
24
25
  },
25
26
  "devDependencies": {
26
27
  "@types/cross-spawn": "^6.0.6",