@ponythewhite/base-context 1.0.6 → 1.0.8

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.
Files changed (136) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/base-context-runtime/pyproject.toml +1 -1
  3. package/dist/base-context-runtime/src/rlm/__init__.py +3 -0
  4. package/dist/base-context-runtime/src/rlm/harness.py +86 -48
  5. package/dist/base-context-runtime/test/test_harness.py +215 -140
  6. package/dist/base-context-runtime/uv.lock +1 -1
  7. package/dist/build-info.json +1 -1
  8. package/dist/bundle/amazon-bedrock.js +3 -3
  9. package/dist/bundle/{anthropic-UV5GZDGU.js → anthropic-5PSCZEER.js} +2 -7
  10. package/dist/bundle/{azure-openai-responses-WSNPXLYC.js → azure-openai-responses-AYTLJR33.js} +2 -2
  11. package/dist/bundle/{bundled-modules-MEACET4U.js → bundled-modules-IGMJH2MU.js} +5 -5
  12. package/dist/bundle/{chunk-UPVDERMD.js → chunk-2IPDSTDR.js} +11 -9
  13. package/dist/bundle/chunk-D3KOF57W.js +22040 -0
  14. package/dist/bundle/{chunk-QKI6KEGP.js → chunk-EXEFSSF4.js} +1509 -577
  15. package/dist/bundle/{chunk-VSIYFWYI.js → chunk-KT2BNEPE.js} +21 -8
  16. package/dist/bundle/{chunk-KTNETTHI.js → chunk-Q3MBSBEB.js} +3 -101
  17. package/dist/bundle/{chunk-LFPWHI42.js → chunk-QKIFBA7N.js} +2 -2
  18. package/dist/bundle/{chunk-LIZVVGHR.js → chunk-RVJVXZCS.js} +13 -10
  19. package/dist/bundle/{cli-main-ANUJY7Y4.js → cli-main-BUK7QTHO.js} +4 -4
  20. package/dist/bundle/cli.js +1 -1
  21. package/dist/bundle/{google-T32FMD4G.js → google-HTMCUJ7X.js} +2 -2
  22. package/dist/bundle/{google-vertex-56KP6I3B.js → google-vertex-ZQO65AXF.js} +2 -2
  23. package/dist/bundle/{main-4GIKOHIT.js → main-2FWHT2WZ.js} +5 -5
  24. package/dist/bundle/{mistral-WGBMYIVU.js → mistral-SB7LU56G.js} +1 -1
  25. package/dist/bundle/{openai-codex-responses-PIV2RQ3G.js → openai-codex-responses-5CEW4VN3.js} +53 -27
  26. package/dist/bundle/{openai-completions-64C3JPR4.js → openai-completions-BAZJQSS2.js} +1 -1
  27. package/dist/bundle/{openai-responses-UI7RSTDV.js → openai-responses-VHOE427P.js} +48 -5
  28. package/dist/core/agent-session.d.ts +6 -1
  29. package/dist/core/agent-session.d.ts.map +1 -1
  30. package/dist/core/agent-session.js +189 -31
  31. package/dist/core/agent-session.js.map +1 -1
  32. package/dist/core/canonical-context.d.ts +8 -0
  33. package/dist/core/canonical-context.d.ts.map +1 -1
  34. package/dist/core/canonical-context.js +37 -5
  35. package/dist/core/canonical-context.js.map +1 -1
  36. package/dist/core/compaction/compaction.d.ts +2 -2
  37. package/dist/core/compaction/compaction.d.ts.map +1 -1
  38. package/dist/core/compaction/compaction.js +31 -13
  39. package/dist/core/compaction/compaction.js.map +1 -1
  40. package/dist/core/context-epoch.d.ts +1 -1
  41. package/dist/core/context-epoch.d.ts.map +1 -1
  42. package/dist/core/context-epoch.js +20 -12
  43. package/dist/core/context-epoch.js.map +1 -1
  44. package/dist/core/extensions/types.d.ts +2 -2
  45. package/dist/core/extensions/types.d.ts.map +1 -1
  46. package/dist/core/extensions/types.js.map +1 -1
  47. package/dist/core/history-index-worker.d.ts.map +1 -1
  48. package/dist/core/history-index-worker.js +29 -8
  49. package/dist/core/history-index-worker.js.map +1 -1
  50. package/dist/core/history-index.d.ts +11 -1
  51. package/dist/core/history-index.d.ts.map +1 -1
  52. package/dist/core/history-index.js +2 -1
  53. package/dist/core/history-index.js.map +1 -1
  54. package/dist/core/inference-coordinator.d.ts.map +1 -1
  55. package/dist/core/inference-coordinator.js +110 -13
  56. package/dist/core/inference-coordinator.js.map +1 -1
  57. package/dist/core/kernel/bootstrap.d.ts.map +1 -1
  58. package/dist/core/kernel/bootstrap.js +97 -107
  59. package/dist/core/kernel/bootstrap.js.map +1 -1
  60. package/dist/core/kernel/repl-manager.d.ts.map +1 -1
  61. package/dist/core/kernel/repl-manager.js +11 -0
  62. package/dist/core/kernel/repl-manager.js.map +1 -1
  63. package/dist/core/kernel/shared.d.ts +2 -0
  64. package/dist/core/kernel/shared.d.ts.map +1 -1
  65. package/dist/core/kernel/shared.js.map +1 -1
  66. package/dist/core/messages.d.ts.map +1 -1
  67. package/dist/core/messages.js +4 -1
  68. package/dist/core/messages.js.map +1 -1
  69. package/dist/core/prompts/rlm.d.ts.map +1 -1
  70. package/dist/core/prompts/rlm.js +4 -2
  71. package/dist/core/prompts/rlm.js.map +1 -1
  72. package/dist/core/public-context.d.ts.map +1 -1
  73. package/dist/core/public-context.js +16 -0
  74. package/dist/core/public-context.js.map +1 -1
  75. package/dist/core/refinement/refinement.d.ts +19 -5
  76. package/dist/core/refinement/refinement.d.ts.map +1 -1
  77. package/dist/core/refinement/refinement.js +90 -35
  78. package/dist/core/refinement/refinement.js.map +1 -1
  79. package/dist/core/request-view-selection.d.ts +12 -4
  80. package/dist/core/request-view-selection.d.ts.map +1 -1
  81. package/dist/core/request-view-selection.js +242 -100
  82. package/dist/core/request-view-selection.js.map +1 -1
  83. package/dist/core/retained-tool-output.d.ts +28 -0
  84. package/dist/core/retained-tool-output.d.ts.map +1 -0
  85. package/dist/core/retained-tool-output.js +102 -0
  86. package/dist/core/retained-tool-output.js.map +1 -0
  87. package/dist/core/sdk.d.ts.map +1 -1
  88. package/dist/core/sdk.js +1 -1
  89. package/dist/core/sdk.js.map +1 -1
  90. package/dist/core/selective-recovery.d.ts +47 -3
  91. package/dist/core/selective-recovery.d.ts.map +1 -1
  92. package/dist/core/selective-recovery.js +428 -109
  93. package/dist/core/selective-recovery.js.map +1 -1
  94. package/dist/core/session-history-index.d.ts +3 -1
  95. package/dist/core/session-history-index.d.ts.map +1 -1
  96. package/dist/core/session-history-index.js +1 -1
  97. package/dist/core/session-history-index.js.map +1 -1
  98. package/dist/core/settings-manager.d.ts +5 -0
  99. package/dist/core/settings-manager.d.ts.map +1 -1
  100. package/dist/core/settings-manager.js +3 -0
  101. package/dist/core/settings-manager.js.map +1 -1
  102. package/dist/core/system-prompt.d.ts +3 -1
  103. package/dist/core/system-prompt.d.ts.map +1 -1
  104. package/dist/core/system-prompt.js +3 -3
  105. package/dist/core/system-prompt.js.map +1 -1
  106. package/dist/core/task-frame.d.ts +1 -1
  107. package/dist/core/task-frame.d.ts.map +1 -1
  108. package/dist/core/task-frame.js +21 -1
  109. package/dist/core/task-frame.js.map +1 -1
  110. package/dist/core/task-state-reader.d.ts +10 -2
  111. package/dist/core/task-state-reader.d.ts.map +1 -1
  112. package/dist/core/task-state-reader.js +75 -9
  113. package/dist/core/task-state-reader.js.map +1 -1
  114. package/dist/core/tools/ipython.d.ts +5 -0
  115. package/dist/core/tools/ipython.d.ts.map +1 -1
  116. package/dist/core/tools/ipython.js +59 -17
  117. package/dist/core/tools/ipython.js.map +1 -1
  118. package/dist/core/tools/prime-context.d.ts.map +1 -1
  119. package/dist/core/tools/prime-context.js +1 -1
  120. package/dist/core/tools/prime-context.js.map +1 -1
  121. package/dist/core/view-units.d.ts +4 -0
  122. package/dist/core/view-units.d.ts.map +1 -1
  123. package/dist/core/view-units.js +28 -19
  124. package/dist/core/view-units.js.map +1 -1
  125. package/dist/installer.mjs +98 -77
  126. package/dist/modes/interactive/components/refinement-outcome-message.d.ts.map +1 -1
  127. package/dist/modes/interactive/components/refinement-outcome-message.js.map +1 -1
  128. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  129. package/dist/modes/interactive/interactive-mode.js +5 -5
  130. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  131. package/dist/skills/skill-creator/SKILL.md +18 -1
  132. package/docs/settings.md +45 -0
  133. package/docs/skills.md +77 -2
  134. package/package.json +4 -4
  135. package/skills/skill-creator/SKILL.md +18 -1
  136. package/dist/bundle/chunk-W46VZFI6.js +0 -21681
