@victor-software-house/pi-acp 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -4,8 +4,7 @@ import { AgentSideConnection, RequestError, ndJsonStream } from "@agentclientpro
4
4
  import { spawnSync } from "node:child_process";
5
5
  import { existsSync, readFileSync, realpathSync } from "node:fs";
6
6
  import { dirname, isAbsolute, join, resolve } from "node:path";
7
- import { fileURLToPath } from "node:url";
8
- import { SessionManager, VERSION, createAgentSession } from "@mariozechner/pi-coding-agent";
7
+ import { SessionManager, createAgentSession } from "@mariozechner/pi-coding-agent";
9
8
  import * as z from "zod";
10
9
  //#region src/acp/auth.ts
11
10
  const AUTH_METHOD_ID = "pi_terminal_login";
@@ -97,6 +96,102 @@ function parseClientCapabilities(caps) {
97
96
  };
98
97
  }
99
98
  //#endregion
99
+ //#region src/acp/model-alias.ts
100
+ /**
101
+ * Tokenize a string: split on non-alphanumeric, lowercase, strip "claude".
102
+ */
103
+ function tokenize(input) {
104
+ return input.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t !== "" && t !== "claude");
105
+ }
106
+ /**
107
+ * Extract a context hint in square brackets, e.g. "opus[1m]" -> { base: "opus", hint: "1m" }.
108
+ */
109
+ function extractContextHint(input) {
110
+ const match = /^(.+?)\[([^\]]+)\]$/.exec(input);
111
+ if (match !== null && match[1] !== void 0 && match[2] !== void 0) return {
112
+ base: match[1],
113
+ hint: match[2]
114
+ };
115
+ return {
116
+ base: input,
117
+ hint: null
118
+ };
119
+ }
120
+ /** Check if a string is purely numeric. */
121
+ function isNumeric(s) {
122
+ return /^\d+$/.test(s);
123
+ }
124
+ /**
125
+ * Score how well a model matches the given preference tokens.
126
+ *
127
+ * Returns a score >= 0 (higher is better), or -1 for no match.
128
+ * Requires at least one non-numeric token to match to avoid false positives
129
+ * from bare version numbers (e.g. "4" matching model version suffixes).
130
+ */
131
+ function scoreModel(model, prefTokens, hint) {
132
+ const modelStr = `${model.provider}/${model.id}/${model.name ?? ""}`.toLowerCase();
133
+ const modelTokens = tokenize(modelStr);
134
+ let matched = 0;
135
+ let hasNonNumericMatch = false;
136
+ for (const pt of prefTokens) if (modelTokens.some((mt) => mt.includes(pt) || pt.includes(mt))) {
137
+ matched++;
138
+ if (!isNumeric(pt)) hasNonNumericMatch = true;
139
+ }
140
+ if (matched === 0) return -1;
141
+ if (!hasNonNumericMatch) return -1;
142
+ let score = matched / prefTokens.length;
143
+ if (hint !== null && modelStr.includes(hint.toLowerCase())) score += .5;
144
+ const pref = prefTokens.join("");
145
+ if (model.id.toLowerCase().includes(pref)) score += .25;
146
+ return score;
147
+ }
148
+ /**
149
+ * Resolve a user-friendly model preference to a concrete model.
150
+ *
151
+ * Matching strategy (in order):
152
+ * 1. Exact match on "provider/id"
153
+ * 2. Exact match on "id" alone
154
+ * 3. Tokenized scored match with optional context hint
155
+ *
156
+ * Returns null if no model matches.
157
+ */
158
+ function resolveModelPreference(models, preference) {
159
+ const trimmed = preference.trim();
160
+ if (trimmed === "") return null;
161
+ if (trimmed.includes("/")) {
162
+ const [p, ...rest] = trimmed.split("/");
163
+ const provider = p ?? "";
164
+ const id = rest.join("/");
165
+ const exact = models.find((m) => m.provider.toLowerCase() === provider.toLowerCase() && m.id.toLowerCase() === id.toLowerCase());
166
+ if (exact !== void 0) return {
167
+ provider: exact.provider,
168
+ id: exact.id
169
+ };
170
+ }
171
+ const byId = models.find((m) => m.id.toLowerCase() === trimmed.toLowerCase());
172
+ if (byId !== void 0) return {
173
+ provider: byId.provider,
174
+ id: byId.id
175
+ };
176
+ const { base, hint } = extractContextHint(trimmed);
177
+ const prefTokens = tokenize(base);
178
+ if (prefTokens.length === 0) return null;
179
+ let bestModel = null;
180
+ let bestScore = -1;
181
+ for (const model of models) {
182
+ const s = scoreModel(model, prefTokens, hint);
183
+ if (s > bestScore) {
184
+ bestScore = s;
185
+ bestModel = model;
186
+ }
187
+ }
188
+ if (bestModel === null || bestScore < .5) return null;
189
+ return {
190
+ provider: bestModel.provider,
191
+ id: bestModel.id
192
+ };
193
+ }
194
+ //#endregion
100
195
  //#region src/acp/pi-settings.ts
