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.
Files changed (61) hide show
  1. package/CHANGELOG.md +69 -1
  2. package/README.md +30 -1
  3. package/package.json +5 -3
  4. package/src/agent/loop.ts +50 -13
  5. package/src/agent/runtime-hosted-session.ts +443 -0
  6. package/src/agent/runtime-provider-retry.ts +115 -0
  7. package/src/agent/runtime-session-contracts.ts +11 -0
  8. package/src/agent/runtime-session.ts +29 -0
  9. package/src/cli/command-line.ts +26 -2
  10. package/src/cli/connect-runner.tsx +26 -0
  11. package/src/cli/main.ts +26 -0
  12. package/src/cli/output.ts +1 -1
  13. package/src/cli/public-cli-contract.ts +18 -0
  14. package/src/cli/public-config-contract.ts +1 -1
  15. package/src/cli/serve-runner.ts +45 -0
  16. package/src/cli/serve-runtime.ts +100 -0
  17. package/src/cli/tui-runner.tsx +1 -0
  18. package/src/context/context-swap-renderer.ts +14 -0
  19. package/src/events/types.ts +8 -0
  20. package/src/image/abortable-file-open.ts +54 -0
  21. package/src/image/image-asset-store.ts +7 -1
  22. package/src/model/fake-model-client.ts +20 -1
  23. package/src/model/openai-model-utils.ts +45 -1
  24. package/src/model/openai-responses-mapping.ts +11 -0
  25. package/src/model/openai-responses-stream.ts +14 -0
  26. package/src/observation/observation-builder.ts +87 -37
  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/scoped-query-database.ts +27 -0
  36. package/src/session/session-history-access.ts +4 -3
  37. package/src/session/session-store.ts +7 -3
  38. package/src/tools/bash-task.ts +26 -16
  39. package/src/tools/bash.ts +44 -2
  40. package/src/tools/glob.ts +107 -19
  41. package/src/tools/grep-output.ts +130 -0
  42. package/src/tools/grep-pagination.ts +73 -0
  43. package/src/tools/grep-path.ts +11 -0
  44. package/src/tools/grep-snippets.ts +111 -0
  45. package/src/tools/grep.ts +148 -155
  46. package/src/tools/read.ts +0 -9
  47. package/src/tools/ripgrep.ts +19 -26
  48. package/src/tools/shell-process.ts +30 -4
  49. package/src/tools/task-stop.ts +2 -1
  50. package/src/tools/terminal-screen.ts +11 -2
  51. package/src/tools/types.ts +30 -2
  52. package/src/tui/app.tsx +45 -3
  53. package/src/tui/components/ask-user.tsx +15 -8
  54. package/src/tui/components/prompt-input.tsx +14 -6
  55. package/src/tui/components/timeline.tsx +17 -9
  56. package/src/tui/event-store.ts +25 -2
  57. package/src/tui/file-mention.ts +29 -5
  58. package/src/tui/remote-app.tsx +210 -0
  59. package/src/tui/tui-projection-store.ts +5 -2
  60. package/src/tui/tui-session-controller.ts +8 -0
  61. 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.9.0...HEAD
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 | `5000` | No | Default Bash foreground timeout in milliseconds. |
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.9.0",
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": "FORCE_COLOR=0 bun test --path-ignore-patterns='**/*pty*.test.ts' --path-ignore-patterns='**/native-host-integration.test.ts'",
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: MODEL_REQUEST_MAX_ATTEMPTS,
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: MODEL_REQUEST_MAX_ATTEMPTS,
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: modelRequestFailureData({
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: MODEL_REQUEST_MAX_ATTEMPTS,
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: MODEL_REQUEST_MAX_ATTEMPTS,
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 }),