@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
|
@@ -1,25 +1,47 @@
|
|
|
1
1
|
import type { WorkspaceModel } from '../domain/workspace.ts';
|
|
2
2
|
import {
|
|
3
|
-
createTaskComposerBuffer
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
createTaskComposerBuffer,
|
|
4
|
+
insertTaskComposerText,
|
|
5
|
+
normalizeTaskComposerBuffer,
|
|
6
|
+
taskComposerBackspace,
|
|
7
|
+
taskComposerDeleteForward,
|
|
8
|
+
taskComposerDeleteToLineEnd,
|
|
9
|
+
taskComposerDeleteToLineStart,
|
|
10
|
+
taskComposerDeleteWordLeft,
|
|
11
|
+
taskComposerMoveLeft,
|
|
12
|
+
taskComposerMoveLineEnd,
|
|
13
|
+
taskComposerMoveLineStart,
|
|
14
|
+
taskComposerMoveRight,
|
|
15
|
+
taskComposerMoveVertical,
|
|
16
|
+
taskComposerMoveWordLeft,
|
|
17
|
+
taskComposerMoveWordRight,
|
|
18
|
+
taskFieldsFromComposerText,
|
|
6
19
|
type TaskComposerBuffer,
|
|
7
20
|
} from '../mux/task-composer.ts';
|
|
8
|
-
import {
|
|
9
|
-
|
|
21
|
+
import {
|
|
22
|
+
detectTaskScreenKeybindingAction,
|
|
23
|
+
type ResolvedTaskScreenKeybindings,
|
|
24
|
+
} from '../mux/task-screen-keybindings.ts';
|
|
25
|
+
|
|
26
|
+
type TaskPaneShortcutAction =
|
|
27
|
+
| 'task.ready'
|
|
28
|
+
| 'task.draft'
|
|
29
|
+
| 'task.complete'
|
|
30
|
+
| 'task.reorder-up'
|
|
31
|
+
| 'task.reorder-down';
|
|
32
|
+
type TaskComposerSubmitMode = 'ready' | 'queue';
|
|
10
33
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
>[0];
|
|
34
|
+
const BRACKETED_PASTE_START = Buffer.from('\u001b[200~', 'utf8');
|
|
35
|
+
const BRACKETED_PASTE_END = Buffer.from('\u001b[201~', 'utf8');
|
|
14
36
|
|
|
15
37
|
interface TaskRecordShape {
|
|
16
38
|
readonly taskId: string;
|
|
17
39
|
readonly repositoryId: string | null;
|
|
18
40
|
readonly title: string;
|
|
19
|
-
readonly
|
|
41
|
+
readonly body: string;
|
|
20
42
|
}
|
|
21
43
|
|
|
22
|
-
interface RuntimeTaskPaneShortcutsOptions<TTaskRecord extends TaskRecordShape> {
|
|
44
|
+
export interface RuntimeTaskPaneShortcutsOptions<TTaskRecord extends TaskRecordShape> {
|
|
23
45
|
readonly workspace: WorkspaceModel;
|
|
24
46
|
readonly taskScreenKeybindings: ResolvedTaskScreenKeybindings;
|
|
25
47
|
readonly repositoriesHas: (repositoryId: string) => boolean;
|
|
@@ -36,154 +58,332 @@ interface RuntimeTaskPaneShortcutsOptions<TTaskRecord extends TaskRecordShape> {
|
|
|
36
58
|
readonly createTask: (payload: {
|
|
37
59
|
repositoryId: string;
|
|
38
60
|
title: string;
|
|
39
|
-
|
|
61
|
+
body: string;
|
|
40
62
|
}) => Promise<TTaskRecord>;
|
|
63
|
+
readonly taskReady: (taskId: string) => Promise<TTaskRecord>;
|
|
41
64
|
readonly applyTaskRecord: (task: TTaskRecord) => void;
|
|
42
65
|
readonly syncTaskPaneSelection: () => void;
|
|
43
66
|
readonly markDirty: () => void;
|
|
44
|
-
readonly createTaskComposerBuffer?: typeof createTaskComposerBufferFrame;
|
|
45
|
-
readonly normalizeTaskComposerBuffer?: typeof normalizeTaskComposerBufferFrame;
|
|
46
|
-
readonly taskFieldsFromComposerText?: typeof taskFieldsFromComposerTextFrame;
|
|
47
|
-
readonly handleTaskPaneShortcutInput?: typeof handleTaskPaneShortcutInputFrame;
|
|
48
67
|
}
|
|
49
68
|
|
|
50
|
-
export
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
homeEditorBuffer(): TaskComposerBuffer {
|
|
68
|
-
const taskEditorTarget = this.options.workspace.taskEditorTarget;
|
|
69
|
+
export interface RuntimeTaskPaneShortcuts {
|
|
70
|
+
homeEditorBuffer(): TaskComposerBuffer;
|
|
71
|
+
updateHomeEditorBuffer(next: TaskComposerBuffer): void;
|
|
72
|
+
selectRepositoryByDirection(direction: 1 | -1): void;
|
|
73
|
+
submitDraftTaskFromComposer(mode: TaskComposerSubmitMode): void;
|
|
74
|
+
moveTaskEditorFocusUp(): void;
|
|
75
|
+
moveTaskEditorFocusDown(): void;
|
|
76
|
+
handleInput(input: Buffer): boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function createRuntimeTaskPaneShortcuts<TTaskRecord extends TaskRecordShape>(
|
|
80
|
+
options: RuntimeTaskPaneShortcutsOptions<TTaskRecord>,
|
|
81
|
+
): RuntimeTaskPaneShortcuts {
|
|
82
|
+
const homeEditorBuffer = (): TaskComposerBuffer => {
|
|
83
|
+
const taskEditorTarget = options.workspace.taskEditorTarget;
|
|
69
84
|
if (taskEditorTarget.kind === 'task') {
|
|
70
|
-
return (
|
|
71
|
-
this.options.taskComposerForTask(taskEditorTarget.taskId) ??
|
|
72
|
-
this.createTaskComposerBuffer('')
|
|
73
|
-
);
|
|
85
|
+
return options.taskComposerForTask(taskEditorTarget.taskId) ?? createTaskComposerBuffer('');
|
|
74
86
|
}
|
|
75
|
-
return
|
|
76
|
-
}
|
|
87
|
+
return options.workspace.taskDraftComposer;
|
|
88
|
+
};
|
|
77
89
|
|
|
78
|
-
updateHomeEditorBuffer(next: TaskComposerBuffer): void {
|
|
79
|
-
const taskEditorTarget =
|
|
90
|
+
const updateHomeEditorBuffer = (next: TaskComposerBuffer): void => {
|
|
91
|
+
const taskEditorTarget = options.workspace.taskEditorTarget;
|
|
80
92
|
if (taskEditorTarget.kind === 'task') {
|
|
81
|
-
|
|
82
|
-
|
|
93
|
+
options.setTaskComposerForTask(taskEditorTarget.taskId, next);
|
|
94
|
+
options.scheduleTaskComposerPersist(taskEditorTarget.taskId);
|
|
83
95
|
} else {
|
|
84
|
-
|
|
96
|
+
options.workspace.taskDraftComposer = normalizeTaskComposerBuffer(next);
|
|
85
97
|
}
|
|
86
|
-
|
|
87
|
-
}
|
|
98
|
+
options.markDirty();
|
|
99
|
+
};
|
|
88
100
|
|
|
89
|
-
selectRepositoryByDirection(direction: 1 | -1): void {
|
|
90
|
-
const orderedIds =
|
|
101
|
+
const selectRepositoryByDirection = (direction: 1 | -1): void => {
|
|
102
|
+
const orderedIds = options.activeRepositoryIds();
|
|
91
103
|
if (orderedIds.length === 0) {
|
|
92
104
|
return;
|
|
93
105
|
}
|
|
94
106
|
const currentIndex = Math.max(
|
|
95
107
|
0,
|
|
96
|
-
orderedIds.indexOf(
|
|
108
|
+
orderedIds.indexOf(options.workspace.taskPaneSelectedRepositoryId ?? ''),
|
|
97
109
|
);
|
|
98
110
|
const nextIndex = Math.max(0, Math.min(orderedIds.length - 1, currentIndex + direction));
|
|
99
111
|
const nextRepositoryId = orderedIds[nextIndex];
|
|
100
112
|
if (nextRepositoryId !== undefined) {
|
|
101
|
-
|
|
113
|
+
options.selectRepositoryById(nextRepositoryId);
|
|
102
114
|
}
|
|
103
|
-
}
|
|
115
|
+
};
|
|
104
116
|
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
117
|
+
const taskNavigationIds = (): readonly string[] => {
|
|
118
|
+
const ids: string[] = [];
|
|
119
|
+
const seen = new Set<string>();
|
|
120
|
+
for (const taskId of options.workspace.latestTaskPaneView.taskIds) {
|
|
121
|
+
if (taskId === null || seen.has(taskId)) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
ids.push(taskId);
|
|
125
|
+
seen.add(taskId);
|
|
126
|
+
}
|
|
127
|
+
if (ids.length > 0) {
|
|
128
|
+
return ids;
|
|
129
|
+
}
|
|
130
|
+
for (const task of options.selectedRepositoryTaskRecords()) {
|
|
131
|
+
if (seen.has(task.taskId)) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
ids.push(task.taskId);
|
|
135
|
+
seen.add(task.taskId);
|
|
136
|
+
}
|
|
137
|
+
return ids;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const submitDraftTaskFromComposer = (mode: TaskComposerSubmitMode): void => {
|
|
141
|
+
const repositoryId = options.workspace.taskPaneSelectedRepositoryId;
|
|
142
|
+
if (repositoryId === null || !options.repositoriesHas(repositoryId)) {
|
|
143
|
+
options.workspace.taskPaneNotice = 'select a repository first';
|
|
144
|
+
options.markDirty();
|
|
110
145
|
return;
|
|
111
146
|
}
|
|
112
|
-
const fields =
|
|
113
|
-
if (fields.
|
|
114
|
-
|
|
115
|
-
|
|
147
|
+
const fields = taskFieldsFromComposerText(options.workspace.taskDraftComposer.text);
|
|
148
|
+
if (fields.body.trim().length === 0) {
|
|
149
|
+
options.workspace.taskPaneNotice = 'task body is required';
|
|
150
|
+
options.markDirty();
|
|
116
151
|
return;
|
|
117
152
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
153
|
+
options.queueControlPlaneOp(
|
|
154
|
+
async () => {
|
|
155
|
+
const created = await options.createTask({
|
|
156
|
+
repositoryId,
|
|
157
|
+
title: fields.title ?? '',
|
|
158
|
+
body: fields.body,
|
|
159
|
+
});
|
|
160
|
+
const task = mode === 'ready' ? await options.taskReady(created.taskId) : created;
|
|
161
|
+
options.applyTaskRecord(task);
|
|
162
|
+
options.workspace.taskDraftComposer = createTaskComposerBuffer('');
|
|
163
|
+
options.workspace.taskPaneNotice = null;
|
|
164
|
+
options.syncTaskPaneSelection();
|
|
165
|
+
options.markDirty();
|
|
166
|
+
},
|
|
167
|
+
mode === 'ready' ? 'task-composer-submit-ready' : 'task-composer-queue',
|
|
168
|
+
);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const moveTaskEditorFocusUp = (): void => {
|
|
172
|
+
const workspace = options.workspace;
|
|
173
|
+
const navigationTaskIds = taskNavigationIds();
|
|
135
174
|
if (workspace.taskEditorTarget.kind === 'draft') {
|
|
136
|
-
const
|
|
137
|
-
if (
|
|
138
|
-
|
|
175
|
+
const fallbackTaskId = navigationTaskIds[navigationTaskIds.length - 1];
|
|
176
|
+
if (fallbackTaskId !== undefined) {
|
|
177
|
+
options.focusTaskComposer(fallbackTaskId);
|
|
139
178
|
}
|
|
140
179
|
return;
|
|
141
180
|
}
|
|
142
181
|
|
|
143
182
|
const focusedTaskId = workspace.taskEditorTarget.taskId;
|
|
144
|
-
const index =
|
|
183
|
+
const index = navigationTaskIds.indexOf(focusedTaskId);
|
|
145
184
|
if (index <= 0) {
|
|
146
185
|
return;
|
|
147
186
|
}
|
|
148
|
-
const
|
|
149
|
-
if (
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const workspace =
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
187
|
+
const targetTaskId = navigationTaskIds[index - 1];
|
|
188
|
+
if (targetTaskId !== undefined) {
|
|
189
|
+
options.focusTaskComposer(targetTaskId);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const moveTaskEditorFocusDown = (): void => {
|
|
194
|
+
const workspace = options.workspace;
|
|
195
|
+
if (workspace.taskEditorTarget.kind !== 'task') {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const navigationTaskIds = taskNavigationIds();
|
|
199
|
+
const focusedTaskId = workspace.taskEditorTarget.taskId;
|
|
200
|
+
const index = navigationTaskIds.indexOf(focusedTaskId);
|
|
201
|
+
if (index < 0) {
|
|
202
|
+
options.focusDraftComposer();
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const targetTaskId = navigationTaskIds[index + 1];
|
|
206
|
+
if (targetTaskId !== undefined) {
|
|
207
|
+
options.focusTaskComposer(targetTaskId);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
options.focusDraftComposer();
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const matchesSequence = (input: Buffer, startIndex: number, sequence: Buffer): boolean => {
|
|
214
|
+
if (startIndex < 0 || startIndex + sequence.length > input.length) {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
for (let index = 0; index < sequence.length; index += 1) {
|
|
218
|
+
if (input[startIndex + index] !== sequence[index]) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return true;
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const extractInsertText = (input: Buffer): string | null => {
|
|
226
|
+
const chunks: Buffer[] = [];
|
|
227
|
+
let inBracketedPaste = false;
|
|
228
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
229
|
+
if (!inBracketedPaste && matchesSequence(input, index, BRACKETED_PASTE_START)) {
|
|
230
|
+
inBracketedPaste = true;
|
|
231
|
+
index += BRACKETED_PASTE_START.length - 1;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (inBracketedPaste && matchesSequence(input, index, BRACKETED_PASTE_END)) {
|
|
235
|
+
inBracketedPaste = false;
|
|
236
|
+
index += BRACKETED_PASTE_END.length - 1;
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
const byte = input[index]!;
|
|
240
|
+
if (inBracketedPaste) {
|
|
241
|
+
chunks.push(Buffer.from([byte]));
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (byte === 0x1b) {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
if (byte >= 32 && byte <= 126) {
|
|
248
|
+
chunks.push(Buffer.from([byte]));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (chunks.length === 0) {
|
|
252
|
+
return '';
|
|
253
|
+
}
|
|
254
|
+
return Buffer.concat(chunks).toString('utf8').replace(/\r\n?/gu, '\n');
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const handleShortcutAction = (action: string): boolean => {
|
|
258
|
+
const workspace = options.workspace;
|
|
259
|
+
switch (action) {
|
|
260
|
+
case 'mux.home.repo.dropdown.toggle':
|
|
261
|
+
workspace.taskRepositoryDropdownOpen = !workspace.taskRepositoryDropdownOpen;
|
|
262
|
+
options.markDirty();
|
|
263
|
+
return true;
|
|
264
|
+
case 'mux.home.repo.next':
|
|
265
|
+
workspace.taskRepositoryDropdownOpen = true;
|
|
266
|
+
selectRepositoryByDirection(1);
|
|
267
|
+
return true;
|
|
268
|
+
case 'mux.home.repo.previous':
|
|
269
|
+
workspace.taskRepositoryDropdownOpen = true;
|
|
270
|
+
selectRepositoryByDirection(-1);
|
|
271
|
+
return true;
|
|
272
|
+
case 'mux.home.task.status.ready':
|
|
273
|
+
options.runTaskPaneAction('task.ready');
|
|
274
|
+
return true;
|
|
275
|
+
case 'mux.home.task.status.draft':
|
|
276
|
+
options.runTaskPaneAction('task.draft');
|
|
277
|
+
return true;
|
|
278
|
+
case 'mux.home.task.status.complete':
|
|
279
|
+
options.runTaskPaneAction('task.complete');
|
|
280
|
+
return true;
|
|
281
|
+
case 'mux.home.task.reorder.up':
|
|
282
|
+
options.runTaskPaneAction('task.reorder-up');
|
|
283
|
+
return true;
|
|
284
|
+
case 'mux.home.task.reorder.down':
|
|
285
|
+
options.runTaskPaneAction('task.reorder-down');
|
|
286
|
+
return true;
|
|
287
|
+
case 'mux.home.task.newline':
|
|
288
|
+
updateHomeEditorBuffer(insertTaskComposerText(homeEditorBuffer(), '\n'));
|
|
289
|
+
return true;
|
|
290
|
+
case 'mux.home.task.queue':
|
|
291
|
+
if (workspace.taskEditorTarget.kind === 'draft') {
|
|
292
|
+
submitDraftTaskFromComposer('queue');
|
|
293
|
+
} else {
|
|
294
|
+
options.focusDraftComposer();
|
|
295
|
+
}
|
|
296
|
+
return true;
|
|
297
|
+
case 'mux.home.task.submit':
|
|
298
|
+
if (workspace.taskEditorTarget.kind === 'draft') {
|
|
299
|
+
submitDraftTaskFromComposer('ready');
|
|
300
|
+
} else {
|
|
301
|
+
options.focusDraftComposer();
|
|
302
|
+
}
|
|
303
|
+
return true;
|
|
304
|
+
case 'mux.home.editor.cursor.left':
|
|
305
|
+
updateHomeEditorBuffer(taskComposerMoveLeft(homeEditorBuffer()));
|
|
306
|
+
return true;
|
|
307
|
+
case 'mux.home.editor.cursor.right':
|
|
308
|
+
updateHomeEditorBuffer(taskComposerMoveRight(homeEditorBuffer()));
|
|
309
|
+
return true;
|
|
310
|
+
case 'mux.home.editor.cursor.up': {
|
|
311
|
+
const vertical = taskComposerMoveVertical(homeEditorBuffer(), -1);
|
|
312
|
+
if (vertical.hitBoundary) {
|
|
313
|
+
moveTaskEditorFocusUp();
|
|
314
|
+
} else {
|
|
315
|
+
updateHomeEditorBuffer(vertical.next);
|
|
316
|
+
}
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
case 'mux.home.editor.cursor.down':
|
|
320
|
+
if (workspace.taskEditorTarget.kind === 'task') {
|
|
321
|
+
const vertical = taskComposerMoveVertical(homeEditorBuffer(), 1);
|
|
322
|
+
if (vertical.hitBoundary) {
|
|
323
|
+
moveTaskEditorFocusDown();
|
|
324
|
+
} else {
|
|
325
|
+
updateHomeEditorBuffer(vertical.next);
|
|
326
|
+
}
|
|
327
|
+
} else {
|
|
328
|
+
updateHomeEditorBuffer(taskComposerMoveVertical(homeEditorBuffer(), 1).next);
|
|
329
|
+
}
|
|
330
|
+
return true;
|
|
331
|
+
case 'mux.home.editor.line.start':
|
|
332
|
+
updateHomeEditorBuffer(taskComposerMoveLineStart(homeEditorBuffer()));
|
|
333
|
+
return true;
|
|
334
|
+
case 'mux.home.editor.line.end':
|
|
335
|
+
updateHomeEditorBuffer(taskComposerMoveLineEnd(homeEditorBuffer()));
|
|
336
|
+
return true;
|
|
337
|
+
case 'mux.home.editor.word.left':
|
|
338
|
+
updateHomeEditorBuffer(taskComposerMoveWordLeft(homeEditorBuffer()));
|
|
339
|
+
return true;
|
|
340
|
+
case 'mux.home.editor.word.right':
|
|
341
|
+
updateHomeEditorBuffer(taskComposerMoveWordRight(homeEditorBuffer()));
|
|
342
|
+
return true;
|
|
343
|
+
case 'mux.home.editor.delete.backward':
|
|
344
|
+
updateHomeEditorBuffer(taskComposerBackspace(homeEditorBuffer()));
|
|
345
|
+
return true;
|
|
346
|
+
case 'mux.home.editor.delete.forward':
|
|
347
|
+
updateHomeEditorBuffer(taskComposerDeleteForward(homeEditorBuffer()));
|
|
348
|
+
return true;
|
|
349
|
+
case 'mux.home.editor.delete.word.backward':
|
|
350
|
+
updateHomeEditorBuffer(taskComposerDeleteWordLeft(homeEditorBuffer()));
|
|
351
|
+
return true;
|
|
352
|
+
case 'mux.home.editor.delete.line.start':
|
|
353
|
+
updateHomeEditorBuffer(taskComposerDeleteToLineStart(homeEditorBuffer()));
|
|
354
|
+
return true;
|
|
355
|
+
case 'mux.home.editor.delete.line.end':
|
|
356
|
+
updateHomeEditorBuffer(taskComposerDeleteToLineEnd(homeEditorBuffer()));
|
|
357
|
+
return true;
|
|
358
|
+
default:
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const handleInput = (input: Buffer): boolean => {
|
|
364
|
+
const workspace = options.workspace;
|
|
365
|
+
if (workspace.mainPaneMode !== 'home' || workspace.leftNavSelection.kind !== 'tasks') {
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
const action = detectTaskScreenKeybindingAction(input, options.taskScreenKeybindings);
|
|
369
|
+
if (action !== null && handleShortcutAction(action)) {
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
const inserted = extractInsertText(input);
|
|
373
|
+
if (inserted === null || inserted.length === 0) {
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
376
|
+
updateHomeEditorBuffer(insertTaskComposerText(homeEditorBuffer(), inserted));
|
|
377
|
+
return true;
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
return {
|
|
381
|
+
homeEditorBuffer,
|
|
382
|
+
updateHomeEditorBuffer,
|
|
383
|
+
selectRepositoryByDirection,
|
|
384
|
+
submitDraftTaskFromComposer,
|
|
385
|
+
moveTaskEditorFocusUp,
|
|
386
|
+
moveTaskEditorFocusDown,
|
|
387
|
+
handleInput,
|
|
388
|
+
};
|
|
189
389
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { RuntimeWorkspaceObservedQueuedReaction } from './runtime-workspace-observed-transition-policy.ts';
|
|
2
|
+
|
|
3
|
+
export interface RuntimeWorkspaceObservedEffectQueueOptions {
|
|
4
|
+
// Must enqueue work; callers should not execute reactions inline from the subscriber path.
|
|
5
|
+
readonly enqueueQueuedReaction: (task: () => Promise<void>, label: string) => void;
|
|
6
|
+
readonly unsubscribeConversationEvents: (sessionId: string) => Promise<void>;
|
|
7
|
+
readonly activateConversation: (sessionId: string) => Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function enqueueRuntimeWorkspaceObservedReactions(input: {
|
|
11
|
+
readonly reactions: readonly RuntimeWorkspaceObservedQueuedReaction[];
|
|
12
|
+
readonly options: RuntimeWorkspaceObservedEffectQueueOptions;
|
|
13
|
+
}): void {
|
|
14
|
+
for (const reaction of input.reactions) {
|
|
15
|
+
if (reaction.kind === 'unsubscribe-conversation') {
|
|
16
|
+
input.options.enqueueQueuedReaction(async () => {
|
|
17
|
+
await input.options.unsubscribeConversationEvents(reaction.sessionId);
|
|
18
|
+
}, reaction.label);
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
input.options.enqueueQueuedReaction(async () => {
|
|
22
|
+
await input.options.activateConversation(reaction.sessionId);
|
|
23
|
+
}, reaction.label);
|
|
24
|
+
}
|
|
25
|
+
}
|