101
196
  /**
102
197
  * Read pi settings from global and project config files.
@@ -149,12 +244,6 @@ function skillCommandsEnabled(cwd) {
149
244
  if (typeof settings.skills?.enableSkillCommands === "boolean") return settings.skills.enableSkillCommands;
150
245
  return true;
151
246
  }
152
- function quietStartupEnabled(cwd) {
153
- const settings = resolvedSettings(cwd);
154
- if (typeof settings.quietStartup === "boolean") return settings.quietStartup;
155
- if (typeof settings.quietStart === "boolean") return settings.quietStart;
156
- return false;
157
- }
158
247
  //#endregion
159
248
  //#region src/acp/translate/tool-content.ts
160
249
  const textBlockSchema = z.object({
@@ -249,13 +338,31 @@ function extractContentBlocks(result) {
249
338
  return blocks;
250
339
  }
251
340
  /**
252
- * Escape text that would be interpreted as markdown formatting.
341
+ * Find the longest consecutive backtick sequence in a string.
342
+ */
343
+ function longestBacktickRun(text) {
344
+ let max = 0;
345
+ let current = 0;
346
+ for (const ch of text) if (ch === "`") {
347
+ current++;
348
+ if (current > max) max = current;
349
+ } else current = 0;
350
+ return max;
351
+ }
352
+ /**
353
+ * Wrap text in a dynamically-sized backtick fence to prevent markdown rendering.
253
354
  *
254
- * Prevents file content from being rendered as headings, links, code
255
- * blocks, or horizontal rules when displayed in an ACP client.
355
+ * Instead of character-level escaping (which fails on files containing backtick
356
+ * sequences, indented code blocks, blockquotes, and list markers), this wraps
357
+ * the entire text in a backtick fence whose length exceeds any backtick sequence
358
+ * in the content. This approach is simpler and strictly more correct (following
359
+ * the claude-agent-acp pattern).
256
360
  */
