dsh-better-display 0.3.7 → 0.3.9

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 (208) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/DESIGN.md +43 -3
  3. package/README.en.md +9 -1
  4. package/README.md +9 -1
  5. package/docs/dependency-update-policy.md +9 -0
  6. package/docs/extension-points.md +172 -0
  7. package/docs/official-rendering-bridge.md +59 -18
  8. package/docs/openapi.yaml +147 -0
  9. package/docs/runbooks/blank-reader.md +39 -0
  10. package/lib/client.js +1629 -586
  11. package/lib/client.js.map +1 -1
  12. package/lib/dsh-better-display.js +535 -57
  13. package/lib/types/client/AssistantNode.d.ts +20 -0
  14. package/lib/types/client/AssistantNode.d.ts.map +1 -0
  15. package/lib/types/client/AssistantNode.js +46 -0
  16. package/lib/types/client/AssistantNode.js.map +1 -0
  17. package/lib/types/client/Blocks.d.ts.map +1 -1
  18. package/lib/types/client/Blocks.js +33 -10
  19. package/lib/types/client/Blocks.js.map +1 -1
  20. package/lib/types/client/ChoreographedFlow.d.ts.map +1 -1
  21. package/lib/types/client/ChoreographedFlow.js +2 -0
  22. package/lib/types/client/ChoreographedFlow.js.map +1 -1
  23. package/lib/types/client/ClosedProcessSummary.d.ts.map +1 -1
  24. package/lib/types/client/ClosedProcessSummary.js +3 -1
  25. package/lib/types/client/ClosedProcessSummary.js.map +1 -1
  26. package/lib/types/client/McpAppFrame.d.ts.map +1 -1
  27. package/lib/types/client/McpAppFrame.js +5 -11
  28. package/lib/types/client/McpAppFrame.js.map +1 -1
  29. package/lib/types/client/OfficialContent.d.ts +17 -3
  30. package/lib/types/client/OfficialContent.d.ts.map +1 -1
  31. package/lib/types/client/OfficialContent.js +33 -7
  32. package/lib/types/client/OfficialContent.js.map +1 -1
  33. package/lib/types/client/Reader.d.ts.map +1 -1
  34. package/lib/types/client/Reader.js +76 -73
  35. package/lib/types/client/Reader.js.map +1 -1
  36. package/lib/types/client/SettingsSection.d.ts.map +1 -1
  37. package/lib/types/client/SettingsSection.js +0 -5
  38. package/lib/types/client/SettingsSection.js.map +1 -1
  39. package/lib/types/client/StickyLane.d.ts +12 -0
  40. package/lib/types/client/StickyLane.d.ts.map +1 -1
  41. package/lib/types/client/StickyLane.js +78 -2
  42. package/lib/types/client/StickyLane.js.map +1 -1
  43. package/lib/types/client/ToolActivity.d.ts.map +1 -1
  44. package/lib/types/client/ToolActivity.js +10 -5
  45. package/lib/types/client/ToolActivity.js.map +1 -1
  46. package/lib/types/client/deliverables.d.ts.map +1 -1
  47. package/lib/types/client/deliverables.js +2 -0
  48. package/lib/types/client/deliverables.js.map +1 -1
  49. package/lib/types/client/index.d.ts +3 -1
  50. package/lib/types/client/index.d.ts.map +1 -1
  51. package/lib/types/client/index.js +15 -1
  52. package/lib/types/client/index.js.map +1 -1
  53. package/lib/types/client/live-turn.d.ts +17 -5
  54. package/lib/types/client/live-turn.d.ts.map +1 -1
  55. package/lib/types/client/live-turn.js +106 -76
  56. package/lib/types/client/live-turn.js.map +1 -1
  57. package/lib/types/client/motion.d.ts +26 -2
  58. package/lib/types/client/motion.d.ts.map +1 -1
  59. package/lib/types/client/motion.js +144 -10
  60. package/lib/types/client/motion.js.map +1 -1
  61. package/lib/types/client/official-slots.d.ts +9 -0
  62. package/lib/types/client/official-slots.d.ts.map +1 -1
  63. package/lib/types/client/official-slots.js +22 -4
  64. package/lib/types/client/official-slots.js.map +1 -1
  65. package/lib/types/client/open-file.d.ts.map +1 -1
  66. package/lib/types/client/open-file.js +1 -6
  67. package/lib/types/client/open-file.js.map +1 -1
  68. package/lib/types/client/projection.d.ts +15 -2
  69. package/lib/types/client/projection.d.ts.map +1 -1
  70. package/lib/types/client/projection.js +16 -4
  71. package/lib/types/client/projection.js.map +1 -1
  72. package/lib/types/client/rewind-cut.d.ts +131 -0
  73. package/lib/types/client/rewind-cut.d.ts.map +1 -0
  74. package/lib/types/client/rewind-cut.js +321 -0
  75. package/lib/types/client/rewind-cut.js.map +1 -0
  76. package/lib/types/client/styles.d.ts +3 -0
  77. package/lib/types/client/styles.d.ts.map +1 -0
  78. package/lib/types/client/styles.js +25 -0
  79. package/lib/types/client/styles.js.map +1 -0
  80. package/lib/types/client/text-transform.d.ts +93 -0
  81. package/lib/types/client/text-transform.d.ts.map +1 -0
  82. package/lib/types/client/text-transform.js +183 -0
  83. package/lib/types/client/text-transform.js.map +1 -0
  84. package/lib/types/client/timeline.d.ts.map +1 -1
  85. package/lib/types/client/timeline.js.map +1 -1
  86. package/lib/types/client/tool-activity.d.ts +11 -4
  87. package/lib/types/client/tool-activity.d.ts.map +1 -1
  88. package/lib/types/client/tool-activity.js +54 -9
  89. package/lib/types/client/tool-activity.js.map +1 -1
  90. package/lib/types/client/turn-slots.d.ts +29 -0
  91. package/lib/types/client/turn-slots.d.ts.map +1 -0
  92. package/lib/types/client/turn-slots.js +62 -0
  93. package/lib/types/client/turn-slots.js.map +1 -0
  94. package/lib/types/client/types.d.ts +18 -0
  95. package/lib/types/client/types.d.ts.map +1 -1
  96. package/lib/types/dsh-better-display.d.ts +4 -1
  97. package/lib/types/dsh-better-display.d.ts.map +1 -1
  98. package/lib/types/dsh-better-display.js +55 -43
  99. package/lib/types/dsh-better-display.js.map +1 -1
  100. package/lib/types/flags.d.ts +7 -0
  101. package/lib/types/flags.d.ts.map +1 -0
  102. package/lib/types/flags.js +37 -0
  103. package/lib/types/flags.js.map +1 -0
  104. package/lib/types/log.d.ts +17 -0
  105. package/lib/types/log.d.ts.map +1 -0
  106. package/lib/types/log.js +122 -0
  107. package/lib/types/log.js.map +1 -0
  108. package/lib/types/metrics.d.ts +18 -0
  109. package/lib/types/metrics.d.ts.map +1 -0
  110. package/lib/types/metrics.js +35 -0
  111. package/lib/types/metrics.js.map +1 -0
  112. package/lib/types/reveal.d.ts +12 -0
  113. package/lib/types/reveal.d.ts.map +1 -0
  114. package/lib/types/reveal.js +147 -0
  115. package/lib/types/reveal.js.map +1 -0
  116. package/lib/types/workspace-path.d.ts +3 -0
  117. package/lib/types/workspace-path.d.ts.map +1 -0
  118. package/lib/types/workspace-path.js +5 -0
  119. package/lib/types/workspace-path.js.map +1 -0
  120. package/package.json +36 -5
  121. package/scripts/changelog.mjs +20 -0
  122. package/scripts/check-agents-md.mjs +67 -0
  123. package/scripts/check-bundle-size.mjs +38 -0
  124. package/scripts/check-dead-flags.mjs +55 -0
  125. package/scripts/check-todo-debt.mjs +69 -0
  126. package/scripts/security-audit-report.mjs +92 -0
  127. package/scripts/verify-commentary-visible.mjs +627 -0
  128. package/src/client/AssistantNode.tsx +72 -0
  129. package/src/client/Blocks.tsx +49 -10
  130. package/src/client/ChoreographedFlow.tsx +3 -1
  131. package/src/client/ClosedProcessSummary.tsx +3 -1
  132. package/src/client/McpAppFrame.tsx +5 -13
  133. package/src/client/OfficialContent.tsx +47 -9
  134. package/src/client/Reader.module.css +103 -21
  135. package/src/client/Reader.tsx +83 -88
  136. package/src/client/SettingsSection.tsx +1 -7
  137. package/src/client/StickyLane.tsx +81 -2
  138. package/src/client/ToolActivity.tsx +10 -6
  139. package/src/client/deliverables.ts +1 -0
  140. package/src/client/index.tsx +18 -2
  141. package/src/client/live-turn.ts +100 -69
  142. package/src/client/motion.tsx +142 -7
  143. package/src/client/official-slots.tsx +23 -4
  144. package/src/client/open-file.ts +2 -8
  145. package/src/client/projection.ts +16 -4
  146. package/src/client/rewind-cut.ts +340 -0
  147. package/src/client/styles.ts +22 -0
  148. package/src/client/text-transform.ts +265 -0
  149. package/src/client/timeline.ts +0 -2
  150. package/src/client/tool-activity.ts +67 -9
  151. package/src/client/turn-slots.ts +80 -0
  152. package/src/client/types.ts +19 -0
  153. package/src/dsh-better-display.ts +57 -42
  154. package/src/flags.ts +40 -0
  155. package/src/log.ts +137 -0
  156. package/src/metrics.ts +50 -0
  157. package/src/reveal.ts +90 -0
  158. package/src/workspace-path.ts +4 -0
  159. package/tests/browser-commentary.tsx +191 -0
  160. package/tests/chat-flow-hook.test.ts +1 -1
  161. package/tests/cloud-gate.test.ts +1 -1
  162. package/tests/context-tool-update.test.ts +1 -1
  163. package/tests/conversation-scroll.test.ts +1 -1
  164. package/tests/deliverables.test.ts +1 -1
  165. package/tests/entry.test.ts +1 -1
  166. package/tests/fixtures/reader-lane-owners.tsx +46 -0
  167. package/tests/fixtures/reader-official-users.tsx +234 -0
  168. package/tests/fixtures/reader-theme-overlap.html +80 -0
  169. package/tests/fixtures/reader-user-bubble.tsx +222 -0
  170. package/tests/flags.test.ts +66 -0
  171. package/tests/fold-choreography.test.ts +32 -8
  172. package/tests/fold-intensity.test.ts +11 -6
  173. package/tests/footer-precedence.test.ts +9 -1
  174. package/tests/history-word-regression.test.ts +1 -1
  175. package/tests/live-turn.test.ts +125 -54
  176. package/tests/log.test.ts +99 -0
  177. package/tests/mcp-apps.test.ts +1 -1
  178. package/tests/message-chrome.test.ts +1 -1
  179. package/tests/metrics.test.ts +52 -0
  180. package/tests/official-slots.test.ts +41 -2
  181. package/tests/open-file.test.ts +1 -1
  182. package/tests/pending-submission.test.ts +12 -1
  183. package/tests/pinned-selection.test.ts +16 -0
  184. package/tests/platform-media.test.ts +1 -1
  185. package/tests/projection.test.ts +66 -3
  186. package/tests/reader-glass-css.test.ts +28 -0
  187. package/tests/reader-official-users.test.ts +91 -0
  188. package/tests/reader-sticky-surface.test.ts +377 -0
  189. package/tests/reader-theme-lanes.test.ts +54 -0
  190. package/tests/reader-theme-overlap.test.ts +151 -0
  191. package/tests/reader-user-bubble.test.ts +137 -0
  192. package/tests/reasoning-follow.test.ts +1 -1
  193. package/tests/retiring-content.test.ts +229 -0
  194. package/tests/reveal.test.ts +128 -0
  195. package/tests/rewind-cut.test.ts +1090 -0
  196. package/tests/settings-copy.test.ts +1 -1
  197. package/tests/skill-status.test.ts +1 -1
  198. package/tests/stock-install.test.ts +9 -1
  199. package/tests/stream-buffer.test.ts +1 -1
  200. package/tests/styles.test.ts +75 -0
  201. package/tests/terminal-input-preview.test.ts +113 -0
  202. package/tests/text-transform.test.ts +272 -0
  203. package/tests/timeline.test.ts +1 -1
  204. package/tests/tool-clock.test.ts +1 -1
  205. package/tests/tool-diff.test.ts +1 -1
  206. package/tests/waiting-clock.test.ts +1 -1
  207. package/tests/word-timeline.test.ts +1 -1
  208. package/tsdown.config.ts +49 -1
