tinker-agent 2.9.0 → 2.11.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 +69 -1
- package/README.md +30 -1
- package/package.json +5 -3
- package/src/agent/loop.ts +50 -13
- package/src/agent/runtime-hosted-session.ts +443 -0
- package/src/agent/runtime-provider-retry.ts +115 -0
- package/src/agent/runtime-session-contracts.ts +11 -0
- package/src/agent/runtime-session.ts +29 -0
- package/src/cli/command-line.ts +26 -2
- package/src/cli/connect-runner.tsx +26 -0
- package/src/cli/main.ts +26 -0
- package/src/cli/output.ts +1 -1
- package/src/cli/public-cli-contract.ts +18 -0
- package/src/cli/public-config-contract.ts +1 -1
- package/src/cli/serve-runner.ts +45 -0
- package/src/cli/serve-runtime.ts +100 -0
- package/src/cli/tui-runner.tsx +1 -0
- package/src/context/context-swap-renderer.ts +14 -0
- package/src/events/types.ts +8 -0
- package/src/image/abortable-file-open.ts +54 -0
- package/src/image/image-asset-store.ts +7 -1
- package/src/model/fake-model-client.ts +20 -1
- package/src/model/openai-model-utils.ts +45 -1
- package/src/model/openai-responses-mapping.ts +11 -0
- package/src/model/openai-responses-stream.ts +14 -0
- package/src/observation/observation-builder.ts +87 -37
- package/src/remote/client.ts +350 -0
- package/src/remote/config.ts +95 -0
- package/src/remote/http-server.ts +240 -0
- package/src/remote/protocol.ts +228 -0
- package/src/remote/service-store.ts +175 -0
- package/src/remote/service.ts +219 -0
- package/src/remote/sync-hub.ts +95 -0
- package/src/session/remote-history-reader.ts +143 -0
- package/src/session/scoped-query-database.ts +27 -0
- package/src/session/session-history-access.ts +4 -3
- package/src/session/session-store.ts +7 -3
- package/src/tools/bash-task.ts +26 -16
- package/src/tools/bash.ts +44 -2
- package/src/tools/glob.ts +107 -19
- package/src/tools/grep-output.ts +130 -0
- package/src/tools/grep-pagination.ts +73 -0
- package/src/tools/grep-path.ts +11 -0
- package/src/tools/grep-snippets.ts +111 -0
- package/src/tools/grep.ts +148 -155
- package/src/tools/read.ts +0 -9
- package/src/tools/ripgrep.ts +19 -26
- package/src/tools/shell-process.ts +30 -4
- package/src/tools/task-stop.ts +2 -1
- package/src/tools/terminal-screen.ts +11 -2
- package/src/tools/types.ts +30 -2
- package/src/tui/app.tsx +45 -3
- package/src/tui/components/ask-user.tsx +15 -8
- package/src/tui/components/prompt-input.tsx +14 -6
- package/src/tui/components/timeline.tsx +17 -9
- package/src/tui/event-store.ts +25 -2
- package/src/tui/file-mention.ts +29 -5
- package/src/tui/remote-app.tsx +210 -0
- package/src/tui/tui-projection-store.ts +5 -2
- package/src/tui/tui-session-controller.ts +8 -0
- package/src/tui/workspace-file-search.ts +21 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,72 @@ All notable user-facing changes to Tinker are documented here. The project follo
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [2.11.0] - 2026-09-08
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Offer directories in the TUI `@` file-mention popup. Ancestor directories of
|
|
13
|
+
workspace files now appear alongside files, ranked after matching files, and
|
|
14
|
+
selecting a directory inserts its path directly instead of attempting an
|
|
15
|
+
image import.
|
|
16
|
+
- Offer a retry selection in the TUI after provider retries are exhausted, so a
|
|
17
|
+
failed model request can be retried without re-entering the prompt.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- Cache ESLint results for `check:fast` and split the fast test tier into
|
|
22
|
+
twelve independent shards, shortening local iteration while keeping the
|
|
23
|
+
serial full test run as the final gate.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- Align `Grep` context-line precedence and remove an unsupported regex hint
|
|
28
|
+
from its documentation.
|
|
29
|
+
- Warm up the fake ripgrep binary in tests to absorb the macOS first-execution
|
|
30
|
+
validation delay.
|
|
31
|
+
- Cancel pending image file opens without leaking file handles.
|
|
32
|
+
- Emphasize TUI timeline labels and add spacing between messages.
|
|
33
|
+
- Finalize scoped SQLite queries before closing the session database.
|
|
34
|
+
|
|
35
|
+
## [2.10.0] - 2026-09-06
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
|
|
39
|
+
- Add remote access for paired clients. `tinker serve --config <path>` runs the
|
|
40
|
+
local daemon that paired remote clients connect to, and
|
|
41
|
+
`tinker connect --config <path>` attaches a terminal client to a running
|
|
42
|
+
service; exiting the client detaches without stopping the service. See
|
|
43
|
+
`docs/remote-access.md`. A native iOS client under `client/Tinker` is included
|
|
44
|
+
for acceptance.
|
|
45
|
+
- Add `offset` and `head_limit` pagination to `Glob` results. Long result sets
|
|
46
|
+
now report whether more matches remain instead of being silently capped.
|
|
47
|
+
- Add `cols` and `rows` options to `Bash` for custom initial PTY dimensions when
|
|
48
|
+
`tty=true`, so terminal applications that require a specific size can run in
|
|
49
|
+
an appropriately sized pseudo-terminal.
|
|
50
|
+
|
|
51
|
+
### Changed
|
|
52
|
+
|
|
53
|
+
- Run `Grep` directory searches with ripgrep's working directory set to the search
|
|
54
|
+
directory and `.` as the path argument. Directory globs such as `sub/**` now
|
|
55
|
+
match from that directory; globs that relied on absolute search paths may need
|
|
56
|
+
updating. Explicit file searches keep their existing invocation.
|
|
57
|
+
- Increase the default `Bash` foreground wait before a command continues in the
|
|
58
|
+
background, and clarify how `TaskOutput` and `TaskInput` poll PTY sessions.
|
|
59
|
+
- Omit internal file hashes from tool observations; the model sees the same
|
|
60
|
+
content without opaque bookkeeping tokens.
|
|
61
|
+
|
|
62
|
+
### Fixed
|
|
63
|
+
|
|
64
|
+
- Stop `TaskStop` from hanging when a shell's child processes survive after the
|
|
65
|
+
shell itself exits; stopping a task now reliably reports its final state.
|
|
66
|
+
- Preserve `Grep` match context across paginated results, keep long-line matches
|
|
67
|
+
instead of dropping them, and report counts and paginated records
|
|
68
|
+
consistently, including a `count-matches` mode with explicit counting units.
|
|
69
|
+
- Resolve symbolic-link search roots in `Glob` while preserving result paths,
|
|
70
|
+
and exclude directory and broken symbolic links from results.
|
|
71
|
+
- Accept pagination arguments in `Read` when reading empty files instead of
|
|
72
|
+
rejecting the call.
|
|
73
|
+
|
|
8
74
|
## [2.9.0] - 2026-09-05
|
|
9
75
|
|
|
10
76
|
### Added
|
|
@@ -398,7 +464,9 @@ All notable user-facing changes to Tinker are documented here. The project follo
|
|
|
398
464
|
- First formal npm release under the `tinker-agent` package name with the `tinker`
|
|
399
465
|
executable.
|
|
400
466
|
|
|
401
|
-
[Unreleased]: https://github.com/ishowshao/tinker/compare/v2.
|
|
467
|
+
[Unreleased]: https://github.com/ishowshao/tinker/compare/v2.11.0...HEAD
|
|
468
|
+
[2.11.0]: https://github.com/ishowshao/tinker/releases/tag/v2.11.0
|
|
469
|
+
[2.10.0]: https://github.com/ishowshao/tinker/releases/tag/v2.10.0
|
|
402
470
|
[2.9.0]: https://github.com/ishowshao/tinker/releases/tag/v2.9.0
|
|
403
471
|
[2.8.0]: https://github.com/ishowshao/tinker/releases/tag/v2.8.0
|
|
404
472
|
[2.7.0]: https://github.com/ishowshao/tinker/releases/tag/v2.7.0
|
package/README.md
CHANGED
|
@@ -34,6 +34,33 @@ This does not pretend that any model has infinite tokens or guarantee that it wi
|
|
|
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
|
+
### Provider retry selection
|
|
38
|
+
|
|
39
|
+
When automatic retries for a provider request are exhausted, the TUI offers
|
|
40
|
+
**Retry again** or **End this turn**. Retrying reuses the same request within the
|
|
41
|
+
same turn and iteration, with a fresh automatic retry allowance. Ending the turn
|
|
42
|
+
(or pressing Esc in the selection) records the original failure. The selection
|
|
43
|
+
adds no conversation message and applies only to the current running process.
|
|
44
|
+
Retryable failures include HTTP 429/500/502/503/504, structured `server_error`
|
|
45
|
+
and `rate_limit_exceeded` errors inside provider streams or failed Responses,
|
|
46
|
+
and transient connection failures, including socket resets during streaming.
|
|
47
|
+
Tool failures and non-retryable errors keep their existing behavior. One-shot
|
|
48
|
+
and remote service runs do not wait for this TUI selection.
|
|
49
|
+
|
|
50
|
+
### PTY screen size
|
|
51
|
+
|
|
52
|
+
`Bash` accepts optional `cols` and `rows` for the initial PTY screen size:
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{"command":"python3 -q","tty":true,"cols":160,"rows":48}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Each omitted dimension keeps its default: 80 columns and 24 rows. Columns accept
|
|
59
|
+
integers from 2 to 1000; rows accept integers from 1 to 1000. When `tty` is omitted
|
|
60
|
+
or `false`, dimensions are ignored and execution continues through ordinary pipes.
|
|
61
|
+
`TaskOutput` and `TaskInput` report the actual screen size. Dimensions are set at
|
|
62
|
+
creation; running tasks cannot be resized through these tools.
|
|
63
|
+
|
|
37
64
|
### Reading another session's history
|
|
38
65
|
|
|
39
66
|
Both Recall tools accept an optional `sessionId` (a canonical session UUID).
|
|
@@ -158,6 +185,8 @@ The installed package exposes this public CLI:
|
|
|
158
185
|
| `tinker run [--profile <profile-name>] [--yolo] --stdin` | Read the prompt from standard input until EOF. |
|
|
159
186
|
| `tinker run [--profile <profile-name>] [--yolo] --file <path>` | Read the prompt from a UTF-8 text file. |
|
|
160
187
|
| `tinker update` | Update the global npm installation from the official npm registry. |
|
|
188
|
+
| `tinker serve --config <path>` | Run the local daemon for paired remote clients. |
|
|
189
|
+
| `tinker connect --config <path>` | Attach a terminal client to a service; exiting detaches only. |
|
|
161
190
|
| `tinker --help` | Show top-level CLI help. |
|
|
162
191
|
| `tinker help run` | Show one-shot command help. |
|
|
163
192
|
| `tinker help update` | Show update command help. |
|
|
@@ -229,7 +258,7 @@ are required. Boolean environment values accept case-insensitive `true/false`,
|
|
|
229
258
|
| `EXA_API_KEY` | Tooling | All modes | No | Non-empty string | — | Yes | Enables WebSearch and the Exa WebFetch backend when set. |
|
|
230
259
|
| `TINKER_MCP_TIMEOUT_MS` | Tooling | All modes | No | Positive integer | `60000` | No | MCP tool-call timeout in milliseconds. |
|
|
231
260
|
| `TINKER_MCP_MAX_OBSERVATION_CHARS` | Tooling | All modes | No | Positive integer | `40000` | No | Maximum model-visible characters in one MCP result. |
|
|
232
|
-
| `TINKER_BASH_DEFAULT_TIMEOUT_MS` | Tooling | All modes | No | Positive integer | `
|
|
261
|
+
| `TINKER_BASH_DEFAULT_TIMEOUT_MS` | Tooling | All modes | No | Positive integer | `60000` | No | Default Bash foreground timeout in milliseconds. |
|
|
233
262
|
| `TINKER_BASH_MAX_TIMEOUT_MS` | Tooling | All modes | No | Positive integer | `600000` | No | Maximum Bash foreground timeout in milliseconds. |
|
|
234
263
|
| `TINKER_YOLO` | Tooling | All modes | No | Boolean | `false` | No | Allow high-confidence destructive Bash commands without confirmation. |
|
|
235
264
|
| `TINKER_GREP_TIMEOUT_MS` | Tooling | All modes | No | Positive integer | `20000` | No | Bundled ripgrep invocation timeout in milliseconds. |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tinker-agent",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.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",
|
|
@@ -65,14 +66,15 @@
|
|
|
65
66
|
"docs:check": "bun scripts/render-public-contract-docs.ts --check",
|
|
66
67
|
"tinker": "bun src/cli/index.ts",
|
|
67
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",
|
|
68
|
-
"check:fast": "bun run check:source-lines && bun run typecheck && bun run format:check && bun run lint && bun run test:fast",
|
|
69
|
+
"check:fast": "bun run check:source-lines && bun run typecheck && bun run format:check && bun run lint:fast && bun run test:fast",
|
|
69
70
|
"check:source-lines": "bun scripts/check-source-lines.ts",
|
|
70
71
|
"format": "biome format --write .",
|
|
71
72
|
"format:check": "biome format .",
|
|
72
73
|
"lint": "eslint \"bin/**/*.js\" \"src/**/*.{ts,tsx}\" \"scripts/**/*.ts\" \"packages/**/*.ts\" --max-warnings=0",
|
|
74
|
+
"lint:fast": "bun run lint --cache --cache-strategy content --cache-location .cache/eslint-fast/",
|
|
73
75
|
"lint:fix": "eslint \"bin/**/*.js\" \"src/**/*.{ts,tsx}\" \"scripts/**/*.ts\" \"packages/**/*.ts\" --fix",
|
|
74
76
|
"test": "FORCE_COLOR=0 bun test",
|
|
75
|
-
"test:fast": "
|
|
77
|
+
"test:fast": "bun scripts/run-test-shards.ts --path-ignore-patterns='**/*pty*.test.ts' --path-ignore-patterns='**/native-host-integration.test.ts'",
|
|
76
78
|
"test:e2e": "FORCE_COLOR=0 bun test src/__tests__/cli-pty src/__tests__/pty-tui-harness packages/tinker-chrome/__tests__/native-host-integration",
|
|
77
79
|
"typecheck": "tsc --noEmit"
|
|
78
80
|
},
|
package/src/agent/loop.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { ModelRequestFailedData } from "../events/types";
|
|
2
|
+
import type { ProviderRetryDecision } from "./runtime-provider-retry";
|
|
1
3
|
import {
|
|
2
4
|
materializeModelRequest,
|
|
3
5
|
type MaterializedModelRequest,
|
|
@@ -74,6 +76,11 @@ export type RunAgentInput = {
|
|
|
74
76
|
usage: ContextUsageSnapshot;
|
|
75
77
|
};
|
|
76
78
|
transientRetryDelaysMs?: readonly number[];
|
|
79
|
+
requestProviderRetry?: (
|
|
80
|
+
iteration: IterationIdentity,
|
|
81
|
+
failure: ModelRequestFailedData,
|
|
82
|
+
signal: AbortSignal,
|
|
83
|
+
) => Promise<ProviderRetryDecision>;
|
|
77
84
|
};
|
|
78
85
|
|
|
79
86
|
export class FatalAgentTurnError extends Error {
|
|
@@ -196,12 +203,13 @@ export async function runAgent(input: RunAgentInput): Promise<RunAgentResult> {
|
|
|
196
203
|
return failedResult(error, iteration);
|
|
197
204
|
}
|
|
198
205
|
|
|
206
|
+
let maxAttempts = MODEL_REQUEST_MAX_ATTEMPTS;
|
|
199
207
|
await input.runtimeSession.append({
|
|
200
208
|
type: "model.request.started",
|
|
201
209
|
...iteration,
|
|
202
210
|
data: {
|
|
203
211
|
attemptNumber: 1,
|
|
204
|
-
maxAttempts
|
|
212
|
+
maxAttempts,
|
|
205
213
|
},
|
|
206
214
|
});
|
|
207
215
|
|
|
@@ -240,7 +248,7 @@ export async function runAgent(input: RunAgentInput): Promise<RunAgentResult> {
|
|
|
240
248
|
...iteration,
|
|
241
249
|
data: {
|
|
242
250
|
attemptNumber,
|
|
243
|
-
maxAttempts
|
|
251
|
+
maxAttempts,
|
|
244
252
|
},
|
|
245
253
|
});
|
|
246
254
|
}
|
|
@@ -289,19 +297,47 @@ export async function runAgent(input: RunAgentInput): Promise<RunAgentResult> {
|
|
|
289
297
|
transientRetries,
|
|
290
298
|
transientRetryDelaysMs,
|
|
291
299
|
});
|
|
300
|
+
const failure = modelRequestFailureData({
|
|
301
|
+
error,
|
|
302
|
+
request,
|
|
303
|
+
attemptNumber,
|
|
304
|
+
maxAttempts,
|
|
305
|
+
retryDisposition: decision.disposition,
|
|
306
|
+
...(decision.disposition === "scheduled" && decision.delayMs > 0
|
|
307
|
+
? { retryDelayMs: decision.delayMs }
|
|
308
|
+
: {}),
|
|
309
|
+
});
|
|
292
310
|
await input.runtimeSession.append({
|
|
293
311
|
type: "model.request.failed",
|
|
294
312
|
...iteration,
|
|
295
|
-
data:
|
|
296
|
-
error,
|
|
297
|
-
request,
|
|
298
|
-
attemptNumber,
|
|
299
|
-
retryDisposition: decision.disposition,
|
|
300
|
-
...(decision.disposition === "scheduled" && decision.delayMs > 0
|
|
301
|
-
? { retryDelayMs: decision.delayMs }
|
|
302
|
-
: {}),
|
|
303
|
-
}),
|
|
313
|
+
data: failure,
|
|
304
314
|
});
|
|
315
|
+
if (decision.disposition === "exhausted" && input.requestProviderRetry) {
|
|
316
|
+
let retry: ProviderRetryDecision;
|
|
317
|
+
try {
|
|
318
|
+
retry = await input.requestProviderRetry(iteration, failure, input.signal);
|
|
319
|
+
} catch (retryError) {
|
|
320
|
+
if (input.signal.aborted) {
|
|
321
|
+
return cancelledResult(
|
|
322
|
+
cancellation(input.signal, iteration, "model_request"),
|
|
323
|
+
iteration,
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
throw retryError;
|
|
327
|
+
}
|
|
328
|
+
if (input.signal.aborted) {
|
|
329
|
+
return cancelledResult(
|
|
330
|
+
cancellation(input.signal, iteration, "model_request"),
|
|
331
|
+
iteration,
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
if (retry === "retry") {
|
|
335
|
+
reasoningOnlyRetries = 0;
|
|
336
|
+
transientRetries = 0;
|
|
337
|
+
maxAttempts = attemptNumber + MODEL_REQUEST_MAX_ATTEMPTS;
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
305
341
|
|
|
306
342
|
if (decision.disposition === "scheduled") {
|
|
307
343
|
if (decision.kind === "reasoning_only") {
|
|
@@ -349,7 +385,7 @@ export async function runAgent(input: RunAgentInput): Promise<RunAgentResult> {
|
|
|
349
385
|
...iteration,
|
|
350
386
|
data: {
|
|
351
387
|
attemptNumber: successfulAttempt,
|
|
352
|
-
maxAttempts
|
|
388
|
+
maxAttempts,
|
|
353
389
|
output: modelOutput,
|
|
354
390
|
},
|
|
355
391
|
});
|
|
@@ -823,6 +859,7 @@ function modelRequestFailureData(input: {
|
|
|
823
859
|
error: unknown;
|
|
824
860
|
request: PreparedModelRequest;
|
|
825
861
|
attemptNumber: number;
|
|
862
|
+
maxAttempts: number;
|
|
826
863
|
retryDisposition: "scheduled" | "not_retryable" | "exhausted";
|
|
827
864
|
retryDelayMs?: number;
|
|
828
865
|
}) {
|
|
@@ -830,7 +867,7 @@ function modelRequestFailureData(input: {
|
|
|
830
867
|
input.error instanceof ProviderResponseError ? input.error : undefined;
|
|
831
868
|
return {
|
|
832
869
|
attemptNumber: input.attemptNumber,
|
|
833
|
-
maxAttempts:
|
|
870
|
+
maxAttempts: input.maxAttempts,
|
|
834
871
|
code: providerError?.code ?? ("provider_request_error" as const),
|
|
835
872
|
retryDisposition: input.retryDisposition,
|
|
836
873
|
...(input.retryDelayMs === undefined ? {} : { retryDelayMs: input.retryDelayMs }),
|