@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
|
@@ -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,55 +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
|
+
}
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
+
}
|
|
54
|
+
|
|
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);
|
|
66
|
+
if (
|
|
67
|
+
targetConversation !== undefined &&
|
|
68
|
+
!targetConversation.live &&
|
|
69
|
+
targetConversation.status !== 'exited'
|
|
70
|
+
) {
|
|
71
|
+
await options.startConversation(sessionId);
|
|
72
|
+
if (signal?.aborted) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (targetConversation?.status !== 'exited') {
|
|
77
|
+
const attached = await attachConversationWithRecovery(sessionId, signal);
|
|
78
|
+
if (!attached) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
options.enterConversationPaneForActiveSession(sessionId);
|
|
83
|
+
options.noteGitActivity(targetConversation?.directoryId ?? null);
|
|
84
|
+
options.schedulePtyResizeImmediate();
|
|
85
|
+
options.markDirty();
|
|
35
86
|
}
|
|
36
87
|
return;
|
|
37
88
|
}
|
|
38
89
|
|
|
39
|
-
|
|
40
|
-
const previousActiveId =
|
|
41
|
-
|
|
90
|
+
options.stopConversationTitleEditForOtherSession(sessionId);
|
|
91
|
+
const previousActiveId = options.getActiveConversationId();
|
|
92
|
+
options.clearSelectionState();
|
|
42
93
|
if (previousActiveId !== null) {
|
|
43
|
-
await
|
|
94
|
+
await options.detachConversation(previousActiveId);
|
|
95
|
+
if (signal?.aborted) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
44
98
|
}
|
|
45
|
-
this.options.setActiveConversationId(sessionId);
|
|
46
|
-
this.options.enterConversationPaneForSessionSwitch(sessionId);
|
|
47
99
|
|
|
48
|
-
const targetConversation =
|
|
49
|
-
this.options.noteGitActivity(targetConversation?.directoryId ?? null);
|
|
100
|
+
const targetConversation = options.conversationById(sessionId);
|
|
50
101
|
|
|
51
102
|
if (
|
|
52
103
|
targetConversation !== undefined &&
|
|
53
104
|
!targetConversation.live &&
|
|
54
105
|
targetConversation.status !== 'exited'
|
|
55
106
|
) {
|
|
56
|
-
await
|
|
107
|
+
await options.startConversation(sessionId);
|
|
108
|
+
if (signal?.aborted) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
57
111
|
}
|
|
58
112
|
|
|
59
113
|
if (targetConversation?.status !== 'exited') {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (
|
|
64
|
-
!this.options.isSessionNotFoundError(error) &&
|
|
65
|
-
!this.options.isSessionNotLiveError(error)
|
|
66
|
-
) {
|
|
67
|
-
throw error;
|
|
68
|
-
}
|
|
69
|
-
this.options.markSessionUnavailable(sessionId);
|
|
70
|
-
await this.options.startConversation(sessionId);
|
|
71
|
-
await this.options.attachConversation(sessionId);
|
|
114
|
+
const attached = await attachConversationWithRecovery(sessionId, signal);
|
|
115
|
+
if (!attached) {
|
|
116
|
+
return;
|
|
72
117
|
}
|
|
73
118
|
}
|
|
74
119
|
|
|
75
|
-
|
|
76
|
-
|
|
120
|
+
options.setActiveConversationId(sessionId);
|
|
121
|
+
options.enterConversationPaneForSessionSwitch(sessionId);
|
|
122
|
+
options.noteGitActivity(targetConversation?.directoryId ?? null);
|
|
123
|
+
options.schedulePtyResizeImmediate();
|
|
124
|
+
options.markDirty();
|
|
77
125
|
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
activateConversation,
|
|
129
|
+
};
|
|
78
130
|
}
|
|
@@ -33,6 +33,13 @@ interface RuntimeConversationStarterLaunchArgsInput {
|
|
|
33
33
|
|
|
34
34
|
type RuntimeConversationStarterSpanAttributes = Record<string, string | number | boolean>;
|
|
35
35
|
|
|
36
|
+
function isSessionAlreadyExistsError(error: unknown): boolean {
|
|
37
|
+
if (!(error instanceof Error)) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
return error.message.includes('session already exists');
|
|
41
|
+
}
|
|
42
|
+
|
|
36
43
|
export interface RuntimeConversationStarterOptions<
|
|
37
44
|
TConversation extends RuntimeConversationStarterConversationRecord,
|
|
38
45
|
TSessionSummary,
|
|
@@ -76,82 +83,113 @@ export interface RuntimeConversationStarterOptions<
|
|
|
76
83
|
readonly subscribeConversationEvents: (sessionId: string) => Promise<void>;
|
|
77
84
|
}
|
|
78
85
|
|
|
79
|
-
export
|
|
86
|
+
export interface RuntimeConversationStarter<
|
|
80
87
|
TConversation extends RuntimeConversationStarterConversationRecord,
|
|
81
|
-
TSessionSummary,
|
|
82
88
|
> {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
+
}
|
|
86
107
|
|
|
87
|
-
async startConversation(sessionId: string): Promise<TConversation> {
|
|
88
|
-
return await
|
|
89
|
-
const existing =
|
|
90
|
-
const targetConversation = existing ??
|
|
91
|
-
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);
|
|
92
113
|
const baseArgsForAgent =
|
|
93
114
|
agentType === 'codex'
|
|
94
|
-
?
|
|
115
|
+
? options.codexArgs
|
|
95
116
|
: agentType === 'critique'
|
|
96
|
-
?
|
|
117
|
+
? options.critiqueDefaultArgs
|
|
97
118
|
: [];
|
|
98
|
-
const sessionCwd =
|
|
99
|
-
const launchArgs =
|
|
119
|
+
const sessionCwd = options.sessionCwdForConversation(targetConversation);
|
|
120
|
+
const launchArgs = options.buildLaunchArgs({
|
|
100
121
|
agentType,
|
|
101
122
|
baseArgsForAgent,
|
|
102
123
|
adapterState: targetConversation.adapterState,
|
|
103
124
|
sessionCwd,
|
|
104
125
|
});
|
|
105
|
-
targetConversation.launchCommand =
|
|
106
|
-
|
|
126
|
+
targetConversation.launchCommand = options.formatCommandForDebugBar(
|
|
127
|
+
options.launchCommandForAgent(agentType),
|
|
107
128
|
launchArgs,
|
|
108
129
|
);
|
|
109
130
|
|
|
110
131
|
if (existing?.live === true) {
|
|
111
|
-
|
|
132
|
+
endStartCommandSpanIfTarget(sessionId, {
|
|
112
133
|
alreadyLive: true,
|
|
113
134
|
});
|
|
114
135
|
return existing;
|
|
115
136
|
}
|
|
116
137
|
|
|
117
|
-
const startSpan =
|
|
138
|
+
const startSpan = options.startConversationSpan(sessionId);
|
|
118
139
|
targetConversation.lastOutputCursor = 0;
|
|
119
|
-
const layout =
|
|
140
|
+
const layout = options.layout();
|
|
120
141
|
const ptyStartInput: RuntimeConversationStarterPtyStartInput = {
|
|
121
142
|
sessionId,
|
|
122
143
|
args: launchArgs,
|
|
123
|
-
env:
|
|
144
|
+
env: options.sessionEnv,
|
|
124
145
|
cwd: sessionCwd,
|
|
125
146
|
initialCols: layout.rightCols,
|
|
126
147
|
initialRows: layout.paneRows,
|
|
127
148
|
};
|
|
128
|
-
if (
|
|
129
|
-
ptyStartInput.worktreeId =
|
|
149
|
+
if (options.worktreeId !== undefined) {
|
|
150
|
+
ptyStartInput.worktreeId = options.worktreeId;
|
|
130
151
|
}
|
|
131
|
-
if (
|
|
132
|
-
ptyStartInput.terminalForegroundHex =
|
|
152
|
+
if (options.terminalForegroundHex !== undefined) {
|
|
153
|
+
ptyStartInput.terminalForegroundHex = options.terminalForegroundHex;
|
|
133
154
|
}
|
|
134
|
-
if (
|
|
135
|
-
ptyStartInput.terminalBackgroundHex =
|
|
155
|
+
if (options.terminalBackgroundHex !== undefined) {
|
|
156
|
+
ptyStartInput.terminalBackgroundHex = options.terminalBackgroundHex;
|
|
136
157
|
}
|
|
137
|
-
|
|
138
|
-
|
|
158
|
+
let startedSession = false;
|
|
159
|
+
try {
|
|
160
|
+
await options.startPtySession(ptyStartInput);
|
|
161
|
+
startedSession = true;
|
|
162
|
+
} catch (error: unknown) {
|
|
163
|
+
if (!isSessionAlreadyExistsError(error)) {
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
options.setPtySize(sessionId, {
|
|
139
168
|
cols: layout.rightCols,
|
|
140
169
|
rows: layout.paneRows,
|
|
141
170
|
});
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
171
|
+
options.sendResize(sessionId, layout.rightCols, layout.paneRows);
|
|
172
|
+
if (startedSession) {
|
|
173
|
+
endStartCommandSpanIfTarget(sessionId, {
|
|
174
|
+
alreadyLive: false,
|
|
175
|
+
argCount: launchArgs.length,
|
|
176
|
+
resumed: launchArgs[0] === 'resume',
|
|
177
|
+
});
|
|
178
|
+
} else {
|
|
179
|
+
endStartCommandSpanIfTarget(sessionId, {
|
|
180
|
+
alreadyLive: true,
|
|
181
|
+
recoveredDuplicateStart: true,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
const state = options.ensureConversation(sessionId);
|
|
185
|
+
if (startedSession) {
|
|
186
|
+
options.recordStartCommand(sessionId, launchArgs);
|
|
187
|
+
}
|
|
188
|
+
const statusSummary = await options.getSessionStatus(sessionId);
|
|
151
189
|
if (statusSummary !== null) {
|
|
152
|
-
|
|
190
|
+
options.upsertFromSessionSummary(statusSummary);
|
|
153
191
|
}
|
|
154
|
-
await
|
|
192
|
+
await options.subscribeConversationEvents(sessionId);
|
|
155
193
|
startSpan.end({
|
|
156
194
|
live: state.live,
|
|
157
195
|
});
|
|
@@ -159,13 +197,7 @@ export class RuntimeConversationStarter<
|
|
|
159
197
|
});
|
|
160
198
|
}
|
|
161
199
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
): void {
|
|
166
|
-
if (this.options.firstPaintTargetSessionId() !== sessionId) {
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
this.options.endStartCommandSpan(payload);
|
|
170
|
-
}
|
|
200
|
+
return {
|
|
201
|
+
startConversation,
|
|
202
|
+
};
|
|
171
203
|
}
|