@namzu/sdk 56.2.0 → 57.1.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.
Files changed (199) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/dist/agents/QueryAgent.d.ts.map +1 -1
  3. package/dist/agents/QueryAgent.js +2 -0
  4. package/dist/agents/QueryAgent.js.map +1 -1
  5. package/dist/agents/index.d.ts +1 -0
  6. package/dist/agents/index.d.ts.map +1 -1
  7. package/dist/agents/index.js +1 -0
  8. package/dist/agents/index.js.map +1 -1
  9. package/dist/agents/plan.d.ts +20 -0
  10. package/dist/agents/plan.d.ts.map +1 -0
  11. package/dist/agents/plan.js +27 -0
  12. package/dist/agents/plan.js.map +1 -0
  13. package/dist/compaction/dangling.d.ts +12 -1
  14. package/dist/compaction/dangling.d.ts.map +1 -1
  15. package/dist/compaction/dangling.js +17 -3
  16. package/dist/compaction/dangling.js.map +1 -1
  17. package/dist/compaction/index.d.ts +3 -2
  18. package/dist/compaction/index.d.ts.map +1 -1
  19. package/dist/compaction/index.js.map +1 -1
  20. package/dist/compaction/manager.d.ts +16 -0
  21. package/dist/compaction/manager.d.ts.map +1 -1
  22. package/dist/compaction/manager.js +55 -0
  23. package/dist/compaction/manager.js.map +1 -1
  24. package/dist/compaction/manual.d.ts +9 -0
  25. package/dist/compaction/manual.d.ts.map +1 -1
  26. package/dist/compaction/manual.js +64 -16
  27. package/dist/compaction/manual.js.map +1 -1
  28. package/dist/compaction/model-summary.d.ts +101 -0
  29. package/dist/compaction/model-summary.d.ts.map +1 -0
  30. package/dist/compaction/model-summary.js +333 -0
  31. package/dist/compaction/model-summary.js.map +1 -0
  32. package/dist/compaction/restore.d.ts +51 -0
  33. package/dist/compaction/restore.d.ts.map +1 -0
  34. package/dist/compaction/restore.js +123 -0
  35. package/dist/compaction/restore.js.map +1 -0
  36. package/dist/compaction/types.d.ts +22 -0
  37. package/dist/compaction/types.d.ts.map +1 -1
  38. package/dist/compaction/verifier.d.ts +6 -1
  39. package/dist/compaction/verifier.d.ts.map +1 -1
  40. package/dist/compaction/verifier.js +3 -3
  41. package/dist/compaction/verifier.js.map +1 -1
  42. package/dist/compaction/wire.d.ts +3 -1
  43. package/dist/compaction/wire.d.ts.map +1 -1
  44. package/dist/compaction/wire.js +16 -0
  45. package/dist/compaction/wire.js.map +1 -1
  46. package/dist/config/runtime.d.ts +62 -0
  47. package/dist/config/runtime.d.ts.map +1 -1
  48. package/dist/config/runtime.js +16 -0
  49. package/dist/config/runtime.js.map +1 -1
  50. package/dist/index.d.ts +1 -1
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/plugin/shell-hook.d.ts +19 -1
  53. package/dist/plugin/shell-hook.d.ts.map +1 -1
  54. package/dist/plugin/shell-hook.js +21 -5
  55. package/dist/plugin/shell-hook.js.map +1 -1
  56. package/dist/prompt/coding-agent-doctrine.d.ts +2 -2
  57. package/dist/prompt/coding-agent-doctrine.d.ts.map +1 -1
  58. package/dist/prompt/coding-agent-doctrine.js +5 -3
  59. package/dist/prompt/coding-agent-doctrine.js.map +1 -1
  60. package/dist/provider/errors.d.ts +3 -0
  61. package/dist/provider/errors.d.ts.map +1 -1
  62. package/dist/provider/errors.js +23 -3
  63. package/dist/provider/errors.js.map +1 -1
  64. package/dist/provider/fallback.d.ts.map +1 -1
  65. package/dist/provider/fallback.js +34 -3
  66. package/dist/provider/fallback.js.map +1 -1
  67. package/dist/provider/retry.d.ts +12 -5
  68. package/dist/provider/retry.d.ts.map +1 -1
  69. package/dist/provider/retry.js +22 -8
  70. package/dist/provider/retry.js.map +1 -1
  71. package/dist/public-runtime.d.ts +5 -2
  72. package/dist/public-runtime.d.ts.map +1 -1
  73. package/dist/public-runtime.js +4 -1
  74. package/dist/public-runtime.js.map +1 -1
  75. package/dist/public-types.d.ts +1 -1
  76. package/dist/public-types.d.ts.map +1 -1
  77. package/dist/runtime/jobs/registry.d.ts +42 -0
  78. package/dist/runtime/jobs/registry.d.ts.map +1 -1
  79. package/dist/runtime/jobs/registry.js +77 -0
  80. package/dist/runtime/jobs/registry.js.map +1 -1
  81. package/dist/runtime/query/calls-that-never-started.d.ts +12 -0
  82. package/dist/runtime/query/calls-that-never-started.d.ts.map +1 -0
  83. package/dist/runtime/query/calls-that-never-started.js +41 -0
  84. package/dist/runtime/query/calls-that-never-started.js.map +1 -0
  85. package/dist/runtime/query/executor/tool-call-admission.d.ts +2 -2
  86. package/dist/runtime/query/executor/tool-call-admission.d.ts.map +1 -1
  87. package/dist/runtime/query/executor/tool-call-admission.js +13 -9
  88. package/dist/runtime/query/executor/tool-call-admission.js.map +1 -1
  89. package/dist/runtime/query/executor.d.ts +11 -0
  90. package/dist/runtime/query/executor.d.ts.map +1 -1
  91. package/dist/runtime/query/executor.js +48 -0
  92. package/dist/runtime/query/executor.js.map +1 -1
  93. package/dist/runtime/query/file-evidence-context.js +1 -1
  94. package/dist/runtime/query/file-evidence-context.js.map +1 -1
  95. package/dist/runtime/query/index.d.ts +12 -0
  96. package/dist/runtime/query/index.d.ts.map +1 -1
  97. package/dist/runtime/query/index.js +9 -0
  98. package/dist/runtime/query/index.js.map +1 -1
  99. package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
  100. package/dist/runtime/query/iteration/phases/compaction.js +66 -1
  101. package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
  102. package/dist/runtime/query/iteration/phases/context.d.ts +5 -0
  103. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  104. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  105. package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
  106. package/dist/runtime/query/iteration/stream-turn.js +45 -25
  107. package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
  108. package/dist/runtime/query/prepare-turn.d.ts.map +1 -1
  109. package/dist/runtime/query/prepare-turn.js +10 -1
  110. package/dist/runtime/query/prepare-turn.js.map +1 -1
  111. package/dist/scheduler/completion-inbox.js +3 -3
  112. package/dist/scheduler/completion-inbox.js.map +1 -1
  113. package/dist/scheduler/local.js +1 -1
  114. package/dist/scheduler/local.js.map +1 -1
  115. package/dist/tools/admit-facts.d.ts.map +1 -1
  116. package/dist/tools/admit-facts.js +1 -0
  117. package/dist/tools/admit-facts.js.map +1 -1
  118. package/dist/tools/builtins/bash.d.ts +42 -1
  119. package/dist/tools/builtins/bash.d.ts.map +1 -1
  120. package/dist/tools/builtins/bash.js +115 -48
  121. package/dist/tools/builtins/bash.js.map +1 -1
  122. package/dist/tools/builtins/read-file.d.ts.map +1 -1
  123. package/dist/tools/builtins/read-file.js +55 -5
  124. package/dist/tools/builtins/read-file.js.map +1 -1
  125. package/dist/tools/file-read-tracker.d.ts.map +1 -1
  126. package/dist/tools/file-read-tracker.js +28 -0
  127. package/dist/tools/file-read-tracker.js.map +1 -1
  128. package/dist/types/agent/base.d.ts +14 -0
  129. package/dist/types/agent/base.d.ts.map +1 -1
  130. package/dist/types/agent/scheduler.d.ts +8 -0
  131. package/dist/types/agent/scheduler.d.ts.map +1 -1
  132. package/dist/types/errors/catalog.d.ts +3 -0
  133. package/dist/types/errors/catalog.d.ts.map +1 -1
  134. package/dist/types/errors/catalog.js +24 -2
  135. package/dist/types/errors/catalog.js.map +1 -1
  136. package/dist/types/plugin/index.d.ts +2 -2
  137. package/dist/types/provider/error.d.ts +7 -0
  138. package/dist/types/provider/error.d.ts.map +1 -1
  139. package/dist/types/provider/errors.d.ts +24 -0
  140. package/dist/types/provider/errors.d.ts.map +1 -1
  141. package/dist/types/provider/errors.js +101 -1
  142. package/dist/types/provider/errors.js.map +1 -1
  143. package/dist/types/provider/stream.d.ts +6 -0
  144. package/dist/types/provider/stream.d.ts.map +1 -1
  145. package/dist/types/session/events.d.ts +8 -0
  146. package/dist/types/session/events.d.ts.map +1 -1
  147. package/dist/types/session/events.js.map +1 -1
  148. package/dist/types/tool/index.d.ts +54 -0
  149. package/dist/types/tool/index.d.ts.map +1 -1
  150. package/dist/types/tool/index.js.map +1 -1
  151. package/dist/types/tool/presentation.d.ts +4 -0
  152. package/dist/types/tool/presentation.d.ts.map +1 -1
  153. package/package.json +1 -1
  154. package/src/agents/QueryAgent.ts +2 -0
  155. package/src/agents/index.ts +1 -0
  156. package/src/agents/plan.ts +30 -0
  157. package/src/compaction/dangling.ts +35 -3
  158. package/src/compaction/index.ts +5 -0
  159. package/src/compaction/manager.ts +53 -0
  160. package/src/compaction/manual.ts +110 -40
  161. package/src/compaction/model-summary.ts +412 -0
  162. package/src/compaction/restore.ts +159 -0
  163. package/src/compaction/types.ts +25 -0
  164. package/src/compaction/verifier.ts +3 -3
  165. package/src/compaction/wire.ts +26 -1
  166. package/src/config/runtime.ts +16 -0
  167. package/src/index.ts +2 -0
  168. package/src/plugin/shell-hook.ts +33 -5
  169. package/src/prompt/coding-agent-doctrine.ts +5 -3
  170. package/src/provider/errors.ts +27 -3
  171. package/src/provider/fallback.ts +34 -2
  172. package/src/provider/retry.ts +30 -10
  173. package/src/public-runtime.ts +12 -0
  174. package/src/public-types.ts +3 -0
  175. package/src/runtime/jobs/registry.ts +105 -0
  176. package/src/runtime/query/calls-that-never-started.ts +37 -0
  177. package/src/runtime/query/executor/tool-call-admission.ts +12 -9
  178. package/src/runtime/query/executor.ts +51 -0
  179. package/src/runtime/query/file-evidence-context.ts +1 -1
  180. package/src/runtime/query/index.ts +23 -0
  181. package/src/runtime/query/iteration/phases/compaction.ts +65 -1
  182. package/src/runtime/query/iteration/phases/context.ts +7 -0
  183. package/src/runtime/query/iteration/stream-turn.ts +47 -25
  184. package/src/runtime/query/prepare-turn.ts +10 -1
  185. package/src/scheduler/completion-inbox.ts +3 -3
  186. package/src/scheduler/local.ts +1 -1
  187. package/src/tools/admit-facts.ts +1 -0
  188. package/src/tools/builtins/bash.ts +149 -54
  189. package/src/tools/builtins/read-file.ts +66 -4
  190. package/src/tools/file-read-tracker.ts +25 -1
  191. package/src/types/agent/base.ts +16 -0
  192. package/src/types/agent/scheduler.ts +9 -0
  193. package/src/types/errors/catalog.ts +31 -2
  194. package/src/types/provider/error.ts +7 -0
  195. package/src/types/provider/errors.ts +120 -1
  196. package/src/types/provider/stream.ts +6 -0
  197. package/src/types/session/events.ts +8 -0
  198. package/src/types/tool/index.ts +56 -0
  199. 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'