@@ -1,10 +1,13 @@
1
+ import { randomUUID } from "node:crypto";
1
2
  import { StringEnum } from "@ponythewhite/base-context-ai";
2
3
  import { Type } from "typebox";
3
4
  import { Value } from "typebox/value";
4
5
  import { AGENT_RESULT_CUSTOM_TYPE } from "./agent-results.js";
5
6
  import { stringifyBoundedJson } from "./bounded-json.js";
7
+ import { decodedTextWindow, PUBLIC_TEXT_WINDOW_BYTES, readRetainedToolOutput, retainedToolOutput, } from "./retained-tool-output.js";
6
8
  import { SELECTED_SKILL_CUSTOM_TYPE, selectedSkillCapture } from "./selected-skills.js";
7
9
  import { hydrateCapturedHistoryEntry } from "./session-history-index.js";
10
+ import { getSessionArtifactPathForFile } from "./session-manager.js";
8
11
  import { projectTaskStateSource } from "./task-state.js";
9
12
  /** Defaults match bounded index pages and 64KiB payload parts; all admission units are bytes or items. */
10
13
  export const DEFAULT_NATIVE_RECOVERY_LIMITS = Object.freeze({
@@ -18,6 +21,9 @@ const byteLimit = Type.Integer({ minimum: 1, maximum: Number.MAX_SAFE_INTEGER })
18
21
  const operationSchema = Type.Object({
19
22
  action: Type.Union([Type.Literal("read"), Type.Literal("search"), Type.Literal("recover")]),
20
23
  ref: Type.Optional(exactString),
24
+ cursor: Type.Optional(exactString),
25
+ startByte: Type.Optional(Type.Integer({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER })),
26
+ endByte: Type.Optional(byteLimit),
21
27
  revision: Type.Optional(exactString),
22
28
  sourceSessionId: Type.Optional(exactString),
23
29
  field: Type.Optional(exactString),
@@ -234,6 +240,10 @@ function assemble(scope, results) {
234
240
  field: record.field,
235
241
  startLine: record.startLine,
236
242
  endLine: record.endLine,
243
+ startByte: record.startByte,
244
+ endByte: record.endByte,
245
+ prefixOmitted: record.prefixOmitted,
246
+ suffixOmitted: record.suffixOmitted,
237
247
  }))),
238
248
  };