@@ -1,9 +1,489 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, isAbsolute, join, posix, resolve, win32 } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { randomUUID } from "node:crypto";
1
5
  import { spawn } from "node:child_process";
2
- import { scanGenerativeMcpappsStatus } from "./skill-roots.ts";
3
- import { skillsFromListResult, toPublicSkillStatus } from "./skill-status.ts";
6
+ import { access, readdir } from "node:fs/promises";
7
+ import { homedir } from "node:os";
8
+ //#region src/flags.ts
9
+ const defaultFlags = { readerEnabled: true };
10
+ function locateFlags(start = dirname(fileURLToPath(import.meta.url))) {
11
+ let dir = start;
12
+ for (let depth = 0; depth < 6; depth += 1) {
13
+ const candidate = resolve(dir, "flags.json");
14
+ if (existsSync(candidate)) return candidate;
15
+ const parent = dirname(dir);
16
+ if (parent === dir) return void 0;
17
+ dir = parent;
18
+ }
19
+ }
20
+ function normalize(value) {
21
+ const record = value !== null && typeof value === "object" ? value : {};
22
+ return { readerEnabled: typeof record.readerEnabled === "boolean" ? record.readerEnabled : defaultFlags.readerEnabled };
23
+ }
24
+ function loadFlags(source) {
25
+ if (source !== void 0 && typeof source !== "string") return normalize(source);
26
+ try {
27
+ const path = typeof source === "string" ? source : locateFlags();
28
+ if (path === void 0 || !existsSync(path)) return { ...defaultFlags };
29
+ return normalize(JSON.parse(readFileSync(path, "utf8")));
30
+ } catch {
31
+ return { ...defaultFlags };
32
+ }
33
+ }
34
+ //#endregion
35
+ //#region src/log.ts
36
+ const REDACTED = "[REDACTED]";
37
+ const REQUEST_ID_HEADER = "x-request-id";
38
+ const REQUEST_ID_PATTERN = /^[A-Za-z0-9._:-]{1,200}$/;
39
+ const SECRET_KEYS = /* @__PURE__ */ new Set([
40
+ "authorization",
41
+ "proxyauthorization",
42
+ "cookie",
43
+ "setcookie",
44
+ "token",
45
+ "accesstoken",
46
+ "refreshtoken",
47
+ "idtoken",
48
+ "apikey",
49
+ "apisecret",
50
+ "secret",
51
+ "clientsecret",
52
+ "password",
53
+ "passwd",
54
+ "credential",
55
+ "credentials",
56
+ "signature",
57
+ "sig",
58
+ "jwt",
59
+ "bearer",
60
+ "privatekey",
61
+ "xapikey",
62
+ "xauthtoken",
63
+ "xaccesstoken",
64
+ "auth"
65
+ ]);
66
+ function normalizeKey(key) {
67
+ return key.toLowerCase().replace(/[^a-z0-9]/g, "");
68
+ }
69
+ function isSecretKey(key) {
70
+ const normalized = normalizeKey(key);
71
+ if (SECRET_KEYS.has(normalized)) return true;
72
+ if (normalized === "requestid" || normalized === "xrequestid") return false;
73
+ return /(?:^|[^a-z])(?:token|secret|password|api[-_]?key)$/i.test(key);
74
+ }
75
+ function redactString(value) {
76
+ return value.replace(/([?&#])([^&#=]+)=([^&#]*)/g, (match, sep, rawKey) => {
77
+ let key;
78
+ try {
79
+ key = decodeURIComponent(rawKey.replace(/\+/g, " "));
80
+ } catch {
81
+ key = rawKey;
82
+ }
83
+ return isSecretKey(key) ? `${sep}${rawKey}=${REDACTED}` : match;
84
+ }).replace(/\b(Bearer|Basic)\s+\S+/gi, `$1 ${REDACTED}`).replace(/\b((?:access[_-]?token|refresh[_-]?token|id[_-]?token|api[_-]?key|client[_-]?secret|password|passwd|authorization|token|secret))(\s*[:=]\s*)([^\s&]+)/gi, `$1$2${REDACTED}`);
85
+ }
86
+ function redact(value) {
87
+ return redactValue(void 0, value, 0, /* @__PURE__ */ new WeakSet());
88
+ }
89
+ function redactValue(key, value, depth, seen) {
90
+ if (key !== void 0 && isSecretKey(key)) return REDACTED;
91
+ if (depth > 8) return REDACTED;
92
+ if (typeof value === "string") return redactString(value);
93
+ if (typeof value === "number" || typeof value === "boolean" || value === null || value === void 0) return value;
94
+ if (typeof value !== "object") return REDACTED;
95
+ if (value instanceof Date) return value.toISOString();
96
+ if (value instanceof Error) return redactValue(void 0, {
97
+ name: value.name,
98
+ message: value.message
99
+ }, depth + 1, seen);
100
+ if (seen.has(value)) return REDACTED;
101
+ seen.add(value);
102
+ if (Array.isArray(value)) return value.map((item) => redactValue(void 0, item, depth + 1, seen));
103
+ const output = {};
104
+ for (const [childKey, childValue] of Object.entries(value)) output[childKey] = redactValue(childKey, childValue, depth + 1, seen);
105
+ return output;
106
+ }
107
+ function createLogger(options = {}) {
108
+ const write = options.write ?? ((line) => {
109
+ console.log(line);
110
+ });
111
+ const clock = options.clock ?? (() => /* @__PURE__ */ new Date());
112
+ const emit = (level, msg, fields) => {
113
+ const details = redact({
114
+ ...options.base,
115
+ ...fields
116
+ });
117
+ const record = {
118
+ ...details !== null && typeof details === "object" ? details : {},
119
+ time: clock().toISOString(),
120
+ level,
121
+ msg: redactString(msg)
122
+ };
123
+ try {
124
+ write(JSON.stringify(record));
125
+ } catch {
126
+ write(JSON.stringify({
127
+ time: record.time,
128
+ level,
129
+ msg: record.msg,
130
+ error: "log record was not serializable"
131
+ }));
132
+ }
133
+ };
134
+ return {
135
+ info: (msg, fields) => emit("info", msg, fields),
136
+ warn: (msg, fields) => emit("warn", msg, fields),
137
+ error: (msg, fields) => emit("error", msg, fields)
138
+ };
139
+ }
140
+ function requestIdOf(req) {
141
+ const raw = req.headers[REQUEST_ID_HEADER];
142
+ const value = Array.isArray(raw) ? raw[0] : raw;
143
+ if (typeof value === "string" && REQUEST_ID_PATTERN.test(value)) return value;
144
+ return randomUUID();
145
+ }
146
+ //#endregion
147
+ //#region src/metrics.ts
148
+ const ROUTE_REVEAL = "/better-display/reveal";
149
+ const ROUTE_SKILL_STATUS = "/better-display/skill-status";
150
+ const ROUTE_METRICS = "/better-display/metrics";
151
+ const ROUTE_HITS = [
152
+ ROUTE_REVEAL,
153
+ ROUTE_SKILL_STATUS,
154
+ ROUTE_METRICS
155
+ ];
156
+ const hits = new Map(ROUTE_HITS.map((route) => [route, 0]));
157
+ const timings = new Map(ROUTE_HITS.map((route) => [route, {
158
+ count: 0,
159
+ totalMs: 0,
160
+ maxMs: 0
161
+ }]));
162
+ function snapshotHits() {
163
+ return Object.fromEntries(hits);
164
+ }
165
+ function snapshotTimings() {
166
+ return Object.fromEntries([...timings].map(([route, timing]) => [route, { ...timing }]));
167
+ }
168
+ function recordRouteHit(route) {
169
+ const next = (hits.get(route) ?? 0) + 1;
170
+ hits.set(route, next);
171
+ return next;
172
+ }
173
+ function recordRouteDuration(route, elapsedMs) {
174
+ const ms = Number.isFinite(elapsedMs) && elapsedMs > 0 ? Math.round(elapsedMs) : 0;
175
+ const current = timings.get(route) ?? {
176
+ count: 0,
177
+ totalMs: 0,
178
+ maxMs: 0
179
+ };
180
+ const next = {
181
+ count: current.count + 1,
182
+ totalMs: current.totalMs + ms,
183
+ maxMs: Math.max(current.maxMs, ms)
184
+ };
185
+ timings.set(route, next);
186
+ return next;
187
+ }
188
+ function metricsPayload() {
189
+ return {
190
+ counters: { dsh_better_display_route_hits_total: snapshotHits() },
191
+ timingsMs: snapshotTimings()
192
+ };
193
+ }
194
+ //#endregion
195
+ //#region src/workspace-path.ts
196
+ /** Absolute paths accepted by both POSIX and Windows hosts. */
197
+ function isAbsoluteWorkspacePath(path) {
198
+ return /^(?:\/|[A-Za-z]:[/\\]|\\\\)/.test(path);
199
+ }
200
+ //#endregion
201
+ //#region \0@oxc-project+runtime@0.154.0/helpers/esm/usingCtx.js
202
+ function _usingCtx() {
203
+ var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
204
+ var n = Error();
205
+ return n.name = "SuppressedError", n.error = r, n.suppressed = e, n;
206
+ }, e = {}, n = [];
207
+ function using(r, e) {
208
+ if (null != e) {
209
+ if (Object(e) !== e) throw new TypeError("using declarations can only be used with objects, functions, null, or undefined.");
210
+ if (r) var o = e[Symbol.asyncDispose || Symbol["for"]("Symbol.asyncDispose")];
211
+ if (void 0 === o && (o = e[Symbol.dispose || Symbol["for"]("Symbol.dispose")], r)) var t = o;
212
+ if ("function" != typeof o) throw new TypeError("Object is not disposable.");
213
+ t && (o = function o() {
214
+ try {
215
+ t.call(e);
216
+ } catch (r) {
217
+ return Promise.reject(r);
218
+ }
219
+ }), n.push({
220
+ v: e,
221
+ d: o,
222
+ a: r
223
+ });
224
+ } else r && n.push({
225
+ d: e,
226
+ a: r
227
+ });
228
+ return e;
229
+ }
230
+ return {
231
+ e,
232
+ u: using.bind(null, !1),
233
+ a: using.bind(null, !0),
234
+ d: function d() {
235
+ var o, t = this.e, s = 0;
236
+ function next() {
237
+ for (; o = n.pop();) try {
238
+ if (!o.a && 1 === s) return s = 0, n.push(o), Promise.resolve().then(next);
239
+ if (o.d) {
240
+ var r = o.d.call(o.v);
241
+ if (o.a) return s |= 2, Promise.resolve(r).then(next, err);
242
+ } else s |= 1;
243
+ } catch (r) {
244
+ return err(r);
245
+ }
246
+ if (1 === s) return t !== e ? Promise.reject(t) : Promise.resolve();
247
+ if (t !== e) throw t;
248
+ }
249
+ function err(n) {
250
+ return t = t !== e ? new r(n, t) : n, next();
251
+ }
252
+ return next();
253
+ }
254
+ };
255
+ }
256
+ //#endregion
257
+ //#region src/reveal.ts
258
+ async function revealRoots(ctx, sessionId) {
259
+ if (sessionId !== void 0) {
260
+ const cwd = (ctx.get?.("sessions"))?.get(sessionId)?.header.cwd;
261
+ if (cwd) return [cwd];
262
+ const query = ctx.get?.("sessionQuery");
263
+ if (query) try {
264
+ var _usingCtx$1 = _usingCtx();
265
+ const observation = _usingCtx$1.u(await query.observeSession(sessionId, { projectionMode: "none" }));
266
+ if (observation.header.cwd) return [observation.header.cwd];
267
+ } catch (_) {
268
+ _usingCtx$1.e = _;
269
+ } finally {
270
+ _usingCtx$1.d();
271
+ }
272
+ }
273
+ return ((ctx.get?.("workspaceRegistry"))?.list() ?? []).filter((workspace) => sessionId === void 0 || workspace.sessionIds.includes(sessionId)).map((workspace) => workspace.path);
274
+ }
275
+ function resolveRevealPath(path, roots, exists = existsSync) {
276
+ if (isAbsoluteWorkspacePath(path)) return path;
277
+ if (/^[A-Za-z]:|^\\/.test(path)) throw new Error("Path is not workspace-relative");
278
+ const matches = /* @__PURE__ */ new Set();
279
+ for (const root of roots) {
280
+ if (!isAbsoluteWorkspacePath(root)) continue;
281
+ const candidate = (/^[A-Za-z]:[/\\]|^\\\\/.test(root) ? win32 : posix).resolve(root, path);
282
+ if (exists(candidate)) matches.add(candidate);
283
+ }
284
+ if (matches.size !== 1) throw new Error(matches.size === 0 ? "Workspace path not found" : "Workspace path is ambiguous");
285
+ return [...matches][0];
286
+ }
287
+ function revealPath(path, platform = process.platform, spawnProcess = spawn) {
288
+ const options = {
289
+ detached: true,
290
+ stdio: "ignore"
291
+ };
292
+ if (platform === "darwin") spawnProcess("open", ["-R", path], options);
293
+ else if (platform === "win32") spawnProcess("explorer.exe", [`/select,"${path}"`], {
294
+ ...options,
295
+ windowsVerbatimArguments: true
296
+ });
297
+ else spawnProcess("xdg-open", [path], options);
298
+ }
299
+ function createRevealHandler({ roots, reveal = revealPath }) {
300
+ return (req, res) => {
301
+ if (req.method !== "POST") {
302
+ res.statusCode = 405;
303
+ res.end();
304
+ return;
305
+ }
306
+ let body = "";
307
+ req.on("data", (chunk) => {
308
+ body += chunk;
309
+ });
310
+ req.on("end", async () => {
311
+ res.setHeader("Content-Type", "application/json");
312
+ try {
313
+ const data = JSON.parse(body);
314
+ const path = typeof data === "object" && data !== null && "path" in data && typeof data.path === "string" ? data.path.trim() : "";
315
+ if (!path) throw new Error("Empty path");
316
+ const sessionId = typeof data === "object" && data !== null && "sessionId" in data && typeof data.sessionId === "string" ? data.sessionId : void 0;
317
+ reveal(resolveRevealPath(path, isAbsoluteWorkspacePath(path) ? [] : await roots(sessionId)));
318
+ res.statusCode = 200;
319
+ res.end(JSON.stringify({ ok: true }));
320
+ } catch (err) {
321
+ res.statusCode = 400;
322
+ res.end(JSON.stringify({
323
+ ok: false,
324
+ error: String(err)
325
+ }));
326
+ }
327
+ });
328
+ };
329
+ }
330
+ //#endregion
331
+ //#region src/skill-status.ts
332
+ /** Shared generative-mcpapps identity. Plugin-tree presence is not installation. */
333
+ const GENERATIVE_MCPAPPS_SKILL = "generative-mcpapps";
334
+ /** Conventional relative roots only. Never expand $HOME or dump host `root.path`. */
335
+ const CONVENTIONAL_SKILL_ROOTS = [".dsh/skills", ".agents/skills"];
336
+ function skillNameMatches(name) {
337
+ return typeof name === "string" && name === "generative-mcpapps";
338
+ }
339
+ function skillListIncludes(entries) {
340
+ return Array.isArray(entries) && entries.some((entry) => skillNameMatches(entry?.name));
341
+ }
342
+ /** Directory bundle `name/SKILL.md` or flat `name.md` at a scanned root. */
343
+ function skillRootEntryMatches(entryName) {
344
+ return entryName === "generative-mcpapps" || entryName === `generative-mcpapps.md`;
345
+ }
346
+ function skillsFromListResult(result) {
347
+ if (Array.isArray(result)) return result;
348
+ if (result === null || typeof result !== "object") return [];
349
+ const record = result;
350
+ if (record.ok === false) return [];
351
+ if (Array.isArray(record.skills)) return record.skills;
352
+ if (record.value && typeof record.value === "object") {
353
+ const value = record.value;
354
+ if (Array.isArray(value.skills)) return value.skills;
355
+ if (Array.isArray(record.value)) return record.value;
356
+ }
357
+ return [];
358
+ }
359
+ function publicSkillRoots() {
360
+ return CONVENTIONAL_SKILL_ROOTS.map((path) => ({
361
+ source: "conventional",
362
+ path
363
+ }));
364
+ }
365
+ /**
366
+ * Strip host home / pack absolutes before anything user-facing (HTTP or Settings).
367
+ * Detection still uses the raw scan; this is the public face.
368
+ */
369
+ function toPublicSkillStatus(status) {
370
+ return {
371
+ name: status.name,
372
+ installed: status.installed,
373
+ via: status.via,
374
+ roots: publicSkillRoots()
375
+ };
376
+ }
377
+ //#endregion
378
+ //#region src/skill-roots.ts
379
+ function expandHomePath(path, home = homedir()) {
380
+ if (path === "~") return home;
381
+ if (path.startsWith("~/") || path.startsWith("~\\")) return join(home, path.slice(2));
382
+ return path;
383
+ }
384
+ /** `$DSH_HOME` or `~/.dsh`, matching official `resolveDshHome`. */
385
+ function resolveDshHome(env = process.env, home = homedir()) {
386
+ const fromEnv = env.DSH_HOME;
387
+ const selected = fromEnv !== void 0 && fromEnv.trim().length > 0 ? fromEnv : join(home, ".dsh");
388
+ return resolve(expandHomePath(selected, home));
389
+ }
390
+ /** `$DSH_AGENTS_HOME` or `~/.agents`. */
391
+ function resolveAgentsHome(env = process.env, home = homedir()) {
392
+ const fromEnv = env.DSH_AGENTS_HOME;
393
+ const selected = fromEnv !== void 0 && fromEnv.trim().length > 0 ? fromEnv : join(home, ".agents");
394
+ return resolve(expandHomePath(selected, home));
395
+ }
396
+ function userSkillRoots(env = process.env, home = homedir()) {
397
+ return [{
398
+ source: "user-dsh",
399
+ path: join(resolveDshHome(env, home), "skills")
400
+ }, {
401
+ source: "user-agents",
402
+ path: join(resolveAgentsHome(env, home), "skills")
403
+ }];
404
+ }
405
+ function projectSkillRoots(projectRoot) {
406
+ return [{
407
+ source: "project-dsh",
408
+ path: join(projectRoot, ".dsh", "skills")
409
+ }, {
410
+ source: "project-agents",
411
+ path: join(projectRoot, ".agents", "skills")
412
+ }];
413
+ }
414
+ async function findProjectRoot(cwd) {
415
+ let current = resolve(cwd);
416
+ while (true) {
417
+ if (existsSync(join(current, ".git"))) return current;
418
+ const parent = dirname(current);
419
+ if (parent === current) return resolve(cwd);
420
+ current = parent;
421
+ }
422
+ }
423
+ async function rootHasGenerativeMcpapps(rootPath) {
424
+ let entries;
425
+ try {
426
+ entries = await readdir(rootPath, { withFileTypes: true });
427
+ } catch {
428
+ return false;
429
+ }
430
+ for (const entry of entries) {
431
+ if (entry.name === ".system") continue;
432
+ if (!skillRootEntryMatches(entry.name)) continue;
433
+ if (entry.isDirectory()) try {
434
+ await access(join(rootPath, entry.name, "SKILL.md"));
435
+ return true;
436
+ } catch {
437
+ continue;
438
+ }
439
+ if (entry.isFile()) return true;
440
+ }
441
+ return false;
442
+ }
443
+ /** Resolve the plugin-shipped pack. This path is never treated as a skill root. */
444
+ function pluginSkillPackPath(moduleUrl = import.meta.url) {
445
+ const here = dirname(fileURLToPath(moduleUrl));
446
+ return [
447
+ join(here, "../skills", GENERATIVE_MCPAPPS_SKILL),
448
+ join(here, "../../skills", GENERATIVE_MCPAPPS_SKILL),
449
+ join(here, "skills", GENERATIVE_MCPAPPS_SKILL)
450
+ ].find((path) => existsSync(join(path, "SKILL.md")));
451
+ }
452
+ async function scanGenerativeMcpappsStatus(options = {}) {
453
+ const roots = [...userSkillRoots(options.env ?? process.env, options.home ?? homedir())];
454
+ const cwd = typeof options.cwd === "string" && options.cwd.trim().length > 0 ? isAbsolute(options.cwd) ? options.cwd : resolve(options.cwd) : void 0;
455
+ if (cwd !== void 0) roots.unshift(...projectSkillRoots(await findProjectRoot(cwd)));
456
+ const inspected = [];
457
+ let rootHit = false;
458
+ for (const root of roots) {
459
+ const present = await rootHasGenerativeMcpapps(root.path);
460
+ inspected.push({
461
+ ...root,
462
+ present
463
+ });
464
+ if (present) rootHit = true;
465
+ }
466
+ let via = null;
467
+ let listHit = false;
468
+ if (options.listSkills) try {
469
+ listHit = skillListIncludes(await options.listSkills());
470
+ if (listHit) via = "skills.list";
471
+ } catch {}
472
+ if (!listHit && rootHit) via = "skill-root";
473
+ const packPath = options.packPath ?? pluginSkillPackPath();
474
+ return {
475
+ name: GENERATIVE_MCPAPPS_SKILL,
476
+ installed: listHit || rootHit,
477
+ via,
478
+ roots: inspected,
479
+ ...packPath !== void 0 ? { packPath } : {}
480
+ };
481
+ }
482
+ //#endregion
4
483
  //#region src/dsh-better-display.ts
