@nowcrew/daemon 0.6.91-beta.6 → 0.6.91-beta.8

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.
@@ -81,7 +81,7 @@ export const DaemonAbilityManifestSchema = z.object({
81
81
  cases: z.array(SafeAbilityPathSchema).max(1_000).default([]),
82
82
  }).strict().default({ cases: [] }),
83
83
  compatibility: z.object({
84
- providers: z.array(z.enum(["claude", "codex", "kimi", "hermes", "opencode", "deepseek-harness"])).min(1),
84
+ providers: z.array(z.enum(["claude", "codex", "kimi", "hermes", "opencode", "deepseek-harness", "pi"])).min(1),
85
85
  minNowCrewVersion: z.string().regex(/^\d+\.\d+\.\d+$/u),
86
86
  }).strict().optional(),
87
87
  permissions: z.object({
@@ -6,6 +6,7 @@ const RUNTIMES = [
6
6
  "hermes",
7
7
  "opencode",
8
8
  "deepseek-harness",
9
+ "pi",
9
10
  ];
10
11
  function snapshotFields(snapshot, tracked) {
11
12
  const runtimeFields = {};
@@ -8,7 +8,7 @@ import { provePosixProcessGroupStopped } from "./execution-posix-stop-proof.js";
8
8
  export { JournalLockedError, JournalLockCorruptionError } from "./execution-journal-lock.js";
9
9
  const ExecutionIdSchema = z.string().uuid();
10
10
  const TimestampSchema = z.string().datetime({ offset: true });
11
- const RuntimeSchema = z.enum(["claude", "codex", "kimi", "hermes", "opencode", "deepseek-harness"]);
11
+ const RuntimeSchema = z.enum(["claude", "codex", "kimi", "hermes", "opencode", "deepseek-harness", "pi"]);
12
12
  const RUNTIME_READY_SUFFIX = ".runtime-ready";
13
13
  const RawJournalEntrySchema = z.object({
14
14
  executionId: ExecutionIdSchema,
@@ -13,7 +13,7 @@ const ExitCodeSchema = z.number().int().min(MIN_SIGNED_32_INTEGER).max(MAX_PG_IN
13
13
  const ProviderErrorCategorySchema = z.enum(["auth", "quota", "rate_limit", "timeout", "network", "upstream", "cli", "unknown"]);
14
14
  const ProviderHttpStatusSchema = z.number().int().min(100).max(599);
15
15
  const ProviderErrorCodeSchema = z.string().min(1).max(128).regex(/^[A-Za-z0-9_.:-]+$/u);
16
- const RuntimeSchema = z.enum(["claude", "codex", "kimi", "hermes", "opencode", "deepseek-harness"]);
16
+ const RuntimeSchema = z.enum(["claude", "codex", "kimi", "hermes", "opencode", "deepseek-harness", "pi"]);
17
17
  const UnsafePathCharacterSchema = /[\p{Cc}<>:"/\\|?*]/u;
18
18
  const WindowsReservedNameSchema = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i;
19
19
  export const AgentHandleSchema = z.string().min(1).max(64).refine((handle) => handle !== "."
@@ -285,11 +285,12 @@ function admission(spec, config, dependencies, at) {
285
285
  return { rejected: rejection(spec.executionId, "resource_limit", "Local execution capacity is exhausted", at) };
286
286
  }
287
287
  const permission = effectivePermission(spec, config);
288
- if ((spec.runtime.name === "kimi" || spec.runtime.name === "hermes" || spec.runtime.name === "opencode")
288
+ if ((spec.runtime.name === "kimi" || spec.runtime.name === "hermes" || spec.runtime.name === "opencode" || spec.runtime.name === "pi")
289
289
  && permission !== "full_access") {
290
290
  const displayName = spec.runtime.name === "kimi"
291
291
  ? "Kimi"
292
- : spec.runtime.name === "hermes" ? "Hermes" : "OpenCode";
292
+ : spec.runtime.name === "hermes" ? "Hermes"
293
+ : spec.runtime.name === "pi" ? "Pi" : "OpenCode";
293
294
  return {
294
295
  rejected: rejection(spec.executionId, "local_policy_denied", `${displayName} cannot enforce ${permission} permission`, at),
295
296
  };
@@ -212,7 +212,8 @@ export function decodeExternalOutputEvent(runtime, event, decoder) {
212
212
  }
213
213
  if ((runtime === "hermes" && candidate.type === "hermes.acp.text_delta")
214
214
  || (runtime === "deepseek-harness" && candidate.type === "deepseek-harness.acp.text_delta")
215
- || (runtime === "opencode" && candidate.type === "opencode.text_delta")) {
215
+ || (runtime === "opencode" && candidate.type === "opencode.text_delta")
216
+ || (runtime === "pi" && candidate.type === "pi.text_delta")) {
216
217
  return typeof candidate.text === "string" ? decoder.push(candidate.text) : [];
217
218
  }
218
219
  return candidate.type === undefined
@@ -89,7 +89,7 @@ export function awaitExit(child) {
89
89
  }
90
90
  export function exitActivity(runtime, exitCode, stderrTail) {
91
91
  if ((runtime === "codex" || runtime === "kimi" || runtime === "hermes"
92
- || runtime === "opencode" || runtime === "deepseek-harness" || exitCode === -1) && exitCode !== 0) {
92
+ || runtime === "opencode" || runtime === "deepseek-harness" || runtime === "pi" || exitCode === -1) && exitCode !== 0) {
93
93
  return {
94
94
  kind: "error",
95
95
  label: "运行出错",
@@ -97,7 +97,7 @@ export function exitActivity(runtime, exitCode, stderrTail) {
97
97
  };
98
98
  }
99
99
  if ((runtime === "kimi" || runtime === "hermes" || runtime === "opencode"
100
- || runtime === "deepseek-harness") && exitCode === 0) {
100
+ || runtime === "deepseek-harness" || runtime === "pi") && exitCode === 0) {
101
101
  return { kind: "done", label: "本轮结束" };
102
102
  }
103
103
  return null;
package/dist/normalize.js CHANGED
@@ -37,7 +37,8 @@ export function extractFinalText(event) {
37
37
  const e = (event ?? {});
38
38
  if ((e.type === "kimi.acp.text_delta" || e.type === "hermes.acp.text_delta"
39
39
  || e.type === "deepseek-harness.acp.text_delta"
40
- || e.type === "opencode.text_delta") && e.text)
40
+ || e.type === "opencode.text_delta"
41
+ || e.type === "pi.text_delta") && e.text)
41
42
  return e.text;
42
43
  if (e.type === "result" && !e.is_error && e.result?.trim())
43
44
  return e.result.trim();
@@ -70,15 +71,18 @@ export function normalizeEvent(event) {
70
71
  }
71
72
  if ((e.type === "kimi.acp.text_delta" || e.type === "hermes.acp.text_delta"
72
73
  || e.type === "deepseek-harness.acp.text_delta"
73
- || e.type === "opencode.text_delta") && e.text?.trim()) {
74
+ || e.type === "opencode.text_delta"
75
+ || e.type === "pi.text_delta") && e.text?.trim()) {
74
76
  return [{ kind: "text", label: "思考/说明", detail: e.text }];
75
77
  }
76
78
  if (e.type === "kimi.acp.tool_call" || e.type === "hermes.acp.tool_call"
77
- || e.type === "deepseek-harness.acp.tool_call" || e.type === "opencode.tool_call") {
79
+ || e.type === "deepseek-harness.acp.tool_call" || e.type === "opencode.tool_call"
80
+ || e.type === "pi.tool_call") {
78
81
  return [{ kind: "tool", label: e.title ? `工具:${e.title}` : "工具调用" }];
79
82
  }
80
83
  if (e.type === "kimi.acp.tool_result" || e.type === "hermes.acp.tool_result"
81
- || e.type === "deepseek-harness.acp.tool_result" || e.type === "opencode.tool_result") {
84
+ || e.type === "deepseek-harness.acp.tool_result" || e.type === "opencode.tool_result"
85
+ || e.type === "pi.tool_result") {
82
86
  return [{ kind: "tool_result", label: "工具返回" }];
83
87
  }
84
88
  if (e.type === "system" && e.subtype === "init") {
@@ -97,7 +101,7 @@ export function normalizeEvent(event) {
97
101
  if (e.type === "turn.completed") {
98
102
  return [{ kind: "done", label: "本轮结束" }];
99
103
  }
100
- if (e.type === "opencode.error") {
104
+ if (e.type === "opencode.error" || e.type === "pi.error") {
101
105
  const detail = typeof event.message === "string"
102
106
  ? event.message
103
107
  : undefined;
@@ -1,6 +1,6 @@
1
1
  import { ATTACHMENT_NATIVE_IMAGE_MAX_BYTES } from "./attachment-limits.js";
2
2
  export const LOCAL_EXECUTION_RUNTIMES = [
3
- "claude", "codex", "kimi", "hermes", "opencode", "deepseek-harness",
3
+ "claude", "codex", "kimi", "hermes", "opencode", "deepseek-harness", "pi",
4
4
  ];
5
5
  export const LOCAL_RUNTIME_CAPABILITIES = Object.freeze({
6
6
  claude: Object.freeze({
@@ -33,6 +33,7 @@ export const LOCAL_RUNTIME_CAPABILITIES = Object.freeze({
33
33
  nativeResume: false,
34
34
  systemPromptTransport: "protocol",
35
35
  }),
36
+ pi: Object.freeze({ transport: "pi-json", nativeResume: false, systemPromptTransport: "protocol" }),
36
37
  });
37
38
  export function runtimeCapability(runtime) {
38
39
  return LOCAL_RUNTIME_CAPABILITIES[runtime];
@@ -95,6 +95,7 @@ export function createRuntimeStartupGate(limits, now = Date.now) {
95
95
  kimi: activeByRuntimeAndLane.get(activeKey("kimi", "foreground")) ?? 0,
96
96
  hermes: activeByRuntimeAndLane.get(activeKey("hermes", "foreground")) ?? 0,
97
97
  opencode: activeByRuntimeAndLane.get(activeKey("opencode", "foreground")) ?? 0,
98
+ pi: activeByRuntimeAndLane.get(activeKey("pi", "foreground")) ?? 0,
98
99
  "deepseek-harness": activeByRuntimeAndLane.get(activeKey("deepseek-harness", "foreground")) ?? 0,
99
100
  },
100
101
  }),
@@ -0,0 +1,28 @@
1
+ import { once } from "node:events";
2
+ import { createInterface } from "node:readline";
3
+ import { parseArgs } from "node:util";
4
+ import { pathToFileURL } from "node:url";
5
+ import spawn from "cross-spawn";
6
+ import { augmentedPath } from "../runtime-path.js";
7
+ import { buildPiArgs, createPiEventDecoder } from "./pi.js";
8
+ async function readInput() { process.stdin.setEncoding("utf8"); let text = ""; for await (const chunk of process.stdin)
9
+ text += String(chunk); return JSON.parse(text); }
10
+ async function emit(event) { if (process.stdout.write(`${JSON.stringify(event)}\n`))
11
+ 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);
17
+ }
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)); }
23
+ const { values } = parseArgs({ args: process.argv.slice(2), options: { bin: { type: "string" } } });
24
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
25
+ if (!values.bin)
26
+ throw new Error("--bin is required");
27
+ runPi(values.bin).then(code => { process.exitCode = code; }).catch(error => { process.stderr.write(`Pi runner failed: ${String(error)}\n`); process.exitCode = 1; });
28
+ }
@@ -0,0 +1,17 @@
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]; }
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
+ sessionId = e.id;
4
+ out.push({ type: "thread.started", thread_id: e.id });
5
+ } const ae = e.assistantMessageEvent; if (e.type === "message_update" && ae?.type === "text_delta" && typeof ae.delta === "string") {
6
+ finalText += ae.delta;
7
+ out.push({ type: "pi.text_delta", text: ae.delta });
8
+ } if (e.type === "message_update" && typeof ae?.type === "string" && /tool(call|_call)/iu.test(ae.type))
9
+ out.push({ type: "pi.tool_call", ...(ae.name ? { title: ae.name } : {}) }); if (e.type === "message_end" && e.message?.role === "assistant")
10
+ usage = e.message.usage; if (e.type === "turn_end") {
11
+ sawTurnEnd = true;
12
+ const u = usage;
13
+ out.push({ type: "turn.completed", ...(u ? { usage: { input_tokens: u.input ?? 0, output_tokens: u.output ?? 0, cache_read_input_tokens: u.cacheRead ?? 0, cache_creation_input_tokens: u.cacheWrite ?? 0 } } : {}) });
14
+ } if (e.type === "error" || e.type === "agent_error") {
15
+ error = typeof e.error === "string" ? e.error : (e.error?.message ?? "Pi error");
16
+ out.push({ type: "pi.error", message: error });
17
+ } return out; }, finish() { return { ok: error === null && sawTurnEnd, ...(error === null && sawTurnEnd ? {} : { error: error ?? "Pi stream ended without turn completion" }), finalText, sessionId, usage }; } }; }
@@ -61,6 +61,26 @@ export function supervisorLaunch(request) {
61
61
  }),
62
62
  };
