@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
@@ -8,6 +8,8 @@ import {
8
8
  type FooterComponentConfig,
9
9
  type PolishedTuiConfig,
10
10
  type SelectorBordersComponentConfig,
11
+ type ThinkingStepsComponentConfig,
12
+ type ThinkingStepsMode,
11
13
  type UserMessagesComponentConfig,
12
14
  type WorkingLineComponentPatch,
13
15
  } from "../extensions/zentui/config";
@@ -23,6 +25,7 @@ const sectionNames = [
23
25
  "Appearance",
24
26
  "Editor",
25
27
  "User messages",
28
+ "Thinking",
26
29
  "Working line",
27
30
  "Footer",
28
31
  "Segments",
@@ -121,6 +124,7 @@ function createHarness(
121
124
  let command: Command | undefined;
122
125
  let component: Component | undefined;
123
126
  const notifications: string[] = [];
127
+ const notificationEvents: Array<{ message: string; severity: string }> = [];
124
128
  let doneCalls = 0;
125
129
  const sessionLifecycle = new SessionLifecycle();
126
130
  const calls = {
@@ -129,6 +133,7 @@ function createHarness(
129
133
  polishedCopyFriendly: [] as Array<Record<string, unknown>>,
130
134
  accentRail: [] as Array<Record<string, unknown>>,
131
135
  messages: [] as Partial<UserMessagesComponentConfig>[],
136
+ thinkingSteps: [] as Partial<ThinkingStepsComponentConfig>[],
132
137
  workingLine: [] as WorkingLineComponentPatch[],
133
138
  renders: { shared: 0, local: 0 },
134
139
  selectors: [] as Partial<SelectorBordersComponentConfig>[],
@@ -169,6 +174,12 @@ function createHarness(
169
174
  calls.messages.push(patch);
170
175
  Object.assign(config.components.userMessages, patch);
171
176
  },
177
+ thinkingStepsCapability: { available: true },
178
+ setThinkingStepsComponent(patch: Partial<ThinkingStepsComponentConfig>) {
179
+ calls.thinkingSteps.push(patch);
180
+ Object.assign(config.components.thinkingSteps, patch);
181
+ return { applied: true };
182
+ },
172
183
  setWorkingLineComponent(patch: WorkingLineComponentPatch) {
173
184
  calls.workingLine.push(patch);
174
185
  const { messages, segments, ...componentPatch } = patch;
@@ -240,8 +251,9 @@ function createHarness(
240
251
  cwd: process.cwd(),
241
252
  ui: {
242
253
  theme: theme(),
243
- notify(message: string) {
254
+ notify(message: string, severity = "info") {
244
255
  notifications.push(message);
256
+ notificationEvents.push({ message, severity });
245
257
  },
246
258
  async custom(factory: (...args: unknown[]) => unknown) {
247
259
  component = factory(
@@ -276,6 +288,7 @@ function createHarness(
276
288
  ctx,
277
289
  calls,
278
290
  notifications,
291
+ notificationEvents,
279
292
  sessionLifecycle,
280
293
  doneCalls: () => doneCalls,
281
294
  };
@@ -286,7 +299,7 @@ afterEach(() => {
286
299
  });
287
300
 
288
301
  describe("component-oriented /zentui settings", () => {
289
- it("uses the exact eight-section order in wide and narrow navigation", async () => {
302
+ it("uses the exact nine-section order in wide and narrow navigation", async () => {
290
303
  const harness = createHarness();
291
304
  await harness.command().handler("", harness.ctx);
292
305
  const component = harness.component();
@@ -300,7 +313,7 @@ describe("component-oriented /zentui settings", () => {
300
313
  for (const [index, name] of sectionNames.entries()) {
301
314
  const lines = component.render(40);
302
315
  expect(lines[1]).toContain(name);
303
- expect(lines[1]).toContain(`(${index + 1}/8)`);
316
+ expect(lines[1]).toContain(`(${index + 1}/9)`);
304
317
  expect(lines.every((line) => visibleWidth(line) <= 40)).toBe(true);
305
318
  component.handleInput("\t");
306
319
  }
@@ -344,6 +357,8 @@ describe("component-oriented /zentui settings", () => {
344
357
  component.handleInput("\t");
345
358
  expectFocusOrder(component, ["User messages", "Message style", "Message colors"]);
346
359
  component.handleInput("\t");
360
+ expectFocusOrder(component, ["Enabled", "Mode"]);
361
+ component.handleInput("\t");
347
362
  expectFocusOrder(component, [
348
363
  "Enabled",
349
364
  "Turn summary",
@@ -356,7 +371,7 @@ describe("component-oriented /zentui settings", () => {
356
371
  "Custom messages",
357
372
  "Tool",
358
373
  "Elapsed",
359
- "Thinking",
374
+ "Thinking time",
360
375
  "Tokens",
361
376
  "Message list",
362
377
  ]);
@@ -533,7 +548,7 @@ describe("component-oriented /zentui settings", () => {
533
548
  { showGit: false },
534
549
  ]);
535
550
 
536
- for (let index = 0; index < 5; index += 1) component.handleInput("\t");
551
+ for (let index = 0; index < 6; index += 1) component.handleInput("\t");
537
552
  for (const [label, value] of [
538
553
  ["Commit only on detached HEAD", "disabled"],
539
554
  ["Show exact-match tag", "disabled"],
@@ -691,6 +706,7 @@ describe("component-oriented /zentui settings", () => {
691
706
  component.handleInput(" ");
692
707
  component.handleInput("\t");
693
708
  component.handleInput("\t");
709
+ component.handleInput("\t");
694
710
  selectLabel(component, "Footer colors");
695
711
  component.handleInput(" ");
696
712
  selectLabel(component, "Footer model label");
@@ -774,6 +790,7 @@ describe("component-oriented /zentui settings", () => {
774
790
  component.handleInput("\t");
775
791
  component.handleInput("\t");
776
792
  component.handleInput("\t");
793
+ component.handleInput("\t");
777
794
  selectLabel(component, "Ignore submodules");
778
795
  component.handleInput(" ");
779
796
  component.handleInput("\x1b[Z");
@@ -792,6 +809,7 @@ describe("component-oriented /zentui settings", () => {
792
809
  component.handleInput("\t");
793
810
  component.handleInput("\t");
794
811
  component.handleInput("\t");
812
+ component.handleInput("\t");
795
813
  expect(row(component, "Ignore submodules")).toContain("enabled");
796
814
  component.handleInput("\x1b[Z");
797
815
  expect(row(component, "Model info")).toContain("enabled");
@@ -823,6 +841,7 @@ describe("component-oriented /zentui settings", () => {
823
841
  component.handleInput("\t");
824
842
  component.handleInput("\t");
825
843
  component.handleInput("\t");
844
+ component.handleInput("\t");
826
845
  selectLabel(component, "Model info");
827
846
  component.handleInput(" ");
828
847
  expect(focusedRow(component)).toContain("> Model info");
@@ -835,6 +854,267 @@ describe("component-oriented /zentui settings", () => {
835
854
  ]);
836
855
  });
837
856
 
857
+ it("shows Thinking (Experimental) with only Enabled and Mode focus rows", async () => {
858
+ const harness = createHarness();
859
+ await harness.command().handler("", harness.ctx);
860
+ const component = harness.component();
861
+ goToSection(component, "Thinking");
862
+ expect(component.render(120).join("\n")).toContain("Thinking (Experimental)");
863
+ expectFocusOrder(component, ["Enabled", "Mode"]);
864
+ expect(component.render(120).join("\n")).toContain(
865
+ "Live switching supports Streaming → Rail/Tree and Rail ↔ Tree. Entering Streaming,",
866
+ );
867
+ });
868
+
869
+ it("shows saved and active mode mismatch honestly", async () => {
870
+ const config = cloneConfig();
871
+ config.components.thinkingSteps.enabled = true;
872
+ config.components.thinkingSteps.mode = "streaming";
873
+ const harness = createHarness(config, {
874
+ thinkingStepsCapability: {
875
+ state: {
876
+ available: true,
877
+ rendererAvailable: true,
878
+ streamingAvailable: false,
879
+ active: true,
880
+ activeMode: "tree",
881
+ startup: { enabled: true, mode: "tree" },
882
+ displaced: false,
883
+ restartRequired: true,
884
+ reason: "Streaming listener unavailable; restart required",
885
+ },
886
+ },
887
+ });
888
+ await harness.command().handler("", harness.ctx);
889
+ const component = harness.component();
890
+ goToSection(component, "Thinking");
891
+ const output = component.render(160).join("\n");
892
+ expect(output).toContain(
893
+ "Saved: Streaming · Active: Tree · Streaming unavailable · restart required · Streaming listener unavailable",
894
+ );
895
+ expect(output.match(/restart required/gi)).toHaveLength(2);
896
+ // One status appears in the preview and one in the focused setting description;
897
+ // neither duplicates reason text that already contained the phrase.
898
+ });
899
+
900
+ it("keeps private-renderer unavailability non-focusable and fails open to native", async () => {
901
+ const harness = createHarness(cloneConfig(), {
902
+ thinkingStepsCapability: { available: false },
903
+ });
904
+ await harness.command().handler("", harness.ctx);
905
+ const component = harness.component();
906
+ goToSection(component, "Thinking");
907
+ const output = component.render(120).join("\n");
908
+ expect(output).toContain("Saved: Disabled · Active: Native · Renderer unavailable");
909
+ expectFocusOrder(component, ["Enabled", "Mode"]);
910
+ });
911
+
912
+ it("routes Thinking-step enablement and mode independently", async () => {
913
+ const harness = createHarness();
914
+ await harness.command().handler("", harness.ctx);
915
+ const component = harness.component();
916
+ goToSection(component, "Thinking");
917
+ expectFocusOrder(component, ["Enabled", "Mode"]);
918
+ selectLabel(component, "Enabled");
919
+ component.handleInput(" ");
920
+ selectLabel(component, "Mode");
921
+ component.handleInput(" ");
922
+ expect(harness.calls.thinkingSteps).toEqual([{ enabled: true }, { mode: "rail" }]);
923
+ expect(harness.config.components.thinkingSteps).toEqual({
924
+ enabled: true,
925
+ mode: "rail",
926
+ });
927
+ expect(harness.notifications).toContain("Thinking (Experimental): Rail");
928
+ });
929
+
930
+ it("cycles the real mode setting through Streaming to Tree", async () => {
931
+ const config = cloneConfig();
932
+ config.components.thinkingSteps = { enabled: true, mode: "streaming" };
933
+ const harness = createHarness(config);
934
+ await harness.command().handler("", harness.ctx);
935
+ const component = harness.component();
936
+ goToSection(component, "Thinking");
937
+ selectLabel(component, "Mode");
938
+ component.handleInput(" ");
939
+ expect(harness.calls.thinkingSteps).toEqual([{ mode: "tree" }]);
940
+ expect(harness.config.components.thinkingSteps.mode).toBe("tree");
941
+ });
942
+
943
+ it("rebuilds Thinking settings in focus with honest live-success status and preview", async () => {
944
+ const config = cloneConfig();
945
+ config.components.thinkingSteps = { enabled: true, mode: "streaming" };
946
+ const state = {
947
+ available: true,
948
+ active: true,
949
+ activeMode: "streaming" as ThinkingStepsMode,
950
+ startup: { enabled: true, mode: "streaming" as ThinkingStepsMode },
951
+ displaced: false,
952
+ restartRequired: false,
953
+ };
954
+ const harness = createHarness(config, {
955
+ thinkingStepsCapability: { state },
956
+ setThinkingStepsComponent(patch: Partial<ThinkingStepsComponentConfig>) {
957
+ Object.assign(config.components.thinkingSteps, patch);
958
+ if (patch.mode) state.activeMode = patch.mode;
959
+ return { applied: true };
960
+ },
961
+ });
962
+ await harness.command().handler("", harness.ctx);
963
+ const component = harness.component();
964
+ goToSection(component, "Thinking");
965
+ selectLabel(component, "Mode");
966
+ component.handleInput(" ");
967
+ const output = component.render(160).join("\n");
968
+ expect(focusedRow(component)).toContain("> Mode");
969
+ expect(output).toContain("Saved: Tree · Active: Tree");
970
+ expect(harness.notificationEvents.at(-1)).toEqual({
971
+ message: "Thinking (Experimental): Tree",
972
+ severity: "info",
973
+ });
974
+ });
975
+
976
+ it("rebuilds Thinking settings in focus with saved/active failure status and preview", async () => {
977
+ const config = cloneConfig();
978
+ config.components.thinkingSteps = { enabled: true, mode: "rail" };
979
+ const state = {
980
+ available: true,
981
+ rendererAvailable: true,
982
+ streamingAvailable: true,
983
+ active: true,
984
+ activeMode: "rail" as ThinkingStepsMode,
985
+ startup: { enabled: true, mode: "tree" as ThinkingStepsMode },
986
+ displaced: false,
987
+ restartRequired: false,
988
+ reason: undefined as string | undefined,
989
+ };
990
+ const harness = createHarness(config, {
991
+ thinkingStepsCapability: { state },
992
+ setThinkingStepsComponent(patch: Partial<ThinkingStepsComponentConfig>) {
993
+ Object.assign(config.components.thinkingSteps, patch);
994
+ state.restartRequired = true;
995
+ return {
996
+ applied: false,
997
+ reason: "Saved: Streaming · Active: Rail · restart required",
998
+ };
999
+ },
1000
+ });
1001
+ await harness.command().handler("", harness.ctx);
1002
+ const component = harness.component();
1003
+ goToSection(component, "Thinking");
1004
+ selectLabel(component, "Mode");
1005
+ component.handleInput(" ");
1006
+ const output = component.render(180).join("\n");
1007
+ expect(focusedRow(component)).toContain("> Mode");
1008
+ const expectedStatus = "Saved: Streaming · Active: Rail · restart required";
1009
+ expect(output).toContain(expectedStatus);
1010
+ expect(output).toContain("Thinking 7.1s");
1011
+ expect(harness.notificationEvents.at(-1)).toEqual({
1012
+ message: `Thinking (Experimental): Streaming (${expectedStatus})`,
1013
+ severity: "warning",
1014
+ });
1015
+ });
1016
+
1017
+ it("includes a cleanup warning in a successful Thinking notification", async () => {
1018
+ const config = cloneConfig();
1019
+ config.components.thinkingSteps = { enabled: true, mode: "streaming" };
1020
+ const state = {
1021
+ available: true,
1022
+ rendererAvailable: true,
1023
+ streamingAvailable: true,
1024
+ active: true,
1025
+ activeMode: "streaming" as ThinkingStepsMode,
1026
+ startup: { enabled: true, mode: "streaming" as ThinkingStepsMode },
1027
+ displaced: false,
1028
+ restartRequired: false,
1029
+ reason: undefined as string | undefined,
1030
+ };
1031
+ const warning =
1032
+ "Saved: Tree · Active: Tree · Streaming unavailable · Pi's terminal input listener cleanup is unavailable";
1033
+ const harness = createHarness(config, {
1034
+ thinkingStepsCapability: { state },
1035
+ setThinkingStepsComponent(patch: Partial<ThinkingStepsComponentConfig>) {
1036
+ Object.assign(config.components.thinkingSteps, patch);
1037
+ state.activeMode = "tree";
1038
+ state.streamingAvailable = false;
1039
+ state.reason = "Pi's terminal input listener cleanup is unavailable";
1040
+ return { applied: true, reason: warning };
1041
+ },
1042
+ });
1043
+ await harness.command().handler("", harness.ctx);
1044
+ const component = harness.component();
1045
+ goToSection(component, "Thinking");
1046
+ selectLabel(component, "Mode");
1047
+ component.handleInput(" ");
1048
+ expect(component.render(180).join("\n")).toContain(warning);
1049
+ expect(harness.notificationEvents.at(-1)).toEqual({
1050
+ message: `Thinking (Experimental): Tree (${warning})`,
1051
+ severity: "warning",
1052
+ });
1053
+ });
1054
+
1055
+ it("shows a warning when disabling Streaming restores native output with degraded cleanup", async () => {
1056
+ const config = cloneConfig();
1057
+ config.components.thinkingSteps = { enabled: true, mode: "streaming" };
1058
+ const state = {
1059
+ available: true,
1060
+ rendererAvailable: true,
1061
+ streamingAvailable: true,
1062
+ active: true,
1063
+ activeMode: "streaming" as ThinkingStepsMode | undefined,
1064
+ startup: { enabled: true, mode: "streaming" as ThinkingStepsMode },
1065
+ displaced: false,
1066
+ restartRequired: false,
1067
+ reason: undefined as string | undefined,
1068
+ };
1069
+ const warning =
1070
+ "Saved: Disabled · Active: Native · Streaming unavailable · Pi's terminal input listener cleanup is unavailable";
1071
+ const harness = createHarness(config, {
1072
+ thinkingStepsCapability: { state },
1073
+ setThinkingStepsComponent(patch: Partial<ThinkingStepsComponentConfig>) {
1074
+ Object.assign(config.components.thinkingSteps, patch);
1075
+ state.active = false;
1076
+ state.activeMode = undefined;
1077
+ state.streamingAvailable = false;
1078
+ state.reason = "Pi's terminal input listener cleanup is unavailable";
1079
+ return { applied: true, reason: warning };
1080
+ },
1081
+ });
1082
+ await harness.command().handler("", harness.ctx);
1083
+ const component = harness.component();
1084
+ goToSection(component, "Thinking");
1085
+ selectLabel(component, "Enabled");
1086
+ component.handleInput(" ");
1087
+ expect(component.render(180).join("\n")).toContain(warning);
1088
+ expect(harness.notificationEvents.at(-1)).toEqual({
1089
+ message: `Thinking (Experimental): disabled (${warning})`,
1090
+ severity: "warning",
1091
+ });
1092
+ });
1093
+
1094
+ it("restores Thinking-step rows after persistence failure and exposes no direct route", async () => {
1095
+ const harness = createHarness(cloneConfig(), {
1096
+ setThinkingStepsComponent() {
1097
+ throw new Error("read-only thinking");
1098
+ },
1099
+ });
1100
+ await harness.command().handler("", harness.ctx);
1101
+ const component = harness.component();
1102
+ goToSection(component, "Thinking");
1103
+ selectLabel(component, "Enabled");
1104
+ component.handleInput(" ");
1105
+ expect(focusedRow(component)).toContain("disabled");
1106
+ expect(harness.notifications).toContain("Could not update Zentui settings: read-only thinking");
1107
+ await harness.command().handler("thinking-steps", harness.ctx);
1108
+ expect(harness.notifications.at(-1)).toMatch(/^Usage:/);
1109
+ expect(
1110
+ harness
1111
+ .command()
1112
+ .getArgumentCompletions("")
1113
+ ?.map((item) => item.value)
1114
+ .join("\n"),
1115
+ ).not.toMatch(/thinking.steps/i);
1116
+ });
1117
+
838
1118
  it("routes all Working-line rows independently", async () => {
839
1119
  const harness = createHarness();
840
1120
  await harness.command().handler("working-line", harness.ctx);
@@ -851,7 +1131,7 @@ describe("component-oriented /zentui settings", () => {
851
1131
  "Custom messages",
852
1132
  "Tool",
853
1133
  "Elapsed",
854
- "Thinking",
1134
+ "Thinking time",
855
1135
  "Tokens",
856
1136
  "Message list",
857
1137
  ]);
@@ -867,7 +1147,7 @@ describe("component-oriented /zentui settings", () => {
867
1147
  ["Custom messages", "disabled"],
868
1148
  ["Tool", "disabled"],
869
1149
  ["Elapsed", "disabled"],
870
- ["Thinking", "disabled"],
1150
+ ["Thinking time", "disabled"],
871
1151
  ["Tokens", "disabled"],
872
1152
  ] as const) {
873
1153
  selectLabel(component, label);
@@ -1154,6 +1434,8 @@ describe("component-oriented /zentui settings", () => {
1154
1434
  ]);
1155
1435
  expect(vi.getTimerCount()).toBe(0);
1156
1436
  component.handleInput("\t");
1437
+ expect(component.render(100).join("\n")).toContain("Thinking");
1438
+ component.handleInput("\t");
1157
1439
  const workingRows = component.render(100);
1158
1440
  expectStackedPreview(workingRows, "Sautéing…");
1159
1441
  component.handleInput("\t");
@@ -10,6 +10,7 @@ import {
10
10
  } from "../extensions/zentui/config";
11
11
  import {
12
12
  renderEditorSettingsPreview,
13
+ renderThinkingStepsSettingsPreview,
13
14
  renderUserMessageSettingsPreview,
14
15
  SETTINGS_PREVIEW_MAX_ROWS,
15
16
  SETTINGS_PREVIEW_MAX_WIDTH,
@@ -275,6 +276,111 @@ describe("settings previews", () => {
275
276
  expect(disabledOutput).not.toContain("preview");
276
277
  });
277
278
 
279
+ it("renders all Rail labels and the latest five Tree labels through native Markdown rows", () => {
280
+ const source = config();
281
+ for (const mode of ["rail", "tree"] as const) {
282
+ source.components.thinkingSteps.mode = mode;
283
+ const output = plain(renderThinkingStepsSettingsPreview(source, theme(), 72));
284
+ expect(output).toContain(mode === "rail" ? "│ Thinking" : "┆ Thinking");
285
+ expect(output).toContain(
286
+ mode === "rail" ? "│ • Verify compatibility" : "└─ • Verify compatibility",
287
+ );
288
+ if (mode === "tree") expect(output).not.toContain("Inspect the change");
289
+ else expect(output).toContain("Inspect the change");
290
+ }
291
+ });
292
+
293
+ it("renders a pure static Streaming preview with saved/restart/active status", () => {
294
+ const current = config();
295
+ current.components.thinkingSteps.enabled = true;
296
+ current.components.thinkingSteps.mode = "streaming";
297
+ const output = plain(
298
+ renderThinkingStepsSettingsPreview(current, theme(), 72, {
299
+ state: {
300
+ available: true,
301
+ active: true,
302
+ activeMode: "tree",
303
+ startup: { enabled: true, mode: "tree" },
304
+ restartRequired: true,
305
+ },
306
+ }),
307
+ );
308
+ expect(output).toContain("Thinking 7.1s (configured thinking toggle to expand)");
309
+ expect(output).toContain("Saved: Streaming · Active: Tree · restart required");
310
+ });
311
+
312
+ it.each([
313
+ { active: true, activeMode: "rail" as const, expected: "active rail" },
314
+ { active: false, activeMode: undefined, expected: "active native" },
315
+ ])("keeps saved, active, restart, and unavailable reason in status ($expected)", (fixture) => {
316
+ const current = config();
317
+ current.components.thinkingSteps.enabled = true;
318
+ current.components.thinkingSteps.mode = "streaming";
319
+ const output = plain(
320
+ renderThinkingStepsSettingsPreview(current, theme(), 72, {
321
+ state: {
322
+ available: true,
323
+ rendererAvailable: true,
324
+ streamingAvailable: false,
325
+ active: fixture.active,
326
+ ...(fixture.activeMode ? { activeMode: fixture.activeMode } : {}),
327
+ startup: { enabled: true, mode: "rail" },
328
+ restartRequired: true,
329
+ reason: "Pi's terminal input listener is unavailable; restart required",
330
+ },
331
+ }),
332
+ );
333
+ expect(output).toContain(
334
+ `Saved: Streaming · ${fixture.expected.replace("active", "Active:").replace("rail", "Rail").replace("native", "Native")} · Streaming unavailable`,
335
+ );
336
+ expect(output).toContain("Pi's terminal input listener is unavailable");
337
+ expect(output.match(/restart required/gi)).toHaveLength(1);
338
+ });
339
+
340
+ it("uses direct accent connectors and native thinking/bold Markdown callbacks", () => {
341
+ const calls: Array<[string, string]> = [];
342
+ const currentTheme = {
343
+ ...theme(),
344
+ fg: (color: string, text: string) => {
345
+ calls.push([color, text]);
346
+ return text;
347
+ },
348
+ } as unknown as Theme;
349
+ const output = plain(renderThinkingStepsSettingsPreview(config(), currentTheme, 72));
350
+ expect(output).toContain("┆ Thinking");
351
+ expect(calls).toEqual(
352
+ expect.arrayContaining([
353
+ ["accent", "┆ "],
354
+ ["accent", "└─ • "],
355
+ ]),
356
+ );
357
+ expect(calls.some(([color]) => color === "thinkingText")).toBe(true);
358
+ });
359
+
360
+ it("keeps unavailable/native status visible while disabled and width-bounded", () => {
361
+ const current = config();
362
+ current.components.thinkingSteps.enabled = false;
363
+ for (const mode of ["rail", "tree", "streaming"] as const) {
364
+ current.components.thinkingSteps.mode = mode;
365
+ const rendered = renderThinkingStepsSettingsPreview(current, theme(), 20, false);
366
+ expect(rendered.every((row) => visibleWidth(row) <= 20)).toBe(true);
367
+ expect(plain(rendered)).toContain("Renderer unavailable");
368
+ }
369
+ });
370
+
371
+ it("re-renders Thinking-step preview with current mode and theme immediately", () => {
372
+ const current = config();
373
+ current.components.thinkingSteps.mode = "rail";
374
+ const rail = renderThinkingStepsSettingsPreview(current, theme(0), 72);
375
+ current.components.thinkingSteps.mode = "tree";
376
+ const tree = renderThinkingStepsSettingsPreview(current, theme(37), 72);
377
+ expect(tree).not.toEqual(rail);
378
+ expect(plain(rail)).toContain("│ Thinking");
379
+ expect(plain(tree)).toContain("┆ Thinking");
380
+ expect(plain(rail)).not.toMatch(/Thinking ·|Thinking.*Rail|Thinking.*Tree/);
381
+ expect(plain(tree)).not.toMatch(/Thinking ·|Thinking.*Rail|Thinking.*Tree/);
382
+ });
383
+
278
384
  it("sanitizes hostile source and configured icons before trusted preview styling", () => {
279
385
  const current = config();
280
386
  current.icons.rail = "\x1b]2;RAIL-OSC\x07│\u009b31m\u009dRAIL-C1\u009c\u009b0m";
@@ -0,0 +1,51 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { describe, expect, it } from "vitest";
4
+
5
+ const root = join(import.meta.dirname, "..");
6
+
7
+ describe("Thinking (Experimental) documentation", () => {
8
+ it("documents live transitions, restart gating, native Markdown rows, all modes, and bounded ownership", () => {
9
+ for (const path of ["README.md", "docs/configuration.md"]) {
10
+ const documentation = readFileSync(join(root, path), "utf8");
11
+ expect(documentation).toContain("Thinking (Experimental)");
12
+ expect(documentation).toMatch(/0\.80\.5.*0\.82\.1.*0\.83\.0.*0\.84\.0.*0\.84\.4/s);
13
+ expect(documentation).toMatch(/switch.*live|live switching/i);
14
+ expect(documentation).toMatch(/restart/i);
15
+ expect(documentation).toMatch(/native.*Markdown|Pi `Markdown`/i);
16
+ expect(documentation).toMatch(/strict(?: 7-bit CSI)? SGR styling is stripped/i);
17
+ expect(documentation).toMatch(/every other terminal control/i);
18
+ expect(documentation).toMatch(/256/);
19
+ expect(documentation).toMatch(/Working line/);
20
+ }
21
+ });
22
+
23
+ it("uses the public Thinking copy and states the live boundary exactly", () => {
24
+ const readme = readFileSync(join(root, "README.md"), "utf8");
25
+ expect(readme).toContain(
26
+ "Active Streaming can switch live to Rail or Tree, and Rail and Tree can switch live between each other. Entering Streaming from a structural mode, first enable, and re-enable after a live disable require restarting Pi.",
27
+ );
28
+ expect(readme).not.toMatch(
29
+ /switches Rail, Tree, and Streaming live|Entering Streaming transactionally|Streaming → Tree → Rail → Streaming/,
30
+ );
31
+ const configuration = readFileSync(join(root, "docs/configuration.md"), "utf8");
32
+ expect(configuration).toContain("Rail parses each native contiguous thinking run");
33
+ expect(configuration).not.toMatch(/Streaming → Tree → Rail → Streaming/);
34
+ for (const path of [
35
+ "README.md",
36
+ "docs/configuration.md",
37
+ "extensions/zentui/settings-command.ts",
38
+ ]) {
39
+ const source = readFileSync(join(root, path), "utf8");
40
+ expect(source, path).not.toMatch(/Thinking steps(?: mode)?/);
41
+ }
42
+ });
43
+
44
+ it("keeps both adapted MIT notices adjacent in packaged renderer source", () => {
45
+ const source = readFileSync(join(root, "extensions/zentui/thinking-experimental.ts"), "utf8");
46
+ expect(source).toContain("Copyright (c) 2026 Zach Yuen");
47
+ expect(source).toContain("Copyright (c) 2026 Marc Mironescu / FluxGear");
48
+ expect(source.match(/Permission is hereby granted, free of charge/g)).toHaveLength(2);
49
+ expect(source).not.toMatch(/setWorkingMessage|setWorkingVisible/);
50
+ });
51
+ });
@@ -0,0 +1,54 @@
1
+ import { pathToFileURL } from "node:url";
2
+
3
+ const diagnosticPatterns = [
4
+ /Failed to load extension(?:\s|:|")/i,
5
+ /Extension\s+(?:"[^"\r\n]*"|'[^'\r\n]*')\s+error\s*:/i,
6
+ /Extension error\s*\(/i,
7
+ /loader diagnostic\s*:/i,
8
+ /\b(?:ERR_MODULE_NOT_FOUND|ERR_PACKAGE_PATH_NOT_EXPORTED|ERR_UNKNOWN_FILE_EXTENSION|MODULE_NOT_FOUND)\b/i,
9
+ ];
10
+
11
+ export function hasExtensionLoaderDiagnostic(output) {
12
+ return diagnosticPatterns.some((pattern) => pattern.test(output));
13
+ }
14
+
15
+ export const rejectedDiagnosticFixtures = Object.freeze([
16
+ 'Extension "/tmp/any path/zentui.ts" error: SyntaxError: unexpected token',
17
+ "Extension '/private/quoted/path with spaces/index.ts' error: missing export",
18
+ 'Failed to load extension "/tmp/zentui.ts": import failed',
19
+ "Failed to load extension: transform failed",
20
+ "Extension error (/tmp/zentui.ts): runtime failure",
21
+ "loader diagnostic: source module rejected",
22
+ "ERR_MODULE_NOT_FOUND: Cannot find package pi-tui",
23
+ "ERR_PACKAGE_PATH_NOT_EXPORTED: Package subpath './package.json' is not defined",
24
+ "ERR_UNKNOWN_FILE_EXTENSION: Unknown file extension .ts",
25
+ "MODULE_NOT_FOUND: Cannot find module 'pi-ai'",
26
+ ]);
27
+
28
+ export const acceptedOutputFixtures = Object.freeze([
29
+ "Thinking 0.1s (ctrl+t to expand)",
30
+ "Extension loaded: /tmp/zentui.ts",
31
+ "The user asked whether a loader diagnostic would help explain an extension error.",
32
+ "The assistant described the phrase extension error while discussing a loader diagnostic.",
33
+ "The assistant discussed extension recovery strategy.",
34
+ ]);
35
+
36
+ export function assertExtensionLoaderDiagnosticFixtures() {
37
+ for (const fixture of rejectedDiagnosticFixtures) {
38
+ if (!hasExtensionLoaderDiagnostic(fixture)) {
39
+ throw new Error(`loader diagnostic fixture was not rejected: ${JSON.stringify(fixture)}`);
40
+ }
41
+ }
42
+ for (const fixture of acceptedOutputFixtures) {
43
+ if (hasExtensionLoaderDiagnostic(fixture)) {
44
+ throw new Error(`ordinary output fixture was rejected: ${JSON.stringify(fixture)}`);
45
+ }
46
+ }
47
+ }
48
+
49
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
50
+ assertExtensionLoaderDiagnosticFixtures();
51
+ console.log(
52
+ `loader-diagnostics: rejected=${rejectedDiagnosticFixtures.length} accepted=${acceptedOutputFixtures.length}`,
53
+ );
54
+ }