@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
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { StringDecoder } from 'node:string_decoder';
|
|
3
|
+
import {
|
|
4
|
+
measureDisplayWidth as measureHarnessUiDisplayWidth,
|
|
5
|
+
wrapTextForColumns as wrapHarnessUiTextForColumns,
|
|
6
|
+
} from '../../packages/harness-ui/src/text-layout.ts';
|
|
3
7
|
|
|
4
8
|
type ParserMode =
|
|
5
9
|
| 'normal'
|
|
@@ -119,11 +123,32 @@ interface InternalLine {
|
|
|
119
123
|
snapshotCacheWrapped: boolean;
|
|
120
124
|
}
|
|
121
125
|
|
|
122
|
-
const DEFAULT_COLOR: TerminalColor = { kind: 'default' };
|
|
126
|
+
const DEFAULT_COLOR: TerminalColor = Object.freeze({ kind: 'default' } as const);
|
|
123
127
|
const DEFAULT_CURSOR_STYLE: TerminalCursorStyle = {
|
|
124
128
|
shape: 'block',
|
|
125
129
|
blinking: true,
|
|
126
130
|
};
|
|
131
|
+
const DEFAULT_CELL_STYLE: Readonly<TerminalCellStyle> = Object.freeze({
|
|
132
|
+
bold: false,
|
|
133
|
+
dim: false,
|
|
134
|
+
italic: false,
|
|
135
|
+
underline: false,
|
|
136
|
+
inverse: false,
|
|
137
|
+
fg: DEFAULT_COLOR,
|
|
138
|
+
bg: DEFAULT_COLOR,
|
|
139
|
+
});
|
|
140
|
+
const DEFAULT_BLANK_CELL: Readonly<TerminalCell> = Object.freeze({
|
|
141
|
+
glyph: ' ',
|
|
142
|
+
width: 1,
|
|
143
|
+
continued: false,
|
|
144
|
+
style: DEFAULT_CELL_STYLE,
|
|
145
|
+
});
|
|
146
|
+
const DEFAULT_CONTINUATION_CELL: Readonly<TerminalCell> = Object.freeze({
|
|
147
|
+
glyph: '',
|
|
148
|
+
width: 0,
|
|
149
|
+
continued: true,
|
|
150
|
+
style: DEFAULT_CELL_STYLE,
|
|
151
|
+
});
|
|
127
152
|
|
|
128
153
|
function cloneCursorStyle(style: TerminalCursorStyle): TerminalCursorStyle {
|
|
129
154
|
return {
|
|
@@ -155,18 +180,26 @@ function cloneColor(color: TerminalColor): TerminalColor {
|
|
|
155
180
|
}
|
|
156
181
|
|
|
157
182
|
function defaultCellStyle(): TerminalCellStyle {
|
|
158
|
-
return
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
183
|
+
return DEFAULT_CELL_STYLE as TerminalCellStyle;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function isDefaultStyle(style: TerminalCellStyle): boolean {
|
|
187
|
+
return (
|
|
188
|
+
style === DEFAULT_CELL_STYLE ||
|
|
189
|
+
(!style.bold &&
|
|
190
|
+
!style.dim &&
|
|
191
|
+
!style.italic &&
|
|
192
|
+
!style.underline &&
|
|
193
|
+
!style.inverse &&
|
|
194
|
+
style.fg.kind === 'default' &&
|
|
195
|
+
style.bg.kind === 'default')
|
|
196
|
+
);
|
|
167
197
|
}
|
|
168
198
|
|
|
169
199
|
function cloneStyle(style: TerminalCellStyle): TerminalCellStyle {
|
|
200
|
+
if (isDefaultStyle(style)) {
|
|
201
|
+
return DEFAULT_CELL_STYLE as TerminalCellStyle;
|
|
202
|
+
}
|
|
170
203
|
return {
|
|
171
204
|
bold: style.bold,
|
|
172
205
|
dim: style.dim,
|
|
@@ -179,6 +212,7 @@ function cloneStyle(style: TerminalCellStyle): TerminalCellStyle {
|
|
|
179
212
|
}
|
|
180
213
|
|
|
181
214
|
function styleEqual(left: TerminalCellStyle, right: TerminalCellStyle): boolean {
|
|
215
|
+
if (left === right) return true;
|
|
182
216
|
return (
|
|
183
217
|
left.bold === right.bold &&
|
|
184
218
|
left.dim === right.dim &&
|
|
@@ -208,6 +242,9 @@ function colorEqual(left: TerminalColor, right: TerminalColor): boolean {
|
|
|
208
242
|
}
|
|
209
243
|
|
|
210
244
|
function blankCell(style: TerminalCellStyle): TerminalCell {
|
|
245
|
+
if (isDefaultStyle(style)) {
|
|
246
|
+
return DEFAULT_BLANK_CELL as TerminalCell;
|
|
247
|
+
}
|
|
211
248
|
return {
|
|
212
249
|
glyph: ' ',
|
|
213
250
|
width: 1,
|
|
@@ -217,6 +254,9 @@ function blankCell(style: TerminalCellStyle): TerminalCell {
|
|
|
217
254
|
}
|
|
218
255
|
|
|
219
256
|
function continuationCell(style: TerminalCellStyle): TerminalCell {
|
|
257
|
+
if (isDefaultStyle(style)) {
|
|
258
|
+
return DEFAULT_CONTINUATION_CELL as TerminalCell;
|
|
259
|
+
}
|
|
220
260
|
return {
|
|
221
261
|
glyph: '',
|
|
222
262
|
width: 0,
|
|
@@ -441,7 +481,16 @@ class ScreenBuffer {
|
|
|
441
481
|
if (targetCell === undefined || targetCell.continued) {
|
|
442
482
|
return;
|
|
443
483
|
}
|
|
444
|
-
targetCell
|
|
484
|
+
if (Object.isFrozen(targetCell)) {
|
|
485
|
+
line.cells[targetCol] = {
|
|
486
|
+
glyph: targetCell.glyph + combiningChar,
|
|
487
|
+
width: targetCell.width,
|
|
488
|
+
continued: targetCell.continued,
|
|
489
|
+
style: targetCell.style,
|
|
490
|
+
};
|
|
491
|
+
} else {
|
|
492
|
+
targetCell.glyph += combiningChar;
|
|
493
|
+
}
|
|
445
494
|
this.touchLine(line);
|
|
446
495
|
}
|
|
447
496
|
|
|
@@ -891,76 +940,12 @@ function parseOscRgbColor(value: string): { r: number; g: number; b: number } |
|
|
|
891
940
|
return null;
|
|
892
941
|
}
|
|
893
942
|
|
|
894
|
-
function isWideCodePoint(codePoint: number): boolean {
|
|
895
|
-
const ranges: ReadonlyArray<readonly [number, number]> = [
|
|
896
|
-
[0x1100, 0x115f],
|
|
897
|
-
[0x2329, 0x232a],
|
|
898
|
-
[0x2e80, 0xa4cf],
|
|
899
|
-
[0xac00, 0xd7a3],
|
|
900
|
-
[0xf900, 0xfaff],
|
|
901
|
-
[0xfe10, 0xfe19],
|
|
902
|
-
[0xfe30, 0xfe6f],
|
|
903
|
-
[0xff00, 0xff60],
|
|
904
|
-
[0xffe0, 0xffe6],
|
|
905
|
-
[0x1f300, 0x1faff],
|
|
906
|
-
];
|
|
907
|
-
|
|
908
|
-
for (const [start, end] of ranges) {
|
|
909
|
-
if (codePoint >= start && codePoint <= end) {
|
|
910
|
-
return true;
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
return false;
|
|
914
|
-
}
|
|
915
|
-
|
|
916
943
|
export function measureDisplayWidth(text: string): number {
|
|
917
|
-
|
|
918
|
-
for (const char of text) {
|
|
919
|
-
const codePoint = char.codePointAt(0)!;
|
|
920
|
-
|
|
921
|
-
if (codePoint < 0x20 || (codePoint >= 0x7f && codePoint < 0xa0)) {
|
|
922
|
-
continue;
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
if (/\p{Mark}/u.test(char)) {
|
|
926
|
-
continue;
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
width += isWideCodePoint(codePoint) ? 2 : 1;
|
|
930
|
-
}
|
|
931
|
-
return width;
|
|
944
|
+
return measureHarnessUiDisplayWidth(text);
|
|
932
945
|
}
|
|
933
946
|
|
|
934
947
|
export function wrapTextForColumns(text: string, cols: number): string[] {
|
|
935
|
-
|
|
936
|
-
return [''];
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
const lines: string[] = [];
|
|
940
|
-
let current = '';
|
|
941
|
-
let currentWidth = 0;
|
|
942
|
-
|
|
943
|
-
for (const char of text) {
|
|
944
|
-
if (char === '\n') {
|
|
945
|
-
lines.push(current);
|
|
946
|
-
current = '';
|
|
947
|
-
currentWidth = 0;
|
|
948
|
-
continue;
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
const charWidth = Math.max(1, measureDisplayWidth(char));
|
|
952
|
-
if (currentWidth + charWidth > cols) {
|
|
953
|
-
lines.push(current);
|
|
954
|
-
current = '';
|
|
955
|
-
currentWidth = 0;
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
current += char;
|
|
959
|
-
currentWidth += charWidth;
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
lines.push(current);
|
|
963
|
-
return lines;
|
|
948
|
+
return [...wrapHarnessUiTextForColumns(text, cols)];
|
|
964
949
|
}
|
|
965
950
|
|
|
966
951
|
function resolveIndexedColor(
|
|
@@ -982,19 +967,121 @@ function resolveIndexedColor(
|
|
|
982
967
|
};
|
|
983
968
|
}
|
|
984
969
|
|
|
970
|
+
function mutableCloneStyle(style: TerminalCellStyle): TerminalCellStyle {
|
|
971
|
+
return {
|
|
972
|
+
bold: style.bold,
|
|
973
|
+
dim: style.dim,
|
|
974
|
+
italic: style.italic,
|
|
975
|
+
underline: style.underline,
|
|
976
|
+
inverse: style.inverse,
|
|
977
|
+
fg: cloneColor(style.fg),
|
|
978
|
+
bg: cloneColor(style.bg),
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
function parseCsiParamToken(rawParams: string, start: number, end: number): number {
|
|
983
|
+
if (start >= end) {
|
|
984
|
+
return Number.NaN;
|
|
985
|
+
}
|
|
986
|
+
let value = 0;
|
|
987
|
+
for (let index = start; index < end; index += 1) {
|
|
988
|
+
const code = rawParams.charCodeAt(index);
|
|
989
|
+
if (code < 0x30 || code > 0x39) {
|
|
990
|
+
return Number(rawParams.slice(start, end));
|
|
991
|
+
}
|
|
992
|
+
value = value * 10 + (code - 0x30);
|
|
993
|
+
}
|
|
994
|
+
return value;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
function parseCsiParams(rawParams: string, startIndex = 0): number[] {
|
|
998
|
+
if (startIndex >= rawParams.length) {
|
|
999
|
+
return [];
|
|
1000
|
+
}
|
|
1001
|
+
const params: number[] = [];
|
|
1002
|
+
let tokenStart = startIndex;
|
|
1003
|
+
for (let index = startIndex; index <= rawParams.length; index += 1) {
|
|
1004
|
+
if (index < rawParams.length && rawParams.charCodeAt(index) !== 0x3b) {
|
|
1005
|
+
continue;
|
|
1006
|
+
}
|
|
1007
|
+
params.push(parseCsiParamToken(rawParams, tokenStart, index));
|
|
1008
|
+
tokenStart = index + 1;
|
|
1009
|
+
}
|
|
1010
|
+
return params;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
function isDigitsOnly(value: string): boolean {
|
|
1014
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
1015
|
+
const code = value.charCodeAt(index);
|
|
1016
|
+
if (code < 0x30 || code > 0x39) {
|
|
1017
|
+
return false;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
return true;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
function isDigitsOrSemicolons(value: string): boolean {
|
|
1024
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
1025
|
+
const code = value.charCodeAt(index);
|
|
1026
|
+
if (code === 0x3b) {
|
|
1027
|
+
continue;
|
|
1028
|
+
}
|
|
1029
|
+
if (code < 0x30 || code > 0x39) {
|
|
1030
|
+
return false;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
return true;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
function shouldDispatchCsiHook(rawParams: string, finalByte: string): boolean {
|
|
1037
|
+
if (finalByte === 'c') {
|
|
1038
|
+
return rawParams === '' || rawParams === '0' || rawParams === '>' || rawParams === '>0';
|
|
1039
|
+
}
|
|
1040
|
+
if (finalByte === 'n') {
|
|
1041
|
+
return isDigitsOnly(rawParams);
|
|
1042
|
+
}
|
|
1043
|
+
if (finalByte === 't') {
|
|
1044
|
+
return rawParams === '14' || rawParams === '16' || rawParams === '18';
|
|
1045
|
+
}
|
|
1046
|
+
if (finalByte === 'p') {
|
|
1047
|
+
return (
|
|
1048
|
+
rawParams.length >= 2 &&
|
|
1049
|
+
rawParams.charCodeAt(0) === 0x3f &&
|
|
1050
|
+
rawParams.endsWith('$') &&
|
|
1051
|
+
isDigitsOrSemicolons(rawParams.slice(1, -1))
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
1054
|
+
if (finalByte === 'q') {
|
|
1055
|
+
return rawParams === '>0';
|
|
1056
|
+
}
|
|
1057
|
+
if (finalByte === 'm') {
|
|
1058
|
+
if (rawParams === '>4' || rawParams === '>4;?') {
|
|
1059
|
+
return true;
|
|
1060
|
+
}
|
|
1061
|
+
return rawParams.startsWith('>4;') && isDigitsOnly(rawParams.slice(3));
|
|
1062
|
+
}
|
|
1063
|
+
if (finalByte === 'u') {
|
|
1064
|
+
if (rawParams === '?') {
|
|
1065
|
+
return true;
|
|
1066
|
+
}
|
|
1067
|
+
return rawParams.startsWith('>') && rawParams.length > 1 && isDigitsOnly(rawParams.slice(1));
|
|
1068
|
+
}
|
|
1069
|
+
return false;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
985
1072
|
function applySgrParams(
|
|
986
1073
|
style: TerminalCellStyle,
|
|
987
1074
|
params: number[],
|
|
988
1075
|
indexedPaletteOverrides: ReadonlyMap<number, { r: number; g: number; b: number }>,
|
|
989
1076
|
): TerminalCellStyle {
|
|
990
|
-
let nextStyle =
|
|
991
|
-
const queue = params.length === 0 ? [0] :
|
|
1077
|
+
let nextStyle = mutableCloneStyle(style);
|
|
1078
|
+
const queue = params.length === 0 ? [0] : params;
|
|
992
1079
|
|
|
993
1080
|
for (let idx = 0; idx < queue.length; idx += 1) {
|
|
994
1081
|
const param = queue[idx]!;
|
|
995
1082
|
|
|
996
1083
|
if (param === 0) {
|
|
997
|
-
nextStyle =
|
|
1084
|
+
nextStyle = mutableCloneStyle(DEFAULT_CELL_STYLE);
|
|
998
1085
|
continue;
|
|
999
1086
|
}
|
|
1000
1087
|
if (param === 1) {
|
|
@@ -1150,8 +1237,19 @@ export class TerminalSnapshotOracle {
|
|
|
1150
1237
|
|
|
1151
1238
|
ingest(chunk: string | Uint8Array): void {
|
|
1152
1239
|
const text = typeof chunk === 'string' ? chunk : this.decoder.write(Buffer.from(chunk));
|
|
1153
|
-
|
|
1154
|
-
|
|
1240
|
+
const len = text.length;
|
|
1241
|
+
let i = 0;
|
|
1242
|
+
while (i < len) {
|
|
1243
|
+
const code = text.charCodeAt(i);
|
|
1244
|
+
if (code < 0xd800 || code > 0xdfff) {
|
|
1245
|
+
this.processChar(text[i]!);
|
|
1246
|
+
i += 1;
|
|
1247
|
+
} else if (code <= 0xdbff && i + 1 < len) {
|
|
1248
|
+
this.processChar(text[i]! + text[i + 1]!);
|
|
1249
|
+
i += 2;
|
|
1250
|
+
} else {
|
|
1251
|
+
i += 1;
|
|
1252
|
+
}
|
|
1155
1253
|
}
|
|
1156
1254
|
}
|
|
1157
1255
|
|
|
@@ -1320,6 +1418,17 @@ export class TerminalSnapshotOracle {
|
|
|
1320
1418
|
return;
|
|
1321
1419
|
}
|
|
1322
1420
|
|
|
1421
|
+
// ASCII printable: width is always 1, never a combining mark
|
|
1422
|
+
if (codePoint >= 0x20 && codePoint < 0x7f) {
|
|
1423
|
+
if (this.pendingWrap) {
|
|
1424
|
+
this.currentScreen().lineFeed(this.cursor, this.style);
|
|
1425
|
+
this.cursor.col = 0;
|
|
1426
|
+
this.pendingWrap = false;
|
|
1427
|
+
}
|
|
1428
|
+
this.pendingWrap = this.currentScreen().putGlyph(this.cursor, char, 1, this.style);
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1323
1432
|
if (this.pendingWrap) {
|
|
1324
1433
|
this.currentScreen().lineFeed(this.cursor, this.style);
|
|
1325
1434
|
this.cursor.col = 0;
|
|
@@ -1419,7 +1528,7 @@ export class TerminalSnapshotOracle {
|
|
|
1419
1528
|
const rawParams = this.csiBuffer;
|
|
1420
1529
|
this.mode = 'normal';
|
|
1421
1530
|
this.csiBuffer = '';
|
|
1422
|
-
this.emitCsiQuery(
|
|
1531
|
+
this.emitCsiQuery(rawParams, finalByte);
|
|
1423
1532
|
this.applyCsi(rawParams, finalByte);
|
|
1424
1533
|
return;
|
|
1425
1534
|
}
|
|
@@ -1478,15 +1587,10 @@ export class TerminalSnapshotOracle {
|
|
|
1478
1587
|
private applyCsi(rawParams: string, finalByte: string): void {
|
|
1479
1588
|
const privateMode = rawParams.startsWith('?');
|
|
1480
1589
|
const privateKeyboardMode = rawParams.startsWith('>');
|
|
1481
|
-
const
|
|
1482
|
-
if (part.length === 0) {
|
|
1483
|
-
return NaN;
|
|
1484
|
-
}
|
|
1485
|
-
return Number(part);
|
|
1486
|
-
});
|
|
1487
|
-
const first = Number.isFinite(params[0]) ? (params[0] as number) : 1;
|
|
1590
|
+
const paramsStart = privateMode || privateKeyboardMode ? 1 : 0;
|
|
1488
1591
|
|
|
1489
1592
|
if (privateMode) {
|
|
1593
|
+
const params = parseCsiParams(rawParams, paramsStart);
|
|
1490
1594
|
if (finalByte === 'h') {
|
|
1491
1595
|
this.applyPrivateMode(params, true);
|
|
1492
1596
|
return;
|
|
@@ -1510,9 +1614,11 @@ export class TerminalSnapshotOracle {
|
|
|
1510
1614
|
return;
|
|
1511
1615
|
}
|
|
1512
1616
|
|
|
1617
|
+
const params = parseCsiParams(rawParams, paramsStart);
|
|
1618
|
+
const first = Number.isFinite(params[0]) ? (params[0] as number) : 1;
|
|
1619
|
+
|
|
1513
1620
|
if (finalByte === 'm') {
|
|
1514
|
-
|
|
1515
|
-
this.style = applySgrParams(this.style, cleaned, this.indexedPaletteOverrides);
|
|
1621
|
+
this.style = applySgrParams(this.style, params, this.indexedPaletteOverrides);
|
|
1516
1622
|
return;
|
|
1517
1623
|
}
|
|
1518
1624
|
|
|
@@ -1626,8 +1732,15 @@ export class TerminalSnapshotOracle {
|
|
|
1626
1732
|
}
|
|
1627
1733
|
}
|
|
1628
1734
|
|
|
1629
|
-
private emitCsiQuery(
|
|
1630
|
-
this.queryHooks?.onCsiQuery
|
|
1735
|
+
private emitCsiQuery(rawParams: string, finalByte: string): void {
|
|
1736
|
+
const onCsiQuery = this.queryHooks?.onCsiQuery;
|
|
1737
|
+
if (onCsiQuery === undefined) {
|
|
1738
|
+
return;
|
|
1739
|
+
}
|
|
1740
|
+
if (!shouldDispatchCsiHook(rawParams, finalByte)) {
|
|
1741
|
+
return;
|
|
1742
|
+
}
|
|
1743
|
+
onCsiQuery(`${rawParams}${finalByte}`, () => this.queryState());
|
|
1631
1744
|
}
|
|
1632
1745
|
|
|
1633
1746
|
private emitOscQuery(useBellTerminator: boolean): void {
|
package/src/ui/mux-theme.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
3
|
import type { HarnessMuxThemeConfig } from '../config/config-core.ts';
|
|
4
|
-
import type {
|
|
5
|
-
import type {
|
|
4
|
+
import type { UiModalTheme } from '../../packages/harness-ui/src/kit.ts';
|
|
5
|
+
import type { UiColor, UiStyle } from '../../packages/harness-ui/src/surface.ts';
|
|
6
6
|
import { BUILTIN_MUX_THEME_PRESETS } from './mux-theme-presets.ts';
|
|
7
7
|
|
|
8
8
|
type OpenCodeThemeMode = 'dark' | 'light';
|
|
@@ -47,11 +47,28 @@ export interface MuxWorkspaceRailTheme {
|
|
|
47
47
|
readonly statusColors: MuxWorkspaceRailStatusColors;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
interface MuxConversationRailTheme {
|
|
51
|
+
readonly headerStyle: UiStyle;
|
|
52
|
+
readonly normalRowStyle: UiStyle;
|
|
53
|
+
readonly activeRowStyle: UiStyle;
|
|
54
|
+
readonly activeIndicatorStyle: UiStyle;
|
|
55
|
+
readonly normalTextStyle: UiStyle;
|
|
56
|
+
readonly activeTextStyle: UiStyle;
|
|
57
|
+
readonly deadTextStyle: UiStyle;
|
|
58
|
+
readonly statusBadgeStyles: {
|
|
59
|
+
readonly needsInput: UiStyle;
|
|
60
|
+
readonly running: UiStyle;
|
|
61
|
+
readonly completed: UiStyle;
|
|
62
|
+
readonly exited: UiStyle;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
50
66
|
interface ActiveMuxTheme {
|
|
51
67
|
readonly name: string;
|
|
52
68
|
readonly mode: OpenCodeThemeMode;
|
|
53
69
|
readonly modalTheme: UiModalTheme;
|
|
54
70
|
readonly workspaceRail: MuxWorkspaceRailTheme;
|
|
71
|
+
readonly conversationRail: MuxConversationRailTheme;
|
|
55
72
|
readonly terminalForegroundHex: string | null;
|
|
56
73
|
readonly terminalBackgroundHex: string | null;
|
|
57
74
|
}
|
|
@@ -170,6 +187,65 @@ const LEGACY_MUX_THEME: ActiveMuxTheme = {
|
|
|
170
187
|
idle: { kind: 'indexed', index: 245 },
|
|
171
188
|
},
|
|
172
189
|
},
|
|
190
|
+
conversationRail: {
|
|
191
|
+
headerStyle: {
|
|
192
|
+
fg: { kind: 'indexed', index: 250 },
|
|
193
|
+
bg: { kind: 'indexed', index: 236 },
|
|
194
|
+
bold: false,
|
|
195
|
+
},
|
|
196
|
+
normalRowStyle: {
|
|
197
|
+
fg: { kind: 'default' },
|
|
198
|
+
bg: { kind: 'default' },
|
|
199
|
+
bold: false,
|
|
200
|
+
},
|
|
201
|
+
activeRowStyle: {
|
|
202
|
+
fg: { kind: 'indexed', index: 255 },
|
|
203
|
+
bg: { kind: 'indexed', index: 238 },
|
|
204
|
+
bold: false,
|
|
205
|
+
},
|
|
206
|
+
activeIndicatorStyle: {
|
|
207
|
+
fg: { kind: 'indexed', index: 231 },
|
|
208
|
+
bg: { kind: 'indexed', index: 238 },
|
|
209
|
+
bold: true,
|
|
210
|
+
},
|
|
211
|
+
normalTextStyle: {
|
|
212
|
+
fg: { kind: 'default' },
|
|
213
|
+
bg: { kind: 'default' },
|
|
214
|
+
bold: false,
|
|
215
|
+
},
|
|
216
|
+
activeTextStyle: {
|
|
217
|
+
fg: { kind: 'indexed', index: 255 },
|
|
218
|
+
bg: { kind: 'indexed', index: 238 },
|
|
219
|
+
bold: false,
|
|
220
|
+
},
|
|
221
|
+
deadTextStyle: {
|
|
222
|
+
fg: { kind: 'indexed', index: 245 },
|
|
223
|
+
bg: { kind: 'default' },
|
|
224
|
+
bold: false,
|
|
225
|
+
},
|
|
226
|
+
statusBadgeStyles: {
|
|
227
|
+
needsInput: {
|
|
228
|
+
fg: { kind: 'indexed', index: 231 },
|
|
229
|
+
bg: { kind: 'indexed', index: 166 },
|
|
230
|
+
bold: true,
|
|
231
|
+
},
|
|
232
|
+
running: {
|
|
233
|
+
fg: { kind: 'indexed', index: 231 },
|
|
234
|
+
bg: { kind: 'indexed', index: 24 },
|
|
235
|
+
bold: true,
|
|
236
|
+
},
|
|
237
|
+
completed: {
|
|
238
|
+
fg: { kind: 'indexed', index: 231 },
|
|
239
|
+
bg: { kind: 'indexed', index: 28 },
|
|
240
|
+
bold: true,
|
|
241
|
+
},
|
|
242
|
+
exited: {
|
|
243
|
+
fg: { kind: 'indexed', index: 250 },
|
|
244
|
+
bg: { kind: 'indexed', index: 239 },
|
|
245
|
+
bold: true,
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
},
|
|
173
249
|
terminalForegroundHex: null,
|
|
174
250
|
terminalBackgroundHex: null,
|
|
175
251
|
};
|
|
@@ -290,6 +366,15 @@ function uiStyle(fgHex: string, bgHex: string | null, bold = false): UiStyle {
|
|
|
290
366
|
};
|
|
291
367
|
}
|
|
292
368
|
|
|
369
|
+
function preferredForegroundHex(backgroundHex: string): string {
|
|
370
|
+
const normalized = normalizeHex(backgroundHex) ?? '#000000';
|
|
371
|
+
const red = Number.parseInt(normalized.slice(1, 3), 16);
|
|
372
|
+
const green = Number.parseInt(normalized.slice(3, 5), 16);
|
|
373
|
+
const blue = Number.parseInt(normalized.slice(5, 7), 16);
|
|
374
|
+
const luminance = 0.299 * red + 0.587 * green + 0.114 * blue;
|
|
375
|
+
return luminance > 150 ? '#111111' : '#f5f7fa';
|
|
376
|
+
}
|
|
377
|
+
|
|
293
378
|
function resolveThemeDocumentFromFile(
|
|
294
379
|
path: string,
|
|
295
380
|
readFile: (path: string) => string,
|
|
@@ -310,10 +395,12 @@ function resolveThemeDocumentFromFile(
|
|
|
310
395
|
}
|
|
311
396
|
|
|
312
397
|
function buildActiveTheme(name: string, input: OpenCodeThemeInput): ActiveMuxTheme {
|
|
398
|
+
const hasSelectedListItemText = input.document.theme['selectedListItemText'] !== undefined;
|
|
313
399
|
const text = themeHex(input, 'text', FALLBACK_HEX.text);
|
|
314
400
|
const textMuted = themeHex(input, 'textMuted', FALLBACK_HEX.textMuted);
|
|
315
401
|
const conceal = themeHex(input, 'conceal', FALLBACK_HEX.conceal);
|
|
316
402
|
const primary = themeHex(input, 'primary', FALLBACK_HEX.primary);
|
|
403
|
+
const accent = themeHex(input, 'accent', primary);
|
|
317
404
|
const success = themeHex(input, 'success', FALLBACK_HEX.success);
|
|
318
405
|
const error = themeHex(input, 'error', FALLBACK_HEX.error);
|
|
319
406
|
const warning = themeHex(input, 'warning', FALLBACK_HEX.warning);
|
|
@@ -321,7 +408,9 @@ function buildActiveTheme(name: string, input: OpenCodeThemeInput): ActiveMuxThe
|
|
|
321
408
|
const background = themeHex(input, 'background', FALLBACK_HEX.background);
|
|
322
409
|
const backgroundPanel = themeHex(input, 'backgroundPanel', FALLBACK_HEX.backgroundPanel);
|
|
323
410
|
const backgroundElement = themeHex(input, 'backgroundElement', FALLBACK_HEX.backgroundElement);
|
|
324
|
-
const
|
|
411
|
+
const selectedListItemText = themeHex(input, 'selectedListItemText', text);
|
|
412
|
+
const selectedForegroundFor = (backgroundHex: string): string =>
|
|
413
|
+
hasSelectedListItemText ? selectedListItemText : preferredForegroundHex(backgroundHex);
|
|
325
414
|
|
|
326
415
|
return {
|
|
327
416
|
name,
|
|
@@ -334,23 +423,38 @@ function buildActiveTheme(name: string, input: OpenCodeThemeInput): ActiveMuxThe
|
|
|
334
423
|
},
|
|
335
424
|
workspaceRail: {
|
|
336
425
|
normalStyle: uiStyle(text, null, false),
|
|
337
|
-
headerStyle: uiStyle(
|
|
426
|
+
headerStyle: uiStyle(text, null, true),
|
|
338
427
|
activeRowStyle: uiStyle(text, backgroundElement, false),
|
|
339
428
|
metaStyle: uiStyle(textMuted, null, false),
|
|
340
429
|
conversationBodyStyle: uiStyle(textMuted, null, false),
|
|
341
|
-
processStyle: uiStyle(
|
|
342
|
-
repositoryRowStyle: uiStyle(
|
|
430
|
+
processStyle: uiStyle(textMuted, null, false),
|
|
431
|
+
repositoryRowStyle: uiStyle(text, null, false),
|
|
343
432
|
mutedStyle: uiStyle(conceal, null, false),
|
|
344
433
|
shortcutStyle: uiStyle(textMuted, null, false),
|
|
345
|
-
actionStyle: uiStyle(
|
|
434
|
+
actionStyle: uiStyle(accent, backgroundElement, false),
|
|
346
435
|
statusColors: {
|
|
347
436
|
working: hexToUiColor(success),
|
|
348
437
|
exited: hexToUiColor(error),
|
|
349
438
|
needsAction: hexToUiColor(warning),
|
|
350
|
-
starting: hexToUiColor(
|
|
439
|
+
starting: hexToUiColor(info),
|
|
351
440
|
idle: hexToUiColor(conceal),
|
|
352
441
|
},
|
|
353
442
|
},
|
|
443
|
+
conversationRail: {
|
|
444
|
+
headerStyle: uiStyle(textMuted, backgroundPanel, false),
|
|
445
|
+
normalRowStyle: uiStyle(text, null, false),
|
|
446
|
+
activeRowStyle: uiStyle(text, backgroundElement, false),
|
|
447
|
+
activeIndicatorStyle: uiStyle(accent, backgroundElement, true),
|
|
448
|
+
normalTextStyle: uiStyle(text, null, false),
|
|
449
|
+
activeTextStyle: uiStyle(text, backgroundElement, false),
|
|
450
|
+
deadTextStyle: uiStyle(conceal, null, false),
|
|
451
|
+
statusBadgeStyles: {
|
|
452
|
+
needsInput: uiStyle(selectedForegroundFor(warning), warning, true),
|
|
453
|
+
running: uiStyle(selectedForegroundFor(info), info, true),
|
|
454
|
+
completed: uiStyle(selectedForegroundFor(success), success, true),
|
|
455
|
+
exited: uiStyle(selectedForegroundFor(backgroundElement), backgroundElement, true),
|
|
456
|
+
},
|
|
457
|
+
},
|
|
354
458
|
terminalForegroundHex: text.slice(1),
|
|
355
459
|
terminalBackgroundHex: background.slice(1),
|
|
356
460
|
};
|