tinker-agent 2.8.0 → 2.10.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 (66) hide show
  1. package/CHANGELOG.md +79 -1
  2. package/README.md +81 -11
  3. package/package.json +5 -3
  4. package/src/agent/runtime-context-capabilities.ts +19 -0
  5. package/src/agent/runtime-context-events.ts +127 -0
  6. package/src/agent/runtime-context-maintenance.ts +780 -0
  7. package/src/agent/runtime-hosted-session.ts +443 -0
  8. package/src/agent/runtime-interactions.ts +291 -0
  9. package/src/agent/runtime-prompt-scheduler.ts +182 -0
  10. package/src/agent/runtime-session-contracts.ts +317 -0
  11. package/src/agent/runtime-session.ts +250 -2130
  12. package/src/agent/runtime-skills.ts +544 -0
  13. package/src/cli/command-line.ts +26 -2
  14. package/src/cli/connect-runner.tsx +26 -0
  15. package/src/cli/main.ts +26 -0
  16. package/src/cli/output.ts +1 -1
  17. package/src/cli/public-cli-contract.ts +18 -0
  18. package/src/cli/public-config-contract.ts +1 -1
  19. package/src/cli/runner-dependencies.ts +6 -5
  20. package/src/cli/serve-runner.ts +45 -0
  21. package/src/cli/serve-runtime.ts +100 -0
  22. package/src/context/context-automation-policy.ts +12 -118
  23. package/src/context/context-swap-renderer.ts +14 -0
  24. package/src/events/types.ts +12 -0
  25. package/src/memory/memory-get-tool.ts +1 -1
  26. package/src/observation/observation-builder.ts +128 -48
  27. package/src/remote/client.ts +350 -0
  28. package/src/remote/config.ts +95 -0
  29. package/src/remote/http-server.ts +240 -0
  30. package/src/remote/protocol.ts +228 -0
  31. package/src/remote/service-store.ts +175 -0
  32. package/src/remote/service.ts +219 -0
  33. package/src/remote/sync-hub.ts +95 -0
  34. package/src/session/remote-history-reader.ts +143 -0
  35. package/src/session/resume-projection.ts +47 -21
  36. package/src/session/session-history-access.ts +238 -0
  37. package/src/session/session-store-context-readers.ts +183 -0
  38. package/src/session/session-store-ledger-writer.ts +315 -0
  39. package/src/session/session-store-record-writer.ts +318 -0
  40. package/src/session/session-store-recovery.ts +225 -0
  41. package/src/session/session-store-revisions.ts +1004 -0
  42. package/src/session/session-store-sql.ts +40 -0
  43. package/src/session/session-store-validation.ts +657 -0
  44. package/src/session/session-store.ts +756 -3186
  45. package/src/tools/bash-task.ts +46 -18
  46. package/src/tools/bash.ts +44 -2
  47. package/src/tools/glob.ts +107 -19
  48. package/src/tools/grep-output.ts +130 -0
  49. package/src/tools/grep-pagination.ts +73 -0
  50. package/src/tools/grep-path.ts +11 -0
  51. package/src/tools/grep-snippets.ts +111 -0
  52. package/src/tools/grep.ts +139 -154
  53. package/src/tools/read.ts +0 -9
  54. package/src/tools/recall.ts +106 -50
  55. package/src/tools/registry.ts +4 -6
  56. package/src/tools/ripgrep.ts +19 -26
  57. package/src/tools/shell-process.ts +30 -4
  58. package/src/tools/task-output-range.ts +146 -0
  59. package/src/tools/task-output-tool.ts +35 -5
  60. package/src/tools/task-output.ts +35 -0
  61. package/src/tools/task-stop.ts +2 -1
  62. package/src/tools/task-tool-args.ts +34 -0
  63. package/src/tools/terminal-screen.ts +11 -2
  64. package/src/tools/types.ts +39 -2
  65. package/src/tui/event-store.ts +23 -5
  66. package/src/tui/remote-app.tsx +210 -0
