pi-acp 0.0.17 → 0.0.19
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 +1 -1
- package/dist/index.js +960 -85
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -5,21 +5,109 @@ import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk";
|
|
|
5
5
|
|
|
6
6
|
// src/acp/agent.ts
|
|
7
7
|
import {
|
|
8
|
-
RequestError as
|
|
8
|
+
RequestError as RequestError3
|
|
9
9
|
} from "@agentclientprotocol/sdk";
|
|
10
10
|
|
|
11
|
+
// src/acp/auth.ts
|
|
12
|
+
var PI_SETUP_METHOD_ID = "pi_terminal_login";
|
|
13
|
+
function getAuthMethods(opts) {
|
|
14
|
+
const supportsTerminalAuthMeta = opts?.supportsTerminalAuthMeta ?? true;
|
|
15
|
+
const method = {
|
|
16
|
+
id: PI_SETUP_METHOD_ID,
|
|
17
|
+
name: "Launch pi in the terminal",
|
|
18
|
+
description: "Start pi in an interactive terminal to configure API keys or login",
|
|
19
|
+
// Registry-required fields
|
|
20
|
+
type: "terminal",
|
|
21
|
+
args: ["--terminal-login"],
|
|
22
|
+
env: {}
|
|
23
|
+
};
|
|
24
|
+
if (supportsTerminalAuthMeta) {
|
|
25
|
+
const launch = terminalAuthLaunchSpec();
|
|
26
|
+
method._meta = {
|
|
27
|
+
...method._meta ?? {},
|
|
28
|
+
"terminal-auth": {
|
|
29
|
+
...launch,
|
|
30
|
+
label: "Launch pi"
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return [method];
|
|
35
|
+
}
|
|
36
|
+
function terminalAuthLaunchSpec() {
|
|
37
|
+
const argv0 = process.argv[0] || "node";
|
|
38
|
+
const argv1 = process.argv[1];
|
|
39
|
+
if (argv1 && argv0) {
|
|
40
|
+
const isNode = argv0.includes("node");
|
|
41
|
+
const isJs = argv1.endsWith(".js");
|
|
42
|
+
if (isNode && isJs) {
|
|
43
|
+
return { command: argv0, args: [argv1, "--terminal-login"] };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return { command: "pi-acp", args: ["--terminal-login"] };
|
|
47
|
+
}
|
|
48
|
+
|
|
11
49
|
// src/acp/session.ts
|
|
50
|
+
import { RequestError as RequestError2 } from "@agentclientprotocol/sdk";
|
|
51
|
+
|
|
52
|
+
// src/acp/auth-required.ts
|
|
12
53
|
import { RequestError } from "@agentclientprotocol/sdk";
|
|
54
|
+
function maybeAuthRequiredError(err) {
|
|
55
|
+
const msg = String(err?.message ?? err ?? "");
|
|
56
|
+
const s = msg.toLowerCase();
|
|
57
|
+
const patterns = [
|
|
58
|
+
"api key",
|
|
59
|
+
"apikey",
|
|
60
|
+
"missing key",
|
|
61
|
+
"no key",
|
|
62
|
+
"not configured",
|
|
63
|
+
"unauthorized",
|
|
64
|
+
"authentication",
|
|
65
|
+
"permission denied",
|
|
66
|
+
"forbidden",
|
|
67
|
+
"401",
|
|
68
|
+
"403"
|
|
69
|
+
];
|
|
70
|
+
const hit = patterns.some((p) => s.includes(p));
|
|
71
|
+
if (!hit) return null;
|
|
72
|
+
return RequestError.authRequired(
|
|
73
|
+
{
|
|
74
|
+
authMethods: getAuthMethods()
|
|
75
|
+
},
|
|
76
|
+
"Configure an API key or log in with an OAuth provider."
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/acp/session.ts
|
|
13
81
|
import { readFileSync as readFileSync3 } from "fs";
|
|
14
82
|
import { isAbsolute, resolve as resolvePath } from "path";
|
|
15
83
|
|
|
16
84
|
// src/pi-rpc/process.ts
|
|
17
85
|
import { spawn } from "child_process";
|
|
18
86
|
import * as readline from "readline";
|
|
87
|
+
var PiRpcSpawnError = class extends Error {
|
|
88
|
+
/** Underlying spawn error code, e.g. ENOENT, EACCES */
|
|
89
|
+
code;
|
|
90
|
+
constructor(message, opts) {
|
|
91
|
+
super(message);
|
|
92
|
+
this.name = "PiRpcSpawnError";
|
|
93
|
+
this.code = opts?.code;
|
|
94
|
+
this.cause = opts?.cause;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
var ESC = String.fromCharCode(27);
|
|
98
|
+
var CSI = String.fromCharCode(155);
|
|
99
|
+
var ANSI_ESCAPE_REGEX = new RegExp(
|
|
100
|
+
`[${ESC}${CSI}][[\\]()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]`,
|
|
101
|
+
"g"
|
|
102
|
+
);
|
|
103
|
+
function stripAnsi(s) {
|
|
104
|
+
return s.replace(ANSI_ESCAPE_REGEX, "");
|
|
105
|
+
}
|
|
19
106
|
var PiRpcProcess = class _PiRpcProcess {
|
|
20
107
|
child;
|
|
21
108
|
pending = /* @__PURE__ */ new Map();
|
|
22
109
|
eventHandlers = [];
|
|
110
|
+
preludeLines = [];
|
|
23
111
|
constructor(child) {
|
|
24
112
|
this.child = child;
|
|
25
113
|
const rl = readline.createInterface({ input: child.stdout });
|
|
@@ -29,6 +117,8 @@ var PiRpcProcess = class _PiRpcProcess {
|
|
|
29
117
|
try {
|
|
30
118
|
msg = JSON.parse(line);
|
|
31
119
|
} catch {
|
|
120
|
+
const cleaned = stripAnsi(String(line)).trimEnd();
|
|
121
|
+
if (cleaned) this.preludeLines.push(cleaned);
|
|
32
122
|
return;
|
|
33
123
|
}
|
|
34
124
|
if (msg?.type === "response") {
|
|
@@ -49,6 +139,10 @@ var PiRpcProcess = class _PiRpcProcess {
|
|
|
49
139
|
for (const [, p] of this.pending) p.reject(err);
|
|
50
140
|
this.pending.clear();
|
|
51
141
|
});
|
|
142
|
+
child.on("error", (err) => {
|
|
143
|
+
for (const [, p] of this.pending) p.reject(err);
|
|
144
|
+
this.pending.clear();
|
|
145
|
+
});
|
|
52
146
|
}
|
|
53
147
|
static async spawn(params) {
|
|
54
148
|
const cmd = params.piCommand ?? "pi";
|
|
@@ -59,6 +153,36 @@ var PiRpcProcess = class _PiRpcProcess {
|
|
|
59
153
|
stdio: "pipe",
|
|
60
154
|
env: process.env
|
|
61
155
|
});
|
|
156
|
+
try {
|
|
157
|
+
await new Promise((resolve3, reject) => {
|
|
158
|
+
const onSpawn = () => {
|
|
159
|
+
cleanup();
|
|
160
|
+
resolve3();
|
|
161
|
+
};
|
|
162
|
+
const onError = (err) => {
|
|
163
|
+
cleanup();
|
|
164
|
+
reject(err);
|
|
165
|
+
};
|
|
166
|
+
const cleanup = () => {
|
|
167
|
+
child.off("spawn", onSpawn);
|
|
168
|
+
child.off("error", onError);
|
|
169
|
+
};
|
|
170
|
+
child.once("spawn", onSpawn);
|
|
171
|
+
child.once("error", onError);
|
|
172
|
+
});
|
|
173
|
+
} catch (e) {
|
|
174
|
+
const code = typeof e?.code === "string" ? e.code : void 0;
|
|
175
|
+
if (code === "ENOENT") {
|
|
176
|
+
throw new PiRpcSpawnError(
|
|
177
|
+
`Could not start pi: executable not found (command: ${cmd}). Pi needs to be installed before it can run in ACP clients. Install it via \`npm install -g @mariozechner/pi-coding-agent\` or ensure \`pi\` is on your PATH. Then try again.`,
|
|
178
|
+
{ code, cause: e }
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
if (code === "EACCES") {
|
|
182
|
+
throw new PiRpcSpawnError(`Could not start pi: permission denied (command: ${cmd}).`, { code, cause: e });
|
|
183
|
+
}
|
|
184
|
+
throw new PiRpcSpawnError(`Could not start pi (command: ${cmd}).`, { code, cause: e });
|
|
185
|
+
}
|
|
62
186
|
child.stderr.on("data", () => {
|
|
63
187
|
});
|
|
64
188
|
const proc = new _PiRpcProcess(child);
|
|
@@ -80,8 +204,23 @@ var PiRpcProcess = class _PiRpcProcess {
|
|
|
80
204
|
this.eventHandlers = this.eventHandlers.filter((h) => h !== handler);
|
|
81
205
|
};
|
|
82
206
|
}
|
|
83
|
-
|
|
84
|
-
|
|
207
|
+
dispose(signal = "SIGTERM") {
|
|
208
|
+
if (this.child.killed) return;
|
|
209
|
+
try {
|
|
210
|
+
this.child.kill(signal);
|
|
211
|
+
} catch {
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Human-readable stdout lines emitted before RPC NDJSON begins (e.g. Context/Skills/Extensions info).
|
|
216
|
+
* Themes are typically noisy/less useful for ACP, so callers can filter as needed.
|
|
217
|
+
*/
|
|
218
|
+
consumePreludeLines() {
|
|
219
|
+
const lines = this.preludeLines.splice(0, this.preludeLines.length);
|
|
220
|
+
return lines;
|
|
221
|
+
}
|
|
222
|
+
async prompt(message, images = []) {
|
|
223
|
+
const res = await this.request({ type: "prompt", message, images });
|
|
85
224
|
if (!res.success) throw new Error(`pi prompt failed: ${res.error ?? JSON.stringify(res.data)}`);
|
|
86
225
|
}
|
|
87
226
|
async abort() {
|
|
@@ -144,18 +283,28 @@ var PiRpcProcess = class _PiRpcProcess {
|
|
|
144
283
|
if (!res.success) throw new Error(`pi get_messages failed: ${res.error ?? JSON.stringify(res.data)}`);
|
|
145
284
|
return res.data;
|
|
146
285
|
}
|
|
286
|
+
async getCommands() {
|
|
287
|
+
const res = await this.request({ type: "get_commands" });
|
|
288
|
+
if (!res.success) throw new Error(`pi get_commands failed: ${res.error ?? JSON.stringify(res.data)}`);
|
|
289
|
+
return res.data;
|
|
290
|
+
}
|
|
147
291
|
request(cmd) {
|
|
148
292
|
const id = crypto.randomUUID();
|
|
149
293
|
const withId = { ...cmd, id };
|
|
150
294
|
const line = JSON.stringify(withId) + "\n";
|
|
151
|
-
return new Promise((
|
|
152
|
-
this.pending.set(id, { resolve:
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
295
|
+
return new Promise((resolve3, reject) => {
|
|
296
|
+
this.pending.set(id, { resolve: resolve3, reject });
|
|
297
|
+
try {
|
|
298
|
+
this.child.stdin.write(line, (err) => {
|
|
299
|
+
if (err) {
|
|
300
|
+
this.pending.delete(id);
|
|
301
|
+
reject(err);
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
} catch (e) {
|
|
305
|
+
this.pending.delete(id);
|
|
306
|
+
reject(e);
|
|
307
|
+
}
|
|
159
308
|
});
|
|
160
309
|
}
|
|
161
310
|
};
|
|
@@ -379,10 +528,24 @@ var SessionManager = class {
|
|
|
379
528
|
return this.sessions.get(sessionId);
|
|
380
529
|
}
|
|
381
530
|
async create(params) {
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
531
|
+
let proc;
|
|
532
|
+
try {
|
|
533
|
+
proc = await PiRpcProcess.spawn({
|
|
534
|
+
cwd: params.cwd,
|
|
535
|
+
piCommand: params.piCommand
|
|
536
|
+
});
|
|
537
|
+
} catch (e) {
|
|
538
|
+
if (e instanceof PiRpcSpawnError) {
|
|
539
|
+
throw RequestError2.internalError({ code: e.code }, e.message);
|
|
540
|
+
}
|
|
541
|
+
throw e;
|
|
542
|
+
}
|
|
543
|
+
let state = null;
|
|
544
|
+
try {
|
|
545
|
+
state = await proc.getState();
|
|
546
|
+
} catch {
|
|
547
|
+
state = null;
|
|
548
|
+
}
|
|
386
549
|
const sessionId = typeof state?.sessionId === "string" ? state.sessionId : crypto.randomUUID();
|
|
387
550
|
const sessionFile = typeof state?.sessionFile === "string" ? state.sessionFile : null;
|
|
388
551
|
if (sessionFile) {
|
|
@@ -401,7 +564,7 @@ var SessionManager = class {
|
|
|
401
564
|
}
|
|
402
565
|
get(sessionId) {
|
|
403
566
|
const s = this.sessions.get(sessionId);
|
|
404
|
-
if (!s) throw
|
|
567
|
+
if (!s) throw RequestError2.invalidParams(`Unknown sessionId: ${sessionId}`);
|
|
405
568
|
return s;
|
|
406
569
|
}
|
|
407
570
|
/**
|
|
@@ -427,6 +590,8 @@ var PiAcpSession = class {
|
|
|
427
590
|
sessionId;
|
|
428
591
|
cwd;
|
|
429
592
|
mcpServers;
|
|
593
|
+
startupInfo = null;
|
|
594
|
+
startupInfoSent = false;
|
|
430
595
|
proc;
|
|
431
596
|
conn;
|
|
432
597
|
fileCommands;
|
|
@@ -459,10 +624,33 @@ var PiAcpSession = class {
|
|
|
459
624
|
this.fileCommands = opts.fileCommands ?? [];
|
|
460
625
|
this.proc.onEvent((ev) => this.handlePiEvent(ev));
|
|
461
626
|
}
|
|
462
|
-
|
|
627
|
+
setStartupInfo(text) {
|
|
628
|
+
this.startupInfo = text;
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Best-effort attempt to send startup info outside of a prompt turn.
|
|
632
|
+
* Some clients (e.g. Zed) may only render agent messages once the UI is ready;
|
|
633
|
+
* callers can invoke this shortly after session/new returns.
|
|
634
|
+
*/
|
|
635
|
+
sendStartupInfoIfPending() {
|
|
636
|
+
if (this.startupInfoSent || !this.startupInfo) return;
|
|
637
|
+
this.startupInfoSent = true;
|
|
638
|
+
this.emit({
|
|
639
|
+
sessionUpdate: "agent_message_chunk",
|
|
640
|
+
content: { type: "text", text: this.startupInfo }
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
async prompt(message, images = []) {
|
|
644
|
+
if (!this.startupInfoSent && this.startupInfo) {
|
|
645
|
+
this.startupInfoSent = true;
|
|
646
|
+
this.emit({
|
|
647
|
+
sessionUpdate: "agent_message_chunk",
|
|
648
|
+
content: { type: "text", text: this.startupInfo }
|
|
649
|
+
});
|
|
650
|
+
}
|
|
463
651
|
const expandedMessage = expandSlashCommand(message, this.fileCommands);
|
|
464
|
-
const turnPromise = new Promise((
|
|
465
|
-
const queued = { message: expandedMessage,
|
|
652
|
+
const turnPromise = new Promise((resolve3, reject) => {
|
|
653
|
+
const queued = { message: expandedMessage, images, resolve: resolve3, reject };
|
|
466
654
|
if (this.pendingTurn) {
|
|
467
655
|
this.turnQueue.push(queued);
|
|
468
656
|
this.emit({
|
|
@@ -521,10 +709,15 @@ var PiAcpSession = class {
|
|
|
521
709
|
sessionUpdate: "session_info_update",
|
|
522
710
|
_meta: { piAcp: { queueDepth: this.turnQueue.length, running: true } }
|
|
523
711
|
});
|
|
524
|
-
this.proc.prompt(t.message, t.
|
|
712
|
+
this.proc.prompt(t.message, t.images).catch((err) => {
|
|
525
713
|
void this.flushEmits().finally(() => {
|
|
526
|
-
const
|
|
527
|
-
|
|
714
|
+
const authErr = maybeAuthRequiredError(err);
|
|
715
|
+
if (authErr) {
|
|
716
|
+
this.pendingTurn?.reject(authErr);
|
|
717
|
+
} else {
|
|
718
|
+
const reason = this.cancelRequested ? "cancelled" : "error";
|
|
719
|
+
this.pendingTurn?.resolve(reason);
|
|
720
|
+
}
|
|
528
721
|
this.pendingTurn = null;
|
|
529
722
|
this.inAgentLoop = false;
|
|
530
723
|
this.emit({
|
|
@@ -728,6 +921,245 @@ function toToolKind(toolName) {
|
|
|
728
921
|
}
|
|
729
922
|
}
|
|
730
923
|
|
|
924
|
+
// src/acp/pi-sessions.ts
|
|
925
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync4, statSync, openSync, readSync, closeSync } from "fs";
|
|
926
|
+
import { homedir as homedir3 } from "os";
|
|
927
|
+
import { join as join3 } from "path";
|
|
928
|
+
var DEFAULT_TAIL_BYTES = 256 * 1024;
|
|
929
|
+
var DEFAULT_HEAD_BYTES = 64 * 1024;
|
|
930
|
+
function getPiAgentDir() {
|
|
931
|
+
return process.env.PI_CODING_AGENT_DIR ?? join3(homedir3(), ".pi", "agent");
|
|
932
|
+
}
|
|
933
|
+
function getPiSessionsDir() {
|
|
934
|
+
return join3(getPiAgentDir(), "sessions");
|
|
935
|
+
}
|
|
936
|
+
function walkJsonlFiles(dir, out) {
|
|
937
|
+
let entries;
|
|
938
|
+
try {
|
|
939
|
+
entries = readdirSync2(dir, { withFileTypes: true, encoding: "utf8" });
|
|
940
|
+
} catch {
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
for (const e of entries) {
|
|
944
|
+
const name = typeof e.name === "string" ? e.name : String(e.name);
|
|
945
|
+
const p = join3(dir, name);
|
|
946
|
+
if (e.isDirectory()) walkJsonlFiles(p, out);
|
|
947
|
+
else if (e.isFile() && name.endsWith(".jsonl")) out.push(p);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
function readFirstLine(path) {
|
|
951
|
+
const fd = openSync(path, "r");
|
|
952
|
+
try {
|
|
953
|
+
const buf = Buffer.alloc(DEFAULT_HEAD_BYTES);
|
|
954
|
+
const n = readSync(fd, buf, 0, buf.length, 0);
|
|
955
|
+
if (n <= 0) return null;
|
|
956
|
+
const s = buf.subarray(0, n).toString("utf-8");
|
|
957
|
+
const idx = s.indexOf("\n");
|
|
958
|
+
return idx === -1 ? s.trim() : s.slice(0, idx).trim();
|
|
959
|
+
} catch {
|
|
960
|
+
return null;
|
|
961
|
+
} finally {
|
|
962
|
+
try {
|
|
963
|
+
closeSync(fd);
|
|
964
|
+
} catch {
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
function readTail(path, tailBytes = DEFAULT_TAIL_BYTES) {
|
|
969
|
+
const st = statSync(path);
|
|
970
|
+
const start = Math.max(0, st.size - tailBytes);
|
|
971
|
+
const len = st.size - start;
|
|
972
|
+
const fd = openSync(path, "r");
|
|
973
|
+
try {
|
|
974
|
+
const buf = Buffer.alloc(len);
|
|
975
|
+
const n = readSync(fd, buf, 0, buf.length, start);
|
|
976
|
+
return buf.subarray(0, n).toString("utf-8");
|
|
977
|
+
} finally {
|
|
978
|
+
try {
|
|
979
|
+
closeSync(fd);
|
|
980
|
+
} catch {
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
function parseSessionHeader(firstLine) {
|
|
985
|
+
try {
|
|
986
|
+
const obj = JSON.parse(firstLine);
|
|
987
|
+
if (obj?.type !== "session") return null;
|
|
988
|
+
const sessionId = typeof obj?.id === "string" ? obj.id : null;
|
|
989
|
+
const cwd = typeof obj?.cwd === "string" ? obj.cwd : null;
|
|
990
|
+
if (!sessionId || !cwd) return null;
|
|
991
|
+
return { sessionId, cwd };
|
|
992
|
+
} catch {
|
|
993
|
+
return null;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
function pickTitleFromTail(tail) {
|
|
997
|
+
const lines = tail.split(/\r?\n/);
|
|
998
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
999
|
+
const line = lines[i].trim();
|
|
1000
|
+
if (!line) continue;
|
|
1001
|
+
try {
|
|
1002
|
+
const obj = JSON.parse(line);
|
|
1003
|
+
if (obj?.type === "session_info" && typeof obj?.name === "string" && obj.name.trim()) {
|
|
1004
|
+
return obj.name.trim();
|
|
1005
|
+
}
|
|
1006
|
+
} catch {
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
return null;
|
|
1010
|
+
}
|
|
1011
|
+
function scanSessionInfoNameFromFile(path) {
|
|
1012
|
+
const fd = openSync(path, "r");
|
|
1013
|
+
try {
|
|
1014
|
+
const buf = Buffer.alloc(256 * 1024);
|
|
1015
|
+
let leftover = "";
|
|
1016
|
+
let offset = 0;
|
|
1017
|
+
let lastName = null;
|
|
1018
|
+
while (true) {
|
|
1019
|
+
const n = readSync(fd, buf, 0, buf.length, offset);
|
|
1020
|
+
if (n <= 0) break;
|
|
1021
|
+
offset += n;
|
|
1022
|
+
const chunk = leftover + buf.subarray(0, n).toString("utf8");
|
|
1023
|
+
const lines = chunk.split(/\r?\n/);
|
|
1024
|
+
leftover = lines.pop() ?? "";
|
|
1025
|
+
for (const line0 of lines) {
|
|
1026
|
+
const line = line0.trim();
|
|
1027
|
+
if (!line) continue;
|
|
1028
|
+
try {
|
|
1029
|
+
const obj = JSON.parse(line);
|
|
1030
|
+
if (obj?.type === "session_info" && typeof obj?.name === "string" && obj.name.trim()) {
|
|
1031
|
+
lastName = obj.name.trim();
|
|
1032
|
+
}
|
|
1033
|
+
} catch {
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
const tailLine = leftover.trim();
|
|
1038
|
+
if (tailLine) {
|
|
1039
|
+
try {
|
|
1040
|
+
const obj = JSON.parse(tailLine);
|
|
1041
|
+
if (obj?.type === "session_info" && typeof obj?.name === "string" && obj.name.trim()) {
|
|
1042
|
+
lastName = obj.name.trim();
|
|
1043
|
+
}
|
|
1044
|
+
} catch {
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
return lastName;
|
|
1048
|
+
} catch {
|
|
1049
|
+
return null;
|
|
1050
|
+
} finally {
|
|
1051
|
+
try {
|
|
1052
|
+
closeSync(fd);
|
|
1053
|
+
} catch {
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
function pickUpdatedAtFromTail(tail) {
|
|
1058
|
+
const lines = tail.split(/\r?\n/);
|
|
1059
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1060
|
+
const line = lines[i].trim();
|
|
1061
|
+
if (!line) continue;
|
|
1062
|
+
try {
|
|
1063
|
+
const obj = JSON.parse(line);
|
|
1064
|
+
if (obj?.type !== "message") continue;
|
|
1065
|
+
const ts = typeof obj?.timestamp === "string" ? obj.timestamp : null;
|
|
1066
|
+
if (!ts) continue;
|
|
1067
|
+
const d = new Date(ts);
|
|
1068
|
+
if (Number.isFinite(d.getTime())) return d.toISOString();
|
|
1069
|
+
} catch {
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1073
|
+
const line = lines[i].trim();
|
|
1074
|
+
if (!line) continue;
|
|
1075
|
+
try {
|
|
1076
|
+
const obj = JSON.parse(line);
|
|
1077
|
+
const ts = typeof obj?.timestamp === "string" ? obj.timestamp : null;
|
|
1078
|
+
if (!ts) continue;
|
|
1079
|
+
const d = new Date(ts);
|
|
1080
|
+
if (Number.isFinite(d.getTime())) return d.toISOString();
|
|
1081
|
+
} catch {
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
return null;
|
|
1085
|
+
}
|
|
1086
|
+
function pickFallbackTitleFromHead(path) {
|
|
1087
|
+
try {
|
|
1088
|
+
const raw = readFileSync4(path, { encoding: "utf8" });
|
|
1089
|
+
const lines = raw.split(/\r?\n/);
|
|
1090
|
+
for (const line0 of lines) {
|
|
1091
|
+
const line = line0.trim();
|
|
1092
|
+
if (!line) continue;
|
|
1093
|
+
try {
|
|
1094
|
+
const obj = JSON.parse(line);
|
|
1095
|
+
if (obj?.type === "message" && obj?.message?.role === "user") {
|
|
1096
|
+
const content = obj?.message?.content;
|
|
1097
|
+
if (typeof content === "string") return content.slice(0, 80);
|
|
1098
|
+
if (Array.isArray(content)) {
|
|
1099
|
+
const t = content.find((c) => c?.type === "text" && typeof c?.text === "string");
|
|
1100
|
+
if (t?.text) return String(t.text).slice(0, 80);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
} catch {
|
|
1104
|
+
}
|
|
1105
|
+
if (lines.length > 2e3) break;
|
|
1106
|
+
}
|
|
1107
|
+
} catch {
|
|
1108
|
+
}
|
|
1109
|
+
return null;
|
|
1110
|
+
}
|
|
1111
|
+
function listPiSessions() {
|
|
1112
|
+
const sessionsDir = getPiSessionsDir();
|
|
1113
|
+
const files = [];
|
|
1114
|
+
walkJsonlFiles(sessionsDir, files);
|
|
1115
|
+
const items = [];
|
|
1116
|
+
for (const file of files) {
|
|
1117
|
+
const first = readFirstLine(file);
|
|
1118
|
+
if (!first) continue;
|
|
1119
|
+
const header = parseSessionHeader(first);
|
|
1120
|
+
if (!header) continue;
|
|
1121
|
+
let updatedAt = null;
|
|
1122
|
+
let title = null;
|
|
1123
|
+
try {
|
|
1124
|
+
const tail = readTail(file);
|
|
1125
|
+
title = pickTitleFromTail(tail);
|
|
1126
|
+
updatedAt = pickUpdatedAtFromTail(tail);
|
|
1127
|
+
} catch {
|
|
1128
|
+
}
|
|
1129
|
+
if (!title) {
|
|
1130
|
+
title = scanSessionInfoNameFromFile(file);
|
|
1131
|
+
}
|
|
1132
|
+
if (!updatedAt) {
|
|
1133
|
+
try {
|
|
1134
|
+
updatedAt = statSync(file).mtime.toISOString();
|
|
1135
|
+
} catch {
|
|
1136
|
+
updatedAt = null;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
if (!title) {
|
|
1140
|
+
title = pickFallbackTitleFromHead(file);
|
|
1141
|
+
}
|
|
1142
|
+
items.push({
|
|
1143
|
+
sessionId: header.sessionId,
|
|
1144
|
+
cwd: header.cwd,
|
|
1145
|
+
title,
|
|
1146
|
+
updatedAt,
|
|
1147
|
+
sessionFile: file
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
items.sort((a, b) => {
|
|
1151
|
+
const aa = a.updatedAt ?? "";
|
|
1152
|
+
const bb = b.updatedAt ?? "";
|
|
1153
|
+
return bb.localeCompare(aa);
|
|
1154
|
+
});
|
|
1155
|
+
return items;
|
|
1156
|
+
}
|
|
1157
|
+
function findPiSessionFile(sessionId) {
|
|
1158
|
+
const all = listPiSessions();
|
|
1159
|
+
const found = all.find((s) => s.sessionId === sessionId);
|
|
1160
|
+
return found?.sessionFile ?? null;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
731
1163
|
// src/acp/translate/pi-messages.ts
|
|
732
1164
|
function normalizePiMessageText(content) {
|
|
733
1165
|
if (typeof content === "string") return content;
|
|
@@ -740,13 +1172,9 @@ function normalizePiAssistantText(content) {
|
|
|
740
1172
|
}
|
|
741
1173
|
|
|
742
1174
|
// src/acp/translate/prompt.ts
|
|
743
|
-
function guessFileNameFromMime(mimeType) {
|
|
744
|
-
const ext = mimeType === "image/png" ? "png" : mimeType === "image/jpeg" ? "jpg" : mimeType === "image/webp" ? "webp" : "bin";
|
|
745
|
-
return `attachment.${ext}`;
|
|
746
|
-
}
|
|
747
1175
|
function promptToPiMessage(blocks) {
|
|
748
1176
|
let message = "";
|
|
749
|
-
const
|
|
1177
|
+
const images = [];
|
|
750
1178
|
for (const b of blocks) {
|
|
751
1179
|
switch (b.type) {
|
|
752
1180
|
case "text":
|
|
@@ -757,15 +1185,10 @@ function promptToPiMessage(blocks) {
|
|
|
757
1185
|
[Context] ${b.uri}`;
|
|
758
1186
|
break;
|
|
759
1187
|
case "image": {
|
|
760
|
-
|
|
761
|
-
const size = Buffer.byteLength(b.data, "base64");
|
|
762
|
-
attachments.push({
|
|
763
|
-
id,
|
|
1188
|
+
images.push({
|
|
764
1189
|
type: "image",
|
|
765
|
-
fileName: guessFileNameFromMime(b.mimeType),
|
|
766
1190
|
mimeType: b.mimeType,
|
|
767
|
-
|
|
768
|
-
content: b.data
|
|
1191
|
+
data: b.data
|
|
769
1192
|
});
|
|
770
1193
|
break;
|
|
771
1194
|
}
|
|
@@ -798,15 +1221,166 @@ ${r.text}`;
|
|
|
798
1221
|
break;
|
|
799
1222
|
}
|
|
800
1223
|
}
|
|
801
|
-
return { message,
|
|
1224
|
+
return { message, images };
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
// src/acp/pi-settings.ts
|
|
1228
|
+
import { existsSync as existsSync2, readFileSync as readFileSync5 } from "fs";
|
|
1229
|
+
import { homedir as homedir4 } from "os";
|
|
1230
|
+
import { join as join4, resolve as resolve2 } from "path";
|
|
1231
|
+
function isObject(x) {
|
|
1232
|
+
return Boolean(x) && typeof x === "object" && !Array.isArray(x);
|
|
1233
|
+
}
|
|
1234
|
+
function deepMerge(a, b) {
|
|
1235
|
+
const out = { ...a };
|
|
1236
|
+
for (const [k, v] of Object.entries(b)) {
|
|
1237
|
+
const av = out[k];
|
|
1238
|
+
if (isObject(av) && isObject(v)) out[k] = deepMerge(av, v);
|
|
1239
|
+
else out[k] = v;
|
|
1240
|
+
}
|
|
1241
|
+
return out;
|
|
1242
|
+
}
|
|
1243
|
+
function readJsonFile(path) {
|
|
1244
|
+
try {
|
|
1245
|
+
if (!existsSync2(path)) return {};
|
|
1246
|
+
const raw = readFileSync5(path, "utf-8");
|
|
1247
|
+
const data = JSON.parse(raw);
|
|
1248
|
+
return isObject(data) ? data : {};
|
|
1249
|
+
} catch {
|
|
1250
|
+
return {};
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
function getAgentDir() {
|
|
1254
|
+
return process.env.PI_CODING_AGENT_DIR ? resolve2(process.env.PI_CODING_AGENT_DIR) : join4(homedir4(), ".pi", "agent");
|
|
1255
|
+
}
|
|
1256
|
+
function getEnableSkillCommands(cwd) {
|
|
1257
|
+
const globalSettingsPath = join4(getAgentDir(), "settings.json");
|
|
1258
|
+
const projectSettingsPath = resolve2(cwd, ".pi", "settings.json");
|
|
1259
|
+
const global = readJsonFile(globalSettingsPath);
|
|
1260
|
+
const project = readJsonFile(projectSettingsPath);
|
|
1261
|
+
const merged = deepMerge(global, project);
|
|
1262
|
+
const direct = merged.enableSkillCommands;
|
|
1263
|
+
if (typeof direct === "boolean") return direct;
|
|
1264
|
+
const nested = isObject(merged.skills) ? merged.skills.enableSkillCommands : void 0;
|
|
1265
|
+
if (typeof nested === "boolean") return nested;
|
|
1266
|
+
return true;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
// src/acp/pi-commands.ts
|
|
1270
|
+
function describeFallback(c) {
|
|
1271
|
+
const source = typeof c.source === "string" ? c.source : "";
|
|
1272
|
+
const location = typeof c.location === "string" ? c.location : "";
|
|
1273
|
+
const parts = [];
|
|
1274
|
+
if (source) parts.push(source);
|
|
1275
|
+
if (location) parts.push(location);
|
|
1276
|
+
return parts.length ? `(${parts.join(":")})` : "(command)";
|
|
1277
|
+
}
|
|
1278
|
+
function toAvailableCommandsFromPiGetCommands(data, opts) {
|
|
1279
|
+
const enableSkillCommands = opts?.enableSkillCommands ?? true;
|
|
1280
|
+
const includeExtensionCommands = opts?.includeExtensionCommands ?? false;
|
|
1281
|
+
const root = data;
|
|
1282
|
+
const commandsRaw = Array.isArray(root?.commands) ? root.commands : Array.isArray(root?.data?.commands) ? root.data.commands : [];
|
|
1283
|
+
const out = [];
|
|
1284
|
+
for (const c of commandsRaw) {
|
|
1285
|
+
const name = typeof c?.name === "string" ? c.name.trim() : "";
|
|
1286
|
+
if (!name) continue;
|
|
1287
|
+
const source = typeof c?.source === "string" ? c.source : "";
|
|
1288
|
+
if (!includeExtensionCommands && source === "extension") continue;
|
|
1289
|
+
if (!enableSkillCommands && name.startsWith("skill:")) continue;
|
|
1290
|
+
const desc = typeof c?.description === "string" ? c.description.trim() : "";
|
|
1291
|
+
out.push({
|
|
1292
|
+
name,
|
|
1293
|
+
description: desc || describeFallback(c)
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
return { commands: out, raw: commandsRaw };
|
|
802
1297
|
}
|
|
803
1298
|
|
|
804
1299
|
// src/acp/agent.ts
|
|
805
1300
|
import { isAbsolute as isAbsolute2 } from "path";
|
|
806
|
-
import { existsSync as
|
|
807
|
-
import { join as
|
|
1301
|
+
import { existsSync as existsSync4, readFileSync as readFileSync7, realpathSync, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
|
|
1302
|
+
import { join as join6, dirname as dirname2, basename } from "path";
|
|
808
1303
|
import { spawnSync } from "child_process";
|
|
1304
|
+
|
|
1305
|
+
// src/pi-auth/status.ts
|
|
1306
|
+
import { existsSync as existsSync3, readFileSync as readFileSync6 } from "fs";
|
|
1307
|
+
import { homedir as homedir5 } from "os";
|
|
1308
|
+
import { join as join5 } from "path";
|
|
1309
|
+
function safeReadJson(path) {
|
|
1310
|
+
try {
|
|
1311
|
+
if (!existsSync3(path)) return null;
|
|
1312
|
+
const raw = readFileSync6(path, "utf-8");
|
|
1313
|
+
if (!raw.trim()) return null;
|
|
1314
|
+
return JSON.parse(raw);
|
|
1315
|
+
} catch {
|
|
1316
|
+
return null;
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
function getPiAgentDir2() {
|
|
1320
|
+
const envDir = process.env.PI_CODING_AGENT_DIR;
|
|
1321
|
+
if (envDir) {
|
|
1322
|
+
if (envDir === "~") return homedir5();
|
|
1323
|
+
if (envDir.startsWith("~/")) return homedir5() + envDir.slice(1);
|
|
1324
|
+
return envDir;
|
|
1325
|
+
}
|
|
1326
|
+
return join5(homedir5(), ".pi", "agent");
|
|
1327
|
+
}
|
|
1328
|
+
function hasAnyPiAuthConfigured() {
|
|
1329
|
+
const agentDir = getPiAgentDir2();
|
|
1330
|
+
const authPath = join5(agentDir, "auth.json");
|
|
1331
|
+
const auth = safeReadJson(authPath);
|
|
1332
|
+
if (auth && typeof auth === "object" && Object.keys(auth).length > 0) return true;
|
|
1333
|
+
const modelsPath = join5(agentDir, "models.json");
|
|
1334
|
+
const models = safeReadJson(modelsPath);
|
|
1335
|
+
const providers = models?.providers;
|
|
1336
|
+
if (providers && typeof providers === "object") {
|
|
1337
|
+
for (const p of Object.values(providers)) {
|
|
1338
|
+
if (p && typeof p === "object" && typeof p.apiKey === "string" && p.apiKey.trim()) {
|
|
1339
|
+
return true;
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
const envVars = [
|
|
1344
|
+
"OPENAI_API_KEY",
|
|
1345
|
+
"AZURE_OPENAI_API_KEY",
|
|
1346
|
+
"GEMINI_API_KEY",
|
|
1347
|
+
"GROQ_API_KEY",
|
|
1348
|
+
"CEREBRAS_API_KEY",
|
|
1349
|
+
"XAI_API_KEY",
|
|
1350
|
+
"OPENROUTER_API_KEY",
|
|
1351
|
+
"AI_GATEWAY_API_KEY",
|
|
1352
|
+
"ZAI_API_KEY",
|
|
1353
|
+
"MISTRAL_API_KEY",
|
|
1354
|
+
"MINIMAX_API_KEY",
|
|
1355
|
+
"MINIMAX_CN_API_KEY",
|
|
1356
|
+
"HF_TOKEN",
|
|
1357
|
+
"OPENCODE_API_KEY",
|
|
1358
|
+
"KIMI_API_KEY",
|
|
1359
|
+
// Copilot/github
|
|
1360
|
+
"COPILOT_GITHUB_TOKEN",
|
|
1361
|
+
"GH_TOKEN",
|
|
1362
|
+
"GITHUB_TOKEN",
|
|
1363
|
+
// Anthropic oauth
|
|
1364
|
+
"ANTHROPIC_OAUTH_TOKEN",
|
|
1365
|
+
"ANTHROPIC_API_KEY"
|
|
1366
|
+
];
|
|
1367
|
+
for (const k of envVars) {
|
|
1368
|
+
const v = process.env[k];
|
|
1369
|
+
if (typeof v === "string" && v.trim()) return true;
|
|
1370
|
+
}
|
|
1371
|
+
return false;
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
// src/acp/agent.ts
|
|
809
1375
|
import { fileURLToPath } from "url";
|
|
1376
|
+
function booleanEnv(name, defaultValue) {
|
|
1377
|
+
const raw = process.env[name];
|
|
1378
|
+
if (raw == null) return defaultValue;
|
|
1379
|
+
const v = String(raw).trim().toLowerCase();
|
|
1380
|
+
if (v === "true") return true;
|
|
1381
|
+
if (v === "false") return false;
|
|
1382
|
+
return defaultValue;
|
|
1383
|
+
}
|
|
810
1384
|
function builtinAvailableCommands() {
|
|
811
1385
|
return [
|
|
812
1386
|
{
|
|
@@ -858,8 +1432,11 @@ var PiAcpAgent = class {
|
|
|
858
1432
|
conn;
|
|
859
1433
|
sessions = new SessionManager();
|
|
860
1434
|
store = new SessionStore();
|
|
861
|
-
|
|
1435
|
+
// Remember recent session cwd and use it as the default filter.
|
|
1436
|
+
lastSessionCwd = null;
|
|
1437
|
+
constructor(conn, _config) {
|
|
862
1438
|
this.conn = conn;
|
|
1439
|
+
void _config;
|
|
863
1440
|
}
|
|
864
1441
|
async initialize(params) {
|
|
865
1442
|
const supportedVersion = 1;
|
|
@@ -871,7 +1448,11 @@ var PiAcpAgent = class {
|
|
|
871
1448
|
title: "pi ACP adapter",
|
|
872
1449
|
version: pkg.version ?? "0.0.0"
|
|
873
1450
|
},
|
|
874
|
-
|
|
1451
|
+
// Zed currently uses ClientCapabilities._meta["terminal-auth"] to decide whether to show
|
|
1452
|
+
// the "Authenticate" banner/button. If not supported, we still return the method for the registry.
|
|
1453
|
+
authMethods: getAuthMethods({
|
|
1454
|
+
supportsTerminalAuthMeta: params?.clientCapabilities?._meta?.["terminal-auth"] === true
|
|
1455
|
+
}),
|
|
875
1456
|
agentCapabilities: {
|
|
876
1457
|
loadSession: true,
|
|
877
1458
|
mcpCapabilities: { http: false, sse: false },
|
|
@@ -880,37 +1461,92 @@ var PiAcpAgent = class {
|
|
|
880
1461
|
audio: false,
|
|
881
1462
|
embeddedContext: false
|
|
882
1463
|
},
|
|
883
|
-
sessionCapabilities: {
|
|
1464
|
+
sessionCapabilities: {
|
|
1465
|
+
// **UNSTABLE** ACP capability used by Zed's codex-acp adapter.
|
|
1466
|
+
// Enables a native session picker in clients that support it.
|
|
1467
|
+
list: {}
|
|
1468
|
+
}
|
|
884
1469
|
}
|
|
885
1470
|
};
|
|
886
1471
|
}
|
|
887
1472
|
async newSession(params) {
|
|
888
1473
|
if (!isAbsolute2(params.cwd)) {
|
|
889
|
-
throw
|
|
1474
|
+
throw RequestError3.invalidParams(`cwd must be an absolute path: ${params.cwd}`);
|
|
1475
|
+
}
|
|
1476
|
+
this.lastSessionCwd = params.cwd;
|
|
1477
|
+
if (!hasAnyPiAuthConfigured()) {
|
|
1478
|
+
throw RequestError3.authRequired(
|
|
1479
|
+
{ authMethods: getAuthMethods() },
|
|
1480
|
+
"Configure an API key or log in with an OAuth provider."
|
|
1481
|
+
);
|
|
890
1482
|
}
|
|
891
1483
|
const fileCommands = loadSlashCommands(params.cwd);
|
|
1484
|
+
const enableSkillCommands = getEnableSkillCommands(params.cwd);
|
|
892
1485
|
const session = await this.sessions.create({
|
|
893
1486
|
cwd: params.cwd,
|
|
894
1487
|
mcpServers: params.mcpServers,
|
|
895
1488
|
conn: this.conn,
|
|
896
|
-
fileCommands
|
|
1489
|
+
fileCommands,
|
|
1490
|
+
piCommand: process.env.PI_ACP_PI_COMMAND
|
|
897
1491
|
});
|
|
1492
|
+
let rawModelsCount = 0;
|
|
1493
|
+
try {
|
|
1494
|
+
const data = await session.proc.getAvailableModels();
|
|
1495
|
+
rawModelsCount = Array.isArray(data?.models) ? data.models.length : 0;
|
|
1496
|
+
} catch {
|
|
1497
|
+
}
|
|
1498
|
+
if (rawModelsCount === 0) {
|
|
1499
|
+
try {
|
|
1500
|
+
session.proc.dispose?.();
|
|
1501
|
+
} catch {
|
|
1502
|
+
}
|
|
1503
|
+
throw RequestError3.authRequired(
|
|
1504
|
+
{ authMethods: getAuthMethods() },
|
|
1505
|
+
"Configure an API key or log in with an OAuth provider."
|
|
1506
|
+
);
|
|
1507
|
+
}
|
|
898
1508
|
const models = await getModelState(session.proc);
|
|
899
1509
|
const thinking = await getThinkingState(session.proc);
|
|
1510
|
+
const showStartupInfo = booleanEnv("PI_ACP_STARTUP_INFO", true);
|
|
1511
|
+
const preludeText = showStartupInfo ? buildStartupInfo({ cwd: params.cwd, fileCommands }) : "";
|
|
1512
|
+
if (preludeText) session.setStartupInfo(preludeText);
|
|
900
1513
|
const response = {
|
|
901
1514
|
sessionId: session.sessionId,
|
|
902
1515
|
models,
|
|
903
1516
|
modes: thinking,
|
|
904
|
-
_meta: {
|
|
1517
|
+
_meta: {
|
|
1518
|
+
piAcp: {
|
|
1519
|
+
startupInfo: preludeText || null
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
905
1522
|
};
|
|
1523
|
+
if (showStartupInfo) setTimeout(() => session.sendStartupInfoIfPending(), 0);
|
|
906
1524
|
setTimeout(() => {
|
|
907
|
-
void
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
1525
|
+
void (async () => {
|
|
1526
|
+
try {
|
|
1527
|
+
const pi = await session.proc.getCommands();
|
|
1528
|
+
const { commands } = toAvailableCommandsFromPiGetCommands(pi, {
|
|
1529
|
+
enableSkillCommands,
|
|
1530
|
+
includeExtensionCommands: false
|
|
1531
|
+
});
|
|
1532
|
+
await this.conn.sessionUpdate({
|
|
1533
|
+
sessionId: session.sessionId,
|
|
1534
|
+
update: {
|
|
1535
|
+
sessionUpdate: "available_commands_update",
|
|
1536
|
+
availableCommands: mergeCommands(commands, builtinAvailableCommands())
|
|
1537
|
+
}
|
|
1538
|
+
});
|
|
1539
|
+
return;
|
|
1540
|
+
} catch {
|
|
912
1541
|
}
|
|
913
|
-
|
|
1542
|
+
await this.conn.sessionUpdate({
|
|
1543
|
+
sessionId: session.sessionId,
|
|
1544
|
+
update: {
|
|
1545
|
+
sessionUpdate: "available_commands_update",
|
|
1546
|
+
availableCommands: mergeCommands(toAvailableCommands(fileCommands), builtinAvailableCommands())
|
|
1547
|
+
}
|
|
1548
|
+
});
|
|
1549
|
+
})();
|
|
914
1550
|
}, 0);
|
|
915
1551
|
return response;
|
|
916
1552
|
}
|
|
@@ -919,8 +1555,8 @@ var PiAcpAgent = class {
|
|
|
919
1555
|
}
|
|
920
1556
|
async prompt(params) {
|
|
921
1557
|
const session = this.sessions.get(params.sessionId);
|
|
922
|
-
const { message,
|
|
923
|
-
if (
|
|
1558
|
+
const { message, images } = promptToPiMessage(params.prompt);
|
|
1559
|
+
if (images.length === 0 && message.trimStart().startsWith("/")) {
|
|
924
1560
|
const trimmed = message.trim();
|
|
925
1561
|
const space = trimmed.indexOf(" ");
|
|
926
1562
|
const cmd = space === -1 ? trimmed.slice(1) : trimmed.slice(1, space);
|
|
@@ -1065,8 +1701,8 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1065
1701
|
if (piPath) {
|
|
1066
1702
|
const resolved = realpathSync(piPath);
|
|
1067
1703
|
const pkgRoot = dirname2(dirname2(resolved));
|
|
1068
|
-
const p =
|
|
1069
|
-
if (
|
|
1704
|
+
const p = join6(pkgRoot, "CHANGELOG.md");
|
|
1705
|
+
if (existsSync4(p)) return p;
|
|
1070
1706
|
}
|
|
1071
1707
|
} catch {
|
|
1072
1708
|
}
|
|
@@ -1074,8 +1710,8 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1074
1710
|
const npmRoot = spawnSync("npm", ["root", "-g"], { encoding: "utf-8" });
|
|
1075
1711
|
const root = String(npmRoot.stdout ?? "").trim();
|
|
1076
1712
|
if (root) {
|
|
1077
|
-
const p =
|
|
1078
|
-
if (
|
|
1713
|
+
const p = join6(root, "@mariozechner", "pi-coding-agent", "CHANGELOG.md");
|
|
1714
|
+
if (existsSync4(p)) return p;
|
|
1079
1715
|
}
|
|
1080
1716
|
} catch {
|
|
1081
1717
|
}
|
|
@@ -1094,7 +1730,7 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1094
1730
|
}
|
|
1095
1731
|
let text = "";
|
|
1096
1732
|
try {
|
|
1097
|
-
text =
|
|
1733
|
+
text = readFileSync7(changelogPath, "utf-8");
|
|
1098
1734
|
} catch (e) {
|
|
1099
1735
|
await this.conn.sessionUpdate({
|
|
1100
1736
|
sessionId: session.sessionId,
|
|
@@ -1120,7 +1756,7 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1120
1756
|
const state = await session.proc.getState();
|
|
1121
1757
|
const sessionFile = typeof state?.sessionFile === "string" ? state.sessionFile : null;
|
|
1122
1758
|
const messageCount = typeof state?.messageCount === "number" ? state.messageCount : 0;
|
|
1123
|
-
if (!sessionFile || messageCount === 0 || !
|
|
1759
|
+
if (!sessionFile || messageCount === 0 || !existsSync4(sessionFile)) {
|
|
1124
1760
|
await this.conn.sessionUpdate({
|
|
1125
1761
|
sessionId: session.sessionId,
|
|
1126
1762
|
update: {
|
|
@@ -1134,7 +1770,7 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1134
1770
|
return { stopReason: "end_turn" };
|
|
1135
1771
|
}
|
|
1136
1772
|
try {
|
|
1137
|
-
const raw =
|
|
1773
|
+
const raw = readFileSync7(sessionFile, "utf-8");
|
|
1138
1774
|
if (raw.trim().length === 0) {
|
|
1139
1775
|
await this.conn.sessionUpdate({
|
|
1140
1776
|
sessionId: session.sessionId,
|
|
@@ -1162,7 +1798,7 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1162
1798
|
return { stopReason: "end_turn" };
|
|
1163
1799
|
}
|
|
1164
1800
|
const safeSessionId = session.sessionId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1165
|
-
const outputPath =
|
|
1801
|
+
const outputPath = join6(session.cwd, `pi-session-${safeSessionId}.html`);
|
|
1166
1802
|
let resultPath = "";
|
|
1167
1803
|
try {
|
|
1168
1804
|
const result2 = await session.proc.exportHtml(outputPath);
|
|
@@ -1243,7 +1879,7 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1243
1879
|
return { stopReason: "end_turn" };
|
|
1244
1880
|
}
|
|
1245
1881
|
}
|
|
1246
|
-
const result = await session.prompt(message,
|
|
1882
|
+
const result = await session.prompt(message, images);
|
|
1247
1883
|
const stopReason = result === "error" ? session.wasCancelRequested() ? "cancelled" : "end_turn" : result;
|
|
1248
1884
|
return { stopReason };
|
|
1249
1885
|
}
|
|
@@ -1251,19 +1887,48 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1251
1887
|
const session = this.sessions.get(params.sessionId);
|
|
1252
1888
|
await session.cancel();
|
|
1253
1889
|
}
|
|
1890
|
+
async unstable_listSessions(params) {
|
|
1891
|
+
const all = listPiSessions();
|
|
1892
|
+
const effectiveCwd = params.cwd ?? this.lastSessionCwd;
|
|
1893
|
+
const filtered = effectiveCwd ? all.filter((s) => s.cwd === effectiveCwd) : all;
|
|
1894
|
+
const offset = params.cursor ? Number.parseInt(params.cursor, 10) : 0;
|
|
1895
|
+
const start = Number.isFinite(offset) && offset > 0 ? offset : 0;
|
|
1896
|
+
const PAGE_SIZE = 50;
|
|
1897
|
+
const page = filtered.slice(start, start + PAGE_SIZE);
|
|
1898
|
+
const sessions = page.map((s) => ({
|
|
1899
|
+
sessionId: s.sessionId,
|
|
1900
|
+
cwd: s.cwd,
|
|
1901
|
+
title: s.title,
|
|
1902
|
+
updatedAt: s.updatedAt
|
|
1903
|
+
}));
|
|
1904
|
+
const nextCursor = start + PAGE_SIZE < filtered.length ? String(start + PAGE_SIZE) : null;
|
|
1905
|
+
return { sessions, nextCursor, _meta: {} };
|
|
1906
|
+
}
|
|
1254
1907
|
async loadSession(params) {
|
|
1255
1908
|
if (!isAbsolute2(params.cwd)) {
|
|
1256
|
-
throw
|
|
1909
|
+
throw RequestError3.invalidParams(`cwd must be an absolute path: ${params.cwd}`);
|
|
1257
1910
|
}
|
|
1911
|
+
this.lastSessionCwd = params.cwd;
|
|
1258
1912
|
const stored = this.store.get(params.sessionId);
|
|
1259
|
-
|
|
1260
|
-
|
|
1913
|
+
const sessionFile = stored?.sessionFile ?? findPiSessionFile(params.sessionId);
|
|
1914
|
+
if (!sessionFile) {
|
|
1915
|
+
throw RequestError3.invalidParams(`Unknown sessionId: ${params.sessionId}`);
|
|
1916
|
+
}
|
|
1917
|
+
let proc;
|
|
1918
|
+
try {
|
|
1919
|
+
proc = await PiRpcProcess.spawn({
|
|
1920
|
+
cwd: params.cwd,
|
|
1921
|
+
sessionPath: sessionFile,
|
|
1922
|
+
piCommand: process.env.PI_ACP_PI_COMMAND
|
|
1923
|
+
});
|
|
1924
|
+
} catch (e) {
|
|
1925
|
+
if (e?.name === "PiRpcSpawnError") {
|
|
1926
|
+
throw RequestError3.internalError({ code: e?.code }, String(e?.message ?? e));
|
|
1927
|
+
}
|
|
1928
|
+
throw e;
|
|
1261
1929
|
}
|
|
1262
|
-
const proc = await PiRpcProcess.spawn({
|
|
1263
|
-
cwd: params.cwd,
|
|
1264
|
-
sessionPath: stored.sessionFile
|
|
1265
|
-
});
|
|
1266
1930
|
const fileCommands = loadSlashCommands(params.cwd);
|
|
1931
|
+
const enableSkillCommands = getEnableSkillCommands(params.cwd);
|
|
1267
1932
|
const session = this.sessions.getOrCreate(params.sessionId, {
|
|
1268
1933
|
cwd: params.cwd,
|
|
1269
1934
|
mcpServers: params.mcpServers,
|
|
@@ -1274,7 +1939,7 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1274
1939
|
this.store.upsert({
|
|
1275
1940
|
sessionId: params.sessionId,
|
|
1276
1941
|
cwd: params.cwd,
|
|
1277
|
-
sessionFile
|
|
1942
|
+
sessionFile
|
|
1278
1943
|
});
|
|
1279
1944
|
const data = await proc.getMessages();
|
|
1280
1945
|
const messages = Array.isArray(data?.messages) ? data.messages : [];
|
|
@@ -1304,22 +1969,72 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1304
1969
|
});
|
|
1305
1970
|
}
|
|
1306
1971
|
}
|
|
1972
|
+
if (role === "toolResult") {
|
|
1973
|
+
const toolName = String(m?.toolName ?? "tool");
|
|
1974
|
+
const toolCallId = String(m?.toolCallId ?? crypto.randomUUID());
|
|
1975
|
+
const isError = Boolean(m?.isError);
|
|
1976
|
+
await this.conn.sessionUpdate({
|
|
1977
|
+
sessionId: session.sessionId,
|
|
1978
|
+
update: {
|
|
1979
|
+
sessionUpdate: "tool_call",
|
|
1980
|
+
toolCallId,
|
|
1981
|
+
title: toolName,
|
|
1982
|
+
kind: toolName === "read" ? "read" : toolName === "write" || toolName === "edit" ? "edit" : "other",
|
|
1983
|
+
status: "completed",
|
|
1984
|
+
rawInput: null,
|
|
1985
|
+
rawOutput: m
|
|
1986
|
+
}
|
|
1987
|
+
});
|
|
1988
|
+
const text = toolResultToText(m);
|
|
1989
|
+
await this.conn.sessionUpdate({
|
|
1990
|
+
sessionId: session.sessionId,
|
|
1991
|
+
update: {
|
|
1992
|
+
sessionUpdate: "tool_call_update",
|
|
1993
|
+
toolCallId,
|
|
1994
|
+
status: isError ? "failed" : "completed",
|
|
1995
|
+
content: text ? [{ type: "content", content: { type: "text", text } }] : null,
|
|
1996
|
+
rawOutput: m
|
|
1997
|
+
}
|
|
1998
|
+
});
|
|
1999
|
+
}
|
|
1307
2000
|
}
|
|
1308
2001
|
const models = await getModelState(proc);
|
|
1309
2002
|
const thinking = await getThinkingState(proc);
|
|
1310
2003
|
const response = {
|
|
1311
2004
|
models,
|
|
1312
2005
|
modes: thinking,
|
|
1313
|
-
_meta: {
|
|
2006
|
+
_meta: {
|
|
2007
|
+
piAcp: {
|
|
2008
|
+
startupInfo: null
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
1314
2011
|
};
|
|
1315
2012
|
setTimeout(() => {
|
|
1316
|
-
void
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
2013
|
+
void (async () => {
|
|
2014
|
+
try {
|
|
2015
|
+
const pi = await proc.getCommands();
|
|
2016
|
+
const { commands } = toAvailableCommandsFromPiGetCommands(pi, {
|
|
2017
|
+
enableSkillCommands,
|
|
2018
|
+
includeExtensionCommands: false
|
|
2019
|
+
});
|
|
2020
|
+
await this.conn.sessionUpdate({
|
|
2021
|
+
sessionId: session.sessionId,
|
|
2022
|
+
update: {
|
|
2023
|
+
sessionUpdate: "available_commands_update",
|
|
2024
|
+
availableCommands: mergeCommands(commands, builtinAvailableCommands())
|
|
2025
|
+
}
|
|
2026
|
+
});
|
|
2027
|
+
return;
|
|
2028
|
+
} catch {
|
|
1321
2029
|
}
|
|
1322
|
-
|
|
2030
|
+
await this.conn.sessionUpdate({
|
|
2031
|
+
sessionId: session.sessionId,
|
|
2032
|
+
update: {
|
|
2033
|
+
sessionUpdate: "available_commands_update",
|
|
2034
|
+
availableCommands: mergeCommands(toAvailableCommands(fileCommands), builtinAvailableCommands())
|
|
2035
|
+
}
|
|
2036
|
+
});
|
|
2037
|
+
})();
|
|
1323
2038
|
}, 0);
|
|
1324
2039
|
return response;
|
|
1325
2040
|
}
|
|
@@ -1344,7 +2059,7 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1344
2059
|
}
|
|
1345
2060
|
}
|
|
1346
2061
|
if (!provider || !modelId) {
|
|
1347
|
-
throw
|
|
2062
|
+
throw RequestError3.invalidParams(`Unknown modelId: ${params.modelId}`);
|
|
1348
2063
|
}
|
|
1349
2064
|
await session.proc.setModel(provider, modelId);
|
|
1350
2065
|
}
|
|
@@ -1352,7 +2067,7 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1352
2067
|
const session = this.sessions.get(params.sessionId);
|
|
1353
2068
|
const mode = String(params.modeId);
|
|
1354
2069
|
if (!isThinkingLevel(mode)) {
|
|
1355
|
-
throw
|
|
2070
|
+
throw RequestError3.invalidParams(`Unknown modeId: ${mode}`);
|
|
1356
2071
|
}
|
|
1357
2072
|
await session.proc.setThinkingLevel(mode);
|
|
1358
2073
|
void this.conn.sessionUpdate({
|
|
@@ -1422,13 +2137,149 @@ async function getModelState(proc) {
|
|
|
1422
2137
|
currentModelId
|
|
1423
2138
|
};
|
|
1424
2139
|
}
|
|
2140
|
+
function isSemver(v) {
|
|
2141
|
+
return /^\d+\.\d+\.\d+(?:[-+].+)?$/.test(v);
|
|
2142
|
+
}
|
|
2143
|
+
function compareSemver(a, b) {
|
|
2144
|
+
const pa = a.split(/[.-]/).slice(0, 3).map((n) => Number(n));
|
|
2145
|
+
const pb = b.split(/[.-]/).slice(0, 3).map((n) => Number(n));
|
|
2146
|
+
for (let i = 0; i < 3; i++) {
|
|
2147
|
+
const da = pa[i] ?? 0;
|
|
2148
|
+
const db = pb[i] ?? 0;
|
|
2149
|
+
if (da > db) return 1;
|
|
2150
|
+
if (da < db) return -1;
|
|
2151
|
+
}
|
|
2152
|
+
return 0;
|
|
2153
|
+
}
|
|
2154
|
+
function buildStartupInfo(opts) {
|
|
2155
|
+
void opts.fileCommands;
|
|
2156
|
+
const md = [];
|
|
2157
|
+
let updateNotice = null;
|
|
2158
|
+
try {
|
|
2159
|
+
const piVersion = spawnSync("pi", ["--version"], { encoding: "utf-8" });
|
|
2160
|
+
const installed = String(piVersion.stdout ?? "").trim().replace(/^v/i, "");
|
|
2161
|
+
if (installed) {
|
|
2162
|
+
md.push(`pi v${installed}`);
|
|
2163
|
+
md.push("---");
|
|
2164
|
+
md.push("");
|
|
2165
|
+
try {
|
|
2166
|
+
const latestRes = spawnSync("npm", ["view", "@mariozechner/pi-coding-agent", "version"], {
|
|
2167
|
+
encoding: "utf-8",
|
|
2168
|
+
timeout: 800
|
|
2169
|
+
});
|
|
2170
|
+
const latest = String(latestRes.stdout ?? "").trim().replace(/^v/i, "");
|
|
2171
|
+
if (latest && isSemver(latest) && isSemver(installed) && compareSemver(latest, installed) > 0) {
|
|
2172
|
+
updateNotice = `New version available: v${latest} (installed v${installed}). Run: \`npm i -g @mariozechner/pi-coding-agent\``;
|
|
2173
|
+
}
|
|
2174
|
+
} catch {
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
} catch {
|
|
2178
|
+
}
|
|
2179
|
+
const addSection = (title, items) => {
|
|
2180
|
+
const cleaned = items.map((s) => s.trim()).filter(Boolean);
|
|
2181
|
+
if (!cleaned.length) return;
|
|
2182
|
+
md.push(`## ${title}`);
|
|
2183
|
+
for (const item of cleaned) md.push(`- ${item}`);
|
|
2184
|
+
md.push("");
|
|
2185
|
+
};
|
|
2186
|
+
const contextItems = [];
|
|
2187
|
+
const contextPath = join6(opts.cwd, "AGENTS.md");
|
|
2188
|
+
if (existsSync4(contextPath)) contextItems.push(contextPath);
|
|
2189
|
+
addSection("Context", contextItems);
|
|
2190
|
+
const skillsItems = [];
|
|
2191
|
+
const pushSkillFromRoot = (root) => {
|
|
2192
|
+
try {
|
|
2193
|
+
for (const e of readdirSync3(root)) {
|
|
2194
|
+
const p = join6(root, e);
|
|
2195
|
+
try {
|
|
2196
|
+
const st = statSync2(p);
|
|
2197
|
+
if (st.isFile() && e.toLowerCase().endsWith(".md")) {
|
|
2198
|
+
skillsItems.push(p);
|
|
2199
|
+
}
|
|
2200
|
+
} catch {
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
const stack = [root];
|
|
2204
|
+
while (stack.length) {
|
|
2205
|
+
const dir = stack.pop();
|
|
2206
|
+
let entries = [];
|
|
2207
|
+
try {
|
|
2208
|
+
entries = readdirSync3(dir);
|
|
2209
|
+
} catch {
|
|
2210
|
+
continue;
|
|
2211
|
+
}
|
|
2212
|
+
for (const name of entries) {
|
|
2213
|
+
if (name === "node_modules" || name === ".git") continue;
|
|
2214
|
+
const p = join6(dir, name);
|
|
2215
|
+
let st;
|
|
2216
|
+
try {
|
|
2217
|
+
st = statSync2(p);
|
|
2218
|
+
} catch {
|
|
2219
|
+
continue;
|
|
2220
|
+
}
|
|
2221
|
+
if (st.isDirectory()) {
|
|
2222
|
+
stack.push(p);
|
|
2223
|
+
} else if (st.isFile() && name === "SKILL.md") {
|
|
2224
|
+
skillsItems.push(p);
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
} catch {
|
|
2229
|
+
}
|
|
2230
|
+
};
|
|
2231
|
+
const globalSkillsDir = join6(getAgentDir(), "skills");
|
|
2232
|
+
pushSkillFromRoot(globalSkillsDir);
|
|
2233
|
+
const legacyAgentsSkillsDir = join6(process.env.HOME ?? "", ".agents", "skills");
|
|
2234
|
+
pushSkillFromRoot(legacyAgentsSkillsDir);
|
|
2235
|
+
const projectSkillsDir = join6(opts.cwd, ".pi", "skills");
|
|
2236
|
+
pushSkillFromRoot(projectSkillsDir);
|
|
2237
|
+
addSection("Skills", skillsItems);
|
|
2238
|
+
const promptsItems = [];
|
|
2239
|
+
const promptsDir = join6(process.env.HOME ?? "", ".pi", "agent", "prompts");
|
|
2240
|
+
try {
|
|
2241
|
+
const prompts = readdirSync3(promptsDir).filter((f) => f.endsWith(".md"));
|
|
2242
|
+
for (const f of prompts) promptsItems.push(`/${basename(f, ".md")}`);
|
|
2243
|
+
} catch {
|
|
2244
|
+
}
|
|
2245
|
+
addSection("Prompts", promptsItems);
|
|
2246
|
+
const extItems = [];
|
|
2247
|
+
const extDir = join6(process.env.HOME ?? "", ".pi", "agent", "extensions");
|
|
2248
|
+
try {
|
|
2249
|
+
const exts = readdirSync3(extDir).filter((f) => f.endsWith(".ts") || f.endsWith(".js"));
|
|
2250
|
+
for (const f of exts) extItems.push(join6(extDir, f));
|
|
2251
|
+
} catch {
|
|
2252
|
+
}
|
|
2253
|
+
try {
|
|
2254
|
+
const settingsPath = join6(process.env.HOME ?? "", ".pi", "agent", "settings.json");
|
|
2255
|
+
const settings = JSON.parse(readFileSync7(settingsPath, "utf-8"));
|
|
2256
|
+
const pkgs = Array.isArray(settings?.packages) ? settings.packages : [];
|
|
2257
|
+
for (const pkg2 of pkgs) {
|
|
2258
|
+
const s = String(pkg2);
|
|
2259
|
+
if (s.startsWith("npm:")) {
|
|
2260
|
+
extItems.push(`${s}
|
|
2261
|
+
- index.ts`);
|
|
2262
|
+
} else {
|
|
2263
|
+
extItems.push(s);
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
} catch {
|
|
2267
|
+
}
|
|
2268
|
+
addSection("Extensions", extItems);
|
|
2269
|
+
if (updateNotice) {
|
|
2270
|
+
md.push("---");
|
|
2271
|
+
md.push(updateNotice);
|
|
2272
|
+
md.push("");
|
|
2273
|
+
}
|
|
2274
|
+
return md.join("\n").trim() + "\n";
|
|
2275
|
+
}
|
|
1425
2276
|
function readNearestPackageJson(metaUrl) {
|
|
1426
2277
|
try {
|
|
1427
2278
|
let dir = dirname2(fileURLToPath(metaUrl));
|
|
1428
2279
|
for (let i = 0; i < 6; i++) {
|
|
1429
|
-
const p =
|
|
1430
|
-
if (
|
|
1431
|
-
const json = JSON.parse(
|
|
2280
|
+
const p = join6(dir, "package.json");
|
|
2281
|
+
if (existsSync4(p)) {
|
|
2282
|
+
const json = JSON.parse(readFileSync7(p, "utf-8"));
|
|
1432
2283
|
return { name: json?.name, version: json?.version };
|
|
1433
2284
|
}
|
|
1434
2285
|
dir = dirname2(dir);
|
|
@@ -1439,13 +2290,31 @@ function readNearestPackageJson(metaUrl) {
|
|
|
1439
2290
|
}
|
|
1440
2291
|
|
|
1441
2292
|
// src/index.ts
|
|
2293
|
+
if (process.argv.includes("--terminal-login")) {
|
|
2294
|
+
const { spawnSync: spawnSync2 } = await import("child_process");
|
|
2295
|
+
const cmd = process.env.PI_ACP_PI_COMMAND ?? "pi";
|
|
2296
|
+
const res = spawnSync2(cmd, [], { stdio: "inherit", env: process.env });
|
|
2297
|
+
if (res.error && res.error.code === "ENOENT") {
|
|
2298
|
+
process.stderr.write(
|
|
2299
|
+
`pi-acp: could not start pi (command not found: ${cmd}). Install it via \`npm install -g @mariozechner/pi-coding-agent\` or ensure \`pi\` is on your PATH.
|
|
2300
|
+
`
|
|
2301
|
+
);
|
|
2302
|
+
process.exit(1);
|
|
2303
|
+
}
|
|
2304
|
+
process.exit(typeof res.status === "number" ? res.status : 1);
|
|
2305
|
+
}
|
|
1442
2306
|
var input = new WritableStream({
|
|
1443
2307
|
write(chunk) {
|
|
1444
|
-
return new Promise((
|
|
1445
|
-
process.stdout.
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
2308
|
+
return new Promise((resolve3) => {
|
|
2309
|
+
if (process.stdout.destroyed || !process.stdout.writable) return resolve3();
|
|
2310
|
+
try {
|
|
2311
|
+
process.stdout.write(chunk, (err) => {
|
|
2312
|
+
void err;
|
|
2313
|
+
resolve3();
|
|
2314
|
+
});
|
|
2315
|
+
} catch {
|
|
2316
|
+
resolve3();
|
|
2317
|
+
}
|
|
1449
2318
|
});
|
|
1450
2319
|
}
|
|
1451
2320
|
});
|
|
@@ -1461,4 +2330,10 @@ new AgentSideConnection((conn) => new PiAcpAgent(conn), stream);
|
|
|
1461
2330
|
process.stdin.resume();
|
|
1462
2331
|
process.on("SIGINT", () => process.exit(0));
|
|
1463
2332
|
process.on("SIGTERM", () => process.exit(0));
|
|
2333
|
+
process.stdout.on("error", () => {
|
|
2334
|
+
try {
|
|
2335
|
+
process.exit(0);
|
|
2336
|
+
} catch {
|
|
2337
|
+
}
|
|
2338
|
+
});
|
|
1464
2339
|
//# sourceMappingURL=index.js.map
|