tinker-agent 2.4.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -1
- package/README.md +30 -7
- package/package.json +1 -1
- package/src/agent/context-pressure-notice.ts +37 -0
- package/src/agent/runtime-session.ts +82 -13
- package/src/cli/main.ts +2 -0
- package/src/cli/public-config-contract.ts +1 -1
- package/src/cli/runner-dependencies.ts +1 -0
- package/src/cli/tui-runner.tsx +2 -0
- package/src/events/stdout-event-printer.ts +5 -0
- package/src/events/types.ts +14 -1
- package/src/model/fake-model-client.ts +8 -2
- package/src/session/session-clone-helpers.ts +249 -0
- package/src/session/session-compatibility-codec.ts +401 -0
- package/src/session/session-store-contracts.ts +304 -0
- package/src/session/session-store-filesystem.ts +245 -0
- package/src/session/session-store-record-codecs.ts +1064 -0
- package/src/session/session-store-value-codecs.ts +156 -0
- package/src/session/session-store.ts +234 -3023
- package/src/session/session-tool-result-codec.ts +590 -0
- package/src/tools/grep.ts +1 -3
- package/src/tui/app.tsx +2 -0
- package/src/tui/components/prompt-input.tsx +4 -0
- package/src/tui/event-store.ts +9 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,33 @@ All notable user-facing changes to Tinker are documented here. The project follo
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [2.6.0] - 2026-09-03
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Show the installed Tinker version at the end of the interactive prompt status
|
|
13
|
+
bar.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Raise the default maximum agent-loop iterations per turn from 512 to 65,536,
|
|
18
|
+
allowing longer autonomous tasks without requiring configuration changes.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- Preserve brace expressions in Grep glob filters, so patterns such as
|
|
23
|
+
`**/*.{ts,tsx}` are passed to ripgrep intact.
|
|
24
|
+
|
|
25
|
+
## [2.5.0] - 2026-09-03
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- Proactively notify the model when input context reaches high or critical
|
|
30
|
+
pressure, prompting it to review and swap evictable historical tool
|
|
31
|
+
observations while keeping swapped content recoverable through Recall. The
|
|
32
|
+
runtime gives the model one iteration to act before automatic compaction
|
|
33
|
+
resumes, while critical pressure still triggers immediate maintenance.
|
|
34
|
+
|
|
8
35
|
## [2.4.0] - 2026-09-02
|
|
9
36
|
|
|
10
37
|
### Added
|
|
@@ -297,7 +324,9 @@ All notable user-facing changes to Tinker are documented here. The project follo
|
|
|
297
324
|
- First formal npm release under the `tinker-agent` package name with the `tinker`
|
|
298
325
|
executable.
|
|
299
326
|
|
|
300
|
-
[Unreleased]: https://github.com/ishowshao/tinker/compare/v2.
|
|
327
|
+
[Unreleased]: https://github.com/ishowshao/tinker/compare/v2.6.0...HEAD
|
|
328
|
+
[2.6.0]: https://github.com/ishowshao/tinker/releases/tag/v2.6.0
|
|
329
|
+
[2.5.0]: https://github.com/ishowshao/tinker/releases/tag/v2.5.0
|
|
301
330
|
[2.4.0]: https://github.com/ishowshao/tinker/releases/tag/v2.4.0
|
|
302
331
|
[2.3.0]: https://github.com/ishowshao/tinker/releases/tag/v2.3.0
|
|
303
332
|
[2.2.0]: https://github.com/ishowshao/tinker/releases/tag/v2.2.0
|
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# Tinker
|
|
2
2
|
|
|
3
|
-
**Tinker** is a personal coding
|
|
3
|
+
**Tinker** is a personal coding-agent harness — an interactive TUI (Terminal User Interface) and one-shot CLI that drives an LLM in an agent loop with file, search, shell, and MCP tools to read and modify a local workspace.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**Tinker is designed for models that work over extremely long horizons — potentially as persistent agents that continue indefinitely, rather than as disposable chat sessions.** Its architecture treats the model's context window as a bounded working set, not as the source of truth. Immutable canonical history, durable sessions, protocol-safe recovery, deterministic context revisions, Recall-addressable cold state, and context-pressure management allow work to continue across compaction, process restarts, and context-window limits.
|
|
6
|
+
|
|
7
|
+
This does not pretend that any model has infinite tokens or guarantee that it will recall every relevant fact. It means the harness is designed so that history remains durable and recoverable while the model repeatedly operates on a bounded, valid view of an ongoing session.
|
|
6
8
|
|
|
7
9
|
## Features
|
|
8
10
|
|
|
@@ -27,16 +29,36 @@ Built with [Bun](https://bun.sh) + TypeScript ESM, powered by [Ink](https://gith
|
|
|
27
29
|
- **Turn cancellation**: Users can cancel an ongoing turn safely, with protocol-safe synthetic tool messages.
|
|
28
30
|
- **Context metering**: Budget-aware context management with protocol validation before sending requests to the model.
|
|
29
31
|
- **Deterministic context compaction**: Idle sessions can swap eligible historical tool output into Recall-addressable placeholders without calling the model.
|
|
30
|
-
- **Infinite Context architecture**: Immutable canonical history, deterministic
|
|
31
|
-
context revisions, Recall-addressable cold state, and qualified prefix retirement
|
|
32
|
-
keep long-running sessions recoverable without pretending the model has infinite
|
|
33
|
-
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 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).
|
|
34
33
|
- **Choice of models**: Uses an OpenAI-compatible Chat Completions transport with
|
|
35
34
|
explicit model and context limits. Actual provider support must be established
|
|
36
35
|
by a qualification matrix; transport compatibility alone is not a guarantee.
|
|
37
36
|
|
|
38
37
|
## Quick Start
|
|
39
38
|
|
|
39
|
+
### Install with your existing agent
|
|
40
|
+
|
|
41
|
+
If you already use a coding agent, you can ask it to read Tinker's documentation, install the package, and prepare a complete local configuration for you. Values it cannot safely determine can be left as clearly named placeholders; when setup is complete, the agent should tell you exactly which file to edit and what each placeholder expects.
|
|
42
|
+
|
|
43
|
+
Copy and send this prompt to your existing agent:
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
Open https://github.com/ishowshao/tinker and read Tinker's README and linked model-configuration documentation, then install and configure Tinker on this machine.
|
|
47
|
+
|
|
48
|
+
Create a complete model profile configuration in an appropriate local file. Use clearly named placeholders for any required values that cannot be determined from the documentation or the current environment; do not invent API credentials, endpoint URLs, model names, or model limits. Configure Tinker to use that file, and verify the installation and configuration as far as possible without making a live model request.
|
|
49
|
+
|
|
50
|
+
When finished, tell me:
|
|
51
|
+
1. the exact path of the configuration file you created;
|
|
52
|
+
2. every placeholder I still need to replace;
|
|
53
|
+
3. what value each placeholder expects;
|
|
54
|
+
4. any command I need to run after filling them in;
|
|
55
|
+
5. the command to start Tinker.
|
|
56
|
+
|
|
57
|
+
Do not ask me for the missing values during setup unless proceeding would be unsafe. Prefer creating a structurally complete configuration with placeholders so I can fill them in locally afterward.
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Install manually
|
|
61
|
+
|
|
40
62
|
```bash
|
|
41
63
|
npm install --global tinker-agent
|
|
42
64
|
|
|
@@ -149,7 +171,7 @@ are required. Boolean environment values accept case-insensitive `true/false`,
|
|
|
149
171
|
| `TINKER_STREAM` | Model | Env mode | No | Boolean | `true` | No | Use streaming transport for the selected model API. |
|
|
150
172
|
| `TINKER_WEBFETCH_REFINE_MODEL` | Model | Env mode | No | Non-empty string | — | No | Optional WebFetch refiner model; currently must match TINKER_MODEL. |
|
|
151
173
|
| `TINKER_WORKSPACE` | Workspace | All modes | No | Non-empty string | Process cwd | No | Workspace path. A leading ~ expands to the home directory; other relative paths resolve from the process cwd. |
|
|
152
|
-
| `TINKER_MAX_ITERATIONS` | Workspace | All modes | No | Positive integer | `
|
|
174
|
+
| `TINKER_MAX_ITERATIONS` | Workspace | All modes | No | Positive integer | `65536` | No | Maximum agent-loop iterations per turn. |
|
|
153
175
|
| `EXA_API_KEY` | Tooling | All modes | No | Non-empty string | — | Yes | Enables WebSearch and the Exa WebFetch backend when set. |
|
|
154
176
|
| `TINKER_MCP_TIMEOUT_MS` | Tooling | All modes | No | Positive integer | `60000` | No | MCP tool-call timeout in milliseconds. |
|
|
155
177
|
| `TINKER_MCP_MAX_OBSERVATION_CHARS` | Tooling | All modes | No | Positive integer | `40000` | No | Maximum model-visible characters in one MCP result. |
|
|
@@ -513,6 +535,7 @@ Runtime data lives in ~/.tinker/ (sessions, bash tasks, assets), not in the repo
|
|
|
513
535
|
- **Fast-fail**: Validate assumptions early and return clear errors close to the source. Structured failures allow the model to correct and retry.
|
|
514
536
|
- **Model sees only text**: Tool execution results are rendered into readable text for the model. Raw result data with extra detail is kept for event logs and the TUI.
|
|
515
537
|
- **Protocol safety**: All tool calls produce protocol-safe messages — even cancellations, fatal errors, or interruptions generate well-formed tool messages so the agent loop can continue.
|
|
538
|
+
- **Long-horizon continuity**: Treat the context window as a replaceable working set over durable canonical history. Sessions should remain resumable and historically recoverable across compaction, interruption, and process restarts.
|
|
516
539
|
- **Session durability**: Every turn, iteration, and tool call is committed to the SQLite ledger before the model is called, enabling reliable resume and history recall.
|
|
517
540
|
|
|
518
541
|
## Requirements
|
package/package.json
CHANGED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ContextUsageSnapshot } from "./context-meter";
|
|
2
|
+
|
|
3
|
+
export const CONTEXT_PRESSURE_NOTICE_PREFIX = "[tinker context notice]";
|
|
4
|
+
|
|
5
|
+
export function isContextPressureNotice(message: {
|
|
6
|
+
readonly role: string;
|
|
7
|
+
readonly content?: unknown;
|
|
8
|
+
}): boolean {
|
|
9
|
+
return (
|
|
10
|
+
message.role === "user" &&
|
|
11
|
+
typeof message.content === "string" &&
|
|
12
|
+
message.content.startsWith(CONTEXT_PRESSURE_NOTICE_PREFIX)
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function contextPressureNoticeText(input: {
|
|
17
|
+
usage: ContextUsageSnapshot;
|
|
18
|
+
toolPressure: "high" | "critical";
|
|
19
|
+
automaticSwapEnabled: boolean;
|
|
20
|
+
}): string {
|
|
21
|
+
const base =
|
|
22
|
+
`${CONTEXT_PRESSURE_NOTICE_PREFIX} Input pressure is now "${input.toolPressure}" ` +
|
|
23
|
+
`(${input.usage.usedInputTokens} of ${input.usage.inputBudgetTokens} input tokens; ` +
|
|
24
|
+
`trigger at ${input.usage.triggerTokens}). ` +
|
|
25
|
+
"Call ContextSwapCandidates to review evictable historical tool observations, " +
|
|
26
|
+
"then ContextSwap to replace them with Recall-backed placeholders; " +
|
|
27
|
+
"swapped content stays recoverable through RecallGet.";
|
|
28
|
+
if (!input.automaticSwapEnabled) {
|
|
29
|
+
return (
|
|
30
|
+
`${base} Automatic compaction is disabled in this session, so pressure will ` +
|
|
31
|
+
"keep growing unless you swap observations or the turn ends."
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
return input.toolPressure === "critical"
|
|
35
|
+
? `${base} Automatic compaction is running immediately because pressure exceeded the input budget.`
|
|
36
|
+
: `${base} Automatic compaction will resume next iteration if you do not act.`;
|
|
37
|
+
}
|
|
@@ -118,7 +118,8 @@ import type {
|
|
|
118
118
|
ToolCallIdentity,
|
|
119
119
|
TurnIdentity,
|
|
120
120
|
} from "./types";
|
|
121
|
-
import { ContextMeter } from "./context-meter";
|
|
121
|
+
import { ContextMeter, type ContextUsageSnapshot } from "./context-meter";
|
|
122
|
+
import { contextPressureNoticeText } from "./context-pressure-notice";
|
|
122
123
|
import { CURRENT_RECALL_RETIREMENT_CONTRACT_VERSION } from "../context/recall-retirement-contract";
|
|
123
124
|
import {
|
|
124
125
|
selectContextAutomation,
|
|
@@ -482,6 +483,7 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
482
483
|
private pendingAutomaticContextMaintenance = false;
|
|
483
484
|
private pendingModelDirectedSwap?: Set<MessageId>;
|
|
484
485
|
private modelDirectedSwapLease = false;
|
|
486
|
+
private pressureNoticeSentThisTurn = false;
|
|
485
487
|
private readonly skillCatalog: SkillCatalogSnapshot;
|
|
486
488
|
private skillCoordinator = new SkillActivationCoordinator();
|
|
487
489
|
private bashGuardMode: "guard" | "yolo";
|
|
@@ -2355,6 +2357,7 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
2355
2357
|
this.pendingAutomaticContextMaintenance = false;
|
|
2356
2358
|
this.pendingModelDirectedSwap = undefined;
|
|
2357
2359
|
this.modelDirectedSwapLease = false;
|
|
2360
|
+
this.pressureNoticeSentThisTurn = false;
|
|
2358
2361
|
if (this.state === "executing") {
|
|
2359
2362
|
this.state = "ready";
|
|
2360
2363
|
}
|
|
@@ -2399,31 +2402,64 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
2399
2402
|
}
|
|
2400
2403
|
const pendingModelDirectedSwap = this.pendingModelDirectedSwap;
|
|
2401
2404
|
this.pendingModelDirectedSwap = undefined;
|
|
2402
|
-
if (pendingModelDirectedSwap === undefined && this.modelDirectedSwapLease) {
|
|
2403
|
-
this.modelDirectedSwapLease = false;
|
|
2404
|
-
return;
|
|
2405
|
-
}
|
|
2406
2405
|
|
|
2407
2406
|
const automation = this.requireContextAutomation();
|
|
2408
|
-
if (pendingModelDirectedSwap === undefined && !automation.automaticSwapOnly) {
|
|
2409
|
-
return;
|
|
2410
|
-
}
|
|
2411
2407
|
const manager = this.requireContextManager();
|
|
2412
2408
|
this.pendingAutomaticContextMaintenance = false;
|
|
2409
|
+
|
|
2410
|
+
let suppressAutomaticSwap = this.modelDirectedSwapLease;
|
|
2413
2411
|
this.modelDirectedSwapLease = false;
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2412
|
+
|
|
2413
|
+
if (pendingModelDirectedSwap !== undefined) {
|
|
2414
|
+
suppressAutomaticSwap = false;
|
|
2415
|
+
this.state = "maintaining_context";
|
|
2416
|
+
try {
|
|
2417
2417
|
await this.performModelDirectedCompaction({
|
|
2418
2418
|
turn: input.turn,
|
|
2419
2419
|
consumedThroughOrdinal: input.consumedThroughOrdinal,
|
|
2420
2420
|
ledger: input.ledger,
|
|
2421
2421
|
messageIds: Object.freeze([...pendingModelDirectedSwap]),
|
|
2422
2422
|
});
|
|
2423
|
+
} finally {
|
|
2424
|
+
if (this.state === "maintaining_context") {
|
|
2425
|
+
this.state = "executing";
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
|
|
2430
|
+
let measured: ContextUsageSnapshot | undefined;
|
|
2431
|
+
if (
|
|
2432
|
+
pendingModelDirectedSwap === undefined &&
|
|
2433
|
+
(suppressAutomaticSwap ||
|
|
2434
|
+
!this.pressureNoticeSentThisTurn ||
|
|
2435
|
+
automation.automaticSwapOnly)
|
|
2436
|
+
) {
|
|
2437
|
+
measured = manager.measureCurrent(input.turn.turnId, input.ledger);
|
|
2438
|
+
if (!this.pressureNoticeSentThisTurn && measured.pressure !== "normal") {
|
|
2439
|
+
await this.injectContextPressureNotice({
|
|
2440
|
+
turn: input.turn,
|
|
2441
|
+
ledger: input.ledger,
|
|
2442
|
+
usage: measured,
|
|
2443
|
+
automaticSwapEnabled: automation.automaticSwapOnly,
|
|
2444
|
+
});
|
|
2445
|
+
this.pressureNoticeSentThisTurn = true;
|
|
2446
|
+
suppressAutomaticSwap = true;
|
|
2423
2447
|
}
|
|
2424
|
-
if (
|
|
2448
|
+
if (measured.pressure === "blocked") {
|
|
2449
|
+
// Emergency override: a lease or notice must never hold automatic
|
|
2450
|
+
// compaction past the budget line; the next preflight would fail the
|
|
2451
|
+
// turn before the model could act.
|
|
2452
|
+
suppressAutomaticSwap = false;
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
if (suppressAutomaticSwap || !automation.automaticSwapOnly) {
|
|
2457
|
+
return;
|
|
2458
|
+
}
|
|
2425
2459
|
|
|
2426
|
-
|
|
2460
|
+
this.state = "maintaining_context";
|
|
2461
|
+
try {
|
|
2462
|
+
const usage = measured ?? manager.measureCurrent(input.turn.turnId, input.ledger);
|
|
2427
2463
|
if (usage.pressure === "normal") return;
|
|
2428
2464
|
|
|
2429
2465
|
const qualificationId = requireAutomationQualificationId(automation);
|
|
@@ -2530,6 +2566,39 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
2530
2566
|
}
|
|
2531
2567
|
}
|
|
2532
2568
|
|
|
2569
|
+
private async injectContextPressureNotice(input: {
|
|
2570
|
+
turn: TurnIdentity;
|
|
2571
|
+
ledger: AgentTurnLedger;
|
|
2572
|
+
usage: ContextUsageSnapshot;
|
|
2573
|
+
automaticSwapEnabled: boolean;
|
|
2574
|
+
}): Promise<void> {
|
|
2575
|
+
const userMessage: UserMessage = Object.freeze({
|
|
2576
|
+
role: "user",
|
|
2577
|
+
content: contextPressureNoticeText({
|
|
2578
|
+
usage: input.usage,
|
|
2579
|
+
toolPressure: toolContextPressure(input.usage.pressure) as "high" | "critical",
|
|
2580
|
+
automaticSwapEnabled: input.automaticSwapEnabled,
|
|
2581
|
+
}),
|
|
2582
|
+
});
|
|
2583
|
+
const records = input.ledger.appendSteeringUserMessages([userMessage]);
|
|
2584
|
+
const record = records[0];
|
|
2585
|
+
if (records.length !== 1 || record === undefined) {
|
|
2586
|
+
throw new Error("Pressure notice steering did not append exactly one message.");
|
|
2587
|
+
}
|
|
2588
|
+
await this.append({
|
|
2589
|
+
type: "context.pressure_notice.sent",
|
|
2590
|
+
...input.turn,
|
|
2591
|
+
data: {
|
|
2592
|
+
usedInputTokens: input.usage.usedInputTokens,
|
|
2593
|
+
inputBudgetTokens: input.usage.inputBudgetTokens,
|
|
2594
|
+
triggerTokens: input.usage.triggerTokens,
|
|
2595
|
+
pressure: input.usage.pressure === "blocked" ? "blocked" : "triggered",
|
|
2596
|
+
automaticSwapEnabled: input.automaticSwapEnabled,
|
|
2597
|
+
ordinal: record.ordinal,
|
|
2598
|
+
},
|
|
2599
|
+
});
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2533
2602
|
private async performModelDirectedCompaction(input: {
|
|
2534
2603
|
turn: TurnIdentity;
|
|
2535
2604
|
consumedThroughOrdinal: number;
|
package/src/cli/main.ts
CHANGED
|
@@ -51,6 +51,7 @@ type TuiRunner = {
|
|
|
51
51
|
readonly publicConfig: ResolvedPublicConfig;
|
|
52
52
|
readonly initialRunnerConfig: RunnerConfig;
|
|
53
53
|
readonly env: NodeJS.ProcessEnv;
|
|
54
|
+
readonly version: string;
|
|
54
55
|
}) => Promise<void>;
|
|
55
56
|
};
|
|
56
57
|
|
|
@@ -187,6 +188,7 @@ export async function main(
|
|
|
187
188
|
publicConfig,
|
|
188
189
|
initialRunnerConfig: runnerConfig,
|
|
189
190
|
env,
|
|
191
|
+
version: metadata.version,
|
|
190
192
|
});
|
|
191
193
|
return finish(0);
|
|
192
194
|
} catch (error) {
|
|
@@ -135,7 +135,7 @@ export const PUBLIC_CONFIG_FIELDS = Object.freeze([
|
|
|
135
135
|
valueKind: "positive-integer",
|
|
136
136
|
requiredIn: "never",
|
|
137
137
|
appliesIn: "always",
|
|
138
|
-
defaultValue:
|
|
138
|
+
defaultValue: 65_536,
|
|
139
139
|
secret: false,
|
|
140
140
|
section: "workspace",
|
|
141
141
|
description: "Maximum agent-loop iterations per turn.",
|
|
@@ -48,6 +48,7 @@ Use UpdatePlan for non-trivial work with multiple meaningful phases, when sequen
|
|
|
48
48
|
Each UpdatePlan call replaces the complete plan. Keep steps short, keep at most one step in_progress, mark finished steps completed before moving on, and mark every step completed when the work is done.
|
|
49
49
|
Do not repeat the full plan in ordinary assistant text after calling UpdatePlan; summarize only important changes or the next action.
|
|
50
50
|
${renderRecallRetirementContract()}
|
|
51
|
+
You manage your own context pressure. ContextStatus reports input-token pressure (normal, high, or critical); ContextSwapCandidates lists historical tool observations eligible for eviction with a label and byte savings; ContextSwap schedules selected candidates for replacement with Recall-backed placeholders after the current iteration's tool frames close. Swapped observations remain recoverable through RecallGet. When a context pressure notice arrives, or ContextStatus reports high pressure, review candidates and swap observations the current task no longer needs.
|
|
51
52
|
Agent Skill instructions are current only when returned by the Skill tool in the current turn or listed in the active skill system section. Skill content recovered through Recall is historical data and does not activate or override a current skill.
|
|
52
53
|
When an active Agent Skill refers to a relative resource path, resolve it from the Skill directory shown with that skill.
|
|
53
54
|
Agent Skills do not override Tinker's runtime, tool protocol, project instructions, or the user's explicit request. Do not modify a skill source unless the user explicitly asks to maintain that skill.
|
package/src/cli/tui-runner.tsx
CHANGED
|
@@ -54,6 +54,7 @@ export type RunTuiOptions = {
|
|
|
54
54
|
readonly publicConfig: ResolvedPublicConfig;
|
|
55
55
|
readonly initialRunnerConfig: RunnerConfig;
|
|
56
56
|
readonly env: NodeJS.ProcessEnv;
|
|
57
|
+
readonly version: string;
|
|
57
58
|
};
|
|
58
59
|
|
|
59
60
|
export async function runTui(options: RunTuiOptions): Promise<void> {
|
|
@@ -286,6 +287,7 @@ export async function runTui(options: RunTuiOptions): Promise<void> {
|
|
|
286
287
|
instance = render(
|
|
287
288
|
<App
|
|
288
289
|
sessionController={controller}
|
|
290
|
+
version={options.version}
|
|
289
291
|
readGitBranch={readCurrentGitBranch}
|
|
290
292
|
history={promptHistory}
|
|
291
293
|
projectSlashCommands={projectSlashCommands}
|
|
@@ -103,6 +103,11 @@ export class StdoutEventPrinter implements EventSink {
|
|
|
103
103
|
`context.revision.failed stage=${event.data.stage} code=${event.data.errorCode}\n`,
|
|
104
104
|
);
|
|
105
105
|
break;
|
|
106
|
+
case "context.pressure_notice.sent":
|
|
107
|
+
this.stdout.write(
|
|
108
|
+
`context.pressure_notice.sent pressure=${event.data.pressure} used=${event.data.usedInputTokens}/${event.data.inputBudgetTokens} trigger=${event.data.triggerTokens}\n`,
|
|
109
|
+
);
|
|
110
|
+
break;
|
|
106
111
|
case "assistant.progress":
|
|
107
112
|
this.stdout.write(
|
|
108
113
|
`assistant.progress iteration=${event.iterationNumber}\n${event.data.content}\n`,
|
package/src/events/types.ts
CHANGED
|
@@ -311,6 +311,14 @@ export type AgentEventDataMap = {
|
|
|
311
311
|
userPrompt: UserPromptProjection;
|
|
312
312
|
ordinal: number;
|
|
313
313
|
};
|
|
314
|
+
"context.pressure_notice.sent": {
|
|
315
|
+
usedInputTokens: number;
|
|
316
|
+
inputBudgetTokens: number;
|
|
317
|
+
triggerTokens: number;
|
|
318
|
+
pressure: "triggered" | "blocked";
|
|
319
|
+
automaticSwapEnabled: boolean;
|
|
320
|
+
ordinal: number;
|
|
321
|
+
};
|
|
314
322
|
"turn.finished": TurnFinishedData;
|
|
315
323
|
"turn.failed": { error: string };
|
|
316
324
|
"turn.cancelled": { cancellation: TurnCancellation };
|
|
@@ -415,7 +423,12 @@ export type AgentEventInput =
|
|
|
415
423
|
>
|
|
416
424
|
| SessionEventInput<"mcp.server.connected" | "mcp.server.failed">
|
|
417
425
|
| SessionEventInput<"diagnostic.sink_failed">
|
|
418
|
-
| TurnEventInput<
|
|
426
|
+
| TurnEventInput<
|
|
427
|
+
| "turn.started"
|
|
428
|
+
| "turn.steering.applied"
|
|
429
|
+
| "context.pressure_notice.sent"
|
|
430
|
+
| "turn.finished"
|
|
431
|
+
>
|
|
419
432
|
| (
|
|
420
433
|
| TurnEventInput<"turn.failed" | "turn.cancelled">
|
|
421
434
|
| IterationEventInput<"turn.failed" | "turn.cancelled">
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { appendFile } from "node:fs/promises";
|
|
3
|
+
import { isContextPressureNotice } from "../agent/context-pressure-notice";
|
|
3
4
|
import type { AgentMessage, AssistantMessage } from "../agent/types";
|
|
4
5
|
import { toolResultDisplayText } from "../agent/tool-result-content";
|
|
5
6
|
import { cancellationError } from "../agent/turn-cancellation";
|
|
@@ -1333,7 +1334,8 @@ function waitForCancellation(signal: AbortSignal): Promise<ModelRequestOutput> {
|
|
|
1333
1334
|
|
|
1334
1335
|
function lastUserMessage(messages: AgentMessage[]): string {
|
|
1335
1336
|
const users = messages.filter(
|
|
1336
|
-
(message): message is { role: "user"; content: string } =>
|
|
1337
|
+
(message): message is { role: "user"; content: string } =>
|
|
1338
|
+
message.role === "user" && !isContextPressureNotice(message),
|
|
1337
1339
|
);
|
|
1338
1340
|
return users.at(-1)?.content ?? "";
|
|
1339
1341
|
}
|
|
@@ -1451,7 +1453,11 @@ function lastMessageIndex(
|
|
|
1451
1453
|
role: AgentMessage["role"],
|
|
1452
1454
|
): number {
|
|
1453
1455
|
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
1454
|
-
|
|
1456
|
+
const message = messages[index];
|
|
1457
|
+
if (message === undefined || isContextPressureNotice(message)) {
|
|
1458
|
+
continue;
|
|
1459
|
+
}
|
|
1460
|
+
if (message.role === role) {
|
|
1455
1461
|
return index;
|
|
1456
1462
|
}
|
|
1457
1463
|
}
|