@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,69 @@
|
|
|
1
|
+
import type { ThinkingStepsComponentConfig, ThinkingStepsMode } from "./config";
|
|
2
|
+
|
|
3
|
+
export type ThinkingStatusState = Readonly<{
|
|
4
|
+
active: boolean;
|
|
5
|
+
activeMode?: ThinkingStepsMode;
|
|
6
|
+
restartRequired: boolean;
|
|
7
|
+
/** Whole private renderer capability, independent of mode-specific resources. */
|
|
8
|
+
rendererAvailable?: boolean;
|
|
9
|
+
/** Streaming input/timer capability for this session. */
|
|
10
|
+
streamingAvailable?: boolean;
|
|
11
|
+
/** Irreversible session-local acquisition/callback poison. */
|
|
12
|
+
streamingPoisoned?: boolean;
|
|
13
|
+
/** Legacy whole-renderer capability accepted by settings adapters. */
|
|
14
|
+
available?: boolean;
|
|
15
|
+
reason?: string;
|
|
16
|
+
}>;
|
|
17
|
+
|
|
18
|
+
export type ThinkingStatusLabels = Readonly<{
|
|
19
|
+
saved: ThinkingStepsMode | "disabled";
|
|
20
|
+
active: ThinkingStepsMode | "native";
|
|
21
|
+
rendererUnavailable: boolean;
|
|
22
|
+
streamingUnavailable: boolean;
|
|
23
|
+
streamingPoisoned: boolean;
|
|
24
|
+
restartRequired: boolean;
|
|
25
|
+
reason?: string;
|
|
26
|
+
}>;
|
|
27
|
+
|
|
28
|
+
function cleanReason(value: string | undefined): string | undefined {
|
|
29
|
+
const reason = value
|
|
30
|
+
?.replace(/(?:[;,·]\s*)?restart required\b[.!]?/gi, "")
|
|
31
|
+
.replace(/^restart pi to apply(?: saved thinking changes)?[.!]?$/i, "")
|
|
32
|
+
.replace(/^[\s;,.·]+|[\s;,.·]+$/g, "")
|
|
33
|
+
.trim();
|
|
34
|
+
if (!reason || /^(?:renderer|streaming) unavailable$/i.test(reason)) return undefined;
|
|
35
|
+
return reason;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Builds independent status facts once so every consumer reports the same state. */
|
|
39
|
+
export function thinkingStatusLabels(
|
|
40
|
+
config: ThinkingStepsComponentConfig,
|
|
41
|
+
state: ThinkingStatusState,
|
|
42
|
+
): ThinkingStatusLabels {
|
|
43
|
+
const rendererAvailable = state.rendererAvailable ?? state.available ?? true;
|
|
44
|
+
const reason = cleanReason(state.reason);
|
|
45
|
+
return {
|
|
46
|
+
saved: config.enabled ? config.mode : "disabled",
|
|
47
|
+
active: state.active && state.activeMode ? state.activeMode : "native",
|
|
48
|
+
rendererUnavailable: !rendererAvailable,
|
|
49
|
+
streamingUnavailable: state.streamingAvailable === false || state.streamingPoisoned === true,
|
|
50
|
+
streamingPoisoned: state.streamingPoisoned === true,
|
|
51
|
+
restartRequired: state.restartRequired,
|
|
52
|
+
...(reason ? { reason } : {}),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const modeLabel = (mode: ThinkingStepsMode | "disabled" | "native"): string =>
|
|
57
|
+
`${mode.charAt(0).toUpperCase()}${mode.slice(1)}`;
|
|
58
|
+
|
|
59
|
+
/** Formats the canonical complete status line used by settings, previews, and apply failures. */
|
|
60
|
+
export function formatThinkingStatus(facts: ThinkingStatusLabels): string {
|
|
61
|
+
return [
|
|
62
|
+
`Saved: ${modeLabel(facts.saved)}`,
|
|
63
|
+
`Active: ${modeLabel(facts.active)}`,
|
|
64
|
+
...(facts.rendererUnavailable ? ["Renderer unavailable"] : []),
|
|
65
|
+
...(facts.streamingUnavailable ? ["Streaming unavailable"] : []),
|
|
66
|
+
...(facts.restartRequired ? ["restart required"] : []),
|
|
67
|
+
...(facts.reason ? [facts.reason] : []),
|
|
68
|
+
].join(" · ");
|
|
69
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { sanitizeSgrOnlySourceText } from "./user-message-osc";
|
|
2
|
+
|
|
3
|
+
/** Hard limits keep private transcript parsing synchronous and bounded. Limits are inclusive. */
|
|
4
|
+
export const THINKING_STEPS_MAX_INPUT_LENGTH = 65_536;
|
|
5
|
+
export const THINKING_STEPS_MAX_STEPS = 128;
|
|
6
|
+
export const THINKING_STEPS_MAX_LABEL_LENGTH = 512;
|
|
7
|
+
|
|
8
|
+
export type ThinkingStep = Readonly<{
|
|
9
|
+
number: number;
|
|
10
|
+
label: string;
|
|
11
|
+
body: string;
|
|
12
|
+
}>;
|
|
13
|
+
|
|
14
|
+
type MutableStep = {
|
|
15
|
+
number: number;
|
|
16
|
+
label: string;
|
|
17
|
+
bodyLines: string[];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type StructuralLabel = { label: string };
|
|
21
|
+
type OpaqueBlock = { end: number; malformed: boolean };
|
|
22
|
+
|
|
23
|
+
const meaningfulLabelPattern = /[\p{L}\p{N}\p{Extended_Pictographic}]/u;
|
|
24
|
+
|
|
25
|
+
function headingLabel(line: string): StructuralLabel | undefined {
|
|
26
|
+
const match = /^(#{1,6})[\t ]+(.+?)\s*$/.exec(line);
|
|
27
|
+
if (!match) return undefined;
|
|
28
|
+
const label = (match[2] ?? "").replace(/[\t ]+#+[\t ]*$/, "").trim();
|
|
29
|
+
return label ? { label } : undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function listLabel(line: string): StructuralLabel | undefined {
|
|
33
|
+
const match = /^(?:[-+*]|\d{1,9}[.)])[\t ]+(.+?)\s*$/.exec(line);
|
|
34
|
+
const label = match?.[1]?.trim() ?? "";
|
|
35
|
+
return label ? { label } : undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function structuralLabel(line: string): StructuralLabel | undefined {
|
|
39
|
+
return headingLabel(line) ?? listLabel(line);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function fenceBlock(lines: readonly string[], start: number): OpaqueBlock | undefined {
|
|
43
|
+
const opening = /^ {0,3}(`{3,}|~{3,})([^\r\n]*)$/.exec(lines[start] ?? "");
|
|
44
|
+
if (!opening) return undefined;
|
|
45
|
+
const marker = opening[1] ?? "";
|
|
46
|
+
const info = opening[2] ?? "";
|
|
47
|
+
if (marker[0] === "`" && info.includes("`")) return { end: start, malformed: true };
|
|
48
|
+
const closing = new RegExp(`^ {0,3}${marker[0]}{${marker.length},}[\\t ]*$`);
|
|
49
|
+
for (let index = start + 1; index < lines.length; index += 1) {
|
|
50
|
+
if (closing.test(lines[index] ?? "")) return { end: index, malformed: false };
|
|
51
|
+
}
|
|
52
|
+
return { end: lines.length - 1, malformed: true };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function mathBlock(lines: readonly string[], start: number): OpaqueBlock | undefined {
|
|
56
|
+
const line = lines[start] ?? "";
|
|
57
|
+
if (/^ {0,3}\$\$.+\$\$[\t ]*$/.test(line)) return { end: start, malformed: false };
|
|
58
|
+
const close = /^ {0,3}\$\$[\t ]*$/.test(line)
|
|
59
|
+
? /^(?: {0,3})\$\$[\t ]*$/
|
|
60
|
+
: /^ {0,3}\\\[[\t ]*$/.test(line)
|
|
61
|
+
? /^(?: {0,3})\\\][\t ]*$/
|
|
62
|
+
: undefined;
|
|
63
|
+
if (!close) return undefined;
|
|
64
|
+
for (let index = start + 1; index < lines.length; index += 1) {
|
|
65
|
+
if (close.test(lines[index] ?? "")) return { end: index, malformed: false };
|
|
66
|
+
}
|
|
67
|
+
return { end: lines.length - 1, malformed: true };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function isTopLevelProse(line: string): boolean {
|
|
71
|
+
if (!line.trim() || /^[\t ]/.test(line)) return false;
|
|
72
|
+
if (/^(?: {0,3})(?:`{3,}|~{3,}|\$\$|\\\[|>)/.test(line)) return false;
|
|
73
|
+
return !/^(?:#{1,6}(?:[\t ]|$)|[-+*](?:[\t ]|$)|\d{1,9}[.)](?:[\t ]|$))/.test(line);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function safeLabel(label: string): string | undefined {
|
|
77
|
+
const trimmed = label.trim();
|
|
78
|
+
if (
|
|
79
|
+
!trimmed ||
|
|
80
|
+
trimmed.length > THINKING_STEPS_MAX_LABEL_LENGTH ||
|
|
81
|
+
!meaningfulLabelPattern.test(trimmed) ||
|
|
82
|
+
trimmed.includes("\t")
|
|
83
|
+
) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
return trimmed;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function finishSteps(steps: MutableStep[]): ThinkingStep[] | undefined {
|
|
90
|
+
const finished: ThinkingStep[] = [];
|
|
91
|
+
for (const step of steps) {
|
|
92
|
+
const label = safeLabel(step.label);
|
|
93
|
+
if (!label) return undefined;
|
|
94
|
+
let first = 0;
|
|
95
|
+
let last = step.bodyLines.length;
|
|
96
|
+
while (first < last && !(step.bodyLines[first] ?? "").trim()) first += 1;
|
|
97
|
+
while (last > first && !(step.bodyLines[last - 1] ?? "").trim()) last -= 1;
|
|
98
|
+
finished.push(
|
|
99
|
+
Object.freeze({
|
|
100
|
+
number: step.number,
|
|
101
|
+
label,
|
|
102
|
+
body: step.bodyLines.slice(first, last).join("\n"),
|
|
103
|
+
}),
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
return finished;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Parse source-level structure; fenced and display-math blocks remain opaque bodies. */
|
|
110
|
+
export function parseThinkingSteps(markdown: string): readonly ThinkingStep[] | undefined {
|
|
111
|
+
if (!markdown || markdown.length > THINKING_STEPS_MAX_INPUT_LENGTH) return undefined;
|
|
112
|
+
const source = sanitizeSgrOnlySourceText(markdown.replace(/\r\n/g, "\n"));
|
|
113
|
+
if (source === undefined || !source.trim()) return undefined;
|
|
114
|
+
|
|
115
|
+
const lines = source.split("\n");
|
|
116
|
+
const steps: MutableStep[] = [];
|
|
117
|
+
let current: MutableStep | undefined;
|
|
118
|
+
let paragraphBoundary = true;
|
|
119
|
+
const startStep = (label: string) => {
|
|
120
|
+
if (steps.length >= THINKING_STEPS_MAX_STEPS) return false;
|
|
121
|
+
current = { number: steps.length + 1, label, bodyLines: [] };
|
|
122
|
+
steps.push(current);
|
|
123
|
+
paragraphBoundary = false;
|
|
124
|
+
return true;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
128
|
+
const line = lines[index] ?? "";
|
|
129
|
+
if (!line.trim()) {
|
|
130
|
+
if (current) current.bodyLines.push(line);
|
|
131
|
+
paragraphBoundary = true;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const opaque = fenceBlock(lines, index) ?? mathBlock(lines, index);
|
|
135
|
+
if (opaque) {
|
|
136
|
+
if (opaque.malformed || !current) return undefined;
|
|
137
|
+
current.bodyLines.push(...lines.slice(index, opaque.end + 1));
|
|
138
|
+
index = opaque.end;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const structural = structuralLabel(line);
|
|
142
|
+
if (structural) {
|
|
143
|
+
if (!startStep(structural.label)) return undefined;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (isTopLevelProse(line) && (!current || paragraphBoundary)) {
|
|
147
|
+
if (!startStep(line.trim())) return undefined;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (!current) return undefined;
|
|
151
|
+
current.bodyLines.push(line);
|
|
152
|
+
}
|
|
153
|
+
if (steps.length === 0) return undefined;
|
|
154
|
+
return finishSteps(steps);
|
|
155
|
+
}
|
|
@@ -602,7 +602,7 @@ function renderAccentRailFrameFromBase({
|
|
|
602
602
|
above: parsedTop?.count,
|
|
603
603
|
below: parsedBottom?.count,
|
|
604
604
|
};
|
|
605
|
-
const
|
|
605
|
+
const renderedLines = renderAccentRailEditorFrame({
|
|
606
606
|
width,
|
|
607
607
|
editorLines,
|
|
608
608
|
autocompleteLines,
|
|
@@ -610,6 +610,7 @@ function renderAccentRailFrameFromBase({
|
|
|
610
610
|
uiTheme,
|
|
611
611
|
config,
|
|
612
612
|
});
|
|
613
|
+
const lines = renderedLines.length === 1 ? ["", ...renderedLines, ""] : renderedLines;
|
|
613
614
|
POLISHED_FRAME_SPLITS.set(lines, {
|
|
614
615
|
rows: Object.freeze([...lines]),
|
|
615
616
|
split: { editorLines, trailingLines: autocompleteLines, viewport },
|
|
@@ -221,15 +221,29 @@ function filterBalancedUserMessageTerminalText(text: string, options: FilterOpti
|
|
|
221
221
|
return filterUserMessageTerminalText(text, { ...options, preserveValidOsc8: false }).text;
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
const sourceFilterOptions = {
|
|
225
|
+
preserveValidOsc8: false,
|
|
226
|
+
preserveSourceWhitespace: true,
|
|
227
|
+
} as const;
|
|
228
|
+
|
|
224
229
|
/** Strip every terminal control from untrusted source text before Markdown rendering. */
|
|
225
230
|
export function sanitizeUserMessageSourceText(text: string): string {
|
|
226
231
|
return filterBalancedUserMessageTerminalText(text, {
|
|
227
|
-
|
|
228
|
-
preserveSourceWhitespace: true,
|
|
232
|
+
...sourceFilterOptions,
|
|
229
233
|
preserveSgr: false,
|
|
230
234
|
});
|
|
231
235
|
}
|
|
232
236
|
|
|
237
|
+
/** Strip strict 7-bit CSI SGR, rejecting source that contains any other terminal control. */
|
|
238
|
+
export function sanitizeSgrOnlySourceText(text: string): string | undefined {
|
|
239
|
+
const sgrPreserved = filterBalancedUserMessageTerminalText(text, {
|
|
240
|
+
...sourceFilterOptions,
|
|
241
|
+
preserveSgr: true,
|
|
242
|
+
});
|
|
243
|
+
if (sgrPreserved !== text) return undefined;
|
|
244
|
+
return sanitizeUserMessageSourceText(text);
|
|
245
|
+
}
|
|
246
|
+
|
|
233
247
|
/**
|
|
234
248
|
* Sanitize a predecessor-rendered row. Pi formatting needs only SGR and
|
|
235
249
|
* structurally valid 7-bit OSC 8; every other terminal control is removed.
|
|
@@ -24,7 +24,7 @@ function themeFg(theme: Theme | undefined, color: ThemeColor, text: string): str
|
|
|
24
24
|
return theme ? theme.fg(color, text) : text;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
function makeMarkdownTheme(theme: Theme | undefined): MarkdownTheme {
|
|
27
|
+
export function makeMarkdownTheme(theme: Theme | undefined): MarkdownTheme {
|
|
28
28
|
return {
|
|
29
29
|
heading: (text) => themeFg(theme, "mdHeading", text),
|
|
30
30
|
link: (text) => themeFg(theme, "mdLink", text),
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { AssistantMessageComponent, initTheme } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { describe, expect, it, vi } from "vitest";
|
|
4
|
+
import type { PolishedTuiConfig } from "../extensions/zentui/config";
|
|
5
|
+
|
|
6
|
+
const mocks = vi.hoisted(() => ({
|
|
7
|
+
config: undefined as unknown as PolishedTuiConfig,
|
|
8
|
+
loadConfig: vi.fn(() => mocks.config),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
vi.mock("../extensions/zentui/config", async (importOriginal) => {
|
|
12
|
+
const actual = await importOriginal<typeof import("../extensions/zentui/config")>();
|
|
13
|
+
return { ...actual, loadConfig: mocks.loadConfig };
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
import { defaultConfig } from "../extensions/zentui/config";
|
|
17
|
+
import zentui from "../extensions/zentui/index";
|
|
18
|
+
|
|
19
|
+
initTheme("dark", false);
|
|
20
|
+
|
|
21
|
+
type Handler = (event: unknown, ctx: unknown) => unknown | Promise<unknown>;
|
|
22
|
+
type Command = { handler(args: string, ctx: unknown): Promise<void> };
|
|
23
|
+
type SettingsComponent = { render(width: number): string[]; handleInput(data: string): void };
|
|
24
|
+
|
|
25
|
+
function config(enabled: boolean): PolishedTuiConfig {
|
|
26
|
+
const value = structuredClone(defaultConfig);
|
|
27
|
+
value.projectRefreshIntervalMs = 0;
|
|
28
|
+
value.components.editor.enabled = false;
|
|
29
|
+
value.components.userMessages.enabled = false;
|
|
30
|
+
value.components.selectorBorders.enabled = false;
|
|
31
|
+
value.components.footer.style = "native";
|
|
32
|
+
value.components.workingLine.enabled = false;
|
|
33
|
+
value.components.thinkingSteps = { enabled, mode: "rail" };
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function theme(): Theme {
|
|
38
|
+
return {
|
|
39
|
+
fg: (_color: string, text: string) => text,
|
|
40
|
+
bold: (text: string) => text,
|
|
41
|
+
italic: (text: string) => text,
|
|
42
|
+
underline: (text: string) => text,
|
|
43
|
+
strikethrough: (text: string) => text,
|
|
44
|
+
getThinkingBorderColor: () => (text: string) => text,
|
|
45
|
+
} as unknown as Theme;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function context() {
|
|
49
|
+
let editor: unknown;
|
|
50
|
+
let settings: SettingsComponent | undefined;
|
|
51
|
+
const ctx = {
|
|
52
|
+
mode: "tui",
|
|
53
|
+
hasUI: true,
|
|
54
|
+
cwd: process.cwd(),
|
|
55
|
+
ui: {
|
|
56
|
+
theme: theme(),
|
|
57
|
+
setFooter() {},
|
|
58
|
+
setEditorComponent(value: unknown) {
|
|
59
|
+
editor = value;
|
|
60
|
+
},
|
|
61
|
+
getEditorComponent: () => editor,
|
|
62
|
+
getEditorText: () => "",
|
|
63
|
+
setEditorText() {},
|
|
64
|
+
onTerminalInput: () => () => {},
|
|
65
|
+
notify() {},
|
|
66
|
+
async custom(factory: (...args: unknown[]) => unknown) {
|
|
67
|
+
settings = factory({ requestRender() {} }, theme(), {}, () => {}) as SettingsComponent;
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
sessionManager: {
|
|
71
|
+
getBranch: () => [],
|
|
72
|
+
getEntries: () => [],
|
|
73
|
+
getSessionName: () => undefined,
|
|
74
|
+
},
|
|
75
|
+
getContextUsage: () => undefined,
|
|
76
|
+
};
|
|
77
|
+
return { ctx, settings: () => settings };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function emit(handlers: Map<string, Handler[]>, event: string, ctx: unknown) {
|
|
81
|
+
for (const handler of handlers.get(event) ?? []) await handler({}, ctx);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
describe("session config loading", () => {
|
|
85
|
+
it("does not read at registration, loads exactly once per start, and observes external changes", async () => {
|
|
86
|
+
mocks.loadConfig.mockClear();
|
|
87
|
+
mocks.config = config(false);
|
|
88
|
+
const handlers = new Map<string, Handler[]>();
|
|
89
|
+
let command: Command | undefined;
|
|
90
|
+
zentui({
|
|
91
|
+
on(event: string, handler: Handler) {
|
|
92
|
+
handlers.set(event, [...(handlers.get(event) ?? []), handler]);
|
|
93
|
+
},
|
|
94
|
+
registerCommand(name: string, value: Command) {
|
|
95
|
+
if (name === "zentui") command = value;
|
|
96
|
+
},
|
|
97
|
+
registerEntryRenderer() {},
|
|
98
|
+
getThinkingLevel: () => "off",
|
|
99
|
+
} as never);
|
|
100
|
+
expect(mocks.loadConfig).not.toHaveBeenCalled();
|
|
101
|
+
|
|
102
|
+
const harness = context();
|
|
103
|
+
const native = AssistantMessageComponent.prototype.updateContent;
|
|
104
|
+
await emit(handlers, "session_start", harness.ctx);
|
|
105
|
+
expect(mocks.loadConfig).toHaveBeenCalledTimes(1);
|
|
106
|
+
expect(AssistantMessageComponent.prototype.updateContent).toBe(native);
|
|
107
|
+
await emit(handlers, "session_shutdown", harness.ctx);
|
|
108
|
+
|
|
109
|
+
mocks.config = config(true);
|
|
110
|
+
await emit(handlers, "session_start", harness.ctx);
|
|
111
|
+
expect(mocks.loadConfig).toHaveBeenCalledTimes(2);
|
|
112
|
+
expect(AssistantMessageComponent.prototype.updateContent).not.toBe(native);
|
|
113
|
+
expect(command).toBeDefined();
|
|
114
|
+
await command?.handler("", harness.ctx);
|
|
115
|
+
const settingsComponent = harness.settings();
|
|
116
|
+
for (let index = 0; index < 3; index += 1) settingsComponent?.handleInput("\t");
|
|
117
|
+
const settings = settingsComponent?.render(160).join("\n") ?? "";
|
|
118
|
+
expect(settings).toContain("Thinking (Experimental)");
|
|
119
|
+
expect(settings).toMatch(/Enabled.*enabled/);
|
|
120
|
+
expect(settings).toMatch(/Mode.*Rail/);
|
|
121
|
+
await emit(handlers, "session_shutdown", harness.ctx);
|
|
122
|
+
expect(AssistantMessageComponent.prototype.updateContent).toBe(native);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -52,6 +52,7 @@ import {
|
|
|
52
52
|
saveSelectorBordersComponentPatch,
|
|
53
53
|
saveSeparatorPatch,
|
|
54
54
|
saveStarshipFooterStylePatch,
|
|
55
|
+
saveThinkingStepsComponentPatch,
|
|
55
56
|
saveUiFeaturesPatch,
|
|
56
57
|
saveUserMessagesComponentPatch,
|
|
57
58
|
saveWorkingLineComponentPatch,
|
|
@@ -132,6 +133,7 @@ describe("canonical config resolution", () => {
|
|
|
132
133
|
colorSource: "theme",
|
|
133
134
|
styles: { framed: {}, "framed-copy-friendly": {}, compact: {}, labeled: {} },
|
|
134
135
|
},
|
|
136
|
+
thinkingSteps: { enabled: false, mode: "tree" },
|
|
135
137
|
workingLine: {
|
|
136
138
|
enabled: true,
|
|
137
139
|
turnSummary: true,
|
|
@@ -569,6 +571,84 @@ describe("canonical config resolution", () => {
|
|
|
569
571
|
});
|
|
570
572
|
});
|
|
571
573
|
|
|
574
|
+
describe("thinking-steps config", () => {
|
|
575
|
+
it("defaults independently and normalizes invalid canonical leaves", () => {
|
|
576
|
+
const first = mergeConfig({});
|
|
577
|
+
const second = mergeConfig({});
|
|
578
|
+
expect(first.components.thinkingSteps).toEqual({ enabled: false, mode: "tree" });
|
|
579
|
+
expect(first.components.thinkingSteps).not.toBe(second.components.thinkingSteps);
|
|
580
|
+
expect(
|
|
581
|
+
mergeConfig({ components: { thinkingSteps: { enabled: true, mode: "rail" } } }).components
|
|
582
|
+
.thinkingSteps,
|
|
583
|
+
).toEqual({ enabled: true, mode: "rail" });
|
|
584
|
+
expect(
|
|
585
|
+
mergeConfig({
|
|
586
|
+
components: {
|
|
587
|
+
thinkingSteps: { enabled: true, mode: "streaming" },
|
|
588
|
+
},
|
|
589
|
+
}).components.thinkingSteps,
|
|
590
|
+
).toEqual({ enabled: true, mode: "streaming" });
|
|
591
|
+
expect(
|
|
592
|
+
mergeConfig({
|
|
593
|
+
components: {
|
|
594
|
+
thinkingSteps: { enabled: true, mode: "streaming-experimental" },
|
|
595
|
+
},
|
|
596
|
+
}).components.thinkingSteps,
|
|
597
|
+
).toEqual({ enabled: true, mode: "streaming" });
|
|
598
|
+
for (const mode of [undefined, "native", "Rail"]) {
|
|
599
|
+
expect(
|
|
600
|
+
mergeConfig({ components: { thinkingSteps: { enabled: "yes", mode } } }).components
|
|
601
|
+
.thinkingSteps,
|
|
602
|
+
).toEqual({ enabled: false, mode: "tree" });
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
it("persists atomic component patches and preserves unknown keys", () => {
|
|
607
|
+
withConfig(
|
|
608
|
+
{
|
|
609
|
+
futureTop: { keep: true },
|
|
610
|
+
components: { thinkingSteps: { future: { keep: true } } },
|
|
611
|
+
},
|
|
612
|
+
(path) => {
|
|
613
|
+
const saved = saveThinkingStepsComponentPatch({ enabled: true, mode: "rail" }, path);
|
|
614
|
+
const raw = readRaw(path);
|
|
615
|
+
expect(saved.components.thinkingSteps).toEqual({ enabled: true, mode: "rail" });
|
|
616
|
+
expect(raw.components.thinkingSteps).toMatchObject({
|
|
617
|
+
enabled: true,
|
|
618
|
+
mode: "rail",
|
|
619
|
+
future: { keep: true },
|
|
620
|
+
});
|
|
621
|
+
expect(raw.futureTop).toEqual({ keep: true });
|
|
622
|
+
expect(configTempFiles(join(path, ".."))).toEqual([]);
|
|
623
|
+
},
|
|
624
|
+
);
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
it("normalizes the one-window Streaming migration alias on the next save", () => {
|
|
628
|
+
withConfig(
|
|
629
|
+
{ components: { thinkingSteps: { enabled: false, mode: "streaming-experimental" } } },
|
|
630
|
+
(path) => {
|
|
631
|
+
const saved = saveThinkingStepsComponentPatch({ enabled: true }, path);
|
|
632
|
+
expect(saved.components.thinkingSteps).toEqual({ enabled: true, mode: "streaming" });
|
|
633
|
+
expect(readRaw(path).components.thinkingSteps).toMatchObject({
|
|
634
|
+
enabled: true,
|
|
635
|
+
mode: "streaming",
|
|
636
|
+
});
|
|
637
|
+
},
|
|
638
|
+
);
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
it("refuses to overwrite corrupt files", () => {
|
|
642
|
+
withConfig(undefined, (path) => {
|
|
643
|
+
writeFileSync(path, "{broken");
|
|
644
|
+
expect(() => saveThinkingStepsComponentPatch({ enabled: true }, path)).toThrow(
|
|
645
|
+
/Refusing to save Zentui config/,
|
|
646
|
+
);
|
|
647
|
+
expect(readFileSync(path, "utf8")).toBe("{broken");
|
|
648
|
+
});
|
|
649
|
+
});
|
|
650
|
+
});
|
|
651
|
+
|
|
572
652
|
describe("working-line config", () => {
|
|
573
653
|
it("defaults to cloned custom presets and migrates legacy modes without losing values", () => {
|
|
574
654
|
const first = mergeConfig({}).components.workingLine.messages;
|
|
@@ -1387,6 +1467,7 @@ describe("compatibility saver recipes", () => {
|
|
|
1387
1467
|
"editor",
|
|
1388
1468
|
"footer",
|
|
1389
1469
|
"selectorBorders",
|
|
1470
|
+
"thinkingSteps",
|
|
1390
1471
|
"userMessages",
|
|
1391
1472
|
"workingLine",
|
|
1392
1473
|
]);
|
|
@@ -1758,8 +1839,7 @@ describe("mergeConfig", () => {
|
|
|
1758
1839
|
mergeConfig({
|
|
1759
1840
|
components: { footer: { styles: { starship: { pathDisplay: { mode: "repository" } } } } },
|
|
1760
1841
|
}).components.footer.styles.starship.pathDisplay,
|
|
1761
|
-
).toEqual({ mode: "repository", depth: 2 });
|
|
1762
|
-
expect(mergeConfig({ pathDisplay: { mode: "fish", depth: -3 } }).pathDisplay).toEqual({
|
|
1842
|
+
).toEqual({ mode: "repository", depth: 2 }); expect(mergeConfig({ pathDisplay: { mode: "fish", depth: -3 } }).pathDisplay).toEqual({
|
|
1763
1843
|
mode: "basename",
|
|
1764
1844
|
depth: 2,
|
|
1765
1845
|
});
|
|
@@ -976,7 +976,7 @@ describe("same-render autocomplete capture", () => {
|
|
|
976
976
|
});
|
|
977
977
|
|
|
978
978
|
describe("accent rail editor integration", () => {
|
|
979
|
-
it("decorates trusted wrapped input and autocomplete
|
|
979
|
+
it("decorates trusted wrapped input and preserves multi-row autocomplete output", () => {
|
|
980
980
|
const editor = wrapped(baseEditor({ autocomplete: ["one", "two"] }), {
|
|
981
981
|
style: "accent-rail",
|
|
982
982
|
});
|
|
@@ -987,6 +987,17 @@ describe("accent rail editor integration", () => {
|
|
|
987
987
|
expect(lines.slice(1).map((line) => line.trimEnd())).toEqual(["one", "two"]);
|
|
988
988
|
});
|
|
989
989
|
|
|
990
|
+
it("places one reserved row on each side of a one-row rail", () => {
|
|
991
|
+
const editor = wrapped(baseEditor({}), { style: "accent-rail" });
|
|
992
|
+
const lines = editor.render(24);
|
|
993
|
+
|
|
994
|
+
expect(lines).toHaveLength(3);
|
|
995
|
+
expect(lines[0]).toBe("");
|
|
996
|
+
expect(lines[1]).toMatch(/^▎ typed text/);
|
|
997
|
+
expect(lines[2]).toBe("");
|
|
998
|
+
expect([...lines, "footer"].slice(-3)).toEqual([lines[1], "", "footer"]);
|
|
999
|
+
});
|
|
1000
|
+
|
|
990
1001
|
it("fails open from the reduced same-render rows for untrusted third-party output", () => {
|
|
991
1002
|
const base = baseEditor({ malformedTop: "third-party header" });
|
|
992
1003
|
const editor = wrapped(base, { style: "accent-rail" });
|
|
@@ -995,9 +1006,11 @@ describe("accent rail editor integration", () => {
|
|
|
995
1006
|
expect(lines.join("\n")).not.toContain("▎");
|
|
996
1007
|
});
|
|
997
1008
|
|
|
998
|
-
it("renders the standalone editor with the same accent rail", () => {
|
|
1009
|
+
it("renders the standalone editor with the same reserved rows and accent rail", () => {
|
|
999
1010
|
const lines = standalone("accent-rail").render(24);
|
|
1000
|
-
expect(lines
|
|
1011
|
+
expect(lines[0]).toBe("");
|
|
1012
|
+
expect(lines[1]).toMatch(/^▎ /);
|
|
1013
|
+
expect(lines[2]).toBe("");
|
|
1001
1014
|
expect(lines.every((line) => visibleWidth(line) <= 24)).toBe(true);
|
|
1002
1015
|
});
|
|
1003
1016
|
});
|
|
@@ -1063,7 +1076,9 @@ describe("minimalist editor integration", () => {
|
|
|
1063
1076
|
expect(rawWidths).toEqual([36, 38, 36, 34, 36]);
|
|
1064
1077
|
expect(polished.join("\n").match(/provider/g)).toHaveLength(1);
|
|
1065
1078
|
expect(lowRail.join("\n").match(/provider/g)).toHaveLength(1);
|
|
1066
|
-
expect(accentRail[0]).
|
|
1079
|
+
expect(accentRail[0]).toBe("");
|
|
1080
|
+
expect(accentRail[1]).toMatch(/^▎ typed text/);
|
|
1081
|
+
expect(accentRail[2]).toBe("");
|
|
1067
1082
|
expect(accentRail.join("\n")).not.toContain("provider");
|
|
1068
1083
|
expect(minimalist[0]).toMatch(/^╭.*╮$/);
|
|
1069
1084
|
expect(minimalist.at(-1)).toMatch(/^╰.*╯$/);
|