@selesai/code 0.13.12 → 0.13.14

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 (203) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/extensions/pi-subagents/CHANGELOG.md +77 -1
  3. package/dist/extensions/pi-subagents/VISION.md +12 -1
  4. package/dist/extensions/pi-subagents/docs/agents.md +13 -6
  5. package/dist/extensions/pi-subagents/docs/configuration.md +35 -9
  6. package/dist/extensions/pi-subagents/docs/extension-api.md +1 -1
  7. package/dist/extensions/pi-subagents/docs/missions.md +1 -1
  8. package/dist/extensions/pi-subagents/docs/models.md +6 -6
  9. package/dist/extensions/pi-subagents/docs/observability.md +6 -3
  10. package/dist/extensions/pi-subagents/docs/tool-reference.md +3 -3
  11. package/dist/extensions/pi-subagents/docs/watchdog.md +92 -114
  12. package/dist/extensions/pi-subagents/docs/workflows.md +1 -1
  13. package/dist/extensions/pi-subagents/install.mjs +1 -2
  14. package/dist/extensions/pi-subagents/package.json +1 -1
  15. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
  16. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +6 -6
  17. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
  18. package/dist/extensions/pi-subagents/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
  19. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +4 -4
  20. package/dist/extensions/pi-subagents/skills/pi-subagents/references/review-and-validation.md +1 -1
  21. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +41 -4
  22. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +3 -0
  23. package/dist/extensions/pi-subagents/src/agents/agents.ts +120 -124
  24. package/dist/extensions/pi-subagents/src/agents/runtime-agent-registry.ts +5 -1
  25. package/dist/extensions/pi-subagents/src/api/preflight.ts +4 -0
  26. package/dist/extensions/pi-subagents/src/api/shared-types.ts +3 -0
  27. package/dist/extensions/pi-subagents/src/extension/config.ts +20 -0
  28. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +1 -0
  29. package/dist/extensions/pi-subagents/src/extension/schemas.ts +6 -2
  30. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +1 -1
  31. package/dist/extensions/pi-subagents/src/inspectors/herdr/inspector-runner.ts +19 -13
  32. package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +26 -8
  33. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +103 -23
  34. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +6 -2
  35. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +18 -2
  36. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +54 -3
  37. package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +16 -0
  38. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +22 -2
  39. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +63 -6
  40. package/dist/extensions/pi-subagents/src/runs/background/steering.ts +4 -1
  41. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +69 -12
  42. package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +13 -0
  43. package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +3 -1
  44. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +21 -8
  45. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +59 -6
  46. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +95 -18
  47. package/dist/extensions/pi-subagents/src/runs/shared/async-status-projection.ts +11 -43
  48. package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +1 -0
  49. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +1 -1
  50. package/dist/extensions/pi-subagents/src/runs/shared/lane-metadata.ts +24 -3
  51. package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +4 -0
  52. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +2 -6
  53. package/dist/extensions/pi-subagents/src/runs/shared/permissions.ts +1 -0
  54. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +33 -18
  55. package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +73 -37
  56. package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +33 -6
  57. package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +4 -2
  58. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +20 -3
  59. package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +21 -7
  60. package/dist/extensions/pi-subagents/src/runs/shared/tool-timeout.ts +1 -1
  61. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +467 -63
  62. package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +3 -1
  63. package/dist/extensions/pi-subagents/src/shared/fork-context.ts +0 -12
  64. package/dist/extensions/pi-subagents/src/shared/fork-session-cwd.ts +27 -0
  65. package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +3 -0
  66. package/dist/extensions/pi-subagents/src/shared/types.ts +42 -4
  67. package/dist/extensions/pi-subagents/src/shared/utils.ts +18 -7
  68. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +1 -1
  69. package/dist/extensions/pi-subagents/src/slash/subagents-admin.ts +26 -12
  70. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +61 -2
  71. package/dist/extensions/pi-subagents/src/tui/fleet.ts +12 -7
  72. package/dist/extensions/pi-subagents/src/tui/render.ts +227 -14
  73. package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +56 -34
  74. package/dist/extensions/pi-subagents/src/watchdog/diff-tool.ts +77 -0
  75. package/dist/extensions/pi-subagents/src/watchdog/emission-guard.ts +5 -3
  76. package/dist/extensions/pi-subagents/src/watchdog/guidance.ts +20 -0
  77. package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +16 -13
  78. package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +10 -9
  79. package/dist/extensions/pi-subagents/src/watchdog/render.ts +4 -5
  80. package/dist/extensions/pi-subagents/src/watchdog/review.ts +15 -4
  81. package/dist/extensions/pi-subagents/src/watchdog/rules.ts +70 -0
  82. package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +75 -92
  83. package/dist/extensions/pi-subagents/src/watchdog/scope.ts +2 -1
  84. package/dist/extensions/pi-subagents/src/watchdog/settings.ts +48 -104
  85. package/dist/extensions/pi-subagents/src/watchdog/types.ts +18 -32
  86. package/dist/extensions/pi-subagents/src/watchdog/warning-format.ts +0 -1
  87. package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +3 -2
  88. package/dist/extensions/pi-subagents/src/workflows/workflow-checklist.ts +439 -0
  89. package/dist/extensions/pi-subagents/src/workflows/workflow-preflight.ts +28 -1
  90. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +335 -21
  91. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +4 -1
  92. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +1 -1
  93. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +22 -18
  94. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +0 -1
  95. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +21 -18
  96. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +307 -22
  97. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +1 -0
  98. package/dist/extensions/pi-subagents/test/support/helpers.ts +25 -0
  99. package/dist/extensions/pi-subagents/test/support/isolated-temp-root.mjs +15 -4
  100. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +17 -2
  101. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +96 -15
  102. package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +53 -5
  103. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +20 -0
  104. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +221 -8
  105. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +45 -24
  106. package/dist/extensions/pi-subagents/test/unit/agent-scan-dirs.test.ts +133 -0
  107. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +28 -2
  108. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +1 -1
  109. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +2 -0
  110. package/dist/extensions/pi-subagents/test/unit/async-retention.test.ts +4 -1
  111. package/dist/extensions/pi-subagents/test/unit/async-status-projection.test.ts +18 -4
  112. package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +16 -0
  113. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +38 -0
  114. package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +2 -2
  115. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +21 -0
  116. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +19 -6
  117. package/dist/extensions/pi-subagents/test/unit/get-final-output.test.ts +26 -0
  118. package/dist/extensions/pi-subagents/test/unit/herdr-inspector.test.ts +22 -1
  119. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +2 -6
  120. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +66 -1
  121. package/dist/extensions/pi-subagents/test/unit/pi-args-permission-system.test.ts +1 -1
  122. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +116 -10
  123. package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +136 -18
  124. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +20 -0
  125. package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +22 -0
  126. package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +1 -0
  127. package/dist/extensions/pi-subagents/test/unit/project-panes-public-api.test.ts +8 -0
  128. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +127 -48
  129. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +68 -0
  130. package/dist/extensions/pi-subagents/test/unit/runtime-agent-registration.test.ts +17 -0
  131. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +88 -0
  132. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +4 -2
  133. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +9 -9
  134. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +2 -2
  135. package/dist/extensions/pi-subagents/test/unit/steering.test.ts +9 -3
  136. package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +22 -9
  137. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +91 -8
  138. package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +27 -0
  139. package/dist/extensions/pi-subagents/test/unit/temp-paths.test.ts +50 -0
  140. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +0 -1
  141. package/dist/extensions/pi-subagents/test/unit/tool-timeout.test.ts +2 -2
  142. package/dist/extensions/pi-subagents/test/unit/wait-completions.test.ts +34 -0
  143. package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +1 -2
  144. package/dist/extensions/pi-subagents/test/unit/watchdog-child-status.test.ts +73 -13
  145. package/dist/extensions/pi-subagents/test/unit/watchdog-diff-tool.test.ts +79 -0
  146. package/dist/extensions/pi-subagents/test/unit/watchdog-permission-arbiter.test.ts +2 -4
  147. package/dist/extensions/pi-subagents/test/unit/watchdog-render.test.ts +7 -8
  148. package/dist/extensions/pi-subagents/test/unit/watchdog-review.test.ts +48 -3
  149. package/dist/extensions/pi-subagents/test/unit/watchdog-rules.test.ts +37 -0
  150. package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +36 -118
  151. package/dist/extensions/pi-subagents/test/unit/watchdog-scope.test.ts +1 -1
  152. package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +37 -14
  153. package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +45 -26
  154. package/dist/extensions/pi-subagents/test/unit/workflow-checklist.test.ts +223 -0
  155. package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +50 -1
  156. package/dist/extensions/pi-subagents/test/unit/workflow-preflight.test.ts +22 -0
  157. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +136 -4
  158. package/dist/extensions/pi-zentui/README.md +19 -5
  159. package/dist/extensions/pi-zentui/docs/configuration.md +37 -9
  160. package/dist/extensions/pi-zentui/extensions/zentui/config.ts +29 -0
  161. package/dist/extensions/pi-zentui/extensions/zentui/index.ts +31 -2
  162. package/dist/extensions/pi-zentui/extensions/zentui/prototype-patch-registry.ts +55 -11
  163. package/dist/extensions/pi-zentui/extensions/zentui/settings-command.ts +115 -3
  164. package/dist/extensions/pi-zentui/extensions/zentui/settings-previews.ts +119 -2
  165. package/dist/extensions/pi-zentui/extensions/zentui/thinking-experimental.ts +1768 -0
  166. package/dist/extensions/pi-zentui/extensions/zentui/thinking-status.ts +69 -0
  167. package/dist/extensions/pi-zentui/extensions/zentui/thinking-steps.ts +155 -0
  168. package/dist/extensions/pi-zentui/extensions/zentui/ui.ts +2 -1
  169. package/dist/extensions/pi-zentui/extensions/zentui/user-message-osc.ts +16 -2
  170. package/dist/extensions/pi-zentui/extensions/zentui/user-message-styles.ts +1 -1
  171. package/dist/extensions/pi-zentui/test/config-load-lifecycle.test.ts +124 -0
  172. package/dist/extensions/pi-zentui/test/config.test.ts +82 -2
  173. package/dist/extensions/pi-zentui/test/editor-viewport-indicators.test.ts +19 -4
  174. package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +58 -9
  175. package/dist/extensions/pi-zentui/test/package-contents.mjs +33 -0
  176. package/dist/extensions/pi-zentui/test/prototype-patch-registry.test.ts +131 -0
  177. package/dist/extensions/pi-zentui/test/responsive-dependencies.test.ts +4 -4
  178. package/dist/extensions/pi-zentui/test/settings-command.test.ts +289 -7
  179. package/dist/extensions/pi-zentui/test/settings-previews.test.ts +106 -0
  180. package/dist/extensions/pi-zentui/test/thinking-experimental-docs.test.ts +51 -0
  181. package/dist/extensions/pi-zentui/test/thinking-experimental-loader-diagnostics.mjs +54 -0
  182. package/dist/extensions/pi-zentui/test/thinking-experimental-missing-export.test.ts +44 -0
  183. package/dist/extensions/pi-zentui/test/thinking-experimental-rows.test.ts +297 -0
  184. package/dist/extensions/pi-zentui/test/thinking-experimental-tui.mjs +2329 -0
  185. package/dist/extensions/pi-zentui/test/thinking-experimental.test.ts +1668 -0
  186. package/dist/extensions/pi-zentui/test/thinking-status.test.ts +85 -0
  187. package/dist/extensions/pi-zentui/test/thinking-steps.test.ts +122 -0
  188. package/dist/extensions/pi-zentui/test/user-message-osc.test.ts +58 -0
  189. package/dist/skills/brandkit/SKILL.md +0 -2
  190. package/dist/skills/{industrial-brutalist-ui → brutalist}/SKILL.md +1 -3
  191. package/dist/skills/gpt-taste/SKILL.md +0 -2
  192. package/dist/skills/image-to-code/SKILL.md +0 -2
  193. package/dist/skills/imagegen-frontend-mobile/SKILL.md +0 -2
  194. package/dist/skills/imagegen-frontend-web/SKILL.md +0 -2
  195. package/dist/skills/{minimalist-ui → minimalist}/SKILL.md +1 -3
  196. package/dist/skills/{full-output-enforcement → output}/SKILL.md +1 -3
  197. package/dist/skills/{redesign-existing-projects → redesign}/SKILL.md +1 -3
  198. package/dist/skills/{high-end-visual-design → soft}/SKILL.md +1 -3
  199. package/dist/skills/stitch/DESIGN.md +121 -0
  200. package/dist/skills/{stitch-design-taste → stitch}/SKILL.md +1 -3
  201. package/dist/skills/{design-taste-frontend → taste}/SKILL.md +1 -3
  202. package/dist/skills/{design-taste-frontend-v1 → taste-v1}/SKILL.md +2 -4
  203. package/package.json +1 -1
