@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
|
@@ -8,13 +8,18 @@ import {
|
|
|
8
8
|
parseSessionSummaryRecord,
|
|
9
9
|
} from '../control-plane/session-summary.ts';
|
|
10
10
|
import {
|
|
11
|
+
type ControlPlaneConversationRecord,
|
|
12
|
+
type ControlPlaneDirectoryGitStatusRecord,
|
|
13
|
+
type ControlPlaneDirectoryRecord,
|
|
14
|
+
type ControlPlaneRepositoryRecord,
|
|
15
|
+
type ControlPlaneTaskRecord,
|
|
11
16
|
parseConversationRecord,
|
|
12
17
|
parseDirectoryGitStatusRecord,
|
|
13
18
|
parseDirectoryRecord,
|
|
14
19
|
parseRepositoryRecord,
|
|
15
20
|
parseSessionControllerRecord,
|
|
16
21
|
parseTaskRecord,
|
|
17
|
-
} from '../
|
|
22
|
+
} from '../core/contracts/records.ts';
|
|
18
23
|
|
|
19
24
|
interface ControlPlaneScope {
|
|
20
25
|
readonly tenantId: string;
|
|
@@ -26,13 +31,6 @@ interface ControlPlaneCommandClient {
|
|
|
26
31
|
sendCommand(command: StreamCommand): Promise<Record<string, unknown>>;
|
|
27
32
|
}
|
|
28
33
|
|
|
29
|
-
type ControlPlaneRepositoryRecord = NonNullable<ReturnType<typeof parseRepositoryRecord>>;
|
|
30
|
-
type ControlPlaneTaskRecord = NonNullable<ReturnType<typeof parseTaskRecord>>;
|
|
31
|
-
type ControlPlaneDirectoryRecord = NonNullable<ReturnType<typeof parseDirectoryRecord>>;
|
|
32
|
-
type ControlPlaneConversationRecord = NonNullable<ReturnType<typeof parseConversationRecord>>;
|
|
33
|
-
type ControlPlaneDirectoryGitStatusRecord = NonNullable<
|
|
34
|
-
ReturnType<typeof parseDirectoryGitStatusRecord>
|
|
35
|
-
>;
|
|
36
34
|
type ControlPlaneSessionControllerRecord = NonNullable<
|
|
37
35
|
ReturnType<typeof parseSessionControllerRecord>
|
|
38
36
|
>;
|
|
@@ -267,19 +265,19 @@ export class ControlPlaneService {
|
|
|
267
265
|
};
|
|
268
266
|
}
|
|
269
267
|
|
|
270
|
-
async
|
|
268
|
+
archiveConversation = async (conversationId: string): Promise<void> => {
|
|
271
269
|
await this.client.sendCommand({
|
|
272
270
|
type: 'conversation.archive',
|
|
273
271
|
conversationId,
|
|
274
272
|
});
|
|
275
|
-
}
|
|
273
|
+
};
|
|
276
274
|
|
|
277
|
-
async
|
|
275
|
+
archiveDirectory = async (directoryId: string): Promise<void> => {
|
|
278
276
|
await this.client.sendCommand({
|
|
279
277
|
type: 'directory.archive',
|
|
280
278
|
directoryId,
|
|
281
279
|
});
|
|
282
|
-
}
|
|
280
|
+
};
|
|
283
281
|
|
|
284
282
|
async attachPty(input: { sessionId: string; sinceCursor: number }): Promise<void> {
|
|
285
283
|
await this.client.sendCommand({
|
|
@@ -349,12 +347,12 @@ export class ControlPlaneService {
|
|
|
349
347
|
await this.client.sendCommand(command);
|
|
350
348
|
}
|
|
351
349
|
|
|
352
|
-
async
|
|
350
|
+
closePtySession = async (sessionId: string): Promise<void> => {
|
|
353
351
|
await this.client.sendCommand({
|
|
354
352
|
type: 'pty.close',
|
|
355
353
|
sessionId,
|
|
356
354
|
});
|
|
357
|
-
}
|
|
355
|
+
};
|
|
358
356
|
|
|
359
357
|
async getSessionStatus(sessionId: string): Promise<ControlPlaneSessionSummary | null> {
|
|
360
358
|
const result = await this.client.sendCommand({
|
|
@@ -384,12 +382,12 @@ export class ControlPlaneService {
|
|
|
384
382
|
return parseSessionSummaryList(result['sessions']);
|
|
385
383
|
}
|
|
386
384
|
|
|
387
|
-
async
|
|
385
|
+
removeSession = async (sessionId: string): Promise<void> => {
|
|
388
386
|
await this.client.sendCommand({
|
|
389
387
|
type: 'session.remove',
|
|
390
388
|
sessionId,
|
|
391
389
|
});
|
|
392
|
-
}
|
|
390
|
+
};
|
|
393
391
|
|
|
394
392
|
async claimSession(input: {
|
|
395
393
|
sessionId: string;
|
|
@@ -461,19 +459,28 @@ export class ControlPlaneService {
|
|
|
461
459
|
}
|
|
462
460
|
|
|
463
461
|
async createTask(input: {
|
|
464
|
-
repositoryId
|
|
465
|
-
|
|
466
|
-
|
|
462
|
+
repositoryId?: string;
|
|
463
|
+
projectId?: string;
|
|
464
|
+
title?: string | null;
|
|
465
|
+
body: string;
|
|
467
466
|
}): Promise<ControlPlaneTaskRecord> {
|
|
468
|
-
const
|
|
467
|
+
const command: StreamCommand = {
|
|
469
468
|
type: 'task.create',
|
|
470
469
|
tenantId: this.scope.tenantId,
|
|
471
470
|
userId: this.scope.userId,
|
|
472
471
|
workspaceId: this.scope.workspaceId,
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
472
|
+
body: input.body,
|
|
473
|
+
};
|
|
474
|
+
if (input.repositoryId !== undefined) {
|
|
475
|
+
command.repositoryId = input.repositoryId;
|
|
476
|
+
}
|
|
477
|
+
if (input.projectId !== undefined) {
|
|
478
|
+
command.projectId = input.projectId;
|
|
479
|
+
}
|
|
480
|
+
if (input.title !== undefined) {
|
|
481
|
+
command.title = input.title;
|
|
482
|
+
}
|
|
483
|
+
const result = await this.client.sendCommand(command);
|
|
477
484
|
return this.parseTaskFromResult(
|
|
478
485
|
result,
|
|
479
486
|
'control-plane task.create returned malformed task record',
|
|
@@ -482,17 +489,28 @@ export class ControlPlaneService {
|
|
|
482
489
|
|
|
483
490
|
async updateTask(input: {
|
|
484
491
|
taskId: string;
|
|
485
|
-
repositoryId
|
|
486
|
-
|
|
487
|
-
|
|
492
|
+
repositoryId?: string | null;
|
|
493
|
+
projectId?: string | null;
|
|
494
|
+
title?: string | null;
|
|
495
|
+
body?: string;
|
|
488
496
|
}): Promise<ControlPlaneTaskRecord> {
|
|
489
|
-
const
|
|
497
|
+
const command: StreamCommand = {
|
|
490
498
|
type: 'task.update',
|
|
491
499
|
taskId: input.taskId,
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
}
|
|
500
|
+
};
|
|
501
|
+
if (input.repositoryId !== undefined) {
|
|
502
|
+
command.repositoryId = input.repositoryId;
|
|
503
|
+
}
|
|
504
|
+
if (input.projectId !== undefined) {
|
|
505
|
+
command.projectId = input.projectId;
|
|
506
|
+
}
|
|
507
|
+
if (input.title !== undefined) {
|
|
508
|
+
command.title = input.title;
|
|
509
|
+
}
|
|
510
|
+
if (input.body !== undefined) {
|
|
511
|
+
command.body = input.body;
|
|
512
|
+
}
|
|
513
|
+
const result = await this.client.sendCommand(command);
|
|
496
514
|
return this.parseTaskFromResult(
|
|
497
515
|
result,
|
|
498
516
|
'control-plane task.update returned malformed task record',
|
|
@@ -1,36 +1,41 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
createConversationStartupHydrationService,
|
|
3
|
+
type ConversationStartupHydrationService,
|
|
3
4
|
type ConversationStartupHydrationServiceOptions,
|
|
4
5
|
type SessionSummaryLike,
|
|
5
6
|
} from './conversation-startup-hydration.ts';
|
|
6
7
|
import {
|
|
7
|
-
|
|
8
|
+
createRuntimeConversationStarter,
|
|
9
|
+
type RuntimeConversationStarter,
|
|
8
10
|
type RuntimeConversationStarterConversationRecord,
|
|
9
11
|
type RuntimeConversationStarterOptions,
|
|
10
12
|
} from './runtime-conversation-starter.ts';
|
|
11
13
|
import {
|
|
12
|
-
|
|
14
|
+
createRuntimeConversationActivation,
|
|
15
|
+
type RuntimeConversationActivation,
|
|
13
16
|
type RuntimeConversationActivationOptions,
|
|
14
17
|
} from './runtime-conversation-activation.ts';
|
|
15
18
|
import {
|
|
16
|
-
|
|
19
|
+
createRuntimeConversationActions,
|
|
20
|
+
type RuntimeConversationActions,
|
|
17
21
|
type RuntimeConversationActionsOptions,
|
|
18
22
|
} from './runtime-conversation-actions.ts';
|
|
19
23
|
import {
|
|
20
|
-
|
|
24
|
+
createRuntimeConversationTitleEditService,
|
|
21
25
|
type RuntimeConversationTitleEditServiceOptions,
|
|
22
26
|
} from './runtime-conversation-title-edit.ts';
|
|
23
27
|
import {
|
|
24
|
-
|
|
28
|
+
createRuntimeStreamSubscriptions,
|
|
25
29
|
type RuntimeStreamSubscriptionsOptions,
|
|
26
30
|
} from './runtime-stream-subscriptions.ts';
|
|
27
31
|
import {
|
|
28
|
-
|
|
32
|
+
createStartupPersistedConversationQueueService,
|
|
33
|
+
type StartupPersistedConversationQueueService,
|
|
29
34
|
type StartupPersistedConversationQueueServiceOptions,
|
|
30
35
|
type StartupQueueConversationRecord,
|
|
31
36
|
} from './startup-persisted-conversation-queue.ts';
|
|
32
37
|
|
|
33
|
-
interface ConversationLifecycleOptions<
|
|
38
|
+
export interface ConversationLifecycleOptions<
|
|
34
39
|
TConversation extends RuntimeConversationStarterConversationRecord &
|
|
35
40
|
StartupQueueConversationRecord & { title: string },
|
|
36
41
|
TSessionSummary extends SessionSummaryLike,
|
|
@@ -57,120 +62,146 @@ interface ConversationLifecycleOptions<
|
|
|
57
62
|
readonly titleEdit: RuntimeConversationTitleEditServiceOptions<TConversation>;
|
|
58
63
|
}
|
|
59
64
|
|
|
60
|
-
export
|
|
65
|
+
export interface ConversationLifecycle<TConversation> {
|
|
66
|
+
subscribeConversationEvents(sessionId: string): Promise<void>;
|
|
67
|
+
unsubscribeConversationEvents(sessionId: string): Promise<void>;
|
|
68
|
+
subscribeTaskPlanningEvents(afterCursor: number | null): Promise<void>;
|
|
69
|
+
unsubscribeTaskPlanningEvents(): Promise<void>;
|
|
70
|
+
startConversation(sessionId: string): Promise<TConversation>;
|
|
71
|
+
activateConversation(
|
|
72
|
+
sessionId: string,
|
|
73
|
+
options?: { readonly signal?: AbortSignal },
|
|
74
|
+
): Promise<void>;
|
|
75
|
+
createAndActivateConversationInDirectory(directoryId: string, agentType: string): Promise<void>;
|
|
76
|
+
openOrCreateCritiqueConversationInDirectory(directoryId: string): Promise<void>;
|
|
77
|
+
takeoverConversation(sessionId: string): Promise<void>;
|
|
78
|
+
scheduleConversationTitlePersist(): void;
|
|
79
|
+
stopConversationTitleEdit(persistPending: boolean): void;
|
|
80
|
+
beginConversationTitleEdit(conversationId: string): void;
|
|
81
|
+
clearConversationTitleEditTimer(): void;
|
|
82
|
+
hydrateConversationList(): Promise<void>;
|
|
83
|
+
queuePersistedConversationsInBackground(activeSessionId: string | null): number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function createConversationLifecycle<
|
|
61
87
|
TConversation extends RuntimeConversationStarterConversationRecord &
|
|
62
88
|
StartupQueueConversationRecord & { title: string },
|
|
63
89
|
TSessionSummary extends SessionSummaryLike,
|
|
64
90
|
TControllerRecord,
|
|
65
|
-
>
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
options: ConversationLifecycleOptions<TConversation, TSessionSummary, TControllerRecord>,
|
|
76
|
-
) {
|
|
77
|
-
this.streamSubscriptions = new RuntimeStreamSubscriptions(options.streamSubscriptions);
|
|
78
|
-
this.starter = new RuntimeConversationStarter({
|
|
79
|
-
...options.starter,
|
|
80
|
-
subscribeConversationEvents: async (sessionId) => {
|
|
81
|
-
await this.subscribeConversationEvents(sessionId);
|
|
82
|
-
},
|
|
83
|
-
});
|
|
84
|
-
this.startupHydration = new ConversationStartupHydrationService({
|
|
85
|
-
...options.startupHydration,
|
|
86
|
-
subscribeConversationEvents: async (sessionId) => {
|
|
87
|
-
await this.subscribeConversationEvents(sessionId);
|
|
88
|
-
},
|
|
89
|
-
});
|
|
90
|
-
this.startupQueue = new StartupPersistedConversationQueueService({
|
|
91
|
-
...options.startupQueue,
|
|
92
|
-
startConversation: async (sessionId) => {
|
|
93
|
-
await this.startConversation(sessionId);
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
this.activation = new RuntimeConversationActivation({
|
|
97
|
-
...options.activation,
|
|
98
|
-
startConversation: async (sessionId) => {
|
|
99
|
-
await this.startConversation(sessionId);
|
|
100
|
-
},
|
|
101
|
-
});
|
|
102
|
-
this.actions = new RuntimeConversationActions({
|
|
103
|
-
...options.actions,
|
|
104
|
-
startConversation: async (sessionId) => {
|
|
105
|
-
await this.startConversation(sessionId);
|
|
106
|
-
},
|
|
107
|
-
activateConversation: async (sessionId) => {
|
|
108
|
-
await this.activateConversation(sessionId);
|
|
109
|
-
},
|
|
110
|
-
});
|
|
111
|
-
this.titleEdit = new RuntimeConversationTitleEditService(options.titleEdit);
|
|
112
|
-
}
|
|
91
|
+
>(
|
|
92
|
+
options: ConversationLifecycleOptions<TConversation, TSessionSummary, TControllerRecord>,
|
|
93
|
+
): ConversationLifecycle<TConversation> {
|
|
94
|
+
const streamSubscriptions = createRuntimeStreamSubscriptions(options.streamSubscriptions);
|
|
95
|
+
let starter: RuntimeConversationStarter<TConversation>;
|
|
96
|
+
let activation: RuntimeConversationActivation;
|
|
97
|
+
let startupHydration: ConversationStartupHydrationService;
|
|
98
|
+
let startupQueue: StartupPersistedConversationQueueService;
|
|
99
|
+
let actions: RuntimeConversationActions;
|
|
100
|
+
const titleEdit = createRuntimeConversationTitleEditService(options.titleEdit);
|
|
113
101
|
|
|
114
|
-
async subscribeConversationEvents(sessionId: string): Promise<void> {
|
|
115
|
-
await
|
|
102
|
+
async function subscribeConversationEvents(sessionId: string): Promise<void> {
|
|
103
|
+
await streamSubscriptions.subscribeConversationEvents(sessionId);
|
|
116
104
|
}
|
|
117
105
|
|
|
118
|
-
async unsubscribeConversationEvents(sessionId: string): Promise<void> {
|
|
119
|
-
await
|
|
106
|
+
async function unsubscribeConversationEvents(sessionId: string): Promise<void> {
|
|
107
|
+
await streamSubscriptions.unsubscribeConversationEvents(sessionId);
|
|
120
108
|
}
|
|
121
109
|
|
|
122
|
-
async subscribeTaskPlanningEvents(afterCursor: number | null): Promise<void> {
|
|
123
|
-
await
|
|
110
|
+
async function subscribeTaskPlanningEvents(afterCursor: number | null): Promise<void> {
|
|
111
|
+
await streamSubscriptions.subscribeTaskPlanningEvents(afterCursor);
|
|
124
112
|
}
|
|
125
113
|
|
|
126
|
-
async unsubscribeTaskPlanningEvents(): Promise<void> {
|
|
127
|
-
await
|
|
114
|
+
async function unsubscribeTaskPlanningEvents(): Promise<void> {
|
|
115
|
+
await streamSubscriptions.unsubscribeTaskPlanningEvents();
|
|
128
116
|
}
|
|
129
117
|
|
|
130
|
-
async startConversation(sessionId: string): Promise<TConversation> {
|
|
131
|
-
return await
|
|
118
|
+
async function startConversation(sessionId: string): Promise<TConversation> {
|
|
119
|
+
return await starter.startConversation(sessionId);
|
|
132
120
|
}
|
|
133
121
|
|
|
134
|
-
async activateConversation(
|
|
135
|
-
|
|
122
|
+
async function activateConversation(
|
|
123
|
+
sessionId: string,
|
|
124
|
+
activateOptions: { readonly signal?: AbortSignal } = {},
|
|
125
|
+
): Promise<void> {
|
|
126
|
+
await activation.activateConversation(sessionId, activateOptions);
|
|
136
127
|
}
|
|
137
128
|
|
|
138
|
-
async createAndActivateConversationInDirectory(
|
|
129
|
+
async function createAndActivateConversationInDirectory(
|
|
139
130
|
directoryId: string,
|
|
140
131
|
agentType: string,
|
|
141
132
|
): Promise<void> {
|
|
142
|
-
await
|
|
133
|
+
await actions.createAndActivateConversationInDirectory(directoryId, agentType);
|
|
143
134
|
}
|
|
144
135
|
|
|
145
|
-
async openOrCreateCritiqueConversationInDirectory(directoryId: string): Promise<void> {
|
|
146
|
-
await
|
|
136
|
+
async function openOrCreateCritiqueConversationInDirectory(directoryId: string): Promise<void> {
|
|
137
|
+
await actions.openOrCreateCritiqueConversationInDirectory(directoryId);
|
|
147
138
|
}
|
|
148
139
|
|
|
149
|
-
async takeoverConversation(sessionId: string): Promise<void> {
|
|
150
|
-
await
|
|
140
|
+
async function takeoverConversation(sessionId: string): Promise<void> {
|
|
141
|
+
await actions.takeoverConversation(sessionId);
|
|
151
142
|
}
|
|
152
143
|
|
|
153
|
-
scheduleConversationTitlePersist(): void {
|
|
154
|
-
|
|
144
|
+
function scheduleConversationTitlePersist(): void {
|
|
145
|
+
titleEdit.schedulePersist();
|
|
155
146
|
}
|
|
156
147
|
|
|
157
|
-
stopConversationTitleEdit(persistPending: boolean): void {
|
|
158
|
-
|
|
148
|
+
function stopConversationTitleEdit(persistPending: boolean): void {
|
|
149
|
+
titleEdit.stop(persistPending);
|
|
159
150
|
}
|
|
160
151
|
|
|
161
|
-
beginConversationTitleEdit(conversationId: string): void {
|
|
162
|
-
|
|
152
|
+
function beginConversationTitleEdit(conversationId: string): void {
|
|
153
|
+
titleEdit.begin(conversationId);
|
|
163
154
|
}
|
|
164
155
|
|
|
165
|
-
clearConversationTitleEditTimer(): void {
|
|
166
|
-
|
|
156
|
+
function clearConversationTitleEditTimer(): void {
|
|
157
|
+
titleEdit.clearCurrentTimer();
|
|
167
158
|
}
|
|
168
159
|
|
|
169
|
-
async hydrateConversationList(): Promise<void> {
|
|
170
|
-
await
|
|
160
|
+
async function hydrateConversationList(): Promise<void> {
|
|
161
|
+
await startupHydration.hydrateConversationList();
|
|
171
162
|
}
|
|
172
163
|
|
|
173
|
-
queuePersistedConversationsInBackground(activeSessionId: string | null): number {
|
|
174
|
-
return
|
|
164
|
+
function queuePersistedConversationsInBackground(activeSessionId: string | null): number {
|
|
165
|
+
return startupQueue.queuePersistedConversationsInBackground(activeSessionId);
|
|
175
166
|
}
|
|
167
|
+
|
|
168
|
+
starter = createRuntimeConversationStarter({
|
|
169
|
+
...options.starter,
|
|
170
|
+
subscribeConversationEvents,
|
|
171
|
+
});
|
|
172
|
+
startupHydration = createConversationStartupHydrationService({
|
|
173
|
+
...options.startupHydration,
|
|
174
|
+
subscribeConversationEvents,
|
|
175
|
+
});
|
|
176
|
+
startupQueue = createStartupPersistedConversationQueueService({
|
|
177
|
+
...options.startupQueue,
|
|
178
|
+
startConversation,
|
|
179
|
+
});
|
|
180
|
+
activation = createRuntimeConversationActivation({
|
|
181
|
+
...options.activation,
|
|
182
|
+
startConversation,
|
|
183
|
+
});
|
|
184
|
+
actions = createRuntimeConversationActions({
|
|
185
|
+
...options.actions,
|
|
186
|
+
startConversation,
|
|
187
|
+
activateConversation,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
subscribeConversationEvents,
|
|
192
|
+
unsubscribeConversationEvents,
|
|
193
|
+
subscribeTaskPlanningEvents,
|
|
194
|
+
unsubscribeTaskPlanningEvents,
|
|
195
|
+
startConversation,
|
|
196
|
+
activateConversation,
|
|
197
|
+
createAndActivateConversationInDirectory,
|
|
198
|
+
openOrCreateCritiqueConversationInDirectory,
|
|
199
|
+
takeoverConversation,
|
|
200
|
+
scheduleConversationTitlePersist,
|
|
201
|
+
stopConversationTitleEdit,
|
|
202
|
+
beginConversationTitleEdit,
|
|
203
|
+
clearConversationTitleEditTimer,
|
|
204
|
+
hydrateConversationList,
|
|
205
|
+
queuePersistedConversationsInBackground,
|
|
206
|
+
};
|
|
176
207
|
}
|
|
@@ -19,24 +19,28 @@ export interface ConversationStartupHydrationServiceOptions<
|
|
|
19
19
|
readonly subscribeConversationEvents: (sessionId: string) => Promise<void>;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
export
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
) {}
|
|
22
|
+
export interface ConversationStartupHydrationService {
|
|
23
|
+
hydrateConversationList(): Promise<void>;
|
|
24
|
+
}
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
export function createConversationStartupHydrationService<
|
|
27
|
+
TSessionSummary extends SessionSummaryLike,
|
|
28
|
+
>(
|
|
29
|
+
options: ConversationStartupHydrationServiceOptions<TSessionSummary>,
|
|
30
|
+
): ConversationStartupHydrationService {
|
|
31
|
+
async function hydrateConversationList(): Promise<void> {
|
|
32
|
+
const hydrateSpan = options.startHydrationSpan();
|
|
33
|
+
await options.hydrateDirectoryList();
|
|
30
34
|
let persistedCount = 0;
|
|
31
|
-
for (const directoryId of
|
|
32
|
-
persistedCount += await
|
|
35
|
+
for (const directoryId of options.directoryIds()) {
|
|
36
|
+
persistedCount += await options.hydratePersistedConversationsForDirectory(directoryId);
|
|
33
37
|
}
|
|
34
38
|
|
|
35
|
-
const summaries = await
|
|
39
|
+
const summaries = await options.listSessions();
|
|
36
40
|
for (const summary of summaries) {
|
|
37
|
-
|
|
41
|
+
options.upsertFromSessionSummary(summary);
|
|
38
42
|
if (summary.live) {
|
|
39
|
-
await
|
|
43
|
+
await options.subscribeConversationEvents(summary.sessionId);
|
|
40
44
|
}
|
|
41
45
|
}
|
|
42
46
|
hydrateSpan.end({
|
|
@@ -44,4 +48,8 @@ export class ConversationStartupHydrationService<TSessionSummary extends Session
|
|
|
44
48
|
live: summaries.length,
|
|
45
49
|
});
|
|
46
50
|
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
hydrateConversationList,
|
|
54
|
+
};
|
|
47
55
|
}
|
|
@@ -8,7 +8,7 @@ interface DirectoryHydrationControlPlane<TDirectoryRecord extends DirectoryRecor
|
|
|
8
8
|
upsertDirectory(input: { directoryId: string; path: string }): Promise<TDirectoryRecord>;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
interface DirectoryHydrationServiceOptions<TDirectoryRecord extends DirectoryRecordLike> {
|
|
11
|
+
export interface DirectoryHydrationServiceOptions<TDirectoryRecord extends DirectoryRecordLike> {
|
|
12
12
|
readonly controlPlaneService: DirectoryHydrationControlPlane<TDirectoryRecord>;
|
|
13
13
|
readonly resolveWorkspacePathForMux: (rawPath: string) => string;
|
|
14
14
|
readonly clearDirectories: () => void;
|
|
@@ -18,32 +18,37 @@ interface DirectoryHydrationServiceOptions<TDirectoryRecord extends DirectoryRec
|
|
|
18
18
|
readonly resolveActiveDirectoryId: () => string | null;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
export
|
|
22
|
-
|
|
21
|
+
export interface DirectoryHydrationService {
|
|
22
|
+
hydrate(): Promise<void>;
|
|
23
|
+
}
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
export function createDirectoryHydrationService<TDirectoryRecord extends DirectoryRecordLike>(
|
|
26
|
+
options: DirectoryHydrationServiceOptions<TDirectoryRecord>,
|
|
27
|
+
): DirectoryHydrationService {
|
|
28
|
+
async function hydrate(): Promise<void> {
|
|
29
|
+
const rows = await options.controlPlaneService.listDirectories();
|
|
30
|
+
options.clearDirectories();
|
|
27
31
|
for (const row of rows) {
|
|
28
|
-
const normalizedPath =
|
|
32
|
+
const normalizedPath = options.resolveWorkspacePathForMux(row.path);
|
|
29
33
|
if (normalizedPath !== row.path) {
|
|
30
|
-
const repairedRecord = await
|
|
34
|
+
const repairedRecord = await options.controlPlaneService.upsertDirectory({
|
|
31
35
|
directoryId: row.directoryId,
|
|
32
36
|
path: normalizedPath,
|
|
33
37
|
});
|
|
34
|
-
|
|
38
|
+
options.setDirectory(row.directoryId, repairedRecord);
|
|
35
39
|
continue;
|
|
36
40
|
}
|
|
37
|
-
|
|
41
|
+
options.setDirectory(row.directoryId, row);
|
|
38
42
|
}
|
|
39
|
-
if (!
|
|
40
|
-
|
|
41
|
-
this.options.persistedDirectory.directoryId,
|
|
42
|
-
this.options.persistedDirectory,
|
|
43
|
-
);
|
|
43
|
+
if (!options.hasDirectory(options.persistedDirectory.directoryId)) {
|
|
44
|
+
options.setDirectory(options.persistedDirectory.directoryId, options.persistedDirectory);
|
|
44
45
|
}
|
|
45
|
-
if (
|
|
46
|
+
if (options.resolveActiveDirectoryId() === null) {
|
|
46
47
|
throw new Error('no active directory available after hydrate');
|
|
47
48
|
}
|
|
48
49
|
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
hydrate,
|
|
53
|
+
};
|
|
49
54
|
}
|
|
@@ -23,6 +23,10 @@ interface EventPersistenceOptions {
|
|
|
23
23
|
const DEFAULT_FLUSH_DELAY_MS = 12;
|
|
24
24
|
const DEFAULT_FLUSH_MAX_BATCH = 64;
|
|
25
25
|
|
|
26
|
+
function shouldPersistEvent(event: NormalizedEventEnvelope): boolean {
|
|
27
|
+
return !(event.source === 'provider' && event.type === 'provider-text-delta');
|
|
28
|
+
}
|
|
29
|
+
|
|
26
30
|
export class EventPersistence {
|
|
27
31
|
private readonly flushDelayMs: number;
|
|
28
32
|
private readonly flushMaxBatch: number;
|
|
@@ -46,6 +50,9 @@ export class EventPersistence {
|
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
enqueue(event: NormalizedEventEnvelope): void {
|
|
53
|
+
if (!shouldPersistEvent(event)) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
49
56
|
this.pendingEvents.push(event);
|
|
50
57
|
if (this.pendingEvents.length >= this.flushMaxBatch) {
|
|
51
58
|
this.flush('immediate');
|