@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,312 @@
|
|
|
1
|
+
import {
|
|
2
|
+
classifyPaneAt as classifyPaneAtFrame,
|
|
3
|
+
type computeDualPaneLayout,
|
|
4
|
+
type parseMuxInputChunk,
|
|
5
|
+
} from '../mux/dual-pane-core.ts';
|
|
6
|
+
import {
|
|
7
|
+
hasAltModifier as hasAltModifierFrame,
|
|
8
|
+
hasShiftModifier as hasShiftModifierFrame,
|
|
9
|
+
isLeftButtonPress as isLeftButtonPressFrame,
|
|
10
|
+
isMotionMouseCode as isMotionMouseCodeFrame,
|
|
11
|
+
type PaneSelection,
|
|
12
|
+
} from '../mux/live-mux/selection.ts';
|
|
13
|
+
import type { TerminalSnapshotFrameCore } from '../terminal/snapshot-oracle.ts';
|
|
14
|
+
|
|
15
|
+
type MainPaneMode = 'conversation' | 'project' | 'home';
|
|
16
|
+
type DualPaneLayout = ReturnType<typeof computeDualPaneLayout>;
|
|
17
|
+
type MuxInputToken = ReturnType<typeof parseMuxInputChunk>['tokens'][number];
|
|
18
|
+
type PointerTarget = ReturnType<typeof classifyPaneAtFrame>;
|
|
19
|
+
|
|
20
|
+
interface MouseSelectionEvent {
|
|
21
|
+
readonly col: number;
|
|
22
|
+
readonly row: number;
|
|
23
|
+
readonly code: number;
|
|
24
|
+
readonly final: 'M' | 'm';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface ConversationInputLike {
|
|
28
|
+
readonly oracle: {
|
|
29
|
+
isMouseTrackingEnabled: () => boolean;
|
|
30
|
+
scrollViewport: (delta: number) => void;
|
|
31
|
+
snapshotWithoutHash: () => TerminalSnapshotFrameCore;
|
|
32
|
+
selectionText: (anchor: PaneSelection['anchor'], focus: PaneSelection['focus']) => string;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface PointerRoutingInputLike {
|
|
37
|
+
handlePaneDividerDrag(event: { code: number; final: 'M' | 'm'; col: number }): boolean;
|
|
38
|
+
handleHomePaneDragRelease(event: {
|
|
39
|
+
final: 'M' | 'm';
|
|
40
|
+
target: PointerTarget;
|
|
41
|
+
rowIndex: number;
|
|
42
|
+
}): boolean;
|
|
43
|
+
handleSeparatorPointerPress(event: {
|
|
44
|
+
target: PointerTarget;
|
|
45
|
+
code: number;
|
|
46
|
+
final: 'M' | 'm';
|
|
47
|
+
col: number;
|
|
48
|
+
}): boolean;
|
|
49
|
+
handleMainPaneWheel(
|
|
50
|
+
event: { target: PointerTarget; code: number },
|
|
51
|
+
onConversationWheel: (delta: number) => void,
|
|
52
|
+
): boolean;
|
|
53
|
+
handleHomePaneDragMove(event: {
|
|
54
|
+
target: PointerTarget;
|
|
55
|
+
code: number;
|
|
56
|
+
final: 'M' | 'm';
|
|
57
|
+
rowIndex: number;
|
|
58
|
+
}): boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface MainPanePointerInputLike {
|
|
62
|
+
handleProjectPanePointerClick(event: {
|
|
63
|
+
target: PointerTarget;
|
|
64
|
+
code: number;
|
|
65
|
+
final: 'M' | 'm';
|
|
66
|
+
row: number;
|
|
67
|
+
col: number;
|
|
68
|
+
rightCols: number;
|
|
69
|
+
paneRows: number;
|
|
70
|
+
rightStartCol: number;
|
|
71
|
+
}): boolean;
|
|
72
|
+
handleHomePanePointerClick(event: {
|
|
73
|
+
target: PointerTarget;
|
|
74
|
+
code: number;
|
|
75
|
+
final: 'M' | 'm';
|
|
76
|
+
row: number;
|
|
77
|
+
col: number;
|
|
78
|
+
rightCols: number;
|
|
79
|
+
paneRows: number;
|
|
80
|
+
rightStartCol: number;
|
|
81
|
+
}): boolean;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface LeftRailPointerInputLike {
|
|
85
|
+
handlePointerClick(input: {
|
|
86
|
+
clickEligible: boolean;
|
|
87
|
+
paneRows: number;
|
|
88
|
+
leftCols: number;
|
|
89
|
+
pointerRow: number;
|
|
90
|
+
pointerCol: number;
|
|
91
|
+
}): boolean;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
interface ConversationSelectionInputLike {
|
|
95
|
+
clearSelectionOnTextToken(textLength: number): boolean;
|
|
96
|
+
handleMouseSelection(input: {
|
|
97
|
+
layout: DualPaneLayout;
|
|
98
|
+
frame: TerminalSnapshotFrameCore;
|
|
99
|
+
isMainPaneTarget: boolean;
|
|
100
|
+
resolveSelectionText?: (selection: PaneSelection) => string;
|
|
101
|
+
event: MouseSelectionEvent;
|
|
102
|
+
}): boolean;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
interface InputTokenRouterOptions {
|
|
106
|
+
readonly getMainPaneMode: () => MainPaneMode;
|
|
107
|
+
readonly pointerRoutingInput: PointerRoutingInputLike;
|
|
108
|
+
readonly mainPanePointerInput: MainPanePointerInputLike;
|
|
109
|
+
readonly leftRailPointerInput: LeftRailPointerInputLike;
|
|
110
|
+
readonly conversationSelectionInput: ConversationSelectionInputLike;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
interface InputTokenRouterDependencies {
|
|
114
|
+
readonly classifyPaneAt?: typeof classifyPaneAtFrame;
|
|
115
|
+
readonly isLeftButtonPress?: typeof isLeftButtonPressFrame;
|
|
116
|
+
readonly hasAltModifier?: typeof hasAltModifierFrame;
|
|
117
|
+
readonly hasShiftModifier?: typeof hasShiftModifierFrame;
|
|
118
|
+
readonly isMotionMouseCode?: typeof isMotionMouseCodeFrame;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
interface RouteTokensInput {
|
|
122
|
+
readonly tokens: readonly MuxInputToken[];
|
|
123
|
+
readonly layout: DualPaneLayout;
|
|
124
|
+
readonly conversation: ConversationInputLike | null;
|
|
125
|
+
readonly snapshotForInput: TerminalSnapshotFrameCore | null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
interface RouteTokensResult {
|
|
129
|
+
readonly routedTokens: MuxInputToken[];
|
|
130
|
+
readonly snapshotForInput: TerminalSnapshotFrameCore | null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export class InputTokenRouter {
|
|
134
|
+
private readonly classifyPaneAt: typeof classifyPaneAtFrame;
|
|
135
|
+
private readonly isLeftButtonPress: typeof isLeftButtonPressFrame;
|
|
136
|
+
private readonly hasAltModifier: typeof hasAltModifierFrame;
|
|
137
|
+
private readonly hasShiftModifier: typeof hasShiftModifierFrame;
|
|
138
|
+
private readonly isMotionMouseCode: typeof isMotionMouseCodeFrame;
|
|
139
|
+
|
|
140
|
+
constructor(
|
|
141
|
+
private readonly options: InputTokenRouterOptions,
|
|
142
|
+
dependencies: InputTokenRouterDependencies = {},
|
|
143
|
+
) {
|
|
144
|
+
this.classifyPaneAt = dependencies.classifyPaneAt ?? classifyPaneAtFrame;
|
|
145
|
+
this.isLeftButtonPress = dependencies.isLeftButtonPress ?? isLeftButtonPressFrame;
|
|
146
|
+
this.hasAltModifier = dependencies.hasAltModifier ?? hasAltModifierFrame;
|
|
147
|
+
this.hasShiftModifier = dependencies.hasShiftModifier ?? hasShiftModifierFrame;
|
|
148
|
+
this.isMotionMouseCode = dependencies.isMotionMouseCode ?? isMotionMouseCodeFrame;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
routeTokens(input: RouteTokensInput): RouteTokensResult {
|
|
152
|
+
let snapshotForInput = input.snapshotForInput;
|
|
153
|
+
const conversation = input.conversation;
|
|
154
|
+
const resolveSelectionText =
|
|
155
|
+
conversation === null
|
|
156
|
+
? null
|
|
157
|
+
: (selection: PaneSelection): string =>
|
|
158
|
+
conversation.oracle.selectionText(selection.anchor, selection.focus);
|
|
159
|
+
const routedTokens: MuxInputToken[] = [];
|
|
160
|
+
for (const token of input.tokens) {
|
|
161
|
+
if (token.kind !== 'mouse') {
|
|
162
|
+
this.options.conversationSelectionInput.clearSelectionOnTextToken(token.text.length);
|
|
163
|
+
routedTokens.push(token);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (
|
|
168
|
+
this.options.pointerRoutingInput.handlePaneDividerDrag({
|
|
169
|
+
code: token.event.code,
|
|
170
|
+
final: token.event.final,
|
|
171
|
+
col: token.event.col,
|
|
172
|
+
})
|
|
173
|
+
) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const target = this.classifyPaneAt(input.layout, token.event.col, token.event.row);
|
|
178
|
+
const rowIndex = Math.max(0, Math.min(input.layout.paneRows - 1, token.event.row - 1));
|
|
179
|
+
const passThroughConversationMouse =
|
|
180
|
+
input.conversation !== null &&
|
|
181
|
+
this.options.getMainPaneMode() === 'conversation' &&
|
|
182
|
+
target === 'right' &&
|
|
183
|
+
snapshotForInput !== null &&
|
|
184
|
+
snapshotForInput.activeScreen === 'alternate' &&
|
|
185
|
+
snapshotForInput.viewport.followOutput &&
|
|
186
|
+
input.conversation.oracle.isMouseTrackingEnabled() &&
|
|
187
|
+
!this.hasShiftModifier(token.event.code);
|
|
188
|
+
if (
|
|
189
|
+
this.options.pointerRoutingInput.handleHomePaneDragRelease({
|
|
190
|
+
final: token.event.final,
|
|
191
|
+
target,
|
|
192
|
+
rowIndex,
|
|
193
|
+
})
|
|
194
|
+
) {
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (
|
|
198
|
+
this.options.pointerRoutingInput.handleSeparatorPointerPress({
|
|
199
|
+
target,
|
|
200
|
+
code: token.event.code,
|
|
201
|
+
final: token.event.final,
|
|
202
|
+
col: token.event.col,
|
|
203
|
+
})
|
|
204
|
+
) {
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (passThroughConversationMouse) {
|
|
208
|
+
routedTokens.push(token);
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const isMainPaneTarget = target === 'right';
|
|
212
|
+
if (
|
|
213
|
+
this.options.pointerRoutingInput.handleMainPaneWheel(
|
|
214
|
+
{
|
|
215
|
+
target,
|
|
216
|
+
code: token.event.code,
|
|
217
|
+
},
|
|
218
|
+
(delta) => {
|
|
219
|
+
if (input.conversation !== null) {
|
|
220
|
+
input.conversation.oracle.scrollViewport(delta);
|
|
221
|
+
snapshotForInput = input.conversation.oracle.snapshotWithoutHash();
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
)
|
|
225
|
+
) {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
if (
|
|
229
|
+
this.options.pointerRoutingInput.handleHomePaneDragMove({
|
|
230
|
+
target,
|
|
231
|
+
code: token.event.code,
|
|
232
|
+
final: token.event.final,
|
|
233
|
+
rowIndex,
|
|
234
|
+
})
|
|
235
|
+
) {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (
|
|
239
|
+
this.options.mainPanePointerInput.handleProjectPanePointerClick({
|
|
240
|
+
target,
|
|
241
|
+
code: token.event.code,
|
|
242
|
+
final: token.event.final,
|
|
243
|
+
row: token.event.row,
|
|
244
|
+
col: token.event.col,
|
|
245
|
+
rightCols: input.layout.rightCols,
|
|
246
|
+
paneRows: input.layout.paneRows,
|
|
247
|
+
rightStartCol: input.layout.rightStartCol,
|
|
248
|
+
})
|
|
249
|
+
) {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
if (
|
|
253
|
+
this.options.mainPanePointerInput.handleHomePanePointerClick({
|
|
254
|
+
target,
|
|
255
|
+
code: token.event.code,
|
|
256
|
+
final: token.event.final,
|
|
257
|
+
row: token.event.row,
|
|
258
|
+
col: token.event.col,
|
|
259
|
+
paneRows: input.layout.paneRows,
|
|
260
|
+
rightCols: input.layout.rightCols,
|
|
261
|
+
rightStartCol: input.layout.rightStartCol,
|
|
262
|
+
})
|
|
263
|
+
) {
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
const leftPaneConversationSelect =
|
|
267
|
+
target === 'left' &&
|
|
268
|
+
this.isLeftButtonPress(token.event.code, token.event.final) &&
|
|
269
|
+
!this.hasAltModifier(token.event.code) &&
|
|
270
|
+
!this.isMotionMouseCode(token.event.code);
|
|
271
|
+
if (
|
|
272
|
+
this.options.leftRailPointerInput.handlePointerClick({
|
|
273
|
+
clickEligible: leftPaneConversationSelect,
|
|
274
|
+
paneRows: input.layout.paneRows,
|
|
275
|
+
leftCols: input.layout.leftCols,
|
|
276
|
+
pointerRow: token.event.row,
|
|
277
|
+
pointerCol: token.event.col,
|
|
278
|
+
})
|
|
279
|
+
) {
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if (snapshotForInput === null || this.options.getMainPaneMode() !== 'conversation') {
|
|
283
|
+
routedTokens.push(token);
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
const mouseSelectionInput =
|
|
287
|
+
resolveSelectionText === null
|
|
288
|
+
? {
|
|
289
|
+
layout: input.layout,
|
|
290
|
+
frame: snapshotForInput,
|
|
291
|
+
isMainPaneTarget,
|
|
292
|
+
event: token.event,
|
|
293
|
+
}
|
|
294
|
+
: {
|
|
295
|
+
layout: input.layout,
|
|
296
|
+
frame: snapshotForInput,
|
|
297
|
+
isMainPaneTarget,
|
|
298
|
+
resolveSelectionText,
|
|
299
|
+
event: token.event,
|
|
300
|
+
};
|
|
301
|
+
if (this.options.conversationSelectionInput.handleMouseSelection(mouseSelectionInput)) {
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
routedTokens.push(token);
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
routedTokens,
|
|
309
|
+
snapshotForInput,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
}
|
package/src/ui/input.ts
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { handleCommandMenuInput as handleCommandMenuInputFrame } from '../mux/live-mux/modal-command-menu-handler.ts';
|
|
2
|
+
import {
|
|
3
|
+
handleConversationTitleEditInput as handleConversationTitleEditInputFrame,
|
|
4
|
+
handleNewThreadPromptInput as handleNewThreadPromptInputFrame,
|
|
5
|
+
} from '../mux/live-mux/modal-conversation-handlers.ts';
|
|
6
|
+
import {
|
|
7
|
+
handleAddDirectoryPromptInput as handleAddDirectoryPromptInputFrame,
|
|
8
|
+
handleRepositoryPromptInput as handleRepositoryPromptInputFrame,
|
|
9
|
+
} from '../mux/live-mux/modal-prompt-handlers.ts';
|
|
10
|
+
import { handleTaskEditorPromptInput as handleTaskEditorPromptInputFrame } from '../mux/live-mux/modal-task-editor-handler.ts';
|
|
11
|
+
import type {
|
|
12
|
+
ConversationTitleEditState,
|
|
13
|
+
RepositoryPromptState,
|
|
14
|
+
TaskEditorPromptState,
|
|
15
|
+
} from '../domain/workspace.ts';
|
|
16
|
+
import type {
|
|
17
|
+
CommandMenuActionDescriptor,
|
|
18
|
+
CommandMenuState,
|
|
19
|
+
} from '../mux/live-mux/command-menu.ts';
|
|
20
|
+
import type {
|
|
21
|
+
createNewThreadPromptState,
|
|
22
|
+
normalizeThreadAgentType,
|
|
23
|
+
} from '../mux/new-thread-prompt.ts';
|
|
24
|
+
|
|
25
|
+
type NewThreadPromptState = ReturnType<typeof createNewThreadPromptState>;
|
|
26
|
+
type ThreadAgentType = ReturnType<typeof normalizeThreadAgentType>;
|
|
27
|
+
type TaskEditorInputResult = ReturnType<typeof handleTaskEditorPromptInputFrame>;
|
|
28
|
+
type TaskEditorSubmitPayload = NonNullable<TaskEditorInputResult['submitPayload']>;
|
|
29
|
+
|
|
30
|
+
interface InputRouterOptions {
|
|
31
|
+
readonly isModalDismissShortcut: (input: Buffer) => boolean;
|
|
32
|
+
readonly isCommandMenuToggleShortcut: (input: Buffer) => boolean;
|
|
33
|
+
readonly isArchiveConversationShortcut: (input: Buffer) => boolean;
|
|
34
|
+
readonly dismissOnOutsideClick: (
|
|
35
|
+
input: Buffer,
|
|
36
|
+
dismiss: () => void,
|
|
37
|
+
onInsidePointerPress?: (col: number, row: number) => boolean,
|
|
38
|
+
) => boolean;
|
|
39
|
+
readonly buildConversationTitleModalOverlay: () => { top: number } | null;
|
|
40
|
+
readonly buildCommandMenuModalOverlay: () => { top: number } | null;
|
|
41
|
+
readonly buildNewThreadModalOverlay: () => { top: number } | null;
|
|
42
|
+
readonly resolveNewThreadPromptAgentByRow: (
|
|
43
|
+
overlayTop: number,
|
|
44
|
+
row: number,
|
|
45
|
+
) => ThreadAgentType | null;
|
|
46
|
+
readonly stopConversationTitleEdit: (persistPending: boolean) => void;
|
|
47
|
+
readonly queueControlPlaneOp: (task: () => Promise<void>, label: string) => void;
|
|
48
|
+
readonly archiveConversation: (sessionId: string) => Promise<void>;
|
|
49
|
+
readonly createAndActivateConversationInDirectory: (
|
|
50
|
+
directoryId: string,
|
|
51
|
+
agentType: ThreadAgentType,
|
|
52
|
+
) => Promise<void>;
|
|
53
|
+
readonly addDirectoryByPath: (path: string) => Promise<void>;
|
|
54
|
+
readonly normalizeGitHubRemoteUrl: (remoteUrl: string) => string | null;
|
|
55
|
+
readonly upsertRepositoryByRemoteUrl: (
|
|
56
|
+
remoteUrl: string,
|
|
57
|
+
existingRepositoryId?: string,
|
|
58
|
+
) => Promise<void>;
|
|
59
|
+
readonly repositoriesHas: (repositoryId: string) => boolean;
|
|
60
|
+
readonly markDirty: () => void;
|
|
61
|
+
readonly conversations: ReadonlyMap<string, { title: string }>;
|
|
62
|
+
readonly scheduleConversationTitlePersist: () => void;
|
|
63
|
+
readonly getTaskEditorPrompt: () => TaskEditorPromptState | null;
|
|
64
|
+
readonly setTaskEditorPrompt: (next: TaskEditorPromptState | null) => void;
|
|
65
|
+
readonly submitTaskEditorPayload: (payload: TaskEditorSubmitPayload) => void;
|
|
66
|
+
readonly getConversationTitleEdit: () => ConversationTitleEditState | null;
|
|
67
|
+
readonly getCommandMenu: () => CommandMenuState | null;
|
|
68
|
+
readonly setCommandMenu: (menu: CommandMenuState | null) => void;
|
|
69
|
+
readonly resolveCommandMenuActions: () => readonly CommandMenuActionDescriptor[];
|
|
70
|
+
readonly executeCommandMenuAction: (actionId: string) => void;
|
|
71
|
+
readonly getNewThreadPrompt: () => NewThreadPromptState | null;
|
|
72
|
+
readonly setNewThreadPrompt: (prompt: NewThreadPromptState | null) => void;
|
|
73
|
+
readonly getAddDirectoryPrompt: () => { value: string; error: string | null } | null;
|
|
74
|
+
readonly setAddDirectoryPrompt: (
|
|
75
|
+
next: {
|
|
76
|
+
value: string;
|
|
77
|
+
error: string | null;
|
|
78
|
+
} | null,
|
|
79
|
+
) => void;
|
|
80
|
+
readonly getRepositoryPrompt: () => RepositoryPromptState | null;
|
|
81
|
+
readonly setRepositoryPrompt: (next: RepositoryPromptState | null) => void;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface InputRouterDependencies {
|
|
85
|
+
readonly handleCommandMenuInput?: typeof handleCommandMenuInputFrame;
|
|
86
|
+
readonly handleTaskEditorPromptInput?: typeof handleTaskEditorPromptInputFrame;
|
|
87
|
+
readonly handleConversationTitleEditInput?: typeof handleConversationTitleEditInputFrame;
|
|
88
|
+
readonly handleNewThreadPromptInput?: typeof handleNewThreadPromptInputFrame;
|
|
89
|
+
readonly handleAddDirectoryPromptInput?: typeof handleAddDirectoryPromptInputFrame;
|
|
90
|
+
readonly handleRepositoryPromptInput?: typeof handleRepositoryPromptInputFrame;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export class InputRouter {
|
|
94
|
+
private readonly handleCommandMenuInputFrame: typeof handleCommandMenuInputFrame;
|
|
95
|
+
private readonly handleTaskEditorPromptInputFrame: typeof handleTaskEditorPromptInputFrame;
|
|
96
|
+
private readonly handleConversationTitleEditInputFrame: typeof handleConversationTitleEditInputFrame;
|
|
97
|
+
private readonly handleNewThreadPromptInputFrame: typeof handleNewThreadPromptInputFrame;
|
|
98
|
+
private readonly handleAddDirectoryPromptInputFrame: typeof handleAddDirectoryPromptInputFrame;
|
|
99
|
+
private readonly handleRepositoryPromptInputFrame: typeof handleRepositoryPromptInputFrame;
|
|
100
|
+
|
|
101
|
+
constructor(
|
|
102
|
+
private readonly options: InputRouterOptions,
|
|
103
|
+
dependencies: InputRouterDependencies = {},
|
|
104
|
+
) {
|
|
105
|
+
this.handleCommandMenuInputFrame =
|
|
106
|
+
dependencies.handleCommandMenuInput ?? handleCommandMenuInputFrame;
|
|
107
|
+
this.handleTaskEditorPromptInputFrame =
|
|
108
|
+
dependencies.handleTaskEditorPromptInput ?? handleTaskEditorPromptInputFrame;
|
|
109
|
+
this.handleConversationTitleEditInputFrame =
|
|
110
|
+
dependencies.handleConversationTitleEditInput ?? handleConversationTitleEditInputFrame;
|
|
111
|
+
this.handleNewThreadPromptInputFrame =
|
|
112
|
+
dependencies.handleNewThreadPromptInput ?? handleNewThreadPromptInputFrame;
|
|
113
|
+
this.handleAddDirectoryPromptInputFrame =
|
|
114
|
+
dependencies.handleAddDirectoryPromptInput ?? handleAddDirectoryPromptInputFrame;
|
|
115
|
+
this.handleRepositoryPromptInputFrame =
|
|
116
|
+
dependencies.handleRepositoryPromptInput ?? handleRepositoryPromptInputFrame;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
handleCommandMenuInput(input: Buffer): boolean {
|
|
120
|
+
return this.handleCommandMenuInputFrame({
|
|
121
|
+
input,
|
|
122
|
+
menu: this.options.getCommandMenu(),
|
|
123
|
+
isQuitShortcut: this.options.isModalDismissShortcut,
|
|
124
|
+
isToggleShortcut: this.options.isCommandMenuToggleShortcut,
|
|
125
|
+
dismissOnOutsideClick: this.options.dismissOnOutsideClick,
|
|
126
|
+
buildCommandMenuModalOverlay: this.options.buildCommandMenuModalOverlay,
|
|
127
|
+
resolveActions: this.options.resolveCommandMenuActions,
|
|
128
|
+
executeAction: this.options.executeCommandMenuAction,
|
|
129
|
+
setMenu: this.options.setCommandMenu,
|
|
130
|
+
markDirty: this.options.markDirty,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
handleTaskEditorPromptInput(input: Buffer): boolean {
|
|
135
|
+
const handled = this.handleTaskEditorPromptInputFrame({
|
|
136
|
+
input,
|
|
137
|
+
prompt: this.options.getTaskEditorPrompt(),
|
|
138
|
+
isQuitShortcut: this.options.isModalDismissShortcut,
|
|
139
|
+
dismissOnOutsideClick: this.options.dismissOnOutsideClick,
|
|
140
|
+
});
|
|
141
|
+
if (!handled.handled) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
if (handled.nextPrompt !== undefined) {
|
|
145
|
+
this.options.setTaskEditorPrompt(handled.nextPrompt);
|
|
146
|
+
}
|
|
147
|
+
if (handled.markDirty) {
|
|
148
|
+
this.options.markDirty();
|
|
149
|
+
}
|
|
150
|
+
if (handled.submitPayload !== undefined) {
|
|
151
|
+
this.options.submitTaskEditorPayload(handled.submitPayload);
|
|
152
|
+
}
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
handleRepositoryPromptInput(input: Buffer): boolean {
|
|
157
|
+
return this.handleRepositoryPromptInputFrame({
|
|
158
|
+
input,
|
|
159
|
+
prompt: this.options.getRepositoryPrompt(),
|
|
160
|
+
isQuitShortcut: this.options.isModalDismissShortcut,
|
|
161
|
+
dismissOnOutsideClick: this.options.dismissOnOutsideClick,
|
|
162
|
+
setPrompt: this.options.setRepositoryPrompt,
|
|
163
|
+
markDirty: this.options.markDirty,
|
|
164
|
+
repositoriesHas: this.options.repositoriesHas,
|
|
165
|
+
normalizeGitHubRemoteUrl: this.options.normalizeGitHubRemoteUrl,
|
|
166
|
+
queueControlPlaneOp: this.options.queueControlPlaneOp,
|
|
167
|
+
upsertRepositoryByRemoteUrl: this.options.upsertRepositoryByRemoteUrl,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
handleNewThreadPromptInput(input: Buffer): boolean {
|
|
172
|
+
return this.handleNewThreadPromptInputFrame({
|
|
173
|
+
input,
|
|
174
|
+
prompt: this.options.getNewThreadPrompt(),
|
|
175
|
+
isQuitShortcut: this.options.isModalDismissShortcut,
|
|
176
|
+
dismissOnOutsideClick: this.options.dismissOnOutsideClick,
|
|
177
|
+
buildNewThreadModalOverlay: this.options.buildNewThreadModalOverlay,
|
|
178
|
+
resolveNewThreadPromptAgentByRow: this.options.resolveNewThreadPromptAgentByRow,
|
|
179
|
+
queueControlPlaneOp: this.options.queueControlPlaneOp,
|
|
180
|
+
createAndActivateConversationInDirectory:
|
|
181
|
+
this.options.createAndActivateConversationInDirectory,
|
|
182
|
+
markDirty: this.options.markDirty,
|
|
183
|
+
setPrompt: this.options.setNewThreadPrompt,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
handleConversationTitleEditInput(input: Buffer): boolean {
|
|
188
|
+
return this.handleConversationTitleEditInputFrame({
|
|
189
|
+
input,
|
|
190
|
+
edit: this.options.getConversationTitleEdit(),
|
|
191
|
+
isQuitShortcut: this.options.isModalDismissShortcut,
|
|
192
|
+
isArchiveShortcut: this.options.isArchiveConversationShortcut,
|
|
193
|
+
dismissOnOutsideClick: this.options.dismissOnOutsideClick,
|
|
194
|
+
buildConversationTitleModalOverlay: this.options.buildConversationTitleModalOverlay,
|
|
195
|
+
stopConversationTitleEdit: this.options.stopConversationTitleEdit,
|
|
196
|
+
queueControlPlaneOp: this.options.queueControlPlaneOp,
|
|
197
|
+
archiveConversation: this.options.archiveConversation,
|
|
198
|
+
markDirty: this.options.markDirty,
|
|
199
|
+
conversations: this.options.conversations,
|
|
200
|
+
scheduleConversationTitlePersist: this.options.scheduleConversationTitlePersist,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
handleAddDirectoryPromptInput(input: Buffer): boolean {
|
|
205
|
+
return this.handleAddDirectoryPromptInputFrame({
|
|
206
|
+
input,
|
|
207
|
+
prompt: this.options.getAddDirectoryPrompt(),
|
|
208
|
+
isQuitShortcut: this.options.isModalDismissShortcut,
|
|
209
|
+
dismissOnOutsideClick: this.options.dismissOnOutsideClick,
|
|
210
|
+
setPrompt: this.options.setAddDirectoryPrompt,
|
|
211
|
+
markDirty: this.options.markDirty,
|
|
212
|
+
queueControlPlaneOp: this.options.queueControlPlaneOp,
|
|
213
|
+
addDirectoryByPath: this.options.addDirectoryByPath,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
routeModalInput(input: Buffer): boolean {
|
|
218
|
+
if (this.handleCommandMenuInput(input)) {
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
if (this.handleTaskEditorPromptInput(input)) {
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
if (this.handleRepositoryPromptInput(input)) {
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
if (this.handleNewThreadPromptInput(input)) {
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
if (this.handleConversationTitleEditInput(input)) {
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
if (this.handleAddDirectoryPromptInput(input)) {
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
}
|