ms-tau-sdk 1.2.8.dev14__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 (73) hide show
  1. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/CHANGELOG.md +12 -0
  2. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/PKG-INFO +6 -2
  3. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/README.md +5 -1
  4. {ms_tau_sdk-1.2.8.dev14 → 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.dev14 → 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.dev14 → 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.dev14 → 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.dev14 → ms_tau_sdk-1.2.9.dev15}/pyproject.toml +1 -1
  9. {ms_tau_sdk-1.2.8.dev14 → 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.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/agent_skills/tau_project_customization/SKILL.md +7 -0
  11. ms_tau_sdk-1.2.9.dev15/src/ms_tau_sdk/api/inspection.py +122 -0
  12. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/api/models.py +11 -0
  13. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/app.py +2 -1
  14. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/errors.py +15 -0
  15. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/runtime/manager.py +665 -3
  16. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/.gitignore +0 -0
  17. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/packages/tau-board/src/ms_tau_board/__init__.py +0 -0
  18. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/packages/tau-board/src/ms_tau_board/app.py +0 -0
  19. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/packages/tau-board/src/ms_tau_board/cli.py +0 -0
  20. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/packages/tau-board/src/ms_tau_board/config.py +0 -0
  21. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/packages/tau-board/src/ms_tau_board/env_file.py +0 -0
  22. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/packages/tau-board/src/ms_tau_board/logs.py +0 -0
  23. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/packages/tau-board/src/ms_tau_board/state.py +0 -0
  24. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/packages/tau-board/src/ms_tau_board/static/BULMA-LICENSE.txt +0 -0
  25. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/packages/tau-board/src/ms_tau_board/static/bulma.min.css +0 -0
  26. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/__init__.py +0 -0
  27. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/agent_skills/tau_a2a_runtime_adapter/SKILL.md +0 -0
  28. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/agent_skills/tau_repository_integration/SKILL.md +0 -0
  29. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/api/__init__.py +0 -0
  30. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/api/a2a.py +0 -0
  31. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/api/chat.py +0 -0
  32. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/api/dependencies.py +0 -0
  33. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/api/health.py +0 -0
  34. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/api/sessions.py +0 -0
  35. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/application.py +0 -0
  36. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/backend/__init__.py +0 -0
  37. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/backend/auth.py +0 -0
  38. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/backend/client.py +0 -0
  39. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/backend/local.py +0 -0
  40. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/backend/mcp.py +0 -0
  41. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/backend/models.py +0 -0
  42. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/backend/routes.py +0 -0
  43. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/cli.py +0 -0
  44. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/logging.py +0 -0
  45. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/protocols/__init__.py +0 -0
  46. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/protocols/a2a_message.py +0 -0
  47. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/protocols/a2a_roles.py +0 -0
  48. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/protocols/assistant_ui.py +0 -0
  49. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/protocols/strict_json.py +0 -0
  50. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/providers/__init__.py +0 -0
  51. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/providers/definitions.py +0 -0
  52. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/providers/factory.py +0 -0
  53. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/resources/SYSTEM.md +0 -0
  54. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/resources/__init__.py +0 -0
  55. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/resources/loader.py +0 -0
  56. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/resources/prompts/review-code-repository.md +0 -0
  57. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/runtime/__init__.py +0 -0
  58. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/runtime/deployment_health.py +0 -0
  59. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/runtime/events.py +0 -0
  60. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/runtime/extensions.py +0 -0
  61. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/runtime/failures.py +0 -0
  62. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/runtime/observability.py +0 -0
  63. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/runtime/provenance.py +0 -0
  64. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/runtime/session.py +0 -0
  65. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/runtime/snapshots.py +0 -0
  66. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/runtime/task_context.py +0 -0
  67. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/sessions/__init__.py +0 -0
  68. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/sessions/storage.py +0 -0
  69. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/settings.py +0 -0
  70. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/skills.py +0 -0
  71. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/tools/__init__.py +0 -0
  72. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/tools/mainsequence_mcp.py +0 -0
  73. {ms_tau_sdk-1.2.8.dev14 → ms_tau_sdk-1.2.9.dev15}/src/ms_tau_sdk/tools/task_control.py +0 -0
@@ -1,5 +1,17 @@
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
+
3
15
  ## 1.2.8 — 2026-09-24
4
16
 
5
17
  - Added independent `TAU_EXCLUDE_BASE_TOOLS` and `TAU_EXCLUDE_MAINSEQUENCE_MCP` process settings.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: ms-tau-sdk
3
- Version: 1.2.8.dev14
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,7 +163,11 @@ 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.
@@ -132,7 +132,11 @@ 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.
@@ -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.dev14"
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
@@ -93,3 +93,10 @@ Verify each project-owned capability through its observable interface and focuse
93
93
  that documented tool names and schemas match the loaded catalog, lifecycle hooks clean up their
94
94
  resources, and health diagnostics report extension load errors without exposing prompt content or
95
95
  secrets.
96
+
97
+ For interactive local verification, start Tau in local mode, load a session through Chat or A2A,
98
+ then open Tau Board's Agent tab. Inspect the effective tool category, JSON Schema, extension entry
99
+ source, and diagnostics. For a project-owned tool, fill the generated form or raw JSON, validate
100
+ the canonical arguments, explicitly acknowledge the side-effect warning, and run it. This invokes
101
+ the exact loaded tool without a model turn or conversation/Task-history entry. It is not a sandbox:
102
+ filesystem, network, credential, and external-system side effects are the project's responsibility.