@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.
- package/CHANGELOG.md +9 -0
- package/dist/extensions/pi-subagents/CHANGELOG.md +77 -1
- package/dist/extensions/pi-subagents/VISION.md +12 -1
- package/dist/extensions/pi-subagents/docs/agents.md +13 -6
- package/dist/extensions/pi-subagents/docs/configuration.md +35 -9
- package/dist/extensions/pi-subagents/docs/extension-api.md +1 -1
- package/dist/extensions/pi-subagents/docs/missions.md +1 -1
- package/dist/extensions/pi-subagents/docs/models.md +6 -6
- package/dist/extensions/pi-subagents/docs/observability.md +6 -3
- package/dist/extensions/pi-subagents/docs/tool-reference.md +3 -3
- package/dist/extensions/pi-subagents/docs/watchdog.md +92 -114
- package/dist/extensions/pi-subagents/docs/workflows.md +1 -1
- package/dist/extensions/pi-subagents/install.mjs +1 -2
- package/dist/extensions/pi-subagents/package.json +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +6 -6
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +4 -4
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/review-and-validation.md +1 -1
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +41 -4
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +3 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +120 -124
- package/dist/extensions/pi-subagents/src/agents/runtime-agent-registry.ts +5 -1
- package/dist/extensions/pi-subagents/src/api/preflight.ts +4 -0
- package/dist/extensions/pi-subagents/src/api/shared-types.ts +3 -0
- package/dist/extensions/pi-subagents/src/extension/config.ts +20 -0
- package/dist/extensions/pi-subagents/src/extension/public-execution.ts +1 -0
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +6 -2
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +1 -1
- package/dist/extensions/pi-subagents/src/inspectors/herdr/inspector-runner.ts +19 -13
- package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +26 -8
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +103 -23
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +6 -2
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +18 -2
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +54 -3
- package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +16 -0
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +22 -2
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +63 -6
- package/dist/extensions/pi-subagents/src/runs/background/steering.ts +4 -1
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +69 -12
- package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +13 -0
- package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +3 -1
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +21 -8
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +59 -6
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +95 -18
- package/dist/extensions/pi-subagents/src/runs/shared/async-status-projection.ts +11 -43
- package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +1 -0
- package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/shared/lane-metadata.ts +24 -3
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +4 -0
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +2 -6
- package/dist/extensions/pi-subagents/src/runs/shared/permissions.ts +1 -0
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +33 -18
- package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +73 -37
- package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +33 -6
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +4 -2
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +20 -3
- package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +21 -7
- package/dist/extensions/pi-subagents/src/runs/shared/tool-timeout.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +467 -63
- package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +3 -1
- package/dist/extensions/pi-subagents/src/shared/fork-context.ts +0 -12
- package/dist/extensions/pi-subagents/src/shared/fork-session-cwd.ts +27 -0
- package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +3 -0
- package/dist/extensions/pi-subagents/src/shared/types.ts +42 -4
- package/dist/extensions/pi-subagents/src/shared/utils.ts +18 -7
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +1 -1
- package/dist/extensions/pi-subagents/src/slash/subagents-admin.ts +26 -12
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +61 -2
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +12 -7
- package/dist/extensions/pi-subagents/src/tui/render.ts +227 -14
- package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +56 -34
- package/dist/extensions/pi-subagents/src/watchdog/diff-tool.ts +77 -0
- package/dist/extensions/pi-subagents/src/watchdog/emission-guard.ts +5 -3
- package/dist/extensions/pi-subagents/src/watchdog/guidance.ts +20 -0
- package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +16 -13
- package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +10 -9
- package/dist/extensions/pi-subagents/src/watchdog/render.ts +4 -5
- package/dist/extensions/pi-subagents/src/watchdog/review.ts +15 -4
- package/dist/extensions/pi-subagents/src/watchdog/rules.ts +70 -0
- package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +75 -92
- package/dist/extensions/pi-subagents/src/watchdog/scope.ts +2 -1
- package/dist/extensions/pi-subagents/src/watchdog/settings.ts +48 -104
- package/dist/extensions/pi-subagents/src/watchdog/types.ts +18 -32
- package/dist/extensions/pi-subagents/src/watchdog/warning-format.ts +0 -1
- package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +3 -2
- package/dist/extensions/pi-subagents/src/workflows/workflow-checklist.ts +439 -0
- package/dist/extensions/pi-subagents/src/workflows/workflow-preflight.ts +28 -1
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +335 -21
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +4 -1
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +22 -18
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +0 -1
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +21 -18
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +307 -22
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/support/helpers.ts +25 -0
- package/dist/extensions/pi-subagents/test/support/isolated-temp-root.mjs +15 -4
- package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +17 -2
- package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +96 -15
- package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +53 -5
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +20 -0
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +221 -8
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +45 -24
- package/dist/extensions/pi-subagents/test/unit/agent-scan-dirs.test.ts +133 -0
- package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +28 -2
- package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +2 -0
- package/dist/extensions/pi-subagents/test/unit/async-retention.test.ts +4 -1
- package/dist/extensions/pi-subagents/test/unit/async-status-projection.test.ts +18 -4
- package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +16 -0
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +38 -0
- package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +21 -0
- package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +19 -6
- package/dist/extensions/pi-subagents/test/unit/get-final-output.test.ts +26 -0
- package/dist/extensions/pi-subagents/test/unit/herdr-inspector.test.ts +22 -1
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +2 -6
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +66 -1
- package/dist/extensions/pi-subagents/test/unit/pi-args-permission-system.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +116 -10
- package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +136 -18
- package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +20 -0
- package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +22 -0
- package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/unit/project-panes-public-api.test.ts +8 -0
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +127 -48
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +68 -0
- package/dist/extensions/pi-subagents/test/unit/runtime-agent-registration.test.ts +17 -0
- package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +88 -0
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +4 -2
- package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +9 -9
- package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/steering.test.ts +9 -3
- package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +22 -9
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +91 -8
- package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +27 -0
- package/dist/extensions/pi-subagents/test/unit/temp-paths.test.ts +50 -0
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +0 -1
- package/dist/extensions/pi-subagents/test/unit/tool-timeout.test.ts +2 -2
- package/dist/extensions/pi-subagents/test/unit/wait-completions.test.ts +34 -0
- package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +1 -2
- package/dist/extensions/pi-subagents/test/unit/watchdog-child-status.test.ts +73 -13
- package/dist/extensions/pi-subagents/test/unit/watchdog-diff-tool.test.ts +79 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-permission-arbiter.test.ts +2 -4
- package/dist/extensions/pi-subagents/test/unit/watchdog-render.test.ts +7 -8
- package/dist/extensions/pi-subagents/test/unit/watchdog-review.test.ts +48 -3
- package/dist/extensions/pi-subagents/test/unit/watchdog-rules.test.ts +37 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +36 -118
- package/dist/extensions/pi-subagents/test/unit/watchdog-scope.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +37 -14
- package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +45 -26
- package/dist/extensions/pi-subagents/test/unit/workflow-checklist.test.ts +223 -0
- package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +50 -1
- package/dist/extensions/pi-subagents/test/unit/workflow-preflight.test.ts +22 -0
- package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +136 -4
- package/dist/extensions/pi-zentui/README.md +19 -5
- package/dist/extensions/pi-zentui/docs/configuration.md +37 -9
- package/dist/extensions/pi-zentui/extensions/zentui/config.ts +29 -0
- package/dist/extensions/pi-zentui/extensions/zentui/index.ts +31 -2
- package/dist/extensions/pi-zentui/extensions/zentui/prototype-patch-registry.ts +55 -11
- package/dist/extensions/pi-zentui/extensions/zentui/settings-command.ts +115 -3
- package/dist/extensions/pi-zentui/extensions/zentui/settings-previews.ts +119 -2
- package/dist/extensions/pi-zentui/extensions/zentui/thinking-experimental.ts +1768 -0
- package/dist/extensions/pi-zentui/extensions/zentui/thinking-status.ts +69 -0
- package/dist/extensions/pi-zentui/extensions/zentui/thinking-steps.ts +155 -0
- package/dist/extensions/pi-zentui/extensions/zentui/ui.ts +2 -1
- package/dist/extensions/pi-zentui/extensions/zentui/user-message-osc.ts +16 -2
- package/dist/extensions/pi-zentui/extensions/zentui/user-message-styles.ts +1 -1
- package/dist/extensions/pi-zentui/test/config-load-lifecycle.test.ts +124 -0
- package/dist/extensions/pi-zentui/test/config.test.ts +82 -2
- package/dist/extensions/pi-zentui/test/editor-viewport-indicators.test.ts +19 -4
- package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +58 -9
- package/dist/extensions/pi-zentui/test/package-contents.mjs +33 -0
- package/dist/extensions/pi-zentui/test/prototype-patch-registry.test.ts +131 -0
- package/dist/extensions/pi-zentui/test/responsive-dependencies.test.ts +4 -4
- package/dist/extensions/pi-zentui/test/settings-command.test.ts +289 -7
- package/dist/extensions/pi-zentui/test/settings-previews.test.ts +106 -0
- package/dist/extensions/pi-zentui/test/thinking-experimental-docs.test.ts +51 -0
- package/dist/extensions/pi-zentui/test/thinking-experimental-loader-diagnostics.mjs +54 -0
- package/dist/extensions/pi-zentui/test/thinking-experimental-missing-export.test.ts +44 -0
- package/dist/extensions/pi-zentui/test/thinking-experimental-rows.test.ts +297 -0
- package/dist/extensions/pi-zentui/test/thinking-experimental-tui.mjs +2329 -0
- package/dist/extensions/pi-zentui/test/thinking-experimental.test.ts +1668 -0
- package/dist/extensions/pi-zentui/test/thinking-status.test.ts +85 -0
- package/dist/extensions/pi-zentui/test/thinking-steps.test.ts +122 -0
- package/dist/extensions/pi-zentui/test/user-message-osc.test.ts +58 -0
- package/dist/skills/brandkit/SKILL.md +0 -2
- package/dist/skills/{industrial-brutalist-ui → brutalist}/SKILL.md +1 -3
- package/dist/skills/gpt-taste/SKILL.md +0 -2
- package/dist/skills/image-to-code/SKILL.md +0 -2
- package/dist/skills/imagegen-frontend-mobile/SKILL.md +0 -2
- package/dist/skills/imagegen-frontend-web/SKILL.md +0 -2
- package/dist/skills/{minimalist-ui → minimalist}/SKILL.md +1 -3
- package/dist/skills/{full-output-enforcement → output}/SKILL.md +1 -3
- package/dist/skills/{redesign-existing-projects → redesign}/SKILL.md +1 -3
- package/dist/skills/{high-end-visual-design → soft}/SKILL.md +1 -3
- package/dist/skills/stitch/DESIGN.md +121 -0
- package/dist/skills/{stitch-design-taste → stitch}/SKILL.md +1 -3
- package/dist/skills/{design-taste-frontend → taste}/SKILL.md +1 -3
- package/dist/skills/{design-taste-frontend-v1 → taste-v1}/SKILL.md +2 -4
- 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
|
+
}
|