@namzu/sdk 56.1.0 → 57.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 +54 -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/agents/index.d.ts +1 -0
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +1 -0
- package/dist/agents/index.js.map +1 -1
- package/dist/agents/plan.d.ts +20 -0
- package/dist/agents/plan.d.ts.map +1 -0
- package/dist/agents/plan.js +27 -0
- package/dist/agents/plan.js.map +1 -0
- package/dist/compaction/context-breakdown.d.ts +1 -1
- package/dist/compaction/context-breakdown.d.ts.map +1 -1
- package/dist/compaction/context-breakdown.js +9 -2
- package/dist/compaction/context-breakdown.js.map +1 -1
- package/dist/compaction/dangling.d.ts +12 -1
- package/dist/compaction/dangling.d.ts.map +1 -1
- package/dist/compaction/dangling.js +17 -3
- package/dist/compaction/dangling.js.map +1 -1
- package/dist/compaction/index.d.ts +2 -2
- package/dist/compaction/index.d.ts.map +1 -1
- package/dist/compaction/index.js.map +1 -1
- package/dist/compaction/manager.d.ts +16 -0
- package/dist/compaction/manager.d.ts.map +1 -1
- package/dist/compaction/manager.js +55 -0
- package/dist/compaction/manager.js.map +1 -1
- package/dist/compaction/manual.d.ts +2 -0
- package/dist/compaction/manual.d.ts.map +1 -1
- package/dist/compaction/manual.js +33 -4
- package/dist/compaction/manual.js.map +1 -1
- package/dist/compaction/restore.d.ts +51 -0
- package/dist/compaction/restore.d.ts.map +1 -0
- package/dist/compaction/restore.js +123 -0
- package/dist/compaction/restore.js.map +1 -0
- package/dist/compaction/types.d.ts +22 -0
- package/dist/compaction/types.d.ts.map +1 -1
- package/dist/compaction/verifier.d.ts.map +1 -1
- package/dist/compaction/verifier.js +3 -1
- package/dist/compaction/verifier.js.map +1 -1
- package/dist/compaction/wire.d.ts +3 -1
- package/dist/compaction/wire.d.ts.map +1 -1
- package/dist/compaction/wire.js +16 -0
- package/dist/compaction/wire.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/plugin/shell-hook.d.ts +19 -1
- package/dist/plugin/shell-hook.d.ts.map +1 -1
- package/dist/plugin/shell-hook.js +21 -5
- package/dist/plugin/shell-hook.js.map +1 -1
- package/dist/prompt/coding-agent-doctrine.d.ts +2 -2
- package/dist/prompt/coding-agent-doctrine.d.ts.map +1 -1
- package/dist/prompt/coding-agent-doctrine.js +3 -2
- package/dist/prompt/coding-agent-doctrine.js.map +1 -1
- package/dist/provider/errors.d.ts +3 -0
- package/dist/provider/errors.d.ts.map +1 -1
- package/dist/provider/errors.js +23 -3
- package/dist/provider/errors.js.map +1 -1
- package/dist/provider/fallback.d.ts.map +1 -1
- package/dist/provider/fallback.js +34 -3
- package/dist/provider/fallback.js.map +1 -1
- package/dist/provider/retry.d.ts +12 -5
- package/dist/provider/retry.d.ts.map +1 -1
- package/dist/provider/retry.js +22 -8
- package/dist/provider/retry.js.map +1 -1
- package/dist/public-runtime.d.ts +5 -3
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +5 -2
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-types.d.ts +1 -1
- package/dist/public-types.d.ts.map +1 -1
- package/dist/runtime/jobs/registry.d.ts +42 -0
- package/dist/runtime/jobs/registry.d.ts.map +1 -1
- package/dist/runtime/jobs/registry.js +77 -0
- package/dist/runtime/jobs/registry.js.map +1 -1
- package/dist/runtime/query/calls-that-never-started.d.ts +12 -0
- package/dist/runtime/query/calls-that-never-started.d.ts.map +1 -0
- package/dist/runtime/query/calls-that-never-started.js +41 -0
- package/dist/runtime/query/calls-that-never-started.js.map +1 -0
- package/dist/runtime/query/executor/tool-call-admission.d.ts +2 -2
- package/dist/runtime/query/executor/tool-call-admission.d.ts.map +1 -1
- package/dist/runtime/query/executor/tool-call-admission.js +13 -9
- package/dist/runtime/query/executor/tool-call-admission.js.map +1 -1
- package/dist/runtime/query/executor.d.ts +16 -1
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +45 -2
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/file-evidence-context.d.ts +21 -4
- package/dist/runtime/query/file-evidence-context.d.ts.map +1 -1
- package/dist/runtime/query/file-evidence-context.js +70 -8
- package/dist/runtime/query/file-evidence-context.js.map +1 -1
- package/dist/runtime/query/index.d.ts +6 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +7 -11
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts +1 -2
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +30 -19
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.js +20 -0
- package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context.d.ts +2 -0
- package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/context.js.map +1 -1
- package/dist/runtime/query/iteration/recorded-context.d.ts +18 -0
- package/dist/runtime/query/iteration/recorded-context.d.ts.map +1 -0
- package/dist/runtime/query/iteration/recorded-context.js +37 -0
- package/dist/runtime/query/iteration/recorded-context.js.map +1 -0
- package/dist/runtime/query/iteration/step-context.d.ts +15 -51
- package/dist/runtime/query/iteration/step-context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/step-context.js +24 -134
- package/dist/runtime/query/iteration/step-context.js.map +1 -1
- package/dist/runtime/query/iteration/step-shaping.d.ts +20 -8
- package/dist/runtime/query/iteration/step-shaping.d.ts.map +1 -1
- package/dist/runtime/query/iteration/step-shaping.js +52 -26
- package/dist/runtime/query/iteration/step-shaping.js.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.js +45 -25
- package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
- package/dist/runtime/query/prepare-turn.d.ts.map +1 -1
- package/dist/runtime/query/prepare-turn.js +10 -1
- package/dist/runtime/query/prepare-turn.js.map +1 -1
- package/dist/scheduler/completion-inbox.d.ts +27 -0
- package/dist/scheduler/completion-inbox.d.ts.map +1 -1
- package/dist/scheduler/completion-inbox.js +90 -23
- package/dist/scheduler/completion-inbox.js.map +1 -1
- package/dist/store/session-index/fts.d.ts.map +1 -1
- package/dist/store/session-index/fts.js +6 -0
- package/dist/store/session-index/fts.js.map +1 -1
- package/dist/test-support/file-evidence-held.d.ts +11 -0
- package/dist/test-support/file-evidence-held.d.ts.map +1 -0
- package/dist/test-support/file-evidence-held.js +28 -0
- package/dist/test-support/file-evidence-held.js.map +1 -0
- package/dist/test-support/request-only.d.ts +10 -0
- package/dist/test-support/request-only.d.ts.map +1 -0
- package/dist/test-support/request-only.js +15 -0
- package/dist/test-support/request-only.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 +42 -1
- package/dist/tools/builtins/bash.d.ts.map +1 -1
- package/dist/tools/builtins/bash.js +115 -48
- package/dist/tools/builtins/bash.js.map +1 -1
- package/dist/tools/builtins/read-file.d.ts.map +1 -1
- package/dist/tools/builtins/read-file.js +55 -5
- package/dist/tools/builtins/read-file.js.map +1 -1
- package/dist/tools/file-read-tracker.d.ts.map +1 -1
- package/dist/tools/file-read-tracker.js +28 -0
- package/dist/tools/file-read-tracker.js.map +1 -1
- package/dist/tools/untrusted-envelope.d.ts +7 -0
- package/dist/tools/untrusted-envelope.d.ts.map +1 -1
- package/dist/tools/untrusted-envelope.js +3 -1
- package/dist/tools/untrusted-envelope.js.map +1 -1
- package/dist/turn/evidence-recall.d.ts +19 -8
- package/dist/turn/evidence-recall.d.ts.map +1 -1
- package/dist/turn/evidence-recall.js +134 -53
- package/dist/turn/evidence-recall.js.map +1 -1
- package/dist/turn/memory-recall.d.ts +18 -0
- package/dist/turn/memory-recall.d.ts.map +1 -1
- package/dist/turn/memory-recall.js +43 -4
- package/dist/turn/memory-recall.js.map +1 -1
- package/dist/types/agent/base.d.ts +6 -0
- package/dist/types/agent/base.d.ts.map +1 -1
- package/dist/types/errors/catalog.d.ts +3 -0
- package/dist/types/errors/catalog.d.ts.map +1 -1
- package/dist/types/errors/catalog.js +24 -2
- package/dist/types/errors/catalog.js.map +1 -1
- package/dist/types/message/index.d.ts +46 -1
- package/dist/types/message/index.d.ts.map +1 -1
- package/dist/types/message/index.js +78 -1
- package/dist/types/message/index.js.map +1 -1
- package/dist/types/provider/error.d.ts +7 -0
- package/dist/types/provider/error.d.ts.map +1 -1
- package/dist/types/provider/errors.d.ts +24 -0
- package/dist/types/provider/errors.d.ts.map +1 -1
- package/dist/types/provider/errors.js +101 -1
- package/dist/types/provider/errors.js.map +1 -1
- package/dist/types/provider/stream.d.ts +6 -0
- package/dist/types/provider/stream.d.ts.map +1 -1
- package/dist/types/session/events.d.ts +8 -0
- package/dist/types/session/events.d.ts.map +1 -1
- package/dist/types/session/events.js.map +1 -1
- package/dist/types/session/prepare-step.d.ts +24 -0
- package/dist/types/session/prepare-step.d.ts.map +1 -1
- package/dist/types/tool/index.d.ts +39 -0
- package/dist/types/tool/index.d.ts.map +1 -1
- package/dist/types/tool/index.js.map +1 -1
- package/dist/types/tool/presentation.d.ts +4 -0
- package/dist/types/tool/presentation.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/agents/QueryAgent.ts +1 -0
- package/src/agents/index.ts +1 -0
- package/src/agents/plan.ts +30 -0
- package/src/compaction/context-breakdown.ts +11 -3
- package/src/compaction/dangling.ts +35 -3
- package/src/compaction/index.ts +4 -0
- package/src/compaction/manager.ts +53 -0
- package/src/compaction/manual.ts +42 -10
- package/src/compaction/restore.ts +159 -0
- package/src/compaction/types.ts +25 -0
- package/src/compaction/verifier.ts +3 -1
- package/src/compaction/wire.ts +26 -1
- package/src/index.ts +2 -0
- package/src/plugin/shell-hook.ts +33 -5
- package/src/prompt/coding-agent-doctrine.ts +3 -2
- package/src/provider/errors.ts +27 -3
- package/src/provider/fallback.ts +34 -2
- package/src/provider/retry.ts +30 -10
- package/src/public-runtime.ts +15 -0
- package/src/public-types.ts +3 -0
- package/src/runtime/jobs/registry.ts +105 -0
- package/src/runtime/query/calls-that-never-started.ts +37 -0
- package/src/runtime/query/executor/tool-call-admission.ts +12 -9
- package/src/runtime/query/executor.ts +57 -2
- package/src/runtime/query/file-evidence-context.ts +79 -11
- package/src/runtime/query/index.ts +14 -10
- package/src/runtime/query/iteration/index.ts +32 -26
- package/src/runtime/query/iteration/phases/compaction.ts +19 -0
- package/src/runtime/query/iteration/phases/context.ts +3 -0
- package/src/runtime/query/iteration/recorded-context.ts +48 -0
- package/src/runtime/query/iteration/step-context.ts +27 -155
- package/src/runtime/query/iteration/step-shaping.ts +64 -40
- package/src/runtime/query/iteration/stream-turn.ts +47 -25
- package/src/runtime/query/prepare-turn.ts +10 -1
- package/src/scheduler/completion-inbox.ts +89 -22
- package/src/store/session-index/fts.ts +5 -0
- package/src/test-support/file-evidence-held.ts +27 -0
- package/src/test-support/request-only.ts +19 -0
- package/src/tools/admit-facts.ts +1 -0
- package/src/tools/builtins/bash.ts +149 -54
- package/src/tools/builtins/read-file.ts +66 -4
- package/src/tools/file-read-tracker.ts +25 -1
- package/src/tools/untrusted-envelope.ts +10 -1
- package/src/turn/evidence-recall.ts +189 -58
- package/src/turn/memory-recall.ts +59 -5
- package/src/types/agent/base.ts +7 -0
- package/src/types/errors/catalog.ts +31 -2
- package/src/types/message/index.ts +108 -1
- package/src/types/provider/error.ts +7 -0
- package/src/types/provider/errors.ts +120 -1
- package/src/types/provider/stream.ts +6 -0
- package/src/types/session/events.ts +8 -0
- package/src/types/session/prepare-step.ts +26 -0
- package/src/types/tool/index.ts +40 -0
- package/src/types/tool/presentation.ts +4 -0
|
@@ -6,6 +6,7 @@ import { unknownProgramInLine } from '../../authorization/program.js'
|
|
|
6
6
|
import { type SkillGrantSet, compileSkillGrant } from '../../authorization/skill-grant.js'
|
|
7
7
|
import { extractFromToolCall, extractFromToolResult } from '../../compaction/extractor.js'
|
|
8
8
|
import type { WorkingStateManager } from '../../compaction/manager.js'
|
|
9
|
+
import { recordRestorable } from '../../compaction/restore.js'
|
|
9
10
|
import { GENAI, NAMZU } from '../../constants/telemetry/index.js'
|
|
10
11
|
import type { PluginLifecycleManager } from '../../plugin/lifecycle.js'
|
|
11
12
|
import { buildProbeContext } from '../../probe/context.js'
|
|
@@ -13,6 +14,7 @@ import { ProbeVetoError } from '../../probe/errors.js'
|
|
|
13
14
|
import { probe as defaultProbeRegistry } from '../../probe/registry.js'
|
|
14
15
|
import type { ProbeEnforcement } from '../../probe/registry.js'
|
|
15
16
|
import type { ActivityStore } from '../../store/activity/memory.js'
|
|
17
|
+
import { fingerprintContent } from '../../tools/builtins/content-fingerprint.js'
|
|
16
18
|
import { sandboxShellSpawn, withoutBashStartup } from '../../tools/command-shell.js'
|
|
17
19
|
import { isAlwaysDestructive } from '../../tools/defineTool.js'
|
|
18
20
|
import { createFileReadTracker } from '../../tools/file-read-tracker.js'
|
|
@@ -72,7 +74,11 @@ import {
|
|
|
72
74
|
runPreToolHook,
|
|
73
75
|
unknownToolMessage,
|
|
74
76
|
} from './executor/tool-call-admission.js'
|
|
75
|
-
import {
|
|
77
|
+
import {
|
|
78
|
+
type FileEvidence,
|
|
79
|
+
describeVisibleFileEvidence,
|
|
80
|
+
visibleFileEvidence,
|
|
81
|
+
} from './file-evidence-context.js'
|
|
76
82
|
import { seedObservationLedger } from './file-evidence-seed.js'
|
|
77
83
|
import { DEFAULT_TOOL_RESULT_GUARDRAILS } from './guardrail-presets.js'
|
|
78
84
|
import { declaredWriteEscalation, shellEscalations } from './path-escalation.js'
|
|
@@ -696,6 +702,7 @@ export class ToolExecutor {
|
|
|
696
702
|
private emitEvent: EmitEvent
|
|
697
703
|
private log: Logger
|
|
698
704
|
private workingStateManager?: WorkingStateManager
|
|
705
|
+
private historyView?: () => readonly Message[]
|
|
699
706
|
private probes: ProbeEnforcement
|
|
700
707
|
private parentSpan?: Span
|
|
701
708
|
private readonly toolCallBudget?: ToolCallBudget
|
|
@@ -745,6 +752,30 @@ export class ToolExecutor {
|
|
|
745
752
|
this.workingStateManager = manager
|
|
746
753
|
}
|
|
747
754
|
|
|
755
|
+
/**
|
|
756
|
+
* Read the conversation the model is shown, as it is now. Lets a tool ask
|
|
757
|
+
* whether an earlier receipt is still there (see `ToolContext.receiptVisible`)
|
|
758
|
+
* without being handed the history.
|
|
759
|
+
*/
|
|
760
|
+
setHistoryView(view: () => readonly Message[]): void {
|
|
761
|
+
this.historyView = view
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
private receiptVisible = (callId: string, renderedFingerprint: string): boolean => {
|
|
765
|
+
const history = this.historyView?.()
|
|
766
|
+
if (!history) return false
|
|
767
|
+
for (let index = history.length - 1; index >= 0; index -= 1) {
|
|
768
|
+
const message = history[index]
|
|
769
|
+
if (message?.role !== 'tool' || message.toolCallId !== callId) continue
|
|
770
|
+
return (
|
|
771
|
+
typeof message.content === 'string' &&
|
|
772
|
+
message.isError !== true &&
|
|
773
|
+
fingerprintContent(message.content) === renderedFingerprint
|
|
774
|
+
)
|
|
775
|
+
}
|
|
776
|
+
return false
|
|
777
|
+
}
|
|
778
|
+
|
|
748
779
|
setSandbox(sandbox: Sandbox): void {
|
|
749
780
|
this.config = { ...this.config, sandbox }
|
|
750
781
|
this.sandboxGate = undefined
|
|
@@ -845,7 +876,21 @@ export class ToolExecutor {
|
|
|
845
876
|
})
|
|
846
877
|
}
|
|
847
878
|
|
|
848
|
-
/**
|
|
879
|
+
/**
|
|
880
|
+
* The entries that hold now, from the same ledger used by mutation
|
|
881
|
+
* admission. No filesystem I/O. The loop records the difference from what
|
|
882
|
+
* the conversation already says.
|
|
883
|
+
*/
|
|
884
|
+
fileEvidenceEntries(messages: readonly Message[]): FileEvidence[] {
|
|
885
|
+
return visibleFileEvidence(
|
|
886
|
+
messages,
|
|
887
|
+
this.fileReadTracker,
|
|
888
|
+
this.config.workingDirectory,
|
|
889
|
+
this.sandboxed,
|
|
890
|
+
)
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/** The whole of the evidence as one text, from the same ledger. No filesystem I/O. */
|
|
849
894
|
describeFileEvidence(messages: readonly Message[]): string | undefined {
|
|
850
895
|
return describeVisibleFileEvidence(
|
|
851
896
|
messages,
|
|
@@ -2167,6 +2212,7 @@ export class ToolExecutor {
|
|
|
2167
2212
|
this.dispatchNested(name, input, context, recordObservation, undefined, options),
|
|
2168
2213
|
sandbox: this.config.sandbox,
|
|
2169
2214
|
fileReadTracker: this.fileReadTracker,
|
|
2215
|
+
receiptVisible: this.receiptVisible,
|
|
2170
2216
|
...(this.config.codeAnalysis ? { codeAnalysis: this.config.codeAnalysis } : {}),
|
|
2171
2217
|
// Bound to this turn, once. Binding here rather than passing the
|
|
2172
2218
|
// owner from the tool is what makes the scoping structural: there
|
|
@@ -2566,6 +2612,15 @@ export class ToolExecutor {
|
|
|
2566
2612
|
|
|
2567
2613
|
if (this.workingStateManager) {
|
|
2568
2614
|
extractFromToolResult(this.workingStateManager, toolName, output, effectiveIsError)
|
|
2615
|
+
// What the model was shown, kept whole so a compaction can put it back.
|
|
2616
|
+
recordRestorable(this.workingStateManager, {
|
|
2617
|
+
toolName,
|
|
2618
|
+
input,
|
|
2619
|
+
output: typeof modelContent === 'string' ? modelContent : output,
|
|
2620
|
+
success: !effectiveIsError,
|
|
2621
|
+
unchanged: result.facts?.kind === 'read' && result.facts.unchanged === true,
|
|
2622
|
+
workingDirectory: this.config.workingDirectory,
|
|
2623
|
+
})
|
|
2569
2624
|
for (const pin of this.config.abortSignal.aborted ? [] : (result.workingState ?? [])) {
|
|
2570
2625
|
this.workingStateManager.pin(pin.key, pin.text, toolName)
|
|
2571
2626
|
}
|
|
@@ -29,14 +29,13 @@ import {
|
|
|
29
29
|
*/
|
|
30
30
|
const MAX_RECEIPT_UNITS = 32_000
|
|
31
31
|
|
|
32
|
-
/** The
|
|
33
|
-
export const FILE_EVIDENCE_HEADING =
|
|
34
|
-
'Visible file evidence (as of this step; a later block with this heading replaces this one)'
|
|
32
|
+
/** The words every file-evidence entry opens with. */
|
|
33
|
+
export const FILE_EVIDENCE_HEADING = 'Visible file evidence'
|
|
35
34
|
|
|
36
|
-
/**
|
|
37
|
-
|
|
35
|
+
/** Starts the line of an entry that names the paths whose earlier entries no longer hold. */
|
|
36
|
+
const NO_LONGER_HOLDS = 'No longer holds: '
|
|
38
37
|
|
|
39
|
-
interface FileEvidence {
|
|
38
|
+
export interface FileEvidence {
|
|
40
39
|
readonly path: string
|
|
41
40
|
/** Absent on the write-rooted entries; `'read'` on a body that is a receipt. */
|
|
42
41
|
readonly kind?: 'read'
|
|
@@ -46,14 +45,14 @@ interface FileEvidence {
|
|
|
46
45
|
}
|
|
47
46
|
|
|
48
47
|
/** Join visible call bodies, successful receipts and this executor's observation ledger. */
|
|
49
|
-
export function
|
|
48
|
+
export function visibleFileEvidence(
|
|
50
49
|
messages: readonly Message[],
|
|
51
50
|
tracker: FileReadTracker,
|
|
52
51
|
workingDirectory: string,
|
|
53
52
|
sandboxed: boolean,
|
|
54
|
-
):
|
|
53
|
+
): FileEvidence[] {
|
|
55
54
|
// Execution-owned witnesses survive transparent wrappers; a name alone proves nothing.
|
|
56
|
-
if (!tracker.writeCallId || !tracker.fingerprint) return
|
|
55
|
+
if (!tracker.writeCallId || !tracker.fingerprint) return []
|
|
57
56
|
// Lexical, as this projection has always looked entries up: a spelling that
|
|
58
57
|
// does not resolve to the tool's canonical key finds nothing and the path is
|
|
59
58
|
// simply not admitted. Writing the ledger is the side that has to key the
|
|
@@ -85,8 +84,77 @@ export function describeVisibleFileEvidence(
|
|
|
85
84
|
// Malformed retained inputs or a custom tracker cannot establish evidence.
|
|
86
85
|
}
|
|
87
86
|
}
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
return [...files.values()]
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const FILE_EVIDENCE_GUIDANCE = `each entry names a file (path), the successful write call that holds its complete current body (bodyInCall, that call's id), the edit calls to apply to that body in order (editsInCalls, when present), and a short code for the file as this runtime last observed it (observedFingerprint, which changes when the content does). An entry marked kind:"read" instead names a successful read call whose own receipt shows the whole file, every line behind its own N<tab> prefix; that receipt is the body, and such an entry never carries edit calls. The runtime did that reconstruction and checked it against its own file observation. Reuse the body for a targeted edit; a read only to recall it is unnecessary. This is not a fresh disk check, and after a resume the observation behind an entry may have been rebuilt from this conversation's earlier calls rather than made while this process ran. The built-in edit and write tools still compare the disk body when they run and refuse on drift; on refusal, inspect the current file and replan. Missing entries establish nothing. An entry for a path replaces every earlier entry for that path, and a path listed under "No longer holds" has no entry any more.`
|
|
91
|
+
|
|
92
|
+
/** The whole of what holds now: every entry, with the guidance for reading them. */
|
|
93
|
+
export function describeVisibleFileEvidence(
|
|
94
|
+
messages: readonly Message[],
|
|
95
|
+
tracker: FileReadTracker,
|
|
96
|
+
workingDirectory: string,
|
|
97
|
+
sandboxed: boolean,
|
|
98
|
+
): string | undefined {
|
|
99
|
+
const entries = visibleFileEvidence(messages, tracker, workingDirectory, sandboxed)
|
|
100
|
+
if (entries.length === 0) return
|
|
101
|
+
return `${FILE_EVIDENCE_HEADING}: ${FILE_EVIDENCE_GUIDANCE}\n${JSON.stringify(entries)}`
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The entries an earlier record still holds, by path, as the JSON each was
|
|
106
|
+
* written with. Read from the record's text, in order, so a later entry for a
|
|
107
|
+
* path replaces an earlier one and a retracted path is gone.
|
|
108
|
+
*/
|
|
109
|
+
function heldFileEvidence(recorded: readonly string[]): Map<string, string> {
|
|
110
|
+
const held = new Map<string, string>()
|
|
111
|
+
for (const text of recorded) {
|
|
112
|
+
for (const line of text.split('\n')) {
|
|
113
|
+
try {
|
|
114
|
+
if (line.startsWith(NO_LONGER_HOLDS)) {
|
|
115
|
+
const paths: unknown = JSON.parse(line.slice(NO_LONGER_HOLDS.length))
|
|
116
|
+
if (Array.isArray(paths))
|
|
117
|
+
for (const path of paths) if (typeof path === 'string') held.delete(path)
|
|
118
|
+
} else if (line.startsWith('[')) {
|
|
119
|
+
const entries: unknown = JSON.parse(line)
|
|
120
|
+
if (Array.isArray(entries))
|
|
121
|
+
for (const entry of entries)
|
|
122
|
+
if (typeof entry?.path === 'string') held.set(entry.path, JSON.stringify(entry))
|
|
123
|
+
}
|
|
124
|
+
} catch {
|
|
125
|
+
// a line of the record that is not an entry list
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return held
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* What to record now about the files, given what holds (`current`) and what
|
|
134
|
+
* the entries already in the conversation say (`recorded`, oldest first):
|
|
135
|
+
* only the entries that are new or changed, and the paths whose earlier entry
|
|
136
|
+
* no longer holds. An earlier entry is never edited; this says what replaces
|
|
137
|
+
* it. The first record of a conversation carries the guidance for reading
|
|
138
|
+
* entries, later ones do not repeat it. Nothing to say returns undefined.
|
|
139
|
+
*/
|
|
140
|
+
export function recordFileEvidence(
|
|
141
|
+
current: readonly FileEvidence[],
|
|
142
|
+
recorded: readonly string[],
|
|
143
|
+
): string | undefined {
|
|
144
|
+
const held = heldFileEvidence(recorded)
|
|
145
|
+
const changed = current.filter((entry) => held.get(entry.path) !== JSON.stringify(entry))
|
|
146
|
+
const now = new Set(current.map((entry) => entry.path))
|
|
147
|
+
const gone = [...held.keys()].filter((path) => !now.has(path))
|
|
148
|
+
if (changed.length === 0 && gone.length === 0) return
|
|
149
|
+
const lead =
|
|
150
|
+
recorded.length === 0
|
|
151
|
+
? `${FILE_EVIDENCE_HEADING}: ${FILE_EVIDENCE_GUIDANCE}`
|
|
152
|
+
: `${FILE_EVIDENCE_HEADING}, changed since the last entry; \`bodyInCall\` and \`observedFingerprint\` read as before. An entry for a path replaces the earlier entries for that path.`
|
|
153
|
+
return [
|
|
154
|
+
lead,
|
|
155
|
+
...(gone.length > 0 ? [`${NO_LONGER_HOLDS}${JSON.stringify(gone)}`] : []),
|
|
156
|
+
...(changed.length > 0 ? [JSON.stringify(changed)] : []),
|
|
157
|
+
].join('\n')
|
|
90
158
|
}
|
|
91
159
|
|
|
92
160
|
/** A full body that arrived whole in one call and is still the file's body. */
|
|
@@ -64,6 +64,7 @@ import {
|
|
|
64
64
|
type AssistantMessage,
|
|
65
65
|
type Message,
|
|
66
66
|
createSystemMessage,
|
|
67
|
+
lastPromptText,
|
|
67
68
|
} from '../../types/message/index.js'
|
|
68
69
|
import type { AgentPersona } from '../../types/persona/index.js'
|
|
69
70
|
import type { LLMProvider } from '../../types/provider/index.js'
|
|
@@ -813,6 +814,13 @@ export interface QueryParams {
|
|
|
813
814
|
*/
|
|
814
815
|
projectInstructionContext?: ProjectInstructionContext
|
|
815
816
|
|
|
817
|
+
/**
|
|
818
|
+
* Text the host wants carried across a compaction, such as where its plan
|
|
819
|
+
* file is. Asked once per pass, after the summary is written; the answer is
|
|
820
|
+
* appended to the restored context. Returning nothing adds nothing.
|
|
821
|
+
*/
|
|
822
|
+
postCompactionContext?: () => string | undefined | Promise<string | undefined>
|
|
823
|
+
|
|
816
824
|
/**
|
|
817
825
|
* Where this conversation's durable state lives.
|
|
818
826
|
*
|
|
@@ -1655,6 +1663,8 @@ export async function* query(params: QueryParams): AsyncGenerator<SessionEvent,
|
|
|
1655
1663
|
const compactionConfig = params.compactionConfig
|
|
1656
1664
|
? CompactionConfigSchema.parse(params.compactionConfig)
|
|
1657
1665
|
: undefined
|
|
1666
|
+
// The live history, so a tool can ask whether an earlier receipt is still in it.
|
|
1667
|
+
toolExecutor.setHistoryView(() => ctx.recorder.messages)
|
|
1658
1668
|
if (compactionConfig && compactionConfig.strategy !== 'disabled') {
|
|
1659
1669
|
workingStateManager = new WorkingStateManager(compactionConfig)
|
|
1660
1670
|
toolExecutor.setWorkingStateManager(workingStateManager)
|
|
@@ -1828,6 +1838,9 @@ export async function* query(params: QueryParams): AsyncGenerator<SessionEvent,
|
|
|
1828
1838
|
...(params.projectInstructionContext
|
|
1829
1839
|
? { projectInstructionContext: params.projectInstructionContext }
|
|
1830
1840
|
: {}),
|
|
1841
|
+
...(params.postCompactionContext
|
|
1842
|
+
? { postCompactionContext: params.postCompactionContext }
|
|
1843
|
+
: {}),
|
|
1831
1844
|
...(providerContextWindow !== undefined ? { providerContextWindow } : {}),
|
|
1832
1845
|
resolveModelContextWindow: async (model) => {
|
|
1833
1846
|
if (!modelContextWindows.has(model)) {
|
|
@@ -2028,7 +2041,7 @@ export async function* query(params: QueryParams): AsyncGenerator<SessionEvent,
|
|
|
2028
2041
|
{
|
|
2029
2042
|
turnId: ctx.turnId,
|
|
2030
2043
|
sessionId: params.sessionId,
|
|
2031
|
-
prompt:
|
|
2044
|
+
prompt: lastPromptText(initialMessages),
|
|
2032
2045
|
signal: ctx.abortController.signal,
|
|
2033
2046
|
},
|
|
2034
2047
|
eventTranslator.emitEvent,
|
|
@@ -2893,15 +2906,6 @@ async function settleAbandonedTurn(
|
|
|
2893
2906
|
}
|
|
2894
2907
|
}
|
|
2895
2908
|
|
|
2896
|
-
/** The text of the newest user turn, which is what a prompt hook is asked about. */
|
|
2897
|
-
function lastUserPrompt(messages: readonly Message[]): string {
|
|
2898
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
2899
|
-
const m = messages[i]
|
|
2900
|
-
if (m?.role === 'user' && typeof m.content === 'string') return m.content
|
|
2901
|
-
}
|
|
2902
|
-
return ''
|
|
2903
|
-
}
|
|
2904
|
-
|
|
2905
2909
|
type DrainQueryParams = Omit<QueryParams, 'resumeHandler'> & {
|
|
2906
2910
|
resumeHandler?: ResumeHandler
|
|
2907
2911
|
}
|
|
@@ -28,7 +28,6 @@ import {
|
|
|
28
28
|
import { getTracer } from '../../../telemetry/runtime-accessors.js'
|
|
29
29
|
import { STRUCTURED_OUTPUT_TOOL_NAME } from '../../../tools/builtins/structuredOutput.js'
|
|
30
30
|
import type { ToolManager } from '../../../toolsets/manager.js'
|
|
31
|
-
import { splitAtEvidenceBlocks } from '../../../turn/evidence-recall.js'
|
|
32
31
|
import type { CostInfo, TokenUsage } from '../../../types/common/index.js'
|
|
33
32
|
import { NamzuError } from '../../../types/errors/index.js'
|
|
34
33
|
import type { MessageId, SessionId, TurnId } from '../../../types/ids/index.js'
|
|
@@ -58,7 +57,6 @@ import { createCallbackInference } from '../callback-inference.js'
|
|
|
58
57
|
import { SandboxAcquisitionStopped } from '../deferred-sandbox.js'
|
|
59
58
|
import { deliverDurableInbound } from '../durable-inbound.js'
|
|
60
59
|
import type { ToolCallOutcome } from '../executor.js'
|
|
61
|
-
import { FILE_EVIDENCE_NONE } from '../file-evidence-context.js'
|
|
62
60
|
import { projectObservationContext } from '../observation-context.js'
|
|
63
61
|
import { applyLifecycleHookResults } from '../plugin-hooks.js'
|
|
64
62
|
import {
|
|
@@ -103,17 +101,16 @@ import {
|
|
|
103
101
|
type TextOnlyModelRefusal,
|
|
104
102
|
streamWithProviderRejectedImageRecovery,
|
|
105
103
|
} from './provider-rejected-image.js'
|
|
104
|
+
import { appendRecordedContext } from './recorded-context.js'
|
|
106
105
|
import { type MediaRemoval, RequestRepair } from './request-repair.js'
|
|
107
|
-
import {
|
|
106
|
+
import { placeRequestContext } from './step-context.js'
|
|
108
107
|
import {
|
|
109
|
-
FILE_EVIDENCE_KEY,
|
|
110
108
|
type StepShaping,
|
|
111
|
-
appendWorkContext,
|
|
112
109
|
beforeStep,
|
|
113
110
|
prepareStep,
|
|
114
111
|
selectContextModel,
|
|
115
112
|
stepContextMessage,
|
|
116
|
-
|
|
113
|
+
workContext,
|
|
117
114
|
} from './step-shaping.js'
|
|
118
115
|
import { streamProviderTurn } from './stream-turn.js'
|
|
119
116
|
|
|
@@ -194,8 +191,7 @@ export class IterationOrchestrator {
|
|
|
194
191
|
* one process must not suppress each other's first envelope.
|
|
195
192
|
*/
|
|
196
193
|
private lastEnvelopeKey: string | undefined
|
|
197
|
-
/**
|
|
198
|
-
private readonly turnContext = new TurnContextCarry()
|
|
194
|
+
/** The previous request's context, so a pre-call hook is told what changed. */
|
|
199
195
|
private previousRequestContext: RequestContextSnapshot | undefined
|
|
200
196
|
|
|
201
197
|
private projectObservations(messages: Message[]): Message[] {
|
|
@@ -838,6 +834,11 @@ export class IterationOrchestrator {
|
|
|
838
834
|
'limit-finalization',
|
|
839
835
|
)
|
|
840
836
|
: undefined
|
|
837
|
+
// What the step asked to have kept in the conversation joins the
|
|
838
|
+
// history now, after its last message, so this request and every
|
|
839
|
+
// later one begin with it. Nothing is inserted ahead of a message
|
|
840
|
+
// that is already recorded.
|
|
841
|
+
appendRecordedContext(recorder, step.recordedContext, this.ctx.log)
|
|
841
842
|
const baseMessages = recorder.messages
|
|
842
843
|
|
|
843
844
|
// Step guidance is appended to the REQUEST, never pushed onto
|
|
@@ -911,24 +912,30 @@ export class IterationOrchestrator {
|
|
|
911
912
|
DEFAULT_MAX_REQUEST_RICH_CONTENT_BYTES,
|
|
912
913
|
{ imagesReadable: this.modelReadsImages(stepModel) },
|
|
913
914
|
)
|
|
914
|
-
//
|
|
915
|
-
//
|
|
916
|
-
//
|
|
917
|
-
//
|
|
918
|
-
//
|
|
919
|
-
//
|
|
920
|
-
// request begins with the whole of the one before it. The short
|
|
921
|
-
// reminders and notices stay after the history.
|
|
915
|
+
// Request-only data (the context contributions, notes about the
|
|
916
|
+
// search, the work context) goes last on every call, after the
|
|
917
|
+
// whole history, so the history a caching driver reads never
|
|
918
|
+
// changes shape from one call to the next. What a step wants
|
|
919
|
+
// kept is recorded in the history above instead. The short
|
|
920
|
+
// reminders and notices follow it.
|
|
922
921
|
const messages = [...projected]
|
|
923
|
-
|
|
922
|
+
// What the loop learns about files and finished work holds until it
|
|
923
|
+
// changes, so it is recorded, after the history as it stands, and
|
|
924
|
+
// carried in this request where it will sit in the next one's history.
|
|
925
|
+
const work = workContext(shaping, projected, iterationNum, step)
|
|
926
|
+
const recordedNow = appendRecordedContext(recorder, work.recorded, this.ctx.log)
|
|
927
|
+
placeRequestContext(
|
|
924
928
|
messages,
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
929
|
+
recordedNow,
|
|
930
|
+
// The working-memory block and the policy notice already end the
|
|
931
|
+
// request; the entries just recorded belong ahead of them.
|
|
932
|
+
(workingMemory.context ? 1 : 0) + (policyNotice ? 1 : 0),
|
|
933
|
+
[
|
|
934
|
+
...[contextText, step.context]
|
|
935
|
+
.filter((text): text is string => Boolean(text))
|
|
936
|
+
.map(stepContextMessage),
|
|
937
|
+
...work.requestOnly,
|
|
938
|
+
],
|
|
932
939
|
)
|
|
933
940
|
if (closingDirective) messages.push(closingDirective)
|
|
934
941
|
await this.reportUnsupportedToolResults(messages)
|
|
@@ -2896,9 +2903,8 @@ export class IterationOrchestrator {
|
|
|
2896
2903
|
this.ctx.turnConfig.maxRequestRichContentBytes ?? DEFAULT_MAX_REQUEST_RICH_CONTENT_BYTES,
|
|
2897
2904
|
{ imagesReadable: this.modelReadsImages(model) },
|
|
2898
2905
|
)
|
|
2899
|
-
appendWorkContext(this.stepShaping(), finalMessages, this.steps.length + 1, { model })
|
|
2900
2906
|
// The closing directive is the last thing the model reads: after
|
|
2901
|
-
// the working memory
|
|
2907
|
+
// the working memory above.
|
|
2902
2908
|
finalMessages.push(
|
|
2903
2909
|
createRuntimeContextMessage(
|
|
2904
2910
|
`[SYSTEM] Turn is ending due to ${reason}. ${CLOSING_RESPONSE_GUIDANCE}`,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from '../../../../compaction/plan.js'
|
|
12
12
|
import type { ContextReducer, ContextReduction } from '../../../../compaction/reducer.js'
|
|
13
13
|
import { createSlidingWindowReducer } from '../../../../compaction/reducer.js'
|
|
14
|
+
import { renderRestoredContext } from '../../../../compaction/restore.js'
|
|
14
15
|
import { findRetainedIndices } from '../../../../compaction/retention.js'
|
|
15
16
|
import { serializeState } from '../../../../compaction/serializer.js'
|
|
16
17
|
import {
|
|
@@ -1003,6 +1004,24 @@ async function runCompactionCheckInner(
|
|
|
1003
1004
|
compactedContent = serializeState(manager.getState())
|
|
1004
1005
|
}
|
|
1005
1006
|
|
|
1007
|
+
// Skills the model loaded and files it read go back verbatim after the
|
|
1008
|
+
// summary, with whatever the host wants carried across (its plan file).
|
|
1009
|
+
let hostNote: string | undefined
|
|
1010
|
+
try {
|
|
1011
|
+
hostNote = await ctx.postCompactionContext?.()
|
|
1012
|
+
} catch (error) {
|
|
1013
|
+
// A host note is an addition; the pass does not depend on it.
|
|
1014
|
+
ctx.log.warn('The host note for a compaction could not be read — leaving it out', {
|
|
1015
|
+
[NAMZU.TURN_ID]: ctx.recorder.turnId,
|
|
1016
|
+
'exception.message': error instanceof Error ? error.message : String(error),
|
|
1017
|
+
})
|
|
1018
|
+
}
|
|
1019
|
+
const restored = renderRestoredContext(manager.getState(), {
|
|
1020
|
+
contextWindowTokens: budget,
|
|
1021
|
+
...(hostNote ? { hostNote } : {}),
|
|
1022
|
+
})
|
|
1023
|
+
if (restored) compactedContent = `${compactedContent}\n\n${restored}`
|
|
1024
|
+
|
|
1006
1025
|
// The pointer is only worth a line when both tools it names are mounted.
|
|
1007
1026
|
const mounted = new Set(ctx.tools?.listNames?.() ?? [])
|
|
1008
1027
|
const compactionMessage = buildCompactionMessage(
|
|
@@ -256,6 +256,9 @@ export interface IterationContext {
|
|
|
256
256
|
/** Live project policy; separate from human inbound continuation. */
|
|
257
257
|
readonly projectInstructionContext?: ProjectInstructionContext
|
|
258
258
|
|
|
259
|
+
/** Host text appended to the restored context after a compaction. */
|
|
260
|
+
readonly postCompactionContext?: () => string | undefined | Promise<string | undefined>
|
|
261
|
+
|
|
259
262
|
readonly workingStateManager?: WorkingStateManager
|
|
260
263
|
|
|
261
264
|
/**
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { NAMZU } from '../../../constants/telemetry/index.js'
|
|
2
|
+
import type { TurnRecorder } from '../../../manager/session/turn-recorder.js'
|
|
3
|
+
import {
|
|
4
|
+
type Message,
|
|
5
|
+
createTurnContextEntry,
|
|
6
|
+
isTurnContextKey,
|
|
7
|
+
turnContextEntries,
|
|
8
|
+
} from '../../../types/message/index.js'
|
|
9
|
+
import type { RecordedContextItem } from '../../../types/session/index.js'
|
|
10
|
+
import type { Logger } from '../../../utils/logger.js'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Append what a step asked to have recorded, after whatever the history
|
|
14
|
+
* ends with.
|
|
15
|
+
*
|
|
16
|
+
* Only at the end: an entry slipped in ahead of the operator's message would
|
|
17
|
+
* put the live array out of log order, and the recorder would then write a
|
|
18
|
+
* whole compaction record to re-base it on every turn. An item adds nothing
|
|
19
|
+
* when its text is empty or equals the last entry under its key, so a step
|
|
20
|
+
* that offers the same text on every call records it once. Returns the
|
|
21
|
+
* entries it added, so a request built before they were added can carry them
|
|
22
|
+
* at the place they will hold.
|
|
23
|
+
*/
|
|
24
|
+
export function appendRecordedContext(
|
|
25
|
+
recorder: Pick<TurnRecorder, 'messages' | 'pushMessage' | 'turnId'>,
|
|
26
|
+
items: readonly RecordedContextItem[] | undefined,
|
|
27
|
+
log: Pick<Logger, 'warn'>,
|
|
28
|
+
at: Date = new Date(),
|
|
29
|
+
): Message[] {
|
|
30
|
+
const appended: Message[] = []
|
|
31
|
+
for (const item of items ?? []) {
|
|
32
|
+
const text = item.text.trim()
|
|
33
|
+
if (text.length === 0) continue
|
|
34
|
+
if (!isTurnContextKey(item.key)) {
|
|
35
|
+
log.warn('A step recorded context under a key that is not a name; it was skipped', {
|
|
36
|
+
[NAMZU.TURN_ID]: recorder.turnId,
|
|
37
|
+
'namzu.runtime.key': String(item.key).slice(0, 80),
|
|
38
|
+
})
|
|
39
|
+
continue
|
|
40
|
+
}
|
|
41
|
+
const earlier = turnContextEntries(recorder.messages, item.key)
|
|
42
|
+
if (earlier.at(-1) === text) continue
|
|
43
|
+
const entry = createTurnContextEntry(item.key, text, at, { later: earlier.length > 0 })
|
|
44
|
+
recorder.pushMessage(entry)
|
|
45
|
+
appended.push(entry)
|
|
46
|
+
}
|
|
47
|
+
return appended
|
|
48
|
+
}
|