pi2dsh 0.23.0 → 0.25.0
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 +19 -8
- package/README.zh.md +16 -6
- package/dist/{analyzer-C139Y2S7.mjs → analyzer-DbgL3neo.mjs} +2 -2
- package/dist/{analyzer-C139Y2S7.mjs.map → analyzer-DbgL3neo.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-CZs3LwYn.mjs → compatibility-DS6POFZD.mjs} +29 -26
- package/dist/compatibility-DS6POFZD.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 +7 -5
- package/dist/index.mjs.map +1 -1
- 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-Dnk9mhrc.mjs → runtime-B3N7s93Q.mjs} +1086 -249
- package/dist/runtime-B3N7s93Q.mjs.map +1 -0
- package/dist/runtime.d.mts +107 -50
- 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-CZs3LwYn.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-Dnk9mhrc.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;
|
|
@@ -1165,102 +1475,6 @@ var BrowserSurfaces = class {
|
|
|
1165
1475
|
imageToolNames() {
|
|
1166
1476
|
return [...this.#imageToolNames];
|
|
1167
1477
|
}
|
|
1168
|
-
#scene;
|
|
1169
|
-
#sceneRevision = 0;
|
|
1170
|
-
#sceneWidth = 100;
|
|
1171
|
-
openScene(packageName, factory, theme, keybindings, options) {
|
|
1172
|
-
this.#finishScene(void 0);
|
|
1173
|
-
return new Promise((resolve, reject) => {
|
|
1174
|
-
const run = {
|
|
1175
|
-
package: packageName,
|
|
1176
|
-
component: void 0,
|
|
1177
|
-
options,
|
|
1178
|
-
resolve,
|
|
1179
|
-
reject
|
|
1180
|
-
};
|
|
1181
|
-
this.#scene = run;
|
|
1182
|
-
const driver = { requestRender: () => this.#invalidateScene() };
|
|
1183
|
-
const done = (value) => {
|
|
1184
|
-
if (this.#scene !== run) return;
|
|
1185
|
-
this.#finishScene(value);
|
|
1186
|
-
};
|
|
1187
|
-
Promise.resolve().then(() => factory(driver, theme, keybindings, done)).then((component) => {
|
|
1188
|
-
if (this.#scene !== run) {
|
|
1189
|
-
component.dispose?.();
|
|
1190
|
-
return;
|
|
1191
|
-
}
|
|
1192
|
-
if (typeof component?.render !== "function") {
|
|
1193
|
-
this.#scene = void 0;
|
|
1194
|
-
this.#invalidateScene();
|
|
1195
|
-
console.warn(`[pi2dsh] web scene for ${packageName}: component lacks render(width)`);
|
|
1196
|
-
reject(/* @__PURE__ */ new TypeError("Pi custom component must implement render(width)"));
|
|
1197
|
-
return;
|
|
1198
|
-
}
|
|
1199
|
-
run.component = component;
|
|
1200
|
-
this.#invalidateScene();
|
|
1201
|
-
}, (error) => {
|
|
1202
|
-
if (this.#scene !== run) return;
|
|
1203
|
-
this.#scene = void 0;
|
|
1204
|
-
this.#invalidateScene();
|
|
1205
|
-
console.warn(`[pi2dsh] web scene for ${packageName}: component factory failed: ${error instanceof Error ? error.stack ?? error.message : String(error)}`);
|
|
1206
|
-
reject(error);
|
|
1207
|
-
});
|
|
1208
|
-
});
|
|
1209
|
-
}
|
|
1210
|
-
/** What the browser paints: the live component's frame, or a closed marker. */
|
|
1211
|
-
sceneSnapshot() {
|
|
1212
|
-
const scene = this.#scene;
|
|
1213
|
-
if (scene?.component === void 0) return {
|
|
1214
|
-
open: false,
|
|
1215
|
-
revision: this.#sceneRevision
|
|
1216
|
-
};
|
|
1217
|
-
const requested = (typeof scene.options?.overlayOptions === "function" ? scene.options.overlayOptions() : scene.options?.overlayOptions)?.width;
|
|
1218
|
-
const width = typeof requested === "number" && Number.isFinite(requested) && requested > 0 ? Math.min(this.#sceneWidth, Math.floor(requested)) : this.#sceneWidth;
|
|
1219
|
-
let lines;
|
|
1220
|
-
try {
|
|
1221
|
-
lines = scene.component.render(width);
|
|
1222
|
-
} catch (error) {
|
|
1223
|
-
const failed = this.#scene;
|
|
1224
|
-
this.#scene = void 0;
|
|
1225
|
-
this.#invalidateScene();
|
|
1226
|
-
console.warn(`[pi2dsh] web scene for ${scene.package}: render(width) failed: ${error instanceof Error ? error.stack ?? error.message : String(error)}`);
|
|
1227
|
-
failed?.reject(error);
|
|
1228
|
-
return {
|
|
1229
|
-
open: false,
|
|
1230
|
-
revision: this.#sceneRevision
|
|
1231
|
-
};
|
|
1232
|
-
}
|
|
1233
|
-
return {
|
|
1234
|
-
open: true,
|
|
1235
|
-
package: scene.package,
|
|
1236
|
-
revision: this.#sceneRevision,
|
|
1237
|
-
lines
|
|
1238
|
-
};
|
|
1239
|
-
}
|
|
1240
|
-
/** Raw terminal input from the browser keyboard, verbatim into the component. */
|
|
1241
|
-
sceneInput(sequence, width) {
|
|
1242
|
-
if (typeof width === "number" && Number.isFinite(width)) this.#sceneWidth = Math.max(20, Math.min(400, Math.floor(width)));
|
|
1243
|
-
const component = this.#scene?.component;
|
|
1244
|
-
if (component === void 0 || sequence.length === 0) return;
|
|
1245
|
-
component.handleInput?.(sequence);
|
|
1246
|
-
component.invalidate?.();
|
|
1247
|
-
this.#invalidateScene();
|
|
1248
|
-
}
|
|
1249
|
-
/** The browser dismissed the overlay: resolve the Pi caller with undefined. */
|
|
1250
|
-
closeScene() {
|
|
1251
|
-
this.#finishScene(void 0);
|
|
1252
|
-
}
|
|
1253
|
-
#finishScene(value) {
|
|
1254
|
-
const scene = this.#scene;
|
|
1255
|
-
if (scene === void 0) return;
|
|
1256
|
-
this.#scene = void 0;
|
|
1257
|
-
scene.component?.dispose?.();
|
|
1258
|
-
this.#invalidateScene();
|
|
1259
|
-
scene.resolve(value);
|
|
1260
|
-
}
|
|
1261
|
-
#invalidateScene() {
|
|
1262
|
-
this.#sceneRevision += 1;
|
|
1263
|
-
}
|
|
1264
1478
|
/**
|
|
1265
1479
|
* Track one child session under its parent.
|
|
1266
1480
|
* @param parentSessionId - the session the panel floats over.
|
|
@@ -1300,9 +1514,20 @@ var BrowserSurfaces = class {
|
|
|
1300
1514
|
if (live.size === 0) this.#commandRunners.delete(sessionId);
|
|
1301
1515
|
};
|
|
1302
1516
|
}
|
|
1303
|
-
/**
|
|
1517
|
+
/**
|
|
1518
|
+
* The registered runner for (session, package), when one is mounted.
|
|
1519
|
+
*
|
|
1520
|
+
* An empty sessionId means "any session where the package is mounted":
|
|
1521
|
+
* session-free product UI (a Settings page) invoking a package command
|
|
1522
|
+
* whose effect is global state — the handler itself is the package's own
|
|
1523
|
+
* either way, and every mounted copy routes to the same package storage.
|
|
1524
|
+
*/
|
|
1304
1525
|
commandRunner(sessionId, packageName) {
|
|
1305
|
-
return this.#commandRunners.get(sessionId)?.get(packageName);
|
|
1526
|
+
if (sessionId !== "") return this.#commandRunners.get(sessionId)?.get(packageName);
|
|
1527
|
+
for (const runners of this.#commandRunners.values()) {
|
|
1528
|
+
const runner = runners.get(packageName);
|
|
1529
|
+
if (runner !== void 0) return runner;
|
|
1530
|
+
}
|
|
1306
1531
|
}
|
|
1307
1532
|
/**
|
|
1308
1533
|
* The panel's view of one parent session.
|
|
@@ -1718,6 +1943,46 @@ function registerBrowserSurfaceRoute(ctx, registry, hooks) {
|
|
|
1718
1943
|
response.end(JSON.stringify(outcome ?? { ok: true }));
|
|
1719
1944
|
return;
|
|
1720
1945
|
}
|
|
1946
|
+
if (url.pathname === "/pi2dsh/login-state" && (method === "GET" || method === "HEAD")) {
|
|
1947
|
+
if (hooks?.loginState === void 0) {
|
|
1948
|
+
response.writeHead(404);
|
|
1949
|
+
response.end();
|
|
1950
|
+
return;
|
|
1951
|
+
}
|
|
1952
|
+
const body = JSON.stringify(await hooks.loginState());
|
|
1953
|
+
response.writeHead(200, {
|
|
1954
|
+
"content-type": "application/json; charset=utf-8",
|
|
1955
|
+
"cache-control": "no-store"
|
|
1956
|
+
});
|
|
1957
|
+
response.end(method === "HEAD" ? void 0 : body);
|
|
1958
|
+
return;
|
|
1959
|
+
}
|
|
1960
|
+
if (url.pathname === "/pi2dsh/login-action" && method === "POST") {
|
|
1961
|
+
if (hooks?.loginAction === void 0) {
|
|
1962
|
+
response.writeHead(404);
|
|
1963
|
+
response.end();
|
|
1964
|
+
return;
|
|
1965
|
+
}
|
|
1966
|
+
const chunks = [];
|
|
1967
|
+
const raw = await new Promise((settle) => {
|
|
1968
|
+
const stream = req;
|
|
1969
|
+
stream.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
1970
|
+
stream.on("end", () => settle(Buffer.concat(chunks).toString("utf8")));
|
|
1971
|
+
});
|
|
1972
|
+
let outcome;
|
|
1973
|
+
try {
|
|
1974
|
+
const payload = JSON.parse(raw || "{}");
|
|
1975
|
+
if (typeof payload.action !== "string") throw new TypeError("login-action needs { action }");
|
|
1976
|
+
outcome = await hooks.loginAction(payload.action, typeof payload.provider === "string" ? payload.provider : "", typeof payload.value === "string" ? payload.value : "");
|
|
1977
|
+
} catch (error) {
|
|
1978
|
+
response.writeHead(400, { "content-type": "application/json; charset=utf-8" });
|
|
1979
|
+
response.end(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
|
|
1980
|
+
return;
|
|
1981
|
+
}
|
|
1982
|
+
response.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
|
1983
|
+
response.end(JSON.stringify(outcome ?? { ok: true }));
|
|
1984
|
+
return;
|
|
1985
|
+
}
|
|
1721
1986
|
if (url.pathname === "/pi2dsh/pi-command" && method === "POST") {
|
|
1722
1987
|
const chunks = [];
|
|
1723
1988
|
const raw = await new Promise((settle) => {
|
|
@@ -1746,22 +2011,6 @@ function registerBrowserSurfaceRoute(ctx, registry, hooks) {
|
|
|
1746
2011
|
}
|
|
1747
2012
|
return;
|
|
1748
2013
|
}
|
|
1749
|
-
if (method === "POST" && (url.pathname === "/pi2dsh/scene-input" || url.pathname === "/pi2dsh/scene-close")) {
|
|
1750
|
-
const chunks = [];
|
|
1751
|
-
const body = await new Promise((settle) => {
|
|
1752
|
-
const stream = req;
|
|
1753
|
-
stream.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
1754
|
-
stream.on("end", () => settle(Buffer.concat(chunks).toString("utf8")));
|
|
1755
|
-
});
|
|
1756
|
-
if (url.pathname === "/pi2dsh/scene-close") registry.closeScene();
|
|
1757
|
-
else try {
|
|
1758
|
-
const payload = JSON.parse(body || "{}");
|
|
1759
|
-
if (typeof payload.sequence === "string") registry.sceneInput(payload.sequence, typeof payload.width === "number" ? payload.width : void 0);
|
|
1760
|
-
} catch {}
|
|
1761
|
-
response.writeHead(204);
|
|
1762
|
-
response.end();
|
|
1763
|
-
return;
|
|
1764
|
-
}
|
|
1765
2014
|
if (method !== "GET" && method !== "HEAD") {
|
|
1766
2015
|
response.writeHead(405);
|
|
1767
2016
|
response.end();
|
|
@@ -1807,13 +2056,11 @@ function registerBrowserSurfaceRoute(ctx, registry, hooks) {
|
|
|
1807
2056
|
const body = JSON.stringify(session === "" ? {
|
|
1808
2057
|
threads: [],
|
|
1809
2058
|
surfaces: [],
|
|
1810
|
-
entries: []
|
|
1811
|
-
scene: registry.sceneSnapshot()
|
|
2059
|
+
entries: []
|
|
1812
2060
|
} : {
|
|
1813
2061
|
threads: registry.snapshot(session),
|
|
1814
2062
|
surfaces: registry.surfaces(session),
|
|
1815
2063
|
entries: registry.entries(session),
|
|
1816
|
-
scene: registry.sceneSnapshot(),
|
|
1817
2064
|
...registry.draftRequest(session) === void 0 ? {} : { draft: registry.draftRequest(session) }
|
|
1818
2065
|
});
|
|
1819
2066
|
response.writeHead(200, {
|
|
@@ -2084,6 +2331,7 @@ var ModelCatalog = class {
|
|
|
2084
2331
|
if (exact.contextWindow !== void 0) model.contextWindow = exact.contextWindow;
|
|
2085
2332
|
if (exact.maxTokens !== void 0) model.maxTokens = exact.maxTokens;
|
|
2086
2333
|
model.reasoning = exact.reasoning;
|
|
2334
|
+
if (exact.thinkingLevelMap !== void 0) model.thinkingLevelMap = exact.thinkingLevelMap;
|
|
2087
2335
|
resolved.set(`${model.provider}${model.id}`, model);
|
|
2088
2336
|
}));
|
|
2089
2337
|
this.#models = next;
|
|
@@ -2109,7 +2357,17 @@ var ModelCatalog = class {
|
|
|
2109
2357
|
const defaultMaxTokens = info.defaultMaxTokens;
|
|
2110
2358
|
if (typeof defaultMaxTokens === "number") model.maxTokens = defaultMaxTokens;
|
|
2111
2359
|
const reasoning = info.reasoning;
|
|
2112
|
-
|
|
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]));
|
|
2113
2371
|
return model;
|
|
2114
2372
|
} catch {
|
|
2115
2373
|
return;
|
|
@@ -2234,6 +2492,7 @@ function streamViaDshLlm(llm, request) {
|
|
|
2234
2492
|
...Array.isArray(request.context.tools) ? { tools: normalizeToolSchemas(request.context.tools) } : {},
|
|
2235
2493
|
...typeof options.maxTokens === "number" ? { maxTokens: options.maxTokens } : {},
|
|
2236
2494
|
...typeof options.temperature === "number" ? { temperature: options.temperature } : {},
|
|
2495
|
+
...typeof options.reasoning === "string" ? { reasoningEffort: options.reasoning } : {},
|
|
2237
2496
|
...options.signal instanceof AbortSignal ? { signal: options.signal } : {}
|
|
2238
2497
|
};
|
|
2239
2498
|
(async () => {
|
|
@@ -2441,15 +2700,21 @@ function normalizeToolSchemas(tools) {
|
|
|
2441
2700
|
/** DSH GenerateOptions parts → the Pi request context a provider.stream expects. */
|
|
2442
2701
|
async function dshRequestToPiContext(options, attachments) {
|
|
2443
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;
|
|
2444
2708
|
const toolNamesById = /* @__PURE__ */ new Map();
|
|
2445
|
-
for (const message of
|
|
2709
|
+
for (const message of history) {
|
|
2446
2710
|
if (message.role !== "assistant") continue;
|
|
2447
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);
|
|
2448
2712
|
}
|
|
2449
|
-
for (const message of
|
|
2713
|
+
for (const message of history) {
|
|
2450
2714
|
const content = Array.isArray(message.content) ? message.content : [];
|
|
2451
2715
|
const source = message.source;
|
|
2452
|
-
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") {
|
|
2453
2718
|
const result = content[0];
|
|
2454
2719
|
const toolCallId = String(result?.toolCallId ?? "");
|
|
2455
2720
|
const toolName = toolNamesById.get(toolCallId);
|
|
@@ -2470,7 +2735,7 @@ async function dshRequestToPiContext(options, attachments) {
|
|
|
2470
2735
|
});
|
|
2471
2736
|
}
|
|
2472
2737
|
return {
|
|
2473
|
-
...
|
|
2738
|
+
...systemPrompt !== void 0 && systemPrompt.length > 0 ? { systemPrompt } : {},
|
|
2474
2739
|
messages: piMessages,
|
|
2475
2740
|
...Array.isArray(options.tools) ? { tools: options.tools } : {}
|
|
2476
2741
|
};
|
|
@@ -4103,9 +4368,10 @@ function isSubagentOrigin(subject) {
|
|
|
4103
4368
|
function currentPiModel(state, agent) {
|
|
4104
4369
|
const override = state.modelOverrides.get(agent);
|
|
4105
4370
|
const options = agent.options;
|
|
4106
|
-
const
|
|
4371
|
+
const durable = lastRequestRouteOf(agentSession(agent));
|
|
4372
|
+
const selectedProvider = String(override?.provider ?? durable?.provider ?? options?.provider ?? "");
|
|
4107
4373
|
const provider = state.companionRoutes.get(selectedProvider) ?? selectedProvider;
|
|
4108
|
-
const id = String(override?.model ?? options?.model ?? "");
|
|
4374
|
+
const id = String(override?.model ?? durable?.model ?? options?.model ?? "");
|
|
4109
4375
|
if (id.length === 0) return override;
|
|
4110
4376
|
return (provider.length > 0 ? state.modelCatalog?.find(provider, id) : void 0) ?? {
|
|
4111
4377
|
id,
|
|
@@ -4156,9 +4422,7 @@ function resolveCallerRoute(override, durable, snapshot) {
|
|
|
4156
4422
|
}
|
|
4157
4423
|
/** A session's durable events, whether the store exposes them as value or method. */
|
|
4158
4424
|
function eventsOf(session) {
|
|
4159
|
-
|
|
4160
|
-
if (typeof events === "function") return events.call(session) ?? [];
|
|
4161
|
-
return Array.isArray(events) ? events : [];
|
|
4425
|
+
return readSessionEvents(session);
|
|
4162
4426
|
}
|
|
4163
4427
|
/** The session id a presentation call belongs to, or '' outside a session. */
|
|
4164
4428
|
function sessionIdOf(state, agent) {
|
|
@@ -4358,8 +4622,16 @@ function contextFor(ctx, state, agent, signal, command = false, sessionOverride,
|
|
|
4358
4622
|
if (headlessUi) return void 0;
|
|
4359
4623
|
const surfaces = state.tuiSurfaces;
|
|
4360
4624
|
if (surfaces !== void 0) return surfaces.custom(factory, state.theme, getKeybindings(), options);
|
|
4361
|
-
|
|
4362
|
-
|
|
4625
|
+
if (humanAnswererAvailable(userQuestions, agent, state.shared)) {
|
|
4626
|
+
const gap = {
|
|
4627
|
+
capability: "ui.custom",
|
|
4628
|
+
reason: "native dialogs are available, but this surface has no terminal-component renderer.",
|
|
4629
|
+
guidance: "Use the package's non-terminal fallback or a DSH terminal surface.",
|
|
4630
|
+
packageName: state.packageName
|
|
4631
|
+
};
|
|
4632
|
+
capabilityLedgerOf(ctx, state).reportDegraded(gap);
|
|
4633
|
+
throw new PiCapabilityError(gap);
|
|
4634
|
+
}
|
|
4363
4635
|
},
|
|
4364
4636
|
pasteToEditor(text) {
|
|
4365
4637
|
const surfaces = state.shared.browserSurfaces;
|
|
@@ -4556,8 +4828,7 @@ function contextFor(ctx, state, agent, signal, command = false, sessionOverride,
|
|
|
4556
4828
|
},
|
|
4557
4829
|
ui,
|
|
4558
4830
|
get mode() {
|
|
4559
|
-
|
|
4560
|
-
return state.shared.browserSurfaces !== void 0 && state.shared.browserSurfacesRouted === true ? "tui" : "rpc";
|
|
4831
|
+
return state.tuiSurfaces?.available === true ? "tui" : "rpc";
|
|
4561
4832
|
},
|
|
4562
4833
|
get hasUI() {
|
|
4563
4834
|
return state.tuiSurfaces?.available === true || humanAnswererAvailable(userQuestions, agent, state.shared);
|
|
@@ -4582,7 +4853,10 @@ function contextFor(ctx, state, agent, signal, command = false, sessionOverride,
|
|
|
4582
4853
|
reason: "pi2dsh: aborted by migrated Pi extension"
|
|
4583
4854
|
});
|
|
4584
4855
|
},
|
|
4585
|
-
hasPendingMessages: () =>
|
|
4856
|
+
hasPendingMessages: () => {
|
|
4857
|
+
const inbox = agent?.inbox;
|
|
4858
|
+
return inbox?.nextStep !== void 0 || inbox?.nextTurn !== void 0 ? (inbox.nextStep?.length ?? 0) + (inbox.nextTurn?.length ?? 0) > 0 : inbox?.hasPending === true;
|
|
4859
|
+
},
|
|
4586
4860
|
shutdown: () => {
|
|
4587
4861
|
capabilityLedgerOf(ctx, state).reportHostDecision({
|
|
4588
4862
|
capability: "ctx.shutdown",
|
|
@@ -4683,7 +4957,7 @@ function contextFor(ctx, state, agent, signal, command = false, sessionOverride,
|
|
|
4683
4957
|
const seq = durableSeqOf(String(entryId));
|
|
4684
4958
|
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`);
|
|
4685
4959
|
const position = options?.position ?? "before";
|
|
4686
|
-
const events = source
|
|
4960
|
+
const events = readSessionEvents(source);
|
|
4687
4961
|
const requested = Math.min(position === "at" ? seq : seq - 1, events.length - 1);
|
|
4688
4962
|
const boundary = requested < 0 ? -1 : shrinkToTurnBoundary(events, requested);
|
|
4689
4963
|
const child = boundary < 0 ? sessions.create(void 0, { meta: {
|
|
@@ -4714,7 +4988,7 @@ function contextFor(ctx, state, agent, signal, command = false, sessionOverride,
|
|
|
4714
4988
|
}
|
|
4715
4989
|
const seq = durableSeqOf(String(targetId));
|
|
4716
4990
|
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`);
|
|
4717
|
-
const events = source
|
|
4991
|
+
const events = readSessionEvents(source);
|
|
4718
4992
|
const capped = Math.min(seq, events.length - 1);
|
|
4719
4993
|
const boundary = capped < 0 ? -1 : shrinkToTurnBoundary(events, capped);
|
|
4720
4994
|
const child = boundary < 0 ? sessions.create(void 0, { meta: {
|
|
@@ -4747,7 +5021,7 @@ function contextFor(ctx, state, agent, signal, command = false, sessionOverride,
|
|
|
4747
5021
|
}
|
|
4748
5022
|
const raw = String(sessionPath);
|
|
4749
5023
|
const base = raw.replace(/\\/g, "/").split("/").pop() ?? raw;
|
|
4750
|
-
const candidate = base.endsWith(".jsonl") ? base.slice(0, -6) : base;
|
|
5024
|
+
const candidate = state.bridge.sessionIdOfArchiveFile(raw) ?? (base.endsWith(".jsonl") ? base.slice(0, -6) : base);
|
|
4751
5025
|
const target = sessions.get(candidate) ?? sessions.list().find((entry) => String(entry.id) === candidate);
|
|
4752
5026
|
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)`);
|
|
4753
5027
|
const withSession = options?.withSession;
|
|
@@ -4784,6 +5058,20 @@ async function dispatch(state, eventName, event, eventContext) {
|
|
|
4784
5058
|
for (const handler of state.handlers.get(eventName) ?? []) results.push(await runInPiRuntime(state, agent, () => handler(event, eventContext)));
|
|
4785
5059
|
return results;
|
|
4786
5060
|
}
|
|
5061
|
+
function shutdownPiSession(ctx, state, agent, reason, signal) {
|
|
5062
|
+
const existing = state.shutdownTasks.get(agent);
|
|
5063
|
+
if (existing !== void 0) return existing;
|
|
5064
|
+
if (state.piShutdownAgents.has(agent)) return Promise.resolve();
|
|
5065
|
+
state.piShutdownAgents.add(agent);
|
|
5066
|
+
const task = dispatch(state, "session_shutdown", {
|
|
5067
|
+
type: "session_shutdown",
|
|
5068
|
+
reason
|
|
5069
|
+
}, contextFor(ctx, state, agent, signal)).then(() => {});
|
|
5070
|
+
state.shutdownTasks.set(agent, task);
|
|
5071
|
+
state.pendingShutdowns.add(task);
|
|
5072
|
+
task.then(() => state.pendingShutdowns.delete(task), () => state.pendingShutdowns.delete(task));
|
|
5073
|
+
return task;
|
|
5074
|
+
}
|
|
4787
5075
|
/**
|
|
4788
5076
|
* Bind every compatibility singleton to this exact Agent runtime for the
|
|
4789
5077
|
* duration of extension-owned work. AsyncLocalStorage follows promises and
|
|
@@ -4875,7 +5163,8 @@ async function dshToPiContent(ctx, content) {
|
|
|
4875
5163
|
else if (block.type === "image") {
|
|
4876
5164
|
const image = await piImageBlock(ctx, block.attachment);
|
|
4877
5165
|
if (image !== void 0) out.push(image);
|
|
4878
|
-
} else out.push(
|
|
5166
|
+
} else if (block.type === "file") out.push(fileContentForPi(block.attachment, ctx));
|
|
5167
|
+
else out.push({ type: block.type });
|
|
4879
5168
|
return out;
|
|
4880
5169
|
}
|
|
4881
5170
|
/**
|
|
@@ -4905,7 +5194,11 @@ async function messageFromSessionEvent(ctx, event) {
|
|
|
4905
5194
|
if (typeof data !== "object" || data === null) return void 0;
|
|
4906
5195
|
const record = data;
|
|
4907
5196
|
if (type === "user/message") return {
|
|
4908
|
-
role: "user",
|
|
5197
|
+
role: isPiUserMessage(record) ? "user" : "custom",
|
|
5198
|
+
...!isPiUserMessage(record) ? {
|
|
5199
|
+
customType: String(record.source?.piCustomType ?? record.source?.kind ?? "context"),
|
|
5200
|
+
display: false
|
|
5201
|
+
} : {},
|
|
4909
5202
|
content: await dshToPiContent(ctx, record.content ?? [])
|
|
4910
5203
|
};
|
|
4911
5204
|
if (type === "assistant/message") {
|
|
@@ -5003,6 +5296,20 @@ function untrackRuntimeSession(state, agent) {
|
|
|
5003
5296
|
states?.delete(state);
|
|
5004
5297
|
if (states?.size === 0) state.shared.runtimeStatesBySession.delete(id);
|
|
5005
5298
|
}
|
|
5299
|
+
const sessionExportTasks = /* @__PURE__ */ new WeakMap();
|
|
5300
|
+
async function prepareSessionFileExports(ctx, state) {
|
|
5301
|
+
const persistence = optionalService(ctx, "sessionPersistence");
|
|
5302
|
+
if (typeof persistence?.list !== "function" || typeof persistence.open !== "function" && typeof persistence.inspect !== "function") return;
|
|
5303
|
+
let task = sessionExportTasks.get(ctx.root);
|
|
5304
|
+
if (task === void 0) {
|
|
5305
|
+
task = state.bridge.exportStoredSessions(persistence);
|
|
5306
|
+
sessionExportTasks.set(ctx.root, task);
|
|
5307
|
+
task.catch(() => {
|
|
5308
|
+
sessionExportTasks.delete(ctx.root);
|
|
5309
|
+
});
|
|
5310
|
+
}
|
|
5311
|
+
await task;
|
|
5312
|
+
}
|
|
5006
5313
|
/** Dispatch or join the one in-flight Pi session_start for this session. */
|
|
5007
5314
|
async function startPiSession(ctx, state, agent, reason, signal) {
|
|
5008
5315
|
if (!acceptsAgent(state, agent)) return;
|
|
@@ -5015,24 +5322,24 @@ async function startPiSession(ctx, state, agent, reason, signal) {
|
|
|
5015
5322
|
const previous = state.hostAgent;
|
|
5016
5323
|
const replacing = typeof previous === "object" && previous !== null && previous !== agent;
|
|
5017
5324
|
const transitionReason = replacing ? replacementReason(reason) : reason;
|
|
5018
|
-
if (replacing
|
|
5019
|
-
state.piShutdownAgents.add(previous);
|
|
5325
|
+
if (replacing) {
|
|
5020
5326
|
releasePiSessionClaim(state, previous);
|
|
5021
|
-
await
|
|
5022
|
-
type: "session_shutdown",
|
|
5023
|
-
reason: replacementReason(reason)
|
|
5024
|
-
}, contextFor(ctx, state, previous, signal)));
|
|
5327
|
+
await shutdownPiSession(ctx, state, previous, replacementReason(reason), signal);
|
|
5025
5328
|
}
|
|
5026
5329
|
if (typeof agent === "object" && agent !== null) {
|
|
5027
5330
|
state.hostAgent = agent;
|
|
5028
5331
|
state.piShutdownAgents.delete(agent);
|
|
5332
|
+
state.shutdownTasks.delete(agent);
|
|
5029
5333
|
}
|
|
5030
5334
|
if (!claimPiSessionStart(state, agent)) return;
|
|
5031
5335
|
try {
|
|
5336
|
+
await prepareSessionFileExports(ctx, state);
|
|
5032
5337
|
await dispatch(state, "session_start", {
|
|
5033
5338
|
type: "session_start",
|
|
5034
5339
|
reason: transitionReason
|
|
5035
5340
|
}, contextFor(ctx, state, agent, signal));
|
|
5341
|
+
await discoverPiResources(ctx, state, agent, transitionReason, signal);
|
|
5342
|
+
await refreshNativeSkillCommands(ctx, state, agent, signal);
|
|
5036
5343
|
} catch (error) {
|
|
5037
5344
|
releasePiSessionClaim(state, agent);
|
|
5038
5345
|
throw error;
|
|
@@ -5077,15 +5384,65 @@ function subscribeLifecycle(ctx, state) {
|
|
|
5077
5384
|
state.disposedAgents.add(agent);
|
|
5078
5385
|
releasePiSessionClaim(state, agent);
|
|
5079
5386
|
if (ownsHostSession && !state.piShutdownAgents.has(agent)) {
|
|
5080
|
-
state.piShutdownAgents.add(agent);
|
|
5081
5387
|
state.hostAgent = void 0;
|
|
5082
|
-
|
|
5083
|
-
type: "session_shutdown",
|
|
5084
|
-
reason: "quit"
|
|
5085
|
-
}, contextFor(ctx, state, agent, void 0)).catch((error) => warn("session_shutdown", error));
|
|
5388
|
+
shutdownPiSession(ctx, state, agent, "quit").catch((error) => warn("session_shutdown", error));
|
|
5086
5389
|
}
|
|
5087
5390
|
}
|
|
5088
5391
|
});
|
|
5392
|
+
const liveStreams = /* @__PURE__ */ new WeakMap();
|
|
5393
|
+
const projectAssistantChunk = (session, data, chunk, eventContext) => {
|
|
5394
|
+
if ((state.handlers.get("message_update")?.length ?? 0) === 0) return;
|
|
5395
|
+
if (chunk.type !== void 0 && chunk.type !== "text-delta") return;
|
|
5396
|
+
const key = `${String(session.id ?? "")}:${String(data.turn ?? 0)}:${String(data.step ?? 0)}`;
|
|
5397
|
+
const delta = typeof chunk.text === "string" ? chunk.text : typeof chunk.delta === "string" ? chunk.delta : "";
|
|
5398
|
+
const accumulated = (state.streamingTexts.get(key) ?? "") + delta;
|
|
5399
|
+
state.streamingTexts.set(key, accumulated);
|
|
5400
|
+
const message = {
|
|
5401
|
+
role: "assistant",
|
|
5402
|
+
content: [{
|
|
5403
|
+
type: "text",
|
|
5404
|
+
text: accumulated
|
|
5405
|
+
}]
|
|
5406
|
+
};
|
|
5407
|
+
dispatch(state, "message_update", {
|
|
5408
|
+
type: "message_update",
|
|
5409
|
+
message,
|
|
5410
|
+
assistantMessageEvent: {
|
|
5411
|
+
type: "text_delta",
|
|
5412
|
+
contentIndex: 0,
|
|
5413
|
+
delta,
|
|
5414
|
+
partial: message
|
|
5415
|
+
}
|
|
5416
|
+
}, eventContext).catch((error) => warn("message_update", error));
|
|
5417
|
+
};
|
|
5418
|
+
cordis.on("agent/assistant-stream", (payload) => {
|
|
5419
|
+
const agent = payload.agent;
|
|
5420
|
+
if (agent === void 0 || state.hostAnchor || !acceptsAgent(state, agent)) return;
|
|
5421
|
+
const session = agentSession(agent);
|
|
5422
|
+
if (session === void 0) return;
|
|
5423
|
+
const frame = payload.frame;
|
|
5424
|
+
if (frame.type === "start") {
|
|
5425
|
+
liveStreams.set(session, {
|
|
5426
|
+
attemptId: frame.attemptId,
|
|
5427
|
+
turn: frame.turn,
|
|
5428
|
+
step: frame.step,
|
|
5429
|
+
index: -1,
|
|
5430
|
+
ended: false
|
|
5431
|
+
});
|
|
5432
|
+
state.streamingTexts.delete(`${String(session.id ?? "")}:${String(frame.turn ?? 0)}:${String(frame.step ?? 0)}`);
|
|
5433
|
+
return;
|
|
5434
|
+
}
|
|
5435
|
+
const active = liveStreams.get(session);
|
|
5436
|
+
if (active === void 0 || active.ended || active.attemptId !== frame.attemptId) return;
|
|
5437
|
+
if (frame.type === "chunk" && Number(frame.index) > active.index) {
|
|
5438
|
+
active.index = Number(frame.index);
|
|
5439
|
+
projectAssistantChunk(session, active, frame.chunk, contextFor(ctx, state, agent, void 0));
|
|
5440
|
+
}
|
|
5441
|
+
if (frame.type === "end") {
|
|
5442
|
+
active.ended = true;
|
|
5443
|
+
state.streamingTexts.delete(`${String(session.id ?? "")}:${String(active.turn ?? 0)}:${String(active.step ?? 0)}`);
|
|
5444
|
+
}
|
|
5445
|
+
});
|
|
5089
5446
|
const projectSessionEvent = (session, event) => {
|
|
5090
5447
|
const agent = [...state.activeAgents].find((candidate) => candidate.session === session);
|
|
5091
5448
|
const eventContext = contextFor(ctx, state, agent, void 0);
|
|
@@ -5107,7 +5464,7 @@ function subscribeLifecycle(ctx, state) {
|
|
|
5107
5464
|
const index = state.piTurnIndex.get(key) ?? 0;
|
|
5108
5465
|
state.piTurnIndex.set(key, index + 1);
|
|
5109
5466
|
state.projection = state.projection.then(async () => {
|
|
5110
|
-
const stepEvents = (session
|
|
5467
|
+
const stepEvents = readSessionEvents(session).filter((entry) => {
|
|
5111
5468
|
const entryData = entry.data;
|
|
5112
5469
|
return Number(entryData?.turn ?? -1) === turn && Number(entryData?.step ?? -1) === step;
|
|
5113
5470
|
});
|
|
@@ -5140,27 +5497,12 @@ function subscribeLifecycle(ctx, state) {
|
|
|
5140
5497
|
type: "tool_execution_start",
|
|
5141
5498
|
toolCallId: data.callId,
|
|
5142
5499
|
toolName: data.name,
|
|
5143
|
-
args
|
|
5500
|
+
args: piViewOfToolArguments(state, String(data.name), args)
|
|
5144
5501
|
}, eventContext).catch((error) => warn("tool_execution_start", error));
|
|
5145
5502
|
}
|
|
5146
|
-
if (type === "assistant/chunk" &&
|
|
5503
|
+
if (type === "assistant/chunk" && !liveStreams.has(session)) {
|
|
5147
5504
|
const data = event.data;
|
|
5148
|
-
|
|
5149
|
-
const key = `${String(session.id ?? "")}:${String(data.turn ?? 0)}:${String(data.step ?? 0)}`;
|
|
5150
|
-
const delta = typeof chunk.text === "string" ? chunk.text : typeof chunk.delta === "string" ? chunk.delta : "";
|
|
5151
|
-
const accumulated = (state.streamingTexts.get(key) ?? "") + delta;
|
|
5152
|
-
state.streamingTexts.set(key, accumulated);
|
|
5153
|
-
dispatch(state, "message_update", {
|
|
5154
|
-
type: "message_update",
|
|
5155
|
-
message: {
|
|
5156
|
-
role: "assistant",
|
|
5157
|
-
content: [{
|
|
5158
|
-
type: "text",
|
|
5159
|
-
text: accumulated
|
|
5160
|
-
}]
|
|
5161
|
-
},
|
|
5162
|
-
assistantMessageEvent: chunk
|
|
5163
|
-
}, eventContext).catch((error) => warn("message_update", error));
|
|
5505
|
+
projectAssistantChunk(session, data, data.chunk ?? {}, eventContext);
|
|
5164
5506
|
}
|
|
5165
5507
|
if (type === "assistant/message") {
|
|
5166
5508
|
const data = event.data;
|
|
@@ -5173,18 +5515,21 @@ function subscribeLifecycle(ctx, state) {
|
|
|
5173
5515
|
name: typeof data.title === "string" ? data.title : void 0
|
|
5174
5516
|
}, eventContext).catch((error) => warn("session_info_changed", error));
|
|
5175
5517
|
}
|
|
5176
|
-
if (type === "compaction/start")
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5518
|
+
if (type === "compaction/start") {
|
|
5519
|
+
const before = dispatch(state, "session_before_compact", {
|
|
5520
|
+
type: "session_before_compact",
|
|
5521
|
+
preparation: { ...event.data },
|
|
5522
|
+
branchEntries: [],
|
|
5523
|
+
reason: compactionReason(event.data),
|
|
5524
|
+
willRetry: false
|
|
5525
|
+
}, eventContext).catch((error) => warn("session_before_compact", error));
|
|
5526
|
+
state.projection = Promise.all([state.projection, before]).then(() => {});
|
|
5527
|
+
}
|
|
5183
5528
|
if (type === "compaction/summary") {
|
|
5184
5529
|
const data = event.data;
|
|
5185
5530
|
const shadowed = data.shadowedRange;
|
|
5186
5531
|
const usage = data.usage;
|
|
5187
|
-
dispatch(state, "session_compact", {
|
|
5532
|
+
state.projection = state.projection.then(() => dispatch(state, "session_compact", {
|
|
5188
5533
|
type: "session_compact",
|
|
5189
5534
|
compactionEntry: {
|
|
5190
5535
|
type: "compaction",
|
|
@@ -5198,7 +5543,7 @@ function subscribeLifecycle(ctx, state) {
|
|
|
5198
5543
|
fromExtension: false,
|
|
5199
5544
|
reason: compactionReason(data),
|
|
5200
5545
|
willRetry: false
|
|
5201
|
-
}, eventContext).catch((error) => warn("session_compact", error));
|
|
5546
|
+
}, eventContext)).then(() => {}).catch((error) => warn("session_compact", error));
|
|
5202
5547
|
}
|
|
5203
5548
|
if (type === "request/header") {
|
|
5204
5549
|
const header = event.data.header ?? {};
|
|
@@ -5253,19 +5598,17 @@ function subscribeLifecycle(ctx, state) {
|
|
|
5253
5598
|
...config,
|
|
5254
5599
|
...override?.provider === void 0 ? {} : { provider: override.provider },
|
|
5255
5600
|
...override?.model === void 0 ? {} : { model: override.model },
|
|
5256
|
-
...thinking === void 0 || thinking === "off" ? {} : { reasoningEffort: thinking }
|
|
5601
|
+
...thinking === void 0 || thinking === "off" && (currentPiModel(state, agent)?.thinkingLevelMap)?.off === null ? {} : { reasoningEffort: thinking }
|
|
5257
5602
|
};
|
|
5258
5603
|
});
|
|
5259
5604
|
cordis.effect(() => async () => {
|
|
5605
|
+
await state.projection;
|
|
5260
5606
|
const agent = state.hostAgent;
|
|
5261
|
-
if (typeof agent === "object" && agent !== null
|
|
5262
|
-
state.piShutdownAgents.add(agent);
|
|
5607
|
+
if (typeof agent === "object" && agent !== null) {
|
|
5263
5608
|
state.disposedAgents.add(agent);
|
|
5264
|
-
await
|
|
5265
|
-
type: "session_shutdown",
|
|
5266
|
-
reason: "quit"
|
|
5267
|
-
}, contextFor(ctx, state, agent, void 0));
|
|
5609
|
+
await shutdownPiSession(ctx, state, agent, "quit");
|
|
5268
5610
|
}
|
|
5611
|
+
await Promise.all(state.pendingShutdowns);
|
|
5269
5612
|
state.hostAgent = void 0;
|
|
5270
5613
|
for (const active of state.activeAgents) untrackRuntimeSession(state, active);
|
|
5271
5614
|
state.activeAgents.clear();
|
|
@@ -5296,7 +5639,7 @@ async function flushPendingSessionStarts(ctx, state) {
|
|
|
5296
5639
|
if (state.ownerAgent !== void 0 && state.projectSessionEvent !== void 0) {
|
|
5297
5640
|
const session = agentSession(state.ownerAgent);
|
|
5298
5641
|
if (session !== void 0) {
|
|
5299
|
-
const events = session
|
|
5642
|
+
const events = readSessionEvents(session);
|
|
5300
5643
|
let from = -1;
|
|
5301
5644
|
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
5302
5645
|
if (events[index].type === "turn/end") break;
|
|
@@ -5342,13 +5685,144 @@ async function restartReloadedPiSessions(ctx, state) {
|
|
|
5342
5685
|
type: "session_start",
|
|
5343
5686
|
reason: pending.get(agent) ?? "resume"
|
|
5344
5687
|
}, contextFor(ctx, state, agent, void 0)));
|
|
5688
|
+
await discoverPiResources(ctx, state, agent, pending.get(agent) ?? "resume", void 0);
|
|
5689
|
+
await refreshNativeSkillCommands(ctx, state, agent, void 0);
|
|
5345
5690
|
}
|
|
5346
5691
|
}
|
|
5692
|
+
/**
|
|
5693
|
+
* Pi's `resources_discover`, fired right after session_start (Pi:
|
|
5694
|
+
* AgentSession.extendResourcesFromExtensions): handlers return extra
|
|
5695
|
+
* `skillPaths` (and prompt/theme paths) for the session's cwd, and Pi extends
|
|
5696
|
+
* its resource loader with them. On DSH the skills registry is the host's
|
|
5697
|
+
* `skills` service, so every discovered skill ROOT is handed to the official
|
|
5698
|
+
* filesystem provider (dsh-skill-filesystem) exactly like a package's static
|
|
5699
|
+
* skill directories — once per root, per package (the registry is host-level
|
|
5700
|
+
* and provider names must stay unique). Prompt templates and TUI themes have
|
|
5701
|
+
* no DSH seat and are reported, not silently dropped. A single SKILL.md file
|
|
5702
|
+
* path (Pi accepts those too) has no root to mount and is reported likewise.
|
|
5703
|
+
*/
|
|
5704
|
+
async function refreshNativeSkillCommands(ctx, state, agent, signal) {
|
|
5705
|
+
const skills = optionalService(ctx, "skills");
|
|
5706
|
+
if (skills === void 0) {
|
|
5707
|
+
state.nativeSkillCommands = [];
|
|
5708
|
+
return;
|
|
5709
|
+
}
|
|
5710
|
+
const options = {
|
|
5711
|
+
cwd: cwdOf(agent),
|
|
5712
|
+
scope: scopeOf(ctx),
|
|
5713
|
+
signal: signal ?? new AbortController().signal
|
|
5714
|
+
};
|
|
5715
|
+
try {
|
|
5716
|
+
const entries = [];
|
|
5717
|
+
for (const summary of await skills.list(options)) {
|
|
5718
|
+
if (summary.invocation?.userInvocable === false) continue;
|
|
5719
|
+
const skill = await skills.get(String(summary.name), options);
|
|
5720
|
+
if (typeof skill?.path !== "string") continue;
|
|
5721
|
+
entries.push({
|
|
5722
|
+
name: `skill:${String(summary.name)}`,
|
|
5723
|
+
description: String(summary.description ?? ""),
|
|
5724
|
+
source: "skill",
|
|
5725
|
+
sourceInfo: {
|
|
5726
|
+
path: skill.path,
|
|
5727
|
+
source: skill.path,
|
|
5728
|
+
scope: String(summary.source ?? "").startsWith("project") ? "project" : "user",
|
|
5729
|
+
origin: "top-level",
|
|
5730
|
+
baseDir: dirname(skill.path)
|
|
5731
|
+
}
|
|
5732
|
+
});
|
|
5733
|
+
}
|
|
5734
|
+
state.nativeSkillCommands = entries;
|
|
5735
|
+
} catch (error) {
|
|
5736
|
+
state.nativeSkillCommands = [];
|
|
5737
|
+
logger(ctx).warn(`[pi2dsh] skill command metadata could not be refreshed: ${error instanceof Error ? error.message : String(error)}`);
|
|
5738
|
+
}
|
|
5739
|
+
}
|
|
5740
|
+
async function discoverPiResources(ctx, state, agent, reason, signal) {
|
|
5741
|
+
if ((state.handlers.get("resources_discover")?.length ?? 0) === 0) return;
|
|
5742
|
+
const cwd = cwdOf(agent);
|
|
5743
|
+
const results = await dispatch(state, "resources_discover", {
|
|
5744
|
+
type: "resources_discover",
|
|
5745
|
+
cwd,
|
|
5746
|
+
reason: reason === "reload" ? "reload" : "startup"
|
|
5747
|
+
}, contextFor(ctx, state, agent, signal));
|
|
5748
|
+
const roots = [];
|
|
5749
|
+
const skipped = [];
|
|
5750
|
+
let promptPaths = 0;
|
|
5751
|
+
let themePaths = 0;
|
|
5752
|
+
for (const result of results) {
|
|
5753
|
+
if (typeof result !== "object" || result === null) continue;
|
|
5754
|
+
const record = result;
|
|
5755
|
+
for (const entry of Array.isArray(record.skillPaths) ? record.skillPaths : []) {
|
|
5756
|
+
if (typeof entry !== "string" || entry.length === 0) continue;
|
|
5757
|
+
const absolute = resolve(cwd, entry);
|
|
5758
|
+
let isDirectory = false;
|
|
5759
|
+
try {
|
|
5760
|
+
isDirectory = (await stat(absolute)).isDirectory();
|
|
5761
|
+
} catch {
|
|
5762
|
+
continue;
|
|
5763
|
+
}
|
|
5764
|
+
if (!isDirectory) {
|
|
5765
|
+
skipped.push(absolute);
|
|
5766
|
+
continue;
|
|
5767
|
+
}
|
|
5768
|
+
if (!state.discoveredSkillRoots.has(absolute) && !roots.includes(absolute)) roots.push(absolute);
|
|
5769
|
+
}
|
|
5770
|
+
if (Array.isArray(record.promptPaths)) promptPaths += record.promptPaths.length;
|
|
5771
|
+
if (Array.isArray(record.themePaths)) themePaths += record.themePaths.length;
|
|
5772
|
+
}
|
|
5773
|
+
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`);
|
|
5774
|
+
if (roots.length === 0) return;
|
|
5775
|
+
if (ctx.get("skills") === void 0) {
|
|
5776
|
+
logger(ctx).warn(`[pi2dsh] ${state.packageName}: resources_discover found ${roots.length} skill root(s) but this DSH composition has no ctx.skills`);
|
|
5777
|
+
return;
|
|
5778
|
+
}
|
|
5779
|
+
for (const root of roots) state.discoveredSkillRoots.add(root);
|
|
5780
|
+
state.discoveredSkillProviders += 1;
|
|
5781
|
+
const { apply: applyFilesystemSkills } = await import("@deepseek-ai/dsh-skill-filesystem");
|
|
5782
|
+
const config = {
|
|
5783
|
+
providerName: `pi2dsh-${state.packageName.replace(/[^a-zA-Z0-9_-]+/gu, "-").replace(/^-+|-+$/gu, "")}-discovered-${state.discoveredSkillProviders}`,
|
|
5784
|
+
includeDefaultRoots: false,
|
|
5785
|
+
customSkillDirs: roots,
|
|
5786
|
+
watch: false
|
|
5787
|
+
};
|
|
5788
|
+
await ctx.plugin(Object.assign((skillCtx) => applyFilesystemSkills(skillCtx, config), { inject: ["skills"] }));
|
|
5789
|
+
logger(ctx).info(`[pi2dsh] ${state.packageName}: resources_discover mounted ${roots.length} skill root(s) as ${config.providerName}`);
|
|
5790
|
+
}
|
|
5791
|
+
/**
|
|
5792
|
+
* The Pi view of a host tool's arguments. DSH's built-in file tools carry
|
|
5793
|
+
* Claude-style argument names (`file_path`, `old_string`/`new_string`,
|
|
5794
|
+
* grep's `include`); Pi's same-named built-ins carry `path`,
|
|
5795
|
+
* `oldText`/`newText`, `glob`. Extensions read `event.input` by Pi's names —
|
|
5796
|
+
* pi-code attaches a path-scoped rule to the read of a matching `input.path`,
|
|
5797
|
+
* and its hooks translate Pi shapes to Claude's `tool_input` — so a host call
|
|
5798
|
+
* is projected into Pi's shape for the tool_call / tool_result /
|
|
5799
|
+
* tool_execution_start events. Migrated Pi tools already speak Pi and pass
|
|
5800
|
+
* through untouched; unknown keys are kept, so nothing is lost.
|
|
5801
|
+
*/
|
|
5802
|
+
const HOST_TOOL_ARGUMENT_PROJECTION = {
|
|
5803
|
+
read: { file_path: "path" },
|
|
5804
|
+
write: { file_path: "path" },
|
|
5805
|
+
edit: {
|
|
5806
|
+
file_path: "path",
|
|
5807
|
+
old_string: "oldText",
|
|
5808
|
+
new_string: "newText"
|
|
5809
|
+
},
|
|
5810
|
+
grep: { include: "glob" }
|
|
5811
|
+
};
|
|
5812
|
+
function piViewOfToolArguments(state, name, args) {
|
|
5813
|
+
if (state.tools.has(name)) return cloneJson(args);
|
|
5814
|
+
const mapping = HOST_TOOL_ARGUMENT_PROJECTION[name];
|
|
5815
|
+
if (mapping === void 0 || typeof args !== "object" || args === null || Array.isArray(args)) return cloneJson(args);
|
|
5816
|
+
const projected = {};
|
|
5817
|
+
for (const [key, value] of Object.entries(args)) projected[mapping[key] ?? key] = value;
|
|
5818
|
+
return cloneJson(projected);
|
|
5819
|
+
}
|
|
5347
5820
|
function subscribeInterceptors(ctx, state) {
|
|
5348
5821
|
const cordis = ctx;
|
|
5349
5822
|
cordis.on("tools/pre-execute", async (exec, next) => {
|
|
5350
5823
|
if (!acceptsAgent(state, exec.agent)) return next();
|
|
5351
|
-
const
|
|
5824
|
+
const original = piViewOfToolArguments(state, exec.name, exec.arguments);
|
|
5825
|
+
const input = cloneJson(original);
|
|
5352
5826
|
const event = {
|
|
5353
5827
|
type: "tool_call",
|
|
5354
5828
|
toolName: exec.name,
|
|
@@ -5356,7 +5830,7 @@ function subscribeInterceptors(ctx, state) {
|
|
|
5356
5830
|
input
|
|
5357
5831
|
};
|
|
5358
5832
|
const results = await dispatch(state, "tool_call", event, contextFor(ctx, state, exec.agent, exec.signal));
|
|
5359
|
-
if (!jsonEqual(event.input,
|
|
5833
|
+
if (!jsonEqual(event.input, original)) {
|
|
5360
5834
|
if (state.tools.has(exec.name)) state.argMutations.set(exec, cloneJson(event.input));
|
|
5361
5835
|
else return {
|
|
5362
5836
|
kind: "deny",
|
|
@@ -5402,7 +5876,7 @@ function subscribeInterceptors(ctx, state) {
|
|
|
5402
5876
|
type: "tool_result",
|
|
5403
5877
|
toolName: exec.name,
|
|
5404
5878
|
toolCallId: exec.callId,
|
|
5405
|
-
input:
|
|
5879
|
+
input: piViewOfToolArguments(state, exec.name, exec.arguments),
|
|
5406
5880
|
content: await dshToPiContent(ctx, result.content),
|
|
5407
5881
|
details: result.meta ?? null,
|
|
5408
5882
|
isError: result.isError,
|
|
@@ -5537,18 +6011,20 @@ async function runBeforeAgentStart(ctx, state, agent, signal, assembled) {
|
|
|
5537
6011
|
prompt,
|
|
5538
6012
|
...images.length > 0 ? { images } : {},
|
|
5539
6013
|
systemPrompt: assembled,
|
|
5540
|
-
systemPromptOptions:
|
|
6014
|
+
systemPromptOptions: await piSystemPromptOptions(ctx, state, agent, claimed.map((entry) => entry.message))
|
|
5541
6015
|
};
|
|
5542
|
-
const
|
|
6016
|
+
const eventContext = contextFor(ctx, state, agent, signal);
|
|
6017
|
+
const results = [];
|
|
6018
|
+
for (const handler of state.handlers.get("before_agent_start") ?? []) {
|
|
6019
|
+
const result = await runInPiRuntime(state, agent, () => handler(event, eventContext));
|
|
6020
|
+
results.push(result);
|
|
6021
|
+
if (typeof result === "object" && result !== null && typeof result.systemPrompt === "string") event.systemPrompt = result.systemPrompt;
|
|
6022
|
+
}
|
|
6023
|
+
if (event.systemPrompt !== assembled) state.turnSystemPromptOverrides.set(agent, event.systemPrompt);
|
|
5543
6024
|
const injected = [];
|
|
5544
6025
|
for (const result of results) {
|
|
5545
6026
|
if (typeof result !== "object" || result === null) continue;
|
|
5546
|
-
const
|
|
5547
|
-
if (typeof record.systemPrompt === "string") {
|
|
5548
|
-
state.turnSystemPromptOverrides.set(agent, record.systemPrompt);
|
|
5549
|
-
event.systemPrompt = record.systemPrompt;
|
|
5550
|
-
}
|
|
5551
|
-
const message = record.message;
|
|
6027
|
+
const message = result.message;
|
|
5552
6028
|
if (message === void 0) continue;
|
|
5553
6029
|
const content = message.content;
|
|
5554
6030
|
const blocks = await piToDshContent(ctx, typeof content === "string" ? [{
|
|
@@ -5566,6 +6042,79 @@ async function runBeforeAgentStart(ctx, state, agent, signal, assembled) {
|
|
|
5566
6042
|
}
|
|
5567
6043
|
if (injected.length > 0) state.pendingInjections.set(agent, injected);
|
|
5568
6044
|
}
|
|
6045
|
+
/**
|
|
6046
|
+
* Pi's `BuildSystemPromptOptions` for the before_agent_start event: the
|
|
6047
|
+
* structured view of "what the host loaded", which extensions read instead of
|
|
6048
|
+
* re-discovering resources. Two fields carry weight on DSH:
|
|
6049
|
+
* - `cwd`: the session's working directory (pi-code's context-imports
|
|
6050
|
+
* otherwise falls back to process.cwd(), the CLI's directory).
|
|
6051
|
+
* - `contextFiles`: the AGENTS.md/CLAUDE.md chain the host loaded, as
|
|
6052
|
+
* `{path, content}`. DSH delivers those as an `agent-instructions` user
|
|
6053
|
+
* message rather than system-prompt text, so the set is recovered from the
|
|
6054
|
+
* official loader itself: the baseline message records the exact
|
|
6055
|
+
* discovery/budget identity DSH used, and `loadBaselineInstructionSet`
|
|
6056
|
+
* from `@deepseek-ai/dsh-agent-instructions` re-runs that same walk. No
|
|
6057
|
+
* parallel discovery logic lives here; without the package or a baseline
|
|
6058
|
+
* the list is empty, exactly Pi's value when nothing was loaded.
|
|
6059
|
+
* (pi-code's CLAUDE.md `@import` expansion reads this list — with `{}` it
|
|
6060
|
+
* had nothing to expand and the model "found" the imported file with `read`.)
|
|
6061
|
+
*/
|
|
6062
|
+
async function piSystemPromptOptions(ctx, state, agent, claimed) {
|
|
6063
|
+
const cwd = cwdOf(agent);
|
|
6064
|
+
const baselineOf = (message) => {
|
|
6065
|
+
const source = message?.source;
|
|
6066
|
+
return source?.kind === "agent-instructions" && source.baseline === true ? source : void 0;
|
|
6067
|
+
};
|
|
6068
|
+
let baseline;
|
|
6069
|
+
for (const message of claimed) {
|
|
6070
|
+
baseline = baselineOf(message);
|
|
6071
|
+
if (baseline !== void 0) break;
|
|
6072
|
+
}
|
|
6073
|
+
if (baseline === void 0) {
|
|
6074
|
+
const events = readSessionEvents(agentSession(agent));
|
|
6075
|
+
for (let index = events.length - 1; index >= 0 && baseline === void 0; index -= 1) {
|
|
6076
|
+
const event = events[index];
|
|
6077
|
+
const data = event.data;
|
|
6078
|
+
if (event.type === "user/message") baseline = baselineOf(data);
|
|
6079
|
+
else if (event.type === "agent/inbox/spliced" && Array.isArray(data?.inserted)) for (const inserted of data.inserted) {
|
|
6080
|
+
baseline = baselineOf(inserted);
|
|
6081
|
+
if (baseline !== void 0) break;
|
|
6082
|
+
}
|
|
6083
|
+
}
|
|
6084
|
+
}
|
|
6085
|
+
try {
|
|
6086
|
+
const identity = typeof baseline?.baselineIdentity === "string" ? JSON.parse(baseline.baselineIdentity) : {};
|
|
6087
|
+
const { discoverBaselineInstructionFiles } = await import("@deepseek-ai/dsh-agent-instructions");
|
|
6088
|
+
const list = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string") ? value : void 0;
|
|
6089
|
+
const discovered = await discoverBaselineInstructionFiles({
|
|
6090
|
+
cwd,
|
|
6091
|
+
...typeof identity.projectRoot === "string" && identity.projectRoot.length > 0 ? { projectRoot: identity.projectRoot } : {},
|
|
6092
|
+
...list(identity.projectRootMarkers) !== void 0 ? { projectRootMarkers: list(identity.projectRootMarkers) } : {},
|
|
6093
|
+
...list(identity.instructionFileCandidates) !== void 0 ? { instructionFileCandidates: list(identity.instructionFileCandidates) } : {},
|
|
6094
|
+
...list(identity.localInstructionFileCandidates) !== void 0 ? { localInstructionFileCandidates: list(identity.localInstructionFileCandidates) } : {}
|
|
6095
|
+
});
|
|
6096
|
+
const maxSourceBytes = typeof identity.maxSourceBytes === "number" ? identity.maxSourceBytes : 1048576;
|
|
6097
|
+
const contextFiles = [];
|
|
6098
|
+
for (const file of discovered) try {
|
|
6099
|
+
const bytes = await readFile(file.absolutePath);
|
|
6100
|
+
if (bytes.byteLength > maxSourceBytes) continue;
|
|
6101
|
+
contextFiles.push({
|
|
6102
|
+
path: file.absolutePath,
|
|
6103
|
+
content: bytes.toString("utf8")
|
|
6104
|
+
});
|
|
6105
|
+
} catch {}
|
|
6106
|
+
return {
|
|
6107
|
+
cwd,
|
|
6108
|
+
contextFiles
|
|
6109
|
+
};
|
|
6110
|
+
} catch (error) {
|
|
6111
|
+
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)}`);
|
|
6112
|
+
return {
|
|
6113
|
+
cwd,
|
|
6114
|
+
contextFiles: []
|
|
6115
|
+
};
|
|
6116
|
+
}
|
|
6117
|
+
}
|
|
5569
6118
|
async function piShapeOfPending(ctx, message) {
|
|
5570
6119
|
const source = message.source;
|
|
5571
6120
|
const content = await dshToPiContent(ctx, message.content ?? []);
|
|
@@ -5582,7 +6131,7 @@ async function applyPiContextTransform(ctx, state, agent, signal, pending) {
|
|
|
5582
6131
|
if ((state.handlers.get("context")?.length ?? 0) === 0) return pending;
|
|
5583
6132
|
const session = agentSession(agent);
|
|
5584
6133
|
const history = [];
|
|
5585
|
-
for (const event of session
|
|
6134
|
+
for (const event of readSessionEvents(session)) {
|
|
5586
6135
|
const projected = await messageFromSessionEvent(ctx, event);
|
|
5587
6136
|
if (projected === void 0) continue;
|
|
5588
6137
|
if (event.type === "user/message") {
|
|
@@ -5630,12 +6179,18 @@ async function applyPiContextTransform(ctx, state, agent, signal, pending) {
|
|
|
5630
6179
|
rebuilt.push(original);
|
|
5631
6180
|
continue;
|
|
5632
6181
|
}
|
|
6182
|
+
if (JSON.stringify(shape.content) === JSON.stringify(projectedPending[index]?.content)) {
|
|
6183
|
+
rebuilt.push(original);
|
|
6184
|
+
continue;
|
|
6185
|
+
}
|
|
5633
6186
|
const blocks = await piToDshContent(ctx, typeof shape.content === "string" ? [{
|
|
5634
6187
|
type: "text",
|
|
5635
6188
|
text: shape.content
|
|
5636
6189
|
}] : shape.content ?? []);
|
|
6190
|
+
const files = /* @__PURE__ */ new Map();
|
|
6191
|
+
for (const block of original.content ?? []) if (block.type === "file") files.set(fileContentForPi(block.attachment, ctx).text, block);
|
|
5637
6192
|
rebuilt.push(createUserMessage({
|
|
5638
|
-
content: blocks,
|
|
6193
|
+
content: blocks.map((block) => block.type === "text" ? files.get(block.text) ?? block : block),
|
|
5639
6194
|
source: original.source ?? {
|
|
5640
6195
|
kind: "plugin",
|
|
5641
6196
|
plugin: state.messageSource
|
|
@@ -6053,6 +6608,37 @@ async function supersedeActiveLogin(state) {
|
|
|
6053
6608
|
* @param config - the provider config, whose oauth block made it loginable.
|
|
6054
6609
|
* @returns whether this call put a route in place.
|
|
6055
6610
|
*/
|
|
6611
|
+
/**
|
|
6612
|
+
* Mount-time route restore that tolerates composition order.
|
|
6613
|
+
*
|
|
6614
|
+
* The credentials service (credentials-local) can compose AFTER a
|
|
6615
|
+
* zero-package profile's host mount — probing immediately loses that race and
|
|
6616
|
+
* a stored login silently produces no route: an engine-only profile with a
|
|
6617
|
+
* valid auth.json hit MISSING_CREDENTIAL on every turn, while installing any
|
|
6618
|
+
* Pi package shifted mount timing enough to mask the bug (2026-08-30,
|
|
6619
|
+
* community/full-audit-work/zero-package-oauth-bug.md). When the service is
|
|
6620
|
+
* already there the restore stays awaited — that is mount readiness, see the
|
|
6621
|
+
* caller's comment. Otherwise the official inject seam re-runs it the moment
|
|
6622
|
+
* the service composes, the same pattern maybeProjectAuthorizationFlow uses.
|
|
6623
|
+
*/
|
|
6624
|
+
async function restoreLoggedInRouteWhenReady(ctx, state, name, config) {
|
|
6625
|
+
if (optionalService(ctx, "credentials") !== void 0) {
|
|
6626
|
+
await ensureLoggedInProviderRoute(ctx, state, name, config);
|
|
6627
|
+
return;
|
|
6628
|
+
}
|
|
6629
|
+
const shared = state.shared;
|
|
6630
|
+
shared.routeRestoreArmedIds ??= /* @__PURE__ */ new Set();
|
|
6631
|
+
if (shared.routeRestoreArmedIds.has(name)) return;
|
|
6632
|
+
const inject = ctx.inject;
|
|
6633
|
+
if (typeof inject !== "function") {
|
|
6634
|
+
await ensureLoggedInProviderRoute(ctx, state, name, config);
|
|
6635
|
+
return;
|
|
6636
|
+
}
|
|
6637
|
+
shared.routeRestoreArmedIds.add(name);
|
|
6638
|
+
inject.call(ctx, ["credentials"], (scope) => {
|
|
6639
|
+
ensureLoggedInProviderRoute(scope, state, name, config).catch((error) => logger(ctx).warn(`[pi2dsh] could not restore the route for logged-in provider ${JSON.stringify(name)}: ${error instanceof Error ? error.message : String(error)}`));
|
|
6640
|
+
});
|
|
6641
|
+
}
|
|
6056
6642
|
async function ensureLoggedInProviderRoute(ctx, state, name, config) {
|
|
6057
6643
|
if (!providerSupportsOAuth(config)) return false;
|
|
6058
6644
|
if (providerCarriesTransport(config) || state.providerRouteDisposers.has(name)) return false;
|
|
@@ -6364,11 +6950,16 @@ async function projectAuthorizationFlow(scope, state, providerId) {
|
|
|
6364
6950
|
throw error;
|
|
6365
6951
|
}
|
|
6366
6952
|
},
|
|
6367
|
-
deviceCode:
|
|
6953
|
+
deviceCode: (title, detail, signal) => {
|
|
6368
6954
|
session.notify({
|
|
6369
6955
|
message: String(title),
|
|
6370
6956
|
code: String(detail)
|
|
6371
6957
|
});
|
|
6958
|
+
return new Promise((resolve) => {
|
|
6959
|
+
if (!(signal instanceof AbortSignal)) return;
|
|
6960
|
+
if (signal.aborted) resolve();
|
|
6961
|
+
else signal.addEventListener("abort", () => resolve(), { once: true });
|
|
6962
|
+
});
|
|
6372
6963
|
}
|
|
6373
6964
|
};
|
|
6374
6965
|
const canonical = canonicalOf();
|
|
@@ -6670,7 +7261,7 @@ async function sendPiMessage(ctx, state, content, mode, sessionOverride, customT
|
|
|
6670
7261
|
source: {
|
|
6671
7262
|
kind: "plugin",
|
|
6672
7263
|
plugin: state.messageSource,
|
|
6673
|
-
...customType === void 0 ? {} : { piCustomType: customType }
|
|
7264
|
+
...customType === void 0 ? { form: "relay" } : { piCustomType: customType }
|
|
6674
7265
|
}
|
|
6675
7266
|
});
|
|
6676
7267
|
if (sessionOverride !== void 0) {
|
|
@@ -6728,16 +7319,17 @@ function combineExecSignal(options) {
|
|
|
6728
7319
|
}
|
|
6729
7320
|
};
|
|
6730
7321
|
}
|
|
6731
|
-
async function executePiCommand(service, cwd, command, args, options) {
|
|
7322
|
+
async function executePiCommand(service, cwd, command, args, options, environment) {
|
|
6732
7323
|
const operation = combineExecSignal(options);
|
|
6733
7324
|
try {
|
|
6734
7325
|
if (typeof command !== "string" || command.length === 0) throw new TypeError("Pi exec command must be a non-empty string");
|
|
6735
7326
|
if (!Array.isArray(args) || args.some((value) => typeof value !== "string")) throw new TypeError("Pi exec args must be strings");
|
|
6736
|
-
const executable = await service.resolveExecutable(command,
|
|
7327
|
+
const executable = await service.resolveExecutable(command, environment, operation.signal);
|
|
6737
7328
|
const collect = { maxBytes: 67108864 };
|
|
6738
7329
|
const handle = service.spawn({
|
|
6739
7330
|
argv: [executable, ...args],
|
|
6740
7331
|
cwd: options.cwd ?? cwd,
|
|
7332
|
+
...environment === void 0 ? {} : { env: environment },
|
|
6741
7333
|
stdio: {
|
|
6742
7334
|
stdin: "ignore",
|
|
6743
7335
|
stdout: collect,
|
|
@@ -6809,6 +7401,7 @@ function registerCommand(ctx, state, command) {
|
|
|
6809
7401
|
const live = state.commands.get(command.name) ?? command;
|
|
6810
7402
|
await runInPiRuntime(state, agent, async () => {
|
|
6811
7403
|
await ensurePiSessionStarted(ctx, state, agent, invocation.signal);
|
|
7404
|
+
await refreshNativeSkillCommands(ctx, state, agent, invocation.signal);
|
|
6812
7405
|
await live.handler(String(invocation.rawInput ?? "").trimStart(), commandContext);
|
|
6813
7406
|
});
|
|
6814
7407
|
const notices = commandContext.__notices;
|
|
@@ -6939,6 +7532,85 @@ function registerSharedProviderRoute(ctx, state, name, value) {
|
|
|
6939
7532
|
state.ownedProviderRoutes.add(name);
|
|
6940
7533
|
return route;
|
|
6941
7534
|
}
|
|
7535
|
+
async function runPiPrintRequest(ctx, state, parent, request, signal) {
|
|
7536
|
+
if (parent === void 0 || state.subagentSessionFactory === void 0) throw new Error("pi2dsh CLI: native print work requires an active DSH agent");
|
|
7537
|
+
const catalog = state.shared.childExtensions;
|
|
7538
|
+
const requestedPaths = [];
|
|
7539
|
+
for (const requested of request.extensions) {
|
|
7540
|
+
let matched;
|
|
7541
|
+
for (const path of catalog?.packageByEntryPath.keys() ?? []) if (await realpath(path).catch(() => path) === requested) {
|
|
7542
|
+
matched = path;
|
|
7543
|
+
break;
|
|
7544
|
+
}
|
|
7545
|
+
if (matched === void 0) throw new Error(`pi2dsh CLI: extension ${requested} is not installed in this DSH profile`);
|
|
7546
|
+
requestedPaths.push(matched);
|
|
7547
|
+
}
|
|
7548
|
+
const paths = [.../* @__PURE__ */ new Set([...request.noExtensions ? [] : catalog?.packageByEntryPath.keys() ?? [], ...requestedPaths])];
|
|
7549
|
+
let model;
|
|
7550
|
+
if (request.model !== void 0) {
|
|
7551
|
+
model = state.modelCatalog?.all().find((entry) => `${entry.provider}/${entry.id}` === request.model || entry.id === request.model && (request.provider === void 0 || entry.provider === request.provider));
|
|
7552
|
+
if (model === void 0) throw new Error(`pi2dsh CLI: model ${request.model} is unavailable in the DSH model directory`);
|
|
7553
|
+
} else if (request.provider !== void 0) {
|
|
7554
|
+
const inherited = currentPiModel(state, parent);
|
|
7555
|
+
if (inherited?.provider !== request.provider) throw new Error("pi2dsh CLI: --provider requires an explicit --model for a different route");
|
|
7556
|
+
model = inherited;
|
|
7557
|
+
}
|
|
7558
|
+
const created = await runInPiRuntime(state, parent, () => state.subagentSessionFactory({
|
|
7559
|
+
cwd: request.cwd,
|
|
7560
|
+
signal,
|
|
7561
|
+
...model === void 0 ? {} : { model },
|
|
7562
|
+
...request.thinking === void 0 ? {} : { thinkingLevel: request.thinking },
|
|
7563
|
+
...request.tools === void 0 ? {} : { tools: request.tools },
|
|
7564
|
+
...request.noSession ? { sessionManager: { getSessionFile: () => void 0 } } : {},
|
|
7565
|
+
label: "Background task",
|
|
7566
|
+
resourceLoader: {
|
|
7567
|
+
getExtensions: () => ({
|
|
7568
|
+
extensions: paths.map((path) => ({ path })),
|
|
7569
|
+
errors: []
|
|
7570
|
+
}),
|
|
7571
|
+
getSystemPrompt: () => request.systemPrompt,
|
|
7572
|
+
getAppendSystemPrompt: () => request.appendSystemPrompt === void 0 ? [] : [request.appendSystemPrompt]
|
|
7573
|
+
}
|
|
7574
|
+
}));
|
|
7575
|
+
if (request.noSession) capabilityLedgerOf(ctx, state).reportHostDecision({
|
|
7576
|
+
capability: "pi print --no-session",
|
|
7577
|
+
reason: "the print worker has no Pi session file; DSH retains its native child audit log.",
|
|
7578
|
+
guidance: "Native retention remains a DSH host policy.",
|
|
7579
|
+
packageName: state.packageName
|
|
7580
|
+
});
|
|
7581
|
+
const child = created.session;
|
|
7582
|
+
let rejectCancellation;
|
|
7583
|
+
const cancellation = new Promise((_resolve, reject) => {
|
|
7584
|
+
rejectCancellation = reject;
|
|
7585
|
+
});
|
|
7586
|
+
cancellation.catch(() => {});
|
|
7587
|
+
const abort = () => {
|
|
7588
|
+
child.abort();
|
|
7589
|
+
rejectCancellation(signal.reason ?? /* @__PURE__ */ new Error("Pi print worker cancelled"));
|
|
7590
|
+
};
|
|
7591
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
7592
|
+
try {
|
|
7593
|
+
const failures = [];
|
|
7594
|
+
await child.bindExtensions({ onError: (error) => {
|
|
7595
|
+
failures.push(error);
|
|
7596
|
+
} });
|
|
7597
|
+
if (failures.length > 0) throw new Error(`pi2dsh CLI: child extension mount failed: ${JSON.stringify(failures)}`);
|
|
7598
|
+
signal.throwIfAborted();
|
|
7599
|
+
await Promise.race([child.prompt(request.prompt), cancellation]);
|
|
7600
|
+
signal.throwIfAborted();
|
|
7601
|
+
const session = optionalService(ctx, "sessions")?.get(child.sessionManager?.getSessionId() ?? "");
|
|
7602
|
+
const end = readSessionEvents(session).findLast((event) => event.type === "turn/end");
|
|
7603
|
+
if ((end?.data)?.reason?.kind !== "completed") throw new Error(`pi2dsh CLI: native child did not complete: ${JSON.stringify(end?.data ?? {})}`);
|
|
7604
|
+
return {
|
|
7605
|
+
code: 0,
|
|
7606
|
+
stdout: textBlocks(child.messages.findLast((message) => message.role === "assistant")?.content).map((block) => block.text).join("\n")
|
|
7607
|
+
};
|
|
7608
|
+
} finally {
|
|
7609
|
+
signal.removeEventListener("abort", abort);
|
|
7610
|
+
if (signal.aborted) child.abort();
|
|
7611
|
+
await child.dispose();
|
|
7612
|
+
}
|
|
7613
|
+
}
|
|
6942
7614
|
function createPiApi(ctx, state) {
|
|
6943
7615
|
return {
|
|
6944
7616
|
on(event, handler) {
|
|
@@ -7030,10 +7702,22 @@ function createPiApi(ctx, state) {
|
|
|
7030
7702
|
const session = requireSession(state, "setLabel");
|
|
7031
7703
|
state.bridge.appendLabel(session.id, String(entryId), label);
|
|
7032
7704
|
},
|
|
7033
|
-
exec(command, args = [], options = {}) {
|
|
7705
|
+
async exec(command, args = [], options = {}) {
|
|
7034
7706
|
const service = optionalService(ctx, "subprocess");
|
|
7035
7707
|
if (service === void 0) unsupported("exec");
|
|
7036
|
-
|
|
7708
|
+
const parent = currentAgent(state);
|
|
7709
|
+
let local = false;
|
|
7710
|
+
try {
|
|
7711
|
+
const { LocalSubprocessRuntime } = await import("@deepseek-ai/dsh-subprocess-local");
|
|
7712
|
+
local = service instanceof LocalSubprocessRuntime;
|
|
7713
|
+
} catch {}
|
|
7714
|
+
if (!local) return executePiCommand(service, cwdOf(parent), command, args, options);
|
|
7715
|
+
return withPiPrintTransport((request, signal) => runPiPrintRequest(ctx, state, parent, request, signal), (environment) => executePiCommand(service, cwdOf(parent), command, args, options, environment)).catch((error) => ({
|
|
7716
|
+
stdout: "",
|
|
7717
|
+
stderr: error instanceof Error ? error.message : String(error),
|
|
7718
|
+
code: 1,
|
|
7719
|
+
killed: options.signal?.aborted === true
|
|
7720
|
+
}));
|
|
7037
7721
|
},
|
|
7038
7722
|
getActiveTools: () => getActiveTools(ctx, state),
|
|
7039
7723
|
getAllTools: () => {
|
|
@@ -7052,7 +7736,7 @@ function createPiApi(ctx, state) {
|
|
|
7052
7736
|
}));
|
|
7053
7737
|
},
|
|
7054
7738
|
setActiveTools: (names) => setActiveTools(ctx, state, names),
|
|
7055
|
-
getCommands: () => [...state.commands.values()].map((command) => ({
|
|
7739
|
+
getCommands: () => [...[...state.commands.values()].map((command) => ({
|
|
7056
7740
|
name: command.name,
|
|
7057
7741
|
description: command.description,
|
|
7058
7742
|
source: "extension",
|
|
@@ -7062,7 +7746,7 @@ function createPiApi(ctx, state) {
|
|
|
7062
7746
|
scope: "user",
|
|
7063
7747
|
origin: "package"
|
|
7064
7748
|
}
|
|
7065
|
-
})),
|
|
7749
|
+
})), ...state.nativeSkillCommands],
|
|
7066
7750
|
async setModel(model) {
|
|
7067
7751
|
const agent = currentAgent(state);
|
|
7068
7752
|
if (agent === void 0) return false;
|
|
@@ -7235,7 +7919,7 @@ async function loadExtensions(rootDir, manifest, api, onExtensionError, onCapabi
|
|
|
7235
7919
|
for (const extension of manifest.extensions) {
|
|
7236
7920
|
try {
|
|
7237
7921
|
const source = await readFile(join(rootDir, extension), "utf8");
|
|
7238
|
-
const infraImport = /import[^;]*?\b(
|
|
7922
|
+
const infraImport = /import[^;]*?\b(DefaultPackageManager)\b[^;]*?from\s*['"]@(?:earendil-works|mariozechner)\/pi-coding-agent['"]/su.exec(source);
|
|
7239
7923
|
if (infraImport !== null) onHostInfraReference?.(infraImport[1], extension);
|
|
7240
7924
|
} catch {}
|
|
7241
7925
|
try {
|
|
@@ -7285,6 +7969,7 @@ async function applyPiPackage(ctx, options) {
|
|
|
7285
7969
|
toolDisposers: /* @__PURE__ */ new Map(),
|
|
7286
7970
|
toolRestrictions: /* @__PURE__ */ new WeakMap(),
|
|
7287
7971
|
commands: /* @__PURE__ */ new Map(),
|
|
7972
|
+
nativeSkillCommands: [],
|
|
7288
7973
|
commandDisposers: /* @__PURE__ */ new Map(),
|
|
7289
7974
|
dshCommandOwners: /* @__PURE__ */ new Map(),
|
|
7290
7975
|
companionRoutes: shared.companionRoutes,
|
|
@@ -7293,6 +7978,8 @@ async function applyPiPackage(ctx, options) {
|
|
|
7293
7978
|
activeAgents: /* @__PURE__ */ new Set(),
|
|
7294
7979
|
hostAgent: void 0,
|
|
7295
7980
|
piShutdownAgents: /* @__PURE__ */ new WeakSet(),
|
|
7981
|
+
shutdownTasks: /* @__PURE__ */ new WeakMap(),
|
|
7982
|
+
pendingShutdowns: /* @__PURE__ */ new Set(),
|
|
7296
7983
|
disposedAgents: /* @__PURE__ */ new WeakSet(),
|
|
7297
7984
|
startedSessions: /* @__PURE__ */ new Set(),
|
|
7298
7985
|
sessionStartTasks: /* @__PURE__ */ new Map(),
|
|
@@ -7300,6 +7987,8 @@ async function applyPiPackage(ctx, options) {
|
|
|
7300
7987
|
sessionlessStartTasks: /* @__PURE__ */ new WeakMap(),
|
|
7301
7988
|
extensionsReady: false,
|
|
7302
7989
|
pendingSessionStarts: /* @__PURE__ */ new Map(),
|
|
7990
|
+
discoveredSkillRoots: /* @__PURE__ */ new Set(),
|
|
7991
|
+
discoveredSkillProviders: 0,
|
|
7303
7992
|
currentSystemPrompt: "",
|
|
7304
7993
|
messageSource: `pi2dsh:${options.manifest.package.name}`,
|
|
7305
7994
|
eventBus: sharedEventBusFor(shared, ownerAgent),
|
|
@@ -7308,7 +7997,7 @@ async function applyPiPackage(ctx, options) {
|
|
|
7308
7997
|
llmBridge: void 0,
|
|
7309
7998
|
piAiRegistry: __createPiAiRuntimeRegistry(),
|
|
7310
7999
|
subagentSessionFactory: void 0,
|
|
7311
|
-
bridge: new PiSessionBridge(),
|
|
8000
|
+
bridge: new PiSessionBridge((ref) => fileContentForPi(ref, ctx)),
|
|
7312
8001
|
theme: sharedThemeFor(shared, ownerAgent),
|
|
7313
8002
|
shortcuts: /* @__PURE__ */ new Map(),
|
|
7314
8003
|
messageRenderers: /* @__PURE__ */ new Map(),
|
|
@@ -7365,7 +8054,7 @@ async function applyPiPackage(ctx, options) {
|
|
|
7365
8054
|
}
|
|
7366
8055
|
ensureLoginCommand(ctx, state);
|
|
7367
8056
|
for (const [name, config] of [...state.providers]) try {
|
|
7368
|
-
await
|
|
8057
|
+
await restoreLoggedInRouteWhenReady(ctx, state, name, config);
|
|
7369
8058
|
} catch (error) {
|
|
7370
8059
|
logger(ctx).warn(`[pi2dsh] could not restore the route for logged-in provider ${JSON.stringify(name)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
7371
8060
|
}
|
|
@@ -7448,6 +8137,131 @@ async function applyPiPackage(ctx, options) {
|
|
|
7448
8137
|
ok: true,
|
|
7449
8138
|
note: "run /reload (or restart the session) to apply"
|
|
7450
8139
|
};
|
|
8140
|
+
},
|
|
8141
|
+
async loginState() {
|
|
8142
|
+
const flow = state.shared.browserLogin;
|
|
8143
|
+
return {
|
|
8144
|
+
providers: await Promise.all([...state.providers.entries()].filter(([, config]) => providerSupportsOAuth(config)).map(async ([id, config]) => ({
|
|
8145
|
+
id,
|
|
8146
|
+
name: config.oauth?.name ?? id,
|
|
8147
|
+
signedIn: await storedOAuthCredential(oauthStoreOf(state), id).then((c) => c !== void 0, () => false)
|
|
8148
|
+
}))),
|
|
8149
|
+
...flow === void 0 ? {} : { flow: {
|
|
8150
|
+
provider: flow.provider,
|
|
8151
|
+
providerName: flow.providerName,
|
|
8152
|
+
notices: flow.notices,
|
|
8153
|
+
...flow.question === void 0 ? {} : { question: {
|
|
8154
|
+
id: flow.question.id,
|
|
8155
|
+
kind: flow.question.kind,
|
|
8156
|
+
title: flow.question.title,
|
|
8157
|
+
...flow.question.placeholder === void 0 ? {} : { placeholder: flow.question.placeholder },
|
|
8158
|
+
...flow.question.options === void 0 ? {} : { options: flow.question.options }
|
|
8159
|
+
} },
|
|
8160
|
+
...flow.done === void 0 ? {} : { done: flow.done }
|
|
8161
|
+
} }
|
|
8162
|
+
};
|
|
8163
|
+
},
|
|
8164
|
+
async loginAction(action, provider, value) {
|
|
8165
|
+
const shared = state.shared;
|
|
8166
|
+
if (action === "dismiss") {
|
|
8167
|
+
shared.browserLogin = void 0;
|
|
8168
|
+
return { ok: true };
|
|
8169
|
+
}
|
|
8170
|
+
if (action === "answer") {
|
|
8171
|
+
const flow = shared.browserLogin;
|
|
8172
|
+
if (flow?.answer === void 0) throw new TypeError("no login question is waiting for an answer");
|
|
8173
|
+
const settle = flow.answer;
|
|
8174
|
+
flow.answer = void 0;
|
|
8175
|
+
delete flow.question;
|
|
8176
|
+
settle(value);
|
|
8177
|
+
return { ok: true };
|
|
8178
|
+
}
|
|
8179
|
+
if (action === "cancel") {
|
|
8180
|
+
const cancelled = await supersedeActiveLogin(state);
|
|
8181
|
+
const flow = shared.browserLogin;
|
|
8182
|
+
if (flow !== void 0 && flow.done === void 0) flow.done = {
|
|
8183
|
+
ok: false,
|
|
8184
|
+
summary: "Login cancelled"
|
|
8185
|
+
};
|
|
8186
|
+
return {
|
|
8187
|
+
ok: true,
|
|
8188
|
+
...cancelled === void 0 ? {} : { cancelled }
|
|
8189
|
+
};
|
|
8190
|
+
}
|
|
8191
|
+
const config = state.providers.get(provider);
|
|
8192
|
+
if (config === void 0 || !providerSupportsOAuth(config)) throw new TypeError(`unknown OAuth provider ${JSON.stringify(provider)}`);
|
|
8193
|
+
const providerName = config.oauth?.name ?? provider;
|
|
8194
|
+
if (action === "signout") {
|
|
8195
|
+
await oauthStoreOf(state).delete(provider);
|
|
8196
|
+
if (!providerCarriesTransport(config)) retireSharedProviderRoute(shared, provider);
|
|
8197
|
+
return {
|
|
8198
|
+
ok: true,
|
|
8199
|
+
signedOut: provider
|
|
8200
|
+
};
|
|
8201
|
+
}
|
|
8202
|
+
if (action !== "begin") throw new TypeError(`unknown login action ${JSON.stringify(action)}`);
|
|
8203
|
+
let questionSeq = 0;
|
|
8204
|
+
const flow = {
|
|
8205
|
+
provider,
|
|
8206
|
+
providerName,
|
|
8207
|
+
notices: []
|
|
8208
|
+
};
|
|
8209
|
+
shared.browserLogin = flow;
|
|
8210
|
+
const ask = (kind, title, extras, signal) => new Promise((resolve) => {
|
|
8211
|
+
const id = ++questionSeq;
|
|
8212
|
+
flow.question = {
|
|
8213
|
+
id,
|
|
8214
|
+
kind,
|
|
8215
|
+
title,
|
|
8216
|
+
...extras
|
|
8217
|
+
};
|
|
8218
|
+
flow.answer = (answered) => {
|
|
8219
|
+
resolve(answered);
|
|
8220
|
+
};
|
|
8221
|
+
const withdraw = () => {
|
|
8222
|
+
if (flow.question?.id !== id) return;
|
|
8223
|
+
delete flow.question;
|
|
8224
|
+
flow.answer = void 0;
|
|
8225
|
+
resolve(void 0);
|
|
8226
|
+
};
|
|
8227
|
+
if (signal !== void 0) {
|
|
8228
|
+
if (signal.aborted) withdraw();
|
|
8229
|
+
else signal.addEventListener("abort", withdraw, { once: true });
|
|
8230
|
+
}
|
|
8231
|
+
});
|
|
8232
|
+
runProviderLogin(ctx, state, provider, config, {
|
|
8233
|
+
notify: (message) => {
|
|
8234
|
+
flow.notices.push({ message: String(message) });
|
|
8235
|
+
},
|
|
8236
|
+
input: (title, placeholder, signal) => ask("input", String(title), typeof placeholder === "string" && placeholder.length > 0 ? { placeholder } : {}, signal),
|
|
8237
|
+
select: (title, options, signal) => ask("select", String(title), { options: options.map((option) => String(option)) }, signal),
|
|
8238
|
+
deviceCode: (title, detail, signal) => {
|
|
8239
|
+
const code = /`([^`]+)`/u.exec(String(detail))?.[1];
|
|
8240
|
+
flow.notices.push({
|
|
8241
|
+
message: String(title),
|
|
8242
|
+
...code === void 0 ? {} : { code }
|
|
8243
|
+
});
|
|
8244
|
+
return new Promise((resolve) => {
|
|
8245
|
+
if (!(signal instanceof AbortSignal)) return;
|
|
8246
|
+
if (signal.aborted) resolve();
|
|
8247
|
+
else signal.addEventListener("abort", () => resolve(), { once: true });
|
|
8248
|
+
});
|
|
8249
|
+
}
|
|
8250
|
+
}).then((summary) => {
|
|
8251
|
+
if (shared.browserLogin === flow) flow.done = {
|
|
8252
|
+
ok: true,
|
|
8253
|
+
summary
|
|
8254
|
+
};
|
|
8255
|
+
}, (error) => {
|
|
8256
|
+
if (shared.browserLogin === flow) flow.done = {
|
|
8257
|
+
ok: false,
|
|
8258
|
+
summary: error instanceof Error ? error.message : String(error)
|
|
8259
|
+
};
|
|
8260
|
+
});
|
|
8261
|
+
return {
|
|
8262
|
+
ok: true,
|
|
8263
|
+
started: provider
|
|
8264
|
+
};
|
|
7451
8265
|
}
|
|
7452
8266
|
});
|
|
7453
8267
|
}
|
|
@@ -7556,6 +8370,7 @@ async function applyPiPackage(ctx, options) {
|
|
|
7556
8370
|
messageSource: state.messageSource,
|
|
7557
8371
|
packageName: state.packageName,
|
|
7558
8372
|
parentAgentContext: () => currentAgent(state)?.ctx,
|
|
8373
|
+
parentAgent: () => currentAgent(state),
|
|
7559
8374
|
registerChildTools: (childCtx, tools) => {
|
|
7560
8375
|
for (const tool of tools) registerChildPiTool(childCtx, state, tool);
|
|
7561
8376
|
},
|
|
@@ -7594,7 +8409,10 @@ async function applyPiPackage(ctx, options) {
|
|
|
7594
8409
|
const persistence = optionalService(ctx, "sessionPersistence");
|
|
7595
8410
|
if (typeof persistence?.list !== "function") return void 0;
|
|
7596
8411
|
try {
|
|
7597
|
-
return !(await persistence.list()).some((
|
|
8412
|
+
return !(await persistence.list()).some((snapshot) => {
|
|
8413
|
+
const header = snapshot.header ?? snapshot;
|
|
8414
|
+
return String(header.id ?? "") === sessionId;
|
|
8415
|
+
});
|
|
7598
8416
|
} catch {
|
|
7599
8417
|
return;
|
|
7600
8418
|
}
|
|
@@ -7612,7 +8430,19 @@ async function applyPiPackage(ctx, options) {
|
|
|
7612
8430
|
adoptChildAgent: (child, thinkingLevel) => {
|
|
7613
8431
|
if (typeof child !== "object" || child === null) return;
|
|
7614
8432
|
state.childAgents.add(child);
|
|
7615
|
-
if (typeof thinkingLevel === "string" && thinkingLevel.length > 0
|
|
8433
|
+
if (typeof thinkingLevel === "string" && thinkingLevel.length > 0) {
|
|
8434
|
+
state.thinkingLevels.set(child, thinkingLevel);
|
|
8435
|
+
child.ctx?.on?.("agent/request", async (payload, next) => {
|
|
8436
|
+
const config = await next();
|
|
8437
|
+
if (payload.agent !== child) return config;
|
|
8438
|
+
const level = state.thinkingLevels.get(child);
|
|
8439
|
+
if (level === void 0 || level === "off" && (currentPiModel(state, child)?.thinkingLevelMap)?.off === null) return config;
|
|
8440
|
+
return {
|
|
8441
|
+
...config,
|
|
8442
|
+
reasoningEffort: level
|
|
8443
|
+
};
|
|
8444
|
+
});
|
|
8445
|
+
}
|
|
7616
8446
|
}
|
|
7617
8447
|
});
|
|
7618
8448
|
await registerPromptCommands(ctx, state, rootDir, options.manifest);
|
|
@@ -7625,7 +8455,7 @@ async function applyPiPackage(ctx, options) {
|
|
|
7625
8455
|
});
|
|
7626
8456
|
const onHostInfraReference = (symbol) => capabilityLedgerOf(ctx, state).reportStartupReference({
|
|
7627
8457
|
capability: symbol,
|
|
7628
|
-
reason:
|
|
8458
|
+
reason: "installing packages is owned by the DSH host and its security gates (dsh plugin add/remove).",
|
|
7629
8459
|
guidance: "",
|
|
7630
8460
|
packageName: state.packageName
|
|
7631
8461
|
});
|
|
@@ -7634,6 +8464,13 @@ async function applyPiPackage(ctx, options) {
|
|
|
7634
8464
|
await flushPendingSessionStarts(ctx, state);
|
|
7635
8465
|
const remounts = packageRemountsOf(ctx, shared);
|
|
7636
8466
|
remounts.set(state.packageName, async () => {
|
|
8467
|
+
await state.projection;
|
|
8468
|
+
const previousAgent = state.hostAgent ?? currentAgent(state);
|
|
8469
|
+
if (previousAgent !== void 0) {
|
|
8470
|
+
await shutdownPiSession(ctx, state, previousAgent, "reload");
|
|
8471
|
+
state.piShutdownAgents.delete(previousAgent);
|
|
8472
|
+
state.shutdownTasks.delete(previousAgent);
|
|
8473
|
+
}
|
|
7637
8474
|
state.extensionsReady = false;
|
|
7638
8475
|
state.handlers.clear();
|
|
7639
8476
|
for (const off of state.eventBusOffs.splice(0)) off();
|
|
@@ -7671,4 +8508,4 @@ const runtimeInternals = {
|
|
|
7671
8508
|
//#endregion
|
|
7672
8509
|
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 };
|
|
7673
8510
|
|
|
7674
|
-
//# sourceMappingURL=runtime-
|
|
8511
|
+
//# sourceMappingURL=runtime-B3N7s93Q.mjs.map
|