pi-acp 0.0.15 → 0.0.16
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 +17 -7
- package/dist/index.js +631 -68
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,17 +5,95 @@ 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
|
+
};
|
|
19
97
|
function stripAnsi(s) {
|
|
20
98
|
return s.replace(/[\u001B\u009B][[\]()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "");
|
|
21
99
|
}
|
|
@@ -55,6 +133,10 @@ var PiRpcProcess = class _PiRpcProcess {
|
|
|
55
133
|
for (const [, p] of this.pending) p.reject(err);
|
|
56
134
|
this.pending.clear();
|
|
57
135
|
});
|
|
136
|
+
child.on("error", (err) => {
|
|
137
|
+
for (const [, p] of this.pending) p.reject(err);
|
|
138
|
+
this.pending.clear();
|
|
139
|
+
});
|
|
58
140
|
}
|
|
59
141
|
static async spawn(params) {
|
|
60
142
|
const cmd = params.piCommand ?? "pi";
|
|
@@ -65,6 +147,36 @@ var PiRpcProcess = class _PiRpcProcess {
|
|
|
65
147
|
stdio: "pipe",
|
|
66
148
|
env: process.env
|
|
67
149
|
});
|
|
150
|
+
try {
|
|
151
|
+
await new Promise((resolve2, reject) => {
|
|
152
|
+
const onSpawn = () => {
|
|
153
|
+
cleanup();
|
|
154
|
+
resolve2();
|
|
155
|
+
};
|
|
156
|
+
const onError = (err) => {
|
|
157
|
+
cleanup();
|
|
158
|
+
reject(err);
|
|
159
|
+
};
|
|
160
|
+
const cleanup = () => {
|
|
161
|
+
child.off("spawn", onSpawn);
|
|
162
|
+
child.off("error", onError);
|
|
163
|
+
};
|
|
164
|
+
child.once("spawn", onSpawn);
|
|
165
|
+
child.once("error", onError);
|
|
166
|
+
});
|
|
167
|
+
} catch (e) {
|
|
168
|
+
const code = typeof e?.code === "string" ? e.code : void 0;
|
|
169
|
+
if (code === "ENOENT") {
|
|
170
|
+
throw new PiRpcSpawnError(
|
|
171
|
+
`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.`,
|
|
172
|
+
{ code, cause: e }
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
if (code === "EACCES") {
|
|
176
|
+
throw new PiRpcSpawnError(`Could not start pi: permission denied (command: ${cmd}).`, { code, cause: e });
|
|
177
|
+
}
|
|
178
|
+
throw new PiRpcSpawnError(`Could not start pi (command: ${cmd}).`, { code, cause: e });
|
|
179
|
+
}
|
|
68
180
|
child.stderr.on("data", () => {
|
|
69
181
|
});
|
|
70
182
|
const proc = new _PiRpcProcess(child);
|
|
@@ -86,6 +198,13 @@ var PiRpcProcess = class _PiRpcProcess {
|
|
|
86
198
|
this.eventHandlers = this.eventHandlers.filter((h) => h !== handler);
|
|
87
199
|
};
|
|
88
200
|
}
|
|
201
|
+
dispose(signal = "SIGTERM") {
|
|
202
|
+
if (this.child.killed) return;
|
|
203
|
+
try {
|
|
204
|
+
this.child.kill(signal);
|
|
205
|
+
} catch {
|
|
206
|
+
}
|
|
207
|
+
}
|
|
89
208
|
/**
|
|
90
209
|
* Human-readable stdout lines emitted before RPC NDJSON begins (e.g. Context/Skills/Extensions info).
|
|
91
210
|
* Themes are typically noisy/less useful for ACP, so callers can filter as needed.
|
|
@@ -164,12 +283,17 @@ var PiRpcProcess = class _PiRpcProcess {
|
|
|
164
283
|
const line = JSON.stringify(withId) + "\n";
|
|
165
284
|
return new Promise((resolve2, reject) => {
|
|
166
285
|
this.pending.set(id, { resolve: resolve2, reject });
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
286
|
+
try {
|
|
287
|
+
this.child.stdin.write(line, (err) => {
|
|
288
|
+
if (err) {
|
|
289
|
+
this.pending.delete(id);
|
|
290
|
+
reject(err);
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
} catch (e) {
|
|
294
|
+
this.pending.delete(id);
|
|
295
|
+
reject(e);
|
|
296
|
+
}
|
|
173
297
|
});
|
|
174
298
|
}
|
|
175
299
|
};
|
|
@@ -393,10 +517,24 @@ var SessionManager = class {
|
|
|
393
517
|
return this.sessions.get(sessionId);
|
|
394
518
|
}
|
|
395
519
|
async create(params) {
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
520
|
+
let proc;
|
|
521
|
+
try {
|
|
522
|
+
proc = await PiRpcProcess.spawn({
|
|
523
|
+
cwd: params.cwd,
|
|
524
|
+
piCommand: params.piCommand
|
|
525
|
+
});
|
|
526
|
+
} catch (e) {
|
|
527
|
+
if (e instanceof PiRpcSpawnError) {
|
|
528
|
+
throw RequestError2.internalError({ code: e.code }, e.message);
|
|
529
|
+
}
|
|
530
|
+
throw e;
|
|
531
|
+
}
|
|
532
|
+
let state = null;
|
|
533
|
+
try {
|
|
534
|
+
state = await proc.getState();
|
|
535
|
+
} catch {
|
|
536
|
+
state = null;
|
|
537
|
+
}
|
|
400
538
|
const sessionId = typeof state?.sessionId === "string" ? state.sessionId : crypto.randomUUID();
|
|
401
539
|
const sessionFile = typeof state?.sessionFile === "string" ? state.sessionFile : null;
|
|
402
540
|
if (sessionFile) {
|
|
@@ -415,7 +553,7 @@ var SessionManager = class {
|
|
|
415
553
|
}
|
|
416
554
|
get(sessionId) {
|
|
417
555
|
const s = this.sessions.get(sessionId);
|
|
418
|
-
if (!s) throw
|
|
556
|
+
if (!s) throw RequestError2.invalidParams(`Unknown sessionId: ${sessionId}`);
|
|
419
557
|
return s;
|
|
420
558
|
}
|
|
421
559
|
/**
|
|
@@ -562,8 +700,13 @@ var PiAcpSession = class {
|
|
|
562
700
|
});
|
|
563
701
|
this.proc.prompt(t.message, t.attachments).catch((err) => {
|
|
564
702
|
void this.flushEmits().finally(() => {
|
|
565
|
-
const
|
|
566
|
-
|
|
703
|
+
const authErr = maybeAuthRequiredError(err);
|
|
704
|
+
if (authErr) {
|
|
705
|
+
this.pendingTurn?.reject(authErr);
|
|
706
|
+
} else {
|
|
707
|
+
const reason = this.cancelRequested ? "cancelled" : "error";
|
|
708
|
+
this.pendingTurn?.resolve(reason);
|
|
709
|
+
}
|
|
567
710
|
this.pendingTurn = null;
|
|
568
711
|
this.inAgentLoop = false;
|
|
569
712
|
this.emit({
|
|
@@ -767,6 +910,245 @@ function toToolKind(toolName) {
|
|
|
767
910
|
}
|
|
768
911
|
}
|
|
769
912
|
|
|
913
|
+
// src/acp/pi-sessions.ts
|
|
914
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync4, statSync, openSync, readSync, closeSync } from "fs";
|
|
915
|
+
import { homedir as homedir3 } from "os";
|
|
916
|
+
import { join as join3 } from "path";
|
|
917
|
+
var DEFAULT_TAIL_BYTES = 256 * 1024;
|
|
918
|
+
var DEFAULT_HEAD_BYTES = 64 * 1024;
|
|
919
|
+
function getPiAgentDir() {
|
|
920
|
+
return process.env.PI_CODING_AGENT_DIR ?? join3(homedir3(), ".pi", "agent");
|
|
921
|
+
}
|
|
922
|
+
function getPiSessionsDir() {
|
|
923
|
+
return join3(getPiAgentDir(), "sessions");
|
|
924
|
+
}
|
|
925
|
+
function walkJsonlFiles(dir, out) {
|
|
926
|
+
let entries;
|
|
927
|
+
try {
|
|
928
|
+
entries = readdirSync2(dir, { withFileTypes: true, encoding: "utf8" });
|
|
929
|
+
} catch {
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
for (const e of entries) {
|
|
933
|
+
const name = typeof e.name === "string" ? e.name : String(e.name);
|
|
934
|
+
const p = join3(dir, name);
|
|
935
|
+
if (e.isDirectory()) walkJsonlFiles(p, out);
|
|
936
|
+
else if (e.isFile() && name.endsWith(".jsonl")) out.push(p);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
function readFirstLine(path) {
|
|
940
|
+
const fd = openSync(path, "r");
|
|
941
|
+
try {
|
|
942
|
+
const buf = Buffer.alloc(DEFAULT_HEAD_BYTES);
|
|
943
|
+
const n = readSync(fd, buf, 0, buf.length, 0);
|
|
944
|
+
if (n <= 0) return null;
|
|
945
|
+
const s = buf.subarray(0, n).toString("utf-8");
|
|
946
|
+
const idx = s.indexOf("\n");
|
|
947
|
+
return idx === -1 ? s.trim() : s.slice(0, idx).trim();
|
|
948
|
+
} catch {
|
|
949
|
+
return null;
|
|
950
|
+
} finally {
|
|
951
|
+
try {
|
|
952
|
+
closeSync(fd);
|
|
953
|
+
} catch {
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
function readTail(path, tailBytes = DEFAULT_TAIL_BYTES) {
|
|
958
|
+
const st = statSync(path);
|
|
959
|
+
const start = Math.max(0, st.size - tailBytes);
|
|
960
|
+
const len = st.size - start;
|
|
961
|
+
const fd = openSync(path, "r");
|
|
962
|
+
try {
|
|
963
|
+
const buf = Buffer.alloc(len);
|
|
964
|
+
const n = readSync(fd, buf, 0, buf.length, start);
|
|
965
|
+
return buf.subarray(0, n).toString("utf-8");
|
|
966
|
+
} finally {
|
|
967
|
+
try {
|
|
968
|
+
closeSync(fd);
|
|
969
|
+
} catch {
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
function parseSessionHeader(firstLine) {
|
|
974
|
+
try {
|
|
975
|
+
const obj = JSON.parse(firstLine);
|
|
976
|
+
if (obj?.type !== "session") return null;
|
|
977
|
+
const sessionId = typeof obj?.id === "string" ? obj.id : null;
|
|
978
|
+
const cwd = typeof obj?.cwd === "string" ? obj.cwd : null;
|
|
979
|
+
if (!sessionId || !cwd) return null;
|
|
980
|
+
return { sessionId, cwd };
|
|
981
|
+
} catch {
|
|
982
|
+
return null;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
function pickTitleFromTail(tail) {
|
|
986
|
+
const lines = tail.split(/\r?\n/);
|
|
987
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
988
|
+
const line = lines[i].trim();
|
|
989
|
+
if (!line) continue;
|
|
990
|
+
try {
|
|
991
|
+
const obj = JSON.parse(line);
|
|
992
|
+
if (obj?.type === "session_info" && typeof obj?.name === "string" && obj.name.trim()) {
|
|
993
|
+
return obj.name.trim();
|
|
994
|
+
}
|
|
995
|
+
} catch {
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
return null;
|
|
999
|
+
}
|
|
1000
|
+
function scanSessionInfoNameFromFile(path) {
|
|
1001
|
+
const fd = openSync(path, "r");
|
|
1002
|
+
try {
|
|
1003
|
+
const buf = Buffer.alloc(256 * 1024);
|
|
1004
|
+
let leftover = "";
|
|
1005
|
+
let offset = 0;
|
|
1006
|
+
let lastName = null;
|
|
1007
|
+
while (true) {
|
|
1008
|
+
const n = readSync(fd, buf, 0, buf.length, offset);
|
|
1009
|
+
if (n <= 0) break;
|
|
1010
|
+
offset += n;
|
|
1011
|
+
const chunk = leftover + buf.subarray(0, n).toString("utf8");
|
|
1012
|
+
const lines = chunk.split(/\r?\n/);
|
|
1013
|
+
leftover = lines.pop() ?? "";
|
|
1014
|
+
for (const line0 of lines) {
|
|
1015
|
+
const line = line0.trim();
|
|
1016
|
+
if (!line) continue;
|
|
1017
|
+
try {
|
|
1018
|
+
const obj = JSON.parse(line);
|
|
1019
|
+
if (obj?.type === "session_info" && typeof obj?.name === "string" && obj.name.trim()) {
|
|
1020
|
+
lastName = obj.name.trim();
|
|
1021
|
+
}
|
|
1022
|
+
} catch {
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
const tailLine = leftover.trim();
|
|
1027
|
+
if (tailLine) {
|
|
1028
|
+
try {
|
|
1029
|
+
const obj = JSON.parse(tailLine);
|
|
1030
|
+
if (obj?.type === "session_info" && typeof obj?.name === "string" && obj.name.trim()) {
|
|
1031
|
+
lastName = obj.name.trim();
|
|
1032
|
+
}
|
|
1033
|
+
} catch {
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
return lastName;
|
|
1037
|
+
} catch {
|
|
1038
|
+
return null;
|
|
1039
|
+
} finally {
|
|
1040
|
+
try {
|
|
1041
|
+
closeSync(fd);
|
|
1042
|
+
} catch {
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
function pickUpdatedAtFromTail(tail) {
|
|
1047
|
+
const lines = tail.split(/\r?\n/);
|
|
1048
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1049
|
+
const line = lines[i].trim();
|
|
1050
|
+
if (!line) continue;
|
|
1051
|
+
try {
|
|
1052
|
+
const obj = JSON.parse(line);
|
|
1053
|
+
if (obj?.type !== "message") continue;
|
|
1054
|
+
const ts = typeof obj?.timestamp === "string" ? obj.timestamp : null;
|
|
1055
|
+
if (!ts) continue;
|
|
1056
|
+
const d = new Date(ts);
|
|
1057
|
+
if (Number.isFinite(d.getTime())) return d.toISOString();
|
|
1058
|
+
} catch {
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1062
|
+
const line = lines[i].trim();
|
|
1063
|
+
if (!line) continue;
|
|
1064
|
+
try {
|
|
1065
|
+
const obj = JSON.parse(line);
|
|
1066
|
+
const ts = typeof obj?.timestamp === "string" ? obj.timestamp : null;
|
|
1067
|
+
if (!ts) continue;
|
|
1068
|
+
const d = new Date(ts);
|
|
1069
|
+
if (Number.isFinite(d.getTime())) return d.toISOString();
|
|
1070
|
+
} catch {
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
return null;
|
|
1074
|
+
}
|
|
1075
|
+
function pickFallbackTitleFromHead(path) {
|
|
1076
|
+
try {
|
|
1077
|
+
const raw = readFileSync4(path, { encoding: "utf8" });
|
|
1078
|
+
const lines = raw.split(/\r?\n/);
|
|
1079
|
+
for (const line0 of lines) {
|
|
1080
|
+
const line = line0.trim();
|
|
1081
|
+
if (!line) continue;
|
|
1082
|
+
try {
|
|
1083
|
+
const obj = JSON.parse(line);
|
|
1084
|
+
if (obj?.type === "message" && obj?.message?.role === "user") {
|
|
1085
|
+
const content = obj?.message?.content;
|
|
1086
|
+
if (typeof content === "string") return content.slice(0, 80);
|
|
1087
|
+
if (Array.isArray(content)) {
|
|
1088
|
+
const t = content.find((c) => c?.type === "text" && typeof c?.text === "string");
|
|
1089
|
+
if (t?.text) return String(t.text).slice(0, 80);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
} catch {
|
|
1093
|
+
}
|
|
1094
|
+
if (lines.length > 2e3) break;
|
|
1095
|
+
}
|
|
1096
|
+
} catch {
|
|
1097
|
+
}
|
|
1098
|
+
return null;
|
|
1099
|
+
}
|
|
1100
|
+
function listPiSessions() {
|
|
1101
|
+
const sessionsDir = getPiSessionsDir();
|
|
1102
|
+
const files = [];
|
|
1103
|
+
walkJsonlFiles(sessionsDir, files);
|
|
1104
|
+
const items = [];
|
|
1105
|
+
for (const file of files) {
|
|
1106
|
+
const first = readFirstLine(file);
|
|
1107
|
+
if (!first) continue;
|
|
1108
|
+
const header = parseSessionHeader(first);
|
|
1109
|
+
if (!header) continue;
|
|
1110
|
+
let updatedAt = null;
|
|
1111
|
+
let title = null;
|
|
1112
|
+
try {
|
|
1113
|
+
const tail = readTail(file);
|
|
1114
|
+
title = pickTitleFromTail(tail);
|
|
1115
|
+
updatedAt = pickUpdatedAtFromTail(tail);
|
|
1116
|
+
} catch {
|
|
1117
|
+
}
|
|
1118
|
+
if (!title) {
|
|
1119
|
+
title = scanSessionInfoNameFromFile(file);
|
|
1120
|
+
}
|
|
1121
|
+
if (!updatedAt) {
|
|
1122
|
+
try {
|
|
1123
|
+
updatedAt = statSync(file).mtime.toISOString();
|
|
1124
|
+
} catch {
|
|
1125
|
+
updatedAt = null;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
if (!title) {
|
|
1129
|
+
title = pickFallbackTitleFromHead(file);
|
|
1130
|
+
}
|
|
1131
|
+
items.push({
|
|
1132
|
+
sessionId: header.sessionId,
|
|
1133
|
+
cwd: header.cwd,
|
|
1134
|
+
title,
|
|
1135
|
+
updatedAt,
|
|
1136
|
+
sessionFile: file
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
items.sort((a, b) => {
|
|
1140
|
+
const aa = a.updatedAt ?? "";
|
|
1141
|
+
const bb = b.updatedAt ?? "";
|
|
1142
|
+
return bb.localeCompare(aa);
|
|
1143
|
+
});
|
|
1144
|
+
return items;
|
|
1145
|
+
}
|
|
1146
|
+
function findPiSessionFile(sessionId) {
|
|
1147
|
+
const all = listPiSessions();
|
|
1148
|
+
const found = all.find((s) => s.sessionId === sessionId);
|
|
1149
|
+
return found?.sessionFile ?? null;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
770
1152
|
// src/acp/translate/pi-messages.ts
|
|
771
1153
|
function normalizePiMessageText(content) {
|
|
772
1154
|
if (typeof content === "string") return content;
|
|
@@ -842,9 +1224,80 @@ ${r.text}`;
|
|
|
842
1224
|
|
|
843
1225
|
// src/acp/agent.ts
|
|
844
1226
|
import { isAbsolute as isAbsolute2 } from "path";
|
|
845
|
-
import { existsSync as
|
|
846
|
-
import { join as
|
|
1227
|
+
import { existsSync as existsSync3, readFileSync as readFileSync6, realpathSync, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
|
|
1228
|
+
import { join as join5, dirname as dirname2, basename } from "path";
|
|
847
1229
|
import { spawnSync } from "child_process";
|
|
1230
|
+
|
|
1231
|
+
// src/pi-auth/status.ts
|
|
1232
|
+
import { existsSync as existsSync2, readFileSync as readFileSync5 } from "fs";
|
|
1233
|
+
import { homedir as homedir4 } from "os";
|
|
1234
|
+
import { join as join4 } from "path";
|
|
1235
|
+
function safeReadJson(path) {
|
|
1236
|
+
try {
|
|
1237
|
+
if (!existsSync2(path)) return null;
|
|
1238
|
+
const raw = readFileSync5(path, "utf-8");
|
|
1239
|
+
if (!raw.trim()) return null;
|
|
1240
|
+
return JSON.parse(raw);
|
|
1241
|
+
} catch {
|
|
1242
|
+
return null;
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
function getPiAgentDir2() {
|
|
1246
|
+
const envDir = process.env.PI_CODING_AGENT_DIR;
|
|
1247
|
+
if (envDir) {
|
|
1248
|
+
if (envDir === "~") return homedir4();
|
|
1249
|
+
if (envDir.startsWith("~/")) return homedir4() + envDir.slice(1);
|
|
1250
|
+
return envDir;
|
|
1251
|
+
}
|
|
1252
|
+
return join4(homedir4(), ".pi", "agent");
|
|
1253
|
+
}
|
|
1254
|
+
function hasAnyPiAuthConfigured() {
|
|
1255
|
+
const agentDir = getPiAgentDir2();
|
|
1256
|
+
const authPath = join4(agentDir, "auth.json");
|
|
1257
|
+
const auth = safeReadJson(authPath);
|
|
1258
|
+
if (auth && typeof auth === "object" && Object.keys(auth).length > 0) return true;
|
|
1259
|
+
const modelsPath = join4(agentDir, "models.json");
|
|
1260
|
+
const models = safeReadJson(modelsPath);
|
|
1261
|
+
const providers = models?.providers;
|
|
1262
|
+
if (providers && typeof providers === "object") {
|
|
1263
|
+
for (const p of Object.values(providers)) {
|
|
1264
|
+
if (p && typeof p === "object" && typeof p.apiKey === "string" && p.apiKey.trim()) {
|
|
1265
|
+
return true;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
const envVars = [
|
|
1270
|
+
"OPENAI_API_KEY",
|
|
1271
|
+
"AZURE_OPENAI_API_KEY",
|
|
1272
|
+
"GEMINI_API_KEY",
|
|
1273
|
+
"GROQ_API_KEY",
|
|
1274
|
+
"CEREBRAS_API_KEY",
|
|
1275
|
+
"XAI_API_KEY",
|
|
1276
|
+
"OPENROUTER_API_KEY",
|
|
1277
|
+
"AI_GATEWAY_API_KEY",
|
|
1278
|
+
"ZAI_API_KEY",
|
|
1279
|
+
"MISTRAL_API_KEY",
|
|
1280
|
+
"MINIMAX_API_KEY",
|
|
1281
|
+
"MINIMAX_CN_API_KEY",
|
|
1282
|
+
"HF_TOKEN",
|
|
1283
|
+
"OPENCODE_API_KEY",
|
|
1284
|
+
"KIMI_API_KEY",
|
|
1285
|
+
// Copilot/github
|
|
1286
|
+
"COPILOT_GITHUB_TOKEN",
|
|
1287
|
+
"GH_TOKEN",
|
|
1288
|
+
"GITHUB_TOKEN",
|
|
1289
|
+
// Anthropic oauth
|
|
1290
|
+
"ANTHROPIC_OAUTH_TOKEN",
|
|
1291
|
+
"ANTHROPIC_API_KEY"
|
|
1292
|
+
];
|
|
1293
|
+
for (const k of envVars) {
|
|
1294
|
+
const v = process.env[k];
|
|
1295
|
+
if (typeof v === "string" && v.trim()) return true;
|
|
1296
|
+
}
|
|
1297
|
+
return false;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
// src/acp/agent.ts
|
|
848
1301
|
import { fileURLToPath } from "url";
|
|
849
1302
|
function booleanEnv(name, defaultValue) {
|
|
850
1303
|
const raw = process.env[name];
|
|
@@ -905,6 +1358,8 @@ var PiAcpAgent = class {
|
|
|
905
1358
|
conn;
|
|
906
1359
|
sessions = new SessionManager();
|
|
907
1360
|
store = new SessionStore();
|
|
1361
|
+
// Remember recent session cwd and use it as the default filter.
|
|
1362
|
+
lastSessionCwd = null;
|
|
908
1363
|
constructor(conn, _config) {
|
|
909
1364
|
this.conn = conn;
|
|
910
1365
|
void _config;
|
|
@@ -919,7 +1374,11 @@ var PiAcpAgent = class {
|
|
|
919
1374
|
title: "pi ACP adapter",
|
|
920
1375
|
version: pkg.version ?? "0.0.0"
|
|
921
1376
|
},
|
|
922
|
-
|
|
1377
|
+
// Zed currently uses ClientCapabilities._meta["terminal-auth"] to decide whether to show
|
|
1378
|
+
// the "Authenticate" banner/button. If not supported, we still return the method for the registry.
|
|
1379
|
+
authMethods: getAuthMethods({
|
|
1380
|
+
supportsTerminalAuthMeta: params?.clientCapabilities?._meta?.["terminal-auth"] === true
|
|
1381
|
+
}),
|
|
923
1382
|
agentCapabilities: {
|
|
924
1383
|
loadSession: true,
|
|
925
1384
|
mcpCapabilities: { http: false, sse: false },
|
|
@@ -928,21 +1387,49 @@ var PiAcpAgent = class {
|
|
|
928
1387
|
audio: false,
|
|
929
1388
|
embeddedContext: false
|
|
930
1389
|
},
|
|
931
|
-
sessionCapabilities: {
|
|
1390
|
+
sessionCapabilities: {
|
|
1391
|
+
// **UNSTABLE** ACP capability used by Zed's codex-acp adapter.
|
|
1392
|
+
// Enables a native session picker in clients that support it.
|
|
1393
|
+
list: {}
|
|
1394
|
+
}
|
|
932
1395
|
}
|
|
933
1396
|
};
|
|
934
1397
|
}
|
|
935
1398
|
async newSession(params) {
|
|
936
1399
|
if (!isAbsolute2(params.cwd)) {
|
|
937
|
-
throw
|
|
1400
|
+
throw RequestError3.invalidParams(`cwd must be an absolute path: ${params.cwd}`);
|
|
1401
|
+
}
|
|
1402
|
+
this.lastSessionCwd = params.cwd;
|
|
1403
|
+
if (!hasAnyPiAuthConfigured()) {
|
|
1404
|
+
throw RequestError3.authRequired(
|
|
1405
|
+
{ authMethods: getAuthMethods() },
|
|
1406
|
+
"Configure an API key or log in with an OAuth provider."
|
|
1407
|
+
);
|
|
938
1408
|
}
|
|
939
1409
|
const fileCommands = loadSlashCommands(params.cwd);
|
|
940
1410
|
const session = await this.sessions.create({
|
|
941
1411
|
cwd: params.cwd,
|
|
942
1412
|
mcpServers: params.mcpServers,
|
|
943
1413
|
conn: this.conn,
|
|
944
|
-
fileCommands
|
|
1414
|
+
fileCommands,
|
|
1415
|
+
piCommand: process.env.PI_ACP_PI_COMMAND
|
|
945
1416
|
});
|
|
1417
|
+
let rawModelsCount = 0;
|
|
1418
|
+
try {
|
|
1419
|
+
const data = await session.proc.getAvailableModels();
|
|
1420
|
+
rawModelsCount = Array.isArray(data?.models) ? data.models.length : 0;
|
|
1421
|
+
} catch {
|
|
1422
|
+
}
|
|
1423
|
+
if (rawModelsCount === 0) {
|
|
1424
|
+
try {
|
|
1425
|
+
session.proc.dispose?.();
|
|
1426
|
+
} catch {
|
|
1427
|
+
}
|
|
1428
|
+
throw RequestError3.authRequired(
|
|
1429
|
+
{ authMethods: getAuthMethods() },
|
|
1430
|
+
"Configure an API key or log in with an OAuth provider."
|
|
1431
|
+
);
|
|
1432
|
+
}
|
|
946
1433
|
const models = await getModelState(session.proc);
|
|
947
1434
|
const thinking = await getThinkingState(session.proc);
|
|
948
1435
|
const showStartupInfo = booleanEnv("PI_ACP_STARTUP_INFO", true);
|
|
@@ -1121,8 +1608,8 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1121
1608
|
if (piPath) {
|
|
1122
1609
|
const resolved = realpathSync(piPath);
|
|
1123
1610
|
const pkgRoot = dirname2(dirname2(resolved));
|
|
1124
|
-
const p =
|
|
1125
|
-
if (
|
|
1611
|
+
const p = join5(pkgRoot, "CHANGELOG.md");
|
|
1612
|
+
if (existsSync3(p)) return p;
|
|
1126
1613
|
}
|
|
1127
1614
|
} catch {
|
|
1128
1615
|
}
|
|
@@ -1130,8 +1617,8 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1130
1617
|
const npmRoot = spawnSync("npm", ["root", "-g"], { encoding: "utf-8" });
|
|
1131
1618
|
const root = String(npmRoot.stdout ?? "").trim();
|
|
1132
1619
|
if (root) {
|
|
1133
|
-
const p =
|
|
1134
|
-
if (
|
|
1620
|
+
const p = join5(root, "@mariozechner", "pi-coding-agent", "CHANGELOG.md");
|
|
1621
|
+
if (existsSync3(p)) return p;
|
|
1135
1622
|
}
|
|
1136
1623
|
} catch {
|
|
1137
1624
|
}
|
|
@@ -1150,7 +1637,7 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1150
1637
|
}
|
|
1151
1638
|
let text = "";
|
|
1152
1639
|
try {
|
|
1153
|
-
text =
|
|
1640
|
+
text = readFileSync6(changelogPath, "utf-8");
|
|
1154
1641
|
} catch (e) {
|
|
1155
1642
|
await this.conn.sessionUpdate({
|
|
1156
1643
|
sessionId: session.sessionId,
|
|
@@ -1176,7 +1663,7 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1176
1663
|
const state = await session.proc.getState();
|
|
1177
1664
|
const sessionFile = typeof state?.sessionFile === "string" ? state.sessionFile : null;
|
|
1178
1665
|
const messageCount = typeof state?.messageCount === "number" ? state.messageCount : 0;
|
|
1179
|
-
if (!sessionFile || messageCount === 0 || !
|
|
1666
|
+
if (!sessionFile || messageCount === 0 || !existsSync3(sessionFile)) {
|
|
1180
1667
|
await this.conn.sessionUpdate({
|
|
1181
1668
|
sessionId: session.sessionId,
|
|
1182
1669
|
update: {
|
|
@@ -1190,7 +1677,7 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1190
1677
|
return { stopReason: "end_turn" };
|
|
1191
1678
|
}
|
|
1192
1679
|
try {
|
|
1193
|
-
const raw =
|
|
1680
|
+
const raw = readFileSync6(sessionFile, "utf-8");
|
|
1194
1681
|
if (raw.trim().length === 0) {
|
|
1195
1682
|
await this.conn.sessionUpdate({
|
|
1196
1683
|
sessionId: session.sessionId,
|
|
@@ -1218,7 +1705,7 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1218
1705
|
return { stopReason: "end_turn" };
|
|
1219
1706
|
}
|
|
1220
1707
|
const safeSessionId = session.sessionId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1221
|
-
const outputPath =
|
|
1708
|
+
const outputPath = join5(session.cwd, `pi-session-${safeSessionId}.html`);
|
|
1222
1709
|
let resultPath = "";
|
|
1223
1710
|
try {
|
|
1224
1711
|
const result2 = await session.proc.exportHtml(outputPath);
|
|
@@ -1307,18 +1794,46 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1307
1794
|
const session = this.sessions.get(params.sessionId);
|
|
1308
1795
|
await session.cancel();
|
|
1309
1796
|
}
|
|
1797
|
+
async unstable_listSessions(params) {
|
|
1798
|
+
const all = listPiSessions();
|
|
1799
|
+
const effectiveCwd = params.cwd ?? this.lastSessionCwd;
|
|
1800
|
+
const filtered = effectiveCwd ? all.filter((s) => s.cwd === effectiveCwd) : all;
|
|
1801
|
+
const offset = params.cursor ? Number.parseInt(params.cursor, 10) : 0;
|
|
1802
|
+
const start = Number.isFinite(offset) && offset > 0 ? offset : 0;
|
|
1803
|
+
const PAGE_SIZE = 50;
|
|
1804
|
+
const page = filtered.slice(start, start + PAGE_SIZE);
|
|
1805
|
+
const sessions = page.map((s) => ({
|
|
1806
|
+
sessionId: s.sessionId,
|
|
1807
|
+
cwd: s.cwd,
|
|
1808
|
+
title: s.title,
|
|
1809
|
+
updatedAt: s.updatedAt
|
|
1810
|
+
}));
|
|
1811
|
+
const nextCursor = start + PAGE_SIZE < filtered.length ? String(start + PAGE_SIZE) : null;
|
|
1812
|
+
return { sessions, nextCursor, _meta: {} };
|
|
1813
|
+
}
|
|
1310
1814
|
async loadSession(params) {
|
|
1311
1815
|
if (!isAbsolute2(params.cwd)) {
|
|
1312
|
-
throw
|
|
1816
|
+
throw RequestError3.invalidParams(`cwd must be an absolute path: ${params.cwd}`);
|
|
1313
1817
|
}
|
|
1818
|
+
this.lastSessionCwd = params.cwd;
|
|
1314
1819
|
const stored = this.store.get(params.sessionId);
|
|
1315
|
-
|
|
1316
|
-
|
|
1820
|
+
const sessionFile = stored?.sessionFile ?? findPiSessionFile(params.sessionId);
|
|
1821
|
+
if (!sessionFile) {
|
|
1822
|
+
throw RequestError3.invalidParams(`Unknown sessionId: ${params.sessionId}`);
|
|
1823
|
+
}
|
|
1824
|
+
let proc;
|
|
1825
|
+
try {
|
|
1826
|
+
proc = await PiRpcProcess.spawn({
|
|
1827
|
+
cwd: params.cwd,
|
|
1828
|
+
sessionPath: sessionFile,
|
|
1829
|
+
piCommand: process.env.PI_ACP_PI_COMMAND
|
|
1830
|
+
});
|
|
1831
|
+
} catch (e) {
|
|
1832
|
+
if (e?.name === "PiRpcSpawnError") {
|
|
1833
|
+
throw RequestError3.internalError({ code: e?.code }, String(e?.message ?? e));
|
|
1834
|
+
}
|
|
1835
|
+
throw e;
|
|
1317
1836
|
}
|
|
1318
|
-
const proc = await PiRpcProcess.spawn({
|
|
1319
|
-
cwd: params.cwd,
|
|
1320
|
-
sessionPath: stored.sessionFile
|
|
1321
|
-
});
|
|
1322
1837
|
const fileCommands = loadSlashCommands(params.cwd);
|
|
1323
1838
|
const session = this.sessions.getOrCreate(params.sessionId, {
|
|
1324
1839
|
cwd: params.cwd,
|
|
@@ -1330,7 +1845,7 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1330
1845
|
this.store.upsert({
|
|
1331
1846
|
sessionId: params.sessionId,
|
|
1332
1847
|
cwd: params.cwd,
|
|
1333
|
-
sessionFile
|
|
1848
|
+
sessionFile
|
|
1334
1849
|
});
|
|
1335
1850
|
const data = await proc.getMessages();
|
|
1336
1851
|
const messages = Array.isArray(data?.messages) ? data.messages : [];
|
|
@@ -1360,22 +1875,46 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1360
1875
|
});
|
|
1361
1876
|
}
|
|
1362
1877
|
}
|
|
1878
|
+
if (role === "toolResult") {
|
|
1879
|
+
const toolName = String(m?.toolName ?? "tool");
|
|
1880
|
+
const toolCallId = String(m?.toolCallId ?? crypto.randomUUID());
|
|
1881
|
+
const isError = Boolean(m?.isError);
|
|
1882
|
+
await this.conn.sessionUpdate({
|
|
1883
|
+
sessionId: session.sessionId,
|
|
1884
|
+
update: {
|
|
1885
|
+
sessionUpdate: "tool_call",
|
|
1886
|
+
toolCallId,
|
|
1887
|
+
title: toolName,
|
|
1888
|
+
kind: toolName === "read" ? "read" : toolName === "write" || toolName === "edit" ? "edit" : "other",
|
|
1889
|
+
status: "completed",
|
|
1890
|
+
rawInput: null,
|
|
1891
|
+
rawOutput: m
|
|
1892
|
+
}
|
|
1893
|
+
});
|
|
1894
|
+
const text = toolResultToText(m);
|
|
1895
|
+
await this.conn.sessionUpdate({
|
|
1896
|
+
sessionId: session.sessionId,
|
|
1897
|
+
update: {
|
|
1898
|
+
sessionUpdate: "tool_call_update",
|
|
1899
|
+
toolCallId,
|
|
1900
|
+
status: isError ? "failed" : "completed",
|
|
1901
|
+
content: text ? [{ type: "content", content: { type: "text", text } }] : null,
|
|
1902
|
+
rawOutput: m
|
|
1903
|
+
}
|
|
1904
|
+
});
|
|
1905
|
+
}
|
|
1363
1906
|
}
|
|
1364
1907
|
const models = await getModelState(proc);
|
|
1365
1908
|
const thinking = await getThinkingState(proc);
|
|
1366
|
-
const showStartupInfo = booleanEnv("PI_ACP_STARTUP_INFO", true);
|
|
1367
|
-
const preludeText = showStartupInfo ? buildStartupInfo({ cwd: params.cwd, fileCommands }) : "";
|
|
1368
|
-
if (preludeText) session.setStartupInfo(preludeText);
|
|
1369
1909
|
const response = {
|
|
1370
1910
|
models,
|
|
1371
1911
|
modes: thinking,
|
|
1372
1912
|
_meta: {
|
|
1373
1913
|
piAcp: {
|
|
1374
|
-
startupInfo:
|
|
1914
|
+
startupInfo: null
|
|
1375
1915
|
}
|
|
1376
1916
|
}
|
|
1377
1917
|
};
|
|
1378
|
-
if (showStartupInfo) setTimeout(() => session.sendStartupInfoIfPending(), 0);
|
|
1379
1918
|
setTimeout(() => {
|
|
1380
1919
|
void this.conn.sessionUpdate({
|
|
1381
1920
|
sessionId: session.sessionId,
|
|
@@ -1408,7 +1947,7 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1408
1947
|
}
|
|
1409
1948
|
}
|
|
1410
1949
|
if (!provider || !modelId) {
|
|
1411
|
-
throw
|
|
1950
|
+
throw RequestError3.invalidParams(`Unknown modelId: ${params.modelId}`);
|
|
1412
1951
|
}
|
|
1413
1952
|
await session.proc.setModel(provider, modelId);
|
|
1414
1953
|
}
|
|
@@ -1416,7 +1955,7 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1416
1955
|
const session = this.sessions.get(params.sessionId);
|
|
1417
1956
|
const mode = String(params.modeId);
|
|
1418
1957
|
if (!isThinkingLevel(mode)) {
|
|
1419
|
-
throw
|
|
1958
|
+
throw RequestError3.invalidParams(`Unknown modeId: ${mode}`);
|
|
1420
1959
|
}
|
|
1421
1960
|
await session.proc.setThinkingLevel(mode);
|
|
1422
1961
|
void this.conn.sessionUpdate({
|
|
@@ -1533,16 +2072,16 @@ function buildStartupInfo(opts) {
|
|
|
1533
2072
|
md.push("");
|
|
1534
2073
|
};
|
|
1535
2074
|
const contextItems = [];
|
|
1536
|
-
const contextPath =
|
|
1537
|
-
if (
|
|
2075
|
+
const contextPath = join5(opts.cwd, "AGENTS.md");
|
|
2076
|
+
if (existsSync3(contextPath)) contextItems.push(contextPath);
|
|
1538
2077
|
addSection("Context", contextItems);
|
|
1539
2078
|
const skillsItems = [];
|
|
1540
2079
|
const pushSkillFromRoot = (root) => {
|
|
1541
2080
|
try {
|
|
1542
|
-
for (const e of
|
|
1543
|
-
const p =
|
|
2081
|
+
for (const e of readdirSync3(root)) {
|
|
2082
|
+
const p = join5(root, e);
|
|
1544
2083
|
try {
|
|
1545
|
-
const st =
|
|
2084
|
+
const st = statSync2(p);
|
|
1546
2085
|
if (st.isFile() && e.toLowerCase().endsWith(".md")) {
|
|
1547
2086
|
skillsItems.push(p);
|
|
1548
2087
|
}
|
|
@@ -1554,16 +2093,16 @@ function buildStartupInfo(opts) {
|
|
|
1554
2093
|
const dir = stack.pop();
|
|
1555
2094
|
let entries = [];
|
|
1556
2095
|
try {
|
|
1557
|
-
entries =
|
|
2096
|
+
entries = readdirSync3(dir);
|
|
1558
2097
|
} catch {
|
|
1559
2098
|
continue;
|
|
1560
2099
|
}
|
|
1561
2100
|
for (const name of entries) {
|
|
1562
2101
|
if (name === "node_modules" || name === ".git") continue;
|
|
1563
|
-
const p =
|
|
2102
|
+
const p = join5(dir, name);
|
|
1564
2103
|
let st;
|
|
1565
2104
|
try {
|
|
1566
|
-
st =
|
|
2105
|
+
st = statSync2(p);
|
|
1567
2106
|
} catch {
|
|
1568
2107
|
continue;
|
|
1569
2108
|
}
|
|
@@ -1577,29 +2116,29 @@ function buildStartupInfo(opts) {
|
|
|
1577
2116
|
} catch {
|
|
1578
2117
|
}
|
|
1579
2118
|
};
|
|
1580
|
-
const globalSkillsDir =
|
|
2119
|
+
const globalSkillsDir = join5(process.env.HOME ?? "", ".pi", "agent", "skills");
|
|
1581
2120
|
pushSkillFromRoot(globalSkillsDir);
|
|
1582
|
-
const projectSkillsDir =
|
|
2121
|
+
const projectSkillsDir = join5(opts.cwd, ".pi", "skills");
|
|
1583
2122
|
pushSkillFromRoot(projectSkillsDir);
|
|
1584
2123
|
addSection("Skills", skillsItems);
|
|
1585
2124
|
const promptsItems = [];
|
|
1586
|
-
const promptsDir =
|
|
2125
|
+
const promptsDir = join5(process.env.HOME ?? "", ".pi", "agent", "prompts");
|
|
1587
2126
|
try {
|
|
1588
|
-
const prompts =
|
|
2127
|
+
const prompts = readdirSync3(promptsDir).filter((f) => f.endsWith(".md"));
|
|
1589
2128
|
for (const f of prompts) promptsItems.push(`/${basename(f, ".md")}`);
|
|
1590
2129
|
} catch {
|
|
1591
2130
|
}
|
|
1592
2131
|
addSection("Prompts", promptsItems);
|
|
1593
2132
|
const extItems = [];
|
|
1594
|
-
const extDir =
|
|
2133
|
+
const extDir = join5(process.env.HOME ?? "", ".pi", "agent", "extensions");
|
|
1595
2134
|
try {
|
|
1596
|
-
const exts =
|
|
1597
|
-
for (const f of exts) extItems.push(
|
|
2135
|
+
const exts = readdirSync3(extDir).filter((f) => f.endsWith(".ts") || f.endsWith(".js"));
|
|
2136
|
+
for (const f of exts) extItems.push(join5(extDir, f));
|
|
1598
2137
|
} catch {
|
|
1599
2138
|
}
|
|
1600
2139
|
try {
|
|
1601
|
-
const settingsPath =
|
|
1602
|
-
const settings = JSON.parse(
|
|
2140
|
+
const settingsPath = join5(process.env.HOME ?? "", ".pi", "agent", "settings.json");
|
|
2141
|
+
const settings = JSON.parse(readFileSync6(settingsPath, "utf-8"));
|
|
1603
2142
|
const pkgs = Array.isArray(settings?.packages) ? settings.packages : [];
|
|
1604
2143
|
for (const pkg2 of pkgs) {
|
|
1605
2144
|
const s = String(pkg2);
|
|
@@ -1624,9 +2163,9 @@ function readNearestPackageJson(metaUrl) {
|
|
|
1624
2163
|
try {
|
|
1625
2164
|
let dir = dirname2(fileURLToPath(metaUrl));
|
|
1626
2165
|
for (let i = 0; i < 6; i++) {
|
|
1627
|
-
const p =
|
|
1628
|
-
if (
|
|
1629
|
-
const json = JSON.parse(
|
|
2166
|
+
const p = join5(dir, "package.json");
|
|
2167
|
+
if (existsSync3(p)) {
|
|
2168
|
+
const json = JSON.parse(readFileSync6(p, "utf-8"));
|
|
1630
2169
|
return { name: json?.name, version: json?.version };
|
|
1631
2170
|
}
|
|
1632
2171
|
dir = dirname2(dir);
|
|
@@ -1637,13 +2176,31 @@ function readNearestPackageJson(metaUrl) {
|
|
|
1637
2176
|
}
|
|
1638
2177
|
|
|
1639
2178
|
// src/index.ts
|
|
2179
|
+
if (process.argv.includes("--terminal-login")) {
|
|
2180
|
+
const { spawnSync: spawnSync2 } = await import("child_process");
|
|
2181
|
+
const cmd = process.env.PI_ACP_PI_COMMAND ?? "pi";
|
|
2182
|
+
const res = spawnSync2(cmd, [], { stdio: "inherit", env: process.env });
|
|
2183
|
+
if (res.error && res.error.code === "ENOENT") {
|
|
2184
|
+
process.stderr.write(
|
|
2185
|
+
`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.
|
|
2186
|
+
`
|
|
2187
|
+
);
|
|
2188
|
+
process.exit(1);
|
|
2189
|
+
}
|
|
2190
|
+
process.exit(typeof res.status === "number" ? res.status : 1);
|
|
2191
|
+
}
|
|
1640
2192
|
var input = new WritableStream({
|
|
1641
2193
|
write(chunk) {
|
|
1642
|
-
return new Promise((resolve2
|
|
1643
|
-
process.stdout.
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
2194
|
+
return new Promise((resolve2) => {
|
|
2195
|
+
if (process.stdout.destroyed || !process.stdout.writable) return resolve2();
|
|
2196
|
+
try {
|
|
2197
|
+
process.stdout.write(chunk, (err) => {
|
|
2198
|
+
void err;
|
|
2199
|
+
resolve2();
|
|
2200
|
+
});
|
|
2201
|
+
} catch {
|
|
2202
|
+
resolve2();
|
|
2203
|
+
}
|
|
1647
2204
|
});
|
|
1648
2205
|
}
|
|
1649
2206
|
});
|
|
@@ -1659,4 +2216,10 @@ new AgentSideConnection((conn) => new PiAcpAgent(conn), stream);
|
|
|
1659
2216
|
process.stdin.resume();
|
|
1660
2217
|
process.on("SIGINT", () => process.exit(0));
|
|
1661
2218
|
process.on("SIGTERM", () => process.exit(0));
|
|
2219
|
+
process.stdout.on("error", () => {
|
|
2220
|
+
try {
|
|
2221
|
+
process.exit(0);
|
|
2222
|
+
} catch {
|
|
2223
|
+
}
|
|
2224
|
+
});
|
|
1662
2225
|
//# sourceMappingURL=index.js.map
|