@@ -0,0 +1,44 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { describe, expect, it, vi } from "vitest";
4
+
5
+ vi.mock("@earendil-works/pi-coding-agent", async (importOriginal) => ({
6
+ ...(await importOriginal<typeof import("@earendil-works/pi-coding-agent")>()),
7
+ AssistantMessageComponent: undefined,
8
+ }));
9
+
10
+ describe("Streaming (Experimental) optional private export", () => {
11
+ it("loads fail-open and preserves the selected mode when the private constructor is absent", async () => {
12
+ const { ThinkingExperimentalController } = await import(
13
+ "../extensions/zentui/thinking-experimental"
14
+ );
15
+ const config = { enabled: true, mode: "streaming" as const };
16
+ const value = new ThinkingExperimentalController(() => config);
17
+ const ctx = {
18
+ mode: "tui",
19
+ hasUI: true,
20
+ ui: { onTerminalInput: vi.fn() },
21
+ sessionManager: { getEntries: () => [] },
22
+ };
23
+
24
+ expect(value.state).toMatchObject({ available: false, active: false });
25
+ expect(value.startSession(ctx as never)).toMatchObject({
26
+ applied: false,
27
+ reason: expect.stringContaining("unavailable"),
28
+ });
29
+ expect(config).toEqual({ enabled: true, mode: "streaming" });
30
+ value.shutdown();
31
+ });
32
+
33
+ it("does not statically import or deep-import the private renderer", () => {
34
+ const source = readFileSync(
35
+ join(import.meta.dirname, "../extensions/zentui/thinking-experimental.ts"),
36
+ "utf8",
37
+ );
38
+ expect(source).not.toMatch(
39
+ /import\s*\{[^}]*AssistantMessageComponent[^}]*\}\s*from\s*["']@earendil-works\/pi-coding-agent["']/s,
40
+ );
41
+ expect(source).not.toMatch(/pi-coding-agent\/(?:dist|src)\//);
42
+ expect(source).toContain("AssistantMessageComponent?: PrivateAssistantConstructor");
43
+ });
44
+ });
@@ -0,0 +1,297 @@
1
+ import { Markdown, type MarkdownTheme, visibleWidth } from "@earendil-works/pi-tui";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { createThinkingStepsRows } from "../extensions/zentui/thinking-experimental";
4
+ import { parseThinkingSteps } from "../extensions/zentui/thinking-steps";
5
+
6
+ const identityTheme = Object.fromEntries(
7
+ [
8
+ "heading",
9
+ "link",
10
+ "linkUrl",
11
+ "code",
12
+ "codeBlock",
13
+ "codeBlockBorder",
14
+ "quote",
15
+ "quoteBorder",
16
+ "hr",
17
+ "listBullet",
18
+ "bold",
19
+ "italic",
20
+ "strikethrough",
21
+ "underline",
22
+ ].map((key) => [key, (text: string) => text]),
23
+ ) as unknown as MarkdownTheme;
24
+
25
+ function plain(row: string): string {
26
+ return row
27
+ .replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "")
28
+ .replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "")
29
+ .trimEnd();
30
+ }
31
+
32
+ function rows(
33
+ source: string,
34
+ mode: "rail" | "tree",
35
+ width = 80,
36
+ incomplete = false,
37
+ paddingX = 0,
38
+ markdownTheme = identityTheme,
39
+ options: ConstructorParameters<typeof Markdown>[5] = { renderLatex: true },
40
+ ) {
41
+ const style = { color: (text: string) => text, italic: true };
42
+ const native = new Markdown(source, paddingX, 0, markdownTheme, style, options);
43
+ const steps = parseThinkingSteps(source);
44
+ if (!steps) throw new Error("fixture must parse");
45
+ const accent = vi.fn((_: "accent", text: string) => `\x1b[35m${text}\x1b[0m`);
46
+ const component = createThinkingStepsRows(
47
+ native,
48
+ {
49
+ text: source,
50
+ paddingX,
51
+ paddingY: 0,
52
+ theme: markdownTheme,
53
+ defaultTextStyle: style,
54
+ options,
55
+ },
56
+ steps,
57
+ mode,
58
+ incomplete,
59
+ () => ({ fg: accent }),
60
+ );
61
+ return { rendered: component.render(width), component, native, accent };
62
+ }
63
+
64
+ describe("Experimental native Rail/Tree rows", () => {
65
+ it("renders all Rail labels and the latest five Tree labels with active/settled markers", () => {
66
+ const source = Array.from({ length: 7 }, (_, index) => `# Label ${index + 1}`).join("\n");
67
+ expect(rows(source, "rail", 80, true).rendered.map(plain)).toEqual([
68
+ "│ Thinking",
69
+ "│ Label 1",
70
+ "│ Label 2",
71
+ "│ Label 3",
72
+ "│ Label 4",
73
+ "│ Label 5",
74
+ "│ Label 6",
75
+ "│ • Label 7",
76
+ ]);
77
+ expect(rows(source, "tree").rendered.map(plain)).toEqual([
78
+ "┆ Thinking",
79
+ "├─ · Label 3",
80
+ "├─ · Label 4",
81
+ "├─ · Label 5",
82
+ "├─ · Label 6",
83
+ "└─ · Label 7",
84
+ ]);
85
+ });
86
+
87
+ it.each(["rail", "tree"] as const)(
88
+ "renders SGR-decorated thinking structurally in %s without carrying source SGR forward",
89
+ (mode) => {
90
+ const truecolor = "\x1b[38;2;137;180;250m";
91
+ const muted = "\x1b[38;2;186;194;222m";
92
+ const reset = "\x1b[39m";
93
+ const source = `${truecolor}# First${reset}\n${muted}body${reset}\n${truecolor}# Second${reset}`;
94
+ const output = rows(source, mode).rendered;
95
+ expect(output.map(plain)).toEqual(
96
+ mode === "rail"
97
+ ? ["│ Thinking", "│ First", "│ Second"]
98
+ : ["┆ Thinking", "├─ · First", "└─ · Second"],
99
+ );
100
+ expect(output.join("\n")).not.toContain(truecolor);
101
+ expect(output.join("\n")).not.toContain(muted);
102
+ expect(output.join("\n")).not.toContain(reset);
103
+ },
104
+ );
105
+
106
+ it("fails open when stripping SGR reconstructs image syntax", () => {
107
+ const source = "# ![im\x1b[31mage](asset.png)";
108
+ const value = rows(source, "tree");
109
+ expect(value.rendered).toEqual(value.native.render(80));
110
+ });
111
+
112
+ it("preserves native label Markdown callbacks and accents connectors separately", () => {
113
+ const italic = vi.fn((text: string) => `<i>${text}</i>`);
114
+ const code = vi.fn((text: string) => `<c>${text}</c>`);
115
+ const link = vi.fn((text: string) => `<a>${text}</a>`);
116
+ const current = rows("# *emphasis* `code` [link](https://example.com)", "rail", 80, false, 0, {
117
+ ...identityTheme,
118
+ italic,
119
+ code,
120
+ link,
121
+ });
122
+ current.rendered.map(plain);
123
+ expect(italic).toHaveBeenCalled();
124
+ expect(code).toHaveBeenCalledWith("code");
125
+ expect(link).toHaveBeenCalled();
126
+ expect(current.accent).toHaveBeenCalledWith("accent", "│ ");
127
+ });
128
+
129
+ it("preserves host transform, HTML, and literal-LaTeX behavior on fresh label Markdown", () => {
130
+ const capabilityTransform = vi.fn(() => "**transformed-capability**");
131
+ const capabilityOutput = plain(
132
+ new Markdown(
133
+ "plain-capability",
134
+ 0,
135
+ 0,
136
+ {
137
+ ...identityTheme,
138
+ bold: (text) => `<b>${text}</b>`,
139
+ },
140
+ undefined,
141
+ {
142
+ transform: capabilityTransform,
143
+ },
144
+ ).render(78)[0] ?? "",
145
+ );
146
+ const hostExecutesTransform = capabilityOutput === "<b>transformed-capability</b>";
147
+ expect(capabilityOutput).toBe(
148
+ hostExecutesTransform ? "<b>transformed-capability</b>" : "plain-capability",
149
+ );
150
+ expect(capabilityTransform).toHaveBeenCalledTimes(hostExecutesTransform ? 1 : 0);
151
+
152
+ const source = "native <span>HTML</span> $x^2$";
153
+ const transform = vi.fn((text: string) => text.replace("native", "**native**"));
154
+ const bold = vi.fn((text: string) => `<b>${text}</b>`);
155
+ const theme = { ...identityTheme, bold };
156
+ const options = { transform, renderLatex: false };
157
+ const nativeLabel = plain(
158
+ new Markdown(source, 0, 0, theme, undefined, options).render(78)[0] ?? "",
159
+ );
160
+ expect(nativeLabel).toContain("$x^2$");
161
+ expect(transform).toHaveBeenCalledTimes(hostExecutesTransform ? 1 : 0);
162
+ transform.mockClear();
163
+ bold.mockClear();
164
+
165
+ const output = rows(`# ${source}`, "rail", 80, false, 0, theme, options).rendered.map(plain);
166
+ expect(output[1]).toBe(`│ ${nativeLabel}`);
167
+ if (hostExecutesTransform) {
168
+ expect(transform.mock.calls).toEqual([
169
+ ["**Thinking**", 78],
170
+ [source, 78],
171
+ ]);
172
+ expect(bold.mock.calls).toEqual([["Thinking"], ["native"]]);
173
+ } else {
174
+ expect(transform).not.toHaveBeenCalled();
175
+ expect(bold.mock.calls).toEqual([["Thinking"]]);
176
+ }
177
+ expect(output[1]).toContain("HTML");
178
+ expect(output[1]).toContain("$x^2$");
179
+ });
180
+
181
+ it.each([
182
+ {
183
+ name: "ordered-list source markers",
184
+ source: "7) ordered marker",
185
+ option: "preserveOrderedListMarkers" as const,
186
+ expectedWhenSupported: "7) ordered marker",
187
+ expectedWhenDisabled: "7. ordered marker",
188
+ },
189
+ {
190
+ name: "backslash escapes",
191
+ source: String.raw`escaped \*asterisks\*`,
192
+ option: "preserveBackslashEscapes" as const,
193
+ expectedWhenSupported: String.raw`escaped \*asterisks\*`,
194
+ expectedWhenDisabled: "escaped *asterisks*",
195
+ },
196
+ ])("preserves $name behavior exactly when the host supports it", (fixture) => {
197
+ const enabledOptions = { [fixture.option]: true, renderLatex: false };
198
+ const disabledOptions = { [fixture.option]: false, renderLatex: false };
199
+ const nativeEnabled = plain(
200
+ new Markdown(fixture.source, 0, 0, identityTheme, undefined, enabledOptions).render(78)[0] ??
201
+ "",
202
+ );
203
+ const nativeDisabled = plain(
204
+ new Markdown(fixture.source, 0, 0, identityTheme, undefined, disabledOptions).render(78)[0] ??
205
+ "",
206
+ );
207
+ const output = rows(
208
+ `# ${fixture.source}`,
209
+ "rail",
210
+ 80,
211
+ false,
212
+ 0,
213
+ identityTheme,
214
+ enabledOptions,
215
+ ).rendered.map(plain);
216
+
217
+ expect(nativeDisabled).toBe(fixture.expectedWhenDisabled);
218
+ expect(output[1]).toBe(`│ ${nativeEnabled}`);
219
+ if (nativeEnabled !== nativeDisabled) {
220
+ expect(nativeEnabled).toBe(fixture.expectedWhenSupported);
221
+ } else {
222
+ expect(output[1]).toBe(`│ ${nativeDisabled}`);
223
+ }
224
+ });
225
+
226
+ it.each(["界語 emoji 👨‍👩‍👧‍👦", "cafe\u0301 punctuation /().;:"])(
227
+ "crops %s to one ANSI-safe bounded row",
228
+ (label) => {
229
+ for (const width of [10, 20, 80]) {
230
+ const rendered = rows(`# ${label.repeat(10)}`, "tree", width, true).rendered;
231
+ expect(rendered).toHaveLength(2);
232
+ expect(rendered.every((row) => visibleWidth(row) <= width)).toBe(true);
233
+ expect(plain(rendered[1] ?? "")).toContain("…");
234
+ }
235
+ },
236
+ );
237
+
238
+ it("uses one-cell ellipsis, preserves outer padding, and invalidates derived Markdown", () => {
239
+ const value = rows("# long label", "rail", 5, false, 1);
240
+ expect(value.rendered.every((row) => visibleWidth(row) <= 5)).toBe(true);
241
+ expect(plain(value.rendered[1] ?? "")).toBe(" │ …");
242
+ const invalidate = vi.spyOn(value.native, "invalidate");
243
+ value.component.invalidate();
244
+ expect(invalidate).toHaveBeenCalledOnce();
245
+ });
246
+
247
+ it("renders an Error label through native Markdown semantics", () => {
248
+ const value = rows("# Error: **retry** with `safe-code`", "rail");
249
+ expect(value.rendered.map(plain)).toEqual(["│ Thinking", "│ Error: retry with safe-code"]);
250
+ });
251
+
252
+ it("fails open to the complete native run for an image label", () => {
253
+ const value = rows("# ![image](asset.png)", "rail");
254
+ expect(value.rendered).toEqual(value.native.render(80));
255
+ });
256
+
257
+ it.each(["empty", "throw"] as const)(
258
+ "fails open to the complete native run when a derived label render is %s",
259
+ (failure) => {
260
+ const original = Markdown.prototype.render;
261
+ const render = vi
262
+ .spyOn(Markdown.prototype, "render")
263
+ .mockImplementation(function derivedFailure(this: Markdown, width: number) {
264
+ const text = (this as unknown as { text?: string }).text;
265
+ if (text === "fallback label") {
266
+ if (failure === "throw") throw new Error("derived render failed");
267
+ return [];
268
+ }
269
+ return Reflect.apply(original, this, [width]);
270
+ });
271
+ try {
272
+ const value = rows("# fallback label", "rail");
273
+ expect(value.rendered).toEqual(value.native.render(80));
274
+ } finally {
275
+ render.mockRestore();
276
+ }
277
+ },
278
+ );
279
+
280
+ it("fails open to native rows when current-theme connector rendering throws", () => {
281
+ const source = "# theme fallback";
282
+ const native = new Markdown(source, 0, 0, identityTheme);
283
+ const steps = parseThinkingSteps(source);
284
+ if (!steps) throw new Error("fixture must parse");
285
+ const component = createThinkingStepsRows(
286
+ native,
287
+ { text: source, paddingX: 0, paddingY: 0, theme: identityTheme },
288
+ steps,
289
+ "tree",
290
+ false,
291
+ () => {
292
+ throw new Error("theme unavailable");
293
+ },
294
+ );
295
+ expect(component.render(80)).toEqual(native.render(80));
296
+ });
297
+ });