dsh-better-display 0.3.7 → 0.3.8

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 (69) hide show
  1. package/CHANGELOG.md +11 -1
  2. package/docs/official-rendering-bridge.md +12 -0
  3. package/lib/client.js +221 -105
  4. package/lib/client.js.map +1 -1
  5. package/lib/dsh-better-display.js +297 -53
  6. package/lib/types/client/Reader.d.ts.map +1 -1
  7. package/lib/types/client/Reader.js +43 -12
  8. package/lib/types/client/Reader.js.map +1 -1
  9. package/lib/types/client/ToolActivity.d.ts.map +1 -1
  10. package/lib/types/client/ToolActivity.js +10 -5
  11. package/lib/types/client/ToolActivity.js.map +1 -1
  12. package/lib/types/client/deliverables.d.ts.map +1 -1
  13. package/lib/types/client/deliverables.js +2 -0
  14. package/lib/types/client/deliverables.js.map +1 -1
  15. package/lib/types/client/index.d.ts.map +1 -1
  16. package/lib/types/client/index.js +9 -1
  17. package/lib/types/client/index.js.map +1 -1
  18. package/lib/types/client/motion.d.ts.map +1 -1
  19. package/lib/types/client/motion.js +9 -1
  20. package/lib/types/client/motion.js.map +1 -1
  21. package/lib/types/client/official-slots.d.ts.map +1 -1
  22. package/lib/types/client/official-slots.js +6 -3
  23. package/lib/types/client/official-slots.js.map +1 -1
  24. package/lib/types/client/open-file.d.ts.map +1 -1
  25. package/lib/types/client/open-file.js +1 -6
  26. package/lib/types/client/open-file.js.map +1 -1
  27. package/lib/types/client/styles.d.ts +3 -0
  28. package/lib/types/client/styles.d.ts.map +1 -0
  29. package/lib/types/client/styles.js +25 -0
  30. package/lib/types/client/styles.js.map +1 -0
  31. package/lib/types/client/tool-activity.d.ts +2 -2
  32. package/lib/types/client/tool-activity.d.ts.map +1 -1
  33. package/lib/types/client/tool-activity.js +12 -7
  34. package/lib/types/client/tool-activity.js.map +1 -1
  35. package/lib/types/dsh-better-display.d.ts.map +1 -1
  36. package/lib/types/dsh-better-display.js +2 -39
  37. package/lib/types/dsh-better-display.js.map +1 -1
  38. package/lib/types/reveal.d.ts +12 -0
  39. package/lib/types/reveal.d.ts.map +1 -0
  40. package/lib/types/reveal.js +147 -0
  41. package/lib/types/reveal.js.map +1 -0
  42. package/lib/types/workspace-path.d.ts +3 -0
  43. package/lib/types/workspace-path.d.ts.map +1 -0
  44. package/lib/types/workspace-path.js +5 -0
  45. package/lib/types/workspace-path.js.map +1 -0
  46. package/package.json +1 -1
  47. package/src/client/Reader.module.css +41 -9
  48. package/src/client/Reader.tsx +56 -21
  49. package/src/client/ToolActivity.tsx +9 -5
  50. package/src/client/deliverables.ts +1 -0
  51. package/src/client/index.tsx +10 -1
  52. package/src/client/motion.tsx +8 -1
  53. package/src/client/official-slots.tsx +6 -3
  54. package/src/client/open-file.ts +2 -8
  55. package/src/client/styles.ts +22 -0
  56. package/src/client/tool-activity.ts +18 -7
  57. package/src/dsh-better-display.ts +2 -38
  58. package/src/reveal.ts +90 -0
  59. package/src/workspace-path.ts +4 -0
  60. package/tests/fixtures/reader-official-users.tsx +170 -0
  61. package/tests/footer-precedence.test.ts +8 -0
  62. package/tests/reader-glass-css.test.ts +28 -0
  63. package/tests/reader-official-users.test.ts +81 -0
  64. package/tests/retiring-content.test.ts +137 -0
  65. package/tests/reveal.test.ts +128 -0
  66. package/tests/stock-install.test.ts +8 -0
  67. package/tests/styles.test.ts +48 -0
  68. package/tests/terminal-input-preview.test.ts +113 -0
  69. package/tsdown.config.ts +49 -1
