@ai-setting/roy-plugin-task-show 2.5.11 → 2.5.13
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/chat-markdown-renderer.d.ts +63 -0
- package/dist/chat-markdown-renderer.d.ts.map +1 -0
- package/dist/chat-markdown-renderer.js +123 -0
- package/dist/chat-markdown-renderer.js.map +1 -0
- package/dist/collector.d.ts +11 -0
- package/dist/collector.d.ts.map +1 -1
- package/dist/collector.js +37 -12
- package/dist/collector.js.map +1 -1
- package/dist/file-tree.d.ts +57 -1
- package/dist/file-tree.d.ts.map +1 -1
- package/dist/file-tree.js +22 -2
- package/dist/file-tree.js.map +1 -1
- package/dist/file-viewer-utils.d.ts +76 -0
- package/dist/file-viewer-utils.d.ts.map +1 -0
- package/dist/file-viewer-utils.js +215 -0
- package/dist/file-viewer-utils.js.map +1 -0
- package/dist/logger.d.ts +59 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +181 -0
- package/dist/logger.js.map +1 -0
- package/dist/mermaid-zoom-guardian.d.ts +77 -0
- package/dist/mermaid-zoom-guardian.d.ts.map +1 -0
- package/dist/mermaid-zoom-guardian.js +176 -0
- package/dist/mermaid-zoom-guardian.js.map +1 -0
- package/dist/plugin.d.ts +24 -10
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +79 -33
- package/dist/plugin.js.map +1 -1
- package/dist/server.d.ts +25 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +134 -8
- package/dist/server.js.map +1 -1
- package/dist/task-session-store.js +7 -0
- package/dist/task-session-store.js.map +1 -1
- package/dist/tool-call-detail.d.ts +18 -0
- package/dist/tool-call-detail.d.ts.map +1 -1
- package/dist/tool-call-detail.js +115 -7
- package/dist/tool-call-detail.js.map +1 -1
- package/package.json +2 -2
- package/plugin.json +2 -2
- package/public/app.js +18 -0
- package/public/index.html +7 -0
- package/public/markdown-preprocessor.js +89 -0
- package/public/markdown-renderer.js +29 -1
- package/public/mermaid-zoom-guardian.js +137 -0
- package/public/style.css +109 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview File viewer rendering utilities (Task #2952).
|
|
3
|
+
*
|
|
4
|
+
* The right-hand tool-call detail panel on the task page renders
|
|
5
|
+
* differently depending on the tool name:
|
|
6
|
+
*
|
|
7
|
+
* - **write_file / write / create_file** → render the FULL content
|
|
8
|
+
* of the file as the user just wrote it (line-numbered, no
|
|
9
|
+
* truncation). Previously the panel mounted a Monaco placeholder
|
|
10
|
+
* that fetched the file from disk via /api/file-content — which
|
|
11
|
+
* could be truncated for large files and never reflected the
|
|
12
|
+
* content the tool call actually wrote.
|
|
13
|
+
*
|
|
14
|
+
* - **edit_file / edit / edit_file_v2 / multi_edit_file / multi_edit
|
|
15
|
+
* / apply_patch** → render a git-style diff between
|
|
16
|
+
* `old_string` and `new_string` (LCS-based +/- lines). For these
|
|
17
|
+
* tools we want to highlight what changed, not show the entire
|
|
18
|
+
* resulting file again.
|
|
19
|
+
*
|
|
20
|
+
* Both renderers produce XSS-safe HTML — every user-controlled string
|
|
21
|
+
* passes through {@link escapeHtml} before being injected.
|
|
22
|
+
*/
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Public renderers
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
/**
|
|
27
|
+
* Render a write_file tool call's full content as line-numbered HTML.
|
|
28
|
+
*
|
|
29
|
+
* Returns a `<div class="file-viewer write-file">` containing:
|
|
30
|
+
* - a header with the file path + line/byte counts
|
|
31
|
+
* - a `<pre class="code-block">` with every line (no truncation)
|
|
32
|
+
*
|
|
33
|
+
* The renderer escapes HTML; long lines are preserved verbatim.
|
|
34
|
+
*/
|
|
35
|
+
export function renderWriteFileFull(opts) {
|
|
36
|
+
const lines = opts.content.split("\n");
|
|
37
|
+
// If the content ends with \n, split() produces a trailing "" entry.
|
|
38
|
+
// We keep it (matches editor display) but flag the count accurately.
|
|
39
|
+
const totalChars = opts.content.length;
|
|
40
|
+
const lineNumbered = lines
|
|
41
|
+
.map((line, i) => {
|
|
42
|
+
const num = String(i + 1).padStart(4, " ");
|
|
43
|
+
const escaped = escapeHtml(line);
|
|
44
|
+
return `<div class="code-line"><span class="ln">${num}</span><span class="content">${escaped}</span></div>`;
|
|
45
|
+
})
|
|
46
|
+
.join("");
|
|
47
|
+
return `<div class="file-viewer write-file" data-file-path="${escapeHtmlAttr(opts.path)}">
|
|
48
|
+
<div class="file-header">
|
|
49
|
+
<span class="file-icon">📝</span>
|
|
50
|
+
<span class="file-path">${escapeHtml(opts.path)}</span>
|
|
51
|
+
<span class="file-meta">${lines.length} lines · ${totalChars} bytes</span>
|
|
52
|
+
</div>
|
|
53
|
+
<pre class="code-block">${lineNumbered}</pre>
|
|
54
|
+
</div>`;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Render an edit_file tool call as a git-style unified diff.
|
|
58
|
+
*
|
|
59
|
+
* Returns a `<div class="file-viewer edit-file">` containing:
|
|
60
|
+
* - a header with the file path + line counts
|
|
61
|
+
* - a `<ol class="diff-body">` with LCS-derived +/- / context lines
|
|
62
|
+
* (legacy contract — pre-v2.5.12 callers look for `class="diff-body"`
|
|
63
|
+
* and `diff-line-(added|removed|context)` markers).
|
|
64
|
+
*
|
|
65
|
+
* If `old_string` and `new_string` are identical the result is just
|
|
66
|
+
* the context lines (no +/- markers).
|
|
67
|
+
*
|
|
68
|
+
* The wrapper also carries `data-diff="<json>"` so the client
|
|
69
|
+
* hydrator can rebuild the diff on click without re-parsing the DOM.
|
|
70
|
+
*/
|
|
71
|
+
export function renderEditFileDiff(opts) {
|
|
72
|
+
const oldLines = splitLines(opts.old_string);
|
|
73
|
+
const newLines = splitLines(opts.new_string);
|
|
74
|
+
const ops = computeLineDiff(oldLines, newLines);
|
|
75
|
+
const rendered = ops
|
|
76
|
+
.map((op) => {
|
|
77
|
+
const escaped = escapeHtml(op.line);
|
|
78
|
+
// v2.5.12: dual-class — legacy `diff-line-added/removed/context`
|
|
79
|
+
// (consumed by .diff-body CSS + v2.0.0 client tests) AND the new
|
|
80
|
+
// `add/remove/context` short forms (consumed by .file-viewer CSS).
|
|
81
|
+
// The legacy class MUST be the second token so the regex
|
|
82
|
+
// `class="diff-line diff-line-(added|removed|context)"` continues
|
|
83
|
+
// to match (used by RED-INT3 in
|
|
84
|
+
// test/tool-call-detail-server-integration.test.ts).
|
|
85
|
+
const legacyCls = op.type === "add"
|
|
86
|
+
? "diff-line-added"
|
|
87
|
+
: op.type === "remove"
|
|
88
|
+
? "diff-line-removed"
|
|
89
|
+
: "diff-line-context";
|
|
90
|
+
const newCls = op.type; // "add" | "remove" | "context"
|
|
91
|
+
const marker = op.type === "add" ? "+" : op.type === "remove" ? "-" : " ";
|
|
92
|
+
return `<li class="diff-line ${legacyCls}" data-kind="${newCls}" data-line="${escapeHtmlAttr(String(op.lineNum))}"><span class="diff-gutter">${escapeHtml(marker)}</span><span class="diff-text">${escaped}</span></li>`;
|
|
93
|
+
})
|
|
94
|
+
.join("");
|
|
95
|
+
const removedCount = ops.filter((o) => o.type === "remove").length;
|
|
96
|
+
const addedCount = ops.filter((o) => o.type === "add").length;
|
|
97
|
+
const dataDiff = JSON.stringify(ops.map((o) => ({
|
|
98
|
+
kind: o.type === "add" ? "added" : o.type === "remove" ? "removed" : "context",
|
|
99
|
+
text: o.line,
|
|
100
|
+
lineNum: o.lineNum,
|
|
101
|
+
})));
|
|
102
|
+
return `<div class="file-viewer edit-file" data-file-path="${escapeHtmlAttr(opts.path)}">
|
|
103
|
+
<div class="file-header">
|
|
104
|
+
<span class="file-icon">✏️</span>
|
|
105
|
+
<span class="file-path">${escapeHtml(opts.path)}</span>
|
|
106
|
+
<span class="file-meta">-${removedCount} +${addedCount} lines (${oldLines.length} → ${newLines.length})</span>
|
|
107
|
+
</div>
|
|
108
|
+
<ol class="diff-body" data-diff="${escapeHtmlAttr(dataDiff)}">${rendered}</ol>
|
|
109
|
+
</div>`;
|
|
110
|
+
}
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// LCS-based line diff
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
/**
|
|
115
|
+
* Compute a line-level diff using LCS (Longest Common Subsequence).
|
|
116
|
+
*
|
|
117
|
+
* Algorithm: classic O(N*M) DP table, then a single backtrack to
|
|
118
|
+
* emit the ops in order. For inputs up to a few thousand lines this
|
|
119
|
+
* is well within budget; for pathological inputs we accept the cost
|
|
120
|
+
* (Task #2952 callers are user-authored file edits, not bulk
|
|
121
|
+
* transforms).
|
|
122
|
+
*
|
|
123
|
+
* Output: ordered array of `{type, line, lineNum}` matching how an
|
|
124
|
+
* editor would display them.
|
|
125
|
+
*/
|
|
126
|
+
export function computeLineDiff(oldLines, newLines) {
|
|
127
|
+
const m = oldLines.length;
|
|
128
|
+
const n = newLines.length;
|
|
129
|
+
if (m === 0 && n === 0)
|
|
130
|
+
return [];
|
|
131
|
+
// dp[i+1][j+1] = LCS length of oldLines[0..i] vs newLines[0..j]
|
|
132
|
+
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
133
|
+
for (let i = 0; i < m; i++) {
|
|
134
|
+
const oldLine = oldLines[i];
|
|
135
|
+
const row = dp[i + 1];
|
|
136
|
+
const prev = dp[i];
|
|
137
|
+
for (let j = 0; j < n; j++) {
|
|
138
|
+
if (oldLine === newLines[j]) {
|
|
139
|
+
row[j + 1] = (prev[j] ?? 0) + 1;
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
row[j + 1] = Math.max(prev[j + 1] ?? 0, row[j] ?? 0);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Backtrack to produce the ops in reverse order.
|
|
147
|
+
const reversed = [];
|
|
148
|
+
let i = m;
|
|
149
|
+
let j = n;
|
|
150
|
+
while (i > 0 && j > 0) {
|
|
151
|
+
if (oldLines[i - 1] === newLines[j - 1]) {
|
|
152
|
+
reversed.push({ type: "context", line: oldLines[i - 1], lineNum: j });
|
|
153
|
+
i--;
|
|
154
|
+
j--;
|
|
155
|
+
}
|
|
156
|
+
else if ((dp[i - 1]?.[j] ?? 0) >= (dp[i]?.[j - 1] ?? 0)) {
|
|
157
|
+
reversed.push({ type: "remove", line: oldLines[i - 1], lineNum: i });
|
|
158
|
+
i--;
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
reversed.push({ type: "add", line: newLines[j - 1], lineNum: j });
|
|
162
|
+
j--;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
while (i > 0) {
|
|
166
|
+
reversed.push({ type: "remove", line: oldLines[i - 1], lineNum: i });
|
|
167
|
+
i--;
|
|
168
|
+
}
|
|
169
|
+
while (j > 0) {
|
|
170
|
+
reversed.push({ type: "add", line: newLines[j - 1], lineNum: j });
|
|
171
|
+
j--;
|
|
172
|
+
}
|
|
173
|
+
reversed.reverse();
|
|
174
|
+
return reversed;
|
|
175
|
+
}
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
// Helpers
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
/**
|
|
180
|
+
* Split a string into lines. We split on `\n` and drop a single
|
|
181
|
+
* trailing empty element from a final `\n` (matching how `git diff`
|
|
182
|
+
* and most editors present the result). Empty input → `[]`.
|
|
183
|
+
*/
|
|
184
|
+
function splitLines(s) {
|
|
185
|
+
if (s === "")
|
|
186
|
+
return [];
|
|
187
|
+
const parts = s.split("\n");
|
|
188
|
+
if (parts.length > 0 && parts[parts.length - 1] === "")
|
|
189
|
+
parts.pop();
|
|
190
|
+
return parts;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Escape user-controlled strings for HTML body context. Mirrors
|
|
194
|
+
* `escapeHtml()` in `src/tool-call-detail.ts` so the two renderers
|
|
195
|
+
* stay consistent.
|
|
196
|
+
*/
|
|
197
|
+
function escapeHtml(input) {
|
|
198
|
+
if (input === undefined || input === null)
|
|
199
|
+
return "";
|
|
200
|
+
const s = typeof input === "string" ? input : JSON.stringify(input);
|
|
201
|
+
return s
|
|
202
|
+
.replace(/&/g, "&")
|
|
203
|
+
.replace(/</g, "<")
|
|
204
|
+
.replace(/>/g, ">")
|
|
205
|
+
.replace(/"/g, """)
|
|
206
|
+
.replace(/'/g, "'");
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Attribute-context escape. Same algorithm as {@link escapeHtml} but
|
|
210
|
+
* the separate name makes the intent clear at the call site.
|
|
211
|
+
*/
|
|
212
|
+
function escapeHtmlAttr(input) {
|
|
213
|
+
return escapeHtml(input);
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=file-viewer-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-viewer-utils.js","sourceRoot":"","sources":["../src/file-viewer-utils.ts"],"names":[],"mappings":"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"}
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Plugin-internal unified logger (Task #2961).
|
|
3
|
+
*
|
|
4
|
+
* User feedback: "用 logger.info 走日志系统,不污染 stdout/stderr".
|
|
5
|
+
*
|
|
6
|
+
* The plugin is intentionally zero-runtime-dep (no `@ai-setting/roy-agent-core`,
|
|
7
|
+
* no `pino`/`winston`). To route diagnostic output through a unified logger
|
|
8
|
+
* WITHOUT polluting stdout/stderr, we ship a small, self-contained logger
|
|
9
|
+
* here that mirrors the host agent's `createLogger` semantics:
|
|
10
|
+
*
|
|
11
|
+
* - `info` / `warn` / `error` / `debug` methods on every logger instance.
|
|
12
|
+
* - Quiet-by-default mode: when `quietMode === true` (the default), all
|
|
13
|
+
* log calls go to a per-category log file under
|
|
14
|
+
* `${XDG_DATA_HOME:-~/.local/share}/roy-agent/logs/roy-plugin-task-show-<name>.log`,
|
|
15
|
+
* and DO NOT touch `console.log` / `console.warn` / `console.error`.
|
|
16
|
+
* - Console output is only enabled when an operator explicitly calls
|
|
17
|
+
* `setQuietMode(false)`. The env var `TASK_SHOW_DEBUG=1` no longer
|
|
18
|
+
* re-enables console output — it is intentionally ignored (was the
|
|
19
|
+
* previous band-aid in Task #2953).
|
|
20
|
+
* - The log file path is created on first write (mkdir -p).
|
|
21
|
+
* - Writes are synchronous append (`appendFileSync`) so the log is
|
|
22
|
+
* durable even when the process exits immediately (subprocess shutdown).
|
|
23
|
+
*
|
|
24
|
+
* Why a new file instead of inlining in `plugin.ts`? Tests pin the
|
|
25
|
+
* `createPluginLogger` symbol (RED-3 / RED-4 / RED-5) and the chat
|
|
26
|
+
* subprocess test imports it from `../src/logger.js`. Co-locating the
|
|
27
|
+
* logger means `server.ts`, `collector.ts`, and `plugin.ts` all import
|
|
28
|
+
* the same `createPluginLogger` — one sink, one set of gates.
|
|
29
|
+
*
|
|
30
|
+
* NOTE: this logger is intentionally minimal. If the host agent ever
|
|
31
|
+
* exposes a `createPluginLogger` factory in `@ai-setting/roy-agent-core`,
|
|
32
|
+
* this file can be replaced with a thin re-export.
|
|
33
|
+
*/
|
|
34
|
+
export declare function isQuietMode(): boolean;
|
|
35
|
+
export declare function setQuietMode(enabled: boolean): void;
|
|
36
|
+
export interface PluginLogger {
|
|
37
|
+
/** Log to file unconditionally. Console only when un-quiet. */
|
|
38
|
+
info(message: string, data?: unknown): void;
|
|
39
|
+
warn(message: string, data?: unknown): void;
|
|
40
|
+
error(message: string, data?: unknown): void;
|
|
41
|
+
debug(message: string, data?: unknown): void;
|
|
42
|
+
/**
|
|
43
|
+
* Whether this logger currently routes to the console. True when
|
|
44
|
+
* quiet mode is off AND the env says console output is allowed.
|
|
45
|
+
* Tests use this to assert the default behaviour without spying on
|
|
46
|
+
* `console.*` directly.
|
|
47
|
+
*/
|
|
48
|
+
readonly consoleEnabled: boolean;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Create (or fetch from cache) a plugin logger under the given category
|
|
52
|
+
* prefix. The prefix appears in the log file name and in each formatted
|
|
53
|
+
* line so operators can grep for it.
|
|
54
|
+
*
|
|
55
|
+
* @param prefix A short identifier for the subsystem
|
|
56
|
+
* (e.g. `"plugin"`, `"server"`, `"collector"`).
|
|
57
|
+
*/
|
|
58
|
+
export declare function createPluginLogger(prefix: string): PluginLogger;
|
|
59
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAqBH,wBAAgB,WAAW,IAAI,OAAO,CAErC;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAEnD;AAgDD,MAAM,WAAW,YAAY;IAC3B,+DAA+D;IAC/D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7C;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;CAClC;AA+CD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CA4C/D"}
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Plugin-internal unified logger (Task #2961).
|
|
3
|
+
*
|
|
4
|
+
* User feedback: "用 logger.info 走日志系统,不污染 stdout/stderr".
|
|
5
|
+
*
|
|
6
|
+
* The plugin is intentionally zero-runtime-dep (no `@ai-setting/roy-agent-core`,
|
|
7
|
+
* no `pino`/`winston`). To route diagnostic output through a unified logger
|
|
8
|
+
* WITHOUT polluting stdout/stderr, we ship a small, self-contained logger
|
|
9
|
+
* here that mirrors the host agent's `createLogger` semantics:
|
|
10
|
+
*
|
|
11
|
+
* - `info` / `warn` / `error` / `debug` methods on every logger instance.
|
|
12
|
+
* - Quiet-by-default mode: when `quietMode === true` (the default), all
|
|
13
|
+
* log calls go to a per-category log file under
|
|
14
|
+
* `${XDG_DATA_HOME:-~/.local/share}/roy-agent/logs/roy-plugin-task-show-<name>.log`,
|
|
15
|
+
* and DO NOT touch `console.log` / `console.warn` / `console.error`.
|
|
16
|
+
* - Console output is only enabled when an operator explicitly calls
|
|
17
|
+
* `setQuietMode(false)`. The env var `TASK_SHOW_DEBUG=1` no longer
|
|
18
|
+
* re-enables console output — it is intentionally ignored (was the
|
|
19
|
+
* previous band-aid in Task #2953).
|
|
20
|
+
* - The log file path is created on first write (mkdir -p).
|
|
21
|
+
* - Writes are synchronous append (`appendFileSync`) so the log is
|
|
22
|
+
* durable even when the process exits immediately (subprocess shutdown).
|
|
23
|
+
*
|
|
24
|
+
* Why a new file instead of inlining in `plugin.ts`? Tests pin the
|
|
25
|
+
* `createPluginLogger` symbol (RED-3 / RED-4 / RED-5) and the chat
|
|
26
|
+
* subprocess test imports it from `../src/logger.js`. Co-locating the
|
|
27
|
+
* logger means `server.ts`, `collector.ts`, and `plugin.ts` all import
|
|
28
|
+
* the same `createPluginLogger` — one sink, one set of gates.
|
|
29
|
+
*
|
|
30
|
+
* NOTE: this logger is intentionally minimal. If the host agent ever
|
|
31
|
+
* exposes a `createPluginLogger` factory in `@ai-setting/roy-agent-core`,
|
|
32
|
+
* this file can be replaced with a thin re-export.
|
|
33
|
+
*/
|
|
34
|
+
import * as fs from "node:fs";
|
|
35
|
+
import * as os from "node:os";
|
|
36
|
+
import * as path from "node:path";
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// Module-level quiet mode (mirrors @ai-setting/roy-agent-core quietMode)
|
|
39
|
+
// ============================================================================
|
|
40
|
+
/**
|
|
41
|
+
* Quiet mode = "log to file only, do not touch console". When false, log
|
|
42
|
+
* calls also forward to the matching `console.*` method.
|
|
43
|
+
*
|
|
44
|
+
* Defaults to `true` so the plugin never pollutes the parent process's
|
|
45
|
+
* stdout / stderr unless the operator explicitly asks for it. The env
|
|
46
|
+
* var `TASK_SHOW_DEBUG=1` (legacy band-aid from Task #2953) is ignored
|
|
47
|
+
* — see `envSaysConsoleEnabled()` for the rationale.
|
|
48
|
+
*/
|
|
49
|
+
let quietMode = true;
|
|
50
|
+
export function isQuietMode() {
|
|
51
|
+
return quietMode;
|
|
52
|
+
}
|
|
53
|
+
export function setQuietMode(enabled) {
|
|
54
|
+
quietMode = enabled;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* v2.5.12 (Task #2961): the env flags are intentionally NOT consulted.
|
|
58
|
+
* The user's explicit feedback was "用 logger.info 走日志系统,不污染
|
|
59
|
+
* stdout/stderr". Honouring that means the default behaviour must NEVER
|
|
60
|
+
* touch console, regardless of legacy debug env vars. Operators who
|
|
61
|
+
* want console output for live debugging should call `setQuietMode(false)`
|
|
62
|
+
* directly — this keeps the env variable surface narrow and predictable.
|
|
63
|
+
*/
|
|
64
|
+
function envSaysConsoleEnabled() {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
// ============================================================================
|
|
68
|
+
// Log file path
|
|
69
|
+
// ============================================================================
|
|
70
|
+
/**
|
|
71
|
+
* Resolve the log directory every call so tests that flip
|
|
72
|
+
* `XDG_DATA_HOME` between cases pick up the new path. The cost is a
|
|
73
|
+
* couple of `path.join` calls per log write — negligible.
|
|
74
|
+
*/
|
|
75
|
+
function getLogDir() {
|
|
76
|
+
const xdg = process.env?.XDG_DATA_HOME;
|
|
77
|
+
const base = xdg && xdg.length > 0
|
|
78
|
+
? xdg
|
|
79
|
+
: path.join(os.homedir(), ".local", "share");
|
|
80
|
+
const dir = path.join(base, "roy-agent", "logs");
|
|
81
|
+
try {
|
|
82
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// ignore — fall through; writeToFile will no-op on error.
|
|
86
|
+
}
|
|
87
|
+
return dir;
|
|
88
|
+
}
|
|
89
|
+
function getLogFilePath(category) {
|
|
90
|
+
// Sanitize the category to avoid path traversal — keep alnum + dash/underscore.
|
|
91
|
+
const safe = category.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
|
|
92
|
+
return path.join(getLogDir(), `roy-plugin-task-show-${safe}.log`);
|
|
93
|
+
}
|
|
94
|
+
const TAG = "roy-plugin-task-show";
|
|
95
|
+
function formatLine(level, prefix, message, data) {
|
|
96
|
+
const ts = new Date().toISOString();
|
|
97
|
+
const tail = data !== undefined ? ` ${safeStringify(data)}` : "";
|
|
98
|
+
return `${ts} [${level.toUpperCase()}] [${TAG}:${prefix}] ${message}${tail}`;
|
|
99
|
+
}
|
|
100
|
+
function safeStringify(data) {
|
|
101
|
+
try {
|
|
102
|
+
const s = JSON.stringify(data);
|
|
103
|
+
return s === undefined ? String(data) : s;
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return String(data);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function writeToFile(line, category) {
|
|
110
|
+
try {
|
|
111
|
+
const file = getLogFilePath(category);
|
|
112
|
+
fs.appendFileSync(file, line + "\n", "utf-8");
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// Swallow — the plugin must never crash on log I/O.
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Compute the current console-enabled state. Called on every log
|
|
120
|
+
* call so that toggling `quietMode` at runtime takes effect immediately.
|
|
121
|
+
*/
|
|
122
|
+
function computeConsoleEnabled() {
|
|
123
|
+
return !quietMode || envSaysConsoleEnabled();
|
|
124
|
+
}
|
|
125
|
+
// ============================================================================
|
|
126
|
+
// Logger factory
|
|
127
|
+
// ============================================================================
|
|
128
|
+
const cache = new Map();
|
|
129
|
+
/**
|
|
130
|
+
* Create (or fetch from cache) a plugin logger under the given category
|
|
131
|
+
* prefix. The prefix appears in the log file name and in each formatted
|
|
132
|
+
* line so operators can grep for it.
|
|
133
|
+
*
|
|
134
|
+
* @param prefix A short identifier for the subsystem
|
|
135
|
+
* (e.g. `"plugin"`, `"server"`, `"collector"`).
|
|
136
|
+
*/
|
|
137
|
+
export function createPluginLogger(prefix) {
|
|
138
|
+
const cached = cache.get(prefix);
|
|
139
|
+
if (cached)
|
|
140
|
+
return cached;
|
|
141
|
+
const logger = {
|
|
142
|
+
get consoleEnabled() {
|
|
143
|
+
return computeConsoleEnabled();
|
|
144
|
+
},
|
|
145
|
+
info(message, data) {
|
|
146
|
+
const line = formatLine("info", prefix, message, data);
|
|
147
|
+
writeToFile(line, prefix);
|
|
148
|
+
if (computeConsoleEnabled()) {
|
|
149
|
+
// eslint-disable-next-line no-console
|
|
150
|
+
console.log(line);
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
warn(message, data) {
|
|
154
|
+
const line = formatLine("warn", prefix, message, data);
|
|
155
|
+
writeToFile(line, prefix);
|
|
156
|
+
if (computeConsoleEnabled()) {
|
|
157
|
+
// eslint-disable-next-line no-console
|
|
158
|
+
console.warn(line);
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
error(message, data) {
|
|
162
|
+
const line = formatLine("error", prefix, message, data);
|
|
163
|
+
writeToFile(line, prefix);
|
|
164
|
+
if (computeConsoleEnabled()) {
|
|
165
|
+
// eslint-disable-next-line no-console
|
|
166
|
+
console.error(line);
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
debug(message, data) {
|
|
170
|
+
const line = formatLine("debug", prefix, message, data);
|
|
171
|
+
writeToFile(line, prefix);
|
|
172
|
+
if (computeConsoleEnabled()) {
|
|
173
|
+
// eslint-disable-next-line no-console
|
|
174
|
+
console.debug(line);
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
cache.set(prefix, logger);
|
|
179
|
+
return logger;
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Mermaid zoom-stage guardian (v2.5.12 / Task #2951).
|
|
3
|
+
*
|
|
4
|
+
* Why this module exists
|
|
5
|
+
* ----------------------
|
|
6
|
+
* The Mermaid controller (`public/mermaid-renderer.js`) wraps every
|
|
7
|
+
* freshly-rendered SVG in a `.mermaid-zoom-stage` div that carries
|
|
8
|
+
* the pan/zoom CSS transform and the pointer-drag handlers. Without
|
|
9
|
+
* that wrapper the user can neither pan nor zoom the lifecycle
|
|
10
|
+
* diagram.
|
|
11
|
+
*
|
|
12
|
+
* Mermaid's library auto-runs `mermaid.run({nodes})` once
|
|
13
|
+
* `startOnLoad: true` is set in `mermaid.initialize`. We DO set
|
|
14
|
+
* `startOnLoad: true` in the SSR template (server.ts line ~2041) so
|
|
15
|
+
* the initial paint works for hosts that don't ship our controller.
|
|
16
|
+
* The auto-runner scans the DOM for `.mermaid` elements that are not
|
|
17
|
+
* already marked `data-processed` and replaces their innerHTML with a
|
|
18
|
+
* freshly-rendered SVG.
|
|
19
|
+
*
|
|
20
|
+
* Even after our controller's `update()` has set `data-processed="true"`
|
|
21
|
+
* and wrapped the SVG, Mermaid's auto-runner fires roughly 1.5–3s
|
|
22
|
+
* later (observed via Playwright trace on 2026-08-12) and replaces
|
|
23
|
+
* the contents of the `.mermaid` element with a fresh, bare SVG. The
|
|
24
|
+
* bare SVG is not wrapped in `.mermaid-zoom-stage`, so the pan/zoom
|
|
25
|
+
* UI silently breaks for the rest of the page lifetime.
|
|
26
|
+
*
|
|
27
|
+
* What this module does
|
|
28
|
+
* ---------------------
|
|
29
|
+
* `ensureMermaidZoomStage(container)` — one-shot helper that walks
|
|
30
|
+
* the container and wraps any existing `<svg>` in a
|
|
31
|
+
* `.mermaid-zoom-stage` div. Idempotent: calling it twice keeps ONE
|
|
32
|
+
* wrapper.
|
|
33
|
+
*
|
|
34
|
+
* `MermaidZoomGuardian` — a long-lived `MutationObserver` that
|
|
35
|
+
* detects when something (Mermaid's late `mermaid.run()`, an SSE
|
|
36
|
+
* patch, etc.) replaces the SVG and re-applies the wrapper. The
|
|
37
|
+
* guardian stops observing after `stop()` is called.
|
|
38
|
+
*
|
|
39
|
+
* Both helpers are deliberately framework-free so they can be unit-
|
|
40
|
+
* tested in happy-dom / jsdom without a full browser.
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* Wrap any existing SVG inside `container` in a single
|
|
44
|
+
* `.mermaid-zoom-stage` div. Idempotent.
|
|
45
|
+
*
|
|
46
|
+
* @returns the (possibly newly-created) stage element, or `null`
|
|
47
|
+
* when the container has no SVG to wrap.
|
|
48
|
+
*/
|
|
49
|
+
export declare function ensureMermaidZoomStage(container: Element): HTMLElement | null;
|
|
50
|
+
/**
|
|
51
|
+
* v2.5.12 (Task #2951): the MermaidZoomGuardian watches a container
|
|
52
|
+
* and re-wraps any replacement SVG in `.mermaid-zoom-stage`. Use
|
|
53
|
+
* this for diagrams that survive past the initial render — e.g. the
|
|
54
|
+
* lifecycle mermaid on the per-task page, where Mermaid's late
|
|
55
|
+
* `mermaid.run()` otherwise destroys the wrapper.
|
|
56
|
+
*
|
|
57
|
+
* Lifecycle:
|
|
58
|
+
* const g = new MermaidZoomGuardian(container);
|
|
59
|
+
* g.start();
|
|
60
|
+
* // ...later, when the page is torn down:
|
|
61
|
+
* g.stop();
|
|
62
|
+
*/
|
|
63
|
+
export declare class MermaidZoomGuardian {
|
|
64
|
+
private readonly container;
|
|
65
|
+
private observer;
|
|
66
|
+
private scheduled;
|
|
67
|
+
constructor(container: Element);
|
|
68
|
+
start(): void;
|
|
69
|
+
stop(): void;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Export the constants so tests can reference them without hardcoding.
|
|
73
|
+
*/
|
|
74
|
+
export declare const MERMAID_STAGE_CLASS = "mermaid-zoom-stage";
|
|
75
|
+
export declare const MERMAID_TOOLBAR_CLASS = "mermaid-zoom-toolbar";
|
|
76
|
+
export declare const MERMAID_CONTAINER_CLASS = "mermaid";
|
|
77
|
+
//# sourceMappingURL=mermaid-zoom-guardian.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mermaid-zoom-guardian.d.ts","sourceRoot":"","sources":["../src/mermaid-zoom-guardian.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAgBH;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,OAAO,GAAG,WAAW,GAAG,IAAI,CAsC7E;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAU;IACpC,OAAO,CAAC,QAAQ,CAAiC;IACjD,OAAO,CAAC,SAAS,CAAS;gBAEd,SAAS,EAAE,OAAO;IAI9B,KAAK,IAAI,IAAI;IAwCb,IAAI,IAAI,IAAI;CAOb;AAED;;GAEG;AACH,eAAO,MAAM,mBAAmB,uBAAc,CAAC;AAC/C,eAAO,MAAM,qBAAqB,yBAAgB,CAAC;AACnD,eAAO,MAAM,uBAAuB,YAAgB,CAAC"}
|