@jmoyers/harness 0.1.0
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/LICENSE +21 -0
- package/README.md +145 -0
- package/native/ptyd/Cargo.lock +16 -0
- package/native/ptyd/Cargo.toml +7 -0
- package/native/ptyd/src/main.rs +257 -0
- package/package.json +90 -0
- package/scripts/build-ptyd.sh +73 -0
- package/scripts/control-plane-daemon.ts +277 -0
- package/scripts/cursor-hook-relay.ts +82 -0
- package/scripts/harness-animate.ts +469 -0
- package/scripts/harness-bin.js +77 -0
- package/scripts/harness-core.ts +1 -0
- package/scripts/harness-inspector.ts +439 -0
- package/scripts/harness.ts +2493 -0
- package/src/adapters/agent-session-state.ts +390 -0
- package/src/cli/gateway-record.ts +173 -0
- package/src/codex/live-session.ts +872 -0
- package/src/config/config-core.ts +1359 -0
- package/src/config/secrets-core.ts +170 -0
- package/src/control-plane/agent-realtime-api.ts +2441 -0
- package/src/control-plane/codex-session-stream.ts +392 -0
- package/src/control-plane/codex-telemetry.ts +1325 -0
- package/src/control-plane/lifecycle-hooks.ts +706 -0
- package/src/control-plane/session-summary.ts +380 -0
- package/src/control-plane/status/agent-status-reducer.ts +21 -0
- package/src/control-plane/status/reducer-base.ts +170 -0
- package/src/control-plane/status/reducers/claude-status-reducer.ts +37 -0
- package/src/control-plane/status/reducers/codex-status-reducer.ts +48 -0
- package/src/control-plane/status/reducers/critique-status-reducer.ts +15 -0
- package/src/control-plane/status/reducers/cursor-status-reducer.ts +37 -0
- package/src/control-plane/status/reducers/terminal-status-reducer.ts +15 -0
- package/src/control-plane/status/session-status-engine.ts +76 -0
- package/src/control-plane/stream-client.ts +396 -0
- package/src/control-plane/stream-command-parser.ts +1673 -0
- package/src/control-plane/stream-protocol.ts +1808 -0
- package/src/control-plane/stream-server-background.ts +486 -0
- package/src/control-plane/stream-server-command.ts +2557 -0
- package/src/control-plane/stream-server-connection.ts +234 -0
- package/src/control-plane/stream-server-observed-filter.ts +112 -0
- package/src/control-plane/stream-server-session-runtime.ts +566 -0
- package/src/control-plane/stream-server-state-store.ts +15 -0
- package/src/control-plane/stream-server.ts +3192 -0
- package/src/cursor/managed-hooks.ts +282 -0
- package/src/domain/conversations.ts +414 -0
- package/src/domain/directories.ts +78 -0
- package/src/domain/repositories.ts +123 -0
- package/src/domain/tasks.ts +148 -0
- package/src/domain/workspace.ts +156 -0
- package/src/events/normalized-events.ts +124 -0
- package/src/mux/ansi-integrity.ts +103 -0
- package/src/mux/control-plane-op-queue.ts +212 -0
- package/src/mux/conversation-rail.ts +339 -0
- package/src/mux/double-click.ts +78 -0
- package/src/mux/dual-pane-core.ts +435 -0
- package/src/mux/harness-core-ui.ts +817 -0
- package/src/mux/input-shortcuts.ts +667 -0
- package/src/mux/live-mux/actions-conversation.ts +344 -0
- package/src/mux/live-mux/actions-repository.ts +246 -0
- package/src/mux/live-mux/actions-task.ts +115 -0
- package/src/mux/live-mux/args.ts +142 -0
- package/src/mux/live-mux/command-menu.ts +298 -0
- package/src/mux/live-mux/control-plane-records.ts +546 -0
- package/src/mux/live-mux/conversation-state.ts +188 -0
- package/src/mux/live-mux/directory-resolution.ts +34 -0
- package/src/mux/live-mux/event-mapping.ts +96 -0
- package/src/mux/live-mux/gateway-profiler.ts +152 -0
- package/src/mux/live-mux/gateway-render-trace.ts +177 -0
- package/src/mux/live-mux/gateway-status-timeline.ts +166 -0
- package/src/mux/live-mux/git-parsing.ts +131 -0
- package/src/mux/live-mux/git-snapshot.ts +263 -0
- package/src/mux/live-mux/git-state.ts +136 -0
- package/src/mux/live-mux/global-shortcut-handlers.ts +143 -0
- package/src/mux/live-mux/home-pane-actions.ts +58 -0
- package/src/mux/live-mux/home-pane-drop.ts +44 -0
- package/src/mux/live-mux/home-pane-entity-click.ts +96 -0
- package/src/mux/live-mux/home-pane-pointer.ts +96 -0
- package/src/mux/live-mux/input-forwarding.ts +112 -0
- package/src/mux/live-mux/layout.ts +30 -0
- package/src/mux/live-mux/left-nav-activation.ts +103 -0
- package/src/mux/live-mux/left-nav.ts +85 -0
- package/src/mux/live-mux/left-rail-actions.ts +118 -0
- package/src/mux/live-mux/left-rail-conversation-click.ts +82 -0
- package/src/mux/live-mux/left-rail-pointer.ts +74 -0
- package/src/mux/live-mux/modal-command-menu-handler.ts +101 -0
- package/src/mux/live-mux/modal-conversation-handlers.ts +217 -0
- package/src/mux/live-mux/modal-input-reducers.ts +94 -0
- package/src/mux/live-mux/modal-overlays.ts +287 -0
- package/src/mux/live-mux/modal-pointer.ts +70 -0
- package/src/mux/live-mux/modal-prompt-handlers.ts +187 -0
- package/src/mux/live-mux/modal-task-editor-handler.ts +156 -0
- package/src/mux/live-mux/observed-stream.ts +87 -0
- package/src/mux/live-mux/palette-parsing.ts +128 -0
- package/src/mux/live-mux/pointer-routing.ts +108 -0
- package/src/mux/live-mux/process-usage.ts +53 -0
- package/src/mux/live-mux/project-pane-pointer.ts +44 -0
- package/src/mux/live-mux/rail-layout.ts +244 -0
- package/src/mux/live-mux/render-trace-analysis.ts +213 -0
- package/src/mux/live-mux/render-trace-state.ts +84 -0
- package/src/mux/live-mux/repository-folding.ts +207 -0
- package/src/mux/live-mux/runtime-shutdown.ts +51 -0
- package/src/mux/live-mux/selection.ts +411 -0
- package/src/mux/live-mux/startup-utils.ts +187 -0
- package/src/mux/live-mux/status-timeline-state.ts +82 -0
- package/src/mux/live-mux/task-pane-shortcuts.ts +206 -0
- package/src/mux/live-mux/terminal-palette.ts +79 -0
- package/src/mux/new-thread-prompt.ts +165 -0
- package/src/mux/project-tree.ts +295 -0
- package/src/mux/render-frame.ts +113 -0
- package/src/mux/runtime-wiring.ts +185 -0
- package/src/mux/selector-index.ts +160 -0
- package/src/mux/startup-sequencer.ts +238 -0
- package/src/mux/task-composer.ts +289 -0
- package/src/mux/task-focused-pane.ts +417 -0
- package/src/mux/task-screen-keybindings.ts +539 -0
- package/src/mux/terminal-input-modes.ts +35 -0
- package/src/mux/workspace-path.ts +55 -0
- package/src/mux/workspace-rail-model.ts +701 -0
- package/src/mux/workspace-rail.ts +247 -0
- package/src/perf/perf-core.ts +307 -0
- package/src/pty/pty_host.ts +217 -0
- package/src/pty/session-broker.ts +158 -0
- package/src/recording/terminal-recording.ts +383 -0
- package/src/services/control-plane.ts +567 -0
- package/src/services/conversation-lifecycle.ts +176 -0
- package/src/services/conversation-startup-hydration.ts +47 -0
- package/src/services/directory-hydration.ts +49 -0
- package/src/services/event-persistence.ts +104 -0
- package/src/services/mux-ui-state-persistence.ts +82 -0
- package/src/services/output-load-sampler.ts +231 -0
- package/src/services/process-usage-refresh.ts +88 -0
- package/src/services/recording.ts +75 -0
- package/src/services/render-trace-recorder.ts +177 -0
- package/src/services/runtime-control-actions.ts +123 -0
- package/src/services/runtime-control-plane-ops.ts +131 -0
- package/src/services/runtime-conversation-actions.ts +113 -0
- package/src/services/runtime-conversation-activation.ts +78 -0
- package/src/services/runtime-conversation-starter.ts +171 -0
- package/src/services/runtime-conversation-title-edit.ts +149 -0
- package/src/services/runtime-directory-actions.ts +164 -0
- package/src/services/runtime-envelope-handler.ts +198 -0
- package/src/services/runtime-git-state.ts +92 -0
- package/src/services/runtime-input-pipeline.ts +50 -0
- package/src/services/runtime-input-router.ts +202 -0
- package/src/services/runtime-layout-resize.ts +236 -0
- package/src/services/runtime-left-rail-render.ts +159 -0
- package/src/services/runtime-main-pane-input.ts +230 -0
- package/src/services/runtime-modal-input.ts +119 -0
- package/src/services/runtime-navigation-input.ts +207 -0
- package/src/services/runtime-process-wiring.ts +68 -0
- package/src/services/runtime-rail-input.ts +287 -0
- package/src/services/runtime-render-flush.ts +146 -0
- package/src/services/runtime-render-lifecycle.ts +104 -0
- package/src/services/runtime-render-orchestrator.ts +108 -0
- package/src/services/runtime-render-pipeline.ts +167 -0
- package/src/services/runtime-render-state.ts +72 -0
- package/src/services/runtime-repository-actions.ts +197 -0
- package/src/services/runtime-right-pane-render.ts +132 -0
- package/src/services/runtime-shutdown.ts +79 -0
- package/src/services/runtime-stream-subscriptions.ts +56 -0
- package/src/services/runtime-task-composer-persistence.ts +139 -0
- package/src/services/runtime-task-editor-actions.ts +83 -0
- package/src/services/runtime-task-pane-actions.ts +198 -0
- package/src/services/runtime-task-pane-shortcuts.ts +189 -0
- package/src/services/runtime-task-pane.ts +62 -0
- package/src/services/runtime-workspace-actions.ts +153 -0
- package/src/services/runtime-workspace-observed-events.ts +190 -0
- package/src/services/session-projection-instrumentation.ts +190 -0
- package/src/services/startup-background-probe.ts +91 -0
- package/src/services/startup-background-resume.ts +65 -0
- package/src/services/startup-orchestrator.ts +166 -0
- package/src/services/startup-output-tracker.ts +54 -0
- package/src/services/startup-paint-tracker.ts +115 -0
- package/src/services/startup-persisted-conversation-queue.ts +45 -0
- package/src/services/startup-settled-gate.ts +67 -0
- package/src/services/startup-shutdown.ts +53 -0
- package/src/services/startup-span-tracker.ts +77 -0
- package/src/services/startup-state-hydration.ts +94 -0
- package/src/services/startup-visibility.ts +35 -0
- package/src/services/status-timeline-recorder.ts +144 -0
- package/src/services/task-pane-selection-actions.ts +153 -0
- package/src/services/task-planning-hydration.ts +58 -0
- package/src/services/task-planning-observed-events.ts +89 -0
- package/src/services/workspace-observed-events.ts +113 -0
- package/src/store/control-plane-store-normalize.ts +760 -0
- package/src/store/control-plane-store-types.ts +224 -0
- package/src/store/control-plane-store.ts +2951 -0
- package/src/store/event-store.ts +253 -0
- package/src/store/sqlite.ts +81 -0
- package/src/terminal/compat-matrix.ts +345 -0
- package/src/terminal/differential-checkpoints.ts +132 -0
- package/src/terminal/parity-suite.ts +441 -0
- package/src/terminal/snapshot-oracle.ts +1840 -0
- package/src/ui/conversation-input-forwarder.ts +114 -0
- package/src/ui/conversation-selection-input.ts +103 -0
- package/src/ui/debug-footer-notice.ts +39 -0
- package/src/ui/global-shortcut-input.ts +126 -0
- package/src/ui/input-preflight.ts +68 -0
- package/src/ui/input-token-router.ts +312 -0
- package/src/ui/input.ts +238 -0
- package/src/ui/kit.ts +509 -0
- package/src/ui/left-nav-input.ts +80 -0
- package/src/ui/left-rail-pointer-input.ts +148 -0
- package/src/ui/main-pane-pointer-input.ts +150 -0
- package/src/ui/modals/manager.ts +192 -0
- package/src/ui/mux-theme.ts +529 -0
- package/src/ui/panes/conversation.ts +19 -0
- package/src/ui/panes/home-gridfire.ts +302 -0
- package/src/ui/panes/home.ts +109 -0
- package/src/ui/panes/left-rail.ts +12 -0
- package/src/ui/panes/project.ts +44 -0
- package/src/ui/pointer-routing-input.ts +158 -0
- package/src/ui/repository-fold-input.ts +91 -0
- package/src/ui/screen.ts +210 -0
- package/src/ui/surface.ts +224 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
interface RuntimeRenderFlushResult {
|
|
2
|
+
readonly changedRowCount: number;
|
|
3
|
+
readonly wroteOutput: boolean;
|
|
4
|
+
readonly shouldShowCursor: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface RuntimeRenderFlushInput<TConversation, TFrame, TSelection, TLayout> {
|
|
8
|
+
readonly layout: TLayout;
|
|
9
|
+
readonly projectPaneActive: boolean;
|
|
10
|
+
readonly homePaneActive: boolean;
|
|
11
|
+
readonly activeConversation: TConversation | null;
|
|
12
|
+
readonly rightFrame: TFrame | null;
|
|
13
|
+
readonly renderSelection: TSelection | null;
|
|
14
|
+
readonly selectionRows: readonly number[];
|
|
15
|
+
readonly railAnsiRows: readonly string[];
|
|
16
|
+
readonly rightRows: readonly string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface RuntimeRenderFlushOptions<
|
|
20
|
+
TConversation,
|
|
21
|
+
TFrame,
|
|
22
|
+
TSelection,
|
|
23
|
+
TLayout,
|
|
24
|
+
TModalOverlay,
|
|
25
|
+
TStatusRow,
|
|
26
|
+
> {
|
|
27
|
+
readonly perfNowNs: () => bigint;
|
|
28
|
+
readonly statusFooterForConversation: (conversation: TConversation) => string;
|
|
29
|
+
readonly currentStatusNotice: () => string | null;
|
|
30
|
+
readonly currentStatusRow: () => TStatusRow;
|
|
31
|
+
readonly onStatusLineComposed?: (input: {
|
|
32
|
+
activeConversation: TConversation | null;
|
|
33
|
+
statusFooter: string;
|
|
34
|
+
statusRow: TStatusRow;
|
|
35
|
+
projectPaneActive: boolean;
|
|
36
|
+
homePaneActive: boolean;
|
|
37
|
+
}) => void;
|
|
38
|
+
readonly buildRenderRows: (
|
|
39
|
+
layout: TLayout,
|
|
40
|
+
railRows: readonly string[],
|
|
41
|
+
rightRows: readonly string[],
|
|
42
|
+
statusRow: TStatusRow,
|
|
43
|
+
statusFooter: string,
|
|
44
|
+
) => string[];
|
|
45
|
+
readonly buildModalOverlay: () => TModalOverlay | null;
|
|
46
|
+
readonly applyModalOverlay: (rows: string[], overlay: TModalOverlay) => void;
|
|
47
|
+
readonly renderSelectionOverlay: (
|
|
48
|
+
layout: TLayout,
|
|
49
|
+
rightFrame: TFrame,
|
|
50
|
+
selection: TSelection | null,
|
|
51
|
+
) => string;
|
|
52
|
+
readonly flush: (input: {
|
|
53
|
+
layout: TLayout;
|
|
54
|
+
rows: readonly string[];
|
|
55
|
+
rightFrame: TFrame | null;
|
|
56
|
+
selectionRows: readonly number[];
|
|
57
|
+
selectionOverlay: string;
|
|
58
|
+
}) => RuntimeRenderFlushResult;
|
|
59
|
+
readonly onFlushOutput: (input: {
|
|
60
|
+
activeConversation: TConversation | null;
|
|
61
|
+
rightFrame: TFrame | null;
|
|
62
|
+
rows: readonly string[];
|
|
63
|
+
flushResult: RuntimeRenderFlushResult;
|
|
64
|
+
changedRowCount: number;
|
|
65
|
+
}) => void;
|
|
66
|
+
readonly recordRenderSample: (durationMs: number, changedRowCount: number) => void;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export class RuntimeRenderFlush<
|
|
70
|
+
TConversation,
|
|
71
|
+
TFrame,
|
|
72
|
+
TSelection,
|
|
73
|
+
TLayout,
|
|
74
|
+
TModalOverlay,
|
|
75
|
+
TStatusRow,
|
|
76
|
+
> {
|
|
77
|
+
constructor(
|
|
78
|
+
private readonly options: RuntimeRenderFlushOptions<
|
|
79
|
+
TConversation,
|
|
80
|
+
TFrame,
|
|
81
|
+
TSelection,
|
|
82
|
+
TLayout,
|
|
83
|
+
TModalOverlay,
|
|
84
|
+
TStatusRow
|
|
85
|
+
>,
|
|
86
|
+
) {}
|
|
87
|
+
|
|
88
|
+
flushRender(input: RuntimeRenderFlushInput<TConversation, TFrame, TSelection, TLayout>): void {
|
|
89
|
+
const renderStartedAtNs = this.options.perfNowNs();
|
|
90
|
+
const baseStatusFooter =
|
|
91
|
+
!input.projectPaneActive && !input.homePaneActive && input.activeConversation !== null
|
|
92
|
+
? this.options.statusFooterForConversation(input.activeConversation)
|
|
93
|
+
: '';
|
|
94
|
+
const statusNotice = this.options.currentStatusNotice();
|
|
95
|
+
const statusFooter =
|
|
96
|
+
statusNotice === null || statusNotice.length === 0
|
|
97
|
+
? baseStatusFooter
|
|
98
|
+
: `${baseStatusFooter.length > 0 ? `${baseStatusFooter} ` : ''}${statusNotice}`;
|
|
99
|
+
const statusRow = this.options.currentStatusRow();
|
|
100
|
+
this.options.onStatusLineComposed?.({
|
|
101
|
+
activeConversation: input.activeConversation,
|
|
102
|
+
statusFooter,
|
|
103
|
+
statusRow,
|
|
104
|
+
projectPaneActive: input.projectPaneActive,
|
|
105
|
+
homePaneActive: input.homePaneActive,
|
|
106
|
+
});
|
|
107
|
+
const rows = this.options.buildRenderRows(
|
|
108
|
+
input.layout,
|
|
109
|
+
input.railAnsiRows,
|
|
110
|
+
input.rightRows,
|
|
111
|
+
statusRow,
|
|
112
|
+
statusFooter,
|
|
113
|
+
);
|
|
114
|
+
const modalOverlay = this.options.buildModalOverlay();
|
|
115
|
+
if (modalOverlay !== null) {
|
|
116
|
+
this.options.applyModalOverlay(rows, modalOverlay);
|
|
117
|
+
}
|
|
118
|
+
const selectionOverlay =
|
|
119
|
+
input.rightFrame === null
|
|
120
|
+
? ''
|
|
121
|
+
: this.options.renderSelectionOverlay(
|
|
122
|
+
input.layout,
|
|
123
|
+
input.rightFrame,
|
|
124
|
+
input.renderSelection,
|
|
125
|
+
);
|
|
126
|
+
const flushResult = this.options.flush({
|
|
127
|
+
layout: input.layout,
|
|
128
|
+
rows,
|
|
129
|
+
rightFrame: input.rightFrame,
|
|
130
|
+
selectionRows: input.selectionRows,
|
|
131
|
+
selectionOverlay,
|
|
132
|
+
});
|
|
133
|
+
const changedRowCount = flushResult.changedRowCount;
|
|
134
|
+
if (flushResult.wroteOutput) {
|
|
135
|
+
this.options.onFlushOutput({
|
|
136
|
+
activeConversation: input.activeConversation,
|
|
137
|
+
rightFrame: input.rightFrame,
|
|
138
|
+
rows,
|
|
139
|
+
flushResult,
|
|
140
|
+
changedRowCount,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
const renderDurationMs = Number(this.options.perfNowNs() - renderStartedAtNs) / 1e6;
|
|
144
|
+
this.options.recordRenderSample(renderDurationMs, changedRowCount);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
interface ScreenLike {
|
|
2
|
+
clearDirty(): void;
|
|
3
|
+
isDirty(): boolean;
|
|
4
|
+
markDirty(): void;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface RuntimeRenderLifecycleOptions {
|
|
8
|
+
readonly screen: ScreenLike;
|
|
9
|
+
readonly render: () => void;
|
|
10
|
+
readonly isShuttingDown: () => boolean;
|
|
11
|
+
readonly setShuttingDown: (next: boolean) => void;
|
|
12
|
+
readonly setStop: (next: boolean) => void;
|
|
13
|
+
readonly restoreTerminalState: () => void;
|
|
14
|
+
readonly formatErrorMessage: (error: unknown) => string;
|
|
15
|
+
readonly writeStderr: (text: string) => void;
|
|
16
|
+
readonly exitProcess: (code: number) => void;
|
|
17
|
+
readonly setImmediateFn?: (callback: () => void) => void;
|
|
18
|
+
readonly setTimeoutFn?: (callback: () => void, delayMs: number) => ReturnType<typeof setTimeout>;
|
|
19
|
+
readonly clearTimeoutFn?: (timer: ReturnType<typeof setTimeout>) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const FATAL_EXIT_DELAY_MS = 1200;
|
|
23
|
+
|
|
24
|
+
export class RuntimeRenderLifecycle {
|
|
25
|
+
private readonly setImmediateFn: (callback: () => void) => void;
|
|
26
|
+
private readonly setTimeoutFn: (
|
|
27
|
+
callback: () => void,
|
|
28
|
+
delayMs: number,
|
|
29
|
+
) => ReturnType<typeof setTimeout>;
|
|
30
|
+
private readonly clearTimeoutFn: (timer: ReturnType<typeof setTimeout>) => void;
|
|
31
|
+
private renderScheduled = false;
|
|
32
|
+
private runtimeFatal: { origin: string; error: unknown } | null = null;
|
|
33
|
+
private runtimeFatalExitTimer: ReturnType<typeof setTimeout> | null = null;
|
|
34
|
+
|
|
35
|
+
constructor(private readonly options: RuntimeRenderLifecycleOptions) {
|
|
36
|
+
this.setImmediateFn = options.setImmediateFn ?? setImmediate;
|
|
37
|
+
this.setTimeoutFn = options.setTimeoutFn ?? setTimeout;
|
|
38
|
+
this.clearTimeoutFn = options.clearTimeoutFn ?? clearTimeout;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
hasFatal(): boolean {
|
|
42
|
+
return this.runtimeFatal !== null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
clearRenderScheduled(): void {
|
|
46
|
+
this.renderScheduled = false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
clearRuntimeFatalExitTimer(): void {
|
|
50
|
+
if (this.runtimeFatalExitTimer === null) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
this.clearTimeoutFn(this.runtimeFatalExitTimer);
|
|
54
|
+
this.runtimeFatalExitTimer = null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
markDirty(): void {
|
|
58
|
+
if (this.options.isShuttingDown()) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
this.options.screen.markDirty();
|
|
62
|
+
this.scheduleRender();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
scheduleRender(): void {
|
|
66
|
+
if (this.options.isShuttingDown() || this.renderScheduled) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
this.renderScheduled = true;
|
|
70
|
+
this.setImmediateFn(() => {
|
|
71
|
+
this.renderScheduled = false;
|
|
72
|
+
try {
|
|
73
|
+
this.options.render();
|
|
74
|
+
if (this.options.screen.isDirty()) {
|
|
75
|
+
this.scheduleRender();
|
|
76
|
+
}
|
|
77
|
+
} catch (error: unknown) {
|
|
78
|
+
this.handleRuntimeFatal('render', error);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
handleRuntimeFatal(origin: string, error: unknown): void {
|
|
84
|
+
if (this.runtimeFatal !== null) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
this.runtimeFatal = {
|
|
88
|
+
origin,
|
|
89
|
+
error,
|
|
90
|
+
};
|
|
91
|
+
this.options.setShuttingDown(true);
|
|
92
|
+
this.options.setStop(true);
|
|
93
|
+
this.options.screen.clearDirty();
|
|
94
|
+
this.options.writeStderr(
|
|
95
|
+
`[mux] fatal runtime error (${origin}): ${this.options.formatErrorMessage(error)}\n`,
|
|
96
|
+
);
|
|
97
|
+
this.options.restoreTerminalState();
|
|
98
|
+
this.runtimeFatalExitTimer = this.setTimeoutFn(() => {
|
|
99
|
+
this.options.writeStderr('[mux] fatal runtime error forced exit\n');
|
|
100
|
+
this.options.exitProcess(1);
|
|
101
|
+
}, FATAL_EXIT_DELAY_MS);
|
|
102
|
+
this.runtimeFatalExitTimer.unref?.();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
interface RuntimeRenderStateResult<TConversation, TFrame, TSelection> {
|
|
2
|
+
readonly projectPaneActive: boolean;
|
|
3
|
+
readonly homePaneActive: boolean;
|
|
4
|
+
readonly activeConversation: TConversation | null;
|
|
5
|
+
readonly rightFrame: TFrame | null;
|
|
6
|
+
readonly renderSelection: TSelection | null;
|
|
7
|
+
readonly selectionRows: readonly number[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface RuntimeLeftRailRenderResult<TRailViewRows> {
|
|
11
|
+
readonly ansiRows: readonly string[];
|
|
12
|
+
readonly viewRows: TRailViewRows;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface RuntimeRenderOrchestratorOptions<
|
|
16
|
+
TLayout,
|
|
17
|
+
TConversation,
|
|
18
|
+
TFrame,
|
|
19
|
+
TSelection,
|
|
20
|
+
TSelectionDrag,
|
|
21
|
+
TRailViewRows,
|
|
22
|
+
> {
|
|
23
|
+
readonly isScreenDirty: () => boolean;
|
|
24
|
+
readonly clearDirty: () => void;
|
|
25
|
+
readonly prepareRenderState: (
|
|
26
|
+
selection: TSelection | null,
|
|
27
|
+
selectionDrag: TSelectionDrag | null,
|
|
28
|
+
) => RuntimeRenderStateResult<TConversation, TFrame, TSelection> | null;
|
|
29
|
+
readonly renderLeftRail: (layout: TLayout) => RuntimeLeftRailRenderResult<TRailViewRows>;
|
|
30
|
+
readonly setLatestRailViewRows: (rows: TRailViewRows) => void;
|
|
31
|
+
readonly renderRightRows: (input: {
|
|
32
|
+
layout: TLayout;
|
|
33
|
+
rightFrame: TFrame | null;
|
|
34
|
+
homePaneActive: boolean;
|
|
35
|
+
projectPaneActive: boolean;
|
|
36
|
+
activeDirectoryId: string | null;
|
|
37
|
+
}) => readonly string[];
|
|
38
|
+
readonly flushRender: (input: {
|
|
39
|
+
layout: TLayout;
|
|
40
|
+
projectPaneActive: boolean;
|
|
41
|
+
homePaneActive: boolean;
|
|
42
|
+
activeConversation: TConversation | null;
|
|
43
|
+
rightFrame: TFrame | null;
|
|
44
|
+
renderSelection: TSelection | null;
|
|
45
|
+
selectionRows: readonly number[];
|
|
46
|
+
railAnsiRows: readonly string[];
|
|
47
|
+
rightRows: readonly string[];
|
|
48
|
+
}) => void;
|
|
49
|
+
readonly activeDirectoryId: () => string | null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface RuntimeRenderOrchestratorInput<TLayout, TSelection, TSelectionDrag> {
|
|
53
|
+
readonly shuttingDown: boolean;
|
|
54
|
+
readonly layout: TLayout;
|
|
55
|
+
readonly selection: TSelection | null;
|
|
56
|
+
readonly selectionDrag: TSelectionDrag | null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export class RuntimeRenderOrchestrator<
|
|
60
|
+
TLayout,
|
|
61
|
+
TConversation,
|
|
62
|
+
TFrame,
|
|
63
|
+
TSelection,
|
|
64
|
+
TSelectionDrag,
|
|
65
|
+
TRailViewRows,
|
|
66
|
+
> {
|
|
67
|
+
constructor(
|
|
68
|
+
private readonly options: RuntimeRenderOrchestratorOptions<
|
|
69
|
+
TLayout,
|
|
70
|
+
TConversation,
|
|
71
|
+
TFrame,
|
|
72
|
+
TSelection,
|
|
73
|
+
TSelectionDrag,
|
|
74
|
+
TRailViewRows
|
|
75
|
+
>,
|
|
76
|
+
) {}
|
|
77
|
+
|
|
78
|
+
render(input: RuntimeRenderOrchestratorInput<TLayout, TSelection, TSelectionDrag>): void {
|
|
79
|
+
if (input.shuttingDown || !this.options.isScreenDirty()) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const renderState = this.options.prepareRenderState(input.selection, input.selectionDrag);
|
|
83
|
+
if (renderState === null) {
|
|
84
|
+
this.options.clearDirty();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const rail = this.options.renderLeftRail(input.layout);
|
|
88
|
+
this.options.setLatestRailViewRows(rail.viewRows);
|
|
89
|
+
const rightRows = this.options.renderRightRows({
|
|
90
|
+
layout: input.layout,
|
|
91
|
+
rightFrame: renderState.rightFrame,
|
|
92
|
+
homePaneActive: renderState.homePaneActive,
|
|
93
|
+
projectPaneActive: renderState.projectPaneActive,
|
|
94
|
+
activeDirectoryId: this.options.activeDirectoryId(),
|
|
95
|
+
});
|
|
96
|
+
this.options.flushRender({
|
|
97
|
+
layout: input.layout,
|
|
98
|
+
projectPaneActive: renderState.projectPaneActive,
|
|
99
|
+
homePaneActive: renderState.homePaneActive,
|
|
100
|
+
activeConversation: renderState.activeConversation,
|
|
101
|
+
rightFrame: renderState.rightFrame,
|
|
102
|
+
renderSelection: renderState.renderSelection,
|
|
103
|
+
selectionRows: renderState.selectionRows,
|
|
104
|
+
railAnsiRows: rail.ansiRows,
|
|
105
|
+
rightRows,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type { computeDualPaneLayout } from '../mux/dual-pane-core.ts';
|
|
2
|
+
import type { PaneSelection, PaneSelectionDrag } from '../mux/live-mux/selection.ts';
|
|
3
|
+
import type {
|
|
4
|
+
TaskFocusedPaneRepositoryRecord,
|
|
5
|
+
TaskFocusedPaneTaskRecord,
|
|
6
|
+
} from '../mux/task-focused-pane.ts';
|
|
7
|
+
import type { TerminalSnapshotFrameCore } from '../terminal/snapshot-oracle.ts';
|
|
8
|
+
import { RuntimeLeftRailRender } from './runtime-left-rail-render.ts';
|
|
9
|
+
import { RuntimeRenderFlush } from './runtime-render-flush.ts';
|
|
10
|
+
import { RuntimeRenderOrchestrator } from './runtime-render-orchestrator.ts';
|
|
11
|
+
import { RuntimeRenderState } from './runtime-render-state.ts';
|
|
12
|
+
import { RuntimeRightPaneRender } from './runtime-right-pane-render.ts';
|
|
13
|
+
|
|
14
|
+
type RuntimeLayout = ReturnType<typeof computeDualPaneLayout>;
|
|
15
|
+
|
|
16
|
+
type RuntimeRenderFlushOptions<TConversation, TModalOverlay, TStatusRow> = ConstructorParameters<
|
|
17
|
+
typeof RuntimeRenderFlush<
|
|
18
|
+
TConversation,
|
|
19
|
+
TerminalSnapshotFrameCore,
|
|
20
|
+
PaneSelection,
|
|
21
|
+
RuntimeLayout,
|
|
22
|
+
TModalOverlay,
|
|
23
|
+
TStatusRow
|
|
24
|
+
>
|
|
25
|
+
>[0];
|
|
26
|
+
|
|
27
|
+
type RuntimeRightPaneRenderOptions<
|
|
28
|
+
TRepositoryRecord extends TaskFocusedPaneRepositoryRecord,
|
|
29
|
+
TTaskRecord extends TaskFocusedPaneTaskRecord,
|
|
30
|
+
> = ConstructorParameters<typeof RuntimeRightPaneRender<TRepositoryRecord, TTaskRecord>>[0];
|
|
31
|
+
|
|
32
|
+
type RuntimeLeftRailRenderOptions<
|
|
33
|
+
TDirectoryRecord,
|
|
34
|
+
TConversation,
|
|
35
|
+
TRepositoryRecord,
|
|
36
|
+
TRepositorySnapshot,
|
|
37
|
+
TGitSummary,
|
|
38
|
+
TProcessUsage,
|
|
39
|
+
TShortcutBindings,
|
|
40
|
+
TRailViewRows,
|
|
41
|
+
> = ConstructorParameters<
|
|
42
|
+
typeof RuntimeLeftRailRender<
|
|
43
|
+
TDirectoryRecord,
|
|
44
|
+
TConversation,
|
|
45
|
+
TRepositoryRecord,
|
|
46
|
+
TRepositorySnapshot,
|
|
47
|
+
TGitSummary,
|
|
48
|
+
TProcessUsage,
|
|
49
|
+
TShortcutBindings,
|
|
50
|
+
TRailViewRows
|
|
51
|
+
>
|
|
52
|
+
>[0];
|
|
53
|
+
|
|
54
|
+
type RuntimeRenderStateOptions<TConversation> = ConstructorParameters<
|
|
55
|
+
typeof RuntimeRenderState<TConversation, TerminalSnapshotFrameCore>
|
|
56
|
+
>[0];
|
|
57
|
+
|
|
58
|
+
interface RuntimeRenderPipelineOptions<
|
|
59
|
+
TConversation,
|
|
60
|
+
TRepositoryRecord extends TaskFocusedPaneRepositoryRecord,
|
|
61
|
+
TTaskRecord extends TaskFocusedPaneTaskRecord,
|
|
62
|
+
TDirectoryRecord,
|
|
63
|
+
TRepositorySnapshot,
|
|
64
|
+
TGitSummary,
|
|
65
|
+
TProcessUsage,
|
|
66
|
+
TShortcutBindings,
|
|
67
|
+
TRailViewRows,
|
|
68
|
+
TModalOverlay,
|
|
69
|
+
TStatusRow,
|
|
70
|
+
> {
|
|
71
|
+
readonly renderFlush: RuntimeRenderFlushOptions<TConversation, TModalOverlay, TStatusRow>;
|
|
72
|
+
readonly rightPaneRender: RuntimeRightPaneRenderOptions<TRepositoryRecord, TTaskRecord>;
|
|
73
|
+
readonly leftRailRender: RuntimeLeftRailRenderOptions<
|
|
74
|
+
TDirectoryRecord,
|
|
75
|
+
TConversation,
|
|
76
|
+
TRepositoryRecord,
|
|
77
|
+
TRepositorySnapshot,
|
|
78
|
+
TGitSummary,
|
|
79
|
+
TProcessUsage,
|
|
80
|
+
TShortcutBindings,
|
|
81
|
+
TRailViewRows
|
|
82
|
+
>;
|
|
83
|
+
readonly renderState: RuntimeRenderStateOptions<TConversation>;
|
|
84
|
+
readonly isScreenDirty: () => boolean;
|
|
85
|
+
readonly clearDirty: () => void;
|
|
86
|
+
readonly setLatestRailViewRows: (rows: TRailViewRows) => void;
|
|
87
|
+
readonly activeDirectoryId: () => string | null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
type RuntimeRenderPipelineInput = Parameters<
|
|
91
|
+
RuntimeRenderOrchestrator<
|
|
92
|
+
RuntimeLayout,
|
|
93
|
+
unknown,
|
|
94
|
+
TerminalSnapshotFrameCore,
|
|
95
|
+
PaneSelection,
|
|
96
|
+
PaneSelectionDrag,
|
|
97
|
+
unknown
|
|
98
|
+
>['render']
|
|
99
|
+
>[0];
|
|
100
|
+
|
|
101
|
+
export class RuntimeRenderPipeline<
|
|
102
|
+
TConversation,
|
|
103
|
+
TRepositoryRecord extends TaskFocusedPaneRepositoryRecord,
|
|
104
|
+
TTaskRecord extends TaskFocusedPaneTaskRecord,
|
|
105
|
+
TDirectoryRecord,
|
|
106
|
+
TRepositorySnapshot,
|
|
107
|
+
TGitSummary,
|
|
108
|
+
TProcessUsage,
|
|
109
|
+
TShortcutBindings,
|
|
110
|
+
TRailViewRows,
|
|
111
|
+
TModalOverlay,
|
|
112
|
+
TStatusRow,
|
|
113
|
+
> {
|
|
114
|
+
private readonly renderOrchestrator: RuntimeRenderOrchestrator<
|
|
115
|
+
RuntimeLayout,
|
|
116
|
+
TConversation,
|
|
117
|
+
TerminalSnapshotFrameCore,
|
|
118
|
+
PaneSelection,
|
|
119
|
+
PaneSelectionDrag,
|
|
120
|
+
TRailViewRows
|
|
121
|
+
>;
|
|
122
|
+
|
|
123
|
+
constructor(
|
|
124
|
+
options: RuntimeRenderPipelineOptions<
|
|
125
|
+
TConversation,
|
|
126
|
+
TRepositoryRecord,
|
|
127
|
+
TTaskRecord,
|
|
128
|
+
TDirectoryRecord,
|
|
129
|
+
TRepositorySnapshot,
|
|
130
|
+
TGitSummary,
|
|
131
|
+
TProcessUsage,
|
|
132
|
+
TShortcutBindings,
|
|
133
|
+
TRailViewRows,
|
|
134
|
+
TModalOverlay,
|
|
135
|
+
TStatusRow
|
|
136
|
+
>,
|
|
137
|
+
) {
|
|
138
|
+
const renderFlush = new RuntimeRenderFlush(options.renderFlush);
|
|
139
|
+
const rightPaneRender = new RuntimeRightPaneRender(options.rightPaneRender);
|
|
140
|
+
const leftRailRender = new RuntimeLeftRailRender(options.leftRailRender);
|
|
141
|
+
const renderState = new RuntimeRenderState(options.renderState);
|
|
142
|
+
this.renderOrchestrator = new RuntimeRenderOrchestrator({
|
|
143
|
+
isScreenDirty: options.isScreenDirty,
|
|
144
|
+
clearDirty: options.clearDirty,
|
|
145
|
+
prepareRenderState: (selection, selectionDrag) =>
|
|
146
|
+
renderState.prepareRenderState(selection, selectionDrag),
|
|
147
|
+
renderLeftRail: (layout) => leftRailRender.render(layout),
|
|
148
|
+
setLatestRailViewRows: options.setLatestRailViewRows,
|
|
149
|
+
renderRightRows: (input) =>
|
|
150
|
+
rightPaneRender.renderRightRows({
|
|
151
|
+
layout: input.layout,
|
|
152
|
+
rightFrame: input.rightFrame,
|
|
153
|
+
homePaneActive: input.homePaneActive,
|
|
154
|
+
projectPaneActive: input.projectPaneActive,
|
|
155
|
+
activeDirectoryId: input.activeDirectoryId,
|
|
156
|
+
}),
|
|
157
|
+
flushRender: (input) => {
|
|
158
|
+
renderFlush.flushRender(input);
|
|
159
|
+
},
|
|
160
|
+
activeDirectoryId: options.activeDirectoryId,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
render(input: RuntimeRenderPipelineInput): void {
|
|
165
|
+
this.renderOrchestrator.render(input);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { WorkspaceModel } from '../domain/workspace.ts';
|
|
2
|
+
import type { PaneSelection, PaneSelectionDrag } from '../mux/live-mux/selection.ts';
|
|
3
|
+
|
|
4
|
+
interface RuntimeRenderStateOptions<TConversation, TFrame> {
|
|
5
|
+
readonly workspace: WorkspaceModel;
|
|
6
|
+
readonly hasDirectory: (directoryId: string) => boolean;
|
|
7
|
+
readonly activeConversationId: () => string | null;
|
|
8
|
+
readonly activeConversation: () => TConversation | null;
|
|
9
|
+
readonly snapshotFrame: (conversation: TConversation) => TFrame;
|
|
10
|
+
readonly selectionVisibleRows: (
|
|
11
|
+
frame: TFrame,
|
|
12
|
+
selection: PaneSelection | null,
|
|
13
|
+
) => readonly number[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface RuntimeRenderStateResult<TConversation, TFrame> {
|
|
17
|
+
readonly projectPaneActive: boolean;
|
|
18
|
+
readonly homePaneActive: boolean;
|
|
19
|
+
readonly activeConversation: TConversation | null;
|
|
20
|
+
readonly rightFrame: TFrame | null;
|
|
21
|
+
readonly renderSelection: PaneSelection | null;
|
|
22
|
+
readonly selectionRows: readonly number[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class RuntimeRenderState<TConversation, TFrame> {
|
|
26
|
+
constructor(private readonly options: RuntimeRenderStateOptions<TConversation, TFrame>) {}
|
|
27
|
+
|
|
28
|
+
prepareRenderState(
|
|
29
|
+
selection: PaneSelection | null,
|
|
30
|
+
selectionDrag: PaneSelectionDrag | null,
|
|
31
|
+
): RuntimeRenderStateResult<TConversation, TFrame> | null {
|
|
32
|
+
const workspace = this.options.workspace;
|
|
33
|
+
const projectPaneActive =
|
|
34
|
+
workspace.mainPaneMode === 'project' &&
|
|
35
|
+
workspace.activeDirectoryId !== null &&
|
|
36
|
+
this.options.hasDirectory(workspace.activeDirectoryId);
|
|
37
|
+
const homePaneActive = workspace.mainPaneMode === 'home';
|
|
38
|
+
if (!projectPaneActive && !homePaneActive && this.options.activeConversationId() === null) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const activeConversation = this.options.activeConversation();
|
|
43
|
+
if (!projectPaneActive && !homePaneActive && activeConversation === null) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const rightFrame =
|
|
48
|
+
!projectPaneActive && !homePaneActive && activeConversation !== null
|
|
49
|
+
? this.options.snapshotFrame(activeConversation)
|
|
50
|
+
: null;
|
|
51
|
+
const renderSelection =
|
|
52
|
+
rightFrame !== null && selectionDrag !== null && selectionDrag.hasDragged
|
|
53
|
+
? {
|
|
54
|
+
anchor: selectionDrag.anchor,
|
|
55
|
+
focus: selectionDrag.focus,
|
|
56
|
+
text: '',
|
|
57
|
+
}
|
|
58
|
+
: rightFrame !== null
|
|
59
|
+
? selection
|
|
60
|
+
: null;
|
|
61
|
+
const selectionRows =
|
|
62
|
+
rightFrame === null ? [] : this.options.selectionVisibleRows(rightFrame, renderSelection);
|
|
63
|
+
return {
|
|
64
|
+
projectPaneActive,
|
|
65
|
+
homePaneActive,
|
|
66
|
+
activeConversation,
|
|
67
|
+
rightFrame,
|
|
68
|
+
renderSelection,
|
|
69
|
+
selectionRows,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|