@@ -700,6 +702,8 @@ export class ToolExecutor {
700
702
  private emitEvent: EmitEvent
701
703
  private log: Logger
702
704
  private workingStateManager?: WorkingStateManager
705
+ private historyView?: () => readonly Message[]
706
+ private systemPromptView?: () => string | undefined
703
707
  private probes: ProbeEnforcement
704
708
  private parentSpan?: Span
705
709
  private readonly toolCallBudget?: ToolCallBudget
@@ -749,6 +753,35 @@ export class ToolExecutor {
749
753
  this.workingStateManager = manager
750
754
  }
751
755
 
756
+ /**
757
+ * Read the conversation the model is shown, as it is now. Lets a tool ask
758
+ * whether an earlier receipt is still there (see `ToolContext.receiptVisible`)
759
+ * without being handed the history.
760
+ */
761
+ setHistoryView(view: () => readonly Message[]): void {
762
+ this.historyView = view
763
+ }
764
+
765
+ /** The system prompt assembled for the current iteration, for `ToolContext.conversation`. */
766
+ setSystemPromptView(view: () => string | undefined): void {
767
+ this.systemPromptView = view
768
+ }
769
+
770
+ private receiptVisible = (callId: string, renderedFingerprint: string): boolean => {
771
+ const history = this.historyView?.()
772
+ if (!history) return false
773
+ for (let index = history.length - 1; index >= 0; index -= 1) {
774
+ const message = history[index]
775
+ if (message?.role !== 'tool' || message.toolCallId !== callId) continue
776
+ return (
777
+ typeof message.content === 'string' &&
778
+ message.isError !== true &&
779
+ fingerprintContent(message.content) === renderedFingerprint
780
+ )
781
+ }
782
+ return false
783
+ }
784
+
752
785
  setSandbox(sandbox: Sandbox): void {
753
786
  this.config = { ...this.config, sandbox }
754
787
  this.sandboxGate = undefined
@@ -2185,6 +2218,15 @@ export class ToolExecutor {
2185
2218
  this.dispatchNested(name, input, context, recordObservation, undefined, options),
2186
2219
  sandbox: this.config.sandbox,
2187
2220
  fileReadTracker: this.fileReadTracker,
2221
+ receiptVisible: this.receiptVisible,
2222
+ ...(this.historyView
2223
+ ? {
2224
+ conversation: {
2225
+ messages: () => this.historyView?.() ?? [],
2226
+ systemPrompt: () => this.systemPromptView?.(),
2227
+ },
2228
+ }
2229
+ : {}),
2188
2230
  ...(this.config.codeAnalysis ? { codeAnalysis: this.config.codeAnalysis } : {}),
2189
2231
  // Bound to this turn, once. Binding here rather than passing the
2190
2232
  // owner from the tool is what makes the scoping structural: there
@@ -2584,6 +2626,15 @@ export class ToolExecutor {
2584
2626
 
2585
2627
  if (this.workingStateManager) {
2586
2628
  extractFromToolResult(this.workingStateManager, toolName, output, effectiveIsError)
2629
+ // What the model was shown, kept whole so a compaction can put it back.
2630
+ recordRestorable(this.workingStateManager, {
2631
+ toolName,
2632
+ input,
2633
+ output: typeof modelContent === 'string' ? modelContent : output,
2634
+ success: !effectiveIsError,
2635
+ unchanged: result.facts?.kind === 'read' && result.facts.unchanged === true,
2636
+ workingDirectory: this.config.workingDirectory,
2637
+ })
2587
2638
  for (const pin of this.config.abortSignal.aborted ? [] : (result.workingState ?? [])) {
2588
2639
  this.workingStateManager.pin(pin.key, pin.text, toolName)
2589
2640
  }
@@ -149,7 +149,7 @@ export function recordFileEvidence(
149
149
  const lead =
150
150
  recorded.length === 0
151
151
  ? `${FILE_EVIDENCE_HEADING}: ${FILE_EVIDENCE_GUIDANCE}`
152
- : `${FILE_EVIDENCE_HEADING}, changed since the last entry. An entry for a path replaces the earlier entries for that path.`
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
153
  return [
154
154
  lead,
155
155
  ...(gone.length > 0 ? [`${NO_LONGER_HOLDS}${JSON.stringify(gone)}`] : []),
@@ -10,6 +10,7 @@ import { SkillGrantSet } from '../../authorization/skill-grant.js'
10
10
  import { repairToolMessageHistory, toolHistoryRepairChanged } from '../../compaction/dangling.js'
11
11
  import { extractFromUserMessage } from '../../compaction/extractor.js'
12
12
  import { WorkingStateManager } from '../../compaction/manager.js'
13
+ import type { CompactionSummaryRouter } from '../../compaction/model-summary.js'
13
14
  import type { ContextReducer } from '../../compaction/reducer.js'
14
15
  import { serializeState as serializeWorkingState } from '../../compaction/serializer.js'
15
16
  import { restoreWorkingState, snapshotWorkingState } from '../../compaction/wire.js'
@@ -814,6 +815,19 @@ export interface QueryParams {
814
815
  */
815
816
  projectInstructionContext?: ProjectInstructionContext
816
817
 
818
+ /**
819
+ * Text the host wants carried across a compaction, such as where its plan
820
+ * file is. Asked once per pass, after the summary is written; the answer is
821
+ * appended to the restored context. Returning nothing adds nothing.
822
+ */
823
+ postCompactionContext?: () => string | undefined | Promise<string | undefined>
824
+
825
+ /**
826
+ * Names the model that writes a compaction summary. Asked once per pass;
827
+ * resolving nothing, or a call that fails, keeps the extracted summary.
828
+ */
829
+ compactionSummaryRoute?: CompactionSummaryRouter
830
+
817
831
  /**
818
832
  * Where this conversation's durable state lives.
819
833
  *
@@ -1656,6 +1670,8 @@ export async function* query(params: QueryParams): AsyncGenerator<SessionEvent,
1656
1670
  const compactionConfig = params.compactionConfig
1657
1671
  ? CompactionConfigSchema.parse(params.compactionConfig)
1658
1672
  : undefined
1673
+ // The live history, so a tool can ask whether an earlier receipt is still in it.
1674
+ toolExecutor.setHistoryView(() => ctx.recorder.messages)
1659
1675
  if (compactionConfig && compactionConfig.strategy !== 'disabled') {
1660
1676
  workingStateManager = new WorkingStateManager(compactionConfig)
1661
1677
  toolExecutor.setWorkingStateManager(workingStateManager)
@@ -1829,6 +1845,12 @@ export async function* query(params: QueryParams): AsyncGenerator<SessionEvent,
1829
1845
  ...(params.projectInstructionContext
1830
1846
  ? { projectInstructionContext: params.projectInstructionContext }
1831
1847
  : {}),
1848
+ ...(params.postCompactionContext
1849
+ ? { postCompactionContext: params.postCompactionContext }
1850
+ : {}),
1851
+ ...(params.compactionSummaryRoute
1852
+ ? { compactionSummaryRoute: params.compactionSummaryRoute }
1853
+ : {}),
1832
1854
  ...(providerContextWindow !== undefined ? { providerContextWindow } : {}),
1833
1855
  resolveModelContextWindow: async (model) => {
1834
1856
  if (!modelContextWindows.has(model)) {
@@ -2339,6 +2361,7 @@ export async function* query(params: QueryParams): AsyncGenerator<SessionEvent,
2339
2361
  ? `${segments.static}\n\n---\n\n${segments.dynamic}`
2340
2362
  : segments.static
2341
2363
 
2364
+ toolExecutor.setSystemPromptView(() => assembledPrompt)
2342
2365
  ctx.recorder.markRunning()
2343
2366
  // A resumed turn already began; `turn_resuming` above continued it.
2344
2367
  if (!params.resumeFromCheckpoint) {
@@ -3,6 +3,7 @@ import {
3
3
  resolveTriggerThreshold,
4
4
  } from '../../../../compaction/context-window.js'
5
5
  import { findDanglingMessages } from '../../../../compaction/dangling.js'
6
+ import { replaceableSummaries, summarizeWithModel } from '../../../../compaction/model-summary.js'
6
7
  import {
7
8
  type CompactionPlan,
8
9
  DEFAULT_SOFT_TARGET,
@@ -11,6 +12,7 @@ import {
11
12
  } from '../../../../compaction/plan.js'
12
13
  import type { ContextReducer, ContextReduction } from '../../../../compaction/reducer.js'
13
14
  import { createSlidingWindowReducer } from '../../../../compaction/reducer.js'
15
+ import { renderRestoredContext } from '../../../../compaction/restore.js'
14
16
  import { findRetainedIndices } from '../../../../compaction/retention.js'
15
17
  import { serializeState } from '../../../../compaction/serializer.js'
16
18
  import {
@@ -959,7 +961,51 @@ async function runCompactionCheckInner(
959
961
 
960
962
  let compactedContent: string
961
963
 
962
- if (config.llmVerification && manager.slotCount() < config.richStateThreshold) {
964
+ // A model the host set aside writes the summary when it can. Its own cost
965
+ // goes on the turn's ledger at the price of the model that answered. When
966
+ // there is no such model, or its call does not give a summary, the
967
+ // extracted state below is used, with its verifier step where that applies.
968
+ let modelRoute: { providerId: string; model: string } | undefined
969
+ const modelWritten = !ctx.compactionSummaryRoute
970
+ ? undefined
971
+ : await summarizeWithModel({
972
+ manager,
973
+ olderMessages,
974
+ previousSummaries: replaceableSummaries(systemMessages),
975
+ config,
976
+ resolveRoute: async (signal) => {
977
+ const route = await ctx.compactionSummaryRoute?.(signal)
978
+ if (route) {
979
+ modelRoute = {
980
+ providerId: route.providerId ?? ctx.recorder.servingProviderId,
981
+ model: route.model,
982
+ }
983
+ }
984
+ return route
985
+ },
986
+ onUsage: (usage) => {
987
+ if (modelRoute) ctx.recorder.accumulateUsage(usage, modelRoute)
988
+ },
989
+ signal: ctx.abortController.signal,
990
+ onFallback: (reason, error) => {
991
+ if (reason === 'no_route') return
992
+ if (error !== undefined) noteCompactionError(ctx, error)
993
+ ctx.log.warn(
994
+ 'The model-written compaction summary was not used — using the extracted state',
995
+ {
996
+ [NAMZU.TURN_ID]: ctx.recorder.turnId,
997
+ 'namzu.runtime.reason': reason,
998
+ ...(error !== undefined
999
+ ? { 'exception.message': error instanceof Error ? error.message : String(error) }
1000
+ : {}),
1001
+ },
1002
+ )
1003
+ },
1004
+ })
1005
+
1006
+ if (modelWritten !== undefined) {
1007
+ compactedContent = modelWritten
1008
+ } else if (config.llmVerification && manager.slotCount() < config.richStateThreshold) {
963
1009
  // Named once and used twice on purpose. The model the summariser is
964
1010
  // asked for is also the model its tokens are priced at, and computing
965
1011
  // the two separately is how they drift — a router that sends compaction
@@ -1003,6 +1049,24 @@ async function runCompactionCheckInner(
1003
1049
  compactedContent = serializeState(manager.getState())
1004
1050
  }
1005
1051
 
1052
+ // Skills the model loaded and files it read go back verbatim after the
1053
+ // summary, with whatever the host wants carried across (its plan file).
1054
+ let hostNote: string | undefined
1055
+ try {
1056
+ hostNote = await ctx.postCompactionContext?.()
1057
+ } catch (error) {
1058
+ // A host note is an addition; the pass does not depend on it.
1059
+ ctx.log.warn('The host note for a compaction could not be read — leaving it out', {
1060
+ [NAMZU.TURN_ID]: ctx.recorder.turnId,
1061
+ 'exception.message': error instanceof Error ? error.message : String(error),
1062
+ })
1063
+ }
1064
+ const restored = renderRestoredContext(manager.getState(), {
1065
+ contextWindowTokens: budget,
1066
+ ...(hostNote ? { hostNote } : {}),
1067
+ })
1068
+ if (restored) compactedContent = `${compactedContent}\n\n${restored}`
1069
+
1006
1070
  // The pointer is only worth a line when both tools it names are mounted.
1007
1071
  const mounted = new Set(ctx.tools?.listNames?.() ?? [])
1008
1072
  const compactionMessage = buildCompactionMessage(
@@ -2,6 +2,7 @@ import type { AdvisoryContext } from '../../../../advisory/context.js'
2
2
  import type { SkillGrantSet } from '../../../../authorization/skill-grant.js'
3
3
  import type { AgentBus } from '../../../../bus/index.js'
4
4
  import type { WorkingStateManager } from '../../../../compaction/manager.js'
5
+ import type { CompactionSummaryRouter } from '../../../../compaction/model-summary.js'
5
6
  import type { ContextReducer } from '../../../../compaction/reducer.js'
6
7
  import type { CompactionConfig } from '../../../../config/runtime.js'
7
8
  import { NAMZU } from '../../../../constants/telemetry/index.js'
@@ -256,6 +257,12 @@ export interface IterationContext {
256
257
  /** Live project policy; separate from human inbound continuation. */
257
258
  readonly projectInstructionContext?: ProjectInstructionContext
258
259
 
260
+ /** Host text appended to the restored context after a compaction. */
261
+ readonly postCompactionContext?: () => string | undefined | Promise<string | undefined>
262
+
263
+ /** Where a model-written compaction summary is asked for; see {@link CompactionSummaryRouter}. */
264
+ readonly compactionSummaryRoute?: CompactionSummaryRouter
265
+
259
266
  readonly workingStateManager?: WorkingStateManager
260
267
 
261
268
  /**
@@ -30,7 +30,7 @@ import type {
30
30
  ReasoningBlock,
31
31
  ToolInputError,
32
32
  } from '../../../types/message/index.js'
33
- import { ProviderError } from '../../../types/provider/errors.js'
33
+ import { ProviderError, classifyProviderError } from '../../../types/provider/errors.js'
34
34
  import type {
35
35
  ChatCompletionResponse,
36
36
  LLMProvider,
@@ -298,7 +298,7 @@ export async function* streamProviderTurn(
298
298
  let streamedLength = 0
299
299
  const toolBuckets = new Map<number, ToolCallBucket>()
300
300
  // Places a fragment that came without an index; see `ToolCallIndexer`.
301
- const toolIndexer = new ToolCallIndexer()
301
+ let toolIndexer = new ToolCallIndexer()
302
302
  // The call the model's latest output went to, or `undefined` once text,
303
303
  // reasoning or a hosted tool followed it. An output limit, a content
304
304
  // filter or a dropped stream stops the response wherever it is, so this
@@ -397,6 +397,36 @@ export async function* streamProviderTurn(
397
397
  })
398
398
  : undefined
399
399
 
400
+ /**
401
+ * Drop everything the failed attempt wrote, so the request starts over under
402
+ * the same message: after a restarting retry, or a swap that says the same.
403
+ * The failed attempt's text, reasoning and tool calls are void. A call is
404
+ * restarted only while still being written and nothing runs before the
405
+ * response settles, so a half-written call never reaches the model.
406
+ */
407
+ const discardAttempt = () => {
408
+ toolBuckets.clear()
409
+ toolIndexer = new ToolCallIndexer()
410
+ framingViolated = false
411
+ text = new StreamTextAccumulator()
412
+ reasoningBuckets.clear()
413
+ citations.length = 0
414
+ replayState = undefined
415
+ id = ''
416
+ finishReason = 'stop'
417
+ reportedFinishReason = undefined
418
+ finishDetail = undefined
419
+ streamedLength = 0
420
+ lastOutputCall = undefined
421
+ usage = {
422
+ promptTokens: 0,
423
+ completionTokens: 0,
424
+ totalTokens: 0,
425
+ cachedTokens: 0,
426
+ cacheWriteTokens: 0,
427
+ }
428
+ }
429
+
400
430
  try {
401
431
  for (;;) {
402
432
  const next = it.next()
@@ -426,28 +456,7 @@ export async function* streamProviderTurn(
426
456
  ...(chunk.retry.restart ? { restart: true } : {}),
427
457
  })
428
458
  yield* drainPending()
429
- if (chunk.retry.restart) {
430
- // The failed attempt's text and reasoning are void: the request
431
- // starts over under the same message. The decorator never
432
- // restarts once a tool call began, so no bucket is left behind.
433
- text = new StreamTextAccumulator()
434
- reasoningBuckets.clear()
435
- citations.length = 0
436
- replayState = undefined
437
- id = ''
438
- finishReason = 'stop'
439
- reportedFinishReason = undefined
440
- finishDetail = undefined
441
- streamedLength = 0
442
- lastOutputCall = undefined
443
- usage = {
444
- promptTokens: 0,
445
- completionTokens: 0,
446
- totalTokens: 0,
447
- cachedTokens: 0,
448
- cacheWriteTokens: 0,
449
- }
450
- }
459
+ if (chunk.retry.restart) discardAttempt()
451
460
  continue
452
461
  }
453
462
 
@@ -472,8 +481,10 @@ export async function* streamProviderTurn(
472
481
  code: chunk.fallback.code,
473
482
  ...(chunk.fallback.status !== undefined ? { status: chunk.fallback.status } : {}),
474
483
  reason: chunk.fallback.reason,
484
+ ...(chunk.fallback.restart ? { restart: true } : {}),
475
485
  })
476
486
  yield* drainPending()
487
+ if (chunk.fallback.restart) discardAttempt()
477
488
  continue
478
489
  }
479
490
 
@@ -947,10 +958,21 @@ export async function* streamProviderTurn(
947
958
 
948
959
  // Not after a framing violation: the calls it would recover are the ones
949
960
  // the stream was just refused for garbling.
961
+ //
962
+ // Nor when the stream was cut by a connection that may come back and no
963
+ // call came through whole: the retries are spent, the half-written call is
964
+ // not the model's mistake, and telling the model about it would spend a
965
+ // turn on a fault it cannot fix. The failure goes up and the turn pauses.
966
+ const cutByConnection =
967
+ streamError !== undefined &&
968
+ streamCause !== undefined &&
969
+ classifyProviderError(streamCause).retryable
950
970
  const recoveredToolInputFromStreamError =
951
971
  streamError !== undefined &&
952
972
  !framingViolated &&
953
- toolCalls.some((tc) => tc.id && tc.function.name)
973
+ toolCalls.some(
974
+ (tc) => tc.id && tc.function.name && (!cutByConnection || !tc.metadata?.inputError),
975
+ )
954
976
  const effectiveFinishReason: ChatCompletionResponse['finishReason'] =
955
977
  recoveredToolInputFromStreamError ? 'tool_calls' : finishReason
956
978
 
@@ -43,6 +43,7 @@ import { generateCheckpointId, generateTurnId } from '../../utils/id.js'
43
43
  import { errorAttributes } from '../../utils/log/exception.js'
44
44
  import type { Logger } from '../../utils/logger.js'
45
45
  import { AUTO_APPROVE_POLICY_NAME, createSessionApprovalPolicy } from './approval-policy.js'
46
+ import { readCallsThatNeverStarted } from './calls-that-never-started.js'
46
47
  import { type TurnContext, TurnContextFactory } from './context.js'
47
48
  import { EventTranslator } from './events.js'
48
49
  import type { QueryParams } from './index.js'
@@ -681,9 +682,17 @@ export async function prepareTurn(params: QueryParams): Promise<PreparedTurn> {
681
682
  pinCompaction: true,
682
683
  })),
683
684
  ])
684
- const initialRepair = params.resumeFromCheckpoint
685
+ let initialRepair = params.resumeFromCheckpoint
685
686
  ? { messages: projectedInitialMessages, report: undefined }
686
687
  : repairToolMessageHistory(projectedInitialMessages)
688
+ if (initialRepair.report && initialRepair.report.syntheticToolResultsInserted > 0) {
689
+ // A call that was waiting for approval when the session ended never ran;
690
+ // the model is told so rather than that its outcome is unknown.
691
+ const neverRan = await readCallsThatNeverStarted(storage.log)
692
+ if (neverRan.size > 0) {
693
+ initialRepair = repairToolMessageHistory(projectedInitialMessages, { neverRan })
694
+ }
695
+ }
687
696
  const initialMessages = initialRepair.messages
688
697
  if (initialRepair.report && toolHistoryRepairChanged(initialRepair.report)) {
689
698
  pendingHistoryRepairs.push({
@@ -46,7 +46,7 @@ const OWNED_WORK_DISPLAY_LIMIT = 16
46
46
 
47
47
  /** What the model is told about owned work, wherever it is shown. */
48
48
  const OWNED_WORK_GUIDANCE =
49
- 'Each task gives its id (taskId), its state, how its turn ended (turnStatus), why it stopped (stopReason), and whether its result has reached this conversation (resultDelivery: delivered-to-history, or not-delivered). Result delivery is not proof that you answered the original request. Answer the latest operator message and complete the requested synthesis of available results unless the operator cancelled or changed that request. Reuse delivered results when sufficient; do not relaunch completed work. If a delivered result is no longer visible, retrieve it by task ID. A terminal state with a non-end_turn stop reason is not successful task completion. Do not repeat a synthesis already given.'
49
+ 'Each task gives its id (taskId), its state, how its turn ended (turnStatus), why it stopped (stopReason), and whether its result has reached this conversation (resultDelivery: delivered-to-history, or not-delivered). Result delivery is not proof that you answered the original request. Answer the latest operator message and complete the requested synthesis of available results unless the operator cancelled or changed that request. Reuse delivered results when sufficient; do not relaunch completed work. If a delivered result is no longer visible, retrieve it by task ID. A terminal state with a non-end_turn stop reason is not successful task completion. Results reach you and not the user, so before you end a reply check that the result of every finished task has been reported to the user, once. Do not repeat a synthesis already given.'
50
50
 
51
51
  /**
52
52
  * The ids of the tasks that earlier recorded entries name, read from the
@@ -809,8 +809,8 @@ export function formatCompletionNotification(handles: readonly TaskHandle[]): st
809
809
 
810
810
  const preamble =
811
811
  handles.length === 1
812
- ? 'A task you launched has finished. This is its result — you were not waiting on it, so it arrives here rather than as a tool result.'
813
- : `${handles.length} tasks you launched have finished. These are their results — you were not waiting on them, so they arrive here rather than as tool results.`
812
+ ? 'A task you launched has finished. This is its result — you were not waiting on it, so it arrives here rather than as a tool result. The user has not seen it; tell them what it found in your next message.'
813
+ : `${handles.length} tasks you launched have finished. These are their results — you were not waiting on them, so they arrive here rather than as tool results. The user has not seen them; tell them what each found in your next message.`
814
814
 
815
815
  return `${preamble}\n\n${blocks.join('\n\n')}`
816
816
  }
@@ -133,7 +133,7 @@ export class LocalTaskScheduler implements TaskScheduler {
133
133
  ...(options.phaseDetail ? { phaseDetail: options.phaseDetail } : {}),
134
134
  ...(options.phaseOrder !== undefined ? { phaseOrder: options.phaseOrder } : {}),
135
135
  input: {
136
- messages: [createUserMessage(options.prompt)],
136
+ messages: [...(options.inheritedMessages ?? []), createUserMessage(options.prompt)],
137
137
  workingDirectory: options.workingDirectory,
138
138
  taskStore: this.parentInput?.taskStore,
139
139
  runtimeToolOverrides: this.parentInput?.runtimeToolOverrides,
@@ -118,6 +118,7 @@ export function admitToolFacts(value: unknown): ToolFacts | undefined {
118
118
  ? { bytes: count(value.binary.bytes) }
119
119
  : undefined,
120
120
  background: flag(value.background),
121
+ continued: flag(value.continued),
121
122
  })
122
123
  case 'read': {
123
124
  const path = text(value.path)