@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
|
@@ -12,24 +12,25 @@ interface StartupBackgroundResumeOptions {
|
|
|
12
12
|
readonly clearTimeoutFn?: (handle: TimeoutHandle) => void;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
export interface StartupBackgroundResumeService {
|
|
16
|
+
run(initialActiveId: string | null): Promise<void>;
|
|
17
|
+
}
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
export function createStartupBackgroundResumeService(
|
|
20
|
+
options: StartupBackgroundResumeOptions,
|
|
21
|
+
): StartupBackgroundResumeService {
|
|
22
|
+
const setTimeoutFn = options.setTimeoutFn ?? setTimeout;
|
|
23
|
+
const clearTimeoutFn = options.clearTimeoutFn ?? clearTimeout;
|
|
23
24
|
|
|
24
|
-
async run(initialActiveId: string | null): Promise<void> {
|
|
25
|
+
async function run(initialActiveId: string | null): Promise<void> {
|
|
25
26
|
const sessionId = initialActiveId ?? 'none';
|
|
26
|
-
|
|
27
|
+
options.recordPerfEvent('mux.startup.background-start.wait', {
|
|
27
28
|
sessionId,
|
|
28
|
-
maxWaitMs:
|
|
29
|
-
enabled:
|
|
29
|
+
maxWaitMs: options.maxWaitMs,
|
|
30
|
+
enabled: options.enabled ? 1 : 0,
|
|
30
31
|
});
|
|
31
|
-
if (!
|
|
32
|
-
|
|
32
|
+
if (!options.enabled) {
|
|
33
|
+
options.recordPerfEvent('mux.startup.background-start.skipped', {
|
|
33
34
|
sessionId,
|
|
34
35
|
reason: 'disabled',
|
|
35
36
|
});
|
|
@@ -39,27 +40,31 @@ export class StartupBackgroundResumeService {
|
|
|
39
40
|
let timedOut = false;
|
|
40
41
|
let timeoutHandle: TimeoutHandle | null = null;
|
|
41
42
|
await Promise.race([
|
|
42
|
-
|
|
43
|
+
options.waitForSettled(),
|
|
43
44
|
new Promise<void>((resolve) => {
|
|
44
|
-
timeoutHandle =
|
|
45
|
+
timeoutHandle = setTimeoutFn(() => {
|
|
45
46
|
timedOut = true;
|
|
46
47
|
resolve();
|
|
47
|
-
},
|
|
48
|
+
}, options.maxWaitMs);
|
|
48
49
|
}),
|
|
49
50
|
]);
|
|
50
51
|
if (timeoutHandle !== null) {
|
|
51
|
-
|
|
52
|
+
clearTimeoutFn(timeoutHandle);
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
|
|
55
|
+
options.recordPerfEvent('mux.startup.background-start.begin', {
|
|
55
56
|
sessionId,
|
|
56
57
|
timedOut,
|
|
57
|
-
settledObserved:
|
|
58
|
+
settledObserved: options.settledObserved(),
|
|
58
59
|
});
|
|
59
|
-
const queued =
|
|
60
|
-
|
|
60
|
+
const queued = options.queuePersistedConversationsInBackground(initialActiveId);
|
|
61
|
+
options.recordPerfEvent('mux.startup.background-start.queued', {
|
|
61
62
|
sessionId,
|
|
62
63
|
queued,
|
|
63
64
|
});
|
|
64
65
|
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
run,
|
|
69
|
+
};
|
|
65
70
|
}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import type { ConversationState } from '../mux/live-mux/conversation-state.ts';
|
|
2
2
|
import { StartupSequencer } from '../mux/startup-sequencer.ts';
|
|
3
3
|
import { StartupBackgroundProbeService } from './startup-background-probe.ts';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
createStartupBackgroundResumeService,
|
|
6
|
+
type StartupBackgroundResumeService,
|
|
7
|
+
} from './startup-background-resume.ts';
|
|
5
8
|
import { StartupOutputTracker } from './startup-output-tracker.ts';
|
|
6
|
-
import { StartupPaintTracker } from './startup-paint-tracker.ts';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
+
import { createStartupPaintTracker, type StartupPaintTracker } from './startup-paint-tracker.ts';
|
|
10
|
+
import { createStartupSettledGate } from './startup-settled-gate.ts';
|
|
11
|
+
import { finalizeStartupShutdown, type StartupShutdownServiceOptions } from './startup-shutdown.ts';
|
|
9
12
|
import { StartupSpanTracker } from './startup-span-tracker.ts';
|
|
10
|
-
import {
|
|
13
|
+
import { createStartupVisibility } from './startup-visibility.ts';
|
|
11
14
|
|
|
12
15
|
type PerfAttrs = Record<string, boolean | number | string>;
|
|
13
16
|
|
|
@@ -47,7 +50,7 @@ export class StartupOrchestrator {
|
|
|
47
50
|
private readonly startupPaintTracker: StartupPaintTracker;
|
|
48
51
|
private readonly startupBackgroundProbeService: StartupBackgroundProbeService;
|
|
49
52
|
private readonly startupBackgroundResumeService: StartupBackgroundResumeService;
|
|
50
|
-
private readonly
|
|
53
|
+
private readonly startupShutdownOptions: StartupShutdownServiceOptions;
|
|
51
54
|
|
|
52
55
|
constructor(private readonly options: StartupOrchestratorOptions) {
|
|
53
56
|
this.startupSequencer = new StartupSequencer({
|
|
@@ -58,8 +61,8 @@ export class StartupOrchestrator {
|
|
|
58
61
|
options.startPerfSpan,
|
|
59
62
|
options.startupSettleQuietMs,
|
|
60
63
|
);
|
|
61
|
-
const startupVisibility =
|
|
62
|
-
const startupSettledGate =
|
|
64
|
+
const startupVisibility = createStartupVisibility();
|
|
65
|
+
const startupSettledGate = createStartupSettledGate({
|
|
63
66
|
startupSequencer: this.startupSequencer,
|
|
64
67
|
startupSpanTracker: this.startupSpanTracker,
|
|
65
68
|
getConversation: options.getConversation,
|
|
@@ -72,7 +75,7 @@ export class StartupOrchestrator {
|
|
|
72
75
|
startupSpanTracker: this.startupSpanTracker,
|
|
73
76
|
recordPerfEvent: options.recordPerfEvent,
|
|
74
77
|
});
|
|
75
|
-
this.startupPaintTracker =
|
|
78
|
+
this.startupPaintTracker = createStartupPaintTracker({
|
|
76
79
|
startupSequencer: this.startupSequencer,
|
|
77
80
|
startupSpanTracker: this.startupSpanTracker,
|
|
78
81
|
startupVisibility,
|
|
@@ -88,7 +91,7 @@ export class StartupOrchestrator {
|
|
|
88
91
|
refreshProcessUsage: options.refreshProcessUsage,
|
|
89
92
|
recordPerfEvent: options.recordPerfEvent,
|
|
90
93
|
});
|
|
91
|
-
this.startupBackgroundResumeService =
|
|
94
|
+
this.startupBackgroundResumeService = createStartupBackgroundResumeService({
|
|
92
95
|
enabled: options.backgroundResumeEnabled,
|
|
93
96
|
maxWaitMs: options.backgroundWaitMaxMs,
|
|
94
97
|
waitForSettled: () => this.startupSequencer.waitForSettled(),
|
|
@@ -96,11 +99,11 @@ export class StartupOrchestrator {
|
|
|
96
99
|
queuePersistedConversationsInBackground: options.queuePersistedConversationsInBackground,
|
|
97
100
|
recordPerfEvent: options.recordPerfEvent,
|
|
98
101
|
});
|
|
99
|
-
this.
|
|
102
|
+
this.startupShutdownOptions = {
|
|
100
103
|
startupSequencer: this.startupSequencer,
|
|
101
104
|
startupSpanTracker: this.startupSpanTracker,
|
|
102
105
|
startupSettledGate,
|
|
103
|
-
}
|
|
106
|
+
};
|
|
104
107
|
}
|
|
105
108
|
|
|
106
109
|
get firstPaintTargetSessionId(): string | null {
|
|
@@ -161,6 +164,6 @@ export class StartupOrchestrator {
|
|
|
161
164
|
}
|
|
162
165
|
|
|
163
166
|
finalize(): void {
|
|
164
|
-
this.
|
|
167
|
+
finalizeStartupShutdown(this.startupShutdownOptions);
|
|
165
168
|
}
|
|
166
169
|
}
|
|
@@ -26,7 +26,7 @@ interface StartupSettledGateLike {
|
|
|
26
26
|
scheduleProbe(sessionId: string): void;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
interface StartupPaintTrackerOptions {
|
|
29
|
+
export interface StartupPaintTrackerOptions {
|
|
30
30
|
readonly startupSequencer: StartupSequencerLike;
|
|
31
31
|
readonly startupSpanTracker: StartupSpanTrackerLike;
|
|
32
32
|
readonly startupVisibility: StartupVisibilityLike;
|
|
@@ -41,11 +41,16 @@ interface StartupRenderFlushInput {
|
|
|
41
41
|
readonly changedRowCount: number;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
export
|
|
45
|
-
|
|
44
|
+
export interface StartupPaintTracker {
|
|
45
|
+
onRenderFlush(input: StartupRenderFlushInput): void;
|
|
46
|
+
onOutputChunk(sessionId: string): void;
|
|
47
|
+
}
|
|
46
48
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
export function createStartupPaintTracker(
|
|
50
|
+
options: StartupPaintTrackerOptions,
|
|
51
|
+
): StartupPaintTracker {
|
|
52
|
+
function onRenderFlush(input: StartupRenderFlushInput): void {
|
|
53
|
+
const targetSessionId = options.startupSpanTracker.firstPaintTargetSessionId;
|
|
49
54
|
if (targetSessionId === null) {
|
|
50
55
|
return;
|
|
51
56
|
}
|
|
@@ -56,24 +61,22 @@ export class StartupPaintTracker {
|
|
|
56
61
|
) {
|
|
57
62
|
return;
|
|
58
63
|
}
|
|
59
|
-
const startupSnapshot =
|
|
64
|
+
const startupSnapshot = options.startupSequencer.snapshot();
|
|
60
65
|
if (!startupSnapshot.firstOutputObserved) {
|
|
61
66
|
return;
|
|
62
67
|
}
|
|
63
68
|
|
|
64
|
-
const glyphCells =
|
|
65
|
-
input.activeConversation,
|
|
66
|
-
);
|
|
69
|
+
const glyphCells = options.startupVisibility.visibleGlyphCellCount(input.activeConversation);
|
|
67
70
|
if (
|
|
68
71
|
!startupSnapshot.firstPaintObserved &&
|
|
69
|
-
|
|
72
|
+
options.startupSequencer.markFirstPaintVisible(targetSessionId, glyphCells)
|
|
70
73
|
) {
|
|
71
|
-
|
|
74
|
+
options.recordPerfEvent('mux.startup.active-first-visible-paint', {
|
|
72
75
|
sessionId: targetSessionId,
|
|
73
76
|
changedRows: input.changedRowCount,
|
|
74
77
|
glyphCells,
|
|
75
78
|
});
|
|
76
|
-
|
|
79
|
+
options.startupSpanTracker.endFirstPaintSpan({
|
|
77
80
|
observed: true,
|
|
78
81
|
changedRows: input.changedRowCount,
|
|
79
82
|
glyphCells,
|
|
@@ -81,35 +84,40 @@ export class StartupPaintTracker {
|
|
|
81
84
|
}
|
|
82
85
|
|
|
83
86
|
if (
|
|
84
|
-
|
|
87
|
+
options.startupSequencer.markHeaderVisible(
|
|
85
88
|
targetSessionId,
|
|
86
|
-
|
|
89
|
+
options.startupVisibility.codexHeaderVisible(input.activeConversation),
|
|
87
90
|
)
|
|
88
91
|
) {
|
|
89
|
-
|
|
92
|
+
options.recordPerfEvent('mux.startup.active-header-visible', {
|
|
90
93
|
sessionId: targetSessionId,
|
|
91
94
|
glyphCells,
|
|
92
95
|
});
|
|
93
96
|
}
|
|
94
|
-
const selectedGate =
|
|
97
|
+
const selectedGate = options.startupSequencer.maybeSelectSettleGate(
|
|
95
98
|
targetSessionId,
|
|
96
99
|
glyphCells,
|
|
97
100
|
);
|
|
98
101
|
if (selectedGate !== null) {
|
|
99
|
-
|
|
102
|
+
options.recordPerfEvent('mux.startup.active-settle-gate', {
|
|
100
103
|
sessionId: targetSessionId,
|
|
101
104
|
gate: selectedGate,
|
|
102
105
|
glyphCells,
|
|
103
106
|
});
|
|
104
107
|
}
|
|
105
|
-
|
|
108
|
+
options.startupSettledGate.scheduleProbe(targetSessionId);
|
|
106
109
|
}
|
|
107
110
|
|
|
108
|
-
onOutputChunk(sessionId: string): void {
|
|
109
|
-
const targetSessionId =
|
|
111
|
+
function onOutputChunk(sessionId: string): void {
|
|
112
|
+
const targetSessionId = options.startupSpanTracker.firstPaintTargetSessionId;
|
|
110
113
|
if (targetSessionId === null || sessionId !== targetSessionId) {
|
|
111
114
|
return;
|
|
112
115
|
}
|
|
113
|
-
|
|
116
|
+
options.startupSettledGate.scheduleProbe(sessionId);
|
|
114
117
|
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
onRenderFlush,
|
|
121
|
+
onOutputChunk,
|
|
122
|
+
};
|
|
115
123
|
}
|
|
@@ -12,34 +12,40 @@ export interface StartupPersistedConversationQueueServiceOptions<
|
|
|
12
12
|
readonly markDirty: () => void;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
constructor(
|
|
19
|
-
private readonly options: StartupPersistedConversationQueueServiceOptions<TConversation>,
|
|
20
|
-
) {}
|
|
15
|
+
export interface StartupPersistedConversationQueueService {
|
|
16
|
+
queuePersistedConversationsInBackground(activeSessionId: string | null): number;
|
|
17
|
+
}
|
|
21
18
|
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
export function createStartupPersistedConversationQueueService<
|
|
20
|
+
TConversation extends StartupQueueConversationRecord,
|
|
21
|
+
>(
|
|
22
|
+
options: StartupPersistedConversationQueueServiceOptions<TConversation>,
|
|
23
|
+
): StartupPersistedConversationQueueService {
|
|
24
|
+
function queuePersistedConversationsInBackground(activeSessionId: string | null): number {
|
|
25
|
+
const ordered = options.orderedConversationIds();
|
|
24
26
|
let queued = 0;
|
|
25
27
|
for (const sessionId of ordered) {
|
|
26
28
|
if (activeSessionId !== null && sessionId === activeSessionId) {
|
|
27
29
|
continue;
|
|
28
30
|
}
|
|
29
|
-
const conversation =
|
|
31
|
+
const conversation = options.conversationById(sessionId);
|
|
30
32
|
if (conversation === undefined || conversation.live) {
|
|
31
33
|
continue;
|
|
32
34
|
}
|
|
33
|
-
|
|
34
|
-
const latest =
|
|
35
|
+
options.queueBackgroundOp(async () => {
|
|
36
|
+
const latest = options.conversationById(sessionId);
|
|
35
37
|
if (latest === undefined || latest.live) {
|
|
36
38
|
return;
|
|
37
39
|
}
|
|
38
|
-
await
|
|
39
|
-
|
|
40
|
+
await options.startConversation(sessionId);
|
|
41
|
+
options.markDirty();
|
|
40
42
|
}, `background-start:${sessionId}`);
|
|
41
43
|
queued += 1;
|
|
42
44
|
}
|
|
43
45
|
return queued;
|
|
44
46
|
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
queuePersistedConversationsInBackground,
|
|
50
|
+
};
|
|
45
51
|
}
|
|
@@ -22,7 +22,7 @@ interface StartupSpanTrackerLike {
|
|
|
22
22
|
endSettledSpan(attrs: PerfAttrs): void;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
interface StartupSettledGateOptions {
|
|
25
|
+
export interface StartupSettledGateOptions {
|
|
26
26
|
readonly startupSequencer: StartupSequencerLike;
|
|
27
27
|
readonly startupSpanTracker: StartupSpanTrackerLike;
|
|
28
28
|
readonly getConversation: (sessionId: string) => ConversationState | undefined;
|
|
@@ -30,38 +30,48 @@ interface StartupSettledGateOptions {
|
|
|
30
30
|
readonly recordPerfEvent: (name: string, attrs: PerfAttrs) => void;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
export
|
|
34
|
-
|
|
33
|
+
export interface StartupSettledGate {
|
|
34
|
+
clearTimer(): void;
|
|
35
|
+
signalSettled(): void;
|
|
36
|
+
scheduleProbe(sessionId: string): void;
|
|
37
|
+
}
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
export function createStartupSettledGate(options: StartupSettledGateOptions): StartupSettledGate {
|
|
40
|
+
function clearTimer(): void {
|
|
41
|
+
options.startupSequencer.clearSettledTimer();
|
|
38
42
|
}
|
|
39
43
|
|
|
40
|
-
signalSettled(): void {
|
|
41
|
-
|
|
44
|
+
function signalSettled(): void {
|
|
45
|
+
options.startupSequencer.signalSettled();
|
|
42
46
|
}
|
|
43
47
|
|
|
44
|
-
scheduleProbe(sessionId: string): void {
|
|
45
|
-
|
|
46
|
-
if (
|
|
48
|
+
function scheduleProbe(sessionId: string): void {
|
|
49
|
+
options.startupSequencer.scheduleSettledProbe(sessionId, (event) => {
|
|
50
|
+
if (options.startupSpanTracker.firstPaintTargetSessionId !== event.sessionId) {
|
|
47
51
|
return;
|
|
48
52
|
}
|
|
49
|
-
const conversation =
|
|
53
|
+
const conversation = options.getConversation(event.sessionId);
|
|
50
54
|
const glyphCells =
|
|
51
|
-
conversation === undefined ? 0 :
|
|
52
|
-
|
|
55
|
+
conversation === undefined ? 0 : options.visibleGlyphCellCount(conversation);
|
|
56
|
+
options.recordPerfEvent('mux.startup.active-settled', {
|
|
53
57
|
sessionId: event.sessionId,
|
|
54
58
|
gate: event.gate,
|
|
55
59
|
quietMs: event.quietMs,
|
|
56
60
|
glyphCells,
|
|
57
61
|
});
|
|
58
|
-
|
|
62
|
+
options.startupSpanTracker.endSettledSpan({
|
|
59
63
|
observed: true,
|
|
60
64
|
gate: event.gate,
|
|
61
65
|
quietMs: event.quietMs,
|
|
62
66
|
glyphCells,
|
|
63
67
|
});
|
|
64
|
-
|
|
68
|
+
options.startupSequencer.signalSettled();
|
|
65
69
|
});
|
|
66
70
|
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
clearTimer,
|
|
74
|
+
signalSettled,
|
|
75
|
+
scheduleProbe,
|
|
76
|
+
};
|
|
67
77
|
}
|
|
@@ -23,31 +23,27 @@ interface StartupSettledGateLike {
|
|
|
23
23
|
signalSettled(): void;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
interface StartupShutdownServiceOptions {
|
|
26
|
+
export interface StartupShutdownServiceOptions {
|
|
27
27
|
readonly startupSequencer: StartupSequencerLike;
|
|
28
28
|
readonly startupSpanTracker: StartupSpanTrackerLike;
|
|
29
29
|
readonly startupSettledGate: StartupSettledGateLike;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
export
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
gate: startupSnapshot.settleGate ?? 'none',
|
|
50
|
-
});
|
|
51
|
-
this.options.startupSettledGate.signalSettled();
|
|
52
|
-
}
|
|
32
|
+
export function finalizeStartupShutdown(options: StartupShutdownServiceOptions): void {
|
|
33
|
+
options.startupSpanTracker.endStartCommandSpan({
|
|
34
|
+
observed: false,
|
|
35
|
+
});
|
|
36
|
+
const startupSnapshot = options.startupSequencer.snapshot();
|
|
37
|
+
options.startupSpanTracker.endFirstOutputSpan({
|
|
38
|
+
observed: startupSnapshot.firstOutputObserved,
|
|
39
|
+
});
|
|
40
|
+
options.startupSpanTracker.endFirstPaintSpan({
|
|
41
|
+
observed: startupSnapshot.firstPaintObserved,
|
|
42
|
+
});
|
|
43
|
+
options.startupSettledGate.clearTimer();
|
|
44
|
+
options.startupSpanTracker.endSettledSpan({
|
|
45
|
+
observed: startupSnapshot.settledObserved,
|
|
46
|
+
gate: startupSnapshot.settleGate ?? 'none',
|
|
47
|
+
});
|
|
48
|
+
options.startupSettledGate.signalSettled();
|
|
53
49
|
}
|
|
@@ -10,7 +10,7 @@ interface DirectoryGitStatusLike<TRepository, TSummary, TSnapshot> {
|
|
|
10
10
|
readonly repository: TRepository | null;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
interface StartupStateHydrationServiceOptions<
|
|
13
|
+
export interface StartupStateHydrationServiceOptions<
|
|
14
14
|
TRepository extends RepositoryRecordLike,
|
|
15
15
|
TSummary,
|
|
16
16
|
TSnapshot,
|
|
@@ -34,56 +34,66 @@ interface StartupStateHydrationServiceOptions<
|
|
|
34
34
|
readonly ensureActiveConversationId: () => void;
|
|
35
35
|
readonly activeConversationId: () => string | null;
|
|
36
36
|
readonly selectLeftNavConversation: (sessionId: string) => void;
|
|
37
|
-
readonly
|
|
37
|
+
readonly enterStartupPane: () => void;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
export
|
|
40
|
+
export interface StartupStateHydrationService {
|
|
41
|
+
hydrateRepositoryList(): Promise<void>;
|
|
42
|
+
hydrateDirectoryGitStatus(): Promise<void>;
|
|
43
|
+
hydrateStartupState(afterCursor: number | null): Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function createStartupStateHydrationService<
|
|
41
47
|
TRepository extends RepositoryRecordLike,
|
|
42
48
|
TSummary,
|
|
43
49
|
TSnapshot,
|
|
44
50
|
TDirectoryGitStatus extends DirectoryGitStatusLike<TRepository, TSummary, TSnapshot>,
|
|
45
|
-
>
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const rows = await this.options.listRepositories();
|
|
57
|
-
this.options.clearRepositories();
|
|
51
|
+
>(
|
|
52
|
+
options: StartupStateHydrationServiceOptions<
|
|
53
|
+
TRepository,
|
|
54
|
+
TSummary,
|
|
55
|
+
TSnapshot,
|
|
56
|
+
TDirectoryGitStatus
|
|
57
|
+
>,
|
|
58
|
+
): StartupStateHydrationService {
|
|
59
|
+
async function hydrateRepositoryList(): Promise<void> {
|
|
60
|
+
const rows = await options.listRepositories();
|
|
61
|
+
options.clearRepositories();
|
|
58
62
|
for (const record of rows) {
|
|
59
|
-
|
|
63
|
+
options.setRepository(record.repositoryId, record);
|
|
60
64
|
}
|
|
61
|
-
|
|
65
|
+
options.syncRepositoryAssociationsWithDirectorySnapshots();
|
|
62
66
|
}
|
|
63
67
|
|
|
64
|
-
async hydrateDirectoryGitStatus(): Promise<void> {
|
|
65
|
-
if (!
|
|
68
|
+
async function hydrateDirectoryGitStatus(): Promise<void> {
|
|
69
|
+
if (!options.gitHydrationEnabled) {
|
|
66
70
|
return;
|
|
67
71
|
}
|
|
68
|
-
const rows = await
|
|
72
|
+
const rows = await options.listDirectoryGitStatuses();
|
|
69
73
|
for (const record of rows) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
74
|
+
options.setDirectoryGitSummary(record.directoryId, record.summary);
|
|
75
|
+
options.setDirectoryRepositorySnapshot(record.directoryId, record.repositorySnapshot);
|
|
76
|
+
options.setDirectoryRepositoryAssociation(record.directoryId, record.repositoryId);
|
|
73
77
|
if (record.repository !== null) {
|
|
74
|
-
|
|
78
|
+
options.setRepository(record.repository.repositoryId, record.repository);
|
|
75
79
|
}
|
|
76
80
|
}
|
|
77
|
-
|
|
81
|
+
options.syncRepositoryAssociationsWithDirectorySnapshots();
|
|
78
82
|
}
|
|
79
83
|
|
|
80
|
-
async hydrateStartupState(afterCursor: number | null): Promise<void> {
|
|
81
|
-
await
|
|
82
|
-
await
|
|
83
|
-
await
|
|
84
|
-
await
|
|
85
|
-
await
|
|
86
|
-
|
|
87
|
-
|
|
84
|
+
async function hydrateStartupState(afterCursor: number | null): Promise<void> {
|
|
85
|
+
await options.hydrateConversationList();
|
|
86
|
+
await hydrateRepositoryList();
|
|
87
|
+
await options.hydrateTaskPlanningState();
|
|
88
|
+
await hydrateDirectoryGitStatus();
|
|
89
|
+
await options.subscribeTaskPlanningEvents(afterCursor);
|
|
90
|
+
options.ensureActiveConversationId();
|
|
91
|
+
options.enterStartupPane();
|
|
88
92
|
}
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
hydrateRepositoryList,
|
|
96
|
+
hydrateDirectoryGitStatus,
|
|
97
|
+
hydrateStartupState,
|
|
98
|
+
};
|
|
89
99
|
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type { ConversationState } from '../mux/live-mux/conversation-state.ts';
|
|
2
2
|
|
|
3
|
-
export
|
|
4
|
-
|
|
3
|
+
export interface StartupVisibility {
|
|
4
|
+
visibleGlyphCellCount(conversation: ConversationState): number;
|
|
5
|
+
codexHeaderVisible(conversation: ConversationState): boolean;
|
|
6
|
+
}
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
export function createStartupVisibility(): StartupVisibility {
|
|
9
|
+
function visibleGlyphCellCount(conversation: ConversationState): number {
|
|
7
10
|
const frame = conversation.oracle.snapshotWithoutHash();
|
|
8
11
|
let count = 0;
|
|
9
12
|
for (const line of frame.richLines) {
|
|
@@ -16,7 +19,7 @@ export class StartupVisibility {
|
|
|
16
19
|
return count;
|
|
17
20
|
}
|
|
18
21
|
|
|
19
|
-
codexHeaderVisible(conversation: ConversationState): boolean {
|
|
22
|
+
function codexHeaderVisible(conversation: ConversationState): boolean {
|
|
20
23
|
const frame = conversation.oracle.snapshotWithoutHash();
|
|
21
24
|
const rows: string[] = [];
|
|
22
25
|
for (const line of frame.richLines) {
|
|
@@ -32,4 +35,9 @@ export class StartupVisibility {
|
|
|
32
35
|
const text = rows.join('\n');
|
|
33
36
|
return text.includes('OpenAI Codex') && text.includes('model:') && text.includes('directory:');
|
|
34
37
|
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
visibleGlyphCellCount,
|
|
41
|
+
codexHeaderVisible,
|
|
42
|
+
};
|
|
35
43
|
}
|