tinker-agent 1.0.65 → 1.2.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 (208) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +6 -0
  4. package/README.md +90 -9
  5. package/bin/tinker.js +39 -0
  6. package/node_modules/ansi-regex/index.d.ts +33 -0
  7. package/node_modules/ansi-regex/index.js +14 -0
  8. package/node_modules/ansi-regex/license +9 -0
  9. package/node_modules/ansi-regex/package.json +61 -0
  10. package/node_modules/ansi-regex/readme.md +66 -0
  11. package/node_modules/ansi-styles/index.d.ts +236 -0
  12. package/node_modules/ansi-styles/index.js +223 -0
  13. package/node_modules/ansi-styles/license +9 -0
  14. package/node_modules/ansi-styles/package.json +54 -0
  15. package/node_modules/ansi-styles/readme.md +173 -0
  16. package/node_modules/chalk/license +9 -0
  17. package/node_modules/chalk/package.json +83 -0
  18. package/node_modules/chalk/readme.md +297 -0
  19. package/node_modules/chalk/source/index.d.ts +325 -0
  20. package/node_modules/chalk/source/index.js +225 -0
  21. package/node_modules/chalk/source/utilities.js +33 -0
  22. package/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
  23. package/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
  24. package/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
  25. package/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
  26. package/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
  27. package/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
  28. package/node_modules/character-entities/index.d.ts +6 -0
  29. package/node_modules/character-entities/index.js +2132 -0
  30. package/node_modules/character-entities/license +22 -0
  31. package/node_modules/character-entities/package.json +78 -0
  32. package/node_modules/character-entities/readme.md +152 -0
  33. package/node_modules/decode-named-character-reference/index.d.ts +13 -0
  34. package/node_modules/decode-named-character-reference/index.d.ts.map +1 -0
  35. package/node_modules/decode-named-character-reference/index.dom.d.ts +6 -0
  36. package/node_modules/decode-named-character-reference/index.dom.d.ts.map +1 -0
  37. package/node_modules/decode-named-character-reference/index.dom.js +32 -0
  38. package/node_modules/decode-named-character-reference/index.js +19 -0
  39. package/node_modules/decode-named-character-reference/license +22 -0
  40. package/node_modules/decode-named-character-reference/package.json +90 -0
  41. package/node_modules/decode-named-character-reference/readme.md +136 -0
  42. package/node_modules/get-east-asian-width/index.d.ts +60 -0
  43. package/node_modules/get-east-asian-width/index.js +30 -0
  44. package/node_modules/get-east-asian-width/license +9 -0
  45. package/node_modules/get-east-asian-width/lookup-data.js +21 -0
  46. package/node_modules/get-east-asian-width/lookup.js +138 -0
  47. package/node_modules/get-east-asian-width/package.json +71 -0
  48. package/node_modules/get-east-asian-width/readme.md +65 -0
  49. package/node_modules/get-east-asian-width/utilities.js +24 -0
  50. package/node_modules/has-flag/index.d.ts +39 -0
  51. package/node_modules/has-flag/index.js +8 -0
  52. package/node_modules/has-flag/license +9 -0
  53. package/node_modules/has-flag/package.json +49 -0
  54. package/node_modules/has-flag/readme.md +74 -0
  55. package/node_modules/is-fullwidth-code-point/index.d.ts +17 -0
  56. package/node_modules/is-fullwidth-code-point/index.js +12 -0
  57. package/node_modules/is-fullwidth-code-point/license +9 -0
  58. package/node_modules/is-fullwidth-code-point/package.json +53 -0
  59. package/node_modules/is-fullwidth-code-point/readme.md +31 -0
  60. package/node_modules/markdansi/LICENSE +21 -0
  61. package/node_modules/markdansi/README.md +167 -0
  62. package/node_modules/markdansi/dist/ast.d.ts +99 -0
  63. package/node_modules/markdansi/dist/ast.js +1 -0
  64. package/node_modules/markdansi/dist/cli.d.ts +17 -0
  65. package/node_modules/markdansi/dist/cli.js +208 -0
  66. package/node_modules/markdansi/dist/hyperlink.d.ts +9 -0
  67. package/node_modules/markdansi/dist/hyperlink.js +26 -0
  68. package/node_modules/markdansi/dist/index.d.ts +10 -0
  69. package/node_modules/markdansi/dist/index.js +16 -0
  70. package/node_modules/markdansi/dist/parser.d.ts +2 -0
  71. package/node_modules/markdansi/dist/parser.js +205 -0
  72. package/node_modules/markdansi/dist/render.d.ts +9 -0
  73. package/node_modules/markdansi/dist/render.js +680 -0
  74. package/node_modules/markdansi/dist/stream.d.ts +39 -0
  75. package/node_modules/markdansi/dist/stream.js +192 -0
  76. package/node_modules/markdansi/dist/theme.d.ts +18 -0
  77. package/node_modules/markdansi/dist/theme.js +105 -0
  78. package/node_modules/markdansi/dist/types.d.ts +58 -0
  79. package/node_modules/markdansi/dist/types.js +1 -0
  80. package/node_modules/markdansi/dist/wrap.d.ts +10 -0
  81. package/node_modules/markdansi/dist/wrap.js +73 -0
  82. package/node_modules/markdansi/docs/spec.md +110 -0
  83. package/node_modules/markdansi/package.json +78 -0
  84. package/node_modules/markdansi/tsconfig.json +18 -0
  85. package/node_modules/marked/LICENSE +44 -0
  86. package/node_modules/marked/README.md +115 -0
  87. package/node_modules/marked/bin/main.js +284 -0
  88. package/node_modules/marked/bin/marked.js +16 -0
  89. package/node_modules/marked/lib/marked.d.ts +760 -0
  90. package/node_modules/marked/lib/marked.esm.js +77 -0
  91. package/node_modules/marked/lib/marked.esm.js.map +7 -0
  92. package/node_modules/marked/lib/marked.umd.js +79 -0
  93. package/node_modules/marked/lib/marked.umd.js.map +7 -0
  94. package/node_modules/marked/man/marked.1 +113 -0
  95. package/node_modules/marked/man/marked.1.md +93 -0
  96. package/node_modules/marked/package.json +103 -0
  97. package/node_modules/slice-ansi/index.d.ts +19 -0
  98. package/node_modules/slice-ansi/index.js +317 -0
  99. package/node_modules/slice-ansi/license +10 -0
  100. package/node_modules/slice-ansi/package.json +59 -0
  101. package/node_modules/slice-ansi/readme.md +55 -0
  102. package/node_modules/slice-ansi/tokenize-ansi.js +752 -0
  103. package/node_modules/string-width/index.d.ts +39 -0
  104. package/node_modules/string-width/index.js +203 -0
  105. package/node_modules/string-width/license +9 -0
  106. package/node_modules/string-width/package.json +65 -0
  107. package/node_modules/string-width/readme.md +66 -0
  108. package/node_modules/strip-ansi/index.d.ts +15 -0
  109. package/node_modules/strip-ansi/index.js +19 -0
  110. package/node_modules/strip-ansi/license +9 -0
  111. package/node_modules/strip-ansi/package.json +59 -0
  112. package/node_modules/strip-ansi/readme.md +37 -0
  113. package/node_modules/supports-color/browser.d.ts +1 -0
  114. package/node_modules/supports-color/browser.js +35 -0
  115. package/node_modules/supports-color/index.d.ts +55 -0
  116. package/node_modules/supports-color/index.js +202 -0
  117. package/node_modules/supports-color/license +9 -0
  118. package/node_modules/supports-color/package.json +64 -0
  119. package/node_modules/supports-color/readme.md +75 -0
  120. package/node_modules/supports-hyperlinks/browser.js +10 -0
  121. package/node_modules/supports-hyperlinks/index.d.ts +25 -0
  122. package/node_modules/supports-hyperlinks/index.js +163 -0
  123. package/node_modules/supports-hyperlinks/license +10 -0
  124. package/node_modules/supports-hyperlinks/package.json +47 -0
  125. package/node_modules/supports-hyperlinks/readme.md +45 -0
  126. package/package.json +41 -10
  127. package/patches/markdansi@0.3.2.patch +22 -2
  128. package/src/agent/context-builder.ts +24 -10
  129. package/src/agent/context-meter.ts +89 -5
  130. package/src/agent/loop.ts +107 -10
  131. package/src/agent/runtime-session.ts +1437 -82
  132. package/src/agent/session-ledger.ts +169 -29
  133. package/src/agent/types.ts +12 -1
  134. package/src/agent/user-prompt-projection.ts +62 -0
  135. package/src/cli/config.ts +57 -14
  136. package/src/cli/model-profiles.ts +124 -0
  137. package/src/cli/run-runner.ts +4 -1
  138. package/src/cli/tui-runner.tsx +68 -30
  139. package/src/context/compiled-context-hash.ts +1 -1
  140. package/src/context/compiled-context-validator.ts +92 -15
  141. package/src/context/context-automation-policy.ts +121 -0
  142. package/src/context/context-manager.ts +390 -11
  143. package/src/context/context-policy.ts +8 -0
  144. package/src/context/context-protocol-validator.ts +11 -1
  145. package/src/context/context-revision-compiler.ts +190 -27
  146. package/src/context/context-revision.ts +105 -19
  147. package/src/context/context-surface.ts +329 -0
  148. package/src/context/prefix-retirement-planner.ts +570 -0
  149. package/src/context/protocol-frame.ts +30 -3
  150. package/src/context/recall-retirement-contract.ts +27 -0
  151. package/src/context/swap-planner.ts +34 -10
  152. package/src/events/observation-text-log.ts +3 -3
  153. package/src/events/stdout-event-printer.ts +39 -3
  154. package/src/events/types.ts +172 -35
  155. package/src/ids/runtime-id.ts +6 -0
  156. package/src/ids/uuid-v7.ts +7 -0
  157. package/src/image/image-asset-store.ts +319 -0
  158. package/src/image/image-input-policy.ts +14 -0
  159. package/src/image/image-probe.ts +233 -0
  160. package/src/image/image-types.ts +262 -0
  161. package/src/instructions/project-instructions.ts +4 -1
  162. package/src/mcp/mcp-manager.ts +83 -19
  163. package/src/model/fake-model-client.ts +9 -1
  164. package/src/model/input-token-estimator.ts +25 -0
  165. package/src/model/model-client.ts +60 -0
  166. package/src/model/model-request-preflight.ts +1 -0
  167. package/src/model/moonshot-input-token-estimator.ts +107 -0
  168. package/src/model/openai-chat-mapping.ts +77 -1
  169. package/src/model/openai-chat-model-client.ts +389 -59
  170. package/src/model/openai-chat-stream.ts +272 -0
  171. package/src/model/prompt-prefix-hash.ts +2 -4
  172. package/src/model/token-estimator.ts +4 -0
  173. package/src/observation/observation-builder.ts +29 -0
  174. package/src/session/resume-projection.ts +94 -34
  175. package/src/session/session-catalog.ts +4 -4
  176. package/src/session/session-errors.ts +1 -1
  177. package/src/session/session-history-reader.ts +151 -4
  178. package/src/session/session-last-response-reader.ts +189 -0
  179. package/src/session/session-schema.ts +349 -35
  180. package/src/session/session-store.ts +3266 -255
  181. package/src/session/sqlite-session-ledger.ts +9 -4
  182. package/src/skills/skill-catalog.ts +182 -0
  183. package/src/skills/skill-context.ts +364 -0
  184. package/src/skills/skill-loader.ts +779 -0
  185. package/src/skills/skill-tool.ts +297 -0
  186. package/src/tools/edit.ts +51 -64
  187. package/src/tools/ensure-parent-directory.ts +6 -0
  188. package/src/tools/read.ts +2 -2
  189. package/src/tools/recall.ts +43 -40
  190. package/src/tools/registry.ts +24 -3
  191. package/src/tools/ripgrep.ts +3 -2
  192. package/src/tools/types.ts +43 -4
  193. package/src/tools/write.ts +20 -31
  194. package/src/tui/app.tsx +333 -96
  195. package/src/tui/clipboard.ts +5 -0
  196. package/src/tui/components/mcp-panel.tsx +28 -0
  197. package/src/tui/components/prompt-input.tsx +790 -257
  198. package/src/tui/components/skills-panel.tsx +31 -0
  199. package/src/tui/components/timeline.tsx +30 -1
  200. package/src/tui/event-store.ts +85 -2
  201. package/src/tui/file-mention.ts +234 -0
  202. package/src/tui/line-editor.ts +1 -0
  203. package/src/tui/project-slash-commands.ts +229 -0
  204. package/src/tui/prompt-draft.ts +510 -0
  205. package/src/tui/prompt-history.ts +294 -22
  206. package/src/tui/slash-commands.ts +46 -2
  207. package/src/tui/tui-session-controller.ts +110 -48
  208. package/src/tui/workspace-file-search.ts +94 -0
