@nowcrew/daemon 0.6.15 → 0.6.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.
@@ -1,5 +1,10 @@
1
1
  import { AbilityResolveCommandSchema } from "./types.js";
2
2
  import { resolveAbilityRelease } from "./resolver.js";
3
+ export const normalizeAbilityCommandError = (error, fallback) => {
4
+ const message = error instanceof Error ? error.message : fallback;
5
+ const structured = message.match(/^(ability_[a-z0-9_]+)(?::|$)/u)?.[1];
6
+ return structured ?? fallback;
7
+ };
3
8
  export function createAgentAbilityController(deps) {
4
9
  const resolver = deps.resolve ?? resolveAbilityRelease;
5
10
  return {
@@ -11,8 +16,7 @@ export function createAgentAbilityController(deps) {
11
16
  return { ok: true, data: await resolver(deps.agentsRoot, parsed.data) };
12
17
  }
13
18
  catch (error) {
14
- const message = error instanceof Error ? error.message : "ability_resolve_failed";
15
- return { ok: false, error: /^[a-z0-9_:-]+$/u.test(message) ? message.slice(0, 200) : "ability_resolve_failed" };
19
+ return { ok: false, error: normalizeAbilityCommandError(error, "ability_resolve_failed") };
16
20
  }
17
21
  },
18
22
  };
@@ -12,6 +12,7 @@ const MAX_TOTAL_BYTES = 25 * 1024 * 1024;
12
12
  const MAX_FILE_BYTES = 2 * 1024 * 1024;
13
13
  const MAX_INSTRUCTIONS_BYTES = 96 * 1024;
14
14
  const MAX_MEMORY_BYTES = 64 * 1024;
15
+ const AUTHORING_ONLY_ROOTS = new Set([".claude"]);
15
16
  const DEFAULT_GIT_HOSTS = ["codeup.aliyun.com", "github.com", "gitlab.com", "bitbucket.org"];
