@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,5 @@
|
|
|
1
1
|
import { measureDisplayWidth } from '../../terminal/snapshot-oracle.ts';
|
|
2
|
-
import {
|
|
2
|
+
import { SurfaceBuffer, type UiStyle } from '../../../packages/harness-ui/src/surface.ts';
|
|
3
3
|
|
|
4
4
|
interface HomeGridfireOptions {
|
|
5
5
|
readonly cols: number;
|
|
@@ -8,6 +8,7 @@ interface HomeGridfireOptions {
|
|
|
8
8
|
readonly timeMs: number;
|
|
9
9
|
readonly overlayTitle: string | null;
|
|
10
10
|
readonly overlaySubtitle: string | null;
|
|
11
|
+
readonly overlayPlacement?: 'center' | 'bottom';
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
type RgbTriplet = readonly [number, number, number];
|
|
@@ -26,25 +27,25 @@ const GRID_CHARS = {
|
|
|
26
27
|
} as const;
|
|
27
28
|
|
|
28
29
|
const FG_PALETTE: readonly RgbTriplet[] = [
|
|
29
|
-
[
|
|
30
|
-
[
|
|
31
|
-
[
|
|
32
|
-
[
|
|
33
|
-
[
|
|
34
|
-
[
|
|
35
|
-
[
|
|
36
|
-
[
|
|
30
|
+
[8, 20, 24],
|
|
31
|
+
[10, 35, 42],
|
|
32
|
+
[14, 58, 70],
|
|
33
|
+
[20, 86, 105],
|
|
34
|
+
[28, 118, 146],
|
|
35
|
+
[42, 154, 188],
|
|
36
|
+
[90, 198, 220],
|
|
37
|
+
[180, 236, 244],
|
|
37
38
|
];
|
|
38
39
|
|
|
39
40
|
const BG_PALETTE: readonly RgbTriplet[] = [
|
|
40
|
-
[
|
|
41
|
-
[
|
|
42
|
-
[
|
|
43
|
-
[
|
|
44
|
-
[
|
|
45
|
-
[
|
|
46
|
-
[
|
|
47
|
-
[
|
|
41
|
+
[2, 10, 12],
|
|
42
|
+
[4, 16, 18],
|
|
43
|
+
[6, 22, 24],
|
|
44
|
+
[8, 28, 30],
|
|
45
|
+
[10, 34, 36],
|
|
46
|
+
[12, 42, 44],
|
|
47
|
+
[14, 50, 54],
|
|
48
|
+
[18, 62, 66],
|
|
48
49
|
];
|
|
49
50
|
|
|
50
51
|
function lerp(start: number, end: number, t: number): number {
|
|
@@ -90,7 +91,7 @@ function styleFromColors(fg: RgbTriplet, bg: RgbTriplet, bold = false): UiStyle
|
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
function writeGlyph(
|
|
93
|
-
surface:
|
|
94
|
+
surface: SurfaceBuffer,
|
|
94
95
|
row: number,
|
|
95
96
|
col: number,
|
|
96
97
|
glyph: string,
|
|
@@ -198,7 +199,7 @@ function pickGridGlyph(
|
|
|
198
199
|
: GRID_CHARS.empty;
|
|
199
200
|
}
|
|
200
201
|
|
|
201
|
-
function paintBackground(surface:
|
|
202
|
+
function paintBackground(surface: SurfaceBuffer, phase: number): void {
|
|
202
203
|
for (let row = 0; row < surface.rows; row += 1) {
|
|
203
204
|
for (let col = 0; col < surface.cols; col += 1) {
|
|
204
205
|
const energy = gridEnergy(col, row, surface.cols, surface.rows, phase);
|
|
@@ -211,7 +212,7 @@ function paintBackground(surface: ReturnType<typeof createUiSurface>, phase: num
|
|
|
211
212
|
}
|
|
212
213
|
|
|
213
214
|
function paintOverlayTextRow(
|
|
214
|
-
surface:
|
|
215
|
+
surface: SurfaceBuffer,
|
|
215
216
|
row: number,
|
|
216
217
|
text: string,
|
|
217
218
|
phase: number,
|
|
@@ -242,7 +243,7 @@ function paintOverlayTextRow(
|
|
|
242
243
|
}
|
|
243
244
|
|
|
244
245
|
function paintCenteredLabel(
|
|
245
|
-
surface:
|
|
246
|
+
surface: SurfaceBuffer,
|
|
246
247
|
row: number,
|
|
247
248
|
text: string | null,
|
|
248
249
|
phase: number,
|
|
@@ -279,7 +280,7 @@ function paintCenteredLabel(
|
|
|
279
280
|
export function renderHomeGridfireAnsiRows(options: HomeGridfireOptions): readonly string[] {
|
|
280
281
|
const safeCols = Math.max(1, options.cols);
|
|
281
282
|
const safeRows = Math.max(1, options.rows);
|
|
282
|
-
const surface =
|
|
283
|
+
const surface = new SurfaceBuffer(safeCols, safeRows);
|
|
283
284
|
const phase = options.timeMs / 1000;
|
|
284
285
|
|
|
285
286
|
paintBackground(surface, phase);
|
|
@@ -289,14 +290,22 @@ export function renderHomeGridfireAnsiRows(options: HomeGridfireOptions): readon
|
|
|
289
290
|
paintOverlayTextRow(surface, row, line, phase);
|
|
290
291
|
}
|
|
291
292
|
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
options.overlaySubtitle,
|
|
298
|
-
|
|
299
|
-
|
|
293
|
+
const overlayPlacement = options.overlayPlacement ?? 'center';
|
|
294
|
+
if (overlayPlacement === 'bottom') {
|
|
295
|
+
const subtitleRow = Math.max(0, safeRows - 2);
|
|
296
|
+
const titleRow = Math.max(0, subtitleRow - 1);
|
|
297
|
+
paintCenteredLabel(surface, titleRow, options.overlayTitle, phase);
|
|
298
|
+
paintCenteredLabel(surface, subtitleRow, options.overlaySubtitle, phase);
|
|
299
|
+
} else {
|
|
300
|
+
const centerRow = Math.floor(safeRows / 2);
|
|
301
|
+
paintCenteredLabel(surface, centerRow, options.overlayTitle, phase);
|
|
302
|
+
paintCenteredLabel(
|
|
303
|
+
surface,
|
|
304
|
+
Math.min(safeRows - 1, centerRow + 2),
|
|
305
|
+
options.overlaySubtitle,
|
|
306
|
+
phase,
|
|
307
|
+
);
|
|
308
|
+
}
|
|
300
309
|
|
|
301
|
-
return
|
|
310
|
+
return surface.renderAnsiRows();
|
|
302
311
|
}
|
package/src/ui/panes/home.ts
CHANGED
|
@@ -29,6 +29,7 @@ interface HomePaneRenderInput {
|
|
|
29
29
|
readonly layout: HomePaneLayout;
|
|
30
30
|
readonly repositories: ReadonlyMap<string, TaskFocusedPaneRepositoryRecord>;
|
|
31
31
|
readonly tasks: ReadonlyMap<string, TaskFocusedPaneTaskRecord>;
|
|
32
|
+
readonly showTaskPlanningUi?: boolean;
|
|
32
33
|
readonly selectedRepositoryId: string | null;
|
|
33
34
|
readonly repositoryDropdownOpen: boolean;
|
|
34
35
|
readonly editorTarget: TaskFocusedPaneEditorTarget;
|
|
@@ -69,6 +70,7 @@ export class HomePane {
|
|
|
69
70
|
const rows = Array.from({ length: safeRows }, () => blankRow);
|
|
70
71
|
return {
|
|
71
72
|
rows,
|
|
73
|
+
plainRows: rows,
|
|
72
74
|
taskIds: Array.from({ length: safeRows }, () => null),
|
|
73
75
|
repositoryIds: Array.from({ length: safeRows }, () => null),
|
|
74
76
|
actions: Array.from({ length: safeRows }, () => null),
|
|
@@ -79,7 +81,10 @@ export class HomePane {
|
|
|
79
81
|
}
|
|
80
82
|
|
|
81
83
|
render(input: HomePaneRenderInput): TaskFocusedPaneView {
|
|
82
|
-
const
|
|
84
|
+
const showTaskPlanningUi = input.showTaskPlanningUi ?? this.showTaskPlanningUi;
|
|
85
|
+
const nowMs = this.animateBackground ? this.nowMs() : this.staticBackgroundTimeMs;
|
|
86
|
+
const cursorVisible = Math.floor(nowMs / 530) % 2 === 0;
|
|
87
|
+
const view = showTaskPlanningUi
|
|
83
88
|
? this.renderTaskFocusedPaneView({
|
|
84
89
|
repositories: input.repositories,
|
|
85
90
|
tasks: input.tasks,
|
|
@@ -92,17 +97,20 @@ export class HomePane {
|
|
|
92
97
|
cols: input.layout.rightCols,
|
|
93
98
|
rows: input.layout.paneRows,
|
|
94
99
|
scrollTop: input.scrollTop,
|
|
100
|
+
cursorVisible,
|
|
95
101
|
})
|
|
96
102
|
: this.hiddenTaskPlanningView(input.layout);
|
|
97
103
|
return {
|
|
98
104
|
...view,
|
|
105
|
+
plainRows: view.rows,
|
|
99
106
|
rows: this.renderBackgroundRows({
|
|
100
107
|
cols: input.layout.rightCols,
|
|
101
108
|
rows: input.layout.paneRows,
|
|
102
109
|
contentRows: view.rows,
|
|
103
|
-
timeMs:
|
|
110
|
+
timeMs: nowMs,
|
|
104
111
|
overlayTitle: 'GSV Sleeper Service',
|
|
105
112
|
overlaySubtitle: this.overlaySubtitle,
|
|
113
|
+
overlayPlacement: showTaskPlanningUi ? 'bottom' : 'center',
|
|
106
114
|
}),
|
|
107
115
|
};
|
|
108
116
|
}
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_UI_STYLE,
|
|
3
|
+
SurfaceBuffer,
|
|
4
|
+
type UiColor,
|
|
5
|
+
type UiStyle,
|
|
6
|
+
} from '../../../packages/harness-ui/src/surface.ts';
|
|
7
|
+
import { UiKit } from '../../../packages/harness-ui/src/kit.ts';
|
|
8
|
+
import { measureDisplayWidth } from '../../terminal/snapshot-oracle.ts';
|
|
9
|
+
import { getActiveMuxTheme } from '../mux-theme.ts';
|
|
10
|
+
|
|
11
|
+
interface NimPaneLayout {
|
|
12
|
+
readonly rightCols: number;
|
|
13
|
+
readonly paneRows: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface NimPaneRenderInput {
|
|
17
|
+
readonly layout: NimPaneLayout;
|
|
18
|
+
readonly viewModel: NimPaneViewModel;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface NimPaneRenderResult {
|
|
22
|
+
readonly rows: readonly string[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const HEADER = 'nim';
|
|
26
|
+
const COMPOSER_PROMPT = 'nim> ';
|
|
27
|
+
const USER_TRANSCRIPT_PREFIX = 'you> ';
|
|
28
|
+
const ASSISTANT_TRANSCRIPT_PREFIX = 'nim> ';
|
|
29
|
+
const uiKit = new UiKit();
|
|
30
|
+
|
|
31
|
+
export interface NimPaneViewModel {
|
|
32
|
+
readonly sessionId: string | null;
|
|
33
|
+
readonly status: 'thinking' | 'tool-calling' | 'responding' | 'idle';
|
|
34
|
+
readonly uiMode: 'debug' | 'user';
|
|
35
|
+
readonly composerText: string;
|
|
36
|
+
readonly queuedCount: number;
|
|
37
|
+
readonly transcriptLines: readonly string[];
|
|
38
|
+
readonly assistantDraftText: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class NimPane {
|
|
42
|
+
render(input: NimPaneRenderInput): NimPaneRenderResult {
|
|
43
|
+
const viewModel = input.viewModel;
|
|
44
|
+
const safeRows = Math.max(0, input.layout.paneRows);
|
|
45
|
+
const safeCols = Math.max(1, input.layout.rightCols);
|
|
46
|
+
if (safeRows === 0) {
|
|
47
|
+
return { rows: [] };
|
|
48
|
+
}
|
|
49
|
+
const theme = getActiveMuxTheme();
|
|
50
|
+
const railTheme = theme.workspaceRail;
|
|
51
|
+
const conversationTheme = theme.conversationRail;
|
|
52
|
+
const surface = new SurfaceBuffer(safeCols, safeRows, DEFAULT_UI_STYLE);
|
|
53
|
+
const backgroundStyle = withStyle(railTheme.normalStyle, {
|
|
54
|
+
bg: resolveDefaultBackgroundColor(theme),
|
|
55
|
+
});
|
|
56
|
+
for (let row = 0; row < safeRows; row += 1) {
|
|
57
|
+
surface.fillRow(row, backgroundStyle);
|
|
58
|
+
}
|
|
59
|
+
const topBandFill = withStyle(conversationTheme.headerStyle, {
|
|
60
|
+
bg: resolveTopBandBackgroundColor(theme),
|
|
61
|
+
dim: false,
|
|
62
|
+
});
|
|
63
|
+
const topBandText = withStyle(railTheme.headerStyle, {
|
|
64
|
+
bg: topBandFill.bg,
|
|
65
|
+
bold: true,
|
|
66
|
+
});
|
|
67
|
+
const bodyText = withStyle(railTheme.normalStyle, {
|
|
68
|
+
bg: backgroundStyle.bg,
|
|
69
|
+
});
|
|
70
|
+
const mutedText = withStyle(railTheme.mutedStyle, {
|
|
71
|
+
bg: backgroundStyle.bg,
|
|
72
|
+
dim: true,
|
|
73
|
+
});
|
|
74
|
+
const actionText = withStyle(railTheme.actionStyle, {
|
|
75
|
+
bg: backgroundStyle.bg,
|
|
76
|
+
bold: true,
|
|
77
|
+
});
|
|
78
|
+
const statusBadge = statusBadgeStyle(viewModel.status, railTheme.statusColors);
|
|
79
|
+
|
|
80
|
+
paintRow(surface, 0, ` ${HEADER}`, topBandText, topBandFill);
|
|
81
|
+
drawStatusChip(surface, 0, safeCols, viewModel.status, statusBadge);
|
|
82
|
+
if (safeRows > 1) {
|
|
83
|
+
const sessionLabel =
|
|
84
|
+
viewModel.sessionId === null ? 'no-session' : viewModel.sessionId.slice(0, 8);
|
|
85
|
+
paintRow(
|
|
86
|
+
surface,
|
|
87
|
+
1,
|
|
88
|
+
` session:${sessionLabel} mode:${viewModel.uiMode} queued:${String(viewModel.queuedCount)}`,
|
|
89
|
+
withStyle(railTheme.metaStyle, {
|
|
90
|
+
bg: topBandFill.bg,
|
|
91
|
+
}),
|
|
92
|
+
topBandFill,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
if (safeRows > 2) {
|
|
96
|
+
paintRow(
|
|
97
|
+
surface,
|
|
98
|
+
2,
|
|
99
|
+
' enter=send/steer tab=queue esc=abort /mode debug|user',
|
|
100
|
+
mutedText,
|
|
101
|
+
topBandFill,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
if (safeRows > 3) {
|
|
105
|
+
paintSectionDivider(surface, 3, 'transcript', mutedText, backgroundStyle);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const composerDividerRow = Math.max(0, safeRows - 2);
|
|
109
|
+
const composerRow = Math.max(0, safeRows - 1);
|
|
110
|
+
const composerFill = withStyle(railTheme.activeRowStyle, {
|
|
111
|
+
bg: resolveComposerBackgroundColor(theme),
|
|
112
|
+
});
|
|
113
|
+
paintSectionDivider(surface, composerDividerRow, 'composer', mutedText, composerFill);
|
|
114
|
+
surface.fillRow(composerRow, composerFill);
|
|
115
|
+
const promptWidth = measureDisplayWidth(COMPOSER_PROMPT);
|
|
116
|
+
surface.drawText(0, composerRow, COMPOSER_PROMPT, actionText);
|
|
117
|
+
surface.drawText(promptWidth, composerRow, viewModel.composerText, bodyText);
|
|
118
|
+
|
|
119
|
+
const transcriptStartRow = Math.min(4, safeRows - 1);
|
|
120
|
+
const transcriptEndRow = Math.max(transcriptStartRow - 1, composerDividerRow - 1);
|
|
121
|
+
const transcriptCapacity = Math.max(0, transcriptEndRow - transcriptStartRow + 1);
|
|
122
|
+
const assistantDraftRow =
|
|
123
|
+
viewModel.assistantDraftText.length > 0 ? [`nim> ${viewModel.assistantDraftText}`] : [];
|
|
124
|
+
const transcriptRows = [...viewModel.transcriptLines, ...assistantDraftRow];
|
|
125
|
+
const visibleRows =
|
|
126
|
+
transcriptCapacity === 0
|
|
127
|
+
? []
|
|
128
|
+
: transcriptRows.slice(Math.max(0, transcriptRows.length - transcriptCapacity));
|
|
129
|
+
for (let index = 0; index < visibleRows.length; index += 1) {
|
|
130
|
+
const row = visibleRows[index];
|
|
131
|
+
if (row === undefined) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const rowIndex = transcriptStartRow + index;
|
|
135
|
+
const formatted = formatTranscriptLine(row);
|
|
136
|
+
surface.fillRow(rowIndex, backgroundStyle);
|
|
137
|
+
surface.drawText(1, rowIndex, formatted.symbol, formatted.symbolStyle);
|
|
138
|
+
uiKit.paintRow(
|
|
139
|
+
surface,
|
|
140
|
+
rowIndex,
|
|
141
|
+
` ${formatted.text}`,
|
|
142
|
+
formatted.textStyle,
|
|
143
|
+
backgroundStyle,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
rows: surface.renderAnsiRows(),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function formatTranscriptLine(line: string): {
|
|
154
|
+
readonly symbol: string;
|
|
155
|
+
readonly symbolStyle: UiStyle;
|
|
156
|
+
readonly text: string;
|
|
157
|
+
readonly textStyle: UiStyle;
|
|
158
|
+
} {
|
|
159
|
+
const theme = getActiveMuxTheme();
|
|
160
|
+
const railTheme = theme.workspaceRail;
|
|
161
|
+
const bodyText = withStyle(railTheme.normalStyle, {
|
|
162
|
+
bg: resolveDefaultBackgroundColor(theme),
|
|
163
|
+
});
|
|
164
|
+
const mutedText = withStyle(railTheme.mutedStyle, {
|
|
165
|
+
bg: resolveDefaultBackgroundColor(theme),
|
|
166
|
+
dim: true,
|
|
167
|
+
});
|
|
168
|
+
const accentText = withStyle(railTheme.actionStyle, {
|
|
169
|
+
bg: resolveDefaultBackgroundColor(theme),
|
|
170
|
+
bold: false,
|
|
171
|
+
});
|
|
172
|
+
if (line.startsWith(USER_TRANSCRIPT_PREFIX)) {
|
|
173
|
+
return {
|
|
174
|
+
symbol: '›',
|
|
175
|
+
symbolStyle: accentText,
|
|
176
|
+
text: line,
|
|
177
|
+
textStyle: accentText,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
if (line.startsWith(ASSISTANT_TRANSCRIPT_PREFIX)) {
|
|
181
|
+
return {
|
|
182
|
+
symbol: '•',
|
|
183
|
+
symbolStyle: bodyText,
|
|
184
|
+
text: line,
|
|
185
|
+
textStyle: bodyText,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
if (line.startsWith('[error]')) {
|
|
189
|
+
return {
|
|
190
|
+
symbol: '!',
|
|
191
|
+
symbolStyle: withStyle(railTheme.actionStyle, {
|
|
192
|
+
fg: railTheme.statusColors.exited,
|
|
193
|
+
bg: resolveDefaultBackgroundColor(theme),
|
|
194
|
+
bold: true,
|
|
195
|
+
}),
|
|
196
|
+
text: line,
|
|
197
|
+
textStyle: withStyle(railTheme.metaStyle, {
|
|
198
|
+
fg: railTheme.statusColors.exited,
|
|
199
|
+
bg: resolveDefaultBackgroundColor(theme),
|
|
200
|
+
}),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
if (line.startsWith('[tool:')) {
|
|
204
|
+
return {
|
|
205
|
+
symbol: '↳',
|
|
206
|
+
symbolStyle: withStyle(railTheme.metaStyle, {
|
|
207
|
+
fg: railTheme.statusColors.starting,
|
|
208
|
+
bg: resolveDefaultBackgroundColor(theme),
|
|
209
|
+
}),
|
|
210
|
+
text: line,
|
|
211
|
+
textStyle: mutedText,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
symbol: '•',
|
|
216
|
+
symbolStyle: mutedText,
|
|
217
|
+
text: line,
|
|
218
|
+
textStyle: mutedText,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function paintRow(
|
|
223
|
+
surface: SurfaceBuffer,
|
|
224
|
+
row: number,
|
|
225
|
+
text: string,
|
|
226
|
+
textStyle: UiStyle,
|
|
227
|
+
fillStyle: UiStyle,
|
|
228
|
+
): void {
|
|
229
|
+
surface.fillRow(row, fillStyle);
|
|
230
|
+
uiKit.paintRow(surface, row, text, textStyle, fillStyle);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function paintSectionDivider(
|
|
234
|
+
surface: SurfaceBuffer,
|
|
235
|
+
row: number,
|
|
236
|
+
label: string,
|
|
237
|
+
textStyle: UiStyle,
|
|
238
|
+
fillStyle: UiStyle,
|
|
239
|
+
): void {
|
|
240
|
+
const divider = ` ${'-'.repeat(Math.max(0, surface.cols - label.length - 3))} ${label}`;
|
|
241
|
+
paintRow(surface, row, divider, textStyle, fillStyle);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function withStyle(base: UiStyle, overrides: Partial<UiStyle>): UiStyle {
|
|
245
|
+
return {
|
|
246
|
+
fg: overrides.fg ?? base.fg,
|
|
247
|
+
bg: overrides.bg ?? base.bg,
|
|
248
|
+
bold: overrides.bold ?? base.bold,
|
|
249
|
+
...(resolveStyleFlag(base.dim, overrides.dim) ? { dim: true } : {}),
|
|
250
|
+
...(resolveStyleFlag(base.italic, overrides.italic) ? { italic: true } : {}),
|
|
251
|
+
...(resolveStyleFlag(base.underline, overrides.underline) ? { underline: true } : {}),
|
|
252
|
+
...(resolveStyleFlag(base.inverse, overrides.inverse) ? { inverse: true } : {}),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function resolveStyleFlag(base: boolean | undefined, override: boolean | undefined): boolean {
|
|
257
|
+
if (override === undefined) {
|
|
258
|
+
return base === true;
|
|
259
|
+
}
|
|
260
|
+
return override;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function resolveDefaultBackgroundColor(theme: ReturnType<typeof getActiveMuxTheme>): UiColor {
|
|
264
|
+
const bg = theme.conversationRail.normalRowStyle.bg;
|
|
265
|
+
return bg.kind === 'default' ? theme.workspaceRail.normalStyle.bg : bg;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function resolveTopBandBackgroundColor(theme: ReturnType<typeof getActiveMuxTheme>): UiColor {
|
|
269
|
+
const bg = theme.conversationRail.headerStyle.bg;
|
|
270
|
+
return bg.kind === 'default' ? resolveDefaultBackgroundColor(theme) : bg;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function resolveComposerBackgroundColor(theme: ReturnType<typeof getActiveMuxTheme>): UiColor {
|
|
274
|
+
const bg = theme.workspaceRail.activeRowStyle.bg;
|
|
275
|
+
return bg.kind === 'default' ? resolveTopBandBackgroundColor(theme) : bg;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function statusBadgeStyle(
|
|
279
|
+
status: NimPaneViewModel['status'],
|
|
280
|
+
colors: {
|
|
281
|
+
readonly working: UiColor;
|
|
282
|
+
readonly exited: UiColor;
|
|
283
|
+
readonly needsAction: UiColor;
|
|
284
|
+
readonly starting: UiColor;
|
|
285
|
+
readonly idle: UiColor;
|
|
286
|
+
},
|
|
287
|
+
): UiStyle {
|
|
288
|
+
const color =
|
|
289
|
+
status === 'thinking'
|
|
290
|
+
? colors.starting
|
|
291
|
+
: status === 'tool-calling'
|
|
292
|
+
? colors.needsAction
|
|
293
|
+
: status === 'responding'
|
|
294
|
+
? colors.working
|
|
295
|
+
: colors.idle;
|
|
296
|
+
return {
|
|
297
|
+
fg: color,
|
|
298
|
+
bg: { kind: 'default' },
|
|
299
|
+
bold: true,
|
|
300
|
+
inverse: true,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function drawStatusChip(
|
|
305
|
+
surface: SurfaceBuffer,
|
|
306
|
+
row: number,
|
|
307
|
+
cols: number,
|
|
308
|
+
status: NimPaneViewModel['status'],
|
|
309
|
+
style: UiStyle,
|
|
310
|
+
): void {
|
|
311
|
+
const label = ` ${status} `;
|
|
312
|
+
const width = measureDisplayWidth(label);
|
|
313
|
+
const col = Math.max(0, cols - width - 1);
|
|
314
|
+
surface.drawText(col, row, label, style);
|
|
315
|
+
}
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import type { TaskPaneAction } from '../harness-core-ui.ts';
|
|
2
|
-
|
|
3
|
-
interface TaskRecordActionState {
|
|
4
|
-
readonly taskId: string;
|
|
5
|
-
readonly status: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
interface RunTaskPaneActionOptions {
|
|
9
|
-
action: TaskPaneAction;
|
|
10
|
-
openTaskCreatePrompt: () => void;
|
|
11
|
-
openRepositoryPromptForCreate: () => void;
|
|
12
|
-
selectedRepositoryId: string | null;
|
|
13
|
-
repositoryExists: (repositoryId: string) => boolean;
|
|
14
|
-
setTaskPaneNotice: (notice: string | null) => void;
|
|
15
|
-
markDirty: () => void;
|
|
16
|
-
setTaskPaneSelectionFocus: (focus: 'task' | 'repository') => void;
|
|
17
|
-
openRepositoryPromptForEdit: (repositoryId: string) => void;
|
|
18
|
-
queueArchiveRepository: (repositoryId: string) => void;
|
|
19
|
-
selectedTask: TaskRecordActionState | null;
|
|
20
|
-
openTaskEditPrompt: (taskId: string) => void;
|
|
21
|
-
queueDeleteTask: (taskId: string) => void;
|
|
22
|
-
queueTaskReady: (taskId: string) => void;
|
|
23
|
-
queueTaskDraft: (taskId: string) => void;
|
|
24
|
-
queueTaskComplete: (taskId: string) => void;
|
|
25
|
-
orderedTaskRecords: () => readonly TaskRecordActionState[];
|
|
26
|
-
queueTaskReorderByIds: (orderedTaskIds: readonly string[], label: string) => void;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function runTaskPaneAction(options: RunTaskPaneActionOptions): void {
|
|
30
|
-
if (options.action === 'task.create') {
|
|
31
|
-
options.openTaskCreatePrompt();
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
if (options.action === 'repository.create') {
|
|
35
|
-
options.setTaskPaneNotice(null);
|
|
36
|
-
options.openRepositoryPromptForCreate();
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
if (options.action === 'repository.edit') {
|
|
40
|
-
const selectedRepositoryId = options.selectedRepositoryId;
|
|
41
|
-
if (selectedRepositoryId === null || !options.repositoryExists(selectedRepositoryId)) {
|
|
42
|
-
options.setTaskPaneNotice('select a repository first');
|
|
43
|
-
options.markDirty();
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
options.setTaskPaneSelectionFocus('repository');
|
|
47
|
-
options.setTaskPaneNotice(null);
|
|
48
|
-
options.openRepositoryPromptForEdit(selectedRepositoryId);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
if (options.action === 'repository.archive') {
|
|
52
|
-
const selectedRepositoryId = options.selectedRepositoryId;
|
|
53
|
-
if (selectedRepositoryId === null || !options.repositoryExists(selectedRepositoryId)) {
|
|
54
|
-
options.setTaskPaneNotice('select a repository first');
|
|
55
|
-
options.markDirty();
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
options.setTaskPaneSelectionFocus('repository');
|
|
59
|
-
options.queueArchiveRepository(selectedRepositoryId);
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
const selected = options.selectedTask;
|
|
63
|
-
if (selected === null) {
|
|
64
|
-
options.setTaskPaneNotice('select a task first');
|
|
65
|
-
options.markDirty();
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
if (options.action === 'task.edit') {
|
|
69
|
-
options.setTaskPaneSelectionFocus('task');
|
|
70
|
-
options.openTaskEditPrompt(selected.taskId);
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
if (options.action === 'task.delete') {
|
|
74
|
-
options.setTaskPaneSelectionFocus('task');
|
|
75
|
-
options.queueDeleteTask(selected.taskId);
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
if (options.action === 'task.ready') {
|
|
79
|
-
options.setTaskPaneSelectionFocus('task');
|
|
80
|
-
options.queueTaskReady(selected.taskId);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
if (options.action === 'task.draft') {
|
|
84
|
-
options.setTaskPaneSelectionFocus('task');
|
|
85
|
-
options.queueTaskDraft(selected.taskId);
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
if (options.action === 'task.complete') {
|
|
89
|
-
options.setTaskPaneSelectionFocus('task');
|
|
90
|
-
options.queueTaskComplete(selected.taskId);
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
if (options.action === 'task.reorder-up' || options.action === 'task.reorder-down') {
|
|
94
|
-
const activeTasks = options.orderedTaskRecords().filter((task) => task.status !== 'completed');
|
|
95
|
-
const selectedIndex = activeTasks.findIndex((task) => task.taskId === selected.taskId);
|
|
96
|
-
if (selectedIndex < 0) {
|
|
97
|
-
options.setTaskPaneNotice('cannot reorder completed tasks');
|
|
98
|
-
options.markDirty();
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
const swapIndex = options.action === 'task.reorder-up' ? selectedIndex - 1 : selectedIndex + 1;
|
|
102
|
-
if (swapIndex < 0 || swapIndex >= activeTasks.length) {
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
const reordered = [...activeTasks];
|
|
106
|
-
const currentTask = reordered[selectedIndex]!;
|
|
107
|
-
reordered[selectedIndex] = reordered[swapIndex]!;
|
|
108
|
-
reordered[swapIndex] = currentTask;
|
|
109
|
-
options.setTaskPaneSelectionFocus('task');
|
|
110
|
-
options.queueTaskReorderByIds(
|
|
111
|
-
reordered.map((task) => task.taskId),
|
|
112
|
-
options.action === 'task.reorder-up' ? 'tasks-reorder-up' : 'tasks-reorder-down',
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
}
|