@@ -10,35 +10,61 @@ import type { ModelClient } from "../model/model-client";
10
10
  import { promptPrefixFingerprint } from "../model/prompt-prefix-hash";
11
11
  import { estimatePromptSegments } from "../model/token-estimator";
12
12
  import { SessionError } from "../session/session-errors";
13
- import type { SessionStore } from "../session/session-store";
13
+ import {
14
+ skillActivationManifestSha256,
15
+ type CommitSkillsUpdateInput,
16
+ type SessionStore,
17
+ } from "../session/session-store";
14
18
  import type { ToolDefinition } from "../tools/types";
15
- import { canonicalSequenceHash, renderedMessageHash } from "./compiled-context-hash";
19
+ import {
20
+ activeOverrideManifestHash,
21
+ canonicalSequenceHash,
22
+ renderedMessageHash,
23
+ } from "./compiled-context-hash";
16
24
  import { CompiledContextError } from "./compiled-context-validator";
17
- import { swapOnlyPolicyV1 } from "./context-policy";
25
+ import { recallFirstRetirementPolicyV1, swapOnlyPolicyV1 } from "./context-policy";
18
26
  import { ContextProtocolError } from "./context-protocol-validator";
27
+ import type { StoredContextSnapshotV8, SwapOverride } from "./context-revision";
19
28
  import {
20
29
  ContextRevisionCompiler,
21
30
  ContextRevisionError,
22
31
  } from "./context-revision-compiler";
