@nowcrew/daemon 0.5.15 → 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,22 @@
1
+ import { readFile, unlink } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ export function boundImDecisionFilePath(runDir) {
4
+ return join(runDir, ".bound-im-decision.json");
5
+ }
6
+ export async function resetBoundImDecisionFile(path) {
7
+ await unlink(path).catch((error) => {
8
+ if (error.code !== "ENOENT")
9
+ throw error;
10
+ });
11
+ }
12
+ export async function readBoundImDecisionFile(path) {
13
+ try {
14
+ const parsed = JSON.parse(await readFile(path, "utf8"));
15
+ if (!parsed || typeof parsed !== "object")
16
+ return null;
17
+ return parsed.decision === "notify" ? { decision: "notify" } : null;
18
+ }
19
+ catch {
20
+ return null;
21
+ }
22
+ }
@@ -55,6 +55,7 @@ export const LegacyAgentStartSchema = z.object({
55
55
  runId: z.string().min(1),
56
56
  title: z.string().optional(),
57
57
  outputPolicy: z.unknown().optional(),
58
+ externalNotificationPolicy: z.unknown().optional(),
58
59
  }).passthrough().optional(),
59
60
  silent: z.boolean().optional(),
60
61
  }).passthrough();
@@ -119,6 +120,7 @@ export const ExecutionStartSchema = z.object({
119
120
  captureFinal: z.boolean(),
120
121
  streamActivity: z.boolean(),
121
122
  streamConsole: z.boolean(),
123
+ allowBoundImDecision: z.boolean().optional(),
122
124
  }).strict(),
123
125
  }).strict();
