@nowcrew/daemon 0.6.99-beta.4 → 0.6.100
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/console.js +17 -2
- package/dist/machine-info.js +3 -1
- package/dist/marketplace-controller.js +21 -8
- package/dist/marketplace-maintenance-bridge.js +79 -0
- package/dist/marketplace-maintenance-protocol.js +40 -0
- package/dist/marketplace-serve.js +4 -2
- package/dist/runtimes/pi-runner.js +1 -0
- package/dist/runtimes/pi.js +54 -20
- package/dist/serve.js +1 -1
- package/package.json +1 -1
package/dist/console.js
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* 会附带结构化 payload(diff/命令/todo 清单/计划…),供前端做富渲染(红绿 diff、清单勾选);
|
|
10
10
|
* 前端不识别 payload.kind 时降级为纯文本行,协议向后兼容。
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
* kimi(OpenAI 消息风格行)。
|
|
12
|
+
* 覆盖 runtime 的流:claude(stream-json)、codex(exec --json 的 item.* 事件)、
|
|
13
|
+
* kimi(OpenAI 消息风格行)、ACP 适配器与 Pi 适配器(pi.* 块级事件)。
|
|
14
14
|
*/
|
|
15
15
|
import { detectDaemonLang, translateDaemon } from "./i18n.js";
|
|
16
16
|
import { buildFilePreview, buildSnippetDiff } from "./console-payload.js";
|
|
@@ -280,6 +280,21 @@ export function toConsoleLines(event, runtime) {
|
|
|
280
280
|
const text = typeof e.content === "string" ? e.content.trim() : "";
|
|
281
281
|
return text ? [{ stream: "tool_result", text: clip(text, TOOL_RESULT_CAP) }] : [];
|
|
282
282
|
}
|
|
283
|
+
if (e.type === "pi.text" && e.text)
|
|
284
|
+
return [{ stream: "text", text: e.text }];
|
|
285
|
+
if (e.type === "pi.thinking" && e.text)
|
|
286
|
+
return [{ stream: "thinking", text: e.text }];
|
|
287
|
+
if (e.type === "pi.tool_call") {
|
|
288
|
+
return [{ stream: "tool", text: e.title ? `⏺ ${clip(e.title, TOOL_INPUT_CAP)}` : "⏺ Pi tool" }];
|
|
289
|
+
}
|
|
290
|
+
if (e.type === "pi.tool_result") {
|
|
291
|
+
const text = typeof e.content === "string" ? e.content.trim() : "";
|
|
292
|
+
return text ? [{ stream: e.is_error ? "error" : "tool_result", text: clip(text, TOOL_RESULT_CAP) }] : [];
|
|
293
|
+
}
|
|
294
|
+
if (e.type === "pi.error") {
|
|
295
|
+
const message = event.message;
|
|
296
|
+
return typeof message === "string" && message.trim() ? [{ stream: "error", text: message.trim() }] : [];
|
|
297
|
+
}
|
|
283
298
|
if (e.type === "system" && e.subtype === "init") {
|
|
284
299
|
return [{ stream: "system", text: `● ${td("Claude Code session started")}` }];
|
|
285
300
|
}
|
package/dist/machine-info.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { MAINTENANCE_CAPABILITY } from "./marketplace-maintenance-protocol.js";
|
|
1
2
|
import { retryHarnessProbe } from "./runtimes/harness-probe-retry.js";
|
|
2
3
|
import { MARKETPLACE_CAPABILITY } from "./marketplace-protocol.js";
|
|
3
4
|
/**
|
|
@@ -40,6 +41,7 @@ export const NATIVE_PROJECT_WORKSPACE_READINESS = Object.freeze({
|
|
|
40
41
|
});
|
|
41
42
|
export const DAEMON_CAPABILITIES = Object.freeze([
|
|
42
43
|
MARKETPLACE_CAPABILITY,
|
|
44
|
+
MAINTENANCE_CAPABILITY,
|
|
43
45
|
"scheduled_job_v1",
|
|
44
46
|
"reply_origin_v1",
|
|
45
47
|
"origin_decision_v1",
|
|
@@ -72,7 +74,7 @@ export const daemonCapabilities = (runtimePlatform = process.platform, readiness
|
|
|
72
74
|
const nativeWorkspaceReady = nativeWorkspacePlatform
|
|
73
75
|
&& readiness.resolverReady
|
|
74
76
|
&& readiness.nativeRuntimeAdaptersReady;
|
|
75
|
-
return Object.freeze(DAEMON_CAPABILITIES.filter((capability) => ((capability !== MARKETPLACE_CAPABILITY || nativeWorkspacePlatform)
|
|
77
|
+
return Object.freeze(DAEMON_CAPABILITIES.filter((capability) => (((capability !== MARKETPLACE_CAPABILITY && capability !== MAINTENANCE_CAPABILITY) || nativeWorkspacePlatform)
|
|
76
78
|
&& (capability !== PROJECT_WORKSPACES_CAPABILITY || nativeWorkspaceReady)
|
|
77
79
|
&& (capability !== "message_file_preview_v1" || nativeWorkspacePlatform)
|
|
78
80
|
&& (nativeWorkspacePlatform
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { MaintenanceRequestSchema, MaintenanceReportSchema } from "./marketplace-maintenance-protocol.js";
|
|
1
2
|
import { MarketplaceRequestSchema, marketplaceActive } from "./marketplace-protocol.js";
|
|
2
3
|
import { resolveMarketplaceScopes, runMarketplaceScope } from "./marketplace-runner.js";
|
|
3
4
|
export function createMarketplaceController(deps) {
|
|
@@ -11,22 +12,31 @@ export function createMarketplaceController(deps) {
|
|
|
11
12
|
const deadline = now() + 10 * 60_000;
|
|
12
13
|
let lease = null;
|
|
13
14
|
try {
|
|
14
|
-
const scopes = await (deps.resolve ?? resolveMarketplaceScopes)(request.targets, deps.agentsRoot);
|
|
15
|
+
const scopes = request.type === "marketplace:operation" ? await (deps.resolve ?? resolveMarketplaceScopes)(request.targets, deps.agentsRoot) : [];
|
|
15
16
|
// 查询不使用任务执行槽;只有写入运行时共用市场/插件目录时才等待独占保护。
|
|
16
|
-
if (request.mode === "update") {
|
|
17
|
+
if (request.type === "marketplace:maintenance" || request.mode === "update") {
|
|
17
18
|
while (!stopped && now() < deadline && lease === null) {
|
|
18
19
|
lease = await deps.acquire();
|
|
19
20
|
if (!lease)
|
|
20
21
|
await wait();
|
|
21
22
|
}
|
|
22
23
|
if (!lease || stopped) {
|
|
23
|
-
operations.set(id, {
|
|
24
|
+
operations.set(id, { ...operations.get(id), status: "failed", errorCode: "runtime_busy" });
|
|
24
25
|
return;
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
if (stopped || now() >= deadline)
|
|
28
29
|
throw new Error("operation_deadline");
|
|
29
|
-
operations.set(id, {
|
|
30
|
+
operations.set(id, { ...operations.get(id), status: "running" });
|
|
31
|
+
if (request.type === "marketplace:maintenance") {
|
|
32
|
+
if (!deps.maintain)
|
|
33
|
+
throw new Error("maintenance_unavailable");
|
|
34
|
+
const result = MaintenanceReportSchema.parse(await deps.maintain(request));
|
|
35
|
+
if (result.operationId !== id)
|
|
36
|
+
throw new Error("operation_mismatch");
|
|
37
|
+
operations.set(id, result);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
30
40
|
const results = [];
|
|
31
41
|
for (const scope of scopes) {
|
|
32
42
|
if (stopped || now() >= deadline)
|
|
@@ -41,7 +51,9 @@ export function createMarketplaceController(deps) {
|
|
|
41
51
|
}
|
|
42
52
|
catch {
|
|
43
53
|
const previous = operations.get(id);
|
|
44
|
-
operations.set(id,
|
|
54
|
+
operations.set(id, request.type === "marketplace:maintenance"
|
|
55
|
+
? { ...previous, status: "unknown", errorCode: "operation_unknown" }
|
|
56
|
+
: { ...previous, status: previous.results.some(x => !x.errorCode) ? "partial" : "failed", errorCode: "execution_failed" });
|
|
45
57
|
}
|
|
46
58
|
finally {
|
|
47
59
|
try {
|
|
@@ -55,22 +67,23 @@ export function createMarketplaceController(deps) {
|
|
|
55
67
|
};
|
|
56
68
|
return {
|
|
57
69
|
handle(raw) {
|
|
58
|
-
const parsed = MarketplaceRequestSchema.safeParse(raw);
|
|
70
|
+
const parsed = MarketplaceRequestSchema.or(MaintenanceRequestSchema).safeParse(raw);
|
|
59
71
|
if (!parsed.success || stopped)
|
|
60
72
|
return { ok: false, error: "invalid_request" };
|
|
61
73
|
const request = parsed.data;
|
|
74
|
+
const version = request.type === "marketplace:maintenance" ? { version: 2 } : {};
|
|
62
75
|
const existing = operations.get(request.operationId);
|
|
63
76
|
if (existing)
|
|
64
77
|
return { ok: true, data: existing };
|
|
65
78
|
if (request.action === "status")
|
|
66
|
-
return { ok: true, data: { operationId: request.operationId, status: "unknown", results: [], errorCode: "operation_unknown" } };
|
|
79
|
+
return { ok: true, data: { ...version, operationId: request.operationId, status: "unknown", results: [], errorCode: "operation_unknown" } };
|
|
67
80
|
if (process.platform !== "darwin" && process.platform !== "linux")
|
|
68
81
|
return { ok: false, error: "unsupported_platform" };
|
|
69
82
|
if (current !== null || [...operations.values()].some(x => marketplaceActive(x.status)))
|
|
70
83
|
return { ok: false, error: "runtime_busy" };
|
|
71
84
|
if (operations.size >= 100)
|
|
72
85
|
operations.delete(operations.keys().next().value);
|
|
73
|
-
const accepted = { operationId: request.operationId, status: "queued", results: [], errorCode: null };
|
|
86
|
+
const accepted = { ...version, operationId: request.operationId, status: "queued", results: [], errorCode: null };
|
|
74
87
|
operations.set(request.operationId, accepted);
|
|
75
88
|
current = execute(request).finally(() => { current = null; });
|
|
76
89
|
return { ok: true, data: accepted };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import { augmentedPath } from "./runtime-path.js";
|
|
7
|
+
import { MaintenanceReportSchema } from "./marketplace-maintenance-protocol.js";
|
|
8
|
+
export function maintenanceEnvironment(env) {
|
|
9
|
+
// Keep the machine user's login/config, never forward control-plane credentials.
|
|
10
|
+
return Object.fromEntries(Object.entries(env).filter(([key]) => !/^(CREW_|NOWCREW_|NOWWORK_)/i.test(key)));
|
|
11
|
+
}
|
|
12
|
+
async function supervise(file, id, cwd, env) {
|
|
13
|
+
await new Promise((resolve, reject) => {
|
|
14
|
+
const child = spawn(process.execPath, [file, "--run", id], { cwd, env, detached: true, stdio: "ignore" });
|
|
15
|
+
let expired = false;
|
|
16
|
+
const kill = () => { if (child.pid) {
|
|
17
|
+
try {
|
|
18
|
+
process.kill(-child.pid, "SIGKILL");
|
|
19
|
+
}
|
|
20
|
+
catch { /* already gone */ }
|
|
21
|
+
} };
|
|
22
|
+
const timer = setTimeout(() => { expired = true; kill(); }, 18 * 60_000);
|
|
23
|
+
child.once("error", () => { clearTimeout(timer); kill(); reject(new Error("worker_failed")); });
|
|
24
|
+
child.once("close", code => {
|
|
25
|
+
clearTimeout(timer);
|
|
26
|
+
// Never release the machine lock while descendants can still write shared configuration.
|
|
27
|
+
kill();
|
|
28
|
+
if (expired || code !== 0)
|
|
29
|
+
reject(new Error("worker_failed"));
|
|
30
|
+
else
|
|
31
|
+
resolve();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
export async function runMaintenanceBridge(config, request, acquireEnv) {
|
|
36
|
+
const fail = (errorCode) => ({ version: 2, operationId: request.operationId, status: "failed", results: [], errorCode });
|
|
37
|
+
let dir;
|
|
38
|
+
let started = false;
|
|
39
|
+
try {
|
|
40
|
+
const response = await fetch(`${config.serverUrl.replace(/\/+$/, "")}/machine/marketplace-maintenance/${request.operationId}/runner/${request.plan.runner.releaseId}`, {
|
|
41
|
+
headers: { authorization: `Bearer ${config.machineToken}` }, redirect: "error", signal: AbortSignal.timeout(15_000),
|
|
42
|
+
});
|
|
43
|
+
if (!response.ok)
|
|
44
|
+
return fail("runner_unavailable");
|
|
45
|
+
// Bound even a malformed server response before buffering or executing it.
|
|
46
|
+
if (!response.body)
|
|
47
|
+
return fail("runner_unavailable");
|
|
48
|
+
const chunks = [];
|
|
49
|
+
let bytes = 0;
|
|
50
|
+
for await (const chunk of response.body) {
|
|
51
|
+
bytes += chunk.length;
|
|
52
|
+
if (bytes > 256_000)
|
|
53
|
+
return fail("runner_unavailable");
|
|
54
|
+
chunks.push(chunk);
|
|
55
|
+
}
|
|
56
|
+
const content = Buffer.concat(chunks);
|
|
57
|
+
if (createHash("sha256").update(content).digest("hex") !== request.plan.runner.sha256)
|
|
58
|
+
return fail("runner_unavailable");
|
|
59
|
+
const env = maintenanceEnvironment(await acquireEnv());
|
|
60
|
+
env.PATH = augmentedPath(env, {}, process.platform, "append");
|
|
61
|
+
dir = await mkdtemp(join(tmpdir(), "nowwork-maintenance-"));
|
|
62
|
+
const file = join(dir, "runner.mjs");
|
|
63
|
+
await writeFile(file, content, { mode: 0o600 });
|
|
64
|
+
started = true;
|
|
65
|
+
await supervise(file, request.operationId, dir, env);
|
|
66
|
+
const report = MaintenanceReportSchema.parse(JSON.parse(await readFile(join(dir, "report.json"), "utf8")));
|
|
67
|
+
if (report.operationId !== request.operationId)
|
|
68
|
+
throw new Error("operation_mismatch");
|
|
69
|
+
return report;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// A killed worker may have changed files: require readback/manual confirmation before another write.
|
|
73
|
+
return started ? { ...fail("operation_unknown"), status: "unknown" } : fail("runner_unavailable");
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
if (dir)
|
|
77
|
+
await rm(dir, { recursive: true, force: true });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// 与 daemon 同名文件逐字一致;这是独立于旧单插件协议的版本化契约。
|
|
3
|
+
export const MAINTENANCE_CAPABILITY = "marketplace_codex_maintenance_v1";
|
|
4
|
+
export const MaintenancePlanSchema = z.object({
|
|
5
|
+
marketplace: z.literal("nowwork-pm"),
|
|
6
|
+
actions: z.tuple([z.literal("install-missing"), z.literal("update-installed")]),
|
|
7
|
+
hosts: z.tuple([z.literal("claude"), z.literal("codex"), z.literal("pi")]),
|
|
8
|
+
runner: z.object({ releaseId: z.string().regex(/^[a-f0-9]{64}$/), sha256: z.string().regex(/^[a-f0-9]{64}$/) }).strict(),
|
|
9
|
+
}).strict();
|
|
10
|
+
export const MaintenanceRequestSchema = z.discriminatedUnion("action", [
|
|
11
|
+
z.object({ type: z.literal("marketplace:maintenance"), action: z.literal("start"), operationId: z.string().uuid(), plan: MaintenancePlanSchema }).strict(),
|
|
12
|
+
z.object({ type: z.literal("marketplace:maintenance"), action: z.literal("status"), operationId: z.string().uuid() }).strict(),
|
|
13
|
+
]);
|
|
14
|
+
export const MaintenanceErrorSchema = z.enum(["runtime_busy", "execution_failed", "operation_unknown", "runner_unavailable",
|
|
15
|
+
"manager_missing", "cli_missing", "command_failed", "verification_failed", "source_conflict", "unsupported_platform"]);
|
|
16
|
+
const Version = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._+-]{0,79}$/).nullable();
|
|
17
|
+
export const MaintenanceResultSchema = z.object({
|
|
18
|
+
runtime: z.enum(["claude", "codex", "pi"]), plugin: z.string().regex(/^[a-z0-9][a-z0-9_-]{0,127}$/).nullable(),
|
|
19
|
+
status: z.enum(["installed", "updated", "unchanged", "empty", "missing_cli", "failed"]),
|
|
20
|
+
beforeVersion: Version, afterVersion: Version, targetVersion: Version,
|
|
21
|
+
activation: z.enum(["reload_required", "not_changed", "unverified"]), errorCode: MaintenanceErrorSchema.nullable(),
|
|
22
|
+
}).strict();
|
|
23
|
+
export const MaintenanceReportSchema = z.object({
|
|
24
|
+
version: z.literal(2), operationId: z.string().uuid(),
|
|
25
|
+
status: z.enum(["queued", "running", "completed", "partial", "failed", "unknown"]),
|
|
26
|
+
results: z.array(MaintenanceResultSchema).max(1500), errorCode: MaintenanceErrorSchema.nullable(),
|
|
27
|
+
}).strict().superRefine((r, ctx) => {
|
|
28
|
+
const keys = r.results.map(x => `${x.runtime}:${x.plugin}`);
|
|
29
|
+
if (new Set(keys).size !== keys.length)
|
|
30
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Duplicate plugin result" });
|
|
31
|
+
for (const x of r.results) {
|
|
32
|
+
if (["installed", "updated", "unchanged"].includes(x.status) && (!x.plugin || !x.targetVersion || x.targetVersion !== x.afterVersion || x.errorCode)) {
|
|
33
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Successful plugin result requires a verified target version" });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (r.status === "completed" && (r.errorCode || ["claude", "codex", "pi"].some(host => !r.results.some(x => x.runtime === host))
|
|
37
|
+
|| r.results.some(x => x.errorCode || x.status === "failed" || x.status === "missing_cli"))) {
|
|
38
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Completion requires verified coverage of every host" });
|
|
39
|
+
}
|
|
40
|
+
});
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { runMaintenanceBridge } from "./marketplace-maintenance-bridge.js";
|
|
1
2
|
import { createMarketplaceController } from "./marketplace-controller.js";
|
|
2
3
|
import { prepareDaemonRestart } from "./daemon-updater.js";
|
|
3
|
-
export function createServeMarketplace(config, localSlots, hostCoordinator) {
|
|
4
|
+
export function createServeMarketplace(config, localSlots, hostCoordinator, acquireEnv = async () => ({ ...process.env })) {
|
|
4
5
|
const controller = createMarketplaceController({ agentsRoot: config.agentsRoot,
|
|
6
|
+
maintain: request => runMaintenanceBridge(config, request, acquireEnv),
|
|
5
7
|
acquire: async () => {
|
|
6
8
|
// Marketplace updates can mutate shared Codex/Claude homes across profiles.
|
|
7
9
|
const local = await prepareDaemonRestart({ localSlots });
|
|
@@ -29,7 +31,7 @@ export function createServeMarketplace(config, localSlots, hostCoordinator) {
|
|
|
29
31
|
} });
|
|
30
32
|
return {
|
|
31
33
|
handle(raw, send) {
|
|
32
|
-
if (!raw || typeof raw !== "object" || !("type" in raw) || raw.type !== "marketplace:operation")
|
|
34
|
+
if (!raw || typeof raw !== "object" || !("type" in raw) || (raw.type !== "marketplace:operation" && raw.type !== "marketplace:maintenance"))
|
|
33
35
|
return false;
|
|
34
36
|
const { reqId, ...request } = raw;
|
|
35
37
|
if (typeof reqId === "string")
|
|
@@ -40,6 +40,7 @@ export async function runPi(bin) {
|
|
|
40
40
|
if (inputError)
|
|
41
41
|
throw new Error(`Pi prompt delivery failed: ${inputError.message}`);
|
|
42
42
|
const result = decoder.finish();
|
|
43
|
+
await emit(result.event);
|
|
43
44
|
if (!result.ok) {
|
|
44
45
|
process.stderr.write(`${result.error}\n`);
|
|
45
46
|
return 1;
|
package/dist/runtimes/pi.js
CHANGED
|
@@ -3,6 +3,23 @@ export function buildPiArgs(options) {
|
|
|
3
3
|
...(options.model ? ["--model", options.model] : []),
|
|
4
4
|
...(options.reasoning && options.reasoning !== "default" ? ["--thinking", options.reasoning] : [])];
|
|
5
5
|
}
|
|
6
|
+
const TOOL_ARG_KEYS = ["command", "path", "file_path", "pattern", "url"];
|
|
7
|
+
function toolTitle(name, args) {
|
|
8
|
+
const arg = TOOL_ARG_KEYS.map(key => args?.[key]).find((value) => typeof value === "string" && !!value.trim());
|
|
9
|
+
return arg ? `${name}(${arg.trim()})` : name;
|
|
10
|
+
}
|
|
11
|
+
function toolResultText(result) {
|
|
12
|
+
return (result?.content ?? []).map(item => item.type === "text" && typeof item.text === "string" ? item.text : "")
|
|
13
|
+
.filter(Boolean).join("\n");
|
|
14
|
+
}
|
|
15
|
+
function addUsage(total, next) {
|
|
16
|
+
if (!next)
|
|
17
|
+
return total;
|
|
18
|
+
return {
|
|
19
|
+
input: (total?.input ?? 0) + (next.input ?? 0), output: (total?.output ?? 0) + (next.output ?? 0),
|
|
20
|
+
cacheRead: (total?.cacheRead ?? 0) + (next.cacheRead ?? 0), cacheWrite: (total?.cacheWrite ?? 0) + (next.cacheWrite ?? 0),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
6
23
|
// 保留稳定的原因标识供 server 本地化;不要用 token 数量猜测结束原因。
|
|
7
24
|
function failureReason(message) {
|
|
8
25
|
const reason = message?.stopReason;
|
|
@@ -15,7 +32,17 @@ export function createPiEventDecoder() {
|
|
|
15
32
|
let lastAssistant = null;
|
|
16
33
|
let assistantOpen = false;
|
|
17
34
|
let sawTurnEnd = false;
|
|
35
|
+
let turnUsageCounted = false;
|
|
18
36
|
let usage;
|
|
37
|
+
// 正常 stop 可为空(任务可能要求静默);length/error/aborted 或停在工具轮都必须明确失败。
|
|
38
|
+
const runFailure = () => error ?? (assistantOpen
|
|
39
|
+
? "pi_runtime_failed: incomplete"
|
|
40
|
+
: failureReason(lastAssistant)
|
|
41
|
+
?? (!sawTurnEnd || lastAssistant?.stopReason === "toolUse" ? "pi_runtime_failed: incomplete" : null));
|
|
42
|
+
const normalizedUsage = () => usage ? { usage: {
|
|
43
|
+
input_tokens: usage.input ?? 0, output_tokens: usage.output ?? 0,
|
|
44
|
+
cache_read_input_tokens: usage.cacheRead ?? 0, cache_creation_input_tokens: usage.cacheWrite ?? 0,
|
|
45
|
+
} } : {};
|
|
19
46
|
return {
|
|
20
47
|
push(raw) {
|
|
21
48
|
const e = (raw ?? {});
|
|
@@ -24,8 +51,10 @@ export function createPiEventDecoder() {
|
|
|
24
51
|
sessionId = e.id;
|
|
25
52
|
out.push({ type: "thread.started", thread_id: e.id });
|
|
26
53
|
}
|
|
27
|
-
if (e.type === "turn_start")
|
|
54
|
+
if (e.type === "turn_start") {
|
|
28
55
|
sawTurnEnd = false;
|
|
56
|
+
turnUsageCounted = false;
|
|
57
|
+
}
|
|
29
58
|
if ((e.type === "message_start" || e.type === "message_update") && e.message?.role === "assistant") {
|
|
30
59
|
assistantOpen = true;
|
|
31
60
|
sawTurnEnd = false;
|
|
@@ -38,34 +67,39 @@ export function createPiEventDecoder() {
|
|
|
38
67
|
if (e.type === "message_update" && ae?.type === "thinking_delta" && typeof ae.delta === "string") {
|
|
39
68
|
out.push({ type: "pi.thinking_delta", text: ae.delta });
|
|
40
69
|
}
|
|
41
|
-
|
|
42
|
-
|
|
70
|
+
// delta 逐 token 到达,console 只消费块结束事件,避免一个 token 一行。
|
|
71
|
+
if (e.type === "message_update" && ae?.type === "text_end" && ae.content?.trim()) {
|
|
72
|
+
out.push({ type: "pi.text", text: ae.content.trim() });
|
|
73
|
+
}
|
|
74
|
+
if (e.type === "message_update" && ae?.type === "thinking_end" && ae.content?.trim()) {
|
|
75
|
+
out.push({ type: "pi.thinking", text: ae.content.trim() });
|
|
76
|
+
}
|
|
77
|
+
if (e.type === "tool_execution_start" && typeof e.toolName === "string") {
|
|
78
|
+
out.push({ type: "pi.tool_call", title: toolTitle(e.toolName, e.args) });
|
|
79
|
+
}
|
|
80
|
+
if (e.type === "tool_execution_end") {
|
|
81
|
+
out.push({ type: "pi.tool_result", content: toolResultText(e.result), is_error: e.isError === true });
|
|
43
82
|
}
|
|
44
83
|
if (e.type === "message_end" && e.message?.role === "assistant") {
|
|
45
84
|
lastAssistant = e.message;
|
|
46
|
-
usage = e.message.usage;
|
|
85
|
+
usage = addUsage(usage, e.message.usage);
|
|
86
|
+
turnUsageCounted = true;
|
|
47
87
|
assistantOpen = false;
|
|
48
88
|
sawTurnEnd = false;
|
|
49
89
|
}
|
|
50
90
|
if (e.type === "turn_end") {
|
|
51
|
-
//
|
|
91
|
+
// turn_end 只是 Pi 内部一轮:工具调用后会继续,provider 出错还会自动重试(agent_end.willRetry)。
|
|
52
92
|
if (e.message?.role === "assistant") {
|
|
53
93
|
lastAssistant = e.message;
|
|
54
|
-
|
|
94
|
+
if (!turnUsageCounted)
|
|
95
|
+
usage = addUsage(usage, e.message.usage);
|
|
55
96
|
assistantOpen = false;
|
|
56
97
|
}
|
|
57
98
|
else if (e.message) {
|
|
58
99
|
lastAssistant = null;
|
|
59
100
|
}
|
|
60
101
|
sawTurnEnd = true;
|
|
61
|
-
|
|
62
|
-
input_tokens: usage.input ?? 0, output_tokens: usage.output ?? 0,
|
|
63
|
-
cache_read_input_tokens: usage.cacheRead ?? 0, cache_creation_input_tokens: usage.cacheWrite ?? 0,
|
|
64
|
-
} } : {};
|
|
65
|
-
const failure = failureReason(lastAssistant);
|
|
66
|
-
out.push(failure
|
|
67
|
-
? { type: "result", is_error: true, result: failure, ...normalizedUsage }
|
|
68
|
-
: { type: "turn.completed", ...normalizedUsage });
|
|
102
|
+
turnUsageCounted = false;
|
|
69
103
|
}
|
|
70
104
|
if (e.type === "error" || e.type === "agent_error") {
|
|
71
105
|
error = typeof e.error === "string" ? e.error : (e.error?.message ?? "Pi error");
|
|
@@ -74,12 +108,12 @@ export function createPiEventDecoder() {
|
|
|
74
108
|
return out;
|
|
75
109
|
},
|
|
76
110
|
finish() {
|
|
77
|
-
//
|
|
78
|
-
const failure =
|
|
79
|
-
|
|
80
|
-
:
|
|
81
|
-
|
|
82
|
-
return { ok: failure === null, ...(failure ? { error: failure } : {}), finalText, sessionId, usage };
|
|
111
|
+
// 终态只在 stdout 结束后产出一次:重试/中断都以最终状态为准,usage 覆盖所有已完成轮次。
|
|
112
|
+
const failure = runFailure();
|
|
113
|
+
const event = failure
|
|
114
|
+
? { type: "result", is_error: true, result: failure, ...normalizedUsage() }
|
|
115
|
+
: { type: "turn.completed", ...normalizedUsage() };
|
|
116
|
+
return { ok: failure === null, ...(failure ? { error: failure } : {}), finalText, sessionId, usage, event };
|
|
83
117
|
},
|
|
84
118
|
};
|
|
85
119
|
}
|
package/dist/serve.js
CHANGED
|
@@ -122,7 +122,7 @@ export function serve(config, opts = {}) {
|
|
|
122
122
|
catch { /* reconnect/timeout reconciliation handles a lost status frame */ }
|
|
123
123
|
},
|
|
124
124
|
});
|
|
125
|
-
const marketplace = createServeMarketplace(config, localSlots, hostCoordinator);
|
|
125
|
+
const marketplace = createServeMarketplace(config, localSlots, hostCoordinator, async () => ({ ...(await machineRuntime.provider.acquire()).env }));
|
|
126
126
|
const storageIdentityAdmission = createStorageIdentityAdmission(opts.storageRuntime);
|
|
127
127
|
const projectionCoordinator = createAgentProjectionCoordinator();
|
|
128
128
|
const { projectRegistry, projectWorkspaceRegistry, projectDependencyProvisioner } = createProjectRegistryWiring(config.agentsRoot, opts.execution?.dependencies);
|