@@ -1,6 +1,297 @@
1
+ import { access, readdir } from "node:fs/promises";
2
+ import { existsSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { dirname, isAbsolute, join, posix, resolve, win32 } from "node:path";
5
+ import { fileURLToPath } from "node:url";
1
6
  import { spawn } from "node:child_process";
2
- import { scanGenerativeMcpappsStatus } from "./skill-roots.ts";
3
- import { skillsFromListResult, toPublicSkillStatus } from "./skill-status.ts";
7
+ //#region src/skill-status.ts
8
+ /** Shared generative-mcpapps identity. Plugin-tree presence is not installation. */
9
+ const GENERATIVE_MCPAPPS_SKILL = "generative-mcpapps";
10
+ /** Conventional relative roots only. Never expand $HOME or dump host `root.path`. */
11
+ const CONVENTIONAL_SKILL_ROOTS = [".dsh/skills", ".agents/skills"];
12
+ function skillNameMatches(name) {
13
+ return typeof name === "string" && name === "generative-mcpapps";
14
+ }
15
+ function skillListIncludes(entries) {
16
+ return Array.isArray(entries) && entries.some((entry) => skillNameMatches(entry?.name));
17
+ }
18
+ /** Directory bundle `name/SKILL.md` or flat `name.md` at a scanned root. */
19
+ function skillRootEntryMatches(entryName) {
20
+ return entryName === "generative-mcpapps" || entryName === `generative-mcpapps.md`;
21
+ }
22
+ function skillsFromListResult(result) {
23
+ if (Array.isArray(result)) return result;
24
+ if (result === null || typeof result !== "object") return [];
25
+ const record = result;
26
+ if (record.ok === false) return [];
27
+ if (Array.isArray(record.skills)) return record.skills;
28
+ if (record.value && typeof record.value === "object") {
29
+ const value = record.value;
30
+ if (Array.isArray(value.skills)) return value.skills;
31
+ if (Array.isArray(record.value)) return record.value;
32
+ }
33
+ return [];
34
+ }
35
+ function publicSkillRoots() {
36
+ return CONVENTIONAL_SKILL_ROOTS.map((path) => ({
37
+ source: "conventional",
38
+ path
39
+ }));
40
+ }
41
+ /**
42
+ * Strip host home / pack absolutes before anything user-facing (HTTP or Settings).
43
+ * Detection still uses the raw scan; this is the public face.
44
+ */
45
+ function toPublicSkillStatus(status) {
46
+ return {
47
+ name: status.name,
48
+ installed: status.installed,
49
+ via: status.via,
50
+ roots: publicSkillRoots()
51
+ };
52
+ }
53
+ //#endregion
54
+ //#region src/skill-roots.ts
55
+ function expandHomePath(path, home = homedir()) {
56
+ if (path === "~") return home;
57
+ if (path.startsWith("~/") || path.startsWith("~\\")) return join(home, path.slice(2));
58
+ return path;
59
+ }
60
+ /** `$DSH_HOME` or `~/.dsh`, matching official `resolveDshHome`. */
61
+ function resolveDshHome(env = process.env, home = homedir()) {
62
+ const fromEnv = env.DSH_HOME;
63
+ const selected = fromEnv !== void 0 && fromEnv.trim().length > 0 ? fromEnv : join(home, ".dsh");
64
+ return resolve(expandHomePath(selected, home));
65
+ }
66
+ /** `$DSH_AGENTS_HOME` or `~/.agents`. */
67
+ function resolveAgentsHome(env = process.env, home = homedir()) {
68
+ const fromEnv = env.DSH_AGENTS_HOME;
69
+ const selected = fromEnv !== void 0 && fromEnv.trim().length > 0 ? fromEnv : join(home, ".agents");
70
+ return resolve(expandHomePath(selected, home));
71
+ }
72
+ function userSkillRoots(env = process.env, home = homedir()) {
73
+ return [{
74
+ source: "user-dsh",
75
+ path: join(resolveDshHome(env, home), "skills")
76
+ }, {
77
+ source: "user-agents",
78
+ path: join(resolveAgentsHome(env, home), "skills")
79
+ }];
80
+ }
81
+ function projectSkillRoots(projectRoot) {
82
+ return [{
83
+ source: "project-dsh",
84
+ path: join(projectRoot, ".dsh", "skills")
85
+ }, {
86
+ source: "project-agents",
87
+ path: join(projectRoot, ".agents", "skills")
88
+ }];
89
+ }
90
+ async function findProjectRoot(cwd) {
91
+ let current = resolve(cwd);
92
+ while (true) {
93
+ if (existsSync(join(current, ".git"))) return current;
94
+ const parent = dirname(current);
95
+ if (parent === current) return resolve(cwd);
96
+ current = parent;
97
+ }
98
+ }
99
+ async function rootHasGenerativeMcpapps(rootPath) {
100
+ let entries;
101
+ try {
102
+ entries = await readdir(rootPath, { withFileTypes: true });
103
+ } catch {
104
+ return false;
105
+ }
106
+ for (const entry of entries) {
107
+ if (entry.name === ".system") continue;
108
+ if (!skillRootEntryMatches(entry.name)) continue;
109
+ if (entry.isDirectory()) try {
110
+ await access(join(rootPath, entry.name, "SKILL.md"));
111
+ return true;
112
+ } catch {
113
+ continue;
114
+ }
115
+ if (entry.isFile()) return true;
116
+ }
117
+ return false;
118
+ }
119
+ /** Resolve the plugin-shipped pack. This path is never treated as a skill root. */
120
+ function pluginSkillPackPath(moduleUrl = import.meta.url) {
121
+ const here = dirname(fileURLToPath(moduleUrl));
122
+ return [
123
+ join(here, "../skills", GENERATIVE_MCPAPPS_SKILL),
124
+ join(here, "../../skills", GENERATIVE_MCPAPPS_SKILL),
125
+ join(here, "skills", GENERATIVE_MCPAPPS_SKILL)
126
+ ].find((path) => existsSync(join(path, "SKILL.md")));
127
+ }
128
+ async function scanGenerativeMcpappsStatus(options = {}) {
129
+ const roots = [...userSkillRoots(options.env ?? process.env, options.home ?? homedir())];
130
+ const cwd = typeof options.cwd === "string" && options.cwd.trim().length > 0 ? isAbsolute(options.cwd) ? options.cwd : resolve(options.cwd) : void 0;
131
+ if (cwd !== void 0) roots.unshift(...projectSkillRoots(await findProjectRoot(cwd)));
132
+ const inspected = [];
133
+ let rootHit = false;
134
+ for (const root of roots) {
135
+ const present = await rootHasGenerativeMcpapps(root.path);
136
+ inspected.push({
137
+ ...root,
138
+ present
139
+ });
140
+ if (present) rootHit = true;
141
+ }
142
+ let via = null;
143
+ let listHit = false;
144
+ if (options.listSkills) try {
145
+ listHit = skillListIncludes(await options.listSkills());
146
+ if (listHit) via = "skills.list";
147
+ } catch {}
148
+ if (!listHit && rootHit) via = "skill-root";
149
+ const packPath = options.packPath ?? pluginSkillPackPath();
150
+ return {
151
+ name: GENERATIVE_MCPAPPS_SKILL,
152
+ installed: listHit || rootHit,
153
+ via,
154
+ roots: inspected,
155
+ ...packPath !== void 0 ? { packPath } : {}
156
+ };
157
+ }
158
+ //#endregion
159
+ //#region src/workspace-path.ts
160
+ /** Absolute paths accepted by both POSIX and Windows hosts. */
161
+ function isAbsoluteWorkspacePath(path) {
162
+ return /^(?:\/|[A-Za-z]:[/\\]|\\\\)/.test(path);
163
+ }
164
+ //#endregion
165
+ //#region \0@oxc-project+runtime@0.153.0/helpers/esm/usingCtx.js
166
+ function _usingCtx() {
167
+ var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
168
+ var n = Error();
169
+ return n.name = "SuppressedError", n.error = r, n.suppressed = e, n;
170
+ }, e = {}, n = [];
171
+ function using(r, e) {
172
+ if (null != e) {
173
+ if (Object(e) !== e) throw new TypeError("using declarations can only be used with objects, functions, null, or undefined.");
174
+ if (r) var o = e[Symbol.asyncDispose || Symbol["for"]("Symbol.asyncDispose")];
175
+ if (void 0 === o && (o = e[Symbol.dispose || Symbol["for"]("Symbol.dispose")], r)) var t = o;
176
+ if ("function" != typeof o) throw new TypeError("Object is not disposable.");
177
+ t && (o = function o() {
178
+ try {
179
+ t.call(e);
180
+ } catch (r) {
181
+ return Promise.reject(r);
182
+ }
183
+ }), n.push({
184
+ v: e,
185
+ d: o,
186
+ a: r
187
+ });
188
+ } else r && n.push({
189
+ d: e,
190
+ a: r
191
+ });
192
+ return e;
193
+ }
194
+ return {
195
+ e,
196
+ u: using.bind(null, !1),
197
+ a: using.bind(null, !0),
198
+ d: function d() {
199
+ var o, t = this.e, s = 0;
200
+ function next() {
201
+ for (; o = n.pop();) try {
202
+ if (!o.a && 1 === s) return s = 0, n.push(o), Promise.resolve().then(next);
203
+ if (o.d) {
204
+ var r = o.d.call(o.v);
205
+ if (o.a) return s |= 2, Promise.resolve(r).then(next, err);
206
+ } else s |= 1;
207
+ } catch (r) {
208
+ return err(r);
209
+ }
210
+ if (1 === s) return t !== e ? Promise.reject(t) : Promise.resolve();
211
+ if (t !== e) throw t;
212
+ }
213
+ function err(n) {
214
+ return t = t !== e ? new r(n, t) : n, next();
215
+ }
216
+ return next();
217
+ }
218
+ };
219
+ }
220
+ //#endregion
221
+ //#region src/reveal.ts
222
+ async function revealRoots(ctx, sessionId) {
223
+ if (sessionId !== void 0) {
224
+ const cwd = (ctx.get?.("sessions"))?.get(sessionId)?.header.cwd;
225
+ if (cwd) return [cwd];
226
+ const query = ctx.get?.("sessionQuery");
227
+ if (query) try {
228
+ var _usingCtx$1 = _usingCtx();
229
+ const observation = _usingCtx$1.u(await query.observeSession(sessionId, { projectionMode: "none" }));
230
+ if (observation.header.cwd) return [observation.header.cwd];
231
+ } catch (_) {
232
+ _usingCtx$1.e = _;
233
+ } finally {
234
+ _usingCtx$1.d();
235
+ }
236
+ }
237
+ return ((ctx.get?.("workspaceRegistry"))?.list() ?? []).filter((workspace) => sessionId === void 0 || workspace.sessionIds.includes(sessionId)).map((workspace) => workspace.path);
238
+ }
239
+ function resolveRevealPath(path, roots, exists = existsSync) {
240
+ if (isAbsoluteWorkspacePath(path)) return path;
241
+ if (/^[A-Za-z]:|^\\/.test(path)) throw new Error("Path is not workspace-relative");
242
+ const matches = /* @__PURE__ */ new Set();
243
+ for (const root of roots) {
244
+ if (!isAbsoluteWorkspacePath(root)) continue;
245
+ const candidate = (/^[A-Za-z]:[/\\]|^\\\\/.test(root) ? win32 : posix).resolve(root, path);
246
+ if (exists(candidate)) matches.add(candidate);
247
+ }
248
+ if (matches.size !== 1) throw new Error(matches.size === 0 ? "Workspace path not found" : "Workspace path is ambiguous");
249
+ return [...matches][0];
250
+ }
251
+ function revealPath(path, platform = process.platform, spawnProcess = spawn) {
252
+ const options = {
253
+ detached: true,
254
+ stdio: "ignore"
255
+ };
256
+ if (platform === "darwin") spawnProcess("open", ["-R", path], options);
257
+ else if (platform === "win32") spawnProcess("explorer.exe", [`/select,"${path}"`], {
258
+ ...options,
259
+ windowsVerbatimArguments: true
260
+ });
261
+ else spawnProcess("xdg-open", [path], options);
262
+ }
263
+ function createRevealHandler({ roots, reveal = revealPath }) {
264
+ return (req, res) => {
265
+ if (req.method !== "POST") {
266
+ res.statusCode = 405;
267
+ res.end();
268
+ return;
269
+ }
270
+ let body = "";
271
+ req.on("data", (chunk) => {
272
+ body += chunk;
273
+ });
274
+ req.on("end", async () => {
275
+ res.setHeader("Content-Type", "application/json");
276
+ try {
277
+ const data = JSON.parse(body);
278
+ const path = typeof data === "object" && data !== null && "path" in data && typeof data.path === "string" ? data.path.trim() : "";
279
+ if (!path) throw new Error("Empty path");
280
+ const sessionId = typeof data === "object" && data !== null && "sessionId" in data && typeof data.sessionId === "string" ? data.sessionId : void 0;
281
+ reveal(resolveRevealPath(path, isAbsoluteWorkspacePath(path) ? [] : await roots(sessionId)));
282
+ res.statusCode = 200;
283
+ res.end(JSON.stringify({ ok: true }));
284
+ } catch (err) {
285
+ res.statusCode = 400;
286
+ res.end(JSON.stringify({
287
+ ok: false,
288
+ error: String(err)
289
+ }));
290
+ }
291
+ });
292
+ };
293
+ }
294
+ //#endregion
4
295
  //#region src/dsh-better-display.ts
