@namzu/sdk 53.2.2 → 55.0.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 +56 -0
- package/dist/agents/QueryAgent.d.ts.map +1 -1
- package/dist/agents/QueryAgent.js +1 -0
- package/dist/agents/QueryAgent.js.map +1 -1
- package/dist/compaction/context-breakdown.d.ts +85 -0
- package/dist/compaction/context-breakdown.d.ts.map +1 -0
- package/dist/compaction/context-breakdown.js +149 -0
- package/dist/compaction/context-breakdown.js.map +1 -0
- package/dist/compaction/extractor.d.ts.map +1 -1
- package/dist/compaction/extractor.js +5 -1
- package/dist/compaction/extractor.js.map +1 -1
- package/dist/compaction/managers/structured.d.ts.map +1 -1
- package/dist/compaction/managers/structured.js +5 -0
- package/dist/compaction/managers/structured.js.map +1 -1
- package/dist/compaction/manual.d.ts.map +1 -1
- package/dist/compaction/manual.js +6 -0
- package/dist/compaction/manual.js.map +1 -1
- package/dist/compaction/serializer.d.ts +6 -0
- package/dist/compaction/serializer.d.ts.map +1 -1
- package/dist/compaction/serializer.js +21 -3
- package/dist/compaction/serializer.js.map +1 -1
- package/dist/compaction/token-estimate.d.ts +2 -0
- package/dist/compaction/token-estimate.d.ts.map +1 -1
- package/dist/compaction/token-estimate.js +4 -0
- package/dist/compaction/token-estimate.js.map +1 -1
- package/dist/compaction/verifier.d.ts.map +1 -1
- package/dist/compaction/verifier.js +27 -6
- package/dist/compaction/verifier.js.map +1 -1
- package/dist/constants/compaction/index.d.ts +1 -0
- package/dist/constants/compaction/index.d.ts.map +1 -1
- package/dist/constants/compaction/index.js +1 -0
- package/dist/constants/compaction/index.js.map +1 -1
- package/dist/manager/resident/proposal.d.ts +2 -2
- package/dist/manager/resident/store.d.ts +4 -4
- package/dist/prompt/coding-agent-doctrine.d.ts +3 -3
- package/dist/prompt/coding-agent-doctrine.d.ts.map +1 -1
- package/dist/prompt/coding-agent-doctrine.js +18 -20
- package/dist/prompt/coding-agent-doctrine.js.map +1 -1
- package/dist/public-runtime.d.ts +8 -3
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +7 -2
- package/dist/public-runtime.js.map +1 -1
- package/dist/runtime/cache-clock.d.ts +39 -0
- package/dist/runtime/cache-clock.d.ts.map +1 -0
- package/dist/runtime/cache-clock.js +48 -0
- package/dist/runtime/cache-clock.js.map +1 -0
- package/dist/runtime/query/checkpoint.d.ts +12 -2
- package/dist/runtime/query/checkpoint.d.ts.map +1 -1
- package/dist/runtime/query/checkpoint.js +42 -8
- package/dist/runtime/query/checkpoint.js.map +1 -1
- package/dist/runtime/query/compact-session.d.ts +37 -2
- package/dist/runtime/query/compact-session.d.ts.map +1 -1
- package/dist/runtime/query/compact-session.js +77 -3
- package/dist/runtime/query/compact-session.js.map +1 -1
- package/dist/runtime/query/executor/tool-call-admission.d.ts.map +1 -1
- package/dist/runtime/query/executor/tool-call-admission.js +3 -0
- package/dist/runtime/query/executor/tool-call-admission.js.map +1 -1
- package/dist/runtime/query/executor.d.ts +39 -1
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +115 -7
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/index.d.ts +18 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +20 -1
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +25 -8
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context.d.ts +24 -2
- package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/context.js +22 -22
- package/dist/runtime/query/iteration/phases/context.js.map +1 -1
- package/dist/runtime/query/iteration/phases/per-call-review.d.ts +78 -0
- package/dist/runtime/query/iteration/phases/per-call-review.d.ts.map +1 -0
- package/dist/runtime/query/iteration/phases/per-call-review.js +494 -0
- package/dist/runtime/query/iteration/phases/per-call-review.js.map +1 -0
- package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.js +129 -62
- package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
- package/dist/runtime/query/iteration/step-context.d.ts +14 -0
- package/dist/runtime/query/iteration/step-context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/step-context.js +29 -0
- package/dist/runtime/query/iteration/step-context.js.map +1 -1
- package/dist/runtime/query/iteration/step-shaping.d.ts.map +1 -1
- package/dist/runtime/query/iteration/step-shaping.js +6 -2
- package/dist/runtime/query/iteration/step-shaping.js.map +1 -1
- package/dist/runtime/query/prepare-turn.d.ts.map +1 -1
- package/dist/runtime/query/prepare-turn.js +16 -3
- package/dist/runtime/query/prepare-turn.js.map +1 -1
- package/dist/runtime/query/project-instructions.d.ts +24 -5
- package/dist/runtime/query/project-instructions.d.ts.map +1 -1
- package/dist/runtime/query/project-instructions.js +107 -35
- package/dist/runtime/query/project-instructions.js.map +1 -1
- package/dist/runtime/query/prompt.d.ts +6 -0
- package/dist/runtime/query/prompt.d.ts.map +1 -1
- package/dist/runtime/query/prompt.js +10 -49
- package/dist/runtime/query/prompt.js.map +1 -1
- package/dist/runtime/query/resume-pending.d.ts +8 -0
- package/dist/runtime/query/resume-pending.d.ts.map +1 -1
- package/dist/runtime/query/resume-pending.js +101 -0
- package/dist/runtime/query/resume-pending.js.map +1 -1
- package/dist/runtime/query/review-policy.d.ts +18 -0
- package/dist/runtime/query/review-policy.d.ts.map +1 -1
- package/dist/runtime/query/review-policy.js +10 -0
- package/dist/runtime/query/review-policy.js.map +1 -1
- package/dist/runtime/query/steering.d.ts +55 -56
- package/dist/runtime/query/steering.d.ts.map +1 -1
- package/dist/runtime/query/steering.js +78 -72
- package/dist/runtime/query/steering.js.map +1 -1
- package/dist/store/budget/disk.js +4 -1
- package/dist/store/budget/disk.js.map +1 -1
- package/dist/tools/admit-facts.d.ts.map +1 -1
- package/dist/tools/admit-facts.js +6 -0
- package/dist/tools/admit-facts.js.map +1 -1
- package/dist/tools/builtins/bash.d.ts +8 -0
- package/dist/tools/builtins/bash.d.ts.map +1 -1
- package/dist/tools/builtins/bash.js +36 -27
- package/dist/tools/builtins/bash.js.map +1 -1
- package/dist/tools/builtins/browser.d.ts.map +1 -1
- package/dist/tools/builtins/browser.js +38 -12
- package/dist/tools/builtins/browser.js.map +1 -1
- package/dist/tools/builtins/edit-apply.d.ts +7 -4
- package/dist/tools/builtins/edit-apply.d.ts.map +1 -1
- package/dist/tools/builtins/edit-apply.js +3 -0
- package/dist/tools/builtins/edit-apply.js.map +1 -1
- package/dist/tools/builtins/edit.d.ts.map +1 -1
- package/dist/tools/builtins/edit.js +31 -11
- package/dist/tools/builtins/edit.js.map +1 -1
- package/dist/tools/builtins/exit-semantics.d.ts +31 -0
- package/dist/tools/builtins/exit-semantics.d.ts.map +1 -0
- package/dist/tools/builtins/exit-semantics.js +198 -0
- package/dist/tools/builtins/exit-semantics.js.map +1 -0
- package/dist/tools/builtins/job.js +1 -1
- package/dist/tools/builtins/job.js.map +1 -1
- package/dist/tools/builtins/skill.js +1 -1
- package/dist/tools/builtins/skill.js.map +1 -1
- package/dist/tools/builtins/structuredOutput.js +1 -1
- package/dist/tools/builtins/structuredOutput.js.map +1 -1
- package/dist/tools/builtins/verify-outputs.js +1 -1
- package/dist/tools/builtins/verify-outputs.js.map +1 -1
- package/dist/tools/builtins/write-file.js +11 -6
- package/dist/tools/builtins/write-file.js.map +1 -1
- package/dist/tools/coordinator/ask-user-question.d.ts +6 -0
- package/dist/tools/coordinator/ask-user-question.d.ts.map +1 -1
- package/dist/tools/coordinator/ask-user-question.js +6 -1
- package/dist/tools/coordinator/ask-user-question.js.map +1 -1
- package/dist/tools/coordinator/index.d.ts.map +1 -1
- package/dist/tools/coordinator/index.js +10 -3
- package/dist/tools/coordinator/index.js.map +1 -1
- package/dist/tools/memory/delete.js +1 -1
- package/dist/tools/memory/delete.js.map +1 -1
- package/dist/tools/memory/read.js +1 -1
- package/dist/tools/memory/read.js.map +1 -1
- package/dist/tools/memory/save.d.ts.map +1 -1
- package/dist/tools/memory/save.js +4 -3
- package/dist/tools/memory/save.js.map +1 -1
- package/dist/tools/memory/search.js +1 -1
- package/dist/tools/memory/search.js.map +1 -1
- package/dist/tools/memory/update.js +1 -1
- package/dist/tools/memory/update.js.map +1 -1
- package/dist/tools/schedules/loop-tool.d.ts.map +1 -1
- package/dist/tools/schedules/loop-tool.js +4 -2
- package/dist/tools/schedules/loop-tool.js.map +1 -1
- package/dist/tools/schedules/schedule-tool.js +2 -2
- package/dist/tools/schedules/schedule-tool.js.map +1 -1
- package/dist/tools/task/create.js +1 -1
- package/dist/tools/task/create.js.map +1 -1
- package/dist/tools/untrusted-envelope.js +1 -1
- package/dist/tools/untrusted-envelope.js.map +1 -1
- package/dist/toolsets/manager.d.ts.map +1 -1
- package/dist/toolsets/manager.js +6 -1
- package/dist/toolsets/manager.js.map +1 -1
- package/dist/turn/evidence-query.d.ts.map +1 -1
- package/dist/turn/evidence-query.js +16 -1
- package/dist/turn/evidence-query.js.map +1 -1
- package/dist/turn/evidence-recall.js +1 -1
- package/dist/turn/evidence-recall.js.map +1 -1
- package/dist/types/agent/base.d.ts +7 -0
- package/dist/types/agent/base.d.ts.map +1 -1
- package/dist/types/common/index.d.ts +19 -0
- package/dist/types/common/index.d.ts.map +1 -1
- package/dist/types/common/index.js +9 -0
- package/dist/types/common/index.js.map +1 -1
- package/dist/types/hitl/index.d.ts +66 -0
- package/dist/types/hitl/index.d.ts.map +1 -1
- package/dist/types/hitl/index.js.map +1 -1
- package/dist/types/message/index.d.ts +8 -0
- package/dist/types/message/index.d.ts.map +1 -1
- package/dist/types/message/index.js +12 -2
- package/dist/types/message/index.js.map +1 -1
- package/dist/types/message/notice-frame.d.ts +41 -0
- package/dist/types/message/notice-frame.d.ts.map +1 -0
- package/dist/types/message/notice-frame.js +65 -0
- package/dist/types/message/notice-frame.js.map +1 -0
- package/dist/types/provider/chat.d.ts +8 -0
- package/dist/types/provider/chat.d.ts.map +1 -1
- package/dist/types/provider/config.d.ts +6 -0
- package/dist/types/provider/config.d.ts.map +1 -1
- package/dist/types/session/checkpoint.d.ts +52 -0
- package/dist/types/session/checkpoint.d.ts.map +1 -1
- package/dist/types/session/checkpoint.js +5 -0
- package/dist/types/session/checkpoint.js.map +1 -1
- package/dist/types/session/events.d.ts +9 -0
- package/dist/types/session/events.d.ts.map +1 -1
- package/dist/types/session/events.js.map +1 -1
- package/dist/types/session/records.d.ts +181 -6
- package/dist/types/session/records.d.ts.map +1 -1
- package/dist/types/session/records.js +1 -1
- package/dist/types/session/records.js.map +1 -1
- package/dist/types/tool/presentation.d.ts +17 -1
- package/dist/types/tool/presentation.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/agents/QueryAgent.ts +1 -0
- package/src/compaction/context-breakdown.ts +228 -0
- package/src/compaction/extractor.ts +8 -1
- package/src/compaction/managers/structured.ts +3 -0
- package/src/compaction/manual.ts +5 -0
- package/src/compaction/serializer.ts +28 -3
- package/src/compaction/token-estimate.ts +5 -0
- package/src/compaction/verifier.ts +32 -6
- package/src/constants/compaction/index.ts +1 -0
- package/src/prompt/coding-agent-doctrine.ts +18 -20
- package/src/public-runtime.ts +24 -2
- package/src/runtime/cache-clock.ts +67 -0
- package/src/runtime/query/checkpoint.ts +49 -8
- package/src/runtime/query/compact-session.ts +101 -3
- package/src/runtime/query/executor/tool-call-admission.ts +3 -0
- package/src/runtime/query/executor.ts +165 -15
- package/src/runtime/query/index.ts +45 -1
- package/src/runtime/query/iteration/index.ts +27 -6
- package/src/runtime/query/iteration/phases/context.ts +37 -11
- package/src/runtime/query/iteration/phases/per-call-review.ts +598 -0
- package/src/runtime/query/iteration/phases/tool-review.ts +168 -83
- package/src/runtime/query/iteration/step-context.ts +29 -0
- package/src/runtime/query/iteration/step-shaping.ts +6 -2
- package/src/runtime/query/prepare-turn.ts +18 -1
- package/src/runtime/query/project-instructions.ts +119 -38
- package/src/runtime/query/prompt.ts +11 -54
- package/src/runtime/query/resume-pending.ts +131 -0
- package/src/runtime/query/review-policy.ts +28 -0
- package/src/runtime/query/steering.ts +100 -96
- package/src/store/budget/disk.ts +4 -1
- package/src/tools/admit-facts.ts +6 -0
- package/src/tools/builtins/bash.ts +38 -28
- package/src/tools/builtins/browser.ts +38 -12
- package/src/tools/builtins/edit-apply.ts +12 -2
- package/src/tools/builtins/edit.ts +38 -12
- package/src/tools/builtins/exit-semantics.ts +198 -0
- package/src/tools/builtins/job.ts +1 -1
- package/src/tools/builtins/skill.ts +1 -1
- package/src/tools/builtins/structuredOutput.ts +1 -1
- package/src/tools/builtins/verify-outputs.ts +1 -1
- package/src/tools/builtins/write-file.ts +11 -6
- package/src/tools/coordinator/ask-user-question.ts +12 -1
- package/src/tools/coordinator/index.ts +10 -3
- package/src/tools/memory/delete.ts +1 -1
- package/src/tools/memory/read.ts +1 -1
- package/src/tools/memory/save.ts +4 -3
- package/src/tools/memory/search.ts +1 -1
- package/src/tools/memory/update.ts +1 -1
- package/src/tools/schedules/loop-tool.ts +4 -2
- package/src/tools/schedules/schedule-tool.ts +2 -2
- package/src/tools/task/create.ts +1 -1
- package/src/tools/untrusted-envelope.ts +1 -1
- package/src/toolsets/manager.ts +6 -1
- package/src/turn/evidence-query.ts +19 -1
- package/src/turn/evidence-recall.ts +1 -1
- package/src/types/agent/base.ts +8 -0
- package/src/types/common/index.ts +27 -0
- package/src/types/hitl/index.ts +67 -0
- package/src/types/message/index.ts +20 -2
- package/src/types/message/notice-frame.ts +88 -0
- package/src/types/provider/chat.ts +8 -0
- package/src/types/provider/config.ts +6 -0
- package/src/types/session/checkpoint.ts +5 -0
- package/src/types/session/events.ts +6 -0
- package/src/types/session/records.ts +1 -1
- package/src/types/tool/presentation.ts +16 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { TokenUsage } from '../types/common/index.js'
|
|
2
|
+
|
|
3
|
+
/** What the clock knows about the prompt cache after the latest model call. */
|
|
4
|
+
export interface CacheClockState {
|
|
5
|
+
/** When the last call that touched the cache finished (ms since epoch). */
|
|
6
|
+
readonly lastActivityAt: number
|
|
7
|
+
/** How long the provider keeps the cache, as that call reported it. */
|
|
8
|
+
readonly ttlMs: number
|
|
9
|
+
/**
|
|
10
|
+
* When the cache lapses: `lastActivityAt + ttlMs`. Only set when the call
|
|
11
|
+
* actually read or wrote cache; a call that touched none leaves the cache
|
|
12
|
+
* (and so its expiry) unknown rather than assumed.
|
|
13
|
+
*/
|
|
14
|
+
readonly expiresAt: number | null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Tracks when a conversation's prompt cache will lapse.
|
|
19
|
+
*
|
|
20
|
+
* Fed by each completed model call. The lifetime is never guessed: a call whose
|
|
21
|
+
* usage carries no `cacheTtlMs` (a driver that does not report one) clears the
|
|
22
|
+
* clock, so nothing downstream can act on a lifetime nobody stated. Pure of
|
|
23
|
+
* timers: the caller passes `now`.
|
|
24
|
+
*/
|
|
25
|
+
export class CacheClock {
|
|
26
|
+
#state: CacheClockState | null = null
|
|
27
|
+
|
|
28
|
+
/** Record a finished call. `usage` is that call's own usage, not a turn total. */
|
|
29
|
+
record(usage: TokenUsage, at: number): void {
|
|
30
|
+
if (usage.cacheTtlMs === undefined || !(usage.cacheTtlMs > 0)) {
|
|
31
|
+
this.#state = null
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
const touched = usage.cachedTokens + usage.cacheWriteTokens > 0
|
|
35
|
+
this.#state = {
|
|
36
|
+
lastActivityAt: at,
|
|
37
|
+
ttlMs: usage.cacheTtlMs,
|
|
38
|
+
expiresAt: touched ? at + usage.cacheTtlMs : null,
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Forget everything (the conversation was replaced or its context rewritten). */
|
|
43
|
+
reset(): void {
|
|
44
|
+
this.#state = null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get state(): CacheClockState | null {
|
|
48
|
+
return this.#state
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Whether the cache is still alive at `now`. False when unknown. */
|
|
52
|
+
warm(now: number): boolean {
|
|
53
|
+
const state = this.#state
|
|
54
|
+
return state !== null && state.expiresAt !== null && now < state.expiresAt
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The moment `fraction` of the cache's remaining life since the last
|
|
59
|
+
* activity has passed: `lastActivityAt + (expiresAt - lastActivityAt) * fraction`.
|
|
60
|
+
* `null` when the expiry is unknown.
|
|
61
|
+
*/
|
|
62
|
+
dueAt(fraction: number): number | null {
|
|
63
|
+
const state = this.#state
|
|
64
|
+
if (state === null || state.expiresAt === null) return null
|
|
65
|
+
return state.lastActivityAt + (state.expiresAt - state.lastActivityAt) * fraction
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -19,11 +19,12 @@ import {
|
|
|
19
19
|
type UserMessage,
|
|
20
20
|
createRuntimeContextMessage,
|
|
21
21
|
} from '../../types/message/index.js'
|
|
22
|
+
import { escapeNoticeFrame } from '../../types/message/notice-frame.js'
|
|
22
23
|
import { CHECKPOINT_DOCUMENT_VERSION, type Checkpoint } from '../../types/session/checkpoint.js'
|
|
23
24
|
import type { CheckpointListEntry } from '../../types/session/fork.js'
|
|
24
25
|
import type { SessionRecord } from '../../types/session/records.js'
|
|
25
26
|
import { asGoalId, generateCheckpointId } from '../../utils/id.js'
|
|
26
|
-
import {
|
|
27
|
+
import { readSteeringNote } from './steering.js'
|
|
27
28
|
|
|
28
29
|
/** Keep intent text/provenance without copying attachment payloads into a second slot. */
|
|
29
30
|
function snapshotUserIntent(value: unknown): UserMessage {
|
|
@@ -460,8 +461,8 @@ export class CheckpointManager {
|
|
|
460
461
|
|
|
461
462
|
/**
|
|
462
463
|
* The id of the record that carries a message's text: its own record,
|
|
463
|
-
* or
|
|
464
|
-
*
|
|
464
|
+
* or, for the operator's message sent while a tool ran, the record of the
|
|
465
|
+
* framed message that carries it.
|
|
465
466
|
*/
|
|
466
467
|
private messageIdOf(recorder: TurnRecorder, message: Message): MessageId | undefined {
|
|
467
468
|
if (message === this.restoredUserMessage) return this.restoredUserMessageId
|
|
@@ -483,14 +484,17 @@ export class CheckpointManager {
|
|
|
483
484
|
) {
|
|
484
485
|
return undefined
|
|
485
486
|
}
|
|
486
|
-
|
|
487
|
+
// The operator's words were sent as a framed message of their own whose
|
|
488
|
+
// marker is random, so it is found by what its frame holds.
|
|
489
|
+
const wanted = escapeNoticeFrame(message.content)
|
|
487
490
|
const messages = recorder.messages
|
|
488
491
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
489
492
|
const candidate = messages[i] as Message
|
|
490
493
|
if (
|
|
491
|
-
candidate.role === '
|
|
494
|
+
candidate.role === 'user' &&
|
|
492
495
|
typeof candidate.content === 'string' &&
|
|
493
|
-
candidate.
|
|
496
|
+
(candidate as UserMessage).source?.type === 'runtime-context' &&
|
|
497
|
+
readSteeringNote(candidate.content) === wanted
|
|
494
498
|
) {
|
|
495
499
|
const id = recorder.recordedIdOf(candidate)
|
|
496
500
|
if (id) this.steeringCarriers.set(message, id)
|
|
@@ -578,6 +582,42 @@ export class CheckpointManager {
|
|
|
578
582
|
return park
|
|
579
583
|
}
|
|
580
584
|
|
|
585
|
+
/**
|
|
586
|
+
* End the parks that were the other questions of a batch whose decision has
|
|
587
|
+
* just been applied (the per-call review parks one question per call).
|
|
588
|
+
*
|
|
589
|
+
* The batch is answered now, so a question about one of its calls is moot;
|
|
590
|
+
* left open, an approval queue would keep serving it and a host resuming it
|
|
591
|
+
* would rewind the turn to a batch that has long since been carried out.
|
|
592
|
+
* Recorded as superseded, not as an answer: nobody gave one.
|
|
593
|
+
*/
|
|
594
|
+
async supersedeBatchParks(
|
|
595
|
+
callIds: ReadonlySet<string>,
|
|
596
|
+
except: CheckpointId,
|
|
597
|
+
superseding: HITLResumeDecision,
|
|
598
|
+
): Promise<number> {
|
|
599
|
+
await this.recorder.flush()
|
|
600
|
+
const parks = await readParks(this.recorder.log, {
|
|
601
|
+
turnId: this.scope.turnId,
|
|
602
|
+
})
|
|
603
|
+
let ended = 0
|
|
604
|
+
for (const park of parks) {
|
|
605
|
+
const request = park.pending.request
|
|
606
|
+
if (
|
|
607
|
+
park.checkpointId === except ||
|
|
608
|
+
park.pending.resolvedAt !== undefined ||
|
|
609
|
+
request.type !== 'tool_review' ||
|
|
610
|
+
request.perCall !== true ||
|
|
611
|
+
!request.toolCalls.every((call) => callIds.has(call.id))
|
|
612
|
+
) {
|
|
613
|
+
continue
|
|
614
|
+
}
|
|
615
|
+
await this.unpark(park.checkpointId, superseding)
|
|
616
|
+
ended += 1
|
|
617
|
+
}
|
|
618
|
+
return ended
|
|
619
|
+
}
|
|
620
|
+
|
|
581
621
|
private async openPark(checkpointId: CheckpointId): Promise<RecordedPark | null> {
|
|
582
622
|
await this.recorder.flush()
|
|
583
623
|
const parks = await readParks(this.recorder.log, { turnId: this.scope.turnId })
|
|
@@ -693,9 +733,10 @@ export async function restoreCheckpointContext(
|
|
|
693
733
|
`Checkpoint latestUserMessage names message ${intentId}, which its session log does not hold`,
|
|
694
734
|
)
|
|
695
735
|
}
|
|
696
|
-
// Guidance
|
|
736
|
+
// Guidance the operator sent while a tool ran: the intent is the note, whether
|
|
737
|
+
// it is a message of its own or, in an older session, the end of a tool result.
|
|
697
738
|
const guidance =
|
|
698
|
-
intent?.role === 'tool' && typeof intent.content === 'string'
|
|
739
|
+
(intent?.role === 'tool' || intent?.role === 'user') && typeof intent.content === 'string'
|
|
699
740
|
? readSteeringNote(intent.content)
|
|
700
741
|
: undefined
|
|
701
742
|
const operatorIntent =
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type CompactNowInput,
|
|
3
|
+
type CompactionResult,
|
|
4
|
+
compactNow,
|
|
5
|
+
compactRegion,
|
|
6
|
+
} from '../../compaction/manual.js'
|
|
2
7
|
import { readFoldedHistory } from '../../manager/session/turn-recorder.js'
|
|
3
8
|
import { NamzuError } from '../../types/errors/index.js'
|
|
4
9
|
import type { MessageId, SessionId } from '../../types/ids/index.js'
|
|
@@ -10,13 +15,19 @@ import { type SessionLocatorOptions, locateSessionLog, withSessionLease } from '
|
|
|
10
15
|
/** A compaction a host asks for between turns. */
|
|
11
16
|
export interface CompactSessionParams extends Omit<CompactNowInput, 'messages' | 'onShed'> {
|
|
12
17
|
readonly sessionId: SessionId
|
|
18
|
+
/**
|
|
19
|
+
* Who asked. `'manual'` (the default) is a person's request; `'idle'` is a
|
|
20
|
+
* host compacting a conversation nobody is using before its prompt cache
|
|
21
|
+
* lapses. Recorded on the `compaction` record.
|
|
22
|
+
*/
|
|
23
|
+
readonly trigger?: 'manual' | 'idle'
|
|
13
24
|
/** Where to find the session's log, and the lease to append under. */
|
|
14
25
|
readonly locator?: SessionLocatorOptions
|
|
15
26
|
}
|
|
16
27
|
|
|
17
28
|
/**
|
|
18
29
|
* Compact a session outside any turn. Reads the session's context from its
|
|
19
|
-
* log, compacts it, and appends a `compaction{ trigger
|
|
30
|
+
* log, compacts it, and appends a `compaction{ trigger }` record (`'manual'` unless the host says `'idle'`)
|
|
20
31
|
* with no `turnId`: the messages the pass kept are named by id, the summary
|
|
21
32
|
* is recorded in full, and the shed messages stay in the log for audit.
|
|
22
33
|
*
|
|
@@ -26,7 +37,7 @@ export interface CompactSessionParams extends Omit<CompactNowInput, 'messages' |
|
|
|
26
37
|
export async function compactSession(
|
|
27
38
|
params: CompactSessionParams,
|
|
28
39
|
): Promise<CompactionResult | null> {
|
|
29
|
-
const { sessionId, locator = {}, ...input } = params
|
|
40
|
+
const { sessionId, locator = {}, trigger = 'manual', ...input } = params
|
|
30
41
|
const log = await locateSessionLog(sessionId, locator)
|
|
31
42
|
return withSessionLease(log, locator.lease, async (lease) => {
|
|
32
43
|
const active = await log.activeTurn({ lease })
|
|
@@ -47,6 +58,93 @@ export async function compactSession(
|
|
|
47
58
|
})
|
|
48
59
|
}
|
|
49
60
|
const { summary, keptMessageIds } = compactionShape(result.messages, ids)
|
|
61
|
+
await log.append(lease, {
|
|
62
|
+
type: 'compaction',
|
|
63
|
+
compactionId: generateMessageId(),
|
|
64
|
+
strategy: input.config.strategy,
|
|
65
|
+
trigger,
|
|
66
|
+
replacesSeqRange: [1, head.pointer.seq],
|
|
67
|
+
summary,
|
|
68
|
+
keptMessageIds,
|
|
69
|
+
tokensBefore: 0,
|
|
70
|
+
tokensAfter: 0,
|
|
71
|
+
})
|
|
72
|
+
return result
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** A compaction of one span of a session's context, asked for between turns. */
|
|
77
|
+
export interface CompactSessionRangeParams extends CompactSessionParams {
|
|
78
|
+
/**
|
|
79
|
+
* The first message to summarise, by the id the log recorded it under.
|
|
80
|
+
* Omitted: the span starts at the beginning of the context.
|
|
81
|
+
*/
|
|
82
|
+
readonly from?: MessageId
|
|
83
|
+
/**
|
|
84
|
+
* The message the span stops before; it and everything after it stay as
|
|
85
|
+
* they are. Omitted: the span runs to the end of the context.
|
|
86
|
+
*/
|
|
87
|
+
readonly until?: MessageId
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Compact one span of a session's context outside any turn.
|
|
92
|
+
*
|
|
93
|
+
* Like {@link compactSession}, but only the messages from `from` up to (not
|
|
94
|
+
* including) `until` are summarised; the rest of the context stays verbatim.
|
|
95
|
+
* A host uses it to fold away "everything before this message" or "this
|
|
96
|
+
* message and everything after it" without touching the other side. Pinned
|
|
97
|
+
* (`retain`) messages inside the span stay as they are.
|
|
98
|
+
*
|
|
99
|
+
* Throws `not_found` when `from` or `until` is not in the session's current
|
|
100
|
+
* context (a compaction may already have folded it away), and
|
|
101
|
+
* `invalid_config` when a boundary would split a tool call from its result.
|
|
102
|
+
* `null` when the span holds nothing worth summarising; nothing is appended
|
|
103
|
+
* then. Refused while a turn is active (`TurnInProgressError`).
|
|
104
|
+
*/
|
|
105
|
+
export async function compactSessionRange(
|
|
106
|
+
params: CompactSessionRangeParams,
|
|
107
|
+
): Promise<CompactionResult | null> {
|
|
108
|
+
const { sessionId, locator = {}, from, until, ...input } = params
|
|
109
|
+
const log = await locateSessionLog(sessionId, locator)
|
|
110
|
+
return withSessionLease(log, locator.lease, async (lease) => {
|
|
111
|
+
const active = await log.activeTurn({ lease })
|
|
112
|
+
if (active) {
|
|
113
|
+
throw new TurnInProgressError({ sessionId, activeTurnId: active.turnId, state: active.state })
|
|
114
|
+
}
|
|
115
|
+
const history = await readFoldedHistory(log)
|
|
116
|
+
const position = (id: MessageId | undefined, fallback: number): number => {
|
|
117
|
+
if (id === undefined) return fallback
|
|
118
|
+
const at = history.findIndex((entry) => entry.messageId === id)
|
|
119
|
+
if (at < 0) {
|
|
120
|
+
throw new NamzuError({
|
|
121
|
+
code: 'not_found',
|
|
122
|
+
message: `Message ${id} is not in session ${sessionId}'s current context; it may already have been summarised.`,
|
|
123
|
+
details: { sessionId, messageId: id },
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
return at
|
|
127
|
+
}
|
|
128
|
+
const start = position(from, 0)
|
|
129
|
+
const end = position(until, history.length)
|
|
130
|
+
const ids = new Map<Message, MessageId>()
|
|
131
|
+
for (const entry of history) if (entry.messageId) ids.set(entry.message, entry.messageId)
|
|
132
|
+
const result = await compactRegion({
|
|
133
|
+
...input,
|
|
134
|
+
messages: history.map((entry) => entry.message),
|
|
135
|
+
start,
|
|
136
|
+
end,
|
|
137
|
+
})
|
|
138
|
+
if (!result) return null
|
|
139
|
+
const head = await log.head()
|
|
140
|
+
if (!head) {
|
|
141
|
+
throw new NamzuError({
|
|
142
|
+
code: 'not_found',
|
|
143
|
+
message: `Session ${sessionId} has an empty log; there is nothing to compact.`,
|
|
144
|
+
details: { sessionId },
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
const { summary, keptMessageIds } = compactionShape(result.messages, ids)
|
|
50
148
|
await log.append(lease, {
|
|
51
149
|
type: 'compaction',
|
|
52
150
|
compactionId: generateMessageId(),
|
|
@@ -290,6 +290,9 @@ export async function prepareDirectCall(
|
|
|
290
290
|
...(providerInput && !repairUsed && preparation.inputFailure
|
|
291
291
|
? { inputFailure: preparation.inputFailure }
|
|
292
292
|
: {}),
|
|
293
|
+
// The registry's typed reason (e.g. invalid arguments) is what a
|
|
294
|
+
// host keys its short label on; the message alone is wording.
|
|
295
|
+
...(preparation.result.facts ? { facts: preparation.result.facts } : {}),
|
|
293
296
|
}
|
|
294
297
|
}
|
|
295
298
|
|
|
@@ -567,6 +567,43 @@ export interface ToolCallOutcome {
|
|
|
567
567
|
revealedTools?: readonly ToolRevealReceipt[]
|
|
568
568
|
}
|
|
569
569
|
|
|
570
|
+
/** What the per-call review decided about one call that reached the front of the walk. */
|
|
571
|
+
export type CallAdmission =
|
|
572
|
+
| { readonly run: true }
|
|
573
|
+
| {
|
|
574
|
+
readonly run: false
|
|
575
|
+
readonly reason: string
|
|
576
|
+
readonly declined?: ToolCallDeclined
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* The per-call review, as the executor sees it: a batch executed while its
|
|
581
|
+
* calls are still being asked about.
|
|
582
|
+
*
|
|
583
|
+
* The executor walks the calls in the model's order. A call that is safe to run
|
|
584
|
+
* beside others starts as soon as every call already in flight is also safe;
|
|
585
|
+
* any other call starts only when nothing is in flight, so it never overlaps an
|
|
586
|
+
* earlier call, and nothing after it starts before it does. Starting a call
|
|
587
|
+
* means asking `admit` and, on a yes, running it right away.
|
|
588
|
+
*/
|
|
589
|
+
export interface ReviewedExecution {
|
|
590
|
+
admit(call: ToolCall, index: number): Promise<CallAdmission>
|
|
591
|
+
/**
|
|
592
|
+
* Aborts when a refusal ends the turn. Calls that are running are cancelled
|
|
593
|
+
* through it, and a call that has not started is refused without `admit`
|
|
594
|
+
* being consulted for a yes.
|
|
595
|
+
*/
|
|
596
|
+
readonly ended: AbortSignal
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
interface ReviewUnit {
|
|
600
|
+
readonly index: number
|
|
601
|
+
readonly toolCall: ToolCall
|
|
602
|
+
readonly preparedCall?: PreparedDirectCall
|
|
603
|
+
readonly safe: boolean
|
|
604
|
+
readonly run: () => Promise<void>
|
|
605
|
+
}
|
|
606
|
+
|
|
570
607
|
export interface ToolExecutionBatch {
|
|
571
608
|
messages: Message[]
|
|
572
609
|
results: ToolCallOutcome[]
|
|
@@ -1268,6 +1305,7 @@ export class ToolExecutor {
|
|
|
1268
1305
|
prior?: PriorToolResults,
|
|
1269
1306
|
preparedBatch?: PreparedToolBatch,
|
|
1270
1307
|
declines?: ToolCallDeclines,
|
|
1308
|
+
review?: ReviewedExecution,
|
|
1271
1309
|
): Promise<ToolExecutionBatch> {
|
|
1272
1310
|
const toolCalls = response.message.toolCalls
|
|
1273
1311
|
if (!toolCalls) {
|
|
@@ -1294,9 +1332,9 @@ export class ToolExecutor {
|
|
|
1294
1332
|
if (refusal) {
|
|
1295
1333
|
const refused = new Map(denials)
|
|
1296
1334
|
for (const call of toolCalls) if (!refused.has(call.id)) refused.set(call.id, refusal)
|
|
1297
|
-
return await this.runBatch(toolCalls, refused, prior, owned, declines)
|
|
1335
|
+
return await this.runBatch(toolCalls, refused, prior, owned, declines, review)
|
|
1298
1336
|
}
|
|
1299
|
-
return await this.runBatch(toolCalls, denials, prior, owned, declines)
|
|
1337
|
+
return await this.runBatch(toolCalls, denials, prior, owned, declines, review)
|
|
1300
1338
|
} finally {
|
|
1301
1339
|
// Cleared so a later single execution outside a batch resolves
|
|
1302
1340
|
// live rather than inheriting the last batch's sample.
|
|
@@ -1310,6 +1348,7 @@ export class ToolExecutor {
|
|
|
1310
1348
|
prior?: PriorToolResults,
|
|
1311
1349
|
preparedBatch?: OwnedPreparedToolBatch,
|
|
1312
1350
|
declines?: ToolCallDeclines,
|
|
1351
|
+
review?: ReviewedExecution,
|
|
1313
1352
|
): Promise<ToolExecutionBatch> {
|
|
1314
1353
|
this.log.debug('Executing tool batch', {
|
|
1315
1354
|
[NAMZU.TURN_ID]: this.config.turnId,
|
|
@@ -1370,6 +1409,9 @@ export class ToolExecutor {
|
|
|
1370
1409
|
// gate keeps that at a working-set size while preserving completion
|
|
1371
1410
|
// order independence.
|
|
1372
1411
|
const gate = new Semaphore(this.config.maxToolConcurrency ?? DEFAULT_TOOL_CONCURRENCY)
|
|
1412
|
+
// Under a per-call review the calls to run are collected here and walked
|
|
1413
|
+
// afterwards; refusals that were settled before the review need no walk.
|
|
1414
|
+
const reviewUnits: ReviewUnit[] = []
|
|
1373
1415
|
toolCalls.forEach((toolCall, i) => {
|
|
1374
1416
|
const preparedCall = preparedBatch?.calls.get(toolCall.id)
|
|
1375
1417
|
const tool = this.config.tools.get(preparedCall?.toolName ?? toolCall.function.name)
|
|
@@ -1391,7 +1433,7 @@ export class ToolExecutor {
|
|
|
1391
1433
|
? { structuredResultJson: recovered.structuredResultJson }
|
|
1392
1434
|
: {}),
|
|
1393
1435
|
}
|
|
1394
|
-
if (isBarrier) schedule(async () => {}, true, true)
|
|
1436
|
+
if (isBarrier && !review) schedule(async () => {}, true, true)
|
|
1395
1437
|
return
|
|
1396
1438
|
}
|
|
1397
1439
|
|
|
@@ -1400,18 +1442,16 @@ export class ToolExecutor {
|
|
|
1400
1442
|
// Denied calls never touch the tool; they still get a result
|
|
1401
1443
|
// message so the assistant turn stays fully answered. They can
|
|
1402
1444
|
// overlap other calls within a segment, respecting its barriers.
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
isBarrier,
|
|
1414
|
-
)
|
|
1445
|
+
const recordDenied = async () => {
|
|
1446
|
+
results[i] = await this.recordDenial(
|
|
1447
|
+
toolCall,
|
|
1448
|
+
denialReason,
|
|
1449
|
+
preparedCall,
|
|
1450
|
+
declines?.get(toolCall.id),
|
|
1451
|
+
)
|
|
1452
|
+
}
|
|
1453
|
+
if (review) parallel.push(recordDenied())
|
|
1454
|
+
else schedule(recordDenied, true, isBarrier)
|
|
1415
1455
|
return
|
|
1416
1456
|
}
|
|
1417
1457
|
// Per-call, because the event has to name which call it is about:
|
|
@@ -1436,6 +1476,13 @@ export class ToolExecutor {
|
|
|
1436
1476
|
...(escalation?.outsidePaths ? { approvedPaths: escalation.outsidePaths } : {}),
|
|
1437
1477
|
...(escalation?.sandboxEscape ? { sandboxEscapeApproved: true } : {}),
|
|
1438
1478
|
source: { kind: 'direct' },
|
|
1479
|
+
// A refusal that ends the turn cancels what is running, through the
|
|
1480
|
+
// same signal a tool already honours for a cancelled turn.
|
|
1481
|
+
...(review
|
|
1482
|
+
? {
|
|
1483
|
+
abortSignal: AbortSignal.any([baseContext.abortSignal, review.ended]),
|
|
1484
|
+
}
|
|
1485
|
+
: {}),
|
|
1439
1486
|
// Overridden per call, so a nested dispatch can name the call
|
|
1440
1487
|
// that made it. The base context has no `toolUseId`, and a
|
|
1441
1488
|
// closure built there would report every nested call as
|
|
@@ -1477,8 +1524,19 @@ export class ToolExecutor {
|
|
|
1477
1524
|
// non-JSON args → treat as unsafe (serialize), the conservative path
|
|
1478
1525
|
}
|
|
1479
1526
|
const safe = tool?.isConcurrencySafe?.(input) === true
|
|
1527
|
+
if (review) {
|
|
1528
|
+
reviewUnits.push({
|
|
1529
|
+
index: i,
|
|
1530
|
+
toolCall,
|
|
1531
|
+
...(preparedCall ? { preparedCall } : {}),
|
|
1532
|
+
safe: safe && !isBarrier,
|
|
1533
|
+
run: safe ? gated : run,
|
|
1534
|
+
})
|
|
1535
|
+
return
|
|
1536
|
+
}
|
|
1480
1537
|
schedule(safe ? gated : run, safe, isBarrier)
|
|
1481
1538
|
})
|
|
1539
|
+
if (review) await this.walkReviewed(reviewUnits, results, review)
|
|
1482
1540
|
await Promise.all([...parallel, serial])
|
|
1483
1541
|
|
|
1484
1542
|
// Whatever failed above left a hole in `results`; fill every one, so
|
|
@@ -1518,6 +1576,98 @@ export class ToolExecutor {
|
|
|
1518
1576
|
return { messages, results, observations }
|
|
1519
1577
|
}
|
|
1520
1578
|
|
|
1579
|
+
/**
|
|
1580
|
+
* Walk the calls of a batch in the model's order, asking about each as it
|
|
1581
|
+
* reaches the front and running it right after a yes.
|
|
1582
|
+
*
|
|
1583
|
+
* A call may start when nothing is in flight, or when it and every call in
|
|
1584
|
+
* flight are safe to run side by side. The walk stops at the first call that
|
|
1585
|
+
* may not start yet, so an unsafe call is never overtaken by one that came
|
|
1586
|
+
* after it. Once a refusal has ended the turn, nothing waits: every call that
|
|
1587
|
+
* has not started is refused at once and the ones in flight are cancelled.
|
|
1588
|
+
*/
|
|
1589
|
+
private async walkReviewed(
|
|
1590
|
+
units: readonly ReviewUnit[],
|
|
1591
|
+
results: ToolCallOutcome[],
|
|
1592
|
+
review: ReviewedExecution,
|
|
1593
|
+
): Promise<void> {
|
|
1594
|
+
const inFlight = new Set<ReviewUnit>()
|
|
1595
|
+
let cursor = 0
|
|
1596
|
+
let failure: { readonly error: unknown } | undefined
|
|
1597
|
+
// Once the turn has ended, a refusal is written only after every call
|
|
1598
|
+
// before it has settled, so the transcript lists the calls in the order the
|
|
1599
|
+
// model wrote them: the declined call before the ones refused unasked.
|
|
1600
|
+
const settledUnits = new Map<number, Promise<void>>()
|
|
1601
|
+
const refusedAfterEnd =
|
|
1602
|
+
'An earlier call in this batch was refused and the turn was stopped, so this call was not run.'
|
|
1603
|
+
await new Promise<void>((resolve) => {
|
|
1604
|
+
const start = (unit: ReviewUnit): void => {
|
|
1605
|
+
inFlight.add(unit)
|
|
1606
|
+
let settledUnit!: () => void
|
|
1607
|
+
settledUnits.set(
|
|
1608
|
+
unit.index,
|
|
1609
|
+
new Promise<void>((done) => {
|
|
1610
|
+
settledUnit = done
|
|
1611
|
+
}),
|
|
1612
|
+
)
|
|
1613
|
+
void (async () => {
|
|
1614
|
+
try {
|
|
1615
|
+
let admission: CallAdmission
|
|
1616
|
+
try {
|
|
1617
|
+
admission = await review.admit(unit.toolCall, unit.index)
|
|
1618
|
+
} catch {
|
|
1619
|
+
// An answer that could not be obtained is not a yes.
|
|
1620
|
+
admission = {
|
|
1621
|
+
run: false,
|
|
1622
|
+
reason: 'The approval for this call could not be completed, so it was not run.',
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
if (admission.run && !review.ended.aborted) {
|
|
1626
|
+
await unit.run()
|
|
1627
|
+
} else {
|
|
1628
|
+
const refusal = admission.run
|
|
1629
|
+
? { reason: refusedAfterEnd }
|
|
1630
|
+
: { reason: admission.reason, declined: admission.declined }
|
|
1631
|
+
if (review.ended.aborted) {
|
|
1632
|
+
await Promise.all(
|
|
1633
|
+
[...settledUnits].filter(([index]) => index < unit.index).map(([, done]) => done),
|
|
1634
|
+
)
|
|
1635
|
+
}
|
|
1636
|
+
results[unit.index] = await this.recordDenial(
|
|
1637
|
+
unit.toolCall,
|
|
1638
|
+
refusal.reason,
|
|
1639
|
+
unit.preparedCall,
|
|
1640
|
+
'declined' in refusal ? refusal.declined : undefined,
|
|
1641
|
+
)
|
|
1642
|
+
}
|
|
1643
|
+
} catch (error) {
|
|
1644
|
+
failure ??= { error }
|
|
1645
|
+
} finally {
|
|
1646
|
+
settledUnit()
|
|
1647
|
+
inFlight.delete(unit)
|
|
1648
|
+
pump()
|
|
1649
|
+
}
|
|
1650
|
+
})()
|
|
1651
|
+
}
|
|
1652
|
+
const pump = (): void => {
|
|
1653
|
+
while (cursor < units.length) {
|
|
1654
|
+
const unit = units[cursor] as ReviewUnit
|
|
1655
|
+
if (!review.ended.aborted) {
|
|
1656
|
+
const mayStart =
|
|
1657
|
+
inFlight.size === 0 || (unit.safe && [...inFlight].every((other) => other.safe))
|
|
1658
|
+
if (!mayStart) break
|
|
1659
|
+
}
|
|
1660
|
+
cursor++
|
|
1661
|
+
start(unit)
|
|
1662
|
+
}
|
|
1663
|
+
if (cursor >= units.length && inFlight.size === 0) resolve()
|
|
1664
|
+
}
|
|
1665
|
+
review.ended.addEventListener('abort', pump, { once: true })
|
|
1666
|
+
pump()
|
|
1667
|
+
})
|
|
1668
|
+
if (failure) throw failure.error
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1521
1671
|
/**
|
|
1522
1672
|
* Hold the batch's combined result text under the per-message cap.
|
|
1523
1673
|
*
|
|
@@ -786,6 +786,15 @@ export interface QueryParams {
|
|
|
786
786
|
* See {@link ReminderSource}.
|
|
787
787
|
*/
|
|
788
788
|
reminders?: import('./reminders.js').ReminderSource
|
|
789
|
+
/**
|
|
790
|
+
* Whether the kernel tells the model, once, that the approval policy
|
|
791
|
+
* changed ("Approval policy changed from X to Y ..."), as request-only
|
|
792
|
+
* context after the history. Default `true`. A host that already tells the
|
|
793
|
+
* model about a mode change itself (through `reminders`) sets `false` so
|
|
794
|
+
* the model hears it once; the `approval_policy_changed` event is
|
|
795
|
+
* recorded either way.
|
|
796
|
+
*/
|
|
797
|
+
announceApprovalPolicyChange?: boolean
|
|
789
798
|
|
|
790
799
|
/**
|
|
791
800
|
* Wake a background-task hold when operator input is available, without draining it.
|
|
@@ -840,6 +849,16 @@ export interface QueryParams {
|
|
|
840
849
|
*/
|
|
841
850
|
reviewAllowedCalls?: () => boolean
|
|
842
851
|
|
|
852
|
+
/**
|
|
853
|
+
* Ask about one call at a time while the batch runs, instead of reviewing the
|
|
854
|
+
* whole batch as a unit. Opt-in; absent, nothing about review changes. See
|
|
855
|
+
* {@link import('../../types/hitl/index.js').PerCallReviewConfig}.
|
|
856
|
+
*
|
|
857
|
+
* Only this turn is affected: a delegated child reviews its own batches as
|
|
858
|
+
* it did before.
|
|
859
|
+
*/
|
|
860
|
+
perCallReview?: import('../../types/hitl/index.js').PerCallReviewConfig
|
|
861
|
+
|
|
843
862
|
/**
|
|
844
863
|
* A name for the policy `resumeHandler` implements.
|
|
845
864
|
*
|
|
@@ -1770,7 +1789,11 @@ export async function* query(params: QueryParams): AsyncGenerator<SessionEvent,
|
|
|
1770
1789
|
// Read through the box on every call, so a swap lands on the next
|
|
1771
1790
|
// question rather than the next turn.
|
|
1772
1791
|
resumeHandler: (request, context) => approvalPolicy.current.handler(request, context),
|
|
1773
|
-
takeApprovalPolicyChange: () =>
|
|
1792
|
+
takeApprovalPolicyChange: () => {
|
|
1793
|
+
// Taken either way, so the change is not left pending for a later turn.
|
|
1794
|
+
const change = approvalPolicy.takeUnannouncedChange()
|
|
1795
|
+
return params.announceApprovalPolicyChange === false ? undefined : change
|
|
1796
|
+
},
|
|
1774
1797
|
promptContributions,
|
|
1775
1798
|
...(params.steering ? { steering: params.steering } : {}),
|
|
1776
1799
|
...(jobNotices ? { jobNotices } : {}),
|
|
@@ -1788,6 +1811,7 @@ export async function* query(params: QueryParams): AsyncGenerator<SessionEvent,
|
|
|
1788
1811
|
toolGrants: new ToolGrantSet(),
|
|
1789
1812
|
skillGrants,
|
|
1790
1813
|
...(params.reviewAllowedCalls ? { reviewAllowedCalls: params.reviewAllowedCalls } : {}),
|
|
1814
|
+
...(params.perCallReview ? { perCallReview: params.perCallReview } : {}),
|
|
1791
1815
|
// Turn-scoped for the same reason. A repeat count carried into a later
|
|
1792
1816
|
// run is a claim about work nobody repeated, and a module-level map
|
|
1793
1817
|
// would leak exactly that way.
|
|
@@ -2690,6 +2714,26 @@ export async function* query(params: QueryParams): AsyncGenerator<SessionEvent,
|
|
|
2690
2714
|
})
|
|
2691
2715
|
return null
|
|
2692
2716
|
})
|
|
2717
|
+
// A decision about one call of a batch answered the batch: the
|
|
2718
|
+
// batch's other open questions are moot.
|
|
2719
|
+
if (pendingResume?.partial && params.pendingDecision) {
|
|
2720
|
+
const batchIds = new Set(
|
|
2721
|
+
pendingResume.response.message.toolCalls?.map((call) => call.id) ?? [],
|
|
2722
|
+
)
|
|
2723
|
+
await checkpointMgr
|
|
2724
|
+
.supersedeBatchParks(
|
|
2725
|
+
batchIds,
|
|
2726
|
+
resolvedCheckpointId,
|
|
2727
|
+
supersededByRecovery(params.pendingDecision),
|
|
2728
|
+
)
|
|
2729
|
+
.catch((err: unknown) => {
|
|
2730
|
+
ctx.log.error('Applied a pending decision but failed to clear its sibling parks', {
|
|
2731
|
+
[NAMZU.TURN_ID]: ctx.turnId,
|
|
2732
|
+
'exception.message': err instanceof Error ? err.message : String(err),
|
|
2733
|
+
})
|
|
2734
|
+
return 0
|
|
2735
|
+
})
|
|
2736
|
+
}
|
|
2693
2737
|
}
|
|
2694
2738
|
|
|
2695
2739
|
yield* iterationOrchestrator.runLoop()
|