Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
37fa62b
feat(ai-persistence): LogStore v2 contract and the records API with a…
AlemTuzlak Oct 16, 2026
5ec0a98
feat(ai-persistence): LogStore ownership with an epoch fence
AlemTuzlak Oct 16, 2026
1c99ac0
docs(ai-persistence): LogStore v2 reference
AlemTuzlak Oct 16, 2026
5d4ed6e
refactor(ai-harness): move the harness to the LogStore v2 call shapes
AlemTuzlak Oct 16, 2026
9846f1f
feat(ai-persistence): LogStore input lane with atomic consumption
AlemTuzlak Oct 16, 2026
6a84757
feat(ai-persistence): LogStore snapshots, truncate, and delete
AlemTuzlak Oct 16, 2026
2000f1a
feat(ai-persistence): memory log backend shared by store instances, a…
AlemTuzlak Oct 16, 2026
fcf51e4
docs(ai-persistence): acquire adds a missing owner row and deleteLog …
AlemTuzlak Oct 17, 2026
6ef65e4
feat(ai-harness): seal the public API with subpaths, one-object calls…
AlemTuzlak Oct 20, 2026
d45f9aa
fix(ai-persistence)!: owner-only pins and snapshots, a delete tombsto…
AlemTuzlak Oct 20, 2026
d65efbc
fix(ai-persistence,ai-harness)!: offer to a deleted log returns 'dele…
AlemTuzlak Oct 20, 2026
72ac9fa
docs(ai-harness): bump updatedAt of the inputs page
AlemTuzlak Oct 20, 2026
dfa0371
test(ai-harness): check that no sign-in deadline starts, not the proc…
AlemTuzlak Oct 20, 2026
43e90da
refactor(ai-harness, ai-harness-cli, ai-mcp): remove what the harness…
AlemTuzlak Oct 20, 2026
a53ddfa
test(ai-harness): a second host's plugin state does not stop the owne…
AlemTuzlak Oct 20, 2026
bda919c
docs(ai-harness): match the code on who answers a human wait, send to…
AlemTuzlak Oct 20, 2026
19d8742
feat(ai-harness): client.sessions.share and unshare, and export the H…
AlemTuzlak Oct 20, 2026
ae0a9b5
feat(ai-harness): add a handoff note to the transcript for router and…
AlemTuzlak Oct 20, 2026
e9365c1
feat(ai-harness): session.jobs() lists the background jobs that did n…
AlemTuzlak Oct 20, 2026
59bb908
feat(ai-harness): accept onModelError as data that the log keeps
AlemTuzlak Oct 20, 2026
b0bebc4
feat(ai-harness, ai, ai-persistence): an agent tool waits with the turn
AlemTuzlak Oct 20, 2026
2d682ad
feat(ai-harness): every host and host.observe see the waits of a harn…
AlemTuzlak Oct 20, 2026
ab52439
feat(ai-harness): resume background agents of the model after a host …
AlemTuzlak Oct 20, 2026
95a1427
docs(ai-harness): an agent tool waits with the turn
AlemTuzlak Oct 20, 2026
9f5f0bc
feat(ai-harness): start a queued top-level agent start on the next host
AlemTuzlak Oct 20, 2026
fa68a22
fix(ai-harness): throw for a parent that names no running agent run
AlemTuzlak Oct 20, 2026
63e0c49
feat(ai-harness): presence hooks for the client streams of a host
AlemTuzlak Oct 20, 2026
94de1d6
feat(ai-harness): give agent runs a subagentRunId, and read a run tra…
AlemTuzlak Oct 20, 2026
c63cd42
Merge branch 'wp/1807-child-waits' into docs/1807-mismatch
AlemTuzlak Oct 20, 2026
0c9e9b7
Merge branch 'wp/1807-reads-presence' into docs/1807-mismatch
AlemTuzlak Oct 20, 2026
832081f
fix(ai-harness): report error, not closed, when the server closes a s…
AlemTuzlak Oct 20, 2026
8d71147
feat(ai-harness): export the PresenceEndReason type
AlemTuzlak Oct 20, 2026
9880b0c
docs(ai-harness): name the url limit of step.ask in an agent tool
AlemTuzlak Oct 20, 2026
f27cda1
fix(ai-harness): read the child id of generated media by its place in…
AlemTuzlak Oct 20, 2026
245e152
test(ai-harness): answer the wait of an agent tool from a second live…
AlemTuzlak Oct 20, 2026
4c3de43
fix(ai-harness): start a model background child that never ran as the…
AlemTuzlak Oct 20, 2026
5d0e200
test(ai-e2e): an agent tool that waits for an approval, and the hando…
AlemTuzlak Oct 20, 2026
8887b79
Merge branch 'wp/1807-child-waits' into docs/1807-mismatch
AlemTuzlak Oct 20, 2026
65f2da9
fix(ai-harness): end the waits of a harnessAgent child whose host sto…
AlemTuzlak Oct 20, 2026
7c27c3d
Merge branch 'wp/1807-agent-fixes' into docs/1807-mismatch
AlemTuzlak Oct 20, 2026
75c9945
fix(ai-harness): start a model background child that never ran, also …
AlemTuzlak Oct 20, 2026
becf88a
Merge branch 'wp/1807-agent-fixes' into docs/1807-mismatch
AlemTuzlak Oct 20, 2026
e2e8ca2
docs(ai): mark subagents.binding as internal and remove it from the p…
AlemTuzlak Oct 20, 2026
8da064a
docs(ai-harness): add a Host options page with every createHarnessHos…
AlemTuzlak Oct 20, 2026
03ea378
refactor(ai-harness, ai-mcp, ai-acp, ai-harness-cli): rename principa…
AlemTuzlak Oct 20, 2026
e508e2b
Merge branch 'wp/1807-user-rename' into docs/1807-mismatch
AlemTuzlak Oct 20, 2026
52e3f39
docs(ai-harness): link the Host options page from the createHarnessHo…
AlemTuzlak Oct 20, 2026
728b982
refactor(ai-harness, ai-mcp, ai-acp, ai-harness-cli, ai-skills): give…
AlemTuzlak Oct 20, 2026
aa393d4
refactor(ai-harness, ai-mcp, ai-acp, ai-harness-cli, ai-skills, ai-sa…
AlemTuzlak Oct 20, 2026
fd61b94
refactor(ai-harness, ai-acp, ai-harness-cli): give the agent start op…
AlemTuzlak Oct 20, 2026
8c04a1e
refactor(ai-persistence, ai-harness): give the LogStore methods clear…
AlemTuzlak Oct 20, 2026
f37c837
refactor(ai-harness): use the new session method names in the last do…
AlemTuzlak Oct 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
11 changes: 6 additions & 5 deletions .changeset/ai-harness-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@

Add `@tanstack/ai-harness-cli`: `runCli(harness)` runs a harness from the terminal.

- Line mode reads one message or `/command` per line, answers approvals and questions, hides a secret answer while the user types, and sends `@path` files. Pass `ui` to show your own screen on a session view instead.
- `-p "prompt"` runs one prompt for scripts and CI, and `--output ndjson` prints every AG-UI event.
- `--serve` serves the session protocol over HTTP behind a bearer token.
- `--acp`, `--mcp`, and `--dashboard` load `@tanstack/ai-acp`, `@tanstack/ai-mcp`, and `@tanstack/ai-dashboard` only when you use the flag. A missing package, or one without harness support, fails that flag with a clear message.
- `host` and `principal` options let the CLI use your durable host and run as your user.
- Line mode reads one message or `/command` per line, hides a secret answer while the user types, and sends `@path` files. In a terminal, `/cancel` and a message typed while a turn runs act at once: the message joins the running turn. When a turn waits, each next line answers one wait: `y` or `n` for an approval (`once`, `always`, or `reject` for an approval of `permissions()`), and an answer read with the schema for a question. Pass `ui` to show your own screen on a session view instead.
- `-p "prompt"` runs one prompt for scripts and CI, and `--output ndjson` prints every AG-UI event. `-p` exits with code 3 (`EXIT.needsAction`) at the first wait of the turn: an approval, a question, or a sign-in.
- `--serve` serves the session protocol over HTTP behind a bearer token. It signs media URLs with its own key, never with the token: `--media-key` or `HARNESS_MEDIA_KEY` (at least 32 bytes), or a new random key at each start. Without a set key, signed media URLs stop working after a restart. On a host without `access`, it also passes `openAccess`: the `user` of `runCli`, or `true` without one. So each request with the token runs as the user of the CLI.
- `--acp` and `--mcp` load `@tanstack/ai-acp` and `@tanstack/ai-mcp` only when you use the flag. A missing package, or one without harness support, fails that flag with a clear message. Any other error while the package loads is reported, not hidden.
- In every mode, the first SIGINT or SIGTERM cancels the running turn. A second one, or one while no turn runs, stops the CLI, and `runCli` resolves to 130 (SIGINT) or 143 (SIGTERM). The CLI closes a host that it made with `recoverable: true`, or releases its session handle on your own host and does not wait for a turn that ignores the cancel. The `signals` option takes another signal source, for example an `EventEmitter` in a test.
- `host` and `user` options let the CLI use your durable host and run as your user. Every mode opens its sessions as that user. On a host with `access`, `--serve` reads the user of each request with `getUser`, and `--mcp` does not start, because stdio has no login.
15 changes: 11 additions & 4 deletions .changeset/ai-harness-coding-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,17 @@

Add coding tools in the new Node entry `@tanstack/ai-harness/plugins/coding`:

- `workspaceTools({ root })` gives `read_file` (text pages, images, PDFs), `write_file`, `edit_file` or `patch` (`editStyle`), `list_files`, `grep`, `bash` (with `background`), `webfetch`, and `websearch` (with a `SearchProvider`). With `permissions()`, edits, `bash`, and `webfetch` ask first, and the rules also apply to the real path of a link. `outside: 'ask'` asks before a path outside `root`. The `cwd` thread setting gives each thread its own working folder.
- A `WorkspaceBackend` runs the tools. `hostBackend` is the default. On Windows, commands do not run a program from the working folder by its name.
- `snapshots()` adds `/undo` and `/redo`, which restore only the files of the turn (git 2.26 or newer), file restore for `session.revert()`, and a diff for each model step.
- `formatter()` formats each written file with the formatter of the project. With `permissions()`, each run asks first.
- `workspaceTools({ root })` gives `read_file` (text pages, images, PDFs), `write_file`, `edit_file` or `patch` (`editStyle`), `list_files`, `grep`, `bash` (with `background`), `webfetch`, and `websearch` (with a `SearchProvider`). With `permissions()`, edits, `bash`, and `webfetch` ask first, and the rules also apply to the real path of a link. `outside: 'ask'` makes a path outside `root` need an approval. `permissions()` decides outside paths and the real paths of links before the call runs, so a tool never asks while it runs. The `cwd` thread setting gives each thread its own working folder.
- `workspaceTools({ workspace: { bind, reattach? } })` gives each thread its own workspace. The root of the workspace is the boundary of every tool. The session log keeps where the workspace is (`harness.workspace.bound`). On another host, `reattach` gives the workspace back (for example a resumed sandbox), or `'pinned'` or `'gone'`. A gone workspace sends `tanstack/workspace-tools:stranded`. With this plugin first, `snapshots()` and `formatter()` use the workspace of each thread, and their `root` is optional.
- `createHarnessHost({ ownership: { hostId } })` gives a host a stable id. Then a thread that uses a folder on that host is pinned to it, and other hosts refuse its tool calls. Without `hostId`, threads are not pinned. A host with `hostId` sends a heartbeat to the log store, and its pins lapse when no heartbeat came for `ownership.expiresAfterMs`: then another host reattaches its threads. `host.unpin({ harness, threadId })` unpins a thread from a host that does not come back, once its ownership expired.
- `workspaceRoot(base, ...parts)` gives a folder in `base` for a tenant and a thread id. It encodes each part, so a thread id from a client cannot leave `base` or reach another thread.
- Commands, `rg`, `git`, and the formatters get only a short list of variables of the server process, plus the `env` of the backend call. Git runs without the hooks and the `fsmonitor` of the project.
- The tools resolve links with `realpath` under the file lock, and fail when a backend cannot resolve a path. A write fails when the file changed after the tool read it in the same chat run, so a lead and its subagents do not lose each other's edits. The file locks are shared by the sessions of a host, by the sandbox (or host) and the real path of the file.
- Background jobs: at most 8 per session (`maxJobs`), the newest 1 MiB of output per job, and the events `tanstack/workspace-tools:job-started`, `job-output`, and `job-ended`. A running command that `session.moveToolCallToBackground()` moves counts as a job, and its process id goes to the log. A job that an earlier host left running is stopped at the next tool call, with a note to the model. When the backend cannot check the job (for example with no `ps`), the log keeps it, and the note says that it can still run. The wake turn of a job runs as the user who started it. `session.jobs()` and `jobs()` of a `host.observe` view list the jobs that did not end (`JobInfo`: the job id, the thread, the command or tool name, who moved it, when it started, and `running` on this host). The `bash` spill file has `[redacted]` in place of each secret of the session.
- `read_file` sends images and PDFs up to 256 KB. `webfetch` gives the model the first 256 KB of a page.
- A `WorkspaceBackend` runs the tools. `hostBackend` is the default. `realpath` is required. `spawn` can give a `jobId`, `exec` can give one with `onStart`, and `killJob`, `binding`, and `close` are optional. `killJob` rejects when it cannot check that the process is the job. On Windows, commands do not run a program from the working folder by its name.
- `snapshots()` adds `/undo` and `/redo`, which restore only the files of the turn (git 2.26 or newer), file restore for `session.revert()`, and a diff for each model step. Each thread of each tenant has its own shadow repository, which never stores `.env` files. A step keeps only the files of its own run. The steps and the undo point are in the session log, so another host with the same repository can undo. A revert uses the last 500 steps. `host.sessions.delete` removes the repository of the thread, and `purgeSnapshots()` does it from your own code. A failed snapshot sends `tanstack/snapshots:failed`.
- `formatter()` formats each written file with the formatter of the project. With `permissions()`, the rules decide the formatter with the call that writes, before that call runs: one approval covers both, and the formatter runs after the write. An `always` answer is tied to a hash of the formatter config files. A write of a formatter config below the root, or under `node_modules`, asks each time, and an `always` answer saves no rule for that formatter run.
- `WorkspaceHooks` lets other plugins run code after a read or a write.

`@vscode/ripgrep` and `turndown` are optional peer dependencies.
Loading
Loading