@nowcrew/daemon 0.3.0 → 0.4.2
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/normalize.js +3 -0
- package/dist/runner.js +34 -4
- package/dist/serve.js +23 -1
- package/package.json +1 -1
package/dist/normalize.js
CHANGED
|
@@ -106,6 +106,9 @@ export function extractRunMeta(event) {
|
|
|
106
106
|
meta.sessionId = e.session_id;
|
|
107
107
|
if (typeof e.thread_id === "string" && e.thread_id)
|
|
108
108
|
meta.sessionId = e.thread_id;
|
|
109
|
+
// claude 的 system/init 事件自报实际模型(配置可能为空/别名,以 runtime 自报为准)
|
|
110
|
+
if (typeof e.model === "string" && e.model)
|
|
111
|
+
meta.model = e.model;
|
|
109
112
|
// 仅 result 事件携带本轮 usage 汇总
|
|
110
113
|
if ((e.type === "result" || e.type === "turn.completed") && e.usage) {
|
|
111
114
|
meta.usage = {
|
package/dist/runner.js
CHANGED
|
@@ -72,6 +72,9 @@ onConsole = () => { }) {
|
|
|
72
72
|
: baseWake;
|
|
73
73
|
const childEnv = {
|
|
74
74
|
...process.env,
|
|
75
|
+
// 用户自定义 env(表单 ENVIRONMENT VARIABLES):先合入,可覆盖继承的 shell 环境;
|
|
76
|
+
// 但 PATH/CREW_*/XDG_* 及下方由表单生成的值(ANTHROPIC_*/思考预算)在其后合入,始终以系统为准。
|
|
77
|
+
...sanitizeEnvVars(cfg.envVars),
|
|
75
78
|
PATH: `${ws.crewDir}${delimiter}${process.env.PATH ?? ""}`,
|
|
76
79
|
CREW_SERVER_URL: config.serverUrl,
|
|
77
80
|
CREW_TOKEN: cred.token,
|
|
@@ -139,6 +142,7 @@ onConsole = () => { }) {
|
|
|
139
142
|
// 身份是本轮下发的 launchSessionId;仍兜底采 claude 自报的 session_id(理应一致)。
|
|
140
143
|
let sessionId = launchSessionId;
|
|
141
144
|
let usage;
|
|
145
|
+
let observedModel = currentModel;
|
|
142
146
|
let finalText = null;
|
|
143
147
|
let sentViaCrew = false;
|
|
144
148
|
const rl = createInterface({ input: child.stdout });
|
|
@@ -151,6 +155,8 @@ onConsole = () => { }) {
|
|
|
151
155
|
sessionId = meta.sessionId;
|
|
152
156
|
if (meta.usage)
|
|
153
157
|
usage = meta.usage;
|
|
158
|
+
if (meta.model)
|
|
159
|
+
observedModel = meta.model;
|
|
154
160
|
for (const a of normalizeEvent(evt)) {
|
|
155
161
|
if (a.kind === "sending")
|
|
156
162
|
sentViaCrew = true;
|
|
@@ -183,15 +189,21 @@ onConsole = () => { }) {
|
|
|
183
189
|
onActivity(a);
|
|
184
190
|
}
|
|
185
191
|
if ((runtime === "codex" || runtime === "kimi") && exitCode === 0 && !sentViaCrew && finalText) {
|
|
192
|
+
// force:兜底回帖锚定本轮触发消息的线程,语义上必须送达;不 force 时 agent(-p 单发不跑
|
|
193
|
+
// crew read)游标落后,回帖会被 freshness hold 成 draft(202)而永远不可见。
|
|
186
194
|
const sent = await sendAgentMessage(config.serverUrl, cred.token, input.channelId, {
|
|
187
195
|
content: finalText,
|
|
196
|
+
force: true,
|
|
188
197
|
...(input.wakeMessageId ? { thread: input.wakeMessageId } : {}),
|
|
189
198
|
});
|
|
190
|
-
if (sent.
|
|
199
|
+
if (sent.delivered) {
|
|
191
200
|
const a = { kind: "sending", label: "发消息", detail: `${runtime} final answer fallback` };
|
|
192
201
|
activities.push(a);
|
|
193
202
|
onActivity(a);
|
|
194
203
|
}
|
|
204
|
+
else if (sent.status === 202) {
|
|
205
|
+
process.stderr.write(`${runtime} fallback reply was held as draft (HTTP 202) — not visible in channel\n`);
|
|
206
|
+
}
|
|
195
207
|
else {
|
|
196
208
|
process.stderr.write(`${runtime} fallback send failed (HTTP ${sent.status})\n`);
|
|
197
209
|
}
|
|
@@ -210,14 +222,30 @@ onConsole = () => { }) {
|
|
|
210
222
|
process.stdout.write(`📊 tokens: in=${u.inputTokens} out=${u.outputTokens} cache_read=${u.cacheReadTokens} cache_create=${u.cacheCreationTokens}` +
|
|
211
223
|
`${u.costUsd != null ? ` cost=$${u.costUsd.toFixed(4)}` : ""} ${resuming ? "(resumed)" : "(fresh)"}\n`);
|
|
212
224
|
}
|
|
213
|
-
return { exitCode, activities, ...(usage ? { usage } : {}) };
|
|
225
|
+
return { exitCode, activities, model: observedModel, runtime, resumed: resuming, ...(usage ? { usage } : {}) };
|
|
214
226
|
}
|
|
215
227
|
function defaultPrint(a) {
|
|
216
228
|
const icon = ICON[a.kind] ?? "·";
|
|
217
229
|
const detail = a.detail ? ` ${a.detail.replace(/\s+/g, " ").slice(0, 120)}` : "";
|
|
218
230
|
process.stdout.write(`${icon} ${a.label}${detail}\n`);
|
|
219
231
|
}
|
|
220
|
-
|
|
232
|
+
// daemon 侧兜底(server 已校验,这里防旧数据/绕过):只接受合法 key 的 string 值,CREW_ 前缀保留给系统。
|
|
233
|
+
const ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
234
|
+
export function sanitizeEnvVars(raw) {
|
|
235
|
+
if (!raw)
|
|
236
|
+
return {};
|
|
237
|
+
const out = {};
|
|
238
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
239
|
+
if (typeof v !== "string")
|
|
240
|
+
continue;
|
|
241
|
+
if (!ENV_KEY_RE.test(k) || k.toUpperCase().startsWith("CREW_"))
|
|
242
|
+
continue;
|
|
243
|
+
out[k] = v;
|
|
244
|
+
}
|
|
245
|
+
return out;
|
|
246
|
+
}
|
|
247
|
+
/** exported for tests */
|
|
248
|
+
export async function sendAgentMessage(serverUrl, token, channelId, body) {
|
|
221
249
|
const res = await fetch(`${serverUrl}/agent/channels/${encodeURIComponent(channelId)}/messages`, {
|
|
222
250
|
method: "POST",
|
|
223
251
|
headers: {
|
|
@@ -226,5 +254,7 @@ async function sendAgentMessage(serverUrl, token, channelId, body) {
|
|
|
226
254
|
},
|
|
227
255
|
body: JSON.stringify(body),
|
|
228
256
|
});
|
|
229
|
-
|
|
257
|
+
// 只有 201(sent)算送达;202 表示被 freshness hold 成 draft——消息没有出现在频道里,
|
|
258
|
+
// 不能当成功(把 202 当成功正是 kimi/codex fallback 回帖丢失的根因)。
|
|
259
|
+
return { delivered: res.status === 201, status: res.status };
|
|
230
260
|
}
|
package/dist/serve.js
CHANGED
|
@@ -222,7 +222,7 @@ export function serve(config, opts = {}) {
|
|
|
222
222
|
const readCmd = threadId
|
|
223
223
|
? `crew thread read`
|
|
224
224
|
: `crew message read --channel ${msg.channelId}`;
|
|
225
|
-
await runAgent(config, {
|
|
225
|
+
const result = await runAgent(config, {
|
|
226
226
|
handle: msg.agentHandle,
|
|
227
227
|
channelId: msg.channelId,
|
|
228
228
|
taskKey, // 每任务隔离 cwd + work-log(并行不冲突)
|
|
@@ -230,6 +230,28 @@ export function serve(config, opts = {}) {
|
|
|
230
230
|
...(threadId ? { wakeMessageId: threadId } : {}),
|
|
231
231
|
...(msg.wake?.content ? { wake: `你被唤醒(${msg.reason}): ${msg.wake.content}\n用 ${readCmd} 读${threadId ? "本线程" : "频道"}后按需处理。${reasonHint}${ackHint}${threadHint}${attHint}` } : {}),
|
|
232
232
|
}, reportActivity, reportConsole);
|
|
233
|
+
// 本轮 token 用量上报:runner 已从 result 事件提取(含缓存读/写细分),
|
|
234
|
+
// 连同模型/runtime 一起上送控制面落库 → 支撑每 agent / 每任务(线程)的用量监控与成本核算。
|
|
235
|
+
if (result.usage) {
|
|
236
|
+
const u = result.usage;
|
|
237
|
+
try {
|
|
238
|
+
ws?.send(JSON.stringify({
|
|
239
|
+
type: "agent:usage",
|
|
240
|
+
agentHandle: msg.agentHandle,
|
|
241
|
+
channelId: msg.channelId,
|
|
242
|
+
threadId: threadId ?? null, // 线程根消息 id(= 任务锚点);null = 频道级唤醒
|
|
243
|
+
runtime: result.runtime,
|
|
244
|
+
model: result.model,
|
|
245
|
+
resumed: result.resumed,
|
|
246
|
+
inputTokens: u.inputTokens,
|
|
247
|
+
outputTokens: u.outputTokens,
|
|
248
|
+
cacheReadTokens: u.cacheReadTokens,
|
|
249
|
+
cacheCreationTokens: u.cacheCreationTokens,
|
|
250
|
+
...(u.costUsd != null ? { costUsd: u.costUsd } : {}),
|
|
251
|
+
}));
|
|
252
|
+
}
|
|
253
|
+
catch { /* ws 非 OPEN,忽略(用量非关键路径,丢一轮不阻塞) */ }
|
|
254
|
+
}
|
|
233
255
|
reportActivity({ kind: "done", label: "本轮结束" });
|
|
234
256
|
reportConsole({ stream: "result", text: "● 本轮结束" });
|
|
235
257
|
log(`✅ agent=${msg.agentHandle} 本轮完成`);
|