@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
|
@@ -7,50 +7,58 @@ export interface RuntimeStreamSubscriptionsOptions {
|
|
|
7
7
|
readonly unsubscribeObservedStream: (subscriptionId: string) => Promise<void>;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
export
|
|
11
|
-
|
|
10
|
+
export interface RuntimeStreamSubscriptions {
|
|
11
|
+
subscribeConversationEvents(sessionId: string): Promise<void>;
|
|
12
|
+
unsubscribeConversationEvents(sessionId: string): Promise<void>;
|
|
13
|
+
subscribeTaskPlanningEvents(afterCursor: number | null): Promise<void>;
|
|
14
|
+
unsubscribeTaskPlanningEvents(): Promise<void>;
|
|
15
|
+
}
|
|
12
16
|
|
|
13
|
-
|
|
17
|
+
export function createRuntimeStreamSubscriptions(
|
|
18
|
+
options: RuntimeStreamSubscriptionsOptions,
|
|
19
|
+
): RuntimeStreamSubscriptions {
|
|
20
|
+
let observedStreamSubscriptionId: string | null = null;
|
|
14
21
|
|
|
15
|
-
async
|
|
22
|
+
const subscribeConversationEvents = async (sessionId: string): Promise<void> => {
|
|
16
23
|
try {
|
|
17
|
-
await
|
|
24
|
+
await options.subscribePtyEvents(sessionId);
|
|
18
25
|
} catch (error: unknown) {
|
|
19
|
-
if (
|
|
20
|
-
!this.options.isSessionNotFoundError(error) &&
|
|
21
|
-
!this.options.isSessionNotLiveError(error)
|
|
22
|
-
) {
|
|
26
|
+
if (!options.isSessionNotFoundError(error) && !options.isSessionNotLiveError(error)) {
|
|
23
27
|
throw error;
|
|
24
28
|
}
|
|
25
29
|
}
|
|
26
|
-
}
|
|
30
|
+
};
|
|
27
31
|
|
|
28
|
-
async
|
|
32
|
+
const unsubscribeConversationEvents = async (sessionId: string): Promise<void> => {
|
|
29
33
|
try {
|
|
30
|
-
await
|
|
34
|
+
await options.unsubscribePtyEvents(sessionId);
|
|
31
35
|
} catch (error: unknown) {
|
|
32
|
-
if (
|
|
33
|
-
!this.options.isSessionNotFoundError(error) &&
|
|
34
|
-
!this.options.isSessionNotLiveError(error)
|
|
35
|
-
) {
|
|
36
|
+
if (!options.isSessionNotFoundError(error) && !options.isSessionNotLiveError(error)) {
|
|
36
37
|
throw error;
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
|
-
}
|
|
40
|
+
};
|
|
40
41
|
|
|
41
|
-
async
|
|
42
|
-
if (
|
|
42
|
+
const subscribeTaskPlanningEvents = async (afterCursor: number | null): Promise<void> => {
|
|
43
|
+
if (observedStreamSubscriptionId !== null) {
|
|
43
44
|
return;
|
|
44
45
|
}
|
|
45
|
-
|
|
46
|
-
}
|
|
46
|
+
observedStreamSubscriptionId = await options.subscribeObservedStream(afterCursor);
|
|
47
|
+
};
|
|
47
48
|
|
|
48
|
-
async
|
|
49
|
-
if (
|
|
49
|
+
const unsubscribeTaskPlanningEvents = async (): Promise<void> => {
|
|
50
|
+
if (observedStreamSubscriptionId === null) {
|
|
50
51
|
return;
|
|
51
52
|
}
|
|
52
|
-
const subscriptionId =
|
|
53
|
-
|
|
54
|
-
await
|
|
55
|
-
}
|
|
53
|
+
const subscriptionId = observedStreamSubscriptionId;
|
|
54
|
+
observedStreamSubscriptionId = null;
|
|
55
|
+
await options.unsubscribeObservedStream(subscriptionId);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
subscribeConversationEvents,
|
|
60
|
+
unsubscribeConversationEvents,
|
|
61
|
+
subscribeTaskPlanningEvents,
|
|
62
|
+
unsubscribeTaskPlanningEvents,
|
|
63
|
+
};
|
|
56
64
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
interface TaskComposerFields {
|
|
2
|
-
readonly title: string;
|
|
3
|
-
readonly
|
|
2
|
+
readonly title: string | null;
|
|
3
|
+
readonly body: string;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
interface TaskRecordShape {
|
|
7
7
|
readonly taskId: string;
|
|
8
8
|
readonly repositoryId: string | null;
|
|
9
9
|
readonly title: string;
|
|
10
|
-
readonly
|
|
10
|
+
readonly body: string;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
interface TaskComposerBufferShape {
|
|
@@ -33,7 +33,7 @@ interface RuntimeTaskComposerPersistenceOptions<
|
|
|
33
33
|
taskId: string;
|
|
34
34
|
repositoryId: string | null;
|
|
35
35
|
title: string;
|
|
36
|
-
|
|
36
|
+
body: string;
|
|
37
37
|
}) => Promise<TTaskRecord>;
|
|
38
38
|
readonly applyTaskRecord: (task: TTaskRecord) => void;
|
|
39
39
|
readonly queueControlPlaneOp: (task: () => Promise<void>, label: string) => void;
|
|
@@ -44,96 +44,108 @@ interface RuntimeTaskComposerPersistenceOptions<
|
|
|
44
44
|
readonly clearTimeoutFn?: (timer: TTaskAutosaveTimer) => void;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
export
|
|
48
|
-
TTaskRecord extends TaskRecordShape,
|
|
47
|
+
export interface RuntimeTaskComposerPersistenceService<
|
|
49
48
|
TTaskComposerBuffer extends TaskComposerBufferShape,
|
|
50
|
-
TTaskAutosaveTimer extends { unref?: () => void } = NodeJS.Timeout,
|
|
51
49
|
> {
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
taskComposerForTask(taskId: string): TTaskComposerBuffer | null;
|
|
51
|
+
setTaskComposerForTask(taskId: string, buffer: TTaskComposerBuffer): void;
|
|
52
|
+
clearTaskAutosaveTimer(taskId: string): void;
|
|
53
|
+
scheduleTaskComposerPersist(taskId: string): void;
|
|
54
|
+
flushTaskComposerPersist(taskId: string): void;
|
|
55
|
+
}
|
|
54
56
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
57
|
+
export function createRuntimeTaskComposerPersistenceService<
|
|
58
|
+
TTaskRecord extends TaskRecordShape,
|
|
59
|
+
TTaskComposerBuffer extends TaskComposerBufferShape,
|
|
60
|
+
TTaskAutosaveTimer extends { unref?: () => void } = NodeJS.Timeout,
|
|
61
|
+
>(
|
|
62
|
+
options: RuntimeTaskComposerPersistenceOptions<
|
|
63
|
+
TTaskRecord,
|
|
64
|
+
TTaskComposerBuffer,
|
|
65
|
+
TTaskAutosaveTimer
|
|
66
|
+
>,
|
|
67
|
+
): RuntimeTaskComposerPersistenceService<TTaskComposerBuffer> {
|
|
68
|
+
const setTimeoutFn =
|
|
69
|
+
options.setTimeoutFn ??
|
|
70
|
+
((callback, ms) => setTimeout(callback, ms) as unknown as TTaskAutosaveTimer);
|
|
71
|
+
const clearTimeoutFn =
|
|
72
|
+
options.clearTimeoutFn ?? ((timer) => clearTimeout(timer as unknown as NodeJS.Timeout));
|
|
68
73
|
|
|
69
|
-
taskComposerForTask(taskId: string): TTaskComposerBuffer | null {
|
|
70
|
-
const existing =
|
|
74
|
+
function taskComposerForTask(taskId: string): TTaskComposerBuffer | null {
|
|
75
|
+
const existing = options.getTaskComposer(taskId);
|
|
71
76
|
if (existing !== undefined) {
|
|
72
77
|
return existing;
|
|
73
78
|
}
|
|
74
|
-
const task =
|
|
79
|
+
const task = options.getTask(taskId);
|
|
75
80
|
if (task === undefined) {
|
|
76
81
|
return null;
|
|
77
82
|
}
|
|
78
|
-
return
|
|
83
|
+
return options.buildComposerFromTask(task);
|
|
79
84
|
}
|
|
80
85
|
|
|
81
|
-
setTaskComposerForTask(taskId: string, buffer: TTaskComposerBuffer): void {
|
|
82
|
-
|
|
86
|
+
function setTaskComposerForTask(taskId: string, buffer: TTaskComposerBuffer): void {
|
|
87
|
+
options.setTaskComposer(taskId, options.normalizeTaskComposerBuffer(buffer));
|
|
83
88
|
}
|
|
84
89
|
|
|
85
|
-
clearTaskAutosaveTimer(taskId: string): void {
|
|
86
|
-
const timer =
|
|
90
|
+
function clearTaskAutosaveTimer(taskId: string): void {
|
|
91
|
+
const timer = options.getTaskAutosaveTimer(taskId);
|
|
87
92
|
if (timer !== undefined) {
|
|
88
|
-
|
|
89
|
-
|
|
93
|
+
clearTimeoutFn(timer);
|
|
94
|
+
options.deleteTaskAutosaveTimer(taskId);
|
|
90
95
|
}
|
|
91
96
|
}
|
|
92
97
|
|
|
93
|
-
scheduleTaskComposerPersist(taskId: string): void {
|
|
94
|
-
|
|
95
|
-
const timer =
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
},
|
|
98
|
+
function scheduleTaskComposerPersist(taskId: string): void {
|
|
99
|
+
clearTaskAutosaveTimer(taskId);
|
|
100
|
+
const timer = setTimeoutFn(() => {
|
|
101
|
+
options.deleteTaskAutosaveTimer(taskId);
|
|
102
|
+
queuePersistTaskComposer(taskId, 'debounced');
|
|
103
|
+
}, options.autosaveDebounceMs);
|
|
99
104
|
timer.unref?.();
|
|
100
|
-
|
|
105
|
+
options.setTaskAutosaveTimer(taskId, timer);
|
|
101
106
|
}
|
|
102
107
|
|
|
103
|
-
flushTaskComposerPersist(taskId: string): void {
|
|
104
|
-
|
|
105
|
-
|
|
108
|
+
function flushTaskComposerPersist(taskId: string): void {
|
|
109
|
+
clearTaskAutosaveTimer(taskId);
|
|
110
|
+
queuePersistTaskComposer(taskId, 'flush');
|
|
106
111
|
}
|
|
107
112
|
|
|
108
|
-
|
|
109
|
-
const task =
|
|
110
|
-
const buffer =
|
|
113
|
+
function queuePersistTaskComposer(taskId: string, reason: 'debounced' | 'flush'): void {
|
|
114
|
+
const task = options.getTask(taskId);
|
|
115
|
+
const buffer = options.getTaskComposer(taskId);
|
|
111
116
|
if (task === undefined || buffer === undefined) {
|
|
112
117
|
return;
|
|
113
118
|
}
|
|
114
|
-
const fields =
|
|
115
|
-
if (fields.
|
|
116
|
-
|
|
117
|
-
|
|
119
|
+
const fields = options.taskFieldsFromComposerText(buffer.text);
|
|
120
|
+
if (fields.body.trim().length === 0) {
|
|
121
|
+
options.setTaskPaneNotice('task body is required');
|
|
122
|
+
options.markDirty();
|
|
118
123
|
return;
|
|
119
124
|
}
|
|
120
|
-
if (fields.title === task.title && fields.
|
|
125
|
+
if (fields.title === task.title && fields.body === task.body) {
|
|
121
126
|
return;
|
|
122
127
|
}
|
|
123
|
-
|
|
124
|
-
const parsed = await
|
|
128
|
+
options.queueControlPlaneOp(async () => {
|
|
129
|
+
const parsed = await options.updateTask({
|
|
125
130
|
taskId,
|
|
126
131
|
repositoryId: task.repositoryId,
|
|
127
|
-
title: fields.title,
|
|
128
|
-
|
|
132
|
+
title: fields.title ?? '',
|
|
133
|
+
body: fields.body,
|
|
129
134
|
});
|
|
130
|
-
|
|
131
|
-
const persistedText =
|
|
132
|
-
|
|
133
|
-
const latestBuffer = this.options.getTaskComposer(taskId);
|
|
135
|
+
options.applyTaskRecord(parsed);
|
|
136
|
+
const persistedText = parsed.body.length === 0 ? parsed.title : parsed.body;
|
|
137
|
+
const latestBuffer = options.getTaskComposer(taskId);
|
|
134
138
|
if (latestBuffer !== undefined && latestBuffer.text === persistedText) {
|
|
135
|
-
|
|
139
|
+
options.deleteTaskComposer(taskId);
|
|
136
140
|
}
|
|
137
141
|
}, `task-editor-save:${reason}:${taskId}`);
|
|
138
142
|
}
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
taskComposerForTask,
|
|
146
|
+
setTaskComposerForTask,
|
|
147
|
+
clearTaskAutosaveTimer,
|
|
148
|
+
scheduleTaskComposerPersist,
|
|
149
|
+
flushTaskComposerPersist,
|
|
150
|
+
};
|
|
139
151
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { TaskComposerBuffer } from '../mux/task-composer.ts';
|
|
2
|
+
|
|
3
|
+
export function snapshotTaskComposerBuffers(
|
|
4
|
+
buffers: ReadonlyMap<string, TaskComposerBuffer>,
|
|
5
|
+
): ReadonlyMap<string, TaskComposerBuffer> {
|
|
6
|
+
const snapshot = new Map<string, TaskComposerBuffer>();
|
|
7
|
+
for (const [taskId, buffer] of buffers) {
|
|
8
|
+
snapshot.set(taskId, {
|
|
9
|
+
text: buffer.text,
|
|
10
|
+
cursor: buffer.cursor,
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
return snapshot;
|
|
14
|
+
}
|
|
@@ -6,28 +6,31 @@ interface TaskRecordShape {
|
|
|
6
6
|
|
|
7
7
|
interface RuntimeTaskEditorActionService<TTaskRecord extends TaskRecordShape> {
|
|
8
8
|
createTask(input: {
|
|
9
|
-
repositoryId
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
repositoryId?: string;
|
|
10
|
+
projectId?: string;
|
|
11
|
+
title?: string | null;
|
|
12
|
+
body: string;
|
|
12
13
|
}): Promise<TTaskRecord>;
|
|
13
14
|
updateTask(input: {
|
|
14
15
|
taskId: string;
|
|
15
|
-
repositoryId
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
repositoryId?: string | null;
|
|
17
|
+
projectId?: string | null;
|
|
18
|
+
title?: string | null;
|
|
19
|
+
body?: string;
|
|
18
20
|
}): Promise<TTaskRecord>;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
export interface RuntimeTaskEditorSubmitPayload {
|
|
22
24
|
readonly mode: 'create' | 'edit';
|
|
23
25
|
readonly taskId: string | null;
|
|
24
|
-
readonly repositoryId: string;
|
|
25
|
-
readonly
|
|
26
|
-
readonly
|
|
26
|
+
readonly repositoryId: string | null;
|
|
27
|
+
readonly projectId?: string | null;
|
|
28
|
+
readonly title: string | null;
|
|
29
|
+
readonly body: string;
|
|
27
30
|
readonly commandLabel: string;
|
|
28
31
|
}
|
|
29
32
|
|
|
30
|
-
interface RuntimeTaskEditorActionsOptions<TTaskRecord extends TaskRecordShape> {
|
|
33
|
+
export interface RuntimeTaskEditorActionsOptions<TTaskRecord extends TaskRecordShape> {
|
|
31
34
|
readonly workspace: WorkspaceModel;
|
|
32
35
|
readonly controlPlaneService: RuntimeTaskEditorActionService<TTaskRecord>;
|
|
33
36
|
readonly applyTaskRecord: (task: TTaskRecord) => void;
|
|
@@ -39,45 +42,59 @@ function formatErrorMessage(error: unknown): string {
|
|
|
39
42
|
return error instanceof Error ? error.message : String(error);
|
|
40
43
|
}
|
|
41
44
|
|
|
42
|
-
export
|
|
43
|
-
|
|
45
|
+
export interface RuntimeTaskEditorActions {
|
|
46
|
+
submitTaskEditorPayload(payload: RuntimeTaskEditorSubmitPayload): void;
|
|
47
|
+
}
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
export function createRuntimeTaskEditorActions<TTaskRecord extends TaskRecordShape>(
|
|
50
|
+
options: RuntimeTaskEditorActionsOptions<TTaskRecord>,
|
|
51
|
+
): RuntimeTaskEditorActions {
|
|
52
|
+
const submitTaskEditorPayload = (payload: RuntimeTaskEditorSubmitPayload): void => {
|
|
53
|
+
options.queueControlPlaneOp(async () => {
|
|
47
54
|
try {
|
|
48
55
|
if (payload.mode === 'create') {
|
|
49
|
-
|
|
50
|
-
await
|
|
51
|
-
repositoryId: payload.repositoryId,
|
|
56
|
+
options.applyTaskRecord(
|
|
57
|
+
await options.controlPlaneService.createTask({
|
|
58
|
+
...(payload.repositoryId === null ? {} : { repositoryId: payload.repositoryId }),
|
|
59
|
+
...(payload.projectId === undefined || payload.projectId === null
|
|
60
|
+
? {}
|
|
61
|
+
: { projectId: payload.projectId }),
|
|
52
62
|
title: payload.title,
|
|
53
|
-
|
|
63
|
+
body: payload.body,
|
|
54
64
|
}),
|
|
55
65
|
);
|
|
56
66
|
} else {
|
|
57
67
|
if (payload.taskId === null) {
|
|
58
68
|
throw new Error('task edit state missing task id');
|
|
59
69
|
}
|
|
60
|
-
|
|
61
|
-
await
|
|
70
|
+
options.applyTaskRecord(
|
|
71
|
+
await options.controlPlaneService.updateTask({
|
|
62
72
|
taskId: payload.taskId,
|
|
63
|
-
repositoryId: payload.repositoryId,
|
|
73
|
+
...(payload.repositoryId === null ? {} : { repositoryId: payload.repositoryId }),
|
|
74
|
+
...(payload.projectId === undefined || payload.projectId === null
|
|
75
|
+
? {}
|
|
76
|
+
: { projectId: payload.projectId }),
|
|
64
77
|
title: payload.title,
|
|
65
|
-
|
|
78
|
+
body: payload.body,
|
|
66
79
|
}),
|
|
67
80
|
);
|
|
68
81
|
}
|
|
69
|
-
|
|
70
|
-
|
|
82
|
+
options.workspace.taskEditorPrompt = null;
|
|
83
|
+
options.workspace.taskPaneNotice = null;
|
|
71
84
|
} catch (error: unknown) {
|
|
72
85
|
const message = formatErrorMessage(error);
|
|
73
|
-
if (
|
|
74
|
-
|
|
86
|
+
if (options.workspace.taskEditorPrompt !== null) {
|
|
87
|
+
options.workspace.taskEditorPrompt.error = message;
|
|
75
88
|
} else {
|
|
76
|
-
|
|
89
|
+
options.workspace.taskPaneNotice = message;
|
|
77
90
|
}
|
|
78
91
|
} finally {
|
|
79
|
-
|
|
92
|
+
options.markDirty();
|
|
80
93
|
}
|
|
81
94
|
}, payload.commandLabel);
|
|
82
|
-
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
submitTaskEditorPayload,
|
|
99
|
+
};
|
|
83
100
|
}
|