@nowcrew/daemon 0.6.95-beta.1 → 0.6.95

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.
File without changes
@@ -182,10 +182,35 @@ export function parseOpencodeModels(stdout) {
182
182
  }
183
183
  return models;
184
184
  }
185
- function parsePiModels(stdout) {
186
- return stdout
187
- .split(/\r?\n/)
188
- .map((line) => line.trim())
189
- .filter((line) => line.length > 0 && !line.toLowerCase().startsWith("warning"))
190
- .map((line, index) => ({ id: line, label: line, ...(index === 0 ? { default: true } : {}) }));
185
+ export function parsePiModels(stdout) {
186
+ const reasoning = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
187
+ const models = [];
188
+ const seen = new Set();
189
+ for (const rawLine of stdout.split(/\r?\n/)) {
190
+ const line = rawLine.trim();
191
+ if (line.length === 0 || line.toLowerCase().startsWith("warning"))
192
+ continue;
193
+ const columns = line.split(/\s+/);
194
+ const [provider, model, context, maxOutput, thinking, images] = columns;
195
+ if (provider === "provider" && model === "model"
196
+ || !provider || !model
197
+ || !/^\d+(?:\.\d+)?[KMG]$/i.test(context ?? "")
198
+ || !/^\d+(?:\.\d+)?[KMG]$/i.test(maxOutput ?? "")
199
+ || !/^(?:yes|no)$/i.test(thinking ?? "")
200
+ || !/^(?:yes|no)$/i.test(images ?? ""))
201
+ continue;
202
+ const id = `${provider}/${model}`;
203
+ if (seen.has(id))
204
+ continue;
205
+ seen.add(id);
206
+ models.push({
207
+ id,
208
+ label: id,
209
+ ...(thinking?.toLowerCase() === "yes" ? { reasoning } : {}),
210
+ });
211
+ }
212
+ // `pi --list-models` does not identify the configured default model, and its
213
+ // output order is not a default-model contract. Leave default unset so the
214
+ // UI does not infer capabilities from an unrelated first row.
215
+ return models;
191
216
  }
@@ -14,7 +14,7 @@ import { spawnKimi } from "./runtimes/kimi.js";
14
14
  import { applyDeepSeekHarnessMachineEnv, applyProviderEnv, providerFingerprint, } from "./provider-env.js";
15
15
  import { deliverCloudReports, prepareCloudReportExecution } from "./cloud-report-uploader.js";
16
16
  import { reportDeliveryContext } from "./cloud-report-receipt.js";
17
- import { extractFinalText, extractRunMeta, normalizeEvent, parseLine, } from "./normalize.js";
17
+ import { extractFinalText, isIncrementalTextEvent, extractRunMeta, normalizeEvent, parseLine, } from "./normalize.js";
18
18
  import { readSession, writeSession, pickResumeId, isNearBudget } from "./session.js";
19
19
  import { NativeThreadContext, isolateMaintenanceSession } from "./native-thread-context.js";
20
20
  import { prepareThreadDelivery } from "./thread-input-delivery.js";
@@ -811,12 +811,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
811
811
  }
812
812
  const extracted = extractFinalText(event);
813
813
  if (extracted) {
814
- const incremental = typeof event === "object" && event !== null
815
- && "type" in event && (event.type === "kimi.acp.text_delta"
816
- || event.type === "hermes.acp.text_delta"
817
- || event.type === "deepseek-harness.acp.text_delta"
818
- || event.type === "opencode.text_delta");
819
- finalText = incremental ? `${finalText ?? ""}${extracted}` : extracted;
814
+ finalText = isIncrementalTextEvent(event) ? `${finalText ?? ""}${extracted}` : extracted;
820
815
  }
