@jmoyers/harness 0.1.11 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -39
- package/package.json +31 -11
- package/packages/harness-ai/src/anthropic-protocol.ts +68 -68
- package/packages/harness-ai/src/stream-text.ts +13 -91
- package/packages/harness-ui/src/frame-primitives.ts +158 -0
- package/packages/harness-ui/src/index.ts +18 -0
- package/packages/harness-ui/src/interaction/conversation-input-forwarder.ts +221 -0
- package/packages/harness-ui/src/interaction/conversation-selection-input.ts +213 -0
- package/packages/harness-ui/src/interaction/global-shortcut-input.ts +172 -0
- package/{src/ui → packages/harness-ui/src/interaction}/input-preflight.ts +10 -12
- package/{src/ui → packages/harness-ui/src/interaction}/input-token-router.ts +120 -69
- package/packages/harness-ui/src/interaction/input.ts +420 -0
- package/packages/harness-ui/src/interaction/left-nav-input.ts +166 -0
- package/{src/ui → packages/harness-ui/src/interaction}/main-pane-pointer-input.ts +91 -23
- package/{src/ui → packages/harness-ui/src/interaction}/pointer-routing-input.ts +112 -48
- package/packages/harness-ui/src/interaction/rail-pointer-input.ts +62 -0
- package/packages/harness-ui/src/interaction/repository-fold-input.ts +118 -0
- package/packages/harness-ui/src/kit.ts +476 -0
- package/packages/harness-ui/src/layout.ts +238 -0
- package/packages/harness-ui/src/modal-manager.ts +222 -0
- package/{src/ui → packages/harness-ui/src}/screen.ts +53 -26
- package/packages/harness-ui/src/surface.ts +252 -0
- package/packages/harness-ui/src/text-layout.ts +210 -0
- package/packages/nim-core/src/contracts.ts +239 -0
- package/packages/nim-core/src/event-store.ts +299 -0
- package/packages/nim-core/src/events.ts +53 -0
- package/packages/nim-core/src/index.ts +9 -0
- package/packages/nim-core/src/provider-router.ts +129 -0
- package/packages/nim-core/src/providers/anthropic-driver.ts +291 -0
- package/packages/nim-core/src/runtime-factory.ts +49 -0
- package/packages/nim-core/src/runtime.ts +1797 -0
- package/packages/nim-core/src/session-store.ts +516 -0
- package/packages/nim-core/src/telemetry.ts +48 -0
- package/packages/nim-test-tui/src/index.ts +150 -0
- package/packages/nim-ui-core/src/index.ts +1 -0
- package/packages/nim-ui-core/src/projection.ts +87 -0
- package/scripts/codex-live-mux-runtime.ts +2 -3872
- package/scripts/control-plane-daemon.ts +11 -0
- package/scripts/harness-bin.js +5 -0
- package/scripts/harness-commands.ts +300 -0
- package/scripts/harness-runtime.ts +82 -0
- package/scripts/harness.ts +33 -3019
- package/scripts/nim-tui-smoke.ts +748 -0
- package/src/cli/auth/runtime.ts +948 -0
- package/src/cli/gateway/runtime.ts +1872 -0
- package/src/cli/parsing/flags.ts +23 -0
- package/src/cli/parsing/session.ts +42 -0
- package/src/cli/runtime/context.ts +193 -0
- package/src/cli/runtime-app/application.ts +392 -0
- package/src/cli/runtime-infra/gateway-control.ts +729 -0
- package/{scripts/harness-inspector.ts → src/cli/workflows/inspector.ts} +14 -11
- package/src/cli/workflows/runtime.ts +965 -0
- package/src/clients/tui/left-rail-interactions.ts +519 -0
- package/src/clients/tui/main-pane-interactions.ts +509 -0
- package/src/clients/tui/modal-input-routing.ts +71 -0
- package/src/clients/tui/render-snapshot-adapter.ts +88 -0
- package/src/clients/web/synced-selectors.ts +132 -0
- package/src/codex/live-session.ts +82 -29
- package/src/config/config-core.ts +348 -8
- package/src/config/harness.config.template.jsonc +33 -0
- package/src/control-plane/agent-realtime-api.ts +82 -427
- package/src/control-plane/session-summary.ts +10 -81
- package/src/control-plane/status/reducer-base.ts +12 -12
- package/src/control-plane/status/reducers/claude-status-reducer.ts +3 -3
- package/src/control-plane/status/reducers/codex-status-reducer.ts +4 -4
- package/src/control-plane/status/reducers/cursor-status-reducer.ts +3 -3
- package/src/control-plane/stream-client.ts +12 -2
- package/src/control-plane/stream-command-parser.ts +83 -143
- package/src/control-plane/stream-protocol.ts +53 -37
- package/src/control-plane/stream-server-command.ts +376 -69
- package/src/control-plane/stream-server-session-runtime.ts +3 -2
- package/src/control-plane/stream-server.ts +864 -70
- package/src/control-plane/stream-session-runtime-types.ts +41 -0
- package/src/{mux/live-mux/control-plane-records.ts → core/contracts/records.ts} +24 -97
- package/src/core/state/observed-stream-cursor.ts +43 -0
- package/src/core/state/synced-observed-state.ts +273 -0
- package/src/core/store/harness-synced-store.ts +81 -0
- package/src/diff/budget.ts +136 -0
- package/src/diff/build.ts +289 -0
- package/src/diff/chunker.ts +146 -0
- package/src/diff/git-invoke.ts +315 -0
- package/src/diff/git-parse.ts +472 -0
- package/src/diff/hash.ts +70 -0
- package/src/diff/index.ts +24 -0
- package/src/diff/normalize.ts +134 -0
- package/src/diff/types.ts +178 -0
- package/src/diff-ui/args.ts +346 -0
- package/src/diff-ui/commands.ts +123 -0
- package/src/diff-ui/finder.ts +94 -0
- package/src/diff-ui/highlight.ts +127 -0
- package/src/diff-ui/index.ts +2 -0
- package/src/diff-ui/model.ts +141 -0
- package/src/diff-ui/pager.ts +412 -0
- package/src/diff-ui/render.ts +337 -0
- package/src/diff-ui/runtime.ts +379 -0
- package/src/diff-ui/state.ts +224 -0
- package/src/diff-ui/types.ts +236 -0
- package/src/domain/workspace.ts +68 -5
- package/src/mux/control-plane-op-queue.ts +93 -7
- package/src/mux/conversation-rail.ts +28 -71
- package/src/mux/dual-pane-core.ts +13 -13
- package/src/mux/harness-core-ui.ts +313 -42
- package/src/mux/input-shortcuts.ts +13 -131
- package/src/mux/keybinding-catalog.ts +340 -0
- package/src/mux/keybinding-registry.ts +103 -0
- package/src/mux/live-mux/command-menu-open-in.ts +280 -0
- package/src/mux/live-mux/command-menu.ts +167 -4
- package/src/mux/live-mux/conversation-state.ts +13 -0
- package/src/mux/live-mux/directory-resolution.ts +1 -1
- package/src/mux/live-mux/git-snapshot.ts +33 -2
- package/src/mux/live-mux/global-shortcut-handlers.ts +6 -0
- package/src/mux/live-mux/home-pane-drop.ts +1 -1
- package/src/mux/live-mux/home-pane-pointer.ts +10 -0
- package/src/mux/live-mux/input-forwarding.ts +59 -2
- package/src/mux/live-mux/left-nav-activation.ts +124 -7
- package/src/mux/live-mux/left-nav.ts +35 -0
- package/src/mux/live-mux/link-click.ts +292 -0
- package/src/mux/live-mux/modal-command-menu-handler.ts +46 -9
- package/src/mux/live-mux/modal-conversation-handlers.ts +5 -1
- package/src/mux/live-mux/modal-input-reducers.ts +77 -12
- package/src/mux/live-mux/modal-overlays.ts +168 -34
- package/src/mux/live-mux/modal-pointer.ts +3 -7
- package/src/mux/live-mux/modal-prompt-handlers.ts +23 -2
- package/src/mux/live-mux/modal-release-notes-handler.ts +111 -0
- package/src/mux/live-mux/modal-task-editor-handler.ts +16 -11
- package/src/mux/live-mux/pointer-routing.ts +5 -2
- package/src/mux/live-mux/project-pane-pointer.ts +8 -0
- package/src/mux/live-mux/rail-layout.ts +33 -30
- package/src/mux/live-mux/release-notes.ts +383 -0
- package/src/mux/live-mux/render-trace-analysis.ts +52 -7
- package/src/mux/live-mux/repository-folding.ts +3 -0
- package/src/mux/live-mux/selection.ts +0 -4
- package/src/mux/live-mux/session-diagnostics-paths.ts +21 -0
- package/src/mux/project-pane-github-review.ts +271 -0
- package/src/mux/render-frame.ts +4 -0
- package/src/mux/runtime-app/codex-live-mux-runtime.ts +5191 -0
- package/src/mux/task-composer.ts +21 -14
- package/src/mux/task-focused-pane.ts +118 -117
- package/src/mux/task-screen-keybindings.ts +10 -101
- package/src/mux/workspace-rail-model.ts +270 -104
- package/src/mux/workspace-rail.ts +45 -22
- package/src/pty/session-broker.ts +1 -1
- package/{scripts → src/recording}/terminal-recording-gif-lib.ts +2 -2
- package/src/services/control-plane.ts +50 -32
- package/src/services/conversation-lifecycle.ts +118 -87
- package/src/services/conversation-startup-hydration.ts +20 -12
- package/src/services/directory-hydration.ts +21 -16
- package/src/services/event-persistence.ts +7 -0
- package/src/services/left-rail-pointer-handler.ts +329 -0
- package/src/services/mux-ui-state-persistence.ts +5 -1
- package/src/services/recording.ts +34 -26
- package/src/services/runtime-command-menu-agent-tools.ts +1 -1
- package/src/services/runtime-control-actions.ts +79 -61
- package/src/services/runtime-control-plane-ops.ts +122 -83
- package/src/services/runtime-conversation-actions.ts +40 -26
- package/src/services/runtime-conversation-activation.ts +73 -46
- package/src/services/runtime-conversation-starter.ts +53 -45
- package/src/services/runtime-conversation-title-edit.ts +91 -80
- package/src/services/runtime-envelope-handler.ts +107 -105
- package/src/services/runtime-git-state.ts +42 -29
- package/src/services/runtime-layout-resize.ts +3 -1
- package/src/services/runtime-left-rail-render.ts +99 -63
- package/src/services/runtime-nim-cli-session.ts +438 -0
- package/src/services/runtime-nim-session.ts +705 -0
- package/src/services/runtime-nim-tool-bridge.ts +141 -0
- package/src/services/runtime-observed-event-projection-pipeline.ts +45 -0
- package/src/services/runtime-process-wiring.ts +29 -36
- package/src/services/runtime-project-pane-github-review-cache.ts +164 -0
- package/src/services/runtime-render-flush.ts +63 -70
- package/src/services/runtime-render-lifecycle.ts +65 -64
- package/src/services/runtime-render-orchestrator.ts +55 -45
- package/src/services/runtime-render-pipeline.ts +106 -103
- package/src/services/runtime-render-state.ts +62 -49
- package/src/services/runtime-repository-actions.ts +97 -72
- package/src/services/runtime-right-pane-render.ts +80 -53
- package/src/services/runtime-shutdown.ts +38 -35
- package/src/services/runtime-stream-subscriptions.ts +35 -27
- package/src/services/runtime-task-composer-persistence.ts +71 -59
- package/src/services/runtime-task-composer-snapshot.ts +14 -0
- package/src/services/runtime-task-editor-actions.ts +46 -29
- package/src/services/runtime-task-pane-actions.ts +220 -134
- package/src/services/runtime-task-pane-shortcuts.ts +323 -123
- package/src/services/runtime-workspace-observed-effect-queue.ts +25 -0
- package/src/services/runtime-workspace-observed-events.ts +33 -184
- package/src/services/runtime-workspace-observed-transition-policy.ts +228 -0
- package/src/services/session-diagnostics-store.ts +217 -0
- package/src/services/startup-background-resume.ts +26 -21
- package/src/services/startup-orchestrator.ts +16 -13
- package/src/services/startup-paint-tracker.ts +29 -21
- package/src/services/startup-persisted-conversation-queue.ts +19 -13
- package/src/services/startup-settled-gate.ts +25 -15
- package/src/services/startup-shutdown.ts +18 -22
- package/src/services/startup-state-hydration.ts +44 -34
- package/src/services/startup-visibility.ts +12 -4
- package/src/services/task-pane-selection-actions.ts +89 -72
- package/src/services/task-planning-hydration.ts +24 -18
- package/src/services/task-planning-observed-events.ts +50 -52
- package/src/services/workspace-observed-events.ts +66 -63
- package/src/storage/storage-lifecycle-core.ts +438 -0
- package/src/store/control-plane-store-normalize.ts +33 -242
- package/src/store/control-plane-store-types.ts +1 -35
- package/src/store/control-plane-store.ts +360 -56
- package/src/store/event-store.ts +366 -8
- package/src/terminal/snapshot-oracle.ts +207 -94
- package/src/ui/mux-theme.ts +112 -8
- package/src/ui/panes/home-gridfire.ts +40 -31
- package/src/ui/panes/home.ts +10 -2
- package/src/ui/panes/nim.ts +315 -0
- package/src/mux/live-mux/actions-task.ts +0 -115
- package/src/mux/live-mux/left-rail-actions.ts +0 -118
- package/src/mux/live-mux/left-rail-conversation-click.ts +0 -85
- package/src/mux/live-mux/left-rail-pointer.ts +0 -74
- package/src/mux/live-mux/task-pane-shortcuts.ts +0 -206
- package/src/services/runtime-directory-actions.ts +0 -164
- package/src/services/runtime-input-pipeline.ts +0 -50
- package/src/services/runtime-input-router.ts +0 -195
- package/src/services/runtime-main-pane-input.ts +0 -230
- package/src/services/runtime-modal-input.ts +0 -137
- package/src/services/runtime-navigation-input.ts +0 -197
- package/src/services/runtime-rail-input.ts +0 -279
- package/src/services/runtime-task-pane.ts +0 -62
- package/src/services/runtime-workspace-actions.ts +0 -158
- package/src/ui/conversation-input-forwarder.ts +0 -114
- package/src/ui/conversation-selection-input.ts +0 -103
- package/src/ui/global-shortcut-input.ts +0 -89
- package/src/ui/input.ts +0 -269
- package/src/ui/kit.ts +0 -509
- package/src/ui/left-nav-input.ts +0 -80
- package/src/ui/left-rail-pointer-input.ts +0 -148
- package/src/ui/modals/manager.ts +0 -218
- package/src/ui/repository-fold-input.ts +0 -91
- package/src/ui/surface.ts +0 -224
|
@@ -10,7 +10,7 @@ interface RuntimeControlPlaneOpEvent {
|
|
|
10
10
|
readonly waitMs: number;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
interface RuntimeControlPlaneOpsOptions {
|
|
13
|
+
export interface RuntimeControlPlaneOpsOptions {
|
|
14
14
|
readonly onFatal: (error: unknown) => void;
|
|
15
15
|
readonly startPerfSpan: (
|
|
16
16
|
name: string,
|
|
@@ -23,94 +23,43 @@ interface RuntimeControlPlaneOpsOptions {
|
|
|
23
23
|
readonly schedule?: (callback: () => void) => void;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
export
|
|
27
|
-
|
|
26
|
+
export interface RuntimeControlPlaneOps {
|
|
27
|
+
enqueueInteractive(task: () => Promise<void>, label?: string): void;
|
|
28
|
+
enqueueInteractiveLatest(
|
|
29
|
+
key: string,
|
|
30
|
+
task: (options: { readonly signal: AbortSignal }) => Promise<void>,
|
|
31
|
+
label?: string,
|
|
32
|
+
): void;
|
|
33
|
+
enqueueBackgroundLatest(
|
|
34
|
+
key: string,
|
|
35
|
+
task: (options: { readonly signal: AbortSignal }) => Promise<void>,
|
|
36
|
+
label?: string,
|
|
37
|
+
): void;
|
|
38
|
+
enqueueBackground(task: () => Promise<void>, label?: string): void;
|
|
39
|
+
waitForDrain(): Promise<void>;
|
|
40
|
+
metrics(): {
|
|
41
|
+
readonly interactiveQueued: number;
|
|
42
|
+
readonly backgroundQueued: number;
|
|
43
|
+
readonly running: boolean;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function createRuntimeControlPlaneOps(
|
|
48
|
+
options: RuntimeControlPlaneOpsOptions,
|
|
49
|
+
): RuntimeControlPlaneOps {
|
|
50
|
+
const opSpans = new Map<
|
|
28
51
|
number,
|
|
29
52
|
{
|
|
30
53
|
end: (attrs?: PerfAttrs) => void;
|
|
31
54
|
}
|
|
32
55
|
>();
|
|
33
56
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
constructor(private readonly options: RuntimeControlPlaneOpsOptions) {
|
|
37
|
-
this.queue = new ControlPlaneOpQueue({
|
|
38
|
-
...(options.nowMs === undefined
|
|
39
|
-
? {}
|
|
40
|
-
: {
|
|
41
|
-
nowMs: options.nowMs,
|
|
42
|
-
}),
|
|
43
|
-
...(options.schedule === undefined
|
|
44
|
-
? {}
|
|
45
|
-
: {
|
|
46
|
-
schedule: options.schedule,
|
|
47
|
-
}),
|
|
48
|
-
onFatal: (error: unknown) => {
|
|
49
|
-
this.options.onFatal(error);
|
|
50
|
-
},
|
|
51
|
-
onEnqueued: (event, metrics) => {
|
|
52
|
-
this.options.recordPerfEvent('mux.control-plane.op.enqueued', {
|
|
53
|
-
id: event.id,
|
|
54
|
-
label: event.label,
|
|
55
|
-
priority: event.priority,
|
|
56
|
-
interactiveQueued: metrics.interactiveQueued,
|
|
57
|
-
backgroundQueued: metrics.backgroundQueued,
|
|
58
|
-
});
|
|
59
|
-
},
|
|
60
|
-
onStart: (event, metrics) => {
|
|
61
|
-
const opSpan = this.options.startPerfSpan('mux.control-plane.op', {
|
|
62
|
-
id: event.id,
|
|
63
|
-
label: event.label,
|
|
64
|
-
priority: event.priority,
|
|
65
|
-
waitMs: event.waitMs,
|
|
66
|
-
});
|
|
67
|
-
this.opSpans.set(event.id, opSpan);
|
|
68
|
-
this.options.recordPerfEvent('mux.control-plane.op.start', {
|
|
69
|
-
id: event.id,
|
|
70
|
-
label: event.label,
|
|
71
|
-
priority: event.priority,
|
|
72
|
-
waitMs: event.waitMs,
|
|
73
|
-
interactiveQueued: metrics.interactiveQueued,
|
|
74
|
-
backgroundQueued: metrics.backgroundQueued,
|
|
75
|
-
});
|
|
76
|
-
},
|
|
77
|
-
onSuccess: (event) => {
|
|
78
|
-
this.endSpan(event, 'ok');
|
|
79
|
-
},
|
|
80
|
-
onError: (event, _metrics, error) => {
|
|
81
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
82
|
-
this.endSpan(event, 'error', message);
|
|
83
|
-
this.options.writeStderr(`[mux] control-plane error ${message}\n`);
|
|
84
|
-
},
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
enqueueInteractive(task: () => Promise<void>, label = 'interactive-op'): void {
|
|
89
|
-
this.queue.enqueueInteractive(task, label);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
enqueueBackground(task: () => Promise<void>, label = 'background-op'): void {
|
|
93
|
-
this.queue.enqueueBackground(task, label);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async waitForDrain(): Promise<void> {
|
|
97
|
-
await this.queue.waitForDrain();
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
metrics(): {
|
|
101
|
-
readonly interactiveQueued: number;
|
|
102
|
-
readonly backgroundQueued: number;
|
|
103
|
-
readonly running: boolean;
|
|
104
|
-
} {
|
|
105
|
-
return this.queue.metrics();
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
private endSpan(
|
|
57
|
+
const endSpan = (
|
|
109
58
|
event: RuntimeControlPlaneOpEvent,
|
|
110
|
-
status: 'ok' | 'error',
|
|
59
|
+
status: 'ok' | 'error' | 'canceled',
|
|
111
60
|
message?: string,
|
|
112
|
-
): void {
|
|
113
|
-
const opSpan =
|
|
61
|
+
): void => {
|
|
62
|
+
const opSpan = opSpans.get(event.id);
|
|
114
63
|
if (opSpan === undefined) {
|
|
115
64
|
return;
|
|
116
65
|
}
|
|
@@ -126,6 +75,96 @@ export class RuntimeControlPlaneOps {
|
|
|
126
75
|
message,
|
|
127
76
|
}),
|
|
128
77
|
});
|
|
129
|
-
|
|
130
|
-
}
|
|
78
|
+
opSpans.delete(event.id);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const queue = new ControlPlaneOpQueue({
|
|
82
|
+
...(options.nowMs === undefined
|
|
83
|
+
? {}
|
|
84
|
+
: {
|
|
85
|
+
nowMs: options.nowMs,
|
|
86
|
+
}),
|
|
87
|
+
...(options.schedule === undefined
|
|
88
|
+
? {}
|
|
89
|
+
: {
|
|
90
|
+
schedule: options.schedule,
|
|
91
|
+
}),
|
|
92
|
+
onFatal: (error: unknown) => {
|
|
93
|
+
options.onFatal(error);
|
|
94
|
+
},
|
|
95
|
+
onEnqueued: (event, metrics) => {
|
|
96
|
+
options.recordPerfEvent('mux.control-plane.op.enqueued', {
|
|
97
|
+
id: event.id,
|
|
98
|
+
label: event.label,
|
|
99
|
+
priority: event.priority,
|
|
100
|
+
interactiveQueued: metrics.interactiveQueued,
|
|
101
|
+
backgroundQueued: metrics.backgroundQueued,
|
|
102
|
+
});
|
|
103
|
+
},
|
|
104
|
+
onStart: (event, metrics) => {
|
|
105
|
+
const opSpan = options.startPerfSpan('mux.control-plane.op', {
|
|
106
|
+
id: event.id,
|
|
107
|
+
label: event.label,
|
|
108
|
+
priority: event.priority,
|
|
109
|
+
waitMs: event.waitMs,
|
|
110
|
+
});
|
|
111
|
+
opSpans.set(event.id, opSpan);
|
|
112
|
+
options.recordPerfEvent('mux.control-plane.op.start', {
|
|
113
|
+
id: event.id,
|
|
114
|
+
label: event.label,
|
|
115
|
+
priority: event.priority,
|
|
116
|
+
waitMs: event.waitMs,
|
|
117
|
+
interactiveQueued: metrics.interactiveQueued,
|
|
118
|
+
backgroundQueued: metrics.backgroundQueued,
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
onSuccess: (event) => {
|
|
122
|
+
endSpan(event, 'ok');
|
|
123
|
+
},
|
|
124
|
+
onError: (event, _metrics, error) => {
|
|
125
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
126
|
+
endSpan(event, 'error', message);
|
|
127
|
+
options.writeStderr(`[mux] control-plane error ${message}\n`);
|
|
128
|
+
},
|
|
129
|
+
onCanceled: (event) => {
|
|
130
|
+
endSpan(event, 'canceled');
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
enqueueInteractive: (task: () => Promise<void>, label = 'interactive-op'): void => {
|
|
136
|
+
queue.enqueueInteractive(async () => {
|
|
137
|
+
await task();
|
|
138
|
+
}, label);
|
|
139
|
+
},
|
|
140
|
+
enqueueInteractiveLatest: (
|
|
141
|
+
key: string,
|
|
142
|
+
task: (options: { readonly signal: AbortSignal }) => Promise<void>,
|
|
143
|
+
label = 'interactive-op',
|
|
144
|
+
): void => {
|
|
145
|
+
queue.enqueueInteractive(task, label, {
|
|
146
|
+
key,
|
|
147
|
+
supersede: 'pending-and-running',
|
|
148
|
+
});
|
|
149
|
+
},
|
|
150
|
+
enqueueBackgroundLatest: (
|
|
151
|
+
key: string,
|
|
152
|
+
task: (options: { readonly signal: AbortSignal }) => Promise<void>,
|
|
153
|
+
label = 'background-op',
|
|
154
|
+
): void => {
|
|
155
|
+
queue.enqueueBackground(task, label, {
|
|
156
|
+
key,
|
|
157
|
+
supersede: 'pending-and-running',
|
|
158
|
+
});
|
|
159
|
+
},
|
|
160
|
+
enqueueBackground: (task: () => Promise<void>, label = 'background-op'): void => {
|
|
161
|
+
queue.enqueueBackground(async () => {
|
|
162
|
+
await task();
|
|
163
|
+
}, label);
|
|
164
|
+
},
|
|
165
|
+
waitForDrain: async (): Promise<void> => {
|
|
166
|
+
await queue.waitForDrain();
|
|
167
|
+
},
|
|
168
|
+
metrics: () => queue.metrics(),
|
|
169
|
+
};
|
|
131
170
|
}
|
|
@@ -52,19 +52,25 @@ export interface RuntimeConversationActionsOptions<TControllerRecord> {
|
|
|
52
52
|
readonly markDirty: () => void;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
export
|
|
56
|
-
|
|
55
|
+
export interface RuntimeConversationActions {
|
|
56
|
+
createAndActivateConversationInDirectory(directoryId: string, agentType: string): Promise<void>;
|
|
57
|
+
openOrCreateCritiqueConversationInDirectory(directoryId: string): Promise<void>;
|
|
58
|
+
takeoverConversation(sessionId: string): Promise<void>;
|
|
59
|
+
}
|
|
57
60
|
|
|
58
|
-
|
|
61
|
+
export function createRuntimeConversationActions<TControllerRecord>(
|
|
62
|
+
options: RuntimeConversationActionsOptions<TControllerRecord>,
|
|
63
|
+
): RuntimeConversationActions {
|
|
64
|
+
const createAndActivateConversationInDirectory = async (
|
|
59
65
|
directoryId: string,
|
|
60
66
|
agentType: string,
|
|
61
|
-
): Promise<void> {
|
|
67
|
+
): Promise<void> => {
|
|
62
68
|
await createAndActivateConversationInDirectoryFn({
|
|
63
69
|
directoryId,
|
|
64
70
|
agentType,
|
|
65
|
-
createConversationId:
|
|
71
|
+
createConversationId: options.createConversationId,
|
|
66
72
|
createConversationRecord: async (sessionId, targetDirectoryId, targetAgentType) => {
|
|
67
|
-
await
|
|
73
|
+
await options.controlPlaneService.createConversation({
|
|
68
74
|
conversationId: sessionId,
|
|
69
75
|
directoryId: targetDirectoryId,
|
|
70
76
|
title: '',
|
|
@@ -72,42 +78,50 @@ export class RuntimeConversationActions<TControllerRecord> {
|
|
|
72
78
|
adapterState: {},
|
|
73
79
|
});
|
|
74
80
|
},
|
|
75
|
-
ensureConversation:
|
|
76
|
-
noteGitActivity:
|
|
77
|
-
startConversation:
|
|
78
|
-
activateConversation:
|
|
81
|
+
ensureConversation: options.ensureConversation,
|
|
82
|
+
noteGitActivity: options.noteGitActivity,
|
|
83
|
+
startConversation: options.startConversation,
|
|
84
|
+
activateConversation: options.activateConversation,
|
|
79
85
|
});
|
|
80
|
-
}
|
|
86
|
+
};
|
|
81
87
|
|
|
82
|
-
|
|
88
|
+
const openOrCreateCritiqueConversationInDirectory = async (
|
|
89
|
+
directoryId: string,
|
|
90
|
+
): Promise<void> => {
|
|
83
91
|
await openOrCreateCritiqueConversationInDirectoryFn({
|
|
84
92
|
directoryId,
|
|
85
|
-
orderedConversationIds:
|
|
86
|
-
conversationById:
|
|
87
|
-
activateConversation:
|
|
93
|
+
orderedConversationIds: options.orderedConversationIds,
|
|
94
|
+
conversationById: options.conversationById,
|
|
95
|
+
activateConversation: options.activateConversation,
|
|
88
96
|
createAndActivateCritiqueConversationInDirectory: async (targetDirectoryId) => {
|
|
89
|
-
await
|
|
97
|
+
await createAndActivateConversationInDirectory(targetDirectoryId, 'critique');
|
|
90
98
|
},
|
|
91
99
|
});
|
|
92
|
-
}
|
|
100
|
+
};
|
|
93
101
|
|
|
94
|
-
async
|
|
102
|
+
const takeoverConversation = async (sessionId: string): Promise<void> => {
|
|
95
103
|
await takeoverConversationFn({
|
|
96
104
|
sessionId,
|
|
97
|
-
conversationsHas:
|
|
105
|
+
conversationsHas: options.conversationsHas,
|
|
98
106
|
claimSession: async (targetSessionId) => {
|
|
99
|
-
return await
|
|
107
|
+
return await options.controlPlaneService.claimSession({
|
|
100
108
|
sessionId: targetSessionId,
|
|
101
|
-
controllerId:
|
|
109
|
+
controllerId: options.muxControllerId,
|
|
102
110
|
controllerType: 'human',
|
|
103
|
-
controllerLabel:
|
|
111
|
+
controllerLabel: options.muxControllerLabel,
|
|
104
112
|
reason: 'human takeover',
|
|
105
113
|
takeover: true,
|
|
106
114
|
});
|
|
107
115
|
},
|
|
108
|
-
applyController:
|
|
109
|
-
setLastEventNow:
|
|
110
|
-
markDirty:
|
|
116
|
+
applyController: options.applyController,
|
|
117
|
+
setLastEventNow: options.setLastEventNow,
|
|
118
|
+
markDirty: options.markDirty,
|
|
111
119
|
});
|
|
112
|
-
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
createAndActivateConversationInDirectory,
|
|
124
|
+
openOrCreateCritiqueConversationInDirectory,
|
|
125
|
+
takeoverConversation,
|
|
126
|
+
};
|
|
113
127
|
}
|
|
@@ -24,80 +24,107 @@ export interface RuntimeConversationActivationOptions {
|
|
|
24
24
|
readonly markDirty: () => void;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
export
|
|
28
|
-
|
|
27
|
+
export interface RuntimeConversationActivation {
|
|
28
|
+
activateConversation(sessionId: string, input?: { readonly signal?: AbortSignal }): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function createRuntimeConversationActivation(
|
|
32
|
+
options: RuntimeConversationActivationOptions,
|
|
33
|
+
): RuntimeConversationActivation {
|
|
34
|
+
async function attachConversationWithRecovery(
|
|
35
|
+
sessionId: string,
|
|
36
|
+
signal: AbortSignal | undefined,
|
|
37
|
+
): Promise<boolean> {
|
|
38
|
+
try {
|
|
39
|
+
await options.attachConversation(sessionId);
|
|
40
|
+
return !(signal?.aborted ?? false);
|
|
41
|
+
} catch (error: unknown) {
|
|
42
|
+
if (!options.isSessionNotFoundError(error) && !options.isSessionNotLiveError(error)) {
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
options.markSessionUnavailable(sessionId);
|
|
46
|
+
await options.startConversation(sessionId);
|
|
47
|
+
if (signal?.aborted) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
await options.attachConversation(sessionId);
|
|
51
|
+
return !(signal?.aborted ?? false);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
29
54
|
|
|
30
|
-
async activateConversation(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
55
|
+
async function activateConversation(
|
|
56
|
+
sessionId: string,
|
|
57
|
+
input: { readonly signal?: AbortSignal } = {},
|
|
58
|
+
): Promise<void> {
|
|
59
|
+
const signal = input.signal;
|
|
60
|
+
if (signal?.aborted) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (options.getActiveConversationId() === sessionId) {
|
|
64
|
+
if (!options.isConversationPaneMode()) {
|
|
65
|
+
const targetConversation = options.conversationById(sessionId);
|
|
36
66
|
if (
|
|
37
67
|
targetConversation !== undefined &&
|
|
38
68
|
!targetConversation.live &&
|
|
39
69
|
targetConversation.status !== 'exited'
|
|
40
70
|
) {
|
|
41
|
-
await
|
|
71
|
+
await options.startConversation(sessionId);
|
|
72
|
+
if (signal?.aborted) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
42
75
|
}
|
|
43
76
|
if (targetConversation?.status !== 'exited') {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (
|
|
48
|
-
!this.options.isSessionNotFoundError(error) &&
|
|
49
|
-
!this.options.isSessionNotLiveError(error)
|
|
50
|
-
) {
|
|
51
|
-
throw error;
|
|
52
|
-
}
|
|
53
|
-
this.options.markSessionUnavailable(sessionId);
|
|
54
|
-
await this.options.startConversation(sessionId);
|
|
55
|
-
await this.options.attachConversation(sessionId);
|
|
77
|
+
const attached = await attachConversationWithRecovery(sessionId, signal);
|
|
78
|
+
if (!attached) {
|
|
79
|
+
return;
|
|
56
80
|
}
|
|
57
81
|
}
|
|
58
|
-
|
|
59
|
-
|
|
82
|
+
options.enterConversationPaneForActiveSession(sessionId);
|
|
83
|
+
options.noteGitActivity(targetConversation?.directoryId ?? null);
|
|
84
|
+
options.schedulePtyResizeImmediate();
|
|
85
|
+
options.markDirty();
|
|
60
86
|
}
|
|
61
87
|
return;
|
|
62
88
|
}
|
|
63
89
|
|
|
64
|
-
|
|
65
|
-
const previousActiveId =
|
|
66
|
-
|
|
90
|
+
options.stopConversationTitleEditForOtherSession(sessionId);
|
|
91
|
+
const previousActiveId = options.getActiveConversationId();
|
|
92
|
+
options.clearSelectionState();
|
|
67
93
|
if (previousActiveId !== null) {
|
|
68
|
-
await
|
|
94
|
+
await options.detachConversation(previousActiveId);
|
|
95
|
+
if (signal?.aborted) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
69
98
|
}
|
|
70
|
-
this.options.setActiveConversationId(sessionId);
|
|
71
|
-
this.options.enterConversationPaneForSessionSwitch(sessionId);
|
|
72
99
|
|
|
73
|
-
const targetConversation =
|
|
74
|
-
this.options.noteGitActivity(targetConversation?.directoryId ?? null);
|
|
100
|
+
const targetConversation = options.conversationById(sessionId);
|
|
75
101
|
|
|
76
102
|
if (
|
|
77
103
|
targetConversation !== undefined &&
|
|
78
104
|
!targetConversation.live &&
|
|
79
105
|
targetConversation.status !== 'exited'
|
|
80
106
|
) {
|
|
81
|
-
await
|
|
107
|
+
await options.startConversation(sessionId);
|
|
108
|
+
if (signal?.aborted) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
82
111
|
}
|
|
83
112
|
|
|
84
113
|
if (targetConversation?.status !== 'exited') {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (
|
|
89
|
-
!this.options.isSessionNotFoundError(error) &&
|
|
90
|
-
!this.options.isSessionNotLiveError(error)
|
|
91
|
-
) {
|
|
92
|
-
throw error;
|
|
93
|
-
}
|
|
94
|
-
this.options.markSessionUnavailable(sessionId);
|
|
95
|
-
await this.options.startConversation(sessionId);
|
|
96
|
-
await this.options.attachConversation(sessionId);
|
|
114
|
+
const attached = await attachConversationWithRecovery(sessionId, signal);
|
|
115
|
+
if (!attached) {
|
|
116
|
+
return;
|
|
97
117
|
}
|
|
98
118
|
}
|
|
99
119
|
|
|
100
|
-
|
|
101
|
-
|
|
120
|
+
options.setActiveConversationId(sessionId);
|
|
121
|
+
options.enterConversationPaneForSessionSwitch(sessionId);
|
|
122
|
+
options.noteGitActivity(targetConversation?.directoryId ?? null);
|
|
123
|
+
options.schedulePtyResizeImmediate();
|
|
124
|
+
options.markDirty();
|
|
102
125
|
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
activateConversation,
|
|
129
|
+
};
|
|
103
130
|
}
|
|
@@ -83,99 +83,113 @@ export interface RuntimeConversationStarterOptions<
|
|
|
83
83
|
readonly subscribeConversationEvents: (sessionId: string) => Promise<void>;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
export
|
|
86
|
+
export interface RuntimeConversationStarter<
|
|
87
87
|
TConversation extends RuntimeConversationStarterConversationRecord,
|
|
88
|
-
TSessionSummary,
|
|
89
88
|
> {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
startConversation(sessionId: string): Promise<TConversation>;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function createRuntimeConversationStarter<
|
|
93
|
+
TConversation extends RuntimeConversationStarterConversationRecord,
|
|
94
|
+
TSessionSummary,
|
|
95
|
+
>(
|
|
96
|
+
options: RuntimeConversationStarterOptions<TConversation, TSessionSummary>,
|
|
97
|
+
): RuntimeConversationStarter<TConversation> {
|
|
98
|
+
function endStartCommandSpanIfTarget(
|
|
99
|
+
sessionId: string,
|
|
100
|
+
payload: RuntimeConversationStarterSpanAttributes,
|
|
101
|
+
): void {
|
|
102
|
+
if (options.firstPaintTargetSessionId() !== sessionId) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
options.endStartCommandSpan(payload);
|
|
106
|
+
}
|
|
93
107
|
|
|
94
|
-
async startConversation(sessionId: string): Promise<TConversation> {
|
|
95
|
-
return await
|
|
96
|
-
const existing =
|
|
97
|
-
const targetConversation = existing ??
|
|
98
|
-
const agentType =
|
|
108
|
+
async function startConversation(sessionId: string): Promise<TConversation> {
|
|
109
|
+
return await options.runWithStartInFlight(sessionId, async () => {
|
|
110
|
+
const existing = options.conversationById(sessionId);
|
|
111
|
+
const targetConversation = existing ?? options.ensureConversation(sessionId);
|
|
112
|
+
const agentType = options.normalizeThreadAgentType(targetConversation.agentType);
|
|
99
113
|
const baseArgsForAgent =
|
|
100
114
|
agentType === 'codex'
|
|
101
|
-
?
|
|
115
|
+
? options.codexArgs
|
|
102
116
|
: agentType === 'critique'
|
|
103
|
-
?
|
|
117
|
+
? options.critiqueDefaultArgs
|
|
104
118
|
: [];
|
|
105
|
-
const sessionCwd =
|
|
106
|
-
const launchArgs =
|
|
119
|
+
const sessionCwd = options.sessionCwdForConversation(targetConversation);
|
|
120
|
+
const launchArgs = options.buildLaunchArgs({
|
|
107
121
|
agentType,
|
|
108
122
|
baseArgsForAgent,
|
|
109
123
|
adapterState: targetConversation.adapterState,
|
|
110
124
|
sessionCwd,
|
|
111
125
|
});
|
|
112
|
-
targetConversation.launchCommand =
|
|
113
|
-
|
|
126
|
+
targetConversation.launchCommand = options.formatCommandForDebugBar(
|
|
127
|
+
options.launchCommandForAgent(agentType),
|
|
114
128
|
launchArgs,
|
|
115
129
|
);
|
|
116
130
|
|
|
117
131
|
if (existing?.live === true) {
|
|
118
|
-
|
|
132
|
+
endStartCommandSpanIfTarget(sessionId, {
|
|
119
133
|
alreadyLive: true,
|
|
120
134
|
});
|
|
121
135
|
return existing;
|
|
122
136
|
}
|
|
123
137
|
|
|
124
|
-
const startSpan =
|
|
138
|
+
const startSpan = options.startConversationSpan(sessionId);
|
|
125
139
|
targetConversation.lastOutputCursor = 0;
|
|
126
|
-
const layout =
|
|
140
|
+
const layout = options.layout();
|
|
127
141
|
const ptyStartInput: RuntimeConversationStarterPtyStartInput = {
|
|
128
142
|
sessionId,
|
|
129
143
|
args: launchArgs,
|
|
130
|
-
env:
|
|
144
|
+
env: options.sessionEnv,
|
|
131
145
|
cwd: sessionCwd,
|
|
132
146
|
initialCols: layout.rightCols,
|
|
133
147
|
initialRows: layout.paneRows,
|
|
134
148
|
};
|
|
135
|
-
if (
|
|
136
|
-
ptyStartInput.worktreeId =
|
|
149
|
+
if (options.worktreeId !== undefined) {
|
|
150
|
+
ptyStartInput.worktreeId = options.worktreeId;
|
|
137
151
|
}
|
|
138
|
-
if (
|
|
139
|
-
ptyStartInput.terminalForegroundHex =
|
|
152
|
+
if (options.terminalForegroundHex !== undefined) {
|
|
153
|
+
ptyStartInput.terminalForegroundHex = options.terminalForegroundHex;
|
|
140
154
|
}
|
|
141
|
-
if (
|
|
142
|
-
ptyStartInput.terminalBackgroundHex =
|
|
155
|
+
if (options.terminalBackgroundHex !== undefined) {
|
|
156
|
+
ptyStartInput.terminalBackgroundHex = options.terminalBackgroundHex;
|
|
143
157
|
}
|
|
144
158
|
let startedSession = false;
|
|
145
159
|
try {
|
|
146
|
-
await
|
|
160
|
+
await options.startPtySession(ptyStartInput);
|
|
147
161
|
startedSession = true;
|
|
148
162
|
} catch (error: unknown) {
|
|
149
163
|
if (!isSessionAlreadyExistsError(error)) {
|
|
150
164
|
throw error;
|
|
151
165
|
}
|
|
152
166
|
}
|
|
153
|
-
|
|
167
|
+
options.setPtySize(sessionId, {
|
|
154
168
|
cols: layout.rightCols,
|
|
155
169
|
rows: layout.paneRows,
|
|
156
170
|
});
|
|
157
|
-
|
|
171
|
+
options.sendResize(sessionId, layout.rightCols, layout.paneRows);
|
|
158
172
|
if (startedSession) {
|
|
159
|
-
|
|
173
|
+
endStartCommandSpanIfTarget(sessionId, {
|
|
160
174
|
alreadyLive: false,
|
|
161
175
|
argCount: launchArgs.length,
|
|
162
176
|
resumed: launchArgs[0] === 'resume',
|
|
163
177
|
});
|
|
164
178
|
} else {
|
|
165
|
-
|
|
179
|
+
endStartCommandSpanIfTarget(sessionId, {
|
|
166
180
|
alreadyLive: true,
|
|
167
181
|
recoveredDuplicateStart: true,
|
|
168
182
|
});
|
|
169
183
|
}
|
|
170
|
-
const state =
|
|
184
|
+
const state = options.ensureConversation(sessionId);
|
|
171
185
|
if (startedSession) {
|
|
172
|
-
|
|
186
|
+
options.recordStartCommand(sessionId, launchArgs);
|
|
173
187
|
}
|
|
174
|
-
const statusSummary = await
|
|
188
|
+
const statusSummary = await options.getSessionStatus(sessionId);
|
|
175
189
|
if (statusSummary !== null) {
|
|
176
|
-
|
|
190
|
+
options.upsertFromSessionSummary(statusSummary);
|
|
177
191
|
}
|
|
178
|
-
await
|
|
192
|
+
await options.subscribeConversationEvents(sessionId);
|
|
179
193
|
startSpan.end({
|
|
180
194
|
live: state.live,
|
|
181
195
|
});
|
|
@@ -183,13 +197,7 @@ export class RuntimeConversationStarter<
|
|
|
183
197
|
});
|
|
184
198
|
}
|
|
185
199
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
): void {
|
|
190
|
-
if (this.options.firstPaintTargetSessionId() !== sessionId) {
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
this.options.endStartCommandSpan(payload);
|
|
194
|
-
}
|
|
200
|
+
return {
|
|
201
|
+
startConversation,
|
|
202
|
+
};
|
|
195
203
|
}
|