239
249
  }
@@ -250,15 +260,41 @@ export function parseNativeRecoveryInput(value) {
250
260
  throw new Error("Invalid native recovery request");
251
261
  return request;
252
262
  }
263
+ /** Session-owned, ephemeral searches. No read lease or canonical body survives a call. */
264
+ export class NativeRecoveryCursorStore {
265
+ states = new Map();
266
+ generation = 0;
267
+ clear() {
268
+ this.states.clear();
269
+ this.generation++;
270
+ }
271
+ get(id) {
272
+ return this.states.get(id);
273
+ }
274
+ delete(id) {
275
+ this.states.delete(id);
276
+ }
277
+ get version() {
278
+ return this.generation;
279
+ }
280
+ save(state, version, id = randomUUID()) {
281
+ if (version !== this.generation)
282
+ throw new Error("Expired recovery cursor");
283
+ this.states.delete(id);
284
+ this.states.set(id, structuredClone(state));
285
+ while (this.states.size > 16)
286
+ this.states.delete(this.states.keys().next().value);
287
+ return id;
288
+ }
289
+ }
253
290
  /** The caller captures ONE server branch before entering this service, including for a batch. */
254
- export async function recoverCapturedHistory(view, input, limits = DEFAULT_NATIVE_RECOVERY_LIMITS, signal) {
291
+ export async function recoverCapturedHistory(view, input, limits = DEFAULT_NATIVE_RECOVERY_LIMITS, signal, cursors) {
255
292
  const cap = { ...limits };
256
293
  for (const key of ["maxBytes", "maxSourceBytes", "maxItems", "maxRequests"]) {
257
294
  if (!Number.isSafeInteger(cap[key]) || cap[key] < 1)
258
295
  throw new NativeRecoveryBudgetRefusal();
259
296
  cap[key] = Math.min(cap[key], DEFAULT_NATIVE_RECOVERY_LIMITS[key]);
260
297
  }
261
- // Bound and detach input before schema traversal or the first await. Unknown selectors are rejected.
262
298
  let request;
263
299
  try {
264
300
  request = parseNativeRecoveryInput(input);
@@ -267,6 +303,24 @@ export async function recoverCapturedHistory(view, input, limits = DEFAULT_NATIV
267
303
  return createNativeRecoveryRefusal(error instanceof NativeRecoveryBudgetRefusal ? "budget_refused" : "unavailable", "invalid_request", cap.maxBytes);
268
304
  }
269
305
  const maxBytes = Math.min(cap.maxBytes, request.maxBytes ?? cap.maxBytes);
306
+ if (request.action === "skill")
307
+ return createNativeRecoveryRefusal("not_authorized", "native_skill_selection_owner_required", maxBytes);
308
+ const operations = request.action === "batch" ? request.requests : [request];
309
+ const version = cursors?.version ?? 0;
310
+ const resumed = operations.map((operation) => (operation.cursor ? cursors?.get(operation.cursor) : undefined));
311
+ const anchor = resumed.find(Boolean)?.source;
312
+ if (anchor) {
313
+ if (resumed.some((state) => state && JSON.stringify(state.source) !== JSON.stringify(anchor)))
314
+ return createNativeRecoveryRefusal("unavailable", "conflicting_selection", maxBytes);
315
+ try {
316
+ if (!view.atSnapshot)
317
+ throw new Error("Captured source is unavailable");
318
+ view = await view.atSnapshot(anchor);
319
+ }
320
+ catch {
321
+ return createNativeRecoveryRefusal("unavailable", "cursor_source_unavailable", maxBytes);
322
+ }
323
+ }
270
324
  const { sessionId: sourceSessionId, leafId, sourceSequence } = view.source;
271
325
  const scope = {
272
326
  kind: "branch",
@@ -275,11 +329,7 @@ export async function recoverCapturedHistory(view, input, limits = DEFAULT_NATIV
275
329
  leafId,
276
330
  sourceSequence,
277
331
  };
278
- if (request.action === "skill")
279
- return createNativeRecoveryRefusal("not_authorized", "native_skill_selection_owner_required", maxBytes);
280
- const operations = request.action === "batch" ? request.requests : [request];
281
332
  const results = operations.map((_, i) => outcome(i, "budget_refused", "unknown", [], "not_admitted"));
282
- // Reserve an explicit result for EVERY requested operation before touching canonical payloads.
283
333
  stringifyNativeRecoveryResponse(assemble(scope, results), maxBytes);
284
334
  if (operations.length > cap.maxRequests)
285
335
  return assemble(scope, results);
@@ -293,14 +343,10 @@ export async function recoverCapturedHistory(view, input, limits = DEFAULT_NATIV
293
343
  if (cached && cached.revision !== metadata.revision)
294
344
  throw new Error("Captured revision changed");
295
345
  if (!cached) {
296
- if (++examined > cap.maxItems || metadata.locator.length > cap.maxSourceBytes - sourceBytes) {
346
+ if (metadata.locator.length > cap.maxSourceBytes - sourceBytes)
297
347
  throw new NativeRecoveryBudgetRefusal();
298
- }
299
348
  sourceBytes += metadata.locator.length;
300
- const hydrated = await hydrateCapturedHistoryEntry(metadata, metadata.locator.length, async (id, options) => {
301
- signal?.throwIfAborted();
302
- return view.readPayload(id, options);
303
- });
349
+ const hydrated = await hydrateCapturedHistoryEntry(metadata, metadata.locator.length, (id, options) => view.readPayload(id, options));
304
350
  signal?.throwIfAborted();
305
351
  cached = { revision: metadata.revision, entry: hydrated.entry };
306
352
  cache.set(metadata.id, cached);
@@ -310,8 +356,6 @@ export async function recoverCapturedHistory(view, input, limits = DEFAULT_NATIV
310
356
  metadata.qualification !== "native-admission" ||
311
357
  metadata.retention === "retained-import")
312
358
  return undefined;
313
- // Reuse the existing exact native-input qualification, not generic origin traversal.
314
- // The first qualifying projection is submitted.text; return before visiting content parts.
315
359
  for (const projection of projectTaskStateSource({
316
360
  sessionId: sourceSessionId,
317
361
  sequence: metadata.sequence,
@@ -323,121 +367,396 @@ export async function recoverCapturedHistory(view, input, limits = DEFAULT_NATIV
323
367
  if (projection.source.field === requestedField &&
324
368
  projection.authority === "user" &&
325
369
  projection.attribution === "source-backed" &&
326
- typeof projection.text === "string") {
370
+ typeof projection.text === "string")
327
371
  return [{ field: requestedField, text: projection.text }];
328
- }
329
372
  return undefined;
330
373
  }
331
374
  return undefined;
332
375
  }
333
- return publicFields(cached.entry, cap.maxItems, metadata.qualification);
376
+ const fields = publicFields(cached.entry, DEFAULT_NATIVE_RECOVERY_LIMITS.maxItems, metadata.qualification);
377
+ if (cached.entry.type === "message") {
378
+ const message = object(cached.entry.message);
379
+ const retained = message?.role === "toolResult" ? retainedToolOutput(object(message.details)?.retainedOutput) : undefined;
380
+ const field = fields?.find((field) => field.field === retained?.field);
381
+ if (field && retained) {
382
+ if (metadata.qualification === "native-tool-execution" && metadata.retention !== "retained-import")
383
+ field.retained = retained;
384
+ else
385
+ field.unavailable = true; // A preview is not the complete retained field, nor artifact authority.
386
+ }
387
+ }
388
+ return fields;
334
389
  }
335
- async function recover(operation, i) {
336
- if (operation.sourceSessionId !== undefined && operation.sourceSessionId !== scope.sourceSessionId) {
390
+ async function recover(supplied, i) {
391
+ const saved = resumed[i];
392
+ if (supplied.cursor && !saved)
393
+ return outcome(i, "unavailable", "unknown", [], "expired_cursor");
394
+ if (saved && supplied.action === "read")
395
+ return outcome(i, "unavailable", "unknown", [], "conflicting_selection");
396
+ const operation = saved ? { ...saved.operation, maxBytes: supplied.maxBytes } : supplied;
397
+ const needle = operation.need ?? operation.query;
398
+ if (saved &&
399
+ Object.entries(supplied).some(([key, value]) => !["action", "cursor", "maxBytes"].includes(key) &&
400
+ value !==
401
+ (key === "need" || key === "query" ? needle : saved.operation[key])))
402
+ return outcome(i, "unavailable", "unknown", [], "conflicting_selection");
403
+ if (operation.sourceSessionId !== undefined && operation.sourceSessionId !== sourceSessionId)
337
404
  return outcome(i, "not_authorized", "unknown", [], "source_not_in_capture");
338
- }
339
405
  if ((operation.need !== undefined && operation.query !== undefined && operation.need !== operation.query) ||
340
- (operation.endLine !== undefined && operation.endLine < (operation.startLine ?? 1))) {
406
+ (operation.endLine !== undefined && operation.endLine < (operation.startLine ?? 1)) ||
407
+ (operation.endByte !== undefined && operation.endByte <= (operation.startByte ?? 0)) ||
408
+ ((operation.startByte !== undefined || operation.endByte !== undefined) &&
409
+ (operation.startLine !== undefined || operation.endLine !== undefined)))
341
410
  return outcome(i, "unavailable", "unknown", [], "conflicting_selection");
342
- }
343
- const needle = operation.need ?? operation.query;
344
- let candidates;
345
- let coverage;
346
- if (operation.ref !== undefined) {
347
- const metadata = await view.get(operation.ref);
348
- if (!metadata || metadata.id !== operation.ref) {
349
- return outcome(i, "not_authorized", "unknown", [], "ref_not_in_captured_branch");
350
- }
351
- if (operation.revision !== undefined && operation.revision !== metadata.revision) {
352
- return outcome(i, "unavailable", "unknown", [], "revision_mismatch");
353
- }
354
- candidates = [metadata];
355
- coverage = "complete";
356
- }
357
- else {
358
- if (operation.action === "read" ||
411
+ if (operation.ref === undefined &&
412
+ (operation.action === "read" ||
359
413
  needle === undefined ||
360
414
  operation.revision !== undefined ||
361
- operation.field !== undefined ||
362
415
  operation.startLine !== undefined ||
363
- operation.endLine !== undefined) {
364
- return outcome(i, "unavailable", "unknown", [], "exact_selection_requires_ref");
365
- }
366
- if (Buffer.byteLength(needle) > 8192)
367
- throw new NativeRecoveryBudgetRefusal();
368
- if (examined >= cap.maxItems)
369
- throw new NativeRecoveryBudgetRefusal();
370
- const page = await view.search(needle, cap.maxItems - examined);
371
- candidates = page.events;
372
- // Search indexes token conjunctions, including non-public strings. It is not an exhaustive
373
- // literal-substring search even when its indexed text coverage is reported complete.
374
- coverage =
375
- page.coverage === "partial" || page.truncated || page.indexedThrough < sourceSequence
376
- ? "partial"
377
- : "unknown";
378
- }
416
+ operation.endLine !== undefined ||
417
+ operation.startByte !== undefined ||
418
+ operation.endByte !== undefined))
419
+ return outcome(i, "unavailable", "unknown", [], "exact_selection_requires_ref");
420
+ if (needle !== undefined && Buffer.byteLength(needle) > 8192)
421
+ throw new NativeRecoveryBudgetRefusal();
422
+ const searching = needle !== undefined;
423
+ const continuable = operation.action !== "read" && searching;
424
+ const state = saved
425
+ ? structuredClone(saved)
426
+ : {
427
+ source: { ...view.source },
428
+ operation: { ...operation, cursor: undefined, maxBytes: undefined },
429
+ after: 0,
430
+ field: 0,
431
+ byte: 0,
432
+ line: 1,
433
+ overlap: "",
434
+ unavailable: false,
435
+ matched: false,
436
+ };
379
437
  const records = [];
380
- let unavailable = false;
381
- let beyondWindow = false;
382
- let needWindow = false;
383
- for (const metadata of candidates) {
384
- const fields = await fieldsFor(metadata, operation.field);
385
- if (!fields || (operation.field !== undefined && !fields.some((field) => field.field === operation.field))) {
386
- unavailable = true;
387
- continue;
438
+ let partialWindow = false;
439
+ let reason;
440
+ let exhausted = false;
441
+ let windows = 0;
442
+ const cursorId = supplied.cursor ?? randomUUID();
443
+ const progress = (complete) => ({
444
+ ...outcome(i, complete && !state.unavailable && !partialWindow
445
+ ? records.length || state.matched
446
+ ? "found"
447
+ : "complete-miss"
448
+ : "partial", complete && !state.unavailable && !partialWindow ? "complete" : "partial", records, reason),
449
+ ...(continuable ? { exhausted: complete, ...(!complete && cursors ? { cursor: cursorId } : {}) } : {}),
450
+ });
451
+ const reserve = () => {
452
+ const candidate = progress(false);
453
+ candidate.reason = "canonical_source_unavailable";
454
+ stringifyBoundedJson(candidate, Math.min(maxBytes, supplied.maxBytes ?? maxBytes));
455
+ const prospective = [...results];
456
+ prospective[i] = candidate;
457
+ stringifyNativeRecoveryResponse(assemble(scope, prospective), maxBytes);
458
+ };
459
+ reserve(); // Keep cursor/status space before examining any source material.
460
+ const admit = (record) => {
461
+ if (selectedItems + records.length >= cap.maxItems)
462
+ return false;
463
+ records.push(record);
464
+ try {
465
+ reserve();
466
+ return true;
388
467
  }
389
- for (const selected of fields) {
390
- if (operation.field !== undefined && operation.field !== selected.field)
468
+ catch {
469
+ records.pop();
470
+ return false;
471
+ }
472
+ };
473
+ const resetField = () => {
474
+ state.byte = 0;
475
+ state.line = 1;
476
+ state.overlap = "";
477
+ };
478
+ const nextEntry = (metadata) => {
479
+ state.after = metadata.sequence;
480
+ delete state.entryId;
481
+ state.field = 0;
482
+ resetField();
483
+ };
484
+ while (!exhausted) {
485
+ signal?.throwIfAborted();
486
+ if (examined >= cap.maxItems) {
487
+ reason = "scan_limit";
488
+ break;
489
+ }
490
+ let metadata;
491
+ let afterPage;
492
+ if (state.entryId || operation.ref) {
493
+ metadata = await view.get(state.entryId ?? operation.ref);
494
+ if (!metadata)
495
+ return outcome(i, "not_authorized", "unknown", records, "ref_not_in_captured_branch");
496
+ if (operation.revision !== undefined && metadata.revision !== operation.revision)
497
+ return outcome(i, "unavailable", "unknown", records, "revision_mismatch");
498
+ }
499
+ else {
500
+ const page = await view.page(state.after, 1, { scan: true });
501
+ if (page.coverage !== "complete" || page.indexedThrough < sourceSequence)
502
+ state.unavailable = true;
503
+ metadata = page.events[0];
504
+ afterPage = page.nextAfter;
505
+ if (!metadata) {
506
+ examined++;
507
+ if (afterPage === null) {
508
+ exhausted = true;
509
+ break;
510
+ }
511
+ if (afterPage <= state.after)
512
+ throw new Error("History scan did not advance");
513
+ state.after = afterPage;
391
514
  continue;
392
- const lines = selected.text.split("\n");
393
- let startLine = operation.startLine ?? 1;
394
- let endLine = Math.min(operation.endLine ?? lines.length, lines.length);
395
- if (needle !== undefined && operation.startLine === undefined && operation.endLine === undefined) {
396
- const first = selected.text.indexOf(needle);
397
- if (first < 0)
398
- continue;
399
- // Select whole exact lines containing the first literal occurrence, including every
400
- // line of a multiline needle. A required huge line is refused, never prefix-clipped.
401
- startLine = selected.text.slice(0, first).split("\n").length;
402
- endLine = startLine + needle.split("\n").length - 1;
403
- if (startLine > 1 || endLine < lines.length)
404
- needWindow = true;
405
515
  }
406
- if (startLine > lines.length)
516
+ }
517
+ examined++;
518
+ state.entryId = metadata.id;
519
+ if (!operation.ref &&
520
+ !["message", "custom_message", "compaction", "branch_summary", "custom"].includes(metadata.kind)) {
521
+ nextEntry(metadata);
522
+ continue;
523
+ }
524
+ if (metadata.locator.length > cap.maxSourceBytes) {
525
+ if (operation.action === "read")
526
+ return outcome(i, operation.startByte !== undefined || operation.endByte !== undefined
527
+ ? "unavailable"
528
+ : "budget_refused", "unknown", [], "field_window_unsupported");
529
+ state.unavailable = true;
530
+ reason = "field_window_unsupported";
531
+ nextEntry(metadata);
532
+ if (operation.ref)
533
+ exhausted = true;
534
+ continue;
535
+ }
536
+ if (!cache.has(metadata.id) && metadata.locator.length > cap.maxSourceBytes - sourceBytes) {
537
+ reason = "source_limit";
538
+ break;
539
+ }
540
+ let fields;
541
+ try {
542
+ fields = await fieldsFor(metadata, operation.field);
543
+ }
544
+ catch (error) {
545
+ state.unavailable = true;
546
+ reason =
547
+ error instanceof NativeRecoveryBudgetRefusal
548
+ ? "unsupported_public_shape"
549
+ : "canonical_source_unavailable";
550
+ nextEntry(metadata);
551
+ if (operation.ref)
552
+ exhausted = true;
553
+ continue;
554
+ }
555
+ if (!fields || (operation.field !== undefined && !fields.some((field) => field.field === operation.field))) {
556
+ if (operation.ref)
557
+ return outcome(i, "unavailable", "unknown", records, "unsupported_public_field");
558
+ if (!fields)
559
+ state.unavailable = true;
560
+ nextEntry(metadata);
561
+ continue;
562
+ }
563
+ let paused = false;
564
+ for (; state.field < fields.length; state.field++, resetField()) {
565
+ const field = fields[state.field];
566
+ if (operation.field !== undefined && field.field !== operation.field)
407
567
  continue;
408
- const text = lines.slice(startLine - 1, endLine).join("\n");
409
- if (needle !== undefined && !text.includes(needle))
568
+ if (field.unavailable) {
569
+ state.unavailable = true;
570
+ reason = "retained_output_unavailable";
410
571
  continue;
411
- if (operation.endLine !== undefined && operation.endLine > lines.length)
412
- beyondWindow = true;
413
- if (selectedItems + records.length >= cap.maxItems)
414
- throw new NativeRecoveryBudgetRefusal();
415
- records.push({
416
- ref: metadata.id,
417
- revision: metadata.revision,
418
- sourceSessionId,
419
- kind: metadata.kind,
420
- field: selected.field,
421
- startLine,
422
- endLine,
423
- text,
424
- });
572
+ }
573
+ const total = field.retained?.byteLength ?? Buffer.byteLength(field.text);
574
+ if (field.retained && !field.retained.captureComplete) {
575
+ state.unavailable = true;
576
+ reason = "capture_incomplete";
577
+ }
578
+ if (state.byte === 0 && operation.startByte !== undefined)
579
+ state.byte = operation.startByte;
580
+ if (operation.endByte !== undefined && operation.endByte > total) {
581
+ partialWindow = true;
582
+ reason = "requested_end_beyond_source";
583
+ }
584
+ const byteSelection = operation.startByte !== undefined || operation.endByte !== undefined;
585
+ let readText = "";
586
+ let readStart = state.byte;
587
+ let readLine = byteSelection && state.byte > 0 ? undefined : state.line;
588
+ while (state.byte < total || (total === 0 && state.byte === 0)) {
589
+ signal?.throwIfAborted();
590
+ if (++windows > cap.maxItems) {
591
+ paused = true;
592
+ reason = "scan_limit";
593
+ break;
594
+ }
595
+ const allowance = field.retained
596
+ ? Math.min(PUBLIC_TEXT_WINDOW_BYTES, cap.maxSourceBytes - sourceBytes - 4)
597
+ : PUBLIC_TEXT_WINDOW_BYTES;
598
+ if (allowance < 4) {
599
+ paused = true;
600
+ reason = "source_limit";
601
+ break;
602
+ }
603
+ const stop = Math.min(total, operation.endByte ?? total, state.byte + allowance);
604
+ if (stop < state.byte)
605
+ break;
606
+ let window;
607
+ try {
608
+ if (field.retained) {
609
+ if (!view.source.sessionFile)
610
+ throw new Error("Retained output owner unavailable");
611
+ const selected = await readRetainedToolOutput(getSessionArtifactPathForFile(view.source.sessionFile, sourceSessionId), field.retained, state.byte, stop - state.byte);
612
+ sourceBytes += selected.sourceBytes;
613
+ window = selected;
614
+ }
615
+ else
616
+ window = decodedTextWindow(field.text, state.byte, stop);
617
+ }
618
+ catch {
619
+ state.unavailable = true;
620
+ reason = "retained_output_unavailable";
621
+ break;
622
+ }
623
+ const startLine = state.line;
624
+ const combined = state.overlap + window.text;
625
+ const combinedStart = window.startByte - Buffer.byteLength(state.overlap);
626
+ const combinedLine = startLine - (state.overlap.match(/\n/g)?.length ?? 0);
627
+ let text = window.text;
628
+ let startByte = window.startByte;
629
+ let endByte = window.endByte;
630
+ let recordLine = byteSelection && state.byte > 0 ? undefined : startLine;
631
+ let selected = !searching;
632
+ if (searching) {
633
+ const lines = combined.split("\n");
634
+ const skip = Math.max(0, (operation.startLine ?? 1) - combinedLine);
635
+ const take = Math.min(lines.length, (operation.endLine ?? Infinity) - combinedLine + 1);
636
+ const prefix = lines.slice(0, skip).join("\n") + (skip ? "\n" : "");
637
+ const selectedText = lines.slice(skip, Math.max(skip, take)).join("\n");
638
+ const localMatch = selectedText.indexOf(needle);
639
+ const first = localMatch < 0 ? -1 : prefix.length + localMatch;
640
+ selected = first >= 0;
641
+ if (selected) {
642
+ const start = Math.max(prefix.length, first === 0 ? 0 : combined.lastIndexOf("\n", first - 1) + 1);
643
+ const newline = combined.indexOf("\n", first + needle.length);
644
+ text = combined.slice(start, Math.min(prefix.length + selectedText.length, newline < 0 ? combined.length : newline));
645
+ startByte = combinedStart + Buffer.byteLength(combined.slice(0, start));
646
+ endByte = startByte + Buffer.byteLength(text);
647
+ recordLine =
648
+ byteSelection && (operation.startByte ?? 0) > 0
649
+ ? undefined
650
+ : combinedLine + (combined.slice(0, start).match(/\n/g)?.length ?? 0);
651
+ if (Buffer.byteLength(text) > Math.max(Buffer.byteLength(needle) + 256, Math.floor(maxBytes / 8))) {
652
+ const matchByte = Buffer.byteLength(combined.slice(start, first));
653
+ const excerpt = decodedTextWindow(text, Math.max(0, matchByte - 128), matchByte + Buffer.byteLength(needle) + 128);
654
+ if (recordLine !== undefined)
655
+ recordLine += decodedTextWindow(text, 0, excerpt.startByte).text.match(/\n/g)?.length ?? 0;
656
+ text = excerpt.text;
657
+ startByte += excerpt.startByte;
658
+ endByte = startByte + Buffer.byteLength(text);
659
+ }
660
+ }
661
+ }
662
+ else if (!byteSelection) {
663
+ const lines = text.split("\n");
664
+ const skip = Math.max(0, (operation.startLine ?? 1) - startLine);
665
+ const take = Math.min(lines.length, (operation.endLine ?? Infinity) - startLine + 1);
666
+ selected = take > skip;
667
+ if (selected) {
668
+ const prefix = lines.slice(0, skip).join("\n") + (skip ? "\n" : "");
669
+ startByte += Buffer.byteLength(prefix);
670
+ text = lines.slice(skip, take).join("\n");
671
+ endByte = startByte + Buffer.byteLength(text);
672
+ recordLine = startLine + skip;
673
+ }
674
+ }
675
+ if (selected) {
676
+ if (!searching) {
677
+ if (!readText) {
678
+ readStart = startByte;
679
+ readLine = recordLine;
680
+ }
681
+ readText += text;
682
+ text = readText;
683
+ startByte = readStart;
684
+ recordLine = readLine;
685
+ }
686
+ const record = {
687
+ ref: metadata.id,
688
+ revision: metadata.revision,
689
+ sourceSessionId,
690
+ kind: metadata.kind,
691
+ field: field.field,
692
+ startByte,
693
+ endByte,
694
+ prefixOmitted: startByte > 0,
695
+ suffixOmitted: endByte < total,
696
+ ...(recordLine === undefined
697
+ ? {}
698
+ : { startLine: recordLine, endLine: recordLine + (text.match(/\n/g)?.length ?? 0) }),
699
+ text,
700
+ };
701
+ const done = searching ||
702
+ window.endByte >= total ||
703
+ (operation.endByte !== undefined && stop >= operation.endByte) ||
704
+ (operation.endLine !== undefined &&
705
+ startLine + (window.text.match(/\n/g)?.length ?? 0) > operation.endLine);
706
+ if (done) {
707
+ if (operation.endLine !== undefined &&
708
+ window.endByte >= total &&
709
+ startLine + (window.text.match(/\n/g)?.length ?? 0) < operation.endLine) {
710
+ partialWindow = true;
711
+ reason = "requested_end_beyond_source";
712
+ }
713
+ if (!admit(record)) {
714
+ paused = true;
715
+ reason = "output_limit";
716
+ break;
717
+ }
718
+ state.matched = true;
719
+ if (searching && (startByte > 0 || endByte < total) && operation.ref) {
720
+ partialWindow = true;
721
+ reason = "need_window";
722
+ }
723
+ break;
724
+ }
725
+ if (Buffer.byteLength(readText) > maxBytes)
726
+ throw new NativeRecoveryBudgetRefusal();
727
+ }
728
+ if (window.endByte <= state.byte ||
729
+ window.endByte >= total ||
730
+ (operation.endByte !== undefined && window.endByte >= operation.endByte))
731
+ break;
732
+ state.byte = window.endByte;
733
+ state.line += window.text.match(/\n/g)?.length ?? 0;
734
+ let tail = searching ? combined.slice(-needle.length - 2) : "";
735
+ if (tail.charCodeAt(0) >= 0xdc00 && tail.charCodeAt(0) <= 0xdfff)
736
+ tail = tail.slice(1);
737
+ state.overlap = searching
738
+ ? decodedTextWindow(tail, Math.max(0, Buffer.byteLength(tail) - Buffer.byteLength(needle) - 3)).text
739
+ : "";
740
+ }
741
+ if (paused)
742
+ break;
743
+ }
744
+ if (paused)
745
+ break;
746
+ nextEntry(metadata);
747
+ if (operation.ref || afterPage === null) {
748
+ exhausted = true;
749
+ break;
425
750
  }
426
751
  }
427
- if (operation.ref !== undefined && unavailable)
428
- return outcome(i, "unavailable", "unknown", [], "unsupported_public_field");
429
- if (unavailable || beyondWindow || needWindow)
430
- coverage = "partial";
431
- const status = coverage === "complete" ? (records.length ? "found" : "complete-miss") : "partial";
432
- return outcome(i, status, coverage, records, coverage === "unknown"
433
- ? "candidate_search_not_exhaustive"
434
- : unavailable
435
- ? "unsupported_public_shape"
436
- : beyondWindow
437
- ? "requested_end_beyond_source"
438
- : needWindow
439
- ? "need_window"
440
- : undefined);
752
+ signal?.throwIfAborted();
753
+ if (!exhausted && continuable && cursors)
754
+ cursors.save(state, version, cursorId);
755
+ else if (supplied.cursor && exhausted)
756
+ cursors?.delete(supplied.cursor);
757
+ if (!searching && !exhausted && records.length === 0)
758
+ return outcome(i, "budget_refused", "unknown", [], reason ?? "operation_budget");
759
+ return progress(exhausted);
441
760
  }
442
761
  for (let i = 0; i < operations.length; i++) {
443
762
  try {