package/CHANGELOG.md CHANGED
@@ -5,6 +5,82 @@ All notable user-facing changes to Tinker are documented here. The project follo
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [2.10.0] - 2026-09-06
9
+
10
+ ### Added
11
+
12
+ - Add remote access for paired clients. `tinker serve --config <path>` runs the
13
+ local daemon that paired remote clients connect to, and
14
+ `tinker connect --config <path>` attaches a terminal client to a running
15
+ service; exiting the client detaches without stopping the service. See
16
+ `docs/remote-access.md`. A native iOS client under `client/Tinker` is included
17
+ for acceptance.
18
+ - Add `offset` and `head_limit` pagination to `Glob` results. Long result sets
19
+ now report whether more matches remain instead of being silently capped.
20
+ - Add `cols` and `rows` options to `Bash` for custom initial PTY dimensions when
21
+ `tty=true`, so terminal applications that require a specific size can run in
22
+ an appropriately sized pseudo-terminal.
23
+
24
+ ### Changed
25
+
26
+ - Run `Grep` directory searches with ripgrep's working directory set to the search
27
+ directory and `.` as the path argument. Directory globs such as `sub/**` now
28
+ match from that directory; globs that relied on absolute search paths may need
29
+ updating. Explicit file searches keep their existing invocation.
30
+ - Increase the default `Bash` foreground wait before a command continues in the
31
+ background, and clarify how `TaskOutput` and `TaskInput` poll PTY sessions.
32
+ - Omit internal file hashes from tool observations; the model sees the same
33
+ content without opaque bookkeeping tokens.
34
+
35
+ ### Fixed
36
+
37
+ - Stop `TaskStop` from hanging when a shell's child processes survive after the
38
+ shell itself exits; stopping a task now reliably reports its final state.
39
+ - Preserve `Grep` match context across paginated results, keep long-line matches
40
+ instead of dropping them, and report counts and paginated records
41
+ consistently, including a `count-matches` mode with explicit counting units.
42
+ - Resolve symbolic-link search roots in `Glob` while preserving result paths,
43
+ and exclude directory and broken symbolic links from results.
44
+ - Accept pagination arguments in `Read` when reading empty files instead of
45
+ rejecting the call.
46
+
47
+ ## [2.9.0] - 2026-09-05
48
+
49
+ ### Added
50
+
51
+ - Add optional `sessionId` selection to `RecallSearch` and `RecallGet`, allowing
52
+ the agent to retrieve original history from another session, including a
53
+ Memory result's source session or another workspace under the same Tinker
54
+ home. Results identify their source session and workspace. External history is
55
+ read without resuming, repairing, or migrating the source session. Retrieved
56
+ text is sent to the current model provider and stored in the current session;
57
+ cross-workspace access does not show an additional confirmation dialog.
58
+ - Add `offset` and `limit` to `TaskOutput` for reading consecutive, numbered lines
59
+ from non-PTY task logs. Omitting both preserves the head/tail preview; PTY tasks
60
+ continue to return the current terminal screen.
61
+
62
+ ### Changed
63
+
64
+ - Enable automatic context swaps and prefix retirement as product defaults,
65
+ independent of model names, evaluation results, report availability, and tool
66
+ description hashes. Existing Recall capability, protocol, transaction, and
67
+ request-consistency checks remain enforced. Evaluation still measures model
68
+ behavior but no longer enables or disables runtime maintenance; this does not
69
+ guarantee reliable history retrieval by every model.
70
+ - Clarify shell guidance so finite builds, tests, and checks run in the foreground
71
+ when their result is needed next and no independent work remains. Reserve
72
+ background execution for persistent processes or useful concurrent work, and
73
+ describe task tools in terms of their capabilities.
74
+
75
+ ### Fixed
76
+
77
+ - Restore sessions containing follow-up messages submitted during an active turn
78
+ instead of rejecting turns with multiple user messages. Resumed timelines keep
79
+ follow-ups in their original order for completed and failed turns.
80
+ - Include available exit codes, termination signals, and errors in `TaskOutput`
81
+ observations. Distinguish PTY screen snapshots from cumulative log statistics,
82
+ and clarify byte-limited log ranges and polling of a still-growing final line.
83
+
8
84
  ## [2.8.0] - 2026-09-04
