@namzu/sdk 62.6.1 → 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 +42 -0
- package/dist/agents/QueryAgent.d.ts.map +1 -1
- package/dist/agents/QueryAgent.js +5 -0
- package/dist/agents/QueryAgent.js.map +1 -1
- package/dist/agents/examples/SupervisorAgent.d.ts.map +1 -1
- package/dist/agents/examples/SupervisorAgent.js +1 -0
- package/dist/agents/examples/SupervisorAgent.js.map +1 -1
- package/dist/agents/explore.d.ts.map +1 -1
- package/dist/agents/explore.js +2 -1
- package/dist/agents/explore.js.map +1 -1
- package/dist/compaction/index.d.ts +1 -1
- package/dist/compaction/index.d.ts.map +1 -1
- package/dist/compaction/index.js +1 -1
- package/dist/compaction/index.js.map +1 -1
- package/dist/compaction/model-summary.js +2 -2
- package/dist/compaction/model-summary.js.map +1 -1
- package/dist/compaction/summary.d.ts +16 -1
- package/dist/compaction/summary.d.ts.map +1 -1
- package/dist/compaction/summary.js +25 -2
- package/dist/compaction/summary.js.map +1 -1
- package/dist/config/runtime.d.ts +8 -0
- package/dist/config/runtime.d.ts.map +1 -1
- package/dist/config/runtime.js +15 -0
- package/dist/config/runtime.js.map +1 -1
- package/dist/execution/workflow/runtime.d.ts.map +1 -1
- package/dist/execution/workflow/runtime.js +3 -0
- package/dist/execution/workflow/runtime.js.map +1 -1
- package/dist/execution/workflow/types.d.ts +8 -0
- package/dist/execution/workflow/types.d.ts.map +1 -1
- package/dist/manager/agent/child-session.d.ts +12 -0
- package/dist/manager/agent/child-session.d.ts.map +1 -1
- package/dist/manager/agent/child-session.js +29 -0
- package/dist/manager/agent/child-session.js.map +1 -1
- package/dist/manager/agent/lifecycle.d.ts.map +1 -1
- package/dist/manager/agent/lifecycle.js +8 -0
- package/dist/manager/agent/lifecycle.js.map +1 -1
- package/dist/manager/session/turn-recorder.d.ts +13 -2
- package/dist/manager/session/turn-recorder.d.ts.map +1 -1
- package/dist/manager/session/turn-recorder.js +107 -29
- package/dist/manager/session/turn-recorder.js.map +1 -1
- 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 +5 -3
- package/dist/prompt/coding-agent-doctrine.js.map +1 -1
- package/dist/provider/fallback.d.ts.map +1 -1
- package/dist/provider/fallback.js +5 -1
- package/dist/provider/fallback.js.map +1 -1
- package/dist/provider/retry.d.ts +25 -0
- package/dist/provider/retry.d.ts.map +1 -1
- package/dist/provider/retry.js +28 -0
- package/dist/provider/retry.js.map +1 -1
- package/dist/public-runtime.d.ts +3 -3
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +3 -3
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-tools.d.ts +4 -2
- package/dist/public-tools.d.ts.map +1 -1
- package/dist/public-tools.js +5 -2
- package/dist/public-tools.js.map +1 -1
- package/dist/runtime/query/auto-review/classifier.d.ts +7 -1
- package/dist/runtime/query/auto-review/classifier.d.ts.map +1 -1
- package/dist/runtime/query/auto-review/classifier.js +8 -4
- package/dist/runtime/query/auto-review/classifier.js.map +1 -1
- package/dist/runtime/query/auto-review/prompt.d.ts.map +1 -1
- package/dist/runtime/query/auto-review/prompt.js +2 -0
- package/dist/runtime/query/auto-review/prompt.js.map +1 -1
- package/dist/runtime/query/auto-review/script-evidence.d.ts +93 -0
- package/dist/runtime/query/auto-review/script-evidence.d.ts.map +1 -0
- package/dist/runtime/query/auto-review/script-evidence.js +566 -0
- package/dist/runtime/query/auto-review/script-evidence.js.map +1 -0
- package/dist/runtime/query/checkpoint.d.ts +13 -0
- package/dist/runtime/query/checkpoint.d.ts.map +1 -1
- package/dist/runtime/query/checkpoint.js +37 -0
- package/dist/runtime/query/checkpoint.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 +38 -1
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +154 -13
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/finalize-turn.d.ts +7 -1
- package/dist/runtime/query/finalize-turn.d.ts.map +1 -1
- package/dist/runtime/query/finalize-turn.js +2 -0
- package/dist/runtime/query/finalize-turn.js.map +1 -1
- package/dist/runtime/query/index.d.ts +8 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +7 -0
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts +32 -1
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +158 -10
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.d.ts +12 -0
- package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.js +48 -1
- package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/context.js +5 -0
- package/dist/runtime/query/iteration/phases/context.js.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.js +3 -3
- package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
- package/dist/runtime/query/prelude-lease.d.ts.map +1 -1
- package/dist/runtime/query/prelude-lease.js +22 -0
- package/dist/runtime/query/prelude-lease.js.map +1 -1
- package/dist/runtime/query/prepare-turn.d.ts.map +1 -1
- package/dist/runtime/query/prepare-turn.js +30 -12
- package/dist/runtime/query/prepare-turn.js.map +1 -1
- package/dist/runtime/query/review-policy.d.ts.map +1 -1
- package/dist/runtime/query/review-policy.js +29 -0
- package/dist/runtime/query/review-policy.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/runtime/query/tooling.d.ts +2 -0
- package/dist/runtime/query/tooling.d.ts.map +1 -1
- package/dist/runtime/query/tooling.js +1 -0
- package/dist/runtime/query/tooling.js.map +1 -1
- package/dist/runtime/query/turn/completion-audit.d.ts +27 -1
- package/dist/runtime/query/turn/completion-audit.d.ts.map +1 -1
- package/dist/runtime/query/turn/completion-audit.js +71 -6
- package/dist/runtime/query/turn/completion-audit.js.map +1 -1
- package/dist/runtime/query/turn/outcome-ledger.d.ts +113 -0
- package/dist/runtime/query/turn/outcome-ledger.d.ts.map +1 -0
- package/dist/runtime/query/turn/outcome-ledger.js +578 -0
- package/dist/runtime/query/turn/outcome-ledger.js.map +1 -0
- 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/sandbox/glob-segment.d.ts +8 -1
- package/dist/sandbox/glob-segment.d.ts.map +1 -1
- package/dist/sandbox/glob-segment.js +28 -9
- package/dist/sandbox/glob-segment.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/store/session-log/index.d.ts +1 -0
- package/dist/store/session-log/index.d.ts.map +1 -1
- package/dist/store/session-log/index.js +1 -0
- package/dist/store/session-log/index.js.map +1 -1
- package/dist/store/session-log/turn-outcome.d.ts +37 -0
- package/dist/store/session-log/turn-outcome.d.ts.map +1 -0
- package/dist/store/session-log/turn-outcome.js +77 -0
- package/dist/store/session-log/turn-outcome.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 +11 -2
- 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/edit-apply.d.ts.map +1 -1
- package/dist/tools/builtins/edit-apply.js +24 -3
- package/dist/tools/builtins/edit-apply.js.map +1 -1
- package/dist/tools/builtins/edit.d.ts +31 -17
- package/dist/tools/builtins/edit.d.ts.map +1 -1
- package/dist/tools/builtins/edit.js +92 -27
- package/dist/tools/builtins/edit.js.map +1 -1
- package/dist/tools/builtins/glob.js +1 -1
- package/dist/tools/builtins/glob.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 +148 -21
- package/dist/tools/builtins/grep.js.map +1 -1
- package/dist/tools/builtins/notebook-guard.d.ts +19 -0
- package/dist/tools/builtins/notebook-guard.d.ts.map +1 -0
- package/dist/tools/builtins/notebook-guard.js +75 -0
- package/dist/tools/builtins/notebook-guard.js.map +1 -0
- package/dist/tools/builtins/read-render.d.ts.map +1 -1
- package/dist/tools/builtins/read-render.js +8 -2
- package/dist/tools/builtins/read-render.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/tools/builtins/write-file.d.ts.map +1 -1
- package/dist/tools/builtins/write-file.js +14 -1
- package/dist/tools/builtins/write-file.js.map +1 -1
- package/dist/tools/coordinator/agent.d.ts.map +1 -1
- package/dist/tools/coordinator/agent.js +25 -6
- package/dist/tools/coordinator/agent.js.map +1 -1
- package/dist/tools/coordinator/index.d.ts.map +1 -1
- package/dist/tools/coordinator/index.js +6 -1
- package/dist/tools/coordinator/index.js.map +1 -1
- package/dist/tools/coordinator/outcome-record.d.ts +22 -0
- package/dist/tools/coordinator/outcome-record.d.ts.map +1 -0
- package/dist/tools/coordinator/outcome-record.js +91 -0
- package/dist/tools/coordinator/outcome-record.js.map +1 -0
- package/dist/tools/defineTool.d.ts +2 -0
- package/dist/tools/defineTool.d.ts.map +1 -1
- package/dist/tools/defineTool.js +1 -0
- package/dist/tools/defineTool.js.map +1 -1
- package/dist/tools/file-read-tracker.d.ts +23 -2
- package/dist/tools/file-read-tracker.d.ts.map +1 -1
- package/dist/tools/file-read-tracker.js +37 -2
- package/dist/tools/file-read-tracker.js.map +1 -1
- package/dist/tools/untrusted-envelope.d.ts +6 -0
- package/dist/tools/untrusted-envelope.d.ts.map +1 -1
- package/dist/tools/untrusted-envelope.js +22 -1
- package/dist/tools/untrusted-envelope.js.map +1 -1
- package/dist/toolsets/manager.d.ts.map +1 -1
- package/dist/toolsets/manager.js +3 -4
- package/dist/toolsets/manager.js.map +1 -1
- package/dist/toolsets/validation-issues.d.ts +27 -0
- package/dist/toolsets/validation-issues.d.ts.map +1 -0
- package/dist/toolsets/validation-issues.js +83 -0
- package/dist/toolsets/validation-issues.js.map +1 -0
- package/dist/turn/memory-recall.d.ts +13 -0
- package/dist/turn/memory-recall.d.ts.map +1 -1
- package/dist/turn/memory-recall.js +18 -0
- package/dist/turn/memory-recall.js.map +1 -1
- package/dist/types/agent/base.d.ts +19 -0
- package/dist/types/agent/base.d.ts.map +1 -1
- package/dist/types/agent/query.d.ts +2 -0
- package/dist/types/agent/query.d.ts.map +1 -1
- package/dist/types/common/index.d.ts +17 -0
- package/dist/types/common/index.d.ts.map +1 -1
- package/dist/types/common/index.js +8 -1
- package/dist/types/common/index.js.map +1 -1
- package/dist/types/hitl/index.d.ts +22 -0
- package/dist/types/hitl/index.d.ts.map +1 -1
- package/dist/types/hitl/index.js.map +1 -1
- package/dist/types/session/config.d.ts +20 -0
- package/dist/types/session/config.d.ts.map +1 -1
- package/dist/types/session/events.d.ts +3 -3
- package/dist/types/session/events.d.ts.map +1 -1
- package/dist/types/session/index.d.ts +1 -0
- package/dist/types/session/index.d.ts.map +1 -1
- package/dist/types/session/index.js +1 -0
- package/dist/types/session/index.js.map +1 -1
- package/dist/types/session/outcome.d.ts +95 -0
- package/dist/types/session/outcome.d.ts.map +1 -0
- package/dist/types/session/outcome.js +45 -0
- package/dist/types/session/outcome.js.map +1 -0
- package/dist/types/session/prepare-step.d.ts +2 -0
- package/dist/types/session/prepare-step.d.ts.map +1 -1
- package/dist/types/session/records.d.ts +167 -2
- package/dist/types/session/records.d.ts.map +1 -1
- package/dist/types/session/records.js +42 -0
- package/dist/types/session/records.js.map +1 -1
- package/dist/types/session/turn.d.ts +18 -0
- package/dist/types/session/turn.d.ts.map +1 -1
- package/dist/types/session/turn.js.map +1 -1
- package/dist/types/tool/index.d.ts +30 -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 +6 -0
- package/dist/types/tool/presentation.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/agents/QueryAgent.ts +5 -0
- package/src/agents/examples/SupervisorAgent.ts +1 -0
- package/src/agents/explore.ts +2 -1
- package/src/compaction/index.ts +1 -0
- package/src/compaction/model-summary.ts +2 -2
- package/src/compaction/summary.ts +30 -2
- package/src/config/runtime.ts +15 -0
- package/src/execution/workflow/runtime.ts +4 -0
- package/src/execution/workflow/types.ts +9 -0
- package/src/manager/agent/child-session.ts +36 -0
- package/src/manager/agent/lifecycle.ts +9 -0
- package/src/manager/session/turn-recorder.ts +115 -30
- package/src/pricing/rates.source.json +1 -1
- package/src/prompt/coding-agent-doctrine.ts +5 -3
- package/src/provider/fallback.ts +6 -1
- package/src/provider/retry.ts +51 -0
- package/src/public-runtime.ts +10 -1
- package/src/public-tools.ts +6 -1
- package/src/runtime/query/auto-review/classifier.ts +21 -5
- package/src/runtime/query/auto-review/prompt.ts +2 -0
- package/src/runtime/query/auto-review/script-evidence.ts +618 -0
- package/src/runtime/query/checkpoint.ts +53 -1
- package/src/runtime/query/context.ts +33 -6
- package/src/runtime/query/executor.ts +183 -21
- package/src/runtime/query/finalize-turn.ts +8 -1
- package/src/runtime/query/index.ts +19 -0
- package/src/runtime/query/iteration/index.ts +167 -14
- package/src/runtime/query/iteration/phases/compaction.ts +53 -3
- package/src/runtime/query/iteration/phases/context.ts +6 -0
- package/src/runtime/query/iteration/stream-turn.ts +3 -3
- package/src/runtime/query/prelude-lease.ts +19 -0
- package/src/runtime/query/prepare-turn.ts +29 -12
- package/src/runtime/query/review-policy.ts +33 -0
- package/src/runtime/query/session-storage.ts +42 -3
- package/src/runtime/query/tooling.ts +3 -0
- package/src/runtime/query/turn/completion-audit.ts +76 -5
- package/src/runtime/query/turn/outcome-ledger.ts +633 -0
- package/src/sandbox/file-walk.ts +41 -4
- package/src/sandbox/glob-segment.ts +48 -24
- 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/store/session-log/index.ts +7 -0
- package/src/store/session-log/turn-outcome.ts +84 -0
- package/src/tools/admit-facts.ts +1 -0
- package/src/tools/builtins/bash.ts +12 -2
- package/src/tools/builtins/bounded-file-read.ts +59 -2
- package/src/tools/builtins/edit-apply.ts +26 -4
- package/src/tools/builtins/edit.ts +106 -41
- package/src/tools/builtins/glob.ts +1 -1
- package/src/tools/builtins/grep.ts +173 -27
- package/src/tools/builtins/notebook-guard.ts +76 -0
- package/src/tools/builtins/read-render.ts +12 -2
- package/src/tools/builtins/search-support.ts +62 -0
- package/src/tools/builtins/write-file.ts +12 -1
- package/src/tools/coordinator/agent.ts +31 -7
- package/src/tools/coordinator/index.ts +6 -1
- package/src/tools/coordinator/outcome-record.ts +93 -0
- package/src/tools/defineTool.ts +3 -0
- package/src/tools/file-read-tracker.ts +51 -2
- package/src/tools/untrusted-envelope.ts +23 -1
- package/src/toolsets/manager.ts +6 -10
- package/src/toolsets/validation-issues.ts +97 -0
- package/src/turn/memory-recall.ts +28 -0
- package/src/types/agent/base.ts +20 -0
- package/src/types/agent/query.ts +2 -0
- package/src/types/common/index.ts +25 -1
- package/src/types/hitl/index.ts +27 -0
- package/src/types/session/config.ts +20 -0
- package/src/types/session/events.ts +3 -3
- package/src/types/session/index.ts +1 -0
- package/src/types/session/outcome.ts +139 -0
- package/src/types/session/prepare-step.ts +2 -0
- package/src/types/session/records.ts +67 -1
- package/src/types/session/turn.ts +19 -0
- package/src/types/tool/index.ts +30 -0
- package/src/types/tool/presentation.ts +6 -0
|
@@ -31,11 +31,12 @@ import { getTracer } from '../../../telemetry/runtime-accessors.js'
|
|
|
31
31
|
import { STRUCTURED_OUTPUT_TOOL_NAME } from '../../../tools/builtins/structuredOutput.js'
|
|
32
32
|
import type { ToolManager } from '../../../toolsets/manager.js'
|
|
33
33
|
import { RECALLED_MEMORY_ENTRY_KEY, recalledMemoryIds } from '../../../turn/memory-recall.js'
|
|
34
|
-
import type
|
|
34
|
+
import { type CostInfo, type TokenUsage, promptSizeOf } from '../../../types/common/index.js'
|
|
35
35
|
import { NamzuError } from '../../../types/errors/index.js'
|
|
36
36
|
import type { MessageId, SessionId, TurnId } from '../../../types/ids/index.js'
|
|
37
37
|
import {
|
|
38
38
|
type Message,
|
|
39
|
+
type ToolCall,
|
|
39
40
|
type UserMessage,
|
|
40
41
|
createAssistantMessage,
|
|
41
42
|
createRuntimeContextMessage,
|
|
@@ -54,6 +55,7 @@ import type {
|
|
|
54
55
|
StepProvenance,
|
|
55
56
|
StepResult,
|
|
56
57
|
StopReason,
|
|
58
|
+
TurnOutcome,
|
|
57
59
|
} from '../../../types/session/index.js'
|
|
58
60
|
import type { LLMToolSchema } from '../../../types/tool/index.js'
|
|
59
61
|
import { toErrorMessage } from '../../../utils/error.js'
|
|
@@ -79,7 +81,8 @@ import {
|
|
|
79
81
|
} from '../request-rich-content.js'
|
|
80
82
|
import { withoutVoidAssistantTurns } from '../request-void-turns.js'
|
|
81
83
|
import { formatSteeringNote, isOperatorUserMessage } from '../steering.js'
|
|
82
|
-
import {
|
|
84
|
+
import { CompletionAudit } from '../turn/completion-audit.js'
|
|
85
|
+
import { type OutcomeCall, OutcomeLedger, type PendingOutcomeCall } from '../turn/outcome-ledger.js'
|
|
83
86
|
import {
|
|
84
87
|
FORCED_STEP_MESSAGE,
|
|
85
88
|
REASONING_CAPPED_MESSAGE,
|
|
@@ -107,6 +110,7 @@ import {
|
|
|
107
110
|
measureContext,
|
|
108
111
|
relieveOverflow,
|
|
109
112
|
runCompactionCheck,
|
|
113
|
+
runStaleResultClear,
|
|
110
114
|
stopOnRefill,
|
|
111
115
|
} from './phases/compaction.js'
|
|
112
116
|
import { runHandoffPause } from './phases/handoff.js'
|
|
@@ -333,6 +337,9 @@ export class IterationOrchestrator {
|
|
|
333
337
|
private readonly stallMonitor?: StallMonitor
|
|
334
338
|
/** Holds the turn back once when it changed files and checked nothing afterwards. */
|
|
335
339
|
private readonly completionAudit?: CompletionAudit
|
|
340
|
+
/** What the turn changed and what checked it; see {@link OutcomeLedger}. Kept whether or not the host asked. */
|
|
341
|
+
private readonly outcomeLedger: OutcomeLedger
|
|
342
|
+
private outcomeHydrated = false
|
|
336
343
|
/** The next step must call a tool, at low effort: set by the stall monitor or a capped step. */
|
|
337
344
|
private forceActionNextStep = false
|
|
338
345
|
/** Steps in a row cut at the per-step reasoning cap; past a few the cap stops applying. */
|
|
@@ -359,6 +366,10 @@ export class IterationOrchestrator {
|
|
|
359
366
|
private turnEndFired = false
|
|
360
367
|
/** A hook already kept this turn going once: it is asked again with `stop_hook_active`. */
|
|
361
368
|
private stopHookActive = false
|
|
369
|
+
/** End-of-turn refusals honoured since the last tool call. */
|
|
370
|
+
private consecutiveHookRefusals = 0
|
|
371
|
+
/** The cap was reached and its notice recorded; refusals are ignored for the rest of the turn. */
|
|
372
|
+
private hookRefusalCapReached = false
|
|
362
373
|
get turnEndHooksFired(): boolean {
|
|
363
374
|
return this.turnEndFired
|
|
364
375
|
}
|
|
@@ -371,12 +382,22 @@ export class IterationOrchestrator {
|
|
|
371
382
|
* are returned, joined, as feedback for the model. A hook that asks for the
|
|
372
383
|
* turn to halt wins over any such feedback (`takeHalt`). With nothing asked,
|
|
373
384
|
* the events have fired for this turn and the finalizer does not repeat them.
|
|
385
|
+
*
|
|
386
|
+
* Refusals are counted in a row (a tool call in between starts the count
|
|
387
|
+
* over). Once `maxHookRefusals` have been honoured the next one is not: the
|
|
388
|
+
* turn ends, with one notice that names the hook.
|
|
374
389
|
*/
|
|
375
390
|
private async *askStopHooks(): AsyncGenerator<SessionEvent, string | undefined> {
|
|
376
391
|
const manager = this.ctx.pluginManager
|
|
377
392
|
if (!manager) return undefined
|
|
378
393
|
const recorder = this.ctx.recorder
|
|
379
394
|
const feedback: string[] = []
|
|
395
|
+
const refusers: string[] = []
|
|
396
|
+
const emit = async (event: SessionEvent): Promise<void> => {
|
|
397
|
+
if (event.type === 'plugin_hook_completed' && event.result.action === 'skip')
|
|
398
|
+
refusers.push(String(event.pluginId))
|
|
399
|
+
await this.ctx.emitEvent(event)
|
|
400
|
+
}
|
|
380
401
|
const ask = async (
|
|
381
402
|
event: 'turn_end' | 'subagent_stop',
|
|
382
403
|
extra: {
|
|
@@ -394,7 +415,7 @@ export class IterationOrchestrator {
|
|
|
394
415
|
...(this.stopHookActive ? { stopHookActive: true } : {}),
|
|
395
416
|
signal: this.ctx.abortController.signal,
|
|
396
417
|
},
|
|
397
|
-
|
|
418
|
+
emit,
|
|
398
419
|
)
|
|
399
420
|
applyLifecycleHookResults(event, results)
|
|
400
421
|
for (const result of results) {
|
|
@@ -414,10 +435,35 @@ export class IterationOrchestrator {
|
|
|
414
435
|
})
|
|
415
436
|
yield* this.ctx.drainPending()
|
|
416
437
|
}
|
|
417
|
-
|
|
438
|
+
// Once the cap has been reached, refusals stay ignored for the rest of the turn,
|
|
439
|
+
// whatever a tool call does to the count; a halt was already passed on above.
|
|
440
|
+
if (feedback.length === 0 || this.hookRefusalCapReached) {
|
|
441
|
+
this.turnEndFired = true
|
|
442
|
+
return undefined
|
|
443
|
+
}
|
|
444
|
+
const limit = this.ctx.turnConfig.maxHookRefusals ?? 0
|
|
445
|
+
if (limit > 0 && this.consecutiveHookRefusals >= limit) {
|
|
446
|
+
// The turn has been kept going `limit` times in a row. A hook that
|
|
447
|
+
// still refuses is not going to stop; say so once, and let the turn end.
|
|
418
448
|
this.turnEndFired = true
|
|
449
|
+
if (!this.hookRefusalCapReached) {
|
|
450
|
+
this.hookRefusalCapReached = true
|
|
451
|
+
const names = [...new Set(refusers)]
|
|
452
|
+
const who = names.length > 0 ? names.map((n) => `"${n}"`).join(', ') : 'A hook'
|
|
453
|
+
recorder.pushMessage(
|
|
454
|
+
createRuntimeContextMessage(
|
|
455
|
+
`${who} asked for more before this turn could end ${limit} times in a row. Namzu is ending the turn anyway and will not honour further requests from it this turn.`,
|
|
456
|
+
'hook-feedback',
|
|
457
|
+
),
|
|
458
|
+
)
|
|
459
|
+
this.ctx.log.warn('Hook refusal limit reached — ending the turn', {
|
|
460
|
+
[NAMZU.TURN_ID]: recorder.turnId,
|
|
461
|
+
'namzu.runtime.limit': limit,
|
|
462
|
+
})
|
|
463
|
+
}
|
|
419
464
|
return undefined
|
|
420
465
|
}
|
|
466
|
+
this.consecutiveHookRefusals++
|
|
421
467
|
this.stopHookActive = true
|
|
422
468
|
return feedback.join('\n')
|
|
423
469
|
}
|
|
@@ -436,6 +482,13 @@ export class IterationOrchestrator {
|
|
|
436
482
|
},
|
|
437
483
|
}
|
|
438
484
|
if (ctx.turnConfig.completionAudit === true) this.completionAudit = new CompletionAudit()
|
|
485
|
+
this.outcomeLedger = new OutcomeLedger(ctx.turnConfig.outcome)
|
|
486
|
+
ctx.checkpointMgr.setOutcomeSource?.(() => {
|
|
487
|
+
this.hydrateOutcome()
|
|
488
|
+
return this.outcomeLedger.recorded
|
|
489
|
+
? (this.outcomeLedger.snapshot() as unknown as Record<string, unknown>)
|
|
490
|
+
: undefined
|
|
491
|
+
})
|
|
439
492
|
if (ctx.turnConfig.reasoning?.stall !== false) {
|
|
440
493
|
this.stallMonitor = new StallMonitor(ctx.turnConfig.reasoning?.stall || undefined)
|
|
441
494
|
}
|
|
@@ -817,6 +870,7 @@ export class IterationOrchestrator {
|
|
|
817
870
|
// edge BEFORE compaction runs (so the refreshed slot is what
|
|
818
871
|
// compaction preserves). No-op when no provider is configured.
|
|
819
872
|
await refreshWorkingMemory(this.ctx)
|
|
873
|
+
if (iterationNum === 1) await runStaleResultClear(this.ctx)
|
|
820
874
|
await runCompactionCheck(this.ctx)
|
|
821
875
|
yield* this.ctx.drainPending()
|
|
822
876
|
if (this.stopForCompactionBreaker()) break
|
|
@@ -1340,7 +1394,7 @@ export class IterationOrchestrator {
|
|
|
1340
1394
|
'namzu.runtime.has_content':
|
|
1341
1395
|
response.message.content !== null && response.message.content.length > 0,
|
|
1342
1396
|
'namzu.runtime.tool_call_count': response.message.toolCalls?.length ?? 0,
|
|
1343
|
-
[GENAI.USAGE_INPUT_TOKENS]: response.usage
|
|
1397
|
+
[GENAI.USAGE_INPUT_TOKENS]: promptSizeOf(response.usage),
|
|
1344
1398
|
[GENAI.USAGE_OUTPUT_TOKENS]: response.usage.completionTokens,
|
|
1345
1399
|
'namzu.usage.total_tokens': recorder.tokenUsage.totalTokens,
|
|
1346
1400
|
'namzu.runtime.total_cost': recorder.costInfo.totalCost,
|
|
@@ -1457,7 +1511,7 @@ export class IterationOrchestrator {
|
|
|
1457
1511
|
yield* this.ctx.drainPending()
|
|
1458
1512
|
|
|
1459
1513
|
iterSpan.setAttributes({
|
|
1460
|
-
[GENAI.USAGE_INPUT_TOKENS]: response.usage
|
|
1514
|
+
[GENAI.USAGE_INPUT_TOKENS]: promptSizeOf(response.usage),
|
|
1461
1515
|
[GENAI.USAGE_OUTPUT_TOKENS]: response.usage.completionTokens,
|
|
1462
1516
|
})
|
|
1463
1517
|
iterSpan.setStatus({ code: SpanStatusCode.OK })
|
|
@@ -1951,17 +2005,15 @@ export class IterationOrchestrator {
|
|
|
1951
2005
|
// back to the model and the turn goes on.
|
|
1952
2006
|
// Files were changed and nothing checked them afterwards: ask once
|
|
1953
2007
|
// for a check before the turn is allowed to end.
|
|
1954
|
-
|
|
1955
|
-
!forceFinalize &&
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
) {
|
|
2008
|
+
const auditReminder =
|
|
2009
|
+
!forceFinalize && closingStopReason === undefined
|
|
2010
|
+
? this.completionAudit?.takeReminder(() => this.outcomeLedger.peek())
|
|
2011
|
+
: undefined
|
|
2012
|
+
if (auditReminder !== undefined) {
|
|
1959
2013
|
this.ctx.log.info('Files changed and nothing checked them — asking for a check', {
|
|
1960
2014
|
[NAMZU.TURN_ID]: recorder.turnId,
|
|
1961
2015
|
})
|
|
1962
|
-
recorder.pushMessage(
|
|
1963
|
-
createRuntimeContextMessage(COMPLETION_AUDIT_MESSAGE, 'progress-nudge'),
|
|
1964
|
-
)
|
|
2016
|
+
recorder.pushMessage(createRuntimeContextMessage(auditReminder, 'progress-nudge'))
|
|
1965
2017
|
continue
|
|
1966
2018
|
}
|
|
1967
2019
|
if (!forceFinalize && closingStopReason === undefined) {
|
|
@@ -1989,6 +2041,10 @@ export class IterationOrchestrator {
|
|
|
1989
2041
|
break
|
|
1990
2042
|
}
|
|
1991
2043
|
|
|
2044
|
+
// A real tool call: whatever a hook refused before it is behind us.
|
|
2045
|
+
this.consecutiveHookRefusals = 0
|
|
2046
|
+
|
|
2047
|
+
await this.outcomeBefore(response)
|
|
1992
2048
|
const reviewOutcome = yield* runToolReview(this.ctx, response, iterationNum)
|
|
1993
2049
|
|
|
1994
2050
|
// The step record is built even for a rejected batch: a turn that
|
|
@@ -2013,6 +2069,7 @@ export class IterationOrchestrator {
|
|
|
2013
2069
|
this.reasoningOnlyRetried = false
|
|
2014
2070
|
this.observeStall(response, reviewOutcome.results)
|
|
2015
2071
|
this.observeAudit(response, reviewOutcome.results)
|
|
2072
|
+
await this.outcomeObserve(iterationNum, response, reviewOutcome.results)
|
|
2016
2073
|
|
|
2017
2074
|
if (reviewOutcome.decision === 'stop') {
|
|
2018
2075
|
return
|
|
@@ -3334,6 +3391,102 @@ export class IterationOrchestrator {
|
|
|
3334
3391
|
}
|
|
3335
3392
|
}
|
|
3336
3393
|
|
|
3394
|
+
/**
|
|
3395
|
+
* Take back the outcome record a checkpoint kept. Done at first use rather
|
|
3396
|
+
* than at construction: the checkpoint is read after this is built. A turn
|
|
3397
|
+
* resumed from a checkpoint with no record cannot say what came before it,
|
|
3398
|
+
* so it keeps none.
|
|
3399
|
+
*/
|
|
3400
|
+
private hydrateOutcome(): void {
|
|
3401
|
+
if (this.outcomeHydrated) return
|
|
3402
|
+
this.outcomeHydrated = true
|
|
3403
|
+
const restored = this.ctx.checkpointMgr.restoredOutcome
|
|
3404
|
+
if (restored?.resumed !== true) return
|
|
3405
|
+
if (restored.snapshot === undefined) this.outcomeLedger.markUnrecorded()
|
|
3406
|
+
else this.outcomeLedger.restore(restored.snapshot)
|
|
3407
|
+
}
|
|
3408
|
+
|
|
3409
|
+
/** Whether a call is one the outcome record may treat as only looking. */
|
|
3410
|
+
private lookedOnly(toolName: string, input: unknown): boolean {
|
|
3411
|
+
try {
|
|
3412
|
+
return !callChangedState(toolName, this.ctx.tools.get(toolName)?.isReadOnly?.(input), false)
|
|
3413
|
+
} catch {
|
|
3414
|
+
return false
|
|
3415
|
+
}
|
|
3416
|
+
}
|
|
3417
|
+
|
|
3418
|
+
private parsedInput(call: ToolCall | undefined): unknown {
|
|
3419
|
+
try {
|
|
3420
|
+
return call ? JSON.parse(call.function.arguments) : undefined
|
|
3421
|
+
} catch {
|
|
3422
|
+
return undefined
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
|
|
3426
|
+
/**
|
|
3427
|
+
* Before a step's calls run: the first one that could change the workspace
|
|
3428
|
+
* takes the starting fingerprint. A failure here must not fail the turn.
|
|
3429
|
+
*/
|
|
3430
|
+
async outcomeBefore(response: ChatCompletionResponse): Promise<void> {
|
|
3431
|
+
try {
|
|
3432
|
+
this.hydrateOutcome()
|
|
3433
|
+
const pending: PendingOutcomeCall[] = (response.message.toolCalls ?? []).map((call) => {
|
|
3434
|
+
const input = this.parsedInput(call)
|
|
3435
|
+
return {
|
|
3436
|
+
toolName: call.function.name,
|
|
3437
|
+
input,
|
|
3438
|
+
readOnly: this.lookedOnly(call.function.name, input),
|
|
3439
|
+
}
|
|
3440
|
+
})
|
|
3441
|
+
await this.outcomeLedger.before(pending)
|
|
3442
|
+
} catch (err) {
|
|
3443
|
+
this.ctx.log.warn('The outcome record could not take its starting point', {
|
|
3444
|
+
'exception.message': toErrorMessage(err),
|
|
3445
|
+
})
|
|
3446
|
+
}
|
|
3447
|
+
}
|
|
3448
|
+
|
|
3449
|
+
/** Feed the outcome record the calls of one finished step. */
|
|
3450
|
+
async outcomeObserve(
|
|
3451
|
+
step: number,
|
|
3452
|
+
response: ChatCompletionResponse,
|
|
3453
|
+
results: readonly ToolCallOutcome[],
|
|
3454
|
+
): Promise<void> {
|
|
3455
|
+
try {
|
|
3456
|
+
this.hydrateOutcome()
|
|
3457
|
+
const calls = new Map((response.message.toolCalls ?? []).map((c) => [c.id, c]))
|
|
3458
|
+
const seen: OutcomeCall[] = results.map((result) => {
|
|
3459
|
+
const input = this.parsedInput(calls.get(result.toolCallId))
|
|
3460
|
+
return {
|
|
3461
|
+
toolName: result.toolName,
|
|
3462
|
+
input,
|
|
3463
|
+
isError: result.isError,
|
|
3464
|
+
...(result.skipped ? { skipped: true } : {}),
|
|
3465
|
+
readOnly: this.lookedOnly(result.toolName, input),
|
|
3466
|
+
...(result.facts ? { facts: result.facts } : {}),
|
|
3467
|
+
}
|
|
3468
|
+
})
|
|
3469
|
+
await this.outcomeLedger.observe(step, seen)
|
|
3470
|
+
} catch (err) {
|
|
3471
|
+
this.ctx.log.warn('The outcome record could not read a step', {
|
|
3472
|
+
'exception.message': toErrorMessage(err),
|
|
3473
|
+
})
|
|
3474
|
+
}
|
|
3475
|
+
}
|
|
3476
|
+
|
|
3477
|
+
/** The outcome record as the turn leaves it; `undefined` when none was kept. */
|
|
3478
|
+
async takeOutcome(): Promise<TurnOutcome | undefined> {
|
|
3479
|
+
try {
|
|
3480
|
+
this.hydrateOutcome()
|
|
3481
|
+
return await this.outcomeLedger.finish()
|
|
3482
|
+
} catch (err) {
|
|
3483
|
+
this.ctx.log.warn('The outcome record could not be settled', {
|
|
3484
|
+
'exception.message': toErrorMessage(err),
|
|
3485
|
+
})
|
|
3486
|
+
return undefined
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3489
|
+
|
|
3337
3490
|
/** Feed the completion audit the calls of one finished step. */
|
|
3338
3491
|
private observeAudit(
|
|
3339
3492
|
response: ChatCompletionResponse,
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
isCompactionMessage,
|
|
22
22
|
} from '../../../../compaction/summary.js'
|
|
23
23
|
import { estimateMessagesTokens } from '../../../../compaction/token-estimate.js'
|
|
24
|
+
import { clearStaleToolResults } from '../../../../compaction/tool-result-editing.js'
|
|
24
25
|
import { buildVerifiedSummaryWithBoundedProvider } from '../../../../compaction/verifier.js'
|
|
25
26
|
import type { CompactionConfig } from '../../../../config/runtime.js'
|
|
26
27
|
import { CHARS_PER_TOKEN } from '../../../../constants/limits.js'
|
|
@@ -172,7 +173,7 @@ async function commitToolResultClear(
|
|
|
172
173
|
plan: ToolResultClearPlan,
|
|
173
174
|
contextWindowTokens: number,
|
|
174
175
|
windowSource: 'config' | 'provider' | 'model-table' | 'default',
|
|
175
|
-
reason: 'threshold' | 'overflow',
|
|
176
|
+
reason: 'threshold' | 'overflow' | 'manual',
|
|
176
177
|
): Promise<void> {
|
|
177
178
|
// A shorter replacement also removes evidence. Preserve its original
|
|
178
179
|
// content in the same archive used by whole-message compaction before
|
|
@@ -184,7 +185,56 @@ async function commitToolResultClear(
|
|
|
184
185
|
ctx.recorder.clearLastPromptTokens?.()
|
|
185
186
|
await emitToolResultClear(ctx, plan)
|
|
186
187
|
await emitContextUsageSnapshot(ctx, estimateTokens(ctx), contextWindowTokens, windowSource)
|
|
187
|
-
|
|
188
|
+
// A host-requested pass is not the kernel's own relief of a full context,
|
|
189
|
+
// so it must not reset the breaker's failure streak.
|
|
190
|
+
if (reason !== 'manual') compactionBreaker(ctx)?.recordRelief()
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* The first request of a turn, for a host that said the prompt cache is gone:
|
|
195
|
+
* shed old, large tool results now, since the prefix is re-read at full price
|
|
196
|
+
* either way. Does nothing unless `clearStaleToolResultsAtStart` is set and the
|
|
197
|
+
* clear would save at least its floor. Reuses the in-turn clear, so the same
|
|
198
|
+
* results are protected, the originals are recorded before they are replaced,
|
|
199
|
+
* and the host gets the same `compaction_tool_results_cleared` event. It is
|
|
200
|
+
* not a threshold pass: the `pre_compact` / `post_compact` hooks, the
|
|
201
|
+
* near-limit and busy guards and the circuit breaker's relief count do not
|
|
202
|
+
* see it.
|
|
203
|
+
*/
|
|
204
|
+
export async function runStaleResultClear(ctx: IterationContext): Promise<void> {
|
|
205
|
+
const config = ctx.compactionConfig
|
|
206
|
+
const option = config?.clearStaleToolResultsAtStart
|
|
207
|
+
if (!config || !option) return
|
|
208
|
+
if (config.strategy === 'disabled' || config.clearToolResults === false) return
|
|
209
|
+
// The pass promises the originals stay in the log. With the log switched
|
|
210
|
+
// off, replacing them would lose them, so the pass does not run.
|
|
211
|
+
if (config.recordShedHistory === false) return
|
|
212
|
+
|
|
213
|
+
const live = ctx.recorder.messages
|
|
214
|
+
const edit = clearStaleToolResults(live, {
|
|
215
|
+
keepRecentToolResults: option.keepRecentToolResults ?? config.keepRecentToolResults,
|
|
216
|
+
minCharsToClear: config.minToolResultCharsToClear,
|
|
217
|
+
...(config.preserveToolResultsFrom ? { preserveTools: config.preserveToolResultsFrom } : {}),
|
|
218
|
+
})
|
|
219
|
+
if (edit.clearedCount === 0) return
|
|
220
|
+
const reclaimedTokens = estimateMessagesTokens(live) - estimateMessagesTokens(edit.messages)
|
|
221
|
+
if (reclaimedTokens < option.minSavedTokens) return
|
|
222
|
+
|
|
223
|
+
const window = activeContextWindow(ctx)
|
|
224
|
+
await commitToolResultClear(
|
|
225
|
+
ctx,
|
|
226
|
+
{
|
|
227
|
+
kind: 'cleared',
|
|
228
|
+
messages: edit.messages,
|
|
229
|
+
clearedCount: edit.clearedCount,
|
|
230
|
+
charsReclaimed: edit.charsReclaimed,
|
|
231
|
+
reclaimedTokens,
|
|
232
|
+
reliefWasEnough: true,
|
|
233
|
+
},
|
|
234
|
+
window.tokens,
|
|
235
|
+
window.source,
|
|
236
|
+
'manual',
|
|
237
|
+
)
|
|
188
238
|
}
|
|
189
239
|
|
|
190
240
|
/**
|
|
@@ -628,7 +678,7 @@ async function recordShed(
|
|
|
628
678
|
ctx: IterationContext,
|
|
629
679
|
before: readonly Message[],
|
|
630
680
|
after: readonly Message[],
|
|
631
|
-
reason: 'threshold' | 'overflow',
|
|
681
|
+
reason: 'threshold' | 'overflow' | 'manual',
|
|
632
682
|
): Promise<void> {
|
|
633
683
|
if (ctx.compactionConfig?.recordShedHistory === false) return
|
|
634
684
|
|
|
@@ -471,6 +471,12 @@ function resumeContextOf(ctx: IterationContext, scope?: AskScope): ResumeContext
|
|
|
471
471
|
return {
|
|
472
472
|
transcript: () => ctx.recorder.messages,
|
|
473
473
|
...(signal ? { signal } : {}),
|
|
474
|
+
...(typeof ctx.toolExecutor?.launchedScripts === 'function'
|
|
475
|
+
? {
|
|
476
|
+
launchedScripts: (callId, line, dialect) =>
|
|
477
|
+
ctx.toolExecutor.launchedScripts(callId, line, dialect),
|
|
478
|
+
}
|
|
479
|
+
: {}),
|
|
474
480
|
}
|
|
475
481
|
}
|
|
476
482
|
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
recordTokenUsage,
|
|
22
22
|
} from '../../../telemetry/metrics.js'
|
|
23
23
|
import { getTracer } from '../../../telemetry/runtime-accessors.js'
|
|
24
|
-
import { mergeTokenUsage } from '../../../types/common/index.js'
|
|
24
|
+
import { mergeTokenUsage, promptSizeOf } from '../../../types/common/index.js'
|
|
25
25
|
import { NamzuError } from '../../../types/errors/index.js'
|
|
26
26
|
import type { ToolUseId } from '../../../types/ids/index.js'
|
|
27
27
|
import type {
|
|
@@ -196,7 +196,7 @@ async function settleCancelledTurn(args: {
|
|
|
196
196
|
recordTokenUsage(args.model, args.usage)
|
|
197
197
|
recordModelDuration(args.model, Date.now() - args.startedAt)
|
|
198
198
|
args.span.setAttributes({
|
|
199
|
-
[GENAI.USAGE_INPUT_TOKENS]: args.usage
|
|
199
|
+
[GENAI.USAGE_INPUT_TOKENS]: promptSizeOf(args.usage),
|
|
200
200
|
[GENAI.USAGE_OUTPUT_TOKENS]: args.usage.completionTokens,
|
|
201
201
|
[NAMZU.CACHE_READ_TOKENS]: args.usage.cachedTokens ?? 0,
|
|
202
202
|
[NAMZU.CACHE_WRITE_TOKENS]: args.usage.cacheWriteTokens ?? 0,
|
|
@@ -1128,7 +1128,7 @@ export async function* streamProviderTurn(
|
|
|
1128
1128
|
[GENAI.RESPONSE_MODEL]: response.model,
|
|
1129
1129
|
[GENAI.RESPONSE_ID]: response.id,
|
|
1130
1130
|
[GENAI.RESPONSE_FINISH_REASONS]: [response.finishReason],
|
|
1131
|
-
[GENAI.USAGE_INPUT_TOKENS]: usage
|
|
1131
|
+
[GENAI.USAGE_INPUT_TOKENS]: promptSizeOf(usage),
|
|
1132
1132
|
[GENAI.USAGE_OUTPUT_TOKENS]: usage.completionTokens,
|
|
1133
1133
|
[NAMZU.CACHE_READ_TOKENS]: usage.cachedTokens ?? 0,
|
|
1134
1134
|
[NAMZU.CACHE_WRITE_TOKENS]: usage.cacheWriteTokens ?? 0,
|
|
@@ -99,6 +99,17 @@ export class PreludeSessionLease {
|
|
|
99
99
|
}
|
|
100
100
|
return
|
|
101
101
|
}
|
|
102
|
+
// A renewal is two durable replaces and four fsyncs; the prelude asks
|
|
103
|
+
// three times in a few milliseconds. While the holding has more than
|
|
104
|
+
// half its time left the heartbeat has nothing to add, and what the
|
|
105
|
+
// question is really about, whether anyone took the session, is
|
|
106
|
+
// answered by reading the fence, as it is for a supplied lease.
|
|
107
|
+
await this.#renewal
|
|
108
|
+
if (this.#renewalError !== undefined) throw this.#renewalError
|
|
109
|
+
if (this.#current.expiresAt - Date.now() >= DEFAULT_TURN_LEASE_TTL_MS / 2) {
|
|
110
|
+
const held = await this.#log.lease()
|
|
111
|
+
if (held?.fence === this.#current.fence && held.holder === this.#current.holder) return
|
|
112
|
+
}
|
|
102
113
|
this.#queueRenewal()
|
|
103
114
|
await this.#renewal
|
|
104
115
|
if (this.#renewalError !== undefined) throw this.#renewalError
|
|
@@ -107,6 +118,14 @@ export class PreludeSessionLease {
|
|
|
107
118
|
/** Hand the freshest token to TurnRecorder while this heartbeat covers open(). */
|
|
108
119
|
async transfer(): Promise<SessionLease> {
|
|
109
120
|
await this.assertCurrent()
|
|
121
|
+
// The recorder's own heartbeat first fires half a lifetime after it
|
|
122
|
+
// opens, so it must be handed a full lifetime, not whatever was left
|
|
123
|
+
// when the last confirmation read the fence.
|
|
124
|
+
if (this.ownsLease) {
|
|
125
|
+
this.#queueRenewal()
|
|
126
|
+
await this.#renewal
|
|
127
|
+
if (this.#renewalError !== undefined) throw this.#renewalError
|
|
128
|
+
}
|
|
110
129
|
this.#transferred = true
|
|
111
130
|
return this.#current
|
|
112
131
|
}
|
|
@@ -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'
|
|
@@ -430,7 +431,11 @@ export async function prepareTurn(params: QueryParams): Promise<PreparedTurn> {
|
|
|
430
431
|
]
|
|
431
432
|
assertCostIsAttributable(chain, params.pricing)
|
|
432
433
|
assertBudgetIsMeasurable(params)
|
|
433
|
-
|
|
434
|
+
// When the caller asks for it (`retry.overloadedFallOverAfter`), a member
|
|
435
|
+
// with a successor leaves after that many overloaded answers in a row
|
|
436
|
+
// instead of spending the whole budget on a saturated provider; the last
|
|
437
|
+
// member has nowhere to go, so it keeps the full budget whatever was asked.
|
|
438
|
+
const withRecovery = (provider: LLMProvider, hasSuccessor: boolean): LLMProvider => {
|
|
434
439
|
const withIdleBound = withStreamIdleTimeout(provider, {
|
|
435
440
|
idleTimeoutMs: streamIdleTimeoutMs,
|
|
436
441
|
log,
|
|
@@ -439,7 +444,12 @@ export async function prepareTurn(params: QueryParams): Promise<PreparedTurn> {
|
|
|
439
444
|
return params.retry === false
|
|
440
445
|
? metered
|
|
441
446
|
: withProviderRetry(metered, {
|
|
442
|
-
config:
|
|
447
|
+
config: {
|
|
448
|
+
...params.retry,
|
|
449
|
+
overloadedFallOverAfter: hasSuccessor
|
|
450
|
+
? (params.retry?.overloadedFallOverAfter ?? 0)
|
|
451
|
+
: 0,
|
|
452
|
+
},
|
|
443
453
|
log,
|
|
444
454
|
canRetry: () => budget.remaining > 0,
|
|
445
455
|
})
|
|
@@ -455,9 +465,9 @@ export async function prepareTurn(params: QueryParams): Promise<PreparedTurn> {
|
|
|
455
465
|
current: { index: 0, providerId: params.provider.id },
|
|
456
466
|
}
|
|
457
467
|
const resilientProvider = withProviderFallback(
|
|
458
|
-
chain.map((member) => ({
|
|
468
|
+
chain.map((member, position) => ({
|
|
459
469
|
...member,
|
|
460
|
-
provider: withRecovery(member.provider),
|
|
470
|
+
provider: withRecovery(member.provider, position < chain.length - 1),
|
|
461
471
|
})),
|
|
462
472
|
{
|
|
463
473
|
log,
|
|
@@ -1223,14 +1233,21 @@ async function assertTurnIdIsNew(
|
|
|
1223
1233
|
storage: SessionStorage,
|
|
1224
1234
|
): Promise<void> {
|
|
1225
1235
|
if (params.turnId === undefined) return
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
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
|
+
})
|
|
1234
1251
|
}
|
|
1235
1252
|
}
|
|
1236
1253
|
|
|
@@ -37,6 +37,7 @@ import { isTrustedReadOnly } from '../../tools/trusted-read-only.js'
|
|
|
37
37
|
import type { ToolManager } from '../../toolsets/manager.js'
|
|
38
38
|
import type {
|
|
39
39
|
HITLResumeDecision,
|
|
40
|
+
LaunchedScript,
|
|
40
41
|
ResumeContext,
|
|
41
42
|
ResumeHandler,
|
|
42
43
|
ToolCallDeclined,
|
|
@@ -933,6 +934,36 @@ export function createReviewHandler(options: ReviewPolicyOptions = {}): ResumeHa
|
|
|
933
934
|
/** Set once the classifier has said the conversation is too long, until it judges something. */
|
|
934
935
|
let transcriptTooLong = false
|
|
935
936
|
const toolOf = (name: string) => registry?.get(name) ?? registry?.get(name.toLowerCase())
|
|
937
|
+
/** The script files a shell call launches that the agent wrote, read now; none for any other call. */
|
|
938
|
+
const launchedScriptsOf = async (
|
|
939
|
+
tc: ToolCallSummary,
|
|
940
|
+
context: ResumeContext | undefined,
|
|
941
|
+
): Promise<readonly LaunchedScript[]> => {
|
|
942
|
+
if (context?.launchedScripts === undefined) return []
|
|
943
|
+
const tool = toolOf(tc.name)
|
|
944
|
+
const argument = tool?.commandArgument
|
|
945
|
+
if (tool === undefined || argument === undefined) return []
|
|
946
|
+
const line = (tc.input as Record<string, unknown> | null)?.[argument]
|
|
947
|
+
if (typeof line !== 'string') return []
|
|
948
|
+
try {
|
|
949
|
+
return await context.launchedScripts(
|
|
950
|
+
tc.id,
|
|
951
|
+
line,
|
|
952
|
+
tool.commandDialect?.({ sandboxed: false }) ?? 'sh',
|
|
953
|
+
)
|
|
954
|
+
} catch {
|
|
955
|
+
// Reading a script is an addition to the review. A failure to do it is
|
|
956
|
+
// said to the reviewer rather than hidden.
|
|
957
|
+
return [
|
|
958
|
+
{
|
|
959
|
+
path: '(unknown)',
|
|
960
|
+
origin: 'unknown',
|
|
961
|
+
state: 'unreadable',
|
|
962
|
+
note: 'reading the script files this line launches failed, so what they run is not known',
|
|
963
|
+
},
|
|
964
|
+
]
|
|
965
|
+
}
|
|
966
|
+
}
|
|
936
967
|
const rootsNow = (): readonly string[] =>
|
|
937
968
|
typeof options.roots === 'function' ? options.roots() : (options.roots ?? [])
|
|
938
969
|
const exempt: ReviewExemption =
|
|
@@ -1215,9 +1246,11 @@ export function createReviewHandler(options: ReviewPolicyOptions = {}): ResumeHa
|
|
|
1215
1246
|
for (const tc of live) {
|
|
1216
1247
|
// The questions a person must answer are asked of a person, never of a model.
|
|
1217
1248
|
if (isBypassImmune(tc) || (await neverSent(tc, request.sessionId))) continue
|
|
1249
|
+
const scripts = await launchedScriptsOf(tc, context)
|
|
1218
1250
|
const decision = await classifier.review({
|
|
1219
1251
|
call: tc,
|
|
1220
1252
|
transcript,
|
|
1253
|
+
...(scripts.length > 0 ? { scripts } : {}),
|
|
1221
1254
|
...(registry ? { tools: registry } : {}),
|
|
1222
1255
|
...(context?.signal ? { signal: context.signal } : {}),
|
|
1223
1256
|
})
|
|
@@ -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
|
}
|