@jmoyers/harness 0.1.0
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/LICENSE +21 -0
- package/README.md +145 -0
- package/native/ptyd/Cargo.lock +16 -0
- package/native/ptyd/Cargo.toml +7 -0
- package/native/ptyd/src/main.rs +257 -0
- package/package.json +90 -0
- package/scripts/build-ptyd.sh +73 -0
- package/scripts/control-plane-daemon.ts +277 -0
- package/scripts/cursor-hook-relay.ts +82 -0
- package/scripts/harness-animate.ts +469 -0
- package/scripts/harness-bin.js +77 -0
- package/scripts/harness-core.ts +1 -0
- package/scripts/harness-inspector.ts +439 -0
- package/scripts/harness.ts +2493 -0
- package/src/adapters/agent-session-state.ts +390 -0
- package/src/cli/gateway-record.ts +173 -0
- package/src/codex/live-session.ts +872 -0
- package/src/config/config-core.ts +1359 -0
- package/src/config/secrets-core.ts +170 -0
- package/src/control-plane/agent-realtime-api.ts +2441 -0
- package/src/control-plane/codex-session-stream.ts +392 -0
- package/src/control-plane/codex-telemetry.ts +1325 -0
- package/src/control-plane/lifecycle-hooks.ts +706 -0
- package/src/control-plane/session-summary.ts +380 -0
- package/src/control-plane/status/agent-status-reducer.ts +21 -0
- package/src/control-plane/status/reducer-base.ts +170 -0
- package/src/control-plane/status/reducers/claude-status-reducer.ts +37 -0
- package/src/control-plane/status/reducers/codex-status-reducer.ts +48 -0
- package/src/control-plane/status/reducers/critique-status-reducer.ts +15 -0
- package/src/control-plane/status/reducers/cursor-status-reducer.ts +37 -0
- package/src/control-plane/status/reducers/terminal-status-reducer.ts +15 -0
- package/src/control-plane/status/session-status-engine.ts +76 -0
- package/src/control-plane/stream-client.ts +396 -0
- package/src/control-plane/stream-command-parser.ts +1673 -0
- package/src/control-plane/stream-protocol.ts +1808 -0
- package/src/control-plane/stream-server-background.ts +486 -0
- package/src/control-plane/stream-server-command.ts +2557 -0
- package/src/control-plane/stream-server-connection.ts +234 -0
- package/src/control-plane/stream-server-observed-filter.ts +112 -0
- package/src/control-plane/stream-server-session-runtime.ts +566 -0
- package/src/control-plane/stream-server-state-store.ts +15 -0
- package/src/control-plane/stream-server.ts +3192 -0
- package/src/cursor/managed-hooks.ts +282 -0
- package/src/domain/conversations.ts +414 -0
- package/src/domain/directories.ts +78 -0
- package/src/domain/repositories.ts +123 -0
- package/src/domain/tasks.ts +148 -0
- package/src/domain/workspace.ts +156 -0
- package/src/events/normalized-events.ts +124 -0
- package/src/mux/ansi-integrity.ts +103 -0
- package/src/mux/control-plane-op-queue.ts +212 -0
- package/src/mux/conversation-rail.ts +339 -0
- package/src/mux/double-click.ts +78 -0
- package/src/mux/dual-pane-core.ts +435 -0
- package/src/mux/harness-core-ui.ts +817 -0
- package/src/mux/input-shortcuts.ts +667 -0
- package/src/mux/live-mux/actions-conversation.ts +344 -0
- package/src/mux/live-mux/actions-repository.ts +246 -0
- package/src/mux/live-mux/actions-task.ts +115 -0
- package/src/mux/live-mux/args.ts +142 -0
- package/src/mux/live-mux/command-menu.ts +298 -0
- package/src/mux/live-mux/control-plane-records.ts +546 -0
- package/src/mux/live-mux/conversation-state.ts +188 -0
- package/src/mux/live-mux/directory-resolution.ts +34 -0
- package/src/mux/live-mux/event-mapping.ts +96 -0
- package/src/mux/live-mux/gateway-profiler.ts +152 -0
- package/src/mux/live-mux/gateway-render-trace.ts +177 -0
- package/src/mux/live-mux/gateway-status-timeline.ts +166 -0
- package/src/mux/live-mux/git-parsing.ts +131 -0
- package/src/mux/live-mux/git-snapshot.ts +263 -0
- package/src/mux/live-mux/git-state.ts +136 -0
- package/src/mux/live-mux/global-shortcut-handlers.ts +143 -0
- package/src/mux/live-mux/home-pane-actions.ts +58 -0
- package/src/mux/live-mux/home-pane-drop.ts +44 -0
- package/src/mux/live-mux/home-pane-entity-click.ts +96 -0
- package/src/mux/live-mux/home-pane-pointer.ts +96 -0
- package/src/mux/live-mux/input-forwarding.ts +112 -0
- package/src/mux/live-mux/layout.ts +30 -0
- package/src/mux/live-mux/left-nav-activation.ts +103 -0
- package/src/mux/live-mux/left-nav.ts +85 -0
- package/src/mux/live-mux/left-rail-actions.ts +118 -0
- package/src/mux/live-mux/left-rail-conversation-click.ts +82 -0
- package/src/mux/live-mux/left-rail-pointer.ts +74 -0
- package/src/mux/live-mux/modal-command-menu-handler.ts +101 -0
- package/src/mux/live-mux/modal-conversation-handlers.ts +217 -0
- package/src/mux/live-mux/modal-input-reducers.ts +94 -0
- package/src/mux/live-mux/modal-overlays.ts +287 -0
- package/src/mux/live-mux/modal-pointer.ts +70 -0
- package/src/mux/live-mux/modal-prompt-handlers.ts +187 -0
- package/src/mux/live-mux/modal-task-editor-handler.ts +156 -0
- package/src/mux/live-mux/observed-stream.ts +87 -0
- package/src/mux/live-mux/palette-parsing.ts +128 -0
- package/src/mux/live-mux/pointer-routing.ts +108 -0
- package/src/mux/live-mux/process-usage.ts +53 -0
- package/src/mux/live-mux/project-pane-pointer.ts +44 -0
- package/src/mux/live-mux/rail-layout.ts +244 -0
- package/src/mux/live-mux/render-trace-analysis.ts +213 -0
- package/src/mux/live-mux/render-trace-state.ts +84 -0
- package/src/mux/live-mux/repository-folding.ts +207 -0
- package/src/mux/live-mux/runtime-shutdown.ts +51 -0
- package/src/mux/live-mux/selection.ts +411 -0
- package/src/mux/live-mux/startup-utils.ts +187 -0
- package/src/mux/live-mux/status-timeline-state.ts +82 -0
- package/src/mux/live-mux/task-pane-shortcuts.ts +206 -0
- package/src/mux/live-mux/terminal-palette.ts +79 -0
- package/src/mux/new-thread-prompt.ts +165 -0
- package/src/mux/project-tree.ts +295 -0
- package/src/mux/render-frame.ts +113 -0
- package/src/mux/runtime-wiring.ts +185 -0
- package/src/mux/selector-index.ts +160 -0
- package/src/mux/startup-sequencer.ts +238 -0
- package/src/mux/task-composer.ts +289 -0
- package/src/mux/task-focused-pane.ts +417 -0
- package/src/mux/task-screen-keybindings.ts +539 -0
- package/src/mux/terminal-input-modes.ts +35 -0
- package/src/mux/workspace-path.ts +55 -0
- package/src/mux/workspace-rail-model.ts +701 -0
- package/src/mux/workspace-rail.ts +247 -0
- package/src/perf/perf-core.ts +307 -0
- package/src/pty/pty_host.ts +217 -0
- package/src/pty/session-broker.ts +158 -0
- package/src/recording/terminal-recording.ts +383 -0
- package/src/services/control-plane.ts +567 -0
- package/src/services/conversation-lifecycle.ts +176 -0
- package/src/services/conversation-startup-hydration.ts +47 -0
- package/src/services/directory-hydration.ts +49 -0
- package/src/services/event-persistence.ts +104 -0
- package/src/services/mux-ui-state-persistence.ts +82 -0
- package/src/services/output-load-sampler.ts +231 -0
- package/src/services/process-usage-refresh.ts +88 -0
- package/src/services/recording.ts +75 -0
- package/src/services/render-trace-recorder.ts +177 -0
- package/src/services/runtime-control-actions.ts +123 -0
- package/src/services/runtime-control-plane-ops.ts +131 -0
- package/src/services/runtime-conversation-actions.ts +113 -0
- package/src/services/runtime-conversation-activation.ts +78 -0
- package/src/services/runtime-conversation-starter.ts +171 -0
- package/src/services/runtime-conversation-title-edit.ts +149 -0
- package/src/services/runtime-directory-actions.ts +164 -0
- package/src/services/runtime-envelope-handler.ts +198 -0
- package/src/services/runtime-git-state.ts +92 -0
- package/src/services/runtime-input-pipeline.ts +50 -0
- package/src/services/runtime-input-router.ts +202 -0
- package/src/services/runtime-layout-resize.ts +236 -0
- package/src/services/runtime-left-rail-render.ts +159 -0
- package/src/services/runtime-main-pane-input.ts +230 -0
- package/src/services/runtime-modal-input.ts +119 -0
- package/src/services/runtime-navigation-input.ts +207 -0
- package/src/services/runtime-process-wiring.ts +68 -0
- package/src/services/runtime-rail-input.ts +287 -0
- package/src/services/runtime-render-flush.ts +146 -0
- package/src/services/runtime-render-lifecycle.ts +104 -0
- package/src/services/runtime-render-orchestrator.ts +108 -0
- package/src/services/runtime-render-pipeline.ts +167 -0
- package/src/services/runtime-render-state.ts +72 -0
- package/src/services/runtime-repository-actions.ts +197 -0
- package/src/services/runtime-right-pane-render.ts +132 -0
- package/src/services/runtime-shutdown.ts +79 -0
- package/src/services/runtime-stream-subscriptions.ts +56 -0
- package/src/services/runtime-task-composer-persistence.ts +139 -0
- package/src/services/runtime-task-editor-actions.ts +83 -0
- package/src/services/runtime-task-pane-actions.ts +198 -0
- package/src/services/runtime-task-pane-shortcuts.ts +189 -0
- package/src/services/runtime-task-pane.ts +62 -0
- package/src/services/runtime-workspace-actions.ts +153 -0
- package/src/services/runtime-workspace-observed-events.ts +190 -0
- package/src/services/session-projection-instrumentation.ts +190 -0
- package/src/services/startup-background-probe.ts +91 -0
- package/src/services/startup-background-resume.ts +65 -0
- package/src/services/startup-orchestrator.ts +166 -0
- package/src/services/startup-output-tracker.ts +54 -0
- package/src/services/startup-paint-tracker.ts +115 -0
- package/src/services/startup-persisted-conversation-queue.ts +45 -0
- package/src/services/startup-settled-gate.ts +67 -0
- package/src/services/startup-shutdown.ts +53 -0
- package/src/services/startup-span-tracker.ts +77 -0
- package/src/services/startup-state-hydration.ts +94 -0
- package/src/services/startup-visibility.ts +35 -0
- package/src/services/status-timeline-recorder.ts +144 -0
- package/src/services/task-pane-selection-actions.ts +153 -0
- package/src/services/task-planning-hydration.ts +58 -0
- package/src/services/task-planning-observed-events.ts +89 -0
- package/src/services/workspace-observed-events.ts +113 -0
- package/src/store/control-plane-store-normalize.ts +760 -0
- package/src/store/control-plane-store-types.ts +224 -0
- package/src/store/control-plane-store.ts +2951 -0
- package/src/store/event-store.ts +253 -0
- package/src/store/sqlite.ts +81 -0
- package/src/terminal/compat-matrix.ts +345 -0
- package/src/terminal/differential-checkpoints.ts +132 -0
- package/src/terminal/parity-suite.ts +441 -0
- package/src/terminal/snapshot-oracle.ts +1840 -0
- package/src/ui/conversation-input-forwarder.ts +114 -0
- package/src/ui/conversation-selection-input.ts +103 -0
- package/src/ui/debug-footer-notice.ts +39 -0
- package/src/ui/global-shortcut-input.ts +126 -0
- package/src/ui/input-preflight.ts +68 -0
- package/src/ui/input-token-router.ts +312 -0
- package/src/ui/input.ts +238 -0
- package/src/ui/kit.ts +509 -0
- package/src/ui/left-nav-input.ts +80 -0
- package/src/ui/left-rail-pointer-input.ts +148 -0
- package/src/ui/main-pane-pointer-input.ts +150 -0
- package/src/ui/modals/manager.ts +192 -0
- package/src/ui/mux-theme.ts +529 -0
- package/src/ui/panes/conversation.ts +19 -0
- package/src/ui/panes/home-gridfire.ts +302 -0
- package/src/ui/panes/home.ts +109 -0
- package/src/ui/panes/left-rail.ts +12 -0
- package/src/ui/panes/project.ts +44 -0
- package/src/ui/pointer-routing-input.ts +158 -0
- package/src/ui/repository-fold-input.ts +91 -0
- package/src/ui/screen.ts +210 -0
- package/src/ui/surface.ts +224 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import type { WorkspaceModel } from '../domain/workspace.ts';
|
|
2
|
+
|
|
3
|
+
interface TaskRecordLike {
|
|
4
|
+
readonly taskId: string;
|
|
5
|
+
readonly repositoryId: string | null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface RepositoryRecordLike {
|
|
9
|
+
readonly archivedAt: string | null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface TaskPaneSelectionActionsOptions<TTaskRecord extends TaskRecordLike> {
|
|
13
|
+
readonly workspace: WorkspaceModel;
|
|
14
|
+
readonly taskRecordById: (taskId: string) => TTaskRecord | undefined;
|
|
15
|
+
readonly hasTask: (taskId: string) => boolean;
|
|
16
|
+
readonly hasRepository: (repositoryId: string) => boolean;
|
|
17
|
+
readonly repositoryById: (repositoryId: string) => RepositoryRecordLike | undefined;
|
|
18
|
+
readonly selectedRepositoryTasks: () => readonly TTaskRecord[];
|
|
19
|
+
readonly activeRepositoryIds: () => readonly string[];
|
|
20
|
+
readonly flushTaskComposerPersist: (taskId: string) => void;
|
|
21
|
+
readonly markDirty: () => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class TaskPaneSelectionActions<TTaskRecord extends TaskRecordLike> {
|
|
25
|
+
constructor(private readonly options: TaskPaneSelectionActionsOptions<TTaskRecord>) {}
|
|
26
|
+
|
|
27
|
+
syncTaskPaneSelectionFocus(): void {
|
|
28
|
+
const hasTaskSelection =
|
|
29
|
+
this.options.workspace.taskPaneSelectedTaskId !== null &&
|
|
30
|
+
this.options.hasTask(this.options.workspace.taskPaneSelectedTaskId);
|
|
31
|
+
const hasRepositorySelection =
|
|
32
|
+
this.options.workspace.taskPaneSelectedRepositoryId !== null &&
|
|
33
|
+
this.options.hasRepository(this.options.workspace.taskPaneSelectedRepositoryId);
|
|
34
|
+
if (this.options.workspace.taskPaneSelectionFocus === 'task' && hasTaskSelection) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (this.options.workspace.taskPaneSelectionFocus === 'repository' && hasRepositorySelection) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (hasTaskSelection) {
|
|
41
|
+
this.options.workspace.taskPaneSelectionFocus = 'task';
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (hasRepositorySelection) {
|
|
45
|
+
this.options.workspace.taskPaneSelectionFocus = 'repository';
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
this.options.workspace.taskPaneSelectionFocus = 'task';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
syncTaskPaneSelection(): void {
|
|
52
|
+
const scopedTaskIds = new Set(
|
|
53
|
+
this.options.selectedRepositoryTasks().map((task) => task.taskId),
|
|
54
|
+
);
|
|
55
|
+
if (
|
|
56
|
+
this.options.workspace.taskPaneSelectedTaskId !== null &&
|
|
57
|
+
!scopedTaskIds.has(this.options.workspace.taskPaneSelectedTaskId)
|
|
58
|
+
) {
|
|
59
|
+
this.options.workspace.taskPaneSelectedTaskId = null;
|
|
60
|
+
}
|
|
61
|
+
if (this.options.workspace.taskPaneSelectedTaskId === null) {
|
|
62
|
+
const scopedTasks = this.options.selectedRepositoryTasks();
|
|
63
|
+
this.options.workspace.taskPaneSelectedTaskId = scopedTasks[0]?.taskId ?? null;
|
|
64
|
+
}
|
|
65
|
+
this.syncTaskPaneSelectionFocus();
|
|
66
|
+
if (
|
|
67
|
+
this.options.workspace.taskEditorTarget.kind === 'task' &&
|
|
68
|
+
!scopedTaskIds.has(this.options.workspace.taskEditorTarget.taskId)
|
|
69
|
+
) {
|
|
70
|
+
this.focusDraftComposer();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
syncTaskPaneRepositorySelection(): void {
|
|
75
|
+
if (this.options.workspace.taskPaneSelectedRepositoryId !== null) {
|
|
76
|
+
const selectedRepository = this.options.repositoryById(
|
|
77
|
+
this.options.workspace.taskPaneSelectedRepositoryId,
|
|
78
|
+
);
|
|
79
|
+
if (selectedRepository === undefined || selectedRepository.archivedAt !== null) {
|
|
80
|
+
this.options.workspace.taskPaneSelectedRepositoryId = null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (this.options.workspace.taskPaneSelectedRepositoryId === null) {
|
|
84
|
+
this.options.workspace.taskPaneSelectedRepositoryId =
|
|
85
|
+
this.options.activeRepositoryIds()[0] ?? null;
|
|
86
|
+
}
|
|
87
|
+
this.options.workspace.taskRepositoryDropdownOpen = false;
|
|
88
|
+
this.syncTaskPaneSelectionFocus();
|
|
89
|
+
this.syncTaskPaneSelection();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
focusDraftComposer(): void {
|
|
93
|
+
if (this.options.workspace.taskEditorTarget.kind === 'task') {
|
|
94
|
+
this.options.flushTaskComposerPersist(this.options.workspace.taskEditorTarget.taskId);
|
|
95
|
+
}
|
|
96
|
+
this.options.workspace.taskEditorTarget = {
|
|
97
|
+
kind: 'draft',
|
|
98
|
+
};
|
|
99
|
+
this.options.workspace.taskPaneSelectionFocus = 'task';
|
|
100
|
+
this.options.markDirty();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
focusTaskComposer(taskId: string): void {
|
|
104
|
+
if (!this.options.hasTask(taskId)) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (
|
|
108
|
+
this.options.workspace.taskEditorTarget.kind === 'task' &&
|
|
109
|
+
this.options.workspace.taskEditorTarget.taskId !== taskId
|
|
110
|
+
) {
|
|
111
|
+
this.options.flushTaskComposerPersist(this.options.workspace.taskEditorTarget.taskId);
|
|
112
|
+
}
|
|
113
|
+
this.options.workspace.taskEditorTarget = {
|
|
114
|
+
kind: 'task',
|
|
115
|
+
taskId,
|
|
116
|
+
};
|
|
117
|
+
this.options.workspace.taskPaneSelectedTaskId = taskId;
|
|
118
|
+
this.options.workspace.taskPaneSelectionFocus = 'task';
|
|
119
|
+
this.options.workspace.taskPaneNotice = null;
|
|
120
|
+
this.options.markDirty();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
selectTaskById(taskId: string): void {
|
|
124
|
+
const taskRecord = this.options.taskRecordById(taskId);
|
|
125
|
+
if (taskRecord === undefined) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
this.options.workspace.taskPaneSelectedTaskId = taskId;
|
|
129
|
+
this.options.workspace.taskPaneSelectionFocus = 'task';
|
|
130
|
+
if (taskRecord.repositoryId !== null && this.options.hasRepository(taskRecord.repositoryId)) {
|
|
131
|
+
this.options.workspace.taskPaneSelectedRepositoryId = taskRecord.repositoryId;
|
|
132
|
+
}
|
|
133
|
+
this.focusTaskComposer(taskId);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
selectRepositoryById(repositoryId: string): void {
|
|
137
|
+
if (!this.options.hasRepository(repositoryId)) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (this.options.workspace.taskEditorTarget.kind === 'task') {
|
|
141
|
+
this.options.flushTaskComposerPersist(this.options.workspace.taskEditorTarget.taskId);
|
|
142
|
+
}
|
|
143
|
+
this.options.workspace.taskPaneSelectedRepositoryId = repositoryId;
|
|
144
|
+
this.options.workspace.taskRepositoryDropdownOpen = false;
|
|
145
|
+
this.options.workspace.taskPaneSelectionFocus = 'repository';
|
|
146
|
+
this.options.workspace.taskEditorTarget = {
|
|
147
|
+
kind: 'draft',
|
|
148
|
+
};
|
|
149
|
+
this.syncTaskPaneSelection();
|
|
150
|
+
this.options.workspace.taskPaneNotice = null;
|
|
151
|
+
this.options.markDirty();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
interface RepositoryRecordLike {
|
|
2
|
+
readonly repositoryId: string;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
interface TaskRecordLike {
|
|
6
|
+
readonly taskId: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface TaskPlanningHydrationServiceControlPlane<
|
|
10
|
+
TRepositoryRecord extends RepositoryRecordLike,
|
|
11
|
+
TTaskRecord extends TaskRecordLike,
|
|
12
|
+
> {
|
|
13
|
+
listRepositories(): Promise<readonly TRepositoryRecord[]>;
|
|
14
|
+
listTasks(limit: number): Promise<readonly TTaskRecord[]>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface TaskPlanningHydrationServiceOptions<
|
|
18
|
+
TRepositoryRecord extends RepositoryRecordLike,
|
|
19
|
+
TTaskRecord extends TaskRecordLike,
|
|
20
|
+
> {
|
|
21
|
+
readonly controlPlaneService: TaskPlanningHydrationServiceControlPlane<
|
|
22
|
+
TRepositoryRecord,
|
|
23
|
+
TTaskRecord
|
|
24
|
+
>;
|
|
25
|
+
readonly clearRepositories: () => void;
|
|
26
|
+
readonly setRepository: (repository: TRepositoryRecord) => void;
|
|
27
|
+
readonly syncTaskPaneRepositorySelection: () => void;
|
|
28
|
+
readonly clearTasks: () => void;
|
|
29
|
+
readonly setTask: (task: TTaskRecord) => void;
|
|
30
|
+
readonly syncTaskPaneSelection: () => void;
|
|
31
|
+
readonly markDirty: () => void;
|
|
32
|
+
readonly taskLimit: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class TaskPlanningHydrationService<
|
|
36
|
+
TRepositoryRecord extends RepositoryRecordLike,
|
|
37
|
+
TTaskRecord extends TaskRecordLike,
|
|
38
|
+
> {
|
|
39
|
+
constructor(
|
|
40
|
+
private readonly options: TaskPlanningHydrationServiceOptions<TRepositoryRecord, TTaskRecord>,
|
|
41
|
+
) {}
|
|
42
|
+
|
|
43
|
+
async hydrate(): Promise<void> {
|
|
44
|
+
this.options.clearRepositories();
|
|
45
|
+
for (const repository of await this.options.controlPlaneService.listRepositories()) {
|
|
46
|
+
this.options.setRepository(repository);
|
|
47
|
+
}
|
|
48
|
+
this.options.syncTaskPaneRepositorySelection();
|
|
49
|
+
|
|
50
|
+
this.options.clearTasks();
|
|
51
|
+
for (const task of await this.options.controlPlaneService.listTasks(this.options.taskLimit)) {
|
|
52
|
+
this.options.setTask(task);
|
|
53
|
+
}
|
|
54
|
+
this.options.syncTaskPaneSelection();
|
|
55
|
+
this.options.syncTaskPaneRepositorySelection();
|
|
56
|
+
this.options.markDirty();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { StreamObservedEvent } from '../control-plane/stream-protocol.ts';
|
|
2
|
+
|
|
3
|
+
interface RepositoryRecordLike {
|
|
4
|
+
readonly repositoryId: string;
|
|
5
|
+
readonly archivedAt: string | null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface TaskRecordLike {
|
|
9
|
+
readonly taskId: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface TaskPlanningObservedEventsOptions<
|
|
13
|
+
TRepositoryRecord extends RepositoryRecordLike,
|
|
14
|
+
TTaskRecord extends TaskRecordLike,
|
|
15
|
+
> {
|
|
16
|
+
readonly parseRepositoryRecord: (value: unknown) => TRepositoryRecord | null;
|
|
17
|
+
readonly parseTaskRecord: (value: unknown) => TTaskRecord | null;
|
|
18
|
+
readonly getRepository: (repositoryId: string) => TRepositoryRecord | undefined;
|
|
19
|
+
readonly setRepository: (repositoryId: string, repository: TRepositoryRecord) => void;
|
|
20
|
+
readonly setTask: (task: TTaskRecord) => void;
|
|
21
|
+
readonly deleteTask: (taskId: string) => boolean;
|
|
22
|
+
readonly syncTaskPaneRepositorySelection: () => void;
|
|
23
|
+
readonly syncTaskPaneSelection: () => void;
|
|
24
|
+
readonly markDirty: () => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class TaskPlanningObservedEvents<
|
|
28
|
+
TRepositoryRecord extends RepositoryRecordLike,
|
|
29
|
+
TTaskRecord extends TaskRecordLike,
|
|
30
|
+
> {
|
|
31
|
+
constructor(
|
|
32
|
+
private readonly options: TaskPlanningObservedEventsOptions<TRepositoryRecord, TTaskRecord>,
|
|
33
|
+
) {}
|
|
34
|
+
|
|
35
|
+
apply(observed: StreamObservedEvent): void {
|
|
36
|
+
if (observed.type === 'repository-upserted' || observed.type === 'repository-updated') {
|
|
37
|
+
const repository = this.options.parseRepositoryRecord(observed.repository);
|
|
38
|
+
if (repository !== null) {
|
|
39
|
+
this.options.setRepository(repository.repositoryId, repository);
|
|
40
|
+
this.options.syncTaskPaneRepositorySelection();
|
|
41
|
+
this.options.markDirty();
|
|
42
|
+
}
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (observed.type === 'repository-archived') {
|
|
46
|
+
const repository = this.options.getRepository(observed.repositoryId);
|
|
47
|
+
if (repository !== undefined) {
|
|
48
|
+
this.options.setRepository(observed.repositoryId, {
|
|
49
|
+
...repository,
|
|
50
|
+
archivedAt: observed.ts,
|
|
51
|
+
});
|
|
52
|
+
this.options.syncTaskPaneRepositorySelection();
|
|
53
|
+
this.options.markDirty();
|
|
54
|
+
}
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (observed.type === 'task-created' || observed.type === 'task-updated') {
|
|
58
|
+
const task = this.options.parseTaskRecord(observed.task);
|
|
59
|
+
if (task !== null) {
|
|
60
|
+
this.options.setTask(task);
|
|
61
|
+
this.options.syncTaskPaneSelection();
|
|
62
|
+
this.options.markDirty();
|
|
63
|
+
}
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (observed.type === 'task-deleted') {
|
|
67
|
+
if (this.options.deleteTask(observed.taskId)) {
|
|
68
|
+
this.options.syncTaskPaneSelection();
|
|
69
|
+
this.options.markDirty();
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (observed.type === 'task-reordered') {
|
|
74
|
+
let changed = false;
|
|
75
|
+
for (const value of observed.tasks) {
|
|
76
|
+
const task = this.options.parseTaskRecord(value);
|
|
77
|
+
if (task === null) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
this.options.setTask(task);
|
|
81
|
+
changed = true;
|
|
82
|
+
}
|
|
83
|
+
if (changed) {
|
|
84
|
+
this.options.syncTaskPaneSelection();
|
|
85
|
+
this.options.markDirty();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { StreamObservedEvent } from '../control-plane/stream-protocol.ts';
|
|
2
|
+
|
|
3
|
+
interface DirectoryRecordLike {
|
|
4
|
+
readonly directoryId: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface ConversationRecordLike {
|
|
8
|
+
readonly conversationId: string;
|
|
9
|
+
readonly directoryId: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface WorkspaceObservedApplyResult {
|
|
13
|
+
readonly changed: boolean;
|
|
14
|
+
readonly removedConversationIds: readonly string[];
|
|
15
|
+
readonly removedDirectoryIds: readonly string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface WorkspaceObservedEventsOptions<
|
|
19
|
+
TDirectoryRecord extends DirectoryRecordLike,
|
|
20
|
+
TConversationRecord extends ConversationRecordLike,
|
|
21
|
+
> {
|
|
22
|
+
readonly parseDirectoryRecord: (value: unknown) => TDirectoryRecord | null;
|
|
23
|
+
readonly parseConversationRecord: (value: unknown) => TConversationRecord | null;
|
|
24
|
+
readonly setDirectory: (directoryId: string, directory: TDirectoryRecord) => void;
|
|
25
|
+
readonly deleteDirectory: (directoryId: string) => boolean;
|
|
26
|
+
readonly deleteDirectoryGitState: (directoryId: string) => void;
|
|
27
|
+
readonly syncGitStateWithDirectories: () => void;
|
|
28
|
+
readonly upsertConversationFromPersistedRecord: (record: TConversationRecord) => void;
|
|
29
|
+
readonly removeConversation: (sessionId: string) => boolean;
|
|
30
|
+
readonly orderedConversationIds: () => readonly string[];
|
|
31
|
+
readonly conversationDirectoryId: (sessionId: string) => string | null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class WorkspaceObservedEvents<
|
|
35
|
+
TDirectoryRecord extends DirectoryRecordLike,
|
|
36
|
+
TConversationRecord extends ConversationRecordLike,
|
|
37
|
+
> {
|
|
38
|
+
constructor(
|
|
39
|
+
private readonly options: WorkspaceObservedEventsOptions<TDirectoryRecord, TConversationRecord>,
|
|
40
|
+
) {}
|
|
41
|
+
|
|
42
|
+
apply(observed: StreamObservedEvent): WorkspaceObservedApplyResult {
|
|
43
|
+
if (observed.type === 'directory-upserted') {
|
|
44
|
+
const directory = this.options.parseDirectoryRecord(observed.directory);
|
|
45
|
+
if (directory === null) {
|
|
46
|
+
return {
|
|
47
|
+
changed: false,
|
|
48
|
+
removedConversationIds: [],
|
|
49
|
+
removedDirectoryIds: [],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
this.options.setDirectory(directory.directoryId, directory);
|
|
53
|
+
this.options.syncGitStateWithDirectories();
|
|
54
|
+
return {
|
|
55
|
+
changed: true,
|
|
56
|
+
removedConversationIds: [],
|
|
57
|
+
removedDirectoryIds: [],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (observed.type === 'directory-archived') {
|
|
62
|
+
const removedConversationIds: string[] = [];
|
|
63
|
+
for (const sessionId of this.options.orderedConversationIds()) {
|
|
64
|
+
if (this.options.conversationDirectoryId(sessionId) !== observed.directoryId) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (this.options.removeConversation(sessionId)) {
|
|
68
|
+
removedConversationIds.push(sessionId);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const removedDirectory = this.options.deleteDirectory(observed.directoryId);
|
|
72
|
+
this.options.deleteDirectoryGitState(observed.directoryId);
|
|
73
|
+
this.options.syncGitStateWithDirectories();
|
|
74
|
+
return {
|
|
75
|
+
changed: removedDirectory || removedConversationIds.length > 0,
|
|
76
|
+
removedConversationIds,
|
|
77
|
+
removedDirectoryIds: removedDirectory ? [observed.directoryId] : [],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (observed.type === 'conversation-created' || observed.type === 'conversation-updated') {
|
|
82
|
+
const conversation = this.options.parseConversationRecord(observed.conversation);
|
|
83
|
+
if (conversation === null) {
|
|
84
|
+
return {
|
|
85
|
+
changed: false,
|
|
86
|
+
removedConversationIds: [],
|
|
87
|
+
removedDirectoryIds: [],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
this.options.upsertConversationFromPersistedRecord(conversation);
|
|
91
|
+
return {
|
|
92
|
+
changed: true,
|
|
93
|
+
removedConversationIds: [],
|
|
94
|
+
removedDirectoryIds: [],
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (observed.type === 'conversation-archived' || observed.type === 'conversation-deleted') {
|
|
99
|
+
const removed = this.options.removeConversation(observed.conversationId);
|
|
100
|
+
return {
|
|
101
|
+
changed: removed,
|
|
102
|
+
removedConversationIds: removed ? [observed.conversationId] : [],
|
|
103
|
+
removedDirectoryIds: [],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
changed: false,
|
|
109
|
+
removedConversationIds: [],
|
|
110
|
+
removedDirectoryIds: [],
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|