257
361
  function markdownEscape(text) {
258
- return text.replace(/^(#{1,6})\s/gm, "\\$1 ").replace(/\[/g, "\\[").replace(/\]/g, "\\]").replace(/^([-*_])\1{2,}$/gm, "\\$1$1$1").replace(/</g, "\\<");
362
+ if (text === "") return "";
363
+ const fenceLen = Math.max(3, longestBacktickRun(text) + 1);
364
+ const fence = "`".repeat(fenceLen);
365
+ return `${fence}\n${text.endsWith("\n") ? text.slice(0, -1) : text}\n${fence}`;
259
366
  }
260
367
  /**
261
368
  * Format tool output into `ToolCallContent[]` by tool name.
@@ -363,6 +470,18 @@ function wrapStreamingBashOutput(text) {
363
470
  return `\`\`\`console\n${text}\n\`\`\``;
364
471
  }
365
472
  //#endregion
473
+ //#region src/acp/unreachable.ts
474
+ /**
475
+ * Exhaustive switch/case helper.
476
+ *
477
+ * Logs unknown values instead of silently ignoring them, aiding debugging
478
+ * when the pi SDK adds new event types.
479
+ */
480
+ function unreachable(value, context) {
481
+ const label = context !== void 0 ? `[${context}] ` : "";
482
+ console.warn(`${label}Unhandled value: ${String(value)}`);
483
+ }
484
+ //#endregion
366
485
  //#region src/acp/session.ts
367
486
  function findUniqueLineNumber(text, needle) {
368
487
  if (!needle) return void 0;
@@ -528,11 +647,12 @@ var PiAcpSession = class {
528
647
  mcpServers;
529
648
  piSession;
530
649
  supportsTerminalOutput;
531
- startupInfo = null;
532
- startupInfoSent = false;
533
650
  conn;
534
651
  cancelRequested = false;
652
+ promptRunning = false;
535
653
  pendingTurn = null;
654
+ /** Queued prompts waiting for the active turn to complete. */
655
+ pendingMessages = [];
536
656
  currentToolCalls = /* @__PURE__ */ new Map();
537
657
  /** Map of toolCallId -> toolName for streaming updates (Phase 5). */
538
658
  toolCallNames = /* @__PURE__ */ new Map();
@@ -553,21 +673,25 @@ var PiAcpSession = class {
553
673
  this.unsubscribe?.();
554
674
  this.piSession.dispose();
555
675
  }
556
- setStartupInfo(text) {
557
- this.startupInfo = text;
558
- }
559
- sendStartupInfoIfPending() {
560
- if (this.startupInfoSent || this.startupInfo === null) return;
561
- this.startupInfoSent = true;
562
- this.emit({
563
- sessionUpdate: "agent_message_chunk",
564
- content: {
565
- type: "text",
566
- text: this.startupInfo
567
- }
676
+ async prompt(message, images = []) {
677
+ if (this.promptRunning) return new Promise((resolve, reject) => {
678
+ this.pendingMessages.push({
679
+ message,
680
+ images,
681
+ resolve,
682
+ reject
683
+ });
568
684
  });
685
+ return this.executePrompt(message, images);
569
686
  }
570
- async prompt(message, images = []) {
687
+ async cancel() {
688
+ this.cancelRequested = true;
689
+ for (const pending of this.pendingMessages) pending.resolve("cancelled");
690
+ this.pendingMessages = [];
691
+ await this.piSession.abort();
692
+ }
693
+ executePrompt(message, images) {
694
+ this.promptRunning = true;
571
695
  const turnPromise = new Promise((resolve, reject) => {
572
696
  this.cancelRequested = false;
573
697
  this.pendingTurn = {
@@ -585,9 +709,17 @@ var PiAcpSession = class {
585
709
  });
586
710
  return turnPromise;
587
711
  }
588
- async cancel() {
589
- this.cancelRequested = true;
590
- await this.piSession.abort();
712
+ /**
713
+ * Dequeue and execute the next pending prompt, if any.
714
+ * Called after a turn completes.
715
+ */
716
+ dequeueNextPrompt() {
717
+ const next = this.pendingMessages.shift();
718
+ if (next === void 0) {
719
+ this.promptRunning = false;
720
+ return;
721
+ }
722
+ this.executePrompt(next.message, next.images).then(next.resolve, next.reject);
591
723
  }
592
724
  wasCancelRequested() {
593
725
  return this.cancelRequested;
@@ -622,7 +754,9 @@ var PiAcpSession = class {
622
754
  case "agent_end":
623
755
  this.handleAgentEnd();
624
756
  break;
625
- default: break;
757
+ default:
758
+ unreachable(ev, "handlePiEvent");
759
+ break;
626
760
  }
627
761
  }
628
762
  handleMessageUpdate(ame) {
@@ -792,11 +926,6 @@ var PiAcpSession = class {
792
926
  }, ...formatted];
793
927
  }
794
928
  } catch {}
795
- const meta = buildToolMeta(toolName, this.supportsTerminalOutput && isTerminalTool(toolName) ? { terminal_exit: {
796
- terminal_id: toolCallId,
797
- exit_code: extractExitCode(result),
798
- signal: null
799
- } } : void 0);
800
929
  if (content === null) {
801
930
  const formatted = formatToolContent(toolName, result, isError);
802
931
  content = formatted.length > 0 ? formatted : null;
@@ -811,6 +940,24 @@ var PiAcpSession = class {
811
940
  }
812
941
  }];
813
942
  }
943
+ if (this.supportsTerminalOutput && isTerminalTool(toolName)) {
944
+ const outputText = extractStreamingText(result);
945
+ if (outputText !== "") this.emit({
946
+ sessionUpdate: "tool_call_update",
947
+ toolCallId,
948
+ status: "in_progress",
949
+ _meta: buildToolMeta(toolName, { terminal_output: {
950
+ terminal_id: toolCallId,
951
+ data: outputText
952
+ } }),
953
+ rawOutput: result
954
+ });
955
+ }
956
+ const meta = buildToolMeta(toolName, this.supportsTerminalOutput && isTerminalTool(toolName) ? { terminal_exit: {
957
+ terminal_id: toolCallId,
958
+ exit_code: extractExitCode(result),
959
+ signal: null
960
+ } } : void 0);
814
961
  this.emit({
815
962
  sessionUpdate: "tool_call_update",
816
963
  toolCallId,
@@ -830,6 +977,7 @@ var PiAcpSession = class {
830
977
  this.lastAssistantStopReason = null;
831
978
  this.pendingTurn?.resolve(reason);
832
979
  this.pendingTurn = null;
980
+ this.dequeueNextPrompt();
833
981
  });
834
982
  }
835
983
  /**
@@ -1037,52 +1185,58 @@ function hasPiAuthConfigured() {
1037
1185
  return hasAuthJson() || hasCustomProviderKey() || hasProviderEnvVar();
1038
1186
  }
1039
1187
  //#endregion
1188
+ //#region package.json
1189
+ var name = "@victor-software-house/pi-acp";
1190
+ var version = "0.4.0";
1191
+ //#endregion
1040
1192
  //#region src/acp/agent.ts
1041
- function builtinAvailableCommands() {
1042
- return [
1043
- {
1044
- name: "compact",
1045
- description: "Manually compact the session context",
1046
- input: { hint: "optional custom instructions" }
1047
- },
1048
- {
1049
- name: "autocompact",
1050
- description: "Toggle automatic context compaction",
1051
- input: { hint: "on|off|toggle" }
1052
- },
1053
- {
1054
- name: "export",
1055
- description: "Export session to an HTML file in the session cwd"
1056
- },
1057
- {
1058
- name: "session",
1059
- description: "Show session stats (messages, tokens, cost, session file)"
1060
- },
1061
- {
1062
- name: "name",
1063
- description: "Set session display name",
1064
- input: { hint: "<name>" }
1065
- },
1066
- {
1067
- name: "steering",
1068
- description: "Get/set pi steering message delivery mode",
1069
- input: { hint: "(no args to show) all | one-at-a-time" }
1070
- },
1071
- {
1072
- name: "follow-up",
1073
- description: "Get/set pi follow-up message delivery mode",
1074
- input: { hint: "(no args to show) all | one-at-a-time" }
1075
- },
1076
- {
1077
- name: "changelog",
1078
- description: "Show pi changelog"
1079
- }
1080
- ];
1081
- }
1082
- function mergeCommands(a, b) {
1083
- const out = [];
1193
+ /** Builtin ACP slash commands handled directly by the adapter. */
1194
+ const BUILTIN_COMMANDS = [
1195
+ {
1196
+ name: "compact",
1197
+ description: "Manually compact the session context",
1198
+ input: { hint: "optional custom instructions" }
1199
+ },
1200
+ {
1201
+ name: "autocompact",
1202
+ description: "Toggle automatic context compaction",
1203
+ input: { hint: "on|off|toggle" }
1204
+ },
1205
+ {
1206
+ name: "export",
1207
+ description: "Export session to an HTML file in the session cwd"
1208
+ },
1209
+ {
1210
+ name: "session",
1211
+ description: "Show session stats (messages, tokens, cost, session file)"
1212
+ },
1213
+ {
1214
+ name: "name",
1215
+ description: "Set session display name",
1216
+ input: { hint: "<name>" }
1217
+ },
1218
+ {
1219
+ name: "steering",
1220
+ description: "Get/set pi steering message delivery mode",
1221
+ input: { hint: "(no args to show) all | one-at-a-time" }
1222
+ },
1223
+ {
1224
+ name: "follow-up",
1225
+ description: "Get/set pi follow-up message delivery mode",
1226
+ input: { hint: "(no args to show) all | one-at-a-time" }
1227
+ },
1228
+ {
1229
+ name: "changelog",
1230
+ description: "Show pi changelog"
1231
+ }
1232
+ ];
1233
+ /**
1234
+ * Deduplicate commands by name. First occurrence wins.
1235
+ */
1236
+ function deduplicateCommands(commands) {
1084
1237
  const seen = /* @__PURE__ */ new Set();
1085
- for (const c of [...a, ...b]) {
1238
+ const out = [];
1239
+ for (const c of commands) {
1086
1240
  if (seen.has(c.name)) continue;
1087
1241
  seen.add(c.name);
1088
1242
  out.push(c);
@@ -1113,7 +1267,6 @@ function truncateSessionTitle(text) {
1113
1267
  if (oneLine.length <= SESSION_TITLE_MAX) return oneLine;
1114
1268
  return `${oneLine.slice(0, SESSION_TITLE_MAX - 1)}…`;
1115
1269
  }
1116
- const pkg = readNearestPackageJson(import.meta.url);
1117
1270
  var PiAcpAgent = class {
1118
1271
  conn;
1119
1272
  sessions = new SessionManager$1();
@@ -1138,9 +1291,9 @@ var PiAcpAgent = class {
1138
1291
  return {
1139
1292
  protocolVersion: requested === supportedVersion ? requested : supportedVersion,
1140
1293
  agentInfo: {
1141
- name: pkg.name,
1294
+ name,
1142
1295
  title: "pi ACP adapter",
1143
- version: pkg.version
1296
+ version
1144
1297
  },
1145
1298
  authMethods: buildAuthMethods({ supportsTerminalAuthMeta: this.clientCapabilities.terminalAuth }),
1146
1299
  agentCapabilities: {
@@ -1192,24 +1345,9 @@ var PiAcpAgent = class {
1192
1345
  supportsTerminalOutput: this.clientCapabilities.terminalOutput
1193
1346
  });
1194
1347
  this.sessions.register(session);
1195
- const quietStartup = quietStartupEnabled(params.cwd);
1196
- const updateNotice = buildUpdateNotice();
1197
- const preludeText = quietStartup ? updateNotice !== null ? `${updateNotice}\n` : "" : buildStartupInfo({
1198
- cwd: params.cwd,
1199
- updateNotice
1200
- });
1201
- if (preludeText) session.setStartupInfo(preludeText);
1202
1348
  const modes = buildThinkingModes(piSession);
1203
1349
  const models = buildModelState(piSession);
1204
1350
  const configOptions = buildConfigOptions(modes, models);
1205
- const response = {
1206
- sessionId: session.sessionId,
1207
- configOptions,
1208
- modes,
1209
- models,
1210
- _meta: { piAcp: { startupInfo: preludeText || null } }
1211
- };
1212
- if (preludeText) setTimeout(() => session.sendStartupInfoIfPending(), 0);
1213
1351
  const enableSkillCommands = skillCommandsEnabled(params.cwd);
1214
1352
  setTimeout(() => {
1215
1353
  (async () => {
@@ -1219,13 +1357,18 @@ var PiAcpAgent = class {
1219
1357
  sessionId: session.sessionId,
1220
1358
  update: {
1221
1359
  sessionUpdate: "available_commands_update",
1222
- availableCommands: mergeCommands(commands, builtinAvailableCommands())
1360
+ availableCommands: deduplicateCommands([...commands, ...BUILTIN_COMMANDS])
1223
1361
  }
1224
1362
  });
1225
1363
  } catch {}
1226
1364
  })();
1227
1365
  }, 0);
1228
- return response;
1366
+ return {
1367
+ sessionId: session.sessionId,
1368
+ configOptions,
1369
+ modes,
1370
+ models
1371
+ };
1229
1372
  }
1230
1373
  async authenticate(_params) {
1231
1374
  return {};
@@ -1453,7 +1596,7 @@ var PiAcpAgent = class {
1453
1596
  sessionId: session.sessionId,
1454
1597
  update: {
1455
1598
  sessionUpdate: "available_commands_update",
1456
- availableCommands: mergeCommands(commands, builtinAvailableCommands())
1599
+ availableCommands: deduplicateCommands([...commands, ...BUILTIN_COMMANDS])
1457
1600
  }
1458
1601
  });
1459
1602
  } catch {}
@@ -1462,8 +1605,7 @@ var PiAcpAgent = class {
1462
1605
  return {
1463
1606
  configOptions,
1464
1607
  modes,
1465
- models,
1466
- _meta: { piAcp: { startupInfo: null } }
1608
+ models
1467
1609
  };
1468
1610
  }
1469
1611
  async unstable_closeSession(params) {
@@ -1518,7 +1660,7 @@ var PiAcpAgent = class {
1518
1660
  sessionId: session.sessionId,
1519
1661
  update: {
1520
1662
  sessionUpdate: "available_commands_update",
1521
- availableCommands: mergeCommands(commands, builtinAvailableCommands())
1663
+ availableCommands: deduplicateCommands([...commands, ...BUILTIN_COMMANDS])
1522
1664
  }
1523
1665
  });
1524
1666
  } catch {}
@@ -1571,7 +1713,7 @@ var PiAcpAgent = class {
1571
1713
  sessionId: session.sessionId,
1572
1714
  update: {
1573
1715
  sessionUpdate: "available_commands_update",
1574
- availableCommands: mergeCommands(commands, builtinAvailableCommands())
1716
+ availableCommands: deduplicateCommands([...commands, ...BUILTIN_COMMANDS])
1575
1717
  }
1576
1718
  });
1577
1719
  } catch {}
@@ -1603,22 +1745,10 @@ var PiAcpAgent = class {
1603
1745
  }
1604
1746
  async unstable_setSessionModel(params) {
1605
1747
  const session = this.sessions.get(params.sessionId);
1606
- let provider = null;
1607
- let modelId = null;
1608
- if (params.modelId.includes("/")) {
1609
- const [p, ...rest] = params.modelId.split("/");
1610
- provider = p ?? null;
1611
- modelId = rest.join("/");
1612
- } else modelId = params.modelId;
1613
- if (provider === null) {
1614
- const found = session.piSession.modelRegistry.getAvailable().find((m) => m.id === modelId);
1615
- if (found) {
1616
- provider = found.provider;
1617
- modelId = found.id;
1618
- }
1619
- }
1620
- if (provider === null || modelId === null) throw RequestError.invalidParams(`Unknown modelId: ${params.modelId}`);
1621
- const model = session.piSession.modelRegistry.getAvailable().find((m) => m.provider === provider && m.id === modelId);
1748
+ const available = session.piSession.modelRegistry.getAvailable();
1749
+ const resolved = resolveModelPreference(available, params.modelId);
1750
+ if (resolved === null) throw RequestError.invalidParams(`Unknown modelId: ${params.modelId}`);
1751
+ const model = available.find((m) => m.provider === resolved.provider && m.id === resolved.id);
1622
1752
  if (!model) throw RequestError.invalidParams(`Unknown modelId: ${params.modelId}`);
1623
1753
  await session.piSession.setModel(model);
1624
1754
  this.emitConfigOptionUpdate(session);
@@ -1628,22 +1758,10 @@ var PiAcpAgent = class {
1628
1758
  const configId = String(params.configId);
1629
1759
  const value = String(params.value);
1630
1760
  if (configId === "model") {
1631
- let provider = null;
1632
- let modelId = null;
1633
- if (value.includes("/")) {
1634
- const [p, ...rest] = value.split("/");
1635
- provider = p ?? null;
1636
- modelId = rest.join("/");
1637
- } else modelId = value;
1638
- if (provider === null) {
1639
- const found = session.piSession.modelRegistry.getAvailable().find((m) => m.id === modelId);
1640
- if (found) {
1641
- provider = found.provider;
1642
- modelId = found.id;
1643
- }
1644
- }
1645
- if (provider === null || modelId === null) throw RequestError.invalidParams(`Unknown model: ${value}`);
1646
- const model = session.piSession.modelRegistry.getAvailable().find((m) => m.provider === provider && m.id === modelId);
1761
+ const available = session.piSession.modelRegistry.getAvailable();
1762
+ const resolved = resolveModelPreference(available, value);
1763
+ if (resolved === null) throw RequestError.invalidParams(`Unknown model: ${value}`);
1764
+ const model = available.find((m) => m.provider === resolved.provider && m.id === resolved.id);
1647
1765
  if (!model) throw RequestError.invalidParams(`Unknown model: ${value}`);
1648
1766
  await session.piSession.setModel(model);
1649
1767
  } else if (configId === "thought_level") {
@@ -2027,64 +2145,6 @@ function buildCommandList(piSession, enableSkillCommands) {
2027
2145
  });
2028
2146
  return commands;
2029
2147
  }
2030
- let cachedUpdateNotice;
2031
- function buildUpdateNotice() {
2032
- if (cachedUpdateNotice !== void 0) return cachedUpdateNotice;
2033
- try {
2034
- const installed = VERSION;
2035
- if (!installed || !isSemver(installed)) {
2036
- cachedUpdateNotice = null;
2037
- return null;
2038
- }
2039
- const latestRes = spawnSync("npm", [
2040
- "view",
2041
- "@mariozechner/pi-coding-agent",
2042
- "version"
2043
- ], {
2044
- encoding: "utf-8",
2045
- timeout: 800
2046
- });
2047
- const latest = String(latestRes.stdout ?? "").trim().replace(/^v/i, "");
2048
- if (!latest || !isSemver(latest)) {
2049
- cachedUpdateNotice = null;
2050
- return null;
2051
- }
2052
- if (compareSemver(latest, installed) <= 0) {
2053
- cachedUpdateNotice = null;
2054
- return null;
2055
- }
2056
- cachedUpdateNotice = `New version available: v${latest} (installed v${installed}). Run: \`npm i -g @mariozechner/pi-coding-agent\``;
2057
- return cachedUpdateNotice;
2058
- } catch {
2059
- cachedUpdateNotice = null;
2060
- return null;
2061
- }
2062
- }
2063
- function buildStartupInfo(opts) {
2064
- const md = [];
2065
- if (VERSION) {
2066
- md.push(`pi v${VERSION}`);
2067
- md.push("---");
2068
- md.push("");
2069
- }
2070
- const addSection = (title, items) => {
2071
- const cleaned = items.map((s) => s.trim()).filter(Boolean);
2072
- if (cleaned.length === 0) return;
2073
- md.push(`## ${title}`);
2074
- for (const item of cleaned) md.push(`- ${item}`);
2075
- md.push("");
2076
- };
2077
- const contextItems = [];
2078
- const contextPath = join(opts.cwd, "AGENTS.md");
2079
- if (existsSync(contextPath)) contextItems.push(contextPath);
2080
- addSection("Context", contextItems);
2081
- if (opts.updateNotice !== void 0 && opts.updateNotice !== null) {
2082
- md.push("---");
2083
- md.push(opts.updateNotice);
2084
- md.push("");
2085
- }
2086
- return `${md.join("\n").trim()}\n`;
2087
- }
2088
2148
  function findChangelog() {
2089
2149
  try {
2090
2150
  const which = spawnSync(process.platform === "win32" ? "where" : "which", ["pi"], { encoding: "utf-8" });
@@ -2104,42 +2164,6 @@ function findChangelog() {
2104
2164
  } catch {}
2105
2165
  return null;
2106
2166
  }
2107
- function isSemver(v) {
2108
- return /^\d+\.\d+\.\d+(?:[-+].+)?$/.test(v);
2109
- }
2110
- function compareSemver(a, b) {
2111
- const pa = a.split(/[.-]/).slice(0, 3).map((n) => Number(n));
2112
- const pb = b.split(/[.-]/).slice(0, 3).map((n) => Number(n));
2113
- for (let i = 0; i < 3; i++) {
2114
- const da = pa[i] ?? 0;
2115
- const db = pb[i] ?? 0;
2116
- if (da > db) return 1;
2117
- if (da < db) return -1;
2118
- }
2119
- return 0;
2120
- }
2121
- function readNearestPackageJson(metaUrl) {
2122
- const fallback = {
2123
- name: "pi-acp",
2124
- version: "0.0.0"
2125
- };
2126
- try {
2127
- let dir = dirname(fileURLToPath(metaUrl));
2128
- for (let i = 0; i < 6; i++) {
2129
- const p = join(dir, "package.json");
2130
- if (existsSync(p)) {
2131
- const raw = JSON.parse(readFileSync(p, "utf-8"));
2132
- if (typeof raw !== "object" || raw === null) return fallback;
2133
- return {
2134
- name: "name" in raw && typeof raw.name === "string" ? raw.name : fallback.name,
2135
- version: "version" in raw && typeof raw.version === "string" ? raw.version : fallback.version
2136
- };
2137
- }
2138
- dir = dirname(dir);
2139
- }
2140
- } catch {}
2141
- return fallback;
2142
- }
2143
2167
  //#endregion
2144
2168
  //#region src/index.ts
2145
2169
  if (process.argv.includes("--terminal-login")) {