@nowcrew/daemon 0.1.1 → 0.2.0

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/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright 2026 OpenSlock contributors
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
@@ -0,0 +1,80 @@
1
+ /**
2
+ * 把 coding-agent runtime 的 stream-json 事件转成**终端 console 行**(完全透传)。
3
+ *
4
+ * 与 normalize.ts 平行,但目标相反:normalize 为状态角标做**语义压缩**,这里为前端终端窗口
5
+ * 做**原文透传**——保留思考文本、工具调用、工具返回正文,尽量还原原生 claude CLI 的观感。
6
+ * 纯函数,无 IO,完整单测。
7
+ *
8
+ * 注:本转换器针对 claude 的 stream-json。codex 的 stream 格式不同,后续按 runtime 分派扩展;
9
+ * 前端/server 消费的 ConsoleChunk 形状是 runtime 无关的。
10
+ */
11
+ /** 单条工具返回正文上限(超出截断并标注),避免单条把终端/DB 撑爆。 */
12
+ export const TOOL_RESULT_CAP = 4000;
13
+ /** 工具输入摘要上限(标题行那一段)。 */
14
+ const TOOL_INPUT_CAP = 160;
15
+ /** 把工具输入压成一行摘要:Bash 取 command,其它取首个字符串字段或紧凑 JSON。 */
16
+ function summarizeToolInput(name, input) {
17
+ if (!input)
18
+ return `⏺ ${name}`;
19
+ if (name === "Bash" && typeof input.command === "string") {
20
+ return `⏺ Bash(${clip(input.command.replace(/\s+/g, " "), TOOL_INPUT_CAP)})`;
21
+ }
22
+ const entries = Object.entries(input);
23
+ const head = entries
24
+ .map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`)
25
+ .join(", ");
26
+ return `⏺ ${name}(${clip(head.replace(/\s+/g, " "), TOOL_INPUT_CAP)})`;
27
+ }
28
+ /** 把 tool_result 的 content(string | block[])抽成纯文本。 */
29
+ function extractToolResult(content) {
30
+ if (typeof content === "string")
31
+ return content;
32
+ if (Array.isArray(content)) {
33
+ return content
34
+ .map((b) => (b && typeof b === "object" && typeof b.text === "string" ? b.text : ""))
35
+ .filter(Boolean)
36
+ .join("\n");
37
+ }
38
+ return "";
39
+ }
40
+ function clip(s, cap) {
41
+ return s.length > cap ? s.slice(0, cap) + `… (+${s.length - cap})` : s;
42
+ }
43
+ /** 把一个 stream-json 事件转成 0..N 条 console 行(完全透传)。 */
44
+ export function toConsoleLines(event) {
45
+ const e = (event ?? {});
46
+ if (e.type === "system" && e.subtype === "init") {
47
+ return [{ stream: "system", text: "● claude 会话启动" }];
48
+ }
49
+ if (e.type === "result") {
50
+ const text = e.result?.trim() || (e.is_error ? "运行出错" : "本轮结束");
51
+ return [{ stream: e.is_error ? "error" : "result", text }];
52
+ }
53
+ if (e.type === "assistant" && Array.isArray(e.message?.content)) {
54
+ const out = [];
55
+ for (const block of e.message.content) {
56
+ if (block.type === "thinking" && block.thinking?.trim()) {
57
+ out.push({ stream: "thinking", text: block.thinking.trim() });
58
+ }
59
+ else if (block.type === "text" && block.text?.trim()) {
60
+ out.push({ stream: "text", text: block.text.trim() });
61
+ }
62
+ else if (block.type === "tool_use" && block.name) {
63
+ out.push({ stream: "tool", text: summarizeToolInput(block.name, block.input) });
64
+ }
65
+ }
66
+ return out;
67
+ }
68
+ if (e.type === "user" && Array.isArray(e.message?.content)) {
69
+ const out = [];
70
+ for (const block of e.message.content) {
71
+ if (block.type === "tool_result") {
72
+ const text = extractToolResult(block.content).trim();
73
+ if (text)
74
+ out.push({ stream: "tool_result", text: clip(text, TOOL_RESULT_CAP) });
75
+ }
76
+ }
77
+ return out;
78
+ }
79
+ return [];
80
+ }
package/dist/main.js CHANGED
File without changes
package/dist/prompt.js CHANGED
@@ -37,7 +37,7 @@ export function buildSystemPrompt(ctx) {
37
37
  \`\`\`
38
38
  也可用 \`--content "<短正文>"\`。线程内回复:加 \`--thread <msg码>\`(msg 码来自 read/search 输出里的 \`msg=\` 前 8 位)。
39
39
  5. **\`crew task list --channel <id>\`** —— 看任务板。支持 \`--status <s>\` / \`--mine\`。
40
- 6. **\`crew task create --channel <id> --title "<标题>"\`** —— 新建任务(把一件事登记成可追踪的工作项)
40
+ 6. **\`crew task create --channel <id> --title "<标题>" --thread <当前线程根msgId>\`** —— 新建任务并**绑定到当前线程**。\`--thread\` 传你读到的那条**触发消息 id**(线程根),任务就和讨论同处一个线程。省略 \`--thread\` 会另起新线程,**几乎总是错的——务必带上**。
41
41
  7. **\`crew task claim <taskId>\`** —— 认领任务(动手前必做)。
42
42
  8. **\`crew task update <taskId> --status <in_progress|in_review|done>\`** —— 推进任务状态。
43
43
  9. **\`crew task unclaim <taskId>\`** —— 释放认领,把任务让给别人。
@@ -64,6 +64,9 @@ CRITICAL 规则:
64
64
  - 你读到的消息形如 \`#<seq> [<type>] <sender>: <正文>\`,\`type\` 为 \`human\` / \`agent\` / \`system\`。
65
65
  - **\`system\` 消息**通报频道状态变化(如新建任务),除非明确要求你行动(如刚给你指派了任务),否则不要回复。
66
66
  - **判定规则**:若满足来信需要你"回复之外的动作"(跑工具/改代码/做变更),先 claim;若只是回答问题或闲聊,无需 claim。
67
+ - **线程 = 工作单元 / 一个请求一个 task(CRITICAL)**:一条对话(thread)对应一件事,最多绑**一个** task。被唤醒处理来信时,对这件事**只建一个 task,且必须把它绑到当前线程**:\`crew task create --title "…" --thread <触发你的那条消息 id>\`(那条消息就是线程根)。**一定要带 \`--thread\`**——不带会另起一条飘在顶层的新线程,task 就和你的讨论分家了(这正是要避免的)。**别把一个请求拆成多个 task**(如"拉代码"+"读文档"+"写记忆"是同一件事 → 一个 task,用 todo/进度推进,不要建第二个)。当前线程已有 task 时再建会被服务端拒绝(报错会提示你)。
68
+ - **进度/产出回本线程**:这件事的认领/进度/完成汇报都用 \`crew message send --thread <当前线程根>\` 回复在**这个线程里**(线程根 = 触发你的那条消息 id,即 \$CREW_WAKE_MESSAGE_ID)。**严禁把进度发到别的线程或频道顶层。**
69
+ - **毫不相关的新任务才另起线程**:只有要处理的事**和当前线程毫不相关**(或用户明确要求新建)时,才用 \`crew task create --new-thread --title "…"\`——系统另起一个子线程(parent=当前线程)绑新 task;之后这件事的回复要发到**这个新子线程**里。能不拆就不拆。
67
70
  - 任务状态流:\`todo → in_progress → in_review → done\`。claim 后用 \`crew task update\` 推进:开工→in_progress、完成待验收→in_review、人类确认后→done。只有 assignee 能改自己任务的状态。
68
71
  - **交接(handoff)**:当你这一环干完、需要别的角色接手时(如开发完成 → 交给 QA 测试),用 \`crew task assign <taskId> --to <下家handle>\` 把任务交接出去,并在线程里给下家足够背景(分支名 / 改动摘要 / 测试建议)。交接后对方会被自动唤醒。**别让任务停在你手里无人跟进**。
69
72
  - **分诊(若你是总管)**:若你收到「【分诊请求】」唤醒,说明频道里有一个无人认领的任务需要你按团队职责分派。唤醒内容里已附上团队成员及其职责:判断谁最合适,用 \`crew task assign <taskId> --to <handle>\` 指派给他(若该你自己做就 \`crew task claim\`);确实没人合适时,在频道里 @发起人 说明并给建议,**不要让任务悬空**。
@@ -95,7 +98,7 @@ ${ctx.memory ? `\n## [注入] 你的 MEMORY.md(索引,只读参考)\n${ctx.memor
95
98
  * 只有发现确实指向自己的事才转为主动处理,否则读完即停、不发声。
96
99
  */
97
100
  export function buildWakePrompt(channelId) {
98
- return `先用 \`crew message read --channel ${channelId}\` 把这个频道读一遍,补齐上下文。
101
+ return `先补齐上下文:若本轮在某个线程里(被唤醒处理某 thread),用 \`crew thread read\` 读**当前线程 + 它的父线程**(聚焦上下文,最多向上一层);需要更全局再用 \`crew message read --channel ${channelId}\` 读整个频道。
99
102
  读完判断:其中是否有明确落到你头上的事——点名找你、@你、指派给你、请你评审,或交给你的任务。
100
103
  - 有:转入主动处理。相关任务先 \`crew task claim <taskId>\` 认领再动手,完成后用 \`crew message send --channel ${channelId}\` 回复。
101
104
  - 没有:本轮什么都不要发,读完即停。你存活期间有新消息会自动送来,无需轮询。`;
package/dist/runner.js CHANGED
@@ -9,15 +9,18 @@ import { prepareWorkspace } from "./workspace.js";
9
9
  import { buildSystemPrompt, buildWakePrompt } from "./prompt.js";
10
10
  import { spawnClaude } from "./runtimes/claude.js";
11
11
  import { normalizeEvent, parseLine } from "./normalize.js";
12
+ import { toConsoleLines } from "./console.js";
12
13
  // 注入提示词的 MEMORY.md 上限:只喂索引/角色,避免把膨胀的记忆全塞进上下文。
13
14
  const MEMORY_INJECT_CAP = 6000;
14
15
  const ICON = {
15
16
  init: "🟢", text: "💬", reading: "📖", sending: "📨", checking: "🔎",
16
17
  claiming: "📌", crew: "⚙️", tool: "🛠️", tool_result: "↩️", done: "✅", error: "❌",
17
18
  };
18
- export async function runAgent(config, input, onActivity = defaultPrint) {
19
+ export async function runAgent(config, input, onActivity = defaultPrint,
20
+ // 终端透传:每条 stream-json 事件除归一化为状态活动外,再产出 console 行供前端终端窗口渲染。
21
+ onConsole = () => { }) {
19
22
  // 1) 用机器令牌换 per-launch agent 令牌
20
- const cred = await mintAgentToken(config.serverUrl, config.machineToken, input.handle, input.displayName);
23
+ const cred = await mintAgentToken(config.serverUrl, config.machineToken, input.handle, input.displayName, input.wakeMessageId);
21
24
  // 2) 准备 workspace(共享 home + 本任务隔离 cwd + per-task work-log)。
22
25
  // 先 prepare 拿到 memory/workLog,再 build 系统提示词(注入记忆索引)写入。
23
26
  const ws = await prepareWorkspace({
@@ -25,6 +28,8 @@ export async function runAgent(config, input, onActivity = defaultPrint) {
25
28
  handle: input.handle,
26
29
  cliPath: config.cliPath,
27
30
  ...(input.taskKey ? { taskKey: input.taskKey } : {}),
31
+ // 仅在首次创建 MEMORY.md 时,用 agent 的 description 种子化 ## Role
32
+ ...(cred.config?.description ? { description: cred.config.description } : {}),
28
33
  });
29
34
  const systemPrompt = buildSystemPrompt({
30
35
  handle: input.handle,
@@ -49,6 +54,8 @@ export async function runAgent(config, input, onActivity = defaultPrint) {
49
54
  wakePrompt: input.wake ?? buildWakePrompt(input.channelId),
50
55
  dangerous: config.dangerous,
51
56
  ...(cfg.model ? { model: cfg.model } : {}),
57
+ // 一线程一会话:首轮 --session-id 固定 id,之后 --resume 续上(更原生地复用 claude 记忆)
58
+ ...(ws.agentSessionId ? { sessionId: ws.agentSessionId, resume: ws.sessionResume } : {}),
52
59
  env: {
53
60
  ...process.env,
54
61
  PATH: `${ws.crewDir}${delimiter}${process.env.PATH ?? ""}`,
@@ -58,6 +65,9 @@ export async function runAgent(config, input, onActivity = defaultPrint) {
58
65
  // 共享持久记忆 home(MEMORY.md/notes 在此;cwd 是本任务隔离目录)+ 本任务 work-log 路径
59
66
  CREW_HOME: ws.dir,
60
67
  CREW_TASK_LOG: ws.workLogPath,
68
+ // 唤醒锚点消息 id:有则 `crew task create` 把任务锚定到这条触发消息(讨论与任务锚点统一),
69
+ // 而非另发一条标题消息当锚点(那会让点开 task 的 thread 永远为空)。
70
+ ...(input.wakeMessageId ? { CREW_WAKE_MESSAGE_ID: input.wakeMessageId } : {}),
61
71
  // per-agent 凭证隔离:XDG 指向本 agent 独立目录(gh/gcloud 等 CLI 的 token 不互相串)。
62
72
  // 不覆盖 HOME(否则会破坏 claude 自身的 ~/.claude 鉴权);常用 CLI 也单独点名隔离。
63
73
  XDG_CONFIG_HOME: join(ws.homeDir, ".config"),
@@ -87,6 +97,9 @@ export async function runAgent(config, input, onActivity = defaultPrint) {
87
97
  activities.push(a);
88
98
  onActivity(a);
89
99
  }
100
+ // 同一事件再透传为终端 console 行(独立于状态活动,内容不压缩)。
101
+ for (const c of toConsoleLines(evt))
102
+ onConsole(c);
90
103
  });
91
104
  child.stderr.on("data", (d) => process.stderr.write(d));
92
105
  const exitCode = await new Promise((resolve) => {
@@ -13,6 +13,9 @@ export function buildClaudeArgs(input) {
13
13
  ];
14
14
  if (input.model)
15
15
  args.push("--model", input.model);
16
+ if (input.sessionId) {
17
+ args.push(input.resume ? "--resume" : "--session-id", input.sessionId);
18
+ }
16
19
  if (input.dangerous)
17
20
  args.push("--dangerously-skip-permissions");
18
21
  args.push(input.wakePrompt);
package/dist/serve.js CHANGED
@@ -167,6 +167,22 @@ export function serve(config, opts = {}) {
167
167
  }
168
168
  catch { /* ws 非 OPEN,忽略 */ }
169
169
  };
170
+ // 终端透传:把底层 claude 的每条 console 行按线程上送给 server(独立 seq,落库+广播给 web 终端窗口)。
171
+ let conSeq = 0;
172
+ const reportConsole = (c) => {
173
+ try {
174
+ ws?.send(JSON.stringify({
175
+ type: "agent:console",
176
+ agentHandle: msg.agentHandle,
177
+ channelId: msg.channelId,
178
+ threadId: threadId ?? null,
179
+ stream: c.stream,
180
+ text: c.text,
181
+ seq: conSeq++,
182
+ }));
183
+ }
184
+ catch { /* ws 非 OPEN,忽略 */ }
185
+ };
170
186
  try {
171
187
  // 线程聚合:触发消息即任务线程根,你的确认+后续所有回复都要发到它的线程里,
172
188
  // 不要发顶层——这样 task 讨论全部聚合在该 thread 下。
@@ -191,9 +207,12 @@ export function serve(config, opts = {}) {
191
207
  handle: msg.agentHandle,
192
208
  channelId: msg.channelId,
193
209
  taskKey, // 每任务隔离 cwd + work-log(并行不冲突)
210
+ // 唤醒锚点是具体消息(非纯频道唤醒)时,把它透传下去,供 `crew task create` 锚定到该消息。
211
+ ...(threadId ? { wakeMessageId: threadId } : {}),
194
212
  ...(msg.wake?.content ? { wake: `你被唤醒(${msg.reason}): ${msg.wake.content}\n用 crew message read --channel ${msg.channelId} 读频道后按需处理。${reasonHint}${ackHint}${threadHint}${attHint}` } : {}),
195
- }, reportActivity);
213
+ }, reportActivity, reportConsole);
196
214
  reportActivity({ kind: "done", label: "本轮结束" });
215
+ reportConsole({ stream: "result", text: "● 本轮结束" });
197
216
  log(`✅ agent=${msg.agentHandle} 本轮完成`);
198
217
  }
199
218
  catch (e) {
package/dist/token.js CHANGED
@@ -1,12 +1,17 @@
1
1
  /** 用机器令牌为某 agent 换取 per-launch 的 sk_agent_*。 */
2
- export async function mintAgentToken(serverUrl, machineToken, handle, displayName, fetchImpl = fetch) {
2
+ export async function mintAgentToken(serverUrl, machineToken, handle, displayName, wakeThreadRoot, fetchImpl = fetch) {
3
3
  const res = await fetchImpl(`${serverUrl}/daemon/agents/token`, {
4
4
  method: "POST",
5
5
  headers: {
6
6
  authorization: `Bearer ${machineToken}`,
7
7
  "content-type": "application/json",
8
8
  },
9
- body: JSON.stringify(displayName ? { handle, displayName } : { handle }),
9
+ // 把本轮唤醒的线程根钉到 per-launch 令牌:服务端据此让 task/消息默认绑/回当前线程。
10
+ body: JSON.stringify({
11
+ handle,
12
+ ...(displayName ? { displayName } : {}),
13
+ ...(wakeThreadRoot ? { wakeThreadRoot } : {}),
14
+ }),
10
15
  });
11
16
  const body = (await res.json().catch(() => null));
12
17
  if (!res.ok || !body?.success || !body.data) {
package/dist/workspace.js CHANGED
@@ -8,6 +8,7 @@
8
8
  * spawn runtime 时把 <ws>/.crew 置于 PATH 最前,于是 agent 的 `crew ...` 命中 wrapper。
9
9
  */
10
10
  import { mkdir, writeFile, readFile, chmod, access } from "node:fs/promises";
11
+ import { randomUUID } from "node:crypto";
11
12
  import { join } from "node:path";
12
13
  /** 文件系统安全的 taskKey:仅留 [\w.-],其余转 _,截断,避免路径穿越/超长。 */
13
14
  export function safeKey(key) {
@@ -29,7 +30,7 @@ export async function prepareWorkspace(input) {
29
30
  // MEMORY.md:首次创建"索引 + Active Context"骨架,之后由 agent 自己维护
30
31
  const memoryPath = join(dir, "MEMORY.md");
31
32
  if (!(await exists(memoryPath))) {
32
- await writeFile(memoryPath, memorySeed(input.handle), "utf8");
33
+ await writeFile(memoryPath, memorySeed(input.handle, input.description), "utf8");
33
34
  }
34
35
  const memory = await readFile(memoryPath, "utf8");
35
36
  // 系统提示词 (每次覆盖,保证最新);省略时由调用方在拿到 memory 后自行写入。
@@ -55,17 +56,35 @@ export async function prepareWorkspace(input) {
55
56
  workLogPath = join(runDir, "work-log.md");
56
57
  }
57
58
  const workLog = (await exists(workLogPath)) ? await readFile(workLogPath, "utf8") : "";
58
- return { dir, crewDir, systemPromptPath, memory, homeDir, runDir, workLogPath, workLog };
59
+ // 一线程一会话:tasks/<taskKey>/.session 存底层会话 id。已存在→resume 续上;否则新建。
60
+ let agentSessionId = null;
61
+ let sessionResume = false;
62
+ if (input.taskKey) {
63
+ const sessionPath = join(runDir, ".session");
64
+ if (await exists(sessionPath)) {
65
+ agentSessionId = (await readFile(sessionPath, "utf8")).trim() || null;
66
+ sessionResume = !!agentSessionId;
67
+ }
68
+ if (!agentSessionId) {
69
+ agentSessionId = randomUUID();
70
+ await writeFile(sessionPath, agentSessionId, "utf8");
71
+ }
72
+ }
73
+ return { dir, crewDir, systemPromptPath, memory, homeDir, runDir, workLogPath, workLog, agentSessionId, sessionResume };
59
74
  }
60
75
  /**
61
76
  * MEMORY.md 种子骨架:分层记忆的"索引 + Active Context"结构。
62
77
  * 第一次准备工作区时写入;之后 agent 自己维护(系统提示词里有协议)。
78
+ * 建 agent 时填了 description,则用它种子化 ## Role(否则留占位符,由 agent 自填)。
63
79
  */
64
- function memorySeed(handle) {
80
+ function memorySeed(handle, description) {
81
+ const role = description?.trim()
82
+ ? description.trim()
83
+ : "(Who you are and what you're responsible for. Keep it to a few lines.)";
65
84
  return `# ${handle}
66
85
 
67
86
  ## Role
68
- (Who you are and what you're responsible for. Keep it to a few lines.)
87
+ ${role}
69
88
 
70
89
  ## Core Goals
71
90
  (What you optimize for.)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nowcrew/daemon",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "crew daemon — 运行在用户机器:拉起/管理 agent 进程,注入 crew CLI,归一化 runtime 事件",
6
6
  "license": "Apache-2.0",
@@ -16,16 +16,9 @@
16
16
  "publishConfig": {
17
17
  "access": "public"
18
18
  },
19
- "scripts": {
20
- "daemon": "tsx src/main.ts",
21
- "build": "tsc -p tsconfig.json",
22
- "prepublishOnly": "tsc -p tsconfig.json",
23
- "test": "vitest run",
24
- "typecheck": "tsc --noEmit"
25
- },
26
19
  "dependencies": {
27
- "@nowcrew/cli": "^0.1.0",
28
- "ws": "^8"
20
+ "ws": "^8",
21
+ "@nowcrew/cli": "^0.2.0"
29
22
  },
30
23
  "devDependencies": {
31
24
  "@types/node": "^22.0.0",
@@ -33,5 +26,11 @@
33
26
  "tsx": "^4.19.0",
34
27
  "typescript": "^5.6.0",
35
28
  "vitest": "^2.1.0"
29
+ },
30
+ "scripts": {
31
+ "daemon": "tsx src/main.ts",
32
+ "build": "tsc -p tsconfig.json",
33
+ "test": "vitest run",
34
+ "typecheck": "tsc --noEmit"
36
35
  }
37
- }
36
+ }