billion-context-pi 0.1.36 → 0.1.37

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.js CHANGED
@@ -132,9 +132,11 @@ function prune(messages, state, options = {}) {
132
132
  const indexById = /* @__PURE__ */ new Map();
133
133
  messages.forEach((message, index) => indexById.set(message.id, index));
134
134
  const anchors = inject ? collectSummaryAnchors(state, indexById) : [];
135
- return stripOrphanedToolResults(
136
- stripOrphanedToolCalls(
137
- rebuildMessages(messages, covered, firstUserIndex, anchors)
135
+ return stripOrphanedReasoning(
136
+ stripOrphanedToolResults(
137
+ stripOrphanedToolCalls(
138
+ rebuildMessages(messages, covered, firstUserIndex, anchors)
139
+ )
138
140
  )
139
141
  );
140
142
  }
@@ -211,6 +213,24 @@ function stripOrphanedToolCalls(messages) {
211
213
  (m) => m.contentType !== "tool-call" || !m.toolCallId || m.toolName === "compress" || knownResultIds.has(m.toolCallId)
212
214
  );
213
215
  }
216
+ function stripOrphanedReasoning(messages) {
217
+ const drop = /* @__PURE__ */ new Set();
218
+ for (let i = 0; i < messages.length; i++) {
219
+ if (drop.has(i)) continue;
220
+ if (messages[i].contentType !== "reasoning") continue;
221
+ let j = i;
222
+ while (j + 1 < messages.length && messages[j + 1].contentType === "reasoning") {
223
+ j++;
224
+ }
225
+ const companion = messages[j + 1];
226
+ const hasCompanion = companion !== void 0 && companion.role === "assistant" && (companion.contentType === "text" || companion.contentType === "tool-call");
227
+ if (!hasCompanion) {
228
+ for (let k = i; k <= j; k++) drop.add(k);
229
+ }
230
+ }
231
+ if (drop.size === 0) return messages;
232
+ return messages.filter((_, i) => !drop.has(i));
233
+ }
214
234
  function syncBlocks(messages, state) {
215
235
  const presentIds = new Set(messages.map((message) => message.id));
216
236
  const deactivated = [];
@@ -343,6 +363,18 @@ function parseBoundary(ref) {
343
363
  }
344
364
  return null;
345
365
  }
366
+ var BoundaryNotFoundError = class extends Error {
367
+ code = "BOUNDARY_NOT_FOUND";
368
+ kind;
369
+ endpoint;
370
+ constructor(kind, endpoint, message) {
371
+ super(message);
372
+ this.name = "BoundaryNotFoundError";
373
+ this.code = "BOUNDARY_NOT_FOUND";
374
+ this.kind = kind;
375
+ this.endpoint = endpoint;
376
+ }
377
+ };
346
378
  function resolveBoundaries(input) {
347
379
  const start = parseBoundary(input.startRef);
348
380
  const end = parseBoundary(input.endRef);
@@ -355,13 +387,8 @@ function resolveBoundaries(input) {
355
387
  input.messages.forEach(
356
388
  (message, index) => indexByRawId.set(message.id, index)
357
389
  );
358
- let startIndex = resolveAnchorIndex(start, input.state, indexByRawId);
359
- let endIndex = resolveAnchorIndex(end, input.state, indexByRawId);
360
- if (startIndex === null || endIndex === null) {
361
- throw new Error(
362
- `Boundary not found in visible context (likely consumed by an existing block). startId="${input.startRef}", endId="${input.endRef}".`
363
- );
364
- }
390
+ let startIndex = resolveAnchorIndex(start, input.state, indexByRawId, "start");
391
+ let endIndex = resolveAnchorIndex(end, input.state, indexByRawId, "end");
365
392
  if (startIndex > endIndex) {
366
393
  [startIndex, endIndex] = [endIndex, startIndex];
367
394
  }
@@ -392,16 +419,51 @@ function resolveBoundaries(input) {
392
419
  protectedGaps
393
420
  };
394
421
  }
395
- function resolveAnchorIndex(boundary, state, indexByRawId) {
422
+ function resolveAnchorIndex(boundary, state, indexByRawId, endpoint) {
423
+ const label = endpoint === "start" ? "startId" : "endId";
396
424
  if (boundary.kind === "message") {
397
425
  const rawId = state.messageRefs.byRef[boundary.raw] ?? state.messageRefs.byRef[formatPaddedRef(boundary.numericId)];
398
- if (!rawId) return null;
426
+ if (!rawId) {
427
+ throw new BoundaryNotFoundError(
428
+ "unknown",
429
+ endpoint,
430
+ `${label}="${boundary.raw}" does not exist in this session (typo or wrong session) \u2014 run acp_status for current refs.`
431
+ );
432
+ }
399
433
  const index = indexByRawId.get(rawId);
400
- return index === void 0 ? null : index;
434
+ if (index === void 0) {
435
+ throw new BoundaryNotFoundError(
436
+ "consumed",
437
+ endpoint,
438
+ `${label}="${boundary.raw}" not found in visible context (likely consumed by an existing block).`
439
+ );
440
+ }
441
+ return index;
401
442
  }
402
443
  const block = blockById(state, `b${boundary.numericId}`);
403
- if (!block || !block.active) return null;
404
- return earliestIndexOfIds(block.effectiveMessageIds, indexByRawId);
444
+ if (!block) {
445
+ throw new BoundaryNotFoundError(
446
+ "unknown",
447
+ endpoint,
448
+ `${label}="b${boundary.numericId}" does not exist in this session (typo or wrong session) \u2014 run acp_status for current refs.`
449
+ );
450
+ }
451
+ if (!block.active) {
452
+ throw new BoundaryNotFoundError(
453
+ "consumed",
454
+ endpoint,
455
+ `${label}="b${boundary.numericId}" not found in visible context (block distilled/consumed by a higher-tier block).`
456
+ );
457
+ }
458
+ const anchor = earliestIndexOfIds(block.effectiveMessageIds, indexByRawId);
459
+ if (anchor === null) {
460
+ throw new BoundaryNotFoundError(
461
+ "consumed",
462
+ endpoint,
463
+ `${label}="b${boundary.numericId}" not found in visible context (block messages consumed by a higher-tier block).`
464
+ );
465
+ }
466
+ return anchor;
405
467
  }
406
468
  function formatPaddedRef(index) {
407
469
  return `m${String(index).padStart(5, "0")}`;
@@ -778,6 +840,38 @@ function adjustBoundariesForToolPairs(startIndex, endIndex, messages, maxScan =
778
840
  }
779
841
  return { startIndex: newStartIndex, endIndex: newEndIndex };
780
842
  }
843
+ function adjustBoundariesForReasoningPairs(startIndex, endIndex, messages) {
844
+ if (startIndex > endIndex) {
845
+ return { startIndex, endIndex };
846
+ }
847
+ let newStartIndex = startIndex;
848
+ let newEndIndex = endIndex;
849
+ for (let i = startIndex; i <= endIndex && i < messages.length; i++) {
850
+ const msg = messages[i];
851
+ if (!msg) continue;
852
+ if (msg.contentType === "reasoning") {
853
+ let j = i;
854
+ while (j + 1 < messages.length && messages[j + 1].contentType === "reasoning") {
855
+ j++;
856
+ }
857
+ const companion = messages[j + 1];
858
+ if (companion !== void 0 && companion.role === "assistant" && (companion.contentType === "text" || companion.contentType === "tool-call") && j + 1 > newEndIndex) {
859
+ newEndIndex = j + 1;
860
+ }
861
+ }
862
+ if (msg.role === "assistant" && (msg.contentType === "text" || msg.contentType === "tool-call")) {
863
+ let k = i - 1;
864
+ while (k >= 0 && messages[k].contentType === "reasoning") {
865
+ k--;
866
+ }
867
+ const runStart = k + 1;
868
+ if (runStart < i && runStart >= 0 && messages[runStart].contentType === "reasoning" && runStart < newStartIndex) {
869
+ newStartIndex = runStart;
870
+ }
871
+ }
872
+ }
873
+ return { startIndex: newStartIndex, endIndex: newEndIndex };
874
+ }
781
875
  function refNum(ref) {
782
876
  const n = parseInt(ref.slice(1), 10);
783
877
  return Number.isNaN(n) ? -1 : n;
@@ -932,6 +1026,9 @@ function runPipeline(nodes, initial, ctx) {
932
1026
  }
933
1027
  return io;
934
1028
  }
1029
+ function rangeError(spec, message) {
1030
+ return `range ${spec.startRef}..${spec.endRef}: ${message}`;
1031
+ }
935
1032
  function createCore(ports = {}) {
936
1033
  const countTokens = ports.countTokens ?? defaultCountTokens;
937
1034
  function applyCompression(input) {
@@ -943,20 +1040,44 @@ function createCore(ports = {}) {
943
1040
  const warnings = [];
944
1041
  const protectedMessageIds = input.protectedMessageIds ?? computeProtectedRefs(input.messages, input.state, input.config, countTokens);
945
1042
  const preExistingCoverage = collectCoverage(state);
946
- const rangeIndexSets = [];
1043
+ const classifications = /* @__PURE__ */ new Map();
1044
+ const classificationErrors = [];
1045
+ const consumedRanges = [];
947
1046
  for (const spec of input.ranges) {
948
- let resolved;
949
1047
  try {
950
- resolved = resolveBoundaries({
1048
+ const resolved = resolveBoundaries({
951
1049
  startRef: spec.startRef,
952
1050
  endRef: spec.endRef,
953
1051
  messages: input.messages,
954
1052
  state
955
1053
  });
956
- } catch {
957
- continue;
1054
+ classifications.set(spec, { status: "ok", resolved });
1055
+ } catch (error) {
1056
+ if (error instanceof BoundaryNotFoundError) {
1057
+ classifications.set(
1058
+ spec,
1059
+ error.kind === "unknown" ? { status: "unknown", error } : { status: "consumed", error }
1060
+ );
1061
+ if (error.kind === "consumed") {
1062
+ consumedRanges.push(spec);
1063
+ } else {
1064
+ classificationErrors.push(rangeError(spec, error.message));
1065
+ }
1066
+ } else {
1067
+ classifications.set(spec, {
1068
+ status: "invalid",
1069
+ error: error instanceof Error ? error : new Error(String(error))
1070
+ });
1071
+ classificationErrors.push(
1072
+ rangeError(spec, error instanceof Error ? error.message : String(error))
1073
+ );
1074
+ }
958
1075
  }
959
- const indices = resolved.messageIds.map(
1076
+ }
1077
+ const rangeIndexSets = [];
1078
+ for (const [spec, resolution] of classifications) {
1079
+ if (resolution.status !== "ok") continue;
1080
+ const indices = resolution.resolved.messageIds.map(
960
1081
  (id) => input.messages.findIndex((m) => m.id === id)
961
1082
  ).filter((i) => i >= 0);
962
1083
  rangeIndexSets.push({ spec, indices });
@@ -983,37 +1104,27 @@ function createCore(ports = {}) {
983
1104
  if (input.config.compress.minCompressRange > 0 && input.ranges.length > 0) {
984
1105
  let totalRangeChars = 0;
985
1106
  let hasBlockBoundaryRange = false;
986
- for (const spec of input.ranges) {
987
- if (skipSpecs.has(spec)) continue;
988
- let resolved;
989
- try {
990
- resolved = resolveBoundaries({
991
- startRef: spec.startRef,
992
- endRef: spec.endRef,
993
- messages: input.messages,
994
- state
995
- });
996
- } catch {
997
- continue;
998
- }
999
- if (resolved.boundaryKind === "block") {
1107
+ let countedRanges = 0;
1108
+ for (const [spec, resolution] of classifications) {
1109
+ if (resolution.status !== "ok" || skipSpecs.has(spec)) continue;
1110
+ if (resolution.resolved.boundaryKind === "block") {
1000
1111
  hasBlockBoundaryRange = true;
1001
1112
  continue;
1002
1113
  }
1003
- for (const id of resolved.messageIds) {
1114
+ countedRanges++;
1115
+ for (const id of resolution.resolved.messageIds) {
1004
1116
  const msg = input.messages.find((m) => m.id === id);
1005
1117
  totalRangeChars += msg?.text?.length ?? 0;
1006
1118
  }
1007
1119
  }
1008
1120
  if (!hasBlockBoundaryRange && totalRangeChars < input.config.compress.minCompressRange) {
1121
+ const gateMessage = consumedRanges.length > 0 ? `Requested range(s) already compressed (e.g. ${consumedRanges[0].startRef}..${consumedRanges[0].endRef}); remaining compressible content ${totalRangeChars} chars < min ${input.config.compress.minCompressRange}. Nothing to do \u2014 run acp_status to see current compressible ranges.` : `Total compressible content too small (${totalRangeChars} chars across ${countedRanges} range(s), min ${input.config.compress.minCompressRange}). Combine more messages into your range(s) to meet the threshold.`;
1009
1122
  return {
1010
1123
  state: input.state,
1011
1124
  result: {
1012
1125
  blocksCreated: 0,
1013
1126
  tokensCompressed: 0,
1014
- errors: [
1015
- `Total compressible content too small (${totalRangeChars} chars across ${input.ranges.length} range(s), min ${input.config.compress.minCompressRange}). Combine more messages into your range(s) to meet the threshold.`
1016
- ],
1127
+ errors: [gateMessage, ...classificationErrors],
1017
1128
  warnings: []
1018
1129
  }
1019
1130
  };
@@ -1021,6 +1132,18 @@ function createCore(ports = {}) {
1021
1132
  }
1022
1133
  for (const spec of input.ranges) {
1023
1134
  if (skipSpecs.has(spec)) continue;
1135
+ const resolution = classifications.get(spec);
1136
+ if (resolution === void 0) continue;
1137
+ if (resolution.status === "consumed") {
1138
+ warnings.push(
1139
+ `Skipped range (${spec.startRef}..${spec.endRef}) \u2014 already compressed (messages consumed by existing block(s)); nothing to compress.`
1140
+ );
1141
+ continue;
1142
+ }
1143
+ if (resolution.status === "unknown" || resolution.status === "invalid") {
1144
+ errors.push(rangeError(spec, resolution.error.message));
1145
+ continue;
1146
+ }
1024
1147
  try {
1025
1148
  const outcome = applySingleRange({
1026
1149
  spec,
@@ -1036,7 +1159,7 @@ function createCore(ports = {}) {
1036
1159
  tokensCompressed += outcome.tokens;
1037
1160
  warnings.push(...outcome.warnings);
1038
1161
  } catch (error) {
1039
- errors.push(error instanceof Error ? error.message : String(error));
1162
+ errors.push(rangeError(spec, error instanceof Error ? error.message : String(error)));
1040
1163
  }
1041
1164
  }
1042
1165
  state.stats.compressionCount += blocksCreated;
@@ -1244,7 +1367,7 @@ function applySingleRange(input) {
1244
1367
  messages: input.messages,
1245
1368
  state: input.state
1246
1369
  });
1247
- const rangeMessageIds = applyToolPairAdjustment(
1370
+ const rangeMessageIds = applyPairBoundaryAdjustments(
1248
1371
  resolved,
1249
1372
  input.messages
1250
1373
  );
@@ -1358,20 +1481,33 @@ function applySingleRange(input) {
1358
1481
  }
1359
1482
  return { tokens: compressedTokens, warnings };
1360
1483
  }
1361
- function applyToolPairAdjustment(resolved, messages) {
1484
+ function applyPairBoundaryAdjustments(resolved, messages) {
1362
1485
  if (resolved.boundaryKind === "block") {
1363
1486
  return resolved.messageIds;
1364
1487
  }
1365
- const adjusted = adjustBoundariesForToolPairs(
1366
- resolved.startIndex,
1367
- resolved.endIndex,
1368
- messages
1369
- );
1370
- if (adjusted.startIndex === resolved.startIndex && adjusted.endIndex === resolved.endIndex) {
1488
+ let startIndex = resolved.startIndex;
1489
+ let endIndex = resolved.endIndex;
1490
+ for (let pass = 0; pass < 2; pass++) {
1491
+ const reasoningAdjusted = adjustBoundariesForReasoningPairs(
1492
+ startIndex,
1493
+ endIndex,
1494
+ messages
1495
+ );
1496
+ const toolAdjusted = adjustBoundariesForToolPairs(
1497
+ reasoningAdjusted.startIndex,
1498
+ reasoningAdjusted.endIndex,
1499
+ messages
1500
+ );
1501
+ const changed = toolAdjusted.startIndex !== startIndex || toolAdjusted.endIndex !== endIndex;
1502
+ startIndex = toolAdjusted.startIndex;
1503
+ endIndex = toolAdjusted.endIndex;
1504
+ if (!changed) break;
1505
+ }
1506
+ if (startIndex === resolved.startIndex && endIndex === resolved.endIndex) {
1371
1507
  return resolved.messageIds;
1372
1508
  }
1373
1509
  const ids = [];
1374
- for (let i = adjusted.startIndex; i <= adjusted.endIndex; i++) {
1510
+ for (let i = startIndex; i <= endIndex; i++) {
1375
1511
  const msg = messages[i];
1376
1512
  if (msg) ids.push(msg.id);
1377
1513
  }
@@ -1727,12 +1863,39 @@ DROP:
1727
1863
  - Anything marked [OBSOLETE] or [SUPERSEDED] \u2014 drop entirely, note "[N blocks obsolete]" in the summary.
1728
1864
 
1729
1865
  SIZE TARGET: 30-60 tokens per source block (including header). For a batch of N source blocks, total output \u2248 N \xD7 40 tokens. If a source block has only one trivial fact, output just the header + one line.`;
1730
- var EFFICIENCY_NOTE = `This is an efficiency nudge to compress early and keep context lean \u2014 not an overflow warning. A separate, stronger alert will appear if the context is actually full.
1866
+ var defaultPrompts = Object.freeze({
1867
+ compressPhilosophy: COMPRESS_PHILOSOPHY,
1868
+ howToCompressRules: HOW_TO_COMPRESS_RULES,
1869
+ tier2DistillRules: TIER2_DISTILL_RULES,
1870
+ tier3CondenseRules: TIER3_CONDENSE_RULES
1871
+ });
1872
+ function resolvePrompts(overrides, options = {}) {
1873
+ const clean = {};
1874
+ if (overrides) {
1875
+ for (const [key, value] of Object.entries(overrides)) {
1876
+ if (typeof value === "string") {
1877
+ clean[key] = value;
1878
+ }
1879
+ }
1880
+ }
1881
+ const keys = Object.keys(clean);
1882
+ if (keys.length > 0 && !options.acknowledgeRisk) {
1883
+ throw new Error(
1884
+ `resolvePrompts: overriding compression rules requires { acknowledgeRisk: true }. Overridden keys: ${keys.join(", ")}. These rules are quality-critical (tuned over months of production use); changing them can degrade summary quality and break retrieval (summaries may lose paths, signatures, decisions).`
1885
+ );
1886
+ }
1887
+ return { ...defaultPrompts, ...clean };
1888
+ }
1889
+ function efficiencyNote(prompts) {
1890
+ return `This is an efficiency nudge to compress early and keep context lean \u2014 not an overflow warning. A separate, stronger alert will appear if the context is actually full.
1731
1891
 
1732
- ${COMPRESS_PHILOSOPHY}`;
1733
- var EMERGENCY_HEADER = `\u26A0\uFE0F Context limit reached \u2014 compress now. Prioritize consumed tool outputs.
1892
+ ${prompts.compressPhilosophy}`;
1893
+ }
1894
+ function emergencyHeader(prompts) {
1895
+ return `\u26A0\uFE0F Context limit reached \u2014 compress now. Prioritize consumed tool outputs.
1734
1896
 
1735
- ${COMPRESS_PHILOSOPHY}`;
1897
+ ${prompts.compressPhilosophy}`;
1898
+ }
1736
1899
  function formatK(n) {
1737
1900
  if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
1738
1901
  return `${n}`;
@@ -1840,7 +2003,7 @@ function formatRanges(compressible, protectedRanges) {
1840
2003
  return `Compressible ranges (${merged.length}, oldest first):
1841
2004
  ${lines.join("\n")}`;
1842
2005
  }
1843
- function renderNudgeText(decision) {
2006
+ function renderNudgeText(decision, prompts = defaultPrompts) {
1844
2007
  const breakdownStr = formatBreakdown(decision.contextBreakdown);
1845
2008
  const rangesStr = formatRanges(decision.compressibleRanges, decision.protectedRanges ?? []);
1846
2009
  if (decision.tier !== null && decision.tier >= 2) {
@@ -1852,7 +2015,7 @@ function renderNudgeText(decision) {
1852
2015
  return {
1853
2016
  voice: "gentle",
1854
2017
  text: [
1855
- EFFICIENCY_NOTE,
2018
+ efficiencyNote(prompts),
1856
2019
  "",
1857
2020
  breakdownStr,
1858
2021
  "",
@@ -1861,9 +2024,9 @@ function renderNudgeText(decision) {
1861
2024
  blockList,
1862
2025
  `Example: compress({ content: [{ startId: "${startId}", endId: "${endId}", summary: "..." }] })`,
1863
2026
  "",
1864
- HOW_TO_COMPRESS_RULES,
2027
+ prompts.howToCompressRules,
1865
2028
  "",
1866
- isT2 ? TIER2_DISTILL_RULES : TIER3_CONDENSE_RULES
2029
+ isT2 ? prompts.tier2DistillRules : prompts.tier3CondenseRules
1867
2030
  ].join("\n")
1868
2031
  };
1869
2032
  }
@@ -1872,11 +2035,11 @@ function renderNudgeText(decision) {
1872
2035
  return {
1873
2036
  voice: "emergency",
1874
2037
  text: [
1875
- EMERGENCY_HEADER,
2038
+ emergencyHeader(prompts),
1876
2039
  "",
1877
2040
  breakdownStr,
1878
2041
  "",
1879
- HOW_TO_COMPRESS_RULES,
2042
+ prompts.howToCompressRules,
1880
2043
  "",
1881
2044
  `{ "topic": "...", "content": [{ "startId": "<ID>", "endId": "<ID>", "summary": "..." }] }`,
1882
2045
  "Only use IDs from visible messages above. Compress older work first.",
@@ -1888,11 +2051,11 @@ function renderNudgeText(decision) {
1888
2051
  return {
1889
2052
  voice: "gentle",
1890
2053
  text: [
1891
- EFFICIENCY_NOTE,
2054
+ efficiencyNote(prompts),
1892
2055
  "",
1893
2056
  breakdownStr,
1894
2057
  "",
1895
- HOW_TO_COMPRESS_RULES,
2058
+ prompts.howToCompressRules,
1896
2059
  "",
1897
2060
  rangesStr,
1898
2061
  "",
@@ -2980,6 +3143,99 @@ function mergeInitialState(parsed) {
2980
3143
  };
2981
3144
  }
2982
3145
 
3146
+ // src/sequence-match.ts
3147
+ function findUniqueLongestRun(candidates, live) {
3148
+ if (candidates.length === 0 || live.length === 0) return void 0;
3149
+ const ids = /* @__PURE__ */ new Map();
3150
+ const intern = (key) => {
3151
+ const existing = ids.get(key);
3152
+ if (existing !== void 0) return existing;
3153
+ const id = ids.size + 1;
3154
+ ids.set(key, id);
3155
+ return id;
3156
+ };
3157
+ const candidateIds = candidates.map(intern);
3158
+ const liveIds = live.map(intern);
3159
+ const separator = ids.size + 1;
3160
+ const sequence = [...candidateIds, separator, ...liveIds];
3161
+ const suffixArray = buildSuffixArray(sequence);
3162
+ const lcp = buildLcp(sequence, suffixArray);
3163
+ const candidateLength = candidates.length;
3164
+ const liveOffset = candidateLength + 1;
3165
+ const sourceOf = (suffix) => {
3166
+ if (suffix < candidateLength) return 0;
3167
+ if (suffix >= liveOffset) return 1;
3168
+ return void 0;
3169
+ };
3170
+ let bestLength = 0;
3171
+ for (let index = 1; index < suffixArray.length; index++) {
3172
+ const leftSource = sourceOf(suffixArray[index - 1]);
3173
+ const rightSource = sourceOf(suffixArray[index]);
3174
+ if (leftSource === void 0 || rightSource === void 0 || leftSource === rightSource) continue;
3175
+ bestLength = Math.max(bestLength, lcp[index]);
3176
+ }
3177
+ if (bestLength === 0) return void 0;
3178
+ let pairCount = 0;
3179
+ let uniqueCandidateStart = -1;
3180
+ let uniqueLiveStart = -1;
3181
+ for (let start = 0; start < suffixArray.length; ) {
3182
+ let end = start;
3183
+ while (end + 1 < suffixArray.length && lcp[end + 1] >= bestLength) end++;
3184
+ if (end > start) {
3185
+ const candidateStarts = [];
3186
+ const liveStarts = [];
3187
+ for (let index = start; index <= end; index++) {
3188
+ const suffix = suffixArray[index];
3189
+ const source = sourceOf(suffix);
3190
+ if (source === 0) candidateStarts.push(suffix);
3191
+ else if (source === 1) liveStarts.push(suffix - liveOffset);
3192
+ }
3193
+ const groupPairs = candidateStarts.length * liveStarts.length;
3194
+ pairCount += groupPairs;
3195
+ if (groupPairs === 1) {
3196
+ uniqueCandidateStart = candidateStarts[0];
3197
+ uniqueLiveStart = liveStarts[0];
3198
+ }
3199
+ if (pairCount > 1) return void 0;
3200
+ }
3201
+ start = end + 1;
3202
+ }
3203
+ return pairCount === 1 ? { candidateStart: uniqueCandidateStart, liveStart: uniqueLiveStart, length: bestLength } : void 0;
3204
+ }
3205
+ function buildSuffixArray(sequence) {
3206
+ const suffixArray = sequence.map((_, index) => index);
3207
+ let ranks = [...sequence];
3208
+ for (let width = 1; width < sequence.length; width *= 2) {
3209
+ suffixArray.sort((left, right) => ranks[left] - ranks[right] || (ranks[left + width] ?? -1) - (ranks[right + width] ?? -1));
3210
+ const nextRanks = Array(sequence.length);
3211
+ nextRanks[suffixArray[0]] = 0;
3212
+ for (let index = 1; index < suffixArray.length; index++) {
3213
+ const previous = suffixArray[index - 1];
3214
+ const current = suffixArray[index];
3215
+ const differs = ranks[previous] !== ranks[current] || (ranks[previous + width] ?? -1) !== (ranks[current + width] ?? -1);
3216
+ nextRanks[current] = nextRanks[previous] + (differs ? 1 : 0);
3217
+ }
3218
+ ranks = nextRanks;
3219
+ if (ranks[suffixArray.at(-1)] === sequence.length - 1) break;
3220
+ }
3221
+ return suffixArray;
3222
+ }
3223
+ function buildLcp(sequence, suffixArray) {
3224
+ const positions = Array(sequence.length);
3225
+ for (let index = 0; index < suffixArray.length; index++) positions[suffixArray[index]] = index;
3226
+ const lcp = Array(sequence.length).fill(0);
3227
+ let length = 0;
3228
+ for (let start = 0; start < sequence.length; start++) {
3229
+ const position = positions[start];
3230
+ if (position === 0) continue;
3231
+ const previous = suffixArray[position - 1];
3232
+ while (start + length < sequence.length && previous + length < sequence.length && sequence[start + length] === sequence[previous + length]) length++;
3233
+ lcp[position] = length;
3234
+ if (length > 0) length--;
3235
+ }
3236
+ return lcp;
3237
+ }
3238
+
2983
3239
  // src/runtime.ts
2984
3240
  function readContextEntries(sm) {
2985
3241
  const source = sm;
@@ -2993,15 +3249,17 @@ function isPiHost(sm) {
2993
3249
  }
2994
3250
  function mergeLiveEntries(entries, live, state, origins) {
2995
3251
  const persisted = entries.filter((e) => e.type === "message");
2996
- const matched = matchPersistedSuffix(persisted, live);
2997
- const prior = matchOrigins(origins, live);
3252
+ const liveIdentities = live.map(messageIdentity);
3253
+ const persistedIdentities = persisted.map((entry) => messageIdentity(entry.message));
3254
+ const persistedRange = findUniqueLongestRun(persistedIdentities, normalizePersistedMatchKeys(persisted, persistedIdentities, live, liveIdentities));
3255
+ const originRange = findUniqueLongestRun(origins.map((origin) => origin.identity), liveIdentities);
2998
3256
  const out = [];
2999
3257
  const nextOrigins = [];
3000
3258
  const usedIds = /* @__PURE__ */ new Set();
3001
3259
  for (let i = 0; i < live.length; i++) {
3002
3260
  const msg = live[i];
3003
- const entry = matched[i];
3004
- const origin = prior[i];
3261
+ const entry = valueInRange(persisted, persistedRange, i);
3262
+ const origin = valueInRange(origins, originRange, i);
3005
3263
  if (entry) {
3006
3264
  if (origin) migrateLiveRefs(state, origin.rawId, entry.id);
3007
3265
  else migrateTaggedRef(state, msg, entry.id);
@@ -3011,23 +3269,13 @@ function mergeLiveEntries(entries, live, state, origins) {
3011
3269
  const id = origin?.rawId ?? nextLiveId(state, usedIds, i);
3012
3270
  usedIds.add(id);
3013
3271
  out.push({ type: "message", id, parentId: null, timestamp: String(msg.timestamp ?? Date.now()), message: msg });
3014
- nextOrigins.push({ rawId: id, identity: messageIdentity(msg) });
3272
+ nextOrigins.push({ rawId: id, identity: liveIdentities[i] });
3015
3273
  }
3016
3274
  origins.splice(0, origins.length, ...nextOrigins);
3017
- const unmatched = live.length - matched.length;
3275
+ const unmatched = live.length - (persistedRange?.length ?? 0);
3018
3276
  if (unmatched > 0) logInfo("runtime", { event: "merge-live-entries", live: live.length, unmatched });
3019
3277
  return out;
3020
3278
  }
3021
- function matchOrigins(origins, live) {
3022
- for (let start = 0; start < origins.length; start++) {
3023
- const count = origins.length - start;
3024
- if (count > live.length) continue;
3025
- if (origins.slice(start).every((origin, index) => origin.identity === messageIdentity(live[index]))) {
3026
- return [...origins.slice(start), ...Array(live.length - count)];
3027
- }
3028
- }
3029
- return Array(live.length);
3030
- }
3031
3279
  function nextLiveId(state, used, index) {
3032
3280
  let id = `live-${index}`;
3033
3281
  let suffix = index;
@@ -3054,31 +3302,39 @@ function migrateLiveRefs(state, liveId, stableId) {
3054
3302
  delete state.messageRefs.byRaw[rawId];
3055
3303
  }
3056
3304
  }
3057
- function matchPersistedSuffix(persisted, live) {
3058
- for (let start = 0; start < persisted.length; start++) {
3059
- const count = persisted.length - start;
3060
- if (count > live.length) continue;
3061
- const suffix = persisted.slice(start);
3062
- if (suffix.every((entry, index) => sameMessage(entry.message, live[index]))) return suffix;
3063
- }
3064
- return [];
3305
+ var NO_PERSISTED_MATCH = /* @__PURE__ */ Symbol("no-persisted-match");
3306
+ function normalizePersistedMatchKeys(persisted, persistedIdentities, live, liveIdentities) {
3307
+ const persistedByStructure = /* @__PURE__ */ new Map();
3308
+ for (let index = 0; index < persisted.length; index++) {
3309
+ const key = toolResultStructureKey(persisted[index].message);
3310
+ if (key === void 0) continue;
3311
+ persistedByStructure.set(key, persistedByStructure.has(key) ? -1 : index);
3312
+ }
3313
+ return live.map((message, liveIndex) => {
3314
+ const key = toolResultStructureKey(message);
3315
+ const candidateIndex = key === void 0 ? void 0 : persistedByStructure.get(key);
3316
+ if (candidateIndex === void 0) return liveIdentities[liveIndex];
3317
+ if (candidateIndex < 0) return NO_PERSISTED_MATCH;
3318
+ return sameToolResult(persisted[candidateIndex].message, message) ? persistedIdentities[candidateIndex] : liveIdentities[liveIndex];
3319
+ });
3065
3320
  }
3066
- function sameMessage(a, b) {
3067
- try {
3068
- if (messageIdentity(a) === messageIdentity(b)) return true;
3069
- const ra = a.role;
3070
- const rb = b.role;
3071
- if (ra !== rb || ra !== "toolResult") return false;
3072
- const ca = a.content;
3073
- const cb = b.content;
3074
- return sameNonTextBlocks(ca, cb) && matchesStoredText(extractText(ca), extractText(cb));
3075
- } catch (e) {
3076
- logWarn("runtime", { event: "message-compare-failed", error: e instanceof Error ? e.message : String(e) });
3077
- return a === b;
3078
- }
3321
+ function toolResultStructureKey(message) {
3322
+ if (message.role !== "toolResult") return void 0;
3323
+ return `${message.toolName}\0${message.toolCallId}`;
3324
+ }
3325
+ function valueInRange(values, range, liveIndex) {
3326
+ if (!range || liveIndex < range.liveStart || liveIndex >= range.liveStart + range.length) return void 0;
3327
+ return values[range.candidateStart + liveIndex - range.liveStart];
3328
+ }
3329
+ function sameToolResult(stored, visible) {
3330
+ if (stored.role !== "toolResult" || visible.role !== "toolResult") return false;
3331
+ return sameNonTextBlocks(stored.content, visible.content) && matchesStoredText(extractText(stored.content), extractText(visible.content));
3079
3332
  }
3080
3333
  function sameNonTextBlocks(a, b) {
3081
- const nonText = (blocks) => blocks.filter((block) => block.type !== "text");
3334
+ const nonText = (blocks) => blocks.filter((block) => {
3335
+ if (!block || typeof block !== "object" || !("type" in block)) return true;
3336
+ return block.type !== "text";
3337
+ });
3082
3338
  try {
3083
3339
  const na = Array.isArray(a) ? nonText(a) : [];
3084
3340
  const nb = Array.isArray(b) ? nonText(b) : [];
@@ -3106,6 +3362,7 @@ function createRuntime(adapter) {
3106
3362
  const store = new SessionStateStore();
3107
3363
  const locks = /* @__PURE__ */ new Map();
3108
3364
  let adapterRef = adapter;
3365
+ let promptsRef = defaultPrompts;
3109
3366
  const nudgeShownTurns = /* @__PURE__ */ new Set();
3110
3367
  async function acquireLock(sid) {
3111
3368
  const prev = locks.get(sid) ?? Promise.resolve();
@@ -3154,6 +3411,10 @@ function createRuntime(adapter) {
3154
3411
  return adapterRef;
3155
3412
  }, setAdapter: (a) => {
3156
3413
  adapterRef = a;
3414
+ }, get prompts() {
3415
+ return promptsRef;
3416
+ }, setPrompts: (p) => {
3417
+ promptsRef = p;
3157
3418
  }, markNudgeShown: (k) => {
3158
3419
  nudgeShownTurns.add(k);
3159
3420
  }, nudgeShownFor: (k) => nudgeShownTurns.has(k), clearNudgeTracking: () => {
@@ -7768,6 +8029,18 @@ function findMessageContent(ref, ctx) {
7768
8029
  }
7769
8030
  return null;
7770
8031
  }
8032
+ function resolveBlockMessages(block, coreMessages, ctx) {
8033
+ const neededBaseIds = new Set(block.effectiveMessageIds.map((id) => id.split("#")[0]));
8034
+ const presentBaseIds = new Set(coreMessages.map((m) => m.id.split("#")[0]));
8035
+ const missingBaseIds = [...neededBaseIds].filter((id) => !presentBaseIds.has(id));
8036
+ if (missingBaseIds.length === 0) return coreMessages;
8037
+ const extra = [];
8038
+ for (const baseId of missingBaseIds) {
8039
+ const entry = ctx.sessionManager.getEntry(baseId);
8040
+ if (entry) extra.push(...entriesToCoreMessages([entry]));
8041
+ }
8042
+ return [...coreMessages, ...extra];
8043
+ }
7771
8044
  async function handleMessageRef(ref, ownerBlockId, args, ctx) {
7772
8045
  const found = findMessageContent(ref, ctx);
7773
8046
  if (!found || !found.text) {
@@ -7815,7 +8088,8 @@ async function handleDecompress(args, runtime, ctx) {
7815
8088
  return `Block ${blockId} not found. Active blocks: ${active || "(none)"}.`;
7816
8089
  }
7817
8090
  const full = args.full ?? false;
7818
- const { text, count } = collectBlockContent(state, block, coreMessages, { full });
8091
+ const resolved = resolveBlockMessages(block, coreMessages, ctx);
8092
+ const { text, count } = collectBlockContent(state, block, resolved, { full });
7819
8093
  if (count === 0) return `Block ${blockId} has no restorable message content.`;
7820
8094
  if (args.inline === true && !args.toFile) {
7821
8095
  debug.event("decompress", { blockId, full, count, mode: "inline" });
@@ -7977,10 +8251,10 @@ function formatSize(tokens) {
7977
8251
  import {
7978
8252
  spawn
7979
8253
  } from "child_process";
7980
- import { createWriteStream } from "fs";
8254
+ import { createWriteStream, existsSync as existsSync2 } from "fs";
7981
8255
  import { mkdir as mkdir2, mkdtemp, writeFile as writeFile2, rm, appendFile } from "fs/promises";
7982
8256
  import { tmpdir as tmpdir2 } from "os";
7983
- import { join as join4 } from "path";
8257
+ import { dirname as dirname3, join as join4, resolve as resolvePath } from "path";
7984
8258
 
7985
8259
  // src/footer-status.ts
7986
8260
  var FOOTER_STATUS_KEY = "billion-context-pi";
@@ -8393,6 +8667,44 @@ function delegateSpawnOptions(cwd, env) {
8393
8667
  shell: false
8394
8668
  };
8395
8669
  }
8670
+ var PI_CLI_ENTRY_RE = /[\\/]pi-coding-agent[\\/]dist[\\/]cli\.js$/;
8671
+ var PI_PACKAGE_REL = join4("@earendil-works", "pi-coding-agent", "dist", "cli.js");
8672
+ function probeUpFromArgv(argv1) {
8673
+ let dir = resolvePath(dirname3(argv1) || process.cwd());
8674
+ for (; ; ) {
8675
+ const candidate = join4(dir, "node_modules", PI_PACKAGE_REL);
8676
+ if (existsSync2(candidate)) return candidate;
8677
+ const parent = dirname3(dir);
8678
+ if (parent === dir) return null;
8679
+ dir = parent;
8680
+ }
8681
+ }
8682
+ function piCliGlobalCandidates(env) {
8683
+ const candidates = [];
8684
+ if (process.platform === "win32") {
8685
+ if (env.APPDATA) candidates.push(join4(env.APPDATA, "npm", "node_modules", PI_PACKAGE_REL));
8686
+ } else {
8687
+ const home = env.HOME ?? env.USERPROFILE;
8688
+ if (home) candidates.push(join4(home, ".local", "lib", "node_modules", PI_PACKAGE_REL));
8689
+ candidates.push(join4("/usr/local", "lib", "node_modules", PI_PACKAGE_REL));
8690
+ candidates.push(join4("/usr", "lib", "node_modules", PI_PACKAGE_REL));
8691
+ }
8692
+ return candidates;
8693
+ }
8694
+ function resolvePiCliEntry(argv1, env = process.env, piHost = true) {
8695
+ const explicit = env.PI_CLI_PATH;
8696
+ if (explicit) return explicit;
8697
+ if (argv1 && PI_CLI_ENTRY_RE.test(argv1)) return argv1;
8698
+ if (piHost) {
8699
+ const probed = probeUpFromArgv(argv1);
8700
+ if (probed) return probed;
8701
+ for (const candidate of piCliGlobalCandidates(env)) {
8702
+ if (existsSync2(candidate)) return candidate;
8703
+ }
8704
+ logWarn("delegate", { event: "cli-entry-unresolved", argv1, fallback: "argv[1]" });
8705
+ }
8706
+ return argv1;
8707
+ }
8396
8708
  var ACP_TOOLS = ["compress", "decompress", "search_context", "acp_status"];
8397
8709
  var RESTRICTED_TOOLS = "read,bash,grep,find,ls";
8398
8710
  var AGENTS = {
@@ -8814,7 +9126,7 @@ async function runDelegate(pi, args, ctx, signal) {
8814
9126
  logInfo("delegate", { event: "spawn", agent: args.agent, runId, cwd, async: isAsync, useJsonStream, mode: ctx.mode, parentDepth });
8815
9127
  const child = spawn(
8816
9128
  process.execPath,
8817
- [process.argv[1], ...cliArgs],
9129
+ [resolvePiCliEntry(process.argv[1] ?? "", process.env, isPiHost(ctx.sessionManager)), ...cliArgs],
8818
9130
  delegateSpawnOptions(cwd, childEnv)
8819
9131
  );
8820
9132
  child.stdin?.once("error", (e) => {
@@ -9346,7 +9658,7 @@ async function statusReport(runtime, ctx) {
9346
9658
  const activeBlocksList = state.blocks.filter((b) => b.active);
9347
9659
  const totalBlocksList = state.blocks;
9348
9660
  const lines = [];
9349
- const versionStr = "0.1.36" ? `billion-context-pi@${"0.1.36"}` : "";
9661
+ const versionStr = "0.1.37" ? `billion-context-pi@${"0.1.37"}` : "";
9350
9662
  lines.push("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E");
9351
9663
  lines.push("\u2502 ACP Context Analysis \u2502");
9352
9664
  lines.push("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F");
@@ -9423,7 +9735,8 @@ async function statusReport(runtime, ctx) {
9423
9735
  }
9424
9736
 
9425
9737
  // src/system-prompt.ts
9426
- var ACP_SYSTEM_PROMPT = `
9738
+ function buildAcpSystemPrompt(prompts) {
9739
+ return `
9427
9740
  ACP context management
9428
9741
 
9429
9742
  ACP TAGS
@@ -9447,7 +9760,7 @@ You have four context-management tools:
9447
9760
  - search_context \u2014 Search compressed block summaries (and optionally visible messages) by keyword. Use BEFORE decompressing to find the right block. Example: search_context({ query: "auth token refresh" }).
9448
9761
  - acp_status \u2014 Context status with compressible ranges. No args = overview + totals. scope:"uncompressed" for range view; add view:"messages" for per-message listing. scope:"compressed" for block details.
9449
9762
 
9450
- ${COMPRESS_PHILOSOPHY}
9763
+ ${prompts.compressPhilosophy}
9451
9764
 
9452
9765
  WHEN TO COMPRESS
9453
9766
 
@@ -9465,7 +9778,7 @@ WHEN NOT TO COMPRESS
9465
9778
  - Important user messages \u2014 preserve their exact intent, constraints, and acceptance criteria. If a message in the range must stay verbatim, exclude it from the compress range instead of compressing it.
9466
9779
  - Protected tool outputs \u2014 hard-excluded from compression ranges, survive intact in visible context.
9467
9780
 
9468
- ${HOW_TO_COMPRESS_RULES}
9781
+ ${prompts.howToCompressRules}
9469
9782
 
9470
9783
  MULTI-TIER COMPRESSION
9471
9784
 
@@ -9473,9 +9786,9 @@ Summaries accumulate as the session grows. When tier-1 summaries pile up, the sy
9473
9786
 
9474
9787
  To compress blocks: use block IDs as boundaries: compress({ content: [{ startId: "b3", endId: "b15", summary: "..." }] }). This deactivates the consumed blocks and creates a new higher-tier block.
9475
9788
 
9476
- ${TIER2_DISTILL_RULES}
9789
+ ${prompts.tier2DistillRules}
9477
9790
 
9478
- ${TIER3_CONDENSE_RULES}
9791
+ ${prompts.tier3CondenseRules}
9479
9792
 
9480
9793
  THE PHILOSOPHY OF DECOMPRESS
9481
9794
 
@@ -9485,6 +9798,7 @@ CONTEXT BREAKDOWN
9485
9798
 
9486
9799
  When context usage passes a threshold, the system appends a breakdown showing where tokens are spent. Compress the largest ranges first when the current step no longer needs them.
9487
9800
  `;
9801
+ }
9488
9802
  var ACP_DELEGATE_PROMPT = `
9489
9803
  ACP_DELEGATE NOTIFICATIONS
9490
9804
 
@@ -9613,7 +9927,7 @@ function wireToolGuardrails(pi, runtime) {
9613
9927
 
9614
9928
  // src/update.ts
9615
9929
  import { readFile, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
9616
- import { join as join5, dirname as dirname3 } from "path";
9930
+ import { join as join5, dirname as dirname4 } from "path";
9617
9931
  import { fileURLToPath } from "url";
9618
9932
  import { execFile } from "child_process";
9619
9933
  import { homedir as homedir3 } from "os";
@@ -9646,7 +9960,7 @@ async function readLastCheck() {
9646
9960
  }
9647
9961
  async function writeLastCheck(timestamp) {
9648
9962
  try {
9649
- await mkdir3(dirname3(THROTTLE_FILE), { recursive: true });
9963
+ await mkdir3(dirname4(THROTTLE_FILE), { recursive: true });
9650
9964
  await writeFile3(THROTTLE_FILE, String(timestamp), "utf-8");
9651
9965
  } catch {
9652
9966
  }
@@ -9660,20 +9974,20 @@ async function readPackageJson(path4) {
9660
9974
  }
9661
9975
  }
9662
9976
  function findNpmRoot(extDir) {
9663
- let dir = dirname3(extDir);
9977
+ let dir = dirname4(extDir);
9664
9978
  for (; ; ) {
9665
- if (dir.endsWith("node_modules")) return dirname3(dir);
9666
- const parent = dirname3(dir);
9979
+ if (dir.endsWith("node_modules")) return dirname4(dir);
9980
+ const parent = dirname4(dir);
9667
9981
  if (parent === dir) return void 0;
9668
9982
  dir = parent;
9669
9983
  }
9670
9984
  }
9671
9985
  async function findExtensionDir() {
9672
- let dir = dirname3(fileURLToPath(import.meta.url));
9986
+ let dir = dirname4(fileURLToPath(import.meta.url));
9673
9987
  for (; ; ) {
9674
9988
  const pkg = await readPackageJson(join5(dir, "package.json"));
9675
9989
  if (pkg?.name === PACKAGE_NAME) return dir;
9676
- const parent = dirname3(dir);
9990
+ const parent = dirname4(dir);
9677
9991
  if (parent === dir) return void 0;
9678
9992
  dir = parent;
9679
9993
  }
@@ -9722,7 +10036,7 @@ async function checkForUpdate(autoUpdate, notify) {
9722
10036
  const data = await res.json();
9723
10037
  const latest = data.version;
9724
10038
  if (!latest) return;
9725
- const current = runtimeVersion ?? "0.1.36";
10039
+ const current = runtimeVersion ?? "0.1.37";
9726
10040
  const hasUpdate = isNewer(latest, current);
9727
10041
  debug.event("update-check", {
9728
10042
  current,
@@ -9758,7 +10072,7 @@ async function getRuntimeVersion() {
9758
10072
 
9759
10073
  // src/setup-subagent-tools.ts
9760
10074
  import { readFile as readFile2, writeFile as writeFile4, stat, copyFile, rename } from "fs/promises";
9761
- import { existsSync as existsSync2 } from "fs";
10075
+ import { existsSync as existsSync3 } from "fs";
9762
10076
  import { homedir as homedir4 } from "os";
9763
10077
  import { join as join6 } from "path";
9764
10078
  import { CONFIG_DIR_NAME as CONFIG_DIR_NAME3 } from "@earendil-works/pi-coding-agent";
@@ -9823,7 +10137,7 @@ async function ensureSubagentAcpTools(settingsPath) {
9823
10137
  return { path: path4, action: "skipped", reason: "all builtin agents already have ACP tools" };
9824
10138
  }
9825
10139
  const backupPath = `${path4}.acp-bak`;
9826
- if (!existsSync2(backupPath)) {
10140
+ if (!existsSync3(backupPath)) {
9827
10141
  try {
9828
10142
  await copyFile(path4, backupPath);
9829
10143
  } catch {
@@ -9923,7 +10237,9 @@ var KNOWN = /* @__PURE__ */ new Set([
9923
10237
  "toolOutputMaxBytes",
9924
10238
  "delegate",
9925
10239
  "compress",
9926
- "displayUsage"
10240
+ "displayUsage",
10241
+ "prompts",
10242
+ "acknowledgePromptsRisk"
9927
10243
  ]);
9928
10244
  function pickKnown(parsed) {
9929
10245
  const out = {};
@@ -9971,7 +10287,7 @@ function wireSessionLifecycle(pi, runtime) {
9971
10287
  resetDelegateUsage();
9972
10288
  setDelegateDisplayUsage("separate");
9973
10289
  const sid = ctx.sessionManager.getSessionId();
9974
- logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.36" : null });
10290
+ logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.37" : null });
9975
10291
  try {
9976
10292
  const user = await loadUserConfig(ctx.cwd);
9977
10293
  runtime.setAdapter(applyUserConfig(runtime.adapter, user));
@@ -9980,6 +10296,12 @@ function wireSessionLifecycle(pi, runtime) {
9980
10296
  } catch (e) {
9981
10297
  logThrow("config", e, { sid, phase: "session_start" });
9982
10298
  }
10299
+ try {
10300
+ runtime.setPrompts(resolvePrompts(runtime.adapter.prompts, { acknowledgeRisk: runtime.adapter.acknowledgePromptsRisk === true }));
10301
+ } catch (e) {
10302
+ logWarn("config", { event: "prompts-resolve-failed", error: e instanceof Error ? e.message : String(e) });
10303
+ runtime.setPrompts(defaultPrompts);
10304
+ }
9983
10305
  if (resolveDelegate(runtime.adapter).enabled) {
9984
10306
  pi.registerTool(makeDelegateTool(pi));
9985
10307
  pi.registerTool(makeDelegateWaitTool(pi));
@@ -10040,7 +10362,7 @@ function wireContextTransform(pi, runtime) {
10040
10362
  prunedMsgs: coreMessages.length - turn.messages.length + turn.messages.filter((m) => m.id.startsWith("acp_summary")).length,
10041
10363
  nudgeShouldInject: turn.nudge?.shouldInject ?? false,
10042
10364
  nudgeReason: turn.nudge?.reason ?? null,
10043
- nudgeVoice: turn.nudge ? renderNudgeText(turn.nudge).voice : null,
10365
+ nudgeVoice: turn.nudge ? renderNudgeText(turn.nudge, runtime.prompts).voice : null,
10044
10366
  nudgePct: turn.nudge ? Math.round(turn.nudge.contextUsage * 100) : null,
10045
10367
  nudgeTier: turn.nudge?.tier ?? null,
10046
10368
  nudgeCompressibleCount: turn.nudge?.compressibleRanges.length ?? 0,
@@ -10057,8 +10379,8 @@ function wireContextTransform(pi, runtime) {
10057
10379
  const turnKey = lastUserMessageId(entries) ?? sid;
10058
10380
  const alreadyShown = !emergency && runtime.nudgeShownFor(turnKey);
10059
10381
  if (!alreadyShown) {
10060
- rebuilt.push(nudgeMessage(turn.nudge, turn.state.blocks.filter((b) => b.active)));
10061
- const rendered = renderNudgeText(turn.nudge);
10382
+ rebuilt.push(nudgeMessage(turn.nudge, turn.state.blocks.filter((b) => b.active), runtime.prompts));
10383
+ const rendered = renderNudgeText(turn.nudge, runtime.prompts);
10062
10384
  const top = [...turn.nudge.compressibleRanges].sort((a, b) => b.tokens - a.tokens)[0];
10063
10385
  const example = top ? `
10064
10386
 
@@ -10092,8 +10414,9 @@ ${rendered.text}${example}`);
10092
10414
  function wireSystemPrompt(pi, runtime) {
10093
10415
  pi.on("before_agent_start", (event) => {
10094
10416
  const delegate = runtime.adapter.delegate !== false;
10095
- const prompt = delegate ? `${ACP_SYSTEM_PROMPT}
10096
- ${ACP_DELEGATE_PROMPT}` : ACP_SYSTEM_PROMPT;
10417
+ const acp = buildAcpSystemPrompt(runtime.prompts);
10418
+ const prompt = delegate ? `${acp}
10419
+ ${ACP_DELEGATE_PROMPT}` : acp;
10097
10420
  return { systemPrompt: formatSystemPromptForEvent(event.systemPrompt, prompt) };
10098
10421
  });
10099
10422
  }
@@ -10109,8 +10432,8 @@ function collectOriginals(entries) {
10109
10432
  }
10110
10433
  return map;
10111
10434
  }
10112
- function nudgeMessage(nudge, blocks) {
10113
- const rendered = renderNudgeText(nudge);
10435
+ function nudgeMessage(nudge, blocks, prompts) {
10436
+ const rendered = renderNudgeText(nudge, prompts);
10114
10437
  const lines = [rendered.text];
10115
10438
  if (blocks.length > 0) {
10116
10439
  const totalSummary = blocks.reduce((s, b) => s + Math.ceil((b.summary || "").length / 4), 0);