@ai-setting/roy-plugin-task-show 2.0.3 → 2.0.5

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.
@@ -58,6 +58,13 @@ export declare class OperationsCache {
58
58
  * Insertion order is preserved by `Map`, so the first keys we iterate
59
59
  * are the oldest. We drop only stale entries — fresh ones survive even
60
60
  * under heavy churn so a hot task id never loses its cache hit.
61
+ *
62
+ * v2.0.5 (Task #2700): the previous threshold `size <= maxEntries`
63
+ * leaked one extra entry per cycle (oscillation between `maxEntries`
64
+ * and `maxEntries + 1`). The fix: evict until `size < maxEntries`
65
+ * so the upcoming `entries.set()` never pushes the cache past the
66
+ * cap. We prefer stale entries; if all entries are fresh we drop the
67
+ * oldest one (Map insertion order).
61
68
  */
62
69
  private evictStale;
63
70
  private refresh;
@@ -1 +1 @@
1
- {"version":3,"file":"operations-cache.d.ts","sourceRoot":"","sources":["../src/operations-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAE3G,MAAM,WAAW,qBAAqB;IACpC,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;CACnD;AAWD,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,qBAAqB,GAAG,oBAAoB,CAAC;IACrD,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iCAAiC;IACjC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;IACpD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwB;IAC/C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,IAAI,EAAE,sBAAsB;IAqBxC;;;;OAIG;IACG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE;QAAE,UAAU,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,OAAO,CAAC,cAAc,CAAC;IA8CvF,sCAAsC;IACtC,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAIhC,yBAAyB;IACzB,KAAK,IAAI,IAAI;IAIb,qDAAqD;IACrD,IAAI,IAAI,MAAM;IAId;;;;;;OAMG;IACH,OAAO,CAAC,UAAU;YAuBJ,OAAO;CActB"}
1
+ {"version":3,"file":"operations-cache.d.ts","sourceRoot":"","sources":["../src/operations-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAE3G,MAAM,WAAW,qBAAqB;IACpC,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;CACnD;AAWD,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,qBAAqB,GAAG,oBAAoB,CAAC;IACrD,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iCAAiC;IACjC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;IACpD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAwB;IAC/C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,IAAI,EAAE,sBAAsB;IAqBxC;;;;OAIG;IACG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE;QAAE,UAAU,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,OAAO,CAAC,cAAc,CAAC;IA8CvF,sCAAsC;IACtC,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAIhC,yBAAyB;IACzB,KAAK,IAAI,IAAI;IAIb,qDAAqD;IACrD,IAAI,IAAI,MAAM;IAId;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,UAAU;YAwBJ,OAAO;CActB"}
@@ -104,29 +104,36 @@ export class OperationsCache {
104
104
  * Insertion order is preserved by `Map`, so the first keys we iterate
105
105
  * are the oldest. We drop only stale entries — fresh ones survive even
106
106
  * under heavy churn so a hot task id never loses its cache hit.
107
+ *
108
+ * v2.0.5 (Task #2700): the previous threshold `size <= maxEntries`
109
+ * leaked one extra entry per cycle (oscillation between `maxEntries`
110
+ * and `maxEntries + 1`). The fix: evict until `size < maxEntries`
111
+ * so the upcoming `entries.set()` never pushes the cache past the
112
+ * cap. We prefer stale entries; if all entries are fresh we drop the
113
+ * oldest one (Map insertion order).
107
114
  */
108
115
  evictStale(now) {
109
- if (this.entries.size <= this.maxEntries)
110
- return;
111
- let overflow = this.entries.size - this.maxEntries;
112
- for (const [key, entry] of this.entries) {
113
- if (overflow <= 0)
114
- break;
115
- if (now - entry.fetchedAtMs >= this.ttlMs) {
116
- this.entries.delete(key);
117
- overflow -= 1;
118
- }
119
- }
120
- // If we still overflow after purging every stale entry (cache is full
121
- // of fresh entries), drop the oldest fresh entries too so we never
122
- // exceed the cap.
123
- if (this.entries.size > this.maxEntries) {
124
- overflow = this.entries.size - this.maxEntries;
125
- for (const key of this.entries.keys()) {
126
- if (overflow <= 0)
116
+ while (this.entries.size >= this.maxEntries) {
117
+ // First pass: try to drop a stale entry (the oldest stale one wins).
118
+ let dropped = false;
119
+ for (const [key, entry] of this.entries) {
120
+ if (now - entry.fetchedAtMs >= this.ttlMs) {
121
+ this.entries.delete(key);
122
+ dropped = true;
127
123
  break;
128
- this.entries.delete(key);
129
- overflow -= 1;
124
+ }
125
+ }
126
+ if (dropped)
127
+ continue;
128
+ // All entries are fresh — drop the oldest one.
129
+ const firstKey = this.entries.keys().next().value;
130
+ if (firstKey !== undefined) {
131
+ this.entries.delete(firstKey);
132
+ }
133
+ else {
134
+ // Safety: empty map, nothing to drop. Shouldn't happen because
135
+ // the loop condition requires `size >= maxEntries >= 1`.
136
+ break;
130
137
  }
131
138
  }
132
139
  }
@@ -1 +1 @@
1
- {"version":3,"file":"operations-cache.js","sourceRoot":"","sources":["../src/operations-cache.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"operations-cache.js","sourceRoot":"","sources":["../src/operations-cache.ts"],"names":[],"mappings":"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"}
@@ -53,6 +53,12 @@ export declare class TasksTreeCache {
53
53
  /**
54
54
  * Sweep entries that are past their TTL once we exceed `maxEntries`.
55
55
  * See OperationsCache.evictStale for the same rationale.
56
+ *
57
+ * v2.0.5 (Task #2700): the previous threshold `size <= maxEntries`
58
+ * left the cache oscillating between `maxEntries` and `maxEntries + 1`
59
+ * (off-by-one — see OperationsCache for details). The fix: evict
60
+ * until `size < maxEntries` so the upcoming insert never pushes us
61
+ * past the cap.
56
62
  */
57
63
  private evictStale;
58
64
  private refresh;
@@ -1 +1 @@
1
- {"version":3,"file":"tasks-tree-cache.d.ts","sourceRoot":"","sources":["../src/tasks-tree-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EACV,iBAAiB,EACjB,eAAe,EACf,eAAe,EAChB,MAAM,6BAA6B,CAAC;AAErC,KAAK,QAAQ,GAAG,CAAC,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAQxE,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,QAAQ,GAAG,eAAe,CAAC;IACnC,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iCAAiC;IACjC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;IACpD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAW;IAClC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,IAAI,EAAE,qBAAqB;IAmBvC;;;;OAIG;IACG,GAAG,CACP,MAAM,EAAE,eAAe,EACvB,IAAI,GAAE;QAAE,UAAU,CAAC,EAAE,OAAO,CAAA;KAAO,GAClC,OAAO,CAAC,iBAAiB,CAAC;IA4C7B,wBAAwB;IACxB,KAAK,IAAI,IAAI;IAIb,qDAAqD;IACrD,IAAI,IAAI,MAAM;IAId;;;OAGG;IACH,OAAO,CAAC,UAAU;YAoBJ,OAAO;CActB"}
1
+ {"version":3,"file":"tasks-tree-cache.d.ts","sourceRoot":"","sources":["../src/tasks-tree-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EACV,iBAAiB,EACjB,eAAe,EACf,eAAe,EAChB,MAAM,6BAA6B,CAAC;AAErC,KAAK,QAAQ,GAAG,CAAC,MAAM,EAAE,eAAe,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAQxE,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,QAAQ,GAAG,eAAe,CAAC;IACnC,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iCAAiC;IACjC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;IACpD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAW;IAClC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,IAAI,EAAE,qBAAqB;IAmBvC;;;;OAIG;IACG,GAAG,CACP,MAAM,EAAE,eAAe,EACvB,IAAI,GAAE;QAAE,UAAU,CAAC,EAAE,OAAO,CAAA;KAAO,GAClC,OAAO,CAAC,iBAAiB,CAAC;IA4C7B,wBAAwB;IACxB,KAAK,IAAI,IAAI;IAIb,qDAAqD;IACrD,IAAI,IAAI,MAAM;IAId;;;;;;;;;OASG;IACH,OAAO,CAAC,UAAU;YAsBJ,OAAO;CActB"}
@@ -96,26 +96,33 @@ export class TasksTreeCache {
96
96
  /**
97
97
  * Sweep entries that are past their TTL once we exceed `maxEntries`.
98
98
  * See OperationsCache.evictStale for the same rationale.
99
+ *
100
+ * v2.0.5 (Task #2700): the previous threshold `size <= maxEntries`
101
+ * left the cache oscillating between `maxEntries` and `maxEntries + 1`
102
+ * (off-by-one — see OperationsCache for details). The fix: evict
103
+ * until `size < maxEntries` so the upcoming insert never pushes us
104
+ * past the cap.
99
105
  */
100
106
  evictStale(now) {
101
- if (this.entries.size <= this.maxEntries)
102
- return;
103
- let overflow = this.entries.size - this.maxEntries;
104
- for (const [key, entry] of this.entries) {
105
- if (overflow <= 0)
106
- break;
107
- if (entry.fetchedAtMs > 0 && now - entry.fetchedAtMs >= this.ttlMs) {
108
- this.entries.delete(key);
109
- overflow -= 1;
110
- }
111
- }
112
- if (this.entries.size > this.maxEntries) {
113
- overflow = this.entries.size - this.maxEntries;
114
- for (const key of this.entries.keys()) {
115
- if (overflow <= 0)
107
+ while (this.entries.size >= this.maxEntries) {
108
+ // First pass: drop a stale entry (the oldest stale one wins).
109
+ let dropped = false;
110
+ for (const [key, entry] of this.entries) {
111
+ if (entry.fetchedAtMs > 0 && now - entry.fetchedAtMs >= this.ttlMs) {
112
+ this.entries.delete(key);
113
+ dropped = true;
116
114
  break;
117
- this.entries.delete(key);
118
- overflow -= 1;
115
+ }
116
+ }
117
+ if (dropped)
118
+ continue;
119
+ // All entries are fresh — drop the oldest one (Map insertion order).
120
+ const firstKey = this.entries.keys().next().value;
121
+ if (firstKey !== undefined) {
122
+ this.entries.delete(firstKey);
123
+ }
124
+ else {
125
+ break;
119
126
  }
120
127
  }
121
128
  }
@@ -1 +1 @@
1
- {"version":3,"file":"tasks-tree-cache.js","sourceRoot":"","sources":["../src/tasks-tree-cache.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tasks-tree-cache.js","sourceRoot":"","sources":["../src/tasks-tree-cache.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-setting/roy-plugin-task-show",
3
- "version": "2.0.3",
3
+ "version": "2.0.5",
4
4
  "description": "roy-agent plugin: visualize task solving process via tool call flow on a local web service",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/plugin.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@ai-setting/roy-plugin-task-show",
3
- "version": "2.0.3",
3
+ "version": "2.0.5",
4
4
  "type": "tool-plugin",
5
- "description": "v2.0.0: Major visual overhaul of the per-task detail page — VS Code-style tool-call browser powered by Monaco Editor + LCS-based diff + project file tree with mermaid-driven navigation. The detail page now renders a 2-column layout: a sticky left sidebar with the project's git-tracked file tree (chevron-toggle directories, `is-affected` highlight for files touched by the current task), and a main column with the existing Mermaid + stats + toolcalls table + a new dedicated `<section id=\"tool-call-detail-panel\">` that hosts the LCS diff body + Monaco container for the currently-selected tool call. The legacy inline `<details class=\"diff-panel\">` (naive split-and-filter, prone to mis-tagging context lines) is replaced by `<ol class=\"diff-body\">` with proper added/removed/context markers driven by a real LCS dynamic-programming table (`computeDiff()` in `src/tool-call-detail.ts`). Tool calls with a file path get a Monaco Editor placeholder (`<div class=\"monaco-editor\" data-file-path=\"...\" data-language=\"...\">`) which the client-side `public/tool-call-detail.js` lazy-loads from `cdn.jsdelivr.net/npm/monaco-editor@0.45.0` the first time the user clicks a tool row. The Mermaid `__toolClick(toolId)` callback now drives THREE things: (a) the existing row scroll + highlight (preserved from v1.x), (b) the dedicated detail panel re-renders with the matching call, (c) the file-tree sidebar highlights the corresponding file (when `data-file-path` is present). New `GET /api/task/:id/file-tree` endpoint serves the git-tracked file list (`{ files: string[] }`) with a 30-second in-memory TTL + 8-entry FIFO bound. New modules: `src/file-tree.ts` (pure data layer: `buildFileTree / extractAffectedPaths / findNodeByPath / collectAllPaths / gitLsFiles / parseLsFiles`), `src/tool-call-detail.ts` (SSR + LCS diff + HTML escaping), `public/file-tree.js` (vanilla JS hydrator with chevron toggle + keyboard navigation + scrollIntoView), `public/tool-call-detail.js` (Monaco AMD loader + `__toolClick` wrapper + file-content fetch). New tests: `test/file-tree.test.ts` (21 cases — empty/single/nested/dedup/sort/depth/parseLsFiles/gitLsFiles), `test/tool-call-detail.test.ts` (22 cases — LCS diff edges, HTML escape, path aliases, summary stats), `test/tool-call-detail-server-integration.test.ts` (7 cases — SSR HTML contracts + endpoint), `test/tool-call-detail-jsdom.test.ts` (7 cases — client-side hydrators). v1.2.0: CSS context & packaged release hotfix. The plugin's public assets (notably `public/style.css`) and runtime adapters now correctly resolve relative to the installed package directory even when consumed via the published npm tarball. The session-scoped `TaskSessionStore` now preserves the full host session context (parent-child task links, plugin-handle id, env scope) across render cycles — previously the session was collapsed to its `sessionId` on first load and never refreshed, so the home page lost the 「session ancestors」 chain and external tasks from outside the current session silently disappeared from the tree. Adds `src/task-metadata.ts` as the single source of truth for the public `Task` shape exposed by `/api/tasks` + `/api/tasks/:id`, including the v1.0.0+ `processDescription` field, and re-exports it through the CLI adapters (`cli-tasks-adapter.ts` + `cli-tasks-tree-adapter.ts`) so the home page tree + the per-task page render against the same metadata contract. Bundles 244-line regression test (`test/context-and-packed-release.test.ts`) that boots the plugin from the **npm-pack** directory (not the repo working tree), spawns `roy-agent tasks get <id> --json`, and asserts (a) `public/style.css` is present and ≥ 64 lines, (b) the `/api/events` SSE endpoint survives a reload, and (c) `task.session` survives a render cycle. v1.1.0: Full Server-Sent Events realtime subscription across 3 event classes (task.created / operation.updated / tool.called) on both the home page and the per-task /task/<id> page. The per-task pipeline now subscribes to /api/events and patches the DOM in place on operation.updated — no more 5s-poll delay before the user sees a new milestone. The 'Task lifecycle pipeline' header badge is replaced by a 5-state SSE-aware badge (stale / connecting / live / reconnecting / error) so the user can tell at a glance whether real-time updates are flowing, the connection dropped, or 3+ consecutive errors triggered the polling fallback. Legacy boolean `stale` cache-TTL pill and `tool.recorded` event name are preserved for back-compat with v0.9.x / v1.0.0 clients. v1.0.0: First stable release. Replaces the v0.9.x fixture-based verify scripts (which built fake TaskOperationsEnvelope and never invoked the real `roy-agent` CLI, masking regressions in the public-schema `processDescription` field) with a real-CLI scenario test + verify (`test/process-description-real-scenario.test.ts` + `scripts/verify-v100-real-scenario.ts`) that spawns `roy-agent tasks get <id> --operations --json` via `defaultRunner` and asserts the API response carries `processDescription` end-to-end. The 0.9.9 processDescription fix is preserved verbatim — this release only swaps the verify surface. Visualize the tool call chain of a task on a local web service with real-time SSE updates. v0.9.9: Task lifecycle pipeline on /task/<id> now exposes BOTH the milestone badge AND the 「过程描述」 column at a glance — the server-side `/api/tasks/:id/operations` endpoint exposes `processDescription` on every operation (no longer stripped from the public schema), the client-side `renderPipelineHtml` mirrors the server's `.op-desc-block` + `.op-proc-block` block layout so SSR ↔ CSR stay in sync, and a long-standing CSS right-side text-truncation bug in the pipeline timeline (long CJK titles overflowing the panel edge) is fixed via `min-width: 0` on `.op-row1` + `overflow-wrap: anywhere` on `.op-title`. v0.9.0: Session-scoped home page (only show tasks created after plugin load + their external ancestors), with per-row 「显示全部栏位」 toggle and lazy-loaded operations timeline; per-task Mermaid labels now correctly render CJK / mixed-Latin / emoji text (encoded as \\uXXXX before emission, decoded by the browser); detail page layout reordered to lifecycle → pipeline → stats → toolcalls → rawjson. v0.5.0+: page refreshes stream over GET /api/events (Server-Sent Events). Subscribes to tool:before.execute, tool:after.execute, task:before.create, task:after.create, task:after.complete (preferred, 2026-07-10+), and task:after.update (legacy fallback). v0.6.11: Mermaid re-rendering is delegated to a self-contained controller (public/mermaid-renderer.js) that prevents the SVG→raw-source regression on async updates and surfaces recoverable .mermaid-error states. v0.6.12: Task lifecycle pipeline (operations timeline) server now emits data-task-id on the pipeline section; client preserves it on swap, so the page actually fetches /api/tasks/<id>/operations and renders the 7-op timeline (previously silently bailed). v0.7.0: Home page redesigned as a hierarchical task tree (driven by `roy-agent tasks tree --json`); new /api/tasks/tree endpoint with status / priority / type / root-id filters, expand/collapse UI, search, and live 30s polling. v0.8.0: per-task page Mermaid area now renders the hierarchical 'Task lifecycle + tools' view — each operation record owns a subgraph that nests its tool calls, with click callbacks (`window.__toolClick`) that scroll-into-view + highlight + auto-expand the matching row in the tool-call table below. Operation record descriptions (`description` + `processDescription`) are now always rendered inline (no `<details>` collapse) so the user sees the lifecycle state at a glance; a fallback `<details>` kicks in only for descriptions longer than 600 chars. v0.8.1: hotfix for two pre-existing bugs in v0.8.0 (browser smoke test surfaced after merge). (a) Mermaid click directives were emitted as `click t1 __toolClick(1)` (missing `call` keyword) — Mermaid 10's parser rejects this with `got 'PS'`. Fixed to `click t1 call __toolClick(1)` (the v10 grammar requires `call` to invoke a callback with arguments). (b) `buildMermaidSource` lived inside the `attachTaskPageTimeline` IIFE but was also called from a listener in the `attachToolClickBridge` IIFE — sibling IIFEs cannot see each other's locals, so the listener threw `ReferenceError: buildMermaidSource is not defined` and the Mermaid diagram silently failed to re-render after `task-show:lifecycle-ops-loaded`. Fixed by hoisting the function (and its three helpers) to script top-level so both IIFEs can see it via the script-wide closure; the function is also exposed on `window.buildMermaidSource` for tests + tooling. v0.8.3: tree-display fix (Task #2426). The home page used to look like a flat list of root tasks because `autoExpandFirstLevels(..., 2)` only opened the first 2 levels — 30/47 roots were leaf nodes and the remaining 17 collapsed to one level so grandchildren were never visible. Default expand depth is now 3 (root + child + grandchild + great-grandchild are visible on first paint), the summary line now shows per-depth count pills (root / child / grandchild / great-grandchild / level-N), each `tree-row` carries a `data-depth` attribute so CSS can paint coloured left rails per level, and the duplicated 'Live tool-call sessions (legacy view)' panel that made the page look like both a flat table AND a tree is now hidden behind `#legacy-sessions[hidden]` (kept for future debug-toggle restoration). v0.8.10: bug-fix release (Task #2537 + Task #2534). (a) Heap-bounded plugin caches: OperationsCache and TasksTreeCache now enforce a hard maxEntries cap (default 256 / 64). Oldest stale entries are evicted before inserting a new one, so long-lived roy-agent sessions (BackgroundTaskManager + MemorySessionStore) no longer leak Map entries through the plugin's per-task caches — see Task #2537 for the heap-unbounded-state RED→GREEN repro. (b) Mermaid CJK font-family: server.ts renderTaskPage now configures mermaid.initialize({ themeVariables: { fontFamily: '\"PingFang SC\", \"Microsoft YaHei\", \"Noto Sans CJK SC\", \"Source Han Sans CN\", \"WenQuanYi Micro Hei\", sans-serif' } }) so Chinese node labels render correctly in browsers that have at least one of those fonts installed (see Task #2534).",
5
+ "description": "v2.0.0: Major visual overhaul of the per-task detail page \u2014 VS Code-style tool-call browser powered by Monaco Editor + LCS-based diff + project file tree with mermaid-driven navigation. The detail page now renders a 2-column layout: a sticky left sidebar with the project's git-tracked file tree (chevron-toggle directories, `is-affected` highlight for files touched by the current task), and a main column with the existing Mermaid + stats + toolcalls table + a new dedicated `<section id=\"tool-call-detail-panel\">` that hosts the LCS diff body + Monaco container for the currently-selected tool call. The legacy inline `<details class=\"diff-panel\">` (naive split-and-filter, prone to mis-tagging context lines) is replaced by `<ol class=\"diff-body\">` with proper added/removed/context markers driven by a real LCS dynamic-programming table (`computeDiff()` in `src/tool-call-detail.ts`). Tool calls with a file path get a Monaco Editor placeholder (`<div class=\"monaco-editor\" data-file-path=\"...\" data-language=\"...\">`) which the client-side `public/tool-call-detail.js` lazy-loads from `cdn.jsdelivr.net/npm/monaco-editor@0.45.0` the first time the user clicks a tool row. The Mermaid `__toolClick(toolId)` callback now drives THREE things: (a) the existing row scroll + highlight (preserved from v1.x), (b) the dedicated detail panel re-renders with the matching call, (c) the file-tree sidebar highlights the corresponding file (when `data-file-path` is present). New `GET /api/task/:id/file-tree` endpoint serves the git-tracked file list (`{ files: string[] }`) with a 30-second in-memory TTL + 8-entry FIFO bound. New modules: `src/file-tree.ts` (pure data layer: `buildFileTree / extractAffectedPaths / findNodeByPath / collectAllPaths / gitLsFiles / parseLsFiles`), `src/tool-call-detail.ts` (SSR + LCS diff + HTML escaping), `public/file-tree.js` (vanilla JS hydrator with chevron toggle + keyboard navigation + scrollIntoView), `public/tool-call-detail.js` (Monaco AMD loader + `__toolClick` wrapper + file-content fetch). New tests: `test/file-tree.test.ts` (21 cases \u2014 empty/single/nested/dedup/sort/depth/parseLsFiles/gitLsFiles), `test/tool-call-detail.test.ts` (22 cases \u2014 LCS diff edges, HTML escape, path aliases, summary stats), `test/tool-call-detail-server-integration.test.ts` (7 cases \u2014 SSR HTML contracts + endpoint), `test/tool-call-detail-jsdom.test.ts` (7 cases \u2014 client-side hydrators). v1.2.0: CSS context & packaged release hotfix. The plugin's public assets (notably `public/style.css`) and runtime adapters now correctly resolve relative to the installed package directory even when consumed via the published npm tarball. The session-scoped `TaskSessionStore` now preserves the full host session context (parent-child task links, plugin-handle id, env scope) across render cycles \u2014 previously the session was collapsed to its `sessionId` on first load and never refreshed, so the home page lost the \u300csession ancestors\u300d chain and external tasks from outside the current session silently disappeared from the tree. Adds `src/task-metadata.ts` as the single source of truth for the public `Task` shape exposed by `/api/tasks` + `/api/tasks/:id`, including the v1.0.0+ `processDescription` field, and re-exports it through the CLI adapters (`cli-tasks-adapter.ts` + `cli-tasks-tree-adapter.ts`) so the home page tree + the per-task page render against the same metadata contract. Bundles 244-line regression test (`test/context-and-packed-release.test.ts`) that boots the plugin from the **npm-pack** directory (not the repo working tree), spawns `roy-agent tasks get <id> --json`, and asserts (a) `public/style.css` is present and \u2265 64 lines, (b) the `/api/events` SSE endpoint survives a reload, and (c) `task.session` survives a render cycle. v1.1.0: Full Server-Sent Events realtime subscription across 3 event classes (task.created / operation.updated / tool.called) on both the home page and the per-task /task/<id> page. The per-task pipeline now subscribes to /api/events and patches the DOM in place on operation.updated \u2014 no more 5s-poll delay before the user sees a new milestone. The 'Task lifecycle pipeline' header badge is replaced by a 5-state SSE-aware badge (stale / connecting / live / reconnecting / error) so the user can tell at a glance whether real-time updates are flowing, the connection dropped, or 3+ consecutive errors triggered the polling fallback. Legacy boolean `stale` cache-TTL pill and `tool.recorded` event name are preserved for back-compat with v0.9.x / v1.0.0 clients. v1.0.0: First stable release. Replaces the v0.9.x fixture-based verify scripts (which built fake TaskOperationsEnvelope and never invoked the real `roy-agent` CLI, masking regressions in the public-schema `processDescription` field) with a real-CLI scenario test + verify (`test/process-description-real-scenario.test.ts` + `scripts/verify-v100-real-scenario.ts`) that spawns `roy-agent tasks get <id> --operations --json` via `defaultRunner` and asserts the API response carries `processDescription` end-to-end. The 0.9.9 processDescription fix is preserved verbatim \u2014 this release only swaps the verify surface. Visualize the tool call chain of a task on a local web service with real-time SSE updates. v0.9.9: Task lifecycle pipeline on /task/<id> now exposes BOTH the milestone badge AND the \u300c\u8fc7\u7a0b\u63cf\u8ff0\u300d column at a glance \u2014 the server-side `/api/tasks/:id/operations` endpoint exposes `processDescription` on every operation (no longer stripped from the public schema), the client-side `renderPipelineHtml` mirrors the server's `.op-desc-block` + `.op-proc-block` block layout so SSR \u2194 CSR stay in sync, and a long-standing CSS right-side text-truncation bug in the pipeline timeline (long CJK titles overflowing the panel edge) is fixed via `min-width: 0` on `.op-row1` + `overflow-wrap: anywhere` on `.op-title`. v0.9.0: Session-scoped home page (only show tasks created after plugin load + their external ancestors), with per-row \u300c\u663e\u793a\u5168\u90e8\u680f\u4f4d\u300d toggle and lazy-loaded operations timeline; per-task Mermaid labels now correctly render CJK / mixed-Latin / emoji text (encoded as \\uXXXX before emission, decoded by the browser); detail page layout reordered to lifecycle \u2192 pipeline \u2192 stats \u2192 toolcalls \u2192 rawjson. v0.5.0+: page refreshes stream over GET /api/events (Server-Sent Events). Subscribes to tool:before.execute, tool:after.execute, task:before.create, task:after.create, task:after.complete (preferred, 2026-07-10+), and task:after.update (legacy fallback). v0.6.11: Mermaid re-rendering is delegated to a self-contained controller (public/mermaid-renderer.js) that prevents the SVG\u2192raw-source regression on async updates and surfaces recoverable .mermaid-error states. v0.6.12: Task lifecycle pipeline (operations timeline) server now emits data-task-id on the pipeline section; client preserves it on swap, so the page actually fetches /api/tasks/<id>/operations and renders the 7-op timeline (previously silently bailed). v0.7.0: Home page redesigned as a hierarchical task tree (driven by `roy-agent tasks tree --json`); new /api/tasks/tree endpoint with status / priority / type / root-id filters, expand/collapse UI, search, and live 30s polling. v0.8.0: per-task page Mermaid area now renders the hierarchical 'Task lifecycle + tools' view \u2014 each operation record owns a subgraph that nests its tool calls, with click callbacks (`window.__toolClick`) that scroll-into-view + highlight + auto-expand the matching row in the tool-call table below. Operation record descriptions (`description` + `processDescription`) are now always rendered inline (no `<details>` collapse) so the user sees the lifecycle state at a glance; a fallback `<details>` kicks in only for descriptions longer than 600 chars. v0.8.1: hotfix for two pre-existing bugs in v0.8.0 (browser smoke test surfaced after merge). (a) Mermaid click directives were emitted as `click t1 __toolClick(1)` (missing `call` keyword) \u2014 Mermaid 10's parser rejects this with `got 'PS'`. Fixed to `click t1 call __toolClick(1)` (the v10 grammar requires `call` to invoke a callback with arguments). (b) `buildMermaidSource` lived inside the `attachTaskPageTimeline` IIFE but was also called from a listener in the `attachToolClickBridge` IIFE \u2014 sibling IIFEs cannot see each other's locals, so the listener threw `ReferenceError: buildMermaidSource is not defined` and the Mermaid diagram silently failed to re-render after `task-show:lifecycle-ops-loaded`. Fixed by hoisting the function (and its three helpers) to script top-level so both IIFEs can see it via the script-wide closure; the function is also exposed on `window.buildMermaidSource` for tests + tooling. v0.8.3: tree-display fix (Task #2426). The home page used to look like a flat list of root tasks because `autoExpandFirstLevels(..., 2)` only opened the first 2 levels \u2014 30/47 roots were leaf nodes and the remaining 17 collapsed to one level so grandchildren were never visible. Default expand depth is now 3 (root + child + grandchild + great-grandchild are visible on first paint), the summary line now shows per-depth count pills (root / child / grandchild / great-grandchild / level-N), each `tree-row` carries a `data-depth` attribute so CSS can paint coloured left rails per level, and the duplicated 'Live tool-call sessions (legacy view)' panel that made the page look like both a flat table AND a tree is now hidden behind `#legacy-sessions[hidden]` (kept for future debug-toggle restoration). v0.8.10: bug-fix release (Task #2537 + Task #2534). (a) Heap-bounded plugin caches: OperationsCache and TasksTreeCache now enforce a hard maxEntries cap (default 256 / 64). Oldest stale entries are evicted before inserting a new one, so long-lived roy-agent sessions (BackgroundTaskManager + MemorySessionStore) no longer leak Map entries through the plugin's per-task caches \u2014 see Task #2537 for the heap-unbounded-state RED\u2192GREEN repro. (b) Mermaid CJK font-family: server.ts renderTaskPage now configures mermaid.initialize({ themeVariables: { fontFamily: '\"PingFang SC\", \"Microsoft YaHei\", \"Noto Sans CJK SC\", \"Source Han Sans CN\", \"WenQuanYi Micro Hei\", sans-serif' } }) so Chinese node labels render correctly in browsers that have at least one of those fonts installed (see Task #2534).",
6
6
  "main": "dist/index.js",
7
7
  "hooks": [
8
8
  {
@@ -23,11 +23,11 @@
23
23
  },
24
24
  {
25
25
  "point": "task:after.complete",
26
- "purpose": "PREFERRED hook point (roy-agent 2026-07-10+). When a task transitions to a terminal status (completed/failed/cancelled), finalize the session and broadcast a `task.completed` SSE event. terminalStatus field is explicit no status filtering required. The handler dedupes against any `task:after.update` that may also fire on the same transition."
26
+ "purpose": "PREFERRED hook point (roy-agent 2026-07-10+). When a task transitions to a terminal status (completed/failed/cancelled), finalize the session and broadcast a `task.completed` SSE event. terminalStatus field is explicit \u2014 no status filtering required. The handler dedupes against any `task:after.update` that may also fire on the same transition."
27
27
  },
28
28
  {
29
29
  "point": "task:after.update",
30
- "purpose": "GENERIC status update hook (v0.5.0+). Used for non-terminal transitions (running paused running) AND as a legacy fallback for hosts predating the AFTER_COMPLETE hook point. Broadcasts `task.updated` for non-terminal status, `task.completed` for terminal status. The internal dedup set guarantees at most one terminal broadcast per task even if both hooks fire."
30
+ "purpose": "GENERIC status update hook (v0.5.0+). Used for non-terminal transitions (running \u2192 paused \u2192 running) AND as a legacy fallback for hosts predating the AFTER_COMPLETE hook point. Broadcasts `task.updated` for non-terminal status, `task.completed` for terminal status. The internal dedup set guarantees at most one terminal broadcast per task even if both hooks fire."
31
31
  }
32
32
  ],
33
33
  "commands": [
@@ -64,20 +64,20 @@
64
64
  }
65
65
  },
66
66
  "endpoints": {
67
- "index": "GET / index of recent task sessions",
68
- "taskDetail": "GET /task/:taskId detail page (mermaid flow + tables)",
69
- "apiSessions": "GET /api/sessions JSON list of sessions",
70
- "apiSessionDetail": "GET /api/sessions/:taskId JSON detail of one session",
71
- "apiEvents": "GET /api/events Server-Sent Events stream (real-time push)",
72
- "static": "GET /static/* static frontend assets (CSS/JS)"
67
+ "index": "GET / \u2014 index of recent task sessions",
68
+ "taskDetail": "GET /task/:taskId \u2014 detail page (mermaid flow + tables)",
69
+ "apiSessions": "GET /api/sessions \u2014 JSON list of sessions",
70
+ "apiSessionDetail": "GET /api/sessions/:taskId \u2014 JSON detail of one session",
71
+ "apiEvents": "GET /api/events \u2014 Server-Sent Events stream (real-time push)",
72
+ "static": "GET /static/* \u2014 static frontend assets (CSS/JS)"
73
73
  },
74
74
  "sse": {
75
75
  "events": [
76
- "snapshot initial state dump on connect",
77
- "task.created new task session opened",
78
- "task.updated non-terminal status change",
79
- "task.completed task transitioned to a terminal status",
80
- "tool.recorded tool call appended to a session"
76
+ "snapshot \u2014 initial state dump on connect",
77
+ "task.created \u2014 new task session opened",
78
+ "task.updated \u2014 non-terminal status change",
79
+ "task.completed \u2014 task transitioned to a terminal status",
80
+ "tool.recorded \u2014 tool call appended to a session"
81
81
  ],
82
82
  "heartbeatMs": 15000,
83
83
  "snapshotFrame": "event: snapshot\ndata: {type:'snapshot', data:{sessions:[...]}}\n\n"
@@ -89,4 +89,4 @@
89
89
  },
90
90
  "repository": "github:ai-setting/roy-plugin-task-visualize",
91
91
  "bugs": "https://github.com/ai-setting/roy-plugin-task-visualize/issues"
92
- }
92
+ }
@@ -31,10 +31,6 @@
31
31
  let panel = null;
32
32
  /** @type {any} */
33
33
  let monaco = null;
34
- /** @type {boolean} */
35
- let monacoLoading = false;
36
- /** @type {Array<() => void>} */
37
- const monacoWaiters = [];
38
34
  /**
39
35
  * v2.0.3: how long we wait for Monaco's CDN before falling back
40
36
  * to a plain `<pre>` render. If jsDelivr is slow or the user is
@@ -61,10 +57,18 @@
61
57
  * `<pre>` render of the file content (better than a stuck
62
58
  * "Loading…" placeholder).
63
59
  */
60
+ /** @type {Promise<any>|null} */
61
+ let monacoLoadPromise = null;
62
+ /**
63
+ * v2.0.4 (Task #2690): single in-flight monaco load promise. Both
64
+ * the dedicated panel and the inline row editors share this — so
65
+ * multiple concurrent `loadMonaco()` callers don't fight over
66
+ * `monacoWaiters` and don't leak stuck promises when the CDN
67
+ * times out.
68
+ */
64
69
  function loadMonaco() {
65
70
  if (monaco) return Promise.resolve(monaco);
66
- if (monacoLoading) return new Promise((r) => monacoWaiters.push(() => r(monaco)));
67
- monacoLoading = true;
71
+ if (monacoLoadPromise) return monacoLoadPromise;
68
72
  /** @type {any} */
69
73
  const w = window;
70
74
  const work = new Promise((resolve, reject) => {
@@ -75,13 +79,9 @@
75
79
  // eslint-disable-next-line no-undef
76
80
  require(["vs/editor/editor.main"], () => {
77
81
  monaco = w.monaco;
78
- monacoLoading = false;
79
- for (const waiter of monacoWaiters) waiter();
80
- monacoWaiters.length = 0;
81
82
  resolve(monaco);
82
83
  });
83
84
  } catch (err) {
84
- monacoLoading = false;
85
85
  reject(err);
86
86
  }
87
87
  };
@@ -93,25 +93,36 @@
93
93
  s.src = `${MONACO_BASE}/loader.js`;
94
94
  s.onload = () => onReady();
95
95
  s.onerror = () => {
96
- monacoLoading = false;
97
96
  reject(new Error("Failed to load Monaco loader script"));
98
97
  };
99
98
  document.head.appendChild(s);
100
99
  });
101
- // Race the load against a hard timeout. We swallow the timeout
102
- // rejection so the caller's `Promise.all` doesn't short-circuit —
103
- // they'll see `monaco === null` and choose the `<pre>` fallback.
100
+ // Race the load against a hard timeout. The timeout RESOLVES
101
+ // (not rejects) with `null` so the caller's `Promise.all` doesn't
102
+ // short-circuit — they'll see `monaco === null` and choose the
103
+ // `<pre>` fallback. The shared `monacoLoadPromise` is cleared
104
+ // on either path so a subsequent click can retry.
104
105
  const timeout = new Promise((resolve) =>
105
106
  setTimeout(() => resolve(null), MONACO_LOAD_TIMEOUT_MS),
106
107
  );
107
- return Promise.race([work, timeout]).then((res) => {
108
+ monacoLoadPromise = Promise.race([work, timeout]).then((res) => {
108
109
  if (res === null && !monaco) {
109
- // Timed out. Reset the loading flag so a subsequent click
110
- // can retry instead of being stuck on a stale promise.
111
- monacoLoading = false;
110
+ // Timed out clear the in-flight promise so a future click
111
+ // can re-attempt the load. (Multiple callers all get `null`
112
+ // here and fall back to <pre>.)
113
+ } else {
114
+ // Success — lock in the result so future calls return immediately.
112
115
  }
116
+ monacoLoadPromise = null;
117
+ return monaco;
118
+ }).catch((err) => {
119
+ // The CDN `<script>` rejected (network error, blocked CSP, etc.).
120
+ // We swallow the rejection for `Promise.all` compatibility and
121
+ // let callers see `monaco === null`.
122
+ monacoLoadPromise = null;
113
123
  return monaco;
114
124
  });
125
+ return monacoLoadPromise;
115
126
  }
116
127
 
117
128
  // -----------------------------------------------------------------------
@@ -149,41 +160,24 @@
149
160
  // -----------------------------------------------------------------------
150
161
 
151
162
  /**
152
- * Replace the panel contents with the detail for a given tool call.
163
+ * v2.0.4 (Task #2690): hydrate a single monaco container with the
164
+ * skeleton + load + fallback lifecycle. Used by BOTH the dedicated
165
+ * `#tool-call-detail-panel` and the inline `<div class="monaco-editor">`
166
+ * placeholders that v2.0.0 ships inside every tool row.
153
167
  *
154
- * We can read the existing `<section class="tool-call-detail">` from
155
- * the SSR-rendered table row (the same `data-tool-id`) and reuse it
156
- * verbatim — the LCS diff is already computed server-side. The
157
- * Monaco container is the only thing that needs lazy-loading.
168
+ * Idempotent: a container with `data-monaco-hydrated="true"` is a
169
+ * no-op (prevents double-init from toggle/click races).
158
170
  *
159
- * @param {string} toolId
171
+ * @param {HTMLElement|null} monacoContainer
172
+ * @returns {Promise<void>}
160
173
  */
161
- async function renderDetailForTool(toolId) {
162
- if (!panel) return;
163
- currentToolId = toolId;
164
- // Find the SSR-rendered detail (from the toolcalls table). There
165
- // may be several `<section class="tool-call-detail">` blocks — one
166
- // per row. Pick the one matching toolId.
167
- const source = document.querySelector(
168
- `section.tool-call-detail[data-tool-id="${cssEscape(toolId)}"]`,
169
- );
170
- if (!source) {
171
- // Fallback: empty placeholder
172
- panel.innerHTML = `<div class="tool-call-detail-empty">No detail available for tool call #${escapeHtml(toolId)}.</div>`;
173
- return;
174
- }
175
- // Move the SSR block into the panel (clone so we don't tear it
176
- // out of the row — the row's "show args" <details> still references
177
- // it for diff display).
178
- const cloned = source.cloneNode(true);
179
- panel.innerHTML = "";
180
- panel.appendChild(cloned);
181
- panel.setAttribute("data-current-call", toolId);
182
-
183
- const monacoContainer = cloned.querySelector(".monaco-editor");
174
+ async function hydrateMonacoContainer(monacoContainer) {
184
175
  if (!monacoContainer) return;
176
+ if (monacoContainer.getAttribute("data-monaco-hydrated") === "true") return;
185
177
  const filePath = monacoContainer.getAttribute("data-file-path");
186
178
  if (!filePath) return;
179
+ // Mark as hydrated BEFORE the await so concurrent callers short-circuit.
180
+ monacoContainer.setAttribute("data-monaco-hydrated", "true");
187
181
  currentFilePath = filePath;
188
182
  // v2.0.3: show an animated skeleton immediately so the user
189
183
  // gets visual feedback that the editor is loading (instead of
@@ -254,6 +248,91 @@
254
248
  }
255
249
  }
256
250
 
251
+ /**
252
+ * v2.0.4 (Task #2690): hydrate the inline `<div class="monaco-editor">`
253
+ * inside a specific row's `<td class="args-cell">`. Triggered when
254
+ * the user opens the row's `<details class="tool-detail">`.
255
+ *
256
+ * @param {HTMLElement} monacoContainer
257
+ */
258
+ function hydrateInlineMonaco(monacoContainer) {
259
+ // Fire and forget — `hydrateMonacoContainer` is idempotent and
260
+ // manages its own skeleton / fallback lifecycle.
261
+ hydrateMonacoContainer(monacoContainer).catch((err) => {
262
+ // eslint-disable-next-line no-console
263
+ console.warn("[task-show] inline monaco hydration failed:", err);
264
+ });
265
+ }
266
+
267
+ /**
268
+ * v2.0.4 (Task #2690): walk every `<details class="tool-detail">`
269
+ * that contains an inline `<div class="monaco-editor">` (i.e. lives
270
+ * inside a `<td class="args-cell">` or `<td class="result-cell">`
271
+ * of a tool row). On `toggle`, if the details is opening, hydrate
272
+ * the inline editor so the user actually sees the file content
273
+ * instead of a stuck "Monaco loading…" placeholder.
274
+ *
275
+ * We delegate per-container work to `hydrateInlineMonaco()` which
276
+ * reuses the existing skeleton + 5s timeout + `<pre>` fallback.
277
+ */
278
+ function attachInlineRowHydrators() {
279
+ const allDetails = document.querySelectorAll("tr.tool-row details.tool-detail");
280
+ allDetails.forEach((details) => {
281
+ // Only register once per details element.
282
+ if (details.getAttribute("data-inline-monaco-wired") === "true") return;
283
+ details.setAttribute("data-inline-monaco-wired", "true");
284
+ details.addEventListener("toggle", () => {
285
+ // Find inline monaco editors that are descendants of this
286
+ // details OR siblings under the same <td>.
287
+ const td = details.closest("td");
288
+ if (!td) return;
289
+ const editors = td.querySelectorAll(
290
+ ".monaco-editor[data-file-path]:not([data-monaco-hydrated='true'])",
291
+ );
292
+ editors.forEach((ed) => hydrateInlineMonaco(ed));
293
+ });
294
+ });
295
+ }
296
+
297
+ /**
298
+ * Replace the panel contents with the detail for a given tool call.
299
+ *
300
+ * We can read the existing `<section class="tool-call-detail">` from
301
+ * the SSR-rendered table row (the same `data-tool-id`) and reuse it
302
+ * verbatim — the LCS diff is already computed server-side. The
303
+ * Monaco container is the only thing that needs lazy-loading.
304
+ *
305
+ * @param {string} toolId
306
+ */
307
+ async function renderDetailForTool(toolId) {
308
+ if (!panel) return;
309
+ currentToolId = toolId;
310
+ // Find the SSR-rendered detail (from the toolcalls table). There
311
+ // may be several `<section class="tool-call-detail">` blocks — one
312
+ // per row. Pick the one matching toolId.
313
+ const source = document.querySelector(
314
+ `section.tool-call-detail[data-tool-id="${cssEscape(toolId)}"]`,
315
+ );
316
+ if (!source) {
317
+ // Fallback: empty placeholder
318
+ panel.innerHTML = `<div class="tool-call-detail-empty">No detail available for tool call #${escapeHtml(toolId)}.</div>`;
319
+ return;
320
+ }
321
+ // Move the SSR block into the panel (clone so we don't tear it
322
+ // out of the row — the row's "show args" <details> still references
323
+ // it for diff display).
324
+ const cloned = source.cloneNode(true);
325
+ panel.innerHTML = "";
326
+ panel.appendChild(cloned);
327
+ panel.setAttribute("data-current-call", toolId);
328
+
329
+ const monacoContainer = cloned.querySelector(".monaco-editor");
330
+ // v2.0.4 (Task #2690): delegate to the shared monaco hydrator so
331
+ // the dedicated panel and the inline row editors share the same
332
+ // skeleton + load + 5s timeout + <pre> fallback lifecycle.
333
+ await hydrateMonacoContainer(monacoContainer);
334
+ }
335
+
257
336
  // -----------------------------------------------------------------------
258
337
  // Hooks
259
338
  // -----------------------------------------------------------------------
@@ -316,6 +395,10 @@
316
395
  panel = document.querySelector(PANEL_SELECTOR);
317
396
  if (!panel) return;
318
397
  attachToolClickHook();
398
+ // v2.0.4 (Task #2690): wire up inline row editors so opening
399
+ // "show args" / "show result" on a tool row hydrates the inline
400
+ // monaco editor (skeleton → 5s timeout → <pre> fallback).
401
+ attachInlineRowHydrators();
319
402
  // If the SSR rendered an initial detail (first tool call), pre-fill
320
403
  // Monaco for it so the user sees the editor immediately on page load.
321
404
  const initialMonaco = panel.querySelector(".monaco-editor");