@jmoyers/harness 0.1.11 → 0.1.20
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/README.md +31 -39
- package/package.json +31 -11
- package/packages/harness-ai/src/anthropic-protocol.ts +68 -68
- package/packages/harness-ai/src/stream-text.ts +13 -91
- package/packages/harness-ui/src/frame-primitives.ts +158 -0
- package/packages/harness-ui/src/index.ts +18 -0
- package/packages/harness-ui/src/interaction/conversation-input-forwarder.ts +221 -0
- package/packages/harness-ui/src/interaction/conversation-selection-input.ts +213 -0
- package/packages/harness-ui/src/interaction/global-shortcut-input.ts +172 -0
- package/{src/ui → packages/harness-ui/src/interaction}/input-preflight.ts +10 -12
- package/{src/ui → packages/harness-ui/src/interaction}/input-token-router.ts +120 -69
- package/packages/harness-ui/src/interaction/input.ts +420 -0
- package/packages/harness-ui/src/interaction/left-nav-input.ts +166 -0
- package/{src/ui → packages/harness-ui/src/interaction}/main-pane-pointer-input.ts +91 -23
- package/{src/ui → packages/harness-ui/src/interaction}/pointer-routing-input.ts +112 -48
- package/packages/harness-ui/src/interaction/rail-pointer-input.ts +62 -0
- package/packages/harness-ui/src/interaction/repository-fold-input.ts +118 -0
- package/packages/harness-ui/src/kit.ts +476 -0
- package/packages/harness-ui/src/layout.ts +238 -0
- package/packages/harness-ui/src/modal-manager.ts +222 -0
- package/{src/ui → packages/harness-ui/src}/screen.ts +53 -26
- package/packages/harness-ui/src/surface.ts +252 -0
- package/packages/harness-ui/src/text-layout.ts +210 -0
- package/packages/nim-core/src/contracts.ts +239 -0
- package/packages/nim-core/src/event-store.ts +299 -0
- package/packages/nim-core/src/events.ts +53 -0
- package/packages/nim-core/src/index.ts +9 -0
- package/packages/nim-core/src/provider-router.ts +129 -0
- package/packages/nim-core/src/providers/anthropic-driver.ts +291 -0
- package/packages/nim-core/src/runtime-factory.ts +49 -0
- package/packages/nim-core/src/runtime.ts +1797 -0
- package/packages/nim-core/src/session-store.ts +516 -0
- package/packages/nim-core/src/telemetry.ts +48 -0
- package/packages/nim-test-tui/src/index.ts +150 -0
- package/packages/nim-ui-core/src/index.ts +1 -0
- package/packages/nim-ui-core/src/projection.ts +87 -0
- package/scripts/codex-live-mux-runtime.ts +2 -3872
- package/scripts/control-plane-daemon.ts +11 -0
- package/scripts/harness-bin.js +5 -0
- package/scripts/harness-commands.ts +300 -0
- package/scripts/harness-runtime.ts +82 -0
- package/scripts/harness.ts +33 -3019
- package/scripts/nim-tui-smoke.ts +748 -0
- package/src/cli/auth/runtime.ts +948 -0
- package/src/cli/gateway/runtime.ts +1872 -0
- package/src/cli/parsing/flags.ts +23 -0
- package/src/cli/parsing/session.ts +42 -0
- package/src/cli/runtime/context.ts +193 -0
- package/src/cli/runtime-app/application.ts +392 -0
- package/src/cli/runtime-infra/gateway-control.ts +729 -0
- package/{scripts/harness-inspector.ts → src/cli/workflows/inspector.ts} +14 -11
- package/src/cli/workflows/runtime.ts +965 -0
- package/src/clients/tui/left-rail-interactions.ts +519 -0
- package/src/clients/tui/main-pane-interactions.ts +509 -0
- package/src/clients/tui/modal-input-routing.ts +71 -0
- package/src/clients/tui/render-snapshot-adapter.ts +88 -0
- package/src/clients/web/synced-selectors.ts +132 -0
- package/src/codex/live-session.ts +82 -29
- package/src/config/config-core.ts +348 -8
- package/src/config/harness.config.template.jsonc +33 -0
- package/src/control-plane/agent-realtime-api.ts +82 -427
- package/src/control-plane/session-summary.ts +10 -81
- package/src/control-plane/status/reducer-base.ts +12 -12
- package/src/control-plane/status/reducers/claude-status-reducer.ts +3 -3
- package/src/control-plane/status/reducers/codex-status-reducer.ts +4 -4
- package/src/control-plane/status/reducers/cursor-status-reducer.ts +3 -3
- package/src/control-plane/stream-client.ts +12 -2
- package/src/control-plane/stream-command-parser.ts +83 -143
- package/src/control-plane/stream-protocol.ts +53 -37
- package/src/control-plane/stream-server-command.ts +376 -69
- package/src/control-plane/stream-server-session-runtime.ts +3 -2
- package/src/control-plane/stream-server.ts +864 -70
- package/src/control-plane/stream-session-runtime-types.ts +41 -0
- package/src/{mux/live-mux/control-plane-records.ts → core/contracts/records.ts} +24 -97
- package/src/core/state/observed-stream-cursor.ts +43 -0
- package/src/core/state/synced-observed-state.ts +273 -0
- package/src/core/store/harness-synced-store.ts +81 -0
- package/src/diff/budget.ts +136 -0
- package/src/diff/build.ts +289 -0
- package/src/diff/chunker.ts +146 -0
- package/src/diff/git-invoke.ts +315 -0
- package/src/diff/git-parse.ts +472 -0
- package/src/diff/hash.ts +70 -0
- package/src/diff/index.ts +24 -0
- package/src/diff/normalize.ts +134 -0
- package/src/diff/types.ts +178 -0
- package/src/diff-ui/args.ts +346 -0
- package/src/diff-ui/commands.ts +123 -0
- package/src/diff-ui/finder.ts +94 -0
- package/src/diff-ui/highlight.ts +127 -0
- package/src/diff-ui/index.ts +2 -0
- package/src/diff-ui/model.ts +141 -0
- package/src/diff-ui/pager.ts +412 -0
- package/src/diff-ui/render.ts +337 -0
- package/src/diff-ui/runtime.ts +379 -0
- package/src/diff-ui/state.ts +224 -0
- package/src/diff-ui/types.ts +236 -0
- package/src/domain/workspace.ts +68 -5
- package/src/mux/control-plane-op-queue.ts +93 -7
- package/src/mux/conversation-rail.ts +28 -71
- package/src/mux/dual-pane-core.ts +13 -13
- package/src/mux/harness-core-ui.ts +313 -42
- package/src/mux/input-shortcuts.ts +13 -131
- package/src/mux/keybinding-catalog.ts +340 -0
- package/src/mux/keybinding-registry.ts +103 -0
- package/src/mux/live-mux/command-menu-open-in.ts +280 -0
- package/src/mux/live-mux/command-menu.ts +167 -4
- package/src/mux/live-mux/conversation-state.ts +13 -0
- package/src/mux/live-mux/directory-resolution.ts +1 -1
- package/src/mux/live-mux/git-snapshot.ts +33 -2
- package/src/mux/live-mux/global-shortcut-handlers.ts +6 -0
- package/src/mux/live-mux/home-pane-drop.ts +1 -1
- package/src/mux/live-mux/home-pane-pointer.ts +10 -0
- package/src/mux/live-mux/input-forwarding.ts +59 -2
- package/src/mux/live-mux/left-nav-activation.ts +124 -7
- package/src/mux/live-mux/left-nav.ts +35 -0
- package/src/mux/live-mux/link-click.ts +292 -0
- package/src/mux/live-mux/modal-command-menu-handler.ts +46 -9
- package/src/mux/live-mux/modal-conversation-handlers.ts +5 -1
- package/src/mux/live-mux/modal-input-reducers.ts +77 -12
- package/src/mux/live-mux/modal-overlays.ts +168 -34
- package/src/mux/live-mux/modal-pointer.ts +3 -7
- package/src/mux/live-mux/modal-prompt-handlers.ts +23 -2
- package/src/mux/live-mux/modal-release-notes-handler.ts +111 -0
- package/src/mux/live-mux/modal-task-editor-handler.ts +16 -11
- package/src/mux/live-mux/pointer-routing.ts +5 -2
- package/src/mux/live-mux/project-pane-pointer.ts +8 -0
- package/src/mux/live-mux/rail-layout.ts +33 -30
- package/src/mux/live-mux/release-notes.ts +383 -0
- package/src/mux/live-mux/render-trace-analysis.ts +52 -7
- package/src/mux/live-mux/repository-folding.ts +3 -0
- package/src/mux/live-mux/selection.ts +0 -4
- package/src/mux/live-mux/session-diagnostics-paths.ts +21 -0
- package/src/mux/project-pane-github-review.ts +271 -0
- package/src/mux/render-frame.ts +4 -0
- package/src/mux/runtime-app/codex-live-mux-runtime.ts +5191 -0
- package/src/mux/task-composer.ts +21 -14
- package/src/mux/task-focused-pane.ts +118 -117
- package/src/mux/task-screen-keybindings.ts +10 -101
- package/src/mux/workspace-rail-model.ts +270 -104
- package/src/mux/workspace-rail.ts +45 -22
- package/src/pty/session-broker.ts +1 -1
- package/{scripts → src/recording}/terminal-recording-gif-lib.ts +2 -2
- package/src/services/control-plane.ts +50 -32
- package/src/services/conversation-lifecycle.ts +118 -87
- package/src/services/conversation-startup-hydration.ts +20 -12
- package/src/services/directory-hydration.ts +21 -16
- package/src/services/event-persistence.ts +7 -0
- package/src/services/left-rail-pointer-handler.ts +329 -0
- package/src/services/mux-ui-state-persistence.ts +5 -1
- package/src/services/recording.ts +34 -26
- package/src/services/runtime-command-menu-agent-tools.ts +1 -1
- package/src/services/runtime-control-actions.ts +79 -61
- package/src/services/runtime-control-plane-ops.ts +122 -83
- package/src/services/runtime-conversation-actions.ts +40 -26
- package/src/services/runtime-conversation-activation.ts +73 -46
- package/src/services/runtime-conversation-starter.ts +53 -45
- package/src/services/runtime-conversation-title-edit.ts +91 -80
- package/src/services/runtime-envelope-handler.ts +107 -105
- package/src/services/runtime-git-state.ts +42 -29
- package/src/services/runtime-layout-resize.ts +3 -1
- package/src/services/runtime-left-rail-render.ts +99 -63
- package/src/services/runtime-nim-cli-session.ts +438 -0
- package/src/services/runtime-nim-session.ts +705 -0
- package/src/services/runtime-nim-tool-bridge.ts +141 -0
- package/src/services/runtime-observed-event-projection-pipeline.ts +45 -0
- package/src/services/runtime-process-wiring.ts +29 -36
- package/src/services/runtime-project-pane-github-review-cache.ts +164 -0
- package/src/services/runtime-render-flush.ts +63 -70
- package/src/services/runtime-render-lifecycle.ts +65 -64
- package/src/services/runtime-render-orchestrator.ts +55 -45
- package/src/services/runtime-render-pipeline.ts +106 -103
- package/src/services/runtime-render-state.ts +62 -49
- package/src/services/runtime-repository-actions.ts +97 -72
- package/src/services/runtime-right-pane-render.ts +80 -53
- package/src/services/runtime-shutdown.ts +38 -35
- package/src/services/runtime-stream-subscriptions.ts +35 -27
- package/src/services/runtime-task-composer-persistence.ts +71 -59
- package/src/services/runtime-task-composer-snapshot.ts +14 -0
- package/src/services/runtime-task-editor-actions.ts +46 -29
- package/src/services/runtime-task-pane-actions.ts +220 -134
- package/src/services/runtime-task-pane-shortcuts.ts +323 -123
- package/src/services/runtime-workspace-observed-effect-queue.ts +25 -0
- package/src/services/runtime-workspace-observed-events.ts +33 -184
- package/src/services/runtime-workspace-observed-transition-policy.ts +228 -0
- package/src/services/session-diagnostics-store.ts +217 -0
- package/src/services/startup-background-resume.ts +26 -21
- package/src/services/startup-orchestrator.ts +16 -13
- package/src/services/startup-paint-tracker.ts +29 -21
- package/src/services/startup-persisted-conversation-queue.ts +19 -13
- package/src/services/startup-settled-gate.ts +25 -15
- package/src/services/startup-shutdown.ts +18 -22
- package/src/services/startup-state-hydration.ts +44 -34
- package/src/services/startup-visibility.ts +12 -4
- package/src/services/task-pane-selection-actions.ts +89 -72
- package/src/services/task-planning-hydration.ts +24 -18
- package/src/services/task-planning-observed-events.ts +50 -52
- package/src/services/workspace-observed-events.ts +66 -63
- package/src/storage/storage-lifecycle-core.ts +438 -0
- package/src/store/control-plane-store-normalize.ts +33 -242
- package/src/store/control-plane-store-types.ts +1 -35
- package/src/store/control-plane-store.ts +360 -56
- package/src/store/event-store.ts +366 -8
- package/src/terminal/snapshot-oracle.ts +207 -94
- package/src/ui/mux-theme.ts +112 -8
- package/src/ui/panes/home-gridfire.ts +40 -31
- package/src/ui/panes/home.ts +10 -2
- package/src/ui/panes/nim.ts +315 -0
- package/src/mux/live-mux/actions-task.ts +0 -115
- package/src/mux/live-mux/left-rail-actions.ts +0 -118
- package/src/mux/live-mux/left-rail-conversation-click.ts +0 -85
- package/src/mux/live-mux/left-rail-pointer.ts +0 -74
- package/src/mux/live-mux/task-pane-shortcuts.ts +0 -206
- package/src/services/runtime-directory-actions.ts +0 -164
- package/src/services/runtime-input-pipeline.ts +0 -50
- package/src/services/runtime-input-router.ts +0 -195
- package/src/services/runtime-main-pane-input.ts +0 -230
- package/src/services/runtime-modal-input.ts +0 -137
- package/src/services/runtime-navigation-input.ts +0 -197
- package/src/services/runtime-rail-input.ts +0 -279
- package/src/services/runtime-task-pane.ts +0 -62
- package/src/services/runtime-workspace-actions.ts +0 -158
- package/src/ui/conversation-input-forwarder.ts +0 -114
- package/src/ui/conversation-selection-input.ts +0 -103
- package/src/ui/global-shortcut-input.ts +0 -89
- package/src/ui/input.ts +0 -269
- package/src/ui/kit.ts +0 -509
- package/src/ui/left-nav-input.ts +0 -80
- package/src/ui/left-rail-pointer-input.ts +0 -148
- package/src/ui/modals/manager.ts +0 -218
- package/src/ui/repository-fold-input.ts +0 -91
- package/src/ui/surface.ts +0 -224
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export * from './text-layout.ts';
|
|
2
|
+
export * from './surface.ts';
|
|
3
|
+
export * from './kit.ts';
|
|
4
|
+
export * from './frame-primitives.ts';
|
|
5
|
+
export * from './screen.ts';
|
|
6
|
+
export * from './modal-manager.ts';
|
|
7
|
+
export * from './layout.ts';
|
|
8
|
+
export * from './interaction/conversation-input-forwarder.ts';
|
|
9
|
+
export * from './interaction/conversation-selection-input.ts';
|
|
10
|
+
export * from './interaction/global-shortcut-input.ts';
|
|
11
|
+
export * from './interaction/input-preflight.ts';
|
|
12
|
+
export * from './interaction/input-token-router.ts';
|
|
13
|
+
export * from './interaction/input.ts';
|
|
14
|
+
export * from './interaction/left-nav-input.ts';
|
|
15
|
+
export * from './interaction/rail-pointer-input.ts';
|
|
16
|
+
export * from './interaction/main-pane-pointer-input.ts';
|
|
17
|
+
export * from './interaction/pointer-routing-input.ts';
|
|
18
|
+
export * from './interaction/repository-fold-input.ts';
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
type MainPaneMode = 'conversation' | 'project' | 'home' | 'nim';
|
|
2
|
+
|
|
3
|
+
interface ConversationSnapshotViewport {
|
|
4
|
+
readonly top: number;
|
|
5
|
+
readonly totalRows: number;
|
|
6
|
+
readonly followOutput: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ConversationSnapshotFrame {
|
|
10
|
+
readonly activeScreen: 'primary' | 'alternate';
|
|
11
|
+
readonly viewport: ConversationSnapshotViewport;
|
|
12
|
+
readonly lines: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface ConversationInputOracle<TSnapshotForInput extends ConversationSnapshotFrame> {
|
|
16
|
+
snapshotWithoutHash: () => TSnapshotForInput;
|
|
17
|
+
isMouseTrackingEnabled: () => boolean;
|
|
18
|
+
scrollViewport: (deltaRows: number) => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ConversationInputState<
|
|
22
|
+
TSnapshotForInput extends ConversationSnapshotFrame = ConversationSnapshotFrame,
|
|
23
|
+
> {
|
|
24
|
+
readonly sessionId: string;
|
|
25
|
+
readonly directoryId: string | null;
|
|
26
|
+
readonly controller: unknown | null;
|
|
27
|
+
readonly oracle: ConversationInputOracle<TSnapshotForInput>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface ConversationInputLayout {
|
|
31
|
+
readonly cols: number;
|
|
32
|
+
readonly rows: number;
|
|
33
|
+
readonly paneRows: number;
|
|
34
|
+
readonly statusRow: number;
|
|
35
|
+
readonly leftCols: number;
|
|
36
|
+
readonly rightCols: number;
|
|
37
|
+
readonly separatorCol: number;
|
|
38
|
+
readonly rightStartCol: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type ConversationInputToken =
|
|
42
|
+
| {
|
|
43
|
+
readonly kind: 'passthrough';
|
|
44
|
+
readonly text: string;
|
|
45
|
+
}
|
|
46
|
+
| {
|
|
47
|
+
readonly kind: 'mouse';
|
|
48
|
+
readonly event: {
|
|
49
|
+
readonly sequence: string;
|
|
50
|
+
readonly code: number;
|
|
51
|
+
readonly col: number;
|
|
52
|
+
readonly row: number;
|
|
53
|
+
readonly final: 'M' | 'm';
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
interface ParsedConversationInputChunk {
|
|
58
|
+
readonly tokens: readonly ConversationInputToken[];
|
|
59
|
+
readonly remainder: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface InputTokenRouterLike<
|
|
63
|
+
TConversation extends ConversationInputState<TSnapshotForInput>,
|
|
64
|
+
TSnapshotForInput extends ConversationSnapshotFrame,
|
|
65
|
+
> {
|
|
66
|
+
routeTokens(input: {
|
|
67
|
+
readonly tokens: readonly ConversationInputToken[];
|
|
68
|
+
readonly layout: ConversationInputLayout;
|
|
69
|
+
readonly conversation: TConversation | null;
|
|
70
|
+
readonly snapshotForInput: TSnapshotForInput | null;
|
|
71
|
+
}): {
|
|
72
|
+
readonly routedTokens: readonly ConversationInputToken[];
|
|
73
|
+
readonly snapshotForInput: TSnapshotForInput | null;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface RouteConversationTokensOptions<TSnapshotForInput extends ConversationSnapshotFrame> {
|
|
78
|
+
readonly tokens: readonly ConversationInputToken[];
|
|
79
|
+
readonly mainPaneMode: MainPaneMode;
|
|
80
|
+
readonly normalizeMuxKeyboardInputForPty: (input: Buffer) => Buffer;
|
|
81
|
+
readonly classifyPaneAt: (col: number, row: number) => string;
|
|
82
|
+
readonly wheelDeltaRowsFromCode: (code: number) => number | null;
|
|
83
|
+
readonly hasShiftModifier: (code: number) => boolean;
|
|
84
|
+
readonly layout: {
|
|
85
|
+
readonly paneRows: number;
|
|
86
|
+
readonly rightCols: number;
|
|
87
|
+
readonly rightStartCol: number;
|
|
88
|
+
};
|
|
89
|
+
readonly snapshotForInput: TSnapshotForInput | null;
|
|
90
|
+
readonly appMouseTrackingEnabled: boolean;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
interface RouteConversationTokensResult {
|
|
94
|
+
readonly mainPaneScrollRows: number;
|
|
95
|
+
readonly forwardToSession: readonly Buffer[];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function hasShiftModifier(code: number): boolean {
|
|
99
|
+
return (code & 0b0000_0100) !== 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function wheelDeltaRowsFromCode(code: number): number | null {
|
|
103
|
+
if ((code & 0b0100_0000) === 0) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
return (code & 0b0000_0001) === 0 ? -1 : 1;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface ConversationInputForwarderOptions<
|
|
110
|
+
TSnapshotForInput extends ConversationSnapshotFrame = ConversationSnapshotFrame,
|
|
111
|
+
TConversation extends ConversationInputState<TSnapshotForInput> =
|
|
112
|
+
ConversationInputState<TSnapshotForInput>,
|
|
113
|
+
> {
|
|
114
|
+
readonly getInputRemainder: () => string;
|
|
115
|
+
readonly setInputRemainder: (next: string) => void;
|
|
116
|
+
readonly getMainPaneMode: () => MainPaneMode;
|
|
117
|
+
readonly getLayout: () => ConversationInputLayout;
|
|
118
|
+
readonly inputTokenRouter: InputTokenRouterLike<TConversation, TSnapshotForInput>;
|
|
119
|
+
readonly getActiveConversation: () => TConversation | null;
|
|
120
|
+
readonly markDirty: () => void;
|
|
121
|
+
isControlledByLocalHuman(input: {
|
|
122
|
+
readonly conversation: TConversation;
|
|
123
|
+
readonly controllerId: string;
|
|
124
|
+
}): boolean;
|
|
125
|
+
readonly controllerId: string;
|
|
126
|
+
readonly sendInputToSession: (sessionId: string, chunk: Buffer) => void;
|
|
127
|
+
readonly noteGitActivity: (directoryId: string | null) => void;
|
|
128
|
+
parseMuxInputChunk(previousRemainder: string, chunk: Buffer): ParsedConversationInputChunk;
|
|
129
|
+
routeInputTokensForConversation(
|
|
130
|
+
options: RouteConversationTokensOptions<TSnapshotForInput>,
|
|
131
|
+
): RouteConversationTokensResult;
|
|
132
|
+
classifyPaneAt(layout: ConversationInputLayout, col: number, row: number): string;
|
|
133
|
+
normalizeMuxKeyboardInputForPty(input: Buffer): Buffer;
|
|
134
|
+
handlePassthroughTextInMainPaneMode?: (input: {
|
|
135
|
+
readonly mainPaneMode: Exclude<MainPaneMode, 'conversation'>;
|
|
136
|
+
readonly text: string;
|
|
137
|
+
}) => void;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export class ConversationInputForwarder<
|
|
141
|
+
TSnapshotForInput extends ConversationSnapshotFrame = ConversationSnapshotFrame,
|
|
142
|
+
TConversation extends ConversationInputState<TSnapshotForInput> =
|
|
143
|
+
ConversationInputState<TSnapshotForInput>,
|
|
144
|
+
> {
|
|
145
|
+
constructor(
|
|
146
|
+
private readonly options: ConversationInputForwarderOptions<TSnapshotForInput, TConversation>,
|
|
147
|
+
) {}
|
|
148
|
+
|
|
149
|
+
handleInput(input: Buffer): void {
|
|
150
|
+
const mainPaneMode = this.options.getMainPaneMode();
|
|
151
|
+
const parsed = this.options.parseMuxInputChunk(this.options.getInputRemainder(), input);
|
|
152
|
+
this.options.setInputRemainder(parsed.remainder);
|
|
153
|
+
|
|
154
|
+
const layout = this.options.getLayout();
|
|
155
|
+
const inputConversation = this.options.getActiveConversation();
|
|
156
|
+
const { routedTokens, snapshotForInput } = this.options.inputTokenRouter.routeTokens({
|
|
157
|
+
tokens: parsed.tokens,
|
|
158
|
+
layout,
|
|
159
|
+
conversation: inputConversation,
|
|
160
|
+
snapshotForInput:
|
|
161
|
+
inputConversation === null ? null : inputConversation.oracle.snapshotWithoutHash(),
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const { mainPaneScrollRows, forwardToSession } = this.options.routeInputTokensForConversation({
|
|
165
|
+
tokens: routedTokens,
|
|
166
|
+
mainPaneMode,
|
|
167
|
+
normalizeMuxKeyboardInputForPty: this.options.normalizeMuxKeyboardInputForPty,
|
|
168
|
+
classifyPaneAt: (col, row) => this.options.classifyPaneAt(layout, col, row),
|
|
169
|
+
wheelDeltaRowsFromCode,
|
|
170
|
+
hasShiftModifier,
|
|
171
|
+
layout: {
|
|
172
|
+
paneRows: layout.paneRows,
|
|
173
|
+
rightCols: layout.rightCols,
|
|
174
|
+
rightStartCol: layout.rightStartCol,
|
|
175
|
+
},
|
|
176
|
+
snapshotForInput,
|
|
177
|
+
appMouseTrackingEnabled:
|
|
178
|
+
inputConversation === null ? false : inputConversation.oracle.isMouseTrackingEnabled(),
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
if (
|
|
182
|
+
mainPaneMode !== 'conversation' &&
|
|
183
|
+
this.options.handlePassthroughTextInMainPaneMode !== undefined
|
|
184
|
+
) {
|
|
185
|
+
for (const token of routedTokens) {
|
|
186
|
+
if (token.kind !== 'passthrough' || token.text.length === 0) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
this.options.handlePassthroughTextInMainPaneMode({
|
|
190
|
+
mainPaneMode,
|
|
191
|
+
text: token.text,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (mainPaneScrollRows !== 0 && inputConversation !== null) {
|
|
197
|
+
inputConversation.oracle.scrollViewport(mainPaneScrollRows);
|
|
198
|
+
this.options.markDirty();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (inputConversation === null) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (
|
|
205
|
+
inputConversation.controller !== null &&
|
|
206
|
+
!this.options.isControlledByLocalHuman({
|
|
207
|
+
conversation: inputConversation,
|
|
208
|
+
controllerId: this.options.controllerId,
|
|
209
|
+
})
|
|
210
|
+
) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
for (const forwardChunk of forwardToSession) {
|
|
215
|
+
this.options.sendInputToSession(inputConversation.sessionId, forwardChunk);
|
|
216
|
+
}
|
|
217
|
+
if (forwardToSession.length > 0) {
|
|
218
|
+
this.options.noteGitActivity(inputConversation.directoryId);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
export interface SelectionPoint {
|
|
2
|
+
readonly rowAbs: number;
|
|
3
|
+
readonly col: number;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface PaneSelection {
|
|
7
|
+
readonly anchor: SelectionPoint;
|
|
8
|
+
readonly focus: SelectionPoint;
|
|
9
|
+
readonly text: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface PaneSelectionDrag {
|
|
13
|
+
readonly anchor: SelectionPoint;
|
|
14
|
+
readonly focus: SelectionPoint;
|
|
15
|
+
readonly hasDragged: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface SelectionLayout {
|
|
19
|
+
readonly paneRows: number;
|
|
20
|
+
readonly rightCols: number;
|
|
21
|
+
readonly rightStartCol: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ConversationSelectionSnapshotFrame {
|
|
25
|
+
readonly colorKind?: 'default' | 'indexed' | 'rgb';
|
|
26
|
+
readonly rows: number;
|
|
27
|
+
readonly cols: number;
|
|
28
|
+
readonly activeScreen: 'primary' | 'alternate';
|
|
29
|
+
readonly modes: {
|
|
30
|
+
readonly bracketedPaste: boolean;
|
|
31
|
+
readonly decMouseX10: boolean;
|
|
32
|
+
readonly decMouseButtonEvent: boolean;
|
|
33
|
+
readonly decMouseAnyEvent: boolean;
|
|
34
|
+
readonly decFocusTracking: boolean;
|
|
35
|
+
readonly decMouseSgrEncoding: boolean;
|
|
36
|
+
};
|
|
37
|
+
readonly cursor: {
|
|
38
|
+
readonly row: number;
|
|
39
|
+
readonly col: number;
|
|
40
|
+
readonly visible: boolean;
|
|
41
|
+
readonly style: {
|
|
42
|
+
readonly shape: 'block' | 'bar' | 'underline';
|
|
43
|
+
readonly blinking: boolean;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
readonly viewport: {
|
|
47
|
+
readonly top: number;
|
|
48
|
+
readonly totalRows: number;
|
|
49
|
+
readonly followOutput: boolean;
|
|
50
|
+
};
|
|
51
|
+
readonly lines: string[];
|
|
52
|
+
readonly richLines: Array<{
|
|
53
|
+
readonly wrapped: boolean;
|
|
54
|
+
readonly text: string;
|
|
55
|
+
readonly cells: Array<{
|
|
56
|
+
readonly glyph: string;
|
|
57
|
+
readonly width: number;
|
|
58
|
+
readonly continued: boolean;
|
|
59
|
+
readonly style: {
|
|
60
|
+
readonly bold: boolean;
|
|
61
|
+
readonly dim: boolean;
|
|
62
|
+
readonly italic: boolean;
|
|
63
|
+
readonly underline: boolean;
|
|
64
|
+
readonly inverse: boolean;
|
|
65
|
+
readonly fg:
|
|
66
|
+
| { readonly kind: 'default' }
|
|
67
|
+
| { readonly kind: 'indexed'; readonly index: number }
|
|
68
|
+
| { readonly kind: 'rgb'; readonly r: number; readonly g: number; readonly b: number };
|
|
69
|
+
readonly bg:
|
|
70
|
+
| { readonly kind: 'default' }
|
|
71
|
+
| { readonly kind: 'indexed'; readonly index: number }
|
|
72
|
+
| { readonly kind: 'rgb'; readonly r: number; readonly g: number; readonly b: number };
|
|
73
|
+
};
|
|
74
|
+
}>;
|
|
75
|
+
}>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function isWheelMouseCode(code: number): boolean {
|
|
79
|
+
return (code & 0b0100_0000) !== 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function hasAltModifier(code: number): boolean {
|
|
83
|
+
return (code & 0b0000_1000) !== 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function isMotionMouseCode(code: number): boolean {
|
|
87
|
+
return (code & 0b0010_0000) !== 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isLeftButtonPress(code: number, final: 'M' | 'm'): boolean {
|
|
91
|
+
if (final !== 'M') {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
if (isWheelMouseCode(code) || isMotionMouseCode(code)) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
return (code & 0b0000_0011) === 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function isMouseRelease(final: 'M' | 'm'): boolean {
|
|
101
|
+
return final === 'm';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function isSelectionDrag(code: number, final: 'M' | 'm'): boolean {
|
|
105
|
+
return final === 'M' && isMotionMouseCode(code);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface ConversationSelectionInputOptions {
|
|
109
|
+
readonly getSelection: () => PaneSelection | null;
|
|
110
|
+
readonly setSelection: (next: PaneSelection | null) => void;
|
|
111
|
+
readonly getSelectionDrag: () => PaneSelectionDrag | null;
|
|
112
|
+
readonly setSelectionDrag: (next: PaneSelectionDrag | null) => void;
|
|
113
|
+
readonly pinViewportForSelection: () => void;
|
|
114
|
+
readonly releaseViewportPinForSelection: () => void;
|
|
115
|
+
readonly markDirty: () => void;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface ReduceConversationMouseSelectionInput {
|
|
119
|
+
readonly selection: PaneSelection | null;
|
|
120
|
+
readonly selectionDrag: PaneSelectionDrag | null;
|
|
121
|
+
readonly point: SelectionPoint;
|
|
122
|
+
readonly isMainPaneTarget: boolean;
|
|
123
|
+
readonly isLeftButtonPress: boolean;
|
|
124
|
+
readonly isSelectionDrag: boolean;
|
|
125
|
+
readonly isMouseRelease: boolean;
|
|
126
|
+
readonly isWheelMouseCode: boolean;
|
|
127
|
+
readonly selectionTextForPane: (selection: PaneSelection) => string;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface ReduceConversationMouseSelectionResult {
|
|
131
|
+
readonly selection: PaneSelection | null;
|
|
132
|
+
readonly selectionDrag: PaneSelectionDrag | null;
|
|
133
|
+
readonly pinViewport: boolean;
|
|
134
|
+
readonly releaseViewportPin: boolean;
|
|
135
|
+
readonly markDirty: boolean;
|
|
136
|
+
readonly consumed: boolean;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface ConversationSelectionStrategies {
|
|
140
|
+
pointFromMouseEvent(
|
|
141
|
+
layout: SelectionLayout,
|
|
142
|
+
frame: ConversationSelectionSnapshotFrame,
|
|
143
|
+
event: { col: number; row: number },
|
|
144
|
+
): SelectionPoint;
|
|
145
|
+
reduceConversationMouseSelection(
|
|
146
|
+
options: ReduceConversationMouseSelectionInput,
|
|
147
|
+
): ReduceConversationMouseSelectionResult;
|
|
148
|
+
selectionText(frame: ConversationSelectionSnapshotFrame, selection: PaneSelection | null): string;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
interface MouseSelectionInput {
|
|
152
|
+
readonly layout: SelectionLayout;
|
|
153
|
+
readonly frame: ConversationSelectionSnapshotFrame;
|
|
154
|
+
readonly isMainPaneTarget: boolean;
|
|
155
|
+
readonly resolveSelectionText?: (selection: PaneSelection) => string;
|
|
156
|
+
readonly event: {
|
|
157
|
+
readonly col: number;
|
|
158
|
+
readonly row: number;
|
|
159
|
+
readonly code: number;
|
|
160
|
+
readonly final: 'M' | 'm';
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export class ConversationSelectionInput {
|
|
165
|
+
constructor(
|
|
166
|
+
private readonly options: ConversationSelectionInputOptions,
|
|
167
|
+
private readonly strategies: ConversationSelectionStrategies,
|
|
168
|
+
) {}
|
|
169
|
+
|
|
170
|
+
clearSelectionOnTextToken(textLength: number): boolean {
|
|
171
|
+
const hasSelection =
|
|
172
|
+
this.options.getSelection() !== null || this.options.getSelectionDrag() !== null;
|
|
173
|
+
if (textLength <= 0 || !hasSelection) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
this.options.setSelection(null);
|
|
177
|
+
this.options.setSelectionDrag(null);
|
|
178
|
+
this.options.releaseViewportPinForSelection();
|
|
179
|
+
this.options.markDirty();
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
handleMouseSelection(input: MouseSelectionInput): boolean {
|
|
184
|
+
const reduced = this.strategies.reduceConversationMouseSelection({
|
|
185
|
+
selection: this.options.getSelection(),
|
|
186
|
+
selectionDrag: this.options.getSelectionDrag(),
|
|
187
|
+
point: this.strategies.pointFromMouseEvent(input.layout, input.frame, input.event),
|
|
188
|
+
isMainPaneTarget: input.isMainPaneTarget,
|
|
189
|
+
isLeftButtonPress:
|
|
190
|
+
isLeftButtonPress(input.event.code, input.event.final) && !hasAltModifier(input.event.code),
|
|
191
|
+
isSelectionDrag:
|
|
192
|
+
isSelectionDrag(input.event.code, input.event.final) && !hasAltModifier(input.event.code),
|
|
193
|
+
isMouseRelease: isMouseRelease(input.event.final),
|
|
194
|
+
isWheelMouseCode: isWheelMouseCode(input.event.code),
|
|
195
|
+
selectionTextForPane: (nextSelection) =>
|
|
196
|
+
input.resolveSelectionText === undefined
|
|
197
|
+
? this.strategies.selectionText(input.frame, nextSelection)
|
|
198
|
+
: input.resolveSelectionText(nextSelection),
|
|
199
|
+
});
|
|
200
|
+
this.options.setSelection(reduced.selection);
|
|
201
|
+
this.options.setSelectionDrag(reduced.selectionDrag);
|
|
202
|
+
if (reduced.pinViewport) {
|
|
203
|
+
this.options.pinViewportForSelection();
|
|
204
|
+
}
|
|
205
|
+
if (reduced.releaseViewportPin) {
|
|
206
|
+
this.options.releaseViewportPinForSelection();
|
|
207
|
+
}
|
|
208
|
+
if (reduced.markDirty) {
|
|
209
|
+
this.options.markDirty();
|
|
210
|
+
}
|
|
211
|
+
return reduced.consumed;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
type ShortcutCycleDirection = 'next' | 'previous';
|
|
2
|
+
type MainPaneMode = 'conversation' | 'project' | 'home' | 'nim';
|
|
3
|
+
|
|
4
|
+
function parseNumericPrefix(value: string): number | null {
|
|
5
|
+
const parsed = Number.parseInt(value, 10);
|
|
6
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function isCtrlOnlyShortcutInput(input: Buffer): boolean {
|
|
10
|
+
if (input.length === 1) {
|
|
11
|
+
const byte = input[0]!;
|
|
12
|
+
return (
|
|
13
|
+
(byte >= 0x01 && byte <= 0x1a) ||
|
|
14
|
+
byte === 0x1c ||
|
|
15
|
+
byte === 0x1d ||
|
|
16
|
+
byte === 0x1e ||
|
|
17
|
+
byte === 0x1f
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const text = input.toString('utf8');
|
|
22
|
+
if (text.startsWith('\u001b[') && text.endsWith('u')) {
|
|
23
|
+
const kittyMatch = text.slice(2, -1).match(/^(\d+)(?::\d+)?(?:;(\d+)(?::\d+)?)?$/u);
|
|
24
|
+
if (kittyMatch !== null) {
|
|
25
|
+
const modifierCode = parseNumericPrefix(kittyMatch[2] ?? '1');
|
|
26
|
+
return modifierCode === 5;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (text.startsWith('\u001b[27;') && text.endsWith('~')) {
|
|
31
|
+
const modifyOtherKeysMatch = text.slice(2, -1).match(/^27;(\d+);(\d+)$/u);
|
|
32
|
+
if (modifyOtherKeysMatch !== null) {
|
|
33
|
+
const modifierCode = parseNumericPrefix(modifyOtherKeysMatch[1]!);
|
|
34
|
+
return modifierCode === 5;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isTerminalAgentType(agentType: string | null): boolean {
|
|
42
|
+
return agentType !== null && agentType.trim().toLowerCase() === 'terminal';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function shouldBypassCtrlOnlyShortcutInTerminalConversation(shortcut: string | null): boolean {
|
|
46
|
+
if (
|
|
47
|
+
shortcut === 'mux.conversation.archive' ||
|
|
48
|
+
shortcut === 'mux.conversation.delete' ||
|
|
49
|
+
shortcut === 'mux.conversation.next' ||
|
|
50
|
+
shortcut === 'mux.conversation.previous'
|
|
51
|
+
) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface GlobalShortcutState {
|
|
58
|
+
readonly mainPaneMode: () => MainPaneMode;
|
|
59
|
+
readonly activeConversationId: () => string | null;
|
|
60
|
+
readonly activeConversationAgentType?: () => string | null;
|
|
61
|
+
readonly resolveConversationForAction?: () => string | null;
|
|
62
|
+
readonly conversationsHas: (sessionId: string) => boolean;
|
|
63
|
+
readonly activeDirectoryId: () => string | null;
|
|
64
|
+
readonly directoryExists: (directoryId: string) => boolean;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface GlobalShortcutActions {
|
|
68
|
+
readonly requestStop: () => void;
|
|
69
|
+
readonly resolveDirectoryForAction: () => string | null;
|
|
70
|
+
readonly openNewThreadPrompt: (directoryId: string) => void;
|
|
71
|
+
readonly toggleCommandMenu: () => void;
|
|
72
|
+
readonly openOrCreateCritiqueConversationInDirectory: (directoryId: string) => Promise<void>;
|
|
73
|
+
readonly toggleGatewayProfile: () => Promise<void>;
|
|
74
|
+
readonly toggleGatewayStatusTimeline: () => Promise<void>;
|
|
75
|
+
readonly toggleGatewayRenderTrace: (conversationId: string | null) => Promise<void>;
|
|
76
|
+
readonly queueControlPlaneOp: (task: () => Promise<void>, label: string) => void;
|
|
77
|
+
readonly archiveConversation: (sessionId: string) => Promise<void>;
|
|
78
|
+
readonly refreshAllConversationTitles: () => Promise<void>;
|
|
79
|
+
readonly interruptConversation: (sessionId: string) => Promise<void>;
|
|
80
|
+
readonly takeoverConversation: (sessionId: string) => Promise<void>;
|
|
81
|
+
readonly openAddDirectoryPrompt: () => void;
|
|
82
|
+
readonly closeDirectory: (directoryId: string) => Promise<void>;
|
|
83
|
+
readonly cycleLeftNavSelection: (direction: ShortcutCycleDirection) => void;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface GlobalShortcutHandlerInput {
|
|
87
|
+
readonly shortcut: string | null;
|
|
88
|
+
readonly requestStop: () => void;
|
|
89
|
+
readonly resolveDirectoryForAction: () => string | null;
|
|
90
|
+
readonly openNewThreadPrompt: (directoryId: string) => void;
|
|
91
|
+
readonly toggleCommandMenu: () => void;
|
|
92
|
+
readonly openOrCreateCritiqueConversationInDirectory: (directoryId: string) => Promise<void>;
|
|
93
|
+
readonly toggleGatewayProfile: () => Promise<void>;
|
|
94
|
+
readonly toggleGatewayStatusTimeline: () => Promise<void>;
|
|
95
|
+
readonly toggleGatewayRenderTrace: (conversationId: string | null) => Promise<void>;
|
|
96
|
+
readonly resolveConversationForAction: () => string | null;
|
|
97
|
+
readonly conversationsHas: (sessionId: string) => boolean;
|
|
98
|
+
readonly queueControlPlaneOp: (task: () => Promise<void>, label: string) => void;
|
|
99
|
+
readonly archiveConversation: (sessionId: string) => Promise<void>;
|
|
100
|
+
readonly refreshAllConversationTitles: () => Promise<void>;
|
|
101
|
+
readonly interruptConversation: (sessionId: string) => Promise<void>;
|
|
102
|
+
readonly takeoverConversation: (sessionId: string) => Promise<void>;
|
|
103
|
+
readonly openAddDirectoryPrompt: () => void;
|
|
104
|
+
readonly resolveClosableDirectoryId: () => string | null;
|
|
105
|
+
readonly closeDirectory: (directoryId: string) => Promise<void>;
|
|
106
|
+
readonly cycleLeftNavSelection: (direction: ShortcutCycleDirection) => void;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface GlobalShortcutStrategies<TShortcutBindings> {
|
|
110
|
+
detectShortcut(input: Buffer, bindings: TShortcutBindings): string | null;
|
|
111
|
+
handleShortcut(input: GlobalShortcutHandlerInput): boolean;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export class GlobalShortcutInput<TShortcutBindings> {
|
|
115
|
+
constructor(
|
|
116
|
+
private readonly shortcutBindings: TShortcutBindings,
|
|
117
|
+
private readonly state: GlobalShortcutState,
|
|
118
|
+
private readonly actions: GlobalShortcutActions,
|
|
119
|
+
private readonly strategies: GlobalShortcutStrategies<TShortcutBindings>,
|
|
120
|
+
) {}
|
|
121
|
+
|
|
122
|
+
handleInput(input: Buffer): boolean {
|
|
123
|
+
const shortcut = this.strategies.detectShortcut(input, this.shortcutBindings);
|
|
124
|
+
if (
|
|
125
|
+
shortcut !== null &&
|
|
126
|
+
shouldBypassCtrlOnlyShortcutInTerminalConversation(shortcut) &&
|
|
127
|
+
this.state.mainPaneMode() === 'conversation' &&
|
|
128
|
+
isTerminalAgentType(
|
|
129
|
+
this.state.activeConversationAgentType === undefined
|
|
130
|
+
? null
|
|
131
|
+
: this.state.activeConversationAgentType(),
|
|
132
|
+
) &&
|
|
133
|
+
isCtrlOnlyShortcutInput(input)
|
|
134
|
+
) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
return this.strategies.handleShortcut({
|
|
138
|
+
shortcut,
|
|
139
|
+
requestStop: this.actions.requestStop,
|
|
140
|
+
resolveDirectoryForAction: this.actions.resolveDirectoryForAction,
|
|
141
|
+
openNewThreadPrompt: this.actions.openNewThreadPrompt,
|
|
142
|
+
toggleCommandMenu: this.actions.toggleCommandMenu,
|
|
143
|
+
openOrCreateCritiqueConversationInDirectory:
|
|
144
|
+
this.actions.openOrCreateCritiqueConversationInDirectory,
|
|
145
|
+
toggleGatewayProfile: this.actions.toggleGatewayProfile,
|
|
146
|
+
toggleGatewayStatusTimeline: this.actions.toggleGatewayStatusTimeline,
|
|
147
|
+
toggleGatewayRenderTrace: this.actions.toggleGatewayRenderTrace,
|
|
148
|
+
resolveConversationForAction: () =>
|
|
149
|
+
this.state.resolveConversationForAction !== undefined
|
|
150
|
+
? this.state.resolveConversationForAction()
|
|
151
|
+
: this.state.mainPaneMode() === 'conversation'
|
|
152
|
+
? this.state.activeConversationId()
|
|
153
|
+
: null,
|
|
154
|
+
conversationsHas: this.state.conversationsHas,
|
|
155
|
+
queueControlPlaneOp: this.actions.queueControlPlaneOp,
|
|
156
|
+
archiveConversation: this.actions.archiveConversation,
|
|
157
|
+
refreshAllConversationTitles: this.actions.refreshAllConversationTitles,
|
|
158
|
+
interruptConversation: this.actions.interruptConversation,
|
|
159
|
+
takeoverConversation: this.actions.takeoverConversation,
|
|
160
|
+
openAddDirectoryPrompt: this.actions.openAddDirectoryPrompt,
|
|
161
|
+
resolveClosableDirectoryId: () => {
|
|
162
|
+
const activeDirectoryId = this.state.activeDirectoryId();
|
|
163
|
+
if (this.state.mainPaneMode() !== 'project' || activeDirectoryId === null) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
return this.state.directoryExists(activeDirectoryId) ? activeDirectoryId : null;
|
|
167
|
+
},
|
|
168
|
+
closeDirectory: this.actions.closeDirectory,
|
|
169
|
+
cycleLeftNavSelection: this.actions.cycleLeftNavSelection,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
interface InputPreflightOptions {
|
|
1
|
+
export interface InputPreflightOptions {
|
|
4
2
|
readonly isShuttingDown: () => boolean;
|
|
5
3
|
readonly routeModalInput: (input: Buffer) => boolean;
|
|
6
4
|
readonly handleEscapeInput: (input: Buffer) => void;
|
|
@@ -12,19 +10,19 @@ interface InputPreflightOptions {
|
|
|
12
10
|
readonly handleCopyShortcutInput: (input: Buffer) => boolean;
|
|
13
11
|
}
|
|
14
12
|
|
|
15
|
-
interface
|
|
16
|
-
|
|
13
|
+
export interface InputPreflightStrategies {
|
|
14
|
+
extractFocusEvents(input: Buffer): {
|
|
15
|
+
readonly sanitized: Buffer;
|
|
16
|
+
readonly focusInCount: number;
|
|
17
|
+
readonly focusOutCount: number;
|
|
18
|
+
};
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
export class InputPreflight {
|
|
20
|
-
private readonly extractFocusEvents: typeof extractFocusEventsFrame;
|
|
21
|
-
|
|
22
22
|
constructor(
|
|
23
23
|
private readonly options: InputPreflightOptions,
|
|
24
|
-
|
|
25
|
-
) {
|
|
26
|
-
this.extractFocusEvents = dependencies.extractFocusEvents ?? extractFocusEventsFrame;
|
|
27
|
-
}
|
|
24
|
+
private readonly strategies: InputPreflightStrategies,
|
|
25
|
+
) {}
|
|
28
26
|
|
|
29
27
|
nextInput(input: Buffer): Buffer | null {
|
|
30
28
|
if (this.options.isShuttingDown()) {
|
|
@@ -39,7 +37,7 @@ export class InputPreflight {
|
|
|
39
37
|
return null;
|
|
40
38
|
}
|
|
41
39
|
|
|
42
|
-
const focusExtraction = this.extractFocusEvents(input);
|
|
40
|
+
const focusExtraction = this.strategies.extractFocusEvents(input);
|
|
43
41
|
if (focusExtraction.focusInCount > 0) {
|
|
44
42
|
this.options.onFocusIn();
|
|
45
43
|
}
|