9
85
 
10
86
  ### Changed
@@ -361,7 +437,9 @@ All notable user-facing changes to Tinker are documented here. The project follo
361
437
  - First formal npm release under the `tinker-agent` package name with the `tinker`
362
438
  executable.
363
439
 
364
- [Unreleased]: https://github.com/ishowshao/tinker/compare/v2.8.0...HEAD
440
+ [Unreleased]: https://github.com/ishowshao/tinker/compare/v2.10.0...HEAD
441
+ [2.10.0]: https://github.com/ishowshao/tinker/releases/tag/v2.10.0
442
+ [2.9.0]: https://github.com/ishowshao/tinker/releases/tag/v2.9.0
365
443
  [2.8.0]: https://github.com/ishowshao/tinker/releases/tag/v2.8.0
366
444
  [2.7.0]: https://github.com/ishowshao/tinker/releases/tag/v2.7.0
367
445
  [2.6.0]: https://github.com/ishowshao/tinker/releases/tag/v2.6.0
package/README.md CHANGED
@@ -19,7 +19,7 @@ This does not pretend that any model has infinite tokens or guarantee that it wi
19
19
  - `TaskList` / `TaskOutput` / `TaskInput` / `TaskStop` — Inspect, interact with, and stop long-running shell tasks
20
20
  - `WebSearch` — Search the web via Exa API
21
21
  - `WebFetch` — Fetch and refine web page content (local, browser, or Exa backend)
22
- - `Recall` — Search or retrieve model-visible history from the current session
22
+ - `RecallSearch` / `RecallGet` — Search or retrieve model-visible history from the current session or an explicitly selected session
23
23
  - **Agent Skills**: Discover compatible `SKILL.md` packages at project and user
24
24
  scope, disclose their catalog progressively, and keep activated instructions
25
25
  durable across context compaction and session resume.
@@ -29,11 +29,79 @@ This does not pretend that any model has infinite tokens or guarantee that it wi
29
29
  - **Turn cancellation**: Users can cancel an ongoing turn safely, with protocol-safe synthetic tool messages.
30
30
  - **Context metering**: Budget-aware context management with protocol validation before sending requests to the model.
31
31
  - **Deterministic context compaction**: Idle sessions can swap eligible historical tool output into Recall-addressable placeholders without calling the model.
32
- - **Infinite Context architecture**: Immutable canonical history, deterministic context revisions, Recall-addressable cold state, and qualified prefix retirement support sessions designed to continue indefinitely without pretending the model has infinite tokens. See the [technical design](docs/infinite-context-technical-design.md).
32
+ - **Infinite Context architecture**: Immutable canonical history, deterministic context revisions, Recall-addressable cold state, and protocol-safe prefix retirement support sessions designed to continue indefinitely without pretending the model has infinite tokens.
33
33
  - **Choice of models**: Uses an OpenAI-compatible Chat Completions transport with
34
34
  explicit model and context limits. Actual provider support must be established
35
35
  by a qualification matrix; transport compatibility alone is not a guarantee.
36
36
 
