@brimveyn/aimux 1.20.4 → 1.22.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/README.md +27 -25
- package/package.json +2 -2
- package/src/app-runtime/auto-commit-driver.ts +22 -27
- package/src/app-runtime/backend-attach-runtime.ts +43 -36
- package/src/app-runtime/backend-runtime-events.ts +107 -88
- package/src/app-runtime/editor-actions.ts +201 -0
- package/src/app-runtime/git-actions.ts +197 -0
- package/src/app-runtime/measure-ref.ts +19 -0
- package/src/app-runtime/navigation-actions.ts +213 -0
- package/src/app-runtime/project-actions.ts +188 -0
- package/src/app-runtime/prompt-injection.ts +123 -0
- package/src/app-runtime/selection.ts +29 -0
- package/src/app-runtime/settings-actions.ts +156 -0
- package/src/app-runtime/setup-actions.ts +237 -0
- package/src/app-runtime/side-effect-context.ts +33 -0
- package/src/app-runtime/side-effects.ts +305 -1589
- package/src/app-runtime/snippet-actions.ts +3 -2
- package/src/app-runtime/split-drag-controller.ts +4 -8
- package/src/app-runtime/tab-actions.ts +218 -0
- package/src/app-runtime/tab-runtime-timeouts.ts +1 -1
- package/src/app-runtime/use-auto-commit-driver.ts +14 -13
- package/src/app-runtime/use-backend-runtime.ts +12 -11
- package/src/app-runtime/use-directory-search.ts +5 -4
- package/src/app-runtime/use-mouse-handlers.ts +28 -42
- package/src/app-runtime/{use-workspace-autosave.ts → use-project-autosave.ts} +4 -4
- package/src/app-runtime/use-renderer-bindings.ts +2 -1
- package/src/app-runtime/use-setup-runner.ts +96 -0
- package/src/app-runtime/use-terminal-resize.ts +28 -33
- package/src/app-runtime/workspace-actions.ts +366 -0
- package/src/app-runtime/workspace-activity.ts +138 -0
- package/src/app-runtime/workspace-naming.ts +90 -0
- package/src/app.tsx +102 -85
- package/src/assets/sounds/bell.wav +0 -0
- package/src/assets/sounds/ding.wav +0 -0
- package/src/assets/sounds/plane.wav +0 -0
- package/src/cli/client/daemon-client.ts +2 -2
- package/src/cli/client/project-resolver.ts +112 -0
- package/src/cli/commands/project/close.ts +32 -0
- package/src/cli/commands/project/create.ts +93 -0
- package/src/cli/commands/project/list.ts +25 -0
- package/src/cli/commands/project/show.ts +32 -0
- package/src/cli/commands/{workspace → project}/switch.ts +19 -19
- package/src/cli/commands/tab/await.ts +2 -2
- package/src/cli/commands/tab/close.ts +3 -3
- package/src/cli/commands/tab/create.ts +74 -73
- package/src/cli/commands/tab/focus.ts +3 -3
- package/src/cli/commands/tab/list.ts +6 -6
- package/src/cli/commands/tab/run.ts +2 -2
- package/src/cli/commands/tab/send.ts +2 -2
- package/src/cli/commands/tab/snapshot.ts +2 -2
- package/src/cli/commands/tab/tail.ts +2 -2
- package/src/cli/commands/tab/wait.ts +2 -2
- package/src/cli/commands/worker/await.ts +3 -3
- package/src/cli/commands/worker/doctor.ts +30 -30
- package/src/cli/commands/worker/list.ts +15 -15
- package/src/cli/commands/worker/prompt.ts +3 -3
- package/src/cli/commands/worker/run.ts +23 -23
- package/src/cli/commands/worker/shared.ts +53 -59
- package/src/cli/commands/worker/stop.ts +29 -29
- package/src/cli/commands/worker/submit.ts +3 -3
- package/src/cli/commands/{worktree → workspace}/create-core.ts +26 -26
- package/src/cli/commands/workspace/create.ts +30 -68
- package/src/cli/commands/workspace/list.ts +52 -9
- package/src/cli/commands/workspace/remove.ts +81 -0
- package/src/cli/completion/plan.ts +2 -2
- package/src/cli/completion/sources.ts +18 -18
- package/src/cli/context.ts +13 -13
- package/src/cli/flags.ts +30 -10
- package/src/cli/index.ts +26 -16
- package/src/cli/output.ts +1 -1
- package/src/cli/registry.ts +12 -12
- package/src/config/loader.ts +16 -5
- package/src/config.ts +149 -101
- package/src/daemon/catalog-writer.ts +67 -67
- package/src/daemon/daemon.ts +205 -201
- package/src/daemon/session-manager.ts +39 -39
- package/src/daemon/session-registry.ts +14 -14
- package/src/git/divergence.ts +33 -2
- package/src/git/git-poller.ts +1 -1
- package/src/git/git-status.ts +2 -2
- package/src/git/{move-worktree.ts → move-workspace.ts} +5 -5
- package/src/git/pr-merge.ts +64 -0
- package/src/git/pr-status-poller.ts +57 -0
- package/src/git/pr-status.ts +227 -0
- package/src/git/repo-discovery.ts +1 -1
- package/src/git/use-repo-discovery.ts +1 -1
- package/src/git/workspace-branch-poller.ts +54 -0
- package/src/git/workspace-divergence-poller.ts +65 -0
- package/src/git/worktree.ts +29 -0
- package/src/index.tsx +11 -6
- package/src/input/keymap/help-entries.ts +7 -5
- package/src/input/modes/bridge.ts +13 -12
- package/src/input/modes/handlers/shared.ts +1 -1
- package/src/input/modes/transitions.ts +34 -21
- package/src/input/modes/types.ts +48 -31
- package/src/ipc/manager-protocol.ts +44 -44
- package/src/ipc/protocol.ts +192 -166
- package/src/platform/open-url.ts +39 -0
- package/src/platform/play-sound.ts +184 -0
- package/src/platform/project-search.ts +8 -8
- package/src/platform/worktree-paths.ts +8 -6
- package/src/pty/assistant-question-extractor.ts +1 -1
- package/src/pty/assistant-status-detection-loop.ts +79 -55
- package/src/pty/assistant-status-detector.ts +40 -15
- package/src/pty/command-registry.ts +81 -1
- package/src/restart-terminal-manager.ts +1 -1
- package/src/services/ai-usage/provider.ts +9 -2
- package/src/services/ai-usage/spawn.ts +3 -1
- package/src/session-backend/bootstrap.ts +4 -4
- package/src/session-backend/local-session-backend.ts +78 -75
- package/src/session-backend/remote-session-backend.ts +64 -52
- package/src/session-backend/types.ts +43 -32
- package/src/settings/live.ts +90 -0
- package/src/settings/search.ts +43 -0
- package/src/settings/sections/about.ts +49 -0
- package/src/settings/sections/appearance.ts +54 -0
- package/src/settings/sections/automation.ts +105 -0
- package/src/settings/sections/commands.ts +72 -0
- package/src/settings/sections/editor.ts +56 -0
- package/src/settings/sections/experimental.ts +57 -0
- package/src/settings/sections/git.ts +112 -0
- package/src/settings/sections/index.ts +95 -0
- package/src/settings/sections/integrations.ts +23 -0
- package/src/settings/sections/layout.ts +72 -0
- package/src/settings/sections/notifications.ts +99 -0
- package/src/settings/sections/setup.ts +54 -0
- package/src/settings/sections/status-bar.ts +63 -0
- package/src/settings/settings-store.ts +207 -0
- package/src/settings/types.ts +108 -0
- package/src/snippets/run-shell-var.ts +48 -22
- package/src/state/actions.ts +342 -0
- package/src/state/app-store.ts +2 -1
- package/src/state/bars.ts +75 -0
- package/src/state/dispatch-ref.ts +1 -1
- package/src/state/git-pane-sizing.ts +0 -9
- package/src/state/layout-resize.ts +2 -2
- package/src/state/pr-status-store.ts +39 -0
- package/src/state/project-catalog.ts +275 -0
- package/src/state/project-data.ts +143 -0
- package/src/state/{session-persistence.ts → project-persistence.ts} +77 -73
- package/src/state/project-save.ts +46 -0
- package/src/state/project-workspaces.ts +392 -0
- package/src/state/reducers/auto-commit-state.ts +11 -10
- package/src/state/reducers/git-commit-modal-state.ts +122 -0
- package/src/state/reducers/git-mode-state.ts +1 -1
- package/src/state/reducers/git-panel-state.ts +10 -53
- package/src/state/reducers/modal-state.ts +290 -681
- package/src/state/reducers/multi-repo-state.ts +3 -6
- package/src/state/reducers/project-state.ts +312 -0
- package/src/state/reducers/settings-state.ts +80 -0
- package/src/state/reducers/tab-state.ts +107 -60
- package/src/state/reducers/ui-state.ts +86 -15
- package/src/state/selectors.ts +49 -37
- package/src/state/store.ts +83 -57
- package/src/state/types.ts +261 -417
- package/src/state/validation.ts +14 -12
- package/src/terminal-manager/manager-client.ts +32 -32
- package/src/terminal-manager/terminal-manager.ts +24 -24
- package/src/ui/components/git/diff-renderer/pierre-diff.tsx +2 -1
- package/src/ui/components/git/git-panel.tsx +2 -2
- package/src/ui/components/git/git-view.tsx +12 -12
- package/src/ui/components/git/image-diff/image-diff-view.tsx +1 -1
- package/src/ui/components/git/pane/git-pane-header.tsx +105 -39
- package/src/ui/components/git/pane/git-pane-widget.tsx +37 -23
- package/src/ui/components/git/pane/pr-checks-panel.tsx +197 -0
- package/src/ui/components/git/pane/pr-state-row.tsx +103 -0
- package/src/ui/components/layout/bar.tsx +191 -0
- package/src/ui/components/layout/sidebar/project-list.tsx +444 -0
- package/src/ui/components/layout/sidebar/tab-item.tsx +27 -12
- package/src/ui/components/layout/sidebar/use-sidebar-auto-scroll.ts +1 -1
- package/src/ui/components/layout/sidebar/workspace-row.tsx +253 -0
- package/src/ui/components/layout/status-bar.tsx +6 -2
- package/src/ui/components/layout/terminal-pane.tsx +34 -29
- package/src/ui/components/layout/top-tab-bar.tsx +48 -37
- package/src/ui/components/modals/git/git-commit-modal.tsx +8 -8
- package/src/ui/components/modals/{sessions/create-session-modal.tsx → projects/create-project-modal.tsx} +12 -12
- package/src/ui/components/modals/{sessions/session-name-modal.tsx → projects/project-name-modal.tsx} +2 -2
- package/src/ui/components/modals/{sessions/session-picker-modal.tsx → projects/project-picker-modal.tsx} +33 -35
- package/src/ui/components/modals/settings/settings-search-modal.tsx +70 -0
- package/src/ui/components/modals/shared/form.tsx +34 -22
- package/src/ui/components/modals/shared/picker.tsx +10 -10
- package/src/ui/components/modals/shared/{worktree-delete-confirm.tsx → workspace-delete-confirm.tsx} +9 -9
- package/src/ui/components/modals/tabs/new-tab-modal.tsx +30 -324
- package/src/ui/components/modals/workspace/create-workspace-modal.tsx +101 -0
- package/src/ui/components/modals/{worktree/worktree-move-confirm-modal.tsx → workspace/workspace-move-confirm-modal.tsx} +5 -5
- package/src/ui/components/modals/{worktree/worktree-move-modal.tsx → workspace/workspace-move-modal.tsx} +29 -27
- package/src/ui/components/primitives/input-field.tsx +7 -3
- package/src/ui/components/primitives/surface.tsx +3 -0
- package/src/ui/components/settings/row-value.tsx +74 -0
- package/src/ui/components/settings/settings-row.tsx +78 -0
- package/src/ui/components/settings/settings-view.tsx +169 -0
- package/src/ui/components/setup/setup-widget.tsx +152 -0
- package/src/ui/flash/build-labels.ts +27 -27
- package/src/ui/hooks/use-activity-sprite.ts +73 -0
- package/src/ui/hooks/use-scroll-active-into-view.ts +26 -0
- package/src/ui/{session-ordering.ts → project-ordering.ts} +4 -4
- package/src/ui/root.tsx +164 -230
- package/src/ui/status-bar-model.ts +48 -34
- package/src/ui/terminal-graphics/kitty.ts +28 -2
- package/src/ui/terminal-graphics/sprites/done@330/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/done@330/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/idle@1000/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/idle@1000/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/waiting@170/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/waiting@170/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/2.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/3.png +0 -0
- package/src/ui/terminal-graphics/sprites.ts +246 -0
- package/src/ui/truncate.ts +7 -0
- package/src/ui/widgets/registry.tsx +21 -0
- package/src/ui/widgets/widget-context-menu.ts +85 -0
- package/src/update.ts +2 -2
- package/src/app-runtime/session-actions.ts +0 -187
- package/src/cli/client/workspace-resolver.ts +0 -110
- package/src/cli/commands/workspace/close.ts +0 -32
- package/src/cli/commands/workspace/show.ts +0 -32
- package/src/cli/commands/worktree/create.ts +0 -55
- package/src/cli/commands/worktree/list.ts +0 -68
- package/src/cli/commands/worktree/remove.ts +0 -81
- package/src/git/worktree-branch-poller.ts +0 -54
- package/src/git/worktree-divergence-poller.ts +0 -59
- package/src/state/reducers/session-state.ts +0 -262
- package/src/state/session-catalog.ts +0 -143
- package/src/state/session-worktrees.ts +0 -260
- package/src/state/workspace-save.ts +0 -45
- package/src/ui/components/git/pane/git-pane-context-menu.ts +0 -26
- package/src/ui/components/layout/sidebar/sidebar.tsx +0 -163
- package/src/ui/components/layout/sidebar/use-top-tab-bar-auto-scroll.ts +0 -30
- package/src/ui/components/layout/sidebar/workspace-list.tsx +0 -418
- package/src/ui/components/layout/sidebar/worktree-row.tsx +0 -149
- /package/src/ui/{components/git/image-diff → terminal-graphics}/dimensions.ts +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { SessionBackend } from '../session-backend/types'
|
|
2
|
-
import type { AppAction
|
|
2
|
+
import type { AppAction } from '../state/actions'
|
|
3
|
+
import type { AppState, SnippetRecord, TabSession } from '../state/types'
|
|
3
4
|
|
|
4
5
|
import { createPrefixedId } from '../platform/id'
|
|
5
6
|
import { isConfigSnippetId, saveSnippetCatalog } from '../state/snippet-catalog'
|
|
@@ -40,7 +41,7 @@ export function saveSnippetEditorState(state: AppState): SnippetRecord[] | null
|
|
|
40
41
|
return null
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
const snippetId = state.modal.
|
|
44
|
+
const snippetId = state.modal.projectTargetId
|
|
44
45
|
// Config-pinned snippets are sticky and read-only in the UI.
|
|
45
46
|
if (snippetId != null && snippetId !== '' && isConfigSnippetId(snippetId)) {
|
|
46
47
|
return null
|
|
@@ -16,8 +16,7 @@ export interface AxisDragState {
|
|
|
16
16
|
screenStart: number
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export interface
|
|
20
|
-
anchor: 'start' | 'end'
|
|
19
|
+
export interface RatioDragState {
|
|
21
20
|
axis: ScreenAxis
|
|
22
21
|
screenStart: number
|
|
23
22
|
totalSize: number
|
|
@@ -38,10 +37,7 @@ export function getAxisDeltaFromDrag(event: OtuiMouseEvent, drag: AxisDragState)
|
|
|
38
37
|
return getScreenPosition(event, drag.axis) - drag.screenStart
|
|
39
38
|
}
|
|
40
39
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
drag
|
|
44
|
-
): number {
|
|
45
|
-
const offset = getScreenPosition(event, drag.axis) - drag.screenStart
|
|
46
|
-
return drag.anchor === 'start' ? offset / drag.totalSize : 1 - offset / drag.totalSize
|
|
40
|
+
/** Cursor position as a 0..1 fraction of the dragged container. */
|
|
41
|
+
export function getRatioFromDrag(event: OtuiMouseEvent, drag: RatioDragState): number {
|
|
42
|
+
return (getScreenPosition(event, drag.axis) - drag.screenStart) / drag.totalSize
|
|
47
43
|
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import type { AssistantId, TabSession } from '../state/types'
|
|
2
|
+
import type { SideEffectContext } from './side-effect-context'
|
|
3
|
+
|
|
4
|
+
import { logInputDebug } from '../debug/input-log'
|
|
5
|
+
import { createPrefixedId } from '../platform/id'
|
|
6
|
+
import {
|
|
7
|
+
getAllAssistantOptions,
|
|
8
|
+
getAssistantOption,
|
|
9
|
+
isCommandAvailable,
|
|
10
|
+
parseCommand,
|
|
11
|
+
} from '../pty/command-registry'
|
|
12
|
+
import { createTerminalBounds } from '../state/layout-resize'
|
|
13
|
+
import {
|
|
14
|
+
computePaneRects,
|
|
15
|
+
createLeaf,
|
|
16
|
+
getTreeForTab,
|
|
17
|
+
PANE_BORDER,
|
|
18
|
+
type SplitDirection,
|
|
19
|
+
splitNode,
|
|
20
|
+
} from '../state/layout-tree'
|
|
21
|
+
import { getActiveWorkspace, getCurrentProject } from '../state/project-workspaces'
|
|
22
|
+
import { createDefaultTerminalModes } from '../state/terminal-modes'
|
|
23
|
+
import { getSelectedAssistantOption } from './selection'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* How long a freshly spawned assistant is given to draw something before the
|
|
27
|
+
* tab stops being treated as "still starting up".
|
|
28
|
+
*/
|
|
29
|
+
const STARTUP_GRACE_MS = 5_000
|
|
30
|
+
|
|
31
|
+
export function confirmSplitSelection(ctx: SideEffectContext): void {
|
|
32
|
+
const { dispatch, state } = ctx
|
|
33
|
+
const option = getSelectedAssistantOption(state)
|
|
34
|
+
const direction = state.modal.type === 'split-picker' ? state.modal.splitDirection : 'vertical'
|
|
35
|
+
const customCommand = state.customCommands[option.id]
|
|
36
|
+
const tab = createTabSession(
|
|
37
|
+
option.id,
|
|
38
|
+
customCommand,
|
|
39
|
+
state.customCommands,
|
|
40
|
+
getActiveWorkspace(getCurrentProject(state))?.id
|
|
41
|
+
)
|
|
42
|
+
dispatch({ type: 'close-modal' })
|
|
43
|
+
executeSplitPane(ctx, direction, tab)
|
|
44
|
+
dispatch({ focusMode: 'terminal-input', type: 'set-focus-mode' })
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function createTabId(): string {
|
|
48
|
+
return createPrefixedId('tab')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function createTabSession(
|
|
52
|
+
assistant: AssistantId,
|
|
53
|
+
customCommand?: string,
|
|
54
|
+
customCommands?: Record<string, string>,
|
|
55
|
+
workspaceId?: string
|
|
56
|
+
): TabSession {
|
|
57
|
+
const allOptions = getAllAssistantOptions(customCommands ?? {})
|
|
58
|
+
const option = allOptions.find((o) => o.id === assistant) ?? getAssistantOption(0)
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
activity: 'idle',
|
|
62
|
+
assistant,
|
|
63
|
+
buffer: '',
|
|
64
|
+
command: customCommand ?? option.command,
|
|
65
|
+
id: createTabId(),
|
|
66
|
+
status: 'starting',
|
|
67
|
+
terminalModes: createDefaultTerminalModes(),
|
|
68
|
+
title: option.label,
|
|
69
|
+
workspaceId,
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Everything `startTabSession` needs from the side-effect context. */
|
|
74
|
+
type StartTabSessionContext = Pick<
|
|
75
|
+
SideEffectContext,
|
|
76
|
+
'backend' | 'clearStartupGrace' | 'dispatch' | 'startStartupGrace'
|
|
77
|
+
>
|
|
78
|
+
|
|
79
|
+
export interface StartTabSessionOptions {
|
|
80
|
+
cols: number
|
|
81
|
+
rows: number
|
|
82
|
+
cwd?: string
|
|
83
|
+
/** Default true: only a tab the user did not just ask for opts out. */
|
|
84
|
+
autoRenameCandidate?: boolean
|
|
85
|
+
/**
|
|
86
|
+
* Appended after the command's own args. Kept out of `tab.command` on purpose:
|
|
87
|
+
* an initial prompt is up to a few thousand characters, and `command` is what
|
|
88
|
+
* the UI shows, what the snapshot persists, and what the custom-command editor
|
|
89
|
+
* round-trips.
|
|
90
|
+
*/
|
|
91
|
+
extraArgs?: string[]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function startTabSession(
|
|
95
|
+
ctx: StartTabSessionContext,
|
|
96
|
+
tab: Pick<TabSession, 'id' | 'assistant' | 'title' | 'command' | 'workspaceId'>,
|
|
97
|
+
{ autoRenameCandidate = true, cols, cwd, extraArgs, rows }: StartTabSessionOptions
|
|
98
|
+
): void {
|
|
99
|
+
const { backend, clearStartupGrace, dispatch } = ctx
|
|
100
|
+
logInputDebug('app.tab.start.request', {
|
|
101
|
+
cols,
|
|
102
|
+
command: tab.command,
|
|
103
|
+
cwd: cwd ?? null,
|
|
104
|
+
rows,
|
|
105
|
+
tabId: tab.id,
|
|
106
|
+
title: tab.title,
|
|
107
|
+
workspaceId: tab.workspaceId ?? null,
|
|
108
|
+
})
|
|
109
|
+
ctx.startStartupGrace(tab.id, STARTUP_GRACE_MS)
|
|
110
|
+
|
|
111
|
+
const { args, executable } = parseCommand(tab.command)
|
|
112
|
+
|
|
113
|
+
if (!isCommandAvailable(executable)) {
|
|
114
|
+
clearStartupGrace(tab.id)
|
|
115
|
+
dispatch({
|
|
116
|
+
message: `[command not found] ${executable} is not available in PATH.`,
|
|
117
|
+
tabId: tab.id,
|
|
118
|
+
type: 'set-tab-error',
|
|
119
|
+
})
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
backend.createSession({
|
|
124
|
+
args: extraArgs ? [...args, ...extraArgs] : args,
|
|
125
|
+
assistant: tab.assistant,
|
|
126
|
+
autoRenameCandidate,
|
|
127
|
+
cols,
|
|
128
|
+
command: executable,
|
|
129
|
+
cwd,
|
|
130
|
+
rows,
|
|
131
|
+
tabId: tab.id,
|
|
132
|
+
title: tab.title,
|
|
133
|
+
workspaceId: tab.workspaceId,
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Returns the id of the tab it created, so a caller can write into it.
|
|
139
|
+
*
|
|
140
|
+
* `initialPromptArgs` hands the prompt to the CLI at spawn — see
|
|
141
|
+
* `assistantAcceptsPromptArg`. Callers pass it only for assistants that support
|
|
142
|
+
* it, and fall back to `injectPromptWhenReady` otherwise.
|
|
143
|
+
*/
|
|
144
|
+
export function launchAssistant(
|
|
145
|
+
ctx: SideEffectContext,
|
|
146
|
+
assistant: AssistantId,
|
|
147
|
+
workspaceId?: string,
|
|
148
|
+
initialPromptArgs?: string[]
|
|
149
|
+
): string {
|
|
150
|
+
const { dispatch, state } = ctx
|
|
151
|
+
const customCommand = state.customCommands[assistant]
|
|
152
|
+
const tab = createTabSession(
|
|
153
|
+
assistant,
|
|
154
|
+
customCommand,
|
|
155
|
+
state.customCommands,
|
|
156
|
+
workspaceId ?? getActiveWorkspace(getCurrentProject(state))?.id
|
|
157
|
+
)
|
|
158
|
+
logInputDebug('app.launchAssistant', {
|
|
159
|
+
assistant,
|
|
160
|
+
command: tab.command,
|
|
161
|
+
tabId: tab.id,
|
|
162
|
+
})
|
|
163
|
+
dispatch({ tab, type: 'add-tab' })
|
|
164
|
+
dispatch({ focusMode: 'terminal-input', type: 'set-focus-mode' })
|
|
165
|
+
startTabSession(ctx, tab, {
|
|
166
|
+
cols: state.layout.terminalCols,
|
|
167
|
+
cwd: getTabProjectPath(ctx, tab),
|
|
168
|
+
extraArgs: initialPromptArgs,
|
|
169
|
+
rows: state.layout.terminalRows,
|
|
170
|
+
})
|
|
171
|
+
return tab.id
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function getTabProjectPath(
|
|
175
|
+
ctx: SideEffectContext,
|
|
176
|
+
tab: Pick<TabSession, 'workspaceId'>
|
|
177
|
+
): string | undefined {
|
|
178
|
+
// Scoped to the current project on purpose: a tab pinned to a workspace of
|
|
179
|
+
// some other project gets the current project's path, not that project's.
|
|
180
|
+
const project = getCurrentProject(ctx.state)
|
|
181
|
+
const workspace = project?.workspaces?.find((entry) => entry.id === tab.workspaceId)
|
|
182
|
+
if (workspace) return workspace.path
|
|
183
|
+
return ctx.getCurrentProjectProjectPath()
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function startExistingTab(ctx: SideEffectContext, tab: TabSession): void {
|
|
187
|
+
startTabSession(ctx, tab, {
|
|
188
|
+
autoRenameCandidate: false,
|
|
189
|
+
cols: ctx.state.layout.terminalCols,
|
|
190
|
+
cwd: getTabProjectPath(ctx, tab),
|
|
191
|
+
rows: ctx.state.layout.terminalRows,
|
|
192
|
+
})
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function executeSplitPane(
|
|
196
|
+
ctx: SideEffectContext,
|
|
197
|
+
direction: SplitDirection,
|
|
198
|
+
tab: TabSession
|
|
199
|
+
): void {
|
|
200
|
+
const { dispatch, state } = ctx
|
|
201
|
+
const activeTabId = state.activeTabId
|
|
202
|
+
if (!(activeTabId != null && activeTabId !== '')) {
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const existingTree = getTreeForTab(state.layoutTrees, state.tabGroupMap, activeTabId)
|
|
207
|
+
const baseTree = existingTree ?? createLeaf(activeTabId)
|
|
208
|
+
const newTree = splitNode(baseTree, activeTabId, direction, tab.id)
|
|
209
|
+
const bounds = createTerminalBounds(state.layout.terminalCols, state.layout.terminalRows)
|
|
210
|
+
const paneRect = computePaneRects(newTree, bounds).get(tab.id)
|
|
211
|
+
|
|
212
|
+
dispatch({ direction, newTab: tab, type: 'split-pane' })
|
|
213
|
+
startTabSession(ctx, tab, {
|
|
214
|
+
cols: Math.max(1, (paneRect?.cols ?? state.layout.terminalCols) - PANE_BORDER * 2),
|
|
215
|
+
cwd: getTabProjectPath(ctx, tab),
|
|
216
|
+
rows: Math.max(1, (paneRect?.rows ?? state.layout.terminalRows) - PANE_BORDER * 2),
|
|
217
|
+
})
|
|
218
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type MutableRefObject, useEffect, useRef } from 'react'
|
|
2
2
|
|
|
3
|
-
import type { AppAction
|
|
3
|
+
import type { AppAction } from '../state/actions'
|
|
4
|
+
import type { AppState, GitRefreshPayload, TabActivity } from '../state/types'
|
|
4
5
|
|
|
5
6
|
import {
|
|
6
7
|
type AutoCommitConfigSnapshot,
|
|
@@ -61,7 +62,7 @@ export function useAutoCommitDriver({
|
|
|
61
62
|
const prevActivityRef = useRef<Map<string, TabActivity | undefined>>(new Map())
|
|
62
63
|
|
|
63
64
|
useEffect(() => {
|
|
64
|
-
// Always refresh the activity map so re-enabling mid-
|
|
65
|
+
// Always refresh the activity map so re-enabling mid-project doesn't fire
|
|
65
66
|
// a stale "became idle" transition. Skip only the dispatch work.
|
|
66
67
|
const prev = prevActivityRef.current
|
|
67
68
|
const next = new Map<string, TabActivity | undefined>()
|
|
@@ -73,15 +74,15 @@ export function useAutoCommitDriver({
|
|
|
73
74
|
const becameIdle =
|
|
74
75
|
(before === 'working' || before === 'waiting-input') && tab.activity === 'idle'
|
|
75
76
|
if (becameIdle) {
|
|
76
|
-
const
|
|
77
|
-
if (!(
|
|
78
|
-
const
|
|
77
|
+
const projectId = state.currentProjectId
|
|
78
|
+
if (!(projectId != null && projectId !== '')) continue
|
|
79
|
+
const project = state.projects.find((s) => s.id === projectId)
|
|
79
80
|
const git = gitPayloadFromState(state)
|
|
80
81
|
void onActivityTransition(depsRef.current, {
|
|
81
82
|
assistant: tab.assistant,
|
|
82
83
|
git,
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
projectId,
|
|
85
|
+
projectPath: project?.projectPath,
|
|
85
86
|
tabId: tab.id,
|
|
86
87
|
})
|
|
87
88
|
}
|
|
@@ -94,14 +95,14 @@ export function useAutoCommitDriver({
|
|
|
94
95
|
|
|
95
96
|
useEffect(() => {
|
|
96
97
|
if (!configRef.current.enabled) return
|
|
97
|
-
const
|
|
98
|
-
if (!(
|
|
98
|
+
const projectId = state.currentProjectId
|
|
99
|
+
if (!(projectId != null && projectId !== '')) return
|
|
99
100
|
const payload = gitPayloadFromState(state)
|
|
100
101
|
if (!payload) return
|
|
101
102
|
const cacheKey = JSON.stringify(payload)
|
|
102
103
|
if (lastGitHashRef.current === cacheKey) return
|
|
103
104
|
lastGitHashRef.current = cacheKey
|
|
104
|
-
onGitRefresh(depsRef.current,
|
|
105
|
+
onGitRefresh(depsRef.current, projectId, payload)
|
|
105
106
|
|
|
106
107
|
// Debounced trigger: when the working tree stays stable for
|
|
107
108
|
// GIT_STABILIZATION_DEBOUNCE_MS, try to start generation. Covers cases
|
|
@@ -114,14 +115,14 @@ export function useAutoCommitDriver({
|
|
|
114
115
|
? state.tabs.find((tab) => tab.id === activeTabId)
|
|
115
116
|
: undefined
|
|
116
117
|
if (!activeTab) return
|
|
117
|
-
const
|
|
118
|
+
const project = state.projects.find((s) => s.id === projectId)
|
|
118
119
|
gitStabilizeTimerRef.current = setTimeout(() => {
|
|
119
120
|
gitStabilizeTimerRef.current = null
|
|
120
121
|
void onActivityTransition(depsRef.current, {
|
|
121
122
|
assistant: activeTab.assistant,
|
|
122
123
|
git: payload,
|
|
123
|
-
|
|
124
|
-
|
|
124
|
+
projectId,
|
|
125
|
+
projectPath: project?.projectPath,
|
|
125
126
|
tabId: activeTab.id,
|
|
126
127
|
})
|
|
127
128
|
}, GIT_STABILIZATION_DEBOUNCE_MS)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type MutableRefObject, useEffect, useRef } from 'react'
|
|
2
2
|
|
|
3
3
|
import type { SessionBackend } from '../session-backend/types'
|
|
4
|
-
import type { AppAction
|
|
4
|
+
import type { AppAction } from '../state/actions'
|
|
5
|
+
import type { LayoutState } from '../state/types'
|
|
5
6
|
|
|
6
7
|
import { attachCurrentSession } from './backend-attach-runtime'
|
|
7
8
|
import { bindBackendRuntimeEvents } from './backend-runtime-events'
|
|
@@ -11,10 +12,10 @@ interface BackendRuntimeOptions {
|
|
|
11
12
|
backend: SessionBackend
|
|
12
13
|
dispatch: (action: AppAction) => void
|
|
13
14
|
activeTabId: string | null
|
|
14
|
-
|
|
15
|
+
currentProjectId: string | null
|
|
15
16
|
layoutRef: MutableRefObject<LayoutState>
|
|
16
17
|
resizingRef: MutableRefObject<boolean>
|
|
17
|
-
|
|
18
|
+
currentProjectProjectSnapshot: Parameters<SessionBackend['attach']>[0]['projectSnapshot']
|
|
18
19
|
syntaxOverlayEnabled: () => boolean
|
|
19
20
|
}
|
|
20
21
|
|
|
@@ -27,8 +28,8 @@ export interface TabRuntimeControls {
|
|
|
27
28
|
export function useBackendRuntime({
|
|
28
29
|
activeTabId,
|
|
29
30
|
backend,
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
currentProjectId,
|
|
32
|
+
currentProjectProjectSnapshot,
|
|
32
33
|
dispatch,
|
|
33
34
|
layoutRef,
|
|
34
35
|
resizingRef,
|
|
@@ -39,7 +40,7 @@ export function useBackendRuntime({
|
|
|
39
40
|
const { clearAllTimers, clearIdleTimer, clearStartupGrace, startStartupGrace } = timeouts
|
|
40
41
|
|
|
41
42
|
useEffect(() => {
|
|
42
|
-
if (!(
|
|
43
|
+
if (!(currentProjectId != null && currentProjectId !== '')) {
|
|
43
44
|
attachRequestIdRef.current += 1
|
|
44
45
|
return
|
|
45
46
|
}
|
|
@@ -47,22 +48,22 @@ export function useBackendRuntime({
|
|
|
47
48
|
return attachCurrentSession({
|
|
48
49
|
attachRequestIdRef,
|
|
49
50
|
backend,
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
currentProjectId,
|
|
52
|
+
currentProjectProjectSnapshot,
|
|
52
53
|
dispatch,
|
|
53
54
|
layoutRef,
|
|
54
55
|
})
|
|
55
|
-
}, [backend,
|
|
56
|
+
}, [backend, currentProjectId, currentProjectProjectSnapshot, dispatch, layoutRef])
|
|
56
57
|
|
|
57
58
|
useEffect(() => {
|
|
58
|
-
if (!(
|
|
59
|
+
if (!(currentProjectId != null && currentProjectId !== '')) {
|
|
59
60
|
return
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
// The backend owns each tab's scroll position; a backgrounded tab keeps it
|
|
63
64
|
// in its own emulator, so switching back needs no re-anchor from here.
|
|
64
65
|
backend.setActiveTab(activeTabId)
|
|
65
|
-
}, [activeTabId, backend,
|
|
66
|
+
}, [activeTabId, backend, currentProjectId])
|
|
66
67
|
|
|
67
68
|
useEffect(() => {
|
|
68
69
|
return bindBackendRuntimeEvents({
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { useEffect } from 'react'
|
|
2
2
|
|
|
3
|
-
import type { AppAction
|
|
3
|
+
import type { AppAction } from '../state/actions'
|
|
4
|
+
import type { ModalState } from '../state/types'
|
|
4
5
|
|
|
5
6
|
import { searchProjectDirectories, warmDirectoryCache } from '../platform/project-search'
|
|
6
7
|
|
|
7
8
|
const DEFAULT_DIRECTORY_SEARCH_DEBOUNCE_MS = 200
|
|
8
9
|
|
|
9
10
|
function getDirectoryQuery(modal: ModalState): string {
|
|
10
|
-
if (modal.type !== 'create-
|
|
11
|
+
if (modal.type !== 'create-project') {
|
|
11
12
|
return ''
|
|
12
13
|
}
|
|
13
14
|
|
|
@@ -26,12 +27,12 @@ export function useDirectorySearch(
|
|
|
26
27
|
const directoryQuery = getDirectoryQuery(modal)
|
|
27
28
|
|
|
28
29
|
useEffect(() => {
|
|
29
|
-
if (modal.type !== 'create-
|
|
30
|
+
if (modal.type !== 'create-project') return
|
|
30
31
|
void warmDirectoryCache()
|
|
31
32
|
}, [modal.type])
|
|
32
33
|
|
|
33
34
|
useEffect(() => {
|
|
34
|
-
if (modal.type !== 'create-
|
|
35
|
+
if (modal.type !== 'create-project') {
|
|
35
36
|
return
|
|
36
37
|
}
|
|
37
38
|
|
|
@@ -4,8 +4,9 @@ import { useEffect, useRef } from 'react'
|
|
|
4
4
|
|
|
5
5
|
import type { TerminalContentOrigin } from '../input/raw-input-handler'
|
|
6
6
|
import type { SessionBackend } from '../session-backend/types'
|
|
7
|
+
import type { AppAction } from '../state/actions'
|
|
7
8
|
import type { SplitDirection } from '../state/layout-tree'
|
|
8
|
-
import type {
|
|
9
|
+
import type { AppState, BarSide, TabSession, TerminalLine } from '../state/types'
|
|
9
10
|
|
|
10
11
|
import { logInputDebug } from '../debug/input-log'
|
|
11
12
|
import { MultiClickDetector } from '../input/multi-click-detector'
|
|
@@ -23,20 +24,19 @@ import { recordMultiClickClipboardWrite } from './multi-click-clipboard-guard'
|
|
|
23
24
|
import { requestRenderUpTree } from './render-invalidation'
|
|
24
25
|
import { shouldWriteSelectionToClipboard } from './selection-clipboard-dedup'
|
|
25
26
|
import {
|
|
26
|
-
type AnchoredRatioDragState,
|
|
27
27
|
type AxisDragState,
|
|
28
|
-
getAnchoredRatioFromDrag,
|
|
29
28
|
getAxisDeltaFromDrag,
|
|
29
|
+
getRatioFromDrag,
|
|
30
30
|
getSplitRatioFromDrag,
|
|
31
|
+
type RatioDragState,
|
|
31
32
|
type SplitDragState,
|
|
32
33
|
} from './split-drag-controller'
|
|
33
34
|
import { getForwardedMouseSequence, getScrollViewportDelta } from './terminal-mouse-adapter'
|
|
34
35
|
|
|
35
36
|
type ResizeDragState =
|
|
36
37
|
| ({ kind: 'split' } & SplitDragState)
|
|
37
|
-
| ({ initialWidth: number; kind: '
|
|
38
|
-
| ({
|
|
39
|
-
| ({ kind: 'embedded-git'; position: 'top' | 'bottom' } & AnchoredRatioDragState)
|
|
38
|
+
| ({ initialWidth: number; kind: 'bar'; side: BarSide } & AxisDragState)
|
|
39
|
+
| ({ index: number; kind: 'bar-boundary'; side: BarSide } & RatioDragState)
|
|
40
40
|
|
|
41
41
|
interface MultiClickDragState {
|
|
42
42
|
mode: MultiClickMode
|
|
@@ -322,40 +322,32 @@ export function useMouseHandlers({
|
|
|
322
322
|
separatorDragRef.current = { kind: 'split', ...info }
|
|
323
323
|
}
|
|
324
324
|
|
|
325
|
-
const
|
|
326
|
-
separatorDragRef.current = {
|
|
327
|
-
axis: 'x',
|
|
328
|
-
initialWidth: info.initialWidth,
|
|
329
|
-
kind: 'sidebar',
|
|
330
|
-
screenStart: info.screenStart,
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
const handleGitPaneResizeStart = (info: {
|
|
325
|
+
const handleBarResizeStart = (info: {
|
|
335
326
|
initialWidth: number
|
|
336
327
|
screenStart: number
|
|
337
|
-
side:
|
|
328
|
+
side: BarSide
|
|
338
329
|
}) => {
|
|
339
330
|
separatorDragRef.current = {
|
|
340
331
|
axis: 'x',
|
|
341
332
|
initialWidth: info.initialWidth,
|
|
342
|
-
kind: '
|
|
333
|
+
kind: 'bar',
|
|
343
334
|
screenStart: info.screenStart,
|
|
344
335
|
side: info.side,
|
|
345
336
|
}
|
|
346
337
|
}
|
|
347
338
|
|
|
348
|
-
const
|
|
339
|
+
const handleBarBoundaryResizeStart = (info: {
|
|
349
340
|
containerStart: number
|
|
350
|
-
|
|
341
|
+
index: number
|
|
342
|
+
side: BarSide
|
|
351
343
|
totalSize: number
|
|
352
344
|
}) => {
|
|
353
345
|
separatorDragRef.current = {
|
|
354
|
-
anchor: info.position === 'top' ? 'start' : 'end',
|
|
355
346
|
axis: 'y',
|
|
356
|
-
|
|
357
|
-
|
|
347
|
+
index: info.index,
|
|
348
|
+
kind: 'bar-boundary',
|
|
358
349
|
screenStart: info.containerStart,
|
|
350
|
+
side: info.side,
|
|
359
351
|
totalSize: info.totalSize,
|
|
360
352
|
}
|
|
361
353
|
}
|
|
@@ -377,27 +369,22 @@ export function useMouseHandlers({
|
|
|
377
369
|
})
|
|
378
370
|
break
|
|
379
371
|
}
|
|
380
|
-
case '
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
break
|
|
384
|
-
}
|
|
385
|
-
case 'git-pane': {
|
|
386
|
-
const delta = getAxisDeltaFromDrag(event, drag)
|
|
387
|
-
const direction = drag.side === 'left' ? 1 : -1
|
|
388
|
-
const nextWidth = drag.initialWidth + delta * direction
|
|
372
|
+
case 'bar': {
|
|
373
|
+
// The right bar grows as the cursor moves left, hence the flipped sign.
|
|
374
|
+
const delta = getAxisDeltaFromDrag(event, drag) * (drag.side === 'left' ? 1 : -1)
|
|
389
375
|
dispatch({
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
376
|
+
side: drag.side,
|
|
377
|
+
type: 'set-bar-width',
|
|
378
|
+
width: Math.round(drag.initialWidth + delta),
|
|
393
379
|
})
|
|
394
380
|
break
|
|
395
381
|
}
|
|
396
|
-
case '
|
|
382
|
+
case 'bar-boundary': {
|
|
397
383
|
dispatch({
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
384
|
+
index: drag.index,
|
|
385
|
+
ratio: getRatioFromDrag(event, drag),
|
|
386
|
+
side: drag.side,
|
|
387
|
+
type: 'set-bar-boundary',
|
|
401
388
|
})
|
|
402
389
|
break
|
|
403
390
|
}
|
|
@@ -586,13 +573,12 @@ export function useMouseHandlers({
|
|
|
586
573
|
}
|
|
587
574
|
|
|
588
575
|
return {
|
|
589
|
-
|
|
590
|
-
|
|
576
|
+
handleBarBoundaryResizeStart,
|
|
577
|
+
handleBarResizeStart,
|
|
591
578
|
handlePaneActivate,
|
|
592
579
|
handleSeparatorDrag,
|
|
593
580
|
handleSeparatorDragEnd,
|
|
594
581
|
handleSeparatorDragStart,
|
|
595
|
-
handleSidebarResizeStart,
|
|
596
582
|
handleSplitResize,
|
|
597
583
|
handleTerminalClick,
|
|
598
584
|
handleTerminalDrag,
|
|
@@ -2,9 +2,9 @@ import { useEffect, useRef } from 'react'
|
|
|
2
2
|
|
|
3
3
|
import type { AppState } from '../state/types'
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { saveCurrentProject } from '../state/project-save'
|
|
6
6
|
|
|
7
|
-
export function
|
|
7
|
+
export function useProjectAutosave(state: AppState, debounceMs: number): void {
|
|
8
8
|
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
9
9
|
const latestStateRef = useRef(state)
|
|
10
10
|
latestStateRef.current = state
|
|
@@ -15,7 +15,7 @@ export function useWorkspaceAutosave(state: AppState, debounceMs: number): void
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
timeoutRef.current = setTimeout(() => {
|
|
18
|
-
|
|
18
|
+
saveCurrentProject(latestStateRef.current)
|
|
19
19
|
timeoutRef.current = null
|
|
20
20
|
}, debounceMs)
|
|
21
21
|
|
|
@@ -33,7 +33,7 @@ export function useWorkspaceAutosave(state: AppState, debounceMs: number): void
|
|
|
33
33
|
clearTimeout(timeoutRef.current)
|
|
34
34
|
timeoutRef.current = null
|
|
35
35
|
}
|
|
36
|
-
|
|
36
|
+
saveCurrentProject(latestStateRef.current)
|
|
37
37
|
}
|
|
38
38
|
}, [])
|
|
39
39
|
}
|
|
@@ -4,7 +4,8 @@ import { type MutableRefObject, useEffect, useRef } from 'react'
|
|
|
4
4
|
|
|
5
5
|
import type { KeyChord } from '../input/keymap/key-chord'
|
|
6
6
|
import type { SessionBackend } from '../session-backend/types'
|
|
7
|
-
import type { AppAction
|
|
7
|
+
import type { AppAction } from '../state/actions'
|
|
8
|
+
import type { FocusMode, SnippetRecord, TabSession } from '../state/types'
|
|
8
9
|
|
|
9
10
|
import { INPUT_DEBUG_LOG_PATH, logInputDebug } from '../debug/input-log'
|
|
10
11
|
import { createRawInputHandler } from '../input/raw-input-handler'
|