124
126
  export const ExecutionCancelSchema = z.object({
@@ -203,6 +205,7 @@ const RawExecutionCompletedSchema = z.object({
203
205
  model: z.string().optional(),
204
206
  resumed: z.boolean(),
205
207
  finalText: z.string().optional(),
208
+ boundImDecision: z.enum(["notify", "silent"]).optional(),
206
209
  usage: ExecutionUsageSchema.optional(),
207
210
  startedAt: TimestampSchema,
208
211
  finishedAt: TimestampSchema,
@@ -1,4 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
+ import { join } from "node:path";
2
3
  import { fileURLToPath } from "node:url";
3
4
  import { DaemonToServerExecutionFrameSchema, ExecutionCompletedSchema, ExecutionRejectedSchema, ExecutionStartSchema, } from "./execution-protocol.js";
4
5
  import { JournalConflictError } from "./execution-journal.js";
@@ -10,6 +11,7 @@ import { buildClaudeArgs, CLAUDE_EFFORT_LEVELS } from "./runtimes/claude.js";
10
11
  import { buildCodexArgs, CODEX_EFFORT_LEVELS } from "./runtimes/codex.js";
11
12
  import { KIMI_EFFORT_LEVELS } from "./runtimes/kimi.js";
12
13
  import { executionBackendCapability } from "./execution-backend.js";
14
+ import { readBoundImDecisionFile, resetBoundImDecisionFile } from "./bound-im-decision.js";
13
15
  const ACTIVITY_KIND = {
14
16
  init: "working",
15
17
  text: "thinking",
@@ -411,6 +413,8 @@ export async function runExecution(config, input, dependencies) {
411
413
  const mint = dependencies.mintAgentToken ?? mintAgentToken;
412
414
  const execute = dependencies.executeLocal ?? executeLocal;
413
415
  const startSupervisor = dependencies.startSupervisor ?? startDormantSupervisor;
416
+ const readBoundImDecision = dependencies.readBoundImDecision ?? readBoundImDecisionFile;
417
+ const resetBoundImDecision = dependencies.resetBoundImDecision ?? resetBoundImDecisionFile;
414
418
  const telemetry = new TelemetryQueue(dependencies.report, bestEffortTimeoutMs, positiveTelemetryLimit(dependencies.telemetryMaxPendingFrames, DEFAULT_TELEMETRY_MAX_PENDING_FRAMES), Math.max(config.executionLimits.maxEventBytes, positiveTelemetryLimit(dependencies.telemetryMaxPendingBytes, DEFAULT_TELEMETRY_MAX_PENDING_BYTES)));
415
419
  const supervisorState = { active: null, abortOnce: null };
416
420
  let launchClosed = false;
@@ -423,6 +427,9 @@ export async function runExecution(config, input, dependencies) {
423
427
  let timeout;
424
428
  let timedOut = false;
425
429
  let completion;
430
+ let boundImDecision = spec.reporting.allowBoundImDecision
431
+ ? "silent"
432
+ : undefined;
426
433
  let rejectCancellationFailure;
427
434
  const cancellationFailure = new Promise((_resolve, reject) => {
428
435
  rejectCancellationFailure = reject;
@@ -431,7 +438,7 @@ export async function runExecution(config, input, dependencies) {
431
438
  if (dependencies.slot !== undefined) {
432
439
  await cancellable(dependencies.slot.ready, dependencies.cancellation);
433
440
  }
434
- const credential = await cancellable(mint(config.serverUrl, config.machineToken, spec.agent.handle, undefined, { executionId: spec.executionId }), dependencies.cancellation);
441
+ const credential = await cancellable(mint(config.serverUrl, config.machineToken, spec.agent.handle, undefined, { executionId: spec.executionId, agentRunId: spec.executionId }), dependencies.cancellation);
435
442
  const providerConfig = launchProviderConfig(credential.config);
436
443
  let activitySequence = 0;
437
444
  let consoleSequence = 0;
@@ -554,6 +561,11 @@ export async function runExecution(config, input, dependencies) {
554
561
  cliPath: config.cliPath,
555
562
  providerConfig,
556
563
  ...(providerConfig.description ? { description: providerConfig.description } : {}),
564
+ ...(spec.reporting.allowBoundImDecision ? {
565
+ systemEnv: {
566
+ CREW_BOUND_IM_DECISION_FILE: `.bound-im-decision-${spec.executionId}.json`,
567
+ },
568
+ } : {}),
557
569
  },
558
570
  session: {
559
571
  enabled: config.resume,
@@ -570,6 +582,12 @@ export async function runExecution(config, input, dependencies) {
570
582
  if (timeout !== undefined)
571
583
  clearTimeout(timeout);
572
584
  const finishedAt = now().toISOString();
585
+ if (spec.reporting.allowBoundImDecision) {
586
+ const path = join(result.workspaceRunDir, `.bound-im-decision-${spec.executionId}.json`);
587
+ const selected = await readBoundImDecision(path);
588
+ await resetBoundImDecision(path);
589
+ boundImDecision = selected?.decision ?? "silent";
590
+ }
573
591
  completion = ExecutionCompletedSchema.parse(timedOut ? {
574
592
  type: "execution:completed",
575
593
  protocolVersion: 1,
@@ -580,6 +598,7 @@ export async function runExecution(config, input, dependencies) {
580
598
  runtime: result.runtime,
581
599
  ...(result.model === null ? {} : { model: result.model }),
582
600
  resumed: result.resumed,
601
+ ...(boundImDecision ? { boundImDecision } : {}),
583
602
  startedAt,
584
603
  finishedAt,
585
604
  } : {
@@ -596,6 +615,7 @@ export async function runExecution(config, input, dependencies) {
596
615
  runtime: result.runtime,
597
616
  ...(result.model === null ? {} : { model: result.model }),
598
617
  resumed: result.resumed,
618
+ ...(boundImDecision ? { boundImDecision } : {}),
599
619
  ...(!spec.reporting.captureFinal || result.finalText === null
600
620
  ? {}
601
621
  : { finalText: result.finalText }),
@@ -27,7 +27,7 @@ export async function readOriginDecisionFile(path) {
27
27
  }
28
28
  }
29
29
  export function shouldRetryOriginDecision(wakeOrigin, decision, attempt) {
30
- return wakeOrigin === "wecom" && decision === null && attempt === 0;
30
+ return wakeOrigin === "wecom" && decision?.decision !== "reply" && attempt === 0;
31
31
  }
32
32
  export async function runWithOriginDecisionGuard(wakeOrigin, runAttempt) {
33
33
  const first = await runAttempt(0);
package/dist/prompt.js CHANGED
@@ -91,14 +91,15 @@ export function buildSystemPrompt(ctx) {
91
91
  ## 定时任务创建与管理
92
92
  平台不会用关键词预先区分创建或管理意图,由你根据完整来信和上下文判断用户是要创建定时任务,还是查看、修改、暂停、恢复、取消或立即执行已有任务。
93
93
  1. 定时任务创建与管理无需 \`crew task claim\`;不要仅为创建或管理定时任务而创建 task。
94
- 2. 创建时使用 \`crew schedule create --agent ${ctx.handle} --channel ${ctx.channelId} --prompt '<text>' (--cron '<expr>' | --at '<ISO>')\`,不得编造或替换当前 agent 身份与频道。
94
+ 2. 创建时使用 \`crew schedule create --agent ${ctx.handle} --channel ${ctx.channelId} --prompt '<text>' (--cron '<expr>' | --at '<ISO>') [--external-notification <disabled|agent-decides>]\`,不得编造或替换当前 agent 身份与频道。
95
95
  3. \`crew schedule create\` 和 \`crew schedule update\` 都必须遵守这条规则:\`prompt\`、\`title\`、\`cron\`、\`at\`、\`timezone\` 的值都必须分别作为单个 shell 参数传入,使用单引号 shell 引用;值内若含单引号,必须按标准方式关闭单引号、写入转义后的单引号、再重新开启单引号。不得在创建或更新持久化前展开或执行用户内容中的 \`$\`、反引号或 \`$()\`。
96
96
  4. 创建时默认使用 \`--output-policy always-report\`。创建时只有用户明确要求静默、不发送正常结果或仅在异常时汇报时,才使用 \`--output-policy on-exception\`。
97
97
  5. 更新时若用户未明确要求改变汇报行为,必须省略 \`--output-policy\` 并保留已有策略。只有用户明确要求改变汇报行为时,才按同样规则映射输出策略:正常汇报用 \`always-report\`,静默或仅异常汇报用 \`on-exception\`。创建或更新汇报行为时,把用户原始的汇报条件保留在 \`--prompt\` 中,不要改写或省略。汇报要求确实有歧义时,先向用户确认。
98
- 6. 执行时间、时区或执行指令信息不足时,先向用户确认,不得猜测。
99
- 7. 查询现状或执行 create/update/pause/resume/cancel/run-now 任何操作前,先运行 \`crew schedule list --channel ${ctx.channelId} --agent ${ctx.handle} --json\` 查询本频道绑定到你自己的候选;仅查看时也使用这条 JSON 查询,以获取完整的 cron/at/timezone/prompt/output policy。
100
- 8. 只有一个明确匹配时,才运行 \`crew schedule update <jobId> ...\` 或对应 pause/resume/cancel/run-now 命令。有多个合理候选时,列出候选并先让用户选择;用户选定前不得修改任何任务。
101
- 9. 修改请求不能用 \`crew schedule create\` 代替。完成后回复 job ID、最终 schedule、timezone 和 output policy;更新还要列出变更字段的前后值。`;
98
+ 6. 绑定会话通知创建时默认使用 \`disabled\`。只有用户明确要求将合适结果通知绑定会话时,才使用 \`--external-notification agent-decides\`;用户未明确要求修改该授权时,更新时必须省略 \`--external-notification\` 并保留已有策略。
99
+ 7. 执行时间、时区或执行指令信息不足时,先向用户确认,不得猜测。
100
+ 8. 查询现状或执行 create/update/pause/resume/cancel/run-now 任何操作前,先运行 \`crew schedule list --channel ${ctx.channelId} --agent ${ctx.handle} --json\` 查询本频道绑定到你自己的候选;仅查看时也使用这条 JSON 查询,以获取完整的 cron/at/timezone/prompt/output policy/external notification policy。
101
+ 9. 只有一个明确匹配时,才运行 \`crew schedule update <jobId> ...\` 或对应 pause/resume/cancel/run-now 命令。有多个合理候选时,列出候选并先让用户选择;用户选定前不得修改任何任务。
102
+ 10. 修改请求不能用 \`crew schedule create\` 代替。完成后回复 job ID、最终 schedule、timezone、output policy 和 external notification policy;更新还要列出变更字段的前后值。`;
102
103
  const voiceRule = alwaysReport
103
104
  ? "- **本轮报告只写在 runtime 最终回复中**:不要调用 `crew message send`;daemon 会把最终回复投递到频道。"
104
105
  : "- **始终只通过 crew CLI 发声。在 crew 命令之外产生的任何文字都不会送达任何人。**";
@@ -115,7 +116,7 @@ export function buildSystemPrompt(ctx) {
115
116
  8. **\`crew task update <taskId> --status <in_progress|in_review|done>\`** —— 推进任务状态。
116
117
  9. **\`crew task unclaim <taskId>\`** —— 释放认领,把任务让给别人。
117
118
  10. **\`crew task assign <taskId> --to <handle>\`** —— 把任务指派/交接给另一个 agent(用于交接,见下)。
118
- 11. **\`crew schedule create --agent <handle> --channel <id> --prompt <text> [--title <t>] (--cron <expr> | --at <ISO>) [--timezone <iana>] [--output-policy <always-report|on-exception>]\`** —— 创建定时任务。
119
+ 11. **\`crew schedule create --agent <handle> --channel <id> --prompt <text> [--title <t>] (--cron <expr> | --at <ISO>) [--timezone <iana>] [--output-policy <always-report|on-exception>] [--external-notification <disabled|agent-decides>]\`** —— 创建定时任务。
119
120
  12. **\`crew schedule list --channel <id> --agent <handle> [--json]\`** —— 查询本频道指定 agent 的定时任务,\`--json\` 返回完整字段。
120
121
  13. **\`crew schedule update <jobId> ...\`** —— 修改定时任务标题、指令、时间或输出策略。
121
122
  14. **\`crew schedule pause|resume|cancel|run-now <jobId>\`** —— 控制定时任务。`;
@@ -139,10 +140,13 @@ ${taskAndScheduleCommands}`;
139
140
  : `
140
141
  - **freshness/draft**:发送若被保存为 draft(kind=held),要么重读后用普通 send 改写,要么用 \`crew message send --send-draft\` 原样发出(不要在改内容时用 --send-draft)。`;
141
142
  const externalReplyRule = scheduled
142
- ? ""
143
+ ? ctx.scheduledExternalNotificationPolicy === "agent_decides"
144
+ ? `
145
+ - **绑定会话通知由你选择**:只有本轮完整结果确实值得打扰绑定会话时,运行 \`crew message notify-bound-im --channel ${ctx.channelId}\` 一次,然后仍只返回一个完整最终报告。该命令只记录本轮决策,不会自行发消息,也不能指定收件人。`
146
+ : ""
143
147
  : ctx.wakeOrigin === "wecom"
144
148
  ? `
145
- - **本轮来自企微,结束本轮前必须明确选择外部回复决策**:确认、过程进展和内部协作仍用普通 \`crew message send\`,只写入 NowWork。完整结果确实要回复企微时,用 \`crew message send --reply-origin\`(同时带当前 channel/thread/content 参数);判断无需回复企微时,用 \`crew message skip-origin --reason "简短原因"\`。两者必须选择一个;不要把 \`--send-draft\` 当成草稿 ID 或外部回复开关。Server 仍会校验 thread 来源和机器人绑定。`
149
+ - **本轮来自企微,结束本轮前必须给出一条完整回复**:确认、过程进展和内部协作仍用普通 \`crew message send\`,只写入 NowWork。最终只用一次 \`crew message send --reply-origin\`(同时带当前 channel/thread/content 参数)发送有实质内容的完整结果;不要把 \`--send-draft\` 当成草稿 ID 或外部回复开关。Server 仍会校验 thread 来源和机器人绑定,并在本轮未形成可交付内容时发送统一兜底回复。`
146
150
  : `
147
151
  - **外部来源回复由你决定**:普通 \`crew message send\` 只写入 NowWork。只有当前 thread 明确来自企微等外部 IM、且这条消息确实要回复外部发言人时,才给该次发送加 \`--reply-origin\`;Server 会按当前 thread 的已验证来源路由,你不能指定任意机器人或会话。确认、过程进展、内部协作消息不要使用该参数。若判断无需回外部,只发普通内部消息或保持沉默。`;
148
152
  const interactiveTaskRules = scheduled ? "" : `
@@ -255,11 +259,14 @@ ${ctx.memory ? `\n## [注入] 你的 MEMORY.md(索引,只读参考)\n${ctx.memor
255
259
  /** 静默定时任务的唤醒提示词:不注入协作礼仪/线程提示;默认零输出(设计文档 §5.4)。
256
260
  * 汇报用 --send-draft:绕过 freshness hold(-p 单发不跑 crew read,游标落后,
257
261
  * 普通 send 会被 202 扣成 draft——监控告警绝不能被静默扣留)。 */
258
- export function buildScheduledPrompt(channelId, jobPrompt, outputPolicy = "silent_unless_report") {
259
- if (outputPolicy === "always_report") {
262
+ export function buildScheduledPrompt(channelId, jobPrompt, outputPolicy = "silent_unless_report", externalNotificationPolicy = "disabled") {
263
+ if (outputPolicy === "always_report" || externalNotificationPolicy === "agent_decides") {
260
264
  return [
261
265
  "You are executing a scheduled job. No one is waiting for an acknowledgement.",
262
266
  `Scheduled instruction: ${jobPrompt}`,
267
+ ...(externalNotificationPolicy === "agent_decides" ? [
268
+ `If and only if this complete result warrants notifying the bound conversation, run \`crew message notify-bound-im --channel ${channelId}\` once. The command records a local decision and does not send a second message.`,
269
+ ] : []),
263
270
  "Return exactly one self-contained final report as your runtime final response.",
264
271
  "Do not call crew message send; the daemon delivers the final response.",
265
272
  "Do not send acknowledgements or progress messages.",
@@ -289,8 +296,8 @@ export function buildOriginDecisionRetryPrompt(channelId, threadId) {
289
296
  const thread = threadId ? ` --thread ${threadId}` : "";
290
297
  return [
291
298
  "本轮尚未完成企微回复决策。不要重复执行已经完成的工作,只完成下面这个决策后结束:",
292
- `- 需要回复企微:用 crew message send --channel ${channelId}${thread} --reply-origin --content "完整最终回复"。`,
293
- `- 不需要回复企微:用 crew message skip-origin --channel ${channelId} --reason "简短原因"。`,
299
+ `- 必须回复企微:用 crew message send --channel ${channelId}${thread} --reply-origin --content "完整最终回复"。`,
300
+ "- 只发送一条有实质内容的完整回复,不要拆成确认、进展和结论多条外部消息。",
294
301
  "普通内部消息不算外部回复决策;--send-draft 也不是外部回复开关。",
295
302
  ].join("\n");
296
303
  }
package/dist/runner.js CHANGED
@@ -7,6 +7,7 @@ import { deliverScheduledReport, } from "./scheduled-report.js";
7
7
  import { executeLocal } from "./local-executor.js";
8
8
  import { ReasoningSchema } from "./execution-protocol.js";
9
9
  import { readOriginDecisionFile, resetOriginDecisionFile } from "./origin-decision.js";
10
+ import { readBoundImDecisionFile, resetBoundImDecisionFile } from "./bound-im-decision.js";
10
11
  export { awaitExit, exitActivity, sanitizeEnvVars } from "./local-executor.js";
11
12
  const ICON = {
12
13
  init: "🟢", text: "💬", reading: "📖", sending: "📨", checking: "🔎",
@@ -31,6 +32,9 @@ export async function runAgent(config, input, onActivity = defaultPrint, onConso
31
32
  const originDecisionFileName = input.wakeOrigin === "wecom"
32
33
  ? `.origin-decision-${executionId}.json`
33
34
  : null;
35
+ const boundImDecisionFileName = input.scheduled?.externalNotificationPolicy === "agent_decides"
36
+ ? `.bound-im-decision-${executionId}.json`
37
+ : null;
34
38
  const local = await executeLocal({
35
39
  executionId,
36
40
  handle: input.handle,
@@ -45,6 +49,9 @@ export async function runAgent(config, input, onActivity = defaultPrint, onConso
45
49
  productName: config.productName,
46
50
  platform: process.platform,
47
51
  ...(input.scheduled ? { scheduledOutputPolicy: input.scheduled.outputPolicy } : {}),
52
+ ...(input.scheduled?.externalNotificationPolicy
53
+ ? { scheduledExternalNotificationPolicy: input.scheduled.externalNotificationPolicy }
54
+ : {}),
48
55
  ...(input.wakeOrigin ? { wakeOrigin: input.wakeOrigin } : {}),
49
56
  memory: capMemoryForInject(workspace.memory),
50
57
  ...(resuming ? {} : { workLog: capWorkLogForInject(workspace.workLog) }),
@@ -82,6 +89,9 @@ export async function runAgent(config, input, onActivity = defaultPrint, onConso
82
89
  CREW_WAKE_ORIGIN: "wecom",
83
90
  CREW_ORIGIN_DECISION_FILE: originDecisionFileName,
84
91
  } : {}),
92
+ ...(boundImDecisionFileName ? {
93
+ CREW_BOUND_IM_DECISION_FILE: boundImDecisionFileName,
94
+ } : {}),
85
95
  },
86
96
  },
87
97
  session: {
@@ -117,9 +127,20 @@ export async function runAgent(config, input, onActivity = defaultPrint, onConso
117
127
  process.stdout.write(`📊 tokens: in=${usage.inputTokens} out=${usage.outputTokens} cache_read=${usage.cacheReadTokens} cache_create=${usage.cacheCreationTokens}`
118
128
  + `${usage.costUsd != null ? ` cost=$${usage.costUsd.toFixed(4)}` : ""} ${local.resumed ? "(resumed)" : "(fresh)"}\n`);
119
129
  }
130
+ const boundImDecisionPath = boundImDecisionFileName
131
+ ? join(local.workspaceRunDir, boundImDecisionFileName)
132
+ : null;
133
+ const selectedBoundImDecision = boundImDecisionPath
134
+ ? await readBoundImDecisionFile(boundImDecisionPath)
135
+ : null;
136
+ if (boundImDecisionPath)
137
+ await resetBoundImDecisionFile(boundImDecisionPath);
138
+ const boundImDecision = boundImDecisionPath
139
+ ? selectedBoundImDecision?.decision ?? "silent"
140
+ : undefined;
120
141
  const report = input.scheduled
121
142
  ? await deliverScheduledReport({
122
- policy: input.scheduled.outputPolicy,
143
+ policy: boundImDecision === "notify" ? "always_report" : input.scheduled.outputPolicy,
123
144
  title: input.scheduled.title,
124
145
  exitCode: local.exitCode,
125
146
  finalText: local.finalText,
@@ -127,6 +148,7 @@ export async function runAgent(config, input, onActivity = defaultPrint, onConso
127
148
  send: (content) => sendAgentMessage(config.serverUrl, credential.token, input.channelId, {
128
149
  content,
129
150
  force: true,
151
+ ...(boundImDecision === "notify" ? { notifyBoundIm: true } : {}),
130
152
  }),
131
153
  })
132
154
  : undefined;
@@ -146,6 +168,7 @@ export async function runAgent(config, input, onActivity = defaultPrint, onConso
146
168
  errorMessage: local.errorMessage,
147
169
  ...(local.usage ? { usage: local.usage } : {}),
148
170
  ...(report ? { report } : {}),
171
+ ...(boundImDecision ? { boundImDecision } : {}),
149
172
  ...(input.wakeOrigin === "wecom" ? {
150
173
  originDecision: originDecision?.decision ?? "missing",
151
174
  ...(originDecision?.decision === "silent" && originDecision.reason
@@ -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
@@ -515,6 +515,9 @@ export function serve(config, opts = {}) {
515
515
  ...(msg.scheduledRun.outputPolicy !== undefined
516
516
  ? { outputPolicy: msg.scheduledRun.outputPolicy }
517
517
  : {}),
518
+ ...(msg.scheduledRun.externalNotificationPolicy !== undefined
519
+ ? { externalNotificationPolicy: msg.scheduledRun.externalNotificationPolicy }
520
+ : {}),
518
521
  })
519
522
  : null;
520
523
  const threadId = msg.wake?.threadId;
@@ -619,7 +622,7 @@ export function serve(config, opts = {}) {
619
622
  ? `crew thread read`
620
623
  : `crew message read --channel ${msg.channelId}`;
621
624
  const wakeText = scheduled
622
- ? buildScheduledPrompt(msg.channelId, msg.wake?.content ?? "", scheduled.outputPolicy)
625
+ ? buildScheduledPrompt(msg.channelId, msg.wake?.content ?? "", scheduled.outputPolicy, scheduled.externalNotificationPolicy)
623
626
  : (msg.wake?.content
624
627
  ? `你被唤醒(${msg.reason}): ${msg.wake.content}\n用 ${readCmd} 读${threadId ? "本线程" : "频道"}后按需处理。${reasonHint}${ackHint}${threadHint}${attHint}`
625
628
  : undefined);
@@ -639,7 +642,11 @@ export function serve(config, opts = {}) {
639
642
  taskKey, // 每任务隔离 cwd + work-log(并行不冲突)
640
643
  runId, // 贯穿 SLS 日志的单轮关联键
641
644
  ...(scheduled ? {
642
- scheduled: { title: scheduled.title, outputPolicy: scheduled.outputPolicy },
645
+ scheduled: {
646
+ title: scheduled.title,
647
+ outputPolicy: scheduled.outputPolicy,
648
+ externalNotificationPolicy: scheduled.externalNotificationPolicy,
649
+ },
643
650
  } : {}),
644
651
  ...(wakeOrigin ? { wakeOrigin, originDecisionAttempt: attempt } : {}),
645
652
  // 唤醒锚点是具体消息(非纯频道唤醒)时,把它透传下去,供 `crew task create` 锚定到该消息。
@@ -685,6 +692,7 @@ export function serve(config, opts = {}) {
685
692
  ...(wakeOrigin ? {
686
693
  wakeOrigin,
687
694
  originDecision: result.originDecision ?? "missing",
695
+ ...(msg.wake?.seq !== undefined ? { contextUpToSeq: msg.wake.seq } : {}),
688
696
  } : {}),
689
697
  ...(scheduled ? { scheduledRunId: scheduled.runId } : {}),
690
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.15",
3
+ "version": "0.5.16",
4
4
  "type": "module",
5
5
  "description": "crew daemon — 运行在用户机器:拉起/管理 agent 进程,注入 crew CLI,归一化 runtime 事件",
6
6
  "license": "Apache-2.0",
@@ -21,7 +21,7 @@
21
21
  "cross-spawn": "^7.0.6",
22
22
  "ws": "^8",
23
23
  "zod": "^3.23.0",
24
- "@nowcrew/cli": "^0.4.7"
24
+ "@nowcrew/cli": "^0.4.8"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/cross-spawn": "^6.0.6",