37
+ ### PTY screen size
38
+
39
+ `Bash` accepts optional `cols` and `rows` for the initial PTY screen size:
40
+
41
+ ```json
42
+ {"command":"python3 -q","tty":true,"cols":160,"rows":48}
43
+ ```
44
+
45
+ Each omitted dimension keeps its default: 80 columns and 24 rows. Columns accept
46
+ integers from 2 to 1000; rows accept integers from 1 to 1000. When `tty` is omitted
47
+ or `false`, dimensions are ignored and execution continues through ordinary pipes.
48
+ `TaskOutput` and `TaskInput` report the actual screen size. Dimensions are set at
49
+ creation; running tasks cannot be resized through these tools.
50
+
51
+ ### Reading another session's history
52
+
53
+ Both Recall tools accept an optional `sessionId` (a canonical session UUID).
54
+ Omitting it keeps the current-session behavior. In the TUI or one-shot CLI, the
55
+ agent can use a session ID you supply or a Memory result's `sourceSessionId`:
56
+
57
+ ```ts
58
+ RecallSearch({ sessionId: sourceSessionId, query: "distinctive-anchor" })
59
+ RecallGet({ sessionId: sourceSessionId, source: "ctx://message/<message-UUID>" })
60
+ ```
61
+
62
+ Reuse the same `sessionId` for Get and subsequent pages. Search's
63
+ `snapshot_through_ordinal`, ordinals and turn numbers belong to that session only.
64
+ Results identify their source session and workspace. Missing, ambiguous, unsafe,
65
+ unsupported or corrupt sessions return errors; Recall never silently switches
66
+ back to the current session. Memory is not required.
67
+
68
+ **Data scope:** explicit session selection can read other workspaces under the
69
+ configured Tinker home (`TINKER_HOME` overrides the OS home). Retrieved text is
70
+ sent to the **current model provider** and saved as a tool result in the **current
71
+ session**, including when projects or providers differ. There is no additional
72
+ cross-workspace confirmation dialog.
73
+
74
+ External history uses short read-only SQLite snapshots, without resuming the
75
+ source session, taking its execution lock, repairing indexes or migrating its
76
+ schema. Only the current readable schema is supported. Active writers' committed
77
+ history is readable; an open/interrupted turn may be incomplete. Historical
78
+ instructions, Skills and authorizations are not activated, and content hashes
79
+ prove storage consistency, not truth. Use Read/Grep to verify current facts.
80
+ Original workspaces need not still exist. SQLite WAL reads can coordinate through
81
+ SHM; read-only access does not promise that an active writer's directory remains
82
+ byte-for-byte unchanged. See the
83
+ [history access implementation](src/session/session-history-access.ts).
84
+
85
+ ### Automatic context maintenance and evaluation
86
+
87
+ Automatic swap and prefix retirement are product defaults, independent of model
88
+ names, evaluation scores, report availability, and tool-description hashes. Under
89
+ context pressure, the runtime tries swap first and attempts retirement only when
90
+ swap has no eligible candidates or insufficient candidates. Existing scheduling,
91
+ protocol boundaries, cancellation, transaction, and request/surface consistency
92
+ checks remain in force. Required Recall tools and the current retirement contract
93
+ must be available; enabling a product policy does not bypass execution safety.
94
+ There are no new user-facing switches or model allowlists.
95
+
96
+ Evaluation measures model behavior; it does not enable or disable these features.
97
+ New evaluation reports (`active-recall-qualification-v2`) contain metrics, gates,
98
+ and provenance but no automation flags. Historical reports remain unchanged. The
99
+ session-selection evaluation
100
+ retains its measured failure: Search → Get missed the threshold even though all
101
+ Recall-only tasks passed. Its old automation fields are legacy data, not runtime
102
+ policy. The subsequent description cleanup has not been re-evaluated with a live
103
+ model.
104
+
37
105
  ## Quick Start
38
106
 
39
107
  ### Install with your existing agent
@@ -104,6 +172,8 @@ The installed package exposes this public CLI:
104
172
  | `tinker run [--profile <profile-name>] [--yolo] --stdin` | Read the prompt from standard input until EOF. |
105
173
  | `tinker run [--profile <profile-name>] [--yolo] --file <path>` | Read the prompt from a UTF-8 text file. |
106
174
  | `tinker update` | Update the global npm installation from the official npm registry. |
175
+ | `tinker serve --config <path>` | Run the local daemon for paired remote clients. |
176
+ | `tinker connect --config <path>` | Attach a terminal client to a service; exiting detaches only. |
107
177
  | `tinker --help` | Show top-level CLI help. |
108
178
  | `tinker help run` | Show one-shot command help. |
109
179
  | `tinker help update` | Show update command help. |
@@ -175,7 +245,7 @@ are required. Boolean environment values accept case-insensitive `true/false`,
175
245
  | `EXA_API_KEY` | Tooling | All modes | No | Non-empty string | — | Yes | Enables WebSearch and the Exa WebFetch backend when set. |
