@ai-setting/roy-plugin-task-show 1.1.0 → 2.0.0
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.
- package/dist/cli-tasks-adapter.d.ts +5 -0
- package/dist/cli-tasks-adapter.d.ts.map +1 -1
- package/dist/cli-tasks-adapter.js +5 -0
- package/dist/cli-tasks-adapter.js.map +1 -1
- package/dist/cli-tasks-tree-adapter.d.ts +5 -0
- package/dist/cli-tasks-tree-adapter.d.ts.map +1 -1
- package/dist/cli-tasks-tree-adapter.js +5 -0
- package/dist/cli-tasks-tree-adapter.js.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +9 -7
- package/dist/plugin.js.map +1 -1
- package/dist/server.js +13 -6
- package/dist/server.js.map +1 -1
- package/dist/task-metadata.d.ts +21 -0
- package/dist/task-metadata.d.ts.map +1 -0
- package/dist/task-metadata.js +50 -0
- package/dist/task-metadata.js.map +1 -0
- package/dist/task-session-store.d.ts +2 -1
- package/dist/task-session-store.d.ts.map +1 -1
- package/dist/task-session-store.js +28 -4
- package/dist/task-session-store.js.map +1 -1
- package/package.json +1 -1
- package/plugin.json +2 -2
- package/public/file-tree.js +210 -0
- package/public/style.css +566 -0
- package/public/tool-call-detail.js +311 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/* v2.0.0: tool-call detail panel hydrator.
|
|
2
|
+
*
|
|
3
|
+
* Wires up the dedicated `<section id="tool-call-detail-panel">`:
|
|
4
|
+
*
|
|
5
|
+
* 1. Hooks `window.__toolClick(toolId)` (set by mermaid-renderer.js
|
|
6
|
+
* + app.js) so a Mermaid click scrolls the panel into view AND
|
|
7
|
+
* re-renders its content with the clicked call's Monaco + diff.
|
|
8
|
+
*
|
|
9
|
+
* 2. Lazy-loads Monaco Editor from a CDN the first time the user
|
|
10
|
+
* clicks any tool row. Subsequent clicks reuse the same Monaco
|
|
11
|
+
* model — no extra network round-trips.
|
|
12
|
+
*
|
|
13
|
+
* 3. Fetches the file content for `data-file-path` via
|
|
14
|
+
* `/api/file-content` and feeds it into Monaco. If the path is
|
|
15
|
+
* outside the server's sandbox (403), shows a graceful
|
|
16
|
+
* "file not accessible" placeholder rather than a broken editor.
|
|
17
|
+
*
|
|
18
|
+
* 4. Re-renders the inline `<details class="tool-detail">` block for
|
|
19
|
+
* the active tool row so the diff/Monaco surfaces both in the
|
|
20
|
+
* detail panel AND in the original table position.
|
|
21
|
+
*
|
|
22
|
+
* Loaded AFTER app.js + file-tree.js.
|
|
23
|
+
*
|
|
24
|
+
* The Monaco loader is loaded from jsDelivr (per design doc §2.1)
|
|
25
|
+
* and exposes the AMD `require` to set language workers.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
(function attachToolCallDetailHydrator() {
|
|
29
|
+
const PANEL_SELECTOR = "#tool-call-detail-panel";
|
|
30
|
+
/** @type {HTMLElement|null} */
|
|
31
|
+
let panel = null;
|
|
32
|
+
/** @type {any} */
|
|
33
|
+
let monaco = null;
|
|
34
|
+
/** @type {boolean} */
|
|
35
|
+
let monacoLoading = false;
|
|
36
|
+
/** @type {Array<() => void>} */
|
|
37
|
+
const monacoWaiters = [];
|
|
38
|
+
/** @type {string|null} */
|
|
39
|
+
let currentToolId = null;
|
|
40
|
+
/** @type {string|null} */
|
|
41
|
+
let currentFilePath = null;
|
|
42
|
+
|
|
43
|
+
// -----------------------------------------------------------------------
|
|
44
|
+
// Monaco loader (CDN, AMD)
|
|
45
|
+
// -----------------------------------------------------------------------
|
|
46
|
+
const MONACO_VERSION = "0.45.0";
|
|
47
|
+
const MONACO_BASE = `https://cdn.jsdelivr.net/npm/monaco-editor@${MONACO_VERSION}/min/vs`;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Inject the AMD loader script and resolve once `require` is ready.
|
|
51
|
+
* The script is idempotent — calling twice is a no-op the second time.
|
|
52
|
+
*/
|
|
53
|
+
function loadMonaco() {
|
|
54
|
+
if (monaco) return Promise.resolve(monaco);
|
|
55
|
+
if (monacoLoading) return new Promise((r) => monacoWaiters.push(() => r(monaco)));
|
|
56
|
+
monacoLoading = true;
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
/** @type {any} */
|
|
59
|
+
const w = window;
|
|
60
|
+
const onReady = () => {
|
|
61
|
+
try {
|
|
62
|
+
// eslint-disable-next-line no-undef
|
|
63
|
+
require.config({ paths: { vs: MONACO_BASE } });
|
|
64
|
+
// eslint-disable-next-line no-undef
|
|
65
|
+
require(["vs/editor/editor.main"], () => {
|
|
66
|
+
monaco = w.monaco;
|
|
67
|
+
monacoLoading = false;
|
|
68
|
+
for (const waiter of monacoWaiters) waiter();
|
|
69
|
+
monacoWaiters.length = 0;
|
|
70
|
+
resolve(monaco);
|
|
71
|
+
});
|
|
72
|
+
} catch (err) {
|
|
73
|
+
monacoLoading = false;
|
|
74
|
+
reject(err);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
if (typeof w.require === "function" && w.require.config) {
|
|
78
|
+
onReady();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const s = document.createElement("script");
|
|
82
|
+
s.src = `${MONACO_BASE}/loader.js`;
|
|
83
|
+
s.onload = () => onReady();
|
|
84
|
+
s.onerror = () => {
|
|
85
|
+
monacoLoading = false;
|
|
86
|
+
reject(new Error("Failed to load Monaco loader script"));
|
|
87
|
+
};
|
|
88
|
+
document.head.appendChild(s);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// -----------------------------------------------------------------------
|
|
93
|
+
// File fetch
|
|
94
|
+
// -----------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Fetch the file content via the server's sandboxed endpoint. Returns
|
|
98
|
+
* `null` when the file isn't accessible (404/403/network). We surface
|
|
99
|
+
* a graceful "not accessible" placeholder rather than blowing up the
|
|
100
|
+
* page.
|
|
101
|
+
* @param {string} path
|
|
102
|
+
* @returns {Promise<{content: string, truncated: boolean, byteSize: number} | null>}
|
|
103
|
+
*/
|
|
104
|
+
async function fetchFileContent(path) {
|
|
105
|
+
try {
|
|
106
|
+
const res = await fetch(
|
|
107
|
+
`/api/file-content?path=${encodeURIComponent(path)}&max=${64 * 1024}`,
|
|
108
|
+
);
|
|
109
|
+
if (!res.ok) return null;
|
|
110
|
+
const data = await res.json();
|
|
111
|
+
if (typeof data.content !== "string") return null;
|
|
112
|
+
return {
|
|
113
|
+
content: data.content,
|
|
114
|
+
truncated: Boolean(data.truncated),
|
|
115
|
+
byteSize: Number(data.byteSize) || data.content.length,
|
|
116
|
+
};
|
|
117
|
+
} catch (_) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// -----------------------------------------------------------------------
|
|
123
|
+
// Render
|
|
124
|
+
// -----------------------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Replace the panel contents with the detail for a given tool call.
|
|
128
|
+
*
|
|
129
|
+
* We can read the existing `<section class="tool-call-detail">` from
|
|
130
|
+
* the SSR-rendered table row (the same `data-tool-id`) and reuse it
|
|
131
|
+
* verbatim — the LCS diff is already computed server-side. The
|
|
132
|
+
* Monaco container is the only thing that needs lazy-loading.
|
|
133
|
+
*
|
|
134
|
+
* @param {string} toolId
|
|
135
|
+
*/
|
|
136
|
+
async function renderDetailForTool(toolId) {
|
|
137
|
+
if (!panel) return;
|
|
138
|
+
currentToolId = toolId;
|
|
139
|
+
// Find the SSR-rendered detail (from the toolcalls table). There
|
|
140
|
+
// may be several `<section class="tool-call-detail">` blocks — one
|
|
141
|
+
// per row. Pick the one matching toolId.
|
|
142
|
+
const source = document.querySelector(
|
|
143
|
+
`section.tool-call-detail[data-tool-id="${cssEscape(toolId)}"]`,
|
|
144
|
+
);
|
|
145
|
+
if (!source) {
|
|
146
|
+
// Fallback: empty placeholder
|
|
147
|
+
panel.innerHTML = `<div class="tool-call-detail-empty">No detail available for tool call #${escapeHtml(toolId)}.</div>`;
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
// Move the SSR block into the panel (clone so we don't tear it
|
|
151
|
+
// out of the row — the row's "show args" <details> still references
|
|
152
|
+
// it for diff display).
|
|
153
|
+
const cloned = source.cloneNode(true);
|
|
154
|
+
panel.innerHTML = "";
|
|
155
|
+
panel.appendChild(cloned);
|
|
156
|
+
panel.setAttribute("data-current-call", toolId);
|
|
157
|
+
|
|
158
|
+
const monacoContainer = cloned.querySelector(".monaco-editor");
|
|
159
|
+
if (!monacoContainer) return;
|
|
160
|
+
const filePath = monacoContainer.getAttribute("data-file-path");
|
|
161
|
+
if (!filePath) return;
|
|
162
|
+
currentFilePath = filePath;
|
|
163
|
+
monacoContainer.innerHTML = `<div class="monaco-placeholder">Loading ${escapeHtml(filePath)}…</div>`;
|
|
164
|
+
try {
|
|
165
|
+
const [file] = await Promise.all([fetchFileContent(filePath), loadMonaco()]);
|
|
166
|
+
if (!monaco) return; // loadMonaco() rejected; placeholder stays
|
|
167
|
+
if (!file) {
|
|
168
|
+
monacoContainer.innerHTML = `<div class="monaco-placeholder monaco-error">File not accessible (sandboxed or missing): <code>${escapeHtml(filePath)}</code></div>`;
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const language = monacoContainer.getAttribute("data-language") || "plaintext";
|
|
172
|
+
// eslint-disable-next-line no-undef
|
|
173
|
+
const editor = monaco.editor.create(monacoContainer, {
|
|
174
|
+
value: file.content,
|
|
175
|
+
language,
|
|
176
|
+
readOnly: true,
|
|
177
|
+
minimap: { enabled: false },
|
|
178
|
+
fontSize: 13,
|
|
179
|
+
lineNumbers: "on",
|
|
180
|
+
scrollBeyondLastLine: false,
|
|
181
|
+
automaticLayout: true,
|
|
182
|
+
theme: "vs",
|
|
183
|
+
});
|
|
184
|
+
// Dispose when the panel is cleared (next render or page unload).
|
|
185
|
+
const dispose = () => {
|
|
186
|
+
try {
|
|
187
|
+
editor.dispose();
|
|
188
|
+
} catch (_) {
|
|
189
|
+
/* noop */
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
monacoContainer.addEventListener("monaco-dispose", dispose, { once: true });
|
|
193
|
+
// Annotate with truncation notice if needed.
|
|
194
|
+
if (file.truncated) {
|
|
195
|
+
const note = document.createElement("div");
|
|
196
|
+
note.className = "monaco-truncation-note";
|
|
197
|
+
note.textContent = `(truncated — first ${file.content.length} bytes of ${file.byteSize})`;
|
|
198
|
+
monacoContainer.appendChild(note);
|
|
199
|
+
}
|
|
200
|
+
} catch (err) {
|
|
201
|
+
monacoContainer.innerHTML = `<div class="monaco-placeholder monaco-error">Monaco failed to load: ${escapeHtml(String((err && err.message) || err))}</div>`;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// -----------------------------------------------------------------------
|
|
206
|
+
// Hooks
|
|
207
|
+
// -----------------------------------------------------------------------
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Replace the existing `window.__toolClick` (set by app.js) with our
|
|
211
|
+
* wrapper that ALSO updates the panel. We preserve the original
|
|
212
|
+
* scroll-into-view + highlight behaviour so the table row still
|
|
213
|
+
* flashes — we just add: panel render + file-tree highlight.
|
|
214
|
+
*/
|
|
215
|
+
function attachToolClickHook() {
|
|
216
|
+
/** @type {any} */
|
|
217
|
+
const w = window;
|
|
218
|
+
const existing = w.__toolClick;
|
|
219
|
+
w.__toolClick = function (toolId) {
|
|
220
|
+
try {
|
|
221
|
+
if (typeof existing === "function") existing(toolId);
|
|
222
|
+
} catch (err) {
|
|
223
|
+
// eslint-disable-next-line no-console
|
|
224
|
+
console.warn("[task-show] prior __toolClick failed:", err);
|
|
225
|
+
}
|
|
226
|
+
try {
|
|
227
|
+
renderDetailForTool(String(toolId));
|
|
228
|
+
// Highlight the corresponding file-tree node (when the call
|
|
229
|
+
// has a file_path).
|
|
230
|
+
const detail = document.querySelector(
|
|
231
|
+
`section.tool-call-detail[data-tool-id="${cssEscape(String(toolId))}"]`,
|
|
232
|
+
);
|
|
233
|
+
const filePath = detail && detail.querySelector(".monaco-editor")
|
|
234
|
+
? detail.querySelector(".monaco-editor").getAttribute("data-file-path")
|
|
235
|
+
: null;
|
|
236
|
+
if (filePath && w.__fileTree && typeof w.__fileTree.setActive === "function") {
|
|
237
|
+
w.__fileTree.setActive(filePath);
|
|
238
|
+
}
|
|
239
|
+
// Scroll the panel into view (block: 'start' so the heading
|
|
240
|
+
// is at the top of the viewport).
|
|
241
|
+
if (panel) {
|
|
242
|
+
try {
|
|
243
|
+
panel.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
244
|
+
} catch (_) {
|
|
245
|
+
try {
|
|
246
|
+
panel.scrollIntoView();
|
|
247
|
+
} catch (_) {
|
|
248
|
+
/* noop */
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
} catch (err) {
|
|
253
|
+
// eslint-disable-next-line no-console
|
|
254
|
+
console.warn("[task-show] tool-call-detail render failed:", err);
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Initial hydration: find the panel + install the __toolClick hook.
|
|
261
|
+
* Idempotent.
|
|
262
|
+
*/
|
|
263
|
+
function hydrate() {
|
|
264
|
+
panel = document.querySelector(PANEL_SELECTOR);
|
|
265
|
+
if (!panel) return;
|
|
266
|
+
attachToolClickHook();
|
|
267
|
+
// If the SSR rendered an initial detail (first tool call), pre-fill
|
|
268
|
+
// Monaco for it so the user sees the editor immediately on page load.
|
|
269
|
+
const initialMonaco = panel.querySelector(".monaco-editor");
|
|
270
|
+
const initialFilePath = initialMonaco && initialMonaco.getAttribute("data-file-path");
|
|
271
|
+
if (initialMonaco && initialFilePath) {
|
|
272
|
+
// Defer to next microtask so we don't block the first paint.
|
|
273
|
+
Promise.resolve().then(() => {
|
|
274
|
+
renderDetailForTool(currentToolId || panel.getAttribute("data-current-call") || "");
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* CSS.escape polyfill for older browsers + jsdom.
|
|
281
|
+
* @param {string} s
|
|
282
|
+
*/
|
|
283
|
+
function cssEscape(s) {
|
|
284
|
+
if (typeof CSS !== "undefined" && typeof CSS.escape === "function") {
|
|
285
|
+
return CSS.escape(s);
|
|
286
|
+
}
|
|
287
|
+
return String(s).replace(/([^a-zA-Z0-9_-])/g, "\\$1");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Tiny HTML escape for the placeholders we inject via innerHTML.
|
|
292
|
+
* @param {unknown} s
|
|
293
|
+
*/
|
|
294
|
+
function escapeHtml(s) {
|
|
295
|
+
return String(s ?? "")
|
|
296
|
+
.replace(/&/g, "&")
|
|
297
|
+
.replace(/</g, "<")
|
|
298
|
+
.replace(/>/g, ">")
|
|
299
|
+
.replace(/"/g, """)
|
|
300
|
+
.replace(/'/g, "'");
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// -----------------------------------------------------------------------
|
|
304
|
+
// Boot
|
|
305
|
+
// -----------------------------------------------------------------------
|
|
306
|
+
if (document.readyState === "loading") {
|
|
307
|
+
document.addEventListener("DOMContentLoaded", hydrate, { once: true });
|
|
308
|
+
} else {
|
|
309
|
+
hydrate();
|
|
310
|
+
}
|
|
311
|
+
})();
|