tinker-agent 2.8.0 → 2.9.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 (35) hide show
  1. package/CHANGELOG.md +39 -1
  2. package/README.md +64 -10
  3. package/package.json +4 -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-interactions.ts +291 -0
  8. package/src/agent/runtime-prompt-scheduler.ts +182 -0
  9. package/src/agent/runtime-session-contracts.ts +317 -0
  10. package/src/agent/runtime-session.ts +250 -2130
  11. package/src/agent/runtime-skills.ts +544 -0
  12. package/src/cli/runner-dependencies.ts +6 -5
  13. package/src/context/context-automation-policy.ts +12 -118
  14. package/src/events/types.ts +12 -0
  15. package/src/memory/memory-get-tool.ts +1 -1
  16. package/src/observation/observation-builder.ts +41 -11
  17. package/src/session/resume-projection.ts +47 -21
  18. package/src/session/session-history-access.ts +238 -0
  19. package/src/session/session-store-context-readers.ts +183 -0
  20. package/src/session/session-store-ledger-writer.ts +315 -0
  21. package/src/session/session-store-record-writer.ts +318 -0
  22. package/src/session/session-store-recovery.ts +225 -0
  23. package/src/session/session-store-revisions.ts +1004 -0
  24. package/src/session/session-store-sql.ts +40 -0
  25. package/src/session/session-store-validation.ts +657 -0
  26. package/src/session/session-store.ts +756 -3186
  27. package/src/tools/bash-task.ts +20 -2
  28. package/src/tools/recall.ts +106 -50
  29. package/src/tools/registry.ts +4 -6
  30. package/src/tools/task-output-range.ts +146 -0
  31. package/src/tools/task-output-tool.ts +35 -5
  32. package/src/tools/task-output.ts +35 -0
  33. package/src/tools/task-tool-args.ts +34 -0
  34. package/src/tools/types.ts +9 -0
  35. package/src/tui/event-store.ts +8 -3
package/CHANGELOG.md CHANGED
@@ -5,6 +5,43 @@ All notable user-facing changes to Tinker are documented here. The project follo
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [2.9.0] - 2026-09-05
9
+
10
+ ### Added
11
+
12
+ - Add optional `sessionId` selection to `RecallSearch` and `RecallGet`, allowing
13
+ the agent to retrieve original history from another session, including a
14
+ Memory result's source session or another workspace under the same Tinker
15
+ home. Results identify their source session and workspace. External history is
16
+ read without resuming, repairing, or migrating the source session. Retrieved
17
+ text is sent to the current model provider and stored in the current session;
18
+ cross-workspace access does not show an additional confirmation dialog.
19
+ - Add `offset` and `limit` to `TaskOutput` for reading consecutive, numbered lines
20
+ from non-PTY task logs. Omitting both preserves the head/tail preview; PTY tasks
21
+ continue to return the current terminal screen.
22
+
23
+ ### Changed
24
+
25
+ - Enable automatic context swaps and prefix retirement as product defaults,
26
+ independent of model names, evaluation results, report availability, and tool
27
+ description hashes. Existing Recall capability, protocol, transaction, and
28
+ request-consistency checks remain enforced. Evaluation still measures model
29
+ behavior but no longer enables or disables runtime maintenance; this does not
30
+ guarantee reliable history retrieval by every model.
31
+ - Clarify shell guidance so finite builds, tests, and checks run in the foreground
32
+ when their result is needed next and no independent work remains. Reserve
33
+ background execution for persistent processes or useful concurrent work, and
34
+ describe task tools in terms of their capabilities.
35
+
36
+ ### Fixed
37
+
38
+ - Restore sessions containing follow-up messages submitted during an active turn
39
+ instead of rejecting turns with multiple user messages. Resumed timelines keep
40
+ follow-ups in their original order for completed and failed turns.
41
+ - Include available exit codes, termination signals, and errors in `TaskOutput`
42
+ observations. Distinguish PTY screen snapshots from cumulative log statistics,
43
+ and clarify byte-limited log ranges and polling of a still-growing final line.
44
+
8
45
  ## [2.8.0] - 2026-09-04
9
46
 
10
47
  ### Changed
@@ -361,7 +398,8 @@ All notable user-facing changes to Tinker are documented here. The project follo
361
398
  - First formal npm release under the `tinker-agent` package name with the `tinker`
362
399
  executable.
363
400
 
364
- [Unreleased]: https://github.com/ishowshao/tinker/compare/v2.8.0...HEAD
401
+ [Unreleased]: https://github.com/ishowshao/tinker/compare/v2.9.0...HEAD
402
+ [2.9.0]: https://github.com/ishowshao/tinker/releases/tag/v2.9.0
365
403
  [2.8.0]: https://github.com/ishowshao/tinker/releases/tag/v2.8.0
366
404
  [2.7.0]: https://github.com/ishowshao/tinker/releases/tag/v2.7.0