176
246
  | `TINKER_MCP_TIMEOUT_MS` | Tooling | All modes | No | Positive integer | `60000` | No | MCP tool-call timeout in milliseconds. |
177
247
  | `TINKER_MCP_MAX_OBSERVATION_CHARS` | Tooling | All modes | No | Positive integer | `40000` | No | Maximum model-visible characters in one MCP result. |
178
- | `TINKER_BASH_DEFAULT_TIMEOUT_MS` | Tooling | All modes | No | Positive integer | `5000` | No | Default Bash foreground timeout in milliseconds. |
248
+ | `TINKER_BASH_DEFAULT_TIMEOUT_MS` | Tooling | All modes | No | Positive integer | `60000` | No | Default Bash foreground timeout in milliseconds. |
179
249
  | `TINKER_BASH_MAX_TIMEOUT_MS` | Tooling | All modes | No | Positive integer | `600000` | No | Maximum Bash foreground timeout in milliseconds. |
180
250
  | `TINKER_YOLO` | Tooling | All modes | No | Boolean | `false` | No | Allow high-confidence destructive Bash commands without confirmation. |
181
251
  | `TINKER_GREP_TIMEOUT_MS` | Tooling | All modes | No | Positive integer | `20000` | No | Bundled ripgrep invocation timeout in milliseconds. |
@@ -398,9 +468,9 @@ pixels in total. Provider requests preserve smaller images and proportionally
398
468
  downscale larger images to a maximum 2048-pixel long edge. Context planning uses
399
469
  fixed local token buckets derived from the materialized dimensions and performs no
400
470
  independent token-estimator request. See the
401
- [`image token bucket design`](docs/image-token-bucket-estimation-design.md) and
402
- [`multimodal image input design`](docs/multimodal-image-input-design.md) for the
403
- complete fixed policy and persistence contract.
471
+ [image input policy](src/image/image-input-policy.ts) and
472
+ [image asset store](src/image/image-asset-store.ts) for the policy and storage
473
+ implementations.
404
474
 
405
475
  `ViewImage(file_path)` is registered only when the selected profile declares both
406
476
  `inputModalities: ["text", "image"]` and
@@ -410,8 +480,8 @@ text-only for tool results. Relative paths stay inside the workspace, absolute
410
480
  paths may explicitly select an external local file, and symbolic links are
411
481
  rejected. Canonical history stores content-addressed image references rather than
412
482
  Base64, while stdout, TUI, Recall, and logs show deterministic text summaries.
413
- See the [`ViewImage tool design`](docs/view-image-tool-design.md) for the complete
414
- capability, persistence, provider, and compaction contract.
483
+ See the [`ViewImage implementation`](src/tools/view-image.ts) for the tool's
484
+ validation and execution logic.
415
485
 
416
486
  ### Built-in Slash Commands
417
487
 
@@ -476,9 +546,9 @@ turn. Built-in slash commands appear first in suggestions, followed by project
476
546
  commands in configuration order. Custom commands accept no arguments and cannot
477
547
  override built-ins. The optional configuration is loaded once at TUI startup,
478
548
  must be valid when present, and has a 1 MiB size limit. It is not loaded by the
479
- one-shot `tinker run` command. See
480
- [`docs/project-custom-slash-commands-design.md`](docs/project-custom-slash-commands-design.md)
481
- for the full contract.
549
+ one-shot `tinker run` command. See the
550
+ [project command loader](src/tui/project-slash-commands.ts) for configuration
551
+ validation and loading behavior.
482
552
 
483
553
  ### Global Runtime Data
484
554
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tinker-agent",
3
- "version": "2.8.0",
3
+ "version": "2.10.0",
4
4
  "description": "A personal coding agent with an interactive TUI and one-shot CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -23,6 +23,7 @@
23
23
  "src/memory",
24
24
  "src/model",
25
25
  "src/observation",
26
+ "src/remote",
26
27
  "src/session",
27
28
  "src/skills",
28
29
  "src/tools",
@@ -64,8 +65,9 @@
64
65
  "docs:generate": "bun scripts/render-public-contract-docs.ts --write",
