pi-acp 0.0.25 → 0.0.27
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 +24 -3
- package/dist/index.js +129 -136
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# pi-acp
|
|
2
2
|
|
|
3
|
-
ACP ([Agent Client Protocol](https://agentclientprotocol.com/overview/introduction)) adapter for [`pi`](https://github.com/
|
|
3
|
+
ACP ([Agent Client Protocol](https://agentclientprotocol.com/overview/introduction)) adapter for [`pi`](https://github.com/earendil-works/pi) coding agent (fka shitty coding agent).
|
|
4
4
|
|
|
5
5
|
`pi-acp` communicates **ACP JSON-RPC 2.0 over stdio** to an ACP client (e.g. Zed editor) and spawns `pi --mode rpc`, bridging requests/events between the two.
|
|
6
6
|
|
|
@@ -34,7 +34,7 @@ Expect some minor breaking changes.
|
|
|
34
34
|
Make sure pi is installed
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
npm install -g @
|
|
37
|
+
npm install -g @earendil-works/pi-coding-agent
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
- Node.js 22+
|
|
@@ -109,6 +109,27 @@ Point your ACP client to the built `dist/index.js`:
|
|
|
109
109
|
}
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
+
### Environment variables
|
|
113
|
+
|
|
114
|
+
- `PI_ACP_ENABLE_EMBEDDED_CONTEXT=true` advertises ACP `promptCapabilities.embeddedContext` support to the client.
|
|
115
|
+
- Default: unset/any other value means `false`.
|
|
116
|
+
- When disabled, compliant ACP clients should avoid sending embedded `resource` blocks. If they send them anyway, `pi-acp` still degrades gracefully by converting them into plain-text prompt context.
|
|
117
|
+
|
|
118
|
+
You can add the environment variable in the Zed settings with:
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
"agent_servers": {
|
|
122
|
+
"pi": {
|
|
123
|
+
"type": "custom",
|
|
124
|
+
"command": "node",
|
|
125
|
+
"args": ["/path/to/pi-acp/dist/index.js"],
|
|
126
|
+
"env": {
|
|
127
|
+
"PI_ACP_ENABLE_EMBEDDED_CONTEXT": "true",
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
112
133
|
### Slash commands
|
|
113
134
|
|
|
114
135
|
`pi-acp` supports slash commands:
|
|
@@ -174,7 +195,7 @@ Project layout:
|
|
|
174
195
|
## Limitations
|
|
175
196
|
|
|
176
197
|
- No ACP filesystem delegation (`fs/*`) and no ACP terminal delegation (`terminal/*`). pi reads/writes and executes locally.
|
|
177
|
-
- MCP servers are accepted in ACP params and stored in session state, but not wired through to pi
|
|
198
|
+
- MCP servers are accepted in ACP params and stored in session state, but not wired through to pi in this adapter. If you use [pi MCP adapter](https://github.com/nicobailon/pi-mcp-adapter) it will be available in the ACP client.
|
|
178
199
|
- Assistant streaming is currently sent as `agent_message_chunk` (no separate thought stream).
|
|
179
200
|
- Queue is implemented client-side and should work like pi's `one-at-a-time`
|
|
180
201
|
- ~~ACP clients don't yet suport session history, but ACP sessions from `pi-acp` can be `/resume`d in pi directly~~
|
package/dist/index.js
CHANGED
|
@@ -171,10 +171,10 @@ var PiRpcProcess = class _PiRpcProcess {
|
|
|
171
171
|
shell: shouldUseShellForPiCommand(cmd)
|
|
172
172
|
});
|
|
173
173
|
try {
|
|
174
|
-
await new Promise((
|
|
174
|
+
await new Promise((resolve4, reject) => {
|
|
175
175
|
const onSpawn = () => {
|
|
176
176
|
cleanup();
|
|
177
|
-
|
|
177
|
+
resolve4();
|
|
178
178
|
};
|
|
179
179
|
const onError = (err) => {
|
|
180
180
|
cleanup();
|
|
@@ -191,7 +191,7 @@ var PiRpcProcess = class _PiRpcProcess {
|
|
|
191
191
|
const code = typeof e?.code === "string" ? e.code : void 0;
|
|
192
192
|
if (code === "ENOENT") {
|
|
193
193
|
throw new PiRpcSpawnError(
|
|
194
|
-
`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 @
|
|
194
|
+
`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 @earendil-works/pi-coding-agent\` or ensure \`pi\` is on your PATH. Then try again.`,
|
|
195
195
|
{ code, cause: e }
|
|
196
196
|
);
|
|
197
197
|
}
|
|
@@ -313,8 +313,8 @@ var PiRpcProcess = class _PiRpcProcess {
|
|
|
313
313
|
const id = crypto.randomUUID();
|
|
314
314
|
const withId = { ...cmd, id };
|
|
315
315
|
const line = JSON.stringify(withId) + "\n";
|
|
316
|
-
return new Promise((
|
|
317
|
-
this.pending.set(id, { resolve:
|
|
316
|
+
return new Promise((resolve4, reject) => {
|
|
317
|
+
this.pending.set(id, { resolve: resolve4, reject });
|
|
318
318
|
try {
|
|
319
319
|
this.child.stdin.write(line, (err) => {
|
|
320
320
|
if (err) {
|
|
@@ -383,6 +383,12 @@ var SessionStore = class {
|
|
|
383
383
|
};
|
|
384
384
|
saveFile(this.path, db);
|
|
385
385
|
}
|
|
386
|
+
delete(sessionId) {
|
|
387
|
+
const db = loadFile(this.path);
|
|
388
|
+
if (!db.sessions[sessionId]) return;
|
|
389
|
+
delete db.sessions[sessionId];
|
|
390
|
+
saveFile(this.path, db);
|
|
391
|
+
}
|
|
386
392
|
};
|
|
387
393
|
|
|
388
394
|
// src/acp/translate/pi-tools.ts
|
|
@@ -654,7 +660,8 @@ var PiAcpSession = class {
|
|
|
654
660
|
cwd;
|
|
655
661
|
mcpServers;
|
|
656
662
|
startupInfo = null;
|
|
657
|
-
|
|
663
|
+
startupInfoSentOutOfTurn = false;
|
|
664
|
+
startupInfoSentInPrompt = false;
|
|
658
665
|
proc;
|
|
659
666
|
conn;
|
|
660
667
|
fileCommands;
|
|
@@ -689,6 +696,8 @@ var PiAcpSession = class {
|
|
|
689
696
|
}
|
|
690
697
|
setStartupInfo(text) {
|
|
691
698
|
this.startupInfo = text;
|
|
699
|
+
this.startupInfoSentOutOfTurn = false;
|
|
700
|
+
this.startupInfoSentInPrompt = false;
|
|
692
701
|
}
|
|
693
702
|
/**
|
|
694
703
|
* Best-effort attempt to send startup info outside of a prompt turn.
|
|
@@ -696,17 +705,26 @@ var PiAcpSession = class {
|
|
|
696
705
|
* callers can invoke this shortly after session/new returns.
|
|
697
706
|
*/
|
|
698
707
|
sendStartupInfoIfPending() {
|
|
699
|
-
if (this.
|
|
700
|
-
this.
|
|
708
|
+
if (this.startupInfoSentOutOfTurn || !this.startupInfo) return;
|
|
709
|
+
this.startupInfoSentOutOfTurn = true;
|
|
710
|
+
this.emit({
|
|
711
|
+
sessionUpdate: "agent_message_chunk",
|
|
712
|
+
content: { type: "text", text: this.startupInfo }
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
sendStartupInfoOnFirstPromptIfPending() {
|
|
716
|
+
if (this.startupInfoSentInPrompt || !this.startupInfo) return;
|
|
717
|
+
this.startupInfoSentInPrompt = true;
|
|
701
718
|
this.emit({
|
|
702
719
|
sessionUpdate: "agent_message_chunk",
|
|
703
720
|
content: { type: "text", text: this.startupInfo }
|
|
704
721
|
});
|
|
705
722
|
}
|
|
706
723
|
async prompt(message, images = []) {
|
|
724
|
+
this.sendStartupInfoOnFirstPromptIfPending();
|
|
707
725
|
const expandedMessage = expandSlashCommand(message, this.fileCommands);
|
|
708
|
-
const turnPromise = new Promise((
|
|
709
|
-
const queued = { message: expandedMessage, images, resolve:
|
|
726
|
+
const turnPromise = new Promise((resolve4, reject) => {
|
|
727
|
+
const queued = { message: expandedMessage, images, resolve: resolve4, reject };
|
|
710
728
|
if (this.pendingTurn) {
|
|
711
729
|
this.turnQueue.push(queued);
|
|
712
730
|
this.emit({
|
|
@@ -962,7 +980,10 @@ var PiAcpSession = class {
|
|
|
962
980
|
case "auto_compaction_start": {
|
|
963
981
|
this.emit({
|
|
964
982
|
sessionUpdate: "agent_message_chunk",
|
|
965
|
-
content: {
|
|
983
|
+
content: {
|
|
984
|
+
type: "text",
|
|
985
|
+
text: "Context nearing limit, running automatic compaction..."
|
|
986
|
+
}
|
|
966
987
|
});
|
|
967
988
|
break;
|
|
968
989
|
}
|
|
@@ -1036,16 +1057,31 @@ function toToolKind(toolName) {
|
|
|
1036
1057
|
}
|
|
1037
1058
|
|
|
1038
1059
|
// src/acp/pi-sessions.ts
|
|
1039
|
-
import { readdirSync as readdirSync2, readFileSync as readFileSync4, statSync, openSync, readSync, closeSync } from "fs";
|
|
1060
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync4, statSync, openSync, readSync, closeSync, existsSync as existsSync2 } from "fs";
|
|
1040
1061
|
import { homedir as homedir3 } from "os";
|
|
1041
|
-
import { join as join3 } from "path";
|
|
1062
|
+
import { join as join3, resolve as resolve2, isAbsolute as isAbsolute2 } from "path";
|
|
1042
1063
|
var DEFAULT_TAIL_BYTES = 256 * 1024;
|
|
1043
1064
|
var DEFAULT_HEAD_BYTES = 64 * 1024;
|
|
1044
1065
|
function getPiAgentDir() {
|
|
1045
|
-
return process.env.PI_CODING_AGENT_DIR
|
|
1066
|
+
return process.env.PI_CODING_AGENT_DIR ? resolve2(process.env.PI_CODING_AGENT_DIR) : join3(homedir3(), ".pi", "agent");
|
|
1067
|
+
}
|
|
1068
|
+
function readSessionDirFromSettings(agentDir) {
|
|
1069
|
+
const settingsPath = join3(agentDir, "settings.json");
|
|
1070
|
+
try {
|
|
1071
|
+
if (!existsSync2(settingsPath)) return null;
|
|
1072
|
+
const raw = readFileSync4(settingsPath, "utf8");
|
|
1073
|
+
const data = JSON.parse(raw);
|
|
1074
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) return null;
|
|
1075
|
+
const sessionDir = data.sessionDir;
|
|
1076
|
+
if (typeof sessionDir !== "string" || !sessionDir.trim()) return null;
|
|
1077
|
+
return isAbsolute2(sessionDir) ? sessionDir : resolve2(agentDir, sessionDir);
|
|
1078
|
+
} catch {
|
|
1079
|
+
return null;
|
|
1080
|
+
}
|
|
1046
1081
|
}
|
|
1047
1082
|
function getPiSessionsDir() {
|
|
1048
|
-
|
|
1083
|
+
const agentDir = getPiAgentDir();
|
|
1084
|
+
return readSessionDirFromSettings(agentDir) ?? join3(agentDir, "sessions");
|
|
1049
1085
|
}
|
|
1050
1086
|
function walkJsonlFiles(dir, out) {
|
|
1051
1087
|
let entries;
|
|
@@ -1339,9 +1375,9 @@ ${r.text}`;
|
|
|
1339
1375
|
}
|
|
1340
1376
|
|
|
1341
1377
|
// src/acp/pi-settings.ts
|
|
1342
|
-
import { existsSync as
|
|
1378
|
+
import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
|
|
1343
1379
|
import { homedir as homedir4 } from "os";
|
|
1344
|
-
import { join as join4, resolve as
|
|
1380
|
+
import { join as join4, resolve as resolve3 } from "path";
|
|
1345
1381
|
function isObject(x) {
|
|
1346
1382
|
return Boolean(x) && typeof x === "object" && !Array.isArray(x);
|
|
1347
1383
|
}
|
|
@@ -1356,7 +1392,7 @@ function deepMerge(a, b) {
|
|
|
1356
1392
|
}
|
|
1357
1393
|
function readJsonFile(path) {
|
|
1358
1394
|
try {
|
|
1359
|
-
if (!
|
|
1395
|
+
if (!existsSync3(path)) return {};
|
|
1360
1396
|
const raw = readFileSync5(path, "utf-8");
|
|
1361
1397
|
const data = JSON.parse(raw);
|
|
1362
1398
|
return isObject(data) ? data : {};
|
|
@@ -1366,13 +1402,13 @@ function readJsonFile(path) {
|
|
|
1366
1402
|
}
|
|
1367
1403
|
function getMergedSettings(cwd) {
|
|
1368
1404
|
const globalSettingsPath = join4(getAgentDir(), "settings.json");
|
|
1369
|
-
const projectSettingsPath =
|
|
1405
|
+
const projectSettingsPath = resolve3(cwd, ".pi", "settings.json");
|
|
1370
1406
|
const global = readJsonFile(globalSettingsPath);
|
|
1371
1407
|
const project = readJsonFile(projectSettingsPath);
|
|
1372
1408
|
return deepMerge(global, project);
|
|
1373
1409
|
}
|
|
1374
1410
|
function getAgentDir() {
|
|
1375
|
-
return process.env.PI_CODING_AGENT_DIR ?
|
|
1411
|
+
return process.env.PI_CODING_AGENT_DIR ? resolve3(process.env.PI_CODING_AGENT_DIR) : join4(homedir4(), ".pi", "agent");
|
|
1376
1412
|
}
|
|
1377
1413
|
function getEnableSkillCommands(cwd) {
|
|
1378
1414
|
const merged = getMergedSettings(cwd);
|
|
@@ -1422,81 +1458,10 @@ function toAvailableCommandsFromPiGetCommands(data, opts) {
|
|
|
1422
1458
|
}
|
|
1423
1459
|
|
|
1424
1460
|
// src/acp/agent.ts
|
|
1425
|
-
import { isAbsolute as
|
|
1426
|
-
import { existsSync as existsSync4, readFileSync as
|
|
1427
|
-
import { join as
|
|
1461
|
+
import { isAbsolute as isAbsolute3 } from "path";
|
|
1462
|
+
import { existsSync as existsSync4, readFileSync as readFileSync6, realpathSync, readdirSync as readdirSync3, statSync as statSync2, unlinkSync } from "fs";
|
|
1463
|
+
import { join as join5, dirname as dirname2, basename } from "path";
|
|
1428
1464
|
import { spawnSync } from "child_process";
|
|
1429
|
-
|
|
1430
|
-
// src/pi-auth/status.ts
|
|
1431
|
-
import { existsSync as existsSync3, readFileSync as readFileSync6 } from "fs";
|
|
1432
|
-
import { homedir as homedir5 } from "os";
|
|
1433
|
-
import { join as join5 } from "path";
|
|
1434
|
-
function safeReadJson(path) {
|
|
1435
|
-
try {
|
|
1436
|
-
if (!existsSync3(path)) return null;
|
|
1437
|
-
const raw = readFileSync6(path, "utf-8");
|
|
1438
|
-
if (!raw.trim()) return null;
|
|
1439
|
-
return JSON.parse(raw);
|
|
1440
|
-
} catch {
|
|
1441
|
-
return null;
|
|
1442
|
-
}
|
|
1443
|
-
}
|
|
1444
|
-
function getPiAgentDir2() {
|
|
1445
|
-
const envDir = process.env.PI_CODING_AGENT_DIR;
|
|
1446
|
-
if (envDir) {
|
|
1447
|
-
if (envDir === "~") return homedir5();
|
|
1448
|
-
if (envDir.startsWith("~/")) return homedir5() + envDir.slice(1);
|
|
1449
|
-
return envDir;
|
|
1450
|
-
}
|
|
1451
|
-
return join5(homedir5(), ".pi", "agent");
|
|
1452
|
-
}
|
|
1453
|
-
function hasAnyPiAuthConfigured() {
|
|
1454
|
-
const agentDir = getPiAgentDir2();
|
|
1455
|
-
const authPath = join5(agentDir, "auth.json");
|
|
1456
|
-
const auth = safeReadJson(authPath);
|
|
1457
|
-
if (auth && typeof auth === "object" && Object.keys(auth).length > 0) return true;
|
|
1458
|
-
const modelsPath = join5(agentDir, "models.json");
|
|
1459
|
-
const models = safeReadJson(modelsPath);
|
|
1460
|
-
const providers = models?.providers;
|
|
1461
|
-
if (providers && typeof providers === "object") {
|
|
1462
|
-
for (const p of Object.values(providers)) {
|
|
1463
|
-
if (p && typeof p === "object" && typeof p.apiKey === "string" && p.apiKey.trim()) {
|
|
1464
|
-
return true;
|
|
1465
|
-
}
|
|
1466
|
-
}
|
|
1467
|
-
}
|
|
1468
|
-
const envVars = [
|
|
1469
|
-
"OPENAI_API_KEY",
|
|
1470
|
-
"AZURE_OPENAI_API_KEY",
|
|
1471
|
-
"GEMINI_API_KEY",
|
|
1472
|
-
"GROQ_API_KEY",
|
|
1473
|
-
"CEREBRAS_API_KEY",
|
|
1474
|
-
"XAI_API_KEY",
|
|
1475
|
-
"OPENROUTER_API_KEY",
|
|
1476
|
-
"AI_GATEWAY_API_KEY",
|
|
1477
|
-
"ZAI_API_KEY",
|
|
1478
|
-
"MISTRAL_API_KEY",
|
|
1479
|
-
"MINIMAX_API_KEY",
|
|
1480
|
-
"MINIMAX_CN_API_KEY",
|
|
1481
|
-
"HF_TOKEN",
|
|
1482
|
-
"OPENCODE_API_KEY",
|
|
1483
|
-
"KIMI_API_KEY",
|
|
1484
|
-
// Copilot/github
|
|
1485
|
-
"COPILOT_GITHUB_TOKEN",
|
|
1486
|
-
"GH_TOKEN",
|
|
1487
|
-
"GITHUB_TOKEN",
|
|
1488
|
-
// Anthropic oauth
|
|
1489
|
-
"ANTHROPIC_OAUTH_TOKEN",
|
|
1490
|
-
"ANTHROPIC_API_KEY"
|
|
1491
|
-
];
|
|
1492
|
-
for (const k of envVars) {
|
|
1493
|
-
const v = process.env[k];
|
|
1494
|
-
if (typeof v === "string" && v.trim()) return true;
|
|
1495
|
-
}
|
|
1496
|
-
return false;
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
// src/acp/agent.ts
|
|
1500
1465
|
import { fileURLToPath } from "url";
|
|
1501
1466
|
function builtinAvailableCommands() {
|
|
1502
1467
|
return [
|
|
@@ -1563,6 +1528,17 @@ var PiAcpAgent = class {
|
|
|
1563
1528
|
this.conn = conn;
|
|
1564
1529
|
void _config;
|
|
1565
1530
|
}
|
|
1531
|
+
cleanupFailedNewSession(sessionId, state) {
|
|
1532
|
+
this.sessions.close(sessionId);
|
|
1533
|
+
const sessionFile = typeof state?.sessionFile === "string" && state.sessionFile.trim() ? state.sessionFile : this.store.get(sessionId)?.sessionFile;
|
|
1534
|
+
if (typeof sessionFile === "string" && sessionFile.trim()) {
|
|
1535
|
+
try {
|
|
1536
|
+
if (existsSync4(sessionFile)) unlinkSync(sessionFile);
|
|
1537
|
+
} catch {
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
this.store.delete(sessionId);
|
|
1541
|
+
}
|
|
1566
1542
|
async initialize(params) {
|
|
1567
1543
|
const supportedVersion = 1;
|
|
1568
1544
|
const requested = params.protocolVersion;
|
|
@@ -1584,7 +1560,7 @@ var PiAcpAgent = class {
|
|
|
1584
1560
|
promptCapabilities: {
|
|
1585
1561
|
image: true,
|
|
1586
1562
|
audio: false,
|
|
1587
|
-
embeddedContext:
|
|
1563
|
+
embeddedContext: process.env.PI_ACP_ENABLE_EMBEDDED_CONTEXT === "true"
|
|
1588
1564
|
},
|
|
1589
1565
|
sessionCapabilities: {
|
|
1590
1566
|
// **UNSTABLE** ACP capability used by Zed's codex-acp adapter.
|
|
@@ -1595,16 +1571,10 @@ var PiAcpAgent = class {
|
|
|
1595
1571
|
};
|
|
1596
1572
|
}
|
|
1597
1573
|
async newSession(params) {
|
|
1598
|
-
if (!
|
|
1574
|
+
if (!isAbsolute3(params.cwd)) {
|
|
1599
1575
|
throw RequestError3.invalidParams(`cwd must be an absolute path: ${params.cwd}`);
|
|
1600
1576
|
}
|
|
1601
1577
|
this.lastSessionCwd = params.cwd;
|
|
1602
|
-
if (!hasAnyPiAuthConfigured()) {
|
|
1603
|
-
throw RequestError3.authRequired(
|
|
1604
|
-
{ authMethods: getAuthMethods() },
|
|
1605
|
-
"Configure an API key or log in with an OAuth provider."
|
|
1606
|
-
);
|
|
1607
|
-
}
|
|
1608
1578
|
const fileCommands = loadSlashCommands(params.cwd);
|
|
1609
1579
|
const enableSkillCommands = getEnableSkillCommands(params.cwd);
|
|
1610
1580
|
const session = await this.sessions.create({
|
|
@@ -1616,24 +1586,41 @@ var PiAcpAgent = class {
|
|
|
1616
1586
|
});
|
|
1617
1587
|
let state = null;
|
|
1618
1588
|
let availableModels = null;
|
|
1589
|
+
let stateErr = null;
|
|
1590
|
+
let availableModelsErr = null;
|
|
1619
1591
|
await Promise.all([
|
|
1620
1592
|
session.proc.getState().then((s) => {
|
|
1621
1593
|
state = s;
|
|
1622
|
-
}).catch(() => {
|
|
1594
|
+
}).catch((err) => {
|
|
1595
|
+
stateErr = err;
|
|
1623
1596
|
state = null;
|
|
1624
1597
|
}),
|
|
1625
1598
|
session.proc.getAvailableModels().then((m) => {
|
|
1626
1599
|
availableModels = m;
|
|
1627
|
-
}).catch(() => {
|
|
1600
|
+
}).catch((err) => {
|
|
1601
|
+
availableModelsErr = err;
|
|
1628
1602
|
availableModels = null;
|
|
1629
1603
|
})
|
|
1630
1604
|
]);
|
|
1605
|
+
const availableModelsAuthErr = maybeAuthRequiredError(availableModelsErr);
|
|
1606
|
+
if (availableModelsAuthErr) {
|
|
1607
|
+
this.cleanupFailedNewSession(session.sessionId, state);
|
|
1608
|
+
throw availableModelsAuthErr;
|
|
1609
|
+
}
|
|
1610
|
+
if (availableModelsErr) {
|
|
1611
|
+
this.cleanupFailedNewSession(session.sessionId, state);
|
|
1612
|
+
throw RequestError3.internalError({}, String(availableModelsErr?.message ?? availableModelsErr));
|
|
1613
|
+
}
|
|
1631
1614
|
const rawModelsCount = Array.isArray(availableModels?.models) ? availableModels.models.length : 0;
|
|
1632
1615
|
if (rawModelsCount === 0) {
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1616
|
+
this.cleanupFailedNewSession(session.sessionId, state);
|
|
1617
|
+
throw RequestError3.authRequired(
|
|
1618
|
+
{ authMethods: getAuthMethods() },
|
|
1619
|
+
"Configure an API key or log in with an OAuth provider."
|
|
1620
|
+
);
|
|
1621
|
+
}
|
|
1622
|
+
if (stateErr && maybeAuthRequiredError(stateErr)) {
|
|
1623
|
+
this.cleanupFailedNewSession(session.sessionId, state);
|
|
1637
1624
|
throw RequestError3.authRequired(
|
|
1638
1625
|
{ authMethods: getAuthMethods() },
|
|
1639
1626
|
"Configure an API key or log in with an OAuth provider."
|
|
@@ -1885,7 +1872,7 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1885
1872
|
if (piPath) {
|
|
1886
1873
|
const resolved = realpathSync(piPath);
|
|
1887
1874
|
const pkgRoot = dirname2(dirname2(resolved));
|
|
1888
|
-
const p =
|
|
1875
|
+
const p = join5(pkgRoot, "CHANGELOG.md");
|
|
1889
1876
|
if (existsSync4(p)) return p;
|
|
1890
1877
|
}
|
|
1891
1878
|
} catch {
|
|
@@ -1894,7 +1881,7 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1894
1881
|
const npmRoot = spawnSync("npm", ["root", "-g"], { encoding: "utf-8" });
|
|
1895
1882
|
const root = String(npmRoot.stdout ?? "").trim();
|
|
1896
1883
|
if (root) {
|
|
1897
|
-
const p =
|
|
1884
|
+
const p = join5(root, "@earendil-works", "pi-coding-agent", "CHANGELOG.md");
|
|
1898
1885
|
if (existsSync4(p)) return p;
|
|
1899
1886
|
}
|
|
1900
1887
|
} catch {
|
|
@@ -1914,7 +1901,7 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1914
1901
|
}
|
|
1915
1902
|
let text = "";
|
|
1916
1903
|
try {
|
|
1917
|
-
text =
|
|
1904
|
+
text = readFileSync6(changelogPath, "utf-8");
|
|
1918
1905
|
} catch (e) {
|
|
1919
1906
|
await this.conn.sessionUpdate({
|
|
1920
1907
|
sessionId: session.sessionId,
|
|
@@ -1954,7 +1941,7 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1954
1941
|
return { stopReason: "end_turn" };
|
|
1955
1942
|
}
|
|
1956
1943
|
try {
|
|
1957
|
-
const raw =
|
|
1944
|
+
const raw = readFileSync6(sessionFile, "utf-8");
|
|
1958
1945
|
if (raw.trim().length === 0) {
|
|
1959
1946
|
await this.conn.sessionUpdate({
|
|
1960
1947
|
sessionId: session.sessionId,
|
|
@@ -1982,7 +1969,7 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
1982
1969
|
return { stopReason: "end_turn" };
|
|
1983
1970
|
}
|
|
1984
1971
|
const safeSessionId = session.sessionId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1985
|
-
const outputPath =
|
|
1972
|
+
const outputPath = join5(session.cwd, `pi-session-${safeSessionId}.html`);
|
|
1986
1973
|
let resultPath = "";
|
|
1987
1974
|
try {
|
|
1988
1975
|
const result2 = await session.proc.exportHtml(outputPath);
|
|
@@ -2089,7 +2076,7 @@ ${JSON.stringify(stats, null, 2)}`;
|
|
|
2089
2076
|
return { sessions, nextCursor, _meta: {} };
|
|
2090
2077
|
}
|
|
2091
2078
|
async loadSession(params) {
|
|
2092
|
-
if (!
|
|
2079
|
+
if (!isAbsolute3(params.cwd)) {
|
|
2093
2080
|
throw RequestError3.invalidParams(`cwd must be an absolute path: ${params.cwd}`);
|
|
2094
2081
|
}
|
|
2095
2082
|
this.sessions.close(params.sessionId);
|
|
@@ -2349,16 +2336,19 @@ function compareSemver(a, b) {
|
|
|
2349
2336
|
function buildUpdateNotice() {
|
|
2350
2337
|
try {
|
|
2351
2338
|
const piVersion = spawnSync("pi", ["--version"], { encoding: "utf-8" });
|
|
2352
|
-
const installed = (String(piVersion.stdout ?? "").trim() || String(piVersion.stderr ?? "").trim()).replace(
|
|
2339
|
+
const installed = (String(piVersion.stdout ?? "").trim() || String(piVersion.stderr ?? "").trim()).replace(
|
|
2340
|
+
/^v/i,
|
|
2341
|
+
""
|
|
2342
|
+
);
|
|
2353
2343
|
if (!installed || !isSemver(installed)) return null;
|
|
2354
|
-
const latestRes = spawnSync("npm", ["view", "@
|
|
2344
|
+
const latestRes = spawnSync("npm", ["view", "@earendil-works/pi-coding-agent", "version"], {
|
|
2355
2345
|
encoding: "utf-8",
|
|
2356
2346
|
timeout: 800
|
|
2357
2347
|
});
|
|
2358
2348
|
const latest = String(latestRes.stdout ?? "").trim().replace(/^v/i, "");
|
|
2359
2349
|
if (!latest || !isSemver(latest)) return null;
|
|
2360
2350
|
if (compareSemver(latest, installed) <= 0) return null;
|
|
2361
|
-
return `New version available: v${latest} (installed v${installed}). Run: \`npm i -g @
|
|
2351
|
+
return `New version available: v${latest} (installed v${installed}). Run: \`npm i -g @earendil-works/pi-coding-agent\``;
|
|
2362
2352
|
} catch {
|
|
2363
2353
|
return null;
|
|
2364
2354
|
}
|
|
@@ -2368,7 +2358,10 @@ function buildStartupInfo(opts) {
|
|
|
2368
2358
|
const md = [];
|
|
2369
2359
|
try {
|
|
2370
2360
|
const piVersion = spawnSync("pi", ["--version"], { encoding: "utf-8" });
|
|
2371
|
-
const installed = (String(piVersion.stdout ?? "").trim() || String(piVersion.stderr ?? "").trim()).replace(
|
|
2361
|
+
const installed = (String(piVersion.stdout ?? "").trim() || String(piVersion.stderr ?? "").trim()).replace(
|
|
2362
|
+
/^v/i,
|
|
2363
|
+
""
|
|
2364
|
+
);
|
|
2372
2365
|
if (installed) {
|
|
2373
2366
|
md.push(`pi v${installed}`);
|
|
2374
2367
|
md.push("---");
|
|
@@ -2384,14 +2377,14 @@ function buildStartupInfo(opts) {
|
|
|
2384
2377
|
md.push("");
|
|
2385
2378
|
};
|
|
2386
2379
|
const contextItems = [];
|
|
2387
|
-
const contextPath =
|
|
2380
|
+
const contextPath = join5(opts.cwd, "AGENTS.md");
|
|
2388
2381
|
if (existsSync4(contextPath)) contextItems.push(contextPath);
|
|
2389
2382
|
addSection("Context", contextItems);
|
|
2390
2383
|
const skillsItems = [];
|
|
2391
2384
|
const pushSkillFromRoot = (root) => {
|
|
2392
2385
|
try {
|
|
2393
2386
|
for (const e of readdirSync3(root)) {
|
|
2394
|
-
const p =
|
|
2387
|
+
const p = join5(root, e);
|
|
2395
2388
|
try {
|
|
2396
2389
|
const st = statSync2(p);
|
|
2397
2390
|
if (st.isFile() && e.toLowerCase().endsWith(".md")) {
|
|
@@ -2411,7 +2404,7 @@ function buildStartupInfo(opts) {
|
|
|
2411
2404
|
}
|
|
2412
2405
|
for (const name of entries) {
|
|
2413
2406
|
if (name === "node_modules" || name === ".git") continue;
|
|
2414
|
-
const p =
|
|
2407
|
+
const p = join5(dir, name);
|
|
2415
2408
|
let st;
|
|
2416
2409
|
try {
|
|
2417
2410
|
st = statSync2(p);
|
|
@@ -2428,15 +2421,15 @@ function buildStartupInfo(opts) {
|
|
|
2428
2421
|
} catch {
|
|
2429
2422
|
}
|
|
2430
2423
|
};
|
|
2431
|
-
const globalSkillsDir =
|
|
2424
|
+
const globalSkillsDir = join5(getAgentDir(), "skills");
|
|
2432
2425
|
pushSkillFromRoot(globalSkillsDir);
|
|
2433
|
-
const legacyAgentsSkillsDir =
|
|
2426
|
+
const legacyAgentsSkillsDir = join5(process.env.HOME ?? "", ".agents", "skills");
|
|
2434
2427
|
pushSkillFromRoot(legacyAgentsSkillsDir);
|
|
2435
|
-
const projectSkillsDir =
|
|
2428
|
+
const projectSkillsDir = join5(opts.cwd, ".pi", "skills");
|
|
2436
2429
|
pushSkillFromRoot(projectSkillsDir);
|
|
2437
2430
|
addSection("Skills", skillsItems);
|
|
2438
2431
|
const promptsItems = [];
|
|
2439
|
-
const promptsDir =
|
|
2432
|
+
const promptsDir = join5(process.env.HOME ?? "", ".pi", "agent", "prompts");
|
|
2440
2433
|
try {
|
|
2441
2434
|
const prompts = readdirSync3(promptsDir).filter((f) => f.endsWith(".md"));
|
|
2442
2435
|
for (const f of prompts) promptsItems.push(`/${basename(f, ".md")}`);
|
|
@@ -2444,15 +2437,15 @@ function buildStartupInfo(opts) {
|
|
|
2444
2437
|
}
|
|
2445
2438
|
addSection("Prompts", promptsItems);
|
|
2446
2439
|
const extItems = [];
|
|
2447
|
-
const extDir =
|
|
2440
|
+
const extDir = join5(process.env.HOME ?? "", ".pi", "agent", "extensions");
|
|
2448
2441
|
try {
|
|
2449
2442
|
const exts = readdirSync3(extDir).filter((f) => f.endsWith(".ts") || f.endsWith(".js"));
|
|
2450
|
-
for (const f of exts) extItems.push(
|
|
2443
|
+
for (const f of exts) extItems.push(join5(extDir, f));
|
|
2451
2444
|
} catch {
|
|
2452
2445
|
}
|
|
2453
2446
|
try {
|
|
2454
|
-
const settingsPath =
|
|
2455
|
-
const settings = JSON.parse(
|
|
2447
|
+
const settingsPath = join5(process.env.HOME ?? "", ".pi", "agent", "settings.json");
|
|
2448
|
+
const settings = JSON.parse(readFileSync6(settingsPath, "utf-8"));
|
|
2456
2449
|
const pkgs = Array.isArray(settings?.packages) ? settings.packages : [];
|
|
2457
2450
|
for (const pkg2 of pkgs) {
|
|
2458
2451
|
const s = String(pkg2);
|
|
@@ -2477,9 +2470,9 @@ function readNearestPackageJson(metaUrl) {
|
|
|
2477
2470
|
try {
|
|
2478
2471
|
let dir = dirname2(fileURLToPath(metaUrl));
|
|
2479
2472
|
for (let i = 0; i < 6; i++) {
|
|
2480
|
-
const p =
|
|
2473
|
+
const p = join5(dir, "package.json");
|
|
2481
2474
|
if (existsSync4(p)) {
|
|
2482
|
-
const json = JSON.parse(
|
|
2475
|
+
const json = JSON.parse(readFileSync6(p, "utf-8"));
|
|
2483
2476
|
return { name: json?.name, version: json?.version };
|
|
2484
2477
|
}
|
|
2485
2478
|
dir = dirname2(dir);
|
|
@@ -2500,7 +2493,7 @@ if (process.argv.includes("--terminal-login")) {
|
|
|
2500
2493
|
});
|
|
2501
2494
|
if (res.error && res.error.code === "ENOENT") {
|
|
2502
2495
|
process.stderr.write(
|
|
2503
|
-
`pi-acp: could not start pi (command not found: ${cmd}). Install it via \`npm install -g @
|
|
2496
|
+
`pi-acp: could not start pi (command not found: ${cmd}). Install it via \`npm install -g @earendil-works/pi-coding-agent\` or ensure \`pi\` is on your PATH.
|
|
2504
2497
|
`
|
|
2505
2498
|
);
|
|
2506
2499
|
process.exit(1);
|
|
@@ -2509,15 +2502,15 @@ if (process.argv.includes("--terminal-login")) {
|
|
|
2509
2502
|
}
|
|
2510
2503
|
var input = new WritableStream({
|
|
2511
2504
|
write(chunk) {
|
|
2512
|
-
return new Promise((
|
|
2513
|
-
if (process.stdout.destroyed || !process.stdout.writable) return
|
|
2505
|
+
return new Promise((resolve4) => {
|
|
2506
|
+
if (process.stdout.destroyed || !process.stdout.writable) return resolve4();
|
|
2514
2507
|
try {
|
|
2515
2508
|
process.stdout.write(chunk, (err) => {
|
|
2516
2509
|
void err;
|
|
2517
|
-
|
|
2510
|
+
resolve4();
|
|
2518
2511
|
});
|
|
2519
2512
|
} catch {
|
|
2520
|
-
|
|
2513
|
+
resolve4();
|
|
2521
2514
|
}
|
|
2522
2515
|
});
|
|
2523
2516
|
}
|