@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 +201 -0
- package/dist/console.js +80 -0
- package/dist/main.js +0 -0
- package/dist/prompt.js +5 -2
- package/dist/runner.js +15 -2
- package/dist/runtimes/claude.js +3 -0
- package/dist/serve.js +20 -1
- package/dist/token.js +7 -2
- package/dist/workspace.js +23 -4
- package/package.json +10 -11
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.
|
package/dist/console.js
ADDED
|
@@ -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
|
|
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) => {
|
package/dist/runtimes/claude.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
"
|
|
28
|
-
"
|
|
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
|
+
}
|