@jmoyers/harness 0.1.10 → 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 -35
- 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/{src/ui/modals/manager.ts → packages/harness-ui/src/modal-manager.ts} +94 -64
- 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 -3721
- package/scripts/control-plane-daemon.ts +24 -2
- 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 -3007
- package/scripts/nim-tui-smoke.ts +748 -0
- package/src/cli/auth/runtime.ts +948 -0
- package/src/cli/default-gateway-pointer.ts +193 -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 +361 -10
- package/src/config/harness-paths.ts +4 -7
- package/src/config/harness-runtime-migration.ts +142 -19
- package/src/config/harness.config.template.jsonc +33 -0
- package/src/config/secrets-core.ts +92 -4
- package/src/control-plane/agent-realtime-api.ts +82 -427
- package/src/control-plane/prompt/thread-title-namer.ts +49 -23
- 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-background.ts +18 -2
- 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 +943 -80
- 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/conversations.ts +11 -7
- package/src/domain/workspace.ts +76 -4
- 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 +22 -112
- 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-parsing.ts +16 -0
- 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 +106 -8
- package/src/mux/live-mux/modal-overlays.ts +210 -31
- package/src/mux/live-mux/modal-pointer.ts +3 -7
- package/src/mux/live-mux/modal-prompt-handlers.ts +107 -1
- 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 +19 -82
- 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 +82 -30
- package/src/services/runtime-conversation-starter.ts +80 -48
- 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 -70
- 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 +396 -56
- package/src/store/event-store.ts +397 -3
- 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 -82
- 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 -189
- package/src/services/runtime-main-pane-input.ts +0 -230
- package/src/services/runtime-modal-input.ts +0 -119
- package/src/services/runtime-navigation-input.ts +0 -197
- package/src/services/runtime-rail-input.ts +0 -278
- 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 -238
- 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/repository-fold-input.ts +0 -91
- package/src/ui/surface.ts +0 -224
|
@@ -1,3722 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { dirname } from 'node:path';
|
|
3
|
-
import { randomUUID } from 'node:crypto';
|
|
4
|
-
import { execFileSync, spawn } from 'node:child_process';
|
|
5
|
-
import { startCodexLiveSession } from '../src/codex/live-session.ts';
|
|
6
|
-
import {
|
|
7
|
-
openCodexControlPlaneClient,
|
|
8
|
-
subscribeControlPlaneKeyEvents,
|
|
9
|
-
type ControlPlaneKeyEvent,
|
|
10
|
-
} from '../src/control-plane/codex-session-stream.ts';
|
|
11
|
-
import { startControlPlaneStreamServer } from '../src/control-plane/stream-server.ts';
|
|
12
|
-
import type {
|
|
13
|
-
StreamObservedEvent,
|
|
14
|
-
StreamServerEnvelope,
|
|
15
|
-
} from '../src/control-plane/stream-protocol.ts';
|
|
16
|
-
import { SqliteEventStore } from '../src/store/event-store.ts';
|
|
17
|
-
import { TerminalSnapshotOracle } from '../src/terminal/snapshot-oracle.ts';
|
|
18
|
-
import type { PtyExit } from '../src/pty/pty_host.ts';
|
|
19
|
-
import { computeDualPaneLayout } from '../src/mux/dual-pane-core.ts';
|
|
20
|
-
import {
|
|
21
|
-
loadHarnessConfig,
|
|
22
|
-
updateHarnessConfig,
|
|
23
|
-
updateHarnessMuxUiConfig,
|
|
24
|
-
type HarnessMuxThemeConfig,
|
|
25
|
-
} from '../src/config/config-core.ts';
|
|
26
|
-
import { resolveHarnessRuntimePath } from '../src/config/harness-paths.ts';
|
|
27
|
-
import { migrateLegacyHarnessLayout } from '../src/config/harness-runtime-migration.ts';
|
|
28
|
-
import { loadHarnessSecrets } from '../src/config/secrets-core.ts';
|
|
29
|
-
import { detectMuxGlobalShortcut, resolveMuxShortcutBindings } from '../src/mux/input-shortcuts.ts';
|
|
30
|
-
import { createMuxInputModeManager } from '../src/mux/terminal-input-modes.ts';
|
|
31
|
-
import type { buildWorkspaceRailViewRows } from '../src/mux/workspace-rail-model.ts';
|
|
32
|
-
import {
|
|
33
|
-
normalizeThreadAgentType,
|
|
34
|
-
resolveNewThreadPromptAgentByRow,
|
|
35
|
-
} from '../src/mux/new-thread-prompt.ts';
|
|
36
|
-
import {
|
|
37
|
-
CommandMenuRegistry,
|
|
38
|
-
createCommandMenuState,
|
|
39
|
-
filterThemePresetActionsForScope,
|
|
40
|
-
resolveSelectedCommandMenuActionId,
|
|
41
|
-
type CommandMenuActionDescriptor,
|
|
42
|
-
type RegisteredCommandMenuAction,
|
|
43
|
-
} from '../src/mux/live-mux/command-menu.ts';
|
|
44
|
-
import {
|
|
45
|
-
buildProjectPaneSnapshot,
|
|
46
|
-
projectPaneActionAtRow,
|
|
47
|
-
sortedRepositoryList,
|
|
48
|
-
sortTasksByOrder,
|
|
49
|
-
} from '../src/mux/harness-core-ui.ts';
|
|
50
|
-
import {
|
|
51
|
-
taskFocusedPaneActionAtCell,
|
|
52
|
-
taskFocusedPaneActionAtRow,
|
|
53
|
-
taskFocusedPaneRepositoryIdAtRow,
|
|
54
|
-
taskFocusedPaneTaskIdAtRow,
|
|
55
|
-
} from '../src/mux/task-focused-pane.ts';
|
|
56
|
-
import {
|
|
57
|
-
createTaskComposerBuffer,
|
|
58
|
-
normalizeTaskComposerBuffer,
|
|
59
|
-
taskFieldsFromComposerText,
|
|
60
|
-
type TaskComposerBuffer,
|
|
61
|
-
} from '../src/mux/task-composer.ts';
|
|
62
|
-
import { resolveTaskScreenKeybindings } from '../src/mux/task-screen-keybindings.ts';
|
|
63
|
-
import { applyMuxControlPlaneKeyEvent } from '../src/mux/runtime-wiring.ts';
|
|
64
|
-
import {
|
|
65
|
-
applyModalOverlay,
|
|
66
|
-
buildRenderRows,
|
|
67
|
-
renderCanonicalFrameAnsi,
|
|
68
|
-
} from '../src/mux/render-frame.ts';
|
|
69
|
-
import { createTerminalRecordingWriter } from '../src/recording/terminal-recording.ts';
|
|
70
|
-
import { renderTerminalRecordingToGif } from './terminal-recording-gif-lib.ts';
|
|
71
|
-
import {
|
|
72
|
-
buildAgentSessionStartArgs,
|
|
73
|
-
mergeAdapterStateFromSessionEvent,
|
|
74
|
-
normalizeAdapterState,
|
|
75
|
-
} from '../src/adapters/agent-session-state.ts';
|
|
76
|
-
import {
|
|
77
|
-
configurePerfCore,
|
|
78
|
-
perfNowNs,
|
|
79
|
-
recordPerfEvent,
|
|
80
|
-
shutdownPerfCore,
|
|
81
|
-
startPerfSpan,
|
|
82
|
-
} from '../src/perf/perf-core.ts';
|
|
83
|
-
import {
|
|
84
|
-
parseConversationRecord,
|
|
85
|
-
parseDirectoryRecord,
|
|
86
|
-
parseRepositoryRecord,
|
|
87
|
-
parseTaskRecord,
|
|
88
|
-
} from '../src/mux/live-mux/control-plane-records.ts';
|
|
89
|
-
import {
|
|
90
|
-
leftColsFromPaneWidthPercent,
|
|
91
|
-
paneWidthPercentFromLayout,
|
|
92
|
-
} from '../src/mux/live-mux/layout.ts';
|
|
93
|
-
import {
|
|
94
|
-
normalizeGitHubRemoteUrl,
|
|
95
|
-
repositoryNameFromGitHubRemoteUrl,
|
|
96
|
-
resolveGitHubTrackedBranchForActions,
|
|
97
|
-
shouldShowGitHubPrActions,
|
|
98
|
-
} from '../src/mux/live-mux/git-parsing.ts';
|
|
99
|
-
import { readProcessUsageSample, runGitCommand } from '../src/mux/live-mux/git-snapshot.ts';
|
|
100
|
-
import { probeTerminalPalette } from '../src/mux/live-mux/terminal-palette.ts';
|
|
101
|
-
import { firstDirectoryForRepositoryGroup as firstDirectoryForRepositoryGroupFn } from '../src/mux/live-mux/repository-folding.ts';
|
|
102
|
-
import {
|
|
103
|
-
readObservedStreamCursorBaseline,
|
|
104
|
-
subscribeObservedStream,
|
|
105
|
-
unsubscribeObservedStream,
|
|
106
|
-
} from '../src/mux/live-mux/observed-stream.ts';
|
|
107
|
-
import {
|
|
108
|
-
createConversationState,
|
|
109
|
-
debugFooterForConversation,
|
|
110
|
-
formatCommandForDebugBar,
|
|
111
|
-
launchCommandForAgent,
|
|
112
|
-
type ConversationState,
|
|
113
|
-
} from '../src/mux/live-mux/conversation-state.ts';
|
|
114
|
-
import {
|
|
115
|
-
formatErrorMessage,
|
|
116
|
-
parseBooleanEnv,
|
|
117
|
-
parsePositiveInt,
|
|
118
|
-
prepareArtifactPath,
|
|
119
|
-
readStartupTerminalSize,
|
|
120
|
-
resolveWorkspacePathForMux,
|
|
121
|
-
restoreTerminalState,
|
|
122
|
-
sanitizeProcessEnv,
|
|
123
|
-
terminalSize,
|
|
124
|
-
} from '../src/mux/live-mux/startup-utils.ts';
|
|
125
|
-
import {
|
|
126
|
-
normalizeExitCode,
|
|
127
|
-
isSessionNotFoundError,
|
|
128
|
-
isSessionNotLiveError,
|
|
129
|
-
isConversationNotFoundError,
|
|
130
|
-
mapTerminalOutputToNormalizedEvent,
|
|
131
|
-
mapSessionEventToNormalizedEvent,
|
|
132
|
-
observedAtFromSessionEvent,
|
|
133
|
-
} from '../src/mux/live-mux/event-mapping.ts';
|
|
134
|
-
import { parseMuxArgs } from '../src/mux/live-mux/args.ts';
|
|
135
|
-
import {
|
|
136
|
-
isCopyShortcutInput,
|
|
137
|
-
renderSelectionOverlay,
|
|
138
|
-
selectionText,
|
|
139
|
-
selectionVisibleRows,
|
|
140
|
-
writeTextToClipboard,
|
|
141
|
-
} from '../src/mux/live-mux/selection.ts';
|
|
142
|
-
import { type GitRepositorySnapshot, type GitSummary } from '../src/mux/live-mux/git-state.ts';
|
|
143
|
-
import { resolveDirectoryForAction as resolveDirectoryForActionFn } from '../src/mux/live-mux/directory-resolution.ts';
|
|
144
|
-
import { requestStop as requestStopFn } from '../src/mux/live-mux/runtime-shutdown.ts';
|
|
145
|
-
import {
|
|
146
|
-
hasActiveProfileState,
|
|
147
|
-
resolveProfileStatePath,
|
|
148
|
-
toggleGatewayProfiler as toggleGatewayProfilerFn,
|
|
149
|
-
} from '../src/mux/live-mux/gateway-profiler.ts';
|
|
150
|
-
import { toggleGatewayStatusTimeline as toggleGatewayStatusTimelineFn } from '../src/mux/live-mux/gateway-status-timeline.ts';
|
|
151
|
-
import { toggleGatewayRenderTrace as toggleGatewayRenderTraceFn } from '../src/mux/live-mux/gateway-render-trace.ts';
|
|
152
|
-
import { resolveStatusTimelineStatePath } from '../src/mux/live-mux/status-timeline-state.ts';
|
|
153
|
-
import { resolveRenderTraceStatePath } from '../src/mux/live-mux/render-trace-state.ts';
|
|
154
|
-
import {
|
|
155
|
-
findRenderTraceControlIssues,
|
|
156
|
-
renderTraceChunkPreview,
|
|
157
|
-
} from '../src/mux/live-mux/render-trace-analysis.ts';
|
|
158
|
-
import {
|
|
159
|
-
buildCritiqueReviewCommand,
|
|
160
|
-
resolveCritiqueReviewAgent,
|
|
161
|
-
resolveCritiqueReviewBaseBranch,
|
|
162
|
-
} from '../src/mux/live-mux/critique-review.ts';
|
|
163
|
-
import { WorkspaceModel } from '../src/domain/workspace.ts';
|
|
164
|
-
import { ConversationManager, type ConversationSeed } from '../src/domain/conversations.ts';
|
|
165
|
-
import { RepositoryManager } from '../src/domain/repositories.ts';
|
|
166
|
-
import { DirectoryManager } from '../src/domain/directories.ts';
|
|
167
|
-
import { TaskManager } from '../src/domain/tasks.ts';
|
|
168
|
-
import { ControlPlaneService } from '../src/services/control-plane.ts';
|
|
169
|
-
import { ConversationLifecycle } from '../src/services/conversation-lifecycle.ts';
|
|
170
|
-
import { DirectoryHydrationService } from '../src/services/directory-hydration.ts';
|
|
171
|
-
import { EventPersistence } from '../src/services/event-persistence.ts';
|
|
172
|
-
import { MuxUiStatePersistence } from '../src/services/mux-ui-state-persistence.ts';
|
|
173
|
-
import { OutputLoadSampler } from '../src/services/output-load-sampler.ts';
|
|
174
|
-
import { ProcessUsageRefreshService } from '../src/services/process-usage-refresh.ts';
|
|
175
|
-
import { RecordingService } from '../src/services/recording.ts';
|
|
176
|
-
import { SessionProjectionInstrumentation } from '../src/services/session-projection-instrumentation.ts';
|
|
177
|
-
import { StartupOrchestrator } from '../src/services/startup-orchestrator.ts';
|
|
178
|
-
import { RuntimeProcessWiring } from '../src/services/runtime-process-wiring.ts';
|
|
179
|
-
import { RuntimeControlPlaneOps } from '../src/services/runtime-control-plane-ops.ts';
|
|
180
|
-
import { RuntimeControlActions } from '../src/services/runtime-control-actions.ts';
|
|
181
|
-
import { RuntimeDirectoryActions } from '../src/services/runtime-directory-actions.ts';
|
|
182
|
-
import { RuntimeEnvelopeHandler } from '../src/services/runtime-envelope-handler.ts';
|
|
183
|
-
import { RuntimeRenderPipeline } from '../src/services/runtime-render-pipeline.ts';
|
|
184
|
-
import { RuntimeRepositoryActions } from '../src/services/runtime-repository-actions.ts';
|
|
185
|
-
import { RuntimeGitState } from '../src/services/runtime-git-state.ts';
|
|
186
|
-
import { RuntimeLayoutResize } from '../src/services/runtime-layout-resize.ts';
|
|
187
|
-
import { RuntimeRenderLifecycle } from '../src/services/runtime-render-lifecycle.ts';
|
|
188
|
-
import { RuntimeShutdownService } from '../src/services/runtime-shutdown.ts';
|
|
189
|
-
import { RuntimeTaskEditorActions } from '../src/services/runtime-task-editor-actions.ts';
|
|
190
|
-
import { RuntimeInputPipeline } from '../src/services/runtime-input-pipeline.ts';
|
|
191
|
-
import { RuntimeInputRouter } from '../src/services/runtime-input-router.ts';
|
|
192
|
-
import { RuntimeTaskComposerPersistenceService } from '../src/services/runtime-task-composer-persistence.ts';
|
|
193
|
-
import { RuntimeTaskPane } from '../src/services/runtime-task-pane.ts';
|
|
194
|
-
import { TaskPaneSelectionActions } from '../src/services/task-pane-selection-actions.ts';
|
|
195
|
-
import { TaskPlanningHydrationService } from '../src/services/task-planning-hydration.ts';
|
|
196
|
-
import { TaskPlanningObservedEvents } from '../src/services/task-planning-observed-events.ts';
|
|
197
|
-
import {
|
|
198
|
-
RuntimeCommandMenuAgentTools,
|
|
199
|
-
type InstallableAgentType,
|
|
200
|
-
} from '../src/services/runtime-command-menu-agent-tools.ts';
|
|
201
|
-
import { RuntimeWorkspaceActions } from '../src/services/runtime-workspace-actions.ts';
|
|
202
|
-
import { WorkspaceObservedEvents } from '../src/services/workspace-observed-events.ts';
|
|
203
|
-
import { RuntimeWorkspaceObservedEvents } from '../src/services/runtime-workspace-observed-events.ts';
|
|
204
|
-
import { StartupStateHydrationService } from '../src/services/startup-state-hydration.ts';
|
|
205
|
-
import {
|
|
206
|
-
StatusTimelineRecorder,
|
|
207
|
-
type StatusTimelineLabels,
|
|
208
|
-
} from '../src/services/status-timeline-recorder.ts';
|
|
209
|
-
import {
|
|
210
|
-
RenderTraceRecorder,
|
|
211
|
-
type RenderTraceLabels,
|
|
212
|
-
} from '../src/services/render-trace-recorder.ts';
|
|
213
|
-
import { Screen, type ScreenCursorStyle } from '../src/ui/screen.ts';
|
|
214
|
-
import { ConversationPane } from '../src/ui/panes/conversation.ts';
|
|
215
|
-
import { DebugFooterNotice } from '../src/ui/debug-footer-notice.ts';
|
|
216
|
-
import { HomePane } from '../src/ui/panes/home.ts';
|
|
217
|
-
import { ProjectPane } from '../src/ui/panes/project.ts';
|
|
218
|
-
import { LeftRailPane } from '../src/ui/panes/left-rail.ts';
|
|
219
|
-
import { ModalManager } from '../src/ui/modals/manager.ts';
|
|
220
|
-
import {
|
|
221
|
-
getActiveMuxTheme,
|
|
222
|
-
muxThemePresetNames,
|
|
223
|
-
resolveConfiguredMuxTheme,
|
|
224
|
-
setActiveMuxTheme,
|
|
225
|
-
} from '../src/ui/mux-theme.ts';
|
|
1
|
+
import { runCodexLiveMuxRuntimeProcess } from '../src/mux/runtime-app/codex-live-mux-runtime.ts';
|
|
226
2
|
|
|
227
|
-
|
|
228
|
-
type ControlPlaneConversationRecord = NonNullable<ReturnType<typeof parseConversationRecord>>;
|
|
229
|
-
type ControlPlaneRepositoryRecord = NonNullable<ReturnType<typeof parseRepositoryRecord>>;
|
|
230
|
-
type ControlPlaneTaskRecord = NonNullable<ReturnType<typeof parseTaskRecord>>;
|
|
231
|
-
type ControlPlaneSessionSummary = NonNullable<
|
|
232
|
-
Awaited<ReturnType<ControlPlaneService['getSessionStatus']>>
|
|
233
|
-
>;
|
|
234
|
-
type ControlPlaneDirectoryGitStatusRecord = Awaited<
|
|
235
|
-
ReturnType<ControlPlaneService['listDirectoryGitStatuses']>
|
|
236
|
-
>[number];
|
|
237
|
-
|
|
238
|
-
type ProcessUsageSample = Awaited<ReturnType<typeof readProcessUsageSample>>;
|
|
239
|
-
|
|
240
|
-
interface RuntimeCommandMenuContext {
|
|
241
|
-
readonly activeDirectoryId: string | null;
|
|
242
|
-
readonly activeConversationId: string | null;
|
|
243
|
-
readonly selectedText: string;
|
|
244
|
-
readonly leftNavSelectionKind: WorkspaceModel['leftNavSelection']['kind'];
|
|
245
|
-
readonly profileRunning: boolean;
|
|
246
|
-
readonly statusTimelineRunning: boolean;
|
|
247
|
-
readonly githubRepositoryUrl: string | null;
|
|
248
|
-
readonly githubDefaultBranch: string | null;
|
|
249
|
-
readonly githubTrackedBranch: string | null;
|
|
250
|
-
readonly githubOpenPrUrl: string | null;
|
|
251
|
-
readonly githubProjectPrLoading: boolean;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
interface CommandMenuGitHubProjectPrState {
|
|
255
|
-
readonly directoryId: string;
|
|
256
|
-
readonly branchName: string | null;
|
|
257
|
-
readonly openPrUrl: string | null;
|
|
258
|
-
readonly loading: boolean;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
interface GitHubDebugAuthState {
|
|
262
|
-
enabled: boolean;
|
|
263
|
-
token: 'env' | 'gh' | 'none';
|
|
264
|
-
auth: 'ok' | 'no' | 'er' | 'na' | 'uk';
|
|
265
|
-
projectPr: 'ok' | 'er' | 'na';
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
interface ThemePickerSessionState {
|
|
269
|
-
readonly initialThemeConfig: HarnessMuxThemeConfig | null;
|
|
270
|
-
committed: boolean;
|
|
271
|
-
previewActionId: string | null;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
const DEFAULT_RESIZE_MIN_INTERVAL_MS = 33;
|
|
275
|
-
const DEFAULT_PTY_RESIZE_SETTLE_MS = 75;
|
|
276
|
-
const DEFAULT_STARTUP_SETTLE_QUIET_MS = 300;
|
|
277
|
-
const DEFAULT_STARTUP_SETTLE_NONEMPTY_FALLBACK_MS = 1500;
|
|
278
|
-
const DEFAULT_BACKGROUND_START_MAX_WAIT_MS = 5000;
|
|
279
|
-
const DEFAULT_BACKGROUND_RESUME_PERSISTED = false;
|
|
280
|
-
const DEFAULT_BACKGROUND_PROBES_ENABLED = false;
|
|
281
|
-
const DEBUG_FOOTER_NOTICE_TTL_MS = 8000;
|
|
282
|
-
const DEFAULT_CONVERSATION_TITLE_EDIT_DEBOUNCE_MS = 250;
|
|
283
|
-
const DEFAULT_TASK_EDITOR_AUTOSAVE_DEBOUNCE_MS = 250;
|
|
284
|
-
const CONVERSATION_TITLE_EDIT_DOUBLE_CLICK_WINDOW_MS = 350;
|
|
285
|
-
const HOME_PANE_EDIT_DOUBLE_CLICK_WINDOW_MS = 350;
|
|
286
|
-
const HOME_PANE_BACKGROUND_INTERVAL_MS = 80;
|
|
287
|
-
const UI_STATE_PERSIST_DEBOUNCE_MS = 200;
|
|
288
|
-
const REPOSITORY_TOGGLE_CHORD_TIMEOUT_MS = 1250;
|
|
289
|
-
const REPOSITORY_COLLAPSE_ALL_CHORD_PREFIX = Buffer.from([0x0b]);
|
|
290
|
-
const UNTRACKED_REPOSITORY_GROUP_ID = 'untracked';
|
|
291
|
-
const THEME_PICKER_SCOPE = 'theme-select';
|
|
292
|
-
const THEME_ACTION_ID_PREFIX = 'theme.set.';
|
|
293
|
-
const GIT_SUMMARY_LOADING: GitSummary = {
|
|
294
|
-
branch: '(loading)',
|
|
295
|
-
changedFiles: 0,
|
|
296
|
-
additions: 0,
|
|
297
|
-
deletions: 0,
|
|
298
|
-
};
|
|
299
|
-
|
|
300
|
-
const GIT_REPOSITORY_NONE: GitRepositorySnapshot = {
|
|
301
|
-
normalizedRemoteUrl: null,
|
|
302
|
-
commitCount: null,
|
|
303
|
-
lastCommitAt: null,
|
|
304
|
-
shortCommitHash: null,
|
|
305
|
-
inferredName: null,
|
|
306
|
-
defaultBranch: null,
|
|
307
|
-
};
|
|
308
|
-
|
|
309
|
-
function asRecord(value: unknown): Record<string, unknown> | null {
|
|
310
|
-
if (typeof value !== 'object' || value === null) {
|
|
311
|
-
return null;
|
|
312
|
-
}
|
|
313
|
-
return value as Record<string, unknown>;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
function parseGitHubProjectPrState(
|
|
317
|
-
directoryId: string,
|
|
318
|
-
result: Record<string, unknown>,
|
|
319
|
-
): CommandMenuGitHubProjectPrState {
|
|
320
|
-
const branchNameRaw = result['branchName'];
|
|
321
|
-
const branchName = typeof branchNameRaw === 'string' ? branchNameRaw : null;
|
|
322
|
-
const pr = asRecord(result['pr']);
|
|
323
|
-
const prUrlRaw = pr?.['url'];
|
|
324
|
-
const openPrUrl = typeof prUrlRaw === 'string' ? prUrlRaw : null;
|
|
325
|
-
return {
|
|
326
|
-
directoryId,
|
|
327
|
-
branchName,
|
|
328
|
-
openPrUrl,
|
|
329
|
-
loading: false,
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
function parseGitHubPrUrl(result: Record<string, unknown>): string | null {
|
|
334
|
-
const pr = asRecord(result['pr']);
|
|
335
|
-
if (pr === null) {
|
|
336
|
-
return null;
|
|
337
|
-
}
|
|
338
|
-
const url = pr['url'];
|
|
339
|
-
return typeof url === 'string' ? url : null;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
function commandMenuProjectPathTail(path: string): string {
|
|
343
|
-
const normalized = path.trim().replaceAll('\\', '/').replace(/\/+$/u, '');
|
|
344
|
-
if (normalized.length === 0) {
|
|
345
|
-
return '(project)';
|
|
346
|
-
}
|
|
347
|
-
if (normalized === '/') {
|
|
348
|
-
return '/';
|
|
349
|
-
}
|
|
350
|
-
const segments = normalized.split('/').filter((segment) => segment.length > 0);
|
|
351
|
-
if (segments.length === 0) {
|
|
352
|
-
return normalized;
|
|
353
|
-
}
|
|
354
|
-
if (segments.length <= 2) {
|
|
355
|
-
return segments.join('/');
|
|
356
|
-
}
|
|
357
|
-
return `…/${segments.slice(-2).join('/')}`;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
function openUrlInBrowser(url: string): boolean {
|
|
361
|
-
const target = url.trim();
|
|
362
|
-
if (target.length === 0) {
|
|
363
|
-
return false;
|
|
364
|
-
}
|
|
365
|
-
try {
|
|
366
|
-
if (process.platform === 'darwin') {
|
|
367
|
-
const child = spawn('open', [target], {
|
|
368
|
-
detached: true,
|
|
369
|
-
stdio: 'ignore',
|
|
370
|
-
});
|
|
371
|
-
child.unref();
|
|
372
|
-
return true;
|
|
373
|
-
}
|
|
374
|
-
if (process.platform === 'win32') {
|
|
375
|
-
const child = spawn('cmd', ['/c', 'start', '', target], {
|
|
376
|
-
detached: true,
|
|
377
|
-
stdio: 'ignore',
|
|
378
|
-
windowsHide: true,
|
|
379
|
-
});
|
|
380
|
-
child.unref();
|
|
381
|
-
return true;
|
|
382
|
-
}
|
|
383
|
-
const child = spawn('xdg-open', [target], {
|
|
384
|
-
detached: true,
|
|
385
|
-
stdio: 'ignore',
|
|
386
|
-
});
|
|
387
|
-
child.unref();
|
|
388
|
-
return true;
|
|
389
|
-
} catch {
|
|
390
|
-
return false;
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
function commandExistsOnPath(command: string): boolean {
|
|
395
|
-
const normalized = command.trim();
|
|
396
|
-
if (normalized.length === 0) {
|
|
397
|
-
return false;
|
|
398
|
-
}
|
|
399
|
-
try {
|
|
400
|
-
if (process.platform === 'win32') {
|
|
401
|
-
execFileSync('where', [normalized], {
|
|
402
|
-
stdio: 'ignore',
|
|
403
|
-
});
|
|
404
|
-
return true;
|
|
405
|
-
}
|
|
406
|
-
execFileSync('sh', ['-lc', `command -v ${normalized} >/dev/null 2>&1`], {
|
|
407
|
-
stdio: 'ignore',
|
|
408
|
-
});
|
|
409
|
-
return true;
|
|
410
|
-
} catch {
|
|
411
|
-
return false;
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
function readGhAuthTokenForDebug(): string | null {
|
|
416
|
-
try {
|
|
417
|
-
const stdout = execFileSync('gh', ['auth', 'token'], {
|
|
418
|
-
encoding: 'utf8',
|
|
419
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
420
|
-
timeout: 2000,
|
|
421
|
-
windowsHide: true,
|
|
422
|
-
});
|
|
423
|
-
const token = stdout.trim();
|
|
424
|
-
return token.length > 0 ? token : null;
|
|
425
|
-
} catch {
|
|
426
|
-
return null;
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
function formatGitHubDebugTokens(state: GitHubDebugAuthState): string {
|
|
431
|
-
if (!state.enabled) {
|
|
432
|
-
return '[gh:off tk:na au:na pr:na]';
|
|
433
|
-
}
|
|
434
|
-
return `[gh:on tk:${state.token} au:${state.auth} pr:${state.projectPr}]`;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
async function main(): Promise<number> {
|
|
438
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
439
|
-
process.stderr.write('codex:live:mux requires a TTY stdin/stdout\n');
|
|
440
|
-
return 2;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
const invocationDirectory =
|
|
444
|
-
process.env.HARNESS_INVOKE_CWD ?? process.env.INIT_CWD ?? process.cwd();
|
|
445
|
-
migrateLegacyHarnessLayout(invocationDirectory, process.env);
|
|
446
|
-
loadHarnessSecrets({ cwd: invocationDirectory });
|
|
447
|
-
const options = parseMuxArgs(process.argv.slice(2));
|
|
448
|
-
const loadedConfig = loadHarnessConfig({
|
|
449
|
-
cwd: options.invocationDirectory,
|
|
450
|
-
});
|
|
451
|
-
const debugConfig = loadedConfig.config.debug;
|
|
452
|
-
const perfEnabled = parseBooleanEnv(
|
|
453
|
-
process.env.HARNESS_PERF_ENABLED,
|
|
454
|
-
debugConfig.enabled && debugConfig.perf.enabled,
|
|
455
|
-
);
|
|
456
|
-
const perfFilePath = resolveHarnessRuntimePath(
|
|
457
|
-
options.invocationDirectory,
|
|
458
|
-
process.env.HARNESS_PERF_FILE_PATH ?? debugConfig.perf.filePath,
|
|
459
|
-
);
|
|
460
|
-
const perfTruncateOnStart = parseBooleanEnv(
|
|
461
|
-
process.env.HARNESS_PERF_TRUNCATE_ON_START,
|
|
462
|
-
debugConfig.overwriteArtifactsOnStart,
|
|
463
|
-
);
|
|
464
|
-
if (perfEnabled) {
|
|
465
|
-
prepareArtifactPath(perfFilePath, perfTruncateOnStart);
|
|
466
|
-
}
|
|
467
|
-
configurePerfCore({
|
|
468
|
-
enabled: perfEnabled,
|
|
469
|
-
filePath: perfFilePath,
|
|
470
|
-
});
|
|
471
|
-
const startupSpan = startPerfSpan('mux.startup.total', {
|
|
472
|
-
invocationDirectory: options.invocationDirectory,
|
|
473
|
-
codexArgs: options.codexArgs.length,
|
|
474
|
-
});
|
|
475
|
-
const muxSessionName =
|
|
476
|
-
typeof process.env.HARNESS_SESSION_NAME === 'string' &&
|
|
477
|
-
process.env.HARNESS_SESSION_NAME.trim().length > 0
|
|
478
|
-
? process.env.HARNESS_SESSION_NAME.trim()
|
|
479
|
-
: null;
|
|
480
|
-
recordPerfEvent('mux.startup.begin', {
|
|
481
|
-
stdinTty: process.stdin.isTTY ? 1 : 0,
|
|
482
|
-
stdoutTty: process.stdout.isTTY ? 1 : 0,
|
|
483
|
-
perfFilePath,
|
|
484
|
-
});
|
|
485
|
-
if (loadedConfig.error !== null) {
|
|
486
|
-
process.stderr.write(
|
|
487
|
-
`[config] using last-known-good due to parse error: ${loadedConfig.error}\n`,
|
|
488
|
-
);
|
|
489
|
-
}
|
|
490
|
-
const shortcutBindings = resolveMuxShortcutBindings(loadedConfig.config.mux.keybindings);
|
|
491
|
-
const taskScreenKeybindings = resolveTaskScreenKeybindings(loadedConfig.config.mux.keybindings);
|
|
492
|
-
const modalDismissShortcutBindings = resolveMuxShortcutBindings({
|
|
493
|
-
'mux.app.quit': ['escape'],
|
|
494
|
-
'mux.app.interrupt-all': [],
|
|
495
|
-
'mux.gateway.profile.toggle': [],
|
|
496
|
-
'mux.gateway.status-timeline.toggle': [],
|
|
497
|
-
'mux.conversation.new': [],
|
|
498
|
-
'mux.conversation.critique.open-or-create': [],
|
|
499
|
-
'mux.conversation.next': [],
|
|
500
|
-
'mux.conversation.previous': [],
|
|
501
|
-
'mux.conversation.interrupt': [],
|
|
502
|
-
'mux.conversation.archive': [],
|
|
503
|
-
'mux.conversation.takeover': [],
|
|
504
|
-
'mux.conversation.delete': [],
|
|
505
|
-
'mux.directory.add': [],
|
|
506
|
-
'mux.directory.close': [],
|
|
507
|
-
});
|
|
508
|
-
const store = new SqliteEventStore(options.storePath);
|
|
509
|
-
|
|
510
|
-
let size = await readStartupTerminalSize();
|
|
511
|
-
recordPerfEvent('mux.startup.terminal-size', {
|
|
512
|
-
cols: size.cols,
|
|
513
|
-
rows: size.rows,
|
|
514
|
-
});
|
|
515
|
-
const configuredMuxUi = loadedConfig.config.mux.ui;
|
|
516
|
-
let runtimeThemeConfig: HarnessMuxThemeConfig | null = configuredMuxUi.theme;
|
|
517
|
-
const resolveAndApplyRuntimeTheme = (
|
|
518
|
-
nextThemeConfig: HarnessMuxThemeConfig | null,
|
|
519
|
-
writeErrorToStderr = false,
|
|
520
|
-
) => {
|
|
521
|
-
const resolved = resolveConfiguredMuxTheme({
|
|
522
|
-
config: nextThemeConfig,
|
|
523
|
-
cwd: options.invocationDirectory,
|
|
524
|
-
});
|
|
525
|
-
if (resolved.error !== null && writeErrorToStderr) {
|
|
526
|
-
process.stderr.write(`[theme] ${resolved.error}; using preset fallback\n`);
|
|
527
|
-
}
|
|
528
|
-
setActiveMuxTheme(resolved.theme);
|
|
529
|
-
runtimeThemeConfig = nextThemeConfig;
|
|
530
|
-
return resolved;
|
|
531
|
-
};
|
|
532
|
-
const resolvedMuxTheme = resolveAndApplyRuntimeTheme(runtimeThemeConfig, true);
|
|
533
|
-
let currentModalTheme = resolvedMuxTheme.theme.modalTheme;
|
|
534
|
-
const configuredMuxGit = loadedConfig.config.mux.git;
|
|
535
|
-
const githubTokenEnvVar = loadedConfig.config.github.tokenEnvVar;
|
|
536
|
-
const envGitHubTokenRaw = process.env[githubTokenEnvVar];
|
|
537
|
-
const hasEnvGitHubToken =
|
|
538
|
-
typeof envGitHubTokenRaw === 'string' && envGitHubTokenRaw.trim().length > 0;
|
|
539
|
-
const githubDebugAuthState: GitHubDebugAuthState = {
|
|
540
|
-
enabled: loadedConfig.config.github.enabled,
|
|
541
|
-
token: hasEnvGitHubToken ? 'env' : 'none',
|
|
542
|
-
auth: loadedConfig.config.github.enabled ? (hasEnvGitHubToken ? 'ok' : 'uk') : 'na',
|
|
543
|
-
projectPr: 'na',
|
|
544
|
-
};
|
|
545
|
-
if (githubDebugAuthState.enabled && !hasEnvGitHubToken) {
|
|
546
|
-
const ghToken = commandExistsOnPath('gh') ? readGhAuthTokenForDebug() : null;
|
|
547
|
-
if (ghToken !== null) {
|
|
548
|
-
githubDebugAuthState.token = 'gh';
|
|
549
|
-
githubDebugAuthState.auth = 'ok';
|
|
550
|
-
} else {
|
|
551
|
-
githubDebugAuthState.token = 'none';
|
|
552
|
-
githubDebugAuthState.auth = 'no';
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
const configuredCodexLaunch = loadedConfig.config.codex.launch;
|
|
556
|
-
const configuredCritique = loadedConfig.config.critique;
|
|
557
|
-
const codexLaunchModeByDirectoryPath: Record<string, 'yolo' | 'standard'> = {};
|
|
558
|
-
for (const [directoryPath, mode] of Object.entries(configuredCodexLaunch.directoryModes)) {
|
|
559
|
-
const normalizedDirectoryPath = resolveWorkspacePathForMux(
|
|
560
|
-
options.invocationDirectory,
|
|
561
|
-
directoryPath,
|
|
562
|
-
);
|
|
563
|
-
codexLaunchModeByDirectoryPath[normalizedDirectoryPath] = mode;
|
|
564
|
-
}
|
|
565
|
-
const configuredClaudeLaunch = loadedConfig.config.claude.launch;
|
|
566
|
-
const claudeLaunchModeByDirectoryPath: Record<string, 'yolo' | 'standard'> = {};
|
|
567
|
-
for (const [directoryPath, mode] of Object.entries(configuredClaudeLaunch.directoryModes)) {
|
|
568
|
-
const normalizedDirectoryPath = resolveWorkspacePathForMux(
|
|
569
|
-
options.invocationDirectory,
|
|
570
|
-
directoryPath,
|
|
571
|
-
);
|
|
572
|
-
claudeLaunchModeByDirectoryPath[normalizedDirectoryPath] = mode;
|
|
573
|
-
}
|
|
574
|
-
const configuredCursorLaunch = loadedConfig.config.cursor.launch;
|
|
575
|
-
const cursorLaunchModeByDirectoryPath: Record<string, 'yolo' | 'standard'> = {};
|
|
576
|
-
for (const [directoryPath, mode] of Object.entries(configuredCursorLaunch.directoryModes)) {
|
|
577
|
-
const normalizedDirectoryPath = resolveWorkspacePathForMux(
|
|
578
|
-
options.invocationDirectory,
|
|
579
|
-
directoryPath,
|
|
580
|
-
);
|
|
581
|
-
cursorLaunchModeByDirectoryPath[normalizedDirectoryPath] = mode;
|
|
582
|
-
}
|
|
583
|
-
let leftPaneColsOverride: number | null =
|
|
584
|
-
configuredMuxUi.paneWidthPercent === null
|
|
585
|
-
? null
|
|
586
|
-
: leftColsFromPaneWidthPercent(size.cols, configuredMuxUi.paneWidthPercent);
|
|
587
|
-
let layout = computeDualPaneLayout(size.cols, size.rows, {
|
|
588
|
-
leftCols: leftPaneColsOverride,
|
|
589
|
-
});
|
|
590
|
-
const resizeMinIntervalMs = debugConfig.enabled
|
|
591
|
-
? debugConfig.mux.resizeMinIntervalMs
|
|
592
|
-
: DEFAULT_RESIZE_MIN_INTERVAL_MS;
|
|
593
|
-
const ptyResizeSettleMs = debugConfig.enabled
|
|
594
|
-
? debugConfig.mux.ptyResizeSettleMs
|
|
595
|
-
: DEFAULT_PTY_RESIZE_SETTLE_MS;
|
|
596
|
-
const startupSettleQuietMs = debugConfig.enabled
|
|
597
|
-
? debugConfig.mux.startupSettleQuietMs
|
|
598
|
-
: DEFAULT_STARTUP_SETTLE_QUIET_MS;
|
|
599
|
-
const controlPlaneConnectRetryWindowMs = parsePositiveInt(
|
|
600
|
-
process.env.HARNESS_CONTROL_PLANE_CONNECT_RETRY_WINDOW_MS,
|
|
601
|
-
0,
|
|
602
|
-
);
|
|
603
|
-
const controlPlaneConnectRetryDelayMs = Math.max(
|
|
604
|
-
1,
|
|
605
|
-
parsePositiveInt(process.env.HARNESS_CONTROL_PLANE_CONNECT_RETRY_DELAY_MS, 50),
|
|
606
|
-
);
|
|
607
|
-
const backgroundResumePersisted = parseBooleanEnv(
|
|
608
|
-
process.env.HARNESS_MUX_BACKGROUND_RESUME,
|
|
609
|
-
DEFAULT_BACKGROUND_RESUME_PERSISTED,
|
|
610
|
-
);
|
|
611
|
-
const backgroundProbesEnabled = parseBooleanEnv(
|
|
612
|
-
process.env.HARNESS_MUX_BACKGROUND_PROBES,
|
|
613
|
-
DEFAULT_BACKGROUND_PROBES_ENABLED,
|
|
614
|
-
);
|
|
615
|
-
const validateAnsi = debugConfig.enabled ? debugConfig.mux.validateAnsi : false;
|
|
616
|
-
|
|
617
|
-
process.stdin.setRawMode(true);
|
|
618
|
-
process.stdin.resume();
|
|
619
|
-
const inputModeManager = createMuxInputModeManager((sequence) => {
|
|
620
|
-
process.stdout.write(sequence);
|
|
621
|
-
});
|
|
622
|
-
|
|
623
|
-
const paletteProbeSpan = startPerfSpan('mux.startup.palette-probe');
|
|
624
|
-
const probedPalette = await probeTerminalPalette();
|
|
625
|
-
paletteProbeSpan.end({
|
|
626
|
-
hasForeground: probedPalette.foregroundHex !== undefined,
|
|
627
|
-
hasBackground: probedPalette.backgroundHex !== undefined,
|
|
628
|
-
});
|
|
629
|
-
const resolvedTerminalForegroundHex =
|
|
630
|
-
resolvedMuxTheme.theme.terminalForegroundHex ??
|
|
631
|
-
process.env.HARNESS_TERM_FG ??
|
|
632
|
-
probedPalette.foregroundHex;
|
|
633
|
-
const resolvedTerminalBackgroundHex =
|
|
634
|
-
resolvedMuxTheme.theme.terminalBackgroundHex ??
|
|
635
|
-
process.env.HARNESS_TERM_BG ??
|
|
636
|
-
probedPalette.backgroundHex;
|
|
637
|
-
let muxRecordingWriter: ReturnType<typeof createTerminalRecordingWriter> | null = null;
|
|
638
|
-
let muxRecordingOracle: TerminalSnapshotOracle | null = null;
|
|
639
|
-
if (options.recordingPath !== null) {
|
|
640
|
-
mkdirSync(dirname(options.recordingPath), { recursive: true });
|
|
641
|
-
const recordIntervalMs = Math.max(1, Math.floor(1000 / options.recordingFps));
|
|
642
|
-
const recordingWriterOptions: Parameters<typeof createTerminalRecordingWriter>[0] = {
|
|
643
|
-
filePath: options.recordingPath,
|
|
644
|
-
source: 'codex-live-mux',
|
|
645
|
-
defaultForegroundHex: resolvedTerminalForegroundHex ?? 'd0d7de',
|
|
646
|
-
defaultBackgroundHex: resolvedTerminalBackgroundHex ?? '0f1419',
|
|
647
|
-
minFrameIntervalMs: recordIntervalMs,
|
|
648
|
-
};
|
|
649
|
-
if (probedPalette.indexedHexByCode !== undefined) {
|
|
650
|
-
recordingWriterOptions.ansiPaletteIndexedHex = probedPalette.indexedHexByCode;
|
|
651
|
-
}
|
|
652
|
-
muxRecordingWriter = createTerminalRecordingWriter(recordingWriterOptions);
|
|
653
|
-
muxRecordingOracle = new TerminalSnapshotOracle(size.cols, size.rows);
|
|
654
|
-
}
|
|
655
|
-
const recordingService = new RecordingService({
|
|
656
|
-
recordingWriter: muxRecordingWriter,
|
|
657
|
-
recordingPath: options.recordingPath,
|
|
658
|
-
recordingGifOutputPath: options.recordingGifOutputPath,
|
|
659
|
-
renderTerminalRecordingToGif,
|
|
660
|
-
writeStderr: (text) => {
|
|
661
|
-
process.stderr.write(text);
|
|
662
|
-
},
|
|
663
|
-
});
|
|
664
|
-
const controlPlaneMode =
|
|
665
|
-
options.controlPlaneHost !== null && options.controlPlanePort !== null
|
|
666
|
-
? {
|
|
667
|
-
mode: 'remote' as const,
|
|
668
|
-
host: options.controlPlaneHost,
|
|
669
|
-
port: options.controlPlanePort,
|
|
670
|
-
...(options.controlPlaneAuthToken !== null
|
|
671
|
-
? {
|
|
672
|
-
authToken: options.controlPlaneAuthToken,
|
|
673
|
-
}
|
|
674
|
-
: {}),
|
|
675
|
-
connectRetryWindowMs: controlPlaneConnectRetryWindowMs,
|
|
676
|
-
connectRetryDelayMs: controlPlaneConnectRetryDelayMs,
|
|
677
|
-
}
|
|
678
|
-
: {
|
|
679
|
-
mode: 'embedded' as const,
|
|
680
|
-
};
|
|
681
|
-
const closeLiveSessionsOnClientStop = controlPlaneMode.mode === 'embedded';
|
|
682
|
-
const controlPlaneOpenSpan = startPerfSpan('mux.startup.control-plane-open');
|
|
683
|
-
const controlPlaneClient = await openCodexControlPlaneClient(controlPlaneMode, {
|
|
684
|
-
startEmbeddedServer: async () =>
|
|
685
|
-
await startControlPlaneStreamServer({
|
|
686
|
-
stateStorePath: resolveHarnessRuntimePath(
|
|
687
|
-
options.invocationDirectory,
|
|
688
|
-
process.env.HARNESS_CONTROL_PLANE_DB_PATH ?? '.harness/control-plane.sqlite',
|
|
689
|
-
),
|
|
690
|
-
codexTelemetry: loadedConfig.config.codex.telemetry,
|
|
691
|
-
codexHistory: loadedConfig.config.codex.history,
|
|
692
|
-
critique: loadedConfig.config.critique,
|
|
693
|
-
agentInstall: {
|
|
694
|
-
codex: loadedConfig.config.codex.install,
|
|
695
|
-
claude: loadedConfig.config.claude.install,
|
|
696
|
-
cursor: loadedConfig.config.cursor.install,
|
|
697
|
-
critique: loadedConfig.config.critique.install,
|
|
698
|
-
},
|
|
699
|
-
gitStatus: {
|
|
700
|
-
enabled: loadedConfig.config.mux.git.enabled,
|
|
701
|
-
pollMs: loadedConfig.config.mux.git.idlePollMs,
|
|
702
|
-
maxConcurrency: loadedConfig.config.mux.git.maxConcurrency,
|
|
703
|
-
minDirectoryRefreshMs: Math.max(loadedConfig.config.mux.git.idlePollMs, 30_000),
|
|
704
|
-
},
|
|
705
|
-
github: {
|
|
706
|
-
enabled: loadedConfig.config.github.enabled,
|
|
707
|
-
apiBaseUrl: loadedConfig.config.github.apiBaseUrl,
|
|
708
|
-
tokenEnvVar: loadedConfig.config.github.tokenEnvVar,
|
|
709
|
-
pollMs: loadedConfig.config.github.pollMs,
|
|
710
|
-
maxConcurrency: loadedConfig.config.github.maxConcurrency,
|
|
711
|
-
branchStrategy: loadedConfig.config.github.branchStrategy,
|
|
712
|
-
viewerLogin: loadedConfig.config.github.viewerLogin,
|
|
713
|
-
},
|
|
714
|
-
lifecycleHooks: loadedConfig.config.hooks.lifecycle,
|
|
715
|
-
startSession: (input) => {
|
|
716
|
-
const sessionOptions: Parameters<typeof startCodexLiveSession>[0] = {
|
|
717
|
-
args: input.args,
|
|
718
|
-
initialCols: input.initialCols,
|
|
719
|
-
initialRows: input.initialRows,
|
|
720
|
-
enableSnapshotModel: debugConfig.mux.serverSnapshotModelEnabled,
|
|
721
|
-
};
|
|
722
|
-
if (input.useNotifyHook !== undefined) {
|
|
723
|
-
sessionOptions.useNotifyHook = input.useNotifyHook;
|
|
724
|
-
}
|
|
725
|
-
if (input.command !== undefined) {
|
|
726
|
-
sessionOptions.command = input.command;
|
|
727
|
-
}
|
|
728
|
-
if (input.baseArgs !== undefined) {
|
|
729
|
-
sessionOptions.baseArgs = input.baseArgs;
|
|
730
|
-
}
|
|
731
|
-
if (input.env !== undefined) {
|
|
732
|
-
sessionOptions.env = input.env;
|
|
733
|
-
}
|
|
734
|
-
if (input.cwd !== undefined) {
|
|
735
|
-
sessionOptions.cwd = input.cwd;
|
|
736
|
-
}
|
|
737
|
-
if (input.terminalForegroundHex !== undefined) {
|
|
738
|
-
sessionOptions.terminalForegroundHex = input.terminalForegroundHex;
|
|
739
|
-
}
|
|
740
|
-
if (input.terminalBackgroundHex !== undefined) {
|
|
741
|
-
sessionOptions.terminalBackgroundHex = input.terminalBackgroundHex;
|
|
742
|
-
}
|
|
743
|
-
return startCodexLiveSession(sessionOptions);
|
|
744
|
-
},
|
|
745
|
-
}),
|
|
746
|
-
});
|
|
747
|
-
controlPlaneOpenSpan.end();
|
|
748
|
-
const streamClient = controlPlaneClient.client;
|
|
749
|
-
const controlPlaneService = new ControlPlaneService(streamClient, {
|
|
750
|
-
tenantId: options.scope.tenantId,
|
|
751
|
-
userId: options.scope.userId,
|
|
752
|
-
workspaceId: options.scope.workspaceId,
|
|
753
|
-
});
|
|
754
|
-
const startupObservedCursor = await readObservedStreamCursorBaseline(streamClient, options.scope);
|
|
755
|
-
const directoryUpsertSpan = startPerfSpan('mux.startup.directory-upsert');
|
|
756
|
-
const persistedDirectory = await controlPlaneService.upsertDirectory({
|
|
757
|
-
directoryId: `directory-${options.scope.workspaceId}`,
|
|
758
|
-
path: options.invocationDirectory,
|
|
759
|
-
});
|
|
760
|
-
directoryUpsertSpan.end();
|
|
761
|
-
const workspace = new WorkspaceModel({
|
|
762
|
-
activeDirectoryId: persistedDirectory.directoryId,
|
|
763
|
-
leftNavSelection: {
|
|
764
|
-
kind: 'project',
|
|
765
|
-
directoryId: persistedDirectory.directoryId,
|
|
766
|
-
},
|
|
767
|
-
latestTaskPaneView: {
|
|
768
|
-
rows: [],
|
|
769
|
-
taskIds: [],
|
|
770
|
-
repositoryIds: [],
|
|
771
|
-
actions: [],
|
|
772
|
-
actionCells: [],
|
|
773
|
-
top: 0,
|
|
774
|
-
selectedRepositoryId: null,
|
|
775
|
-
},
|
|
776
|
-
taskDraftComposer: createTaskComposerBuffer(''),
|
|
777
|
-
repositoriesCollapsed: configuredMuxUi.repositoriesCollapsed,
|
|
778
|
-
shortcutsCollapsed: configuredMuxUi.shortcutsCollapsed,
|
|
779
|
-
});
|
|
780
|
-
workspace.repositoryToggleChordPrefixAtMs = null;
|
|
781
|
-
workspace.projectPaneSnapshot = null;
|
|
782
|
-
workspace.projectPaneScrollTop = 0;
|
|
783
|
-
workspace.taskPaneScrollTop = 0;
|
|
784
|
-
workspace.taskPaneSelectedTaskId = null;
|
|
785
|
-
workspace.taskPaneSelectedRepositoryId = null;
|
|
786
|
-
workspace.taskRepositoryDropdownOpen = false;
|
|
787
|
-
workspace.taskEditorTarget = {
|
|
788
|
-
kind: 'draft',
|
|
789
|
-
};
|
|
790
|
-
workspace.taskPaneSelectionFocus = 'task';
|
|
791
|
-
workspace.taskPaneNotice = null;
|
|
792
|
-
workspace.taskPaneTaskEditClickState = null;
|
|
793
|
-
workspace.taskPaneRepositoryEditClickState = null;
|
|
794
|
-
workspace.homePaneDragState = null;
|
|
795
|
-
|
|
796
|
-
const sessionEnv = {
|
|
797
|
-
...sanitizeProcessEnv(),
|
|
798
|
-
TERM: process.env.TERM ?? 'xterm-256color',
|
|
799
|
-
};
|
|
800
|
-
const directoryManager = new DirectoryManager<ControlPlaneDirectoryRecord, GitSummary>();
|
|
801
|
-
directoryManager.setDirectory(persistedDirectory.directoryId, persistedDirectory);
|
|
802
|
-
const directoryRecords = directoryManager.readonlyDirectories();
|
|
803
|
-
const gitSummaryByDirectoryId = directoryManager.mutableGitSummaries();
|
|
804
|
-
const repositoryManager = new RepositoryManager<
|
|
805
|
-
ControlPlaneRepositoryRecord,
|
|
806
|
-
GitRepositorySnapshot
|
|
807
|
-
>();
|
|
808
|
-
const repositories = repositoryManager.mutableRepositories();
|
|
809
|
-
const repositoryAssociationByDirectoryId = repositoryManager.mutableDirectoryAssociations();
|
|
810
|
-
const directoryRepositorySnapshotByDirectoryId = repositoryManager.mutableDirectorySnapshots();
|
|
811
|
-
const muxControllerId = `human-mux-${process.pid}-${randomUUID()}`;
|
|
812
|
-
const muxControllerLabel = `human mux ${process.pid}`;
|
|
813
|
-
const conversationManager = new ConversationManager();
|
|
814
|
-
const conversationRecords = conversationManager.readonlyConversations();
|
|
815
|
-
const taskManager = new TaskManager<ControlPlaneTaskRecord, TaskComposerBuffer, NodeJS.Timeout>();
|
|
816
|
-
const statusTimelineRecorder = new StatusTimelineRecorder({
|
|
817
|
-
statePath: resolveStatusTimelineStatePath(options.invocationDirectory, muxSessionName),
|
|
818
|
-
});
|
|
819
|
-
const renderTraceRecorder = new RenderTraceRecorder({
|
|
820
|
-
statePath: resolveRenderTraceStatePath(options.invocationDirectory, muxSessionName),
|
|
821
|
-
});
|
|
822
|
-
const resolveTraceLabels = (input: {
|
|
823
|
-
sessionId: string | null;
|
|
824
|
-
directoryId: string | null;
|
|
825
|
-
conversationId: string | null;
|
|
826
|
-
}): RenderTraceLabels => {
|
|
827
|
-
const conversation =
|
|
828
|
-
input.sessionId === null
|
|
829
|
-
? input.conversationId === null
|
|
830
|
-
? null
|
|
831
|
-
: (conversationManager.get(input.conversationId) ?? null)
|
|
832
|
-
: (conversationManager.get(input.sessionId) ?? null);
|
|
833
|
-
const resolvedDirectoryId = input.directoryId ?? conversation?.directoryId ?? null;
|
|
834
|
-
const directory =
|
|
835
|
-
resolvedDirectoryId === null ? null : directoryManager.getDirectory(resolvedDirectoryId);
|
|
836
|
-
const repositoryId =
|
|
837
|
-
resolvedDirectoryId === null
|
|
838
|
-
? null
|
|
839
|
-
: (repositoryAssociationByDirectoryId.get(resolvedDirectoryId) ?? null);
|
|
840
|
-
const repository = repositoryId === null ? null : (repositories.get(repositoryId) ?? null);
|
|
841
|
-
return {
|
|
842
|
-
repositoryId,
|
|
843
|
-
repositoryName: repository?.name ?? null,
|
|
844
|
-
projectId: resolvedDirectoryId,
|
|
845
|
-
projectPath: directory?.path ?? null,
|
|
846
|
-
threadId: input.sessionId ?? conversation?.sessionId ?? null,
|
|
847
|
-
threadTitle: conversation?.title ?? null,
|
|
848
|
-
agentType: conversation?.agentType ?? null,
|
|
849
|
-
conversationId: input.conversationId ?? conversation?.sessionId ?? null,
|
|
850
|
-
};
|
|
851
|
-
};
|
|
852
|
-
const recordStatusTimeline = (input: {
|
|
853
|
-
direction: 'incoming' | 'outgoing';
|
|
854
|
-
source: string;
|
|
855
|
-
eventType: string;
|
|
856
|
-
labels: StatusTimelineLabels;
|
|
857
|
-
payload: unknown;
|
|
858
|
-
dedupeKey?: string;
|
|
859
|
-
dedupeValue?: string;
|
|
860
|
-
}): void => {
|
|
861
|
-
const baseRecordInput = {
|
|
862
|
-
direction: input.direction,
|
|
863
|
-
source: input.source,
|
|
864
|
-
eventType: input.eventType,
|
|
865
|
-
labels: input.labels,
|
|
866
|
-
payload: input.payload,
|
|
867
|
-
};
|
|
868
|
-
const recordInput: Parameters<StatusTimelineRecorder['record']>[0] =
|
|
869
|
-
input.dedupeKey !== undefined && input.dedupeValue !== undefined
|
|
870
|
-
? {
|
|
871
|
-
...baseRecordInput,
|
|
872
|
-
dedupeKey: input.dedupeKey,
|
|
873
|
-
dedupeValue: input.dedupeValue,
|
|
874
|
-
}
|
|
875
|
-
: baseRecordInput;
|
|
876
|
-
statusTimelineRecorder.record(recordInput);
|
|
877
|
-
};
|
|
878
|
-
const recordRenderTrace = (input: {
|
|
879
|
-
direction: 'incoming' | 'outgoing';
|
|
880
|
-
source: string;
|
|
881
|
-
eventType: string;
|
|
882
|
-
labels: RenderTraceLabels;
|
|
883
|
-
payload: unknown;
|
|
884
|
-
dedupeKey?: string;
|
|
885
|
-
dedupeValue?: string;
|
|
886
|
-
}): void => {
|
|
887
|
-
const baseRecordInput = {
|
|
888
|
-
direction: input.direction,
|
|
889
|
-
source: input.source,
|
|
890
|
-
eventType: input.eventType,
|
|
891
|
-
labels: input.labels,
|
|
892
|
-
payload: input.payload,
|
|
893
|
-
};
|
|
894
|
-
const recordInput: Parameters<RenderTraceRecorder['record']>[0] =
|
|
895
|
-
input.dedupeKey !== undefined && input.dedupeValue !== undefined
|
|
896
|
-
? {
|
|
897
|
-
...baseRecordInput,
|
|
898
|
-
dedupeKey: input.dedupeKey,
|
|
899
|
-
dedupeValue: input.dedupeValue,
|
|
900
|
-
}
|
|
901
|
-
: baseRecordInput;
|
|
902
|
-
renderTraceRecorder.record(recordInput);
|
|
903
|
-
};
|
|
904
|
-
let keyEventSubscription: Awaited<ReturnType<typeof subscribeControlPlaneKeyEvents>> | null =
|
|
905
|
-
null;
|
|
906
|
-
let hydrateStartupStateForStartupOrchestrator = async (
|
|
907
|
-
_afterCursor: number | null,
|
|
908
|
-
): Promise<void> => {};
|
|
909
|
-
let queuePersistedConversationsForStartupOrchestrator = (
|
|
910
|
-
_activeSessionId: string | null,
|
|
911
|
-
): number => 0;
|
|
912
|
-
let activateConversationForStartupOrchestrator = async (_sessionId: string): Promise<void> => {};
|
|
913
|
-
let shuttingDown = false;
|
|
914
|
-
const startupOrchestrator = new StartupOrchestrator({
|
|
915
|
-
startupSettleQuietMs,
|
|
916
|
-
startupSettleNonemptyFallbackMs: DEFAULT_STARTUP_SETTLE_NONEMPTY_FALLBACK_MS,
|
|
917
|
-
backgroundWaitMaxMs: DEFAULT_BACKGROUND_START_MAX_WAIT_MS,
|
|
918
|
-
backgroundProbeEnabled: backgroundProbesEnabled,
|
|
919
|
-
backgroundResumeEnabled: backgroundResumePersisted,
|
|
920
|
-
startPerfSpan,
|
|
921
|
-
startupSpan,
|
|
922
|
-
recordPerfEvent,
|
|
923
|
-
getConversation: (sessionId) => conversationManager.get(sessionId),
|
|
924
|
-
isShuttingDown: () => shuttingDown,
|
|
925
|
-
refreshProcessUsage: (reason) =>
|
|
926
|
-
void processUsageRefreshService.refresh(reason, conversationRecords),
|
|
927
|
-
queuePersistedConversationsInBackground: (initialActiveId) =>
|
|
928
|
-
queuePersistedConversationsForStartupOrchestrator(initialActiveId),
|
|
929
|
-
hydrateStartupState: async (afterCursor) =>
|
|
930
|
-
await hydrateStartupStateForStartupOrchestrator(afterCursor),
|
|
931
|
-
activateConversation: async (sessionId) =>
|
|
932
|
-
await activateConversationForStartupOrchestrator(sessionId),
|
|
933
|
-
conversationCount: () => conversationManager.size(),
|
|
934
|
-
});
|
|
935
|
-
|
|
936
|
-
const resolveActiveDirectoryId = (): string | null => {
|
|
937
|
-
workspace.activeDirectoryId = directoryManager.resolveActiveDirectoryId(
|
|
938
|
-
workspace.activeDirectoryId,
|
|
939
|
-
);
|
|
940
|
-
return workspace.activeDirectoryId;
|
|
941
|
-
};
|
|
942
|
-
|
|
943
|
-
const resolveDirectoryForAction = (): string | null => {
|
|
944
|
-
return resolveDirectoryForActionFn({
|
|
945
|
-
mainPaneMode: workspace.mainPaneMode,
|
|
946
|
-
activeDirectoryId: workspace.activeDirectoryId,
|
|
947
|
-
activeConversationId: conversationManager.activeConversationId,
|
|
948
|
-
conversations: conversationRecords,
|
|
949
|
-
directoriesHas: (directoryId) => directoryManager.hasDirectory(directoryId),
|
|
950
|
-
});
|
|
951
|
-
};
|
|
952
|
-
|
|
953
|
-
const repositoryGroupIdForDirectory = (directoryId: string): string =>
|
|
954
|
-
repositoryManager.repositoryGroupIdForDirectory(directoryId, UNTRACKED_REPOSITORY_GROUP_ID);
|
|
955
|
-
|
|
956
|
-
const collapseRepositoryGroup = (repositoryGroupId: string): void => {
|
|
957
|
-
repositoryManager.collapseRepositoryGroup(repositoryGroupId, workspace.repositoriesCollapsed);
|
|
958
|
-
};
|
|
959
|
-
|
|
960
|
-
const expandRepositoryGroup = (repositoryGroupId: string): void => {
|
|
961
|
-
repositoryManager.expandRepositoryGroup(repositoryGroupId, workspace.repositoriesCollapsed);
|
|
962
|
-
};
|
|
963
|
-
|
|
964
|
-
const toggleRepositoryGroup = (repositoryGroupId: string): void => {
|
|
965
|
-
repositoryManager.toggleRepositoryGroup(repositoryGroupId, workspace.repositoriesCollapsed);
|
|
966
|
-
};
|
|
967
|
-
|
|
968
|
-
const collapseAllRepositoryGroups = (): void => {
|
|
969
|
-
workspace.repositoriesCollapsed = repositoryManager.collapseAllRepositoryGroups();
|
|
970
|
-
queuePersistMuxUiState();
|
|
971
|
-
};
|
|
972
|
-
|
|
973
|
-
const expandAllRepositoryGroups = (): void => {
|
|
974
|
-
workspace.repositoriesCollapsed = repositoryManager.expandAllRepositoryGroups();
|
|
975
|
-
queuePersistMuxUiState();
|
|
976
|
-
};
|
|
977
|
-
|
|
978
|
-
const firstDirectoryForRepositoryGroup = (repositoryGroupId: string): string | null => {
|
|
979
|
-
return firstDirectoryForRepositoryGroupFn(
|
|
980
|
-
directoryRecords,
|
|
981
|
-
repositoryGroupIdForDirectory,
|
|
982
|
-
repositoryGroupId,
|
|
983
|
-
);
|
|
984
|
-
};
|
|
985
|
-
|
|
986
|
-
conversationManager.configureEnsureDependencies({
|
|
987
|
-
resolveDefaultDirectoryId: resolveActiveDirectoryId,
|
|
988
|
-
normalizeAdapterState,
|
|
989
|
-
createConversation: (input) =>
|
|
990
|
-
createConversationState(
|
|
991
|
-
input.sessionId,
|
|
992
|
-
input.directoryId,
|
|
993
|
-
input.title,
|
|
994
|
-
input.agentType,
|
|
995
|
-
input.adapterState,
|
|
996
|
-
`turn-${randomUUID()}`,
|
|
997
|
-
options.scope,
|
|
998
|
-
layout.rightCols,
|
|
999
|
-
layout.paneRows,
|
|
1000
|
-
),
|
|
1001
|
-
});
|
|
1002
|
-
|
|
1003
|
-
const ensureConversation = (sessionId: string, seed?: ConversationSeed): ConversationState => {
|
|
1004
|
-
return conversationManager.ensure(sessionId, seed);
|
|
1005
|
-
};
|
|
1006
|
-
|
|
1007
|
-
const applyControlPlaneKeyEvent = (event: ControlPlaneKeyEvent): void => {
|
|
1008
|
-
const existing = conversationManager.get(event.sessionId);
|
|
1009
|
-
const beforeProjection =
|
|
1010
|
-
existing === undefined
|
|
1011
|
-
? null
|
|
1012
|
-
: sessionProjectionInstrumentation.snapshotForConversation(existing);
|
|
1013
|
-
const updated = applyMuxControlPlaneKeyEvent(event, {
|
|
1014
|
-
removedConversationIds: conversationManager.removedConversationIds,
|
|
1015
|
-
ensureConversation,
|
|
1016
|
-
});
|
|
1017
|
-
if (updated === null) {
|
|
1018
|
-
return;
|
|
1019
|
-
}
|
|
1020
|
-
if (event.type === 'session-status') {
|
|
1021
|
-
if (event.live) {
|
|
1022
|
-
void conversationLifecycle.subscribeConversationEvents(event.sessionId).catch(() => {});
|
|
1023
|
-
} else {
|
|
1024
|
-
void conversationLifecycle.unsubscribeConversationEvents(event.sessionId).catch(() => {});
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
sessionProjectionInstrumentation.refreshSelectorSnapshot(
|
|
1028
|
-
`event:${event.type}`,
|
|
1029
|
-
directoryRecords,
|
|
1030
|
-
conversationRecords,
|
|
1031
|
-
conversationManager.orderedIds(),
|
|
1032
|
-
);
|
|
1033
|
-
sessionProjectionInstrumentation.recordTransition(event, beforeProjection, updated);
|
|
1034
|
-
};
|
|
1035
|
-
|
|
1036
|
-
const directoryHydrationService = new DirectoryHydrationService<ControlPlaneDirectoryRecord>({
|
|
1037
|
-
controlPlaneService,
|
|
1038
|
-
resolveWorkspacePathForMux: (rawPath) =>
|
|
1039
|
-
resolveWorkspacePathForMux(options.invocationDirectory, rawPath),
|
|
1040
|
-
clearDirectories: () => {
|
|
1041
|
-
directoryManager.clearDirectories();
|
|
1042
|
-
},
|
|
1043
|
-
setDirectory: (directoryId, directory) => {
|
|
1044
|
-
directoryManager.setDirectory(directoryId, directory);
|
|
1045
|
-
},
|
|
1046
|
-
hasDirectory: (directoryId) => directoryManager.hasDirectory(directoryId),
|
|
1047
|
-
persistedDirectory,
|
|
1048
|
-
resolveActiveDirectoryId,
|
|
1049
|
-
});
|
|
1050
|
-
|
|
1051
|
-
const hydrateDirectoryList = async (): Promise<void> => {
|
|
1052
|
-
await directoryHydrationService.hydrate();
|
|
1053
|
-
};
|
|
1054
|
-
|
|
1055
|
-
const syncRepositoryAssociationsWithDirectorySnapshots = (): void => {
|
|
1056
|
-
repositoryManager.syncWithDirectories((directoryId) =>
|
|
1057
|
-
directoryManager.hasDirectory(directoryId),
|
|
1058
|
-
);
|
|
1059
|
-
};
|
|
1060
|
-
|
|
1061
|
-
const hydratePersistedConversationsForDirectory = async (
|
|
1062
|
-
directoryId: string,
|
|
1063
|
-
): Promise<number> => {
|
|
1064
|
-
const persistedRows = await controlPlaneService.listConversations(directoryId);
|
|
1065
|
-
for (const record of persistedRows) {
|
|
1066
|
-
conversationManager.upsertFromPersistedRecord({
|
|
1067
|
-
record,
|
|
1068
|
-
ensureConversation,
|
|
1069
|
-
});
|
|
1070
|
-
}
|
|
1071
|
-
return persistedRows.length;
|
|
1072
|
-
};
|
|
1073
|
-
|
|
1074
|
-
const conversationLifecycle = new ConversationLifecycle<
|
|
1075
|
-
ConversationState,
|
|
1076
|
-
ControlPlaneSessionSummary,
|
|
1077
|
-
ConversationState['controller']
|
|
1078
|
-
>({
|
|
1079
|
-
streamSubscriptions: {
|
|
1080
|
-
subscribePtyEvents: async (sessionId) => {
|
|
1081
|
-
await controlPlaneService.subscribePtyEvents(sessionId);
|
|
1082
|
-
},
|
|
1083
|
-
unsubscribePtyEvents: async (sessionId) => {
|
|
1084
|
-
await controlPlaneService.unsubscribePtyEvents(sessionId);
|
|
1085
|
-
},
|
|
1086
|
-
isSessionNotFoundError,
|
|
1087
|
-
isSessionNotLiveError,
|
|
1088
|
-
subscribeObservedStream: async (afterCursor) => {
|
|
1089
|
-
return await subscribeObservedStream(streamClient, options.scope, afterCursor);
|
|
1090
|
-
},
|
|
1091
|
-
unsubscribeObservedStream: async (subscriptionId) => {
|
|
1092
|
-
await unsubscribeObservedStream(streamClient, subscriptionId);
|
|
1093
|
-
},
|
|
1094
|
-
},
|
|
1095
|
-
starter: {
|
|
1096
|
-
runWithStartInFlight: async (sessionId, run) => {
|
|
1097
|
-
return await conversationManager.runWithStartInFlight(sessionId, run);
|
|
1098
|
-
},
|
|
1099
|
-
conversationById: (sessionId) => conversationManager.get(sessionId),
|
|
1100
|
-
ensureConversation,
|
|
1101
|
-
normalizeThreadAgentType,
|
|
1102
|
-
codexArgs: options.codexArgs,
|
|
1103
|
-
critiqueDefaultArgs: configuredCritique.launch.defaultArgs,
|
|
1104
|
-
sessionCwdForConversation: (conversation) => {
|
|
1105
|
-
const configuredDirectoryPath =
|
|
1106
|
-
conversation.directoryId === null
|
|
1107
|
-
? null
|
|
1108
|
-
: (directoryManager.getDirectory(conversation.directoryId)?.path ?? null);
|
|
1109
|
-
return resolveWorkspacePathForMux(
|
|
1110
|
-
options.invocationDirectory,
|
|
1111
|
-
configuredDirectoryPath ?? options.invocationDirectory,
|
|
1112
|
-
);
|
|
1113
|
-
},
|
|
1114
|
-
buildLaunchArgs: (input) => {
|
|
1115
|
-
return buildAgentSessionStartArgs(
|
|
1116
|
-
input.agentType,
|
|
1117
|
-
input.baseArgsForAgent,
|
|
1118
|
-
input.adapterState,
|
|
1119
|
-
{
|
|
1120
|
-
directoryPath: input.sessionCwd,
|
|
1121
|
-
codexLaunchDefaultMode: configuredCodexLaunch.defaultMode,
|
|
1122
|
-
codexLaunchModeByDirectoryPath: codexLaunchModeByDirectoryPath,
|
|
1123
|
-
claudeLaunchDefaultMode: configuredClaudeLaunch.defaultMode,
|
|
1124
|
-
claudeLaunchModeByDirectoryPath: claudeLaunchModeByDirectoryPath,
|
|
1125
|
-
cursorLaunchDefaultMode: configuredCursorLaunch.defaultMode,
|
|
1126
|
-
cursorLaunchModeByDirectoryPath: cursorLaunchModeByDirectoryPath,
|
|
1127
|
-
},
|
|
1128
|
-
);
|
|
1129
|
-
},
|
|
1130
|
-
launchCommandForAgent,
|
|
1131
|
-
formatCommandForDebugBar,
|
|
1132
|
-
startConversationSpan: (sessionId) =>
|
|
1133
|
-
startPerfSpan('mux.conversation.start', {
|
|
1134
|
-
sessionId,
|
|
1135
|
-
}),
|
|
1136
|
-
firstPaintTargetSessionId: () => startupOrchestrator.firstPaintTargetSessionId,
|
|
1137
|
-
endStartCommandSpan: (input) => {
|
|
1138
|
-
startupOrchestrator.endStartCommandSpan(input);
|
|
1139
|
-
},
|
|
1140
|
-
layout: () => layout,
|
|
1141
|
-
startPtySession: async (input) => {
|
|
1142
|
-
await controlPlaneService.startPtySession(input);
|
|
1143
|
-
},
|
|
1144
|
-
setPtySize: (sessionId, size) => {
|
|
1145
|
-
ptySizeByConversationId.set(sessionId, size);
|
|
1146
|
-
},
|
|
1147
|
-
sendResize: (sessionId, cols, rows) => {
|
|
1148
|
-
streamClient.sendResize(sessionId, cols, rows);
|
|
1149
|
-
},
|
|
1150
|
-
sessionEnv,
|
|
1151
|
-
worktreeId: options.scope.worktreeId,
|
|
1152
|
-
terminalForegroundHex: resolvedTerminalForegroundHex,
|
|
1153
|
-
terminalBackgroundHex: resolvedTerminalBackgroundHex,
|
|
1154
|
-
recordStartCommand: (sessionId, launchArgs) => {
|
|
1155
|
-
recordPerfEvent('mux.conversation.start.command', {
|
|
1156
|
-
sessionId,
|
|
1157
|
-
argCount: launchArgs.length,
|
|
1158
|
-
resumed: launchArgs[0] === 'resume',
|
|
1159
|
-
});
|
|
1160
|
-
},
|
|
1161
|
-
getSessionStatus: async (sessionId) => {
|
|
1162
|
-
return await controlPlaneService.getSessionStatus(sessionId);
|
|
1163
|
-
},
|
|
1164
|
-
upsertFromSessionSummary: (summary) => {
|
|
1165
|
-
conversationManager.upsertFromSessionSummary({
|
|
1166
|
-
summary,
|
|
1167
|
-
ensureConversation,
|
|
1168
|
-
});
|
|
1169
|
-
},
|
|
1170
|
-
},
|
|
1171
|
-
startupHydration: {
|
|
1172
|
-
startHydrationSpan: () => startPerfSpan('mux.startup.hydrate-conversations'),
|
|
1173
|
-
hydrateDirectoryList,
|
|
1174
|
-
directoryIds: () => directoryManager.directoryIds(),
|
|
1175
|
-
hydratePersistedConversationsForDirectory,
|
|
1176
|
-
listSessions: async () => {
|
|
1177
|
-
return await controlPlaneService.listSessions({
|
|
1178
|
-
worktreeId: options.scope.worktreeId,
|
|
1179
|
-
sort: 'started-asc',
|
|
1180
|
-
});
|
|
1181
|
-
},
|
|
1182
|
-
upsertFromSessionSummary: (summary) => {
|
|
1183
|
-
conversationManager.upsertFromSessionSummary({
|
|
1184
|
-
summary,
|
|
1185
|
-
ensureConversation,
|
|
1186
|
-
});
|
|
1187
|
-
},
|
|
1188
|
-
},
|
|
1189
|
-
startupQueue: {
|
|
1190
|
-
orderedConversationIds: () => conversationManager.orderedIds(),
|
|
1191
|
-
conversationById: (sessionId) => conversationManager.get(sessionId),
|
|
1192
|
-
queueBackgroundOp: (task, label) => {
|
|
1193
|
-
queueBackgroundControlPlaneOp(task, label);
|
|
1194
|
-
},
|
|
1195
|
-
markDirty: () => {
|
|
1196
|
-
markDirty();
|
|
1197
|
-
},
|
|
1198
|
-
},
|
|
1199
|
-
activation: {
|
|
1200
|
-
getActiveConversationId: () => conversationManager.activeConversationId,
|
|
1201
|
-
setActiveConversationId: (sessionId) => {
|
|
1202
|
-
conversationManager.setActiveConversationId(sessionId);
|
|
1203
|
-
},
|
|
1204
|
-
isConversationPaneMode: () => workspace.mainPaneMode === 'conversation',
|
|
1205
|
-
enterConversationPaneForActiveSession: (sessionId) => {
|
|
1206
|
-
workspace.mainPaneMode = 'conversation';
|
|
1207
|
-
workspace.selectLeftNavConversation(sessionId);
|
|
1208
|
-
screen.resetFrameCache();
|
|
1209
|
-
},
|
|
1210
|
-
enterConversationPaneForSessionSwitch: (sessionId) => {
|
|
1211
|
-
workspace.mainPaneMode = 'conversation';
|
|
1212
|
-
workspace.selectLeftNavConversation(sessionId);
|
|
1213
|
-
workspace.homePaneDragState = null;
|
|
1214
|
-
workspace.taskPaneTaskEditClickState = null;
|
|
1215
|
-
workspace.taskPaneRepositoryEditClickState = null;
|
|
1216
|
-
workspace.projectPaneSnapshot = null;
|
|
1217
|
-
workspace.projectPaneScrollTop = 0;
|
|
1218
|
-
screen.resetFrameCache();
|
|
1219
|
-
},
|
|
1220
|
-
stopConversationTitleEditForOtherSession: (sessionId) => {
|
|
1221
|
-
if (
|
|
1222
|
-
workspace.conversationTitleEdit !== null &&
|
|
1223
|
-
workspace.conversationTitleEdit.conversationId !== sessionId
|
|
1224
|
-
) {
|
|
1225
|
-
stopConversationTitleEdit(true);
|
|
1226
|
-
}
|
|
1227
|
-
},
|
|
1228
|
-
clearSelectionState: () => {
|
|
1229
|
-
workspace.selection = null;
|
|
1230
|
-
workspace.selectionDrag = null;
|
|
1231
|
-
releaseViewportPinForSelection();
|
|
1232
|
-
},
|
|
1233
|
-
detachConversation: async (sessionId) => {
|
|
1234
|
-
await detachConversation(sessionId);
|
|
1235
|
-
},
|
|
1236
|
-
conversationById: (sessionId) => conversationManager.get(sessionId),
|
|
1237
|
-
noteGitActivity: (directoryId) => {
|
|
1238
|
-
noteGitActivity(directoryId);
|
|
1239
|
-
},
|
|
1240
|
-
attachConversation: async (sessionId) => {
|
|
1241
|
-
await attachConversation(sessionId);
|
|
1242
|
-
},
|
|
1243
|
-
isSessionNotFoundError,
|
|
1244
|
-
isSessionNotLiveError,
|
|
1245
|
-
markSessionUnavailable: (sessionId) => {
|
|
1246
|
-
conversationManager.markSessionUnavailable(sessionId);
|
|
1247
|
-
},
|
|
1248
|
-
schedulePtyResizeImmediate: () => {
|
|
1249
|
-
schedulePtyResize(
|
|
1250
|
-
{
|
|
1251
|
-
cols: layout.rightCols,
|
|
1252
|
-
rows: layout.paneRows,
|
|
1253
|
-
},
|
|
1254
|
-
true,
|
|
1255
|
-
);
|
|
1256
|
-
},
|
|
1257
|
-
markDirty: () => {
|
|
1258
|
-
markDirty();
|
|
1259
|
-
},
|
|
1260
|
-
},
|
|
1261
|
-
actions: {
|
|
1262
|
-
controlPlaneService,
|
|
1263
|
-
createConversationId: () => `conversation-${randomUUID()}`,
|
|
1264
|
-
ensureConversation,
|
|
1265
|
-
noteGitActivity: (directoryId) => {
|
|
1266
|
-
noteGitActivity(directoryId);
|
|
1267
|
-
},
|
|
1268
|
-
orderedConversationIds: () => conversationManager.orderedIds(),
|
|
1269
|
-
conversationById: (sessionId) => {
|
|
1270
|
-
const conversation = conversationManager.get(sessionId);
|
|
1271
|
-
if (conversation === undefined) {
|
|
1272
|
-
return null;
|
|
1273
|
-
}
|
|
1274
|
-
return {
|
|
1275
|
-
directoryId: conversation.directoryId,
|
|
1276
|
-
agentType: conversation.agentType,
|
|
1277
|
-
};
|
|
1278
|
-
},
|
|
1279
|
-
conversationsHas: (sessionId) => conversationManager.has(sessionId),
|
|
1280
|
-
applyController: (sessionId, controller) => {
|
|
1281
|
-
conversationManager.setController(sessionId, controller);
|
|
1282
|
-
},
|
|
1283
|
-
setLastEventNow: (sessionId) => {
|
|
1284
|
-
conversationManager.setLastEventAt(sessionId, new Date().toISOString());
|
|
1285
|
-
},
|
|
1286
|
-
muxControllerId,
|
|
1287
|
-
muxControllerLabel,
|
|
1288
|
-
markDirty: () => {
|
|
1289
|
-
markDirty();
|
|
1290
|
-
},
|
|
1291
|
-
},
|
|
1292
|
-
titleEdit: {
|
|
1293
|
-
workspace,
|
|
1294
|
-
updateConversationTitle: async (input) => {
|
|
1295
|
-
return await controlPlaneService.updateConversationTitle(input);
|
|
1296
|
-
},
|
|
1297
|
-
conversationById: (conversationId) => conversationManager.get(conversationId),
|
|
1298
|
-
markDirty: () => {
|
|
1299
|
-
markDirty();
|
|
1300
|
-
},
|
|
1301
|
-
queueControlPlaneOp: (task, label) => {
|
|
1302
|
-
queueControlPlaneOp(task, label);
|
|
1303
|
-
},
|
|
1304
|
-
debounceMs: DEFAULT_CONVERSATION_TITLE_EDIT_DEBOUNCE_MS,
|
|
1305
|
-
},
|
|
1306
|
-
});
|
|
1307
|
-
|
|
1308
|
-
const queuePersistedConversationsInBackground = (activeSessionId: string | null): number => {
|
|
1309
|
-
return conversationLifecycle.queuePersistedConversationsInBackground(activeSessionId);
|
|
1310
|
-
};
|
|
1311
|
-
|
|
1312
|
-
const hydrateConversationList = async (): Promise<void> => {
|
|
1313
|
-
await conversationLifecycle.hydrateConversationList();
|
|
1314
|
-
};
|
|
1315
|
-
const startupStateHydrationService = new StartupStateHydrationService<
|
|
1316
|
-
ControlPlaneRepositoryRecord,
|
|
1317
|
-
GitSummary,
|
|
1318
|
-
GitRepositorySnapshot,
|
|
1319
|
-
ControlPlaneDirectoryGitStatusRecord
|
|
1320
|
-
>({
|
|
1321
|
-
hydrateConversationList,
|
|
1322
|
-
listRepositories: async () => {
|
|
1323
|
-
return await controlPlaneService.listRepositories();
|
|
1324
|
-
},
|
|
1325
|
-
clearRepositories: () => {
|
|
1326
|
-
repositories.clear();
|
|
1327
|
-
},
|
|
1328
|
-
setRepository: (repositoryId, repository) => {
|
|
1329
|
-
repositories.set(repositoryId, repository);
|
|
1330
|
-
},
|
|
1331
|
-
syncRepositoryAssociationsWithDirectorySnapshots,
|
|
1332
|
-
gitHydrationEnabled: configuredMuxGit.enabled,
|
|
1333
|
-
listDirectoryGitStatuses: async () => {
|
|
1334
|
-
return await controlPlaneService.listDirectoryGitStatuses();
|
|
1335
|
-
},
|
|
1336
|
-
setDirectoryGitSummary: (directoryId, summary) => {
|
|
1337
|
-
gitSummaryByDirectoryId.set(directoryId, summary);
|
|
1338
|
-
},
|
|
1339
|
-
setDirectoryRepositorySnapshot: (directoryId, snapshot) => {
|
|
1340
|
-
repositoryManager.setDirectoryRepositorySnapshot(directoryId, snapshot);
|
|
1341
|
-
},
|
|
1342
|
-
setDirectoryRepositoryAssociation: (directoryId, repositoryId) => {
|
|
1343
|
-
repositoryManager.setDirectoryRepositoryAssociation(directoryId, repositoryId);
|
|
1344
|
-
},
|
|
1345
|
-
hydrateTaskPlanningState,
|
|
1346
|
-
subscribeTaskPlanningEvents: async (afterCursor) => {
|
|
1347
|
-
await conversationLifecycle.subscribeTaskPlanningEvents(afterCursor);
|
|
1348
|
-
},
|
|
1349
|
-
ensureActiveConversationId: () => {
|
|
1350
|
-
conversationManager.ensureActiveConversationId();
|
|
1351
|
-
},
|
|
1352
|
-
activeConversationId: () => conversationManager.activeConversationId,
|
|
1353
|
-
selectLeftNavConversation: (sessionId) => {
|
|
1354
|
-
workspace.selectLeftNavConversation(sessionId);
|
|
1355
|
-
},
|
|
1356
|
-
enterHomePane: () => {
|
|
1357
|
-
workspace.enterHomePane();
|
|
1358
|
-
},
|
|
1359
|
-
});
|
|
1360
|
-
queuePersistedConversationsForStartupOrchestrator = queuePersistedConversationsInBackground;
|
|
1361
|
-
hydrateStartupStateForStartupOrchestrator = async (afterCursor) =>
|
|
1362
|
-
await startupStateHydrationService.hydrateStartupState(afterCursor);
|
|
1363
|
-
|
|
1364
|
-
const runtimeGitState = new RuntimeGitState<ControlPlaneRepositoryRecord>({
|
|
1365
|
-
enabled: configuredMuxGit.enabled,
|
|
1366
|
-
directoryManager,
|
|
1367
|
-
directoryRepositorySnapshotByDirectoryId,
|
|
1368
|
-
repositoryAssociationByDirectoryId,
|
|
1369
|
-
repositories,
|
|
1370
|
-
parseRepositoryRecord,
|
|
1371
|
-
loadingSummary: GIT_SUMMARY_LOADING,
|
|
1372
|
-
emptyRepositorySnapshot: GIT_REPOSITORY_NONE,
|
|
1373
|
-
syncRepositoryAssociationsWithDirectorySnapshots,
|
|
1374
|
-
syncTaskPaneRepositorySelection: () => {
|
|
1375
|
-
syncTaskPaneRepositorySelection();
|
|
1376
|
-
},
|
|
1377
|
-
markDirty: () => {
|
|
1378
|
-
markDirty();
|
|
1379
|
-
},
|
|
1380
|
-
});
|
|
1381
|
-
const deleteDirectoryGitState = (directoryId: string): void => {
|
|
1382
|
-
runtimeGitState.deleteDirectoryGitState(directoryId);
|
|
1383
|
-
};
|
|
1384
|
-
const syncGitStateWithDirectories = (): void => {
|
|
1385
|
-
runtimeGitState.syncGitStateWithDirectories();
|
|
1386
|
-
};
|
|
1387
|
-
const noteGitActivity = (directoryId: string | null): void => {
|
|
1388
|
-
runtimeGitState.noteGitActivity(directoryId);
|
|
1389
|
-
};
|
|
1390
|
-
const applyObservedGitStatusEvent = (observed: StreamObservedEvent): void => {
|
|
1391
|
-
runtimeGitState.applyObservedGitStatusEvent(observed);
|
|
1392
|
-
};
|
|
1393
|
-
|
|
1394
|
-
const idFactory = (): string => `event-${randomUUID()}`;
|
|
1395
|
-
let exit: PtyExit | null = null;
|
|
1396
|
-
const screen = new Screen({
|
|
1397
|
-
writeError: (output) => {
|
|
1398
|
-
process.stderr.write(output);
|
|
1399
|
-
const prefix = '[mux] ansi-integrity-failed ';
|
|
1400
|
-
const prefixIndex = output.indexOf(prefix);
|
|
1401
|
-
if (prefixIndex < 0) {
|
|
1402
|
-
return;
|
|
1403
|
-
}
|
|
1404
|
-
const issueText = output.slice(prefixIndex + prefix.length).trim();
|
|
1405
|
-
const issues = issueText
|
|
1406
|
-
.split(' | ')
|
|
1407
|
-
.map((value) => value.trim())
|
|
1408
|
-
.filter((value) => value.length > 0);
|
|
1409
|
-
const activeConversationId = conversationManager.activeConversationId;
|
|
1410
|
-
const labels = resolveTraceLabels({
|
|
1411
|
-
sessionId: activeConversationId,
|
|
1412
|
-
directoryId: workspace.activeDirectoryId,
|
|
1413
|
-
conversationId: activeConversationId,
|
|
1414
|
-
});
|
|
1415
|
-
recordRenderTrace({
|
|
1416
|
-
direction: 'outgoing',
|
|
1417
|
-
source: 'screen',
|
|
1418
|
-
eventType: 'ansi-integrity-failed',
|
|
1419
|
-
labels,
|
|
1420
|
-
payload: {
|
|
1421
|
-
issues,
|
|
1422
|
-
message: issueText,
|
|
1423
|
-
},
|
|
1424
|
-
dedupeKey: 'ansi-integrity-failed',
|
|
1425
|
-
dedupeValue: issueText,
|
|
1426
|
-
});
|
|
1427
|
-
},
|
|
1428
|
-
});
|
|
1429
|
-
const conversationPane = new ConversationPane();
|
|
1430
|
-
const homePane = new HomePane();
|
|
1431
|
-
const projectPane = new ProjectPane();
|
|
1432
|
-
const leftRailPane = new LeftRailPane();
|
|
1433
|
-
let stop = false;
|
|
1434
|
-
let inputRemainder = '';
|
|
1435
|
-
const debugFooterNotice = new DebugFooterNotice({
|
|
1436
|
-
ttlMs: DEBUG_FOOTER_NOTICE_TTL_MS,
|
|
1437
|
-
});
|
|
1438
|
-
const commandMenuRegistry = new CommandMenuRegistry<RuntimeCommandMenuContext>();
|
|
1439
|
-
let commandMenuGitHubProjectPrState: CommandMenuGitHubProjectPrState | null = null;
|
|
1440
|
-
let commandMenuScopedDirectoryId: string | null = null;
|
|
1441
|
-
let themePickerSession: ThemePickerSessionState | null = null;
|
|
1442
|
-
const isThreadScopedCommandActionId = (actionId: string): boolean =>
|
|
1443
|
-
actionId.startsWith('thread.start.') || actionId.startsWith('thread.install.');
|
|
1444
|
-
const commandMenuContext = (
|
|
1445
|
-
input: {
|
|
1446
|
-
readonly preferThreadScope?: boolean;
|
|
1447
|
-
} = {},
|
|
1448
|
-
): RuntimeCommandMenuContext => {
|
|
1449
|
-
const activeConversation = conversationManager.getActiveConversation();
|
|
1450
|
-
const scopedDirectoryId =
|
|
1451
|
-
(input.preferThreadScope === true || workspace.commandMenu?.scope === 'thread-start') &&
|
|
1452
|
-
commandMenuScopedDirectoryId !== null &&
|
|
1453
|
-
directoryManager.hasDirectory(commandMenuScopedDirectoryId)
|
|
1454
|
-
? commandMenuScopedDirectoryId
|
|
1455
|
-
: null;
|
|
1456
|
-
const activeDirectoryId = scopedDirectoryId ?? resolveDirectoryForAction();
|
|
1457
|
-
const activeDirectoryRepositorySnapshot =
|
|
1458
|
-
activeDirectoryId === null
|
|
1459
|
-
? null
|
|
1460
|
-
: (directoryRepositorySnapshotByDirectoryId.get(activeDirectoryId) ?? null);
|
|
1461
|
-
const githubProjectPrState =
|
|
1462
|
-
activeDirectoryId !== null &&
|
|
1463
|
-
commandMenuGitHubProjectPrState !== null &&
|
|
1464
|
-
commandMenuGitHubProjectPrState.directoryId === activeDirectoryId
|
|
1465
|
-
? commandMenuGitHubProjectPrState
|
|
1466
|
-
: null;
|
|
1467
|
-
const currentBranchForActions =
|
|
1468
|
-
activeDirectoryId === null
|
|
1469
|
-
? null
|
|
1470
|
-
: (gitSummaryByDirectoryId.get(activeDirectoryId)?.branch ?? null);
|
|
1471
|
-
const trackedBranchForActions = resolveGitHubTrackedBranchForActions({
|
|
1472
|
-
projectTrackedBranch: githubProjectPrState?.branchName ?? null,
|
|
1473
|
-
currentBranch: currentBranchForActions,
|
|
1474
|
-
});
|
|
1475
|
-
const selectedText =
|
|
1476
|
-
workspace.selection === null
|
|
1477
|
-
? ''
|
|
1478
|
-
: workspace.selection.text.length > 0
|
|
1479
|
-
? workspace.selection.text
|
|
1480
|
-
: activeConversation === null
|
|
1481
|
-
? ''
|
|
1482
|
-
: selectionText(activeConversation.oracle.snapshotWithoutHash(), workspace.selection);
|
|
1483
|
-
return {
|
|
1484
|
-
activeDirectoryId,
|
|
1485
|
-
activeConversationId: conversationManager.activeConversationId,
|
|
1486
|
-
selectedText,
|
|
1487
|
-
leftNavSelectionKind: workspace.leftNavSelection.kind,
|
|
1488
|
-
profileRunning: hasActiveProfileState(
|
|
1489
|
-
resolveProfileStatePath(options.invocationDirectory, muxSessionName),
|
|
1490
|
-
),
|
|
1491
|
-
statusTimelineRunning: existsSync(
|
|
1492
|
-
resolveStatusTimelineStatePath(options.invocationDirectory, muxSessionName),
|
|
1493
|
-
),
|
|
1494
|
-
githubRepositoryUrl: activeDirectoryRepositorySnapshot?.normalizedRemoteUrl ?? null,
|
|
1495
|
-
githubDefaultBranch: activeDirectoryRepositorySnapshot?.defaultBranch ?? null,
|
|
1496
|
-
githubTrackedBranch: trackedBranchForActions,
|
|
1497
|
-
githubOpenPrUrl: githubProjectPrState?.openPrUrl ?? null,
|
|
1498
|
-
githubProjectPrLoading: githubProjectPrState?.loading ?? false,
|
|
1499
|
-
};
|
|
1500
|
-
};
|
|
1501
|
-
const resolveVisibleCommandMenuActions = (
|
|
1502
|
-
context: RuntimeCommandMenuContext,
|
|
1503
|
-
): readonly RegisteredCommandMenuAction<RuntimeCommandMenuContext>[] => {
|
|
1504
|
-
const actions = commandMenuRegistry.resolveActions(context);
|
|
1505
|
-
if (workspace.commandMenu?.scope === 'thread-start') {
|
|
1506
|
-
return actions.filter(
|
|
1507
|
-
(action) =>
|
|
1508
|
-
action.id.startsWith('thread.start.') || action.id.startsWith('thread.install.'),
|
|
1509
|
-
);
|
|
1510
|
-
}
|
|
1511
|
-
return filterThemePresetActionsForScope(
|
|
1512
|
-
actions,
|
|
1513
|
-
workspace.commandMenu?.scope ?? 'all',
|
|
1514
|
-
THEME_ACTION_ID_PREFIX,
|
|
1515
|
-
);
|
|
1516
|
-
};
|
|
1517
|
-
const resolveCommandMenuActions = (): readonly CommandMenuActionDescriptor[] => {
|
|
1518
|
-
return resolveVisibleCommandMenuActions(commandMenuContext()).map((action) => ({
|
|
1519
|
-
id: action.id,
|
|
1520
|
-
title: action.title,
|
|
1521
|
-
...(action.aliases === undefined
|
|
1522
|
-
? {}
|
|
1523
|
-
: {
|
|
1524
|
-
aliases: action.aliases,
|
|
1525
|
-
}),
|
|
1526
|
-
...(action.keywords === undefined
|
|
1527
|
-
? {}
|
|
1528
|
-
: {
|
|
1529
|
-
keywords: action.keywords,
|
|
1530
|
-
}),
|
|
1531
|
-
...(action.detail === undefined
|
|
1532
|
-
? {}
|
|
1533
|
-
: {
|
|
1534
|
-
detail: action.detail,
|
|
1535
|
-
}),
|
|
1536
|
-
}));
|
|
1537
|
-
};
|
|
1538
|
-
const executeCommandMenuAction = (actionId: string): void => {
|
|
1539
|
-
const context = commandMenuContext({
|
|
1540
|
-
preferThreadScope: isThreadScopedCommandActionId(actionId),
|
|
1541
|
-
});
|
|
1542
|
-
const action =
|
|
1543
|
-
resolveVisibleCommandMenuActions(context).find((candidate) => candidate.id === actionId) ??
|
|
1544
|
-
null;
|
|
1545
|
-
if (action === null) {
|
|
1546
|
-
return;
|
|
1547
|
-
}
|
|
1548
|
-
void Promise.resolve(action.run(context)).catch((error: unknown) => {
|
|
1549
|
-
const message = formatErrorMessage(error);
|
|
1550
|
-
workspace.taskPaneNotice = `command menu failed: ${message}`;
|
|
1551
|
-
debugFooterNotice.set(`command menu failed: ${message}`);
|
|
1552
|
-
markDirty();
|
|
1553
|
-
});
|
|
1554
|
-
};
|
|
1555
|
-
const createModalManager = (): ModalManager =>
|
|
1556
|
-
new ModalManager({
|
|
1557
|
-
theme: currentModalTheme,
|
|
1558
|
-
resolveRepositoryName: (repositoryId) => repositories.get(repositoryId)?.name ?? null,
|
|
1559
|
-
getCommandMenu: () => workspace.commandMenu,
|
|
1560
|
-
resolveCommandMenuActions,
|
|
1561
|
-
getNewThreadPrompt: () => workspace.newThreadPrompt,
|
|
1562
|
-
getAddDirectoryPrompt: () => workspace.addDirectoryPrompt,
|
|
1563
|
-
getTaskEditorPrompt: () => workspace.taskEditorPrompt,
|
|
1564
|
-
getRepositoryPrompt: () => workspace.repositoryPrompt,
|
|
1565
|
-
getConversationTitleEdit: () => workspace.conversationTitleEdit,
|
|
1566
|
-
});
|
|
1567
|
-
let modalManager = createModalManager();
|
|
1568
|
-
let homePaneBackgroundTimer: ReturnType<typeof setInterval> | null = null;
|
|
1569
|
-
const ptySizeByConversationId = new Map<string, { cols: number; rows: number }>();
|
|
1570
|
-
|
|
1571
|
-
const requestStop = (): void => {
|
|
1572
|
-
requestStopFn({
|
|
1573
|
-
stop,
|
|
1574
|
-
hasConversationTitleEdit: workspace.conversationTitleEdit !== null,
|
|
1575
|
-
stopConversationTitleEdit: () => stopConversationTitleEdit(true),
|
|
1576
|
-
activeTaskEditorTaskId:
|
|
1577
|
-
'taskId' in workspace.taskEditorTarget &&
|
|
1578
|
-
typeof workspace.taskEditorTarget.taskId === 'string'
|
|
1579
|
-
? workspace.taskEditorTarget.taskId
|
|
1580
|
-
: null,
|
|
1581
|
-
autosaveTaskIds: [...taskManager.autosaveTaskIds()],
|
|
1582
|
-
flushTaskComposerPersist,
|
|
1583
|
-
closeLiveSessionsOnClientStop,
|
|
1584
|
-
orderedConversationIds: conversationManager.orderedIds(),
|
|
1585
|
-
conversations: conversationRecords,
|
|
1586
|
-
queueControlPlaneOp,
|
|
1587
|
-
sendSignal: (sessionId, signal) => {
|
|
1588
|
-
streamClient.sendSignal(sessionId, signal);
|
|
1589
|
-
},
|
|
1590
|
-
closeSession: async (sessionId) => {
|
|
1591
|
-
await controlPlaneService.closePtySession(sessionId);
|
|
1592
|
-
},
|
|
1593
|
-
markDirty,
|
|
1594
|
-
setStop: (next) => {
|
|
1595
|
-
stop = next;
|
|
1596
|
-
},
|
|
1597
|
-
});
|
|
1598
|
-
};
|
|
1599
|
-
|
|
1600
|
-
const runtimeRenderLifecycle = new RuntimeRenderLifecycle({
|
|
1601
|
-
screen,
|
|
1602
|
-
render: () => {
|
|
1603
|
-
render();
|
|
1604
|
-
},
|
|
1605
|
-
isShuttingDown: () => shuttingDown,
|
|
1606
|
-
setShuttingDown: (next) => {
|
|
1607
|
-
shuttingDown = next;
|
|
1608
|
-
},
|
|
1609
|
-
setStop: (next) => {
|
|
1610
|
-
stop = next;
|
|
1611
|
-
},
|
|
1612
|
-
restoreTerminalState: () => {
|
|
1613
|
-
restoreTerminalState(true, inputModeManager.restore);
|
|
1614
|
-
},
|
|
1615
|
-
formatErrorMessage,
|
|
1616
|
-
writeStderr: (text) => process.stderr.write(text),
|
|
1617
|
-
exitProcess: (code) => {
|
|
1618
|
-
process.exit(code);
|
|
1619
|
-
},
|
|
1620
|
-
});
|
|
1621
|
-
const handleRuntimeFatal = (origin: string, error: unknown): void => {
|
|
1622
|
-
runtimeRenderLifecycle.handleRuntimeFatal(origin, error);
|
|
1623
|
-
};
|
|
1624
|
-
const scheduleRender = (): void => {
|
|
1625
|
-
runtimeRenderLifecycle.scheduleRender();
|
|
1626
|
-
};
|
|
1627
|
-
const markDirty = (): void => {
|
|
1628
|
-
runtimeRenderLifecycle.markDirty();
|
|
1629
|
-
};
|
|
1630
|
-
const controlPlaneOps = new RuntimeControlPlaneOps({
|
|
1631
|
-
onFatal: (error: unknown) => {
|
|
1632
|
-
handleRuntimeFatal('control-plane-pump', error);
|
|
1633
|
-
},
|
|
1634
|
-
startPerfSpan,
|
|
1635
|
-
recordPerfEvent,
|
|
1636
|
-
writeStderr: (text) => process.stderr.write(text),
|
|
1637
|
-
});
|
|
1638
|
-
const waitForControlPlaneDrain = async (): Promise<void> => {
|
|
1639
|
-
await controlPlaneOps.waitForDrain();
|
|
1640
|
-
};
|
|
1641
|
-
const queueControlPlaneOp = (task: () => Promise<void>, label = 'interactive-op'): void => {
|
|
1642
|
-
controlPlaneOps.enqueueInteractive(task, label);
|
|
1643
|
-
};
|
|
1644
|
-
const queueBackgroundControlPlaneOp = (
|
|
1645
|
-
task: () => Promise<void>,
|
|
1646
|
-
label = 'background-op',
|
|
1647
|
-
): void => {
|
|
1648
|
-
controlPlaneOps.enqueueBackground(task, label);
|
|
1649
|
-
};
|
|
1650
|
-
const commandMenuAgentTools = new RuntimeCommandMenuAgentTools({
|
|
1651
|
-
sendCommand: async (command) => await streamClient.sendCommand(command),
|
|
1652
|
-
queueControlPlaneOp,
|
|
1653
|
-
getCommandMenu: () => workspace.commandMenu,
|
|
1654
|
-
markDirty,
|
|
1655
|
-
});
|
|
1656
|
-
const setCommandNotice = (message: string): void => {
|
|
1657
|
-
workspace.taskPaneNotice = message;
|
|
1658
|
-
debugFooterNotice.set(message);
|
|
1659
|
-
markDirty();
|
|
1660
|
-
};
|
|
1661
|
-
const buildPresetThemeConfig = (preset: string): HarnessMuxThemeConfig => {
|
|
1662
|
-
return {
|
|
1663
|
-
preset,
|
|
1664
|
-
mode: runtimeThemeConfig?.mode ?? 'dark',
|
|
1665
|
-
customThemePath: null,
|
|
1666
|
-
};
|
|
1667
|
-
};
|
|
1668
|
-
const applyThemeConfig = (nextThemeConfig: HarnessMuxThemeConfig | null): void => {
|
|
1669
|
-
const resolved = resolveAndApplyRuntimeTheme(nextThemeConfig);
|
|
1670
|
-
currentModalTheme = resolved.theme.modalTheme;
|
|
1671
|
-
modalManager = createModalManager();
|
|
1672
|
-
};
|
|
1673
|
-
const persistThemeConfig = (nextThemeConfig: HarnessMuxThemeConfig): string | null => {
|
|
1674
|
-
if (loadedConfig.error !== null) {
|
|
1675
|
-
return 'config currently using last-known-good due to parse error';
|
|
1676
|
-
}
|
|
1677
|
-
try {
|
|
1678
|
-
const updated = updateHarnessConfig({
|
|
1679
|
-
filePath: loadedConfig.filePath,
|
|
1680
|
-
update: (current) => {
|
|
1681
|
-
return {
|
|
1682
|
-
...current,
|
|
1683
|
-
mux: {
|
|
1684
|
-
...current.mux,
|
|
1685
|
-
ui: {
|
|
1686
|
-
...current.mux.ui,
|
|
1687
|
-
theme: nextThemeConfig,
|
|
1688
|
-
},
|
|
1689
|
-
},
|
|
1690
|
-
};
|
|
1691
|
-
},
|
|
1692
|
-
});
|
|
1693
|
-
runtimeThemeConfig = updated.mux.ui.theme;
|
|
1694
|
-
return null;
|
|
1695
|
-
} catch (error: unknown) {
|
|
1696
|
-
return formatErrorMessage(error);
|
|
1697
|
-
}
|
|
1698
|
-
};
|
|
1699
|
-
const applyThemePreset = (preset: string, persist: boolean): void => {
|
|
1700
|
-
const nextThemeConfig = buildPresetThemeConfig(preset);
|
|
1701
|
-
applyThemeConfig(nextThemeConfig);
|
|
1702
|
-
if (!persist) {
|
|
1703
|
-
markDirty();
|
|
1704
|
-
return;
|
|
1705
|
-
}
|
|
1706
|
-
const persistError = persistThemeConfig(nextThemeConfig);
|
|
1707
|
-
if (persistError === null) {
|
|
1708
|
-
setCommandNotice(`theme set to ${preset}`);
|
|
1709
|
-
return;
|
|
1710
|
-
}
|
|
1711
|
-
setCommandNotice(`theme set to ${preset} (not persisted: ${persistError})`);
|
|
1712
|
-
};
|
|
1713
|
-
const themePresetFromActionId = (actionId: string | null): string | null => {
|
|
1714
|
-
if (actionId === null || !actionId.startsWith(THEME_ACTION_ID_PREFIX)) {
|
|
1715
|
-
return null;
|
|
1716
|
-
}
|
|
1717
|
-
const preset = actionId.slice(THEME_ACTION_ID_PREFIX.length).trim();
|
|
1718
|
-
return preset.length > 0 ? preset : null;
|
|
1719
|
-
};
|
|
1720
|
-
const selectedCommandMenuActionId = (): string | null => {
|
|
1721
|
-
return resolveSelectedCommandMenuActionId(resolveCommandMenuActions(), workspace.commandMenu);
|
|
1722
|
-
};
|
|
1723
|
-
const startThemePickerSession = (): void => {
|
|
1724
|
-
const initialThemeConfig =
|
|
1725
|
-
runtimeThemeConfig === null
|
|
1726
|
-
? null
|
|
1727
|
-
: {
|
|
1728
|
-
preset: runtimeThemeConfig.preset,
|
|
1729
|
-
mode: runtimeThemeConfig.mode,
|
|
1730
|
-
customThemePath: runtimeThemeConfig.customThemePath,
|
|
1731
|
-
};
|
|
1732
|
-
themePickerSession = {
|
|
1733
|
-
initialThemeConfig,
|
|
1734
|
-
committed: false,
|
|
1735
|
-
previewActionId: null,
|
|
1736
|
-
};
|
|
1737
|
-
commandMenuScopedDirectoryId = null;
|
|
1738
|
-
commandMenuGitHubProjectPrState = null;
|
|
1739
|
-
workspace.commandMenu = createCommandMenuState({
|
|
1740
|
-
scope: THEME_PICKER_SCOPE,
|
|
1741
|
-
});
|
|
1742
|
-
markDirty();
|
|
1743
|
-
};
|
|
1744
|
-
const syncThemePickerPreview = (): void => {
|
|
1745
|
-
if (themePickerSession === null) {
|
|
1746
|
-
return;
|
|
1747
|
-
}
|
|
1748
|
-
if (workspace.commandMenu?.scope !== THEME_PICKER_SCOPE) {
|
|
1749
|
-
if (!themePickerSession.committed) {
|
|
1750
|
-
applyThemeConfig(themePickerSession.initialThemeConfig);
|
|
1751
|
-
}
|
|
1752
|
-
themePickerSession = null;
|
|
1753
|
-
return;
|
|
1754
|
-
}
|
|
1755
|
-
const selectedActionId = selectedCommandMenuActionId();
|
|
1756
|
-
if (selectedActionId === themePickerSession.previewActionId) {
|
|
1757
|
-
return;
|
|
1758
|
-
}
|
|
1759
|
-
themePickerSession.previewActionId = selectedActionId;
|
|
1760
|
-
const preset = themePresetFromActionId(selectedActionId);
|
|
1761
|
-
if (preset === null) {
|
|
1762
|
-
return;
|
|
1763
|
-
}
|
|
1764
|
-
applyThemePreset(preset, false);
|
|
1765
|
-
};
|
|
1766
|
-
const githubAuthHintNotice =
|
|
1767
|
-
'GitHub PR actions become available after auth (`gh auth login` or `GITHUB_TOKEN`).';
|
|
1768
|
-
const setGitHubDebugAuthState = (
|
|
1769
|
-
update: Partial<Pick<GitHubDebugAuthState, 'token' | 'auth' | 'projectPr'>>,
|
|
1770
|
-
): void => {
|
|
1771
|
-
githubDebugAuthState.token = update.token ?? githubDebugAuthState.token;
|
|
1772
|
-
githubDebugAuthState.auth = update.auth ?? githubDebugAuthState.auth;
|
|
1773
|
-
githubDebugAuthState.projectPr = update.projectPr ?? githubDebugAuthState.projectPr;
|
|
1774
|
-
};
|
|
1775
|
-
const isGitHubAuthUnavailableError = (error: unknown): boolean => {
|
|
1776
|
-
const message = formatErrorMessage(error).toLowerCase();
|
|
1777
|
-
return (
|
|
1778
|
-
message.includes('github token not configured') ||
|
|
1779
|
-
message.includes('github integration is disabled')
|
|
1780
|
-
);
|
|
1781
|
-
};
|
|
1782
|
-
const refreshCommandMenuGitHubProjectPrState = (directoryId: string): void => {
|
|
1783
|
-
commandMenuGitHubProjectPrState = {
|
|
1784
|
-
directoryId,
|
|
1785
|
-
branchName: null,
|
|
1786
|
-
openPrUrl: null,
|
|
1787
|
-
loading: true,
|
|
1788
|
-
};
|
|
1789
|
-
markDirty();
|
|
1790
|
-
queueControlPlaneOp(async () => {
|
|
1791
|
-
try {
|
|
1792
|
-
const result = await streamClient.sendCommand({
|
|
1793
|
-
type: 'github.project-pr',
|
|
1794
|
-
directoryId,
|
|
1795
|
-
});
|
|
1796
|
-
commandMenuGitHubProjectPrState = parseGitHubProjectPrState(directoryId, result);
|
|
1797
|
-
setGitHubDebugAuthState({
|
|
1798
|
-
projectPr: 'ok',
|
|
1799
|
-
});
|
|
1800
|
-
} catch (error: unknown) {
|
|
1801
|
-
setGitHubDebugAuthState({
|
|
1802
|
-
projectPr: 'er',
|
|
1803
|
-
auth: isGitHubAuthUnavailableError(error) ? 'no' : githubDebugAuthState.auth,
|
|
1804
|
-
});
|
|
1805
|
-
commandMenuGitHubProjectPrState = {
|
|
1806
|
-
directoryId,
|
|
1807
|
-
branchName: null,
|
|
1808
|
-
openPrUrl: null,
|
|
1809
|
-
loading: false,
|
|
1810
|
-
};
|
|
1811
|
-
}
|
|
1812
|
-
if (workspace.commandMenu !== null) {
|
|
1813
|
-
markDirty();
|
|
1814
|
-
}
|
|
1815
|
-
}, 'command-menu-github-project-pr');
|
|
1816
|
-
};
|
|
1817
|
-
const processUsageRefreshService = new ProcessUsageRefreshService<
|
|
1818
|
-
ConversationState,
|
|
1819
|
-
ProcessUsageSample
|
|
1820
|
-
>({
|
|
1821
|
-
readProcessUsageSample,
|
|
1822
|
-
processIdForConversation: (conversation) => conversation.processId,
|
|
1823
|
-
processUsageEqual: (left, right) =>
|
|
1824
|
-
left.cpuPercent === right.cpuPercent && left.memoryMb === right.memoryMb,
|
|
1825
|
-
startPerfSpan,
|
|
1826
|
-
onChanged: markDirty,
|
|
1827
|
-
});
|
|
1828
|
-
const sessionProjectionInstrumentation = new SessionProjectionInstrumentation({
|
|
1829
|
-
getProcessUsageSample: (sessionId) => processUsageRefreshService.getSample(sessionId),
|
|
1830
|
-
recordPerfEvent,
|
|
1831
|
-
onTransition: (transition) => {
|
|
1832
|
-
recordStatusTimeline({
|
|
1833
|
-
direction: 'outgoing',
|
|
1834
|
-
source: 'session-projection',
|
|
1835
|
-
eventType: 'projection-transition',
|
|
1836
|
-
labels: resolveTraceLabels({
|
|
1837
|
-
sessionId: transition.sessionId,
|
|
1838
|
-
directoryId: null,
|
|
1839
|
-
conversationId: transition.sessionId,
|
|
1840
|
-
}),
|
|
1841
|
-
payload: transition,
|
|
1842
|
-
});
|
|
1843
|
-
},
|
|
1844
|
-
});
|
|
1845
|
-
sessionProjectionInstrumentation.refreshSelectorSnapshot(
|
|
1846
|
-
'startup',
|
|
1847
|
-
directoryRecords,
|
|
1848
|
-
conversationRecords,
|
|
1849
|
-
conversationManager.orderedIds(),
|
|
1850
|
-
);
|
|
1851
|
-
|
|
1852
|
-
keyEventSubscription = await subscribeControlPlaneKeyEvents(streamClient, {
|
|
1853
|
-
tenantId: options.scope.tenantId,
|
|
1854
|
-
userId: options.scope.userId,
|
|
1855
|
-
workspaceId: options.scope.workspaceId,
|
|
1856
|
-
...(startupObservedCursor === null
|
|
1857
|
-
? {}
|
|
1858
|
-
: {
|
|
1859
|
-
afterCursor: startupObservedCursor,
|
|
1860
|
-
}),
|
|
1861
|
-
onEvent: (event) => {
|
|
1862
|
-
recordStatusTimeline({
|
|
1863
|
-
direction: 'incoming',
|
|
1864
|
-
source: 'control-plane-key-events',
|
|
1865
|
-
eventType: event.type,
|
|
1866
|
-
labels: resolveTraceLabels({
|
|
1867
|
-
sessionId: event.sessionId,
|
|
1868
|
-
directoryId: event.directoryId,
|
|
1869
|
-
conversationId: event.conversationId,
|
|
1870
|
-
}),
|
|
1871
|
-
payload: event,
|
|
1872
|
-
});
|
|
1873
|
-
applyControlPlaneKeyEvent(event);
|
|
1874
|
-
markDirty();
|
|
1875
|
-
},
|
|
1876
|
-
});
|
|
1877
|
-
|
|
1878
|
-
const muxUiStatePersistence = new MuxUiStatePersistence({
|
|
1879
|
-
enabled: loadedConfig.error === null,
|
|
1880
|
-
initialState: {
|
|
1881
|
-
paneWidthPercent: paneWidthPercentFromLayout(layout),
|
|
1882
|
-
repositoriesCollapsed: configuredMuxUi.repositoriesCollapsed,
|
|
1883
|
-
shortcutsCollapsed: configuredMuxUi.shortcutsCollapsed,
|
|
1884
|
-
},
|
|
1885
|
-
debounceMs: UI_STATE_PERSIST_DEBOUNCE_MS,
|
|
1886
|
-
persistState: (pending) => {
|
|
1887
|
-
const updated = updateHarnessMuxUiConfig(pending, {
|
|
1888
|
-
filePath: loadedConfig.filePath,
|
|
1889
|
-
});
|
|
1890
|
-
return {
|
|
1891
|
-
paneWidthPercent:
|
|
1892
|
-
updated.mux.ui.paneWidthPercent === null
|
|
1893
|
-
? paneWidthPercentFromLayout(layout)
|
|
1894
|
-
: updated.mux.ui.paneWidthPercent,
|
|
1895
|
-
repositoriesCollapsed: updated.mux.ui.repositoriesCollapsed,
|
|
1896
|
-
shortcutsCollapsed: updated.mux.ui.shortcutsCollapsed,
|
|
1897
|
-
};
|
|
1898
|
-
},
|
|
1899
|
-
applyState: (state) => {
|
|
1900
|
-
workspace.repositoriesCollapsed = state.repositoriesCollapsed;
|
|
1901
|
-
workspace.shortcutsCollapsed = state.shortcutsCollapsed;
|
|
1902
|
-
},
|
|
1903
|
-
writeStderr: (text) => process.stderr.write(text),
|
|
1904
|
-
});
|
|
1905
|
-
const persistMuxUiStateNow = (): void => {
|
|
1906
|
-
muxUiStatePersistence.persistNow();
|
|
1907
|
-
};
|
|
1908
|
-
const queuePersistMuxUiState = (): void => {
|
|
1909
|
-
muxUiStatePersistence.queue({
|
|
1910
|
-
paneWidthPercent: paneWidthPercentFromLayout(layout),
|
|
1911
|
-
repositoriesCollapsed: workspace.repositoriesCollapsed,
|
|
1912
|
-
shortcutsCollapsed: workspace.shortcutsCollapsed,
|
|
1913
|
-
});
|
|
1914
|
-
};
|
|
1915
|
-
|
|
1916
|
-
if (configuredMuxGit.enabled) {
|
|
1917
|
-
syncGitStateWithDirectories();
|
|
1918
|
-
if (workspace.activeDirectoryId !== null) {
|
|
1919
|
-
noteGitActivity(workspace.activeDirectoryId);
|
|
1920
|
-
}
|
|
1921
|
-
} else {
|
|
1922
|
-
recordPerfEvent('mux.background.git-summary.skipped', {
|
|
1923
|
-
reason: 'disabled',
|
|
1924
|
-
});
|
|
1925
|
-
}
|
|
1926
|
-
startupOrchestrator.startBackgroundProbe();
|
|
1927
|
-
|
|
1928
|
-
const eventPersistence = new EventPersistence({
|
|
1929
|
-
appendEvents: (events) => store.appendEvents(events),
|
|
1930
|
-
startPerfSpan,
|
|
1931
|
-
writeStderr: (text) => process.stderr.write(text),
|
|
1932
|
-
});
|
|
1933
|
-
const outputLoadSampler = new OutputLoadSampler({
|
|
1934
|
-
recordPerfEvent,
|
|
1935
|
-
getControlPlaneQueueMetrics: () => controlPlaneOps.metrics(),
|
|
1936
|
-
getActiveConversationId: () => conversationManager.activeConversationId,
|
|
1937
|
-
getPendingPersistedEvents: () => eventPersistence.pendingCount(),
|
|
1938
|
-
onStatusRowChanged: markDirty,
|
|
1939
|
-
});
|
|
1940
|
-
outputLoadSampler.start();
|
|
1941
|
-
homePaneBackgroundTimer = setInterval(() => {
|
|
1942
|
-
if (shuttingDown || workspace.mainPaneMode !== 'home') {
|
|
1943
|
-
return;
|
|
1944
|
-
}
|
|
1945
|
-
markDirty();
|
|
1946
|
-
}, HOME_PANE_BACKGROUND_INTERVAL_MS);
|
|
1947
|
-
homePaneBackgroundTimer.unref?.();
|
|
1948
|
-
|
|
1949
|
-
const runtimeLayoutResize = new RuntimeLayoutResize<ConversationState>({
|
|
1950
|
-
getSize: () => size,
|
|
1951
|
-
setSize: (nextSize) => {
|
|
1952
|
-
size = nextSize;
|
|
1953
|
-
},
|
|
1954
|
-
getLayout: () => layout,
|
|
1955
|
-
setLayout: (nextLayout) => {
|
|
1956
|
-
layout = nextLayout;
|
|
1957
|
-
},
|
|
1958
|
-
getLeftPaneColsOverride: () => leftPaneColsOverride,
|
|
1959
|
-
setLeftPaneColsOverride: (nextLeftPaneColsOverride) => {
|
|
1960
|
-
leftPaneColsOverride = nextLeftPaneColsOverride;
|
|
1961
|
-
},
|
|
1962
|
-
conversationManager,
|
|
1963
|
-
ptySizeByConversationId,
|
|
1964
|
-
sendResize: (sessionId, cols, rows) => {
|
|
1965
|
-
streamClient.sendResize(sessionId, cols, rows);
|
|
1966
|
-
},
|
|
1967
|
-
markDirty,
|
|
1968
|
-
resetFrameCache: () => {
|
|
1969
|
-
screen.resetFrameCache();
|
|
1970
|
-
},
|
|
1971
|
-
resizeRecordingOracle: (nextLayout) => {
|
|
1972
|
-
if (muxRecordingOracle !== null) {
|
|
1973
|
-
muxRecordingOracle.resize(nextLayout.cols, nextLayout.rows);
|
|
1974
|
-
}
|
|
1975
|
-
},
|
|
1976
|
-
queuePersistMuxUiState,
|
|
1977
|
-
resizeMinIntervalMs,
|
|
1978
|
-
ptyResizeSettleMs,
|
|
1979
|
-
});
|
|
1980
|
-
|
|
1981
|
-
const schedulePtyResize = (ptySize: { cols: number; rows: number }, immediate = false): void => {
|
|
1982
|
-
runtimeLayoutResize.schedulePtyResize(ptySize, immediate);
|
|
1983
|
-
};
|
|
1984
|
-
|
|
1985
|
-
const applyLayout = (
|
|
1986
|
-
nextSize: { cols: number; rows: number },
|
|
1987
|
-
forceImmediatePtyResize = false,
|
|
1988
|
-
): void => {
|
|
1989
|
-
runtimeLayoutResize.applyLayout(nextSize, forceImmediatePtyResize);
|
|
1990
|
-
};
|
|
1991
|
-
|
|
1992
|
-
const queueResize = (nextSize: { cols: number; rows: number }): void => {
|
|
1993
|
-
runtimeLayoutResize.queueResize(nextSize);
|
|
1994
|
-
};
|
|
1995
|
-
|
|
1996
|
-
const applyPaneDividerAtCol = (col: number): void => {
|
|
1997
|
-
runtimeLayoutResize.applyPaneDividerAtCol(col);
|
|
1998
|
-
};
|
|
1999
|
-
|
|
2000
|
-
const scheduleConversationTitlePersist = (): void => {
|
|
2001
|
-
conversationLifecycle.scheduleConversationTitlePersist();
|
|
2002
|
-
};
|
|
2003
|
-
|
|
2004
|
-
const stopConversationTitleEdit = (persistPending: boolean): void => {
|
|
2005
|
-
conversationLifecycle.stopConversationTitleEdit(persistPending);
|
|
2006
|
-
};
|
|
2007
|
-
|
|
2008
|
-
const beginConversationTitleEdit = (conversationId: string): void => {
|
|
2009
|
-
conversationLifecycle.beginConversationTitleEdit(conversationId);
|
|
2010
|
-
};
|
|
2011
|
-
|
|
2012
|
-
const buildNewThreadModalOverlay = (viewportRows: number) => {
|
|
2013
|
-
return modalManager.buildNewThreadOverlay(layout.cols, viewportRows);
|
|
2014
|
-
};
|
|
2015
|
-
|
|
2016
|
-
const buildCommandMenuModalOverlay = (viewportRows: number) => {
|
|
2017
|
-
return modalManager.buildCommandMenuOverlay(layout.cols, viewportRows);
|
|
2018
|
-
};
|
|
2019
|
-
|
|
2020
|
-
const buildConversationTitleModalOverlay = (viewportRows: number) => {
|
|
2021
|
-
return modalManager.buildConversationTitleOverlay(layout.cols, viewportRows);
|
|
2022
|
-
};
|
|
2023
|
-
|
|
2024
|
-
const buildCurrentModalOverlay = () => {
|
|
2025
|
-
return modalManager.buildCurrentOverlay(layout.cols, layout.rows);
|
|
2026
|
-
};
|
|
2027
|
-
|
|
2028
|
-
const dismissModalOnOutsideClick = (
|
|
2029
|
-
input: Buffer,
|
|
2030
|
-
dismiss: () => void,
|
|
2031
|
-
onInsidePointerPress?: (col: number, row: number) => boolean,
|
|
2032
|
-
): boolean => {
|
|
2033
|
-
const result = modalManager.dismissOnOutsideClick({
|
|
2034
|
-
input,
|
|
2035
|
-
inputRemainder,
|
|
2036
|
-
layoutCols: layout.cols,
|
|
2037
|
-
viewportRows: layout.rows,
|
|
2038
|
-
dismiss,
|
|
2039
|
-
...(onInsidePointerPress === undefined
|
|
2040
|
-
? {}
|
|
2041
|
-
: {
|
|
2042
|
-
onInsidePointerPress,
|
|
2043
|
-
}),
|
|
2044
|
-
});
|
|
2045
|
-
inputRemainder = result.inputRemainder;
|
|
2046
|
-
return result.handled;
|
|
2047
|
-
};
|
|
2048
|
-
|
|
2049
|
-
const attachConversation = async (sessionId: string): Promise<void> => {
|
|
2050
|
-
const attachResult = await conversationManager.attachIfLive({
|
|
2051
|
-
sessionId,
|
|
2052
|
-
attach: async (sinceCursor) => {
|
|
2053
|
-
await controlPlaneService.attachPty({
|
|
2054
|
-
sessionId,
|
|
2055
|
-
sinceCursor,
|
|
2056
|
-
});
|
|
2057
|
-
},
|
|
2058
|
-
});
|
|
2059
|
-
if (attachResult.attached && attachResult.sinceCursor !== null) {
|
|
2060
|
-
recordPerfEvent('mux.conversation.attach', {
|
|
2061
|
-
sessionId,
|
|
2062
|
-
sinceCursor: attachResult.sinceCursor,
|
|
2063
|
-
});
|
|
2064
|
-
}
|
|
2065
|
-
};
|
|
2066
|
-
|
|
2067
|
-
const detachConversation = async (sessionId: string): Promise<void> => {
|
|
2068
|
-
const detachResult = await conversationManager.detachIfAttached({
|
|
2069
|
-
sessionId,
|
|
2070
|
-
detach: async () => {
|
|
2071
|
-
await controlPlaneService.detachPty(sessionId);
|
|
2072
|
-
},
|
|
2073
|
-
});
|
|
2074
|
-
if (detachResult.detached && detachResult.conversation !== null) {
|
|
2075
|
-
recordPerfEvent('mux.conversation.detach', {
|
|
2076
|
-
sessionId,
|
|
2077
|
-
lastOutputCursor: detachResult.conversation.lastOutputCursor,
|
|
2078
|
-
});
|
|
2079
|
-
}
|
|
2080
|
-
};
|
|
2081
|
-
|
|
2082
|
-
const refreshProjectPaneSnapshot = (directoryId: string): void => {
|
|
2083
|
-
const directory = directoryManager.getDirectory(directoryId);
|
|
2084
|
-
if (directory === undefined) {
|
|
2085
|
-
workspace.projectPaneSnapshot = null;
|
|
2086
|
-
return;
|
|
2087
|
-
}
|
|
2088
|
-
workspace.projectPaneSnapshot = buildProjectPaneSnapshot(directory.directoryId, directory.path);
|
|
2089
|
-
};
|
|
2090
|
-
|
|
2091
|
-
const enterProjectPane = (directoryId: string): void => {
|
|
2092
|
-
if (!directoryManager.hasDirectory(directoryId)) {
|
|
2093
|
-
return;
|
|
2094
|
-
}
|
|
2095
|
-
workspace.enterProjectPane(directoryId, repositoryGroupIdForDirectory(directoryId));
|
|
2096
|
-
noteGitActivity(directoryId);
|
|
2097
|
-
refreshProjectPaneSnapshot(directoryId);
|
|
2098
|
-
screen.resetFrameCache();
|
|
2099
|
-
};
|
|
2100
|
-
|
|
2101
|
-
function orderedTaskRecords(): readonly ControlPlaneTaskRecord[] {
|
|
2102
|
-
return taskManager.orderedTasks(sortTasksByOrder);
|
|
2103
|
-
}
|
|
2104
|
-
|
|
2105
|
-
function orderedActiveRepositoryRecords(): readonly ControlPlaneRepositoryRecord[] {
|
|
2106
|
-
return sortedRepositoryList(repositories);
|
|
2107
|
-
}
|
|
2108
|
-
|
|
2109
|
-
const selectedRepositoryTaskRecords = (): readonly ControlPlaneTaskRecord[] => {
|
|
2110
|
-
return taskManager.tasksForRepository({
|
|
2111
|
-
repositoryId: workspace.taskPaneSelectedRepositoryId,
|
|
2112
|
-
sortTasks: sortTasksByOrder,
|
|
2113
|
-
taskRepositoryId: (task) => task.repositoryId,
|
|
2114
|
-
});
|
|
2115
|
-
};
|
|
2116
|
-
|
|
2117
|
-
const applyTaskRecord = (task: ControlPlaneTaskRecord): ControlPlaneTaskRecord => {
|
|
2118
|
-
taskManager.setTask(task);
|
|
2119
|
-
workspace.taskPaneSelectedTaskId = task.taskId;
|
|
2120
|
-
if (task.repositoryId !== null && repositories.has(task.repositoryId)) {
|
|
2121
|
-
workspace.taskPaneSelectedRepositoryId = task.repositoryId;
|
|
2122
|
-
}
|
|
2123
|
-
workspace.taskPaneSelectionFocus = 'task';
|
|
2124
|
-
syncTaskPaneSelection();
|
|
2125
|
-
markDirty();
|
|
2126
|
-
return task;
|
|
2127
|
-
};
|
|
2128
|
-
|
|
2129
|
-
const taskComposerPersistence = new RuntimeTaskComposerPersistenceService<
|
|
2130
|
-
ControlPlaneTaskRecord,
|
|
2131
|
-
TaskComposerBuffer
|
|
2132
|
-
>({
|
|
2133
|
-
getTask: (taskId) => taskManager.getTask(taskId),
|
|
2134
|
-
getTaskComposer: (taskId) => taskManager.getTaskComposer(taskId),
|
|
2135
|
-
setTaskComposer: (taskId, buffer) => {
|
|
2136
|
-
taskManager.setTaskComposer(taskId, buffer);
|
|
2137
|
-
},
|
|
2138
|
-
deleteTaskComposer: (taskId) => {
|
|
2139
|
-
taskManager.deleteTaskComposer(taskId);
|
|
2140
|
-
},
|
|
2141
|
-
getTaskAutosaveTimer: (taskId) => taskManager.getTaskAutosaveTimer(taskId),
|
|
2142
|
-
setTaskAutosaveTimer: (taskId, timer) => {
|
|
2143
|
-
taskManager.setTaskAutosaveTimer(taskId, timer);
|
|
2144
|
-
},
|
|
2145
|
-
deleteTaskAutosaveTimer: (taskId) => {
|
|
2146
|
-
taskManager.deleteTaskAutosaveTimer(taskId);
|
|
2147
|
-
},
|
|
2148
|
-
buildComposerFromTask: (task) =>
|
|
2149
|
-
createTaskComposerBuffer(
|
|
2150
|
-
task.description.length === 0 ? task.title : `${task.title}\n${task.description}`,
|
|
2151
|
-
),
|
|
2152
|
-
normalizeTaskComposerBuffer,
|
|
2153
|
-
taskFieldsFromComposerText,
|
|
2154
|
-
updateTask: async (input) => {
|
|
2155
|
-
return await controlPlaneService.updateTask(input);
|
|
2156
|
-
},
|
|
2157
|
-
applyTaskRecord: (task) => {
|
|
2158
|
-
applyTaskRecord(task);
|
|
2159
|
-
},
|
|
2160
|
-
queueControlPlaneOp,
|
|
2161
|
-
setTaskPaneNotice: (text) => {
|
|
2162
|
-
workspace.taskPaneNotice = text;
|
|
2163
|
-
},
|
|
2164
|
-
markDirty,
|
|
2165
|
-
autosaveDebounceMs: DEFAULT_TASK_EDITOR_AUTOSAVE_DEBOUNCE_MS,
|
|
2166
|
-
});
|
|
2167
|
-
|
|
2168
|
-
const taskComposerForTask = (taskId: string): TaskComposerBuffer | null => {
|
|
2169
|
-
return taskComposerPersistence.taskComposerForTask(taskId);
|
|
2170
|
-
};
|
|
2171
|
-
|
|
2172
|
-
const setTaskComposerForTask = (taskId: string, buffer: TaskComposerBuffer): void => {
|
|
2173
|
-
taskComposerPersistence.setTaskComposerForTask(taskId, buffer);
|
|
2174
|
-
};
|
|
2175
|
-
|
|
2176
|
-
const clearTaskAutosaveTimer = (taskId: string): void => {
|
|
2177
|
-
taskComposerPersistence.clearTaskAutosaveTimer(taskId);
|
|
2178
|
-
};
|
|
2179
|
-
|
|
2180
|
-
const scheduleTaskComposerPersist = (taskId: string): void => {
|
|
2181
|
-
taskComposerPersistence.scheduleTaskComposerPersist(taskId);
|
|
2182
|
-
};
|
|
2183
|
-
|
|
2184
|
-
const flushTaskComposerPersist = (taskId: string): void => {
|
|
2185
|
-
taskComposerPersistence.flushTaskComposerPersist(taskId);
|
|
2186
|
-
};
|
|
2187
|
-
|
|
2188
|
-
const activeRepositoryIds = (): readonly string[] => {
|
|
2189
|
-
return orderedActiveRepositoryRecords().map((repository) => repository.repositoryId);
|
|
2190
|
-
};
|
|
2191
|
-
|
|
2192
|
-
const taskPaneSelectionActions = new TaskPaneSelectionActions<ControlPlaneTaskRecord>({
|
|
2193
|
-
workspace,
|
|
2194
|
-
taskRecordById: (taskId) => taskManager.getTask(taskId),
|
|
2195
|
-
hasTask: (taskId) => taskManager.hasTask(taskId),
|
|
2196
|
-
hasRepository: (repositoryId) => repositories.has(repositoryId),
|
|
2197
|
-
repositoryById: (repositoryId) => repositories.get(repositoryId),
|
|
2198
|
-
selectedRepositoryTasks: selectedRepositoryTaskRecords,
|
|
2199
|
-
activeRepositoryIds,
|
|
2200
|
-
flushTaskComposerPersist,
|
|
2201
|
-
markDirty,
|
|
2202
|
-
});
|
|
2203
|
-
|
|
2204
|
-
const syncTaskPaneSelection = (): void => {
|
|
2205
|
-
taskPaneSelectionActions.syncTaskPaneSelection();
|
|
2206
|
-
};
|
|
2207
|
-
|
|
2208
|
-
const syncTaskPaneRepositorySelection = (): void => {
|
|
2209
|
-
taskPaneSelectionActions.syncTaskPaneRepositorySelection();
|
|
2210
|
-
};
|
|
2211
|
-
|
|
2212
|
-
const focusDraftComposer = (): void => {
|
|
2213
|
-
taskPaneSelectionActions.focusDraftComposer();
|
|
2214
|
-
};
|
|
2215
|
-
|
|
2216
|
-
const focusTaskComposer = (taskId: string): void => {
|
|
2217
|
-
taskPaneSelectionActions.focusTaskComposer(taskId);
|
|
2218
|
-
};
|
|
2219
|
-
|
|
2220
|
-
const selectedTaskRecord = (): ControlPlaneTaskRecord | null => {
|
|
2221
|
-
if (workspace.taskPaneSelectedTaskId === null) {
|
|
2222
|
-
return null;
|
|
2223
|
-
}
|
|
2224
|
-
return taskManager.getTask(workspace.taskPaneSelectedTaskId) ?? null;
|
|
2225
|
-
};
|
|
2226
|
-
|
|
2227
|
-
const selectTaskById = (taskId: string): void => {
|
|
2228
|
-
taskPaneSelectionActions.selectTaskById(taskId);
|
|
2229
|
-
};
|
|
2230
|
-
|
|
2231
|
-
const selectRepositoryById = (repositoryId: string): void => {
|
|
2232
|
-
taskPaneSelectionActions.selectRepositoryById(repositoryId);
|
|
2233
|
-
};
|
|
2234
|
-
|
|
2235
|
-
const enterHomePane = (): void => {
|
|
2236
|
-
workspace.enterHomePane();
|
|
2237
|
-
workspace.selection = null;
|
|
2238
|
-
workspace.selectionDrag = null;
|
|
2239
|
-
releaseViewportPinForSelection();
|
|
2240
|
-
syncTaskPaneSelection();
|
|
2241
|
-
syncTaskPaneRepositorySelection();
|
|
2242
|
-
screen.resetFrameCache();
|
|
2243
|
-
markDirty();
|
|
2244
|
-
};
|
|
2245
|
-
|
|
2246
|
-
const taskPlanningHydrationService = new TaskPlanningHydrationService<
|
|
2247
|
-
ControlPlaneRepositoryRecord,
|
|
2248
|
-
ControlPlaneTaskRecord
|
|
2249
|
-
>({
|
|
2250
|
-
controlPlaneService,
|
|
2251
|
-
clearRepositories: () => {
|
|
2252
|
-
repositories.clear();
|
|
2253
|
-
},
|
|
2254
|
-
setRepository: (repository) => {
|
|
2255
|
-
repositories.set(repository.repositoryId, repository);
|
|
2256
|
-
},
|
|
2257
|
-
syncTaskPaneRepositorySelection,
|
|
2258
|
-
clearTasks: () => {
|
|
2259
|
-
taskManager.clearTasks();
|
|
2260
|
-
},
|
|
2261
|
-
setTask: (task) => {
|
|
2262
|
-
taskManager.setTask(task);
|
|
2263
|
-
},
|
|
2264
|
-
syncTaskPaneSelection,
|
|
2265
|
-
markDirty,
|
|
2266
|
-
taskLimit: 1000,
|
|
2267
|
-
});
|
|
2268
|
-
|
|
2269
|
-
async function hydrateTaskPlanningState(): Promise<void> {
|
|
2270
|
-
await taskPlanningHydrationService.hydrate();
|
|
2271
|
-
}
|
|
2272
|
-
|
|
2273
|
-
const taskPlanningObservedEvents = new TaskPlanningObservedEvents<
|
|
2274
|
-
ControlPlaneRepositoryRecord,
|
|
2275
|
-
ControlPlaneTaskRecord
|
|
2276
|
-
>({
|
|
2277
|
-
parseRepositoryRecord,
|
|
2278
|
-
parseTaskRecord,
|
|
2279
|
-
getRepository: (repositoryId) => repositories.get(repositoryId),
|
|
2280
|
-
setRepository: (repositoryId, repository) => {
|
|
2281
|
-
repositories.set(repositoryId, repository);
|
|
2282
|
-
},
|
|
2283
|
-
setTask: (task) => {
|
|
2284
|
-
taskManager.setTask(task);
|
|
2285
|
-
},
|
|
2286
|
-
deleteTask: (taskId) => taskManager.deleteTask(taskId),
|
|
2287
|
-
syncTaskPaneRepositorySelection,
|
|
2288
|
-
syncTaskPaneSelection,
|
|
2289
|
-
markDirty,
|
|
2290
|
-
});
|
|
2291
|
-
|
|
2292
|
-
const applyObservedTaskPlanningEvent = (observed: StreamObservedEvent): void => {
|
|
2293
|
-
taskPlanningObservedEvents.apply(observed);
|
|
2294
|
-
};
|
|
2295
|
-
|
|
2296
|
-
const workspaceObservedEvents = new WorkspaceObservedEvents<
|
|
2297
|
-
ControlPlaneDirectoryRecord,
|
|
2298
|
-
ControlPlaneConversationRecord
|
|
2299
|
-
>({
|
|
2300
|
-
parseDirectoryRecord,
|
|
2301
|
-
parseConversationRecord,
|
|
2302
|
-
setDirectory: (directoryId, directory) => {
|
|
2303
|
-
directoryManager.setDirectory(directoryId, directory);
|
|
2304
|
-
},
|
|
2305
|
-
deleteDirectory: (directoryId) => {
|
|
2306
|
-
if (!directoryManager.hasDirectory(directoryId)) {
|
|
2307
|
-
return false;
|
|
2308
|
-
}
|
|
2309
|
-
directoryManager.deleteDirectory(directoryId);
|
|
2310
|
-
return true;
|
|
2311
|
-
},
|
|
2312
|
-
deleteDirectoryGitState,
|
|
2313
|
-
syncGitStateWithDirectories,
|
|
2314
|
-
upsertConversationFromPersistedRecord: (record) => {
|
|
2315
|
-
conversationManager.upsertFromPersistedRecord({
|
|
2316
|
-
record,
|
|
2317
|
-
ensureConversation,
|
|
2318
|
-
});
|
|
2319
|
-
},
|
|
2320
|
-
removeConversation: (sessionId) => {
|
|
2321
|
-
if (!conversationManager.has(sessionId)) {
|
|
2322
|
-
return false;
|
|
2323
|
-
}
|
|
2324
|
-
removeConversationState(sessionId);
|
|
2325
|
-
return true;
|
|
2326
|
-
},
|
|
2327
|
-
orderedConversationIds: () => conversationManager.orderedIds(),
|
|
2328
|
-
conversationDirectoryId: (sessionId) => conversationManager.directoryIdOf(sessionId),
|
|
2329
|
-
});
|
|
2330
|
-
|
|
2331
|
-
const runtimeWorkspaceObservedEvents = new RuntimeWorkspaceObservedEvents<StreamObservedEvent>({
|
|
2332
|
-
reducer: workspaceObservedEvents,
|
|
2333
|
-
workspace,
|
|
2334
|
-
orderedConversationIds: () => conversationManager.orderedIds(),
|
|
2335
|
-
conversationDirectoryId: (sessionId) => conversationManager.directoryIdOf(sessionId),
|
|
2336
|
-
hasConversation: (sessionId) => conversationManager.has(sessionId),
|
|
2337
|
-
getActiveConversationId: () => conversationManager.activeConversationId,
|
|
2338
|
-
setActiveConversationId: (sessionId) => {
|
|
2339
|
-
conversationManager.setActiveConversationId(sessionId);
|
|
2340
|
-
},
|
|
2341
|
-
hasDirectory: (directoryId) => directoryManager.hasDirectory(directoryId),
|
|
2342
|
-
resolveActiveDirectoryId,
|
|
2343
|
-
unsubscribeConversationEvents: async (sessionId) => {
|
|
2344
|
-
await conversationLifecycle.unsubscribeConversationEvents(sessionId);
|
|
2345
|
-
},
|
|
2346
|
-
stopConversationTitleEdit: (persistPending) => {
|
|
2347
|
-
stopConversationTitleEdit(persistPending);
|
|
2348
|
-
},
|
|
2349
|
-
enterProjectPane,
|
|
2350
|
-
enterHomePane,
|
|
2351
|
-
queueControlPlaneOp,
|
|
2352
|
-
activateConversation: async (sessionId) => {
|
|
2353
|
-
await conversationLifecycle.activateConversation(sessionId);
|
|
2354
|
-
},
|
|
2355
|
-
markDirty,
|
|
2356
|
-
});
|
|
2357
|
-
|
|
2358
|
-
const applyObservedWorkspaceEvent = (observed: StreamObservedEvent): void => {
|
|
2359
|
-
runtimeWorkspaceObservedEvents.apply(observed);
|
|
2360
|
-
};
|
|
2361
|
-
|
|
2362
|
-
activateConversationForStartupOrchestrator = async (sessionId: string): Promise<void> => {
|
|
2363
|
-
await conversationLifecycle.activateConversation(sessionId);
|
|
2364
|
-
};
|
|
2365
|
-
|
|
2366
|
-
const removeConversationState = (sessionId: string): void => {
|
|
2367
|
-
if (workspace.conversationTitleEdit?.conversationId === sessionId) {
|
|
2368
|
-
stopConversationTitleEdit(false);
|
|
2369
|
-
}
|
|
2370
|
-
conversationManager.remove(sessionId);
|
|
2371
|
-
ptySizeByConversationId.delete(sessionId);
|
|
2372
|
-
processUsageRefreshService.deleteSession(sessionId);
|
|
2373
|
-
};
|
|
2374
|
-
|
|
2375
|
-
const openNewThreadPrompt = (directoryId: string): void => {
|
|
2376
|
-
if (!directoryManager.hasDirectory(directoryId)) {
|
|
2377
|
-
return;
|
|
2378
|
-
}
|
|
2379
|
-
workspace.newThreadPrompt = null;
|
|
2380
|
-
workspace.addDirectoryPrompt = null;
|
|
2381
|
-
workspace.taskEditorPrompt = null;
|
|
2382
|
-
workspace.repositoryPrompt = null;
|
|
2383
|
-
if (workspace.conversationTitleEdit !== null) {
|
|
2384
|
-
stopConversationTitleEdit(true);
|
|
2385
|
-
}
|
|
2386
|
-
workspace.conversationTitleEditClickState = null;
|
|
2387
|
-
commandMenuGitHubProjectPrState = null;
|
|
2388
|
-
commandMenuScopedDirectoryId = directoryId;
|
|
2389
|
-
workspace.commandMenu = createCommandMenuState({
|
|
2390
|
-
scope: 'thread-start',
|
|
2391
|
-
});
|
|
2392
|
-
commandMenuAgentTools.refresh();
|
|
2393
|
-
markDirty();
|
|
2394
|
-
};
|
|
2395
|
-
|
|
2396
|
-
const runtimeRepositoryActions = new RuntimeRepositoryActions<ControlPlaneRepositoryRecord>({
|
|
2397
|
-
workspace,
|
|
2398
|
-
repositories,
|
|
2399
|
-
controlPlaneService,
|
|
2400
|
-
normalizeGitHubRemoteUrl,
|
|
2401
|
-
repositoryNameFromGitHubRemoteUrl,
|
|
2402
|
-
createRepositoryId: () => `repository-${randomUUID()}`,
|
|
2403
|
-
stopConversationTitleEdit: () => {
|
|
2404
|
-
stopConversationTitleEdit(true);
|
|
2405
|
-
},
|
|
2406
|
-
syncRepositoryAssociationsWithDirectorySnapshots,
|
|
2407
|
-
syncTaskPaneRepositorySelection,
|
|
2408
|
-
queueControlPlaneOp,
|
|
2409
|
-
markDirty,
|
|
2410
|
-
});
|
|
2411
|
-
|
|
2412
|
-
const runtimeTaskPane = new RuntimeTaskPane<ControlPlaneTaskRecord>({
|
|
2413
|
-
actions: {
|
|
2414
|
-
workspace,
|
|
2415
|
-
controlPlaneService,
|
|
2416
|
-
repositoriesHas: (repositoryId) => repositories.has(repositoryId),
|
|
2417
|
-
setTask: (task) => {
|
|
2418
|
-
taskManager.setTask(task);
|
|
2419
|
-
},
|
|
2420
|
-
getTask: (taskId) => taskManager.getTask(taskId),
|
|
2421
|
-
taskReorderPayloadIds: (orderedActiveTaskIds) =>
|
|
2422
|
-
taskManager.taskReorderPayloadIds({
|
|
2423
|
-
orderedActiveTaskIds,
|
|
2424
|
-
sortTasks: sortTasksByOrder,
|
|
2425
|
-
isCompleted: (task) => task.status === 'completed',
|
|
2426
|
-
}),
|
|
2427
|
-
reorderedActiveTaskIdsForDrop: (draggedTaskId, targetTaskId) =>
|
|
2428
|
-
taskManager.reorderedActiveTaskIdsForDrop({
|
|
2429
|
-
draggedTaskId,
|
|
2430
|
-
targetTaskId,
|
|
2431
|
-
sortTasks: sortTasksByOrder,
|
|
2432
|
-
isCompleted: (task) => task.status === 'completed',
|
|
2433
|
-
}),
|
|
2434
|
-
clearTaskAutosaveTimer,
|
|
2435
|
-
deleteTask: (taskId) => {
|
|
2436
|
-
taskManager.deleteTask(taskId);
|
|
2437
|
-
},
|
|
2438
|
-
deleteTaskComposer: (taskId) => {
|
|
2439
|
-
taskManager.deleteTaskComposer(taskId);
|
|
2440
|
-
},
|
|
2441
|
-
focusDraftComposer,
|
|
2442
|
-
focusTaskComposer,
|
|
2443
|
-
selectedTask: () => selectedTaskRecord(),
|
|
2444
|
-
orderedTaskRecords,
|
|
2445
|
-
queueControlPlaneOp,
|
|
2446
|
-
syncTaskPaneSelection,
|
|
2447
|
-
syncTaskPaneRepositorySelection,
|
|
2448
|
-
openRepositoryPromptForCreate: () => {
|
|
2449
|
-
runtimeRepositoryActions.openRepositoryPromptForCreate();
|
|
2450
|
-
},
|
|
2451
|
-
openRepositoryPromptForEdit: (repositoryId) => {
|
|
2452
|
-
runtimeRepositoryActions.openRepositoryPromptForEdit(repositoryId);
|
|
2453
|
-
},
|
|
2454
|
-
archiveRepositoryById: async (repositoryId) => {
|
|
2455
|
-
await runtimeRepositoryActions.archiveRepositoryById(repositoryId);
|
|
2456
|
-
},
|
|
2457
|
-
markDirty,
|
|
2458
|
-
},
|
|
2459
|
-
shortcuts: {
|
|
2460
|
-
workspace,
|
|
2461
|
-
taskScreenKeybindings,
|
|
2462
|
-
repositoriesHas: (repositoryId) => repositories.has(repositoryId),
|
|
2463
|
-
activeRepositoryIds,
|
|
2464
|
-
selectRepositoryById,
|
|
2465
|
-
taskComposerForTask,
|
|
2466
|
-
setTaskComposerForTask,
|
|
2467
|
-
scheduleTaskComposerPersist,
|
|
2468
|
-
selectedRepositoryTaskRecords,
|
|
2469
|
-
focusTaskComposer,
|
|
2470
|
-
focusDraftComposer,
|
|
2471
|
-
queueControlPlaneOp,
|
|
2472
|
-
createTask: async (payload) => {
|
|
2473
|
-
return await controlPlaneService.createTask(payload);
|
|
2474
|
-
},
|
|
2475
|
-
syncTaskPaneSelection,
|
|
2476
|
-
markDirty,
|
|
2477
|
-
},
|
|
2478
|
-
});
|
|
2479
|
-
const runtimeTaskEditorActions = new RuntimeTaskEditorActions<ControlPlaneTaskRecord>({
|
|
2480
|
-
workspace,
|
|
2481
|
-
controlPlaneService,
|
|
2482
|
-
applyTaskRecord: (task) => runtimeTaskPane.applyTaskRecord(task),
|
|
2483
|
-
queueControlPlaneOp,
|
|
2484
|
-
markDirty,
|
|
2485
|
-
});
|
|
2486
|
-
|
|
2487
|
-
const runtimeDirectoryActions = new RuntimeDirectoryActions({
|
|
2488
|
-
controlPlaneService,
|
|
2489
|
-
conversations: () => conversationRecords,
|
|
2490
|
-
orderedConversationIds: () => conversationManager.orderedIds(),
|
|
2491
|
-
conversationDirectoryId: (sessionId) => conversationManager.directoryIdOf(sessionId),
|
|
2492
|
-
conversationLive: (sessionId) => conversationManager.isLive(sessionId),
|
|
2493
|
-
removeConversationState,
|
|
2494
|
-
unsubscribeConversationEvents: async (sessionId) => {
|
|
2495
|
-
await conversationLifecycle.unsubscribeConversationEvents(sessionId);
|
|
2496
|
-
},
|
|
2497
|
-
activeConversationId: () => conversationManager.activeConversationId,
|
|
2498
|
-
setActiveConversationId: (sessionId) => {
|
|
2499
|
-
conversationManager.setActiveConversationId(sessionId);
|
|
2500
|
-
},
|
|
2501
|
-
activateConversation: async (sessionId) => {
|
|
2502
|
-
await conversationLifecycle.activateConversation(sessionId);
|
|
2503
|
-
},
|
|
2504
|
-
resolveActiveDirectoryId,
|
|
2505
|
-
enterProjectPane,
|
|
2506
|
-
markDirty,
|
|
2507
|
-
isSessionNotFoundError,
|
|
2508
|
-
isConversationNotFoundError,
|
|
2509
|
-
createDirectoryId: () => `directory-${randomUUID()}`,
|
|
2510
|
-
resolveWorkspacePathForMux: (rawPath) =>
|
|
2511
|
-
resolveWorkspacePathForMux(options.invocationDirectory, rawPath),
|
|
2512
|
-
setDirectory: (directory) => {
|
|
2513
|
-
directoryManager.setDirectory(directory.directoryId, directory);
|
|
2514
|
-
},
|
|
2515
|
-
directoryIdOf: (directory) => directory.directoryId,
|
|
2516
|
-
setActiveDirectoryId: (directoryId) => {
|
|
2517
|
-
workspace.activeDirectoryId = directoryId;
|
|
2518
|
-
},
|
|
2519
|
-
syncGitStateWithDirectories,
|
|
2520
|
-
noteGitActivity,
|
|
2521
|
-
hydratePersistedConversationsForDirectory,
|
|
2522
|
-
findConversationIdByDirectory: (directoryId) =>
|
|
2523
|
-
conversationManager.findConversationIdByDirectory(
|
|
2524
|
-
directoryId,
|
|
2525
|
-
conversationManager.orderedIds(),
|
|
2526
|
-
),
|
|
2527
|
-
directoriesHas: (directoryId) => directoryManager.hasDirectory(directoryId),
|
|
2528
|
-
deleteDirectory: (directoryId) => {
|
|
2529
|
-
directoryManager.deleteDirectory(directoryId);
|
|
2530
|
-
},
|
|
2531
|
-
deleteDirectoryGitState,
|
|
2532
|
-
projectPaneSnapshotDirectoryId: () => workspace.projectPaneSnapshot?.directoryId ?? null,
|
|
2533
|
-
clearProjectPaneSnapshot: () => {
|
|
2534
|
-
workspace.projectPaneSnapshot = null;
|
|
2535
|
-
workspace.projectPaneScrollTop = 0;
|
|
2536
|
-
},
|
|
2537
|
-
directoriesSize: () => directoryManager.directoriesSize(),
|
|
2538
|
-
invocationDirectory: options.invocationDirectory,
|
|
2539
|
-
activeDirectoryId: () => workspace.activeDirectoryId,
|
|
2540
|
-
firstDirectoryId: () => directoryManager.firstDirectoryId(),
|
|
2541
|
-
});
|
|
2542
|
-
const runtimeControlActions = new RuntimeControlActions({
|
|
2543
|
-
conversationById: (sessionId) => conversationManager.get(sessionId),
|
|
2544
|
-
interruptSession: async (sessionId) => {
|
|
2545
|
-
return await controlPlaneService.interruptSession(sessionId);
|
|
2546
|
-
},
|
|
2547
|
-
nowIso: () => new Date().toISOString(),
|
|
2548
|
-
markDirty,
|
|
2549
|
-
toggleGatewayProfiler: async (input) => {
|
|
2550
|
-
return await toggleGatewayProfilerFn(input);
|
|
2551
|
-
},
|
|
2552
|
-
toggleGatewayStatusTimeline: async (input) => {
|
|
2553
|
-
return await toggleGatewayStatusTimelineFn(input);
|
|
2554
|
-
},
|
|
2555
|
-
toggleGatewayRenderTrace: async (input) => {
|
|
2556
|
-
return await toggleGatewayRenderTraceFn(input);
|
|
2557
|
-
},
|
|
2558
|
-
invocationDirectory: options.invocationDirectory,
|
|
2559
|
-
sessionName: muxSessionName,
|
|
2560
|
-
setTaskPaneNotice: (message) => {
|
|
2561
|
-
workspace.taskPaneNotice = message;
|
|
2562
|
-
recordStatusTimeline({
|
|
2563
|
-
direction: 'outgoing',
|
|
2564
|
-
source: 'task-pane-notice',
|
|
2565
|
-
eventType: 'status-notice',
|
|
2566
|
-
labels: resolveTraceLabels({
|
|
2567
|
-
sessionId: conversationManager.activeConversationId,
|
|
2568
|
-
directoryId: workspace.activeDirectoryId,
|
|
2569
|
-
conversationId: conversationManager.activeConversationId,
|
|
2570
|
-
}),
|
|
2571
|
-
payload: {
|
|
2572
|
-
message,
|
|
2573
|
-
},
|
|
2574
|
-
});
|
|
2575
|
-
},
|
|
2576
|
-
setDebugFooterNotice: (message) => {
|
|
2577
|
-
debugFooterNotice.set(message);
|
|
2578
|
-
recordStatusTimeline({
|
|
2579
|
-
direction: 'outgoing',
|
|
2580
|
-
source: 'debug-footer-notice',
|
|
2581
|
-
eventType: 'status-notice',
|
|
2582
|
-
labels: resolveTraceLabels({
|
|
2583
|
-
sessionId: conversationManager.activeConversationId,
|
|
2584
|
-
directoryId: workspace.activeDirectoryId,
|
|
2585
|
-
conversationId: conversationManager.activeConversationId,
|
|
2586
|
-
}),
|
|
2587
|
-
payload: {
|
|
2588
|
-
message,
|
|
2589
|
-
},
|
|
2590
|
-
});
|
|
2591
|
-
},
|
|
2592
|
-
listConversationIdsForTitleRefresh: () => conversationManager.orderedIds(),
|
|
2593
|
-
conversationAgentTypeForTitleRefresh: (sessionId) =>
|
|
2594
|
-
conversationManager.get(sessionId)?.agentType ?? null,
|
|
2595
|
-
refreshConversationTitle: async (sessionId) => {
|
|
2596
|
-
return await controlPlaneService.refreshConversationTitle(sessionId);
|
|
2597
|
-
},
|
|
2598
|
-
});
|
|
2599
|
-
const runtimeWorkspaceActions = new RuntimeWorkspaceActions({
|
|
2600
|
-
conversationActions: conversationLifecycle,
|
|
2601
|
-
directoryActions: runtimeDirectoryActions,
|
|
2602
|
-
repositoryActions: runtimeRepositoryActions,
|
|
2603
|
-
controlActions: runtimeControlActions,
|
|
2604
|
-
taskPaneActions: runtimeTaskPane,
|
|
2605
|
-
taskPaneShortcuts: runtimeTaskPane,
|
|
2606
|
-
orderedActiveRepositoryIds: () =>
|
|
2607
|
-
orderedActiveRepositoryRecords().map((repository) => repository.repositoryId),
|
|
2608
|
-
});
|
|
2609
|
-
|
|
2610
|
-
const toggleCommandMenu = (): void => {
|
|
2611
|
-
if (workspace.commandMenu !== null) {
|
|
2612
|
-
workspace.commandMenu = null;
|
|
2613
|
-
commandMenuGitHubProjectPrState = null;
|
|
2614
|
-
commandMenuScopedDirectoryId = null;
|
|
2615
|
-
markDirty();
|
|
2616
|
-
return;
|
|
2617
|
-
}
|
|
2618
|
-
workspace.newThreadPrompt = null;
|
|
2619
|
-
workspace.addDirectoryPrompt = null;
|
|
2620
|
-
workspace.taskEditorPrompt = null;
|
|
2621
|
-
workspace.repositoryPrompt = null;
|
|
2622
|
-
if (workspace.conversationTitleEdit !== null) {
|
|
2623
|
-
stopConversationTitleEdit(true);
|
|
2624
|
-
}
|
|
2625
|
-
commandMenuScopedDirectoryId = null;
|
|
2626
|
-
workspace.commandMenu = createCommandMenuState({
|
|
2627
|
-
scope: 'all',
|
|
2628
|
-
});
|
|
2629
|
-
commandMenuAgentTools.refresh();
|
|
2630
|
-
const directoryId = resolveDirectoryForAction();
|
|
2631
|
-
if (directoryId === null) {
|
|
2632
|
-
commandMenuGitHubProjectPrState = null;
|
|
2633
|
-
} else {
|
|
2634
|
-
refreshCommandMenuGitHubProjectPrState(directoryId);
|
|
2635
|
-
}
|
|
2636
|
-
markDirty();
|
|
2637
|
-
};
|
|
2638
|
-
|
|
2639
|
-
const startThreadFromCommandMenu = (
|
|
2640
|
-
directoryId: string,
|
|
2641
|
-
agentType: ReturnType<typeof normalizeThreadAgentType>,
|
|
2642
|
-
): void => {
|
|
2643
|
-
queueControlPlaneOp(async () => {
|
|
2644
|
-
await runtimeWorkspaceActions.createAndActivateConversationInDirectory(
|
|
2645
|
-
directoryId,
|
|
2646
|
-
agentType,
|
|
2647
|
-
);
|
|
2648
|
-
}, `command-menu-start-thread:${agentType}`);
|
|
2649
|
-
};
|
|
2650
|
-
|
|
2651
|
-
const installAgentToolFromCommandMenu = (
|
|
2652
|
-
directoryId: string,
|
|
2653
|
-
agentType: InstallableAgentType,
|
|
2654
|
-
installCommand: string,
|
|
2655
|
-
): void => {
|
|
2656
|
-
queueControlPlaneOp(async () => {
|
|
2657
|
-
await runCommandInNewTerminalThread(directoryId, installCommand);
|
|
2658
|
-
commandMenuAgentTools.refresh();
|
|
2659
|
-
}, `command-menu-install-agent-tool:${agentType}`);
|
|
2660
|
-
};
|
|
2661
|
-
|
|
2662
|
-
const runCommandInNewTerminalThread = async (
|
|
2663
|
-
directoryId: string,
|
|
2664
|
-
commandText: string,
|
|
2665
|
-
): Promise<void> => {
|
|
2666
|
-
const priorSessionIds = new Set(conversationManager.orderedIds());
|
|
2667
|
-
await runtimeWorkspaceActions.createAndActivateConversationInDirectory(directoryId, 'terminal');
|
|
2668
|
-
const terminalSessionId =
|
|
2669
|
-
conversationManager.orderedIds().find((sessionId) => !priorSessionIds.has(sessionId)) ??
|
|
2670
|
-
conversationManager.activeConversationId;
|
|
2671
|
-
if (terminalSessionId === null) {
|
|
2672
|
-
throw new Error('failed to locate terminal session for command');
|
|
2673
|
-
}
|
|
2674
|
-
await controlPlaneService.respondToSession(terminalSessionId, `${commandText}\n`);
|
|
2675
|
-
};
|
|
2676
|
-
|
|
2677
|
-
const resolveCritiqueReviewAgentFromEnvironment = (): 'claude' | 'opencode' | null => {
|
|
2678
|
-
const claudeAvailable =
|
|
2679
|
-
commandMenuAgentTools.statusForAgent('claude')?.available === true ||
|
|
2680
|
-
commandExistsOnPath('claude');
|
|
2681
|
-
const opencodeAvailable = commandExistsOnPath('opencode');
|
|
2682
|
-
return resolveCritiqueReviewAgent({
|
|
2683
|
-
claudeAvailable,
|
|
2684
|
-
opencodeAvailable,
|
|
2685
|
-
});
|
|
2686
|
-
};
|
|
2687
|
-
|
|
2688
|
-
const resolveCritiqueReviewBaseBranchForDirectory = async (
|
|
2689
|
-
directoryId: string,
|
|
2690
|
-
): Promise<string> => {
|
|
2691
|
-
const directory = directoryManager.getDirectory(directoryId);
|
|
2692
|
-
if (directory === undefined || directory === null) {
|
|
2693
|
-
return 'main';
|
|
2694
|
-
}
|
|
2695
|
-
return await resolveCritiqueReviewBaseBranch(directory.path, runGitCommand);
|
|
2696
|
-
};
|
|
2697
|
-
|
|
2698
|
-
const runCritiqueReviewFromCommandMenu = (
|
|
2699
|
-
directoryId: string,
|
|
2700
|
-
mode: 'unstaged' | 'staged' | 'base-branch',
|
|
2701
|
-
): void => {
|
|
2702
|
-
queueControlPlaneOp(async () => {
|
|
2703
|
-
const agent = resolveCritiqueReviewAgentFromEnvironment();
|
|
2704
|
-
if (mode !== 'base-branch') {
|
|
2705
|
-
const commandText = buildCritiqueReviewCommand({
|
|
2706
|
-
mode,
|
|
2707
|
-
agent,
|
|
2708
|
-
});
|
|
2709
|
-
await runCommandInNewTerminalThread(directoryId, commandText);
|
|
2710
|
-
const reviewLabelByMode: Readonly<Record<'unstaged' | 'staged', string>> = {
|
|
2711
|
-
unstaged: 'unstaged',
|
|
2712
|
-
staged: 'staged',
|
|
2713
|
-
};
|
|
2714
|
-
setCommandNotice(
|
|
2715
|
-
`running critique ${reviewLabelByMode[mode]} review (${agent ?? 'default'})`,
|
|
2716
|
-
);
|
|
2717
|
-
return;
|
|
2718
|
-
}
|
|
2719
|
-
const baseBranch = await resolveCritiqueReviewBaseBranchForDirectory(directoryId);
|
|
2720
|
-
const commandText = buildCritiqueReviewCommand({
|
|
2721
|
-
mode: 'base-branch',
|
|
2722
|
-
baseBranch,
|
|
2723
|
-
agent,
|
|
2724
|
-
});
|
|
2725
|
-
await runCommandInNewTerminalThread(directoryId, commandText);
|
|
2726
|
-
setCommandNotice(`running critique review vs ${baseBranch} (${agent ?? 'default'})`);
|
|
2727
|
-
}, `command-menu-critique-review:${mode}`);
|
|
2728
|
-
};
|
|
2729
|
-
|
|
2730
|
-
commandMenuRegistry.registerProvider('critique.review', (context) => {
|
|
2731
|
-
const directoryId = context.activeDirectoryId;
|
|
2732
|
-
if (directoryId === null) {
|
|
2733
|
-
return [];
|
|
2734
|
-
}
|
|
2735
|
-
const critiqueStatus = commandMenuAgentTools.statusForAgent('critique');
|
|
2736
|
-
if (critiqueStatus !== null && !critiqueStatus.available) {
|
|
2737
|
-
return [];
|
|
2738
|
-
}
|
|
2739
|
-
return [
|
|
2740
|
-
{
|
|
2741
|
-
id: 'critique.review.unstaged',
|
|
2742
|
-
title: 'Critique AI Review: Unstaged Changes',
|
|
2743
|
-
aliases: ['critique unstaged review', 'review unstaged diff', 'ai review unstaged'],
|
|
2744
|
-
keywords: ['critique', 'review', 'unstaged', 'diff', 'ai'],
|
|
2745
|
-
detail: 'runs critique review',
|
|
2746
|
-
run: () => {
|
|
2747
|
-
runCritiqueReviewFromCommandMenu(directoryId, 'unstaged');
|
|
2748
|
-
},
|
|
2749
|
-
},
|
|
2750
|
-
{
|
|
2751
|
-
id: 'critique.review.staged',
|
|
2752
|
-
title: 'Critique AI Review: Staged Changes',
|
|
2753
|
-
aliases: ['critique staged review', 'review staged diff', 'ai review staged'],
|
|
2754
|
-
keywords: ['critique', 'review', 'staged', 'diff', 'ai'],
|
|
2755
|
-
detail: 'runs critique review --staged',
|
|
2756
|
-
run: () => {
|
|
2757
|
-
runCritiqueReviewFromCommandMenu(directoryId, 'staged');
|
|
2758
|
-
},
|
|
2759
|
-
},
|
|
2760
|
-
{
|
|
2761
|
-
id: 'critique.review.base-branch',
|
|
2762
|
-
title: 'Critique AI Review: Current Branch vs Base',
|
|
2763
|
-
aliases: ['critique base review', 'review against base branch', 'ai review base'],
|
|
2764
|
-
keywords: ['critique', 'review', 'base', 'branch', 'diff', 'ai'],
|
|
2765
|
-
detail: 'runs critique review <base> HEAD',
|
|
2766
|
-
run: () => {
|
|
2767
|
-
runCritiqueReviewFromCommandMenu(directoryId, 'base-branch');
|
|
2768
|
-
},
|
|
2769
|
-
},
|
|
2770
|
-
];
|
|
2771
|
-
});
|
|
2772
|
-
|
|
2773
|
-
commandMenuRegistry.registerProvider('thread.start', (context) => {
|
|
2774
|
-
const directoryId = context.activeDirectoryId;
|
|
2775
|
-
if (directoryId === null) {
|
|
2776
|
-
return [];
|
|
2777
|
-
}
|
|
2778
|
-
const actions: RegisteredCommandMenuAction<RuntimeCommandMenuContext>[] = [
|
|
2779
|
-
{
|
|
2780
|
-
id: 'thread.start.codex',
|
|
2781
|
-
title: 'Start Codex thread',
|
|
2782
|
-
aliases: ['codex', 'start codex'],
|
|
2783
|
-
keywords: ['start', 'thread', 'codex', 'new'],
|
|
2784
|
-
run: () => {
|
|
2785
|
-
startThreadFromCommandMenu(directoryId, 'codex');
|
|
2786
|
-
},
|
|
2787
|
-
},
|
|
2788
|
-
{
|
|
2789
|
-
id: 'thread.start.claude',
|
|
2790
|
-
title: 'Start Claude thread',
|
|
2791
|
-
aliases: ['claude', 'start claude'],
|
|
2792
|
-
keywords: ['start', 'thread', 'claude', 'new'],
|
|
2793
|
-
run: () => {
|
|
2794
|
-
startThreadFromCommandMenu(directoryId, 'claude');
|
|
2795
|
-
},
|
|
2796
|
-
},
|
|
2797
|
-
{
|
|
2798
|
-
id: 'thread.start.cursor',
|
|
2799
|
-
title: 'Start Cursor thread',
|
|
2800
|
-
aliases: ['cursor', 'cur', 'start cursor'],
|
|
2801
|
-
keywords: ['start', 'thread', 'cursor', 'new'],
|
|
2802
|
-
run: () => {
|
|
2803
|
-
startThreadFromCommandMenu(directoryId, 'cursor');
|
|
2804
|
-
},
|
|
2805
|
-
},
|
|
2806
|
-
{
|
|
2807
|
-
id: 'thread.start.terminal',
|
|
2808
|
-
title: 'Start Terminal thread',
|
|
2809
|
-
aliases: ['terminal', 'shell', 'start terminal'],
|
|
2810
|
-
keywords: ['start', 'thread', 'terminal', 'shell', 'new'],
|
|
2811
|
-
run: () => {
|
|
2812
|
-
startThreadFromCommandMenu(directoryId, 'terminal');
|
|
2813
|
-
},
|
|
2814
|
-
},
|
|
2815
|
-
{
|
|
2816
|
-
id: 'thread.start.critique',
|
|
2817
|
-
title: 'Start Critique thread (diff)',
|
|
2818
|
-
aliases: ['critique', 'start critique', 'critique diff'],
|
|
2819
|
-
keywords: ['start', 'thread', 'critique', 'diff', 'new'],
|
|
2820
|
-
run: () => {
|
|
2821
|
-
startThreadFromCommandMenu(directoryId, 'critique');
|
|
2822
|
-
},
|
|
2823
|
-
},
|
|
2824
|
-
];
|
|
2825
|
-
const installableByAgent: Readonly<
|
|
2826
|
-
Record<InstallableAgentType, { startId: string; installId: string; installTitle: string }>
|
|
2827
|
-
> = {
|
|
2828
|
-
codex: {
|
|
2829
|
-
startId: 'thread.start.codex',
|
|
2830
|
-
installId: 'thread.install.codex',
|
|
2831
|
-
installTitle: 'Install Codex CLI',
|
|
2832
|
-
},
|
|
2833
|
-
claude: {
|
|
2834
|
-
startId: 'thread.start.claude',
|
|
2835
|
-
installId: 'thread.install.claude',
|
|
2836
|
-
installTitle: 'Install Claude CLI',
|
|
2837
|
-
},
|
|
2838
|
-
cursor: {
|
|
2839
|
-
startId: 'thread.start.cursor',
|
|
2840
|
-
installId: 'thread.install.cursor',
|
|
2841
|
-
installTitle: 'Install Cursor CLI',
|
|
2842
|
-
},
|
|
2843
|
-
critique: {
|
|
2844
|
-
startId: 'thread.start.critique',
|
|
2845
|
-
installId: 'thread.install.critique',
|
|
2846
|
-
installTitle: 'Install Critique CLI',
|
|
2847
|
-
},
|
|
2848
|
-
};
|
|
2849
|
-
const adjusted: RegisteredCommandMenuAction<RuntimeCommandMenuContext>[] = [];
|
|
2850
|
-
for (const action of actions) {
|
|
2851
|
-
adjusted.push(action);
|
|
2852
|
-
}
|
|
2853
|
-
for (const agentType of ['codex', 'claude', 'cursor', 'critique'] as const) {
|
|
2854
|
-
const status = commandMenuAgentTools.statusForAgent(agentType);
|
|
2855
|
-
if (status === null || status.available || status.installCommand === null) {
|
|
2856
|
-
continue;
|
|
2857
|
-
}
|
|
2858
|
-
const installCommand = status.installCommand;
|
|
2859
|
-
const mapping = installableByAgent[agentType];
|
|
2860
|
-
const startIndex = adjusted.findIndex((action) => action.id === mapping.startId);
|
|
2861
|
-
if (startIndex < 0) {
|
|
2862
|
-
continue;
|
|
2863
|
-
}
|
|
2864
|
-
adjusted.splice(startIndex, 1, {
|
|
2865
|
-
id: mapping.installId,
|
|
2866
|
-
title: mapping.installTitle,
|
|
2867
|
-
aliases: [`install ${agentType}`, `${agentType} install`, `setup ${agentType}`],
|
|
2868
|
-
keywords: ['install', 'thread', agentType, 'setup'],
|
|
2869
|
-
detail: installCommand,
|
|
2870
|
-
run: () => {
|
|
2871
|
-
installAgentToolFromCommandMenu(directoryId, agentType, installCommand);
|
|
2872
|
-
},
|
|
2873
|
-
});
|
|
2874
|
-
}
|
|
2875
|
-
return adjusted;
|
|
2876
|
-
});
|
|
2877
|
-
|
|
2878
|
-
commandMenuRegistry.registerAction({
|
|
2879
|
-
id: 'thread.close.active',
|
|
2880
|
-
title: 'Close active thread',
|
|
2881
|
-
aliases: ['close thread', 'archive thread'],
|
|
2882
|
-
keywords: ['close', 'thread', 'archive'],
|
|
2883
|
-
when: (context) => context.activeConversationId !== null,
|
|
2884
|
-
run: async (context) => {
|
|
2885
|
-
const conversationId = context.activeConversationId;
|
|
2886
|
-
if (conversationId === null) {
|
|
2887
|
-
return;
|
|
2888
|
-
}
|
|
2889
|
-
queueControlPlaneOp(async () => {
|
|
2890
|
-
await runtimeWorkspaceActions.archiveConversation(conversationId);
|
|
2891
|
-
}, 'command-menu-close-thread');
|
|
2892
|
-
},
|
|
2893
|
-
});
|
|
2894
|
-
|
|
2895
|
-
commandMenuRegistry.registerAction({
|
|
2896
|
-
id: 'theme.choose',
|
|
2897
|
-
title: 'Set a Theme',
|
|
2898
|
-
aliases: ['theme', 'change theme', 'set theme'],
|
|
2899
|
-
keywords: ['theme', 'appearance', 'colors'],
|
|
2900
|
-
run: () => {
|
|
2901
|
-
startThemePickerSession();
|
|
2902
|
-
},
|
|
2903
|
-
});
|
|
2904
|
-
|
|
2905
|
-
commandMenuRegistry.registerProvider('theme.set', () => {
|
|
2906
|
-
const selectedThemeName = getActiveMuxTheme().name;
|
|
2907
|
-
return muxThemePresetNames().map(
|
|
2908
|
-
(preset): RegisteredCommandMenuAction<RuntimeCommandMenuContext> => ({
|
|
2909
|
-
id: `${THEME_ACTION_ID_PREFIX}${preset}`,
|
|
2910
|
-
title: preset,
|
|
2911
|
-
aliases: [preset, `theme ${preset}`],
|
|
2912
|
-
keywords: ['theme', 'preset', preset],
|
|
2913
|
-
...(selectedThemeName === preset ||
|
|
2914
|
-
(preset === 'default' && selectedThemeName === 'legacy-default')
|
|
2915
|
-
? {
|
|
2916
|
-
detail: 'current',
|
|
2917
|
-
}
|
|
2918
|
-
: {}),
|
|
2919
|
-
run: () => {
|
|
2920
|
-
if (themePickerSession !== null) {
|
|
2921
|
-
themePickerSession.committed = true;
|
|
2922
|
-
}
|
|
2923
|
-
applyThemePreset(preset, true);
|
|
2924
|
-
},
|
|
2925
|
-
}),
|
|
2926
|
-
);
|
|
2927
|
-
});
|
|
2928
|
-
|
|
2929
|
-
commandMenuRegistry.registerProvider('project.open', () => {
|
|
2930
|
-
return [...directoryRecords.values()].map(
|
|
2931
|
-
(directory): RegisteredCommandMenuAction<RuntimeCommandMenuContext> => ({
|
|
2932
|
-
id: `project.open.${directory.directoryId}`,
|
|
2933
|
-
title: `Project ${commandMenuProjectPathTail(directory.path)}`,
|
|
2934
|
-
aliases: [
|
|
2935
|
-
'go to project',
|
|
2936
|
-
'project',
|
|
2937
|
-
directory.path,
|
|
2938
|
-
commandMenuProjectPathTail(directory.path),
|
|
2939
|
-
],
|
|
2940
|
-
keywords: ['project', 'open', 'go'],
|
|
2941
|
-
run: () => {
|
|
2942
|
-
enterProjectPane(directory.directoryId);
|
|
2943
|
-
markDirty();
|
|
2944
|
-
},
|
|
2945
|
-
}),
|
|
2946
|
-
);
|
|
2947
|
-
});
|
|
2948
|
-
|
|
2949
|
-
commandMenuRegistry.registerProvider('github.repo.open', (context) => {
|
|
2950
|
-
const repositoryUrl = context.githubRepositoryUrl;
|
|
2951
|
-
if (repositoryUrl === null) {
|
|
2952
|
-
return [];
|
|
2953
|
-
}
|
|
2954
|
-
return [
|
|
2955
|
-
{
|
|
2956
|
-
id: 'github.repo.open',
|
|
2957
|
-
title: 'Open GitHub for This Repo',
|
|
2958
|
-
aliases: ['open github for this repo', 'open github repo', 'open repository on github'],
|
|
2959
|
-
keywords: ['github', 'repository', 'repo', 'open'],
|
|
2960
|
-
detail: repositoryUrl,
|
|
2961
|
-
run: () => {
|
|
2962
|
-
const opened = openUrlInBrowser(repositoryUrl);
|
|
2963
|
-
setCommandNotice(
|
|
2964
|
-
opened
|
|
2965
|
-
? 'opened github repository in browser'
|
|
2966
|
-
: `open github repository: ${repositoryUrl}`,
|
|
2967
|
-
);
|
|
2968
|
-
},
|
|
2969
|
-
},
|
|
2970
|
-
];
|
|
2971
|
-
});
|
|
2972
|
-
|
|
2973
|
-
commandMenuRegistry.registerProvider('github.project-pr', (context) => {
|
|
2974
|
-
const directoryId = context.activeDirectoryId;
|
|
2975
|
-
if (directoryId === null || context.githubProjectPrLoading) {
|
|
2976
|
-
return [];
|
|
2977
|
-
}
|
|
2978
|
-
const showPrActions = shouldShowGitHubPrActions({
|
|
2979
|
-
trackedBranch: context.githubTrackedBranch,
|
|
2980
|
-
defaultBranch: context.githubDefaultBranch,
|
|
2981
|
-
});
|
|
2982
|
-
if (!showPrActions) {
|
|
2983
|
-
return [];
|
|
2984
|
-
}
|
|
2985
|
-
if (context.githubOpenPrUrl !== null) {
|
|
2986
|
-
return [
|
|
2987
|
-
{
|
|
2988
|
-
id: 'github.pr.open',
|
|
2989
|
-
title: 'Open PR',
|
|
2990
|
-
aliases: ['open pull request', 'open pr'],
|
|
2991
|
-
keywords: ['github', 'pr', 'open', 'pull-request'],
|
|
2992
|
-
detail: context.githubTrackedBranch ?? 'current project',
|
|
2993
|
-
run: async () => {
|
|
2994
|
-
queueControlPlaneOp(async () => {
|
|
2995
|
-
let result: unknown;
|
|
2996
|
-
try {
|
|
2997
|
-
result = await streamClient.sendCommand({
|
|
2998
|
-
type: 'github.project-pr',
|
|
2999
|
-
directoryId,
|
|
3000
|
-
});
|
|
3001
|
-
} catch (error: unknown) {
|
|
3002
|
-
if (isGitHubAuthUnavailableError(error)) {
|
|
3003
|
-
setGitHubDebugAuthState({
|
|
3004
|
-
auth: 'no',
|
|
3005
|
-
projectPr: 'er',
|
|
3006
|
-
});
|
|
3007
|
-
setCommandNotice(githubAuthHintNotice);
|
|
3008
|
-
return;
|
|
3009
|
-
}
|
|
3010
|
-
setGitHubDebugAuthState({
|
|
3011
|
-
auth: 'er',
|
|
3012
|
-
projectPr: 'er',
|
|
3013
|
-
});
|
|
3014
|
-
throw error;
|
|
3015
|
-
}
|
|
3016
|
-
const parsedResult = asRecord(result);
|
|
3017
|
-
if (parsedResult === null) {
|
|
3018
|
-
setCommandNotice('github project PR state unavailable');
|
|
3019
|
-
return;
|
|
3020
|
-
}
|
|
3021
|
-
const state = parseGitHubProjectPrState(directoryId, parsedResult);
|
|
3022
|
-
setGitHubDebugAuthState({
|
|
3023
|
-
projectPr: 'ok',
|
|
3024
|
-
});
|
|
3025
|
-
commandMenuGitHubProjectPrState = state;
|
|
3026
|
-
if (state.openPrUrl === null) {
|
|
3027
|
-
setCommandNotice('no open pull request for tracked branch');
|
|
3028
|
-
return;
|
|
3029
|
-
}
|
|
3030
|
-
const opened = openUrlInBrowser(state.openPrUrl);
|
|
3031
|
-
setCommandNotice(
|
|
3032
|
-
opened ? 'opened pull request in browser' : `open pull request: ${state.openPrUrl}`,
|
|
3033
|
-
);
|
|
3034
|
-
}, 'command-menu-open-pr');
|
|
3035
|
-
},
|
|
3036
|
-
},
|
|
3037
|
-
];
|
|
3038
|
-
}
|
|
3039
|
-
if (context.githubTrackedBranch !== null) {
|
|
3040
|
-
return [
|
|
3041
|
-
{
|
|
3042
|
-
id: 'github.pr.create',
|
|
3043
|
-
title: 'Create PR',
|
|
3044
|
-
aliases: ['create pull request', 'new pr'],
|
|
3045
|
-
keywords: ['github', 'pr', 'create', 'pull-request'],
|
|
3046
|
-
detail: context.githubTrackedBranch,
|
|
3047
|
-
run: async () => {
|
|
3048
|
-
queueControlPlaneOp(async () => {
|
|
3049
|
-
let result: unknown;
|
|
3050
|
-
try {
|
|
3051
|
-
result = await streamClient.sendCommand({
|
|
3052
|
-
type: 'github.pr-create',
|
|
3053
|
-
directoryId,
|
|
3054
|
-
});
|
|
3055
|
-
} catch (error: unknown) {
|
|
3056
|
-
if (isGitHubAuthUnavailableError(error)) {
|
|
3057
|
-
setGitHubDebugAuthState({
|
|
3058
|
-
auth: 'no',
|
|
3059
|
-
});
|
|
3060
|
-
setCommandNotice(githubAuthHintNotice);
|
|
3061
|
-
return;
|
|
3062
|
-
}
|
|
3063
|
-
setGitHubDebugAuthState({
|
|
3064
|
-
auth: 'er',
|
|
3065
|
-
});
|
|
3066
|
-
throw error;
|
|
3067
|
-
}
|
|
3068
|
-
const parsedResult = asRecord(result);
|
|
3069
|
-
if (parsedResult === null) {
|
|
3070
|
-
setCommandNotice('github PR creation result unavailable');
|
|
3071
|
-
return;
|
|
3072
|
-
}
|
|
3073
|
-
const prUrl = parseGitHubPrUrl(parsedResult);
|
|
3074
|
-
if (prUrl === null) {
|
|
3075
|
-
throw new Error('github.pr-create returned malformed pr url');
|
|
3076
|
-
}
|
|
3077
|
-
setGitHubDebugAuthState({
|
|
3078
|
-
auth: 'ok',
|
|
3079
|
-
projectPr: 'ok',
|
|
3080
|
-
});
|
|
3081
|
-
refreshCommandMenuGitHubProjectPrState(directoryId);
|
|
3082
|
-
const opened = openUrlInBrowser(prUrl);
|
|
3083
|
-
setCommandNotice(
|
|
3084
|
-
opened ? 'opened pull request in browser' : `open pull request: ${prUrl}`,
|
|
3085
|
-
);
|
|
3086
|
-
}, 'command-menu-create-pr');
|
|
3087
|
-
},
|
|
3088
|
-
},
|
|
3089
|
-
];
|
|
3090
|
-
}
|
|
3091
|
-
return [];
|
|
3092
|
-
});
|
|
3093
|
-
|
|
3094
|
-
commandMenuRegistry.registerProvider('profile.toggle', (context) => {
|
|
3095
|
-
const title = context.profileRunning ? 'Stop profiler' : 'Start profiler';
|
|
3096
|
-
const aliases = context.profileRunning
|
|
3097
|
-
? ['stop profile', 'stop profiler']
|
|
3098
|
-
: ['start profile', 'start profiler'];
|
|
3099
|
-
return [
|
|
3100
|
-
{
|
|
3101
|
-
id: context.profileRunning ? 'profile.stop' : 'profile.start',
|
|
3102
|
-
title,
|
|
3103
|
-
aliases,
|
|
3104
|
-
keywords: ['profile', 'profiler'],
|
|
3105
|
-
run: async () => {
|
|
3106
|
-
queueControlPlaneOp(async () => {
|
|
3107
|
-
await runtimeWorkspaceActions.toggleGatewayProfiler();
|
|
3108
|
-
}, 'command-menu-toggle-profile');
|
|
3109
|
-
},
|
|
3110
|
-
},
|
|
3111
|
-
];
|
|
3112
|
-
});
|
|
3113
|
-
|
|
3114
|
-
commandMenuRegistry.registerProvider('status-timeline.toggle', (context) => {
|
|
3115
|
-
const title = context.statusTimelineRunning ? 'Stop status logging' : 'Start status logging';
|
|
3116
|
-
const aliases = context.statusTimelineRunning
|
|
3117
|
-
? ['stop status logging', 'stop status']
|
|
3118
|
-
: ['start status logging', 'start status'];
|
|
3119
|
-
return [
|
|
3120
|
-
{
|
|
3121
|
-
id: context.statusTimelineRunning ? 'status.stop' : 'status.start',
|
|
3122
|
-
title,
|
|
3123
|
-
aliases,
|
|
3124
|
-
keywords: ['status', 'timeline', 'logging'],
|
|
3125
|
-
run: async () => {
|
|
3126
|
-
queueControlPlaneOp(async () => {
|
|
3127
|
-
await runtimeWorkspaceActions.toggleGatewayStatusTimeline();
|
|
3128
|
-
}, 'command-menu-toggle-status-timeline');
|
|
3129
|
-
},
|
|
3130
|
-
},
|
|
3131
|
-
];
|
|
3132
|
-
});
|
|
3133
|
-
|
|
3134
|
-
commandMenuRegistry.registerAction({
|
|
3135
|
-
id: 'app.quit',
|
|
3136
|
-
title: 'Quit',
|
|
3137
|
-
aliases: ['quit app', 'exit'],
|
|
3138
|
-
keywords: ['quit', 'shutdown', 'exit'],
|
|
3139
|
-
run: () => {
|
|
3140
|
-
requestStop();
|
|
3141
|
-
},
|
|
3142
|
-
});
|
|
3143
|
-
|
|
3144
|
-
const pinViewportForSelection = (): void => {
|
|
3145
|
-
if (workspace.selectionPinnedFollowOutput !== null) {
|
|
3146
|
-
return;
|
|
3147
|
-
}
|
|
3148
|
-
const active = conversationManager.getActiveConversation();
|
|
3149
|
-
if (active === null) {
|
|
3150
|
-
return;
|
|
3151
|
-
}
|
|
3152
|
-
const follow = active.oracle.snapshotWithoutHash().viewport.followOutput;
|
|
3153
|
-
workspace.selectionPinnedFollowOutput = follow;
|
|
3154
|
-
if (follow) {
|
|
3155
|
-
active.oracle.setFollowOutput(false);
|
|
3156
|
-
}
|
|
3157
|
-
};
|
|
3158
|
-
|
|
3159
|
-
const releaseViewportPinForSelection = (): void => {
|
|
3160
|
-
if (workspace.selectionPinnedFollowOutput === null) {
|
|
3161
|
-
return;
|
|
3162
|
-
}
|
|
3163
|
-
const shouldRepin = workspace.selectionPinnedFollowOutput;
|
|
3164
|
-
workspace.selectionPinnedFollowOutput = null;
|
|
3165
|
-
if (shouldRepin) {
|
|
3166
|
-
const active = conversationManager.getActiveConversation();
|
|
3167
|
-
if (active === null) {
|
|
3168
|
-
return;
|
|
3169
|
-
}
|
|
3170
|
-
active.oracle.setFollowOutput(true);
|
|
3171
|
-
}
|
|
3172
|
-
};
|
|
3173
|
-
|
|
3174
|
-
const runtimeRenderPipeline = new RuntimeRenderPipeline<
|
|
3175
|
-
ConversationState,
|
|
3176
|
-
ControlPlaneRepositoryRecord,
|
|
3177
|
-
ControlPlaneTaskRecord,
|
|
3178
|
-
ControlPlaneDirectoryRecord,
|
|
3179
|
-
GitRepositorySnapshot,
|
|
3180
|
-
GitSummary,
|
|
3181
|
-
ProcessUsageSample,
|
|
3182
|
-
ReturnType<typeof resolveMuxShortcutBindings>,
|
|
3183
|
-
ReturnType<typeof buildWorkspaceRailViewRows>,
|
|
3184
|
-
NonNullable<ReturnType<typeof buildCurrentModalOverlay>>,
|
|
3185
|
-
ReturnType<OutputLoadSampler['currentStatusRow']>
|
|
3186
|
-
>({
|
|
3187
|
-
renderFlush: {
|
|
3188
|
-
perfNowNs,
|
|
3189
|
-
statusFooterForConversation: (conversation) =>
|
|
3190
|
-
`${formatGitHubDebugTokens(githubDebugAuthState)} ${debugFooterForConversation(conversation)}`,
|
|
3191
|
-
currentStatusNotice: () => debugFooterNotice.current(),
|
|
3192
|
-
currentStatusRow: () => outputLoadSampler.currentStatusRow(),
|
|
3193
|
-
onStatusLineComposed: (input) => {
|
|
3194
|
-
const activeConversationId =
|
|
3195
|
-
input.activeConversation === null ? null : input.activeConversation.sessionId;
|
|
3196
|
-
const payload = {
|
|
3197
|
-
statusFooter: input.statusFooter,
|
|
3198
|
-
statusRow: input.statusRow,
|
|
3199
|
-
projectPaneActive: input.projectPaneActive,
|
|
3200
|
-
homePaneActive: input.homePaneActive,
|
|
3201
|
-
activeConversationId,
|
|
3202
|
-
};
|
|
3203
|
-
recordStatusTimeline({
|
|
3204
|
-
direction: 'outgoing',
|
|
3205
|
-
source: 'render-status-line',
|
|
3206
|
-
eventType: 'status-line',
|
|
3207
|
-
labels: resolveTraceLabels({
|
|
3208
|
-
sessionId: activeConversationId,
|
|
3209
|
-
directoryId: workspace.activeDirectoryId,
|
|
3210
|
-
conversationId: activeConversationId,
|
|
3211
|
-
}),
|
|
3212
|
-
payload,
|
|
3213
|
-
dedupeKey: 'render-status-line',
|
|
3214
|
-
dedupeValue: JSON.stringify(payload),
|
|
3215
|
-
});
|
|
3216
|
-
},
|
|
3217
|
-
buildRenderRows: (renderLayout, railRows, rightRows, statusRow, statusFooter) =>
|
|
3218
|
-
buildRenderRows(renderLayout, railRows, rightRows, statusRow, statusFooter),
|
|
3219
|
-
buildModalOverlay: () => buildCurrentModalOverlay(),
|
|
3220
|
-
applyModalOverlay: (rows, overlay) => {
|
|
3221
|
-
applyModalOverlay(rows, overlay);
|
|
3222
|
-
},
|
|
3223
|
-
renderSelectionOverlay: (renderLayout, frame, renderSelection) =>
|
|
3224
|
-
renderSelectionOverlay(renderLayout, frame, renderSelection),
|
|
3225
|
-
flush: ({ layout: renderLayout, rows, rightFrame, selectionRows, selectionOverlay }) =>
|
|
3226
|
-
screen.flush({
|
|
3227
|
-
layout: renderLayout,
|
|
3228
|
-
rows,
|
|
3229
|
-
rightFrame,
|
|
3230
|
-
selectionRows,
|
|
3231
|
-
selectionOverlay,
|
|
3232
|
-
validateAnsi,
|
|
3233
|
-
}),
|
|
3234
|
-
onFlushOutput: ({ activeConversation, rightFrame, rows, flushResult, changedRowCount }) => {
|
|
3235
|
-
startupOrchestrator.onRenderFlush({
|
|
3236
|
-
activeConversation,
|
|
3237
|
-
activeConversationId: conversationManager.activeConversationId,
|
|
3238
|
-
rightFrameVisible: rightFrame !== null,
|
|
3239
|
-
changedRowCount,
|
|
3240
|
-
});
|
|
3241
|
-
if (muxRecordingWriter !== null && muxRecordingOracle !== null) {
|
|
3242
|
-
const recordingCursorStyle: ScreenCursorStyle =
|
|
3243
|
-
rightFrame === null ? { shape: 'block', blinking: false } : rightFrame.cursor.style;
|
|
3244
|
-
const recordingCursorRow = rightFrame === null ? 0 : rightFrame.cursor.row;
|
|
3245
|
-
const recordingCursorCol =
|
|
3246
|
-
rightFrame === null
|
|
3247
|
-
? layout.rightStartCol - 1
|
|
3248
|
-
: layout.rightStartCol + rightFrame.cursor.col - 1;
|
|
3249
|
-
const canonicalFrame = renderCanonicalFrameAnsi(
|
|
3250
|
-
rows,
|
|
3251
|
-
recordingCursorStyle,
|
|
3252
|
-
flushResult.shouldShowCursor,
|
|
3253
|
-
recordingCursorRow,
|
|
3254
|
-
recordingCursorCol,
|
|
3255
|
-
);
|
|
3256
|
-
muxRecordingOracle.ingest(canonicalFrame);
|
|
3257
|
-
try {
|
|
3258
|
-
muxRecordingWriter.capture(muxRecordingOracle.snapshot());
|
|
3259
|
-
} catch {
|
|
3260
|
-
// Recording failures must never break live interaction.
|
|
3261
|
-
}
|
|
3262
|
-
}
|
|
3263
|
-
},
|
|
3264
|
-
recordRenderSample: (durationMs, changedRowCount) => {
|
|
3265
|
-
outputLoadSampler.recordRenderSample(durationMs, changedRowCount);
|
|
3266
|
-
},
|
|
3267
|
-
},
|
|
3268
|
-
rightPaneRender: {
|
|
3269
|
-
workspace,
|
|
3270
|
-
repositories,
|
|
3271
|
-
taskManager,
|
|
3272
|
-
conversationPane,
|
|
3273
|
-
homePane,
|
|
3274
|
-
projectPane,
|
|
3275
|
-
refreshProjectPaneSnapshot: (directoryId) => {
|
|
3276
|
-
refreshProjectPaneSnapshot(directoryId);
|
|
3277
|
-
return workspace.projectPaneSnapshot;
|
|
3278
|
-
},
|
|
3279
|
-
emptyTaskPaneView: () => ({
|
|
3280
|
-
rows: [],
|
|
3281
|
-
taskIds: [],
|
|
3282
|
-
repositoryIds: [],
|
|
3283
|
-
actions: [],
|
|
3284
|
-
actionCells: [],
|
|
3285
|
-
top: 0,
|
|
3286
|
-
selectedRepositoryId: null,
|
|
3287
|
-
}),
|
|
3288
|
-
},
|
|
3289
|
-
leftRailRender: {
|
|
3290
|
-
leftRailPane,
|
|
3291
|
-
sessionProjectionInstrumentation,
|
|
3292
|
-
workspace,
|
|
3293
|
-
repositoryManager,
|
|
3294
|
-
repositories,
|
|
3295
|
-
repositoryAssociationByDirectoryId,
|
|
3296
|
-
directoryRepositorySnapshotByDirectoryId,
|
|
3297
|
-
directories: directoryRecords,
|
|
3298
|
-
conversations: conversationRecords,
|
|
3299
|
-
gitSummaryByDirectoryId: gitSummaryByDirectoryId,
|
|
3300
|
-
processUsageBySessionId: () => processUsageRefreshService.readonlyUsage(),
|
|
3301
|
-
shortcutBindings,
|
|
3302
|
-
loadingGitSummary: GIT_SUMMARY_LOADING,
|
|
3303
|
-
activeConversationId: () => conversationManager.activeConversationId,
|
|
3304
|
-
orderedConversationIds: () => conversationManager.orderedIds(),
|
|
3305
|
-
},
|
|
3306
|
-
renderState: {
|
|
3307
|
-
workspace,
|
|
3308
|
-
hasDirectory: (directoryId) => directoryManager.hasDirectory(directoryId),
|
|
3309
|
-
activeConversationId: () => conversationManager.activeConversationId,
|
|
3310
|
-
activeConversation: () => conversationManager.getActiveConversation(),
|
|
3311
|
-
snapshotFrame: (conversation) => conversation.oracle.snapshotWithoutHash(),
|
|
3312
|
-
selectionVisibleRows,
|
|
3313
|
-
},
|
|
3314
|
-
isScreenDirty: () => screen.isDirty(),
|
|
3315
|
-
clearDirty: () => {
|
|
3316
|
-
screen.clearDirty();
|
|
3317
|
-
},
|
|
3318
|
-
setLatestRailViewRows: (rows) => {
|
|
3319
|
-
workspace.latestRailViewRows = rows;
|
|
3320
|
-
},
|
|
3321
|
-
activeDirectoryId: () => workspace.activeDirectoryId,
|
|
3322
|
-
});
|
|
3323
|
-
|
|
3324
|
-
const render = (): void => {
|
|
3325
|
-
syncThemePickerPreview();
|
|
3326
|
-
runtimeRenderPipeline.render({
|
|
3327
|
-
shuttingDown,
|
|
3328
|
-
layout,
|
|
3329
|
-
selection: workspace.selection,
|
|
3330
|
-
selectionDrag: workspace.selectionDrag,
|
|
3331
|
-
});
|
|
3332
|
-
};
|
|
3333
|
-
|
|
3334
|
-
const runtimeEnvelopeHandler = new RuntimeEnvelopeHandler<
|
|
3335
|
-
ConversationState,
|
|
3336
|
-
ReturnType<typeof mapTerminalOutputToNormalizedEvent>
|
|
3337
|
-
>({
|
|
3338
|
-
perfNowNs,
|
|
3339
|
-
isRemoved: (sessionId) => conversationManager.isRemoved(sessionId),
|
|
3340
|
-
ensureConversation,
|
|
3341
|
-
ingestOutputChunk: (input) => conversationManager.ingestOutputChunk(input),
|
|
3342
|
-
noteGitActivity,
|
|
3343
|
-
recordOutputChunk: (input) => {
|
|
3344
|
-
outputLoadSampler.recordOutputChunk(input.sessionId, input.chunkLength, input.active);
|
|
3345
|
-
},
|
|
3346
|
-
startupOutputChunk: (sessionId, chunkLength) => {
|
|
3347
|
-
startupOrchestrator.onOutputChunk(sessionId, chunkLength);
|
|
3348
|
-
},
|
|
3349
|
-
startupPaintOutputChunk: (sessionId) => {
|
|
3350
|
-
startupOrchestrator.onPaintOutputChunk(sessionId);
|
|
3351
|
-
},
|
|
3352
|
-
recordPerfEvent,
|
|
3353
|
-
mapTerminalOutputToNormalizedEvent: (chunk, scope, makeId) =>
|
|
3354
|
-
mapTerminalOutputToNormalizedEvent(
|
|
3355
|
-
chunk,
|
|
3356
|
-
scope as Parameters<typeof mapTerminalOutputToNormalizedEvent>[1],
|
|
3357
|
-
makeId,
|
|
3358
|
-
),
|
|
3359
|
-
mapSessionEventToNormalizedEvent: (event, scope, makeId) =>
|
|
3360
|
-
mapSessionEventToNormalizedEvent(
|
|
3361
|
-
event as Parameters<typeof mapSessionEventToNormalizedEvent>[0],
|
|
3362
|
-
scope as Parameters<typeof mapSessionEventToNormalizedEvent>[1],
|
|
3363
|
-
makeId,
|
|
3364
|
-
),
|
|
3365
|
-
observedAtFromSessionEvent: (event) =>
|
|
3366
|
-
observedAtFromSessionEvent(event as Parameters<typeof observedAtFromSessionEvent>[0]),
|
|
3367
|
-
mergeAdapterStateFromSessionEvent: (agentType, adapterState, event, observedAt) =>
|
|
3368
|
-
mergeAdapterStateFromSessionEvent(
|
|
3369
|
-
agentType,
|
|
3370
|
-
adapterState,
|
|
3371
|
-
event as Parameters<typeof mergeAdapterStateFromSessionEvent>[2],
|
|
3372
|
-
observedAt,
|
|
3373
|
-
),
|
|
3374
|
-
enqueueEvent: (event) => {
|
|
3375
|
-
eventPersistence.enqueue(event);
|
|
3376
|
-
},
|
|
3377
|
-
activeConversationId: () => conversationManager.activeConversationId,
|
|
3378
|
-
markSessionExited: (input) => {
|
|
3379
|
-
conversationManager.markSessionExited(input);
|
|
3380
|
-
},
|
|
3381
|
-
deletePtySize: (sessionId) => {
|
|
3382
|
-
ptySizeByConversationId.delete(sessionId);
|
|
3383
|
-
},
|
|
3384
|
-
setExit: (nextExit) => {
|
|
3385
|
-
exit = nextExit;
|
|
3386
|
-
},
|
|
3387
|
-
markDirty,
|
|
3388
|
-
nowIso: () => new Date().toISOString(),
|
|
3389
|
-
recordOutputHandled: (durationMs) => {
|
|
3390
|
-
outputLoadSampler.recordOutputHandled(durationMs);
|
|
3391
|
-
},
|
|
3392
|
-
conversationById: (sessionId) => conversationManager.get(sessionId),
|
|
3393
|
-
applyObservedWorkspaceEvent,
|
|
3394
|
-
applyObservedGitStatusEvent,
|
|
3395
|
-
applyObservedTaskPlanningEvent,
|
|
3396
|
-
idFactory,
|
|
3397
|
-
});
|
|
3398
|
-
const handleEnvelope = (envelope: StreamServerEnvelope): void => {
|
|
3399
|
-
if (envelope.kind === 'pty.output') {
|
|
3400
|
-
const conversation = conversationManager.get(envelope.sessionId);
|
|
3401
|
-
const labels = resolveTraceLabels({
|
|
3402
|
-
sessionId: envelope.sessionId,
|
|
3403
|
-
directoryId: conversation?.directoryId ?? null,
|
|
3404
|
-
conversationId: envelope.sessionId,
|
|
3405
|
-
});
|
|
3406
|
-
if (renderTraceRecorder.shouldCaptureConversation(labels.conversationId)) {
|
|
3407
|
-
const chunk = Buffer.from(envelope.chunkBase64, 'base64');
|
|
3408
|
-
const issues = findRenderTraceControlIssues(chunk);
|
|
3409
|
-
if (issues.length > 0) {
|
|
3410
|
-
const issueSignature = issues.map((issue) => `${issue.kind}:${issue.sequence}`).join('|');
|
|
3411
|
-
recordRenderTrace({
|
|
3412
|
-
direction: 'incoming',
|
|
3413
|
-
source: 'terminal-output',
|
|
3414
|
-
eventType: 'control-sequence-risk',
|
|
3415
|
-
labels,
|
|
3416
|
-
payload: {
|
|
3417
|
-
cursor: envelope.cursor,
|
|
3418
|
-
chunkBytes: chunk.length,
|
|
3419
|
-
chunkPreview: renderTraceChunkPreview(chunk, 320),
|
|
3420
|
-
issues: issues.map((issue) => ({
|
|
3421
|
-
...issue,
|
|
3422
|
-
sequencePreview: renderTraceChunkPreview(issue.sequence, 160),
|
|
3423
|
-
})),
|
|
3424
|
-
},
|
|
3425
|
-
dedupeKey: `render-trace-sequence:${envelope.sessionId}`,
|
|
3426
|
-
dedupeValue: issueSignature,
|
|
3427
|
-
});
|
|
3428
|
-
}
|
|
3429
|
-
}
|
|
3430
|
-
}
|
|
3431
|
-
if (envelope.kind !== 'pty.output') {
|
|
3432
|
-
let eventType: string = envelope.kind;
|
|
3433
|
-
let directoryId: string | null = null;
|
|
3434
|
-
let conversationId: string | null = null;
|
|
3435
|
-
if (envelope.kind === 'pty.event') {
|
|
3436
|
-
eventType = `pty.event.${envelope.event.type}`;
|
|
3437
|
-
} else if (envelope.kind === 'stream.event') {
|
|
3438
|
-
eventType = `stream.event.${envelope.event.type}`;
|
|
3439
|
-
const observedRecord = envelope.event as Record<string, unknown>;
|
|
3440
|
-
directoryId =
|
|
3441
|
-
typeof observedRecord['directoryId'] === 'string' ? observedRecord['directoryId'] : null;
|
|
3442
|
-
conversationId =
|
|
3443
|
-
typeof observedRecord['conversationId'] === 'string'
|
|
3444
|
-
? observedRecord['conversationId']
|
|
3445
|
-
: null;
|
|
3446
|
-
}
|
|
3447
|
-
recordStatusTimeline({
|
|
3448
|
-
direction: 'incoming',
|
|
3449
|
-
source: 'stream-envelope',
|
|
3450
|
-
eventType,
|
|
3451
|
-
labels: resolveTraceLabels({
|
|
3452
|
-
sessionId: 'sessionId' in envelope ? envelope.sessionId : null,
|
|
3453
|
-
directoryId,
|
|
3454
|
-
conversationId,
|
|
3455
|
-
}),
|
|
3456
|
-
payload: envelope,
|
|
3457
|
-
});
|
|
3458
|
-
}
|
|
3459
|
-
runtimeEnvelopeHandler.handleEnvelope(envelope);
|
|
3460
|
-
};
|
|
3461
|
-
|
|
3462
|
-
const removeEnvelopeListener = streamClient.onEnvelope((envelope) => {
|
|
3463
|
-
try {
|
|
3464
|
-
handleEnvelope(envelope);
|
|
3465
|
-
} catch (error: unknown) {
|
|
3466
|
-
handleRuntimeFatal('stream-envelope', error);
|
|
3467
|
-
}
|
|
3468
|
-
});
|
|
3469
|
-
|
|
3470
|
-
const initialActiveId = conversationManager.activeConversationId;
|
|
3471
|
-
conversationManager.setActiveConversationId(null);
|
|
3472
|
-
await startupOrchestrator.activateInitialConversation(initialActiveId);
|
|
3473
|
-
startupOrchestrator.finalizeStartup(initialActiveId);
|
|
3474
|
-
|
|
3475
|
-
const runtimeInputRouter = new RuntimeInputRouter({
|
|
3476
|
-
workspace,
|
|
3477
|
-
conversations: conversationRecords,
|
|
3478
|
-
runtimeWorkspaceActions,
|
|
3479
|
-
runtimeTaskEditorActions: runtimeTaskEditorActions,
|
|
3480
|
-
detectShortcut: detectMuxGlobalShortcut,
|
|
3481
|
-
modalDismissShortcutBindings,
|
|
3482
|
-
shortcutBindings,
|
|
3483
|
-
dismissOnOutsideClick: (rawInput, dismiss, onInsidePointerPress) =>
|
|
3484
|
-
dismissModalOnOutsideClick(rawInput, dismiss, onInsidePointerPress),
|
|
3485
|
-
buildCommandMenuModalOverlay: () => buildCommandMenuModalOverlay(layout.rows),
|
|
3486
|
-
buildConversationTitleModalOverlay: () => buildConversationTitleModalOverlay(layout.rows),
|
|
3487
|
-
buildNewThreadModalOverlay: () => buildNewThreadModalOverlay(layout.rows),
|
|
3488
|
-
resolveNewThreadPromptAgentByRow,
|
|
3489
|
-
stopConversationTitleEdit,
|
|
3490
|
-
queueControlPlaneOp,
|
|
3491
|
-
normalizeGitHubRemoteUrl,
|
|
3492
|
-
repositoriesHas: (repositoryId) => repositories.has(repositoryId),
|
|
3493
|
-
markDirty,
|
|
3494
|
-
scheduleConversationTitlePersist,
|
|
3495
|
-
resolveCommandMenuActions,
|
|
3496
|
-
executeCommandMenuAction,
|
|
3497
|
-
requestStop,
|
|
3498
|
-
resolveDirectoryForAction,
|
|
3499
|
-
openNewThreadPrompt,
|
|
3500
|
-
toggleCommandMenu,
|
|
3501
|
-
firstDirectoryForRepositoryGroup,
|
|
3502
|
-
enterHomePane,
|
|
3503
|
-
enterProjectPane,
|
|
3504
|
-
queuePersistMuxUiState,
|
|
3505
|
-
repositoryGroupIdForDirectory,
|
|
3506
|
-
toggleRepositoryGroup,
|
|
3507
|
-
collapseRepositoryGroup,
|
|
3508
|
-
expandRepositoryGroup,
|
|
3509
|
-
collapseAllRepositoryGroups,
|
|
3510
|
-
expandAllRepositoryGroups,
|
|
3511
|
-
directoriesHas: (directoryId) => directoryManager.hasDirectory(directoryId),
|
|
3512
|
-
conversationDirectoryId: (sessionId) => conversationManager.directoryIdOf(sessionId),
|
|
3513
|
-
conversationsHas: (sessionId) => conversationManager.has(sessionId),
|
|
3514
|
-
getMainPaneMode: () => workspace.mainPaneMode,
|
|
3515
|
-
getActiveConversationId: () => conversationManager.activeConversationId,
|
|
3516
|
-
getActiveDirectoryId: () => workspace.activeDirectoryId,
|
|
3517
|
-
chordTimeoutMs: REPOSITORY_TOGGLE_CHORD_TIMEOUT_MS,
|
|
3518
|
-
collapseAllChordPrefix: REPOSITORY_COLLAPSE_ALL_CHORD_PREFIX,
|
|
3519
|
-
releaseViewportPinForSelection,
|
|
3520
|
-
beginConversationTitleEdit,
|
|
3521
|
-
resetConversationPaneFrameCache: () => {
|
|
3522
|
-
screen.resetFrameCache();
|
|
3523
|
-
},
|
|
3524
|
-
conversationTitleEditDoubleClickWindowMs: CONVERSATION_TITLE_EDIT_DOUBLE_CLICK_WINDOW_MS,
|
|
3525
|
-
projectPaneActionAtRow,
|
|
3526
|
-
queueCloseDirectory: (directoryId) =>
|
|
3527
|
-
queueControlPlaneOp(async () => {
|
|
3528
|
-
await runtimeWorkspaceActions.closeDirectory(directoryId);
|
|
3529
|
-
}, 'project-pane-close-project'),
|
|
3530
|
-
selectTaskById,
|
|
3531
|
-
selectRepositoryById,
|
|
3532
|
-
taskPaneActionAtCell: taskFocusedPaneActionAtCell,
|
|
3533
|
-
taskPaneActionAtRow: taskFocusedPaneActionAtRow,
|
|
3534
|
-
taskPaneTaskIdAtRow: taskFocusedPaneTaskIdAtRow,
|
|
3535
|
-
taskPaneRepositoryIdAtRow: taskFocusedPaneRepositoryIdAtRow,
|
|
3536
|
-
applyPaneDividerAtCol,
|
|
3537
|
-
pinViewportForSelection,
|
|
3538
|
-
homePaneEditDoubleClickWindowMs: HOME_PANE_EDIT_DOUBLE_CLICK_WINDOW_MS,
|
|
3539
|
-
});
|
|
3540
|
-
const runtimeInputPipeline = new RuntimeInputPipeline({
|
|
3541
|
-
preflight: {
|
|
3542
|
-
isShuttingDown: () => shuttingDown,
|
|
3543
|
-
routeModalInput: (input) => runtimeInputRouter.routeModalInput(input),
|
|
3544
|
-
handleEscapeInput: (input) => {
|
|
3545
|
-
if (workspace.selection !== null || workspace.selectionDrag !== null) {
|
|
3546
|
-
workspace.selection = null;
|
|
3547
|
-
workspace.selectionDrag = null;
|
|
3548
|
-
releaseViewportPinForSelection();
|
|
3549
|
-
markDirty();
|
|
3550
|
-
}
|
|
3551
|
-
if (workspace.mainPaneMode === 'conversation') {
|
|
3552
|
-
const escapeTarget = conversationManager.getActiveConversation();
|
|
3553
|
-
if (escapeTarget !== null) {
|
|
3554
|
-
streamClient.sendInput(escapeTarget.sessionId, input);
|
|
3555
|
-
}
|
|
3556
|
-
}
|
|
3557
|
-
},
|
|
3558
|
-
onFocusIn: () => {
|
|
3559
|
-
inputModeManager.enable();
|
|
3560
|
-
markDirty();
|
|
3561
|
-
},
|
|
3562
|
-
onFocusOut: () => {
|
|
3563
|
-
markDirty();
|
|
3564
|
-
},
|
|
3565
|
-
handleRepositoryFoldInput: (input) => runtimeInputRouter.handleRepositoryFoldInput(input),
|
|
3566
|
-
handleGlobalShortcutInput: (input) => runtimeInputRouter.handleGlobalShortcutInput(input),
|
|
3567
|
-
handleTaskPaneShortcutInput: (input) =>
|
|
3568
|
-
runtimeWorkspaceActions.handleTaskPaneShortcutInput(input),
|
|
3569
|
-
handleCopyShortcutInput: (input) => {
|
|
3570
|
-
if (
|
|
3571
|
-
workspace.mainPaneMode !== 'conversation' ||
|
|
3572
|
-
workspace.selection === null ||
|
|
3573
|
-
!isCopyShortcutInput(input)
|
|
3574
|
-
) {
|
|
3575
|
-
return false;
|
|
3576
|
-
}
|
|
3577
|
-
const active = conversationManager.getActiveConversation();
|
|
3578
|
-
if (active === null) {
|
|
3579
|
-
return true;
|
|
3580
|
-
}
|
|
3581
|
-
const selectedFrame = active.oracle.snapshotWithoutHash();
|
|
3582
|
-
const copied = writeTextToClipboard(selectionText(selectedFrame, workspace.selection));
|
|
3583
|
-
if (copied) {
|
|
3584
|
-
markDirty();
|
|
3585
|
-
}
|
|
3586
|
-
return true;
|
|
3587
|
-
},
|
|
3588
|
-
},
|
|
3589
|
-
forwarder: {
|
|
3590
|
-
getInputRemainder: () => inputRemainder,
|
|
3591
|
-
setInputRemainder: (next) => {
|
|
3592
|
-
inputRemainder = next;
|
|
3593
|
-
},
|
|
3594
|
-
getMainPaneMode: () => workspace.mainPaneMode,
|
|
3595
|
-
getLayout: () => layout,
|
|
3596
|
-
inputTokenRouter: runtimeInputRouter.inputTokenRouter(),
|
|
3597
|
-
getActiveConversation: () => conversationManager.getActiveConversation(),
|
|
3598
|
-
markDirty,
|
|
3599
|
-
isControlledByLocalHuman: (input) => conversationManager.isControlledByLocalHuman(input),
|
|
3600
|
-
controllerId: muxControllerId,
|
|
3601
|
-
sendInputToSession: (sessionId, chunk) => {
|
|
3602
|
-
streamClient.sendInput(sessionId, chunk);
|
|
3603
|
-
},
|
|
3604
|
-
noteGitActivity,
|
|
3605
|
-
},
|
|
3606
|
-
});
|
|
3607
|
-
|
|
3608
|
-
const onInput = (chunk: Buffer): void => {
|
|
3609
|
-
runtimeInputPipeline.handleInput(chunk);
|
|
3610
|
-
};
|
|
3611
|
-
|
|
3612
|
-
const onResize = (): void => {
|
|
3613
|
-
const nextSize = terminalSize();
|
|
3614
|
-
queueResize(nextSize);
|
|
3615
|
-
};
|
|
3616
|
-
const runtimeProcessWiring = new RuntimeProcessWiring({
|
|
3617
|
-
onInput,
|
|
3618
|
-
onResize,
|
|
3619
|
-
requestStop,
|
|
3620
|
-
handleRuntimeFatal,
|
|
3621
|
-
});
|
|
3622
|
-
|
|
3623
|
-
await startupOrchestrator.hydrateStartupState(startupObservedCursor);
|
|
3624
|
-
|
|
3625
|
-
runtimeProcessWiring.attach();
|
|
3626
|
-
|
|
3627
|
-
inputModeManager.enable();
|
|
3628
|
-
applyLayout(size, true);
|
|
3629
|
-
scheduleRender();
|
|
3630
|
-
const runtimeShutdownService = new RuntimeShutdownService({
|
|
3631
|
-
screen,
|
|
3632
|
-
outputLoadSampler,
|
|
3633
|
-
startupBackgroundProbeService: startupOrchestrator,
|
|
3634
|
-
clearResizeTimer: () => {
|
|
3635
|
-
runtimeLayoutResize.clearResizeTimer();
|
|
3636
|
-
},
|
|
3637
|
-
clearPtyResizeTimer: () => {
|
|
3638
|
-
runtimeLayoutResize.clearPtyResizeTimer();
|
|
3639
|
-
},
|
|
3640
|
-
clearHomePaneBackgroundTimer: () => {
|
|
3641
|
-
if (homePaneBackgroundTimer !== null) {
|
|
3642
|
-
clearInterval(homePaneBackgroundTimer);
|
|
3643
|
-
homePaneBackgroundTimer = null;
|
|
3644
|
-
}
|
|
3645
|
-
},
|
|
3646
|
-
persistMuxUiStateNow,
|
|
3647
|
-
clearConversationTitleEditTimer: () => {
|
|
3648
|
-
conversationLifecycle.clearConversationTitleEditTimer();
|
|
3649
|
-
},
|
|
3650
|
-
flushTaskComposerPersist: () => {
|
|
3651
|
-
if (
|
|
3652
|
-
'taskId' in workspace.taskEditorTarget &&
|
|
3653
|
-
typeof workspace.taskEditorTarget.taskId === 'string'
|
|
3654
|
-
) {
|
|
3655
|
-
flushTaskComposerPersist(workspace.taskEditorTarget.taskId);
|
|
3656
|
-
}
|
|
3657
|
-
for (const taskId of taskManager.autosaveTaskIds()) {
|
|
3658
|
-
flushTaskComposerPersist(taskId);
|
|
3659
|
-
}
|
|
3660
|
-
},
|
|
3661
|
-
clearRenderScheduled: () => {
|
|
3662
|
-
runtimeRenderLifecycle.clearRenderScheduled();
|
|
3663
|
-
},
|
|
3664
|
-
detachProcessListeners: () => {
|
|
3665
|
-
runtimeProcessWiring.detach();
|
|
3666
|
-
},
|
|
3667
|
-
removeEnvelopeListener,
|
|
3668
|
-
unsubscribeTaskPlanningEvents: async () => {
|
|
3669
|
-
await conversationLifecycle.unsubscribeTaskPlanningEvents();
|
|
3670
|
-
},
|
|
3671
|
-
closeKeyEventSubscription: async () => {
|
|
3672
|
-
if (keyEventSubscription !== null) {
|
|
3673
|
-
await keyEventSubscription.close();
|
|
3674
|
-
keyEventSubscription = null;
|
|
3675
|
-
}
|
|
3676
|
-
},
|
|
3677
|
-
clearRuntimeFatalExitTimer: () => {
|
|
3678
|
-
runtimeRenderLifecycle.clearRuntimeFatalExitTimer();
|
|
3679
|
-
},
|
|
3680
|
-
waitForControlPlaneDrain,
|
|
3681
|
-
controlPlaneClient,
|
|
3682
|
-
eventPersistence,
|
|
3683
|
-
recordingService,
|
|
3684
|
-
store,
|
|
3685
|
-
restoreTerminalState: () => {
|
|
3686
|
-
restoreTerminalState(true, inputModeManager.restore);
|
|
3687
|
-
},
|
|
3688
|
-
startupShutdownService: startupOrchestrator,
|
|
3689
|
-
shutdownPerfCore,
|
|
3690
|
-
});
|
|
3691
|
-
|
|
3692
|
-
try {
|
|
3693
|
-
while (!stop) {
|
|
3694
|
-
await new Promise((resolve) => {
|
|
3695
|
-
setTimeout(resolve, 50);
|
|
3696
|
-
});
|
|
3697
|
-
}
|
|
3698
|
-
} finally {
|
|
3699
|
-
shuttingDown = true;
|
|
3700
|
-
statusTimelineRecorder.close();
|
|
3701
|
-
renderTraceRecorder.close();
|
|
3702
|
-
await runtimeShutdownService.finalize();
|
|
3703
|
-
}
|
|
3704
|
-
|
|
3705
|
-
if (exit === null) {
|
|
3706
|
-
if (runtimeRenderLifecycle.hasFatal()) {
|
|
3707
|
-
return 1;
|
|
3708
|
-
}
|
|
3709
|
-
return 0;
|
|
3710
|
-
}
|
|
3711
|
-
return normalizeExitCode(exit);
|
|
3712
|
-
}
|
|
3713
|
-
|
|
3714
|
-
try {
|
|
3715
|
-
const code = await main();
|
|
3716
|
-
process.exitCode = code;
|
|
3717
|
-
} catch (error: unknown) {
|
|
3718
|
-
shutdownPerfCore();
|
|
3719
|
-
restoreTerminalState(true);
|
|
3720
|
-
process.stderr.write(`codex:live:mux fatal error: ${formatErrorMessage(error)}\n`);
|
|
3721
|
-
process.exitCode = 1;
|
|
3722
|
-
}
|
|
3
|
+
await runCodexLiveMuxRuntimeProcess();
|