821
816
  for (const text of decodeExternalOutputEvent(runtime.name, event, externalOutput)) {
822
817
  callbacks.onExternalOutput?.(text);
package/dist/main.js CHANGED
File without changes
package/dist/normalize.js CHANGED
@@ -32,13 +32,17 @@ export function classifyCommand(command) {
32
32
  return { kind: "crew", label: "crew 命令", detail: c };
33
33
  return { kind: "tool", label: "执行命令", detail: c };
34
34
  }
35
- /** 从各 runtime 的最终事件提取可交付文本。调用方按事件顺序保留最后一个非空值。 */
35
+ /** 提取和汇总共用同一份增量事件判定,避免新增 runtime 时漏掉拼接。 */
36
+ export function isIncrementalTextEvent(event) {
37
+ const e = (event ?? {});
38
+ return e.type === "kimi.acp.text_delta" || e.type === "hermes.acp.text_delta"
39
+ || e.type === "deepseek-harness.acp.text_delta" || e.type === "opencode.text_delta"
40
+ || e.type === "pi.text_delta";
41
+ }
42
+ /** 提取可交付文本;增量事件须拼接,完整消息保留最后一个非空值。 */
36
43
  export function extractFinalText(event) {
37
44
  const e = (event ?? {});
38
- if ((e.type === "kimi.acp.text_delta" || e.type === "hermes.acp.text_delta"
39
- || e.type === "deepseek-harness.acp.text_delta"
40
- || e.type === "opencode.text_delta"
41
- || e.type === "pi.text_delta") && e.text)
45
+ if (isIncrementalTextEvent(e) && e.text)
42
46
  return e.text;
43
47
  if (e.type === "result" && !e.is_error && e.result?.trim())
44
48
  return e.result.trim();
@@ -69,10 +73,7 @@ export function normalizeEvent(event) {
69
73
  const plan = boundedCodexPlan(e.plan);
70
74
  return plan === null ? [] : [{ kind: "tool", label: "计划更新", detail: codexPlanText(plan) }];
71
75
  }
72
- if ((e.type === "kimi.acp.text_delta" || e.type === "hermes.acp.text_delta"
73
- || e.type === "deepseek-harness.acp.text_delta"
74
- || e.type === "opencode.text_delta"
75
- || e.type === "pi.text_delta") && e.text?.trim()) {
76
+ if (isIncrementalTextEvent(e) && e.text?.trim()) {
76
77
  return [{ kind: "text", label: "思考/说明", detail: e.text }];
77
78
  }
78
79
  if (e.type === "kimi.acp.tool_call" || e.type === "hermes.acp.tool_call"
@@ -4,22 +4,48 @@ import { parseArgs } from "node:util";
4
4
  import { pathToFileURL } from "node:url";
5
5
  import spawn from "cross-spawn";
6
6
  import { augmentedPath } from "../runtime-path.js";
7
+ import { prepareThreadDelivery } from "../thread-input-delivery.js";
7
8
  import { buildPiArgs, createPiEventDecoder } from "./pi.js";
8
9
  async function readInput() { process.stdin.setEncoding("utf8"); let text = ""; for await (const chunk of process.stdin)
9
10
  text += String(chunk); return JSON.parse(text); }
10
11
  async function emit(event) { if (process.stdout.write(`${JSON.stringify(event)}\n`))
11
12
  return; await once(process.stdout, "drain"); }
12
- export async function runPi(bin) { const input = await readInput(); const child = spawn(bin, buildPiArgs(input), { cwd: process.cwd(), env: { ...process.env, PATH: augmentedPath(), PWD: process.cwd() }, stdio: ["ignore", "pipe", "pipe"] }); if (!child.stdout || !child.stderr)
13
- throw new Error("Pi process did not expose stdio"); child.stderr.pipe(process.stderr, { end: false }); const exit = once(child, "close"); const decoder = createPiEventDecoder(); for await (const line of createInterface({ input: child.stdout })) {
14
- try {
15
- for (const event of decoder.push(JSON.parse(line)))
16
- await emit(event);
13
+ export async function runPi(bin) {
14
+ const input = await readInput();
15
+ // Pi 不持久化原生会话,每轮都须在启动模型前补齐当前线程快照。
16
+ const delivery = await prepareThreadDelivery({
17
+ env: process.env, resumed: false, sessionId: "unverified-cold-pi", wakePrompt: input.wakePrompt,
18
+ });
19
+ const child = spawn(bin, buildPiArgs({ ...input, wakePrompt: delivery.prompt }), {
20
+ cwd: process.cwd(), env: { ...process.env, PATH: augmentedPath(), PWD: process.cwd() },
21
+ stdio: ["pipe", "pipe", "pipe"],
22
+ });
23
+ if (!child.stdin || !child.stdout || !child.stderr)
24
+ throw new Error("Pi process did not expose stdio");
25
+ child.stderr.pipe(process.stderr, { end: false });
26
+ const exit = once(child, "close");
27
+ let inputError;
28
+ child.stdin.on("error", error => { inputError = error; });
29
+ // Pi print 模式读取 stdin;完整线程历史不能塞进受长度限制的 argv。
30
+ child.stdin.end(delivery.prompt);
31
+ const decoder = createPiEventDecoder();
32
+ for await (const line of createInterface({ input: child.stdout })) {
33
+ try {
34
+ for (const event of decoder.push(JSON.parse(line)))
35
+ await emit(event);
36
+ }
37
+ catch { }
17
38
  }
18
- catch { }
19
- } const [code, signal] = await exit; const result = decoder.finish(); if (!result.ok) {
20
- process.stderr.write(`${result.error}\n`);
21
- return 1;
22
- } return code === 0 ? 0 : (code ?? (signal ? 128 : 1)); }
39
+ const [code, signal] = await exit;
40
+ if (inputError)
41
+ throw new Error(`Pi prompt delivery failed: ${inputError.message}`);
42
+ const result = decoder.finish();
43
+ if (!result.ok) {
44
+ process.stderr.write(`${result.error}\n`);
45
+ return 1;
46
+ }
47
+ return code === 0 ? 0 : (code ?? (signal ? 128 : 1));
48
+ }
23
49
  const { values } = parseArgs({ args: process.argv.slice(2), options: { bin: { type: "string" } } });
24
50
  if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
25
51
  if (!values.bin)
@@ -1,4 +1,4 @@
1
- export function buildPiArgs(options) { return ["--mode", "json", "--print", "--no-session", "--system-prompt", options.systemPrompt, ...(options.model ? ["--model", options.model] : []), ...(options.reasoning && options.reasoning !== "default" ? ["--thinking", options.reasoning] : []), options.wakePrompt]; }
1
+ export function buildPiArgs(options) { return ["--mode", "json", "--print", "--no-session", "--system-prompt", options.systemPrompt, ...(options.model ? ["--model", options.model] : []), ...(options.reasoning && options.reasoning !== "default" ? ["--thinking", options.reasoning] : [])]; }
2
2
  export function createPiEventDecoder() { let sessionId = null; let finalText = ""; let error = null; let sawTurnEnd = false; let usage; return { push(raw) { const e = (raw ?? {}); const out = []; if (e.type === "session" && typeof e.id === "string") {
3
3
  sessionId = e.id;
4
4
  out.push({ type: "thread.started", thread_id: e.id });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nowcrew/daemon",
3
- "version": "0.6.95-beta.1",
3
+ "version": "0.6.95",
4
4
  "type": "module",
5
5
  "description": "crew daemon — 运行在用户机器:拉起/管理 agent 进程,注入 crew CLI,归一化 runtime 事件",
6
6
  "license": "Apache-2.0",
@@ -18,6 +18,13 @@
18
18
  "publishConfig": {
19
19
  "access": "public"
20
20
  },
21
+ "scripts": {
22
+ "daemon": "pnpm --filter @nowcrew/cli build && tsx src/main.ts",
23
+ "build": "tsc -p tsconfig.json",
24
+ "prepublishOnly": "pnpm build && node ../scripts/daemon-release-artifact.mjs --strict-registry",
25
+ "test": "vitest run",
26
+ "typecheck": "tsc --noEmit"
27
+ },
21
28
  "dependencies": {
22
29
  "@agentclientprotocol/sdk": "1.2.1",
23
30
  "@nowcrew/cli": "0.4.17",
@@ -39,11 +46,5 @@
39
46
  "tsx": "^4.19.0",
40
47
  "typescript": "^5.6.0",
41
48
  "vitest": "^2.1.0"
42
- },
43
- "scripts": {
44
- "daemon": "pnpm --filter @nowcrew/cli build && tsx src/main.ts",
45
- "build": "tsc -p tsconfig.json",
46
- "test": "vitest run",
47
- "typecheck": "tsc --noEmit"
48
49
  }
49
- }
50
+ }