63
63
  }
64
+ if (request.runtime === "pi") {
65
+ if (request.effectivePermission !== "full_access") {
66
+ throw new Error(`Pi cannot enforce ${request.effectivePermission} permission`);
67
+ }
68
+ return {
69
+ command: process.execPath,
70
+ args: [
71
+ fileURLToPath(new URL("./runtimes/pi-runner.js", import.meta.url)),
72
+ "--bin", request.bin,
73
+ ],
74
+ cwd: request.cwd,
75
+ env: request.env,
76
+ stdinText: JSON.stringify({
77
+ systemPrompt: request.systemPrompt,
78
+ wakePrompt: request.wakePrompt,
79
+ ...(request.model === undefined ? {} : { model: request.model }),
80
+ ...(request.reasoning === undefined ? {} : { reasoning: request.reasoning }),
81
+ }),
82
+ };
83
+ }
64
84
  if (request.runtime === "opencode") {
65
85
  if (request.effectivePermission !== "full_access") {
66
86
  throw new Error(`OpenCode cannot enforce ${request.effectivePermission} permission`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nowcrew/daemon",
3
- "version": "0.6.91-beta.6",
3
+ "version": "0.6.91-beta.8",
4
4
  "type": "module",
5
5
  "description": "crew daemon — 运行在用户机器:拉起/管理 agent 进程,注入 crew CLI,归一化 runtime 事件",
6
6
  "license": "Apache-2.0",