5
484
  const name = "dsh-better-display";
6
485
  const inject = ["webServer"];
486
+ const log = createLogger({ base: { plugin: name } });
7
487
  function writeJson(res, status, body) {
8
488
  res.setHeader("Content-Type", "application/json");
9
489
  res.statusCode = status;
@@ -14,76 +494,60 @@ function skillLister(ctx) {
14
494
  if (typeof skills?.list !== "function") return void 0;
15
495
  return async () => skillsFromListResult(await skills.list({}));
16
496
  }
17
- function apply(ctx) {
18
- console.log("[my-plugins/dsh-better-display] loaded");
497
+ function beginRequest(req, res, route) {
498
+ const requestId = requestIdOf(req);
499
+ res.setHeader("X-Request-ID", requestId);
500
+ recordRouteHit(route);
501
+ const started = performance.now();
502
+ res.on("finish", () => {
503
+ recordRouteDuration(route, performance.now() - started);
504
+ });
505
+ log.info("http request", {
506
+ requestId,
507
+ route,
508
+ method: req.method ?? "",
509
+ url: req.url ?? "",
510
+ headers: req.headers
511
+ });
512
+ return requestId;
513
+ }
514
+ function apply(ctx, options) {
515
+ const flags = options?.flags ?? loadFlags();
516
+ log.info("[my-plugins/dsh-better-display] loaded", { readerEnabled: flags.readerEnabled });
517
+ if (!flags.readerEnabled) {
518
+ log.info("route registration skipped", { readerEnabled: false });
519
+ return;
520
+ }
19
521
  if (ctx.webServer) ctx.effect(() => {
522
+ const reveal = createRevealHandler({ roots: (sessionId) => revealRoots(ctx, sessionId) });
20
523
  const disposeReveal = ctx.webServer.register({
21
524
  kind: "exact",
22
- path: "/better-display/reveal",
23
- handler: async (req, res) => {
24
- if (req.method !== "POST") {
25
- res.statusCode = 405;
26
- res.end();
27
- return;
28
- }
29
- let body = "";
30
- req.on("data", (chunk) => {
31
- body += chunk;
32
- });
33
- req.on("end", () => {
34
- try {
35
- const data = JSON.parse(body);
36
- const targetPath = typeof data.path === "string" ? data.path.trim() : "";
37
- if (!targetPath) {
38
- res.statusCode = 400;
39
- res.end(JSON.stringify({
40
- ok: false,
41
- error: "Empty path"
42
- }));
43
- return;
44
- }
45
- if (process.platform === "darwin") spawn("open", ["-R", targetPath], {
46
- detached: true,
47
- stdio: "ignore"
48
- });
49
- else if (process.platform === "win32") spawn("explorer.exe", [`/select,${targetPath}`], {
50
- detached: true,
51
- stdio: "ignore"
52
- });
53
- else spawn("xdg-open", [targetPath], {
54
- detached: true,
55
- stdio: "ignore"
56
- });
57
- res.setHeader("Content-Type", "application/json");
58
- res.statusCode = 200;
59
- res.end(JSON.stringify({ ok: true }));
60
- } catch (err) {
61
- res.statusCode = 400;
62
- res.end(JSON.stringify({
63
- ok: false,
64
- error: String(err)
65
- }));
66
- }
67
- });
525
+ path: ROUTE_REVEAL,
526
+ handler: (req, res) => {
527
+ beginRequest(req, res, ROUTE_REVEAL);
528
+ reveal(req, res);
68
529
  }
69
530
  });
70
531
  const disposeSkill = ctx.webServer.register({
71
532
  kind: "exact",
72
- path: "/better-display/skill-status",
533
+ path: ROUTE_SKILL_STATUS,
73
534
  handler: async (req, res) => {
535
+ const requestId = beginRequest(req, res, ROUTE_SKILL_STATUS);
74
536
  if (req.method !== "GET") {
75
537
  res.statusCode = 405;
76
538
  res.end();
77
539
  return;
78
540
  }
79
541
  try {
80
- const cwd = new URL(req.url ?? "", "http://127.0.0.1").searchParams.get("cwd") ?? void 0;
81
- const status = await scanGenerativeMcpappsStatus({
82
- cwd,
542
+ writeJson(res, 200, toPublicSkillStatus(await scanGenerativeMcpappsStatus({
543
+ cwd: new URL(req.url ?? "", "http://127.0.0.1").searchParams.get("cwd") ?? void 0,
83
544
  listSkills: skillLister(ctx)
84
- });
85
- writeJson(res, 200, toPublicSkillStatus(status));
545
+ })));
86
546
  } catch (err) {
547
+ log.error("skill-status failed", {
548
+ requestId,
549
+ error: err instanceof Error ? err.message : String(err)
550
+ });
87
551
  writeJson(res, 500, {
88
552
  ok: false,
89
553
  error: String(err)
@@ -91,11 +555,25 @@ function apply(ctx) {
91
555
  }
92
556
  }
93
557
  });
558
+ const disposeMetrics = ctx.webServer.register({
559
+ kind: "exact",
560
+ path: ROUTE_METRICS,
561
+ handler: (req, res) => {
562
+ beginRequest(req, res, ROUTE_METRICS);
563
+ if (req.method !== "GET") {
564
+ res.statusCode = 405;
565
+ res.end();
566
+ return;
567
+ }
568
+ writeJson(res, 200, metricsPayload());
569
+ }
570
+ });
94
571
  return () => {
95
572
  disposeReveal();
96
573
  disposeSkill();
574
+ disposeMetrics();
97
575
  };
98
- }, "dsh-better-display: reveal and skill-status routes");
576
+ }, "dsh-better-display: reveal, skill-status, and metrics routes");
99
577
  }
100
578
  //#endregion
101
579
  export { apply, inject, name };
@@ -0,0 +1,20 @@
1
+ import type { RefObject } from 'react';
2
+ import type { TurnBoundary } from './projection.js';
3
+ import type { BlockRenderProps, ReaderProps } from './types.js';
4
+ type SeatProps = BlockRenderProps & Pick<ReaderProps, 'useChat' | 'sessionId'> & {
5
+ nodeKey: string;
6
+ boundary: TurnBoundary;
7
+ pinned?: boolean;
8
+ processOpen?: boolean;
9
+ };
10
+ export declare const AssistantNode: import("react").MemoExoticComponent<({ useChat, nodeKey, boundary, processOpen, pinned, folded, partStart, seamSummary, seamAfter, motion, onRead, returnFocusTo, ...render }: SeatProps & {
11
+ motion: boolean;
12
+ onRead: () => void;
13
+ returnFocusTo: RefObject<HTMLButtonElement>;
14
+ partStart?: number;
15
+ folded?: boolean;
16
+ seamSummary?: string;
17
+ seamAfter?: boolean;
18
+ }) => import("react").JSX.Element | null>;
19
+ export {};
20
+ //# sourceMappingURL=AssistantNode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AssistantNode.d.ts","sourceRoot":"","sources":["../../../src/client/AssistantNode.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAQvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAOhE,KAAK,SAAS,GAAG,gBAAgB,GAAG,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,WAAW,CAAC,GAAG;IAC/E,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,YAAY,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAC;CAClF,CAAC;AAEF,eAAO,MAAM,aAAa,iLAAyM,SAAS,GAAG;IAC7O,MAAM,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,IAAI,CAAC;IAAC,aAAa,EAAE,SAAS,CAAC,iBAAiB,CAAC,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IACvH,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAC;CAC3C,wCA+CC,CAAC"}