32
+ import {
33
+ contextSurfaceChangeManifestHash,
34
+ contextSurfaceChanges,
35
+ type StoredContextSurfaceV8,
36
+ } from "./context-surface";
23
37
  import {
24
38
  assertPlanBaseCurrent,
25
39
  SwapPlanner,
26
40
  SwapPlanningDiagnosticError,
27
41
  SwapPlanStaleError,
28
42
  } from "./swap-planner";
43
+ import {
44
+ assertRetirementPlanBaseCurrent,
45
+ PrefixRetirementPlanner,
46
+ PrefixRetirementPlanningError,
47
+ PrefixRetirementPlanStaleError,
48
+ type PrefixRetirementPlan,
49
+ } from "./prefix-retirement-planner";
29
50
 
30
51
  export type ContextCompactionTrigger =
31
52
  | { kind: "manual" }
32
53
  | { kind: "runtime_pressure" }
33
54
  | { kind: "benchmark_forced"; targetTokens: number };
34
55
 
56
+ export type ContextRetirementTrigger =
57
+ | { kind: "manual" }
58
+ | { kind: "runtime_pressure" }
59
+ | { kind: "benchmark_forced"; targetTokens: number };
60
+
35
61
  export type ContextCompactionResult =
36
62
  | {
37
63
  status: "unchanged";
38
- outcome: "below_target" | "no_eligible_candidates";
64
+ outcome: "below_trigger" | "below_target" | "no_eligible_candidates";
39
65
  revisionId: ReturnType<RuntimeIdFactory["createContextRevisionId"]>;
40
66
  revisionNumber: number;
41
- totalOverrideCount: number;
67
+ activeOverrideCount: number;
42
68
  rawTokensBefore: number;
43
69
  guardedTokensBefore: number;
44
70
  targetTokens: number;
@@ -53,7 +79,7 @@ export type ContextCompactionResult =
53
79
  previousRevisionNumber: number;
54
80
  revisionNumber: number;
55
81
  addedOverrideCount: number;
56
- totalOverrideCount: number;
82
+ activeOverrideCount: number;
57
83
  originalObservationBytes: number;
58
84
  projectedObservationBytes: number;
59
85
  rawTokensBefore: number;
@@ -69,6 +95,45 @@ export type ContextCompactionResult =
69
95
  durationMs: number;
70
96
  };
