@ponythewhite/base-context 1.0.7 → 1.0.9
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/CHANGELOG.md +24 -0
- package/dist/base-context-runtime/pyproject.toml +1 -1
- package/dist/base-context-runtime/src/rlm/__init__.py +3 -0
- package/dist/base-context-runtime/src/rlm/harness.py +86 -48
- package/dist/base-context-runtime/test/test_harness.py +215 -140
- package/dist/base-context-runtime/uv.lock +1 -1
- package/dist/build-info.json +1 -1
- package/dist/bundle/amazon-bedrock.js +3 -3
- package/dist/bundle/{anthropic-UV5GZDGU.js → anthropic-5PSCZEER.js} +2 -7
- package/dist/bundle/{azure-openai-responses-WSNPXLYC.js → azure-openai-responses-AYTLJR33.js} +2 -2
- package/dist/bundle/{bundled-modules-PS66L3IT.js → bundled-modules-5JRM6P4T.js} +5 -5
- package/dist/bundle/{chunk-UPVDERMD.js → chunk-2IPDSTDR.js} +11 -9
- package/dist/bundle/chunk-D3KOF57W.js +22040 -0
- package/dist/bundle/{chunk-VSIYFWYI.js → chunk-KT2BNEPE.js} +21 -8
- package/dist/bundle/{chunk-VCW3YDFF.js → chunk-NEHLB7JV.js} +1548 -574
- package/dist/bundle/{chunk-KTNETTHI.js → chunk-Q3MBSBEB.js} +3 -101
- package/dist/bundle/{chunk-LFPWHI42.js → chunk-QKIFBA7N.js} +2 -2
- package/dist/bundle/{chunk-PFROC4EV.js → chunk-VVI3UKGR.js} +12 -9
- package/dist/bundle/{cli-main-MMHTK7EB.js → cli-main-YUDBEYE2.js} +4 -4
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/{google-T32FMD4G.js → google-HTMCUJ7X.js} +2 -2
- package/dist/bundle/{google-vertex-56KP6I3B.js → google-vertex-ZQO65AXF.js} +2 -2
- package/dist/bundle/{main-XAIVH4Y7.js → main-ZLGGAAGU.js} +5 -5
- package/dist/bundle/{mistral-WGBMYIVU.js → mistral-SB7LU56G.js} +1 -1
- package/dist/bundle/{openai-codex-responses-PIV2RQ3G.js → openai-codex-responses-5CEW4VN3.js} +53 -27
- package/dist/bundle/{openai-completions-64C3JPR4.js → openai-completions-BAZJQSS2.js} +1 -1
- package/dist/bundle/{openai-responses-UI7RSTDV.js → openai-responses-VHOE427P.js} +48 -5
- package/dist/core/agent-session.d.ts +9 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +225 -29
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/canonical-context.d.ts +8 -0
- package/dist/core/canonical-context.d.ts.map +1 -1
- package/dist/core/canonical-context.js +37 -5
- package/dist/core/canonical-context.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +2 -2
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +31 -13
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/context-epoch.d.ts +1 -1
- package/dist/core/context-epoch.d.ts.map +1 -1
- package/dist/core/context-epoch.js +2 -1
- package/dist/core/context-epoch.js.map +1 -1
- package/dist/core/extensions/types.d.ts +2 -2
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/history-index-worker.d.ts.map +1 -1
- package/dist/core/history-index-worker.js +29 -8
- package/dist/core/history-index-worker.js.map +1 -1
- package/dist/core/history-index.d.ts +11 -1
- package/dist/core/history-index.d.ts.map +1 -1
- package/dist/core/history-index.js +2 -1
- package/dist/core/history-index.js.map +1 -1
- package/dist/core/inference-coordinator.d.ts +1 -1
- package/dist/core/inference-coordinator.d.ts.map +1 -1
- package/dist/core/inference-coordinator.js +110 -15
- package/dist/core/inference-coordinator.js.map +1 -1
- package/dist/core/kernel/bootstrap.d.ts.map +1 -1
- package/dist/core/kernel/bootstrap.js +97 -107
- package/dist/core/kernel/bootstrap.js.map +1 -1
- package/dist/core/kernel/repl-manager.d.ts.map +1 -1
- package/dist/core/kernel/repl-manager.js +11 -0
- package/dist/core/kernel/repl-manager.js.map +1 -1
- package/dist/core/kernel/shared.d.ts +2 -0
- package/dist/core/kernel/shared.d.ts.map +1 -1
- package/dist/core/kernel/shared.js.map +1 -1
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +4 -1
- package/dist/core/messages.js.map +1 -1
- package/dist/core/prompts/rlm.d.ts.map +1 -1
- package/dist/core/prompts/rlm.js +4 -2
- package/dist/core/prompts/rlm.js.map +1 -1
- package/dist/core/public-context.d.ts.map +1 -1
- package/dist/core/public-context.js +16 -0
- package/dist/core/public-context.js.map +1 -1
- package/dist/core/refinement/refinement.d.ts +19 -5
- package/dist/core/refinement/refinement.d.ts.map +1 -1
- package/dist/core/refinement/refinement.js +90 -35
- package/dist/core/refinement/refinement.js.map +1 -1
- package/dist/core/request-view-selection.d.ts +13 -3
- package/dist/core/request-view-selection.d.ts.map +1 -1
- package/dist/core/request-view-selection.js +239 -101
- package/dist/core/request-view-selection.js.map +1 -1
- package/dist/core/retained-tool-output.d.ts +28 -0
- package/dist/core/retained-tool-output.d.ts.map +1 -0
- package/dist/core/retained-tool-output.js +102 -0
- package/dist/core/retained-tool-output.js.map +1 -0
- package/dist/core/selective-recovery.d.ts +47 -3
- package/dist/core/selective-recovery.d.ts.map +1 -1
- package/dist/core/selective-recovery.js +428 -109
- package/dist/core/selective-recovery.js.map +1 -1
- package/dist/core/session-history-index.d.ts +3 -1
- package/dist/core/session-history-index.d.ts.map +1 -1
- package/dist/core/session-history-index.js +1 -1
- package/dist/core/session-history-index.js.map +1 -1
- package/dist/core/session-manager.d.ts +2 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +38 -13
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +5 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +3 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts +3 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +3 -3
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/task-frame.d.ts +1 -1
- package/dist/core/task-frame.d.ts.map +1 -1
- package/dist/core/task-frame.js +21 -1
- package/dist/core/task-frame.js.map +1 -1
- package/dist/core/task-state-reader.d.ts +10 -2
- package/dist/core/task-state-reader.d.ts.map +1 -1
- package/dist/core/task-state-reader.js +75 -9
- package/dist/core/task-state-reader.js.map +1 -1
- package/dist/core/tools/ipython.d.ts +5 -0
- package/dist/core/tools/ipython.d.ts.map +1 -1
- package/dist/core/tools/ipython.js +59 -17
- package/dist/core/tools/ipython.js.map +1 -1
- package/dist/core/tools/prime-context.d.ts.map +1 -1
- package/dist/core/tools/prime-context.js +1 -1
- package/dist/core/tools/prime-context.js.map +1 -1
- package/dist/core/view-units.d.ts +4 -0
- package/dist/core/view-units.d.ts.map +1 -1
- package/dist/core/view-units.js +28 -19
- package/dist/core/view-units.js.map +1 -1
- package/dist/installer.mjs +98 -77
- package/dist/modes/interactive/components/refinement-outcome-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/refinement-outcome-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +5 -5
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/skills/skill-creator/SKILL.md +18 -1
- package/docs/settings.md +45 -0
- package/docs/skills.md +77 -2
- package/package.json +4 -4
- package/skills/skill-creator/SKILL.md +18 -1
- 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 (
|
|
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,
|
|
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
|
-
|
|
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(
|
|
336
|
-
|
|
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
|
-
|
|
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
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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
|
|
381
|
-
let
|
|
382
|
-
let
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|
-
|
|
390
|
-
|
|
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
|
-
|
|
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
|
-
|
|
409
|
-
|
|
568
|
+
if (field.unavailable) {
|
|
569
|
+
state.unavailable = true;
|
|
570
|
+
reason = "retained_output_unavailable";
|
|
410
571
|
continue;
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
if (
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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 {
|