ms-tau-sdk 1.2.12.dev20__tar.gz → 1.2.13.dev22__tar.gz
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.
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/CHANGELOG.md +46 -3
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/PKG-INFO +29 -8
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/README.md +28 -7
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/packages/tau-board/src/ms_tau_board/state.py +3 -1
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/packages/tau-board/src/ms_tau_board/static/app.js +1 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/packages/tau-board/src/ms_tau_board/static/index.html +1 -1
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/pyproject.toml +1 -1
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/agent_skills/tau_local_development/SKILL.md +35 -13
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/api/chat.py +20 -3
- ms_tau_sdk-1.2.13.dev22/src/ms_tau_sdk/api/local_chat.py +256 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/app.py +6 -1
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/application.py +2 -2
- ms_tau_sdk-1.2.13.dev22/src/ms_tau_sdk/backend/auth.py +480 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/backend/client.py +26 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/backend/local.py +279 -12
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/backend/models.py +31 -0
- ms_tau_sdk-1.2.13.dev22/src/ms_tau_sdk/protocols/chat_history.py +496 -0
- ms_tau_sdk-1.2.13.dev22/src/ms_tau_sdk/runtime/live_turns.py +135 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/runtime/manager.py +174 -4
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/settings.py +122 -9
- ms_tau_sdk-1.2.12.dev20/src/ms_tau_sdk/backend/auth.py +0 -212
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/.gitignore +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/packages/tau-board/src/ms_tau_board/__init__.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/packages/tau-board/src/ms_tau_board/app.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/packages/tau-board/src/ms_tau_board/cli.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/packages/tau-board/src/ms_tau_board/config.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/packages/tau-board/src/ms_tau_board/env_file.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/packages/tau-board/src/ms_tau_board/logs.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/packages/tau-board/src/ms_tau_board/proxy.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/packages/tau-board/src/ms_tau_board/static/BULMA-LICENSE.txt +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/packages/tau-board/src/ms_tau_board/static/board.css +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/packages/tau-board/src/ms_tau_board/static/bulma.min.css +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/__init__.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/agent_skills/tau_a2a_runtime_adapter/SKILL.md +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/agent_skills/tau_project_customization/SKILL.md +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/agent_skills/tau_repository_integration/SKILL.md +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/api/__init__.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/api/a2a.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/api/conversations.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/api/dependencies.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/api/health.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/api/inspection.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/api/models.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/api/sessions.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/backend/__init__.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/backend/mcp.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/backend/routes.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/cli.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/errors.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/logging.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/protocols/__init__.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/protocols/a2a_failure.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/protocols/a2a_message.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/protocols/a2a_roles.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/protocols/assistant_ui.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/protocols/strict_json.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/providers/__init__.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/providers/definitions.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/providers/factory.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/providers/tau_compat.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/resources/SYSTEM.md +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/resources/__init__.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/resources/loader.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/resources/prompts/review-code-repository.md +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/runtime/__init__.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/runtime/deployment_health.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/runtime/events.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/runtime/extensions.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/runtime/failures.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/runtime/observability.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/runtime/provenance.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/runtime/session.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/runtime/snapshots.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/runtime/task_context.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/sessions/__init__.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/sessions/storage.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/skills.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/tools/__init__.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/tools/mainsequence_mcp.py +0 -0
- {ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/src/ms_tau_sdk/tools/task_control.py +0 -0
|
@@ -2,6 +2,49 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- Local mode no longer needs a token in the environment or in the project `.env`. With neither
|
|
6
|
+
`MAINSEQUENCE_ACCESS_TOKEN` nor `MAINSEQUENCE_REFRESH_TOKEN` set, it asks a Main Sequence CLI
|
|
7
|
+
for the access token with `mainsequence auth token --json`: log in once with
|
|
8
|
+
`mainsequence login`. The SDK looks for the CLI in the new `MAINSEQUENCE_CLI` setting, then
|
|
9
|
+
beside the Python interpreter, then on `PATH`. It keeps the token in memory, reuses it until 60
|
|
10
|
+
seconds before it expires, and asks again after a rejected request. It refuses an answer for
|
|
11
|
+
another backend. A missing CLI, a CLI too old for the command, a missing or expired session, a
|
|
12
|
+
machine without a credential store, a time-out, and malformed output each have their own error.
|
|
13
|
+
The SDK still does not depend on or import the `mainsequence` package and does not read the
|
|
14
|
+
CLI's credential store. The token pair stays supported in the process environment, for
|
|
15
|
+
launchers and CI, and with both tokens set the CLI is never run. A pair read from the project
|
|
16
|
+
`.env` file still works but is deprecated: startup logs one warning that names the file and the
|
|
17
|
+
two variables, and `mainsequence refresh-token` run in that directory removes them. `/health`
|
|
18
|
+
and `/ready` report the source in `mainsequence_auth_source` (`cli`, `environment`, or
|
|
19
|
+
`env_file`; `null` in managed mode), and Tau Board shows it in Connect. The owner scope of local
|
|
20
|
+
chat sessions and direct A2A conversations is derived from the subject of the CLI token, so a
|
|
21
|
+
user keeps the same local sessions with either source. See the 2026-09-30 amendments of ADR 0005
|
|
22
|
+
and ADR 0017.
|
|
23
|
+
- Tau Board again reads the local state the SDK writes. 1.2.12 raised the local SQLite schema
|
|
24
|
+
version to 6 without raising the version Tau Board accepts, so the board refused every store
|
|
25
|
+
that 1.2.12 had created or opened: the State, session, Task, and Task-log views returned
|
|
26
|
+
"Unknown Tau local SQLite schema version". Schema 6 only adds the `local_chat_sessions` table,
|
|
27
|
+
and the board's queries are unchanged. The board still accepts exactly one schema version, so a
|
|
28
|
+
store last opened by 1.2.11 or earlier is refused until Tau starts once and migrates it. A Board
|
|
29
|
+
test now reads a store written by the SDK's local backend, so a schema change the board has not
|
|
30
|
+
followed fails the Board suite.
|
|
31
|
+
|
|
32
|
+
## 1.2.12 — 2026-09-27
|
|
33
|
+
|
|
34
|
+
- Local `/api/chat` sessions now survive a UI reload and a Tau restart. Local mode records each chat
|
|
35
|
+
session and adds `GET /api/local/v1/chat-sessions` to list them and
|
|
36
|
+
`GET /api/local/v1/chat-sessions/{sessionUid}/history` to read one back. The history uses the
|
|
37
|
+
shape of the platform's `GET /api/v1/agent-sessions/{uid}/history/`: user and assistant text,
|
|
38
|
+
reasoning, and tool calls with their arguments, results, and `isError`, projected from the Tau
|
|
39
|
+
transcript by the platform's rules. A turn still running is returned in `inProgressMessage`.
|
|
40
|
+
`GET /api/local/v1/agent` returns the Agent's name and description from the workspace's
|
|
41
|
+
`.agents/agent_card.json`. A local chat turn now runs to its durable end when its client
|
|
42
|
+
disconnects or reloads, and only `POST /api/chat/session/cancel` stops it. A second turn for a
|
|
43
|
+
session that is still running returns 409 `session_busy`. After a Stop, the next turn in a local
|
|
44
|
+
session used to fail with "Runtime cancellation was requested" until the idle runtime was
|
|
45
|
+
evicted; the session's next turn now runs on a fresh runtime. Managed chat behavior is unchanged.
|
|
46
|
+
With `MAINSEQUENCE_TAU_TRUSTED_ORIGINS` set, CORS also exposes `X-Agent-Session-Uid` and
|
|
47
|
+
`x-vercel-ai-ui-message-stream`. See ADR 0018. Fixes #47.
|
|
5
48
|
- Added `MAINSEQUENCE_TAU_PROVIDER_TIMEOUT_SECONDS`, the HTTP timeout for model-provider calls.
|
|
6
49
|
It was fixed at 60 seconds for every provider hydrated from the platform, with no setting
|
|
7
50
|
or environment variable to change it. A self-hosted OpenAI-compatible provider such as an
|
|
@@ -9,15 +52,15 @@
|
|
|
9
52
|
prompt exhausted all three attempts, and Tau reported only an empty transport failure. The
|
|
10
53
|
default stays 60 seconds, and an explicit `timeout_seconds` passed to `ProviderFactory.build`
|
|
11
54
|
still takes precedence.
|
|
55
|
+
|
|
56
|
+
## 1.2.11 — 2026-09-27
|
|
57
|
+
|
|
12
58
|
- Added authenticated-process-scoped discovery and bounded hydration for direct local A2A Message
|
|
13
59
|
conversations. The SDK now persists exact public requester/responder Messages independently from
|
|
14
60
|
Tau execution entries, returns stable context and Message identities across restart, supports
|
|
15
61
|
cursor pagination and exact completed-request replay, and exposes
|
|
16
62
|
`GET /api/local/v1/conversations` plus its bounded Message-history route. Existing internal
|
|
17
63
|
sessions are not heuristically reconstructed. Fixes #45.
|
|
18
|
-
|
|
19
|
-
## 1.2.11 — 2026-09-27
|
|
20
|
-
|
|
21
64
|
- Added a version-locked `tau-ai==0.4.2` compatibility patch for OpenAI-compatible transport
|
|
22
65
|
failures that exhausted retries with an empty terminal message. The SDK now preserves the
|
|
23
66
|
concrete HTTPX error type, transport phase, retry evidence, total provider duration, one failure
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ms-tau-sdk
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.13.dev22
|
|
4
4
|
Summary: Workspace-bound Tau application primitives for Main Sequence projects
|
|
5
5
|
Project-URL: Changelog, https://github.com/mainsequence-sdk/ms-tau-sdk/blob/development/CHANGELOG.md
|
|
6
6
|
Project-URL: Documentation, https://github.com/mainsequence-sdk/ms-tau-sdk/tree/development/docs
|
|
@@ -129,9 +129,9 @@ project selects a provider and model explicitly, while Main Sequence still autho
|
|
|
129
129
|
the provider credential and supplies the live MCP catalog:
|
|
130
130
|
|
|
131
131
|
```bash
|
|
132
|
+
mainsequence login # once; the session is kept by the CLI, not in .env
|
|
133
|
+
|
|
132
134
|
export MAINSEQUENCE_AUTH_MODE=jwt
|
|
133
|
-
export MAINSEQUENCE_ACCESS_TOKEN="<exported-user-access-token>"
|
|
134
|
-
export MAINSEQUENCE_REFRESH_TOKEN="<exported-user-refresh-token>"
|
|
135
135
|
export TAU_LOCAL_MODE=true
|
|
136
136
|
export TAU_LOCAL_PROVIDER=openai
|
|
137
137
|
export TAU_LOCAL_MODEL=gpt-5.4
|
|
@@ -139,10 +139,21 @@ export TAU_LOCAL_MODEL=gpt-5.4
|
|
|
139
139
|
uv run ms-tau
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
-
|
|
143
|
-
`
|
|
144
|
-
|
|
145
|
-
|
|
142
|
+
Log in once with the Main Sequence CLI. Local mode then asks that CLI for a short-lived access
|
|
143
|
+
token with `mainsequence auth token --json`, and again when the token is about to expire. No token
|
|
144
|
+
is exported and none is written to the project `.env`. The SDK looks for the CLI in
|
|
145
|
+
`MAINSEQUENCE_CLI`, then beside the Python interpreter, then on `PATH`. `ms-tau-sdk` does not
|
|
146
|
+
install or import the `mainsequence` Python package and does not read the CLI's credential store.
|
|
147
|
+
|
|
148
|
+
Launchers and CI can hand the process a token pair instead. With both variables set, the SDK
|
|
149
|
+
uses them and the public refresh API and never runs the CLI:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
export MAINSEQUENCE_ACCESS_TOKEN="<user-access-token>"
|
|
153
|
+
export MAINSEQUENCE_REFRESH_TOKEN="<user-refresh-token>"
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Provider secrets are never environment settings and are never persisted locally.
|
|
146
157
|
|
|
147
158
|
Local conversations and public A2A Tasks are stored at
|
|
148
159
|
`~/.tau/mainsequence/<workspace-hash>/runtime.sqlite3`; an omitted chat `sessionUid` uses the
|
|
@@ -172,6 +183,15 @@ events as distinct sections. Correlated provider failures appear first as one hu
|
|
|
172
183
|
incident with cause, phase, retries, duration, and bounded traceback frames; raw JSON is an
|
|
173
184
|
expandable secondary view.
|
|
174
185
|
|
|
186
|
+
Local `/api/chat` sessions are reload-safe as well. List them with
|
|
187
|
+
`GET /api/local/v1/chat-sessions` and read one back with
|
|
188
|
+
`GET /api/local/v1/chat-sessions/{sessionUid}/history`, which returns the platform's chat history
|
|
189
|
+
shape: user and assistant text, reasoning, and tool calls with their results, plus a turn still
|
|
190
|
+
running in `inProgressMessage`. Continue a session by sending its `sessionUid` to `POST /api/chat`.
|
|
191
|
+
A local chat turn keeps running when its client disconnects; stop it with
|
|
192
|
+
`POST /api/chat/session/cancel`. `GET /api/local/v1/agent` returns the Agent's name and description
|
|
193
|
+
from the workspace's `.agents/agent_card.json`.
|
|
194
|
+
|
|
175
195
|
Direct local A2A Message conversations are also reload-safe without becoming Tasks. List them with
|
|
176
196
|
`GET /api/local/v1/conversations`, then hydrate one with
|
|
177
197
|
`GET /api/local/v1/conversations/{contextId}/messages`; both reads are bounded and scoped to the
|
|
@@ -234,7 +254,8 @@ are not bundled into the SDK. Main Sequence transport and protocol behavior rema
|
|
|
234
254
|
## Included capabilities
|
|
235
255
|
|
|
236
256
|
- FastAPI application construction and lifecycle management
|
|
237
|
-
- runtime-credential exchange, local
|
|
257
|
+
- runtime-credential exchange, local sign-in through the Main Sequence CLI session or a user-JWT
|
|
258
|
+
environment pair, and authenticated Main Sequence access
|
|
238
259
|
- provider validation and credential hydration
|
|
239
260
|
- durable Tau sessions, leases, restore, persistence, cancellation, eviction, and shutdown
|
|
240
261
|
- local Tau execution without backend AgentSession pre-creation
|
|
@@ -98,9 +98,9 @@ project selects a provider and model explicitly, while Main Sequence still autho
|
|
|
98
98
|
the provider credential and supplies the live MCP catalog:
|
|
99
99
|
|
|
100
100
|
```bash
|
|
101
|
+
mainsequence login # once; the session is kept by the CLI, not in .env
|
|
102
|
+
|
|
101
103
|
export MAINSEQUENCE_AUTH_MODE=jwt
|
|
102
|
-
export MAINSEQUENCE_ACCESS_TOKEN="<exported-user-access-token>"
|
|
103
|
-
export MAINSEQUENCE_REFRESH_TOKEN="<exported-user-refresh-token>"
|
|
104
104
|
export TAU_LOCAL_MODE=true
|
|
105
105
|
export TAU_LOCAL_PROVIDER=openai
|
|
106
106
|
export TAU_LOCAL_MODEL=gpt-5.4
|
|
@@ -108,10 +108,21 @@ export TAU_LOCAL_MODEL=gpt-5.4
|
|
|
108
108
|
uv run ms-tau
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
-
|
|
112
|
-
`
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
Log in once with the Main Sequence CLI. Local mode then asks that CLI for a short-lived access
|
|
112
|
+
token with `mainsequence auth token --json`, and again when the token is about to expire. No token
|
|
113
|
+
is exported and none is written to the project `.env`. The SDK looks for the CLI in
|
|
114
|
+
`MAINSEQUENCE_CLI`, then beside the Python interpreter, then on `PATH`. `ms-tau-sdk` does not
|
|
115
|
+
install or import the `mainsequence` Python package and does not read the CLI's credential store.
|
|
116
|
+
|
|
117
|
+
Launchers and CI can hand the process a token pair instead. With both variables set, the SDK
|
|
118
|
+
uses them and the public refresh API and never runs the CLI:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
export MAINSEQUENCE_ACCESS_TOKEN="<user-access-token>"
|
|
122
|
+
export MAINSEQUENCE_REFRESH_TOKEN="<user-refresh-token>"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Provider secrets are never environment settings and are never persisted locally.
|
|
115
126
|
|
|
116
127
|
Local conversations and public A2A Tasks are stored at
|
|
117
128
|
`~/.tau/mainsequence/<workspace-hash>/runtime.sqlite3`; an omitted chat `sessionUid` uses the
|
|
@@ -141,6 +152,15 @@ events as distinct sections. Correlated provider failures appear first as one hu
|
|
|
141
152
|
incident with cause, phase, retries, duration, and bounded traceback frames; raw JSON is an
|
|
142
153
|
expandable secondary view.
|
|
143
154
|
|
|
155
|
+
Local `/api/chat` sessions are reload-safe as well. List them with
|
|
156
|
+
`GET /api/local/v1/chat-sessions` and read one back with
|
|
157
|
+
`GET /api/local/v1/chat-sessions/{sessionUid}/history`, which returns the platform's chat history
|
|
158
|
+
shape: user and assistant text, reasoning, and tool calls with their results, plus a turn still
|
|
159
|
+
running in `inProgressMessage`. Continue a session by sending its `sessionUid` to `POST /api/chat`.
|
|
160
|
+
A local chat turn keeps running when its client disconnects; stop it with
|
|
161
|
+
`POST /api/chat/session/cancel`. `GET /api/local/v1/agent` returns the Agent's name and description
|
|
162
|
+
from the workspace's `.agents/agent_card.json`.
|
|
163
|
+
|
|
144
164
|
Direct local A2A Message conversations are also reload-safe without becoming Tasks. List them with
|
|
145
165
|
`GET /api/local/v1/conversations`, then hydrate one with
|
|
146
166
|
`GET /api/local/v1/conversations/{contextId}/messages`; both reads are bounded and scoped to the
|
|
@@ -203,7 +223,8 @@ are not bundled into the SDK. Main Sequence transport and protocol behavior rema
|
|
|
203
223
|
## Included capabilities
|
|
204
224
|
|
|
205
225
|
- FastAPI application construction and lifecycle management
|
|
206
|
-
- runtime-credential exchange, local
|
|
226
|
+
- runtime-credential exchange, local sign-in through the Main Sequence CLI session or a user-JWT
|
|
227
|
+
environment pair, and authenticated Main Sequence access
|
|
207
228
|
- provider validation and credential hydration
|
|
208
229
|
- durable Tau sessions, leases, restore, persistence, cancellation, eviction, and shutdown
|
|
209
230
|
- local Tau execution without backend AgentSession pre-creation
|
{ms_tau_sdk-1.2.12.dev20 → ms_tau_sdk-1.2.13.dev22}/packages/tau-board/src/ms_tau_board/state.py
RENAMED
|
@@ -9,7 +9,9 @@ from contextlib import closing
|
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
# The version `ms_tau_sdk.backend.local` stamps on the store. The board never imports the SDK,
|
|
13
|
+
# so this follows it by hand.
|
|
14
|
+
SCHEMA_VERSION = "6"
|
|
13
15
|
TABLES = (
|
|
14
16
|
"sessions",
|
|
15
17
|
"entries",
|
|
@@ -210,6 +210,7 @@ async function checkConnection() {
|
|
|
210
210
|
detailLine(box, "Mode", result.health.mode);
|
|
211
211
|
detailLine(box, "Version", result.health.version);
|
|
212
212
|
detailLine(box, "Ready", result.ready ? "Yes" : "No");
|
|
213
|
+
detailLine(box, "Credential source", result.health.mainsequence_auth_source);
|
|
213
214
|
detailLine(box, "Workspace digest", result.health.workspace_digest);
|
|
214
215
|
detailLine(box, "State directory", result.stateDir);
|
|
215
216
|
detailLine(box, "MCP tools", result.health.mcp_tool_count);
|
|
@@ -192,7 +192,7 @@
|
|
|
192
192
|
<button class="button is-primary" id="settings-apply-board">Apply to this session</button>
|
|
193
193
|
<p class="help mt-2">This updates the selected profile immediately. It does not write <code>.env</code>.</p>
|
|
194
194
|
</div>
|
|
195
|
-
<div class="box"><h2 class="title is-6">Credential presence</h2><p class="help mb-3">Board process and local <code>.env</code> are shown separately. Credential values are never sent to the browser.</p><div id="settings-credentials" class="detail-list"></div></div>
|
|
195
|
+
<div class="box"><h2 class="title is-6">Credential presence</h2><p class="help mb-3">Board process and local <code>.env</code> are shown separately. Credential values are never sent to the browser. A Tau process that uses the Main Sequence CLI session needs no token here. Connect shows its credential source.</p><div id="settings-credentials" class="detail-list"></div></div>
|
|
196
196
|
</div>
|
|
197
197
|
<div class="column">
|
|
198
198
|
<div class="box">
|
|
@@ -11,14 +11,15 @@ an offline, mock-provider, or mock-MCP mode.
|
|
|
11
11
|
|
|
12
12
|
## Start the process
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
The user logs in once with a Main Sequence CLI that provides `mainsequence auth token`. Local mode
|
|
15
|
+
then asks that CLI for a short-lived access token, at startup and again when the token is about to
|
|
16
|
+
expire. No token is exported and none is written to the project `.env`. The SDK does not install or
|
|
17
|
+
import the `mainsequence` Python package and does not read the CLI's private credential store.
|
|
17
18
|
|
|
18
19
|
```bash
|
|
20
|
+
mainsequence login # once; the session is kept by the CLI, not in .env
|
|
21
|
+
|
|
19
22
|
export MAINSEQUENCE_AUTH_MODE=jwt
|
|
20
|
-
export MAINSEQUENCE_ACCESS_TOKEN="<user-access-token>"
|
|
21
|
-
export MAINSEQUENCE_REFRESH_TOKEN="<user-refresh-token>"
|
|
22
23
|
export TAU_LOCAL_MODE=true
|
|
23
24
|
export TAU_LOCAL_PROVIDER="<provider>"
|
|
24
25
|
export TAU_LOCAL_MODEL="<model>"
|
|
@@ -28,12 +29,20 @@ export TAU_LOCAL_THINKING="<thinking-level>"
|
|
|
28
29
|
uv run ms-tau
|
|
29
30
|
```
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
`
|
|
33
|
-
|
|
32
|
+
The SDK looks for the CLI in `MAINSEQUENCE_CLI`, then beside the Python interpreter that runs
|
|
33
|
+
`ms-tau`, then on `PATH`. Launchers and CI can export `MAINSEQUENCE_ACCESS_TOKEN` and
|
|
34
|
+
`MAINSEQUENCE_REFRESH_TOKEN` instead. With both set, the SDK uses that pair and never runs the
|
|
35
|
+
CLI. Do not add token lines to `.env`. A pair found there still works, but it is deprecated and
|
|
36
|
+
startup logs a warning. `mainsequence refresh-token` run in that directory removes the lines.
|
|
37
|
+
|
|
38
|
+
Set `MAINSEQUENCE_ENDPOINT` only for a non-default platform endpoint. The CLI session must be for
|
|
39
|
+
the same endpoint. Local mode binds to `127.0.0.1:8787` by default. Treat an explicit public bind
|
|
40
|
+
as privileged exposure: each accepted request can use the authenticated user's live Main Sequence
|
|
41
|
+
permissions.
|
|
34
42
|
|
|
35
43
|
Never place JWTs or provider credentials in source control, `.tau`, copied skills, command output,
|
|
36
|
-
or debugging artifacts.
|
|
44
|
+
or debugging artifacts. Do not run `mainsequence auth token` to read its output: it prints the
|
|
45
|
+
access token. The user JWT is sent only to Main Sequence. Main Sequence validates the
|
|
37
46
|
explicit `TAU_LOCAL_PROVIDER` and `TAU_LOCAL_MODEL`, returns provider-control evidence, and hydrates
|
|
38
47
|
the provider credential used for inference. Provider secrets are neither environment settings nor
|
|
39
48
|
local state.
|
|
@@ -45,7 +54,7 @@ local state.
|
|
|
45
54
|
| Agent or AgentSession registration | Never created or updated |
|
|
46
55
|
| Chat history, snapshots, leases, activity, and cancellation | Workspace SQLite |
|
|
47
56
|
| Public A2A Message and Task state | Workspace SQLite |
|
|
48
|
-
| Main Sequence authentication and token refresh | Remote
|
|
57
|
+
| Main Sequence authentication and token refresh | Remote. The token comes from the Main Sequence CLI session, or from an exported JWT pair |
|
|
49
58
|
| Provider authorization, hydration, and inference | Remote and real |
|
|
50
59
|
| Main Sequence MCP catalog and tool calls | Remote and real; platform mutations remain possible |
|
|
51
60
|
| Project instructions, skills, hooks, and extensions | Normal repository `.tau` composition |
|
|
@@ -81,6 +90,16 @@ a platform AgentSession. Local mode supports:
|
|
|
81
90
|
rescheduling safe unclaimed work and terminalizing stale uncertain work rather than replaying
|
|
82
91
|
possible side effects.
|
|
83
92
|
|
|
93
|
+
For a chat UI over `POST /api/chat`, do not read `runtime.sqlite3` or keep a browser-only
|
|
94
|
+
transcript either. List sessions with `GET /api/local/v1/chat-sessions?limit=<n>&cursor=<opaque>`
|
|
95
|
+
and hydrate one with `GET /api/local/v1/chat-sessions/{sessionUid}/history`. The history has the
|
|
96
|
+
platform's `GET /api/v1/agent-sessions/{uid}/history/` shape, including reasoning, tool calls with
|
|
97
|
+
results, and a running turn in `inProgressMessage`, so one reader serves local and deployed
|
|
98
|
+
Agents. Reuse the listed `sessionUid` in `POST /api/chat`. A local chat turn keeps running when
|
|
99
|
+
the client disconnects or reloads; stop it only with `POST /api/chat/session/cancel`, and queue a
|
|
100
|
+
new message while a session is `working` (`POST /api/chat` returns 409 `session_busy`). Title the
|
|
101
|
+
UI from `GET /api/local/v1/agent`, which reads the workspace's `.agents/agent_card.json`.
|
|
102
|
+
|
|
84
103
|
For the default direct Message flow, do not read `runtime.sqlite3` or keep a browser-only source of
|
|
85
104
|
truth. Discover conversations with `GET /api/local/v1/conversations?limit=<n>&cursor=<opaque>` and
|
|
86
105
|
hydrate the latest bounded Message tail with
|
|
@@ -179,7 +198,8 @@ platform resources under the authenticated user's permissions.
|
|
|
179
198
|
|
|
180
199
|
Check the process in this order:
|
|
181
200
|
|
|
182
|
-
1. `GET /health` reports `mode: local
|
|
201
|
+
1. `GET /health` reports `mode: local`, safe composition diagnostics, and
|
|
202
|
+
`mainsequence_auth_source`: `cli`, `environment`, or `env_file`.
|
|
183
203
|
2. `GET /ready` confirms user authentication, provider control, local storage, and MCP readiness.
|
|
184
204
|
3. `GET /version` reports the expected installed SDK release.
|
|
185
205
|
4. A chat request without `sessionUid` returns an effective identifier in
|
|
@@ -193,8 +213,10 @@ Check the process in this order:
|
|
|
193
213
|
## Classify failures
|
|
194
214
|
|
|
195
215
|
- Startup setting failure: inspect the exact missing or conflicting environment variable.
|
|
196
|
-
- Authentication failure:
|
|
197
|
-
|
|
216
|
+
- Authentication failure: read the error, which names its remedy. The user runs
|
|
217
|
+
`mainsequence login`, upgrades a Main Sequence CLI that is too old for `auth token`, sets
|
|
218
|
+
`MAINSEQUENCE_CLI`, or provides the JWT pair. With an exported pair, export a fresh one. Do not
|
|
219
|
+
substitute a runtime credential in local mode.
|
|
198
220
|
- Provider/model rejection: verify the exact configured names against the authenticated live
|
|
199
221
|
catalog; do not silently select a different model.
|
|
200
222
|
- OpenAI-compatible transport failure: open the Task's failure incident in Tau Board. Use its
|
|
@@ -95,11 +95,24 @@ async def chat(
|
|
|
95
95
|
message_count=len(body.messages) if body.messages else None,
|
|
96
96
|
),
|
|
97
97
|
)
|
|
98
|
+
# A local turn is recorded as a chat session and outlives this request, so a
|
|
99
|
+
# reload or a closed tab leaves it running; only session/cancel stops it. A
|
|
100
|
+
# managed turn stays bound to the request that streams it.
|
|
101
|
+
local_turn = (
|
|
102
|
+
await manager.start_local_chat_turn(session_uid, prompt, provenance=provenance)
|
|
103
|
+
if manager.settings.local_mode
|
|
104
|
+
else None
|
|
105
|
+
)
|
|
106
|
+
events = (
|
|
107
|
+
local_turn.events()
|
|
108
|
+
if local_turn is not None
|
|
109
|
+
else manager.prompt(session_uid, prompt, provenance=provenance)
|
|
110
|
+
)
|
|
98
111
|
|
|
99
112
|
async def stream() -> AsyncIterator[bytes]:
|
|
100
113
|
encoder = AssistantUiEncoder()
|
|
101
114
|
try:
|
|
102
|
-
async for event in
|
|
115
|
+
async for event in events:
|
|
103
116
|
for payload in encoder.encode(event):
|
|
104
117
|
yield encoder.sse(payload)
|
|
105
118
|
try:
|
|
@@ -107,9 +120,10 @@ async def chat(
|
|
|
107
120
|
yield encoder.sse(payload)
|
|
108
121
|
yield encoder.done()
|
|
109
122
|
finally:
|
|
110
|
-
|
|
123
|
+
if local_turn is None:
|
|
124
|
+
manager.mark_response_delivered(session_uid)
|
|
111
125
|
except asyncio.CancelledError:
|
|
112
|
-
if not encoder.finished:
|
|
126
|
+
if local_turn is None and not encoder.finished:
|
|
113
127
|
await manager.cancel(session_uid)
|
|
114
128
|
raise
|
|
115
129
|
except Exception as error:
|
|
@@ -144,6 +158,9 @@ async def chat(
|
|
|
144
158
|
}
|
|
145
159
|
)
|
|
146
160
|
yield encoder.done()
|
|
161
|
+
finally:
|
|
162
|
+
if local_turn is not None:
|
|
163
|
+
local_turn.detach()
|
|
147
164
|
|
|
148
165
|
return StreamingResponse(
|
|
149
166
|
stream(),
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""Reload-safe local `/api/chat` sessions and the local Agent's identity (ADR 0018)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Annotated, Any
|
|
11
|
+
|
|
12
|
+
import structlog
|
|
13
|
+
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
14
|
+
|
|
15
|
+
from ms_tau_sdk.backend.client import MainSequenceClient
|
|
16
|
+
from ms_tau_sdk.backend.models import LocalChatSessionSummary
|
|
17
|
+
from ms_tau_sdk.protocols.chat_history import (
|
|
18
|
+
HISTORY_VERSION,
|
|
19
|
+
HistoryMessage,
|
|
20
|
+
current_branch,
|
|
21
|
+
iso_timestamp,
|
|
22
|
+
latest_message_preview,
|
|
23
|
+
project_history,
|
|
24
|
+
)
|
|
25
|
+
from ms_tau_sdk.runtime.manager import SessionRuntimeManager
|
|
26
|
+
from ms_tau_sdk.settings import TauSDKSettings
|
|
27
|
+
|
|
28
|
+
from .dependencies import backend, runtime_manager, settings
|
|
29
|
+
|
|
30
|
+
router = APIRouter(prefix="/api/local/v1")
|
|
31
|
+
BackendDep = Annotated[MainSequenceClient, Depends(backend)]
|
|
32
|
+
RuntimeManagerDep = Annotated[SessionRuntimeManager, Depends(runtime_manager)]
|
|
33
|
+
SettingsDep = Annotated[TauSDKSettings, Depends(settings)]
|
|
34
|
+
logger = structlog.get_logger(__name__)
|
|
35
|
+
|
|
36
|
+
AGENT_CARD_PATH = Path(".agents") / "agent_card.json"
|
|
37
|
+
LOCAL_AGENT_NAME = "Local Main Sequence TAU Agent"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _require_local(config: TauSDKSettings) -> None:
|
|
41
|
+
if not config.local_mode:
|
|
42
|
+
raise HTTPException(
|
|
43
|
+
status_code=409,
|
|
44
|
+
detail="Local chat sessions require TAU_LOCAL_MODE=true",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True, slots=True)
|
|
49
|
+
class AgentIdentity:
|
|
50
|
+
name: str | None = None
|
|
51
|
+
description: str | None = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _read_agent_identity(workspace: Path) -> AgentIdentity:
|
|
55
|
+
"""Read the name and description of the workspace's `.agents/agent_card.json`.
|
|
56
|
+
|
|
57
|
+
The platform takes a deployed Agent's name and description from the same file.
|
|
58
|
+
A missing or unreadable card leaves both unset rather than inventing them.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
path = workspace / AGENT_CARD_PATH
|
|
62
|
+
try:
|
|
63
|
+
card = json.loads(path.read_text(encoding="utf-8"))
|
|
64
|
+
except FileNotFoundError:
|
|
65
|
+
return AgentIdentity()
|
|
66
|
+
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as error:
|
|
67
|
+
logger.warning(
|
|
68
|
+
"local_agent.card_unreadable",
|
|
69
|
+
message="The workspace Agent Card could not be read",
|
|
70
|
+
path=str(AGENT_CARD_PATH),
|
|
71
|
+
error_type=type(error).__name__,
|
|
72
|
+
)
|
|
73
|
+
return AgentIdentity()
|
|
74
|
+
if not isinstance(card, dict):
|
|
75
|
+
return AgentIdentity()
|
|
76
|
+
|
|
77
|
+
def text(key: str) -> str | None:
|
|
78
|
+
value = card.get(key)
|
|
79
|
+
return value.strip() if isinstance(value, str) and value.strip() else None
|
|
80
|
+
|
|
81
|
+
return AgentIdentity(name=text("name"), description=text("description"))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
async def workspace_agent_identity(workspace: Path) -> AgentIdentity:
|
|
85
|
+
return await asyncio.to_thread(_read_agent_identity, workspace)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass(frozen=True, slots=True)
|
|
89
|
+
class _SessionProjection:
|
|
90
|
+
summary: LocalChatSessionSummary
|
|
91
|
+
messages: list[HistoryMessage]
|
|
92
|
+
in_progress: HistoryMessage | None
|
|
93
|
+
status: str
|
|
94
|
+
error: str | None
|
|
95
|
+
updated_at: datetime
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
async def _project_session(
|
|
99
|
+
client: MainSequenceClient,
|
|
100
|
+
manager: SessionRuntimeManager,
|
|
101
|
+
config: TauSDKSettings,
|
|
102
|
+
session_uid: str,
|
|
103
|
+
) -> _SessionProjection:
|
|
104
|
+
"""Project a session's durable branch plus the turn this process is running.
|
|
105
|
+
|
|
106
|
+
The running turn is read before the durable entries. If that turn committed in
|
|
107
|
+
between, its durable entries already hold everything the live view had.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
live_turn = manager.live_turn(session_uid)
|
|
111
|
+
live_entries = live_turn.entries() if live_turn is not None else None
|
|
112
|
+
transcript = await client.get_local_chat_transcript(
|
|
113
|
+
session_uid,
|
|
114
|
+
turn_uid=live_turn.turn_uid if live_turn is not None else None,
|
|
115
|
+
)
|
|
116
|
+
if transcript.turn_committed:
|
|
117
|
+
live_entries = None
|
|
118
|
+
durable = [
|
|
119
|
+
item.entry
|
|
120
|
+
for item in transcript.entries
|
|
121
|
+
if live_entries is None or live_turn is None or item.turn_uid != live_turn.turn_uid
|
|
122
|
+
]
|
|
123
|
+
branch = current_branch(durable)
|
|
124
|
+
projected = project_history(
|
|
125
|
+
[*branch, *(live_entries or [])],
|
|
126
|
+
target_agent_uid=config.local_agent_uid,
|
|
127
|
+
)
|
|
128
|
+
messages = projected.messages
|
|
129
|
+
in_progress: HistoryMessage | None = None
|
|
130
|
+
if (
|
|
131
|
+
live_entries is not None
|
|
132
|
+
and messages
|
|
133
|
+
and messages[-1]["role"] == "assistant"
|
|
134
|
+
and projected.source_indexes[-1] >= len(branch)
|
|
135
|
+
):
|
|
136
|
+
# The running turn's newest assistant message, streaming or awaiting a tool.
|
|
137
|
+
in_progress = {**messages.pop(), "completedAt": None}
|
|
138
|
+
running = (
|
|
139
|
+
live_entries is not None
|
|
140
|
+
or transcript.session.working
|
|
141
|
+
or manager.local_session_running(session_uid)
|
|
142
|
+
)
|
|
143
|
+
status = "running" if running else ("error" if projected.last_turn_error else "completed")
|
|
144
|
+
updated_at = transcript.session.updated_at
|
|
145
|
+
if projected.last_timestamp is not None:
|
|
146
|
+
updated_at = max(updated_at, projected.last_timestamp)
|
|
147
|
+
return _SessionProjection(
|
|
148
|
+
summary=transcript.session,
|
|
149
|
+
messages=messages,
|
|
150
|
+
in_progress=in_progress,
|
|
151
|
+
status=status,
|
|
152
|
+
error=projected.last_turn_error if status == "error" else None,
|
|
153
|
+
updated_at=updated_at,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _session_summary(
|
|
158
|
+
summary: LocalChatSessionSummary,
|
|
159
|
+
*,
|
|
160
|
+
message_count: int,
|
|
161
|
+
latest_message_preview: str | None,
|
|
162
|
+
updated_at: datetime,
|
|
163
|
+
working: bool,
|
|
164
|
+
) -> dict[str, Any]:
|
|
165
|
+
return {
|
|
166
|
+
"sessionUid": summary.session_uid,
|
|
167
|
+
"title": summary.title,
|
|
168
|
+
"messageCount": message_count,
|
|
169
|
+
"latestMessagePreview": latest_message_preview,
|
|
170
|
+
"createdAt": iso_timestamp(summary.created_at),
|
|
171
|
+
"updatedAt": iso_timestamp(updated_at),
|
|
172
|
+
"working": working,
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@router.get("/chat-sessions")
|
|
177
|
+
async def list_chat_sessions(
|
|
178
|
+
client: BackendDep,
|
|
179
|
+
manager: RuntimeManagerDep,
|
|
180
|
+
config: SettingsDep,
|
|
181
|
+
limit: int = Query(default=50, ge=1, le=100),
|
|
182
|
+
cursor: str | None = Query(default=None, min_length=1, max_length=1024),
|
|
183
|
+
) -> dict[str, Any]:
|
|
184
|
+
_require_local(config)
|
|
185
|
+
try:
|
|
186
|
+
page = await client.list_local_chat_sessions(limit=limit, cursor=cursor)
|
|
187
|
+
except ValueError as error:
|
|
188
|
+
raise HTTPException(status_code=400, detail=str(error)) from error
|
|
189
|
+
sessions: list[dict[str, Any]] = []
|
|
190
|
+
for summary in page.sessions:
|
|
191
|
+
if manager.live_turn(summary.session_uid) is None:
|
|
192
|
+
sessions.append(
|
|
193
|
+
_session_summary(
|
|
194
|
+
summary,
|
|
195
|
+
message_count=summary.message_count,
|
|
196
|
+
latest_message_preview=summary.latest_message_preview,
|
|
197
|
+
updated_at=summary.updated_at,
|
|
198
|
+
working=(summary.working or manager.local_session_running(summary.session_uid)),
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
continue
|
|
202
|
+
projection = await _project_session(client, manager, config, summary.session_uid)
|
|
203
|
+
visible = [
|
|
204
|
+
*projection.messages,
|
|
205
|
+
*([projection.in_progress] if projection.in_progress is not None else []),
|
|
206
|
+
]
|
|
207
|
+
sessions.append(
|
|
208
|
+
_session_summary(
|
|
209
|
+
summary,
|
|
210
|
+
message_count=len(visible),
|
|
211
|
+
latest_message_preview=latest_message_preview(visible),
|
|
212
|
+
updated_at=max(summary.updated_at, projection.updated_at),
|
|
213
|
+
working=projection.status == "running",
|
|
214
|
+
)
|
|
215
|
+
)
|
|
216
|
+
return {"sessions": sessions, "nextCursor": page.next_cursor}
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@router.get("/chat-sessions/{session_uid}/history")
|
|
220
|
+
async def chat_session_history(
|
|
221
|
+
session_uid: str,
|
|
222
|
+
client: BackendDep,
|
|
223
|
+
manager: RuntimeManagerDep,
|
|
224
|
+
config: SettingsDep,
|
|
225
|
+
) -> dict[str, Any]:
|
|
226
|
+
_require_local(config)
|
|
227
|
+
canonical = config.local_session_uid(session_uid)
|
|
228
|
+
projection = await _project_session(client, manager, config, canonical)
|
|
229
|
+
identity = await workspace_agent_identity(config.workspace)
|
|
230
|
+
return {
|
|
231
|
+
"version": HISTORY_VERSION,
|
|
232
|
+
"session": {
|
|
233
|
+
"sessionId": canonical,
|
|
234
|
+
"threadId": canonical,
|
|
235
|
+
"agentName": identity.name or LOCAL_AGENT_NAME,
|
|
236
|
+
"agentUid": config.local_agent_uid,
|
|
237
|
+
"agentSessionUid": canonical,
|
|
238
|
+
"status": projection.status,
|
|
239
|
+
"startedAt": iso_timestamp(projection.summary.created_at),
|
|
240
|
+
"updatedAt": iso_timestamp(projection.updated_at),
|
|
241
|
+
"error": projection.error,
|
|
242
|
+
},
|
|
243
|
+
"messages": projection.messages,
|
|
244
|
+
"inProgressMessage": projection.in_progress,
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
@router.get("/agent")
|
|
249
|
+
async def local_agent(config: SettingsDep) -> dict[str, str | None]:
|
|
250
|
+
_require_local(config)
|
|
251
|
+
identity = await workspace_agent_identity(config.workspace)
|
|
252
|
+
return {
|
|
253
|
+
"name": identity.name,
|
|
254
|
+
"displayName": identity.name,
|
|
255
|
+
"description": identity.description,
|
|
256
|
+
}
|