71
97
 
98
+ export type ContextRetirementResult =
99
+ | {
100
+ status: "unchanged";
101
+ outcome: "below_target" | "no_complete_prefix";
102
+ revisionId: ReturnType<RuntimeIdFactory["createContextRevisionId"]>;
103
+ revisionNumber: number;
104
+ keepFromOrdinal: number;
105
+ activeOverrideCount: number;
106
+ guardedTokensBefore: number;
107
+ targetTokens: number;
108
+ planningDurationMs: number;
109
+ durationMs: number;
110
+ }
111
+ | {
112
+ status: "retired";
113
+ outcome: "target_reached" | "retirement_floor";
114
+ previousRevisionId: ReturnType<RuntimeIdFactory["createContextRevisionId"]>;
115
+ revisionId: ReturnType<RuntimeIdFactory["createContextRevisionId"]>;
116
+ previousRevisionNumber: number;
117
+ revisionNumber: number;
118
+ previousKeepFromOrdinal: number;
119
+ keepFromOrdinal: number;
120
+ retiredTurnCount: number;
121
+ retiredFrameCount: number;
122
+ retiredMessageCount: number;
123
+ activeOverrideCount: number;
124
+ rawTokensBefore: number;
125
+ rawTokensAfter: number;
126
+ guardedTokensBefore: number;
127
+ guardedTokensAfter: number;
128
+ targetTokens: number;
129
+ planHash: string;
130
+ planningDurationMs: number;
131
+ validationDurationMs: number;
132
+ transactionDurationMs: number;
133
+ activationDurationMs: number;
134
+ durationMs: number;
135
+ };
136
+
72
137
  export class ContextManagerError extends Error {
73
138
  constructor(
74
139
  readonly stage: "snapshot" | "plan" | "validate" | "commit" | "activate",
@@ -85,8 +150,83 @@ export class ContextManagerError extends Error {
85
150
 
86
151
  type ModelPreparer = Pick<ModelClient, "prepare">;
87
152
 
153
+ export function commitAgentSkillsContextUpdate(input: {
154
+ store: SessionStore;
155
+ contextMeter: ContextMeter;
156
+ idFactory: RuntimeIdFactory;
157
+ snapshot: StoredContextSnapshotV8;
158
+ surface: StoredContextSurfaceV8;
159
+ addedOverrides: readonly SwapOverride[];
160
+ settlements: CommitSkillsUpdateInput["settlements"];
161
+ }): ReturnType<SessionStore["commitSkillsUpdate"]> {
162
+ const compiler = new ContextRevisionCompiler();
163
+ let candidateCompiled: ReturnType<ContextRevisionCompiler["compileProspective"]>;
164
+ let changes: ReturnType<typeof contextSurfaceChanges>;
165
+ try {
166
+ input.store.assertContextRevisionIdle();
167
+ const active = compiler.compileActive(input.snapshot);
168
+ changes = contextSurfaceChanges(input.snapshot.surface, input.surface);
169
+ candidateCompiled = compiler.compileProspective({
170
+ active,
171
+ canonical: input.snapshot.canonical,
172
+ activeOverrides: input.snapshot.activeOverrides,
173
+ addedOverrides: input.addedOverrides,
174
+ activeSurface: input.snapshot.surface,
175
+ ...(input.surface === input.snapshot.surface ? {} : { surface: input.surface }),
176
+ allowCombinedSurfaceAndOverrides: true,
177
+ });
178
+ } catch (error) {
179
+ throw managerError("validate", error);
180
+ }
181
+
182
+ let revision: ReturnType<SessionStore["commitSkillsUpdate"]>;
183
+ try {
184
+ revision = input.store.commitSkillsUpdate({
185
+ revisionId: input.idFactory.createContextRevisionId(),
186
+ expectedBaseRevisionId: input.snapshot.revision.revisionId,
187
+ expectedBaseRevisionNumber: input.snapshot.revision.revisionNumber,
188
+ expectedCanonicalThroughOrdinal: input.snapshot.canonical.messages.length,
189
+ expectedBaseActiveOverrideManifestSha256:
190
+ input.snapshot.revision.activeOverrideManifestSha256,
191
+ surface: input.surface,
192
+ changes,
193
+ changeManifestSha256: contextSurfaceChangeManifestHash(changes),
194
+ activationManifestSha256: skillActivationManifestSha256(input.settlements),
195
+ addedOverrides: input.addedOverrides,
196
+ nextActiveOverrideManifestSha256: activeOverrideManifestHash([
197
+ ...input.snapshot.activeOverrides,
198
+ ...input.addedOverrides,
199
+ ]),
200
+ settlements: input.settlements,
201
+ canonicalSequenceSha256: canonicalSequenceHash(input.snapshot.canonical),
202
+ renderedMessageSha256: renderedMessageHash(candidateCompiled.entries),
203
+ });
204
+ } catch (error) {
205
+ throw managerError("commit", error);
206
+ }
207
+
208
+ try {
209
+ input.contextMeter.startRevision({
210
+ reason: "context_rebuilt",
211
+ requestConfigHash: input.surface.requestConfigSha256,
212
+ toolSchemaHash: input.surface.toolSchemaSha256,
213
+ });
214
+ } catch (error) {
215
+ throw new ContextManagerError(
216
+ "activate",
217
+ errorCode(error),
218
+ true,
219
+ true,
220
+ "Agent Skills context revision committed but runtime activation failed.",
221
+ { cause: error },
222
+ );
223
+ }
224
+ return revision;
225
+ }
226
+
88
227
  export class ContextManager {
89
228
  private readonly planner: SwapPlanner;
229
+ private readonly retirementPlanner: PrefixRetirementPlanner;
90
230
  private readonly compiler = new ContextRevisionCompiler();
91
231
  private readonly requestBuilder = new ContextBuilder();
92
232
 
@@ -103,6 +243,7 @@ export class ContextManager {
103
243
  },
104
244
  ) {
105
245
  this.planner = new SwapPlanner(input.model);
246
+ this.retirementPlanner = new PrefixRetirementPlanner(input.model);
106
247
  }
107
248
 
108
249
  async compact(trigger: ContextCompactionTrigger): Promise<ContextCompactionResult> {
@@ -125,6 +266,7 @@ export class ContextManager {
125
266
  planning = this.planner.plan({
126
267
  active: built.compiled,
127
268
  revision: built.revision,
269
+ surface: built.surface,
128
270
  activeOverrides: built.activeOverrides,
129
271
  canonical: built.canonical,
130
272
  activePrepared,
@@ -141,6 +283,20 @@ export class ContextManager {
141
283
  }
142
284
  const planningDurationMs = elapsedMs(startedAt);
143
285
 
286
+ if (planning.outcome === "below_trigger" && trigger.kind === "runtime_pressure") {
287
+ return Object.freeze({
288
+ status: "unchanged",
289
+ outcome: planning.outcome,
290
+ revisionId: built.revision.revisionId,
291
+ revisionNumber: built.revision.revisionNumber,
292
+ activeOverrideCount: built.revision.activeOverrideCount,
293
+ rawTokensBefore: planning.rawTokensBefore,
294
+ guardedTokensBefore: planning.guardedTokensBefore,
295
+ targetTokens: planning.targetTokens,
296
+ planningDurationMs,
297
+ durationMs: elapsedMs(startedAt),
298
+ });
299
+ }
144
300
  if (
145
301
  planning.outcome === "below_target" ||
146
302
  planning.outcome === "no_eligible_candidates"
@@ -150,7 +306,7 @@ export class ContextManager {
150
306
  outcome: planning.outcome,
151
307
  revisionId: built.revision.revisionId,
152
308
  revisionNumber: built.revision.revisionNumber,
153
- totalOverrideCount: built.revision.totalOverrideCount,
309
+ activeOverrideCount: built.revision.activeOverrideCount,
154
310
  rawTokensBefore: planning.rawTokensBefore,
155
311
  guardedTokensBefore: planning.guardedTokensBefore,
156
312
  targetTokens: planning.targetTokens,
@@ -188,10 +344,12 @@ export class ContextManager {
188
344
  canonical: built.canonical,
189
345
  activeOverrides: built.activeOverrides,
190
346
  addedOverrides: plan.addedOverrides,
347
+ activeSurface: built.surface,
191
348
  });
192
349
  const candidate = this.requestBuilder.build({
193
350
  canonical: built.canonical,
194
351
  revision: built.revision,
352
+ surface: built.surface,
195
353
  activeOverrides: candidateOverrides,
196
354
  compiled: candidateCompiled,
197
355
  tools,
@@ -222,12 +380,12 @@ export class ContextManager {
222
380
  expectedBaseRevisionId: plan.baseRevisionId,
223
381
  expectedBaseRevisionNumber: plan.baseRevisionNumber,
224
382
  expectedCanonicalThroughOrdinal: plan.baseCanonicalThroughOrdinal,
225
- expectedBaseOverrideManifestSha256: plan.baseOverrideManifestSha256,
383
+ expectedBaseActiveOverrideManifestSha256: plan.baseActiveOverrideManifestSha256,
226
384
  policyVersion: plan.policyVersion,
227
385
  rendererFormat: "swap-observation-v1",
228
386
  planHash: plan.planHash,
229
387
  addedOverrides: plan.addedOverrides,
230
- nextOverrideManifestSha256: plan.nextOverrideManifestSha256,
388
+ nextActiveOverrideManifestSha256: plan.nextActiveOverrideManifestSha256,
231
389
  canonicalSequenceSha256: canonicalSequenceHash(
232
390
  built.canonical,
233
391
  plan.baseCanonicalThroughOrdinal,
@@ -277,7 +435,7 @@ export class ContextManager {
277
435
  previousRevisionNumber: built.revision.revisionNumber,
278
436
  revisionNumber: revision.revisionNumber,
279
437
  addedOverrideCount: plan.addedOverrides.length,
280
- totalOverrideCount: revision.totalOverrideCount,
438
+ activeOverrideCount: revision.activeOverrideCount,
281
439
  originalObservationBytes: planning.originalObservationBytes,
282
440
  projectedObservationBytes: planning.projectedObservationBytes,
283
441
  rawTokensBefore: plan.rawTokensBefore,
@@ -293,6 +451,224 @@ export class ContextManager {
293
451
  durationMs: elapsedMs(startedAt),
294
452
  });
295
453
  }
454
+
455
+ async retirePrefix(
456
+ trigger: ContextRetirementTrigger,
457
+ ): Promise<ContextRetirementResult> {
458
+ const startedAt = performance.now();
459
+ const tools = this.input.tools();
460
+ let built;
461
+ let activePrepared;
462
+ let activeUsage;
463
+ let closedTurns;
464
+ try {
465
+ this.input.store.assertContextRevisionIdle();
466
+ built = this.input.ledger.buildCommittedModelRequest(tools);
467
+ activePrepared = this.input.model.prepare(built.request);
468
+ activeUsage = this.input.contextMeter.measure(activePrepared);
469
+ closedTurns = this.input.store.loadClosedTurnBoundaries();
470
+ } catch (error) {
471
+ throw managerError("snapshot", error);
472
+ }
473
+
474
+ let planning;
475
+ try {
476
+ planning = this.retirementPlanner.plan({
477
+ active: built.compiled,
478
+ revision: built.revision,
479
+ surface: built.surface,
480
+ activeOverrides: built.activeOverrides,
481
+ canonical: built.canonical,
482
+ closedTurns,
483
+ activePrepared,
484
+ activeUsage,
485
+ tools,
486
+ policy: recallFirstRetirementPolicyV1,
487
+ trigger: trigger.kind,
488
+ ...(trigger.kind === "benchmark_forced"
489
+ ? { forcedTargetTokens: trigger.targetTokens }
490
+ : {}),
491
+ });
492
+ } catch (error) {
493
+ throw managerError("plan", error);
494
+ }
495
+ const planningDurationMs = elapsedMs(startedAt);
496
+ if (
497
+ planning.outcome === "below_target" ||
498
+ planning.outcome === "no_complete_prefix"
499
+ ) {
500
+ return Object.freeze({
501
+ status: "unchanged",
502
+ outcome: planning.outcome,
503
+ revisionId: built.revision.revisionId,
504
+ revisionNumber: built.revision.revisionNumber,
505
+ keepFromOrdinal: built.revision.keepFromOrdinal,
506
+ activeOverrideCount: built.revision.activeOverrideCount,
507
+ guardedTokensBefore: planning.guardedTokensBefore,
508
+ targetTokens: planning.targetTokens,
509
+ planningDurationMs,
510
+ durationMs: elapsedMs(startedAt),
511
+ });
512
+ }
513
+
514
+ if (!("plan" in planning)) {
515
+ throw new ContextManagerError(
516
+ "validate",
517
+ "missing_retirement_plan",
518
+ true,
519
+ false,
520
+ "Committable prefix retirement produced no revision plan.",
521
+ );
522
+ }
523
+ const plan = planning.plan;
524
+ const validationStartedAt = performance.now();
525
+ const nextActiveOverrides = built.activeOverrides.filter(
526
+ (override) => override.ordinal >= plan.nextKeepFromOrdinal,
527
+ );
528
+ let candidateCompiled;
529
+ let candidatePrepared;
530
+ try {
531
+ candidateCompiled = this.compiler.compileProspective({
532
+ active: built.compiled,
533
+ canonical: built.canonical,
534
+ activeOverrides: built.activeOverrides,
535
+ addedOverrides: [],
536
+ activeSurface: built.surface,
537
+ keepFromOrdinal: plan.nextKeepFromOrdinal,
538
+ });
539
+ const candidate = this.requestBuilder.build({
540
+ canonical: built.canonical,
541
+ revision: built.revision,
542
+ surface: built.surface,
543
+ activeOverrides: nextActiveOverrides,
544
+ compiled: candidateCompiled,
545
+ tools,
546
+ });
547
+ candidatePrepared = this.input.model.prepare(candidate.request);
548
+ assertRetirementCandidatePrepared(activePrepared, candidatePrepared, plan);
549
+
550
+ const current = this.input.ledger.buildCommittedModelRequest(tools);
551
+ const currentPrepared = this.input.model.prepare(current.request);
552
+ assertRetirementPlanBaseCurrent(plan, {
553
+ active: current.compiled,
554
+ revision: current.revision,
555
+ surface: current.surface,
556
+ activeOverrides: current.activeOverrides,
557
+ canonical: current.canonical,
558
+ activePrepared: currentPrepared,
559
+ });
560
+ this.input.store.assertContextRevisionIdle();
561
+ } catch (error) {
562
+ throw managerError("validate", error);
563
+ }
564
+ const validationDurationMs = elapsedMs(validationStartedAt);
565
+
566
+ const revisionId = this.input.idFactory.createContextRevisionId();
567
+ const transactionStartedAt = performance.now();
568
+ let revision;
569
+ try {
570
+ revision = this.input.store.commitPrefixRetirementRevision({
571
+ revisionId,
572
+ expectedBaseRevisionId: plan.baseRevisionId,
573
+ expectedBaseRevisionNumber: plan.baseRevisionNumber,
574
+ expectedBaseKeepFromOrdinal: plan.baseKeepFromOrdinal,
575
+ expectedCanonicalThroughOrdinal: plan.baseCanonicalThroughOrdinal,
576
+ expectedSurfaceSha256: plan.baseSurfaceSha256,
577
+ expectedBaseActiveOverrideManifestSha256: plan.baseActiveOverrideManifestSha256,
578
+ policyVersion: plan.policyVersion,
579
+ planHash: plan.planHash,
580
+ nextKeepFromOrdinal: plan.nextKeepFromOrdinal,
581
+ retiredThroughOrdinal: plan.retiredThroughOrdinal,
582
+ retiredTurnCount: plan.retiredTurnCount,
583
+ retiredFrameCount: plan.retiredFrameCount,
584
+ retiredMessageCount: plan.retiredMessageCount,
585
+ nextActiveOverrideCount: plan.nextActiveOverrideCount,
586
+ nextActiveOverrideManifestSha256: plan.nextActiveOverrideManifestSha256,
587
+ canonicalSequenceSha256: plan.canonicalSequenceSha256,
588
+ renderedMessageSha256: plan.renderedMessageSha256,
589
+ });
590
+ } catch (error) {
591
+ throw managerError("commit", error);
592
+ }
593
+ const transactionDurationMs = elapsedMs(transactionStartedAt);
594
+
595
+ const activationStartedAt = performance.now();
596
+ try {
597
+ this.input.contextMeter.startRevision({
598
+ reason: "context_rebuilt",
599
+ requestConfigHash: candidatePrepared.requestConfigHash,
600
+ toolSchemaHash: candidatePrepared.toolSchemaHash,
601
+ });
602
+ this.input.committedPrefixAuditor.audit(revisionId, candidatePrepared);
603
+ const usage = this.input.contextMeter.measure(candidatePrepared);
604
+ if (usage.source !== "estimated_full") {
605
+ throw new Error(
606
+ "First context measurement after prefix retirement was not a full estimate.",
607
+ );
608
+ }
609
+ await this.input.onUsageUpdated(usage);
610
+ } catch (error) {
611
+ throw new ContextManagerError(
612
+ "activate",
613
+ errorCode(error),
614
+ true,
615
+ true,
616
+ "Prefix retirement committed but runtime activation failed.",
617
+ { cause: error },
618
+ );
619
+ }
620
+ const activationDurationMs = elapsedMs(activationStartedAt);
621
+
622
+ return Object.freeze({
623
+ status: "retired",
624
+ outcome: planning.outcome,
625
+ previousRevisionId: built.revision.revisionId,
626
+ revisionId,
627
+ previousRevisionNumber: built.revision.revisionNumber,
628
+ revisionNumber: revision.revisionNumber,
629
+ previousKeepFromOrdinal: built.revision.keepFromOrdinal,
630
+ keepFromOrdinal: revision.keepFromOrdinal,
631
+ retiredTurnCount: plan.retiredTurnCount,
632
+ retiredFrameCount: plan.retiredFrameCount,
633
+ retiredMessageCount: plan.retiredMessageCount,
634
+ activeOverrideCount: revision.activeOverrideCount,
635
+ rawTokensBefore: plan.rawTokensBefore,
636
+ rawTokensAfter: plan.rawTokensAfter,
637
+ guardedTokensBefore: plan.guardedTokensBefore,
638
+ guardedTokensAfter: plan.guardedTokensAfter,
639
+ targetTokens: plan.targetTokens,
640
+ planHash: plan.planHash,
641
+ planningDurationMs,
642
+ validationDurationMs,
643
+ transactionDurationMs,
644
+ activationDurationMs,
645
+ durationMs: elapsedMs(startedAt),
646
+ });
647
+ }
648
+ }
649
+
650
+ function assertRetirementCandidatePrepared(
651
+ active: ReturnType<ModelPreparer["prepare"]>,
652
+ candidate: ReturnType<ModelPreparer["prepare"]>,
653
+ plan: PrefixRetirementPlan,
654
+ ): void {
655
+ const rawTokens = estimatePromptSegments(candidate.promptSegments).totalTokens;
656
+ const fingerprint = promptPrefixFingerprint(candidate);
657
+ if (
658
+ candidate.provider !== active.provider ||
659
+ candidate.model !== active.model ||
660
+ candidate.requestConfigHash !== active.requestConfigHash ||
661
+ candidate.toolSchemaHash !== active.toolSchemaHash ||
662
+ candidate.requestMaxOutputTokens !== active.requestMaxOutputTokens ||
663
+ rawTokens !== plan.rawTokensAfter ||
664
+ fingerprint.prefixHash !== plan.projectedPrefixHash ||
665
+ plan.rawTokensAfter >= plan.rawTokensBefore ||
666
+ plan.guardedTokensAfter >= plan.guardedTokensBefore
667
+ ) {
668
+ throw new Error(
669
+ "Prepared candidate does not match its validated prefix retirement plan.",
670
+ );
671
+ }
296
672
  }
297
673
 
298
674
  function assertCandidatePrepared(
@@ -325,7 +701,9 @@ function managerError(
325
701
  return error;
326
702
  }
327
703
  const fatal = isFatalContextError(error) || stage === "commit";
328
- const diagnostic = error instanceof SwapPlanningDiagnosticError;
704
+ const diagnostic =
705
+ error instanceof SwapPlanningDiagnosticError ||
706
+ error instanceof PrefixRetirementPlanningError;
329
707
  return new ContextManagerError(
330
708
  stage,
331
709
  diagnostic ? error.code : errorCode(error),
@@ -342,6 +720,7 @@ function isFatalContextError(error: unknown): boolean {
342
720
  error instanceof ContextRevisionError ||
343
721
  error instanceof CompiledContextError ||
344
722
  error instanceof SwapPlanStaleError ||
723
+ error instanceof PrefixRetirementPlanStaleError ||
345
724
  error instanceof CommittedPrefixAuditError ||
346
725
  error instanceof SessionError
347
726
  );
@@ -6,3 +6,11 @@ export const swapOnlyPolicyV1 = Object.freeze({
6
6
  } as const);
7
7
 
8
8
  export type SwapOnlyPolicyV1 = typeof swapOnlyPolicyV1;
9
+
10
+ export const recallFirstRetirementPolicyV1 = Object.freeze({
11
+ version: "recall-first-retirement-v1",
12
+ protectedRecentTurnCount: 8,
13
+ targetInputRatio: 0.6,
14
+ } as const);
15
+
16
+ export type RecallFirstRetirementPolicyV1 = typeof recallFirstRetirementPolicyV1;
@@ -1,6 +1,7 @@
1
1
  import type { MessageId, ProtocolFrameId, ToolCallId } from "../ids/runtime-id";
2
2
  import {
3
3
  contentHash,
4
+ userMessageHash,
4
5
  rawResultHash,
5
6
  type CanonicalMessageRecord,
6
7
  type ProtocolContextView,
@@ -103,7 +104,16 @@ export class ContextProtocolValidator {
103
104
 
104
105
  if (
105
106
  options.fullIntegrity === true &&
106
- message.contentSha256 !== contentHash(message.content)
107
+ message.contentSha256 !==
108
+ (message.role === "user"
109
+ ? userMessageHash({
110
+ role: "user",
111
+ content: message.content,
112
+ ...(message.attachments === undefined
113
+ ? {}
114
+ : { attachments: message.attachments }),
115
+ })
116
+ : contentHash(message.content))
107
117
  ) {
108
118
  fail(
109
119
  "content_hash_mismatch",