@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.
- package/dist/bound-im-decision.js +22 -0
- package/dist/execution-protocol.js +3 -0
- package/dist/execution-runner.js +21 -1
- package/dist/origin-decision.js +1 -1
- package/dist/prompt.js +19 -12
- package/dist/runner.js +24 -1
- package/dist/scheduled-report.js +4 -0
- package/dist/scheduled-run-report.js +1 -0
- package/dist/serve.js +10 -2
- package/package.json +2 -2
|
@@ -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,
|
package/dist/execution-runner.js
CHANGED
|
@@ -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 }),
|
package/dist/origin-decision.js
CHANGED
|
@@ -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
|
|
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.
|
|
100
|
-
8.
|
|
101
|
-
9.
|
|
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
|
-
-
|
|
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
|
-
`-
|
|
293
|
-
|
|
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
|
package/dist/scheduled-report.js
CHANGED
|
@@ -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: {
|
|
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.
|
|
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.
|
|
24
|
+
"@nowcrew/cli": "^0.4.8"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/cross-spawn": "^6.0.6",
|