367
405
  [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,65 @@ 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
+ ### Reading another session's history
38
+
39
+ Both Recall tools accept an optional `sessionId` (a canonical session UUID).
40
+ Omitting it keeps the current-session behavior. In the TUI or one-shot CLI, the
41
+ agent can use a session ID you supply or a Memory result's `sourceSessionId`:
42
+
43
+ ```ts
44
+ RecallSearch({ sessionId: sourceSessionId, query: "distinctive-anchor" })
45
+ RecallGet({ sessionId: sourceSessionId, source: "ctx://message/<message-UUID>" })
46
+ ```
47
+
48
+ Reuse the same `sessionId` for Get and subsequent pages. Search's
49
+ `snapshot_through_ordinal`, ordinals and turn numbers belong to that session only.
50
+ Results identify their source session and workspace. Missing, ambiguous, unsafe,
51
+ unsupported or corrupt sessions return errors; Recall never silently switches
52
+ back to the current session. Memory is not required.
53
+
54
+ **Data scope:** explicit session selection can read other workspaces under the
55
+ configured Tinker home (`TINKER_HOME` overrides the OS home). Retrieved text is
56
+ sent to the **current model provider** and saved as a tool result in the **current
57
+ session**, including when projects or providers differ. There is no additional
58
+ cross-workspace confirmation dialog.
59
+
60
+ External history uses short read-only SQLite snapshots, without resuming the
61
+ source session, taking its execution lock, repairing indexes or migrating its
62
+ schema. Only the current readable schema is supported. Active writers' committed
63
+ history is readable; an open/interrupted turn may be incomplete. Historical
64
+ instructions, Skills and authorizations are not activated, and content hashes
65
+ prove storage consistency, not truth. Use Read/Grep to verify current facts.
66
+ Original workspaces need not still exist. SQLite WAL reads can coordinate through
67
+ SHM; read-only access does not promise that an active writer's directory remains
68
+ byte-for-byte unchanged. See the
69
+ [history access implementation](src/session/session-history-access.ts).
70
+
71
+ ### Automatic context maintenance and evaluation
72
+
73
+ Automatic swap and prefix retirement are product defaults, independent of model
74
+ names, evaluation scores, report availability, and tool-description hashes. Under
75
+ context pressure, the runtime tries swap first and attempts retirement only when
76
+ swap has no eligible candidates or insufficient candidates. Existing scheduling,
77
+ protocol boundaries, cancellation, transaction, and request/surface consistency
78
+ checks remain in force. Required Recall tools and the current retirement contract
79
+ must be available; enabling a product policy does not bypass execution safety.
80
+ There are no new user-facing switches or model allowlists.
81
+
82
+ Evaluation measures model behavior; it does not enable or disable these features.
83
+ New evaluation reports (`active-recall-qualification-v2`) contain metrics, gates,
84
+ and provenance but no automation flags. Historical reports remain unchanged. The
85
+ session-selection evaluation
86
+ retains its measured failure: Search → Get missed the threshold even though all
87
+ Recall-only tasks passed. Its old automation fields are legacy data, not runtime
88
+ policy. The subsequent description cleanup has not been re-evaluated with a live
89
+ model.
90
+
37
91
  ## Quick Start
38
92
 
39
93
  ### Install with your existing agent
@@ -398,9 +452,9 @@ pixels in total. Provider requests preserve smaller images and proportionally
398
452
  downscale larger images to a maximum 2048-pixel long edge. Context planning uses
399
453
  fixed local token buckets derived from the materialized dimensions and performs no
400
454
  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.
455
+ [image input policy](src/image/image-input-policy.ts) and
456
+ [image asset store](src/image/image-asset-store.ts) for the policy and storage
457
+ implementations.
404
458
 
405
459
  `ViewImage(file_path)` is registered only when the selected profile declares both
406
460
  `inputModalities: ["text", "image"]` and
@@ -410,8 +464,8 @@ text-only for tool results. Relative paths stay inside the workspace, absolute
410
464
  paths may explicitly select an external local file, and symbolic links are
411
465
  rejected. Canonical history stores content-addressed image references rather than
412
466
  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.
467
+ See the [`ViewImage implementation`](src/tools/view-image.ts) for the tool's
468
+ validation and execution logic.
415
469
 
416
470
  ### Built-in Slash Commands
417
471
 
@@ -476,9 +530,9 @@ turn. Built-in slash commands appear first in suggestions, followed by project
476
530
  commands in configuration order. Custom commands accept no arguments and cannot
477
531
  override built-ins. The optional configuration is loaded once at TUI startup,
478
532
  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.
533
+ one-shot `tinker run` command. See the
534
+ [project command loader](src/tui/project-slash-commands.ts) for configuration
535
+ validation and loading behavior.
482
536
 
483
537
  ### Global Runtime Data
484
538
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tinker-agent",
3
- "version": "2.8.0",
3
+ "version": "2.9.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",
@@ -64,8 +64,9 @@
64
64
  "docs:generate": "bun scripts/render-public-contract-docs.ts --write",
65
65
  "docs:check": "bun scripts/render-public-contract-docs.ts --check",
66
66
  "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",
67
+ "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",
68
+ "check:fast": "bun run check:source-lines && bun run typecheck && bun run format:check && bun run lint && bun run test:fast",
69
+ "check:source-lines": "bun scripts/check-source-lines.ts",
69
70
  "format": "biome format --write .",
70
71
  "format:check": "biome format .",
71
72
  "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
+ }