ms-tau-sdk 1.2.8.dev13__tar.gz → 1.2.9.dev15__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.
Files changed (74) hide show
  1. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/CHANGELOG.md +21 -0
  2. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/PKG-INFO +11 -7
  3. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/README.md +10 -6
  4. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/packages/tau-board/src/ms_tau_board/proxy.py +24 -1
  5. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/packages/tau-board/src/ms_tau_board/static/app.js +250 -7
  6. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/packages/tau-board/src/ms_tau_board/static/board.css +7 -0
  7. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/packages/tau-board/src/ms_tau_board/static/index.html +40 -3
  8. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/pyproject.toml +1 -1
  9. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/agent_skills/tau_local_development/SKILL.md +21 -0
  10. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/agent_skills/tau_project_customization/SKILL.md +36 -2
  11. ms_tau_sdk-1.2.9.dev15/src/ms_tau_sdk/api/inspection.py +122 -0
  12. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/api/models.py +11 -0
  13. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/app.py +2 -1
  14. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/errors.py +15 -0
  15. ms_tau_sdk-1.2.9.dev15/src/ms_tau_sdk/resources/SYSTEM.md +11 -0
  16. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/runtime/extensions.py +32 -0
  17. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/runtime/manager.py +714 -17
  18. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/runtime/session.py +14 -0
  19. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/settings.py +5 -0
  20. ms_tau_sdk-1.2.8.dev13/src/ms_tau_sdk/resources/SYSTEM.md +0 -12
  21. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/.gitignore +0 -0
  22. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/packages/tau-board/src/ms_tau_board/__init__.py +0 -0
  23. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/packages/tau-board/src/ms_tau_board/app.py +0 -0
  24. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/packages/tau-board/src/ms_tau_board/cli.py +0 -0
  25. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/packages/tau-board/src/ms_tau_board/config.py +0 -0
  26. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/packages/tau-board/src/ms_tau_board/env_file.py +0 -0
  27. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/packages/tau-board/src/ms_tau_board/logs.py +0 -0
  28. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/packages/tau-board/src/ms_tau_board/state.py +0 -0
  29. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/packages/tau-board/src/ms_tau_board/static/BULMA-LICENSE.txt +0 -0
  30. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/packages/tau-board/src/ms_tau_board/static/bulma.min.css +0 -0
  31. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/__init__.py +0 -0
  32. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/agent_skills/tau_a2a_runtime_adapter/SKILL.md +0 -0
  33. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/agent_skills/tau_repository_integration/SKILL.md +0 -0
  34. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/api/__init__.py +0 -0
  35. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/api/a2a.py +0 -0
  36. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/api/chat.py +0 -0
  37. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/api/dependencies.py +0 -0
  38. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/api/health.py +0 -0
  39. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/api/sessions.py +0 -0
  40. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/application.py +0 -0
  41. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/backend/__init__.py +0 -0
  42. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/backend/auth.py +0 -0
  43. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/backend/client.py +0 -0
  44. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/backend/local.py +0 -0
  45. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/backend/mcp.py +0 -0
  46. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/backend/models.py +0 -0
  47. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/backend/routes.py +0 -0
  48. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/cli.py +0 -0
  49. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/logging.py +0 -0
  50. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/protocols/__init__.py +0 -0
  51. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/protocols/a2a_message.py +0 -0
  52. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/protocols/a2a_roles.py +0 -0
  53. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/protocols/assistant_ui.py +0 -0
  54. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/protocols/strict_json.py +0 -0
  55. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/providers/__init__.py +0 -0
  56. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/providers/definitions.py +0 -0
  57. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/providers/factory.py +0 -0
  58. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/resources/__init__.py +0 -0
  59. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/resources/loader.py +0 -0
  60. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/resources/prompts/review-code-repository.md +0 -0
  61. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/runtime/__init__.py +0 -0
  62. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/runtime/deployment_health.py +0 -0
  63. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/runtime/events.py +0 -0
  64. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/runtime/failures.py +0 -0
  65. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/runtime/observability.py +0 -0
  66. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/runtime/provenance.py +0 -0
  67. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/runtime/snapshots.py +0 -0
  68. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/runtime/task_context.py +0 -0
  69. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/sessions/__init__.py +0 -0
  70. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/sessions/storage.py +0 -0
  71. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/skills.py +0 -0
  72. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/tools/__init__.py +0 -0
  73. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/tools/mainsequence_mcp.py +0 -0
  74. {ms_tau_sdk-1.2.8.dev13 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/tools/task_control.py +0 -0
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2.9 — 2026-09-25
4
+
5
+ - Added Tau Board's Agent view for readable effective Agent Card inspection, source-grouped tool
6
+ catalog inspection, project extension diagnostics, and bounded registered-entry source viewing.
7
+ - Added an explicit local project-tool workbench with schema-generated inputs, authoritative
8
+ validation, short-lived single-use confirmation, exact loaded-tool execution, streamed bounded
9
+ results, timeout and cancellation. It does not call the model or write conversation/Task history;
10
+ project code still has its normal real side effects.
11
+ - Added creation and completion/status timing to the A2A Task view. Completion uses the standard
12
+ A2A `task.status.timestamp`; creation is joined from Tau's existing local SQLite Task state, so
13
+ the wire contract remains standard.
14
+
15
+ ## 1.2.8 — 2026-09-24
16
+
17
+ - Added independent `TAU_EXCLUDE_BASE_TOOLS` and `TAU_EXCLUDE_MAINSEQUENCE_MCP` process settings.
18
+ Deployments can expose only project extension tools plus the two required A2A Task controls.
19
+ The runtime skips MCP connection and resource guidance when excluded, rejects project overrides
20
+ of the Task controls, and checks the effective catalog on load and after extension reload.
21
+ - Documented the settings for managed workflow `env_vars` and local runtimes in ADR 0011, the
22
+ reference guides, and the version-matched `tau-project-customization` skill.
23
+
3
24
  ## 1.2.7 — 2026-09-21
4
25
 
5
26
  - Bundle the local Tau Board command, Python package, and UI assets in the `ms-tau-sdk` wheel.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ms-tau-sdk
3
- Version: 1.2.8.dev13
3
+ Version: 1.2.9.dev15
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
@@ -163,16 +163,20 @@ Tau Board ships in the same SDK wheel. The `tau-board` extra declares its tested
163
163
  bounds; plain SDK installations never start the board.
164
164
 
165
165
  Open `http://127.0.0.1:8788` to chat, send A2A Messages and Tasks, inspect local SQLite state,
166
- and read logs. The board serves packaged Bulma CSS with no frontend build or CDN. It can select
166
+ inspect the effective Agent Card and tool catalog, validate and explicitly run project extension
167
+ tools, and read logs. The A2A view combines the standard current-status timestamp with Tau's local
168
+ creation timestamp. Tool tests call the exact loaded tool without a model turn or history entry,
169
+ but execute real project code with its normal side effects. The board serves packaged Bulma CSS
170
+ with no frontend build or CDN. It can select
167
171
  another loopback Tau endpoint and local state directory in its Connect view. Its Settings tab shows
168
172
  the board process's safe environment values and edits named non-secret variables in a local `.env`
169
173
  file; Tau or the board must restart before saved startup values take effect.
170
174
 
171
175
  Platform discovery of the unregistered process, internal backend dispatch/caller-delivery hooks,
172
- push notifications, and `resume_caller` remain unavailable. Outbound A2A through Main Sequence MCP
173
- supports Messages and Tasks with polling under authenticated-user semantics. Local mode binds to
174
- `127.0.0.1` unless a host was explicitly configured. Main Sequence MCP remains live, so its tools
175
- can still read or mutate real platform resources.
176
+ push notifications, and `resume_caller` remain unavailable. When Main Sequence MCP is enabled,
177
+ outbound A2A supports Messages and Tasks with polling under authenticated-user semantics, and MCP
178
+ tools can read or mutate real platform resources. Local mode binds to `127.0.0.1` unless a host was
179
+ explicitly configured.
176
180
 
177
181
  ## Workspace-owned Tau behavior
178
182
 
@@ -201,7 +205,7 @@ are not bundled into the SDK. Main Sequence transport and protocol behavior rema
201
205
  - durable Tau sessions, leases, restore, persistence, cancellation, eviction, and shutdown
202
206
  - local Tau execution without backend AgentSession pre-creation
203
207
  - chat, SSE, A2A, health, and readiness transports
204
- - Main Sequence MCP and protocol-required task controls
208
+ - optional Main Sequence MCP and always-present protocol-required task controls
205
209
  - packaged defaults that participate in Tau's normal workspace configuration
206
210
  - explicit, version-matched development skills for repository integration, local debugging,
207
211
  project customization, and TAU's A2A host adapter
@@ -132,16 +132,20 @@ Tau Board ships in the same SDK wheel. The `tau-board` extra declares its tested
132
132
  bounds; plain SDK installations never start the board.
133
133
 
134
134
  Open `http://127.0.0.1:8788` to chat, send A2A Messages and Tasks, inspect local SQLite state,
135
- and read logs. The board serves packaged Bulma CSS with no frontend build or CDN. It can select
135
+ inspect the effective Agent Card and tool catalog, validate and explicitly run project extension
136
+ tools, and read logs. The A2A view combines the standard current-status timestamp with Tau's local
137
+ creation timestamp. Tool tests call the exact loaded tool without a model turn or history entry,
138
+ but execute real project code with its normal side effects. The board serves packaged Bulma CSS
139
+ with no frontend build or CDN. It can select
136
140
  another loopback Tau endpoint and local state directory in its Connect view. Its Settings tab shows
137
141
  the board process's safe environment values and edits named non-secret variables in a local `.env`
138
142
  file; Tau or the board must restart before saved startup values take effect.
139
143
 
140
144
  Platform discovery of the unregistered process, internal backend dispatch/caller-delivery hooks,
141
- push notifications, and `resume_caller` remain unavailable. Outbound A2A through Main Sequence MCP
142
- supports Messages and Tasks with polling under authenticated-user semantics. Local mode binds to
143
- `127.0.0.1` unless a host was explicitly configured. Main Sequence MCP remains live, so its tools
144
- can still read or mutate real platform resources.
145
+ push notifications, and `resume_caller` remain unavailable. When Main Sequence MCP is enabled,
146
+ outbound A2A supports Messages and Tasks with polling under authenticated-user semantics, and MCP
147
+ tools can read or mutate real platform resources. Local mode binds to `127.0.0.1` unless a host was
148
+ explicitly configured.
145
149
 
146
150
  ## Workspace-owned Tau behavior
147
151
 
@@ -170,7 +174,7 @@ are not bundled into the SDK. Main Sequence transport and protocol behavior rema
170
174
  - durable Tau sessions, leases, restore, persistence, cancellation, eviction, and shutdown
171
175
  - local Tau execution without backend AgentSession pre-creation
172
176
  - chat, SSE, A2A, health, and readiness transports
173
- - Main Sequence MCP and protocol-required task controls
177
+ - optional Main Sequence MCP and always-present protocol-required task controls
174
178
  - packaged defaults that participate in Tau's normal workspace configuration
175
179
  - explicit, version-matched development skills for repository integration, local debugging,
176
180
  project customization, and TAU's A2A host adapter
@@ -27,6 +27,24 @@ ALLOWED: tuple[tuple[str, re.Pattern[str]], ...] = (
27
27
  ("GET", re.compile(r"/api/a2a/v1/tasks/[A-Za-z0-9._-]+:subscribe")),
28
28
  ("POST", re.compile(r"/api/a2a/v1/tasks/[A-Za-z0-9._-]+:cancel")),
29
29
  ("GET", re.compile(r"/api/a2a/v1/extendedAgentCard")),
30
+ (
31
+ "GET",
32
+ re.compile(r"/api/local/v1/sessions/[A-Za-z0-9._-]+/agent-inspection"),
33
+ ),
34
+ (
35
+ "GET",
36
+ re.compile(r"/api/local/v1/sessions/[A-Za-z0-9._-]+/extension-sources/[a-f0-9]{24}"),
37
+ ),
38
+ (
39
+ "POST",
40
+ re.compile(
41
+ r"/api/local/v1/sessions/[A-Za-z0-9._-]+/tools/[A-Za-z0-9._-]+:(?:validate|test)"
42
+ ),
43
+ ),
44
+ (
45
+ "POST",
46
+ re.compile(r"/api/local/v1/sessions/[A-Za-z0-9._-]+/tool-tests/[A-Za-z0-9-]+:cancel"),
47
+ ),
30
48
  )
31
49
 
32
50
 
@@ -82,7 +100,12 @@ async def forward(
82
100
  raise TauUnavailable("Tau endpoint redirected a board request")
83
101
  response_headers = {
84
102
  name: upstream.headers[name]
85
- for name in ("x-agent-session-uid", "cache-control", "x-vercel-ai-ui-message-stream")
103
+ for name in (
104
+ "x-agent-session-uid",
105
+ "x-tau-tool-test-uid",
106
+ "cache-control",
107
+ "x-vercel-ai-ui-message-stream",
108
+ )
86
109
  if name in upstream.headers
87
110
  }
88
111
  media_type = upstream.headers.get("content-type", "application/json").split(";", 1)[0]
@@ -1,5 +1,5 @@
1
1
  const $ = (id) => document.getElementById(id);
2
- const views = ["connect", "chat", "a2a", "tasks", "state", "logs", "settings"];
2
+ const views = ["connect", "agent", "chat", "a2a", "tasks", "state", "logs", "settings"];
3
3
  const tables = ["sessions", "entries", "a2a_tasks", "a2a_task_messages", "a2a_task_outputs", "a2a_task_attempts", "a2a_task_events", "snapshots"];
4
4
  let config = { profiles: [], selectedProfile: "" };
5
5
  let activeView = "connect";
@@ -20,6 +20,12 @@ let logOffset = 0;
20
20
  let logRecords = [];
21
21
  let logHasMore = false;
22
22
  let requestedLogSession = "";
23
+ let agentInspection = null;
24
+ let selectedTool = null;
25
+ let toolConfirmation = "";
26
+ let currentToolTest = "";
27
+ let toolTestAbort = null;
28
+ const a2aTaskState = new Map();
23
29
  const catalogs = new Map();
24
30
  const desiredSelection = { chat: null, a2a: null };
25
31
 
@@ -37,7 +43,11 @@ function notice(message, kind = "is-info") {
37
43
  }
38
44
  function dismissNotice() { $("notice").className = "notification is-hidden"; }
39
45
  function errorMessage(data, status) {
40
- if (data && typeof data === "object") return data.error || data.message || data.detail || `HTTP ${status}`;
46
+ if (data && typeof data === "object") {
47
+ const value = data.error || data.message || data.detail;
48
+ if (value && typeof value === "object") return value.message || JSON.stringify(value);
49
+ return value || `HTTP ${status}`;
50
+ }
41
51
  return String(data || `HTTP ${status}`);
42
52
  }
43
53
  async function json(url, options = {}) {
@@ -62,7 +72,7 @@ function populateSelect(select, values, selected) {
62
72
  select.value = selected;
63
73
  }
64
74
  function renderProfiles() {
65
- for (const id of ["connect-select", "chat-profile", "a2a-profile", "tasks-profile", "log-profile"]) {
75
+ for (const id of ["connect-select", "agent-profile", "chat-profile", "a2a-profile", "tasks-profile", "log-profile"]) {
66
76
  const select = $(id);
67
77
  const selected = id === "connect-select" ? config.selectedProfile : (select.value || config.selectedProfile);
68
78
  clear(select);
@@ -214,11 +224,191 @@ function showView(view) {
214
224
  document.querySelector(`[data-nav="${item}"]`).classList.toggle("is-active", item === activeView);
215
225
  }
216
226
  if (activeView === "a2a") refreshTasks().catch(report);
227
+ if (activeView === "agent") refreshAgentSessions().catch(report);
217
228
  if (activeView === "tasks") refreshTaskExplorer().catch(report);
218
229
  if (activeView === "state") refreshState().catch(report);
219
230
  if (activeView === "logs") refreshLogChoices().then(() => refreshLogs()).catch(report);
220
231
  if (activeView === "settings") refreshSettings().catch(report);
221
232
  }
233
+ function agentHeaders(extra = {}) { return { "X-Tau-Board-Profile": $("agent-profile").value, ...extra }; }
234
+ function agentSession() { return $("agent-session").value; }
235
+ function invalidateToolConfirmation() {
236
+ toolConfirmation = "";
237
+ $("tool-run").disabled = true;
238
+ clear($("tool-test-status"));
239
+ }
240
+ async function refreshAgentSessions() {
241
+ const previous = agentSession() || $("chat-session").value.trim() || $("a2a-context").value.trim();
242
+ const result = await json("/api/board/sessions", { headers: agentHeaders() });
243
+ const select = $("agent-session"); clear(select);
244
+ select.append(option("", "Select a loaded local session"));
245
+ for (const session of result.sessions || []) select.append(option(session.uid, `${session.uid} · ${session.model || "unknown model"}`));
246
+ select.value = previous;
247
+ if (!select.value && select.options.length === 2) select.selectedIndex = 1;
248
+ if (select.value) await inspectAgent();
249
+ }
250
+ function renderAgentCard(card) {
251
+ const box = $("agent-card"); clear(box);
252
+ if (!card) { box.append(node("p", "No Agent Card was returned.", "help")); $("agent-card-json").textContent = "null"; return; }
253
+ for (const [label, value] of [["Name", card.name], ["Description", card.description], ["Version", card.version], ["URL", card.url]]) detailLine(box, label, value);
254
+ const capabilities = card.capabilities || {};
255
+ detailLine(box, "Capabilities", Object.entries(capabilities).filter(([, value]) => typeof value === "boolean" && value).map(([name]) => name).join(", ") || "None declared");
256
+ detailLine(box, "Protocol extensions", (capabilities.extensions || []).map((item) => item.uri || "unnamed").join(", ") || "None");
257
+ detailLine(box, "Agent Card skills", (card.skills || []).map((item) => item.name || item.id || "unnamed").join(", ") || "None");
258
+ $("agent-card-json").textContent = JSON.stringify(card, null, 2);
259
+ }
260
+ function renderExtensions(items, diagnostics) {
261
+ const box = $("agent-extensions"); clear(box);
262
+ for (const item of items || []) {
263
+ const card = node("div", "", "extension-card");
264
+ card.append(node("strong", item.name), node("span", ` ${item.origin}`, "tag is-light ml-2"));
265
+ card.append(node("p", item.entryPath || "Source is not exposed", "mono"));
266
+ card.append(node("p", `Tools: ${(item.tools || []).join(", ") || "none"}`, "help")); box.append(card);
267
+ }
268
+ for (const item of diagnostics || []) {
269
+ const card = node("div", "", "extension-card has-text-danger");
270
+ card.textContent = `${item.severity} · ${item.name} · ${item.errorType}${item.path ? ` · ${item.path}` : ""}`; box.append(card);
271
+ }
272
+ if (!(items || []).length && !(diagnostics || []).length) box.append(node("p", "No project extensions or diagnostics.", "help"));
273
+ }
274
+ function renderTools(tools) {
275
+ const box = $("agent-tools"); clear(box);
276
+ const labels = { project_extension: "Project extensions", tau_coding: "Tau coding", mainsequence_mcp: "Main Sequence MCP", protocol: "A2A Task controls", user_extension: "User extensions", built_in_extension: "Tau extensions", explicit_extension: "Explicit extensions", unknown_extension: "Other extensions" };
277
+ const groups = new Map();
278
+ for (const tool of tools || []) {
279
+ if (!groups.has(tool.category)) groups.set(tool.category, []);
280
+ groups.get(tool.category).push(tool);
281
+ }
282
+ const order = ["project_extension", "tau_coding", "mainsequence_mcp", "protocol", ...groups.keys()];
283
+ for (const category of [...new Set(order)]) {
284
+ if (!groups.has(category)) continue;
285
+ box.append(node("p", labels[category] || category, "tool-group"));
286
+ for (const tool of groups.get(category)) {
287
+ const button = node("button", "", "button is-light"); button.type = "button";
288
+ button.append(node("strong", tool.label || tool.name), node("span", `\n${tool.name}`, "mono help"));
289
+ button.addEventListener("click", () => selectTool(tool, button)); box.append(button);
290
+ }
291
+ }
292
+ if (!(tools || []).length) box.append(node("p", "This session has no executable tools.", "help"));
293
+ }
294
+ function defaultFieldValue(schema) {
295
+ if (Object.hasOwn(schema, "default")) return schema.default;
296
+ if (Array.isArray(schema.examples) && schema.examples.length) return schema.examples[0];
297
+ return "";
298
+ }
299
+ function renderToolForm(schema) {
300
+ const form = $("tool-form"); clear(form);
301
+ const required = new Set(schema.required || []);
302
+ for (const [name, field] of Object.entries(schema.properties || {})) {
303
+ const wrapper = node("div", "", "field tool-field");
304
+ wrapper.append(node("label", `${name}${required.has(name) ? " *" : ""}`, "label"));
305
+ let input;
306
+ if (Array.isArray(field.enum)) {
307
+ const control = node("div", "", "select is-fullwidth"); input = node("select");
308
+ if (!required.has(name)) input.append(option("", "Not set"));
309
+ for (const value of field.enum) input.append(option(JSON.stringify(value), String(value)));
310
+ control.append(input); wrapper.append(control); input.dataset.encoding = "json";
311
+ } else if (field.type === "boolean") {
312
+ const control = node("div", "", "select is-fullwidth"); input = node("select");
313
+ if (!required.has(name)) input.append(option("", "Not set"));
314
+ input.append(option("true", "True"), option("false", "False")); control.append(input); wrapper.append(control); input.dataset.encoding = "json";
315
+ } else if (field.type === "object" || field.type === "array") {
316
+ input = node("textarea", "", "textarea mono"); input.rows = 3; input.dataset.encoding = "json"; wrapper.append(input);
317
+ } else {
318
+ input = node("input", "", "input"); input.type = field.type === "number" || field.type === "integer" ? "number" : "text";
319
+ if (field.type === "integer") input.step = "1"; input.dataset.encoding = field.type || "string"; wrapper.append(input);
320
+ }
321
+ input.dataset.toolField = name; input.dataset.required = required.has(name) ? "true" : "false";
322
+ const initial = defaultFieldValue(field);
323
+ if (initial !== "") input.value = input.dataset.encoding === "json" ? JSON.stringify(initial) : String(initial);
324
+ if (field.description) wrapper.append(node("p", field.description, "help"));
325
+ input.addEventListener("input", () => { invalidateToolConfirmation(); try { $("tool-arguments").value = JSON.stringify(formArguments(), null, 2); } catch (_) { /* raw editor remains available */ } });
326
+ form.append(wrapper);
327
+ }
328
+ if (!Object.keys(schema.properties || {}).length) form.append(node("p", "This tool has no declared fields. Use the JSON editor for additional properties.", "help mb-3"));
329
+ try { $("tool-arguments").value = JSON.stringify(formArguments(), null, 2); } catch (_) { $("tool-arguments").value = "{}"; }
330
+ }
331
+ function formArguments() {
332
+ const result = {};
333
+ for (const input of document.querySelectorAll("[data-tool-field]")) {
334
+ if (input.value === "" && input.dataset.required !== "true") continue;
335
+ let value = input.value;
336
+ if (input.dataset.encoding === "json") value = JSON.parse(value || "null");
337
+ else if (input.dataset.encoding === "number") value = Number(value);
338
+ else if (input.dataset.encoding === "integer") value = Number.parseInt(value, 10);
339
+ result[input.dataset.toolField] = value;
340
+ }
341
+ return result;
342
+ }
343
+ function selectTool(tool, button) {
344
+ selectedTool = tool; invalidateToolConfirmation(); currentToolTest = "";
345
+ for (const item of $("agent-tools").querySelectorAll("button")) item.classList.toggle("is-selected", item === button);
346
+ $("tool-title").textContent = tool.label || tool.name;
347
+ $("tool-description").textContent = tool.description || "No description.";
348
+ const meta = $("tool-meta"); clear(meta);
349
+ for (const value of [tool.name, tool.category, tool.executionMode, tool.extension]) if (value) meta.append(node("span", value, "tag is-light"));
350
+ $("tool-schema").textContent = JSON.stringify(tool.parameters, null, 2);
351
+ $("tool-source").disabled = !tool.sourceUid; $("tool-source-output").classList.add("is-hidden");
352
+ renderToolForm(tool.parameters || { type: "object", properties: {} });
353
+ $("tool-validate").disabled = !tool.testable;
354
+ $("tool-confirm").checked = false; $("tool-cancel").disabled = true;
355
+ $("tool-output").textContent = tool.testable ? "Fill the arguments, then validate before running." : (tool.testingReason || "This tool is inspect-only.");
356
+ }
357
+ async function inspectAgent() {
358
+ const session = agentSession();
359
+ if (!session) throw new Error("Select a local session to inspect.");
360
+ agentInspection = await json(`/tau/api/local/v1/sessions/${encodeURIComponent(session)}/agent-inspection`, { headers: agentHeaders() });
361
+ if (!agentInspection.available) {
362
+ clear($("agent-tools")); clear($("agent-card")); clear($("agent-extensions"));
363
+ $("agent-overview").textContent = "Session is not loaded. Send a Chat or A2A message, then inspect again."; return;
364
+ }
365
+ const overview = $("agent-overview"); clear(overview);
366
+ for (const [label, value] of [["Agent", agentInspection.agentUid], ["Session", agentInspection.sessionUid], ["Provider", agentInspection.model?.provider], ["Model", agentInspection.model?.model], ["Catalog", agentInspection.catalogDigest], ["State", agentInspection.busy ? "Busy" : "Idle"]]) detailLine(overview, label, value);
367
+ renderAgentCard(agentInspection.agentCard); renderExtensions(agentInspection.extensions, agentInspection.diagnostics); renderTools(agentInspection.tools);
368
+ }
369
+ async function viewToolSource() {
370
+ if (!selectedTool?.sourceUid) return;
371
+ const result = await json(`/tau/api/local/v1/sessions/${encodeURIComponent(agentSession())}/extension-sources/${selectedTool.sourceUid}`, { headers: agentHeaders() });
372
+ const output = $("tool-source-output"); output.textContent = `${result.path}\n\n${result.content}`; output.classList.remove("is-hidden");
373
+ }
374
+ function rawToolArguments() {
375
+ const value = JSON.parse($("tool-arguments").value || "{}");
376
+ if (!value || Array.isArray(value) || typeof value !== "object") throw new Error("Tool arguments must be a JSON object.");
377
+ return value;
378
+ }
379
+ async function validateTool() {
380
+ if (!selectedTool?.testable) throw new Error("Select a testable project tool.");
381
+ const body = { catalogDigest: agentInspection.catalogDigest, arguments: rawToolArguments() };
382
+ const result = await json(`/tau/api/local/v1/sessions/${encodeURIComponent(agentSession())}/tools/${encodeURIComponent(selectedTool.name)}:validate`, { method: "POST", headers: agentHeaders({ "Content-Type": "application/json" }), body: JSON.stringify(body) });
383
+ toolConfirmation = result.confirmation; $("tool-arguments").value = JSON.stringify(result.arguments, null, 2);
384
+ const status = $("tool-test-status"); clear(status); status.append(node("span", `Validated · confirmation expires in ${result.expiresInSeconds}s`, "tag is-success is-light"));
385
+ $("tool-run").disabled = !$("tool-confirm").checked;
386
+ }
387
+ async function runTool() {
388
+ if (!toolConfirmation || !$("tool-confirm").checked) throw new Error("Validate the arguments and confirm the side-effect warning first.");
389
+ const body = { catalogDigest: agentInspection.catalogDigest, arguments: rawToolArguments(), confirmation: toolConfirmation };
390
+ toolConfirmation = ""; $("tool-run").disabled = true; $("tool-validate").disabled = true; $("tool-output").textContent = "Starting tool…";
391
+ toolTestAbort = new AbortController();
392
+ try {
393
+ const response = await fetch(`/tau/api/local/v1/sessions/${encodeURIComponent(agentSession())}/tools/${encodeURIComponent(selectedTool.name)}:test`, { method: "POST", headers: agentHeaders({ "Content-Type": "application/json" }), body: JSON.stringify(body), signal: toolTestAbort.signal });
394
+ if (!response.ok) throw new Error(errorMessage(await response.json().catch(() => ({})), response.status));
395
+ currentToolTest = response.headers.get("X-Tau-Tool-Test-Uid") || ""; $("tool-cancel").disabled = !currentToolTest;
396
+ await readSSE(response, (event) => {
397
+ if (event.type === "started") $("tool-output").textContent = `Running ${event.toolName}…`;
398
+ if (event.type === "update") $("tool-output").textContent += `\n\nUpdate:\n${JSON.stringify(event.result, null, 2)}`;
399
+ if (event.type === "completed") $("tool-output").textContent = `${event.truncated ? "TRUNCATED\n" : ""}${JSON.stringify(event.result, null, 2)}`;
400
+ if (event.type === "error") $("tool-output").textContent = `${event.errorType || event.error}: ${event.message}`;
401
+ if (event.type === "cancelled") $("tool-output").textContent += "\n\nCancelled.";
402
+ if (event.type === "finished") { const status = $("tool-test-status"); clear(status); status.append(node("span", event.outcome, `tag ${event.outcome === "completed" ? "is-success" : "is-warning"} is-light`)); }
403
+ });
404
+ } finally {
405
+ currentToolTest = ""; toolTestAbort = null; $("tool-cancel").disabled = true; $("tool-validate").disabled = !selectedTool?.testable; $("tool-confirm").checked = false;
406
+ }
407
+ }
408
+ async function cancelTool() {
409
+ if (!currentToolTest) return;
410
+ await json(`/tau/api/local/v1/sessions/${encodeURIComponent(agentSession())}/tool-tests/${encodeURIComponent(currentToolTest)}:cancel`, { method: "POST", headers: agentHeaders({ "Content-Type": "application/json" }), body: "{}" });
411
+ }
222
412
  function report(error) { notice(error.message || String(error), "is-danger"); }
223
413
  function chatLine(who, type = "agent") {
224
414
  const item = node("div", "", `chat-line ${type}`);
@@ -319,11 +509,37 @@ function taskText(task) {
319
509
  const parts = (task.artifacts || []).flatMap((artifact) => artifact.parts || []);
320
510
  return parts.map((part) => part.text ?? (part.data ? JSON.stringify(part.data, null, 2) : "")).filter(Boolean).join("\n");
321
511
  }
512
+ const terminalTaskStates = new Set([
513
+ "TASK_STATE_COMPLETED", "TASK_STATE_FAILED", "TASK_STATE_CANCELED", "TASK_STATE_REJECTED",
514
+ ]);
515
+ function taskTiming(task) {
516
+ const local = a2aTaskState.get(task.id) || {};
517
+ const statusTime = task.status?.timestamp || local.status_timestamp || "";
518
+ const state = task.status?.state || "TASK_STATE_UNKNOWN";
519
+ return {
520
+ created: local.created_at || "",
521
+ statusTime,
522
+ terminal: terminalTaskStates.has(state),
523
+ completed: state === "TASK_STATE_COMPLETED",
524
+ };
525
+ }
526
+ function displayTime(value) {
527
+ if (!value) return "—";
528
+ const parsed = new Date(value);
529
+ return Number.isNaN(parsed.valueOf()) ? value : parsed.toLocaleString();
530
+ }
531
+ function timedCell(value, label = "") {
532
+ const cell = node("td", label ? `${label}: ${displayTime(value)}` : displayTime(value));
533
+ if (value) { cell.classList.add("mono"); cell.title = value; }
534
+ return cell;
535
+ }
322
536
  function displayTask(task) {
323
537
  if (!task) return;
324
538
  $("a2a-task-id").value = task.id || "";
325
539
  $("a2a-context").value = task.contextId || "";
326
- $("task-output").textContent = `${task.id} · ${task.status?.state || "unknown"}\n\n${taskText(task)}`;
540
+ const timing = taskTiming(task);
541
+ const statusLabel = timing.completed ? "Completed" : (timing.terminal ? "Terminal status" : "Status recorded");
542
+ $("task-output").textContent = `${task.id} · ${task.status?.state || "unknown"}\nCreated: ${displayTime(timing.created)}\n${statusLabel}: ${displayTime(timing.statusTime)}\n\n${taskText(task)}`;
327
543
  }
328
544
  function handleA2AFrame(frame) {
329
545
  const value = frame.result || frame;
@@ -392,11 +608,26 @@ async function cancelTask(id) {
392
608
  async function refreshTasks() {
393
609
  const contextId = $("a2a-context").value.trim();
394
610
  const url = "/tau/api/a2a/v1/tasks" + (contextId ? `?contextId=${encodeURIComponent(contextId)}` : "");
395
- const value = await json(url, { headers: tauHeaders("a2a") });
611
+ const headers = tauHeaders("a2a");
612
+ const localUrl = "/api/board/tasks" + (contextId ? `?sessionUid=${encodeURIComponent(contextId)}` : "");
613
+ const [value, localValue] = await Promise.all([
614
+ json(url, { headers }),
615
+ json(localUrl, { headers }),
616
+ ]);
617
+ a2aTaskState.clear();
618
+ for (const task of localValue.tasks || []) a2aTaskState.set(task.task_id, task);
396
619
  const body = $("task-list"); clear(body);
397
620
  for (const task of (value.tasks || []).slice(-100).reverse()) {
398
621
  const row = node("tr");
399
- row.append(node("td", task.id, "mono"), node("td", task.contextId || "", "mono"), node("td", task.status?.state || ""));
622
+ const timing = taskTiming(task);
623
+ const statusLabel = timing.completed ? "Completed" : (timing.terminal ? "Terminal" : "Status");
624
+ row.append(
625
+ node("td", task.id, "mono"),
626
+ node("td", task.contextId || "", "mono"),
627
+ node("td", task.status?.state || ""),
628
+ timedCell(timing.created),
629
+ timedCell(timing.statusTime, statusLabel),
630
+ );
400
631
  const actions = node("td");
401
632
  for (const [label, action] of [["Open", () => getTask(task.id)], ["Inspect", () => { $("tasks-profile").value = $("a2a-profile").value; selectedTaskId = task.id; showView("tasks"); }], ["Follow", () => subscribeTask(task.id)], ["Cancel", () => cancelTask(task.id)], ["Continue", () => {
402
633
  $("a2a-kind").value = "continue"; $("a2a-task-id").value = task.id; $("a2a-context").value = task.contextId || ""; $("a2a-input").focus();
@@ -407,7 +638,10 @@ async function refreshTasks() {
407
638
  }
408
639
  row.append(actions); body.append(row);
409
640
  }
410
- if (!(value.tasks || []).length) { const row = node("tr"); const cell = node("td", "No Tasks in this context yet."); cell.colSpan = 4; row.append(cell); body.append(row); }
641
+ if (!(value.tasks || []).length) { const row = node("tr"); const cell = node("td", "No Tasks in this context yet."); cell.colSpan = 6; row.append(cell); body.append(row); }
642
+ const currentId = $("a2a-task-id").value.trim();
643
+ const current = (value.tasks || []).find((task) => task.id === currentId);
644
+ if (current) displayTask(current);
411
645
  }
412
646
  async function refreshState() {
413
647
  const result = await json("/api/board/state");
@@ -674,6 +908,15 @@ async function start() {
674
908
  bind("save-profile", "click", saveProfile);
675
909
  bind("delete-profile", "click", deleteProfile);
676
910
  bind("check-connection", "click", checkConnection);
911
+ bind("agent-profile", "change", refreshAgentSessions);
912
+ bind("agent-session", "change", inspectAgent);
913
+ bind("agent-refresh", "click", inspectAgent);
914
+ bind("tool-source", "click", viewToolSource);
915
+ bind("tool-validate", "click", validateTool);
916
+ bind("tool-run", "click", runTool);
917
+ bind("tool-cancel", "click", cancelTool);
918
+ bind("tool-confirm", "change", () => { $("tool-run").disabled = !toolConfirmation || !$("tool-confirm").checked; });
919
+ $("tool-arguments").addEventListener("input", invalidateToolConfirmation);
677
920
  for (const kind of ["chat", "a2a"]) {
678
921
  bind(`${kind}-profile`, "change", () => loadCatalog(kind));
679
922
  for (const field of ["provider", "model", "thinking"]) bind(`${kind}-${field}`, "change", () => selectionChanged(kind, field));
@@ -32,6 +32,13 @@ td { max-width: 24rem; overflow-wrap: anywhere; font-size: .82rem; }
32
32
  .task-picker { max-height: 65vh; overflow: auto; }
33
33
  .task-picker button { display: block; width: 100%; text-align: left; white-space: normal; height: auto; margin: .35rem 0; overflow-wrap: anywhere; }
34
34
  .task-picker button.is-selected { border-color: var(--bulma-primary); background: var(--bulma-scheme-main-ter); color: var(--bulma-text); font-weight: 700; }
35
+ .tool-picker { max-height: 72vh; overflow: auto; }
36
+ .tool-group { margin: 1rem 0 .4rem; color: var(--bulma-text-weak); font-size: .7rem; font-weight: 800; text-transform: uppercase; letter-spacing: .06em; }
37
+ .tool-picker button { display: block; width: 100%; height: auto; margin-bottom: .4rem; padding: .65rem; text-align: left; white-space: normal; overflow-wrap: anywhere; }
38
+ .tool-picker button.is-selected { border-color: var(--bulma-primary); background: var(--bulma-primary-soft); }
39
+ .source-panel { max-height: 30rem; overflow: auto; margin-top: .75rem; padding: .75rem; border: 1px solid var(--bulma-border); border-radius: .4rem; background: var(--bulma-scheme-main-bis); white-space: pre-wrap; overflow-wrap: anywhere; font-size: .78rem; }
40
+ .tool-field { margin-bottom: .8rem; }
41
+ .extension-card { border-bottom: 1px solid var(--bulma-border-weak); padding: .55rem 0; font-size: .82rem; overflow-wrap: anywhere; }
35
42
  #tasks-detail { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 1rem; }
36
43
  .settings-table td { vertical-align: middle; }
37
44
  .settings-table td:first-child { min-width: 15rem; }
@@ -5,8 +5,8 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>Tau Board</title>
7
7
  <link rel="stylesheet" href="/assets/bulma.min.css">
8
- <link rel="stylesheet" href="/assets/board.css?v=2">
9
- <script type="module" src="/assets/app.js?v=2"></script>
8
+ <link rel="stylesheet" href="/assets/board.css?v=3">
9
+ <script type="module" src="/assets/app.js?v=3"></script>
10
10
  </head>
11
11
  <body>
12
12
  <header class="board-header">
@@ -22,6 +22,7 @@
22
22
  <nav class="tabs is-boxed" aria-label="Board views">
23
23
  <ul>
24
24
  <li class="is-active" data-nav="connect"><a href="#connect">Connect</a></li>
25
+ <li data-nav="agent"><a href="#agent">Agent</a></li>
25
26
  <li data-nav="chat"><a href="#chat">Chat</a></li>
26
27
  <li data-nav="a2a"><a href="#a2a">A2A</a></li>
27
28
  <li data-nav="tasks"><a href="#tasks">Tasks</a></li>
@@ -59,6 +60,42 @@
59
60
  </div>
60
61
  </section>
61
62
 
63
+ <section id="view-agent" class="board-view is-hidden">
64
+ <div class="columns is-variable is-4">
65
+ <div class="column is-one-quarter"><div class="box">
66
+ <h2 class="title is-5">Loaded agent</h2>
67
+ <div class="field"><label class="label" for="agent-profile">Endpoint profile</label><div class="select is-fullwidth"><select id="agent-profile"></select></div></div>
68
+ <div class="field"><label class="label" for="agent-session">Local session</label><div class="select is-fullwidth"><select id="agent-session"><option value="">No session selected</option></select></div><p class="help">Inspection never loads a session. Start or resume it through Chat or A2A first.</p></div>
69
+ <button class="button is-primary is-fullwidth" id="agent-refresh">Inspect agent</button>
70
+ <div id="agent-overview" class="detail-list mt-4">Select an already loaded session.</div>
71
+ <details class="mt-4"><summary class="has-text-weight-semibold">Effective Agent Card</summary><div id="agent-card" class="detail-list mt-3"></div><pre id="agent-card-json" class="source-panel"></pre></details>
72
+ <details class="mt-4"><summary class="has-text-weight-semibold">Extensions and diagnostics</summary><div id="agent-extensions" class="mt-3"></div></details>
73
+ </div></div>
74
+ <div class="column is-one-quarter"><div class="box">
75
+ <h2 class="title is-5">Effective tools</h2>
76
+ <p class="help mb-3">The exact catalog exposed to the selected session, grouped by runtime ownership.</p>
77
+ <div id="agent-tools" class="tool-picker"></div>
78
+ </div></div>
79
+ <div class="column"><div class="box">
80
+ <h2 id="tool-title" class="title is-5">Tool details</h2>
81
+ <div id="tool-meta" class="tags"></div>
82
+ <p id="tool-description" class="mb-4">Select a tool to inspect its contract.</p>
83
+ <details open><summary class="has-text-weight-semibold">Argument schema</summary><pre id="tool-schema" class="source-panel"></pre></details>
84
+ <div class="buttons mt-3"><button class="button is-small" id="tool-source" disabled>View project source</button></div>
85
+ <pre id="tool-source-output" class="source-panel is-hidden"></pre>
86
+ <hr>
87
+ <h3 class="title is-6">Project tool workbench</h3>
88
+ <div class="notification is-warning is-light">This invokes real project Python code with Tau's filesystem, network, environment, and credential access. It is not a sandbox or rollback.</div>
89
+ <div id="tool-form"></div>
90
+ <div class="field"><label class="label" for="tool-arguments">Canonical JSON arguments</label><textarea class="textarea mono" id="tool-arguments" rows="8" spellcheck="false">{}</textarea></div>
91
+ <div class="buttons"><button class="button" id="tool-validate" disabled>Validate</button><button class="button is-danger" id="tool-run" disabled>Run tool</button><button class="button is-warning is-light" id="tool-cancel" disabled>Cancel</button></div>
92
+ <label class="checkbox mb-4"><input type="checkbox" id="tool-confirm"> I understand this executes real code and may cause side effects.</label>
93
+ <div id="tool-test-status" class="tags"></div>
94
+ <pre id="tool-output" class="output-panel">No tool test has run.</pre>
95
+ </div></div>
96
+ </div>
97
+ </section>
98
+
62
99
  <section id="view-chat" class="board-view is-hidden">
63
100
  <div class="columns is-variable is-5">
64
101
  <div class="column is-one-third"><div class="box">
@@ -93,7 +130,7 @@
93
130
  </div></div><div class="column"><div class="box">
94
131
  <div class="level"><div class="level-left"><h2 class="title is-5">Tasks</h2></div><div class="level-right"><button class="button is-small" id="refresh-tasks">Refresh</button></div></div>
95
132
  <div id="task-output" class="output-panel" aria-live="polite">Choose an action to begin.</div>
96
- <div class="table-container"><table class="table is-fullwidth is-striped is-hoverable"><thead><tr><th>Task</th><th>Context</th><th>Status</th><th></th></tr></thead><tbody id="task-list"></tbody></table></div>
133
+ <div class="table-container"><table class="table is-fullwidth is-striped is-hoverable"><thead><tr><th>Task</th><th>Context</th><th>Status</th><th>Created</th><th>Completed / status time</th><th></th></tr></thead><tbody id="task-list"></tbody></table></div>
97
134
  </div></div></div>
98
135
  </section>
99
136
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ms-tau-sdk"
7
- version = "1.2.8.dev13"
7
+ version = "1.2.9.dev15"
8
8
  description = "Workspace-bound Tau application primitives for Main Sequence projects"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -102,6 +102,27 @@ identity:
102
102
  These are the only A2A-adjacent capability boundaries; public local Message and Task requests must not return
103
103
  `local_mode_capability_unsupported`.
104
104
 
105
+ Each standard A2A Task carries the time its current state was recorded in
106
+ `task.status.timestamp`. For `TASK_STATE_COMPLETED`, that is the completion time. The A2A Task
107
+ object has no standard top-level creation timestamp; Tau persists `created_at` in local SQLite,
108
+ and Tau Board joins that local row when it displays the A2A Task list. Do not invent extra A2A
109
+ wire fields for Board presentation.
110
+
111
+ ## Inspect and test with Tau Board
112
+
113
+ Install `ms-tau-sdk[tau-board]`, run `tau-board` as a separate process, and connect it to the
114
+ loopback local-mode Tau endpoint. The Agent tab requires an already loaded session: first send a
115
+ Chat or A2A request, then select that session. It shows the effective Agent Card, loaded tools by
116
+ source, project extension diagnostics, and registered project extension entry source.
117
+
118
+ Only project-extension tools with an object JSON Schema are runnable in the workbench. Select the
119
+ tool, fill its generated form or raw JSON, validate the exact canonical arguments, acknowledge
120
+ the side-effect warning, then run it. Validation never calls the tool. Run invokes the exact
121
+ loaded tool without a model call and without adding chat entries, A2A Tasks, or artifacts. The
122
+ tool still has the Tau process's real filesystem, network, environment, and credential access;
123
+ there is no automatic rollback. A busy session, stale catalog, expired confirmation, non-project
124
+ tool, oversized output, timeout, or cancellation must fail explicitly.
125
+
105
126
  ## Outbound A2A through Main Sequence MCP
106
127
 
107
128
  Keep the projected `a2a.send_message` tool available. A local agent may send a Message or create a