Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/ai-harness-view.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@tanstack/ai-harness': minor
---

Add `createSessionView()` in `@tanstack/ai-harness/view`. It turns a session (local, or remote through `createHarnessClient()`) into one live TanStack Store state that works with any UI library.

- The state: messages with their parts, tool calls, approvals, client tools, other interrupts, questions, sign-ins, child agents with their results, media with signed URLs, waiting inputs, usage, models, settings, commands, and plugin state.
- Actions: `send` (a prompt, a steer, or a `/command`), `followUp`, `continue`, `upload`, `cancel`, `cancelInput`, `setDelivery`, `configure`, `reset`, `revert`, and the answers to approvals (with edited arguments), client tools, interrupts, and questions.
- Typed events: `view.on('approval' | 'interrupt' | 'turnEnd' | ..., handler)` and plugin events.
- A view that opens while a turn runs shows the turn from its start. After a reload, each child agent shows its own messages again.
6 changes: 6 additions & 0 deletions docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,12 @@
"to": "harness/shared-threads",
"addedAt": "2026-10-13"
},
{
"label": "Build your own UI",
"to": "harness/custom-ui",
"addedAt": "2026-10-13",
"updatedAt": "2026-10-14"
},
{
"label": "Run agents from a harness",
"to": "harness/subagents",
Expand Down
13 changes: 10 additions & 3 deletions docs/harness/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,14 +115,21 @@ When the credential is missing, the turn stops with an interrupt:
- `wait` is for a tool of the running chat turn. Do not pass it in a command, a background agent, or a hook. The credentials of a command never wait.
- Provider keys (`ctx.keys.require`) do not wait.

In a web app, run the connector command when the user presses your connect button. The session sends a `harness.auth_required` event with the `url` and the `userCode` to show. A client can run only the commands in `expose.commands`, so the harness above exposes `connect:github` (see [Choose what clients can change](./connect#choose-what-clients-can-change)):
In a web app, the wait shows in `state.signIns` of the session view, also after a reload. When the user presses your connect button, run the connector command. Each sign-in then has the `url` and the `userCode` to show. A client can run only the commands in `expose.commands`, so the harness above exposes `connect:github` (see [Choose what clients can change](./connect#choose-what-clients-can-change)):

```ts group=harness-auth-wait-client
import { createHarnessClient } from '@tanstack/ai-harness/client'
import { createSessionView } from '@tanstack/ai-harness/view'

const client = createHarnessClient({ url: '/api/harness', threadId: 'user-1-thread' })
const view = createSessionView(
createHarnessClient({ url: '/api/harness', threadId: 'user-1-thread' }),
)

export const connectGithub = () => client.command('connect:github')
view.on('signIn', (signIn) => {
console.log(`Connect ${signIn.connector} to continue the turn.`)
})

export const connectGithub = () => view.command('connect:github')
```

After the sign-in, the turn finishes its work with no new message.
Expand Down
4 changes: 2 additions & 2 deletions docs/harness/connect.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export const team = defineHarness({
| `agents` | Run these agents, for example with `client.agents.<name>.start(input)`. |
| `settings` | Change these [thread settings](./thread-settings) with `client.configure(settings)`. |
| `config` | Set these plugin config keys with `client.setConfig(key, value)`. |
| `commands` | Run these commands with `client.command(name)`. |
| `commands` | Run these commands with `client.command(name)`, or with `/name` in a [session view](./custom-ui) on a client. |

- Any other input of these kinds gets `{ status: 'rejected', reason: 'not_exposed' }`.
- `GET describe` and `client.describe()` list only the exposed commands and config keys. A UI built from them shows only what a client can use.
Expand Down Expand Up @@ -164,7 +164,7 @@ export function addTitle(operationId: string) {
}
```

The id of the run is the `operationId` in the receipt of `client.agents.drafter.start()`.
The id of the run is the `operationId` in the receipt of `client.agents.drafter.start()`. A [session view](./custom-ui#3-act-on-the-session) also lists the running agents with their ids.

- `mode: 'steer'` (the default): the message joins the next model call of the run.
- `mode: 'followUp'`: the agent runs again after the run ends. `receipt.operationId` is the id of the new run.
Expand Down
Loading
Loading