@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,10 +1,10 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events'
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
|
+
ProjectSnapshotV1,
|
|
4
5
|
TabSession,
|
|
5
6
|
TerminalModeState,
|
|
6
7
|
TerminalSnapshot,
|
|
7
|
-
WorkspaceSnapshotV1,
|
|
8
8
|
} from '../state/types'
|
|
9
9
|
|
|
10
10
|
import { logDebug } from '../debug/input-log'
|
|
@@ -12,110 +12,110 @@ import { SessionRegistry } from './session-registry'
|
|
|
12
12
|
|
|
13
13
|
interface SessionManagerEvents {
|
|
14
14
|
render: [
|
|
15
|
-
|
|
15
|
+
projectId: string,
|
|
16
16
|
tabId: string,
|
|
17
17
|
viewport: TerminalSnapshot,
|
|
18
18
|
terminalModes: TerminalModeState,
|
|
19
19
|
]
|
|
20
|
-
exit: [
|
|
21
|
-
error: [
|
|
20
|
+
exit: [projectId: string, tabId: string, exitCode: number]
|
|
21
|
+
error: [projectId: string, tabId: string, message: string]
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export class SessionManager extends EventEmitter<SessionManagerEvents> {
|
|
25
25
|
private registries = new Map<string, SessionRegistry>()
|
|
26
26
|
|
|
27
|
-
private getOrCreateRegistry(
|
|
28
|
-
const existing = this.registries.get(
|
|
27
|
+
private getOrCreateRegistry(projectId: string): SessionRegistry {
|
|
28
|
+
const existing = this.registries.get(projectId)
|
|
29
29
|
if (existing) {
|
|
30
30
|
return existing
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
const registry = new SessionRegistry()
|
|
34
34
|
registry.on('render', (tabId, viewport, terminalModes) => {
|
|
35
|
-
this.emit('render',
|
|
35
|
+
this.emit('render', projectId, tabId, viewport, terminalModes)
|
|
36
36
|
})
|
|
37
37
|
registry.on('exit', (tabId, exitCode) => {
|
|
38
|
-
this.emit('exit',
|
|
38
|
+
this.emit('exit', projectId, tabId, exitCode)
|
|
39
39
|
})
|
|
40
40
|
registry.on('error', (tabId, message) => {
|
|
41
|
-
this.emit('error',
|
|
41
|
+
this.emit('error', projectId, tabId, message)
|
|
42
42
|
})
|
|
43
|
-
this.registries.set(
|
|
43
|
+
this.registries.set(projectId, registry)
|
|
44
44
|
return registry
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
attachSession(
|
|
47
|
+
attachSession(projectId: string, snapshot?: ProjectSnapshotV1) {
|
|
48
48
|
logDebug('daemon.sessionManager.attachSession', {
|
|
49
49
|
hasSnapshot: !!snapshot,
|
|
50
|
-
|
|
50
|
+
projectId,
|
|
51
51
|
snapshotTabs: snapshot?.tabs.length ?? 0,
|
|
52
52
|
})
|
|
53
|
-
return this.getOrCreateRegistry(
|
|
53
|
+
return this.getOrCreateRegistry(projectId).attachFromSnapshot(snapshot)
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
createTab(
|
|
56
|
+
createTab(projectId: string, options: Parameters<SessionRegistry['createSession']>[0]): void {
|
|
57
57
|
logDebug('daemon.sessionManager.createTab', {
|
|
58
58
|
command: options.command,
|
|
59
|
-
|
|
59
|
+
projectId,
|
|
60
60
|
tabId: options.tabId,
|
|
61
61
|
title: options.title,
|
|
62
62
|
})
|
|
63
|
-
this.getOrCreateRegistry(
|
|
63
|
+
this.getOrCreateRegistry(projectId).createSession(options)
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
write(
|
|
67
|
-
this.getOrCreateRegistry(
|
|
66
|
+
write(projectId: string, tabId: string, data: string): void {
|
|
67
|
+
this.getOrCreateRegistry(projectId).write(tabId, data)
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
updateTabMetadata(
|
|
71
|
-
|
|
71
|
+
projectId: string,
|
|
72
72
|
tabId: string,
|
|
73
73
|
patch: { title?: string; autoRenameStatus?: 'eligible' | 'attempted' }
|
|
74
74
|
): void {
|
|
75
|
-
this.getOrCreateRegistry(
|
|
75
|
+
this.getOrCreateRegistry(projectId).updateTabMetadata(tabId, patch)
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
resize(
|
|
79
|
-
this.getOrCreateRegistry(
|
|
78
|
+
resize(projectId: string, cols: number, rows: number, options?: { sync?: boolean }): void {
|
|
79
|
+
this.getOrCreateRegistry(projectId).resizeAll(cols, rows, options)
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
resizeTab(
|
|
83
|
-
|
|
83
|
+
projectId: string,
|
|
84
84
|
tabId: string,
|
|
85
85
|
cols: number,
|
|
86
86
|
rows: number,
|
|
87
87
|
options?: { sync?: boolean }
|
|
88
88
|
): void {
|
|
89
|
-
this.getOrCreateRegistry(
|
|
89
|
+
this.getOrCreateRegistry(projectId).resizeTab(tabId, cols, rows, options)
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
scroll(
|
|
93
|
-
this.getOrCreateRegistry(
|
|
92
|
+
scroll(projectId: string, tabId: string, deltaLines: number): void {
|
|
93
|
+
this.getOrCreateRegistry(projectId).scrollViewport(tabId, deltaLines)
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
scrollToBottom(
|
|
97
|
-
this.getOrCreateRegistry(
|
|
96
|
+
scrollToBottom(projectId: string, tabId: string): void {
|
|
97
|
+
this.getOrCreateRegistry(projectId).scrollViewportToBottom(tabId)
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
setActiveTab(
|
|
101
|
-
this.getOrCreateRegistry(
|
|
100
|
+
setActiveTab(projectId: string, tabId: string | null): void {
|
|
101
|
+
this.getOrCreateRegistry(projectId).setActiveTab(tabId)
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
closeTab(
|
|
105
|
-
this.getOrCreateRegistry(
|
|
104
|
+
closeTab(projectId: string, tabId: string): void {
|
|
105
|
+
this.getOrCreateRegistry(projectId).closeTab(tabId)
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
disposeSession(
|
|
108
|
+
disposeSession(projectId: string): void {
|
|
109
109
|
logDebug('daemon.sessionManager.disposeSession', {
|
|
110
|
-
hadRegistry: this.registries.has(
|
|
111
|
-
|
|
110
|
+
hadRegistry: this.registries.has(projectId),
|
|
111
|
+
projectId,
|
|
112
112
|
})
|
|
113
|
-
const registry = this.registries.get(
|
|
113
|
+
const registry = this.registries.get(projectId)
|
|
114
114
|
if (!registry) {
|
|
115
115
|
return
|
|
116
116
|
}
|
|
117
117
|
registry.disposeAll()
|
|
118
|
-
this.registries.delete(
|
|
118
|
+
this.registries.delete(projectId)
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
disposeAll(): void {
|
|
@@ -143,7 +143,7 @@ export class SessionManager extends EventEmitter<SessionManagerEvents> {
|
|
|
143
143
|
return [...this.registries.keys()]
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
listTabs(
|
|
147
|
-
return this.registries.get(
|
|
146
|
+
listTabs(projectId: string): TabSession[] {
|
|
147
|
+
return this.registries.get(projectId)?.listTabs() ?? []
|
|
148
148
|
}
|
|
149
149
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events'
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
|
+
ProjectSnapshotV1,
|
|
4
5
|
TabSession,
|
|
5
6
|
TerminalModeState,
|
|
6
7
|
TerminalSnapshot,
|
|
7
|
-
WorkspaceSnapshotV1,
|
|
8
8
|
} from '../state/types'
|
|
9
9
|
|
|
10
10
|
import { logDebug } from '../debug/input-log'
|
|
@@ -12,8 +12,8 @@ import { PtyManager } from '../pty/pty-manager'
|
|
|
12
12
|
import {
|
|
13
13
|
normalizeGroupedTabOrder,
|
|
14
14
|
restoreLayoutTrees,
|
|
15
|
-
|
|
16
|
-
} from '../state/
|
|
15
|
+
restoreTabsFromProject,
|
|
16
|
+
} from '../state/project-persistence'
|
|
17
17
|
import { createDefaultTerminalModes } from '../state/terminal-modes'
|
|
18
18
|
|
|
19
19
|
interface SessionRegistryEvents {
|
|
@@ -65,7 +65,7 @@ export class SessionRegistry extends EventEmitter<SessionRegistryEvents> {
|
|
|
65
65
|
})
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
attachFromSnapshot(snapshot:
|
|
68
|
+
attachFromSnapshot(snapshot: ProjectSnapshotV1 | undefined): {
|
|
69
69
|
tabs: TabSession[]
|
|
70
70
|
activeTabId: string | null
|
|
71
71
|
} {
|
|
@@ -75,7 +75,7 @@ export class SessionRegistry extends EventEmitter<SessionRegistryEvents> {
|
|
|
75
75
|
snapshotTabs: snapshot?.tabs.length ?? 0,
|
|
76
76
|
})
|
|
77
77
|
if (this.tabs.size === 0 && snapshot) {
|
|
78
|
-
const restoredTabs =
|
|
78
|
+
const restoredTabs = restoreTabsFromProject(snapshot)
|
|
79
79
|
for (const tab of restoredTabs) {
|
|
80
80
|
this.tabs.set(tab.id, tab)
|
|
81
81
|
}
|
|
@@ -93,7 +93,7 @@ export class SessionRegistry extends EventEmitter<SessionRegistryEvents> {
|
|
|
93
93
|
existing.title = persisted.title
|
|
94
94
|
existing.autoRenameStatus = persisted.autoRenameStatus
|
|
95
95
|
}
|
|
96
|
-
existing.
|
|
96
|
+
existing.workspaceId = persisted.workspaceId
|
|
97
97
|
existing.workerName = persisted.workerName
|
|
98
98
|
}
|
|
99
99
|
}
|
|
@@ -116,10 +116,10 @@ export class SessionRegistry extends EventEmitter<SessionRegistryEvents> {
|
|
|
116
116
|
.map((persistedTab) => this.tabs.get(persistedTab.id))
|
|
117
117
|
.filter((tab): tab is TabSession => tab !== undefined)
|
|
118
118
|
// The registry — not the snapshot — is the source of truth for *which*
|
|
119
|
-
// tabs exist. A tab spawned by a sibling CLI after this
|
|
119
|
+
// tabs exist. A tab spawned by a sibling CLI after this project's
|
|
120
120
|
// snapshot was last persisted lives in `this.tabs` but is absent from
|
|
121
121
|
// `snapshot.tabs`; ordering by the snapshot alone would silently drop it
|
|
122
|
-
// from the attach result, so the UI would render an empty/stale
|
|
122
|
+
// from the attach result, so the UI would render an empty/stale project
|
|
123
123
|
// on switch. Append any live tab the snapshot doesn't mention (in
|
|
124
124
|
// registry order) so membership stays authoritative while the snapshot
|
|
125
125
|
// still supplies ordering + layout for the tabs it captured.
|
|
@@ -151,12 +151,12 @@ export class SessionRegistry extends EventEmitter<SessionRegistryEvents> {
|
|
|
151
151
|
env?: Record<string, string>
|
|
152
152
|
autoRenameStatus?: 'eligible' | 'attempted'
|
|
153
153
|
/**
|
|
154
|
-
*
|
|
154
|
+
* Workspace this tab belongs to (for UI grouping). Stored on the
|
|
155
155
|
* TabSession so an attach-time replay surfaces it inside the right
|
|
156
|
-
*
|
|
156
|
+
* workspace column. Optional — tabs not bound to a workspace are valid.
|
|
157
157
|
*/
|
|
158
|
-
|
|
159
|
-
/**
|
|
158
|
+
workspaceId?: string
|
|
159
|
+
/** Project-scoped orchestration handle; does not affect PTY behavior. */
|
|
160
160
|
workerName?: string
|
|
161
161
|
}): void {
|
|
162
162
|
logDebug('daemon.registry.createSession', {
|
|
@@ -179,7 +179,7 @@ export class SessionRegistry extends EventEmitter<SessionRegistryEvents> {
|
|
|
179
179
|
terminalModes: createDefaultTerminalModes(),
|
|
180
180
|
title: options.title,
|
|
181
181
|
workerName: options.workerName,
|
|
182
|
-
|
|
182
|
+
workspaceId: options.workspaceId,
|
|
183
183
|
})
|
|
184
184
|
} else {
|
|
185
185
|
existing.status = 'starting'
|
|
@@ -192,7 +192,7 @@ export class SessionRegistry extends EventEmitter<SessionRegistryEvents> {
|
|
|
192
192
|
existing.title = options.title
|
|
193
193
|
existing.command = [options.command, ...(options.args ?? [])].join(' ')
|
|
194
194
|
existing.autoRenameStatus = options.autoRenameStatus
|
|
195
|
-
if (options.
|
|
195
|
+
if (options.workspaceId !== undefined) existing.workspaceId = options.workspaceId
|
|
196
196
|
if (options.workerName !== undefined) existing.workerName = options.workerName
|
|
197
197
|
}
|
|
198
198
|
|
package/src/git/divergence.ts
CHANGED
|
@@ -16,7 +16,7 @@ export function parseDivergenceCount(output: string): BranchDivergence | undefin
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
// Commits a
|
|
19
|
+
// Commits a workspace branch is ahead/behind the ref it forked from. Returns
|
|
20
20
|
// undefined when the refs can't be compared (missing ref, not a repo, …) so
|
|
21
21
|
// callers can simply render nothing.
|
|
22
22
|
export async function getBranchDivergence(
|
|
@@ -32,8 +32,39 @@ export async function getBranchDivergence(
|
|
|
32
32
|
return parseDivergenceCount(result.text())
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
// Parses `git diff --shortstat`, e.g.
|
|
36
|
+
// " 7 files changed, 149 insertions(+), 629 deletions(-)". Either side may be
|
|
37
|
+
// absent (a pure addition reports no deletions line at all).
|
|
38
|
+
export function parseShortstat(output: string): { added: number; removed: number } | undefined {
|
|
39
|
+
const trimmed = output.trim()
|
|
40
|
+
if (trimmed === '') return undefined
|
|
41
|
+
const added = /(\d+) insertions?\(\+\)/.exec(trimmed)
|
|
42
|
+
const removed = /(\d+) deletions?\(-\)/.exec(trimmed)
|
|
43
|
+
if (!added && !removed) return undefined
|
|
44
|
+
return {
|
|
45
|
+
added: Number.parseInt(added?.[1] ?? '0', 10),
|
|
46
|
+
removed: Number.parseInt(removed?.[1] ?? '0', 10),
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Lines a workspace has changed since it forked, measured from the merge base
|
|
52
|
+
* to the *working tree* — so commits, staged and unstaged work all count. That
|
|
53
|
+
* is the number the sidebar shows, and it is what a squash-move would land.
|
|
54
|
+
*/
|
|
55
|
+
export async function getWorkspaceDiffStat(
|
|
56
|
+
workspacePath: string,
|
|
57
|
+
baseRef: string
|
|
58
|
+
): Promise<{ added: number; removed: number } | undefined> {
|
|
59
|
+
const mergeBase = await getMergeBase(workspacePath, baseRef)
|
|
60
|
+
if (mergeBase == null) return undefined
|
|
61
|
+
const result = await $`git -C ${workspacePath} diff --shortstat ${mergeBase}`.quiet().nothrow()
|
|
62
|
+
if (result.exitCode !== 0) return undefined
|
|
63
|
+
return parseShortstat(result.text())
|
|
64
|
+
}
|
|
65
|
+
|
|
35
66
|
// Fork point: the most recent commit shared by baseRef and ref. Diffing the
|
|
36
|
-
// working tree against this shows everything the
|
|
67
|
+
// working tree against this shows everything the workspace changed since it
|
|
37
68
|
// branched off (commits + staged + unstaged) — i.e. what a squash-move lands.
|
|
38
69
|
export async function getMergeBase(
|
|
39
70
|
repoPath: string,
|
package/src/git/git-poller.ts
CHANGED
|
@@ -13,7 +13,7 @@ interface Options {
|
|
|
13
13
|
enabled: boolean
|
|
14
14
|
projectPath: string | undefined
|
|
15
15
|
headOffset: number
|
|
16
|
-
// When set, diff the working tree against this ref (
|
|
16
|
+
// When set, diff the working tree against this ref (workspace fork point for
|
|
17
17
|
// review-vs-base) instead of HEAD/HEAD~N.
|
|
18
18
|
compareRef?: string
|
|
19
19
|
}
|
package/src/git/git-status.ts
CHANGED
|
@@ -199,7 +199,7 @@ async function annotateUntrackedCounts(cwd: string, files: GitFileEntry[]): Prom
|
|
|
199
199
|
|
|
200
200
|
interface CollectOptions {
|
|
201
201
|
headOffset?: number
|
|
202
|
-
// Explicit ref to diff the working tree against (e.g. a
|
|
202
|
+
// Explicit ref to diff the working tree against (e.g. a workspace's fork
|
|
203
203
|
// point). Takes precedence over headOffset when set.
|
|
204
204
|
compareRef?: string
|
|
205
205
|
}
|
|
@@ -266,7 +266,7 @@ async function collectAgainstHistorical(
|
|
|
266
266
|
}
|
|
267
267
|
|
|
268
268
|
// Diffs the working tree against an arbitrary ref (a HEAD~N commit, or a
|
|
269
|
-
//
|
|
269
|
+
// workspace's fork point for review-vs-base), surfacing changed files as a
|
|
270
270
|
// single `historical` section plus any untracked files.
|
|
271
271
|
async function collectAgainstRef(cwd: string, ref: string): Promise<GitCollectResult> {
|
|
272
272
|
const [statusResult, nameStatus, numstat, untrackedStatus] = await Promise.all([
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { $ } from 'bun'
|
|
2
2
|
|
|
3
|
-
export interface
|
|
3
|
+
export interface MoveWorkspaceOptions {
|
|
4
4
|
sourcePath: string
|
|
5
5
|
sourceBranch: string
|
|
6
6
|
targetPath: string
|
|
@@ -10,7 +10,7 @@ export interface MoveWorktreeOptions {
|
|
|
10
10
|
keepConflicts?: boolean
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export type
|
|
13
|
+
export type MoveWorkspaceResult =
|
|
14
14
|
| { kind: 'ok'; filesChanged: number; stashedTarget: boolean }
|
|
15
15
|
/** Target dirty/untracked files would be overwritten by the move; nothing was touched. */
|
|
16
16
|
| { kind: 'needs-stash'; files: string[] }
|
|
@@ -76,8 +76,8 @@ function parseOverwrittenFiles(output: string): string[] {
|
|
|
76
76
|
return files
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
// Squashes everything a source
|
|
80
|
-
// target
|
|
79
|
+
// Squashes everything a source workspace changed since its fork point into the
|
|
80
|
+
// target workspace's working tree (staged, uncommitted) — committed work plus
|
|
81
81
|
// any uncommitted/untracked changes. The source is left exactly as it was; the
|
|
82
82
|
// caller decides whether to delete it. The target may be dirty as long as its
|
|
83
83
|
// local changes don't overlap the incoming ones: on overlap the move stops
|
|
@@ -85,7 +85,7 @@ function parseOverwrittenFiles(output: string): string[] {
|
|
|
85
85
|
// conflict nothing is kept — target reset, source restored — unless
|
|
86
86
|
// keepConflicts is set, in which case the conflicted squash state is left in
|
|
87
87
|
// the target for manual resolution.
|
|
88
|
-
export async function
|
|
88
|
+
export async function moveWorkspace(opts: MoveWorkspaceOptions): Promise<MoveWorkspaceResult> {
|
|
89
89
|
const { sourceBranch, sourcePath, targetPath } = opts
|
|
90
90
|
try {
|
|
91
91
|
const headResult = await $`git -C ${sourcePath} rev-parse HEAD`.quiet().nothrow()
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { logDebug } from '../debug/input-log'
|
|
2
|
+
import { runCli } from '../services/ai-usage/spawn'
|
|
3
|
+
|
|
4
|
+
export interface RepoMergeMethods {
|
|
5
|
+
mergeCommitAllowed: boolean
|
|
6
|
+
squashMergeAllowed: boolean
|
|
7
|
+
rebaseMergeAllowed: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Mirrors the method GitHub pre-selects on the merge button: a merge commit
|
|
12
|
+
* when the repo allows one, then squash, then rebase. `gh pr merge` refuses to
|
|
13
|
+
* guess when several are enabled, so we have to name one explicitly.
|
|
14
|
+
*/
|
|
15
|
+
export function pickMergeFlag(methods: RepoMergeMethods): string | null {
|
|
16
|
+
if (methods.mergeCommitAllowed) return '--merge'
|
|
17
|
+
if (methods.squashMergeAllowed) return '--squash'
|
|
18
|
+
if (methods.rebaseMergeAllowed) return '--rebase'
|
|
19
|
+
return null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type MergeResult = { ok: true } | { ok: false; message: string }
|
|
23
|
+
|
|
24
|
+
async function repoMergeMethods(gh: string, cwd: string): Promise<RepoMergeMethods | null> {
|
|
25
|
+
const result = await runCli(
|
|
26
|
+
gh,
|
|
27
|
+
['repo', 'view', '--json', 'mergeCommitAllowed,squashMergeAllowed,rebaseMergeAllowed'],
|
|
28
|
+
15_000,
|
|
29
|
+
cwd
|
|
30
|
+
)
|
|
31
|
+
if (!result.ok) return null
|
|
32
|
+
try {
|
|
33
|
+
const raw = JSON.parse(result.stdout) as Record<string, unknown>
|
|
34
|
+
return {
|
|
35
|
+
mergeCommitAllowed: raw.mergeCommitAllowed === true,
|
|
36
|
+
rebaseMergeAllowed: raw.rebaseMergeAllowed === true,
|
|
37
|
+
squashMergeAllowed: raw.squashMergeAllowed === true,
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function approveAndMergePr(cwd: string): Promise<MergeResult> {
|
|
45
|
+
const gh = Bun.which('gh')
|
|
46
|
+
if (gh === null) return { message: 'gh CLI not found', ok: false }
|
|
47
|
+
|
|
48
|
+
const methods = await repoMergeMethods(gh, cwd)
|
|
49
|
+
if (methods === null) return { message: 'could not read repo merge settings', ok: false }
|
|
50
|
+
const flag = pickMergeFlag(methods)
|
|
51
|
+
if (flag === null) return { message: 'repo allows no merge method', ok: false }
|
|
52
|
+
|
|
53
|
+
// GitHub rejects approving your own PR, and a missing approval is not a
|
|
54
|
+
// reason to skip the merge — best effort, log it, move on.
|
|
55
|
+
const approve = await runCli(gh, ['pr', 'review', '--approve'], 15_000, cwd)
|
|
56
|
+
if (!approve.ok) logDebug('git.pr.approveSkipped', { error: approve.error })
|
|
57
|
+
|
|
58
|
+
const merge = await runCli(gh, ['pr', 'merge', flag], 60_000, cwd)
|
|
59
|
+
if (!merge.ok) {
|
|
60
|
+
const detail = merge.stderr.trim().split('\n')[0] ?? 'merge failed'
|
|
61
|
+
return { message: detail.slice(0, 160), ok: false }
|
|
62
|
+
}
|
|
63
|
+
return { ok: true }
|
|
64
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { useEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
import { prStatusStore } from '../state/pr-status-store'
|
|
4
|
+
import { collectPrStatus } from './pr-status'
|
|
5
|
+
|
|
6
|
+
/** A run in flight is worth watching closely; a settled one barely changes. */
|
|
7
|
+
const ACTIVE_INTERVAL_MS = 15_000
|
|
8
|
+
const IDLE_INTERVAL_MS = 60_000
|
|
9
|
+
const MAX_INTERVAL_MS = 120_000
|
|
10
|
+
|
|
11
|
+
interface Options {
|
|
12
|
+
enabled: boolean
|
|
13
|
+
projectPath: string | undefined
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** One-shot refetch, for when an action we took just invalidated the state. */
|
|
17
|
+
export async function refreshPrStatus(projectPath: string): Promise<void> {
|
|
18
|
+
prStatusStore.getState().setResult(await collectPrStatus(projectPath))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function usePrStatusPolling({ enabled, projectPath }: Options): void {
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (!enabled || !(projectPath != null && projectPath !== '')) return
|
|
24
|
+
|
|
25
|
+
prStatusStore.getState().reset()
|
|
26
|
+
|
|
27
|
+
let cancelled = false
|
|
28
|
+
let timer: ReturnType<typeof setTimeout> | null = null
|
|
29
|
+
let delay = ACTIVE_INTERVAL_MS
|
|
30
|
+
|
|
31
|
+
const schedule = () => {
|
|
32
|
+
if (cancelled) return
|
|
33
|
+
timer = setTimeout(() => void tick(), delay)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const tick = async () => {
|
|
37
|
+
const result = await collectPrStatus(projectPath)
|
|
38
|
+
if (cancelled) return
|
|
39
|
+
prStatusStore.getState().setResult(result)
|
|
40
|
+
if (result.kind === 'error') {
|
|
41
|
+
delay = Math.min(delay * 2, MAX_INTERVAL_MS)
|
|
42
|
+
} else if (result.kind === 'ok' && result.checks.some((c) => c.state === 'pending')) {
|
|
43
|
+
delay = ACTIVE_INTERVAL_MS
|
|
44
|
+
} else {
|
|
45
|
+
delay = IDLE_INTERVAL_MS
|
|
46
|
+
}
|
|
47
|
+
schedule()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
void tick()
|
|
51
|
+
|
|
52
|
+
return () => {
|
|
53
|
+
cancelled = true
|
|
54
|
+
if (timer) clearTimeout(timer)
|
|
55
|
+
}
|
|
56
|
+
}, [enabled, projectPath])
|
|
57
|
+
}
|