pi2dsh 0.24.0 → 0.25.1
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/README.md +11 -3
- package/README.zh.md +10 -3
- package/dist/{analyzer-DqsrD1FG.mjs → analyzer-DWDPeF1a.mjs} +2 -2
- package/dist/{analyzer-DqsrD1FG.mjs.map → analyzer-DWDPeF1a.mjs.map} +1 -1
- package/dist/cli.mjs +3 -3
- package/dist/compat/pi-ai.d.mts +2 -2
- package/dist/compat/pi-ai.mjs +2 -2
- package/dist/compat/pi-coding-agent.d.mts +2 -2
- package/dist/compat/pi-coding-agent.mjs +2 -2
- package/dist/{compatibility-CZ0QpE76.mjs → compatibility-DeV7T_iC.mjs} +28 -25
- package/dist/compatibility-DeV7T_iC.mjs.map +1 -0
- package/dist/credentials-oauth.mjs +2 -2
- package/dist/{google-generative-ai-Dpb_X-Kt.mjs → google-generative-ai-DYSbNJT7.mjs} +2 -2
- package/dist/{google-generative-ai-Dpb_X-Kt.mjs.map → google-generative-ai-DYSbNJT7.mjs.map} +1 -1
- package/dist/{google-shared-CL1Za-ev.mjs → google-shared-LEpwok5n.mjs} +2 -2
- package/dist/{google-shared-CL1Za-ev.mjs.map → google-shared-LEpwok5n.mjs.map} +1 -1
- package/dist/{google-vertex-BXRKa49Y.mjs → google-vertex-BgGRE4WU.mjs} +2 -2
- package/dist/{google-vertex-BXRKa49Y.mjs.map → google-vertex-BgGRE4WU.mjs.map} +1 -1
- package/dist/host.mjs +2 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +5 -5
- package/dist/{multipart-parser-CBAi6PxA.mjs → multipart-parser-BOEZ-KGK.mjs} +2 -2
- package/dist/{multipart-parser-CBAi6PxA.mjs.map → multipart-parser-BOEZ-KGK.mjs.map} +1 -1
- package/dist/{pi-ai-hGbYES_v.d.mts → pi-ai-BHgA3JWd.d.mts} +14 -6
- package/dist/{pi-ai-hGbYES_v.d.mts.map → pi-ai-BHgA3JWd.d.mts.map} +1 -1
- package/dist/{pi-ai-DQPHUR9S.mjs → pi-ai-Cy_BRMmd.mjs} +23 -6
- package/dist/pi-ai-Cy_BRMmd.mjs.map +1 -0
- package/dist/pi-cli-client.mjs +25 -0
- package/dist/{pi-coding-agent-Pvy0m9tS.d.mts → pi-coding-agent-Cas0oHSx.d.mts} +18 -3
- package/dist/{pi-coding-agent-Pvy0m9tS.d.mts.map → pi-coding-agent-Cas0oHSx.d.mts.map} +1 -1
- package/dist/{pi-coding-agent-CPXFo0UO.mjs → pi-coding-agent-lcNs9Wos.mjs} +0 -0
- package/dist/pi-coding-agent-lcNs9Wos.mjs.map +1 -0
- package/dist/{runtime-BiHMcD_8.mjs → runtime-CCzYSxhI.mjs} +870 -127
- package/dist/runtime-CCzYSxhI.mjs.map +1 -0
- package/dist/runtime.d.mts +34 -3
- package/dist/runtime.d.mts.map +1 -1
- package/dist/runtime.mjs +1 -1
- package/dist/{source-0sA5z08z.mjs → source-l77aIDwD.mjs} +76 -3
- package/dist/source-l77aIDwD.mjs.map +1 -0
- package/dist/{src-85HOaW5W.mjs → src-ChZlNrRV.mjs} +3 -3
- package/dist/{src-85HOaW5W.mjs.map → src-ChZlNrRV.mjs.map} +1 -1
- package/package.json +24 -14
- package/dist/compatibility-CZ0QpE76.mjs.map +0 -1
- package/dist/pi-ai-DQPHUR9S.mjs.map +0 -1
- package/dist/pi-coding-agent-CPXFo0UO.mjs.map +0 -1
- package/dist/runtime-BiHMcD_8.mjs.map +0 -1
- package/dist/source-0sA5z08z.mjs.map +0 -1
|
@@ -1,27 +1,193 @@
|
|
|
1
1
|
|
|
2
|
-
import { Dn as
|
|
2
|
+
import { Dn as CapabilityLedger, F as generateBranchSummary, On as PiCapabilityError, b as Theme, d as ExtensionRunner, fn as getDefaultSessionDir, w as __runWithSubagentSessionFactory } from "./pi-coding-agent-lcNs9Wos.mjs";
|
|
3
3
|
import { t as getAgentDir } from "./pi-config-shim-CZ1wFzqM.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { C as getSupportedThinkingLevels, c as builtinProviders, nt as isContextOverflow, o as __runWithPiAiRuntime, r as __createPiAiRuntimeRegistry, tt as AssistantMessageEventStream } from "./pi-ai-Cy_BRMmd.mjs";
|
|
5
5
|
import { W as getKeybindings, _t as stripTerminalSequences } from "./pi-tui-DruSeKmd.mjs";
|
|
6
6
|
import { r as piMcpConfigPaths, t as collectPiMcpServers } from "./mcp-config-C1K-IzzB.mjs";
|
|
7
7
|
import { c as resolvePiProviderAuth, i as oauthCredentialRef, l as storedOAuthCredential, n as joinSignals, o as providerSupportsOAuth, r as loginPiProvider, t as FileCredentialStore } from "./oauth-bridge-C_MAPMGA.mjs";
|
|
8
8
|
import { createRequire } from "node:module";
|
|
9
|
-
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
10
|
-
import { appendFileSync, existsSync, mkdirSync, readFileSync, unlinkSync } from "node:fs";
|
|
11
|
-
import { basename, dirname, join, resolve } from "node:path";
|
|
9
|
+
import { access, chmod, mkdir, mkdtemp, readFile, realpath, rm, stat, writeFile } from "node:fs/promises";
|
|
10
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
12
12
|
import { fileURLToPath } from "node:url";
|
|
13
|
+
import { createServer } from "node:net";
|
|
14
|
+
import { randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
13
15
|
import { homedir, tmpdir } from "node:os";
|
|
14
16
|
import { EventEmitter } from "node:events";
|
|
15
17
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
16
18
|
import { createJiti } from "jiti";
|
|
19
|
+
import * as llm from "@deepseek-ai/dsh-llm";
|
|
17
20
|
import { CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, LlmAdapter, LlmError, QUOTA_EXCEEDED_CODE, createUserMessage, isContextWindowExceededError, isQuotaExceededError } from "@deepseek-ai/dsh-llm";
|
|
18
21
|
import { scopeOf } from "@deepseek-ai/dsh-scope";
|
|
19
22
|
import { renderPrompt } from "@deepseek-ai/dsh-system-prompt";
|
|
20
|
-
import { randomUUID } from "node:crypto";
|
|
21
23
|
import { foldSurface } from "@deepseek-ai/dsh-session";
|
|
22
24
|
import { spawn } from "node:child_process";
|
|
23
25
|
import { Compile } from "typebox/compile";
|
|
24
26
|
import { Value } from "typebox/value";
|
|
27
|
+
//#region src/session-events.ts
|
|
28
|
+
function readSessionEvents(session) {
|
|
29
|
+
if (typeof session !== "object" || session === null) return [];
|
|
30
|
+
const value = session;
|
|
31
|
+
const events = typeof value.snapshotEvents === "function" ? value.snapshotEvents.call(session) : typeof value.events === "function" ? value.events.call(session) : value.events;
|
|
32
|
+
if (!Array.isArray(events)) throw new Error("pi2dsh: the DSH session exposes no supported synchronous history reader");
|
|
33
|
+
return events;
|
|
34
|
+
}
|
|
35
|
+
/** Host-authored context is not a human turn; relay messages intentionally are. */
|
|
36
|
+
function isPiUserMessage(data) {
|
|
37
|
+
const source = data.source;
|
|
38
|
+
return source?.kind === void 0 || source.kind === "user" || source.form === "relay";
|
|
39
|
+
}
|
|
40
|
+
/** The existing native descriptor convention for Pi in-memory side sessions. */
|
|
41
|
+
function isPiEphemeralSession(session) {
|
|
42
|
+
return readSessionEvents(session).some((event) => {
|
|
43
|
+
const data = event.data;
|
|
44
|
+
return event.type === "subagent/descriptor" && data?.provider === "pi2dsh" && data.label?.startsWith("Side: ") === true;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/pi-cli-bridge.ts
|
|
49
|
+
async function parsePiPrintArgs(args, cwd) {
|
|
50
|
+
const parsed = {
|
|
51
|
+
cwd,
|
|
52
|
+
prompt: "",
|
|
53
|
+
extensions: [],
|
|
54
|
+
noExtensions: false,
|
|
55
|
+
noSession: false
|
|
56
|
+
};
|
|
57
|
+
let print = false;
|
|
58
|
+
const prompts = [];
|
|
59
|
+
const value = (index) => {
|
|
60
|
+
const found = args[index + 1];
|
|
61
|
+
if (found === void 0) throw new Error(`pi2dsh CLI: ${args[index]} needs a value`);
|
|
62
|
+
return found;
|
|
63
|
+
};
|
|
64
|
+
for (let i = 0; i < args.length; i++) {
|
|
65
|
+
const arg = args[i];
|
|
66
|
+
if (arg === "-p" || arg === "--print") print = true;
|
|
67
|
+
else if (arg === "--no-session") parsed.noSession = true;
|
|
68
|
+
else if (arg === "--no-extensions") parsed.noExtensions = true;
|
|
69
|
+
else if (arg === "--no-tools") parsed.tools = [];
|
|
70
|
+
else if (arg === "-e" || arg === "--extension") parsed.extensions.push(await realpath(resolve(cwd, value(i++))));
|
|
71
|
+
else if (arg === "--model") parsed.model = value(i++);
|
|
72
|
+
else if (arg === "--provider") parsed.provider = value(i++);
|
|
73
|
+
else if (arg === "--thinking") parsed.thinking = value(i++);
|
|
74
|
+
else if (arg === "--tools") parsed.tools = value(i++).split(",").filter(Boolean);
|
|
75
|
+
else if (arg === "--system-prompt") parsed.systemPrompt = value(i++);
|
|
76
|
+
else if (arg === "--append-system-prompt") parsed.appendSystemPrompt = value(i++);
|
|
77
|
+
else if (arg === "--mode") {
|
|
78
|
+
if (value(i++) !== "text") throw new Error("pi2dsh CLI: only non-interactive text output is supported");
|
|
79
|
+
} else if (arg.startsWith("-")) throw new Error(`pi2dsh CLI: unsupported option ${arg}`);
|
|
80
|
+
else if (arg.startsWith("@")) {
|
|
81
|
+
const path = resolve(cwd, arg.slice(1));
|
|
82
|
+
prompts.push(`<file name=${JSON.stringify(path)}>\n${await readFile(path, "utf8")}\n</file>`);
|
|
83
|
+
} else prompts.push(arg);
|
|
84
|
+
}
|
|
85
|
+
if (!print) throw new Error("pi2dsh CLI: only pi -p/--print is supported; use the DSH surface for interactive work");
|
|
86
|
+
if (prompts.length === 0) throw new Error("pi2dsh CLI: a prompt or @file is required");
|
|
87
|
+
parsed.prompt = prompts.join("\n\n");
|
|
88
|
+
return parsed;
|
|
89
|
+
}
|
|
90
|
+
async function withPiPrintTransport(invoke, execute) {
|
|
91
|
+
if (process.platform === "win32") return execute({});
|
|
92
|
+
const directory = await mkdtemp(join(tmpdir(), "p2cli-"));
|
|
93
|
+
await chmod(directory, 448);
|
|
94
|
+
const socketPath = join(directory, "s");
|
|
95
|
+
const token = randomBytes(32).toString("hex");
|
|
96
|
+
const sockets = /* @__PURE__ */ new Set();
|
|
97
|
+
const work = /* @__PURE__ */ new Set();
|
|
98
|
+
const controllers = /* @__PURE__ */ new Set();
|
|
99
|
+
const server = createServer((socket) => {
|
|
100
|
+
sockets.add(socket);
|
|
101
|
+
const controller = new AbortController();
|
|
102
|
+
controllers.add(controller);
|
|
103
|
+
let input = "";
|
|
104
|
+
let claimed = false;
|
|
105
|
+
let finished = false;
|
|
106
|
+
socket.setEncoding("utf8");
|
|
107
|
+
socket.on("error", () => controller.abort());
|
|
108
|
+
socket.on("close", () => {
|
|
109
|
+
sockets.delete(socket);
|
|
110
|
+
controllers.delete(controller);
|
|
111
|
+
if (!finished) controller.abort(/* @__PURE__ */ new Error("Pi print subprocess disconnected"));
|
|
112
|
+
});
|
|
113
|
+
socket.on("data", (chunk) => {
|
|
114
|
+
if (claimed) return;
|
|
115
|
+
input += chunk;
|
|
116
|
+
if (input.length > 4194304) {
|
|
117
|
+
socket.destroy();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (!input.includes("\n")) return;
|
|
121
|
+
claimed = true;
|
|
122
|
+
const task = (async () => {
|
|
123
|
+
try {
|
|
124
|
+
const request = JSON.parse(input.slice(0, input.indexOf("\n")));
|
|
125
|
+
const supplied = typeof request.token === "string" ? Buffer.from(request.token) : Buffer.alloc(0);
|
|
126
|
+
const expected = Buffer.from(token);
|
|
127
|
+
if (supplied.length !== expected.length || !timingSafeEqual(supplied, expected)) throw new Error("pi2dsh CLI: invalid execution capability");
|
|
128
|
+
if (!Array.isArray(request.args) || request.args.some((arg) => typeof arg !== "string") || typeof request.cwd !== "string") throw new Error("pi2dsh CLI: invalid request");
|
|
129
|
+
const parsed = await parsePiPrintArgs(request.args, request.cwd);
|
|
130
|
+
controller.signal.throwIfAborted();
|
|
131
|
+
const result = await invoke(parsed, controller.signal);
|
|
132
|
+
finished = true;
|
|
133
|
+
socket.end(`${JSON.stringify(result)}\n`);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
finished = true;
|
|
136
|
+
console.warn("[pi2dsh CLI]", error instanceof Error ? error.message : String(error));
|
|
137
|
+
socket.end(`${JSON.stringify({
|
|
138
|
+
code: 1,
|
|
139
|
+
stdout: "",
|
|
140
|
+
stderr: error instanceof Error ? error.message : String(error)
|
|
141
|
+
})}\n`);
|
|
142
|
+
}
|
|
143
|
+
})();
|
|
144
|
+
work.add(task);
|
|
145
|
+
task.finally(() => work.delete(task));
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
try {
|
|
149
|
+
await new Promise((resolveListen, reject) => {
|
|
150
|
+
server.once("error", reject);
|
|
151
|
+
server.listen(socketPath, resolveListen);
|
|
152
|
+
});
|
|
153
|
+
await chmod(socketPath, 384);
|
|
154
|
+
const quote = (text) => `'${text.replaceAll("'", "'\\''")}'`;
|
|
155
|
+
const client = fileURLToPath(new URL("./pi-cli-client.mjs", import.meta.url));
|
|
156
|
+
await writeFile(join(directory, "pi"), `#!/bin/sh\nexec ${quote(process.execPath)} ${quote(client)} "$@"\n`, { mode: 448 });
|
|
157
|
+
return await execute({
|
|
158
|
+
PATH: `${directory}:${process.env.PATH ?? ""}`,
|
|
159
|
+
PI2DSH_CLI_SOCKET: socketPath,
|
|
160
|
+
PI2DSH_CLI_TOKEN: token
|
|
161
|
+
});
|
|
162
|
+
} finally {
|
|
163
|
+
for (const controller of controllers) controller.abort(/* @__PURE__ */ new Error("Pi exec completed"));
|
|
164
|
+
for (const socket of sockets) socket.destroy();
|
|
165
|
+
await Promise.allSettled(work);
|
|
166
|
+
await new Promise((resolveClose) => server.close(() => resolveClose()));
|
|
167
|
+
await rm(directory, {
|
|
168
|
+
recursive: true,
|
|
169
|
+
force: true
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
//#endregion
|
|
174
|
+
//#region src/dsh-content.ts
|
|
175
|
+
/** A file is text on the Pi side, using the host's own execution-world locator. */
|
|
176
|
+
function fileContentForPi(attachment, ctx) {
|
|
177
|
+
const format = llm.fileHandleText;
|
|
178
|
+
if (format === void 0) throw new Error("pi2dsh: this DSH host cannot project file attachments");
|
|
179
|
+
let path;
|
|
180
|
+
try {
|
|
181
|
+
const hostPath = (ctx?.get("attachments"))?.fileHostPath?.(attachment);
|
|
182
|
+
const fs = ctx?.get("fs");
|
|
183
|
+
if (hostPath !== void 0) path = fs?.processPathFromHostPath(hostPath);
|
|
184
|
+
} catch {}
|
|
185
|
+
return {
|
|
186
|
+
type: "text",
|
|
187
|
+
text: format(attachment, path)
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
//#endregion
|
|
25
191
|
//#region src/session-bridge.ts
|
|
26
192
|
/** The durable seq behind a projected entry id (`dsh-<seq>`), when it has one. */
|
|
27
193
|
function entrySeq(id) {
|
|
@@ -107,10 +273,9 @@ function sidecarDir() {
|
|
|
107
273
|
return join(getAgentDir(), "session-entries");
|
|
108
274
|
}
|
|
109
275
|
function sessionEvents(session) {
|
|
110
|
-
|
|
111
|
-
return typeof events === "function" ? events.call(session) : events ?? [];
|
|
276
|
+
return readSessionEvents(session);
|
|
112
277
|
}
|
|
113
|
-
function dshToPiContent$1(content) {
|
|
278
|
+
function dshToPiContent$1(content, fileContent = fileContentForPi) {
|
|
114
279
|
if (!Array.isArray(content)) return [{
|
|
115
280
|
type: "text",
|
|
116
281
|
text: String(content ?? "")
|
|
@@ -121,6 +286,7 @@ function dshToPiContent$1(content) {
|
|
|
121
286
|
text: String(block)
|
|
122
287
|
};
|
|
123
288
|
const record = block;
|
|
289
|
+
if (record.type === "file") return fileContent(record.attachment);
|
|
124
290
|
if (record.type === "text") return {
|
|
125
291
|
type: "text",
|
|
126
292
|
text: String(record.text ?? "")
|
|
@@ -139,8 +305,90 @@ function dshToPiContent$1(content) {
|
|
|
139
305
|
});
|
|
140
306
|
}
|
|
141
307
|
var PiSessionBridge = class {
|
|
308
|
+
fileContent;
|
|
309
|
+
constructor(fileContent = fileContentForPi) {
|
|
310
|
+
this.fileContent = fileContent;
|
|
311
|
+
}
|
|
142
312
|
records = /* @__PURE__ */ new Map();
|
|
143
313
|
loaded = /* @__PURE__ */ new Set();
|
|
314
|
+
exportedSessions = /* @__PURE__ */ new Map();
|
|
315
|
+
async exportStoredSessions(persistence) {
|
|
316
|
+
const priorExports = [];
|
|
317
|
+
const root = join(getAgentDir(), "sessions");
|
|
318
|
+
if (existsSync(root)) for (const directory of readdirSync(root, { withFileTypes: true })) {
|
|
319
|
+
if (!directory.isDirectory()) continue;
|
|
320
|
+
for (const entry of readdirSync(join(root, directory.name), { withFileTypes: true })) {
|
|
321
|
+
if (!entry.isFile() || !entry.name.startsWith("dsh-")) continue;
|
|
322
|
+
const path = join(root, directory.name, entry.name);
|
|
323
|
+
const id = this.sessionIdOfArchiveFile(path);
|
|
324
|
+
if (id !== void 0) priorExports.push({
|
|
325
|
+
path,
|
|
326
|
+
id
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
const snapshots = await persistence.list();
|
|
331
|
+
const present = /* @__PURE__ */ new Set();
|
|
332
|
+
const hidden = /* @__PURE__ */ new Set();
|
|
333
|
+
for (const snapshot of snapshots) {
|
|
334
|
+
const header = snapshot.header ?? snapshot;
|
|
335
|
+
if (typeof header.id !== "string") continue;
|
|
336
|
+
present.add(header.id);
|
|
337
|
+
if (typeof header.cwd !== "string") continue;
|
|
338
|
+
if (typeof persistence.open === "function") {
|
|
339
|
+
const handle = await persistence.open(header.id, "read");
|
|
340
|
+
try {
|
|
341
|
+
const { events } = await handle.read();
|
|
342
|
+
const session = {
|
|
343
|
+
id: header.id,
|
|
344
|
+
header: handle.header,
|
|
345
|
+
events
|
|
346
|
+
};
|
|
347
|
+
if (isPiEphemeralSession(session)) hidden.add(header.id);
|
|
348
|
+
else this.exportSessionFile(session, header.cwd);
|
|
349
|
+
} finally {
|
|
350
|
+
await handle.close();
|
|
351
|
+
}
|
|
352
|
+
} else if (typeof persistence.inspect === "function") {
|
|
353
|
+
const { meta, events } = await persistence.inspect(header.id);
|
|
354
|
+
const session = {
|
|
355
|
+
id: header.id,
|
|
356
|
+
header: meta,
|
|
357
|
+
events
|
|
358
|
+
};
|
|
359
|
+
if (isPiEphemeralSession(session)) hidden.add(header.id);
|
|
360
|
+
else this.exportSessionFile(session, header.cwd);
|
|
361
|
+
}
|
|
362
|
+
this.exportedSessions.delete(header.id);
|
|
363
|
+
}
|
|
364
|
+
for (const exported of priorExports) if ((!present.has(exported.id) || hidden.has(exported.id)) && existsSync(exported.path)) unlinkSync(exported.path);
|
|
365
|
+
}
|
|
366
|
+
/** Materialize the public Pi file contract from the native session, never vice versa. */
|
|
367
|
+
exportSessionFile(session, cwd) {
|
|
368
|
+
this.exportedSessions.set(session.id, {
|
|
369
|
+
session,
|
|
370
|
+
cwd
|
|
371
|
+
});
|
|
372
|
+
const safe = session.id.replace(/[^a-zA-Z0-9._-]+/gu, "_");
|
|
373
|
+
const path = join(getDefaultSessionDir(cwd, getAgentDir()), `dsh-${safe}.jsonl`);
|
|
374
|
+
const text = `${[{
|
|
375
|
+
type: "session",
|
|
376
|
+
version: 3,
|
|
377
|
+
id: session.id,
|
|
378
|
+
cwd,
|
|
379
|
+
timestamp: new Date(Number(session.header?.createdAt ?? sessionEvents(session)[0]?.time ?? Date.now())).toISOString(),
|
|
380
|
+
pi2dshExport: { nativeSessionId: session.id }
|
|
381
|
+
}, ...this.projectEntries(session)].map((value) => JSON.stringify(value)).join("\n")}\n`;
|
|
382
|
+
if (existsSync(path) && readFileSync(path, "utf8") === text) return path;
|
|
383
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
384
|
+
try {
|
|
385
|
+
writeFileSync(temporary, text, { mode: 384 });
|
|
386
|
+
renameSync(temporary, path);
|
|
387
|
+
} finally {
|
|
388
|
+
if (existsSync(temporary)) unlinkSync(temporary);
|
|
389
|
+
}
|
|
390
|
+
return path;
|
|
391
|
+
}
|
|
144
392
|
sidecarPath(sessionId) {
|
|
145
393
|
const safe = sessionId.replace(/[^a-zA-Z0-9._-]+/gu, "_");
|
|
146
394
|
return join(sidecarDir(), `${safe}.jsonl`);
|
|
@@ -179,9 +427,16 @@ var PiSessionBridge = class {
|
|
|
179
427
|
sessionIdOfArchiveFile(path) {
|
|
180
428
|
if (typeof path !== "string" || path.length === 0) return void 0;
|
|
181
429
|
const resolved = resolve(path);
|
|
182
|
-
if (resolve(dirname(resolved)) !== resolve(sidecarDir())) return void 0;
|
|
183
430
|
const base = basename(resolved);
|
|
184
|
-
return base.endsWith(".jsonl") ? base.slice(0, -6) : void 0;
|
|
431
|
+
if (resolve(dirname(resolved)) === resolve(sidecarDir())) return base.endsWith(".jsonl") ? base.slice(0, -6) : void 0;
|
|
432
|
+
if (relative(join(getAgentDir(), "sessions"), resolved).startsWith("..") || !base.startsWith("dsh-") || !base.endsWith(".jsonl")) return void 0;
|
|
433
|
+
try {
|
|
434
|
+
const header = JSON.parse(readFileSync(resolved, "utf8").split("\n")[0]);
|
|
435
|
+
const marker = header.pi2dshExport;
|
|
436
|
+
return typeof marker?.nativeSessionId === "string" && marker.nativeSessionId === header.id ? marker.nativeSessionId : void 0;
|
|
437
|
+
} catch {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
185
440
|
}
|
|
186
441
|
/**
|
|
187
442
|
* Retire one archive this bridge minted: the file whose existence tells Pi
|
|
@@ -195,6 +450,13 @@ var PiSessionBridge = class {
|
|
|
195
450
|
} catch {}
|
|
196
451
|
this.records.delete(sessionId);
|
|
197
452
|
this.loaded.delete(sessionId);
|
|
453
|
+
this.exportedSessions.delete(sessionId);
|
|
454
|
+
const root = join(getAgentDir(), "sessions");
|
|
455
|
+
if (existsSync(root)) for (const directory of readdirSync(root, { withFileTypes: true })) {
|
|
456
|
+
if (!directory.isDirectory()) continue;
|
|
457
|
+
const file = join(root, directory.name, `dsh-${sessionId.replace(/[^a-zA-Z0-9._-]+/gu, "_")}.jsonl`);
|
|
458
|
+
if (this.sessionIdOfArchiveFile(file) === sessionId) unlinkSync(file);
|
|
459
|
+
}
|
|
198
460
|
}
|
|
199
461
|
load(sessionId) {
|
|
200
462
|
if (this.loaded.has(sessionId)) return;
|
|
@@ -221,6 +483,8 @@ var PiSessionBridge = class {
|
|
|
221
483
|
this.records.set(sessionId, list);
|
|
222
484
|
this.archiveFileFor(sessionId);
|
|
223
485
|
appendFileSync(this.sidecarPath(sessionId), `${piEntryLineOf(record)}\n`);
|
|
486
|
+
const exported = this.exportedSessions.get(sessionId);
|
|
487
|
+
if (exported !== void 0) this.exportSessionFile(exported.session, exported.cwd);
|
|
224
488
|
}
|
|
225
489
|
appendCustomEntry(sessionId, customType, data) {
|
|
226
490
|
const id = randomUUID();
|
|
@@ -330,12 +594,19 @@ var PiSessionBridge = class {
|
|
|
330
594
|
if (type === "user/message") merged.push({
|
|
331
595
|
time,
|
|
332
596
|
entry: {
|
|
333
|
-
type: "message",
|
|
334
597
|
id: `dsh-${seq}`,
|
|
335
598
|
timestamp: new Date(time).toISOString(),
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
599
|
+
...isPiUserMessage(data) ? {
|
|
600
|
+
type: "message",
|
|
601
|
+
message: {
|
|
602
|
+
role: "user",
|
|
603
|
+
content: dshToPiContent$1(data.content, this.fileContent)
|
|
604
|
+
}
|
|
605
|
+
} : {
|
|
606
|
+
type: "custom_message",
|
|
607
|
+
customType: String(data.source?.piCustomType ?? data.source?.kind ?? "context"),
|
|
608
|
+
content: dshToPiContent$1(data.content, this.fileContent),
|
|
609
|
+
display: false
|
|
339
610
|
}
|
|
340
611
|
}
|
|
341
612
|
});
|
|
@@ -349,7 +620,7 @@ var PiSessionBridge = class {
|
|
|
349
620
|
timestamp: new Date(time).toISOString(),
|
|
350
621
|
message: {
|
|
351
622
|
role: "assistant",
|
|
352
|
-
content: dshToPiContent$1(message.content)
|
|
623
|
+
content: dshToPiContent$1(message.content, this.fileContent)
|
|
353
624
|
}
|
|
354
625
|
}
|
|
355
626
|
});
|
|
@@ -433,9 +704,9 @@ var PiSessionBridge = class {
|
|
|
433
704
|
const leafOf = () => entriesOf().at(-1);
|
|
434
705
|
return {
|
|
435
706
|
getCwd: () => cwd,
|
|
436
|
-
getSessionDir: () =>
|
|
707
|
+
getSessionDir: () => getDefaultSessionDir(cwd, getAgentDir()),
|
|
437
708
|
getSessionId: () => session.id,
|
|
438
|
-
getSessionFile: () => this.
|
|
709
|
+
getSessionFile: () => isPiEphemeralSession(session) ? void 0 : this.exportSessionFile(session, cwd),
|
|
439
710
|
getLeafId: () => leafOf()?.id ?? null,
|
|
440
711
|
getLeafEntry: () => leafOf(),
|
|
441
712
|
getEntry: (id) => entriesOf().find((entry) => entry.id === id),
|
|
@@ -500,9 +771,9 @@ function nativeToolNameOf(name) {
|
|
|
500
771
|
* roster-owned surfaces the preset tools live above the global layer, so the
|
|
501
772
|
* scoped read is the one that sees them.
|
|
502
773
|
*/
|
|
503
|
-
function childSchemas(toolsService,
|
|
774
|
+
function childSchemas(toolsService, childAgent) {
|
|
504
775
|
try {
|
|
505
|
-
if (
|
|
776
|
+
if (childAgent !== void 0) return toolsService.schemas(childAgent);
|
|
506
777
|
} catch {}
|
|
507
778
|
return toolsService.schemas();
|
|
508
779
|
}
|
|
@@ -561,6 +832,8 @@ var PiBridgedAgentSession = class {
|
|
|
561
832
|
/** Keeps message projections (which await attachment reads) in log order. */
|
|
562
833
|
#messageProjection = Promise.resolve();
|
|
563
834
|
#aborted = false;
|
|
835
|
+
#liveStreamAttempt;
|
|
836
|
+
#streamingText = "";
|
|
564
837
|
constructor(host, handle, tools, thinkingLevel) {
|
|
565
838
|
this.#thinkingLevel = typeof thinkingLevel === "string" && thinkingLevel.length > 0 ? thinkingLevel : "off";
|
|
566
839
|
this.#host = host;
|
|
@@ -572,8 +845,32 @@ var PiBridgedAgentSession = class {
|
|
|
572
845
|
const cordis = unscopedEventContext(host.cordis);
|
|
573
846
|
const offEvents = cordis.on("session/event", (session, event) => {
|
|
574
847
|
if (session !== this.#session) return;
|
|
848
|
+
if (event.type === "assistant/chunk" && this.#liveStreamAttempt !== void 0) return;
|
|
575
849
|
this.#project(event);
|
|
576
850
|
});
|
|
851
|
+
const offStream = cordis.on("agent/assistant-stream", (payload) => {
|
|
852
|
+
if (payload.agent !== this.#handle.agent) return;
|
|
853
|
+
const frame = payload.frame;
|
|
854
|
+
if (frame.type === "start") {
|
|
855
|
+
this.#liveStreamAttempt = {
|
|
856
|
+
id: frame.attemptId,
|
|
857
|
+
index: -1,
|
|
858
|
+
ended: false
|
|
859
|
+
};
|
|
860
|
+
this.#streamingText = "";
|
|
861
|
+
} else {
|
|
862
|
+
const active = this.#liveStreamAttempt;
|
|
863
|
+
if (active === void 0 || active.ended || active.id !== frame.attemptId) return;
|
|
864
|
+
if (frame.type === "end") active.ended = true;
|
|
865
|
+
if (frame.type === "chunk" && Number(frame.index) > active.index) {
|
|
866
|
+
active.index = Number(frame.index);
|
|
867
|
+
this.#project({
|
|
868
|
+
type: "assistant/chunk",
|
|
869
|
+
data: { chunk: frame.chunk }
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
});
|
|
577
874
|
const offPre = cordis.on("tools/pre-execute", async (exec, next) => {
|
|
578
875
|
if (exec.agent !== this.#handle.agent || this.agent.beforeToolCall === void 0) return next();
|
|
579
876
|
const decision = await this.agent.beforeToolCall({ toolCall: {
|
|
@@ -587,7 +884,7 @@ var PiBridgedAgentSession = class {
|
|
|
587
884
|
};
|
|
588
885
|
return next();
|
|
589
886
|
});
|
|
590
|
-
this.#disposers.push(offEvents, offPre);
|
|
887
|
+
this.#disposers.push(offEvents, offStream, offPre);
|
|
591
888
|
const session = this;
|
|
592
889
|
this.#state = {
|
|
593
890
|
get systemPrompt() {
|
|
@@ -712,22 +1009,26 @@ var PiBridgedAgentSession = class {
|
|
|
712
1009
|
args
|
|
713
1010
|
});
|
|
714
1011
|
}
|
|
1012
|
+
if (type === "assistant/message") this.#streamingText = "";
|
|
715
1013
|
if (type === "assistant/chunk") {
|
|
716
1014
|
const chunk = event.data.chunk ?? {};
|
|
1015
|
+
if (chunk.type !== void 0 && chunk.type !== "text-delta") return;
|
|
717
1016
|
const delta = typeof chunk.text === "string" ? chunk.text : typeof chunk.delta === "string" ? chunk.delta : "";
|
|
1017
|
+
const message = {
|
|
1018
|
+
role: "assistant",
|
|
1019
|
+
content: [{
|
|
1020
|
+
type: "text",
|
|
1021
|
+
text: this.#streamingText += delta
|
|
1022
|
+
}]
|
|
1023
|
+
};
|
|
718
1024
|
emit({
|
|
719
1025
|
type: "message_update",
|
|
720
|
-
message
|
|
721
|
-
role: "assistant",
|
|
722
|
-
content: [{
|
|
723
|
-
type: "text",
|
|
724
|
-
text: delta
|
|
725
|
-
}]
|
|
726
|
-
},
|
|
1026
|
+
message,
|
|
727
1027
|
assistantMessageEvent: {
|
|
728
1028
|
type: "text_delta",
|
|
1029
|
+
contentIndex: 0,
|
|
729
1030
|
delta,
|
|
730
|
-
|
|
1031
|
+
partial: message
|
|
731
1032
|
}
|
|
732
1033
|
});
|
|
733
1034
|
}
|
|
@@ -809,7 +1110,8 @@ var PiBridgedAgentSession = class {
|
|
|
809
1110
|
content: blocks,
|
|
810
1111
|
source: {
|
|
811
1112
|
kind: "plugin",
|
|
812
|
-
plugin: this.#host.messageSource
|
|
1113
|
+
plugin: this.#host.messageSource,
|
|
1114
|
+
form: "relay"
|
|
813
1115
|
}
|
|
814
1116
|
}), "followup");
|
|
815
1117
|
await completed;
|
|
@@ -964,6 +1266,9 @@ async function createBridgedAgentSession(host, options) {
|
|
|
964
1266
|
const archiveFile = typeof providedManager?.getSessionFile === "function" ? providedManager.getSessionFile() : void 0;
|
|
965
1267
|
const resumeSessionId = host.resumeSessionIdFor?.(archiveFile);
|
|
966
1268
|
const sideline = providedManager !== void 0 && archiveFile === void 0;
|
|
1269
|
+
const parentId = host.parentSessionId();
|
|
1270
|
+
const parentAgent = host.parentAgent?.() ?? (parentId === void 0 ? void 0 : agents.get?.(parentId));
|
|
1271
|
+
const parentOwnership = parentAgent === void 0 ? {} : { parentAgent };
|
|
967
1272
|
subagentSerial += 1;
|
|
968
1273
|
const sessionId = `pi2dsh-sub-${Date.now().toString(36)}-${subagentSerial}`;
|
|
969
1274
|
let handle;
|
|
@@ -986,8 +1291,9 @@ async function createBridgedAgentSession(host, options) {
|
|
|
986
1291
|
...typeof requestedModel.provider === "string" && requestedModel.provider.length > 0 ? { provider: requestedModel.provider } : {}
|
|
987
1292
|
} : host.parentModelRoute?.();
|
|
988
1293
|
const agentOptionsFragment = typeof route?.model === "string" && route.model.length > 0 ? { agentOptions: route } : {};
|
|
989
|
-
const setup = async (childCtx) => {
|
|
990
|
-
const
|
|
1294
|
+
const setup = async (childCtx, agent) => {
|
|
1295
|
+
const childAgent = agent ?? childCtx.agent;
|
|
1296
|
+
const childSession = childAgent?.session;
|
|
991
1297
|
if (typeof childSession?.append === "function") {
|
|
992
1298
|
if (delegated.sandboxMode !== void 0) childSession.append("sandbox/mode", {
|
|
993
1299
|
mode: delegated.sandboxMode,
|
|
@@ -1017,7 +1323,7 @@ async function createBridgedAgentSession(host, options) {
|
|
|
1017
1323
|
if (restriction !== void 0) {
|
|
1018
1324
|
const toolsService = typeof childCtx.get === "function" ? childCtx.get("tools") : void 0;
|
|
1019
1325
|
if (toolsService !== void 0) {
|
|
1020
|
-
const known = new Set(childSchemas(toolsService,
|
|
1326
|
+
const known = new Set(childSchemas(toolsService, childAgent).map((schema) => schema.name));
|
|
1021
1327
|
initialRestrictionDispose = toolsService.restrict({ allow: restriction.filter((name) => known.has(name)) });
|
|
1022
1328
|
}
|
|
1023
1329
|
}
|
|
@@ -1025,7 +1331,7 @@ async function createBridgedAgentSession(host, options) {
|
|
|
1025
1331
|
if (excluded.length > 0) {
|
|
1026
1332
|
const toolsService = typeof childCtx.get === "function" ? childCtx.get("tools") : void 0;
|
|
1027
1333
|
if (toolsService !== void 0) {
|
|
1028
|
-
const known = new Set(childSchemas(toolsService,
|
|
1334
|
+
const known = new Set(childSchemas(toolsService, childAgent).map((schema) => schema.name));
|
|
1029
1335
|
toolsService.restrict({ deny: excluded.map(nativeToolNameOf).filter((name) => known.has(name)) });
|
|
1030
1336
|
}
|
|
1031
1337
|
}
|
|
@@ -1045,11 +1351,14 @@ async function createBridgedAgentSession(host, options) {
|
|
|
1045
1351
|
if (typeof agents.resume !== "function") throw new Error("pi2dsh: reopening a child conversation needs the DSH agent registry's persisted-resume seam, which this composition does not provide");
|
|
1046
1352
|
handle = await agents.resume({
|
|
1047
1353
|
resumeSessionId,
|
|
1354
|
+
...parentOwnership,
|
|
1048
1355
|
...agentOptionsFragment,
|
|
1049
|
-
setup
|
|
1356
|
+
setup,
|
|
1357
|
+
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
1050
1358
|
});
|
|
1051
1359
|
} else handle = await agents.create({
|
|
1052
1360
|
sessionId,
|
|
1361
|
+
...parentOwnership,
|
|
1053
1362
|
meta: {
|
|
1054
1363
|
cwd: typeof options.cwd === "string" ? options.cwd : host.cwd(),
|
|
1055
1364
|
origin: "subagent",
|
|
@@ -1058,7 +1367,8 @@ async function createBridgedAgentSession(host, options) {
|
|
|
1058
1367
|
...typeof composedPreset === "string" && composedPreset.length > 0 ? { agentPreset: composedPreset } : {}
|
|
1059
1368
|
},
|
|
1060
1369
|
...agentOptionsFragment,
|
|
1061
|
-
setup
|
|
1370
|
+
setup,
|
|
1371
|
+
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
1062
1372
|
});
|
|
1063
1373
|
} catch (error) {
|
|
1064
1374
|
if (resumeSessionId !== void 0) {
|
|
@@ -1068,7 +1378,7 @@ async function createBridgedAgentSession(host, options) {
|
|
|
1068
1378
|
}
|
|
1069
1379
|
throw new Error(`pi2dsh: reopening child session ${JSON.stringify(resumeSessionId)} failed — this composition may lack session persistence (${error instanceof Error ? error.message : String(error)})`);
|
|
1070
1380
|
}
|
|
1071
|
-
throw new Error(`pi2dsh: subagent creation
|
|
1381
|
+
throw new Error(`pi2dsh: native subagent creation failed (${error instanceof Error ? error.message : String(error)})`, { cause: error });
|
|
1072
1382
|
}
|
|
1073
1383
|
const tools = [...Array.isArray(options.tools) ? options.tools : [], ...Array.isArray(options.customTools) ? options.customTools : []];
|
|
1074
1384
|
const childSession = handle.agent.session;
|
|
@@ -1232,7 +1542,7 @@ var BrowserSurfaces = class {
|
|
|
1232
1542
|
label: thread.label,
|
|
1233
1543
|
...thread.package === void 0 ? {} : { package: thread.package },
|
|
1234
1544
|
running: thread.session.isStreaming === true,
|
|
1235
|
-
messages: thread.session.messages.filter((message) => !thread.session.isCarriedContext(message)).map((message) => ({
|
|
1545
|
+
messages: thread.session.messages.filter((message) => !thread.session.isCarriedContext(message)).filter((message) => message.display !== false).map((message) => ({
|
|
1236
1546
|
role: String(message.role ?? ""),
|
|
1237
1547
|
text: messageText(message)
|
|
1238
1548
|
})).filter((entry) => !entry.text.startsWith(`<${SEED_CARRIER_TAG}>`)).filter((entry) => entry.text.length > 0)
|
|
@@ -2021,6 +2331,7 @@ var ModelCatalog = class {
|
|
|
2021
2331
|
if (exact.contextWindow !== void 0) model.contextWindow = exact.contextWindow;
|
|
2022
2332
|
if (exact.maxTokens !== void 0) model.maxTokens = exact.maxTokens;
|
|
2023
2333
|
model.reasoning = exact.reasoning;
|
|
2334
|
+
if (exact.thinkingLevelMap !== void 0) model.thinkingLevelMap = exact.thinkingLevelMap;
|
|
2024
2335
|
resolved.set(`${model.provider}${model.id}`, model);
|
|
2025
2336
|
}));
|
|
2026
2337
|
this.#models = next;
|
|
@@ -2046,7 +2357,17 @@ var ModelCatalog = class {
|
|
|
2046
2357
|
const defaultMaxTokens = info.defaultMaxTokens;
|
|
2047
2358
|
if (typeof defaultMaxTokens === "number") model.maxTokens = defaultMaxTokens;
|
|
2048
2359
|
const reasoning = info.reasoning;
|
|
2049
|
-
|
|
2360
|
+
const efforts = new Set((reasoning?.efforts ?? []).map((effort) => String(effort?.id ?? "")));
|
|
2361
|
+
model.reasoning = [...efforts].some((effort) => effort !== "" && effort !== "off");
|
|
2362
|
+
if (efforts.size > 0) model.thinkingLevelMap = Object.fromEntries([
|
|
2363
|
+
"off",
|
|
2364
|
+
"minimal",
|
|
2365
|
+
"low",
|
|
2366
|
+
"medium",
|
|
2367
|
+
"high",
|
|
2368
|
+
"xhigh",
|
|
2369
|
+
"max"
|
|
2370
|
+
].map((level) => [level, efforts.has(level) ? level : null]));
|
|
2050
2371
|
return model;
|
|
2051
2372
|
} catch {
|
|
2052
2373
|
return;
|
|
@@ -2171,6 +2492,7 @@ function streamViaDshLlm(llm, request) {
|
|
|
2171
2492
|
...Array.isArray(request.context.tools) ? { tools: normalizeToolSchemas(request.context.tools) } : {},
|
|
2172
2493
|
...typeof options.maxTokens === "number" ? { maxTokens: options.maxTokens } : {},
|
|
2173
2494
|
...typeof options.temperature === "number" ? { temperature: options.temperature } : {},
|
|
2495
|
+
...typeof options.reasoning === "string" ? { reasoningEffort: options.reasoning } : {},
|
|
2174
2496
|
...options.signal instanceof AbortSignal ? { signal: options.signal } : {}
|
|
2175
2497
|
};
|
|
2176
2498
|
(async () => {
|
|
@@ -2378,15 +2700,21 @@ function normalizeToolSchemas(tools) {
|
|
|
2378
2700
|
/** DSH GenerateOptions parts → the Pi request context a provider.stream expects. */
|
|
2379
2701
|
async function dshRequestToPiContext(options, attachments) {
|
|
2380
2702
|
const piMessages = [];
|
|
2703
|
+
const messages = Array.isArray(options.messages) ? options.messages : [];
|
|
2704
|
+
const first = messages[0];
|
|
2705
|
+
const hasExplicitSystem = typeof options.system === "string";
|
|
2706
|
+
const systemPrompt = hasExplicitSystem ? options.system : first?.role === "system" ? (Array.isArray(first.content) ? first.content : []).filter((block) => block.type === "text").map((block) => String(block.text ?? "")).join("\n") : void 0;
|
|
2707
|
+
const history = !hasExplicitSystem && first?.role === "system" ? messages.slice(1) : messages;
|
|
2381
2708
|
const toolNamesById = /* @__PURE__ */ new Map();
|
|
2382
|
-
for (const message of
|
|
2709
|
+
for (const message of history) {
|
|
2383
2710
|
if (message.role !== "assistant") continue;
|
|
2384
2711
|
for (const block of Array.isArray(message.content) ? message.content : []) if (block.type === "tool-call" && typeof block.id === "string" && typeof block.name === "string") toolNamesById.set(block.id, block.name);
|
|
2385
2712
|
}
|
|
2386
|
-
for (const message of
|
|
2713
|
+
for (const message of history) {
|
|
2387
2714
|
const content = Array.isArray(message.content) ? message.content : [];
|
|
2388
2715
|
const source = message.source;
|
|
2389
|
-
if (message.role === "
|
|
2716
|
+
if (message.role === "system") throw new Error("pi2dsh: this Pi provider cannot represent an in-history system prompt update");
|
|
2717
|
+
else if (message.role === "user" && source?.kind === "tool") {
|
|
2390
2718
|
const result = content[0];
|
|
2391
2719
|
const toolCallId = String(result?.toolCallId ?? "");
|
|
2392
2720
|
const toolName = toolNamesById.get(toolCallId);
|
|
@@ -2407,7 +2735,7 @@ async function dshRequestToPiContext(options, attachments) {
|
|
|
2407
2735
|
});
|
|
2408
2736
|
}
|
|
2409
2737
|
return {
|
|
2410
|
-
...
|
|
2738
|
+
...systemPrompt !== void 0 && systemPrompt.length > 0 ? { systemPrompt } : {},
|
|
2411
2739
|
messages: piMessages,
|
|
2412
2740
|
...Array.isArray(options.tools) ? { tools: options.tools } : {}
|
|
2413
2741
|
};
|
|
@@ -4040,9 +4368,10 @@ function isSubagentOrigin(subject) {
|
|
|
4040
4368
|
function currentPiModel(state, agent) {
|
|
4041
4369
|
const override = state.modelOverrides.get(agent);
|
|
4042
4370
|
const options = agent.options;
|
|
4043
|
-
const
|
|
4371
|
+
const durable = lastRequestRouteOf(agentSession(agent));
|
|
4372
|
+
const selectedProvider = String(override?.provider ?? durable?.provider ?? options?.provider ?? "");
|
|
4044
4373
|
const provider = state.companionRoutes.get(selectedProvider) ?? selectedProvider;
|
|
4045
|
-
const id = String(override?.model ?? options?.model ?? "");
|
|
4374
|
+
const id = String(override?.model ?? durable?.model ?? options?.model ?? "");
|
|
4046
4375
|
if (id.length === 0) return override;
|
|
4047
4376
|
return (provider.length > 0 ? state.modelCatalog?.find(provider, id) : void 0) ?? {
|
|
4048
4377
|
id,
|
|
@@ -4093,9 +4422,7 @@ function resolveCallerRoute(override, durable, snapshot) {
|
|
|
4093
4422
|
}
|
|
4094
4423
|
/** A session's durable events, whether the store exposes them as value or method. */
|
|
4095
4424
|
function eventsOf(session) {
|
|
4096
|
-
|
|
4097
|
-
if (typeof events === "function") return events.call(session) ?? [];
|
|
4098
|
-
return Array.isArray(events) ? events : [];
|
|
4425
|
+
return readSessionEvents(session);
|
|
4099
4426
|
}
|
|
4100
4427
|
/** The session id a presentation call belongs to, or '' outside a session. */
|
|
4101
4428
|
function sessionIdOf(state, agent) {
|
|
@@ -4295,6 +4622,17 @@ function contextFor(ctx, state, agent, signal, command = false, sessionOverride,
|
|
|
4295
4622
|
if (headlessUi) return void 0;
|
|
4296
4623
|
const surfaces = state.tuiSurfaces;
|
|
4297
4624
|
if (surfaces !== void 0) return surfaces.custom(factory, state.theme, getKeybindings(), options);
|
|
4625
|
+
if (state.shared.browserSurfacesRouted === true) return void 0;
|
|
4626
|
+
if (humanAnswererAvailable(userQuestions, agent, state.shared)) {
|
|
4627
|
+
const gap = {
|
|
4628
|
+
capability: "ui.custom",
|
|
4629
|
+
reason: "native dialogs are available, but this surface has no terminal-component renderer.",
|
|
4630
|
+
guidance: "Use the package's non-terminal fallback or a DSH terminal surface.",
|
|
4631
|
+
packageName: state.packageName
|
|
4632
|
+
};
|
|
4633
|
+
capabilityLedgerOf(ctx, state).reportDegraded(gap);
|
|
4634
|
+
throw new PiCapabilityError(gap);
|
|
4635
|
+
}
|
|
4298
4636
|
},
|
|
4299
4637
|
pasteToEditor(text) {
|
|
4300
4638
|
const surfaces = state.shared.browserSurfaces;
|
|
@@ -4516,7 +4854,10 @@ function contextFor(ctx, state, agent, signal, command = false, sessionOverride,
|
|
|
4516
4854
|
reason: "pi2dsh: aborted by migrated Pi extension"
|
|
4517
4855
|
});
|
|
4518
4856
|
},
|
|
4519
|
-
hasPendingMessages: () =>
|
|
4857
|
+
hasPendingMessages: () => {
|
|
4858
|
+
const inbox = agent?.inbox;
|
|
4859
|
+
return inbox?.nextStep !== void 0 || inbox?.nextTurn !== void 0 ? (inbox.nextStep?.length ?? 0) + (inbox.nextTurn?.length ?? 0) > 0 : inbox?.hasPending === true;
|
|
4860
|
+
},
|
|
4520
4861
|
shutdown: () => {
|
|
4521
4862
|
capabilityLedgerOf(ctx, state).reportHostDecision({
|
|
4522
4863
|
capability: "ctx.shutdown",
|
|
@@ -4617,7 +4958,7 @@ function contextFor(ctx, state, agent, signal, command = false, sessionOverride,
|
|
|
4617
4958
|
const seq = durableSeqOf(String(entryId));
|
|
4618
4959
|
if (seq === void 0) throw new Error(`pi2dsh: ctx.fork(${JSON.stringify(String(entryId))}) — only durable-log entries (projected ids "dsh-<seq>") can anchor a fork; package-appended sidecar entries are not part of the DSH durable log`);
|
|
4619
4960
|
const position = options?.position ?? "before";
|
|
4620
|
-
const events = source
|
|
4961
|
+
const events = readSessionEvents(source);
|
|
4621
4962
|
const requested = Math.min(position === "at" ? seq : seq - 1, events.length - 1);
|
|
4622
4963
|
const boundary = requested < 0 ? -1 : shrinkToTurnBoundary(events, requested);
|
|
4623
4964
|
const child = boundary < 0 ? sessions.create(void 0, { meta: {
|
|
@@ -4648,7 +4989,7 @@ function contextFor(ctx, state, agent, signal, command = false, sessionOverride,
|
|
|
4648
4989
|
}
|
|
4649
4990
|
const seq = durableSeqOf(String(targetId));
|
|
4650
4991
|
if (seq === void 0) throw new Error(`pi2dsh: ctx.navigateTree(${JSON.stringify(String(targetId))}) — only durable-log entries (projected ids "dsh-<seq>") can be navigation targets; package-appended sidecar entries are not part of the DSH durable log`);
|
|
4651
|
-
const events = source
|
|
4992
|
+
const events = readSessionEvents(source);
|
|
4652
4993
|
const capped = Math.min(seq, events.length - 1);
|
|
4653
4994
|
const boundary = capped < 0 ? -1 : shrinkToTurnBoundary(events, capped);
|
|
4654
4995
|
const child = boundary < 0 ? sessions.create(void 0, { meta: {
|
|
@@ -4681,7 +5022,7 @@ function contextFor(ctx, state, agent, signal, command = false, sessionOverride,
|
|
|
4681
5022
|
}
|
|
4682
5023
|
const raw = String(sessionPath);
|
|
4683
5024
|
const base = raw.replace(/\\/g, "/").split("/").pop() ?? raw;
|
|
4684
|
-
const candidate = base.endsWith(".jsonl") ? base.slice(0, -6) : base;
|
|
5025
|
+
const candidate = state.bridge.sessionIdOfArchiveFile(raw) ?? (base.endsWith(".jsonl") ? base.slice(0, -6) : base);
|
|
4685
5026
|
const target = sessions.get(candidate) ?? sessions.list().find((entry) => String(entry.id) === candidate);
|
|
4686
5027
|
if (target === void 0) throw new Error(`pi2dsh: ctx.switchSession(${JSON.stringify(raw)}) — no live DSH session ${JSON.stringify(candidate)}; switching to persisted sessions is host-owned (resume them from the DSH surface first)`);
|
|
4687
5028
|
const withSession = options?.withSession;
|
|
@@ -4718,6 +5059,20 @@ async function dispatch(state, eventName, event, eventContext) {
|
|
|
4718
5059
|
for (const handler of state.handlers.get(eventName) ?? []) results.push(await runInPiRuntime(state, agent, () => handler(event, eventContext)));
|
|
4719
5060
|
return results;
|
|
4720
5061
|
}
|
|
5062
|
+
function shutdownPiSession(ctx, state, agent, reason, signal) {
|
|
5063
|
+
const existing = state.shutdownTasks.get(agent);
|
|
5064
|
+
if (existing !== void 0) return existing;
|
|
5065
|
+
if (state.piShutdownAgents.has(agent)) return Promise.resolve();
|
|
5066
|
+
state.piShutdownAgents.add(agent);
|
|
5067
|
+
const task = dispatch(state, "session_shutdown", {
|
|
5068
|
+
type: "session_shutdown",
|
|
5069
|
+
reason
|
|
5070
|
+
}, contextFor(ctx, state, agent, signal)).then(() => {});
|
|
5071
|
+
state.shutdownTasks.set(agent, task);
|
|
5072
|
+
state.pendingShutdowns.add(task);
|
|
5073
|
+
task.then(() => state.pendingShutdowns.delete(task), () => state.pendingShutdowns.delete(task));
|
|
5074
|
+
return task;
|
|
5075
|
+
}
|
|
4721
5076
|
/**
|
|
4722
5077
|
* Bind every compatibility singleton to this exact Agent runtime for the
|
|
4723
5078
|
* duration of extension-owned work. AsyncLocalStorage follows promises and
|
|
@@ -4809,7 +5164,8 @@ async function dshToPiContent(ctx, content) {
|
|
|
4809
5164
|
else if (block.type === "image") {
|
|
4810
5165
|
const image = await piImageBlock(ctx, block.attachment);
|
|
4811
5166
|
if (image !== void 0) out.push(image);
|
|
4812
|
-
} else out.push(
|
|
5167
|
+
} else if (block.type === "file") out.push(fileContentForPi(block.attachment, ctx));
|
|
5168
|
+
else out.push({ type: block.type });
|
|
4813
5169
|
return out;
|
|
4814
5170
|
}
|
|
4815
5171
|
/**
|
|
@@ -4839,7 +5195,11 @@ async function messageFromSessionEvent(ctx, event) {
|
|
|
4839
5195
|
if (typeof data !== "object" || data === null) return void 0;
|
|
4840
5196
|
const record = data;
|
|
4841
5197
|
if (type === "user/message") return {
|
|
4842
|
-
role: "user",
|
|
5198
|
+
role: isPiUserMessage(record) ? "user" : "custom",
|
|
5199
|
+
...!isPiUserMessage(record) ? {
|
|
5200
|
+
customType: String(record.source?.piCustomType ?? record.source?.kind ?? "context"),
|
|
5201
|
+
display: false
|
|
5202
|
+
} : {},
|
|
4843
5203
|
content: await dshToPiContent(ctx, record.content ?? [])
|
|
4844
5204
|
};
|
|
4845
5205
|
if (type === "assistant/message") {
|
|
@@ -4937,6 +5297,20 @@ function untrackRuntimeSession(state, agent) {
|
|
|
4937
5297
|
states?.delete(state);
|
|
4938
5298
|
if (states?.size === 0) state.shared.runtimeStatesBySession.delete(id);
|
|
4939
5299
|
}
|
|
5300
|
+
const sessionExportTasks = /* @__PURE__ */ new WeakMap();
|
|
5301
|
+
async function prepareSessionFileExports(ctx, state) {
|
|
5302
|
+
const persistence = optionalService(ctx, "sessionPersistence");
|
|
5303
|
+
if (typeof persistence?.list !== "function" || typeof persistence.open !== "function" && typeof persistence.inspect !== "function") return;
|
|
5304
|
+
let task = sessionExportTasks.get(ctx.root);
|
|
5305
|
+
if (task === void 0) {
|
|
5306
|
+
task = state.bridge.exportStoredSessions(persistence);
|
|
5307
|
+
sessionExportTasks.set(ctx.root, task);
|
|
5308
|
+
task.catch(() => {
|
|
5309
|
+
sessionExportTasks.delete(ctx.root);
|
|
5310
|
+
});
|
|
5311
|
+
}
|
|
5312
|
+
await task;
|
|
5313
|
+
}
|
|
4940
5314
|
/** Dispatch or join the one in-flight Pi session_start for this session. */
|
|
4941
5315
|
async function startPiSession(ctx, state, agent, reason, signal) {
|
|
4942
5316
|
if (!acceptsAgent(state, agent)) return;
|
|
@@ -4949,24 +5323,24 @@ async function startPiSession(ctx, state, agent, reason, signal) {
|
|
|
4949
5323
|
const previous = state.hostAgent;
|
|
4950
5324
|
const replacing = typeof previous === "object" && previous !== null && previous !== agent;
|
|
4951
5325
|
const transitionReason = replacing ? replacementReason(reason) : reason;
|
|
4952
|
-
if (replacing
|
|
4953
|
-
state.piShutdownAgents.add(previous);
|
|
5326
|
+
if (replacing) {
|
|
4954
5327
|
releasePiSessionClaim(state, previous);
|
|
4955
|
-
await
|
|
4956
|
-
type: "session_shutdown",
|
|
4957
|
-
reason: replacementReason(reason)
|
|
4958
|
-
}, contextFor(ctx, state, previous, signal)));
|
|
5328
|
+
await shutdownPiSession(ctx, state, previous, replacementReason(reason), signal);
|
|
4959
5329
|
}
|
|
4960
5330
|
if (typeof agent === "object" && agent !== null) {
|
|
4961
5331
|
state.hostAgent = agent;
|
|
4962
5332
|
state.piShutdownAgents.delete(agent);
|
|
5333
|
+
state.shutdownTasks.delete(agent);
|
|
4963
5334
|
}
|
|
4964
5335
|
if (!claimPiSessionStart(state, agent)) return;
|
|
4965
5336
|
try {
|
|
5337
|
+
await prepareSessionFileExports(ctx, state);
|
|
4966
5338
|
await dispatch(state, "session_start", {
|
|
4967
5339
|
type: "session_start",
|
|
4968
5340
|
reason: transitionReason
|
|
4969
5341
|
}, contextFor(ctx, state, agent, signal));
|
|
5342
|
+
await discoverPiResources(ctx, state, agent, transitionReason, signal);
|
|
5343
|
+
await refreshNativeSkillCommands(ctx, state, agent, signal);
|
|
4970
5344
|
} catch (error) {
|
|
4971
5345
|
releasePiSessionClaim(state, agent);
|
|
4972
5346
|
throw error;
|
|
@@ -5011,15 +5385,65 @@ function subscribeLifecycle(ctx, state) {
|
|
|
5011
5385
|
state.disposedAgents.add(agent);
|
|
5012
5386
|
releasePiSessionClaim(state, agent);
|
|
5013
5387
|
if (ownsHostSession && !state.piShutdownAgents.has(agent)) {
|
|
5014
|
-
state.piShutdownAgents.add(agent);
|
|
5015
5388
|
state.hostAgent = void 0;
|
|
5016
|
-
|
|
5017
|
-
type: "session_shutdown",
|
|
5018
|
-
reason: "quit"
|
|
5019
|
-
}, contextFor(ctx, state, agent, void 0)).catch((error) => warn("session_shutdown", error));
|
|
5389
|
+
shutdownPiSession(ctx, state, agent, "quit").catch((error) => warn("session_shutdown", error));
|
|
5020
5390
|
}
|
|
5021
5391
|
}
|
|
5022
5392
|
});
|
|
5393
|
+
const liveStreams = /* @__PURE__ */ new WeakMap();
|
|
5394
|
+
const projectAssistantChunk = (session, data, chunk, eventContext) => {
|
|
5395
|
+
if ((state.handlers.get("message_update")?.length ?? 0) === 0) return;
|
|
5396
|
+
if (chunk.type !== void 0 && chunk.type !== "text-delta") return;
|
|
5397
|
+
const key = `${String(session.id ?? "")}:${String(data.turn ?? 0)}:${String(data.step ?? 0)}`;
|
|
5398
|
+
const delta = typeof chunk.text === "string" ? chunk.text : typeof chunk.delta === "string" ? chunk.delta : "";
|
|
5399
|
+
const accumulated = (state.streamingTexts.get(key) ?? "") + delta;
|
|
5400
|
+
state.streamingTexts.set(key, accumulated);
|
|
5401
|
+
const message = {
|
|
5402
|
+
role: "assistant",
|
|
5403
|
+
content: [{
|
|
5404
|
+
type: "text",
|
|
5405
|
+
text: accumulated
|
|
5406
|
+
}]
|
|
5407
|
+
};
|
|
5408
|
+
dispatch(state, "message_update", {
|
|
5409
|
+
type: "message_update",
|
|
5410
|
+
message,
|
|
5411
|
+
assistantMessageEvent: {
|
|
5412
|
+
type: "text_delta",
|
|
5413
|
+
contentIndex: 0,
|
|
5414
|
+
delta,
|
|
5415
|
+
partial: message
|
|
5416
|
+
}
|
|
5417
|
+
}, eventContext).catch((error) => warn("message_update", error));
|
|
5418
|
+
};
|
|
5419
|
+
cordis.on("agent/assistant-stream", (payload) => {
|
|
5420
|
+
const agent = payload.agent;
|
|
5421
|
+
if (agent === void 0 || state.hostAnchor || !acceptsAgent(state, agent)) return;
|
|
5422
|
+
const session = agentSession(agent);
|
|
5423
|
+
if (session === void 0) return;
|
|
5424
|
+
const frame = payload.frame;
|
|
5425
|
+
if (frame.type === "start") {
|
|
5426
|
+
liveStreams.set(session, {
|
|
5427
|
+
attemptId: frame.attemptId,
|
|
5428
|
+
turn: frame.turn,
|
|
5429
|
+
step: frame.step,
|
|
5430
|
+
index: -1,
|
|
5431
|
+
ended: false
|
|
5432
|
+
});
|
|
5433
|
+
state.streamingTexts.delete(`${String(session.id ?? "")}:${String(frame.turn ?? 0)}:${String(frame.step ?? 0)}`);
|
|
5434
|
+
return;
|
|
5435
|
+
}
|
|
5436
|
+
const active = liveStreams.get(session);
|
|
5437
|
+
if (active === void 0 || active.ended || active.attemptId !== frame.attemptId) return;
|
|
5438
|
+
if (frame.type === "chunk" && Number(frame.index) > active.index) {
|
|
5439
|
+
active.index = Number(frame.index);
|
|
5440
|
+
projectAssistantChunk(session, active, frame.chunk, contextFor(ctx, state, agent, void 0));
|
|
5441
|
+
}
|
|
5442
|
+
if (frame.type === "end") {
|
|
5443
|
+
active.ended = true;
|
|
5444
|
+
state.streamingTexts.delete(`${String(session.id ?? "")}:${String(active.turn ?? 0)}:${String(active.step ?? 0)}`);
|
|
5445
|
+
}
|
|
5446
|
+
});
|
|
5023
5447
|
const projectSessionEvent = (session, event) => {
|
|
5024
5448
|
const agent = [...state.activeAgents].find((candidate) => candidate.session === session);
|
|
5025
5449
|
const eventContext = contextFor(ctx, state, agent, void 0);
|
|
@@ -5041,7 +5465,7 @@ function subscribeLifecycle(ctx, state) {
|
|
|
5041
5465
|
const index = state.piTurnIndex.get(key) ?? 0;
|
|
5042
5466
|
state.piTurnIndex.set(key, index + 1);
|
|
5043
5467
|
state.projection = state.projection.then(async () => {
|
|
5044
|
-
const stepEvents = (session
|
|
5468
|
+
const stepEvents = readSessionEvents(session).filter((entry) => {
|
|
5045
5469
|
const entryData = entry.data;
|
|
5046
5470
|
return Number(entryData?.turn ?? -1) === turn && Number(entryData?.step ?? -1) === step;
|
|
5047
5471
|
});
|
|
@@ -5074,27 +5498,12 @@ function subscribeLifecycle(ctx, state) {
|
|
|
5074
5498
|
type: "tool_execution_start",
|
|
5075
5499
|
toolCallId: data.callId,
|
|
5076
5500
|
toolName: data.name,
|
|
5077
|
-
args
|
|
5501
|
+
args: piViewOfToolArguments(state, String(data.name), args)
|
|
5078
5502
|
}, eventContext).catch((error) => warn("tool_execution_start", error));
|
|
5079
5503
|
}
|
|
5080
|
-
if (type === "assistant/chunk" &&
|
|
5504
|
+
if (type === "assistant/chunk" && !liveStreams.has(session)) {
|
|
5081
5505
|
const data = event.data;
|
|
5082
|
-
|
|
5083
|
-
const key = `${String(session.id ?? "")}:${String(data.turn ?? 0)}:${String(data.step ?? 0)}`;
|
|
5084
|
-
const delta = typeof chunk.text === "string" ? chunk.text : typeof chunk.delta === "string" ? chunk.delta : "";
|
|
5085
|
-
const accumulated = (state.streamingTexts.get(key) ?? "") + delta;
|
|
5086
|
-
state.streamingTexts.set(key, accumulated);
|
|
5087
|
-
dispatch(state, "message_update", {
|
|
5088
|
-
type: "message_update",
|
|
5089
|
-
message: {
|
|
5090
|
-
role: "assistant",
|
|
5091
|
-
content: [{
|
|
5092
|
-
type: "text",
|
|
5093
|
-
text: accumulated
|
|
5094
|
-
}]
|
|
5095
|
-
},
|
|
5096
|
-
assistantMessageEvent: chunk
|
|
5097
|
-
}, eventContext).catch((error) => warn("message_update", error));
|
|
5506
|
+
projectAssistantChunk(session, data, data.chunk ?? {}, eventContext);
|
|
5098
5507
|
}
|
|
5099
5508
|
if (type === "assistant/message") {
|
|
5100
5509
|
const data = event.data;
|
|
@@ -5107,18 +5516,21 @@ function subscribeLifecycle(ctx, state) {
|
|
|
5107
5516
|
name: typeof data.title === "string" ? data.title : void 0
|
|
5108
5517
|
}, eventContext).catch((error) => warn("session_info_changed", error));
|
|
5109
5518
|
}
|
|
5110
|
-
if (type === "compaction/start")
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5519
|
+
if (type === "compaction/start") {
|
|
5520
|
+
const before = dispatch(state, "session_before_compact", {
|
|
5521
|
+
type: "session_before_compact",
|
|
5522
|
+
preparation: { ...event.data },
|
|
5523
|
+
branchEntries: [],
|
|
5524
|
+
reason: compactionReason(event.data),
|
|
5525
|
+
willRetry: false
|
|
5526
|
+
}, eventContext).catch((error) => warn("session_before_compact", error));
|
|
5527
|
+
state.projection = Promise.all([state.projection, before]).then(() => {});
|
|
5528
|
+
}
|
|
5117
5529
|
if (type === "compaction/summary") {
|
|
5118
5530
|
const data = event.data;
|
|
5119
5531
|
const shadowed = data.shadowedRange;
|
|
5120
5532
|
const usage = data.usage;
|
|
5121
|
-
dispatch(state, "session_compact", {
|
|
5533
|
+
state.projection = state.projection.then(() => dispatch(state, "session_compact", {
|
|
5122
5534
|
type: "session_compact",
|
|
5123
5535
|
compactionEntry: {
|
|
5124
5536
|
type: "compaction",
|
|
@@ -5132,7 +5544,7 @@ function subscribeLifecycle(ctx, state) {
|
|
|
5132
5544
|
fromExtension: false,
|
|
5133
5545
|
reason: compactionReason(data),
|
|
5134
5546
|
willRetry: false
|
|
5135
|
-
}, eventContext).catch((error) => warn("session_compact", error));
|
|
5547
|
+
}, eventContext)).then(() => {}).catch((error) => warn("session_compact", error));
|
|
5136
5548
|
}
|
|
5137
5549
|
if (type === "request/header") {
|
|
5138
5550
|
const header = event.data.header ?? {};
|
|
@@ -5187,19 +5599,17 @@ function subscribeLifecycle(ctx, state) {
|
|
|
5187
5599
|
...config,
|
|
5188
5600
|
...override?.provider === void 0 ? {} : { provider: override.provider },
|
|
5189
5601
|
...override?.model === void 0 ? {} : { model: override.model },
|
|
5190
|
-
...thinking === void 0 || thinking === "off" ? {} : { reasoningEffort: thinking }
|
|
5602
|
+
...thinking === void 0 || thinking === "off" && (currentPiModel(state, agent)?.thinkingLevelMap)?.off === null ? {} : { reasoningEffort: thinking }
|
|
5191
5603
|
};
|
|
5192
5604
|
});
|
|
5193
5605
|
cordis.effect(() => async () => {
|
|
5606
|
+
await state.projection;
|
|
5194
5607
|
const agent = state.hostAgent;
|
|
5195
|
-
if (typeof agent === "object" && agent !== null
|
|
5196
|
-
state.piShutdownAgents.add(agent);
|
|
5608
|
+
if (typeof agent === "object" && agent !== null) {
|
|
5197
5609
|
state.disposedAgents.add(agent);
|
|
5198
|
-
await
|
|
5199
|
-
type: "session_shutdown",
|
|
5200
|
-
reason: "quit"
|
|
5201
|
-
}, contextFor(ctx, state, agent, void 0));
|
|
5610
|
+
await shutdownPiSession(ctx, state, agent, "quit");
|
|
5202
5611
|
}
|
|
5612
|
+
await Promise.all(state.pendingShutdowns);
|
|
5203
5613
|
state.hostAgent = void 0;
|
|
5204
5614
|
for (const active of state.activeAgents) untrackRuntimeSession(state, active);
|
|
5205
5615
|
state.activeAgents.clear();
|
|
@@ -5230,7 +5640,7 @@ async function flushPendingSessionStarts(ctx, state) {
|
|
|
5230
5640
|
if (state.ownerAgent !== void 0 && state.projectSessionEvent !== void 0) {
|
|
5231
5641
|
const session = agentSession(state.ownerAgent);
|
|
5232
5642
|
if (session !== void 0) {
|
|
5233
|
-
const events = session
|
|
5643
|
+
const events = readSessionEvents(session);
|
|
5234
5644
|
let from = -1;
|
|
5235
5645
|
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
5236
5646
|
if (events[index].type === "turn/end") break;
|
|
@@ -5276,13 +5686,144 @@ async function restartReloadedPiSessions(ctx, state) {
|
|
|
5276
5686
|
type: "session_start",
|
|
5277
5687
|
reason: pending.get(agent) ?? "resume"
|
|
5278
5688
|
}, contextFor(ctx, state, agent, void 0)));
|
|
5689
|
+
await discoverPiResources(ctx, state, agent, pending.get(agent) ?? "resume", void 0);
|
|
5690
|
+
await refreshNativeSkillCommands(ctx, state, agent, void 0);
|
|
5279
5691
|
}
|
|
5280
5692
|
}
|
|
5693
|
+
/**
|
|
5694
|
+
* Pi's `resources_discover`, fired right after session_start (Pi:
|
|
5695
|
+
* AgentSession.extendResourcesFromExtensions): handlers return extra
|
|
5696
|
+
* `skillPaths` (and prompt/theme paths) for the session's cwd, and Pi extends
|
|
5697
|
+
* its resource loader with them. On DSH the skills registry is the host's
|
|
5698
|
+
* `skills` service, so every discovered skill ROOT is handed to the official
|
|
5699
|
+
* filesystem provider (dsh-skill-filesystem) exactly like a package's static
|
|
5700
|
+
* skill directories — once per root, per package (the registry is host-level
|
|
5701
|
+
* and provider names must stay unique). Prompt templates and TUI themes have
|
|
5702
|
+
* no DSH seat and are reported, not silently dropped. A single SKILL.md file
|
|
5703
|
+
* path (Pi accepts those too) has no root to mount and is reported likewise.
|
|
5704
|
+
*/
|
|
5705
|
+
async function refreshNativeSkillCommands(ctx, state, agent, signal) {
|
|
5706
|
+
const skills = optionalService(ctx, "skills");
|
|
5707
|
+
if (skills === void 0) {
|
|
5708
|
+
state.nativeSkillCommands = [];
|
|
5709
|
+
return;
|
|
5710
|
+
}
|
|
5711
|
+
const options = {
|
|
5712
|
+
cwd: cwdOf(agent),
|
|
5713
|
+
scope: scopeOf(ctx),
|
|
5714
|
+
signal: signal ?? new AbortController().signal
|
|
5715
|
+
};
|
|
5716
|
+
try {
|
|
5717
|
+
const entries = [];
|
|
5718
|
+
for (const summary of await skills.list(options)) {
|
|
5719
|
+
if (summary.invocation?.userInvocable === false) continue;
|
|
5720
|
+
const skill = await skills.get(String(summary.name), options);
|
|
5721
|
+
if (typeof skill?.path !== "string") continue;
|
|
5722
|
+
entries.push({
|
|
5723
|
+
name: `skill:${String(summary.name)}`,
|
|
5724
|
+
description: String(summary.description ?? ""),
|
|
5725
|
+
source: "skill",
|
|
5726
|
+
sourceInfo: {
|
|
5727
|
+
path: skill.path,
|
|
5728
|
+
source: skill.path,
|
|
5729
|
+
scope: String(summary.source ?? "").startsWith("project") ? "project" : "user",
|
|
5730
|
+
origin: "top-level",
|
|
5731
|
+
baseDir: dirname(skill.path)
|
|
5732
|
+
}
|
|
5733
|
+
});
|
|
5734
|
+
}
|
|
5735
|
+
state.nativeSkillCommands = entries;
|
|
5736
|
+
} catch (error) {
|
|
5737
|
+
state.nativeSkillCommands = [];
|
|
5738
|
+
logger(ctx).warn(`[pi2dsh] skill command metadata could not be refreshed: ${error instanceof Error ? error.message : String(error)}`);
|
|
5739
|
+
}
|
|
5740
|
+
}
|
|
5741
|
+
async function discoverPiResources(ctx, state, agent, reason, signal) {
|
|
5742
|
+
if ((state.handlers.get("resources_discover")?.length ?? 0) === 0) return;
|
|
5743
|
+
const cwd = cwdOf(agent);
|
|
5744
|
+
const results = await dispatch(state, "resources_discover", {
|
|
5745
|
+
type: "resources_discover",
|
|
5746
|
+
cwd,
|
|
5747
|
+
reason: reason === "reload" ? "reload" : "startup"
|
|
5748
|
+
}, contextFor(ctx, state, agent, signal));
|
|
5749
|
+
const roots = [];
|
|
5750
|
+
const skipped = [];
|
|
5751
|
+
let promptPaths = 0;
|
|
5752
|
+
let themePaths = 0;
|
|
5753
|
+
for (const result of results) {
|
|
5754
|
+
if (typeof result !== "object" || result === null) continue;
|
|
5755
|
+
const record = result;
|
|
5756
|
+
for (const entry of Array.isArray(record.skillPaths) ? record.skillPaths : []) {
|
|
5757
|
+
if (typeof entry !== "string" || entry.length === 0) continue;
|
|
5758
|
+
const absolute = resolve(cwd, entry);
|
|
5759
|
+
let isDirectory = false;
|
|
5760
|
+
try {
|
|
5761
|
+
isDirectory = (await stat(absolute)).isDirectory();
|
|
5762
|
+
} catch {
|
|
5763
|
+
continue;
|
|
5764
|
+
}
|
|
5765
|
+
if (!isDirectory) {
|
|
5766
|
+
skipped.push(absolute);
|
|
5767
|
+
continue;
|
|
5768
|
+
}
|
|
5769
|
+
if (!state.discoveredSkillRoots.has(absolute) && !roots.includes(absolute)) roots.push(absolute);
|
|
5770
|
+
}
|
|
5771
|
+
if (Array.isArray(record.promptPaths)) promptPaths += record.promptPaths.length;
|
|
5772
|
+
if (Array.isArray(record.themePaths)) themePaths += record.themePaths.length;
|
|
5773
|
+
}
|
|
5774
|
+
if (promptPaths > 0 || themePaths > 0 || skipped.length > 0) logger(ctx).info(`[pi2dsh] ${state.packageName}: resources_discover returned ${promptPaths} prompt path(s), ${themePaths} theme path(s) and ${skipped.length} single skill file(s) that DSH has no seat for; they were not mounted`);
|
|
5775
|
+
if (roots.length === 0) return;
|
|
5776
|
+
if (ctx.get("skills") === void 0) {
|
|
5777
|
+
logger(ctx).warn(`[pi2dsh] ${state.packageName}: resources_discover found ${roots.length} skill root(s) but this DSH composition has no ctx.skills`);
|
|
5778
|
+
return;
|
|
5779
|
+
}
|
|
5780
|
+
for (const root of roots) state.discoveredSkillRoots.add(root);
|
|
5781
|
+
state.discoveredSkillProviders += 1;
|
|
5782
|
+
const { apply: applyFilesystemSkills } = await import("@deepseek-ai/dsh-skill-filesystem");
|
|
5783
|
+
const config = {
|
|
5784
|
+
providerName: `pi2dsh-${state.packageName.replace(/[^a-zA-Z0-9_-]+/gu, "-").replace(/^-+|-+$/gu, "")}-discovered-${state.discoveredSkillProviders}`,
|
|
5785
|
+
includeDefaultRoots: false,
|
|
5786
|
+
customSkillDirs: roots,
|
|
5787
|
+
watch: false
|
|
5788
|
+
};
|
|
5789
|
+
await ctx.plugin(Object.assign((skillCtx) => applyFilesystemSkills(skillCtx, config), { inject: ["skills"] }));
|
|
5790
|
+
logger(ctx).info(`[pi2dsh] ${state.packageName}: resources_discover mounted ${roots.length} skill root(s) as ${config.providerName}`);
|
|
5791
|
+
}
|
|
5792
|
+
/**
|
|
5793
|
+
* The Pi view of a host tool's arguments. DSH's built-in file tools carry
|
|
5794
|
+
* Claude-style argument names (`file_path`, `old_string`/`new_string`,
|
|
5795
|
+
* grep's `include`); Pi's same-named built-ins carry `path`,
|
|
5796
|
+
* `oldText`/`newText`, `glob`. Extensions read `event.input` by Pi's names —
|
|
5797
|
+
* pi-code attaches a path-scoped rule to the read of a matching `input.path`,
|
|
5798
|
+
* and its hooks translate Pi shapes to Claude's `tool_input` — so a host call
|
|
5799
|
+
* is projected into Pi's shape for the tool_call / tool_result /
|
|
5800
|
+
* tool_execution_start events. Migrated Pi tools already speak Pi and pass
|
|
5801
|
+
* through untouched; unknown keys are kept, so nothing is lost.
|
|
5802
|
+
*/
|
|
5803
|
+
const HOST_TOOL_ARGUMENT_PROJECTION = {
|
|
5804
|
+
read: { file_path: "path" },
|
|
5805
|
+
write: { file_path: "path" },
|
|
5806
|
+
edit: {
|
|
5807
|
+
file_path: "path",
|
|
5808
|
+
old_string: "oldText",
|
|
5809
|
+
new_string: "newText"
|
|
5810
|
+
},
|
|
5811
|
+
grep: { include: "glob" }
|
|
5812
|
+
};
|
|
5813
|
+
function piViewOfToolArguments(state, name, args) {
|
|
5814
|
+
if (state.tools.has(name)) return cloneJson(args);
|
|
5815
|
+
const mapping = HOST_TOOL_ARGUMENT_PROJECTION[name];
|
|
5816
|
+
if (mapping === void 0 || typeof args !== "object" || args === null || Array.isArray(args)) return cloneJson(args);
|
|
5817
|
+
const projected = {};
|
|
5818
|
+
for (const [key, value] of Object.entries(args)) projected[mapping[key] ?? key] = value;
|
|
5819
|
+
return cloneJson(projected);
|
|
5820
|
+
}
|
|
5281
5821
|
function subscribeInterceptors(ctx, state) {
|
|
5282
5822
|
const cordis = ctx;
|
|
5283
5823
|
cordis.on("tools/pre-execute", async (exec, next) => {
|
|
5284
5824
|
if (!acceptsAgent(state, exec.agent)) return next();
|
|
5285
|
-
const
|
|
5825
|
+
const original = piViewOfToolArguments(state, exec.name, exec.arguments);
|
|
5826
|
+
const input = cloneJson(original);
|
|
5286
5827
|
const event = {
|
|
5287
5828
|
type: "tool_call",
|
|
5288
5829
|
toolName: exec.name,
|
|
@@ -5290,7 +5831,7 @@ function subscribeInterceptors(ctx, state) {
|
|
|
5290
5831
|
input
|
|
5291
5832
|
};
|
|
5292
5833
|
const results = await dispatch(state, "tool_call", event, contextFor(ctx, state, exec.agent, exec.signal));
|
|
5293
|
-
if (!jsonEqual(event.input,
|
|
5834
|
+
if (!jsonEqual(event.input, original)) {
|
|
5294
5835
|
if (state.tools.has(exec.name)) state.argMutations.set(exec, cloneJson(event.input));
|
|
5295
5836
|
else return {
|
|
5296
5837
|
kind: "deny",
|
|
@@ -5336,7 +5877,7 @@ function subscribeInterceptors(ctx, state) {
|
|
|
5336
5877
|
type: "tool_result",
|
|
5337
5878
|
toolName: exec.name,
|
|
5338
5879
|
toolCallId: exec.callId,
|
|
5339
|
-
input:
|
|
5880
|
+
input: piViewOfToolArguments(state, exec.name, exec.arguments),
|
|
5340
5881
|
content: await dshToPiContent(ctx, result.content),
|
|
5341
5882
|
details: result.meta ?? null,
|
|
5342
5883
|
isError: result.isError,
|
|
@@ -5471,18 +6012,20 @@ async function runBeforeAgentStart(ctx, state, agent, signal, assembled) {
|
|
|
5471
6012
|
prompt,
|
|
5472
6013
|
...images.length > 0 ? { images } : {},
|
|
5473
6014
|
systemPrompt: assembled,
|
|
5474
|
-
systemPromptOptions:
|
|
6015
|
+
systemPromptOptions: await piSystemPromptOptions(ctx, state, agent, claimed.map((entry) => entry.message))
|
|
5475
6016
|
};
|
|
5476
|
-
const
|
|
6017
|
+
const eventContext = contextFor(ctx, state, agent, signal);
|
|
6018
|
+
const results = [];
|
|
6019
|
+
for (const handler of state.handlers.get("before_agent_start") ?? []) {
|
|
6020
|
+
const result = await runInPiRuntime(state, agent, () => handler(event, eventContext));
|
|
6021
|
+
results.push(result);
|
|
6022
|
+
if (typeof result === "object" && result !== null && typeof result.systemPrompt === "string") event.systemPrompt = result.systemPrompt;
|
|
6023
|
+
}
|
|
6024
|
+
if (event.systemPrompt !== assembled) state.turnSystemPromptOverrides.set(agent, event.systemPrompt);
|
|
5477
6025
|
const injected = [];
|
|
5478
6026
|
for (const result of results) {
|
|
5479
6027
|
if (typeof result !== "object" || result === null) continue;
|
|
5480
|
-
const
|
|
5481
|
-
if (typeof record.systemPrompt === "string") {
|
|
5482
|
-
state.turnSystemPromptOverrides.set(agent, record.systemPrompt);
|
|
5483
|
-
event.systemPrompt = record.systemPrompt;
|
|
5484
|
-
}
|
|
5485
|
-
const message = record.message;
|
|
6028
|
+
const message = result.message;
|
|
5486
6029
|
if (message === void 0) continue;
|
|
5487
6030
|
const content = message.content;
|
|
5488
6031
|
const blocks = await piToDshContent(ctx, typeof content === "string" ? [{
|
|
@@ -5500,6 +6043,79 @@ async function runBeforeAgentStart(ctx, state, agent, signal, assembled) {
|
|
|
5500
6043
|
}
|
|
5501
6044
|
if (injected.length > 0) state.pendingInjections.set(agent, injected);
|
|
5502
6045
|
}
|
|
6046
|
+
/**
|
|
6047
|
+
* Pi's `BuildSystemPromptOptions` for the before_agent_start event: the
|
|
6048
|
+
* structured view of "what the host loaded", which extensions read instead of
|
|
6049
|
+
* re-discovering resources. Two fields carry weight on DSH:
|
|
6050
|
+
* - `cwd`: the session's working directory (pi-code's context-imports
|
|
6051
|
+
* otherwise falls back to process.cwd(), the CLI's directory).
|
|
6052
|
+
* - `contextFiles`: the AGENTS.md/CLAUDE.md chain the host loaded, as
|
|
6053
|
+
* `{path, content}`. DSH delivers those as an `agent-instructions` user
|
|
6054
|
+
* message rather than system-prompt text, so the set is recovered from the
|
|
6055
|
+
* official loader itself: the baseline message records the exact
|
|
6056
|
+
* discovery/budget identity DSH used, and `loadBaselineInstructionSet`
|
|
6057
|
+
* from `@deepseek-ai/dsh-agent-instructions` re-runs that same walk. No
|
|
6058
|
+
* parallel discovery logic lives here; without the package or a baseline
|
|
6059
|
+
* the list is empty, exactly Pi's value when nothing was loaded.
|
|
6060
|
+
* (pi-code's CLAUDE.md `@import` expansion reads this list — with `{}` it
|
|
6061
|
+
* had nothing to expand and the model "found" the imported file with `read`.)
|
|
6062
|
+
*/
|
|
6063
|
+
async function piSystemPromptOptions(ctx, state, agent, claimed) {
|
|
6064
|
+
const cwd = cwdOf(agent);
|
|
6065
|
+
const baselineOf = (message) => {
|
|
6066
|
+
const source = message?.source;
|
|
6067
|
+
return source?.kind === "agent-instructions" && source.baseline === true ? source : void 0;
|
|
6068
|
+
};
|
|
6069
|
+
let baseline;
|
|
6070
|
+
for (const message of claimed) {
|
|
6071
|
+
baseline = baselineOf(message);
|
|
6072
|
+
if (baseline !== void 0) break;
|
|
6073
|
+
}
|
|
6074
|
+
if (baseline === void 0) {
|
|
6075
|
+
const events = readSessionEvents(agentSession(agent));
|
|
6076
|
+
for (let index = events.length - 1; index >= 0 && baseline === void 0; index -= 1) {
|
|
6077
|
+
const event = events[index];
|
|
6078
|
+
const data = event.data;
|
|
6079
|
+
if (event.type === "user/message") baseline = baselineOf(data);
|
|
6080
|
+
else if (event.type === "agent/inbox/spliced" && Array.isArray(data?.inserted)) for (const inserted of data.inserted) {
|
|
6081
|
+
baseline = baselineOf(inserted);
|
|
6082
|
+
if (baseline !== void 0) break;
|
|
6083
|
+
}
|
|
6084
|
+
}
|
|
6085
|
+
}
|
|
6086
|
+
try {
|
|
6087
|
+
const identity = typeof baseline?.baselineIdentity === "string" ? JSON.parse(baseline.baselineIdentity) : {};
|
|
6088
|
+
const { discoverBaselineInstructionFiles } = await import("@deepseek-ai/dsh-agent-instructions");
|
|
6089
|
+
const list = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string") ? value : void 0;
|
|
6090
|
+
const discovered = await discoverBaselineInstructionFiles({
|
|
6091
|
+
cwd,
|
|
6092
|
+
...typeof identity.projectRoot === "string" && identity.projectRoot.length > 0 ? { projectRoot: identity.projectRoot } : {},
|
|
6093
|
+
...list(identity.projectRootMarkers) !== void 0 ? { projectRootMarkers: list(identity.projectRootMarkers) } : {},
|
|
6094
|
+
...list(identity.instructionFileCandidates) !== void 0 ? { instructionFileCandidates: list(identity.instructionFileCandidates) } : {},
|
|
6095
|
+
...list(identity.localInstructionFileCandidates) !== void 0 ? { localInstructionFileCandidates: list(identity.localInstructionFileCandidates) } : {}
|
|
6096
|
+
});
|
|
6097
|
+
const maxSourceBytes = typeof identity.maxSourceBytes === "number" ? identity.maxSourceBytes : 1048576;
|
|
6098
|
+
const contextFiles = [];
|
|
6099
|
+
for (const file of discovered) try {
|
|
6100
|
+
const bytes = await readFile(file.absolutePath);
|
|
6101
|
+
if (bytes.byteLength > maxSourceBytes) continue;
|
|
6102
|
+
contextFiles.push({
|
|
6103
|
+
path: file.absolutePath,
|
|
6104
|
+
content: bytes.toString("utf8")
|
|
6105
|
+
});
|
|
6106
|
+
} catch {}
|
|
6107
|
+
return {
|
|
6108
|
+
cwd,
|
|
6109
|
+
contextFiles
|
|
6110
|
+
};
|
|
6111
|
+
} catch (error) {
|
|
6112
|
+
logger(ctx).warn(`[pi2dsh] could not recover the host's instruction files for before_agent_start (contextFiles left empty): ${error instanceof Error ? error.message : String(error)}`);
|
|
6113
|
+
return {
|
|
6114
|
+
cwd,
|
|
6115
|
+
contextFiles: []
|
|
6116
|
+
};
|
|
6117
|
+
}
|
|
6118
|
+
}
|
|
5503
6119
|
async function piShapeOfPending(ctx, message) {
|
|
5504
6120
|
const source = message.source;
|
|
5505
6121
|
const content = await dshToPiContent(ctx, message.content ?? []);
|
|
@@ -5516,7 +6132,7 @@ async function applyPiContextTransform(ctx, state, agent, signal, pending) {
|
|
|
5516
6132
|
if ((state.handlers.get("context")?.length ?? 0) === 0) return pending;
|
|
5517
6133
|
const session = agentSession(agent);
|
|
5518
6134
|
const history = [];
|
|
5519
|
-
for (const event of session
|
|
6135
|
+
for (const event of readSessionEvents(session)) {
|
|
5520
6136
|
const projected = await messageFromSessionEvent(ctx, event);
|
|
5521
6137
|
if (projected === void 0) continue;
|
|
5522
6138
|
if (event.type === "user/message") {
|
|
@@ -5564,12 +6180,18 @@ async function applyPiContextTransform(ctx, state, agent, signal, pending) {
|
|
|
5564
6180
|
rebuilt.push(original);
|
|
5565
6181
|
continue;
|
|
5566
6182
|
}
|
|
6183
|
+
if (JSON.stringify(shape.content) === JSON.stringify(projectedPending[index]?.content)) {
|
|
6184
|
+
rebuilt.push(original);
|
|
6185
|
+
continue;
|
|
6186
|
+
}
|
|
5567
6187
|
const blocks = await piToDshContent(ctx, typeof shape.content === "string" ? [{
|
|
5568
6188
|
type: "text",
|
|
5569
6189
|
text: shape.content
|
|
5570
6190
|
}] : shape.content ?? []);
|
|
6191
|
+
const files = /* @__PURE__ */ new Map();
|
|
6192
|
+
for (const block of original.content ?? []) if (block.type === "file") files.set(fileContentForPi(block.attachment, ctx).text, block);
|
|
5571
6193
|
rebuilt.push(createUserMessage({
|
|
5572
|
-
content: blocks,
|
|
6194
|
+
content: blocks.map((block) => block.type === "text" ? files.get(block.text) ?? block : block),
|
|
5573
6195
|
source: original.source ?? {
|
|
5574
6196
|
kind: "plugin",
|
|
5575
6197
|
plugin: state.messageSource
|
|
@@ -6640,7 +7262,7 @@ async function sendPiMessage(ctx, state, content, mode, sessionOverride, customT
|
|
|
6640
7262
|
source: {
|
|
6641
7263
|
kind: "plugin",
|
|
6642
7264
|
plugin: state.messageSource,
|
|
6643
|
-
...customType === void 0 ? {} : { piCustomType: customType }
|
|
7265
|
+
...customType === void 0 ? { form: "relay" } : { piCustomType: customType }
|
|
6644
7266
|
}
|
|
6645
7267
|
});
|
|
6646
7268
|
if (sessionOverride !== void 0) {
|
|
@@ -6698,16 +7320,17 @@ function combineExecSignal(options) {
|
|
|
6698
7320
|
}
|
|
6699
7321
|
};
|
|
6700
7322
|
}
|
|
6701
|
-
async function executePiCommand(service, cwd, command, args, options) {
|
|
7323
|
+
async function executePiCommand(service, cwd, command, args, options, environment) {
|
|
6702
7324
|
const operation = combineExecSignal(options);
|
|
6703
7325
|
try {
|
|
6704
7326
|
if (typeof command !== "string" || command.length === 0) throw new TypeError("Pi exec command must be a non-empty string");
|
|
6705
7327
|
if (!Array.isArray(args) || args.some((value) => typeof value !== "string")) throw new TypeError("Pi exec args must be strings");
|
|
6706
|
-
const executable = await service.resolveExecutable(command,
|
|
7328
|
+
const executable = await service.resolveExecutable(command, environment, operation.signal);
|
|
6707
7329
|
const collect = { maxBytes: 67108864 };
|
|
6708
7330
|
const handle = service.spawn({
|
|
6709
7331
|
argv: [executable, ...args],
|
|
6710
7332
|
cwd: options.cwd ?? cwd,
|
|
7333
|
+
...environment === void 0 ? {} : { env: environment },
|
|
6711
7334
|
stdio: {
|
|
6712
7335
|
stdin: "ignore",
|
|
6713
7336
|
stdout: collect,
|
|
@@ -6779,6 +7402,7 @@ function registerCommand(ctx, state, command) {
|
|
|
6779
7402
|
const live = state.commands.get(command.name) ?? command;
|
|
6780
7403
|
await runInPiRuntime(state, agent, async () => {
|
|
6781
7404
|
await ensurePiSessionStarted(ctx, state, agent, invocation.signal);
|
|
7405
|
+
await refreshNativeSkillCommands(ctx, state, agent, invocation.signal);
|
|
6782
7406
|
await live.handler(String(invocation.rawInput ?? "").trimStart(), commandContext);
|
|
6783
7407
|
});
|
|
6784
7408
|
const notices = commandContext.__notices;
|
|
@@ -6909,6 +7533,85 @@ function registerSharedProviderRoute(ctx, state, name, value) {
|
|
|
6909
7533
|
state.ownedProviderRoutes.add(name);
|
|
6910
7534
|
return route;
|
|
6911
7535
|
}
|
|
7536
|
+
async function runPiPrintRequest(ctx, state, parent, request, signal) {
|
|
7537
|
+
if (parent === void 0 || state.subagentSessionFactory === void 0) throw new Error("pi2dsh CLI: native print work requires an active DSH agent");
|
|
7538
|
+
const catalog = state.shared.childExtensions;
|
|
7539
|
+
const requestedPaths = [];
|
|
7540
|
+
for (const requested of request.extensions) {
|
|
7541
|
+
let matched;
|
|
7542
|
+
for (const path of catalog?.packageByEntryPath.keys() ?? []) if (await realpath(path).catch(() => path) === requested) {
|
|
7543
|
+
matched = path;
|
|
7544
|
+
break;
|
|
7545
|
+
}
|
|
7546
|
+
if (matched === void 0) throw new Error(`pi2dsh CLI: extension ${requested} is not installed in this DSH profile`);
|
|
7547
|
+
requestedPaths.push(matched);
|
|
7548
|
+
}
|
|
7549
|
+
const paths = [.../* @__PURE__ */ new Set([...request.noExtensions ? [] : catalog?.packageByEntryPath.keys() ?? [], ...requestedPaths])];
|
|
7550
|
+
let model;
|
|
7551
|
+
if (request.model !== void 0) {
|
|
7552
|
+
model = state.modelCatalog?.all().find((entry) => `${entry.provider}/${entry.id}` === request.model || entry.id === request.model && (request.provider === void 0 || entry.provider === request.provider));
|
|
7553
|
+
if (model === void 0) throw new Error(`pi2dsh CLI: model ${request.model} is unavailable in the DSH model directory`);
|
|
7554
|
+
} else if (request.provider !== void 0) {
|
|
7555
|
+
const inherited = currentPiModel(state, parent);
|
|
7556
|
+
if (inherited?.provider !== request.provider) throw new Error("pi2dsh CLI: --provider requires an explicit --model for a different route");
|
|
7557
|
+
model = inherited;
|
|
7558
|
+
}
|
|
7559
|
+
const created = await runInPiRuntime(state, parent, () => state.subagentSessionFactory({
|
|
7560
|
+
cwd: request.cwd,
|
|
7561
|
+
signal,
|
|
7562
|
+
...model === void 0 ? {} : { model },
|
|
7563
|
+
...request.thinking === void 0 ? {} : { thinkingLevel: request.thinking },
|
|
7564
|
+
...request.tools === void 0 ? {} : { tools: request.tools },
|
|
7565
|
+
...request.noSession ? { sessionManager: { getSessionFile: () => void 0 } } : {},
|
|
7566
|
+
label: "Background task",
|
|
7567
|
+
resourceLoader: {
|
|
7568
|
+
getExtensions: () => ({
|
|
7569
|
+
extensions: paths.map((path) => ({ path })),
|
|
7570
|
+
errors: []
|
|
7571
|
+
}),
|
|
7572
|
+
getSystemPrompt: () => request.systemPrompt,
|
|
7573
|
+
getAppendSystemPrompt: () => request.appendSystemPrompt === void 0 ? [] : [request.appendSystemPrompt]
|
|
7574
|
+
}
|
|
7575
|
+
}));
|
|
7576
|
+
if (request.noSession) capabilityLedgerOf(ctx, state).reportHostDecision({
|
|
7577
|
+
capability: "pi print --no-session",
|
|
7578
|
+
reason: "the print worker has no Pi session file; DSH retains its native child audit log.",
|
|
7579
|
+
guidance: "Native retention remains a DSH host policy.",
|
|
7580
|
+
packageName: state.packageName
|
|
7581
|
+
});
|
|
7582
|
+
const child = created.session;
|
|
7583
|
+
let rejectCancellation;
|
|
7584
|
+
const cancellation = new Promise((_resolve, reject) => {
|
|
7585
|
+
rejectCancellation = reject;
|
|
7586
|
+
});
|
|
7587
|
+
cancellation.catch(() => {});
|
|
7588
|
+
const abort = () => {
|
|
7589
|
+
child.abort();
|
|
7590
|
+
rejectCancellation(signal.reason ?? /* @__PURE__ */ new Error("Pi print worker cancelled"));
|
|
7591
|
+
};
|
|
7592
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
7593
|
+
try {
|
|
7594
|
+
const failures = [];
|
|
7595
|
+
await child.bindExtensions({ onError: (error) => {
|
|
7596
|
+
failures.push(error);
|
|
7597
|
+
} });
|
|
7598
|
+
if (failures.length > 0) throw new Error(`pi2dsh CLI: child extension mount failed: ${JSON.stringify(failures)}`);
|
|
7599
|
+
signal.throwIfAborted();
|
|
7600
|
+
await Promise.race([child.prompt(request.prompt), cancellation]);
|
|
7601
|
+
signal.throwIfAborted();
|
|
7602
|
+
const session = optionalService(ctx, "sessions")?.get(child.sessionManager?.getSessionId() ?? "");
|
|
7603
|
+
const end = readSessionEvents(session).findLast((event) => event.type === "turn/end");
|
|
7604
|
+
if ((end?.data)?.reason?.kind !== "completed") throw new Error(`pi2dsh CLI: native child did not complete: ${JSON.stringify(end?.data ?? {})}`);
|
|
7605
|
+
return {
|
|
7606
|
+
code: 0,
|
|
7607
|
+
stdout: textBlocks(child.messages.findLast((message) => message.role === "assistant")?.content).map((block) => block.text).join("\n")
|
|
7608
|
+
};
|
|
7609
|
+
} finally {
|
|
7610
|
+
signal.removeEventListener("abort", abort);
|
|
7611
|
+
if (signal.aborted) child.abort();
|
|
7612
|
+
await child.dispose();
|
|
7613
|
+
}
|
|
7614
|
+
}
|
|
6912
7615
|
function createPiApi(ctx, state) {
|
|
6913
7616
|
return {
|
|
6914
7617
|
on(event, handler) {
|
|
@@ -7000,10 +7703,22 @@ function createPiApi(ctx, state) {
|
|
|
7000
7703
|
const session = requireSession(state, "setLabel");
|
|
7001
7704
|
state.bridge.appendLabel(session.id, String(entryId), label);
|
|
7002
7705
|
},
|
|
7003
|
-
exec(command, args = [], options = {}) {
|
|
7706
|
+
async exec(command, args = [], options = {}) {
|
|
7004
7707
|
const service = optionalService(ctx, "subprocess");
|
|
7005
7708
|
if (service === void 0) unsupported("exec");
|
|
7006
|
-
|
|
7709
|
+
const parent = currentAgent(state);
|
|
7710
|
+
let local = false;
|
|
7711
|
+
try {
|
|
7712
|
+
const { LocalSubprocessRuntime } = await import("@deepseek-ai/dsh-subprocess-local");
|
|
7713
|
+
local = service instanceof LocalSubprocessRuntime;
|
|
7714
|
+
} catch {}
|
|
7715
|
+
if (!local) return executePiCommand(service, cwdOf(parent), command, args, options);
|
|
7716
|
+
return withPiPrintTransport((request, signal) => runPiPrintRequest(ctx, state, parent, request, signal), (environment) => executePiCommand(service, cwdOf(parent), command, args, options, environment)).catch((error) => ({
|
|
7717
|
+
stdout: "",
|
|
7718
|
+
stderr: error instanceof Error ? error.message : String(error),
|
|
7719
|
+
code: 1,
|
|
7720
|
+
killed: options.signal?.aborted === true
|
|
7721
|
+
}));
|
|
7007
7722
|
},
|
|
7008
7723
|
getActiveTools: () => getActiveTools(ctx, state),
|
|
7009
7724
|
getAllTools: () => {
|
|
@@ -7022,7 +7737,7 @@ function createPiApi(ctx, state) {
|
|
|
7022
7737
|
}));
|
|
7023
7738
|
},
|
|
7024
7739
|
setActiveTools: (names) => setActiveTools(ctx, state, names),
|
|
7025
|
-
getCommands: () => [...state.commands.values()].map((command) => ({
|
|
7740
|
+
getCommands: () => [...[...state.commands.values()].map((command) => ({
|
|
7026
7741
|
name: command.name,
|
|
7027
7742
|
description: command.description,
|
|
7028
7743
|
source: "extension",
|
|
@@ -7032,7 +7747,7 @@ function createPiApi(ctx, state) {
|
|
|
7032
7747
|
scope: "user",
|
|
7033
7748
|
origin: "package"
|
|
7034
7749
|
}
|
|
7035
|
-
})),
|
|
7750
|
+
})), ...state.nativeSkillCommands],
|
|
7036
7751
|
async setModel(model) {
|
|
7037
7752
|
const agent = currentAgent(state);
|
|
7038
7753
|
if (agent === void 0) return false;
|
|
@@ -7205,7 +7920,7 @@ async function loadExtensions(rootDir, manifest, api, onExtensionError, onCapabi
|
|
|
7205
7920
|
for (const extension of manifest.extensions) {
|
|
7206
7921
|
try {
|
|
7207
7922
|
const source = await readFile(join(rootDir, extension), "utf8");
|
|
7208
|
-
const infraImport = /import[^;]*?\b(
|
|
7923
|
+
const infraImport = /import[^;]*?\b(DefaultPackageManager)\b[^;]*?from\s*['"]@(?:earendil-works|mariozechner)\/pi-coding-agent['"]/su.exec(source);
|
|
7209
7924
|
if (infraImport !== null) onHostInfraReference?.(infraImport[1], extension);
|
|
7210
7925
|
} catch {}
|
|
7211
7926
|
try {
|
|
@@ -7255,6 +7970,7 @@ async function applyPiPackage(ctx, options) {
|
|
|
7255
7970
|
toolDisposers: /* @__PURE__ */ new Map(),
|
|
7256
7971
|
toolRestrictions: /* @__PURE__ */ new WeakMap(),
|
|
7257
7972
|
commands: /* @__PURE__ */ new Map(),
|
|
7973
|
+
nativeSkillCommands: [],
|
|
7258
7974
|
commandDisposers: /* @__PURE__ */ new Map(),
|
|
7259
7975
|
dshCommandOwners: /* @__PURE__ */ new Map(),
|
|
7260
7976
|
companionRoutes: shared.companionRoutes,
|
|
@@ -7263,6 +7979,8 @@ async function applyPiPackage(ctx, options) {
|
|
|
7263
7979
|
activeAgents: /* @__PURE__ */ new Set(),
|
|
7264
7980
|
hostAgent: void 0,
|
|
7265
7981
|
piShutdownAgents: /* @__PURE__ */ new WeakSet(),
|
|
7982
|
+
shutdownTasks: /* @__PURE__ */ new WeakMap(),
|
|
7983
|
+
pendingShutdowns: /* @__PURE__ */ new Set(),
|
|
7266
7984
|
disposedAgents: /* @__PURE__ */ new WeakSet(),
|
|
7267
7985
|
startedSessions: /* @__PURE__ */ new Set(),
|
|
7268
7986
|
sessionStartTasks: /* @__PURE__ */ new Map(),
|
|
@@ -7270,6 +7988,8 @@ async function applyPiPackage(ctx, options) {
|
|
|
7270
7988
|
sessionlessStartTasks: /* @__PURE__ */ new WeakMap(),
|
|
7271
7989
|
extensionsReady: false,
|
|
7272
7990
|
pendingSessionStarts: /* @__PURE__ */ new Map(),
|
|
7991
|
+
discoveredSkillRoots: /* @__PURE__ */ new Set(),
|
|
7992
|
+
discoveredSkillProviders: 0,
|
|
7273
7993
|
currentSystemPrompt: "",
|
|
7274
7994
|
messageSource: `pi2dsh:${options.manifest.package.name}`,
|
|
7275
7995
|
eventBus: sharedEventBusFor(shared, ownerAgent),
|
|
@@ -7278,7 +7998,7 @@ async function applyPiPackage(ctx, options) {
|
|
|
7278
7998
|
llmBridge: void 0,
|
|
7279
7999
|
piAiRegistry: __createPiAiRuntimeRegistry(),
|
|
7280
8000
|
subagentSessionFactory: void 0,
|
|
7281
|
-
bridge: new PiSessionBridge(),
|
|
8001
|
+
bridge: new PiSessionBridge((ref) => fileContentForPi(ref, ctx)),
|
|
7282
8002
|
theme: sharedThemeFor(shared, ownerAgent),
|
|
7283
8003
|
shortcuts: /* @__PURE__ */ new Map(),
|
|
7284
8004
|
messageRenderers: /* @__PURE__ */ new Map(),
|
|
@@ -7651,6 +8371,7 @@ async function applyPiPackage(ctx, options) {
|
|
|
7651
8371
|
messageSource: state.messageSource,
|
|
7652
8372
|
packageName: state.packageName,
|
|
7653
8373
|
parentAgentContext: () => currentAgent(state)?.ctx,
|
|
8374
|
+
parentAgent: () => currentAgent(state),
|
|
7654
8375
|
registerChildTools: (childCtx, tools) => {
|
|
7655
8376
|
for (const tool of tools) registerChildPiTool(childCtx, state, tool);
|
|
7656
8377
|
},
|
|
@@ -7689,7 +8410,10 @@ async function applyPiPackage(ctx, options) {
|
|
|
7689
8410
|
const persistence = optionalService(ctx, "sessionPersistence");
|
|
7690
8411
|
if (typeof persistence?.list !== "function") return void 0;
|
|
7691
8412
|
try {
|
|
7692
|
-
return !(await persistence.list()).some((
|
|
8413
|
+
return !(await persistence.list()).some((snapshot) => {
|
|
8414
|
+
const header = snapshot.header ?? snapshot;
|
|
8415
|
+
return String(header.id ?? "") === sessionId;
|
|
8416
|
+
});
|
|
7693
8417
|
} catch {
|
|
7694
8418
|
return;
|
|
7695
8419
|
}
|
|
@@ -7707,7 +8431,19 @@ async function applyPiPackage(ctx, options) {
|
|
|
7707
8431
|
adoptChildAgent: (child, thinkingLevel) => {
|
|
7708
8432
|
if (typeof child !== "object" || child === null) return;
|
|
7709
8433
|
state.childAgents.add(child);
|
|
7710
|
-
if (typeof thinkingLevel === "string" && thinkingLevel.length > 0
|
|
8434
|
+
if (typeof thinkingLevel === "string" && thinkingLevel.length > 0) {
|
|
8435
|
+
state.thinkingLevels.set(child, thinkingLevel);
|
|
8436
|
+
child.ctx?.on?.("agent/request", async (payload, next) => {
|
|
8437
|
+
const config = await next();
|
|
8438
|
+
if (payload.agent !== child) return config;
|
|
8439
|
+
const level = state.thinkingLevels.get(child);
|
|
8440
|
+
if (level === void 0 || level === "off" && (currentPiModel(state, child)?.thinkingLevelMap)?.off === null) return config;
|
|
8441
|
+
return {
|
|
8442
|
+
...config,
|
|
8443
|
+
reasoningEffort: level
|
|
8444
|
+
};
|
|
8445
|
+
});
|
|
8446
|
+
}
|
|
7711
8447
|
}
|
|
7712
8448
|
});
|
|
7713
8449
|
await registerPromptCommands(ctx, state, rootDir, options.manifest);
|
|
@@ -7720,7 +8456,7 @@ async function applyPiPackage(ctx, options) {
|
|
|
7720
8456
|
});
|
|
7721
8457
|
const onHostInfraReference = (symbol) => capabilityLedgerOf(ctx, state).reportStartupReference({
|
|
7722
8458
|
capability: symbol,
|
|
7723
|
-
reason:
|
|
8459
|
+
reason: "installing packages is owned by the DSH host and its security gates (dsh plugin add/remove).",
|
|
7724
8460
|
guidance: "",
|
|
7725
8461
|
packageName: state.packageName
|
|
7726
8462
|
});
|
|
@@ -7729,6 +8465,13 @@ async function applyPiPackage(ctx, options) {
|
|
|
7729
8465
|
await flushPendingSessionStarts(ctx, state);
|
|
7730
8466
|
const remounts = packageRemountsOf(ctx, shared);
|
|
7731
8467
|
remounts.set(state.packageName, async () => {
|
|
8468
|
+
await state.projection;
|
|
8469
|
+
const previousAgent = state.hostAgent ?? currentAgent(state);
|
|
8470
|
+
if (previousAgent !== void 0) {
|
|
8471
|
+
await shutdownPiSession(ctx, state, previousAgent, "reload");
|
|
8472
|
+
state.piShutdownAgents.delete(previousAgent);
|
|
8473
|
+
state.shutdownTasks.delete(previousAgent);
|
|
8474
|
+
}
|
|
7732
8475
|
state.extensionsReady = false;
|
|
7733
8476
|
state.handlers.clear();
|
|
7734
8477
|
for (const off of state.eventBusOffs.splice(0)) off();
|
|
@@ -7766,4 +8509,4 @@ const runtimeInternals = {
|
|
|
7766
8509
|
//#endregion
|
|
7767
8510
|
export { overlayProviderConfig as a, runtimeInternals as c, normalizeToolSchema as i, getSharedChildExtensionCatalog as n, registerChildExtensionCatalog as o, mergeProviderRegistration as r, registerVisionCompanions as s, applyPiPackage as t };
|
|
7768
8511
|
|
|
7769
|
-
//# sourceMappingURL=runtime-
|
|
8512
|
+
//# sourceMappingURL=runtime-CCzYSxhI.mjs.map
|