@namzu/sdk 62.7.0 → 62.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/manager/session/turn-recorder.d.ts.map +1 -1
- package/dist/manager/session/turn-recorder.js +50 -26
- package/dist/manager/session/turn-recorder.js.map +1 -1
- package/dist/runtime/query/context.d.ts.map +1 -1
- package/dist/runtime/query/context.js +28 -2
- package/dist/runtime/query/context.js.map +1 -1
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +24 -2
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/prepare-turn.d.ts.map +1 -1
- package/dist/runtime/query/prepare-turn.js +17 -8
- package/dist/runtime/query/prepare-turn.js.map +1 -1
- package/dist/runtime/query/session-storage.d.ts.map +1 -1
- package/dist/runtime/query/session-storage.js +44 -3
- package/dist/runtime/query/session-storage.js.map +1 -1
- package/dist/sandbox/file-walk.d.ts +7 -1
- package/dist/sandbox/file-walk.d.ts.map +1 -1
- package/dist/sandbox/file-walk.js +30 -4
- package/dist/sandbox/file-walk.js.map +1 -1
- package/dist/store/checkpoint/contract.d.ts +12 -0
- package/dist/store/checkpoint/contract.d.ts.map +1 -1
- package/dist/store/checkpoint/contract.js.map +1 -1
- package/dist/store/checkpoint/disk.d.ts.map +1 -1
- package/dist/store/checkpoint/disk.js +34 -2
- package/dist/store/checkpoint/disk.js.map +1 -1
- package/dist/store/checkpoint/prune.d.ts.map +1 -1
- package/dist/store/checkpoint/prune.js +6 -0
- package/dist/store/checkpoint/prune.js.map +1 -1
- package/dist/store/evidence/disk.js +40 -3
- package/dist/store/evidence/disk.js.map +1 -1
- package/dist/store/session-log/incremental.d.ts +23 -0
- package/dist/store/session-log/incremental.d.ts.map +1 -0
- package/dist/store/session-log/incremental.js +117 -0
- package/dist/store/session-log/incremental.js.map +1 -0
- package/dist/tools/admit-facts.d.ts.map +1 -1
- package/dist/tools/admit-facts.js +1 -0
- package/dist/tools/admit-facts.js.map +1 -1
- package/dist/tools/builtins/bash.d.ts.map +1 -1
- package/dist/tools/builtins/bash.js +10 -1
- package/dist/tools/builtins/bash.js.map +1 -1
- package/dist/tools/builtins/bounded-file-read.d.ts +19 -1
- package/dist/tools/builtins/bounded-file-read.d.ts.map +1 -1
- package/dist/tools/builtins/bounded-file-read.js +56 -3
- package/dist/tools/builtins/bounded-file-read.js.map +1 -1
- package/dist/tools/builtins/grep.d.ts +8 -0
- package/dist/tools/builtins/grep.d.ts.map +1 -1
- package/dist/tools/builtins/grep.js +147 -20
- package/dist/tools/builtins/grep.js.map +1 -1
- package/dist/tools/builtins/search-support.d.ts +8 -0
- package/dist/tools/builtins/search-support.d.ts.map +1 -1
- package/dist/tools/builtins/search-support.js +66 -0
- package/dist/tools/builtins/search-support.js.map +1 -1
- package/dist/types/tool/presentation.d.ts +6 -0
- package/dist/types/tool/presentation.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/manager/session/turn-recorder.ts +53 -27
- package/src/runtime/query/context.ts +33 -6
- package/src/runtime/query/executor.ts +24 -2
- package/src/runtime/query/prepare-turn.ts +16 -8
- package/src/runtime/query/session-storage.ts +42 -3
- package/src/sandbox/file-walk.ts +41 -4
- package/src/store/checkpoint/contract.ts +15 -0
- package/src/store/checkpoint/disk.ts +34 -2
- package/src/store/checkpoint/prune.ts +13 -0
- package/src/store/evidence/disk.ts +57 -2
- package/src/store/session-log/incremental.ts +147 -0
- package/src/tools/admit-facts.ts +1 -0
- package/src/tools/builtins/bash.ts +11 -1
- package/src/tools/builtins/bounded-file-read.ts +59 -2
- package/src/tools/builtins/grep.ts +172 -26
- package/src/tools/builtins/search-support.ts +62 -0
- package/src/types/tool/presentation.ts +6 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"presentation.d.ts","sourceRoot":"","sources":["../../../src/types/tool/presentation.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"presentation.d.ts","sourceRoot":"","sources":["../../../src/types/tool/presentation.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@namzu/sdk",
|
|
3
|
-
"version": "62.
|
|
3
|
+
"version": "62.8.0",
|
|
4
4
|
"description": "Open-source AI kernel for TypeScript: runs an agent the way an operating system runs a process, with an identity and a budget, scheduled tool calls and checkpointed turns.",
|
|
5
5
|
"license": "FSL-1.1-MIT",
|
|
6
6
|
"engines": {
|
|
@@ -7,6 +7,7 @@ import { NAMZU } from '../../constants/telemetry/index.js'
|
|
|
7
7
|
import { resolveModelPricing } from '../../pricing/index.js'
|
|
8
8
|
import type { SessionTokenBudget } from '../../store/budget/index.js'
|
|
9
9
|
import type { SessionCheckpointStore } from '../../store/checkpoint/index.js'
|
|
10
|
+
import { foldSessionLog } from '../../store/session-log/incremental.js'
|
|
10
11
|
import {
|
|
11
12
|
type SessionLease,
|
|
12
13
|
type SessionLog,
|
|
@@ -1441,12 +1442,29 @@ export async function readFoldedHistory(
|
|
|
1441
1442
|
log: SessionLog,
|
|
1442
1443
|
options: { readonly throughSeq?: number } = {},
|
|
1443
1444
|
): Promise<RecordedMessage[]> {
|
|
1445
|
+
// Folding the whole log is advanced from the last record the previous call
|
|
1446
|
+
// folded, not redone from byte 0 on every turn. A fold to a seq is a
|
|
1447
|
+
// different question and is read in full.
|
|
1448
|
+
if (options.throughSeq === undefined) {
|
|
1449
|
+
return foldSessionLog(log, {
|
|
1450
|
+
name: 'folded-history',
|
|
1451
|
+
mode: 'strict',
|
|
1452
|
+
create: () => new SessionMessageFold(),
|
|
1453
|
+
apply: (fold, entry) => fold.apply(entry.record as SessionRecord),
|
|
1454
|
+
result: (fold) => recordedMessages(log, fold),
|
|
1455
|
+
})
|
|
1456
|
+
}
|
|
1444
1457
|
const fold = new SessionMessageFold()
|
|
1445
|
-
for await (const entry of log.read(
|
|
1446
|
-
options.throughSeq !== undefined ? { throughSeq: options.throughSeq } : {},
|
|
1447
|
-
)) {
|
|
1458
|
+
for await (const entry of log.read({ throughSeq: options.throughSeq })) {
|
|
1448
1459
|
fold.apply(entry.record as SessionRecord)
|
|
1449
1460
|
}
|
|
1461
|
+
return recordedMessages(log, fold)
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
async function recordedMessages(
|
|
1465
|
+
log: SessionLog,
|
|
1466
|
+
fold: SessionMessageFold,
|
|
1467
|
+
): Promise<RecordedMessage[]> {
|
|
1450
1468
|
const out: RecordedMessage[] = []
|
|
1451
1469
|
const spilled = fold.spilledSummary
|
|
1452
1470
|
if (spilled) {
|
|
@@ -1463,9 +1481,10 @@ export async function readFoldedHistory(
|
|
|
1463
1481
|
}
|
|
1464
1482
|
}
|
|
1465
1483
|
for (const entry of fold.entries()) {
|
|
1484
|
+
// A message from a shared fold is copied: the caller owns what it gets.
|
|
1466
1485
|
const message = entry.spill
|
|
1467
1486
|
? (JSON.parse(await log.readSpill(entry.spill as SpillRef)) as Message)
|
|
1468
|
-
: entry.message
|
|
1487
|
+
: structuredClone(entry.message)
|
|
1469
1488
|
out.push({
|
|
1470
1489
|
message: withMessageId(message, entry.messageId),
|
|
1471
1490
|
...(entry.messageId ? { messageId: entry.messageId } : {}),
|
|
@@ -1488,29 +1507,36 @@ export async function readFoldedHistory(
|
|
|
1488
1507
|
export async function readEverRecordedMessages(
|
|
1489
1508
|
log: SessionLog,
|
|
1490
1509
|
): Promise<ReadonlyMap<MessageId, Message>> {
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1510
|
+
return foldSessionLog(log, {
|
|
1511
|
+
name: 'ever-recorded-messages',
|
|
1512
|
+
mode: 'strict',
|
|
1513
|
+
create: () => new Map<MessageId, { readonly content: Message; readonly spill?: SpillRef }>(),
|
|
1514
|
+
apply: (latest, entry) => {
|
|
1515
|
+
const record = entry.record as SessionRecord
|
|
1516
|
+
if (record.type === 'message') {
|
|
1517
|
+
latest.set(record.messageId, {
|
|
1518
|
+
content: record.content,
|
|
1519
|
+
...(record.spill ? { spill: record.spill } : {}),
|
|
1520
|
+
})
|
|
1521
|
+
} else if (record.type === 'message_replaced') {
|
|
1522
|
+
latest.set(record.targetMessageId, {
|
|
1523
|
+
content: record.content,
|
|
1524
|
+
...(record.spill ? { spill: record.spill } : {}),
|
|
1525
|
+
})
|
|
1526
|
+
}
|
|
1527
|
+
},
|
|
1528
|
+
result: async (latest) => {
|
|
1529
|
+
const resolved = new Map<MessageId, Message>()
|
|
1530
|
+
for (const [id, entry] of latest) {
|
|
1531
|
+
// Copied: the map this is read from is shared with the next call.
|
|
1532
|
+
const content = entry.spill
|
|
1533
|
+
? (JSON.parse(await log.readSpill(entry.spill)) as Message)
|
|
1534
|
+
: structuredClone(entry.content)
|
|
1535
|
+
resolved.set(id, withMessageId(content, id))
|
|
1536
|
+
}
|
|
1537
|
+
return resolved
|
|
1538
|
+
},
|
|
1539
|
+
})
|
|
1514
1540
|
}
|
|
1515
1541
|
|
|
1516
1542
|
/**
|
|
@@ -92,6 +92,34 @@ export interface TurnContext {
|
|
|
92
92
|
trackingConfig: ActivityTrackingConfig
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Forward `signal`'s abort to `controller` without keeping the controller, or
|
|
97
|
+
* anything the turn built around it, alive for the life of `signal`.
|
|
98
|
+
*
|
|
99
|
+
* The session's signal outlives every turn, and a listener on it is strongly
|
|
100
|
+
* held for as long as the signal is. A listener that closed over the turn's
|
|
101
|
+
* controller (and the configuration it was built from) kept each finished
|
|
102
|
+
* turn's whole object graph in memory, one more per turn. This one holds the
|
|
103
|
+
* controller through a `WeakRef`, lives in its own scope so the closure
|
|
104
|
+
* captures nothing else, and is removed once the controller is collected.
|
|
105
|
+
*/
|
|
106
|
+
function relayAbort(signal: AbortSignal, controller: AbortController): void {
|
|
107
|
+
const controllerRef = new WeakRef(controller)
|
|
108
|
+
const signalRef = new WeakRef(signal)
|
|
109
|
+
const relay = (): void => {
|
|
110
|
+
controllerRef.deref()?.abort(signalRef.deref()?.reason)
|
|
111
|
+
}
|
|
112
|
+
signal.addEventListener('abort', relay, { once: true })
|
|
113
|
+
relayCleanup.register(controller, { signalRef, relay })
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const relayCleanup = new FinalizationRegistry<{
|
|
117
|
+
signalRef: WeakRef<AbortSignal>
|
|
118
|
+
relay: () => void
|
|
119
|
+
}>(({ signalRef, relay }) => {
|
|
120
|
+
signalRef.deref()?.removeEventListener('abort', relay)
|
|
121
|
+
})
|
|
122
|
+
|
|
95
123
|
export class TurnContextFactory {
|
|
96
124
|
/**
|
|
97
125
|
* The turn's one correlated logger, built once and handed to every
|
|
@@ -136,11 +164,7 @@ export class TurnContextFactory {
|
|
|
136
164
|
if (config.signal.aborted) {
|
|
137
165
|
abortController.abort(config.signal.reason)
|
|
138
166
|
} else {
|
|
139
|
-
config.signal
|
|
140
|
-
'abort',
|
|
141
|
-
() => abortController.abort(config.signal?.reason),
|
|
142
|
-
{ once: true },
|
|
143
|
-
)
|
|
167
|
+
relayAbort(config.signal, abortController)
|
|
144
168
|
}
|
|
145
169
|
}
|
|
146
170
|
|
|
@@ -178,7 +202,10 @@ export class TurnContextFactory {
|
|
|
178
202
|
config.enableActivityTracking,
|
|
179
203
|
)
|
|
180
204
|
const activityStore = new ActivityStore(config.turnId, trackingConfig)
|
|
181
|
-
const planManager = new PlanManager({
|
|
205
|
+
const planManager = new PlanManager({
|
|
206
|
+
sessionId: config.sessionId,
|
|
207
|
+
turnId: config.turnId,
|
|
208
|
+
})
|
|
182
209
|
const toolResultsDir =
|
|
183
210
|
config.storage.sessionDir === undefined
|
|
184
211
|
? undefined
|
|
@@ -705,7 +705,7 @@ export type PriorToolResults = ReadonlyMap<string, PriorToolResult>
|
|
|
705
705
|
* for administrator rights is named as that; everything else is a plain block.
|
|
706
706
|
* Read from the call's own argument, never from the refusal's wording.
|
|
707
707
|
*/
|
|
708
|
-
function refusalFacts(toolName: string, input: unknown): ToolFacts {
|
|
708
|
+
function refusalFacts(toolName: string, input: unknown, reason?: string): ToolFacts {
|
|
709
709
|
const command =
|
|
710
710
|
input && typeof input === 'object' ? (input as { command?: unknown }).command : undefined
|
|
711
711
|
const elevated = typeof command === 'string' && /(^|[\s;&|(])(sudo|doas)\b/.test(command)
|
|
@@ -713,9 +713,31 @@ function refusalFacts(toolName: string, input: unknown): ToolFacts {
|
|
|
713
713
|
kind: 'failure',
|
|
714
714
|
reason: elevated ? 'needs_administrator' : 'blocked',
|
|
715
715
|
subject: toolName,
|
|
716
|
+
...(refusalNote(reason) ? { note: refusalNote(reason) } : {}),
|
|
716
717
|
}
|
|
717
718
|
}
|
|
718
719
|
|
|
720
|
+
const REFUSAL_NOTE_MAX = 200
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* The refusal's own reason as one line for the person's view: the first
|
|
724
|
+
* non-empty line, the leading "Blocked:" label dropped, first letter up, bounded. The policy
|
|
725
|
+
* wrote it; nothing here reads the model's text.
|
|
726
|
+
*/
|
|
727
|
+
function refusalNote(reason: string | undefined): string | undefined {
|
|
728
|
+
const line = reason
|
|
729
|
+
?.split(/\r?\n/)
|
|
730
|
+
.map((part) => part.trim())
|
|
731
|
+
.find(Boolean)
|
|
732
|
+
if (!line) return undefined
|
|
733
|
+
const plain = line.replace(/^Blocked(?: by [^:]{1,60})?:\s*/i, '').trim()
|
|
734
|
+
if (!plain) return undefined
|
|
735
|
+
const sentence = `${plain.charAt(0).toUpperCase()}${plain.slice(1)}`
|
|
736
|
+
return sentence.length > REFUSAL_NOTE_MAX
|
|
737
|
+
? `${sentence.slice(0, REFUSAL_NOTE_MAX - 1)}…`
|
|
738
|
+
: sentence
|
|
739
|
+
}
|
|
740
|
+
|
|
719
741
|
export function deniedToolOutput(toolName: string, reason: string): string {
|
|
720
742
|
return `Error: Tool "${toolName}" was not executed. ${reason}`
|
|
721
743
|
}
|
|
@@ -3633,7 +3655,7 @@ export class ToolExecutor {
|
|
|
3633
3655
|
...(declined ? { presentation: this.declinedPresentation(toolName, input, declined) } : {}),
|
|
3634
3656
|
// A refusal by policy says what kind it was; the person's own No is
|
|
3635
3657
|
// already carried by the presentation above.
|
|
3636
|
-
...(declined ? {} : { facts: refusalFacts(toolName, input) }),
|
|
3658
|
+
...(declined ? {} : { facts: refusalFacts(toolName, input, reason) }),
|
|
3637
3659
|
isError: true,
|
|
3638
3660
|
})
|
|
3639
3661
|
|
|
@@ -23,6 +23,7 @@ import { withProviderRetry } from '../../provider/retry.js'
|
|
|
23
23
|
import { withTokenBudget } from '../../provider/token-budget.js'
|
|
24
24
|
import { resolveAttachments } from '../../store/attachment/index.js'
|
|
25
25
|
import type { SessionTokenBudget } from '../../store/budget/index.js'
|
|
26
|
+
import { foldSessionLog } from '../../store/session-log/incremental.js'
|
|
26
27
|
import type { SessionLog } from '../../store/session-log/index.js'
|
|
27
28
|
import { NAMZU } from '../../telemetry/attributes.js'
|
|
28
29
|
import type { SerializedSpanContext } from '../../telemetry/attributes.js'
|
|
@@ -1232,14 +1233,21 @@ async function assertTurnIdIsNew(
|
|
|
1232
1233
|
storage: SessionStorage,
|
|
1233
1234
|
): Promise<void> {
|
|
1234
1235
|
if (params.turnId === undefined) return
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1236
|
+
const exists = await foldSessionLog(storage.log, {
|
|
1237
|
+
name: 'turn-ids',
|
|
1238
|
+
mode: 'tolerant',
|
|
1239
|
+
create: () => new Set<string>(),
|
|
1240
|
+
apply: (ids, { record }) => {
|
|
1241
|
+
if (record.type === 'turn_started') ids.add(record.turnId)
|
|
1242
|
+
},
|
|
1243
|
+
result: (ids) => ids.has(turnId),
|
|
1244
|
+
})
|
|
1245
|
+
if (exists) {
|
|
1246
|
+
throw new NamzuError({
|
|
1247
|
+
code: 'invalid_config',
|
|
1248
|
+
message: `Turn ${turnId} already exists in session ${params.sessionId}. A new turn needs a new id; to continue that turn, pass resumeFromCheckpoint.`,
|
|
1249
|
+
details: { sessionId: params.sessionId, turnId },
|
|
1250
|
+
})
|
|
1243
1251
|
}
|
|
1244
1252
|
}
|
|
1245
1253
|
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
InMemorySessionCheckpointStore,
|
|
15
15
|
type SessionCheckpointStore,
|
|
16
16
|
} from '../../store/checkpoint/index.js'
|
|
17
|
+
import { foldSessionLog } from '../../store/session-log/incremental.js'
|
|
17
18
|
import {
|
|
18
19
|
DiskSessionLog,
|
|
19
20
|
type InMemoryLogMedium,
|
|
@@ -175,7 +176,10 @@ export async function locateSession(
|
|
|
175
176
|
const parent = (await findSessionLocator(paths, parentSessionId)) ?? {
|
|
176
177
|
sessionId: parentSessionId,
|
|
177
178
|
}
|
|
178
|
-
return {
|
|
179
|
+
return {
|
|
180
|
+
sessionId,
|
|
181
|
+
ancestors: [...(parent.ancestors ?? []), parent.sessionId],
|
|
182
|
+
}
|
|
179
183
|
}
|
|
180
184
|
|
|
181
185
|
/** Deeper than any delegation tree a project allows; a guard against a cyclic tree on disk. */
|
|
@@ -234,7 +238,10 @@ async function listDirectory(
|
|
|
234
238
|
/** `SessionPaths` for the working directory's project under `resolveNamzuHome()`. */
|
|
235
239
|
export async function defaultSessionPaths(workingDirectory?: string): Promise<SessionPaths> {
|
|
236
240
|
const home = resolveNamzuHome()
|
|
237
|
-
const project = await ensureProject({
|
|
241
|
+
const project = await ensureProject({
|
|
242
|
+
home,
|
|
243
|
+
cwd: workingDirectory ?? process.cwd(),
|
|
244
|
+
})
|
|
238
245
|
return new SessionPaths({ home, slug: project.slug })
|
|
239
246
|
}
|
|
240
247
|
|
|
@@ -242,7 +249,9 @@ function heldState(log: InMemorySessionLog): HeldState {
|
|
|
242
249
|
const current = held.get(log.medium)
|
|
243
250
|
if (current) return current
|
|
244
251
|
const next: HeldState = {
|
|
245
|
-
checkpoints: new InMemorySessionCheckpointStore({
|
|
252
|
+
checkpoints: new InMemorySessionCheckpointStore({
|
|
253
|
+
log: checkpointLogView(log),
|
|
254
|
+
}),
|
|
246
255
|
tokenBudgets: new InMemorySessionTokenBudgetStore(),
|
|
247
256
|
}
|
|
248
257
|
held.set(log.medium, next)
|
|
@@ -303,5 +312,35 @@ export function checkpointLogView(log: SessionLog): CheckpointLogView {
|
|
|
303
312
|
}
|
|
304
313
|
return open.values()
|
|
305
314
|
},
|
|
315
|
+
async pruneFacts(scope, checkpointIds) {
|
|
316
|
+
own(scope)
|
|
317
|
+
// Every committed checkpoint and every open decision, advanced from the
|
|
318
|
+
// last record the previous prune folded rather than read from byte 0:
|
|
319
|
+
// a prune runs every iteration of every turn.
|
|
320
|
+
return foldSessionLog(log, {
|
|
321
|
+
name: 'prune-facts',
|
|
322
|
+
mode: 'tolerant',
|
|
323
|
+
create: () => ({
|
|
324
|
+
written: new Map<CheckpointId, string>(),
|
|
325
|
+
open: new Map<string, CheckpointId>(),
|
|
326
|
+
}),
|
|
327
|
+
apply: ({ written, open }, { record }) => {
|
|
328
|
+
if (record.type === 'checkpoint_written')
|
|
329
|
+
written.set(record.checkpointId, record.docSha256)
|
|
330
|
+
if (record.type === 'decision_requested') open.set(record.decisionId, record.checkpointId)
|
|
331
|
+
if (record.type === 'decision_resolved' || record.type === 'decision_expired') {
|
|
332
|
+
open.delete(record.decisionId)
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
result: ({ written, open }) => {
|
|
336
|
+
const wanted = new Map<CheckpointId, string>()
|
|
337
|
+
for (const id of checkpointIds) {
|
|
338
|
+
const sha256 = written.get(id)
|
|
339
|
+
if (sha256 !== undefined) wanted.set(id, sha256)
|
|
340
|
+
}
|
|
341
|
+
return { written: wanted, open: [...open.values()] }
|
|
342
|
+
},
|
|
343
|
+
})
|
|
344
|
+
},
|
|
306
345
|
}
|
|
307
346
|
}
|
package/src/sandbox/file-walk.ts
CHANGED
|
@@ -15,6 +15,9 @@ import { VCS_NAMES, createIgnoreStack } from './ignore-matcher.js'
|
|
|
15
15
|
/** Entries a walk examines before it stops with what it has: a safety valve, not a budget. */
|
|
16
16
|
export const DEFAULT_MAX_VISITED_ENTRIES = 500_000
|
|
17
17
|
|
|
18
|
+
/** Longest the walk holds the event loop between yields. */
|
|
19
|
+
const WALK_SLICE_MS = 8
|
|
20
|
+
|
|
18
21
|
const GLOBSTAR = Symbol('globstar')
|
|
19
22
|
type Segment = string | { source: string; flags: string } | typeof GLOBSTAR
|
|
20
23
|
|
|
@@ -185,6 +188,7 @@ async function* executeWalk(
|
|
|
185
188
|
plan: WalkPlan,
|
|
186
189
|
signal?: AbortSignal,
|
|
187
190
|
onSummary?: (summary: SandboxWalkSummary) => void,
|
|
191
|
+
withoutSizes = false,
|
|
188
192
|
): AsyncGenerator<SandboxFileEntry> {
|
|
189
193
|
const fs = await import('node:fs/promises')
|
|
190
194
|
const path = await import('node:path')
|
|
@@ -196,6 +200,7 @@ async function* executeWalk(
|
|
|
196
200
|
/** Whether leaving this directory also drops its ignore layer. */
|
|
197
201
|
layered: boolean
|
|
198
202
|
}[] = []
|
|
203
|
+
let sliceStart = performance.now()
|
|
199
204
|
let visited = 0
|
|
200
205
|
let emitted = 0
|
|
201
206
|
let ignoredDirectories = 0
|
|
@@ -327,8 +332,22 @@ async function* executeWalk(
|
|
|
327
332
|
while (open.length > 0) {
|
|
328
333
|
check()
|
|
329
334
|
const frame = open[open.length - 1] as (typeof open)[number]
|
|
330
|
-
|
|
331
|
-
|
|
335
|
+
let entry: import('node:fs').Dirent | null
|
|
336
|
+
if (withoutSizes) {
|
|
337
|
+
// A local search consumer reads a directory's buffered entries
|
|
338
|
+
// synchronously: one promise per entry costs more than the listing.
|
|
339
|
+
// The loop yields on a time slice, and a blocked read cannot be
|
|
340
|
+
// raced, which only the default path promises.
|
|
341
|
+
if (performance.now() - sliceStart > WALK_SLICE_MS) {
|
|
342
|
+
await new Promise<void>((done) => setImmediate(done))
|
|
343
|
+
sliceStart = performance.now()
|
|
344
|
+
}
|
|
345
|
+
check()
|
|
346
|
+
entry = frame.dir.readSync()
|
|
347
|
+
} else {
|
|
348
|
+
entry = await wait(frame.dir.read())
|
|
349
|
+
check()
|
|
350
|
+
}
|
|
332
351
|
if (!entry) {
|
|
333
352
|
open.pop()
|
|
334
353
|
if (frame.layered) ignore?.leave()
|
|
@@ -351,6 +370,13 @@ async function* executeWalk(
|
|
|
351
370
|
}
|
|
352
371
|
if (!entry.isFile() || !matchers.some((matcher) => matcher.test(relative))) continue
|
|
353
372
|
if (ignore?.isIgnored(absolute, false)) continue
|
|
373
|
+
if (withoutSizes) {
|
|
374
|
+
// The caller opens the file anyway and reads its size from the handle.
|
|
375
|
+
emitted += 1
|
|
376
|
+
yield { path: absolute, size: 0 }
|
|
377
|
+
if (emitted >= plan.maxEntries) return
|
|
378
|
+
continue
|
|
379
|
+
}
|
|
354
380
|
let info: import('node:fs').Stats
|
|
355
381
|
try {
|
|
356
382
|
info = await wait(fs.lstat(absolute))
|
|
@@ -384,10 +410,21 @@ function globalIgnoreFiles(): string[] {
|
|
|
384
410
|
/** Internal host path; the caller first resolves its authorized filesystem root. */
|
|
385
411
|
export async function* walkFilesLocally(
|
|
386
412
|
rootPath: string,
|
|
387
|
-
options: SandboxWalkFilesOptions
|
|
413
|
+
options: SandboxWalkFilesOptions & {
|
|
414
|
+
/**
|
|
415
|
+
* Skip the per-file size read: entries carry `size: 0`. For a caller that
|
|
416
|
+
* opens every file itself and takes the size from the handle.
|
|
417
|
+
*/
|
|
418
|
+
readonly withoutSizes?: boolean
|
|
419
|
+
},
|
|
388
420
|
): AsyncGenerator<SandboxFileEntry> {
|
|
389
421
|
options.signal?.throwIfAborted()
|
|
390
|
-
yield* executeWalk(
|
|
422
|
+
yield* executeWalk(
|
|
423
|
+
planWalk(rootPath, options),
|
|
424
|
+
options.signal,
|
|
425
|
+
options.onSummary,
|
|
426
|
+
options.withoutSizes === true,
|
|
427
|
+
)
|
|
391
428
|
}
|
|
392
429
|
|
|
393
430
|
/** The existing sandbox execution seam; no separate worker protocol is needed. */
|
|
@@ -62,6 +62,21 @@ export interface CheckpointLogView {
|
|
|
62
62
|
* `decision_resolved` or `decision_expired` yet. `prune` never deletes one.
|
|
63
63
|
*/
|
|
64
64
|
openDecisionCheckpoints(scope: CheckpointScope): Promise<Iterable<CheckpointId>>
|
|
65
|
+
/**
|
|
66
|
+
* Both answers `prune` needs in one read of the log: for each of
|
|
67
|
+
* `checkpointIds`, what `writtenDocSha256` would return (absent from
|
|
68
|
+
* `written` when there is no record), and what `openDecisionCheckpoints`
|
|
69
|
+
* would return. Optional: a view without it is asked the two questions
|
|
70
|
+
* separately, one walk of the log each, which costs a walk per stored
|
|
71
|
+
* checkpoint on every prune.
|
|
72
|
+
*/
|
|
73
|
+
pruneFacts?(
|
|
74
|
+
scope: CheckpointScope,
|
|
75
|
+
checkpointIds: readonly CheckpointId[],
|
|
76
|
+
): Promise<{
|
|
77
|
+
readonly written: ReadonlyMap<CheckpointId, string>
|
|
78
|
+
readonly open: Iterable<CheckpointId>
|
|
79
|
+
}>
|
|
65
80
|
}
|
|
66
81
|
|
|
67
82
|
/** Why a restore refused a checkpoint. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { link, mkdir, open, readFile, readdir, unlink } from 'node:fs/promises'
|
|
1
|
+
import { link, mkdir, open, readFile, readdir, stat, unlink } from 'node:fs/promises'
|
|
2
2
|
import type { FileHandle } from 'node:fs/promises'
|
|
3
3
|
import { dirname } from 'node:path'
|
|
4
4
|
import type { SessionLocator, SessionPaths } from '../../session/paths.js'
|
|
@@ -38,6 +38,17 @@ export interface DiskSessionCheckpointStoreOptions {
|
|
|
38
38
|
|
|
39
39
|
const DOCUMENT = /^(.+)\.json$/
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Which turn a checkpoint document on disk belongs to, as last read, with the
|
|
43
|
+
* size and mtime it had then. A session keeps its checkpoints for every turn
|
|
44
|
+
* in one directory, so listing one turn's used to read and parse every
|
|
45
|
+
* document of the session, and the directory only grows. A document is not
|
|
46
|
+
* read again while a `stat` says it is the one that was; one that belonged to
|
|
47
|
+
* another turn is skipped on that evidence alone.
|
|
48
|
+
*/
|
|
49
|
+
const documentTurns = new Map<string, { turnId: string; size: number; mtimeMs: number }>()
|
|
50
|
+
const DOCUMENT_TURNS_KEPT = 20_000
|
|
51
|
+
|
|
41
52
|
/**
|
|
42
53
|
* {@link SessionCheckpointStore} at `<session-dir>/checkpoints/<id>.json`.
|
|
43
54
|
*
|
|
@@ -145,10 +156,31 @@ export class DiskSessionCheckpointStore implements SessionCheckpointStore {
|
|
|
145
156
|
// Sidecars (`<id>.json.<pid>.<n>.<hex>.tmp`) and strangers are not documents.
|
|
146
157
|
const id = DOCUMENT.exec(name)?.[1]
|
|
147
158
|
if (id === undefined || !isEntityId(id, 'checkpoint')) continue
|
|
148
|
-
const
|
|
159
|
+
const path = this.#paths.checkpointFile(locator, id)
|
|
160
|
+
const seen = await stat(path).catch((error: NodeJS.ErrnoException) => {
|
|
161
|
+
if (error.code === 'ENOENT') return null
|
|
162
|
+
throw error
|
|
163
|
+
})
|
|
149
164
|
// Deleted between the listing and the read: a concurrent prune, not damage.
|
|
165
|
+
if (seen === null) continue
|
|
166
|
+
const known = documentTurns.get(path)
|
|
167
|
+
if (
|
|
168
|
+
known !== undefined &&
|
|
169
|
+
known.turnId !== checked.turnId &&
|
|
170
|
+
known.size === seen.size &&
|
|
171
|
+
known.mtimeMs === seen.mtimeMs
|
|
172
|
+
) {
|
|
173
|
+
continue
|
|
174
|
+
}
|
|
175
|
+
const text = await readIfPresent(path)
|
|
150
176
|
if (text === null) continue
|
|
151
177
|
const checkpoint = parseStoredCheckpoint(text, checked, id)
|
|
178
|
+
if (documentTurns.size >= DOCUMENT_TURNS_KEPT) documentTurns.clear()
|
|
179
|
+
documentTurns.set(path, {
|
|
180
|
+
turnId: checkpoint.turnId,
|
|
181
|
+
size: seen.size,
|
|
182
|
+
mtimeMs: seen.mtimeMs,
|
|
183
|
+
})
|
|
152
184
|
if (checkpoint.turnId === checked.turnId) checkpoints.push(checkpoint)
|
|
153
185
|
}
|
|
154
186
|
return checkpoints.sort(compareCheckpoints)
|
|
@@ -79,6 +79,19 @@ export async function selectStoredCheckpointsToPrune(
|
|
|
79
79
|
if (!Number.isSafeInteger(keepLast) || keepLast < 0) {
|
|
80
80
|
throw new RangeError(`keepLast must be a nonnegative integer, got ${String(keepLast)}`)
|
|
81
81
|
}
|
|
82
|
+
if (log.pruneFacts) {
|
|
83
|
+
// One read of the log answers both questions for every stored
|
|
84
|
+
// checkpoint, instead of one full walk each.
|
|
85
|
+
const facts = await log.pruneFacts(
|
|
86
|
+
scope,
|
|
87
|
+
stored.map((checkpoint) => checkpoint.checkpointId),
|
|
88
|
+
)
|
|
89
|
+
return selectSessionCheckpointsToPrune(
|
|
90
|
+
stored.filter((checkpoint) => facts.written.has(checkpoint.checkpointId)),
|
|
91
|
+
keepLast,
|
|
92
|
+
new Set(facts.open),
|
|
93
|
+
)
|
|
94
|
+
}
|
|
82
95
|
const committed: Checkpoint[] = []
|
|
83
96
|
for (const checkpoint of stored) {
|
|
84
97
|
if ((await log.writtenDocSha256(scope, checkpoint.checkpointId)) !== null) {
|
|
@@ -157,22 +157,64 @@ export function createSessionEvidenceSource(
|
|
|
157
157
|
})
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
/** What a scan of a session log's complete records, from byte 0 up to `consumed`, found. */
|
|
161
|
+
interface SessionScan {
|
|
162
|
+
readonly identity: string
|
|
163
|
+
/** Offset just past the last complete record scanned. */
|
|
164
|
+
readonly consumed: number
|
|
165
|
+
readonly first: Buffer
|
|
166
|
+
readonly active: string | undefined
|
|
167
|
+
readonly turnClosed: boolean
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Scans already done, by log file and scope, so that an access reads only the
|
|
172
|
+
* records appended since the last one instead of parsing the whole log again.
|
|
173
|
+
* A session log only grows, and every search page makes several accesses.
|
|
174
|
+
*/
|
|
175
|
+
const SCAN_CACHE_LIMIT = 64
|
|
176
|
+
const scanCache = new Map<string, SessionScan>()
|
|
177
|
+
|
|
178
|
+
function remember(key: string, scan: SessionScan): void {
|
|
179
|
+
scanCache.delete(key)
|
|
180
|
+
scanCache.set(key, scan)
|
|
181
|
+
if (scanCache.size > SCAN_CACHE_LIMIT) {
|
|
182
|
+
const oldest = scanCache.keys().next().value
|
|
183
|
+
if (oldest !== undefined) scanCache.delete(oldest)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
160
187
|
/**
|
|
161
188
|
* The first record of a session log (its `session_started`) and whether the
|
|
162
189
|
* scope is closed: no turn of the session active, or — for a scope narrowed
|
|
163
190
|
* to one turn — that turn settled.
|
|
191
|
+
*
|
|
192
|
+
* The scan resumes from the one a previous access left in `scanCache` when it
|
|
193
|
+
* is of the same file (same identity, first record unchanged on disk) and
|
|
194
|
+
* ended at or before `size`; otherwise it starts again from byte 0.
|
|
164
195
|
*/
|
|
165
196
|
async function sessionState(
|
|
166
197
|
handle: FileHandle,
|
|
167
198
|
size: number,
|
|
168
199
|
scope: z.infer<typeof scopeSchema>,
|
|
200
|
+
cacheKey: string,
|
|
201
|
+
identity: string,
|
|
169
202
|
): Promise<{ first: Buffer; closed: boolean }> {
|
|
170
203
|
const unmetered: EvidenceBudget = { bytes: 0, limit: Number.MAX_SAFE_INTEGER }
|
|
171
204
|
let first: Buffer | undefined
|
|
172
205
|
let active: string | undefined
|
|
173
206
|
let turnClosed = false
|
|
174
|
-
let buffered = Buffer.alloc(0)
|
|
175
207
|
let offset = 0
|
|
208
|
+
const cached = scanCache.get(cacheKey)
|
|
209
|
+
if (cached && cached.identity === identity && cached.consumed <= size) {
|
|
210
|
+
const head = await readBytes(handle, 0, cached.first.length, unmetered)
|
|
211
|
+
if (head.equals(cached.first)) {
|
|
212
|
+
;({ first, active, turnClosed } = cached)
|
|
213
|
+
offset = cached.consumed
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
let consumed = offset
|
|
217
|
+
let buffered = Buffer.alloc(0)
|
|
176
218
|
while (offset < size || buffered.length > 0) {
|
|
177
219
|
let newline = buffered.indexOf(10)
|
|
178
220
|
if (newline < 0) {
|
|
@@ -186,6 +228,7 @@ async function sessionState(
|
|
|
186
228
|
const raw = buffered.subarray(0, newline + 1)
|
|
187
229
|
buffered = buffered.subarray(newline + 1)
|
|
188
230
|
newline = -1
|
|
231
|
+
consumed += raw.length
|
|
189
232
|
const record = JSON.parse(decode(raw)) as Record<string, unknown>
|
|
190
233
|
if (!first) {
|
|
191
234
|
if (
|
|
@@ -206,6 +249,12 @@ async function sessionState(
|
|
|
206
249
|
}
|
|
207
250
|
}
|
|
208
251
|
if (!first) throw new Error('The session log is empty; evidence is incomplete.')
|
|
252
|
+
// A source anchored earlier in the log scans less than another access has;
|
|
253
|
+
// keep the longer scan for the next one.
|
|
254
|
+
const kept = scanCache.get(cacheKey)
|
|
255
|
+
if (!kept || kept.identity !== identity || kept.consumed <= consumed) {
|
|
256
|
+
remember(cacheKey, { identity, consumed, first, active, turnClosed })
|
|
257
|
+
}
|
|
209
258
|
return { first, closed: scope.turnId !== undefined ? turnClosed : active === undefined }
|
|
210
259
|
}
|
|
211
260
|
|
|
@@ -258,7 +307,13 @@ function createSource(
|
|
|
258
307
|
? await completePrefix(handle, before.size, budget)
|
|
259
308
|
: before.size
|
|
260
309
|
}
|
|
261
|
-
const state = await sessionState(
|
|
310
|
+
const state = await sessionState(
|
|
311
|
+
handle,
|
|
312
|
+
size,
|
|
313
|
+
scope,
|
|
314
|
+
`${logPath}\0${scopeKey}`,
|
|
315
|
+
fileIdentity(before),
|
|
316
|
+
)
|
|
262
317
|
if (!state.closed && consistency === 'closed') {
|
|
263
318
|
throw new Error('Evidence scope has an active turn; it is not closed.')
|
|
264
319
|
}
|