16
17
  const EvalSuiteSchema = z.object({
17
18
  schemaVersion: z.literal("nowcrew-eval-suite/v1"),
@@ -81,6 +82,11 @@ export const collectAbilityFiles = async (root) => {
81
82
  const visit = async (directory) => {
82
83
  const entries = await readdir(directory, { withFileTypes: true });
83
84
  for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
85
+ // These roots contain package-authoring helpers, not runtime training assets. In particular,
86
+ // the standard template exposes its release helper through a .claude symlink; traversing it
87
+ // both double-counts authoring code and trips the runtime symlink boundary unnecessarily.
88
+ if (directory === root && AUTHORING_ONLY_ROOTS.has(entry.name))
89
+ continue;
84
90
  if (entry.name === ".git" || entry.name === ".DS_Store" || entry.name === "__pycache__"
85
91
  || entry.name.endsWith(".pyc") || entry.name.endsWith(".pyo"))
86
92
  continue;
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { AbilityReleaseSnapshotSchema, AgentHandleSchema } from "../execution-protocol.js";
3
- import { createAgentAbilityController } from "./controller.js";
3
+ import { createAgentAbilityController, normalizeAbilityCommandError } from "./controller.js";
4
4
  import { createAgentAbilityMaterializer } from "./materializer.js";
5
5
  import { AGENT_ABILITY_CAPABILITY, AGENT_ABILITY_RUNTIME_ASSETS_CAPABILITY, AGENT_ABILITY_WORKSPACE_CAPABILITY, } from "./types.js";
6
6
  const AbilityApplyCommandSchema = z.object({
@@ -41,10 +41,9 @@ export function createAgentAbilityRuntime(agentsRoot, options = {}) {
41
41
  result = { ok: true, data: await materializer.apply(agentsRoot, parsed.data.handle, parsed.data.ability) };
42
42
  }
43
43
  catch (error) {
44
- const message = error instanceof Error ? error.message : "ability_workspace_projection_failed";
45
44
  result = {
46
45
  ok: false,
47
- error: /^[a-z0-9_:-]+$/u.test(message) ? message.slice(0, 200) : "ability_workspace_projection_failed",
46
+ error: normalizeAbilityCommandError(error, "ability_workspace_projection_failed"),
48
47
  };
49
48
  }
50
49
  }
package/dist/prompt.js CHANGED
@@ -89,12 +89,16 @@ export function buildSystemPrompt(ctx) {
89
89
  // scheduled 换成静默版表述(2026-07-13 复审 Minor 5)。
90
90
  const threadTaskRule = scheduled
91
91
  ? "\n- **静默任务无线程锚点**:只有存在需要人跟进的具体事项时才用 `crew task create --title \"…\"`(无需 `--thread`——本轮没有触发消息可绑)。"
92
- : `\n- **线程 = 工作单元 / 一个请求一个 task(CRITICAL)**:一条对话(thread)对应一件事,最多绑**一个** task。若这件事需要创建 task,只建一个,且必须把它绑到当前线程:\`crew task create --title "…" --thread <触发你的那条消息 id>\`(那条消息就是线程根)。**一定要带 \`--thread\`**——不带会另起一条飘在顶层的新线程,task 就和你的讨论分家了(这正是要避免的)。**别把一个请求拆成多个 task**(如"拉代码"+"读文档"+"写记忆"是同一件事 → 一个 task,用 todo/进度推进,不要建第二个)。当前线程已有 task 时再建会被服务端拒绝(报错会提示你)。`;
92
+ : ctx.wakeMessageId
93
+ ? `\n- **线程 = 工作单元 / 一个请求一个 task(CRITICAL)**:一条对话(thread)对应一件事,最多绑**一个** task。若这件事需要创建 task,只建一个,且必须把它绑到当前线程:\`crew task create --title "…" --thread <触发你的那条消息 id>\`(那条消息就是线程根)。**一定要带 \`--thread\`**——不带会另起一条飘在顶层的新线程,task 就和你的讨论分家了(这正是要避免的)。**别把一个请求拆成多个 task**(如"拉代码"+"读文档"+"写记忆"是同一件事 → 一个 task,用 todo/进度推进,不要建第二个)。当前线程已有 task 时再建会被服务端拒绝(报错会提示你)。`
94
+ : `\n- **无线程锚点 / 一个请求一个 task(CRITICAL)**:当前来信位于频道顶层,没有可复用的当前线程。若这件事需要创建 task,只建一个,使用 \`crew task create --channel ${ctx.channelId} --new-thread --title "…"\` 创建并绑定新线程;之后以命令返回的线程根作为这件事的唯一线程。**别把一个请求拆成多个 task**(如"拉代码"+"读文档"+"写记忆"是同一件事 → 一个 task,用 todo/进度推进,不要建第二个)。`;
93
95
  const progressRule = alwaysReport
94
96
  ? "\n- **报告交由 daemon 投递**:只返回一个完整最终报告,不要调用 `crew message send`;本轮没有线程锚点。"
95
97
  : scheduled
96
98
  ? "\n- **产出走频道顶层**:静默 run 唯一的输出路径是 `crew message send --channel <id> --send-draft`(顶层、无 thread)——本轮没有触发消息,没有线程锚点可回。"
97
- : "\n- **进度/产出回本线程**:这件事的认领/进度/完成汇报都用 `crew message send --thread <当前线程根>` 回复在**这个线程里**(线程根 = 触发你的那条消息 id,即 \$CREW_WAKE_MESSAGE_ID)。**严禁把进度发到别的线程或频道顶层。**";
99
+ : ctx.wakeMessageId
100
+ ? "\n- **进度/产出回本线程**:这件事的认领/进度/完成汇报都用 `crew message send --thread <当前线程根>` 回复在**这个线程里**(线程根 = 触发你的那条消息 id,即 \$CREW_WAKE_MESSAGE_ID)。**严禁把进度发到别的线程或频道顶层。**"
101
+ : `\n- **无线程锚点时的回复位置**:不创建 task 的直接答复用 \`crew message send --channel ${ctx.channelId}\` 回复频道顶层;创建 task 后,这件事的认领/进度/完成汇报都回复到命令返回的新线程根。不得虚构线程 id。`;
98
102
  const scheduleIntentRule = scheduled ? "" : `
99
103
 
100
104
  ## 定时任务创建与管理
@@ -115,12 +119,15 @@ export function buildSystemPrompt(ctx) {
115
119
  const actionRule = scheduled
116
120
  ? `- **判定规则**:直接执行本轮定时指令,执行前不需要 claim。${threadTaskRule}${progressRule}`
117
121
  : `- **判定规则**:定时任务创建与管理和 memory_prune 无需 task claim,只遵守各自唤醒指令;其它来信若需要你"回复之外的动作"(跑工具/改代码/做变更),先 claim;若只是回答问题或闲聊,无需 claim。${threadTaskRule}${progressRule}`;
122
+ const interactiveTaskCreateCommand = ctx.wakeMessageId
123
+ ? `6. **\`crew task create --channel <id> --title "<标题>" --thread <当前线程根msgId>\`** —— 新建任务并**绑定到当前线程**。\`--thread\` 传你读到的那条**触发消息 id**(线程根),任务就和讨论同处一个线程。省略 \`--thread\` 会另起新线程,**几乎总是错的——务必带上**。`
124
+ : `6. **\`crew task create --channel <id> --new-thread --title "<标题>"\`** —— 当前没有线程锚点时,新建任务并绑定到命令返回的新线程;后续回复使用返回的完整线程根消息 id。`;
118
125
  const taskAndScheduleCommands = alwaysReport
119
126
  ? `4. **\`crew task create --channel <id> --title "<标题>"\`** —— 仅为本轮发现的具体后续事项创建任务;本轮没有线程锚点,不要传 \`--thread\`。`
120
127
  : scheduled
121
128
  ? `5. **\`crew task create --channel <id> --title "<标题>"\`** —— 仅为本轮发现的具体后续事项创建任务;本轮没有线程锚点,不要传 \`--thread\`。`
122
129
  : `5. **\`crew task list --channel <id>\`** —— 看任务板。支持 \`--status <s>\` / \`--mine\`。
123
- 6. **\`crew task create --channel <id> --title "<标题>" --thread <当前线程根msgId>\`** —— 新建任务并**绑定到当前线程**。\`--thread\` 传你读到的那条**触发消息 id**(线程根),任务就和讨论同处一个线程。省略 \`--thread\` 会另起新线程,**几乎总是错的——务必带上**。
130
+ ${interactiveTaskCreateCommand}
124
131
  7. **\`crew task claim <taskId>\`** —— 除定时任务创建与管理和 memory_prune 外,执行需要动作的工作前先认领任务。
125
132
  8. **\`crew task update <taskId> --status <in_progress|in_review|done>\`** —— 推进任务状态。
126
133
  9. **\`crew task unclaim <taskId>\`** —— 释放认领,把任务让给别人。
@@ -129,6 +136,14 @@ export function buildSystemPrompt(ctx) {
129
136
  12. **\`crew schedule list --channel <id> --agent <handle> [--json]\`** —— 查询本频道指定 agent 的定时任务,\`--json\` 返回完整字段。
130
137
  13. **\`crew schedule update <jobId> ...\`** —— 修改定时任务标题、指令、时间或输出策略。
131
138
  14. **\`crew schedule pause|resume|cancel|run-now <jobId>\`** —— 控制定时任务。`;
139
+ const memoryTaskReadRule = scheduled
140
+ ? ""
141
+ : ctx.wakeMessageId
142
+ ? `- 普通交互执行将写共享记忆前,先运行 crew thread read --thread "$CREW_WAKE_MESSAGE_ID"。crew thread read 返回的顶层 task 是当前线程任务的权威事实;不得把 parent.task 或同频道其它 task 当成当前线程任务。顶层 task 为 null 时按当前线程没有 task 处理。`
143
+ : `- 普通交互执行将写共享记忆前,先运行 crew message read --channel ${ctx.channelId}。当前没有线程锚点时不得运行 crew thread read;需要普通 task 时先用 crew task create --channel ${ctx.channelId} --new-thread --title "…" 创建新线程,再对返回的线程根运行 crew thread read --thread <线程根> 并以其顶层 task 为权威事实。不得把同频道其它 task 当成当前任务。`;
144
+ const messageThreadUsage = ctx.wakeMessageId
145
+ ? "被唤醒时优先使用环境变量 `$CREW_WAKE_MESSAGE_ID` 或唤醒提示里的完整 id,不要手动截短。"
146
+ : "当前没有线程锚点:频道顶层回复只带 `--channel`;创建 task 后再使用命令返回的完整线程根消息 id。";
132
147
  const communicationSection = alwaysReport
133
148
  ? `## 控制面工具 —— crew CLI
134
149
  需要读取频道或操作任务时使用 crew CLI;最终报告不得通过消息命令发送。可使用:
@@ -142,7 +157,7 @@ ${taskAndScheduleCommands}`
142
157
  2. **\`crew message read --channel <id>\`** —— 读频道历史(读取即自动推进你的已读/新鲜度游标)。支持 \`--after <seq>\` / \`--limit <n>\`。
143
158
  3. **\`crew message check --channel <id>\`** —— 非阻塞查看未读数。工作中可在自然断点随时用。
144
159
  4. **\`crew message send --channel <id>\`** —— 发消息。${messageBodyInput}
145
- 也可用 \`--content "<短正文>"\`。线程内回复:加 \`--thread <完整线程根消息 id>\`。被唤醒时优先使用环境变量 \`$CREW_WAKE_MESSAGE_ID\` 或唤醒提示里的完整 id,不要手动截短。
160
+ 也可用 \`--content "<短正文>"\`。线程内回复:加 \`--thread <完整线程根消息 id>\`。${messageThreadUsage}
146
161
  ${taskAndScheduleCommands}`;
147
162
  const freshnessRule = alwaysReport
148
163
  ? ""
@@ -162,8 +177,11 @@ ${taskAndScheduleCommands}`;
162
177
  : `
163
178
  - **本轮是 NowWork 内部唤醒**:普通 \`crew message send\` 只写入 NowWork。内部唤醒不得使用 \`--reply-origin\`,该参数只回答直接触发本轮的企微原消息。
164
179
  - **绑定会话主动通知**:用户明确要求同步,或最终结果有实质结论、变更或需群用户行动的阻塞时,才用一次 \`crew message send --notify-bound-im\`(同时带当前 channel/thread/content 参数)请求通知当前频道绑定的外部会话。Server 会校验绑定 owner 授权、绑定 Agent 和单轮边界;不能指定收件人。确认、进度、中间结果、无变化和重复内容一律留在 NowWork。${EXTERNAL_RESULT_READABILITY}`;
180
+ const newThreadRule = ctx.wakeMessageId
181
+ ? "- **毫不相关的新任务才另起线程**:只有要处理的事**和当前线程毫不相关**(或用户明确要求新建)时,才用 `crew task create --new-thread --title \"…\"`——系统另起一个子线程(parent=当前线程)绑新 task;之后这件事的回复要发到**这个新子线程**里。能不拆就不拆。"
182
+ : `- **当前没有线程锚点**:需要普通 task 时用 \`crew task create --channel ${ctx.channelId} --new-thread --title "…"\` 创建根线程;之后这件事的回复都发到返回的新线程里。`;
165
183
  const interactiveTaskRules = scheduled ? "" : `
166
- - **毫不相关的新任务才另起线程**:只有要处理的事**和当前线程毫不相关**(或用户明确要求新建)时,才用 \`crew task create --new-thread --title "…"\`——系统另起一个子线程(parent=当前线程)绑新 task;之后这件事的回复要发到**这个新子线程**里。能不拆就不拆。
184
+ ${newThreadRule}
167
185
  - 任务状态流:\`todo → in_progress → in_review → done\`。claim 后用 \`crew task update\` 推进:开工→in_progress、完成待验收→in_review、人类确认后→done。只有 assignee 能改自己任务的状态。
168
186
  - **交接(handoff)**:当你这一环干完、需要别的角色接手时(如开发完成 → 交给 QA 测试),用 \`crew task assign <taskId> --to <下家handle>\` 把任务交接出去,并在线程里给下家足够背景(分支名 / 改动摘要 / 测试建议)。交接后对方会被自动唤醒。**别让任务停在你手里无人跟进**。
169
187
  - **分诊(若你是总管)**:若你收到「【分诊请求】」唤醒,说明频道里有一个无人认领的任务需要你按团队职责分派。唤醒内容里已附上团队成员及其职责:判断谁最合适,用 \`crew task assign <taskId> --to <handle>\` 指派给他(若该你自己做就 \`crew task claim\`);确实没人合适时,在频道里 @发起人 说明并给建议,**不要让任务悬空**。`;
@@ -237,6 +255,7 @@ ${taskAndScheduleCommands}`;
237
255
  - **普通任务默认只写两处**:① 本任务的隔离 cwd(代码/草稿);② 本任务的工作日志 **\`$CREW_TASK_LOG\`**(每任务一份,别人不会碰)。默认不要修改共享 \`$CREW_HOME/MEMORY.md\` 或 notes/;只有当前用户明确要求更新或记住长期记忆,或明确表达具体且跨后续任务持续生效的规则、偏好或约束时,才按下方条件写入相关共享文件。在工作日志里记:当前阶段 / 下一步 / 卡在谁 / 关键结论(每条尽量 ≤3 行)。
238
256
  - **当前请求明确要求更新或记住长期记忆,明确表达需要跨后续任务持续生效的具体规则、偏好或约束,或当前消息是对本线程紧邻记忆方案的无歧义确认时**,可以按最小范围直接写共享记忆。例如“以后所有测试报告统一按五部分写”属于明确长期意图,即使没有使用“记忆”一词。先读取相关索引和 note,语义已存在时不重复追加,不要把 Agent 自己判断值得记住视为用户授权。
239
257
  - 本节的直接更新共享记忆和相关 task 流转仅适用于用户消息触发的普通交互执行。memory_prune 和 scheduled 执行只遵守各自唤醒指令,不得依据本节认领、创建、重开或推进 task。
258
+ ${memoryTaskReadRule}
240
259
  - 普通交互执行将写共享记忆时,使用当前线程唯一的普通 task:已有非终态 task 时先按归属规则确保当前 Agent 成功认领;当前线程没有 task 时先创建并认领一个普通 task;已有 task 为 done/closed 时不得由 Agent 重开,done 报告当前轻量方案无法安全复用终态 task,closed 保持人工终态。遇到终态或归属问题时报告冲突且不得创建第二个 task 或新的任务类型。完成写入后,只有任务本身的全部工作已完成时才推进终态以触发 prune。
241
260
  - 提出共享记忆修改方案并等待用户确认时,该 task 仍有剩余工作,保持 in_review 或其它非终态;收到无歧义确认后再执行写入,并仅在全部工作完成后推进终态。
242
261
  - 普通执行直接更新后,仍然保留任务结束后的专门收尾执行,不因已经写过而跳过 prune。
package/dist/runner.js CHANGED
@@ -46,6 +46,7 @@ export async function runAgent(config, input, onActivity = defaultPrint, onConso
46
46
  systemPrompt: ({ workspace, resuming }) => buildSystemPrompt({
47
47
  handle: input.handle,
48
48
  channelId: input.channelId,
49
+ ...(input.wakeMessageId ? { wakeMessageId: input.wakeMessageId } : {}),
49
50
  agentId: credential.agentId,
50
51
  homeDir: workspace.dir,
51
52
  productName: config.productName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nowcrew/daemon",
3
- "version": "0.6.15",
3
+ "version": "0.6.16",
4
4
  "type": "module",
5
5
  "description": "crew daemon — 运行在用户机器:拉起/管理 agent 进程,注入 crew CLI,归一化 runtime 事件",
6
6
  "license": "Apache-2.0",