@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,10 +1,16 @@
|
|
|
1
1
|
import type { ConversationRailSessionSummary } from './conversation-rail.ts';
|
|
2
|
-
import {
|
|
2
|
+
import type {
|
|
3
|
+
ProjectPaneGitHubReviewSummary,
|
|
4
|
+
ProjectPaneGitHubReviewThread,
|
|
5
|
+
} from './project-pane-github-review.ts';
|
|
6
|
+
import { UiKit } from '../../packages/harness-ui/src/kit.ts';
|
|
3
7
|
import type {
|
|
4
8
|
StreamSessionController,
|
|
5
9
|
StreamSessionDisplayPhase,
|
|
6
10
|
} from '../control-plane/stream-protocol.ts';
|
|
7
11
|
|
|
12
|
+
const UI_KIT = new UiKit();
|
|
13
|
+
|
|
8
14
|
interface WorkspaceRailGitSummary {
|
|
9
15
|
readonly branch: string;
|
|
10
16
|
readonly changedFiles: number;
|
|
@@ -61,17 +67,25 @@ interface WorkspaceRailModel {
|
|
|
61
67
|
readonly directories: readonly WorkspaceRailDirectorySummary[];
|
|
62
68
|
readonly conversations: readonly WorkspaceRailConversationSummary[];
|
|
63
69
|
readonly processes: readonly WorkspaceRailProcessSummary[];
|
|
70
|
+
readonly showGitHubIntegration?: boolean;
|
|
71
|
+
readonly visibleGitHubDirectoryKeys?: ReadonlySet<string> | readonly string[];
|
|
72
|
+
readonly expandedGitHubDirectoryKeys?: ReadonlySet<string> | readonly string[];
|
|
73
|
+
readonly githubReviewByDirectoryKey?: ReadonlyMap<string, ProjectPaneGitHubReviewSummary>;
|
|
64
74
|
readonly showTaskPlanningUi?: boolean;
|
|
75
|
+
readonly showNimEntry?: boolean;
|
|
76
|
+
readonly showTasksEntry?: boolean;
|
|
65
77
|
readonly activeProjectId: string | null;
|
|
78
|
+
readonly activeGitHubProjectId?: string | null;
|
|
66
79
|
readonly activeRepositoryId?: string | null;
|
|
67
80
|
readonly activeConversationId: string | null;
|
|
68
81
|
readonly projectSelectionEnabled?: boolean;
|
|
82
|
+
readonly githubSelectionEnabled?: boolean;
|
|
69
83
|
readonly repositorySelectionEnabled?: boolean;
|
|
70
84
|
readonly homeSelectionEnabled?: boolean;
|
|
85
|
+
readonly nimSelectionEnabled?: boolean;
|
|
86
|
+
readonly tasksSelectionEnabled?: boolean;
|
|
71
87
|
readonly repositoriesCollapsed?: boolean;
|
|
72
88
|
readonly collapsedRepositoryGroupIds?: readonly string[];
|
|
73
|
-
readonly shortcutHint?: string;
|
|
74
|
-
readonly shortcutsCollapsed?: boolean;
|
|
75
89
|
readonly nowMs?: number;
|
|
76
90
|
}
|
|
77
91
|
|
|
@@ -83,10 +97,10 @@ interface WorkspaceRailViewRow {
|
|
|
83
97
|
| 'conversation-body'
|
|
84
98
|
| 'process-title'
|
|
85
99
|
| 'process-meta'
|
|
100
|
+
| 'github-header'
|
|
101
|
+
| 'github-detail'
|
|
86
102
|
| 'repository-header'
|
|
87
103
|
| 'repository-row'
|
|
88
|
-
| 'shortcut-header'
|
|
89
|
-
| 'shortcut-body'
|
|
90
104
|
| 'action'
|
|
91
105
|
| 'muted';
|
|
92
106
|
readonly text: string;
|
|
@@ -100,7 +114,7 @@ interface WorkspaceRailViewRow {
|
|
|
100
114
|
|
|
101
115
|
const NEW_THREAD_INLINE_LABEL = '[+ thread]';
|
|
102
116
|
const UNTRACKED_REPOSITORY_GROUP_ID = 'untracked';
|
|
103
|
-
const ADD_PROJECT_BUTTON_LABEL =
|
|
117
|
+
const ADD_PROJECT_BUTTON_LABEL = UI_KIT.formatButton({
|
|
104
118
|
label: 'add project',
|
|
105
119
|
prefixIcon: '>',
|
|
106
120
|
});
|
|
@@ -110,8 +124,11 @@ type WorkspaceRailAction =
|
|
|
110
124
|
| 'conversation.delete'
|
|
111
125
|
| 'project.add'
|
|
112
126
|
| 'home.open'
|
|
127
|
+
| 'nim.open'
|
|
128
|
+
| 'tasks.open'
|
|
113
129
|
| 'project.close'
|
|
114
|
-
| '
|
|
130
|
+
| 'project.github.open'
|
|
131
|
+
| 'project.github.toggle'
|
|
115
132
|
| 'repository.toggle'
|
|
116
133
|
| 'repository.add'
|
|
117
134
|
| 'repository.edit'
|
|
@@ -243,6 +260,60 @@ function trackedProjectGitSuffix(git: WorkspaceRailGitSummary): string {
|
|
|
243
260
|
return ` (${git.branch}:+${String(git.additions)},-${String(git.deletions)})`;
|
|
244
261
|
}
|
|
245
262
|
|
|
263
|
+
function githubReviewCommentCount(threads: readonly ProjectPaneGitHubReviewThread[]): number {
|
|
264
|
+
let total = 0;
|
|
265
|
+
for (const thread of threads) {
|
|
266
|
+
total += thread.comments.length;
|
|
267
|
+
}
|
|
268
|
+
return total;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function githubPrLifecycleLabel(pr: NonNullable<ProjectPaneGitHubReviewSummary['pr']>): string {
|
|
272
|
+
if (pr.isDraft || pr.state === 'draft') {
|
|
273
|
+
return 'draft';
|
|
274
|
+
}
|
|
275
|
+
if (pr.state === 'merged') {
|
|
276
|
+
return 'merged';
|
|
277
|
+
}
|
|
278
|
+
if (pr.state === 'closed') {
|
|
279
|
+
return 'closed';
|
|
280
|
+
}
|
|
281
|
+
return 'open';
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function githubRailSummarySuffix(review: ProjectPaneGitHubReviewSummary | null): string {
|
|
285
|
+
if (review === null) {
|
|
286
|
+
return '(not loaded)';
|
|
287
|
+
}
|
|
288
|
+
if (review.status === 'loading') {
|
|
289
|
+
return '(loading)';
|
|
290
|
+
}
|
|
291
|
+
if (review.status === 'error') {
|
|
292
|
+
return '(error)';
|
|
293
|
+
}
|
|
294
|
+
if (review.pr === null) {
|
|
295
|
+
return '(no pr)';
|
|
296
|
+
}
|
|
297
|
+
const unresolvedCommentCount = githubReviewCommentCount(review.openThreads);
|
|
298
|
+
const detailParts = [
|
|
299
|
+
`#${String(review.pr.number)} ${githubPrLifecycleLabel(review.pr)}`,
|
|
300
|
+
`unresolved ${String(unresolvedCommentCount)}`,
|
|
301
|
+
];
|
|
302
|
+
if (review.pr.ciRollup === 'failure') {
|
|
303
|
+
detailParts.push('ci failed');
|
|
304
|
+
}
|
|
305
|
+
return `(${detailParts.join(', ')})`;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function sanitizeInlineText(value: string): string {
|
|
309
|
+
return value.replace(/\s+/gu, ' ').trim();
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function formatAuthor(login: string | null): string {
|
|
313
|
+
const normalized = login?.trim() ?? '';
|
|
314
|
+
return normalized.length > 0 ? `@${normalized}` : '@unknown';
|
|
315
|
+
}
|
|
316
|
+
|
|
246
317
|
function conversationDisplayTitle(conversation: WorkspaceRailConversationSummary): string {
|
|
247
318
|
const title = conversation.title.trim();
|
|
248
319
|
if (title.length === 0) {
|
|
@@ -280,8 +351,29 @@ function buildContentRows(
|
|
|
280
351
|
): readonly WorkspaceRailViewRow[] {
|
|
281
352
|
const rows: WorkspaceRailViewRow[] = [];
|
|
282
353
|
const showTaskPlanningUi = model.showTaskPlanningUi ?? true;
|
|
354
|
+
const showNimEntry = model.showNimEntry ?? true;
|
|
355
|
+
const showGitHubIntegration = model.showGitHubIntegration ?? false;
|
|
356
|
+
const visibleGitHubDirectoryKeys =
|
|
357
|
+
model.visibleGitHubDirectoryKeys === undefined
|
|
358
|
+
? new Set<string>()
|
|
359
|
+
: model.visibleGitHubDirectoryKeys instanceof Set
|
|
360
|
+
? model.visibleGitHubDirectoryKeys
|
|
361
|
+
: new Set(model.visibleGitHubDirectoryKeys);
|
|
362
|
+
const expandedGitHubDirectoryKeys =
|
|
363
|
+
model.expandedGitHubDirectoryKeys === undefined
|
|
364
|
+
? new Set<string>()
|
|
365
|
+
: model.expandedGitHubDirectoryKeys instanceof Set
|
|
366
|
+
? model.expandedGitHubDirectoryKeys
|
|
367
|
+
: new Set(model.expandedGitHubDirectoryKeys);
|
|
368
|
+
const githubReviewByDirectoryKey =
|
|
369
|
+
model.githubReviewByDirectoryKey ?? new Map<string, ProjectPaneGitHubReviewSummary>();
|
|
370
|
+
const showTasksEntry = model.showTasksEntry ?? showTaskPlanningUi;
|
|
283
371
|
const homeSelectionEnabled = model.homeSelectionEnabled ?? false;
|
|
372
|
+
const nimSelectionEnabled = model.nimSelectionEnabled ?? false;
|
|
373
|
+
const tasksSelectionEnabled = model.tasksSelectionEnabled ?? false;
|
|
284
374
|
const projectSelectionEnabled = model.projectSelectionEnabled ?? false;
|
|
375
|
+
const githubSelectionEnabled = model.githubSelectionEnabled ?? false;
|
|
376
|
+
const activeGitHubProjectId = model.activeGitHubProjectId ?? null;
|
|
285
377
|
const repositorySelectionEnabled = model.repositorySelectionEnabled ?? false;
|
|
286
378
|
const collapsedRepositoryGroupIds = new Set(model.collapsedRepositoryGroupIds ?? []);
|
|
287
379
|
const repositoryById = new Map(
|
|
@@ -367,6 +459,21 @@ function buildContentRows(
|
|
|
367
459
|
|
|
368
460
|
if (showTaskPlanningUi) {
|
|
369
461
|
pushRow(rows, 'dir-header', '├─ 🏠 home', homeSelectionEnabled, null, null, null, 'home.open');
|
|
462
|
+
if (showNimEntry) {
|
|
463
|
+
pushRow(rows, 'dir-header', '├─ 🦎 nim', nimSelectionEnabled, null, null, null, 'nim.open');
|
|
464
|
+
}
|
|
465
|
+
if (showTasksEntry) {
|
|
466
|
+
pushRow(
|
|
467
|
+
rows,
|
|
468
|
+
'dir-header',
|
|
469
|
+
'├─ 🗂️ tasks',
|
|
470
|
+
tasksSelectionEnabled,
|
|
471
|
+
null,
|
|
472
|
+
null,
|
|
473
|
+
null,
|
|
474
|
+
'tasks.open',
|
|
475
|
+
);
|
|
476
|
+
}
|
|
370
477
|
}
|
|
371
478
|
|
|
372
479
|
if (orderedRepositoryGroupIds.length === 0) {
|
|
@@ -424,6 +531,132 @@ function buildContentRows(
|
|
|
424
531
|
const conversations = model.conversations.filter(
|
|
425
532
|
(conversation) => conversation.directoryKey === directory.key,
|
|
426
533
|
);
|
|
534
|
+
const processes = model.processes.filter((process) => process.directoryKey === directory.key);
|
|
535
|
+
|
|
536
|
+
const githubVisibleForDirectory =
|
|
537
|
+
visibleGitHubDirectoryKeys.has(directory.key) ||
|
|
538
|
+
(githubSelectionEnabled && directory.key === activeGitHubProjectId);
|
|
539
|
+
if (showGitHubIntegration && group.tracked && githubVisibleForDirectory) {
|
|
540
|
+
const githubReview = githubReviewByDirectoryKey.get(directory.key) ?? null;
|
|
541
|
+
const githubSelected = githubSelectionEnabled && directory.key === activeGitHubProjectId;
|
|
542
|
+
const githubExpanded = expandedGitHubDirectoryKeys.has(directory.key);
|
|
543
|
+
const githubTreePrefix = `${projectChildPrefix}├─ `;
|
|
544
|
+
const githubDetailPrefix = `${projectChildPrefix}│ `;
|
|
545
|
+
pushRow(
|
|
546
|
+
rows,
|
|
547
|
+
'github-header',
|
|
548
|
+
`${githubTreePrefix}${githubExpanded ? '▼' : '▶'} github pr ${githubRailSummarySuffix(
|
|
549
|
+
githubReview,
|
|
550
|
+
)}`,
|
|
551
|
+
githubSelected,
|
|
552
|
+
null,
|
|
553
|
+
directory.key,
|
|
554
|
+
repositoryId,
|
|
555
|
+
'project.github.open',
|
|
556
|
+
);
|
|
557
|
+
if (githubExpanded) {
|
|
558
|
+
if (githubReview === null) {
|
|
559
|
+
pushRow(
|
|
560
|
+
rows,
|
|
561
|
+
'github-detail',
|
|
562
|
+
`${githubDetailPrefix}status not loaded`,
|
|
563
|
+
githubSelected,
|
|
564
|
+
null,
|
|
565
|
+
directory.key,
|
|
566
|
+
repositoryId,
|
|
567
|
+
null,
|
|
568
|
+
);
|
|
569
|
+
} else if (githubReview.status === 'loading') {
|
|
570
|
+
pushRow(
|
|
571
|
+
rows,
|
|
572
|
+
'github-detail',
|
|
573
|
+
`${githubDetailPrefix}status loading GitHub review data…`,
|
|
574
|
+
githubSelected,
|
|
575
|
+
null,
|
|
576
|
+
directory.key,
|
|
577
|
+
repositoryId,
|
|
578
|
+
null,
|
|
579
|
+
);
|
|
580
|
+
} else if (githubReview.status === 'error') {
|
|
581
|
+
const message =
|
|
582
|
+
githubReview.errorMessage === null
|
|
583
|
+
? 'unknown error'
|
|
584
|
+
: sanitizeInlineText(githubReview.errorMessage);
|
|
585
|
+
pushRow(
|
|
586
|
+
rows,
|
|
587
|
+
'github-detail',
|
|
588
|
+
`${githubDetailPrefix}status error ${message}`,
|
|
589
|
+
githubSelected,
|
|
590
|
+
null,
|
|
591
|
+
directory.key,
|
|
592
|
+
repositoryId,
|
|
593
|
+
null,
|
|
594
|
+
);
|
|
595
|
+
} else if (githubReview.pr === null) {
|
|
596
|
+
const branchName = githubReview.branchName?.trim() ?? '';
|
|
597
|
+
pushRow(
|
|
598
|
+
rows,
|
|
599
|
+
'github-detail',
|
|
600
|
+
`${githubDetailPrefix}branch ${branchName.length > 0 ? branchName : '(none)'}`,
|
|
601
|
+
githubSelected,
|
|
602
|
+
null,
|
|
603
|
+
directory.key,
|
|
604
|
+
repositoryId,
|
|
605
|
+
null,
|
|
606
|
+
);
|
|
607
|
+
pushRow(
|
|
608
|
+
rows,
|
|
609
|
+
'github-detail',
|
|
610
|
+
`${githubDetailPrefix}no pull request for tracked branch`,
|
|
611
|
+
githubSelected,
|
|
612
|
+
null,
|
|
613
|
+
directory.key,
|
|
614
|
+
repositoryId,
|
|
615
|
+
null,
|
|
616
|
+
);
|
|
617
|
+
} else {
|
|
618
|
+
const pr = githubReview.pr;
|
|
619
|
+
pushRow(
|
|
620
|
+
rows,
|
|
621
|
+
'github-detail',
|
|
622
|
+
`${githubDetailPrefix}pr #${String(pr.number)} ${githubPrLifecycleLabel(pr)} ${sanitizeInlineText(pr.title)}`,
|
|
623
|
+
githubSelected,
|
|
624
|
+
null,
|
|
625
|
+
directory.key,
|
|
626
|
+
repositoryId,
|
|
627
|
+
null,
|
|
628
|
+
);
|
|
629
|
+
pushRow(
|
|
630
|
+
rows,
|
|
631
|
+
'github-detail',
|
|
632
|
+
`${githubDetailPrefix}from ${pr.headBranch} -> ${pr.baseBranch} by ${formatAuthor(
|
|
633
|
+
pr.authorLogin,
|
|
634
|
+
)}`,
|
|
635
|
+
githubSelected,
|
|
636
|
+
null,
|
|
637
|
+
directory.key,
|
|
638
|
+
repositoryId,
|
|
639
|
+
null,
|
|
640
|
+
);
|
|
641
|
+
pushRow(
|
|
642
|
+
rows,
|
|
643
|
+
'github-detail',
|
|
644
|
+
`${githubDetailPrefix}threads ${String(githubReview.openThreads.length)} open / ${String(
|
|
645
|
+
githubReview.resolvedThreads.length,
|
|
646
|
+
)} resolved (${String(
|
|
647
|
+
githubReviewCommentCount(githubReview.openThreads) +
|
|
648
|
+
githubReviewCommentCount(githubReview.resolvedThreads),
|
|
649
|
+
)} comments)`,
|
|
650
|
+
githubSelected,
|
|
651
|
+
null,
|
|
652
|
+
directory.key,
|
|
653
|
+
repositoryId,
|
|
654
|
+
null,
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
427
660
|
for (
|
|
428
661
|
let conversationIndex = 0;
|
|
429
662
|
conversationIndex < conversations.length;
|
|
@@ -434,6 +667,8 @@ function buildContentRows(
|
|
|
434
667
|
const active =
|
|
435
668
|
!projectSelectionEnabled &&
|
|
436
669
|
!homeSelectionEnabled &&
|
|
670
|
+
!nimSelectionEnabled &&
|
|
671
|
+
!tasksSelectionEnabled &&
|
|
437
672
|
!repositorySelectionEnabled &&
|
|
438
673
|
conversation.sessionId === model.activeConversationId;
|
|
439
674
|
const projection = projectWorkspaceRailConversation(conversation, {
|
|
@@ -472,8 +707,6 @@ function buildContentRows(
|
|
|
472
707
|
);
|
|
473
708
|
}
|
|
474
709
|
}
|
|
475
|
-
|
|
476
|
-
const processes = model.processes.filter((process) => process.directoryKey === directory.key);
|
|
477
710
|
for (const process of processes) {
|
|
478
711
|
pushRow(
|
|
479
712
|
rows,
|
|
@@ -502,70 +735,6 @@ function buildContentRows(
|
|
|
502
735
|
return rows;
|
|
503
736
|
}
|
|
504
737
|
|
|
505
|
-
function shortcutDescriptionRows(shortcutHint: string | undefined): readonly string[] {
|
|
506
|
-
const normalized = shortcutHint?.trim();
|
|
507
|
-
if (normalized === undefined || normalized.length === 0) {
|
|
508
|
-
return [
|
|
509
|
-
'ctrl+t new thread',
|
|
510
|
-
'ctrl+g critique thread',
|
|
511
|
-
'ctrl+x archive thread',
|
|
512
|
-
'ctrl+l take over thread',
|
|
513
|
-
'ctrl+o add project',
|
|
514
|
-
'ctrl+w close project',
|
|
515
|
-
'ctrl+j/k switch nav',
|
|
516
|
-
'→ expand repo',
|
|
517
|
-
'← collapse repo',
|
|
518
|
-
'ctrl+k ctrl+j expand all repos',
|
|
519
|
-
'ctrl+k ctrl+0 collapse all repos',
|
|
520
|
-
'ctrl+c quit mux',
|
|
521
|
-
];
|
|
522
|
-
}
|
|
523
|
-
if (normalized.includes('\n')) {
|
|
524
|
-
return normalized
|
|
525
|
-
.split('\n')
|
|
526
|
-
.map((line) => line.trim())
|
|
527
|
-
.filter((line) => line.length > 0);
|
|
528
|
-
}
|
|
529
|
-
return normalized
|
|
530
|
-
.split(/\s{2,}/u)
|
|
531
|
-
.map((segment) => segment.trim())
|
|
532
|
-
.filter((segment) => segment.length > 0);
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
function shortcutRows(
|
|
536
|
-
shortcutHint: string | undefined,
|
|
537
|
-
shortcutsCollapsed: boolean,
|
|
538
|
-
): readonly WorkspaceRailViewRow[] {
|
|
539
|
-
const rows: WorkspaceRailViewRow[] = [
|
|
540
|
-
{
|
|
541
|
-
kind: 'shortcut-header',
|
|
542
|
-
text: `├─ shortcuts ${shortcutsCollapsed ? '[+]' : '[-]'}`,
|
|
543
|
-
active: false,
|
|
544
|
-
conversationSessionId: null,
|
|
545
|
-
directoryKey: null,
|
|
546
|
-
repositoryId: null,
|
|
547
|
-
railAction: 'shortcuts.toggle',
|
|
548
|
-
conversationStatus: null,
|
|
549
|
-
},
|
|
550
|
-
];
|
|
551
|
-
if (!shortcutsCollapsed) {
|
|
552
|
-
const descriptions = shortcutDescriptionRows(shortcutHint);
|
|
553
|
-
for (const description of descriptions) {
|
|
554
|
-
rows.push({
|
|
555
|
-
kind: 'shortcut-body',
|
|
556
|
-
text: `│ ${description}`,
|
|
557
|
-
active: false,
|
|
558
|
-
conversationSessionId: null,
|
|
559
|
-
directoryKey: null,
|
|
560
|
-
repositoryId: null,
|
|
561
|
-
railAction: null,
|
|
562
|
-
conversationStatus: null,
|
|
563
|
-
});
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
return rows;
|
|
567
|
-
}
|
|
568
|
-
|
|
569
738
|
export function buildWorkspaceRailViewRows(
|
|
570
739
|
model: WorkspaceRailModel,
|
|
571
740
|
maxRows: number,
|
|
@@ -573,15 +742,8 @@ export function buildWorkspaceRailViewRows(
|
|
|
573
742
|
const safeRows = Math.max(1, maxRows);
|
|
574
743
|
const nowMs = model.nowMs ?? Date.now();
|
|
575
744
|
const contentRows = buildContentRows(model, nowMs);
|
|
576
|
-
const
|
|
577
|
-
|
|
578
|
-
if (safeRows <= renderedShortcuts.length) {
|
|
579
|
-
return renderedShortcuts.slice(renderedShortcuts.length - safeRows);
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
const contentCapacity = safeRows - renderedShortcuts.length;
|
|
583
|
-
const rows: WorkspaceRailViewRow[] = [...contentRows.slice(0, Math.max(0, contentCapacity - 1))];
|
|
584
|
-
while (rows.length < contentCapacity) {
|
|
745
|
+
const rows: WorkspaceRailViewRow[] = [...contentRows.slice(0, Math.max(0, safeRows - 1))];
|
|
746
|
+
while (rows.length < safeRows) {
|
|
585
747
|
rows.push({
|
|
586
748
|
kind: 'muted',
|
|
587
749
|
text: '│',
|
|
@@ -603,12 +765,11 @@ export function buildWorkspaceRailViewRows(
|
|
|
603
765
|
railAction: 'project.add',
|
|
604
766
|
conversationStatus: null,
|
|
605
767
|
};
|
|
606
|
-
const projectActionRowIndex = Math.max(0,
|
|
768
|
+
const projectActionRowIndex = Math.max(0, safeRows - 3);
|
|
607
769
|
rows.splice(projectActionRowIndex, 0, projectActionRow);
|
|
608
|
-
if (rows.length >
|
|
609
|
-
rows.length =
|
|
770
|
+
if (rows.length > safeRows) {
|
|
771
|
+
rows.length = safeRows;
|
|
610
772
|
}
|
|
611
|
-
rows.push(...renderedShortcuts);
|
|
612
773
|
return rows;
|
|
613
774
|
}
|
|
614
775
|
|
|
@@ -644,27 +805,32 @@ export function actionAtWorkspaceRailCell(
|
|
|
644
805
|
if (row === undefined) {
|
|
645
806
|
return null;
|
|
646
807
|
}
|
|
647
|
-
|
|
808
|
+
const normalizedCol = Math.max(0, Math.floor(colIndex));
|
|
809
|
+
|
|
810
|
+
if (row.kind === 'github-header') {
|
|
811
|
+
const collapsedGlyphCol = row.text.indexOf('▶');
|
|
812
|
+
const expandedGlyphCol = row.text.indexOf('▼');
|
|
813
|
+
const glyphCol = collapsedGlyphCol >= 0 ? collapsedGlyphCol : expandedGlyphCol;
|
|
814
|
+
if (glyphCol >= 0 && normalizedCol === glyphCol) {
|
|
815
|
+
return 'project.github.toggle';
|
|
816
|
+
}
|
|
648
817
|
return row.railAction;
|
|
649
818
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
normalizedCol < buttonStart ||
|
|
663
|
-
normalizedCol >= buttonStart + NEW_THREAD_INLINE_LABEL.length
|
|
664
|
-
) {
|
|
665
|
-
return null;
|
|
819
|
+
|
|
820
|
+
if (row.kind === 'dir-header' && row.text.includes(NEW_THREAD_INLINE_LABEL)) {
|
|
821
|
+
const buttonStart =
|
|
822
|
+
paneCols === null
|
|
823
|
+
? row.text.lastIndexOf(NEW_THREAD_INLINE_LABEL)
|
|
824
|
+
: Math.max(0, Math.floor(paneCols) - NEW_THREAD_INLINE_LABEL.length);
|
|
825
|
+
if (
|
|
826
|
+
normalizedCol >= buttonStart &&
|
|
827
|
+
normalizedCol < buttonStart + NEW_THREAD_INLINE_LABEL.length
|
|
828
|
+
) {
|
|
829
|
+
return 'conversation.new';
|
|
830
|
+
}
|
|
666
831
|
}
|
|
667
|
-
|
|
832
|
+
|
|
833
|
+
return row.railAction;
|
|
668
834
|
}
|
|
669
835
|
|
|
670
836
|
export function projectIdAtWorkspaceRailRow(
|
|
@@ -1,20 +1,51 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
SurfaceBuffer,
|
|
3
3
|
DEFAULT_UI_STYLE,
|
|
4
|
-
drawUiText,
|
|
5
|
-
fillUiRow,
|
|
6
|
-
renderUiSurfaceAnsiRows,
|
|
7
4
|
type UiStyle,
|
|
8
|
-
} from '
|
|
9
|
-
import {
|
|
5
|
+
} from '../../packages/harness-ui/src/surface.ts';
|
|
6
|
+
import { UiKit } from '../../packages/harness-ui/src/kit.ts';
|
|
10
7
|
import { measureDisplayWidth } from '../terminal/snapshot-oracle.ts';
|
|
11
8
|
import { buildWorkspaceRailViewRows } from './workspace-rail-model.ts';
|
|
12
9
|
import { getActiveMuxTheme, type MuxWorkspaceRailTheme } from '../ui/mux-theme.ts';
|
|
13
10
|
|
|
14
11
|
type WorkspaceRailModel = Parameters<typeof buildWorkspaceRailViewRows>[0];
|
|
15
12
|
type WorkspaceRailViewRow = ReturnType<typeof buildWorkspaceRailViewRows>[number];
|
|
13
|
+
type UiSurface = SurfaceBuffer;
|
|
16
14
|
|
|
17
15
|
const INLINE_THREAD_BUTTON_LABEL = '[+ thread]';
|
|
16
|
+
const uiKit = new UiKit();
|
|
17
|
+
|
|
18
|
+
function createUiSurface(cols: number, rows: number, style: UiStyle): UiSurface {
|
|
19
|
+
return new SurfaceBuffer(cols, rows, style);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function drawUiText(
|
|
23
|
+
surface: UiSurface,
|
|
24
|
+
col: number,
|
|
25
|
+
row: number,
|
|
26
|
+
text: string,
|
|
27
|
+
style: UiStyle,
|
|
28
|
+
): void {
|
|
29
|
+
surface.drawText(col, row, text, style);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function fillUiRow(surface: UiSurface, row: number, style: UiStyle): void {
|
|
33
|
+
surface.fillRow(row, style);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function paintUiRow(
|
|
37
|
+
surface: UiSurface,
|
|
38
|
+
row: number,
|
|
39
|
+
text: string,
|
|
40
|
+
textStyle: UiStyle,
|
|
41
|
+
fillStyle: UiStyle,
|
|
42
|
+
): void {
|
|
43
|
+
uiKit.paintRow(surface, row, text, textStyle, fillStyle);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function renderUiSurfaceAnsiRows(surface: UiSurface): readonly string[] {
|
|
47
|
+
return surface.renderAnsiRows();
|
|
48
|
+
}
|
|
18
49
|
|
|
19
50
|
function conversationStatusIconStyle(
|
|
20
51
|
status: WorkspaceRailViewRow['conversationStatus'],
|
|
@@ -167,6 +198,14 @@ function paintWorkspaceRailRow(
|
|
|
167
198
|
drawTreeRow(surface, rowIndex, row, theme, theme.metaStyle, theme.activeRowStyle);
|
|
168
199
|
return;
|
|
169
200
|
}
|
|
201
|
+
if (row.kind === 'github-header') {
|
|
202
|
+
drawTreeRow(surface, rowIndex, row, theme, theme.headerStyle, theme.activeRowStyle);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (row.kind === 'github-detail') {
|
|
206
|
+
drawTreeRow(surface, rowIndex, row, theme, theme.metaStyle, theme.activeRowStyle);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
170
209
|
if (row.kind === 'conversation-title' || row.kind === 'conversation-body') {
|
|
171
210
|
drawConversationRow(surface, rowIndex, row, theme);
|
|
172
211
|
return;
|
|
@@ -191,22 +230,6 @@ function paintWorkspaceRailRow(
|
|
|
191
230
|
paintUiRow(surface, rowIndex, row.text, theme.repositoryRowStyle, theme.normalStyle);
|
|
192
231
|
return;
|
|
193
232
|
}
|
|
194
|
-
if (row.kind === 'shortcut-header') {
|
|
195
|
-
const buttonLabel = row.text.endsWith('[+]') ? '[+]' : row.text.endsWith('[-]') ? '[-]' : null;
|
|
196
|
-
if (buttonLabel === null) {
|
|
197
|
-
drawTreeRow(surface, rowIndex, row, theme, theme.headerStyle, theme.activeRowStyle);
|
|
198
|
-
} else {
|
|
199
|
-
drawTreeRow(surface, rowIndex, row, theme, theme.headerStyle, theme.activeRowStyle, {
|
|
200
|
-
buttonLabel,
|
|
201
|
-
buttonStyle: theme.actionStyle,
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
if (row.kind === 'shortcut-body') {
|
|
207
|
-
paintUiRow(surface, rowIndex, row.text, theme.shortcutStyle);
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
233
|
if (row.kind === 'action') {
|
|
211
234
|
drawActionRow(surface, rowIndex, row, theme);
|
|
212
235
|
return;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { writeFileSync } from 'node:fs';
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
import { createCanvas, type SKRSContext2D } from '@napi-rs/canvas';
|
|
4
|
-
import type { TerminalSnapshotFrame } from '../
|
|
5
|
-
import { readTerminalRecording } from '
|
|
4
|
+
import type { TerminalSnapshotFrame } from '../terminal/snapshot-oracle.ts';
|
|
5
|
+
import { readTerminalRecording } from './terminal-recording.ts';
|
|
6
6
|
|
|
7
7
|
type GifEncPalette = number[][];
|
|
8
8
|
type TerminalRecording = ReturnType<typeof readTerminalRecording>;
|