pi-acp 0.0.26 → 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 +3 -3
- package/dist/index.js +120 -133
- package/dist/index.js.map +1 -1
- package/package.json +1 -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+
|
|
@@ -195,7 +195,7 @@ Project layout:
|
|
|
195
195
|
## Limitations
|
|
196
196
|
|
|
197
197
|
- No ACP filesystem delegation (`fs/*`) and no ACP terminal delegation (`terminal/*`). pi reads/writes and executes locally.
|
|
198
|
-
- 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.
|
|
199
199
|
- Assistant streaming is currently sent as `agent_message_chunk` (no separate thought stream).
|
|
200
200
|
- Queue is implemented client-side and should work like pi's `one-at-a-time`
|
|
201
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;
|
|
@@ -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);
|
|
@@ -2354,14 +2341,14 @@ function buildUpdateNotice() {
|
|
|
2354
2341
|
""
|
|
2355
2342
|
);
|
|
2356
2343
|
if (!installed || !isSemver(installed)) return null;
|
|
2357
|
-
const latestRes = spawnSync("npm", ["view", "@
|
|
2344
|
+
const latestRes = spawnSync("npm", ["view", "@earendil-works/pi-coding-agent", "version"], {
|
|
2358
2345
|
encoding: "utf-8",
|
|
2359
2346
|
timeout: 800
|
|
2360
2347
|
});
|
|
2361
2348
|
const latest = String(latestRes.stdout ?? "").trim().replace(/^v/i, "");
|
|
2362
2349
|
if (!latest || !isSemver(latest)) return null;
|
|
2363
2350
|
if (compareSemver(latest, installed) <= 0) return null;
|
|
2364
|
-
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\``;
|
|
2365
2352
|
} catch {
|
|
2366
2353
|
return null;
|
|
2367
2354
|
}
|
|
@@ -2390,14 +2377,14 @@ function buildStartupInfo(opts) {
|
|
|
2390
2377
|
md.push("");
|
|
2391
2378
|
};
|
|
2392
2379
|
const contextItems = [];
|
|
2393
|
-
const contextPath =
|
|
2380
|
+
const contextPath = join5(opts.cwd, "AGENTS.md");
|
|
2394
2381
|
if (existsSync4(contextPath)) contextItems.push(contextPath);
|
|
2395
2382
|
addSection("Context", contextItems);
|
|
2396
2383
|
const skillsItems = [];
|
|
2397
2384
|
const pushSkillFromRoot = (root) => {
|
|
2398
2385
|
try {
|
|
2399
2386
|
for (const e of readdirSync3(root)) {
|
|
2400
|
-
const p =
|
|
2387
|
+
const p = join5(root, e);
|
|
2401
2388
|
try {
|
|
2402
2389
|
const st = statSync2(p);
|
|
2403
2390
|
if (st.isFile() && e.toLowerCase().endsWith(".md")) {
|
|
@@ -2417,7 +2404,7 @@ function buildStartupInfo(opts) {
|
|
|
2417
2404
|
}
|
|
2418
2405
|
for (const name of entries) {
|
|
2419
2406
|
if (name === "node_modules" || name === ".git") continue;
|
|
2420
|
-
const p =
|
|
2407
|
+
const p = join5(dir, name);
|
|
2421
2408
|
let st;
|
|
2422
2409
|
try {
|
|
2423
2410
|
st = statSync2(p);
|
|
@@ -2434,15 +2421,15 @@ function buildStartupInfo(opts) {
|
|
|
2434
2421
|
} catch {
|
|
2435
2422
|
}
|
|
2436
2423
|
};
|
|
2437
|
-
const globalSkillsDir =
|
|
2424
|
+
const globalSkillsDir = join5(getAgentDir(), "skills");
|
|
2438
2425
|
pushSkillFromRoot(globalSkillsDir);
|
|
2439
|
-
const legacyAgentsSkillsDir =
|
|
2426
|
+
const legacyAgentsSkillsDir = join5(process.env.HOME ?? "", ".agents", "skills");
|
|
2440
2427
|
pushSkillFromRoot(legacyAgentsSkillsDir);
|
|
2441
|
-
const projectSkillsDir =
|
|
2428
|
+
const projectSkillsDir = join5(opts.cwd, ".pi", "skills");
|
|
2442
2429
|
pushSkillFromRoot(projectSkillsDir);
|
|
2443
2430
|
addSection("Skills", skillsItems);
|
|
2444
2431
|
const promptsItems = [];
|
|
2445
|
-
const promptsDir =
|
|
2432
|
+
const promptsDir = join5(process.env.HOME ?? "", ".pi", "agent", "prompts");
|
|
2446
2433
|
try {
|
|
2447
2434
|
const prompts = readdirSync3(promptsDir).filter((f) => f.endsWith(".md"));
|
|
2448
2435
|
for (const f of prompts) promptsItems.push(`/${basename(f, ".md")}`);
|
|
@@ -2450,15 +2437,15 @@ function buildStartupInfo(opts) {
|
|
|
2450
2437
|
}
|
|
2451
2438
|
addSection("Prompts", promptsItems);
|
|
2452
2439
|
const extItems = [];
|
|
2453
|
-
const extDir =
|
|
2440
|
+
const extDir = join5(process.env.HOME ?? "", ".pi", "agent", "extensions");
|
|
2454
2441
|
try {
|
|
2455
2442
|
const exts = readdirSync3(extDir).filter((f) => f.endsWith(".ts") || f.endsWith(".js"));
|
|
2456
|
-
for (const f of exts) extItems.push(
|
|
2443
|
+
for (const f of exts) extItems.push(join5(extDir, f));
|
|
2457
2444
|
} catch {
|
|
2458
2445
|
}
|
|
2459
2446
|
try {
|
|
2460
|
-
const settingsPath =
|
|
2461
|
-
const settings = JSON.parse(
|
|
2447
|
+
const settingsPath = join5(process.env.HOME ?? "", ".pi", "agent", "settings.json");
|
|
2448
|
+
const settings = JSON.parse(readFileSync6(settingsPath, "utf-8"));
|
|
2462
2449
|
const pkgs = Array.isArray(settings?.packages) ? settings.packages : [];
|
|
2463
2450
|
for (const pkg2 of pkgs) {
|
|
2464
2451
|
const s = String(pkg2);
|
|
@@ -2483,9 +2470,9 @@ function readNearestPackageJson(metaUrl) {
|
|
|
2483
2470
|
try {
|
|
2484
2471
|
let dir = dirname2(fileURLToPath(metaUrl));
|
|
2485
2472
|
for (let i = 0; i < 6; i++) {
|
|
2486
|
-
const p =
|
|
2473
|
+
const p = join5(dir, "package.json");
|
|
2487
2474
|
if (existsSync4(p)) {
|
|
2488
|
-
const json = JSON.parse(
|
|
2475
|
+
const json = JSON.parse(readFileSync6(p, "utf-8"));
|
|
2489
2476
|
return { name: json?.name, version: json?.version };
|
|
2490
2477
|
}
|
|
2491
2478
|
dir = dirname2(dir);
|
|
@@ -2506,7 +2493,7 @@ if (process.argv.includes("--terminal-login")) {
|
|
|
2506
2493
|
});
|
|
2507
2494
|
if (res.error && res.error.code === "ENOENT") {
|
|
2508
2495
|
process.stderr.write(
|
|
2509
|
-
`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.
|
|
2510
2497
|
`
|
|
2511
2498
|
);
|
|
2512
2499
|
process.exit(1);
|
|
@@ -2515,15 +2502,15 @@ if (process.argv.includes("--terminal-login")) {
|
|
|
2515
2502
|
}
|
|
2516
2503
|
var input = new WritableStream({
|
|
2517
2504
|
write(chunk) {
|
|
2518
|
-
return new Promise((
|
|
2519
|
-
if (process.stdout.destroyed || !process.stdout.writable) return
|
|
2505
|
+
return new Promise((resolve4) => {
|
|
2506
|
+
if (process.stdout.destroyed || !process.stdout.writable) return resolve4();
|
|
2520
2507
|
try {
|
|
2521
2508
|
process.stdout.write(chunk, (err) => {
|
|
2522
2509
|
void err;
|
|
2523
|
-
|
|
2510
|
+
resolve4();
|
|
2524
2511
|
});
|
|
2525
2512
|
} catch {
|
|
2526
|
-
|
|
2513
|
+
resolve4();
|
|
2527
2514
|
}
|
|
2528
2515
|
});
|
|
2529
2516
|
}
|