5
296
  const name = "dsh-better-display";
6
297
  const inject = ["webServer"];
@@ -20,52 +311,7 @@ function apply(ctx) {
20
311
  const disposeReveal = ctx.webServer.register({
21
312
  kind: "exact",
22
313
  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
- });
68
- }
314
+ handler: createRevealHandler({ roots: (sessionId) => revealRoots(ctx, sessionId) })
69
315
  });
70
316
  const disposeSkill = ctx.webServer.register({
71
317
  kind: "exact",
@@ -77,12 +323,10 @@ function apply(ctx) {
77
323
  return;
78
324
  }
79
325
  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,
326
+ writeJson(res, 200, toPublicSkillStatus(await scanGenerativeMcpappsStatus({
327
+ cwd: new URL(req.url ?? "", "http://127.0.0.1").searchParams.get("cwd") ?? void 0,
83
328
  listSkills: skillLister(ctx)
84
- });
85
- writeJson(res, 200, toPublicSkillStatus(status));
329
+ })));
86
330
  } catch (err) {
87
331
  writeJson(res, 500, {
88
332
  ok: false,
@@ -1 +1 @@
1
- {"version":3,"file":"Reader.d.ts","sourceRoot":"","sources":["../../../src/client/Reader.tsx"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAoB,WAAW,EAAE,MAAM,YAAY,CAAC;AAomBhE,wBAAgB,MAAM,CAAC,KAAK,EAAE,WAAW,+BAmRxC"}
1
+ {"version":3,"file":"Reader.d.ts","sourceRoot":"","sources":["../../../src/client/Reader.tsx"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EAAoB,WAAW,EAAE,MAAM,YAAY,CAAC;AAooBhE,wBAAgB,MAAM,CAAC,KAAK,EAAE,WAAW,+BAoRxC"}
@@ -1,10 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { Fragment, memo, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
2
+ import { Fragment, memo, useCallback, useEffect, useId, useInsertionEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
3
+ import { createSnapshotStore } from '@deepseek-ai/dsh-client-store';
3
4
  import { JsonBlock, MarkdownText } from '@deepseek-ai/dsh-client-ui-primitives';
4
5
  import { BlockBoundary, Blocks, contentBlocks, CopyAnswer, UserMessageActions } from './Blocks.js';
5
6
  import { ReasoningCard } from './ReasoningCard.js';
6
7
  import { ToolActivity, ToolMedia } from './ToolActivity.js';
7
8
  import { OfficialActions, OfficialNode, OfficialTail } from './OfficialContent.js';
9
+ import { OFFICIAL_SEATS } from './official-slots.js';
8
10
  import { preparingLabel, readerFlow } from './tool-activity.js';
9
11
  import { Disclosure, ProcessFragment, RetiringContent, StatusText, useMotionAllowed, usePinnedSelection, useReadingScroll } from './motion.js';
10
12
  import { StreamMotionContext } from './streaming.js';
@@ -26,6 +28,7 @@ import { StickyLane } from './StickyLane.js';
26
28
  import css from './Reader.module.css';
27
29
  import { markdownLabels, truncatedJsonLabel } from './primitive-labels.js';
28
30
  import { chatSeatProps } from './chat-seat.js';
31
+ import { ensureStyles } from './styles.js';
29
32
  function isNode(node, kind) {
30
33
  return node.kind === kind;
31
34
  }
@@ -141,6 +144,7 @@ const AssistantNode = memo(function AssistantNode({ useChat, nodeKey, boundary,
141
144
  });
142
145
  const MainNode = memo(function MainNode({ useChat, nodeKey, boundary, pinned, processOpen = false, ...render }) {
143
146
  const node = useChat(snapshot => snapshot.nodes.get(nodeKey));
147
+ const [disclosureReset] = useState(() => createSnapshotStore(0));
144
148
  if (!node || node.visibility === 'hidden')
145
149
  return null;
146
150
  if (isNode(node, 'user') || isNode(node, 'steering')) {
@@ -149,7 +153,30 @@ const MainNode = memo(function MainNode({ useChat, nodeKey, boundary, pinned, pr
149
153
  const otherBlocks = blocks.filter(b => b.kind !== 'image');
150
154
  const text = otherBlocks.filter((block) => block.kind === 'text').map(block => block.text).join('\n\n');
151
155
  const time = node.data.time;
152
- return _jsxs("div", { className: css.userCluster, "data-reader-anchor": true, "data-reader-key": nodeKey, ...chatSeatProps(node.kind, nodeKey), children: [node.kind === 'steering' && _jsx("p", { className: css.meta, children: "\u8865\u5145\u6D88\u606F" }), imageBlocks.length > 0 && _jsx("div", { className: css.userImages, children: _jsx(Blocks, { ...render, blocks: imageBlocks, source: "user" }) }), otherBlocks.length > 0 && _jsx("div", { className: css.user, children: _jsx(Blocks, { ...render, blocks: otherBlocks, source: "user" }) }), _jsx(UserMessageActions, { text: text, time: time })] });
156
+ const fallback = _jsxs(_Fragment, { children: [node.kind === 'steering' && _jsx("p", { className: css.meta, children: "\u8865\u5145\u6D88\u606F" }), imageBlocks.length > 0 && _jsx("div", { className: css.userImages, children: _jsx(Blocks, { ...render, blocks: imageBlocks, source: "user" }) }), otherBlocks.length > 0 && _jsx("div", { className: css.user, children: _jsx(Blocks, { ...render, blocks: otherBlocks, source: "user" }) }), _jsx(UserMessageActions, { text: text, time: time })] });
157
+ const official = render.official;
158
+ let rendered = fallback;
159
+ if (official) {
160
+ const turn = node.location.kind === 'turn' || node.location.kind === 'step' ? node.location.turn : undefined;
161
+ const owner = {
162
+ cwd: render.cwd,
163
+ openFile: render.openFile ?? (() => { }),
164
+ openSkill: () => { },
165
+ forkAt: render.forkAt ?? (() => { }),
166
+ inspectCall: callId => official.openView('trajectory', callId),
167
+ loadImage: official.officialImageLoader,
168
+ renderMessageImages: images => official.renderSlot(OFFICIAL_SEATS.images, {
169
+ ...images, loadImage: official.officialImageLoader,
170
+ }),
171
+ fileMentions: official.officialFileMentions,
172
+ sessionId: render.sessionId,
173
+ };
174
+ const options = { hookContext: { turnData: turn?.data, disclosureReset }, fallback };
175
+ rendered = node.kind === 'user'
176
+ ? official.renderSlot(OFFICIAL_SEATS.nodes, { ...owner, node }, { ...options, entryKey: 'user' })
177
+ : official.renderSlot(OFFICIAL_SEATS.nodes, { ...owner, node }, { ...options, entryKey: 'steering' });
178
+ }
179
+ return _jsx("div", { className: css.userCluster, "data-reader-anchor": true, "data-reader-key": nodeKey, ...chatSeatProps(node.kind, nodeKey), children: rendered });
153
180
  }
154
181
  if (isNode(node, 'assistant-step'))
155
182
  return null;
@@ -315,6 +342,8 @@ const TurnGroup = memo(function TurnGroup({ group, motion, autoFold, pinnedKeys,
315
342
  const liveItems = useMemo(() => presentLiveTurn(steps, boundary, autoFold), [steps, boundary, autoFold]);
316
343
  const openMode = deliverableOpenModeOf(useSyncExternalStore(props.openPrefs?.subscribe ?? ((fn) => { void fn; return () => { }; }), () => props.openPrefs?.getSnapshot()?.deliverableOpenMode, () => 'external'));
317
344
  const hasProcess = flow.some(item => item.kind === 'tool' || hasProcessContent(nodes.get(item.nodeKey), boundary));
345
+ const showStatusLane = hasProcess && !isAwaitingModel;
346
+ const showClosedSummary = boundary.status === 'closed' && hasProcess && autoFold;
318
347
  // Only a real, still-active text selection delays folding. Merely clicking,
319
348
  // focusing or scrolling the live card does not create a permanent override.
320
349
  const holdingSelection = selectedProcessKeys.some(key => flow.some(item => item.key === key)
@@ -383,6 +412,7 @@ const TurnGroup = memo(function TurnGroup({ group, motion, autoFold, pinnedKeys,
383
412
  fileMentions,
384
413
  metrics,
385
414
  official,
415
+ sessionId: props.sessionId,
386
416
  cwd,
387
417
  };
388
418
  const terminal = terminalLabel(boundary.reason);
@@ -404,18 +434,19 @@ const TurnGroup = memo(function TurnGroup({ group, motion, autoFold, pinnedKeys,
404
434
  return _jsx(BlockBoundary, { children: _jsx(MainNode, { ...shared, boundary: boundary, nodeKey: step.nodeKey }) });
405
435
  return _jsxs(Fragment, { children: [_jsx(BlockBoundary, { children: _jsx(ProcessNode, { useChat: useFlowChat, t: props.t, nodeKey: step.nodeKey, open: processOpen, motion: motion, onRead: pinProcess, returnFocusTo: processButton }) }), _jsx(BlockBoundary, { children: _jsx(MainNode, { ...shared, boundary: boundary, nodeKey: step.nodeKey, pinned: pinnedKeys.includes(step.nodeKey), processOpen: processOpen }) })] });
406
436
  };
407
- return _jsxs("section", { className: css.turn, "data-reader-turn": group.turn ?? 'unresolved', "data-reader-turn-state": boundary.status, "data-reader-turn-result": boundary.reason ?? undefined, children: [startsWithUser && _jsx(BlockBoundary, { children: _jsx(MainNode, { ...shared, useChat: props.useChat, boundary: boundary, nodeKey: group.keys[0] }) }), hasProcess && !isAwaitingModel && _jsx(StickyLane, { kind: "status", className: css.turnProcessSticky, children: _jsx(Disclosure, { open: expanded, onChange: setExpanded, controls: flowId, buttonRef: processButton, label: _jsx(GroupStatus, { group: group, sessionId: props.sessionId, useChat: props.useChat, pending: interaction, motion: motion }) }) }), boundary.status === 'closed' && hasProcess && autoFold && _jsx(ClosedProcessSummary, { open: expanded, onChange: setExpanded, controls: flowId, steps: steps.filter(step => {
408
- if (step.kind === 'user')
409
- return false;
410
- if (step.kind !== 'body')
411
- return true;
412
- const node = nodes.get(step.nodeKey);
413
- return !!node && isNode(node, 'assistant-step') && (isEarlierNarration(node.data, boundary)
414
- || node.data.blocks.some(block => block.kind === 'tool-call')
415
- || (boundary.latestStep > 0 && node.data.step < boundary.latestStep));
416
- }) }), _jsx(ChoreographedFlow, { id: flowId, frame: presentation, motion: motion, enabled: autoFold && boundary.status === 'open' && !holdingSelection, urgent: hasTurnError || interaction !== undefined || !sessionRunning, open: foldOpenByKey, processOpen: expanded, onOpenChange: (key, value) => { pinProcess(); setFoldOpenByKey(current => ({ ...current, [key]: value })); }, renderStep: renderStep }), !hasProcess && boundary.status === 'open' && !isAwaitingModel && _jsx("div", { className: css.disclosure, "data-reader-status-only": true, children: _jsx(GroupStatus, { group: group, sessionId: props.sessionId, useChat: props.useChat, pending: interaction, motion: motion }) }), showDeliverablesRow(boundary.status, deliverables) && _jsx(DeliverablesRow, { deliverables: deliverables, openFile: props.openFile, revealFile: props.revealFile, openMode: openMode }), boundary.status === 'closed' && _jsx(BlockBoundary, { children: _jsx(OfficialTail, { official: official, owner: tailOwner, produced: deliverables }) }), showTerminalNotice && _jsx("div", { className: css.notice, "data-reader-terminal": true, children: terminal })] });
437
+ return _jsxs("section", { className: css.turn, "data-reader-turn": group.turn ?? 'unresolved', "data-reader-turn-state": boundary.status, "data-reader-turn-result": boundary.reason ?? undefined, children: [startsWithUser && _jsx(BlockBoundary, { children: _jsx(MainNode, { ...shared, useChat: props.useChat, boundary: boundary, nodeKey: group.keys[0] }) }), _jsxs("div", { className: css.turnStickyRegion, "data-reader-sticky-region": true, children: [(showStatusLane || showClosedSummary) && _jsxs("div", { className: css.turnStickyLanes, children: [showStatusLane && _jsx(StickyLane, { kind: "status", className: css.turnProcessSticky, children: _jsx(Disclosure, { open: expanded, onChange: setExpanded, controls: flowId, buttonRef: processButton, label: _jsx(GroupStatus, { group: group, sessionId: props.sessionId, useChat: props.useChat, pending: interaction, motion: motion }) }) }), showClosedSummary && _jsx(ClosedProcessSummary, { open: expanded, onChange: setExpanded, controls: flowId, steps: steps.filter(step => {
438
+ if (step.kind === 'user')
439
+ return false;
440
+ if (step.kind !== 'body')
441
+ return true;
442
+ const node = nodes.get(step.nodeKey);
443
+ return !!node && isNode(node, 'assistant-step') && (isEarlierNarration(node.data, boundary)
444
+ || node.data.blocks.some(block => block.kind === 'tool-call')
445
+ || (boundary.latestStep > 0 && node.data.step < boundary.latestStep));
446
+ }) })] }), _jsx(ChoreographedFlow, { id: flowId, frame: presentation, motion: motion, enabled: autoFold && boundary.status === 'open' && !holdingSelection, urgent: hasTurnError || interaction !== undefined || !sessionRunning, open: foldOpenByKey, processOpen: expanded, onOpenChange: (key, value) => { pinProcess(); setFoldOpenByKey(current => ({ ...current, [key]: value })); }, renderStep: renderStep })] }), !hasProcess && boundary.status === 'open' && !isAwaitingModel && _jsx("div", { className: css.disclosure, "data-reader-status-only": true, children: _jsx(GroupStatus, { group: group, sessionId: props.sessionId, useChat: props.useChat, pending: interaction, motion: motion }) }), showDeliverablesRow(boundary.status, deliverables) && _jsx(DeliverablesRow, { deliverables: deliverables, openFile: props.openFile, revealFile: props.revealFile, openMode: openMode }), boundary.status === 'closed' && _jsx(BlockBoundary, { children: _jsx(OfficialTail, { official: official, owner: tailOwner, produced: deliverables }) }), showTerminalNotice && _jsx("div", { className: css.notice, "data-reader-terminal": true, children: terminal })] });
417
447
  });
418
448
  export function Reader(props) {
449
+ useInsertionEffect(() => { ensureStyles(); }, []);
419
450
  const root = useRef(null);
420
451
  const activatedAt = useRef(Date.now());
421
452
  const order = props.useChat(snapshot => snapshot.order);