@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,2329 @@
1
+ import { execFileSync, spawn } from "node:child_process";
2
+ import {
3
+ cpSync,
4
+ existsSync,
5
+ mkdirSync,
6
+ mkdtempSync,
7
+ readdirSync,
8
+ readFileSync,
9
+ realpathSync,
10
+ rmSync,
11
+ writeFileSync,
12
+ } from "node:fs";
13
+ import { tmpdir } from "node:os";
14
+ import { dirname, join } from "node:path";
15
+ import {
16
+ assertExtensionLoaderDiagnosticFixtures,
17
+ hasExtensionLoaderDiagnostic,
18
+ } from "./thinking-experimental-loader-diagnostics.mjs";
19
+
20
+ const versions = process.env.ZENTUI_PI_VERSIONS?.split(",") ?? [
21
+ "0.80.5",
22
+ "0.82.1",
23
+ "0.83.0",
24
+ "0.84.0",
25
+ "0.84.4",
26
+ ];
27
+ const root = join(import.meta.dirname, "..");
28
+ const workspace = mkdtempSync(join(tmpdir(), "zentui-thinking-tui-"));
29
+ const npmCli = process.env.npm_execpath;
30
+ if (!npmCli) throw new Error("npm_execpath is required; run this harness through npm");
31
+
32
+ function resolveInstalledManifest(installRoot, packageName) {
33
+ const path = realpathSync(join(installRoot, "node_modules", packageName, "package.json"));
34
+ const manifest = JSON.parse(readFileSync(path, "utf8"));
35
+ if (manifest.name !== packageName)
36
+ throw new Error(`Resolved manifest ${path} was ${String(manifest.name)}, not ${packageName}`);
37
+ return { manifest, path };
38
+ }
39
+
40
+ function attestInstalledVersions(installRoot, requestedVersion) {
41
+ const packages = {
42
+ codingAgent: "@earendil-works/pi-coding-agent",
43
+ tui: "@earendil-works/pi-tui",
44
+ ai: "@earendil-works/pi-ai",
45
+ };
46
+ const resolved = Object.fromEntries(
47
+ Object.entries(packages).map(([key, packageName]) => [
48
+ key,
49
+ resolveInstalledManifest(installRoot, packageName),
50
+ ]),
51
+ );
52
+ const versions = Object.fromEntries(
53
+ Object.entries(resolved).map(([key, value]) => [key, value.manifest.version]),
54
+ );
55
+ for (const [key, measured] of Object.entries(versions)) {
56
+ if (measured !== requestedVersion) {
57
+ throw new Error(
58
+ `Installed ${key} version ${measured} did not equal requested ${requestedVersion}`,
59
+ );
60
+ }
61
+ }
62
+ return { resolved, versions };
63
+ }
64
+
65
+ function structuralLinkSemantics(actualLabels, expectedLabels, width) {
66
+ if (width < 80) return { capability: "cropped", preserved: true };
67
+ const osc8 = "\x1b]8;;https://example.com/";
68
+ const fallback = "(https://example.com/";
69
+ const expectedOsc8 = expectedLabels.every((row) => row.includes(osc8));
70
+ const actualOsc8 = actualLabels.every((row) => row.includes(osc8));
71
+ const expectedFallback = expectedLabels.every((row) => row.includes(fallback));
72
+ const actualFallback = actualLabels.every((row) => row.includes(fallback));
73
+ return {
74
+ capability: expectedOsc8 ? "osc8" : "fallback-url",
75
+ preserved: expectedOsc8
76
+ ? actualOsc8 && !actualFallback
77
+ : expectedFallback && actualFallback && !actualOsc8,
78
+ };
79
+ }
80
+
81
+ function assertStructuralLinkSemanticsFixtures() {
82
+ const osc8 = "\x1b]8;;https://example.com/1\x07link\x1b]8;;\x07";
83
+ const fallback = "link (https://example.com/1)";
84
+ if (
85
+ !structuralLinkSemantics([osc8], [osc8], 80).preserved ||
86
+ !structuralLinkSemantics([fallback], [fallback], 80).preserved ||
87
+ structuralLinkSemantics([osc8], [fallback], 80).preserved
88
+ ) {
89
+ throw new Error("Structural link capability simulation failed");
90
+ }
91
+ }
92
+
93
+ function boundedJson(value, maxLength = 12_000) {
94
+ const json = JSON.stringify(value, (_key, current) => {
95
+ if (typeof current === "string" && current.length > 240) {
96
+ return `${current.slice(0, 240)}…<${current.length} chars>`;
97
+ }
98
+ if (Array.isArray(current) && current.length > 20) {
99
+ return [...current.slice(0, 20), `…<${current.length} items>`];
100
+ }
101
+ return current;
102
+ });
103
+ if (json.length <= maxLength) return json;
104
+ return JSON.stringify({
105
+ truncated: true,
106
+ originalLength: json.length,
107
+ preview: json.slice(0, Math.min(2_000, Math.floor(maxLength / 4))),
108
+ });
109
+ }
110
+
111
+ function assertBoundedJsonFixtures() {
112
+ const diagnostic = boundedJson({
113
+ rows: Array.from({ length: 100 }, () => "\x1b[31m".repeat(100)),
114
+ });
115
+ JSON.parse(diagnostic);
116
+ if (diagnostic.length > 12_000) throw new Error("Structural diagnostic bound simulation failed");
117
+ }
118
+
119
+ const streamingHeaderPattern = /^(\s*)Thinking \d+\.\d+s {2}\(ctrl\+t to expand\)$/;
120
+
121
+ function canonicalizeStreamingElapsed(rows) {
122
+ return rows.map((row) =>
123
+ row.replace(streamingHeaderPattern, "$1Thinking <elapsed>s (ctrl+t to expand)"),
124
+ );
125
+ }
126
+
127
+ function assertStreamingElapsedVarianceFixtures() {
128
+ const expected = [" Thinking <elapsed>s (ctrl+t to expand)", " exact native tail row"];
129
+ const startup = [" Thinking 0.0s (ctrl+t to expand)", " exact native tail row"];
130
+ const delayedLinux = [" Thinking 0.1s (ctrl+t to expand)", " exact native tail row"];
131
+ const malformed = [" Thinking soon (ctrl+t to expand)", " exact native tail row"];
132
+ if (
133
+ JSON.stringify(canonicalizeStreamingElapsed(startup)) !== JSON.stringify(expected) ||
134
+ JSON.stringify(canonicalizeStreamingElapsed(delayedLinux)) !== JSON.stringify(expected) ||
135
+ JSON.stringify(canonicalizeStreamingElapsed(malformed)) === JSON.stringify(expected)
136
+ )
137
+ throw new Error("Streaming elapsed-time variance simulation failed");
138
+ }
139
+
140
+ assertExtensionLoaderDiagnosticFixtures();
141
+ assertStructuralLinkSemanticsFixtures();
142
+ assertBoundedJsonFixtures();
143
+ assertStreamingElapsedVarianceFixtures();
144
+
145
+ const completedThinkingRows = [
146
+ "PTY exact row 1 ",
147
+ " PTY indented row 2 ",
148
+ "PTY exact row 3",
149
+ "",
150
+ "PTY exact row 5 ",
151
+ "PTY exact row 6 ",
152
+ "PTY exact row 7 ",
153
+ "PTY exact row 8",
154
+ ];
155
+ const thinking = completedThinkingRows.join("\n");
156
+ const assistant = {
157
+ role: "assistant",
158
+ content: [
159
+ { type: "thinking", thinking },
160
+ { type: "text", text: "Offline fixture answer." },
161
+ ],
162
+ api: "anthropic-messages",
163
+ provider: "anthropic",
164
+ model: "offline-fixture",
165
+ usage: {
166
+ input: 1,
167
+ output: 1,
168
+ cacheRead: 0,
169
+ cacheWrite: 0,
170
+ totalTokens: 2,
171
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
172
+ },
173
+ stopReason: "stop",
174
+ timestamp: Date.parse("2024-01-01T00:00:01.000Z"),
175
+ };
176
+
177
+ const liveThinking = [
178
+ "LIVE rendered row 1 ",
179
+ "LIVE rendered row 2 ",
180
+ "LIVE rendered row 3 ",
181
+ "LIVE wrapping row 4 has enough deliberately repeated padding words padding words padding words padding words KNOWN WRAPPED CONTINUATION ",
182
+ "LIVE rendered row 5 ",
183
+ "LIVE rendered row 6 ",
184
+ "LIVE rendered row 7 ",
185
+ "LIVE rendered row 8 ",
186
+ ].join("\n");
187
+ const liveAssistant = {
188
+ ...assistant,
189
+ content: [{ type: "thinking", thinking: liveThinking }],
190
+ stopReason: "pending",
191
+ timestamp: Date.parse("2024-01-01T00:00:20.000Z"),
192
+ };
193
+
194
+ const resourceProbeSource = `
195
+ const state = { inputs: 0, timers: new Set() };
196
+ globalThis.__ZENTUI_RESOURCE_PROBE__ = state;
197
+ const fromThinkingController = () => String(new Error().stack).includes("thinking-experimental");
198
+ const nativeSetInterval = globalThis.setInterval;
199
+ const nativeClearInterval = globalThis.clearInterval;
200
+ globalThis.setInterval = function (callback, delay, ...args) {
201
+ const owned = fromThinkingController();
202
+ const handle = Reflect.apply(nativeSetInterval, this, [callback, delay, ...args]);
203
+ if (owned) state.timers.add(handle);
204
+ return handle;
205
+ };
206
+ globalThis.clearInterval = function (handle) {
207
+ state.timers.delete(handle);
208
+ return Reflect.apply(nativeClearInterval, this, [handle]);
209
+ };
210
+ export default function (pi) {
211
+ pi.on("session_start", (_event, ctx) => {
212
+ const nativeInput = ctx.ui.onTerminalInput.bind(ctx.ui);
213
+ ctx.ui.onTerminalInput = (handler) => {
214
+ const owned = fromThinkingController();
215
+ const cleanup = nativeInput(handler);
216
+ if (!owned) return cleanup;
217
+ state.inputs += 1;
218
+ let active = true;
219
+ return () => {
220
+ if (!active) return;
221
+ active = false;
222
+ cleanup();
223
+ state.inputs -= 1;
224
+ };
225
+ };
226
+ });
227
+ }
228
+ `;
229
+
230
+ const probeSource = `
231
+ import { appendFileSync, writeFileSync } from "node:fs";
232
+ import {
233
+ AssistantMessageComponent,
234
+ getMarkdownTheme,
235
+ initTheme,
236
+ } from "@earendil-works/pi-coding-agent";
237
+ import { Markdown, SettingsList, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
238
+ import { hasThinkingExperimentalMarkdownIdentity } from "./extensions/zentui/thinking-experimental.ts";
239
+
240
+ const originalUpdate = AssistantMessageComponent.prototype.updateContent;
241
+ const originalDescriptor = Object.getOwnPropertyDescriptor(AssistantMessageComponent.prototype, "updateContent");
242
+ const forwarded = [];
243
+ function predecessorProbe(...args) {
244
+ forwarded.push({ count: args.length, isStreaming: args[1], message: args[0] });
245
+ return Reflect.apply(originalUpdate, this, args);
246
+ }
247
+ Object.defineProperty(AssistantMessageComponent.prototype, "updateContent", { ...originalDescriptor, value: predecessorProbe });
248
+ const nativeUpdate = predecessorProbe;
249
+ const nativeDescriptor = Object.getOwnPropertyDescriptor(AssistantMessageComponent.prototype, "updateContent");
250
+ const settingsInputDescriptor = Object.getOwnPropertyDescriptor(SettingsList.prototype, "handleInput");
251
+ if (process.env.ZENTUI_DIRECT_RAIL === "1" && settingsInputDescriptor?.value) {
252
+ Object.defineProperty(SettingsList.prototype, "handleInput", {
253
+ ...settingsInputDescriptor,
254
+ value: function directRailProbeInput(data) {
255
+ if (data === "\\x1c") {
256
+ const item = this.items?.[this.selectedIndex];
257
+ if (item?.currentValue === "Streaming" && item.values?.includes("Rail")) {
258
+ // Make one real SettingsList activation select Rail without notifying the
259
+ // controller about the intermediate cyclic Tree value.
260
+ item.currentValue = "Tree";
261
+ return Reflect.apply(settingsInputDescriptor.value, this, [" "]);
262
+ }
263
+ }
264
+ return Reflect.apply(settingsInputDescriptor.value, this, [data]);
265
+ },
266
+ });
267
+ }
268
+ const compareDescriptor = (before, after) => {
269
+ const fields = {
270
+ value: after?.value === before?.value,
271
+ get: after?.get === before?.get,
272
+ set: after?.set === before?.set,
273
+ configurable: after?.configurable === before?.configurable,
274
+ enumerable: after?.enumerable === before?.enumerable,
275
+ writable: after?.writable === before?.writable,
276
+ };
277
+ return { fields, all: Object.values(fields).every(Boolean) };
278
+ };
279
+ const probePath = process.env.ZENTUI_PROBE_PATH;
280
+ const fixture = ${JSON.stringify(assistant)};
281
+ const liveFixture = ${JSON.stringify(liveAssistant)};
282
+ const version = process.env.ZENTUI_PI_VERSION;
283
+ const mode = process.env.ZENTUI_THINKING_MODE ?? "streaming";
284
+ const cleanRows = (rows) => rows.map((text) => text.replace(/\\x1b\\](?:[^\\x07\\x1b]|\\x1b(?!\\\\))*(?:\\x07|\\x1b\\\\)/g, "").replace(/\\x1b\\[[0-?]*[ -/]*[@-~]/g, "").trimEnd());
285
+ const structuralLinkSemantics = ${structuralLinkSemantics.toString()};
286
+ const LIVE_SECTION_START = "__ZENTUI_EXPERIMENTAL_LIVE_START_7D91B2__";
287
+ const LIVE_SECTION_END = "__ZENTUI_EXPERIMENTAL_LIVE_END_7D91B2__";
288
+ const COMPLETED_SECTION_START = "__ZENTUI_EXPERIMENTAL_COMPLETED_START_4A62C9__";
289
+ const COMPLETED_SECTION_END = "__ZENTUI_EXPERIMENTAL_COMPLETED_END_4A62C9__";
290
+ const TRANSITION_SECTION_START = "__ZENTUI_EXPERIMENTAL_TRANSITION_START_52F0A8__";
291
+ const TRANSITION_SECTION_END = "__ZENTUI_EXPERIMENTAL_TRANSITION_END_52F0A8__";
292
+
293
+ let removeProbeWidget;
294
+ let structuralWidths = [];
295
+ let structuralGenerations = [];
296
+ let removeLiveProbeWidget;
297
+ let stopProbeInput;
298
+ const ownedProbeWidgets = new Set();
299
+ export default function (pi) {
300
+ pi.on("session_start", (_event, ctx) => {
301
+ const setProbeWidget = (key, factory, options) => {
302
+ ctx.ui.setWidget(key, factory, options);
303
+ if (factory) ownedProbeWidgets.add(key);
304
+ else ownedProbeWidgets.delete(key);
305
+ };
306
+ if (mode === "rail" || mode === "tree") {
307
+ const labels = Array.from(
308
+ { length: 8 },
309
+ (_, index) =>
310
+ "PTY label " +
311
+ (index + 1) +
312
+ " with *emphasis* and \`code\` plus [link](https://example.com/" +
313
+ (index + 1) +
314
+ ")",
315
+ );
316
+ const structuralBlocks = [
317
+ labels.slice(0, 4).map((label) => "# " + label).join("\\n"),
318
+ labels.slice(4).map((label) => "# " + label).join("\\n"),
319
+ ];
320
+ const baseStructuralFixture = {
321
+ ...liveFixture,
322
+ content: structuralBlocks.map((thinking) => ({ type: "thinking", thinking })),
323
+ };
324
+ const specs = ["current", "dark", "light"].flatMap((themeName) => [
325
+ { themeName, active: true },
326
+ { themeName, active: false },
327
+ ]);
328
+ let generation = 0;
329
+ let tested;
330
+ let initialCalls = [];
331
+ const crop = (markdown, source, width) => {
332
+ const rows = markdown.render(width);
333
+ const first = rows[0];
334
+ if (!first) return undefined;
335
+ if (rows.length > 1)
336
+ return width === 1 ? "…" : truncateToWidth(first, width - 1, "") + "…";
337
+ return visibleWidth(first) <= width ? first : truncateToWidth(first, width, "…");
338
+ };
339
+ const compareAnsi = (component, nativeShape, selected, incomplete, width) => {
340
+ const rendered = component.render(width);
341
+ const structuralRows = rendered.filter((row) =>
342
+ /^\\s*(?:│|┆|├─|└─)/.test(cleanRows([row])[0] ?? ""),
343
+ );
344
+ const actualLabels = structuralRows
345
+ .slice(1)
346
+ .map((row) => row.replace(/\\x1b\\]133;[ABC]\\x07/g, ""));
347
+ const outer = nativeShape.paddingX;
348
+ const innerWidth = width - outer * 2;
349
+ const expectedLabels = selected.map((label, index) => {
350
+ const final = index === selected.length - 1;
351
+ const connector =
352
+ mode === "rail"
353
+ ? final && incomplete
354
+ ? "│ • "
355
+ : "│ "
356
+ : final
357
+ ? incomplete
358
+ ? "└─ • "
359
+ : "└─ · "
360
+ : "├─ · ";
361
+ const budget = innerWidth - visibleWidth(connector);
362
+ const markdown = new Markdown(
363
+ label,
364
+ 0,
365
+ 0,
366
+ nativeShape.theme,
367
+ nativeShape.defaultTextStyle,
368
+ nativeShape.options,
369
+ );
370
+ const nativeLabel = crop(markdown, label, budget);
371
+ return (
372
+ " ".repeat(outer) +
373
+ ctx.ui.theme.fg("accent", connector) +
374
+ nativeLabel +
375
+ " ".repeat(outer)
376
+ );
377
+ });
378
+ const links = structuralLinkSemantics(actualLabels, expectedLabels, width);
379
+ return {
380
+ width,
381
+ rendered,
382
+ actualLabels,
383
+ expectedLabels,
384
+ exactNativeLabels: JSON.stringify(actualLabels) === JSON.stringify(expectedLabels),
385
+ linkCapability: links.capability,
386
+ linkSemantics: links.preserved,
387
+ croppedWithEllipsis:
388
+ width > 20 || cleanRows(actualLabels).every((row) => row.includes("…")),
389
+ };
390
+ };
391
+ const createGeneration = () => {
392
+ const spec = specs[generation];
393
+ if (spec.themeName !== "current") initTheme(spec.themeName, false);
394
+ const structuralFixture = {
395
+ ...baseStructuralFixture,
396
+ stopReason: spec.active ? "pending" : "stop",
397
+ timestamp: baseStructuralFixture.timestamp + generation,
398
+ };
399
+ tested = new AssistantMessageComponent(
400
+ undefined,
401
+ false,
402
+ getMarkdownTheme(),
403
+ "Thinking...",
404
+ 1,
405
+ [],
406
+ );
407
+ const beforeWrapperCalls = forwarded.length;
408
+ if (version === "0.80.5" || version === "0.83.0") tested.updateContent(structuralFixture);
409
+ else tested.updateContent(structuralFixture, spec.active);
410
+ if (generation === 0) {
411
+ initialCalls = forwarded.slice(beforeWrapperCalls).map((call) => ({
412
+ count: call.count,
413
+ isStreaming: call.isStreaming,
414
+ messageIdentity: call.message === structuralFixture,
415
+ }));
416
+ }
417
+ const native = new AssistantMessageComponent(
418
+ undefined,
419
+ false,
420
+ getMarkdownTheme(),
421
+ "Thinking...",
422
+ 1,
423
+ [],
424
+ );
425
+ Reflect.apply(originalUpdate, native, [structuralFixture, spec.active]);
426
+ const nativeMarkdown = (native.contentContainer?.children ?? []).find(
427
+ (child) => child instanceof Markdown && child.text.includes("PTY label 1"),
428
+ );
429
+ if (!nativeMarkdown) throw new Error("native structural Markdown shape missing");
430
+ const selected = mode === "rail" ? labels : labels.slice(-5);
431
+ const record = {
432
+ generation,
433
+ themeName: spec.themeName,
434
+ active: spec.active,
435
+ constructors: (tested.contentContainer?.children ?? []).map(
436
+ (child) => child.constructor.name,
437
+ ),
438
+ comparisons: [20, 80].map((width) =>
439
+ compareAnsi(tested, nativeMarkdown, selected, spec.active, width),
440
+ ),
441
+ };
442
+ structuralGenerations.push(record);
443
+ };
444
+ createGeneration();
445
+ const hidden = new AssistantMessageComponent(
446
+ undefined,
447
+ true,
448
+ getMarkdownTheme(),
449
+ "Thinking...",
450
+ 1,
451
+ [],
452
+ );
453
+ hidden.updateContent(baseStructuralFixture, true);
454
+ const nativeHidden = new AssistantMessageComponent(
455
+ undefined,
456
+ true,
457
+ getMarkdownTheme(),
458
+ "Thinking...",
459
+ 1,
460
+ [],
461
+ );
462
+ Reflect.apply(originalUpdate, nativeHidden, [baseStructuralFixture, true]);
463
+ const transition = new AssistantMessageComponent(undefined, false, getMarkdownTheme(), "Thinking...", 1, []);
464
+ const transitionFixture = {
465
+ ...liveFixture,
466
+ content: [{ type: "thinking", thinking: Array.from({ length: 6 }, (_, index) => "# Live transition " + (index + 1)).join("\\n") }],
467
+ };
468
+ transition.updateContent(transitionFixture, true);
469
+ const widget = {
470
+ render(width) {
471
+ structuralWidths.push(width);
472
+ const marker = "Z" + generation + "W" + width;
473
+ const resources = globalThis.__ZENTUI_RESOURCE_PROBE__;
474
+ return [
475
+ "«" + marker + "»",
476
+ ...tested.render(width),
477
+ "«/" + marker + "»",
478
+ TRANSITION_SECTION_START,
479
+ ...transition.render(width),
480
+ TRANSITION_SECTION_END,
481
+ "__ZENTUI_RESOURCES__ input=" + resources.inputs + " timer=" + resources.timers.size,
482
+ ];
483
+ },
484
+ invalidate() {
485
+ tested.invalidate();
486
+ transition.invalidate();
487
+ },
488
+ };
489
+ setProbeWidget("zentui-structural-probe", () => widget, { placement: "aboveEditor" });
490
+ stopProbeInput = ctx.ui.onTerminalInput((data) => {
491
+ if (data !== "\\x1d" || generation >= specs.length - 1) return;
492
+ generation += 1;
493
+ createGeneration();
494
+ process.stdout.emit("resize");
495
+ return { consume: true };
496
+ });
497
+ removeProbeWidget = () => setProbeWidget("zentui-structural-probe", undefined);
498
+ writeFileSync(
499
+ probePath,
500
+ JSON.stringify({
501
+ ready: true,
502
+ mode,
503
+ installed: AssistantMessageComponent.prototype.updateContent !== nativeUpdate,
504
+ wrapperCalls: initialCalls.length,
505
+ testedCalls: initialCalls,
506
+ hidden80: hidden.render(80),
507
+ nativeHidden80: nativeHidden.render(80),
508
+ hiddenStatePreserved: hidden.hideThinkingBlock === true,
509
+ }) + "\\n",
510
+ );
511
+ return;
512
+ }
513
+ const nativeLive = new AssistantMessageComponent(undefined, false, getMarkdownTheme(), "Thinking...", 1, []);
514
+ Reflect.apply(originalUpdate, nativeLive, [liveFixture, true]);
515
+ const nativeChildren = nativeLive.contentContainer?.children ?? [];
516
+ const nativeMarkdown = nativeChildren.find((child) => child instanceof Markdown && child.text.includes("LIVE rendered row 1"));
517
+ const nativeRows = nativeMarkdown ? cleanRows(nativeMarkdown.render(40)) : [];
518
+ const nativeScreenRows = cleanRows(nativeLive.render(100));
519
+ const importedAssistantIdentity = nativeLive.constructor === AssistantMessageComponent;
520
+ const importedMarkdownIdentity = Boolean(nativeMarkdown && nativeMarkdown.constructor === Markdown);
521
+ const markdownIdentity = hasThinkingExperimentalMarkdownIdentity(nativeLive, liveFixture);
522
+ const installed = AssistantMessageComponent.prototype.updateContent !== nativeUpdate;
523
+
524
+ const tested = new AssistantMessageComponent(undefined, false, getMarkdownTheme(), "Thinking...", 1, []);
525
+ const beforeWrapperCalls = forwarded.length;
526
+ if (version === "0.80.5" || version === "0.83.0") tested.updateContent(liveFixture);
527
+ else tested.updateContent(liveFixture, true);
528
+ const testedCalls = forwarded.slice(beforeWrapperCalls).map((call) => ({
529
+ count: call.count,
530
+ isStreaming: call.isStreaming,
531
+ messageIdentity: call.message === liveFixture,
532
+ }));
533
+ const wrapperCalls = testedCalls.length;
534
+ const signature = (component) => (component.contentContainer?.children ?? []).map((child) => ({
535
+ constructor: child.constructor.name,
536
+ text: typeof child.text === "string" ? child.text : undefined,
537
+ }));
538
+ const collision = new AssistantMessageComponent(undefined, false, getMarkdownTheme(), "Thinking...", 1, []);
539
+ collision.updateContent({
540
+ ...fixture,
541
+ content: [
542
+ { type: "thinking", thinking: "collision A" },
543
+ { type: "text", text: "collision B" },
544
+ { type: "thinking", thinking: "collision B" },
545
+ ],
546
+ }, false);
547
+ const equal = new AssistantMessageComponent(undefined, false, getMarkdownTheme(), "Thinking...", 1, []);
548
+ equal.updateContent({
549
+ ...fixture,
550
+ content: [
551
+ { type: "text", text: "equal source" },
552
+ { type: "thinking", thinking: "equal source" },
553
+ ],
554
+ }, false);
555
+ const contiguous = new AssistantMessageComponent(undefined, false, getMarkdownTheme(), "Thinking...", 1, []);
556
+ contiguous.updateContent({
557
+ ...fixture,
558
+ content: [
559
+ { type: "thinking", thinking: "contiguous A" },
560
+ { type: "thinking", thinking: "contiguous B" },
561
+ ],
562
+ }, false);
563
+ const toolSeparated = new AssistantMessageComponent(undefined, false, getMarkdownTheme(), "Thinking...", 1, []);
564
+ toolSeparated.updateContent({
565
+ ...fixture,
566
+ content: [
567
+ { type: "thinking", thinking: "tool-separated A" },
568
+ { type: "toolCall", id: "tool-1", name: "read", arguments: {} },
569
+ { type: "thinking", thinking: "tool-separated B" },
570
+ ],
571
+ }, false);
572
+ const orderedIdentitySignatures = {
573
+ collision: signature(collision),
574
+ equal: signature(equal),
575
+ contiguous: signature(contiguous),
576
+ toolSeparated: signature(toolSeparated),
577
+ };
578
+ const liveCollapsedRows = cleanRows(tested.render(40));
579
+ const liveProbeWidget = {
580
+ render(width) {
581
+ const rendered = tested.render(width);
582
+ const headerIndex = cleanRows(rendered).findIndex((row) =>
583
+ /^Thinking \\d+\\.\\d+s {2}\\(ctrl\\+t to expand\\)$/.test(row.trimStart()),
584
+ );
585
+ return [
586
+ LIVE_SECTION_START,
587
+ ...(headerIndex >= 0 ? rendered.slice(headerIndex) : rendered),
588
+ LIVE_SECTION_END,
589
+ ];
590
+ },
591
+ invalidate() {
592
+ tested.invalidate();
593
+ },
594
+ };
595
+ setProbeWidget("zentui-stream-live-probe", () => liveProbeWidget, { placement: "aboveEditor" });
596
+ const completed = new AssistantMessageComponent(undefined, false, getMarkdownTheme(), "Thinking...", 1, []);
597
+ const completedFixture = { ...fixture, content: fixture.content.filter((part) => part.type === "thinking") };
598
+ completed.updateContent(completedFixture, false);
599
+ const completedProbeWidget = {
600
+ render(width) {
601
+ const rendered = completed.render(width);
602
+ const cleaned = cleanRows(rendered);
603
+ const headerIndex = cleaned.findIndex((row) =>
604
+ /^Thought {2}\\(ctrl\\+t to expand\\)$/.test(row.trimStart()),
605
+ );
606
+ const nativeIndex = cleaned.findIndex((row) => row.trimStart() === "PTY exact row 1");
607
+ const startIndex = headerIndex >= 0 ? headerIndex : nativeIndex >= 0 ? nativeIndex : 0;
608
+ return [
609
+ COMPLETED_SECTION_START,
610
+ ...rendered.slice(startIndex),
611
+ COMPLETED_SECTION_END,
612
+ ];
613
+ },
614
+ invalidate() {
615
+ completed.invalidate();
616
+ },
617
+ };
618
+ setProbeWidget("zentui-stream-completed-probe", () => completedProbeWidget, { placement: "aboveEditor" });
619
+ const transition = new AssistantMessageComponent(undefined, false, getMarkdownTheme(), "Thinking...", 1, []);
620
+ const transitionFixture = {
621
+ ...liveFixture,
622
+ content: [{ type: "thinking", thinking: Array.from({ length: 6 }, (_, index) => "# Live transition " + (index + 1)).join("\\n") }],
623
+ };
624
+ transition.updateContent(transitionFixture, true);
625
+ const transitionProbeWidget = {
626
+ render(width) {
627
+ const resources = globalThis.__ZENTUI_RESOURCE_PROBE__;
628
+ return [
629
+ TRANSITION_SECTION_START,
630
+ ...transition.render(width),
631
+ TRANSITION_SECTION_END,
632
+ "__ZENTUI_RESOURCES__ input=" + resources.inputs + " timer=" + resources.timers.size,
633
+ ];
634
+ },
635
+ invalidate() {
636
+ transition.invalidate();
637
+ },
638
+ };
639
+ setProbeWidget("zentui-stream-transition-probe", () => transitionProbeWidget, { placement: "aboveEditor" });
640
+ removeLiveProbeWidget = () => setProbeWidget("zentui-stream-live-probe", undefined);
641
+ removeProbeWidget = () => {
642
+ removeLiveProbeWidget?.();
643
+ setProbeWidget("zentui-stream-completed-probe", undefined);
644
+ setProbeWidget("zentui-stream-transition-probe", undefined);
645
+ };
646
+ stopProbeInput = ctx.ui.onTerminalInput((data) => {
647
+ if (data !== "\\x07") return;
648
+ removeLiveProbeWidget?.();
649
+ removeLiveProbeWidget = undefined;
650
+ return { consume: true };
651
+ });
652
+
653
+ const hidden = new AssistantMessageComponent(undefined, true, getMarkdownTheme(), "Thinking...", 1, []);
654
+ hidden.updateContent(liveFixture, version === "0.80.5" || version === "0.83.0" ? undefined : true);
655
+ const hiddenFolded = cleanRows(hidden.render(40)).some((row) => row.includes("Thinking"));
656
+ const hiddenStatePreserved = hidden.hideThinkingBlock === true;
657
+
658
+ writeFileSync(probePath, JSON.stringify({
659
+ ready: true,
660
+ installed,
661
+ markdownIdentity,
662
+ importedAssistantIdentity,
663
+ importedMarkdownIdentity,
664
+ liveCollapsedRows,
665
+ nativeTail: nativeRows.slice(-5),
666
+ nativeRows,
667
+ nativeScreenTail: nativeScreenRows.slice(-5),
668
+ liveSectionStart: LIVE_SECTION_START,
669
+ liveSectionEnd: LIVE_SECTION_END,
670
+ completedSectionStart: COMPLETED_SECTION_START,
671
+ completedSectionEnd: COMPLETED_SECTION_END,
672
+ wrapperCalls,
673
+ testedCalls,
674
+ orderedIdentitySignatures,
675
+ hiddenFolded,
676
+ hiddenStatePreserved,
677
+ }) + "\\n");
678
+ });
679
+ pi.on("session_shutdown", () => {
680
+ removeProbeWidget?.();
681
+ removeProbeWidget = undefined;
682
+ stopProbeInput?.();
683
+ stopProbeInput = undefined;
684
+ const shutdownDescriptor = Object.getOwnPropertyDescriptor(AssistantMessageComponent.prototype, "updateContent");
685
+ const descriptorEvidence = compareDescriptor(nativeDescriptor, shutdownDescriptor);
686
+ const resources = globalThis.__ZENTUI_RESOURCE_PROBE__;
687
+ appendFileSync(
688
+ probePath,
689
+ JSON.stringify({
690
+ restored: descriptorEvidence.all,
691
+ descriptorEvidence,
692
+ widgetRemoved: ownedProbeWidgets.size === 0,
693
+ widgetOwnershipCount: ownedProbeWidgets.size,
694
+ controllerResources: { inputs: resources.inputs, timers: resources.timers.size },
695
+ structuralWidths,
696
+ structuralGenerations,
697
+ }) + "\\n",
698
+ );
699
+ Object.defineProperty(AssistantMessageComponent.prototype, "updateContent", originalDescriptor);
700
+ if (settingsInputDescriptor)
701
+ Object.defineProperty(SettingsList.prototype, "handleInput", settingsInputDescriptor);
702
+ });
703
+ }
704
+ `;
705
+
706
+ const ptySource = String.raw`
707
+ import codecs, json, os, select, signal, struct, sys, termios, time, fcntl
708
+
709
+ command = json.loads(sys.argv[1])
710
+ environment = os.environ.copy()
711
+ environment.update(json.loads(sys.argv[2]))
712
+ probe_path = environment["ZENTUI_PROBE_PATH"]
713
+ supervisor_path = environment["ZENTUI_SUPERVISOR_PATH"]
714
+ rows = 40
715
+ cols = 80 if environment.get("ZENTUI_THINKING_MODE") in ("rail", "tree") else 100
716
+
717
+ class Screen:
718
+ def __init__(self):
719
+ self.cells = [[" "] * cols for _ in range(rows)]
720
+ self.r = 0; self.c = 0; self.saved = (0, 0)
721
+ self.decoder = codecs.getincrementaldecoder("utf-8")("replace")
722
+ self.pending = ""
723
+ def resize(self, height, width):
724
+ global rows, cols
725
+ resized = []
726
+ for y in range(height):
727
+ previous = self.cells[y] if y < len(self.cells) else []
728
+ resized.append((previous[:width] + [" "] * width)[:width])
729
+ self.cells = resized
730
+ rows, cols = height, width
731
+ self.r = max(0, min(rows - 1, self.r))
732
+ self.c = max(0, min(cols - 1, self.c))
733
+ self.saved = (
734
+ max(0, min(rows - 1, self.saved[0])),
735
+ max(0, min(cols - 1, self.saved[1])),
736
+ )
737
+ def clear_line(self, mode=0):
738
+ if mode == 2: start, end = 0, cols
739
+ elif mode == 1: start, end = 0, self.c + 1
740
+ else: start, end = self.c, cols
741
+ for x in range(max(0, start), min(cols, end)): self.cells[self.r][x] = " "
742
+ def clear_display(self, mode=0):
743
+ if mode == 0:
744
+ self.clear_line(0)
745
+ for y in range(self.r + 1, rows): self.cells[y] = [" "] * cols
746
+ elif mode == 1:
747
+ for y in range(0, self.r): self.cells[y] = [" "] * cols
748
+ self.clear_line(1)
749
+ elif mode == 2:
750
+ self.cells = [[" "] * cols for _ in range(rows)]
751
+ elif mode == 3:
752
+ pass
753
+ def scroll(self, count=1):
754
+ for _ in range(max(1, count)):
755
+ self.cells.pop(0); self.cells.append([" "] * cols)
756
+ self.r = rows - 1
757
+ def reverse_scroll(self, count=1):
758
+ for _ in range(max(1, count)):
759
+ self.cells.pop(); self.cells.insert(0, [" "] * cols)
760
+ def put(self, ch):
761
+ if ch == "\r": self.c = 0; return
762
+ if ch == "\n":
763
+ self.r += 1
764
+ if self.r >= rows: self.scroll()
765
+ return
766
+ if ch == "\b": self.c = max(0, self.c - 1); return
767
+ if ord(ch) < 32 or ch == "\x7f": return
768
+ if self.c >= cols:
769
+ self.c = 0; self.r += 1
770
+ if self.r >= rows: self.scroll()
771
+ self.cells[self.r][self.c] = ch
772
+ self.c += 1
773
+ def csi(self, body, final):
774
+ private = body.startswith("?")
775
+ if private: body = body[1:]
776
+ values = []
777
+ for value in body.split(";") if body else []:
778
+ try: values.append(int(value.split(":")[0] or "0"))
779
+ except ValueError: values.append(0)
780
+ n = values[0] if values and values[0] else 1
781
+ if final in "Hf":
782
+ self.r = max(0, min(rows - 1, (values[0] if values else 1) - 1))
783
+ self.c = max(0, min(cols - 1, (values[1] if len(values) > 1 else 1) - 1))
784
+ elif final == "A": self.r = max(0, self.r - n)
785
+ elif final == "B": self.r = min(rows - 1, self.r + n)
786
+ elif final == "C": self.c = min(cols - 1, self.c + n)
787
+ elif final == "D": self.c = max(0, self.c - n)
788
+ elif final == "E": self.r = min(rows - 1, self.r + n); self.c = 0
789
+ elif final == "F": self.r = max(0, self.r - n); self.c = 0
790
+ elif final == "G": self.c = max(0, min(cols - 1, n - 1))
791
+ elif final in "de": self.r = max(0, min(rows - 1, (n - 1) if final == "d" else self.r + n))
792
+ elif final == "K": self.clear_line(values[0] if values else 0)
793
+ elif final == "J": self.clear_display(values[0] if values else 0)
794
+ elif final == "X":
795
+ for x in range(self.c, min(cols, self.c + n)): self.cells[self.r][x] = " "
796
+ elif final == "P":
797
+ line = self.cells[self.r]; del line[self.c:min(cols, self.c + n)]; line.extend([" "] * (cols - len(line)))
798
+ elif final == "@":
799
+ line = self.cells[self.r]; line[self.c:self.c] = [" "] * n; del line[cols:]
800
+ elif final == "L":
801
+ for _ in range(n): self.cells.insert(self.r, [" "] * cols); self.cells.pop()
802
+ elif final == "M":
803
+ for _ in range(n): self.cells.pop(self.r); self.cells.append([" "] * cols)
804
+ elif final == "S": self.scroll(n)
805
+ elif final == "T": self.reverse_scroll(n)
806
+ elif final == "s": self.saved = (self.r, self.c)
807
+ elif final == "u": self.r, self.c = self.saved
808
+ def feed(self, data):
809
+ text = self.pending + self.decoder.decode(data)
810
+ self.pending = ""
811
+ i = 0
812
+ while i < len(text):
813
+ if text[i] != "\x1b": self.put(text[i]); i += 1; continue
814
+ if i + 1 >= len(text): self.pending = text[i:]; break
815
+ if text[i + 1] == "[":
816
+ j = i + 2
817
+ while j < len(text) and not ("@" <= text[j] <= "~"): j += 1
818
+ if j >= len(text): self.pending = text[i:]; break
819
+ self.csi(text[i + 2:j], text[j]); i = j + 1; continue
820
+ if text[i + 1] in "78":
821
+ if text[i + 1] == "7": self.saved = (self.r, self.c)
822
+ else: self.r, self.c = self.saved
823
+ i += 2; continue
824
+ if text[i + 1] in "]P_^":
825
+ bell = text.find("\x07", i + 2)
826
+ st = text.find("\x1b\\", i + 2)
827
+ ends = [value for value in (bell, st) if value >= 0]
828
+ if not ends: self.pending = text[i:]; break
829
+ end = min(ends); i = end + (2 if text[end] == "\x1b" else 1); continue
830
+ i += 2
831
+ def text(self):
832
+ return "\n".join("".join(line).rstrip() for line in self.cells).rstrip()
833
+
834
+ master = slave = pid = pgid = None
835
+ group_established = False
836
+ status = None
837
+ cleanup_started = False
838
+ screen = Screen()
839
+ raw = bytearray()
840
+ overall_deadline = time.monotonic() + 18.0
841
+ supervisor_data = {"pythonPid": os.getpid(), "cleanupSignals": []}
842
+
843
+ def record_supervisor(**fields):
844
+ supervisor_data.update(fields)
845
+ temporary = supervisor_path + "." + str(os.getpid()) + ".tmp"
846
+ with open(temporary, "w") as metadata:
847
+ json.dump(supervisor_data, metadata)
848
+ metadata.flush()
849
+ os.fsync(metadata.fileno())
850
+ os.replace(temporary, supervisor_path)
851
+
852
+ record_supervisor()
853
+ if environment.get("ZENTUI_DELAY_METADATA_COMPLETION") == "1": time.sleep(0.2)
854
+
855
+ def group_exists():
856
+ if not group_established or pgid is None: return False
857
+ try: os.killpg(pgid, 0); return True
858
+ except ProcessLookupError: return False
859
+ except PermissionError: return True
860
+
861
+ def child_exists():
862
+ if pid is None or status is not None: return False
863
+ try: os.kill(pid, 0); return True
864
+ except ProcessLookupError: return False
865
+
866
+ def reap_leader(block=False):
867
+ global status
868
+ if pid is None or status is not None: return
869
+ try:
870
+ waited, value = os.waitpid(pid, 0 if block else os.WNOHANG)
871
+ if waited == pid: status = value
872
+ except ChildProcessError:
873
+ if status is None: status = 0
874
+
875
+ def signal_target(sig):
876
+ sent = False
877
+ try:
878
+ if group_established and pgid is not None: os.killpg(pgid, sig); sent = True
879
+ elif pid is not None: os.kill(pid, sig); sent = True
880
+ except ProcessLookupError: pass
881
+ if sent:
882
+ signals = [*supervisor_data.get("cleanupSignals", []), signal.Signals(sig).name]
883
+ record_supervisor(cleanupSignals=signals)
884
+ return sent
885
+
886
+ def cleanup():
887
+ global status, cleanup_started
888
+ if pid is None or cleanup_started: return
889
+ cleanup_started = True
890
+ signal_target(signal.SIGTERM)
891
+ end = time.monotonic() + 1.0
892
+ while time.monotonic() < end:
893
+ reap_leader(False)
894
+ if not group_exists() and not child_exists(): break
895
+ time.sleep(0.02)
896
+ if group_exists() or child_exists():
897
+ signal_target(signal.SIGKILL)
898
+ end = time.monotonic() + 1.0
899
+ while time.monotonic() < end and (group_exists() or child_exists()):
900
+ reap_leader(False); time.sleep(0.02)
901
+ reap_leader(False)
902
+ if status is None and not group_exists() and not child_exists(): reap_leader(True)
903
+ record_supervisor(
904
+ leaderReaped=status is not None,
905
+ groupAliveAfterCleanup=group_exists(),
906
+ leaderAliveAfterCleanup=child_exists(),
907
+ )
908
+
909
+ def handle_signal(_signum, _frame):
910
+ cleanup()
911
+ raise SystemExit(128 + _signum)
912
+
913
+ signal.signal(signal.SIGTERM, handle_signal)
914
+ signal.signal(signal.SIGINT, handle_signal)
915
+
916
+ try:
917
+ master, slave = os.openpty()
918
+ fcntl.ioctl(slave, termios.TIOCSWINSZ, struct.pack("HHHH", rows, cols, 0, 0))
919
+ ready_r, ready_w = os.pipe()
920
+ ack_r, ack_w = os.pipe()
921
+ pid = os.fork()
922
+ if pid == 0:
923
+ os.close(ready_r); os.close(ack_w)
924
+ try:
925
+ if environment.get("ZENTUI_SIMULATE_GROUP_FAILURE") == "1":
926
+ raise RuntimeError("simulated process-group establishment failure")
927
+ os.setsid()
928
+ child_pgid = os.getpgrp()
929
+ os.write(ready_w, ("OK " + str(child_pgid) + "\n").encode())
930
+ os.close(ready_w)
931
+ if os.read(ack_r, 1) != b"1": raise RuntimeError("parent did not acknowledge process group")
932
+ os.close(ack_r)
933
+ os.close(master)
934
+ os.dup2(slave, 0); os.dup2(slave, 1); os.dup2(slave, 2)
935
+ if slave > 2: os.close(slave)
936
+ os.execvpe(command[0], command, environment)
937
+ except BaseException as error:
938
+ try: os.write(ready_w, ("ERR " + repr(error) + "\n").encode())
939
+ except OSError: pass
940
+ os._exit(125)
941
+ os.close(ready_w); os.close(ack_r)
942
+ os.close(slave); slave = None
943
+ record_supervisor(piPid=pid)
944
+ handshake_deadline = time.monotonic() + 2.0
945
+ handshake = b""
946
+ while b"\n" not in handshake and time.monotonic() < handshake_deadline:
947
+ ready, _, _ = select.select([ready_r], [], [], max(0, handshake_deadline - time.monotonic()))
948
+ if not ready: break
949
+ part = os.read(ready_r, 4096)
950
+ if not part: break
951
+ handshake += part
952
+ os.close(ready_r)
953
+ if handshake.startswith(b"OK "):
954
+ pgid = int(handshake.splitlines()[0].split()[1])
955
+ group_established = pgid == pid and os.getpgid(pid) == pgid
956
+ record_supervisor(
957
+ piPgid=pgid,
958
+ groupEstablished=group_established,
959
+ handshake=handshake.decode("utf-8", "replace").strip(),
960
+ )
961
+ if not group_established:
962
+ os.close(ack_w)
963
+ raise RuntimeError("Pi process-group handshake failed: " + handshake.decode("utf-8", "replace"))
964
+ os.write(ack_w, b"1"); os.close(ack_w)
965
+
966
+ def pump_until(predicate, label):
967
+ matched = False
968
+ quiet_since = None
969
+ def reevaluate():
970
+ nonlocal matched, quiet_since
971
+ now = time.monotonic()
972
+ if predicate():
973
+ if not matched:
974
+ matched = True; quiet_since = now
975
+ else:
976
+ matched = False; quiet_since = None
977
+ while time.monotonic() < overall_deadline:
978
+ reevaluate()
979
+ now = time.monotonic()
980
+ timeout = min(0.05, max(0, overall_deadline - now))
981
+ ready, _, _ = select.select([master], [], [], timeout)
982
+ if ready:
983
+ try: part = os.read(master, 65536)
984
+ except OSError: part = b""
985
+ if part:
986
+ raw.extend(part); screen.feed(part)
987
+ reevaluate()
988
+ reap_leader(False)
989
+ reevaluate()
990
+ if status is not None and not matched and label != "process exit":
991
+ raise RuntimeError("Pi exited before " + label + ": " + raw.decode("utf-8", "replace")[-4000:])
992
+ if matched and quiet_since is not None and time.monotonic() - quiet_since >= 0.15:
993
+ return
994
+ raise TimeoutError("deadline waiting for " + label + ": " + screen.text()[-2000:])
995
+
996
+ folded = live_section = expanded = refolded = tree_transition = rail_transition = quiescence_snapshot = ""
997
+ transition_sequence = []
998
+ structural_snapshots = []
999
+
1000
+ def switch_thinking_mode(action, expected_mode, expected_active, expected_row):
1001
+ os.write(master, b"/zentui\r")
1002
+ pump_until(lambda: "Appearance (1/9)" in screen.text(), "settings open")
1003
+ os.write(master, b"\t\t\t")
1004
+ pump_until(lambda: "Thinking (Experimental) (4/9)" in screen.text(), "Thinking settings section")
1005
+ os.write(master, b"\x1b[B")
1006
+ before = screen.text()
1007
+ previous = [value for value in ("Streaming", "Tree", "Rail") if "Mode " + value in before]
1008
+ if len(previous) != 1: raise AssertionError("could not parse previous Thinking mode: " + before[-2000:])
1009
+ os.write(master, action)
1010
+ expected_label = "Mode " + expected_mode
1011
+ expected_status = "Saved: " + expected_mode + " · Active: " + expected_active
1012
+ if expected_mode == "Streaming" and expected_active != "Streaming": expected_status += " · restart required"
1013
+ pump_until(lambda: expected_label in screen.text() and expected_status in screen.text(), "Thinking mode " + expected_mode)
1014
+ settings_snapshot = screen.text()
1015
+ status_rows = [row.strip() for row in settings_snapshot.split("\n") if row.strip().startswith("Saved: ")]
1016
+ status_regions = [row.split(" Rail shows", 1)[0].split(" Live switching", 1)[0] for row in status_rows]
1017
+ if not status_regions or any(row != expected_status for row in status_regions):
1018
+ raise AssertionError("unexpected complete Thinking status regions: " + repr(status_regions))
1019
+ os.write(master, b"\x1b")
1020
+ expected_resource = 1 if expected_active == "Streaming" else 0
1021
+ expected_resources = "__ZENTUI_RESOURCES__ input=" + str(expected_resource) + " timer=" + str(expected_resource)
1022
+ def exact_resource_rows():
1023
+ return [row.strip() for row in screen.text().split("\n") if row.strip().startswith("__ZENTUI_RESOURCES__")]
1024
+ pump_until(lambda: expected_row in screen.text() and exact_resource_rows() == [expected_resources] and "__ZENTUI_EXPERIMENTAL_TRANSITION_START_52F0A8__" in screen.text() and "__ZENTUI_EXPERIMENTAL_TRANSITION_END_52F0A8__" in screen.text(), expected_mode + " transition screen")
1025
+ snapshot = screen.text()
1026
+ transition_sequence.append({"from": previous[0].lower(), "to": expected_mode.lower(), "mode": expected_active.lower(), "saved": expected_mode.lower(), "active": expected_active.lower(), "status": expected_status, "statusRows": status_regions, "resources": expected_resources, "settingsScreen": settings_snapshot, "screen": snapshot})
1027
+ return snapshot
1028
+
1029
+ if environment.get("ZENTUI_QUIESCENCE_SIMULATION") == "1":
1030
+ pump_until(lambda: "TRANSIENT_READY" in screen.text() and "STABLE_GATE" in screen.text(), "stable simulated state")
1031
+ quiescence_snapshot = screen.text()
1032
+ pump_until(lambda: status is not None, "process exit")
1033
+ elif environment.get("ZENTUI_ACTIVE_STREAMING_SHUTDOWN") == "1":
1034
+ pump_until(lambda: os.path.exists(probe_path) and os.path.getsize(probe_path) > 0 and "__ZENTUI_EXPERIMENTAL_TRANSITION_START_52F0A8__" in screen.text() and "__ZENTUI_RESOURCES__ input=1 timer=1" in screen.text(), "active Streaming shutdown readiness")
1035
+ folded = screen.text()
1036
+ os.write(master, b"\x04")
1037
+ pump_until(lambda: status is not None, "process exit")
1038
+ elif environment.get("ZENTUI_DIRECT_RAIL") == "1":
1039
+ pump_until(lambda: os.path.exists(probe_path) and os.path.getsize(probe_path) > 0 and "__ZENTUI_EXPERIMENTAL_TRANSITION_START_52F0A8__" in screen.text() and "__ZENTUI_RESOURCES__ input=1 timer=1" in screen.text(), "direct Rail transition readiness")
1040
+ folded = screen.text()
1041
+ rail_transition = switch_thinking_mode(b"\x1c", "Rail", "Rail", "│ • Live transition 6")
1042
+ os.write(master, b"\x04")
1043
+ pump_until(lambda: status is not None, "process exit")
1044
+ elif environment.get("ZENTUI_THINKING_MODE") in ("rail", "tree"):
1045
+ def sentinel_rows(snapshot, start, end):
1046
+ values = snapshot.split("\n")
1047
+ try: start_index = next(i for i, row in enumerate(values) if row.strip() == start)
1048
+ except StopIteration: return []
1049
+ try: end_index = next(i for i, row in enumerate(values[start_index + 1:], start_index + 1) if row.strip() == end)
1050
+ except StopIteration: return []
1051
+ return [row.rstrip() for row in values[start_index + 1:end_index]]
1052
+ pump_until(lambda: os.path.exists(probe_path) and os.path.getsize(probe_path) > 0 and "«Z0W80»" in screen.text() and "«/Z0W80»" in screen.text(), "initial 80-column structural screen")
1053
+ for generation in range(6):
1054
+ for width in (20, 80):
1055
+ screen.resize(rows, width)
1056
+ fcntl.ioctl(master, termios.TIOCSWINSZ, struct.pack("HHHH", rows, width, 0, 0))
1057
+ os.kill(pid, signal.SIGWINCH)
1058
+ start = "«Z" + str(generation) + "W" + str(width) + "»"
1059
+ end = "«/Z" + str(generation) + "W" + str(width) + "»"
1060
+ pump_until(lambda start=start, end=end: start in screen.text() and end in screen.text(), str(width) + "-column structural generation " + str(generation))
1061
+ snapshot = screen.text()
1062
+ structural_snapshots.append({
1063
+ "generation": generation,
1064
+ "width": width,
1065
+ "rows": sentinel_rows(snapshot, start, end),
1066
+ "screen": snapshot,
1067
+ })
1068
+ if generation == 0 and width == 80: folded = snapshot
1069
+ if generation < 5:
1070
+ os.write(master, b"\x1d")
1071
+ next_start = "«Z" + str(generation + 1) + "W80»"
1072
+ pump_until(lambda next_start=next_start: next_start in screen.text(), "structural generation " + str(generation + 1))
1073
+ mode = environment.get("ZENTUI_THINKING_MODE")
1074
+ if mode == "rail":
1075
+ tree_transition = switch_thinking_mode(b" ", "Tree", "Tree", "└─ • Live transition 6")
1076
+ switch_thinking_mode(b" ", "Streaming", "Tree", "└─ • Live transition 6")
1077
+ else:
1078
+ rail_transition = switch_thinking_mode(b" ", "Rail", "Rail", "│ • Live transition 6")
1079
+ switch_thinking_mode(b" ", "Streaming", "Rail", "│ • Live transition 6")
1080
+ os.write(master, b"\x04")
1081
+ pump_until(lambda: status is not None, "process exit")
1082
+ else:
1083
+ pump_until(lambda: os.path.exists(probe_path) and os.path.getsize(probe_path) > 0 and "Thought" in screen.text() and "ctrl+t to expand" in screen.text() and "Thinking" in screen.text() and "KNOWN WRAPPED CONTINUATION" in screen.text() and "__ZENTUI_EXPERIMENTAL_LIVE_START_7D91B2__" in screen.text() and "__ZENTUI_EXPERIMENTAL_LIVE_END_7D91B2__" in screen.text() and "__ZENTUI_EXPERIMENTAL_COMPLETED_START_4A62C9__" in screen.text() and "__ZENTUI_EXPERIMENTAL_COMPLETED_END_4A62C9__" in screen.text(), "fold readiness")
1084
+ folded = screen.text()
1085
+ folded_lines = folded.split("\n")
1086
+ live_start = next(i for i, row in enumerate(folded_lines) if row.strip() == "__ZENTUI_EXPERIMENTAL_LIVE_START_7D91B2__")
1087
+ live_end = next(i for i, row in enumerate(folded_lines[live_start + 1:], live_start + 1) if row.strip() == "__ZENTUI_EXPERIMENTAL_LIVE_END_7D91B2__")
1088
+ live_section = "\n".join(folded_lines[live_start + 1:live_end])
1089
+ os.write(master, b"\x07")
1090
+ pump_until(lambda: "LIVE rendered row 8" not in screen.text() and "__ZENTUI_EXPERIMENTAL_COMPLETED_START_4A62C9__" in screen.text(), "live widget removal")
1091
+ os.write(master, b"\x14")
1092
+ pump_until(lambda: "PTY exact row 8" in screen.text() and "__ZENTUI_EXPERIMENTAL_COMPLETED_END_4A62C9__" in screen.text(), "expanded screen")
1093
+ expanded = screen.text()
1094
+ os.write(master, b"\x14")
1095
+ pump_until(lambda: "PTY exact row 8" not in screen.text() and "__ZENTUI_EXPERIMENTAL_COMPLETED_START_4A62C9__" in screen.text(), "refolded screen")
1096
+ refolded = screen.text()
1097
+
1098
+ tree_transition = switch_thinking_mode(b" ", "Tree", "Tree", "└─ • Live transition 6")
1099
+ rail_transition = switch_thinking_mode(b" ", "Rail", "Rail", "│ • Live transition 6")
1100
+ os.write(master, b"\x04")
1101
+ pump_until(lambda: status is not None, "process exit")
1102
+ finally:
1103
+ cleanup()
1104
+ if master is not None:
1105
+ try: os.close(master)
1106
+ except OSError: pass
1107
+ if slave is not None:
1108
+ try: os.close(slave)
1109
+ except OSError: pass
1110
+
1111
+ group_alive = group_exists()
1112
+ print(json.dumps({
1113
+ "exit": os.waitstatus_to_exitcode(status),
1114
+ "groupEstablished": group_established,
1115
+ "pgid": pgid,
1116
+ "folded": folded,
1117
+ "liveSection": live_section,
1118
+ "expanded": expanded,
1119
+ "refolded": refolded,
1120
+ "treeTransition": tree_transition,
1121
+ "railTransition": rail_transition,
1122
+ "transitionSequence": transition_sequence,
1123
+ "quiescenceSnapshot": quiescence_snapshot,
1124
+ "structuralSnapshots": structural_snapshots,
1125
+ "raw": raw.decode("utf-8", "replace"),
1126
+ "groupAlive": group_alive,
1127
+ "pid": pid,
1128
+ }))
1129
+ `;
1130
+
1131
+ const delay = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
1132
+
1133
+ function processAlive(pid) {
1134
+ if (!Number.isInteger(pid)) return false;
1135
+ try {
1136
+ process.kill(pid, 0);
1137
+ return true;
1138
+ } catch (error) {
1139
+ return error?.code === "EPERM";
1140
+ }
1141
+ }
1142
+
1143
+ function processGroupAlive(pgid) {
1144
+ if (!Number.isInteger(pgid)) return false;
1145
+ try {
1146
+ process.kill(-pgid, 0);
1147
+ return true;
1148
+ } catch (error) {
1149
+ return error?.code === "EPERM";
1150
+ }
1151
+ }
1152
+
1153
+ function extractSentinelRows(screen, start, end) {
1154
+ const rows = screen.split("\n");
1155
+ const startIndex = rows.findIndex((row) => row.trim() === start);
1156
+ const endIndex = rows.findIndex((row, index) => index > startIndex && row.trim() === end);
1157
+ if (startIndex < 0 || endIndex < 0) return [];
1158
+ return rows.slice(startIndex + 1, endIndex).map((row) => row.trimEnd());
1159
+ }
1160
+
1161
+ function topLevelTransientArtifacts(rootPath) {
1162
+ if (!existsSync(rootPath)) return [];
1163
+ return readdirSync(rootPath, { withFileTypes: true })
1164
+ .filter((entry) => /\.(?:tmp|lock)$/.test(entry.name))
1165
+ .map((entry) => join(rootPath, entry.name));
1166
+ }
1167
+
1168
+ function transientArtifacts(rootPath) {
1169
+ if (!existsSync(rootPath)) return [];
1170
+ const artifacts = topLevelTransientArtifacts(rootPath);
1171
+ for (const entry of readdirSync(rootPath, { withFileTypes: true })) {
1172
+ if (entry.isDirectory()) artifacts.push(...transientArtifacts(join(rootPath, entry.name)));
1173
+ }
1174
+ return artifacts;
1175
+ }
1176
+
1177
+ function assertTransientArtifactFixtures() {
1178
+ const fixture = join(workspace, "artifact-scan-fixture");
1179
+ mkdirSync(join(fixture, "nested"), { recursive: true });
1180
+ const topTmp = join(fixture, "leftover.tmp");
1181
+ const topLock = join(fixture, "leftover.lock");
1182
+ const nestedLock = join(fixture, "nested", "nested.lock");
1183
+ writeFileSync(topTmp, "fixture");
1184
+ writeFileSync(topLock, "fixture");
1185
+ writeFileSync(nestedLock, "fixture");
1186
+ const topLevel = topLevelTransientArtifacts(fixture).sort();
1187
+ const recursive = transientArtifacts(fixture).sort();
1188
+ if (
1189
+ JSON.stringify(topLevel) !== JSON.stringify([topLock, topTmp].sort()) ||
1190
+ JSON.stringify(recursive) !== JSON.stringify([nestedLock, topLock, topTmp].sort())
1191
+ ) {
1192
+ throw new Error("Transient artifact scan simulation failed");
1193
+ }
1194
+ rmSync(fixture, { recursive: true, force: true });
1195
+ }
1196
+
1197
+ function assertNoTransientArtifacts(version, phase, versionRoot, agentDir, home) {
1198
+ const artifacts = [
1199
+ ...topLevelTransientArtifacts(versionRoot),
1200
+ ...transientArtifacts(agentDir),
1201
+ ...transientArtifacts(home),
1202
+ ];
1203
+ if (artifacts.length) {
1204
+ throw new Error(
1205
+ `Pi ${version} left temporary artifacts after ${phase}: ${JSON.stringify(artifacts)}`,
1206
+ );
1207
+ }
1208
+ }
1209
+
1210
+ assertTransientArtifactFixtures();
1211
+
1212
+ async function readSupervisorMetadata(path, fallbackPythonPid) {
1213
+ const deadline = Date.now() + 2_500;
1214
+ while (Date.now() < deadline) {
1215
+ if (existsSync(path)) {
1216
+ try {
1217
+ return { pythonPid: fallbackPythonPid, ...JSON.parse(readFileSync(path, "utf8")) };
1218
+ } catch {
1219
+ // Retry if a platform exposes the atomic replacement between directory operations.
1220
+ }
1221
+ }
1222
+ await delay(20);
1223
+ }
1224
+ return { pythonPid: fallbackPythonPid };
1225
+ }
1226
+
1227
+ async function rereadSupervisorMetadata(path, metadata, fallbackPythonPid) {
1228
+ try {
1229
+ if (existsSync(path)) {
1230
+ return {
1231
+ pythonPid: fallbackPythonPid,
1232
+ ...metadata,
1233
+ ...JSON.parse(readFileSync(path, "utf8")),
1234
+ };
1235
+ }
1236
+ } catch {
1237
+ // Keep the last complete snapshot; the next escalation/detection rereads it again.
1238
+ }
1239
+ return { pythonPid: fallbackPythonPid, ...metadata };
1240
+ }
1241
+
1242
+ function labelsForSnapshot(metadata, fallbackPythonPid) {
1243
+ const leaks = [];
1244
+ if (processAlive(metadata.pythonPid ?? fallbackPythonPid))
1245
+ leaks.push(`python:${metadata.pythonPid ?? fallbackPythonPid}`);
1246
+ if (processAlive(metadata.piPid)) leaks.push(`pi:${metadata.piPid}`);
1247
+ if (processGroupAlive(metadata.piPgid)) leaks.push(`pi-group:${metadata.piPgid}`);
1248
+ return leaks;
1249
+ }
1250
+
1251
+ async function detectSurvivors(path, metadata, fallbackPythonPid) {
1252
+ const refreshed = await rereadSupervisorMetadata(path, metadata, fallbackPythonPid);
1253
+ return { metadata: refreshed, leaks: labelsForSnapshot(refreshed, fallbackPythonPid) };
1254
+ }
1255
+
1256
+ async function terminateSurvivors(path, metadata, fallbackPythonPid) {
1257
+ const signal = async (name) => {
1258
+ metadata = await rereadSupervisorMetadata(path, metadata, fallbackPythonPid);
1259
+ if (processGroupAlive(metadata.piPgid)) {
1260
+ try {
1261
+ process.kill(-metadata.piPgid, name);
1262
+ } catch {}
1263
+ }
1264
+ for (const pid of new Set([metadata.piPid, metadata.pythonPid ?? fallbackPythonPid])) {
1265
+ if (!processAlive(pid)) continue;
1266
+ try {
1267
+ process.kill(pid, name);
1268
+ } catch {}
1269
+ }
1270
+ };
1271
+ await signal("SIGTERM");
1272
+ await delay(1_000);
1273
+ await signal("SIGKILL");
1274
+ const deathDeadline = Date.now() + 1_500;
1275
+ let detected = await detectSurvivors(path, metadata, fallbackPythonPid);
1276
+ while (detected.leaks.length && Date.now() < deathDeadline) {
1277
+ await delay(25);
1278
+ detected = await detectSurvivors(path, detected.metadata, fallbackPythonPid);
1279
+ }
1280
+ return detected;
1281
+ }
1282
+
1283
+ async function runDriver(python, command, environment, cwd, options = {}) {
1284
+ const child = spawn("python3", [python, JSON.stringify(command), JSON.stringify(environment)], {
1285
+ cwd,
1286
+ stdio: ["ignore", "pipe", "pipe"],
1287
+ });
1288
+ let stdout = "";
1289
+ let stderr = "";
1290
+ let spawnError;
1291
+ child.stdout.setEncoding("utf8");
1292
+ child.stderr.setEncoding("utf8");
1293
+ child.stdout.on("data", (data) => (stdout += data));
1294
+ child.stderr.on("data", (data) => (stderr += data));
1295
+ child.on("error", (error) => (spawnError = error));
1296
+ const closed = new Promise((resolve) => child.on("close", (code) => resolve(code)));
1297
+ let timeout;
1298
+ const deadline = new Promise((resolve) => {
1299
+ timeout = setTimeout(() => resolve("timeout"), options.timeoutMs ?? 22_000);
1300
+ });
1301
+ const metadataPath = environment.ZENTUI_SUPERVISOR_PATH;
1302
+ let metadata = await readSupervisorMetadata(metadataPath, child.pid);
1303
+ const initialMetadata = { ...metadata };
1304
+ let outcome = await Promise.race([closed, deadline]);
1305
+ const timedOut = outcome === "timeout";
1306
+ if (timedOut) {
1307
+ metadata = await rereadSupervisorMetadata(metadataPath, metadata, child.pid);
1308
+ child.kill("SIGTERM");
1309
+ outcome = await Promise.race([
1310
+ closed,
1311
+ delay(options.graceMs ?? 2_500).then(() => "grace-expired"),
1312
+ ]);
1313
+ if (outcome === "grace-expired") {
1314
+ metadata = await rereadSupervisorMetadata(metadataPath, metadata, child.pid);
1315
+ child.kill("SIGKILL");
1316
+ outcome = await closed;
1317
+ }
1318
+ }
1319
+ clearTimeout(timeout);
1320
+
1321
+ await delay(100);
1322
+ let detected = await detectSurvivors(metadataPath, metadata, child.pid);
1323
+ metadata = detected.metadata;
1324
+ if (timedOut || detected.leaks.length) {
1325
+ detected = await terminateSurvivors(metadataPath, metadata, child.pid);
1326
+ metadata = detected.metadata;
1327
+ }
1328
+ if (detected.leaks.length)
1329
+ throw new Error(`PTY cleanup leaked after TERM/KILL ${detected.leaks.join(", ")}`);
1330
+ if (spawnError) throw spawnError;
1331
+ if (timedOut && !options.expectTimeout)
1332
+ throw new Error(`PTY driver timed out after cleanup: ${stderr || stdout}`);
1333
+ if (!timedOut && outcome !== 0)
1334
+ throw new Error(`PTY driver exit=${outcome}: ${stderr || stdout}`);
1335
+ if (timedOut)
1336
+ return { timedOut: true, supervisor: metadata, initialMetadata, stdout, stderr, outcome };
1337
+ return { ...JSON.parse(stdout.trim()), supervisor: metadata };
1338
+ }
1339
+
1340
+ try {
1341
+ const python = join(workspace, "driver.py");
1342
+ writeFileSync(python, ptySource);
1343
+ const quiescenceSupervisorPath = join(workspace, "quiescence-supervisor.json");
1344
+ const quiescence = await runDriver(
1345
+ python,
1346
+ [
1347
+ process.execPath,
1348
+ "-e",
1349
+ 'process.stdout.write("TRANSIENT_READY\\nSTABLE_GATE\\n"); setTimeout(() => process.stdout.write("\\x1b[2J\\x1b[HSTABLE_GATE\\n"), 40); setTimeout(() => process.stdout.write("TRANSIENT_READY\\n"), 300); setTimeout(() => process.exit(0), 650);',
1350
+ ],
1351
+ {
1352
+ ZENTUI_PROBE_PATH: join(workspace, "quiescence-unused-probe.jsonl"),
1353
+ ZENTUI_SUPERVISOR_PATH: quiescenceSupervisorPath,
1354
+ ZENTUI_QUIESCENCE_SIMULATION: "1",
1355
+ },
1356
+ workspace,
1357
+ );
1358
+ if (
1359
+ !quiescence.quiescenceSnapshot.includes("STABLE_GATE") ||
1360
+ !quiescence.quiescenceSnapshot.includes("TRANSIENT_READY")
1361
+ )
1362
+ throw new Error(
1363
+ `PTY quiescence simulation captured an erased transient: ${JSON.stringify(quiescence.quiescenceSnapshot)}`,
1364
+ );
1365
+ console.log(
1366
+ `quiescence-simulation: transient-erased=not-captured stable=${JSON.stringify(quiescence.quiescenceSnapshot.split("\n").filter(Boolean))}`,
1367
+ );
1368
+
1369
+ const simulatedSupervisorPath = join(workspace, "simulated-supervisor.json");
1370
+ let simulatedFailure = "";
1371
+ try {
1372
+ await runDriver(
1373
+ python,
1374
+ [process.execPath, "-e", "setInterval(() => {}, 1000)"],
1375
+ {
1376
+ ZENTUI_PROBE_PATH: join(workspace, "unused-probe.jsonl"),
1377
+ ZENTUI_SUPERVISOR_PATH: simulatedSupervisorPath,
1378
+ ZENTUI_SIMULATE_GROUP_FAILURE: "1",
1379
+ },
1380
+ workspace,
1381
+ );
1382
+ } catch (error) {
1383
+ simulatedFailure = error instanceof Error ? error.message : String(error);
1384
+ }
1385
+ const simulatedMetadata = JSON.parse(readFileSync(simulatedSupervisorPath, "utf8"));
1386
+ if (
1387
+ !simulatedFailure.includes("process-group handshake failed") ||
1388
+ simulatedMetadata.groupEstablished !== false ||
1389
+ processAlive(simulatedMetadata.pythonPid) ||
1390
+ processAlive(simulatedMetadata.piPid)
1391
+ )
1392
+ throw new Error(`PTY process-group failure cleanup simulation failed: ${simulatedFailure}`);
1393
+ console.log(
1394
+ `cleanup-simulation: setsid/pgid failure=fallback-child-pid reaped signals=${JSON.stringify(simulatedMetadata.cleanupSignals ?? [])}`,
1395
+ );
1396
+
1397
+ const stubbornPath = join(workspace, "stubborn-group.json");
1398
+ const stubbornSupervisorPath = join(workspace, "stubborn-supervisor.json");
1399
+ const stubbornSource = `
1400
+ import json, os, signal, sys, time
1401
+ signal.signal(signal.SIGTERM, signal.SIG_IGN)
1402
+ descendant = os.fork()
1403
+ if descendant == 0:
1404
+ signal.signal(signal.SIGTERM, signal.SIG_IGN)
1405
+ while True: time.sleep(1)
1406
+ temporary = sys.argv[1] + ".tmp"
1407
+ with open(temporary, "w") as output:
1408
+ json.dump({"leaderPid": os.getpid(), "leaderPgid": os.getpgrp(), "descendantPid": descendant, "descendantPgid": os.getpgid(descendant)}, output)
1409
+ output.flush(); os.fsync(output.fileno())
1410
+ os.replace(temporary, sys.argv[1])
1411
+ while True: time.sleep(1)
1412
+ `;
1413
+ const stubborn = await runDriver(
1414
+ python,
1415
+ ["python3", "-c", stubbornSource, stubbornPath],
1416
+ {
1417
+ ZENTUI_PROBE_PATH: join(workspace, "stubborn-unused-probe.jsonl"),
1418
+ ZENTUI_SUPERVISOR_PATH: stubbornSupervisorPath,
1419
+ ZENTUI_DELAY_METADATA_COMPLETION: "1",
1420
+ },
1421
+ workspace,
1422
+ { timeoutMs: 750, graceMs: 5_000, expectTimeout: true },
1423
+ );
1424
+ const stubbornGroup = JSON.parse(readFileSync(stubbornPath, "utf8"));
1425
+ const stubbornMetadata = stubborn.supervisor;
1426
+ if (
1427
+ !stubborn.timedOut ||
1428
+ Object.hasOwn(stubborn.initialMetadata, "piPid") ||
1429
+ Object.hasOwn(stubborn.initialMetadata, "piPgid") ||
1430
+ Object.hasOwn(stubborn.initialMetadata, "handshake") ||
1431
+ stubbornMetadata.handshake !== `OK ${stubbornMetadata.piPgid}` ||
1432
+ stubbornMetadata.groupEstablished !== true ||
1433
+ stubbornMetadata.piPid !== stubbornGroup.leaderPid ||
1434
+ stubbornMetadata.piPgid !== stubbornGroup.leaderPgid ||
1435
+ stubbornGroup.leaderPgid !== stubbornGroup.descendantPgid ||
1436
+ JSON.stringify(stubbornMetadata.cleanupSignals) !== JSON.stringify(["SIGTERM", "SIGKILL"]) ||
1437
+ stubbornMetadata.leaderReaped !== true ||
1438
+ stubbornMetadata.groupAliveAfterCleanup !== false ||
1439
+ stubbornMetadata.leaderAliveAfterCleanup !== false ||
1440
+ processAlive(stubbornMetadata.pythonPid) ||
1441
+ processAlive(stubbornGroup.leaderPid) ||
1442
+ processAlive(stubbornGroup.descendantPid) ||
1443
+ processGroupAlive(stubbornGroup.leaderPgid)
1444
+ ) {
1445
+ throw new Error(
1446
+ `PTY stubborn TERM/KILL cleanup simulation failed: ${JSON.stringify({ stubborn, stubbornGroup })}`,
1447
+ );
1448
+ }
1449
+ console.log(
1450
+ `cleanup-simulation: late-metadata=${JSON.stringify(stubborn.initialMetadata)}->${stubbornMetadata.handshake} established-group=${stubbornGroup.leaderPgid} same-group-descendant=${stubbornGroup.descendantPgid} signals=${JSON.stringify(stubbornMetadata.cleanupSignals)} python=${stubbornMetadata.pythonPid}:reaped leader=${stubbornGroup.leaderPid}:reaped descendant=${stubbornGroup.descendantPid}:gone group=gone`,
1451
+ );
1452
+
1453
+ for (const version of versions) {
1454
+ const versionRoot = join(workspace, version);
1455
+ mkdirSync(versionRoot, { recursive: true });
1456
+ cpSync(join(root, "extensions"), join(versionRoot, "extensions"), { recursive: true });
1457
+ writeFileSync(join(versionRoot, "package.json"), '{"type":"module","private":true}\n');
1458
+ execFileSync(
1459
+ process.execPath,
1460
+ [
1461
+ npmCli,
1462
+ "install",
1463
+ "--ignore-scripts",
1464
+ "--no-package-lock",
1465
+ "--no-audit",
1466
+ "--no-fund",
1467
+ "--silent",
1468
+ `@earendil-works/pi-ai@${version}`,
1469
+ `@earendil-works/pi-coding-agent@${version}`,
1470
+ `@earendil-works/pi-tui@${version}`,
1471
+ ],
1472
+ { cwd: versionRoot, stdio: "inherit", timeout: 180_000 },
1473
+ );
1474
+
1475
+ const attestation = attestInstalledVersions(versionRoot, version);
1476
+ const runtimeVersion = attestation.versions.codingAgent;
1477
+ const agentDir = join(versionRoot, "agent");
1478
+ const home = join(versionRoot, "home");
1479
+ mkdirSync(agentDir, { recursive: true });
1480
+ mkdirSync(home, { recursive: true });
1481
+ writeFileSync(
1482
+ join(agentDir, "zentui.json"),
1483
+ JSON.stringify({
1484
+ components: {
1485
+ thinkingSteps: { enabled: true, mode: "streaming" },
1486
+ editor: { enabled: false },
1487
+ userMessages: { enabled: false },
1488
+ workingLine: { enabled: false },
1489
+ selectorBorders: { enabled: false },
1490
+ footer: { style: "native" },
1491
+ },
1492
+ }),
1493
+ );
1494
+ const session = join(versionRoot, "fixture.jsonl");
1495
+ const entries = [
1496
+ {
1497
+ type: "session",
1498
+ version: 3,
1499
+ id: `zentui-${version.replaceAll(".", "-")}`,
1500
+ timestamp: "2024-01-01T00:00:00.000Z",
1501
+ cwd: versionRoot,
1502
+ },
1503
+ {
1504
+ type: "message",
1505
+ id: "user-1",
1506
+ parentId: null,
1507
+ timestamp: "2024-01-01T00:00:00.500Z",
1508
+ message: {
1509
+ role: "user",
1510
+ content: [{ type: "text", text: "Use the offline reasoning fixture." }],
1511
+ timestamp: Date.parse("2024-01-01T00:00:00.500Z"),
1512
+ },
1513
+ },
1514
+ {
1515
+ type: "message",
1516
+ id: "assistant-1",
1517
+ parentId: "user-1",
1518
+ timestamp: "2024-01-01T00:00:13.300Z",
1519
+ message: assistant,
1520
+ },
1521
+ ];
1522
+ writeFileSync(session, `${entries.map((entry) => JSON.stringify(entry)).join("\n")}\n`);
1523
+ const resourceProbe = join(versionRoot, "resource-probe.ts");
1524
+ const probe = join(versionRoot, "probe.ts");
1525
+ const probePath = join(versionRoot, "probe.jsonl");
1526
+ const supervisorPath = join(versionRoot, "supervisor.json");
1527
+ writeFileSync(resourceProbe, resourceProbeSource);
1528
+ writeFileSync(probe, probeSource);
1529
+ const codingAgent = attestation.resolved.codingAgent;
1530
+ const cliRelative = codingAgent.manifest.bin?.pi;
1531
+ if (typeof cliRelative !== "string")
1532
+ throw new Error(`Pi ${runtimeVersion} coding-agent manifest has no pi binary`);
1533
+ const cli = join(dirname(codingAgent.path), cliRelative);
1534
+ const command = [
1535
+ process.execPath,
1536
+ cli,
1537
+ "--offline",
1538
+ "--no-extensions",
1539
+ "-e",
1540
+ resourceProbe,
1541
+ "-e",
1542
+ join(versionRoot, "extensions", "zentui", "index.ts"),
1543
+ "-e",
1544
+ probe,
1545
+ "--session",
1546
+ session,
1547
+ "--no-context-files",
1548
+ ];
1549
+ const environment = {
1550
+ HOME: home,
1551
+ PI_CODING_AGENT_DIR: agentDir,
1552
+ PI_OFFLINE: "1",
1553
+ PI_SKIP_VERSION_CHECK: "1",
1554
+ PI_TELEMETRY: "0",
1555
+ TERM: "xterm-256color",
1556
+ ZENTUI_PROBE_PATH: probePath,
1557
+ ZENTUI_SUPERVISOR_PATH: supervisorPath,
1558
+ ZENTUI_PI_VERSION: runtimeVersion,
1559
+ };
1560
+ const result = await runDriver(python, command, environment, versionRoot);
1561
+ if (result.exit !== 0) throw new Error(`Pi ${version} exit=${result.exit}`);
1562
+ if (!result.groupEstablished || !result.supervisor?.groupEstablished)
1563
+ throw new Error(`Pi ${version} process-group handshake was not established`);
1564
+ if (result.pgid !== result.pid || result.supervisor.piPgid !== result.supervisor.piPid)
1565
+ throw new Error(`Pi ${version} recorded inconsistent pid/pgid metadata`);
1566
+ if (result.groupAlive)
1567
+ throw new Error(`Pi ${version} process group ${result.pgid} survived cleanup`);
1568
+ if (hasExtensionLoaderDiagnostic(result.raw)) {
1569
+ throw new Error(`Pi ${runtimeVersion} emitted loader diagnostics`);
1570
+ }
1571
+ const completedStart = "__ZENTUI_EXPERIMENTAL_COMPLETED_START_4A62C9__";
1572
+ const completedEnd = "__ZENTUI_EXPERIMENTAL_COMPLETED_END_4A62C9__";
1573
+ const foldedCompletedRows = extractSentinelRows(result.folded, completedStart, completedEnd);
1574
+ const expandedCompletedRows = extractSentinelRows(
1575
+ result.expanded,
1576
+ completedStart,
1577
+ completedEnd,
1578
+ );
1579
+ const refoldedCompletedRows = extractSentinelRows(
1580
+ result.refolded,
1581
+ completedStart,
1582
+ completedEnd,
1583
+ );
1584
+ const expectedFoldedCompletedRows = [" Thought (ctrl+t to expand)"];
1585
+ const expectedExpandedCompletedRows = [
1586
+ " PTY exact row 1",
1587
+ " PTY indented row 2",
1588
+ " PTY exact row 3",
1589
+ "",
1590
+ " PTY exact row 5",
1591
+ " PTY exact row 6",
1592
+ " PTY exact row 7",
1593
+ " PTY exact row 8",
1594
+ ];
1595
+ const transitionStart = "__ZENTUI_EXPERIMENTAL_TRANSITION_START_52F0A8__";
1596
+ const transitionEnd = "__ZENTUI_EXPERIMENTAL_TRANSITION_END_52F0A8__";
1597
+ const treeTransitionRows = extractSentinelRows(
1598
+ result.treeTransition,
1599
+ transitionStart,
1600
+ transitionEnd,
1601
+ );
1602
+ const railTransitionRows = extractSentinelRows(
1603
+ result.railTransition,
1604
+ transitionStart,
1605
+ transitionEnd,
1606
+ );
1607
+ const expectedTreeTransitionRows = [
1608
+ "",
1609
+ " ┆ Thinking",
1610
+ " ├─ · Live transition 2",
1611
+ " ├─ · Live transition 3",
1612
+ " ├─ · Live transition 4",
1613
+ " ├─ · Live transition 5",
1614
+ " └─ • Live transition 6",
1615
+ ];
1616
+ const expectedRailTransitionRows = [
1617
+ "",
1618
+ " │ Thinking",
1619
+ " │ Live transition 1",
1620
+ " │ Live transition 2",
1621
+ " │ Live transition 3",
1622
+ " │ Live transition 4",
1623
+ " │ Live transition 5",
1624
+ " │ • Live transition 6",
1625
+ ];
1626
+ const expectedTransitionPairs = [
1627
+ ["streaming", "tree"],
1628
+ ["tree", "rail"],
1629
+ ];
1630
+ if (
1631
+ JSON.stringify(result.transitionSequence?.map(({ from, to }) => [from, to])) !==
1632
+ JSON.stringify(expectedTransitionPairs)
1633
+ )
1634
+ throw new Error(
1635
+ `Pi ${version} exact transition sequence was incorrect: ${JSON.stringify(result.transitionSequence)}`,
1636
+ );
1637
+ const startupResourceRows = result.folded
1638
+ .split("\n")
1639
+ .map((row) => row.trim())
1640
+ .filter((row) => /^__ZENTUI_RESOURCES__ input=\d+ timer=\d+$/.test(row));
1641
+ if (
1642
+ JSON.stringify(startupResourceRows) !==
1643
+ JSON.stringify(["__ZENTUI_RESOURCES__ input=1 timer=1"])
1644
+ )
1645
+ throw new Error(
1646
+ `Pi ${version} startup Streaming resources were not exactly 1/1: ${JSON.stringify(startupResourceRows)}`,
1647
+ );
1648
+ for (const transition of result.transitionSequence) {
1649
+ const resourceRows = transition.screen
1650
+ .split("\n")
1651
+ .map((row) => row.trim())
1652
+ .filter((row) => /^__ZENTUI_RESOURCES__ input=\d+ timer=\d+$/.test(row));
1653
+ const resourceMatch = resourceRows[0]?.match(
1654
+ /^__ZENTUI_RESOURCES__ input=(\d+) timer=(\d+)$/,
1655
+ );
1656
+ const expectedResource = transition.mode === "streaming" ? 1 : 0;
1657
+ if (
1658
+ !transition.settingsScreen.split("\n").some((row) => row.trim() === transition.status) ||
1659
+ resourceRows.length !== 1 ||
1660
+ Number(resourceMatch?.[1]) !== expectedResource ||
1661
+ Number(resourceMatch?.[2]) !== expectedResource ||
1662
+ resourceRows[0] !== transition.resources
1663
+ )
1664
+ throw new Error(
1665
+ `Pi ${version} transition state/resources were incorrect: ${JSON.stringify(transition)}`,
1666
+ );
1667
+ const rows = extractSentinelRows(transition.screen, transitionStart, transitionEnd);
1668
+ const expectedRows =
1669
+ transition.mode === "tree"
1670
+ ? expectedTreeTransitionRows
1671
+ : transition.mode === "rail"
1672
+ ? expectedRailTransitionRows
1673
+ : undefined;
1674
+ if (expectedRows && JSON.stringify(rows) !== JSON.stringify(expectedRows))
1675
+ throw new Error(
1676
+ `Pi ${version} ${transition.mode} direct transition rows were incorrect: ${JSON.stringify(rows)}`,
1677
+ );
1678
+ if (transition.mode === "streaming" && !rows.some((row) => row.includes("Thinking")))
1679
+ throw new Error(`Pi ${version} direct Streaming transition did not fold`);
1680
+ }
1681
+ if (JSON.stringify(foldedCompletedRows) !== JSON.stringify(expectedFoldedCompletedRows))
1682
+ throw new Error(
1683
+ `Pi ${version} exact folded completed rows were incorrect: ${JSON.stringify(foldedCompletedRows)}`,
1684
+ );
1685
+ if (JSON.stringify(expandedCompletedRows) !== JSON.stringify(expectedExpandedCompletedRows))
1686
+ throw new Error(
1687
+ `Pi ${version} exact expanded completed rows were incorrect: ${JSON.stringify(expandedCompletedRows)}`,
1688
+ );
1689
+ if (JSON.stringify(refoldedCompletedRows) !== JSON.stringify(expectedFoldedCompletedRows))
1690
+ throw new Error(
1691
+ `Pi ${version} exact refolded completed rows were incorrect: ${JSON.stringify(refoldedCompletedRows)}`,
1692
+ );
1693
+ if (JSON.stringify(treeTransitionRows) !== JSON.stringify(expectedTreeTransitionRows))
1694
+ throw new Error(
1695
+ `Pi ${version} exact live Tree transition rows were incorrect: ${JSON.stringify(treeTransitionRows)}`,
1696
+ );
1697
+ if (JSON.stringify(railTransitionRows) !== JSON.stringify(expectedRailTransitionRows))
1698
+ throw new Error(
1699
+ `Pi ${version} exact live Rail transition rows were incorrect: ${JSON.stringify(railTransitionRows)}`,
1700
+ );
1701
+ if (!existsSync(probePath)) throw new Error(`Pi ${version} probe artifact missing`);
1702
+ const probes = readFileSync(probePath, "utf8")
1703
+ .trim()
1704
+ .split("\n")
1705
+ .map((line) => JSON.parse(line));
1706
+ const live = probes[0];
1707
+ const liveRows = live?.liveCollapsedRows ?? [];
1708
+ const liveBody = liveRows.filter((row) => !row.includes("Thinking"));
1709
+ const liveTail = live?.nativeTail ?? [];
1710
+ const expectedScreenTail = (live?.nativeScreenTail ?? []).map((row) => row.trimEnd());
1711
+ const extractedLiveRows = result.liveSection.split("\n").map((row) => row.trimEnd());
1712
+ const expectedStartupStreamingRows = [
1713
+ " Thinking <elapsed>s (ctrl+t to expand)",
1714
+ " padding words KNOWN WRAPPED CONTINUATION",
1715
+ " LIVE rendered row 5",
1716
+ " LIVE rendered row 6",
1717
+ " LIVE rendered row 7",
1718
+ " LIVE rendered row 8",
1719
+ ];
1720
+ const canonicalExtractedLiveRows = canonicalizeStreamingElapsed(extractedLiveRows);
1721
+ const extractedLiveHeaderIndexes = extractedLiveRows.flatMap((row, index) =>
1722
+ streamingHeaderPattern.test(row) ? [index] : [],
1723
+ );
1724
+ const extractedLiveBody = extractedLiveRows.filter(
1725
+ (_row, index) => index !== extractedLiveHeaderIndexes[0],
1726
+ );
1727
+ const oldContract = runtimeVersion === "0.80.5" || runtimeVersion === "0.83.0";
1728
+ const call = live?.testedCalls?.[0];
1729
+ const signatures = live?.orderedIdentitySignatures;
1730
+ const countConstructor = (signature, name) =>
1731
+ (signature ?? []).filter((child) => child.constructor === name).length;
1732
+ const markdownTexts = (signature) =>
1733
+ (signature ?? [])
1734
+ .filter((child) => child.constructor === "Markdown")
1735
+ .map((child) => child.text);
1736
+ const equalRows = (actual, expected) => JSON.stringify(actual) === JSON.stringify(expected);
1737
+ const callPredicates = {
1738
+ probeCount: probes.length === 2,
1739
+ ready: live?.ready === true,
1740
+ installed: live?.installed === true,
1741
+ markdownIdentity: live?.markdownIdentity === true,
1742
+ importedAssistantIdentity: live?.importedAssistantIdentity === true,
1743
+ importedMarkdownIdentity: live?.importedMarkdownIdentity === true,
1744
+ wrapperCallCount: live?.wrapperCalls === 1,
1745
+ testedCallCount: live?.testedCalls?.length === 1,
1746
+ argumentCount: call?.count === (oldContract ? 1 : 2),
1747
+ messageIdentity: call?.messageIdentity === true,
1748
+ streamingArgument: oldContract ? call?.isStreaming === undefined : call?.isStreaming === true,
1749
+ hiddenFolded: live?.hiddenFolded === true,
1750
+ hiddenStatePreserved: live?.hiddenStatePreserved === true,
1751
+ };
1752
+ const signaturePredicates = {
1753
+ collisionThinkingCount:
1754
+ countConstructor(signatures?.collision, "FoldedThinkingSection") === 2,
1755
+ collisionMarkdown: equalRows(markdownTexts(signatures?.collision), ["collision B"]),
1756
+ equalThinkingCount: countConstructor(signatures?.equal, "FoldedThinkingSection") === 1,
1757
+ equalMarkdown: equalRows(markdownTexts(signatures?.equal), ["equal source"]),
1758
+ contiguousThinkingCount:
1759
+ countConstructor(signatures?.contiguous, "FoldedThinkingSection") ===
1760
+ (runtimeVersion === "0.80.5" ? 2 : 1),
1761
+ toolSeparatedThinkingCount:
1762
+ countConstructor(signatures?.toolSeparated, "FoldedThinkingSection") === 2,
1763
+ toolSeparatedMarkdownCount: markdownTexts(signatures?.toolSeparated).length === 0,
1764
+ };
1765
+ const livePredicates = {
1766
+ liveHeaderPresent: liveRows.some((row) => row.includes("Thinking")),
1767
+ liveRowsOneThroughThreeAbsent: !liveRows.some((row) => /LIVE rendered row [123]/.test(row)),
1768
+ liveBodyTailEqualsNativeTail: equalRows(liveBody.slice(-5), liveTail),
1769
+ extractedRowsEqualExpectedStartup: equalRows(
1770
+ canonicalExtractedLiveRows,
1771
+ expectedStartupStreamingRows,
1772
+ ),
1773
+ nativeScreenTailHasFiveRows: expectedScreenTail.length === 5,
1774
+ extractedHasOneHeader: extractedLiveHeaderIndexes.length === 1,
1775
+ extractedBodyHasFiveRows: extractedLiveBody.length === 5,
1776
+ extractedBodyEqualsNativeScreenTail: equalRows(extractedLiveBody, expectedScreenTail),
1777
+ extractedBodyStartsWithWrappedContinuation:
1778
+ extractedLiveBody[0]?.includes("KNOWN WRAPPED CONTINUATION") === true,
1779
+ extractedBodyHasRowsFiveThroughEight: [5, 6, 7, 8].every((row) =>
1780
+ extractedLiveBody.some((text) => text.includes(`LIVE rendered row ${row}`)),
1781
+ ),
1782
+ extractedBodyRowsOneThroughThreeAbsent: !extractedLiveBody.some((row) =>
1783
+ /LIVE rendered row [123]/.test(row),
1784
+ ),
1785
+ extractedBodyWrappingSourceAbsent: !extractedLiveBody.some((row) =>
1786
+ row.includes("LIVE wrapping row 4"),
1787
+ ),
1788
+ };
1789
+ const shutdown = probes.at(-1);
1790
+ const restoredPredicates = {
1791
+ restored: shutdown?.restored === true,
1792
+ descriptorAll: shutdown?.descriptorEvidence?.all === true,
1793
+ descriptorFields: Object.values(shutdown?.descriptorEvidence?.fields ?? {}).every(Boolean),
1794
+ widgetRemoved: shutdown?.widgetRemoved === true,
1795
+ widgetOwnershipCountZero: shutdown?.widgetOwnershipCount === 0,
1796
+ controllerInputsZero: shutdown?.controllerResources?.inputs === 0,
1797
+ controllerTimersZero: shutdown?.controllerResources?.timers === 0,
1798
+ };
1799
+ const predicateGroups = {
1800
+ call: callPredicates,
1801
+ signature: signaturePredicates,
1802
+ live: livePredicates,
1803
+ restored: restoredPredicates,
1804
+ };
1805
+ const failedPredicates = Object.entries(predicateGroups).flatMap(([group, predicates]) =>
1806
+ Object.entries(predicates).flatMap(([name, passed]) => (passed ? [] : [`${group}.${name}`])),
1807
+ );
1808
+ if (failedPredicates.length > 0) {
1809
+ throw new Error(
1810
+ `Pi ${version} live identity/tail/cleanup probe failed: ${boundedJson({
1811
+ failedPredicates,
1812
+ predicates: predicateGroups,
1813
+ extractedLiveRows,
1814
+ canonicalExtractedLiveRows,
1815
+ expectedStartupStreamingRows,
1816
+ expectedScreenTail,
1817
+ extractedLiveHeaderIndexes,
1818
+ extractedLiveBody,
1819
+ liveRows,
1820
+ liveBody,
1821
+ liveTail,
1822
+ call,
1823
+ signatures,
1824
+ shutdown,
1825
+ })}`,
1826
+ );
1827
+ }
1828
+
1829
+ writeFileSync(
1830
+ join(agentDir, "zentui.json"),
1831
+ JSON.stringify({
1832
+ components: {
1833
+ thinkingSteps: { enabled: true, mode: "streaming" },
1834
+ editor: { enabled: false },
1835
+ userMessages: { enabled: false },
1836
+ workingLine: { enabled: false },
1837
+ selectorBorders: { enabled: false },
1838
+ footer: { style: "native" },
1839
+ },
1840
+ }),
1841
+ );
1842
+ const directProbePath = join(versionRoot, "probe-direct-rail.jsonl");
1843
+ const directSupervisorPath = join(versionRoot, "supervisor-direct-rail.json");
1844
+ const directRail = await runDriver(
1845
+ python,
1846
+ command,
1847
+ {
1848
+ ...environment,
1849
+ ZENTUI_PROBE_PATH: directProbePath,
1850
+ ZENTUI_SUPERVISOR_PATH: directSupervisorPath,
1851
+ ZENTUI_DIRECT_RAIL: "1",
1852
+ },
1853
+ versionRoot,
1854
+ );
1855
+ const directProbes = readFileSync(directProbePath, "utf8")
1856
+ .trim()
1857
+ .split("\n")
1858
+ .map((line) => JSON.parse(line));
1859
+ const directTransition = directRail.transitionSequence?.[0];
1860
+ const directRows = extractSentinelRows(
1861
+ directTransition?.screen ?? "",
1862
+ transitionStart,
1863
+ transitionEnd,
1864
+ );
1865
+ const directResources = (directTransition?.screen ?? "")
1866
+ .split("\n")
1867
+ .map((row) => row.trim())
1868
+ .filter((row) => row.startsWith("__ZENTUI_RESOURCES__"));
1869
+ const directShutdown = directProbes.at(-1);
1870
+ if (
1871
+ directRail.exit !== 0 ||
1872
+ directRail.groupAlive ||
1873
+ hasExtensionLoaderDiagnostic(directRail.raw) ||
1874
+ JSON.stringify(directRail.transitionSequence?.map(({ from, to }) => [from, to])) !==
1875
+ JSON.stringify([["streaming", "rail"]]) ||
1876
+ directTransition?.saved !== "rail" ||
1877
+ directTransition?.active !== "rail" ||
1878
+ directTransition?.status !== "Saved: Rail · Active: Rail" ||
1879
+ directTransition?.statusRows?.some((row) => row !== "Saved: Rail · Active: Rail") ||
1880
+ JSON.stringify(directResources) !==
1881
+ JSON.stringify(["__ZENTUI_RESOURCES__ input=0 timer=0"]) ||
1882
+ JSON.stringify(directRows) !== JSON.stringify(expectedRailTransitionRows) ||
1883
+ directShutdown?.controllerResources?.inputs !== 0 ||
1884
+ directShutdown?.controllerResources?.timers !== 0 ||
1885
+ directShutdown?.restored !== true ||
1886
+ directShutdown?.descriptorEvidence?.all !== true ||
1887
+ !Object.values(directShutdown?.descriptorEvidence?.fields ?? {}).every(Boolean) ||
1888
+ directShutdown?.widgetRemoved !== true ||
1889
+ directShutdown?.widgetOwnershipCount !== 0
1890
+ )
1891
+ throw new Error(
1892
+ `Pi ${version} direct Streaming->Rail PTY probe failed: ${boundedJson({ directTransition, directRows, directResources, directShutdown, exit: directRail.exit, groupAlive: directRail.groupAlive })}`,
1893
+ );
1894
+
1895
+ writeFileSync(
1896
+ join(agentDir, "zentui.json"),
1897
+ JSON.stringify({
1898
+ components: {
1899
+ thinkingSteps: { enabled: true, mode: "streaming" },
1900
+ editor: { enabled: false },
1901
+ userMessages: { enabled: false },
1902
+ workingLine: { enabled: false },
1903
+ selectorBorders: { enabled: false },
1904
+ footer: { style: "native" },
1905
+ },
1906
+ }),
1907
+ );
1908
+ const activeShutdownProbePath = join(versionRoot, "probe-active-shutdown.jsonl");
1909
+ const activeShutdownSupervisorPath = join(versionRoot, "supervisor-active-shutdown.json");
1910
+ const activeShutdown = await runDriver(
1911
+ python,
1912
+ command,
1913
+ {
1914
+ ...environment,
1915
+ ZENTUI_PROBE_PATH: activeShutdownProbePath,
1916
+ ZENTUI_SUPERVISOR_PATH: activeShutdownSupervisorPath,
1917
+ ZENTUI_ACTIVE_STREAMING_SHUTDOWN: "1",
1918
+ },
1919
+ versionRoot,
1920
+ );
1921
+ const activeShutdownProbes = readFileSync(activeShutdownProbePath, "utf8")
1922
+ .trim()
1923
+ .split("\n")
1924
+ .map((line) => JSON.parse(line));
1925
+ const activeShutdownFinal = activeShutdownProbes.at(-1);
1926
+ const activeStartupResources = activeShutdown.folded
1927
+ .split("\n")
1928
+ .map((row) => row.trim())
1929
+ .filter((row) => /^__ZENTUI_RESOURCES__ input=\d+ timer=\d+$/.test(row));
1930
+ if (
1931
+ activeShutdown.exit !== 0 ||
1932
+ activeShutdown.groupAlive ||
1933
+ hasExtensionLoaderDiagnostic(activeShutdown.raw) ||
1934
+ (activeShutdown.transitionSequence?.length ?? -1) !== 0 ||
1935
+ JSON.stringify(activeStartupResources) !==
1936
+ JSON.stringify(["__ZENTUI_RESOURCES__ input=1 timer=1"]) ||
1937
+ activeShutdownFinal?.controllerResources?.inputs !== 0 ||
1938
+ activeShutdownFinal?.controllerResources?.timers !== 0 ||
1939
+ activeShutdownFinal?.restored !== true ||
1940
+ activeShutdownFinal?.descriptorEvidence?.all !== true ||
1941
+ !Object.values(activeShutdownFinal?.descriptorEvidence?.fields ?? {}).every(Boolean) ||
1942
+ activeShutdownFinal?.widgetRemoved !== true ||
1943
+ activeShutdownFinal?.widgetOwnershipCount !== 0
1944
+ )
1945
+ throw new Error(
1946
+ `Pi ${version} active Streaming shutdown PTY probe failed: ${boundedJson({ transitions: activeShutdown.transitionSequence, activeStartupResources, activeShutdownFinal, exit: activeShutdown.exit, groupAlive: activeShutdown.groupAlive })}`,
1947
+ );
1948
+ console.log(
1949
+ `${runtimeVersion} direct/shutdown: Streaming->Rail rows=exact status="${directTransition.status}" resources=${directResources[0]} active-Streaming-EOF=1/1->${activeShutdownFinal.controllerResources.inputs}/${activeShutdownFinal.controllerResources.timers} descriptor=${JSON.stringify(activeShutdownFinal.descriptorEvidence.fields)} widgets=0 process-group=gone`,
1950
+ );
1951
+
1952
+ for (const mode of ["rail", "tree"]) {
1953
+ writeFileSync(
1954
+ join(agentDir, "zentui.json"),
1955
+ JSON.stringify({
1956
+ components: {
1957
+ thinkingSteps: { enabled: true, mode },
1958
+ editor: { enabled: false },
1959
+ userMessages: { enabled: false },
1960
+ workingLine: { enabled: false },
1961
+ selectorBorders: { enabled: false },
1962
+ footer: { style: "native" },
1963
+ },
1964
+ }),
1965
+ );
1966
+ const modeProbePath = join(versionRoot, `probe-${mode}.jsonl`);
1967
+ const modeSupervisorPath = join(versionRoot, `supervisor-${mode}.json`);
1968
+ const modeResult = await runDriver(
1969
+ python,
1970
+ command,
1971
+ {
1972
+ ...environment,
1973
+ ZENTUI_PROBE_PATH: modeProbePath,
1974
+ ZENTUI_SUPERVISOR_PATH: modeSupervisorPath,
1975
+ ZENTUI_THINKING_MODE: mode,
1976
+ },
1977
+ versionRoot,
1978
+ );
1979
+ if (
1980
+ modeResult.exit !== 0 ||
1981
+ modeResult.groupAlive ||
1982
+ hasExtensionLoaderDiagnostic(modeResult.raw)
1983
+ ) {
1984
+ throw new Error(`Pi ${version} ${mode} PTY/process probe failed`);
1985
+ }
1986
+ const expectedModeTransitions =
1987
+ mode === "rail"
1988
+ ? [
1989
+ ["rail", "tree"],
1990
+ ["tree", "streaming"],
1991
+ ]
1992
+ : [
1993
+ ["tree", "rail"],
1994
+ ["rail", "streaming"],
1995
+ ];
1996
+ if (
1997
+ JSON.stringify(modeResult.transitionSequence?.map(({ from, to }) => [from, to])) !==
1998
+ JSON.stringify(expectedModeTransitions)
1999
+ )
2000
+ throw new Error(
2001
+ `Pi ${version} ${mode} exact transition sequence was incorrect: ${JSON.stringify(modeResult.transitionSequence)}`,
2002
+ );
2003
+ for (const [index, transition] of modeResult.transitionSequence.entries()) {
2004
+ const expectedActive = mode === "rail" ? "tree" : "rail";
2005
+ const expectedSaved = index === 0 ? expectedActive : "streaming";
2006
+ const expectedStatus = `Saved: ${expectedSaved[0].toUpperCase()}${expectedSaved.slice(1)} · Active: ${expectedActive[0].toUpperCase()}${expectedActive.slice(1)}${index === 1 ? " · restart required" : ""}`;
2007
+ const expectedRows =
2008
+ expectedActive === "tree" ? expectedTreeTransitionRows : expectedRailTransitionRows;
2009
+ const rows = extractSentinelRows(transition.screen, transitionStart, transitionEnd);
2010
+ const resources = transition.screen
2011
+ .split("\n")
2012
+ .map((row) => row.trim())
2013
+ .filter((row) => row.startsWith("__ZENTUI_RESOURCES__"));
2014
+ if (
2015
+ transition.active !== expectedActive ||
2016
+ transition.saved !== expectedSaved ||
2017
+ transition.status !== expectedStatus ||
2018
+ transition.statusRows.some((row) => row !== expectedStatus) ||
2019
+ JSON.stringify(resources) !== JSON.stringify(["__ZENTUI_RESOURCES__ input=0 timer=0"]) ||
2020
+ JSON.stringify(rows) !== JSON.stringify(expectedRows)
2021
+ )
2022
+ throw new Error(
2023
+ `Pi ${version} ${mode} transition status/resources/screen mismatch: ${JSON.stringify(transition)}`,
2024
+ );
2025
+ }
2026
+ const modeProbes = readFileSync(modeProbePath, "utf8")
2027
+ .trim()
2028
+ .split("\n")
2029
+ .map((line) => JSON.parse(line));
2030
+ const structural = modeProbes[0];
2031
+ const completedStructural = modeProbes.at(-1);
2032
+ const cleanExact = (rows) =>
2033
+ (rows ?? []).map((text) =>
2034
+ text
2035
+ .replace(/\x1b\]133;[ABC]\x07/g, "")
2036
+ .replace(/\x1b\](?:[^\x07\x1b]|\x1b(?!\\))*(?:\x07|\x1b\\)/g, "")
2037
+ .replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "")
2038
+ .trimEnd(),
2039
+ );
2040
+ const clean = (rows) => cleanExact(rows).filter((text) => text.trim().length > 0);
2041
+ const generations = completedStructural?.structuralGenerations ?? [];
2042
+ const snapshots = modeResult.structuralSnapshots ?? [];
2043
+ const oldContract = runtimeVersion === "0.80.5" || runtimeVersion === "0.83.0";
2044
+ const modeCall = structural?.testedCalls?.[0];
2045
+ let generationFailure;
2046
+ for (const generation of generations) {
2047
+ const replacementCount = generation.constructors.filter(
2048
+ (name) => name === "ThinkingStepsRows",
2049
+ ).length;
2050
+ if (replacementCount !== 1) {
2051
+ generationFailure = {
2052
+ generation: generation.generation,
2053
+ predicates: { replacementConstructorCount: replacementCount === 1 },
2054
+ constructors: generation.constructors,
2055
+ };
2056
+ break;
2057
+ }
2058
+ for (const comparison of generation.comparisons ?? []) {
2059
+ const snapshot = snapshots.find(
2060
+ (value) =>
2061
+ value.generation === generation.generation && value.width === comparison.width,
2062
+ );
2063
+ const exactScreen =
2064
+ JSON.stringify(snapshot?.rows) === JSON.stringify(cleanExact(comparison.rendered));
2065
+ const structuralRows = clean(comparison.rendered).filter((row) =>
2066
+ /^(?:│|┆|├─|└─)/.test(row.trimStart()),
2067
+ );
2068
+ const labels = structuralRows.slice(1);
2069
+ const expectedFirst = mode === "rail" ? "PTY label 1" : "PTY label 4";
2070
+ const expectedFinal = generation.active
2071
+ ? mode === "rail"
2072
+ ? "│ • PTY label 8"
2073
+ : "└─ • PTY label 8"
2074
+ : mode === "rail"
2075
+ ? "│ PTY label 8"
2076
+ : "└─ · PTY label 8";
2077
+ const predicates = {
2078
+ snapshotPresent: Boolean(snapshot),
2079
+ exactScreen,
2080
+ exactNativeLabels: comparison.exactNativeLabels === true,
2081
+ linkSemantics: comparison.linkSemantics === true,
2082
+ croppedWithEllipsis: comparison.croppedWithEllipsis === true,
2083
+ structuralRowCount: structuralRows.length === (mode === "rail" ? 9 : 6),
2084
+ oneTitle: structuralRows.filter((row) => row.includes("Thinking")).length === 1,
2085
+ labelCount: labels.length === (mode === "rail" ? 8 : 5),
2086
+ firstLabel: labels[0]?.includes(expectedFirst) === true,
2087
+ finalLabel: labels.at(-1)?.includes(expectedFinal) === true,
2088
+ nonemptyLabels: !labels.some((row) => row.trim().length === 0),
2089
+ };
2090
+ if (!Object.values(predicates).every(Boolean)) {
2091
+ generationFailure = {
2092
+ generation: generation.generation,
2093
+ width: comparison.width,
2094
+ linkCapability: comparison.linkCapability,
2095
+ predicates,
2096
+ structuralRows,
2097
+ actualLabels: comparison.actualLabels,
2098
+ expectedLabels: comparison.expectedLabels,
2099
+ snapshot: snapshot?.rows,
2100
+ };
2101
+ break;
2102
+ }
2103
+ }
2104
+ if (generationFailure) break;
2105
+ }
2106
+ const expectedThemeStates = [
2107
+ ["current", true],
2108
+ ["current", false],
2109
+ ["dark", true],
2110
+ ["dark", false],
2111
+ ["light", true],
2112
+ ["light", false],
2113
+ ];
2114
+ const hiddenExact = structural?.hidden80;
2115
+ const nativeHiddenExact = structural?.nativeHidden80;
2116
+ const hiddenMatchesNativeByteForByte =
2117
+ Array.isArray(hiddenExact) &&
2118
+ Array.isArray(nativeHiddenExact) &&
2119
+ JSON.stringify(hiddenExact) === JSON.stringify(nativeHiddenExact);
2120
+ const nativeHiddenHasThinkingLabel =
2121
+ Array.isArray(nativeHiddenExact) &&
2122
+ nativeHiddenExact.some((row) => row.includes("Thinking..."));
2123
+ const hiddenRows = clean(hiddenExact);
2124
+ const nativeHiddenRows = clean(nativeHiddenExact);
2125
+ const summaryPredicates = {
2126
+ probeRecordCount: modeProbes.length === 2,
2127
+ ready: structural?.ready === true,
2128
+ installed: structural?.installed === true,
2129
+ wrapperCallCount: structural?.wrapperCalls === 1,
2130
+ forwardedArgumentCount: modeCall?.count === (oldContract ? 1 : 2),
2131
+ forwardedMessageIdentity: modeCall?.messageIdentity === true,
2132
+ generationCount: generations.length === 6,
2133
+ snapshotCount: snapshots.length === 12,
2134
+ themeAndActivityOrder:
2135
+ JSON.stringify(generations.map(({ themeName, active }) => [themeName, active])) ===
2136
+ JSON.stringify(expectedThemeStates),
2137
+ generationsValid: !generationFailure,
2138
+ hiddenHasNoStructuralLabels: !hiddenRows.some((row) => row.includes("PTY label")),
2139
+ hiddenMatchesNativeByteForByte,
2140
+ nativeHiddenHasThinkingLabel,
2141
+ hiddenStatePreserved: structural?.hiddenStatePreserved === true,
2142
+ width20Rendered: completedStructural?.structuralWidths?.includes(20) === true,
2143
+ width80Rendered: completedStructural?.structuralWidths?.includes(80) === true,
2144
+ descriptorRestored:
2145
+ completedStructural?.restored === true &&
2146
+ completedStructural?.descriptorEvidence?.all === true &&
2147
+ Object.values(completedStructural?.descriptorEvidence?.fields ?? {}).every(Boolean),
2148
+ widgetRemoved:
2149
+ completedStructural?.widgetRemoved === true &&
2150
+ completedStructural?.widgetOwnershipCount === 0,
2151
+ };
2152
+ if (!Object.values(summaryPredicates).every(Boolean)) {
2153
+ console.error(
2154
+ `ZENTUI_STRUCTURAL_DIAGNOSTIC ${boundedJson({
2155
+ version: runtimeVersion,
2156
+ mode,
2157
+ summaryPredicates,
2158
+ generationFailure,
2159
+ state: {
2160
+ modeCall,
2161
+ themeStates: generations.map(({ themeName, active }) => [themeName, active]),
2162
+ snapshotKeys: snapshots.map(({ generation, width }) => [generation, width]),
2163
+ hiddenRows,
2164
+ nativeHiddenRows,
2165
+ structuralWidths: completedStructural?.structuralWidths,
2166
+ },
2167
+ })}`,
2168
+ );
2169
+ throw new Error(`Pi ${version} ${mode} structural probe failed; see diagnostic above`);
2170
+ }
2171
+ const screenArrays = snapshots.map(({ generation, width, rows }) => ({
2172
+ generation,
2173
+ width,
2174
+ rows,
2175
+ }));
2176
+ const linkCapabilities = [
2177
+ ...new Set(
2178
+ generations.flatMap((generation) =>
2179
+ generation.comparisons.map((comparison) => comparison.linkCapability),
2180
+ ),
2181
+ ),
2182
+ ];
2183
+ console.log(
2184
+ `${runtimeVersion} ${mode}: private-wrapper=exact args=${modeCall.count}:${String(modeCall.isStreaming)} contiguous-run=one-title labels=${mode === "rail" ? "all-8" : "latest-5"} exact-screen=${JSON.stringify(screenArrays)} ansi=current/dark/light:host-Markdown-exact+connector-accent-only links=${linkCapabilities.join("+")}:preserved width=20/80 ellipsis=yes active->settled=yes hidden=native-byte-exact:${JSON.stringify(hiddenExact)} label=Thinking... descriptor=${JSON.stringify(completedStructural.descriptorEvidence.fields)} process-group=gone`,
2185
+ );
2186
+ }
2187
+
2188
+ assertNoTransientArtifacts(version, "normal PTY cases", versionRoot, agentDir, home);
2189
+
2190
+ if (runtimeVersion === "0.84.4") {
2191
+ writeFileSync(
2192
+ join(agentDir, "zentui.json"),
2193
+ JSON.stringify({
2194
+ components: {
2195
+ thinkingSteps: { enabled: true, mode: "streaming" },
2196
+ editor: { enabled: false },
2197
+ userMessages: { enabled: false },
2198
+ workingLine: { enabled: false },
2199
+ selectorBorders: { enabled: false },
2200
+ footer: { style: "native" },
2201
+ },
2202
+ }),
2203
+ );
2204
+ const fullscreenProbePath = join(versionRoot, "probe-fullscreen.jsonl");
2205
+ const fullscreenSupervisorPath = join(versionRoot, "supervisor-fullscreen.json");
2206
+ const fullscreen = await runDriver(
2207
+ python,
2208
+ [...command, "--tui-mode", "fullscreen"],
2209
+ {
2210
+ ...environment,
2211
+ ZENTUI_PROBE_PATH: fullscreenProbePath,
2212
+ ZENTUI_SUPERVISOR_PATH: fullscreenSupervisorPath,
2213
+ },
2214
+ versionRoot,
2215
+ );
2216
+ const fullscreenPairs = fullscreen.transitionSequence?.map(({ from, to }) => [from, to]);
2217
+ const fullscreenProbes = readFileSync(fullscreenProbePath, "utf8")
2218
+ .trim()
2219
+ .split("\n")
2220
+ .map((line) => JSON.parse(line));
2221
+ const fullscreenShutdown = fullscreenProbes.at(-1);
2222
+ const fullscreenTransitionsValid = fullscreen.transitionSequence?.every((transition) => {
2223
+ const expected = transition.mode === "streaming" ? 1 : 0;
2224
+ const resources = transition.screen
2225
+ .split("\n")
2226
+ .map((row) => row.trim())
2227
+ .filter((row) => /^__ZENTUI_RESOURCES__ input=\d+ timer=\d+$/.test(row));
2228
+ const parsed = resources[0]?.match(/^__ZENTUI_RESOURCES__ input=(\d+) timer=(\d+)$/);
2229
+ const rows = extractSentinelRows(transition.screen, transitionStart, transitionEnd);
2230
+ const expectedRows =
2231
+ transition.mode === "tree"
2232
+ ? expectedTreeTransitionRows
2233
+ : transition.mode === "rail"
2234
+ ? expectedRailTransitionRows
2235
+ : undefined;
2236
+ return (
2237
+ transition.settingsScreen.split("\n").some((row) => row.trim() === transition.status) &&
2238
+ resources.length === 1 &&
2239
+ Number(parsed?.[1]) === expected &&
2240
+ Number(parsed?.[2]) === expected &&
2241
+ (expectedRows
2242
+ ? JSON.stringify(rows) === JSON.stringify(expectedRows)
2243
+ : rows.some((row) => /^ Thinking \d+\.\d+s {2}\(ctrl\+t to expand\)$/.test(row)))
2244
+ );
2245
+ });
2246
+ if (
2247
+ fullscreen.exit !== 0 ||
2248
+ fullscreen.groupAlive ||
2249
+ hasExtensionLoaderDiagnostic(fullscreen.raw) ||
2250
+ JSON.stringify(fullscreenPairs) !== JSON.stringify(expectedTransitionPairs) ||
2251
+ fullscreenTransitionsValid !== true ||
2252
+ fullscreenShutdown?.restored !== true ||
2253
+ fullscreenShutdown?.widgetRemoved !== true ||
2254
+ fullscreenShutdown?.widgetOwnershipCount !== 0 ||
2255
+ fullscreenShutdown?.descriptorEvidence?.all !== true ||
2256
+ !Object.values(fullscreenShutdown?.descriptorEvidence?.fields ?? {}).every(Boolean) ||
2257
+ JSON.stringify(
2258
+ fullscreen.folded
2259
+ .split("\n")
2260
+ .map((row) => row.trim())
2261
+ .filter((row) => /^__ZENTUI_RESOURCES__ input=\d+ timer=\d+$/.test(row)),
2262
+ ) !== JSON.stringify(["__ZENTUI_RESOURCES__ input=1 timer=1"])
2263
+ )
2264
+ throw new Error(
2265
+ `Pi 0.84.4 fullscreen live transition smoke failed: ${boundedJson({ fullscreenPairs, fullscreenTransitionsValid, fullscreenShutdown, exit: fullscreen.exit, groupAlive: fullscreen.groupAlive })}`,
2266
+ );
2267
+ writeFileSync(
2268
+ join(agentDir, "zentui.json"),
2269
+ JSON.stringify({
2270
+ components: {
2271
+ thinkingSteps: { enabled: true, mode: "streaming" },
2272
+ editor: { enabled: false },
2273
+ userMessages: { enabled: false },
2274
+ workingLine: { enabled: false },
2275
+ selectorBorders: { enabled: false },
2276
+ footer: { style: "native" },
2277
+ },
2278
+ }),
2279
+ );
2280
+ const fullscreenActiveProbePath = join(versionRoot, "probe-fullscreen-active-shutdown.jsonl");
2281
+ const fullscreenActiveSupervisorPath = join(
2282
+ versionRoot,
2283
+ "supervisor-fullscreen-active-shutdown.json",
2284
+ );
2285
+ const fullscreenActive = await runDriver(
2286
+ python,
2287
+ [...command, "--tui-mode", "fullscreen"],
2288
+ {
2289
+ ...environment,
2290
+ ZENTUI_PROBE_PATH: fullscreenActiveProbePath,
2291
+ ZENTUI_SUPERVISOR_PATH: fullscreenActiveSupervisorPath,
2292
+ ZENTUI_ACTIVE_STREAMING_SHUTDOWN: "1",
2293
+ },
2294
+ versionRoot,
2295
+ );
2296
+ const fullscreenActiveProbes = readFileSync(fullscreenActiveProbePath, "utf8")
2297
+ .trim()
2298
+ .split("\n")
2299
+ .map((line) => JSON.parse(line));
2300
+ const fullscreenActiveFinal = fullscreenActiveProbes.at(-1);
2301
+ if (
2302
+ fullscreenActive.exit !== 0 ||
2303
+ fullscreenActive.groupAlive ||
2304
+ hasExtensionLoaderDiagnostic(fullscreenActive.raw) ||
2305
+ (fullscreenActive.transitionSequence?.length ?? -1) !== 0 ||
2306
+ fullscreenActiveFinal?.controllerResources?.inputs !== 0 ||
2307
+ fullscreenActiveFinal?.controllerResources?.timers !== 0 ||
2308
+ fullscreenActiveFinal?.restored !== true ||
2309
+ fullscreenActiveFinal?.descriptorEvidence?.all !== true ||
2310
+ !Object.values(fullscreenActiveFinal?.descriptorEvidence?.fields ?? {}).every(Boolean) ||
2311
+ fullscreenActiveFinal?.widgetRemoved !== true ||
2312
+ fullscreenActiveFinal?.widgetOwnershipCount !== 0
2313
+ )
2314
+ throw new Error(
2315
+ `Pi 0.84.4 fullscreen active Streaming shutdown failed: ${boundedJson({ transitions: fullscreenActive.transitionSequence, fullscreenActiveFinal, exit: fullscreenActive.exit, groupAlive: fullscreenActive.groupAlive })}`,
2316
+ );
2317
+ assertNoTransientArtifacts(version, "fullscreen PTY cases", versionRoot, agentDir, home);
2318
+ console.log(
2319
+ `0.84.4 fullscreen: live=Streaming->Tree,Tree->Rail entering-Streaming=restart-only rows/status/resources=exact active-Streaming-EOF=1/1->${fullscreenActiveFinal.controllerResources.inputs}/${fullscreenActiveFinal.controllerResources.timers} descriptor=${JSON.stringify(fullscreenActiveFinal.descriptorEvidence.fields)} widget-ownership=0 process-group=gone artifacts=none`,
2320
+ );
2321
+ }
2322
+
2323
+ console.log(
2324
+ `${runtimeVersion}: measured=${JSON.stringify(attestation.versions)} live=Thinking sentinel-rows=${JSON.stringify(extractedLiveRows)} header-index=${extractedLiveHeaderIndexes[0]} native-tail=${JSON.stringify(expectedScreenTail)} exact=5/5 wrapped=row4-continuation+rows5-8 args=${call.count}:${String(call.isStreaming)} wrapperCalls=${live.wrapperCalls} identities=exact ordered-collisions=thinking2+text1/equal1+text1 contiguous=${runtimeVersion === "0.80.5" ? "legacy2" : "coalesced1"} tool-separated=2 binding=validated-ctrl+t descriptor=${JSON.stringify(probes.at(-1).descriptorEvidence.fields)} restored-completed=Thought folded=${JSON.stringify(foldedCompletedRows)} expanded=${JSON.stringify(expandedCompletedRows)} refolded=${JSON.stringify(refoldedCompletedRows)} live-transitions=Streaming->Tree,Tree->Rail entering-Streaming=restart-only states/resources=${JSON.stringify(result.transitionSequence.map(({ from, to, status, resources }) => ({ from, to, status, resources })))} tree=${JSON.stringify(treeTransitionRows)} rail=${JSON.stringify(railTransitionRows)} hidden-start=preserved widget=removed artifacts=none handshake=${result.supervisor.handshake} cleanup-signals=${JSON.stringify(result.supervisor.cleanupSignals ?? [])} python=${result.supervisor.pythonPid}:reaped pi=${result.supervisor.piPid}:reaped group=${result.supervisor.piPgid}:gone`,
2325
+ );
2326
+ }
2327
+ } finally {
2328
+ rmSync(workspace, { recursive: true, force: true });
2329
+ }