billion-context-pi 0.1.53 → 0.1.54-pr.264.52

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.
@@ -8,7 +8,7 @@ export declare function delegateSpawnOptions(cwd: string, env: NodeJS.ProcessEnv
8
8
  * run the SDK inside another node process, so probe instead. Non-pi hosts
9
9
  * (omp) keep argv[1] untouched. */
10
10
  export declare function resolvePiCliEntry(argv1: string, env?: NodeJS.ProcessEnv, piHost?: boolean): string;
11
- type RunStatus = "running" | "completed" | "failed" | "cancelled";
11
+ export type RunStatus = "running" | "completed" | "failed" | "cancelled";
12
12
  interface DelegateRun {
13
13
  runId: string;
14
14
  agent: string;
@@ -104,6 +104,7 @@ declare const WaitParams: Type.TObject<{
104
104
  }>;
105
105
  export declare function accumulateUsage(a: Usage | undefined, b: Usage): Usage;
106
106
  export declare function makeDelegateTool(pi: ExtensionAPI): ToolDefinition<typeof DelegateParams>;
107
+ export declare function formatRunResult(run: DelegateRun): string;
107
108
  /** Runs that reached a terminal state but whose result never reached the
108
109
  * model: no parked waiter, not consumed by a tool result, and never injected
109
110
  * as a notification. The model was promised a notification ("do NOT keep
@@ -163,5 +164,12 @@ export declare function buildChildArgs(args: DelegateArgs, rolePrompt: string, c
163
164
  isAsync: boolean;
164
165
  useJsonStream: boolean;
165
166
  }>;
166
- export declare function injectResult(pi: ExtensionAPI, agent: string, runId: string, task: string, code: number | null, file: string, timedOut?: string, usage?: Usage, mode?: "merged" | "separate", usageAlreadyReported?: boolean, body?: string): boolean;
167
+ /** Watchdog/EOF finalize arrives with code === null (the child was killed or
168
+ * never exited). If a result was delivered (non-empty reply or stderr), the
169
+ * run counts as completed (0); otherwise it stays null = genuine failure. */
170
+ export declare function effectiveExitCode(code: number | null, output: string, stderr: string): number | null;
171
+ /** status (set by finalize from the effective exit code) is the authority for
172
+ * the FAILED/completed decision; the raw code is diagnostic display only
173
+ * ("exit ?"), so the notification can never disagree with run.status. */
174
+ export declare function injectResult(pi: ExtensionAPI, agent: string, runId: string, task: string, status: RunStatus, code: number | null, file: string, timedOut?: string, usage?: Usage, mode?: "merged" | "separate", usageAlreadyReported?: boolean, body?: string): boolean;
167
175
  export {};
package/dist/index.js CHANGED
@@ -424,18 +424,42 @@ function assignRefs(messages, options) {
424
424
  return { map, nextIndex: cursor, newlyAssigned };
425
425
  }
426
426
  function allocateFreeRef(map, start) {
427
- let candidate = Math.max(start, MIN_INDEX);
428
- while (candidate <= MAX_INDEX) {
427
+ const from = Math.max(start, MIN_INDEX);
428
+ for (let candidate = from; candidate <= MAX_INDEX; candidate++) {
429
+ const text = indexToRef(candidate);
430
+ if (!map.byRef[text]) {
431
+ return { text, index: candidate };
432
+ }
433
+ }
434
+ for (let candidate = MIN_INDEX; candidate < from; candidate++) {
429
435
  const text = indexToRef(candidate);
430
436
  if (!map.byRef[text]) {
431
437
  return { text, index: candidate };
432
438
  }
433
- candidate++;
434
439
  }
435
440
  throw new Error(
436
- `ref capacity exhausted: cannot allocate beyond ${indexToRef(MAX_INDEX)}`
441
+ `ref capacity exhausted: all ${MAX_INDEX} ref slots are occupied by live messages \u2014 nothing left to reclaim`
437
442
  );
438
443
  }
444
+ function pruneDeadRefs(map, liveIds) {
445
+ let pruned = 0;
446
+ const byRaw = {};
447
+ for (const [rawId, ref] of Object.entries(map.byRaw)) {
448
+ if (liveIds.has(rawId)) {
449
+ byRaw[rawId] = ref;
450
+ continue;
451
+ }
452
+ pruned++;
453
+ }
454
+ return { map: rebuildRefIndex({ byRaw, byRef: {} }), pruned };
455
+ }
456
+ function rebuildRefIndex(map) {
457
+ const byRef = {};
458
+ for (const [rawId, ref] of Object.entries(map.byRaw)) {
459
+ if (ref !== BLOCKED_REF) byRef[ref] = rawId;
460
+ }
461
+ return { byRaw: { ...map.byRaw }, byRef };
462
+ }
439
463
  function highestUsedIndex(map) {
440
464
  let highest = 0;
441
465
  for (const ref of Object.values(map.byRaw)) {
@@ -634,6 +658,13 @@ function syncBlocks(messages, state) {
634
658
  deactivated.push(block.blockId);
635
659
  }
636
660
  }
661
+ const liveIds = new Set(presentIds);
662
+ for (const block of result.blocks) {
663
+ if (!block.active) continue;
664
+ for (const id of block.effectiveMessageIds) liveIds.add(id);
665
+ }
666
+ const prunedRefs = pruneDeadRefs(result.messageRefs, liveIds);
667
+ result.messageRefs = prunedRefs.map;
637
668
  return { state: result, deactivated };
638
669
  }
639
670
  function defaultConfig(modelContextLimit, overrides = {}) {
@@ -744,12 +775,14 @@ var BoundaryNotFoundError = class extends Error {
744
775
  code = "BOUNDARY_NOT_FOUND";
745
776
  kind;
746
777
  endpoint;
747
- constructor(kind, endpoint, message) {
778
+ reason;
779
+ constructor(kind, endpoint, message, reason = null) {
748
780
  super(message);
749
781
  this.name = "BoundaryNotFoundError";
750
782
  this.code = "BOUNDARY_NOT_FOUND";
751
783
  this.kind = kind;
752
784
  this.endpoint = endpoint;
785
+ this.reason = reason;
753
786
  }
754
787
  };
755
788
  function resolveBoundaries(input) {
@@ -820,7 +853,7 @@ function resolveAnchorIndex(boundary, state, indexByMessageId, endpoint) {
820
853
  throw new BoundaryNotFoundError(
821
854
  "unknown",
822
855
  endpoint,
823
- `${label}="${boundary.raw}" does not exist in this session (typo or wrong session) \u2014 run acp_status for current refs.`
856
+ `${label}="${boundary.raw}" does not exist in this session (typo, wrong session, or the message was edited/removed and its ref released) \u2014 run acp_status for current refs.`
824
857
  );
825
858
  }
826
859
  const index = indexByMessageId.get(rawId);
@@ -834,10 +867,33 @@ function resolveAnchorIndex(boundary, state, indexByMessageId, endpoint) {
834
867
  snapped: `${label}="${boundary.raw}" refers to a message already compressed into an active block \u2014 anchored to the active block covering it instead.`
835
868
  };
836
869
  }
870
+ const coveringActive = state.blocks.filter(
871
+ (block2) => block2.active && block2.effectiveMessageIds.includes(rawId)
872
+ );
873
+ if (coveringActive.length > 0) {
874
+ throw new BoundaryNotFoundError(
875
+ "consumed",
876
+ endpoint,
877
+ `${label}="${boundary.raw}" is already compressed into active block(s) ${coveringActive.map((block2) => block2.blockId).join(", ")} \u2014 retry with the block ref(s) instead of the message ref.`,
878
+ "consumed"
879
+ );
880
+ }
881
+ const generationReplaced = state.blocks.some(
882
+ (block2) => !block2.active && block2.effectiveMessageIds.includes(rawId)
883
+ );
884
+ if (generationReplaced) {
885
+ throw new BoundaryNotFoundError(
886
+ "consumed",
887
+ endpoint,
888
+ `${label}="${boundary.raw}" was compressed into a block that has since been distilled or consumed \u2014 its content is no longer available in this session; run acp_status for current refs.`,
889
+ "generation-replaced"
890
+ );
891
+ }
837
892
  throw new BoundaryNotFoundError(
838
893
  "consumed",
839
894
  endpoint,
840
- `${label}="${boundary.raw}" not found in visible context (likely consumed by an existing block).`
895
+ `${label}="${boundary.raw}" no longer exists \u2014 the message was edited or removed (its content id changed); run acp_status for current refs.`,
896
+ "edit-drift"
841
897
  );
842
898
  }
843
899
  const block = blockById(state, `b${boundary.numericId}`);
@@ -866,16 +922,21 @@ function resolveAnchorIndex(boundary, state, indexByMessageId, endpoint) {
866
922
  };
867
923
  }
868
924
  if (!block.active) {
925
+ const consumer = state.blocks.find(
926
+ (candidate) => candidate.active && candidate.directBlockIds.includes(block.blockId) && visibleBlockAnchor(candidate, indexByMessageId) !== null
927
+ );
869
928
  throw new BoundaryNotFoundError(
870
929
  "consumed",
871
930
  endpoint,
872
- `${label}="b${boundary.numericId}" not found in visible context (block distilled/consumed by a higher-tier block).`
931
+ consumer ? `${label}="b${boundary.numericId}" was consumed by ${consumer.blockId} (higher-tier block) \u2014 retry with ${consumer.blockId} instead.` : `${label}="b${boundary.numericId}" is inactive (distilled or garbage-collected) and none of its content is visible in the current context \u2014 run acp_status for current refs.`,
932
+ "generation-replaced"
873
933
  );
874
934
  }
875
935
  throw new BoundaryNotFoundError(
876
936
  "consumed",
877
937
  endpoint,
878
- `${label}="b${boundary.numericId}" is an active block but none of its content (raw messages or rendered summary) is visible in the current context \u2014 run acp_status to verify.`
938
+ `${label}="b${boundary.numericId}" is an active block but none of its content (raw messages or rendered summary) is present in the current view \u2014 its messages were likely edited or removed (id drift); it will be deactivated on the next state sync. Run acp_status to verify.`,
939
+ "edit-drift"
879
940
  );
880
941
  }
881
942
  function activeOwnerAnchor(state, ownedIds, indexByMessageId) {
@@ -1800,10 +1861,33 @@ function numericBlockId(id) {
1800
1861
  const parsed = /^b(\d+)$/.exec(id);
1801
1862
  return parsed ? Number(parsed[1]) : 0;
1802
1863
  }
1864
+ function refGateDiagnostics(state, requestedRanges, unknownCount) {
1865
+ const highest = highestUsedIndex(state.messageRefs);
1866
+ const highestRef = highest > 0 ? indexToRef(highest) : "none";
1867
+ return `[diagnostics: session highest ref=${highestRef}, unknown ranges in request=${unknownCount}/${requestedRanges}, session history=${state.stats.compressionCount} compression(s), ${state.blocks.length} block(s)]`;
1868
+ }
1869
+ function danglingMessageRefs(state, messages, spec) {
1870
+ const visible = new Set(messages.map((m) => m.id));
1871
+ const dangling = [];
1872
+ for (const ref of [spec.startRef, spec.endRef]) {
1873
+ const parsed = parseBoundary(ref);
1874
+ if (!parsed || parsed.kind !== "message") continue;
1875
+ const rawId = state.messageRefs.byRef[parsed.raw] ?? state.messageRefs.byRef[indexToRef(parsed.numericId)];
1876
+ if (!rawId || visible.has(rawId)) continue;
1877
+ const covered = state.blocks.some(
1878
+ (block) => block.active && block.effectiveMessageIds.includes(rawId)
1879
+ );
1880
+ if (!covered) dangling.push(parsed.raw);
1881
+ }
1882
+ return dangling;
1883
+ }
1803
1884
  function createCore(ports = {}) {
1804
1885
  const countTokens = ports.countTokens ?? defaultCountTokens;
1805
1886
  function applyCompression(input) {
1806
- const state = cloneState(input.state);
1887
+ const state = syncBlocks(
1888
+ input.messages,
1889
+ input.state
1890
+ ).state;
1807
1891
  const runId = allocateRunId(state);
1808
1892
  let blocksCreated = 0;
1809
1893
  let tokensCompressed = 0;
@@ -1900,7 +1984,15 @@ function createCore(ports = {}) {
1900
1984
  if (!hasBlockBoundaryRange && totalRangeChars < input.config.compress.minCompressRange) {
1901
1985
  const live = activeBlocks(state).map((b) => b.blockId).sort((x, y) => numericBlockId(x) - numericBlockId(y));
1902
1986
  const liveHint = live.length > 0 ? ` Current active blocks span ${live[0]}..${live[live.length - 1]} \u2014 retry with startId/endId set to active block IDs in that span.` : "";
1903
- const gateMessage = resolvableCount === 0 && consumedRanges.length === 0 && unknownCount > 0 ? `None of the ${input.ranges.length} requested range(s) resolved \u2014 every ref failed with "does not exist in this session". Refs recorded before an earlier compress are stale: each successful compress renumbers the remaining refs. Run acp_status, then call the compress tool again using only the refs it reports.` : consumedRanges.length > 0 ? `Requested range(s) already compressed (e.g. ${consumedRanges[0].startRef}..${consumedRanges[0].endRef}) \u2014 your refs are stale: a prior compress renumbered the remaining refs, so this range now falls inside an active block. Run acp_status, then call the compress tool again using only the CURRENT compressible ranges it reports.${liveHint}` : `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.`;
1987
+ const diagnostics = refGateDiagnostics(
1988
+ state,
1989
+ input.ranges.length,
1990
+ unknownCount
1991
+ );
1992
+ const danglingRefs = consumedRanges.flatMap(
1993
+ (spec) => danglingMessageRefs(state, input.messages, spec)
1994
+ );
1995
+ const gateMessage = resolvableCount === 0 && consumedRanges.length === 0 && unknownCount > 0 ? `None of the ${input.ranges.length} requested range(s) resolved \u2014 every ref is unknown to this session. Refs are per-session snapshots, assigned once when a message is first rendered; no compress reassigns them, so unknown refs cannot come from an earlier compress in this session. They come from a different generation: a previous session instance (switching model or upstream mid-conversation starts a fresh session whose refs restart at m00001), the generation before a native-compaction rebase (which also resets refs to m00001), a typo, or a message that was edited or removed (its old ref was released when the content id changed). ${diagnostics} Run acp_status, then call the compress tool again using only the refs it reports.` : consumedRanges.length > 0 ? danglingRefs.length > 0 ? `Requested range(s) cannot be anchored (e.g. ${consumedRanges[0].startRef}..${consumedRanges[0].endRef}) \u2014 the refs exist in this session's ref map, but the messages they point to are no longer in the visible context and no active block covers them: the message content changed (or the message was filtered out of the view) and now carries a new ref, leaving your old refs dangling. ${diagnostics} Run acp_status, then call the compress tool again using only the refs it reports.` : `Requested range(s) already compressed (e.g. ${consumedRanges[0].startRef}..${consumedRanges[0].endRef}) \u2014 those refs no longer point to directly compressible content: the range is covered by active block(s) or the block ref(s) are stale (distilled or consumed). ${diagnostics} Run acp_status, then call the compress tool again using only the CURRENT compressible ranges it reports.${liveHint}` : `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.`;
1904
1996
  return {
1905
1997
  state: input.state,
1906
1998
  result: {
@@ -2043,9 +2135,15 @@ var assignRefsNode = {
2043
2135
  run(io, ctx) {
2044
2136
  const hasProtection = ctx.config.protectedTools.length > 0 || !!ctx.config.isToolProtected;
2045
2137
  const protectedFn = hasProtection ? (m) => isMessageProtected(m, ctx.config) : void 0;
2138
+ const liveIds = new Set(io.messages.map((m) => m.id));
2139
+ for (const block of io.state.blocks) {
2140
+ if (!block.active) continue;
2141
+ for (const id of block.effectiveMessageIds) liveIds.add(id);
2142
+ }
2143
+ const pruned = pruneDeadRefs(io.state.messageRefs, liveIds);
2046
2144
  const refResult = assignRefs(io.messages, {
2047
- existing: io.state.messageRefs,
2048
- nextIndex: highestUsedIndex(io.state.messageRefs) + 1,
2145
+ existing: pruned.map,
2146
+ nextIndex: highestUsedIndex(pruned.map) + 1,
2049
2147
  isProtected: protectedFn
2050
2148
  });
2051
2149
  return { ...io, state: { ...io.state, messageRefs: refResult.map } };
@@ -2617,26 +2715,6 @@ function computeContextBreakdown(messages, total, growth, countTokens) {
2617
2715
  }
2618
2716
  return { system, tool, summaries, code, text, total, growth };
2619
2717
  }
2620
- function cloneState(state) {
2621
- return {
2622
- blocks: state.blocks.map((block) => ({
2623
- ...block,
2624
- directMessageIds: [...block.directMessageIds],
2625
- effectiveMessageIds: [...block.effectiveMessageIds],
2626
- directBlockIds: [...block.directBlockIds]
2627
- })),
2628
- messageRefs: {
2629
- byRaw: { ...state.messageRefs.byRaw },
2630
- byRef: { ...state.messageRefs.byRef }
2631
- },
2632
- tokenSnapshot: { ...state.tokenSnapshot ?? {} },
2633
- nudge: { ...state.nudge, anchors: { ...state.nudge.anchors } },
2634
- stats: { ...state.stats },
2635
- absorbed: (state.absorbed ?? []).map((record) => ({ ...record })),
2636
- nextBlockId: state.nextBlockId,
2637
- nextRunId: state.nextRunId
2638
- };
2639
- }
2640
2718
  function scoreRelevance(block, terms) {
2641
2719
  const topic = (block.topic ?? "").toLowerCase();
2642
2720
  const summary = block.summary.toLowerCase();
@@ -2967,9 +3045,9 @@ function parseObjectValue(value, callId, diag) {
2967
3045
  const content = value["content"];
2968
3046
  if (content === void 0) {
2969
3047
  const single = validateEntry(value, callId);
2970
- if (single) {
3048
+ if ("range" in single) {
2971
3049
  diag.kind = "ok";
2972
- return finish([single], diag);
3050
+ return finish([single.range], diag);
2973
3051
  }
2974
3052
  diag.kind = "missing-content";
2975
3053
  return finish([], diag);
@@ -2990,7 +3068,7 @@ function parseObjectValue(value, callId, diag) {
2990
3068
  diag.kind = "content-not-array";
2991
3069
  return finish([], diag);
2992
3070
  }
2993
- const { ranges, invalid } = validateEntries(entries, callId);
3071
+ const { ranges, invalid, reasons } = validateEntries(entries, callId);
2994
3072
  const topTopic = stringOr(value["topic"]);
2995
3073
  const topMaxChars = value["summaryMaxChars"];
2996
3074
  const hasTopMaxChars = typeof topMaxChars === "number" && Number.isFinite(topMaxChars);
@@ -3001,6 +3079,7 @@ function parseObjectValue(value, callId, diag) {
3001
3079
  }
3002
3080
  }
3003
3081
  diag.invalidItems = invalid;
3082
+ if (reasons.length > 0) diag.invalidReasons = reasons;
3004
3083
  diag.kind = salvaged ? "truncated" : ranges.length > 0 ? "ok" : "no-valid-ranges";
3005
3084
  return finish(ranges, diag);
3006
3085
  }
@@ -3024,36 +3103,43 @@ function finish(ranges, diag) {
3024
3103
  return { ranges, diagnostics: diag };
3025
3104
  }
3026
3105
  function finishSalvage(entries, callId, diag, truncatedShape) {
3027
- const { ranges, invalid } = validateEntries(entries, callId);
3106
+ const { ranges, invalid, reasons } = validateEntries(entries, callId);
3028
3107
  diag.invalidItems = invalid;
3108
+ if (reasons.length > 0) diag.invalidReasons = reasons;
3029
3109
  diag.kind = entries.length > 0 || truncatedShape ? "truncated" : "malformed-json";
3030
3110
  return finish(ranges, diag);
3031
3111
  }
3032
3112
  function validateEntries(entries, callId) {
3033
3113
  const ranges = [];
3114
+ const reasons = [];
3034
3115
  let invalid = 0;
3035
- for (const entry of entries) {
3036
- const range = validateEntry(entry, callId);
3037
- if (range !== null) ranges.push(range);
3038
- else invalid++;
3116
+ for (let i = 0; i < entries.length; i++) {
3117
+ const outcome = validateEntry(entries[i], callId);
3118
+ if ("range" in outcome) ranges.push(outcome.range);
3119
+ else {
3120
+ invalid++;
3121
+ reasons.push(`entry ${i}: ${outcome.reason}`);
3122
+ }
3039
3123
  }
3040
- return { ranges, invalid };
3124
+ return { ranges, invalid, reasons };
3041
3125
  }
3042
3126
  function validateEntry(entry, callId) {
3043
- if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return null;
3127
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return { reason: "not an object" };
3044
3128
  const e = entry;
3045
3129
  const start = stringOr(e["startRef"]) ?? stringOr(e["startId"]) ?? stringOr(e["messageId"]);
3046
3130
  const end = stringOr(e["endRef"]) ?? stringOr(e["endId"]) ?? stringOr(e["messageId"]);
3047
- if (start === void 0 || end === void 0) return null;
3131
+ if (start === void 0 || end === void 0) {
3132
+ return { reason: "missing range bounds (need startRef/startId and endRef/endId)" };
3133
+ }
3048
3134
  const summary = stringOr(e["summary"]);
3049
- if (summary === void 0) return null;
3135
+ if (summary === void 0) return { reason: "missing summary" };
3050
3136
  const range = { startRef: start, endRef: end, summary };
3051
3137
  const topic = stringOr(e["topic"]);
3052
3138
  if (topic !== void 0) range.topic = topic;
3053
3139
  const maxChars = e["summaryMaxChars"];
3054
3140
  if (typeof maxChars === "number" && Number.isFinite(maxChars)) range.summaryMaxChars = maxChars;
3055
3141
  if (callId !== void 0) range.compressCallId = callId;
3056
- return range;
3142
+ return { range };
3057
3143
  }
3058
3144
  function stringOr(value) {
3059
3145
  return typeof value === "string" && value.length > 0 ? value : void 0;
@@ -10691,7 +10777,7 @@ async function runDelegate(pi, args, ctx, signal) {
10691
10777
  await appendFile(file2, fallback ? `${fallback}
10692
10778
  ` : "(no output)\n");
10693
10779
  }
10694
- const effectiveCode = code ?? (output || stderrText ? 0 : null);
10780
+ const effectiveCode = effectiveExitCode(code, output, stderrText);
10695
10781
  run.result = { code, file: file2, body: body2 };
10696
10782
  run.status = effectiveCode === 0 ? "completed" : "failed";
10697
10783
  run.finishedAt = Date.now();
@@ -10709,7 +10795,7 @@ async function runDelegate(pi, args, ctx, signal) {
10709
10795
  return;
10710
10796
  }
10711
10797
  const mode = delegateDisplayUsage;
10712
- const injected = injectResult(pi, args.agent, runId, args.task, code, file2, run.timedOut, run.usage, mode, run.usageReported, run.status === "failed" ? body2 : void 0);
10798
+ const injected = injectResult(pi, args.agent, runId, args.task, run.status, code, file2, run.timedOut, run.usage, mode, run.usageReported, run.status === "failed" ? body2 : void 0);
10713
10799
  if (run.usage && !run.usageReported && (mode === "separate" || injected)) {
10714
10800
  run.usageReported = true;
10715
10801
  }
@@ -10834,15 +10920,18 @@ function formatSyncResult(agent, runId, task, r, file) {
10834
10920
  const body = r.timedOut ? "(timed out)" : r.stderr.trim() || "(no stderr)";
10835
10921
  return formatPayload(header, file, task, body);
10836
10922
  }
10837
- function injectResult(pi, agent, runId, task, code, file, timedOut, usage, mode = "separate", usageAlreadyReported, body) {
10923
+ function effectiveExitCode(code, output, stderr) {
10924
+ return code ?? (output || stderr ? 0 : null);
10925
+ }
10926
+ function injectResult(pi, agent, runId, task, status, code, file, timedOut, usage, mode = "separate", usageAlreadyReported, body) {
10838
10927
  const send = pi.sendUserMessage;
10839
10928
  if (typeof send !== "function") {
10840
10929
  debug.event("delegate-inject-skipped", { runId, reason: "sendUserMessage unavailable" });
10841
10930
  logWarn("delegate", { event: "inject-skipped", runId, reason: "sendUserMessage unavailable" });
10842
10931
  return false;
10843
10932
  }
10844
- const failed = code !== 0;
10845
- const status = failed ? "FAILED \u26A0\uFE0F" : "completed";
10933
+ const failed = status === "failed";
10934
+ const statusLabel = failed ? "FAILED \u26A0\uFE0F" : "completed";
10846
10935
  const remaining = Array.from(runs.values()).filter((r) => r.status === "running").length;
10847
10936
  const remainingLine = remaining > 0 ? ` ${remaining} delegate${remaining === 1 ? " is" : "s are"} still running; keep doing other work and their notifications will arrive as they finish.` : " No delegates are currently running.";
10848
10937
  const timeoutNote = timedOut ? ` (timed out: ${timedOut})` : "";
@@ -10877,7 +10966,7 @@ Tokens: ${totalUsage.input.toLocaleString()} in, ${totalUsage.output.toLocaleStr
10877
10966
  if (lines.length) usageNote = ` Usage: ${lines.join(", ")}.`;
10878
10967
  }
10879
10968
  const closing = failed ? "This delegate did NOT complete its task \u2014 its result is missing from your work. Read the error excerpt (and the result file if present), then decide whether to re-dispatch the task before wrapping up. This is an automated system notification, NOT a user message." : "This is an automated system notification, NOT a user message. Read the result file if you need the details, then continue your original task; do not treat this as a new user request.";
10880
- const header = `[acp_delegate ${status}] **${agent}** (runId \`${runId}\`, exit ${code ?? "?"})${timeoutNote}${remainingLine}${usageNote} ${closing}`;
10969
+ const header = `[acp_delegate ${statusLabel}] **${agent}** (runId \`${runId}\`, exit ${code ?? "?"})${timeoutNote}${remainingLine}${usageNote} ${closing}`;
10881
10970
  const { text: recoveryText, covered } = buildRecoveryNotice(Array.from(runs.values()), runId);
10882
10971
  const text = formatPayload(header, file, task, failed ? body : void 0) + (recoveryText ? `
10883
10972
 
@@ -10902,6 +10991,7 @@ function notifyTerminalFailure(pi, run, body) {
10902
10991
  run.agent,
10903
10992
  run.runId,
10904
10993
  run.task,
10994
+ run.status,
10905
10995
  run.result?.code ?? null,
10906
10996
  run.result?.file ?? "",
10907
10997
  run.timedOut,
@@ -11503,7 +11593,7 @@ async function statusReport(runtime, ctx) {
11503
11593
  const coveredIds = collectCoveredMessageIds(state);
11504
11594
  const sentTokens = estimateTokens(coreMessages, coveredIds, imageTokens) + systemPromptTokens;
11505
11595
  const turn = runtime.core.processTurn({ messages: coreMessages, state, config, tokenCount: anchorStale ? sentTokens : Math.max(sentTokens, realUsage?.tokens ?? 0) });
11506
- const versionStr = "0.1.53" ? `billion-context-pi@${"0.1.53"}` : void 0;
11596
+ const versionStr = "0.1.54" ? `billion-context-pi@${"0.1.54"}` : void 0;
11507
11597
  let text = buildStatusPanel({
11508
11598
  version: versionStr,
11509
11599
  tokenCount: sessionTokens,
@@ -11866,7 +11956,7 @@ async function autoInstallLatest(latest, extDirOverride) {
11866
11956
  "--no-fund",
11867
11957
  "--no-save"
11868
11958
  ];
11869
- const prevVersion = (await readPackageJson(join8(extDir, "package.json")))?.version ?? "0.1.53";
11959
+ const prevVersion = (await readPackageJson(join8(extDir, "package.json")))?.version ?? "0.1.54";
11870
11960
  const { code, stderr } = await runNpmImpl(installArgs(latest), { cwd: npmDir, timeout: 6e4 });
11871
11961
  if (code !== 0) {
11872
11962
  logWarn("update", {
@@ -11879,7 +11969,7 @@ async function autoInstallLatest(latest, extDirOverride) {
11879
11969
  }
11880
11970
  const verify = await verifyInstall(npmDir, latest);
11881
11971
  if (!verify.ok) {
11882
- const rollbackTo = SEMVER_RE.test(prevVersion) ? prevVersion : "0.1.53";
11972
+ const rollbackTo = SEMVER_RE.test(prevVersion) ? prevVersion : "0.1.54";
11883
11973
  logWarn("update", { event: "auto-install-verify-failed", latest, reason: verify.reason, rollbackTo });
11884
11974
  const rb = await runNpmImpl(installArgs(rollbackTo), { cwd: npmDir, timeout: 6e4 });
11885
11975
  logInfo("update", { event: "rollback", from: latest, to: rollbackTo, ok: rb.code === 0 });
@@ -11940,7 +12030,7 @@ async function checkForUpdate(autoUpdate, notify) {
11940
12030
  const runtimeVersion = await getRuntimeVersion();
11941
12031
  const latest = await fetchLatestVersion();
11942
12032
  if (!latest) return;
11943
- const current = runtimeVersion ?? "0.1.53";
12033
+ const current = runtimeVersion ?? "0.1.54";
11944
12034
  const hasUpdate = isNewer(latest, current);
11945
12035
  debug.event("update-check", {
11946
12036
  current,
@@ -12016,7 +12106,7 @@ function wireSessionLifecycle(pi, runtime) {
12016
12106
  setDelegateDisplayUsage("separate");
12017
12107
  const sid = ctx.sessionManager.getSessionId();
12018
12108
  const modelInfo = ctx.model;
12019
- logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.53" : null, model: modelInfo?.id ?? null, modelApi: modelInfo?.api ?? null, contextWindow: modelInfo?.contextWindow ?? null });
12109
+ logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.54" : null, model: modelInfo?.id ?? null, modelApi: modelInfo?.api ?? null, contextWindow: modelInfo?.contextWindow ?? null });
12020
12110
  try {
12021
12111
  await runtime.reloadConfig(ctx.cwd);
12022
12112
  setDelegateDisplayUsage(resolveDelegate(runtime.adapter).displayUsage);