65
66
  "docs:check": "bun scripts/render-public-contract-docs.ts --check",
66
67
  "tinker": "bun src/cli/index.ts",
67
- "check": "bun run typecheck && bun run format:check && bun run lint && bun run docs:check && bun run test && bun run bench:smoke",
68
- "check:fast": "bun run typecheck && bun run format:check && bun run lint && bun run test:fast",
68
+ "check": "bun run check:source-lines && bun run typecheck && bun run format:check && bun run lint && bun run docs:check && bun run test && bun run bench:smoke",
69
+ "check:fast": "bun run check:source-lines && bun run typecheck && bun run format:check && bun run lint && bun run test:fast",
70
+ "check:source-lines": "bun scripts/check-source-lines.ts",
69
71
  "format": "biome format --write .",
70
72
  "format:check": "biome format .",
71
73
  "lint": "eslint \"bin/**/*.js\" \"src/**/*.{ts,tsx}\" \"scripts/**/*.ts\" \"packages/**/*.ts\" --max-warnings=0",
@@ -0,0 +1,19 @@
1
+ import { CURRENT_RECALL_RETIREMENT_CONTRACT_VERSION } from "../context/recall-retirement-contract";
2
+ import type { ToolRegistry } from "../tools/registry";
3
+
4
+ /** Structural runtime prerequisites, not model qualification or description hashes. */
5
+ export function assertContextMaintenanceCapabilities(
6
+ registry: Pick<ToolRegistry, "get">,
7
+ recallContractVersion: string,
8
+ ): void {
9
+ for (const name of ["RecallSearch", "RecallGet"]) {
10
+ if (registry.get(name) === undefined) {
11
+ throw new Error(`Context maintenance requires an executable ${name} tool.`);
12
+ }
13
+ }
14
+ if (recallContractVersion !== CURRENT_RECALL_RETIREMENT_CONTRACT_VERSION) {
15
+ throw new Error(
16
+ "Context maintenance requires the current Recall retirement contract.",
17
+ );
18
+ }
19
+ }
@@ -0,0 +1,127 @@
1
+ import {
2
+ type ContextCompactionResult,
3
+ type ContextRetirementResult,
4
+ } from "../context/context-manager";
5
+ import { type StoredContextSurfaceV8 } from "../context/context-surface";
6
+ import type { ContextRevisionFinishedData } from "../events/types";
7
+ import { type ModelClient } from "../model/model-client";
8
+
9
+ export function assertPreparedMatchesSurface(
10
+ prepared: ReturnType<ModelClient["prepare"]>,
11
+ surface: StoredContextSurfaceV8,
12
+ ): void {
13
+ if (
14
+ prepared.requestConfigHash !== surface.requestConfigSha256 ||
15
+ prepared.toolSchemaHash !== surface.toolSchemaSha256 ||
16
+ prepared.requestMaxOutputTokens !== surface.requestMaxOutputTokens
17
+ ) {
18
+ throw new Error("Prepared model request does not match its context surface.");
19
+ }
20
+ }
21
+
22
+ export function elapsedMs(startedAt: number): number {
23
+ return Math.round((performance.now() - startedAt) * 100) / 100;
24
+ }
25
+
26
+ export function boundedContextErrorCode(code: string): string {
27
+ return /^[A-Za-z0-9_]+$/.test(code) && code.length <= 80
28
+ ? code
29
+ : "CONTEXT_COMPACTION_FAILED";
30
+ }
31
+ export function contextRevisionFinishedData(
32
+ result: ContextCompactionResult,
33
+ reason: "manual" | "runtime_pressure" | "model_directed" = "manual",
34
+ automationPolicyId?: string,
35
+ ): ContextRevisionFinishedData {
36
+ if (result.status === "unchanged") {
37
+ return {
38
+ strategy: "swap",
39
+ reason,
40
+ policyVersion: "swap-only-v1",
41
+ outcome: result.outcome,
42
+ baseRevisionNumber: result.revisionNumber,
43
+ addedOverrideCount: 0,
44
+ activeOverrideCount: result.activeOverrideCount,
45
+ originalObservationBytes: 0,
46
+ projectedObservationBytes: 0,
47
+ rawTokensBefore: result.rawTokensBefore,
48
+ guardedTokensBefore: result.guardedTokensBefore,
49
+ targetTokens: result.targetTokens,
50
+ durationMs: result.durationMs,
51
+ ...(automationPolicyId === undefined ? {} : { automationPolicyId }),
52
+ };
53
+ }
54
+ return {
55
+ strategy: "swap",
56
+ reason,
57
+ policyVersion: "swap-only-v1",
58
+ outcome: result.outcome,
59
+ baseRevisionNumber: result.previousRevisionNumber,
60
+ revisionNumber: result.revisionNumber,
61
+ addedOverrideCount: result.addedOverrideCount,
62
+ activeOverrideCount: result.activeOverrideCount,
63
+ originalObservationBytes: result.originalObservationBytes,
64
+ projectedObservationBytes: result.projectedObservationBytes,
65
+ rawTokensBefore: result.rawTokensBefore,
66
+ rawTokensAfter: result.rawTokensAfter,
67
+ guardedTokensBefore: result.guardedTokensBefore,
68
+ guardedTokensAfter: result.guardedTokensAfter,
69
+ targetTokens: result.targetTokens,
70
+ planHash: result.planHash,
71
+ durationMs: result.durationMs,
72
+ ...(automationPolicyId === undefined ? {} : { automationPolicyId }),
73
+ };
74
+ }
75
+
76
+ export function contextRetirementFinishedData(
77
+ result: ContextRetirementResult,
78
+ reason: "manual" | "runtime_pressure" = "manual",
79
+ automationPolicyId?: string,
80
+ ): ContextRevisionFinishedData {
81
+ if (result.status === "unchanged") {
82
+ return {
83
+ strategy: "retire_prefix",
84
+ reason,
85
+ policyVersion: "recall-first-retirement-v1",
86
+ outcome: result.outcome,
87
+ baseRevisionNumber: result.revisionNumber,
88
+ previousKeepFromOrdinal: result.keepFromOrdinal,
89
+ keepFromOrdinal: result.keepFromOrdinal,
90
+ retiredTurnCount: 0,
91
+ retiredFrameCount: 0,
92
+ retiredMessageCount: 0,
93
+ activeOverrideCount: result.activeOverrideCount,
94
+ guardedTokensBefore: result.guardedTokensBefore,
95
+ targetTokens: result.targetTokens,
96
+ planningDurationMs: result.planningDurationMs,
97
+ durationMs: result.durationMs,
98
+ ...(automationPolicyId === undefined ? {} : { automationPolicyId }),
99
+ };
100
+ }
101
+ return {
102
+ strategy: "retire_prefix",
103
+ reason,
104
+ policyVersion: "recall-first-retirement-v1",
105
+ outcome: result.outcome,
106
+ baseRevisionNumber: result.previousRevisionNumber,
107
+ revisionNumber: result.revisionNumber,
108
+ previousKeepFromOrdinal: result.previousKeepFromOrdinal,
109
+ keepFromOrdinal: result.keepFromOrdinal,
110
+ retiredTurnCount: result.retiredTurnCount,
111
+ retiredFrameCount: result.retiredFrameCount,
112
+ retiredMessageCount: result.retiredMessageCount,
113
+ activeOverrideCount: result.activeOverrideCount,
114
+ rawTokensBefore: result.rawTokensBefore,
115
+ rawTokensAfter: result.rawTokensAfter,
116
+ guardedTokensBefore: result.guardedTokensBefore,
117
+ guardedTokensAfter: result.guardedTokensAfter,
118
+ targetTokens: result.targetTokens,
119
+ planHash: result.planHash,
120
+ planningDurationMs: result.planningDurationMs,
121
+ validationDurationMs: result.validationDurationMs,
122
+ transactionDurationMs: result.transactionDurationMs,
123
+ activationDurationMs: result.activationDurationMs,
124
+ durationMs: result.durationMs,
125
+ ...(automationPolicyId === undefined ? {} : { automationPolicyId }),
126
+ };
127
+ }