@polderlabs/bizar-omp 0.4.4 → 0.4.6
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 +9 -5
- package/dist/cli/agents-ink.d.ts +3 -1
- package/dist/cli/agents-ink.d.ts.map +1 -1
- package/dist/cli/agents-ink.js +264 -41
- package/dist/cli/agents-ink.js.map +1 -1
- package/dist/cli/agents.d.ts.map +1 -1
- package/dist/cli/agents.js +15 -28
- package/dist/cli/agents.js.map +1 -1
- package/dist/cli/directory-selector.d.ts +6 -1
- package/dist/cli/directory-selector.d.ts.map +1 -1
- package/dist/cli/directory-selector.js +58 -15
- package/dist/cli/directory-selector.js.map +1 -1
- package/dist/cli/omb-invocation.d.ts +2 -0
- package/dist/cli/omb-invocation.d.ts.map +1 -1
- package/dist/cli/omb-invocation.js +15 -1
- package/dist/cli/omb-invocation.js.map +1 -1
- package/dist/omp/omb-sessions.d.ts.map +1 -1
- package/dist/omp/omb-sessions.js +13 -0
- package/dist/omp/omb-sessions.js.map +1 -1
- package/docs/compatibility/baseline.json +1 -1
- package/docs/releases/0.4.5.md +7 -0
- package/docs/releases/0.4.6.md +7 -0
- package/docs/releases/native-stable.md +1 -1
- package/package.json +1 -1
- package/skills/bizar-omp/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -141,14 +141,18 @@ The `omb agents` hub provides:
|
|
|
141
141
|
- A global registry of every Bizar session, including sessions running outside the directory where `omb agents` was opened.
|
|
142
142
|
- Session attach, rename, delete, refresh, settings, help, and reconnect controls.
|
|
143
143
|
- A new-session selector opened with `n`.
|
|
144
|
-
-
|
|
145
|
-
-
|
|
144
|
+
- `Tab` focus for the workspace rail. Select a workspace with `Up`/`Down`, then press `Enter` to immediately launch a new session rooted in that workspace; use `n` for the full new-session menu.
|
|
145
|
+
- Workspace separators in the session list so sessions from different projects are easy to scan.
|
|
146
|
+
- Inline rename from the hub with `e`; the name is saved without leaving the TUI for a separate prompt.
|
|
147
|
+
- Mouse support for clicking sessions and workspaces and scrolling either list. The hub enables terminal SGR mouse reporting only while it is open.
|
|
148
|
+
- Fuzzy directory search with recursive subdirectory indexing, parent navigation, recent project shortcuts, path autocomplete, and direct absolute paths.
|
|
149
|
+
- A configurable Project directory root in Settings; it becomes the default new-session root and its full subdirectory tree is indexed for quick dispatch from anywhere.
|
|
146
150
|
- Optional initial task text and session naming without leaving the TUI.
|
|
147
151
|
- Native OMP session titles with deterministic project/task fallbacks.
|
|
148
152
|
|
|
149
|
-
Key controls are `Enter` attach, `
|
|
153
|
+
Key controls are `Enter` attach, `Tab` focus workspaces, workspace `Enter` immediate new session, `n` full new-session menu, `e` rename, `d` delete, `s` settings, `r` refresh, `?` help, and `q` exit. Inside a session, `Ctrl-d` or `Ctrl-c` returns to the hub without sending EOF or interrupt input to OMP. Mouse interaction in the hub supports row clicks and scrolling; mouse scrolling for attached OMP sessions remains configurable in Settings.
|
|
150
154
|
|
|
151
|
-
To configure fast project picking, open `s`, select `
|
|
155
|
+
To configure fast project picking, open `s`, select `Project directory root`, press `Enter`, enter a path such as `~/projects`, and press `s` to save. The new-session selector keeps direct absolute paths and parent navigation, while also indexing the configured folder's recursive subdirectories.
|
|
152
156
|
|
|
153
157
|
## Model roles and native assets
|
|
154
158
|
|
|
@@ -169,7 +173,7 @@ Bizar registers visible `bizar_*` roles without replacing operator-owned mapping
|
|
|
169
173
|
| OMP | 18.2.6, with 18.2.5 and 18.2.4 regression-qualified |
|
|
170
174
|
| Node.js | 22.x and 24.x in CI |
|
|
171
175
|
| Bun | 1.3.14 compatibility job |
|
|
172
|
-
| Package | `@polderlabs/bizar-omp@0.4.
|
|
176
|
+
| Package | `@polderlabs/bizar-omp@0.4.5` |
|
|
173
177
|
|
|
174
178
|
Upstream OMP changes require a new compatibility baseline and contract run. The package does not widen a version range to hide an unqualified runtime.
|
|
175
179
|
|
package/dist/cli/agents-ink.d.ts
CHANGED
|
@@ -13,12 +13,14 @@ export type AgentsHubAction = {
|
|
|
13
13
|
} | {
|
|
14
14
|
type: "rename";
|
|
15
15
|
session: OmbSessionInfo;
|
|
16
|
+
nextId: string;
|
|
16
17
|
} | {
|
|
17
18
|
type: "delete";
|
|
18
19
|
session: OmbSessionInfo;
|
|
20
|
+
nextSessionName?: string;
|
|
19
21
|
} | {
|
|
20
22
|
type: "settings";
|
|
21
23
|
settings: OmbSettings;
|
|
22
24
|
};
|
|
23
|
-
export declare function runInkAgentsHub(cwd: string, env: NodeJS.ProcessEnv, sessions: OmbSessionInfo[], settings: OmbSettings): Promise<AgentsHubAction>;
|
|
25
|
+
export declare function runInkAgentsHub(cwd: string, env: NodeJS.ProcessEnv, sessions: OmbSessionInfo[], settings: OmbSettings, initialSelectedName?: string): Promise<AgentsHubAction>;
|
|
24
26
|
//# sourceMappingURL=agents-ink.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agents-ink.d.ts","sourceRoot":"","sources":["../../src/cli/agents-ink.tsx"],"names":[],"mappings":"AAEA,OAAO,EAA0C,KAAK,cAAc,EAAwB,MAAM,wBAAwB,CAAC;AAC3H,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAG1D,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,cAAc,CAAA;CAAE,GAC3C;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACpE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,cAAc,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"agents-ink.d.ts","sourceRoot":"","sources":["../../src/cli/agents-ink.tsx"],"names":[],"mappings":"AAEA,OAAO,EAA0C,KAAK,cAAc,EAAwB,MAAM,wBAAwB,CAAC;AAC3H,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAG1D,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,cAAc,CAAA;CAAE,GAC3C;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACpE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,cAAc,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC3D;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,cAAc,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,GACrE;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,WAAW,CAAA;CAAE,CAAC;AA8tBhD,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,UAAU,EAAE,QAAQ,EAAE,cAAc,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE,mBAAmB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAOpL"}
|
package/dist/cli/agents-ink.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useMemo, useState } from "react";
|
|
3
|
-
import { Box, Text, useApp, useInput, useWindowSize, render } from "ink";
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { Box, Text, useApp, useInput, useStdin, useStdout, useWindowSize, render } from "ink";
|
|
4
4
|
import { listOmbSessions, ombSessionDisplayName } from "../omp/omb-sessions.js";
|
|
5
|
-
import { filterDirectoryOptions, readDirectoryOptions } from "./directory-selector.js";
|
|
5
|
+
import { expandDirectoryPath, filterDirectoryOptions, readDirectoryCompletions, readDirectoryOptions } from "./directory-selector.js";
|
|
6
6
|
const statusLabel = (state) => state === "attached" ? "CONNECTED" : state === "detached" ? "DISCONNECTED" : "STOPPED";
|
|
7
7
|
const statusColor = (state) => state === "attached" ? "green" : state === "detached" ? "yellow" : "gray";
|
|
8
8
|
const statusMark = (state) => state === "attached" ? "●" : state === "detached" ? "◌" : "○";
|
|
@@ -20,14 +20,25 @@ function age(session) {
|
|
|
20
20
|
return `${Math.floor(seconds / 3600)}h ago`;
|
|
21
21
|
return `${Math.floor(seconds / 86400)}d ago`;
|
|
22
22
|
}
|
|
23
|
-
function
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
function workspacePath(session) {
|
|
24
|
+
return session.cwd === "?" ? session.project : session.cwd;
|
|
25
|
+
}
|
|
26
|
+
function workspaceEntries(sessions) {
|
|
27
|
+
const entries = new Map();
|
|
28
|
+
for (const session of sessions) {
|
|
29
|
+
const path = workspacePath(session);
|
|
30
|
+
const current = entries.get(path);
|
|
31
|
+
if (current)
|
|
32
|
+
current.count += 1;
|
|
33
|
+
else
|
|
34
|
+
entries.set(path, { path, label: session.project, count: 1 });
|
|
35
|
+
}
|
|
36
|
+
return [...entries.values()].sort((a, b) => a.path.localeCompare(b.path));
|
|
28
37
|
}
|
|
29
38
|
function visibleSessions(allSessions, settings) {
|
|
30
|
-
|
|
39
|
+
const stateRank = (state) => state === "attached" ? 0 : state === "detached" ? 1 : 2;
|
|
40
|
+
return [...(settings.showStoppedSessions ? allSessions : allSessions.filter(session => session.state !== "dead"))]
|
|
41
|
+
.sort((a, b) => workspacePath(a).localeCompare(workspacePath(b)) || stateRank(a.state) - stateRank(b.state) || a.project.localeCompare(b.project) || a.name.localeCompare(b.name));
|
|
31
42
|
}
|
|
32
43
|
function Header({ cwd, sessions }) {
|
|
33
44
|
const connected = sessions.filter(session => session.state === "attached").length;
|
|
@@ -35,9 +46,9 @@ function Header({ cwd, sessions }) {
|
|
|
35
46
|
const stopped = sessions.filter(session => session.state === "dead").length;
|
|
36
47
|
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { bold: true, color: "cyan", children: "\u25C6 BIZAR / AGENTS HUB" }), _jsxs(Text, { dimColor: true, children: ["GLOBAL \u00B7 launched from ", short(cwd, 34)] })] }), _jsxs(Box, { marginTop: 1, gap: 2, children: [_jsxs(Text, { color: "green", children: ["\u25CF ", connected, " connected"] }), _jsxs(Text, { color: "yellow", children: ["\u25CC ", disconnected, " disconnected"] }), _jsxs(Text, { color: "gray", children: ["\u25CB ", stopped, " stopped"] })] })] }));
|
|
37
48
|
}
|
|
38
|
-
function WorkspaceRail({ sessions,
|
|
39
|
-
const projects =
|
|
40
|
-
return (_jsxs(Box, { width: "28%", minWidth: 24, flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: [
|
|
49
|
+
function WorkspaceRail({ sessions, selectedPath, focused }) {
|
|
50
|
+
const projects = workspaceEntries(sessions);
|
|
51
|
+
return (_jsxs(Box, { width: "28%", minWidth: 24, flexDirection: "column", borderStyle: "round", borderColor: focused ? "cyan" : "gray", paddingX: 1, children: [_jsxs(Text, { bold: true, color: focused ? "white" : "cyan", children: ["WORKSPACES ", _jsx(Text, { dimColor: true, children: focused ? "· select" : "· Tab" })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { color: focused && !selectedPath ? "white" : "cyan", bold: true, children: [focused && !selectedPath ? "›" : " ", " ALL SESSIONS ", _jsx(Text, { dimColor: true, children: sessions.length })] }), projects.length === 0 ? _jsx(Text, { dimColor: true, children: " No active projects" }) : projects.map(workspace => (_jsxs(Text, { color: selectedPath === workspace.path ? "white" : undefined, backgroundColor: focused && selectedPath === workspace.path ? "blue" : undefined, children: [selectedPath === workspace.path ? " › " : " ", short(workspace.label, 18), " ", _jsxs(Text, { dimColor: true, children: [workspace.count, " \u00B7 ", short(workspace.path, 24)] })] }, workspace.path)))] }), _jsxs(Box, { marginTop: 2, flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, children: [_jsx(Text, { bold: true, color: "yellow", children: "LIVE VIEW" }), _jsx(Text, { dimColor: true, children: "Updates every few seconds." }), _jsx(Text, { dimColor: true, children: "Only bizar-* sessions" }), _jsx(Text, { dimColor: true, children: "are shown or changed." })] })] }));
|
|
41
52
|
}
|
|
42
53
|
function SessionRow({ session, selected, width }) {
|
|
43
54
|
const color = statusColor(session.state);
|
|
@@ -49,35 +60,47 @@ function SessionList({ sessions, selected, rows, width }) {
|
|
|
49
60
|
const start = Math.max(0, Math.min(Math.max(0, sessions.length - visibleCount), selected - Math.floor(visibleCount / 2)));
|
|
50
61
|
const visible = sessions.slice(start, start + visibleCount);
|
|
51
62
|
let previous;
|
|
63
|
+
let previousWorkspace;
|
|
52
64
|
return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { bold: true, color: "cyan", children: "SESSIONS" }), _jsx(Text, { dimColor: true, children: sessions.length ? `${start + 1}-${Math.min(start + visibleCount, sessions.length)} of ${sessions.length}` : "empty" })] }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: visible.length === 0 ? (_jsxs(Box, { flexDirection: "column", paddingY: 2, children: [_jsx(Text, { color: "yellow", children: "No Bizar sessions are running." }), _jsx(Text, { dimColor: true, children: "Press n to dispatch a new agent session." })] })) : visible.map((session, offset) => {
|
|
53
65
|
const index = start + offset;
|
|
54
|
-
const
|
|
66
|
+
const workspace = workspacePath(session);
|
|
67
|
+
const workspaceChanged = workspace !== previousWorkspace;
|
|
68
|
+
const heading = workspaceChanged || session.state !== previous;
|
|
69
|
+
const workspaceHeading = workspaceChanged;
|
|
70
|
+
if (workspaceChanged)
|
|
71
|
+
previousWorkspace = workspace;
|
|
55
72
|
previous = session.state;
|
|
56
|
-
return (_jsxs(Box, { flexDirection: "column", children: [heading && _jsxs(Text, { color: statusColor(session.state), bold: true, children: [statusMark(session.state), " ", statusLabel(session.state)] }), _jsx(SessionRow, { session: session, selected: index === selected, width: width })] }, session.name));
|
|
73
|
+
return (_jsxs(Box, { flexDirection: "column", children: [workspaceHeading && _jsxs(Text, { color: "cyan", bold: true, children: ["\u2501\u2501 ", short(session.project, 20), " \u00B7 ", short(workspace, Math.max(20, width - 24)), " \u2501\u2501"] }), heading && _jsxs(Text, { color: statusColor(session.state), bold: true, children: [statusMark(session.state), " ", statusLabel(session.state)] }), _jsx(SessionRow, { session: session, selected: index === selected, width: width })] }, session.name));
|
|
57
74
|
}) })] }));
|
|
58
75
|
}
|
|
59
76
|
function Inspector({ session }) {
|
|
60
77
|
return (_jsxs(Box, { width: "29%", minWidth: 25, flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "SELECTED SESSION" }), !session ? _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Select a session to inspect it." }) }) : (_jsxs(Box, { marginTop: 1, flexDirection: "column", gap: 1, children: [_jsxs(Text, { bold: true, color: statusColor(session.state), children: [statusMark(session.state), " ", statusLabel(session.state)] }), _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "NAME" }), _jsx(Text, { children: ombSessionDisplayName(session) }), _jsx(Text, { dimColor: true, children: session.nativeName ? "OMP session title" : "Generated from task or project" })] }), _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "PROJECT" }), _jsx(Text, { children: short(session.cwd, 30) })] }), _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "COMMAND" }), _jsx(Text, { children: short(session.command || "omp", 30) })] }), _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "LAST ACTIVITY" }), _jsx(Text, { children: age(session) })] })] }))] }));
|
|
61
78
|
}
|
|
62
79
|
function Footer() {
|
|
63
|
-
return (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, justifyContent: "space-between", children: [_jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: "Enter" }), " attach ", _jsx(Text, { color: "cyan", children: "n" }), " new
|
|
80
|
+
return (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, justifyContent: "space-between", children: [_jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: "Tab" }), " workspaces ", _jsx(Text, { color: "cyan", children: "Enter" }), " attach/new ", _jsx(Text, { color: "cyan", children: "n" }), " new ", _jsx(Text, { color: "cyan", children: "e" }), " rename ", _jsx(Text, { color: "cyan", children: "d" }), " delete"] }), _jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: "s" }), " settings ", _jsx(Text, { color: "cyan", children: "r" }), " refresh ", _jsx(Text, { color: "cyan", children: "?" }), " help ", _jsx(Text, { color: "cyan", children: "q" }), " quit"] })] }));
|
|
64
81
|
}
|
|
65
82
|
function Modal({ title, children, width, columns }) {
|
|
66
83
|
return (_jsxs(Box, { position: "absolute", top: 2, left: Math.max(0, Math.floor((columns - width) / 2)), width: width, flexDirection: "column", borderStyle: "double", borderColor: "cyan", backgroundColor: "black", paddingX: 2, paddingY: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: title }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: children })] }));
|
|
67
84
|
}
|
|
68
|
-
function NewSessionModal({ cwd, sessions, projectsFolder, selected, columns, rows, onSubmit, onClose, }) {
|
|
69
|
-
const initialDirectory =
|
|
85
|
+
function NewSessionModal({ cwd, sessions, projectsFolder, initialDirectory: workspaceDirectory, selected, columns, rows, onSubmit, onClose, }) {
|
|
86
|
+
const initialDirectory = workspaceDirectory?.trim()
|
|
87
|
+
? expandDirectoryPath(workspaceDirectory, cwd)
|
|
88
|
+
: projectsFolder.trim()
|
|
89
|
+
? expandDirectoryPath(projectsFolder, cwd)
|
|
90
|
+
: selected?.cwd && selected.cwd !== "?" ? selected.cwd : cwd;
|
|
70
91
|
const [mode, setMode] = useState("directory");
|
|
71
92
|
const [directoryPath, setDirectoryPath] = useState(initialDirectory);
|
|
72
93
|
const [directoryQuery, setDirectoryQuery] = useState("");
|
|
73
94
|
const [directoryOptions, setDirectoryOptions] = useState([]);
|
|
95
|
+
const [pathOptions, setPathOptions] = useState([]);
|
|
74
96
|
const [directorySelected, setDirectorySelected] = useState(0);
|
|
75
97
|
const [projectCwd, setProjectCwd] = useState(initialDirectory);
|
|
76
98
|
const [task, setTask] = useState("");
|
|
77
99
|
const [sessionId, setSessionId] = useState("");
|
|
78
100
|
const [loading, setLoading] = useState(true);
|
|
79
101
|
const recentPaths = useMemo(() => [...new Set(sessions.map(session => session.cwd).filter(path => path && path !== "?"))], [sessions]);
|
|
80
|
-
const
|
|
102
|
+
const recentPathsKey = useMemo(() => [...recentPaths].sort().join("\0"), [recentPaths]);
|
|
103
|
+
const filteredDirectories = useMemo(() => filterDirectoryOptions([...pathOptions, ...directoryOptions], directoryQuery), [directoryOptions, directoryQuery, pathOptions]);
|
|
81
104
|
const visibleCount = Math.max(4, Math.min(9, rows - 14));
|
|
82
105
|
const directoryStart = Math.max(0, Math.min(Math.max(0, filteredDirectories.length - visibleCount), directorySelected - Math.floor(visibleCount / 2)));
|
|
83
106
|
const visibleDirectories = filteredDirectories.slice(directoryStart, directoryStart + visibleCount);
|
|
@@ -85,7 +108,7 @@ function NewSessionModal({ cwd, sessions, projectsFolder, selected, columns, row
|
|
|
85
108
|
useEffect(() => {
|
|
86
109
|
let active = true;
|
|
87
110
|
setLoading(true);
|
|
88
|
-
void readDirectoryOptions(directoryPath, recentPaths,
|
|
111
|
+
void readDirectoryOptions(directoryPath, recentPaths, "", projectsFolder).then(options => {
|
|
89
112
|
if (!active)
|
|
90
113
|
return;
|
|
91
114
|
setDirectoryOptions(options);
|
|
@@ -93,7 +116,20 @@ function NewSessionModal({ cwd, sessions, projectsFolder, selected, columns, row
|
|
|
93
116
|
setLoading(false);
|
|
94
117
|
});
|
|
95
118
|
return () => { active = false; };
|
|
96
|
-
}, [directoryPath,
|
|
119
|
+
}, [directoryPath, projectsFolder, recentPathsKey]);
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
setDirectorySelected(0);
|
|
122
|
+
if (!directoryQuery.includes("/") && !directoryQuery.startsWith("~")) {
|
|
123
|
+
setPathOptions([]);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
let active = true;
|
|
127
|
+
void readDirectoryOptions(directoryPath, [], directoryQuery).then(options => {
|
|
128
|
+
if (active)
|
|
129
|
+
setPathOptions(options.filter(option => option.kind === "path"));
|
|
130
|
+
});
|
|
131
|
+
return () => { active = false; };
|
|
132
|
+
}, [directoryPath, directoryQuery]);
|
|
97
133
|
useEffect(() => {
|
|
98
134
|
setDirectorySelected(value => Math.min(value, Math.max(0, filteredDirectories.length - 1)));
|
|
99
135
|
}, [filteredDirectories.length]);
|
|
@@ -182,25 +218,50 @@ function NewSessionModal({ cwd, sessions, projectsFolder, selected, columns, row
|
|
|
182
218
|
const index = directoryStart + offset;
|
|
183
219
|
const isSelected = index === directorySelected;
|
|
184
220
|
return (_jsxs(Box, { flexDirection: "column", backgroundColor: isSelected ? "blue" : undefined, children: [_jsxs(Text, { color: isSelected ? "white" : option.kind === "recent" ? "green" : "cyan", bold: isSelected, children: [isSelected ? "›" : " ", " ", option.kind === "parent" ? "↰" : option.kind === "recent" ? "●" : option.kind === "current" ? "◆" : option.kind === "projects-root" ? "▣" : option.kind === "project" ? "◇" : "□", " ", option.label] }), isSelected && _jsxs(Text, { dimColor: true, children: [" ", option.detail ?? option.path] })] }, `${option.kind}:${option.path}`));
|
|
185
|
-
}) }), _jsxs(Text, { dimColor: true, children: [filteredDirectories.length ? `${directoryStart + 1}-${Math.min(directoryStart + visibleCount, filteredDirectories.length)} of ${filteredDirectories.length}` : "0 directories", " \u00B7 Enter choose \u2192 open \u2190 parent"] })] })) : (_jsxs(Text, { dimColor: true, children: [" ", projectCwd] })), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: mode === "task" ? "cyan" : undefined, bold: mode === "task", children: "2 Initial task" }), mode === "task" ? _jsxs(Box, { borderStyle: "single", borderColor: "cyan", paddingX: 1, children: [_jsx(Text, { color: "cyan", children: "\u203A " }), _jsx(Text, { children: task || "Optional task description" })] }) : _jsxs(Text, { dimColor: true, children: [" ", task || "Optional"] })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: mode === "name" ? "cyan" : undefined, bold: mode === "name", children: "3 Session name" }), mode === "name" ? _jsxs(Box, { borderStyle: "single", borderColor: "cyan", paddingX: 1, children: [_jsx(Text, { color: "cyan", children: "\u203A " }), _jsx(Text, { children: sessionId || "Blank generates a name" })] }) : _jsxs(Text, { dimColor: true, children: [" ", sessionId || "Generated automatically"] })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "Type to search or edit \u00B7 Up/Down select \u00B7 Enter continue \u00B7 Esc back" }), _jsx(Text, { color: "yellow", children: mode === "directory" ? projectsFolder ? `Global sessions are listed below; ${projectsFolder} is indexed here.` : "Global sessions are listed below; configure a projects folder in settings for faster picking." : mode === "name" ? "Enter starts the durable OMB host." : "Enter moves to the session name." })] })] }) }));
|
|
221
|
+
}) }), _jsxs(Text, { dimColor: true, children: [filteredDirectories.length ? `${directoryStart + 1}-${Math.min(directoryStart + visibleCount, filteredDirectories.length)} of ${filteredDirectories.length}` : "0 directories", " \u00B7 Enter choose \u2192 open \u2190 parent \u00B7 fuzzy matches scan all subdirectories"] })] })) : (_jsxs(Text, { dimColor: true, children: [" ", projectCwd] })), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: mode === "task" ? "cyan" : undefined, bold: mode === "task", children: "2 Initial task" }), mode === "task" ? _jsxs(Box, { borderStyle: "single", borderColor: "cyan", paddingX: 1, children: [_jsx(Text, { color: "cyan", children: "\u203A " }), _jsx(Text, { children: task || "Optional task description" })] }) : _jsxs(Text, { dimColor: true, children: [" ", task || "Optional"] })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: mode === "name" ? "cyan" : undefined, bold: mode === "name", children: "3 Session name" }), mode === "name" ? _jsxs(Box, { borderStyle: "single", borderColor: "cyan", paddingX: 1, children: [_jsx(Text, { color: "cyan", children: "\u203A " }), _jsx(Text, { children: sessionId || "Blank generates a name" })] }) : _jsxs(Text, { dimColor: true, children: [" ", sessionId || "Generated automatically"] })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "Type to search or edit \u00B7 Up/Down select \u00B7 Enter continue \u00B7 Esc back" }), _jsx(Text, { color: "yellow", children: mode === "directory" ? projectsFolder ? `Global sessions are listed below; ${projectsFolder} is indexed here.` : "Global sessions are listed below; configure a projects folder in settings for faster picking." : mode === "name" ? "Enter starts the durable OMB host." : "Enter moves to the session name." })] })] }) }));
|
|
186
222
|
}
|
|
187
|
-
function SettingsModal({ settings, selected, columns, onToggle, onMove, onSetProjectsFolder, onSave, onClose }) {
|
|
223
|
+
function SettingsModal({ cwd, settings, selected, columns, onToggle, onMove, onSetProjectsFolder, onSave, onClose }) {
|
|
188
224
|
const options = [
|
|
225
|
+
["Project directory root", settings.projectsFolder || "not set"],
|
|
189
226
|
["Attach plain omb to latest live session", settings.autoAttachLatest],
|
|
190
227
|
["Enable mouse scrolling in OMB sessions", settings.mouseScrolling],
|
|
191
228
|
["Show stopped sessions in this view", settings.showStoppedSessions],
|
|
192
|
-
["Projects folder", settings.projectsFolder || "not set"],
|
|
193
229
|
];
|
|
194
230
|
const [editingFolder, setEditingFolder] = useState(false);
|
|
195
231
|
const [folderInput, setFolderInput] = useState(settings.projectsFolder);
|
|
232
|
+
const [folderSuggestions, setFolderSuggestions] = useState([]);
|
|
233
|
+
const [folderSuggestionSelected, setFolderSuggestionSelected] = useState(0);
|
|
234
|
+
useEffect(() => {
|
|
235
|
+
if (!editingFolder) {
|
|
236
|
+
setFolderSuggestions([]);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
let active = true;
|
|
240
|
+
void readDirectoryCompletions(folderInput, cwd).then(options => {
|
|
241
|
+
if (!active)
|
|
242
|
+
return;
|
|
243
|
+
setFolderSuggestions(options);
|
|
244
|
+
setFolderSuggestionSelected(0);
|
|
245
|
+
});
|
|
246
|
+
return () => { active = false; };
|
|
247
|
+
}, [cwd, editingFolder, folderInput]);
|
|
196
248
|
useInput((input, key) => {
|
|
197
249
|
if (editingFolder) {
|
|
198
250
|
if (key.escape) {
|
|
199
251
|
setFolderInput(settings.projectsFolder);
|
|
200
252
|
setEditingFolder(false);
|
|
201
253
|
}
|
|
254
|
+
else if (key.upArrow)
|
|
255
|
+
setFolderSuggestionSelected(value => Math.max(0, value - 1));
|
|
256
|
+
else if (key.downArrow)
|
|
257
|
+
setFolderSuggestionSelected(value => Math.min(Math.max(0, folderSuggestions.length - 1), value + 1));
|
|
258
|
+
else if (key.tab || key.rightArrow) {
|
|
259
|
+
const suggestion = folderSuggestions[folderSuggestionSelected];
|
|
260
|
+
if (suggestion)
|
|
261
|
+
setFolderInput(suggestion.path);
|
|
262
|
+
}
|
|
202
263
|
else if (key.return) {
|
|
203
|
-
onSetProjectsFolder(folderInput.trim());
|
|
264
|
+
onSetProjectsFolder(folderSuggestions[folderSuggestionSelected]?.path ?? folderInput.trim());
|
|
204
265
|
setEditingFolder(false);
|
|
205
266
|
}
|
|
206
267
|
else if (key.backspace || (key.ctrl && input === "h"))
|
|
@@ -220,18 +281,18 @@ function SettingsModal({ settings, selected, columns, onToggle, onMove, onSetPro
|
|
|
220
281
|
else if (input === "s")
|
|
221
282
|
onSave();
|
|
222
283
|
else if (input === " " || key.return) {
|
|
223
|
-
if (selected ===
|
|
284
|
+
if (selected === 0)
|
|
224
285
|
setEditingFolder(true);
|
|
225
286
|
else
|
|
226
287
|
onToggle();
|
|
227
288
|
}
|
|
228
289
|
});
|
|
229
|
-
return (_jsxs(Modal, { title: "OMB SETTINGS", width: Math.min(70, Math.max(46, columns - 6)), columns: columns, children: [options.map(([label, enabled], index) => (_jsxs(Text, { color: index === selected ? "white" : undefined, backgroundColor: index === selected ? "blue" : undefined, children: [index === selected ? "›" : " ", " ", typeof enabled === "boolean" ? `[${enabled ? "on " : "off"}]` : enabled, " ", label] }, label))), selected ===
|
|
290
|
+
return (_jsxs(Modal, { title: "OMB SETTINGS", width: Math.min(70, Math.max(46, columns - 6)), columns: columns, children: [options.map(([label, enabled], index) => (_jsxs(Text, { color: index === selected ? "white" : undefined, backgroundColor: index === selected ? "blue" : undefined, children: [index === selected ? "›" : " ", " ", typeof enabled === "boolean" ? `[${enabled ? "on " : "off"}]` : enabled, " ", label] }, label))), selected === 0 && (_jsxs(Box, { marginTop: 1, borderStyle: "single", borderColor: editingFolder ? "cyan" : "gray", paddingX: 1, children: [_jsx(Text, { color: "cyan", children: "\u2315 " }), _jsx(Text, { children: editingFolder ? folderInput || "Type ~/projects or an absolute path" : settings.projectsFolder || "Press Enter to set a projects folder" })] })), selected === 0 && editingFolder && folderSuggestions.length > 0 && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "Autocomplete" }), folderSuggestions.slice(0, 5).map((suggestion, index) => (_jsxs(Text, { color: index === folderSuggestionSelected ? "white" : undefined, backgroundColor: index === folderSuggestionSelected ? "blue" : undefined, children: [index === folderSuggestionSelected ? "›" : " ", " ", suggestion.path] }, suggestion.path)))] })), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: editingFolder ? "Type path Enter apply Ctrl-U clear Esc cancel" : "Up/Down select Space/Enter edit s save Esc cancel" }), _jsx(Text, { color: "yellow", children: selected === 0 ? "Child directories from this folder appear in the new-session picker." : selected === 2 ? "Mouse mode applies to existing OMB sessions when saved." : "The hub always shows every Bizar session across projects." })] }));
|
|
230
291
|
}
|
|
231
292
|
function HelpModal({ columns, onClose }) {
|
|
232
293
|
useInput((input, key) => { if (key.escape || key.return || input === "q")
|
|
233
294
|
onClose(); });
|
|
234
|
-
return (_jsxs(Modal, { title: "OMB AGENTS SHORTCUTS", width: Math.min(64, Math.max(42, columns - 6)), columns: columns, children: [_jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: "Enter" }), " attach to the selected session"] }), _jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: "n" }), " open the
|
|
295
|
+
return (_jsxs(Modal, { title: "OMB AGENTS SHORTCUTS", width: Math.min(64, Math.max(42, columns - 6)), columns: columns, children: [_jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: "Enter" }), " attach to the selected session"] }), _jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: "Tab" }), " focus workspaces; Enter immediately starts a named session there"] }), _jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: "n" }), " open the full new-session menu"] }), _jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: "Mouse" }), " click rows, scroll lists, and select a workspace"] }), _jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: "e" }), " rename the selected session"] }), _jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: "d" }), " delete after confirmation"] }), _jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: "s" }), " open launcher settings"] }), _jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: "r" }), " refresh the session list"] }), _jsxs(Text, { children: [_jsx(Text, { color: "cyan", children: "q / Esc" }), " leave this view"] }), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "Global view: every tmux session with the bizar- prefix is shown." }), _jsx(Text, { dimColor: true, children: "Press Enter or Esc to return." })] }));
|
|
235
296
|
}
|
|
236
297
|
function DeleteModal({ session, columns, onConfirm, onClose }) {
|
|
237
298
|
useInput((input, key) => {
|
|
@@ -242,24 +303,172 @@ function DeleteModal({ session, columns, onConfirm, onClose }) {
|
|
|
242
303
|
});
|
|
243
304
|
return (_jsxs(Modal, { title: "DELETE SESSION?", width: Math.min(64, Math.max(42, columns - 6)), columns: columns, children: [_jsxs(Text, { bold: true, children: [session.project, "/", session.id] }), _jsx(Text, { dimColor: true, children: short(session.cwd, 54) }), _jsx(Text, { children: " " }), _jsx(Text, { color: "yellow", children: "This stops OMP and removes the tmux session." }), _jsxs(Text, { children: ["Press ", _jsx(Text, { color: "red", bold: true, children: "y" }), " to delete, any other key to cancel."] })] }));
|
|
244
305
|
}
|
|
245
|
-
function
|
|
306
|
+
function RenameModal({ session, columns, onConfirm, onClose }) {
|
|
307
|
+
const [value, setValue] = useState(session.id);
|
|
308
|
+
useInput((input, key) => {
|
|
309
|
+
if (key.escape)
|
|
310
|
+
onClose();
|
|
311
|
+
else if (key.return)
|
|
312
|
+
onConfirm(value.trim() || session.id);
|
|
313
|
+
else if (key.backspace || (key.ctrl && input === "h"))
|
|
314
|
+
setValue(current => current.slice(0, -1));
|
|
315
|
+
else if (key.ctrl && input === "u")
|
|
316
|
+
setValue("");
|
|
317
|
+
else if (input && !key.ctrl && !key.meta && input >= " ")
|
|
318
|
+
setValue(current => `${current}${input}`);
|
|
319
|
+
});
|
|
320
|
+
return (_jsxs(Modal, { title: "RENAME SESSION", width: Math.min(64, Math.max(42, columns - 6)), columns: columns, children: [_jsx(Text, { dimColor: true, children: short(session.cwd, 54) }), _jsxs(Box, { marginTop: 1, borderStyle: "single", borderColor: "cyan", paddingX: 1, children: [_jsx(Text, { color: "cyan", children: "\u203A " }), _jsx(Text, { children: value || "Type a session name" })] }), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "Enter save Ctrl-U clear Esc cancel" })] }));
|
|
321
|
+
}
|
|
322
|
+
/** Enable SGR mouse reporting while the hub owns the alternate screen. */
|
|
323
|
+
function useTerminalMouse(onMouse) {
|
|
324
|
+
const { stdin } = useStdin();
|
|
325
|
+
const { stdout } = useStdout();
|
|
326
|
+
const onMouseRef = useRef(onMouse);
|
|
327
|
+
useEffect(() => { onMouseRef.current = onMouse; }, [onMouse]);
|
|
328
|
+
useEffect(() => {
|
|
329
|
+
if (!stdin.isTTY)
|
|
330
|
+
return;
|
|
331
|
+
const enable = "\u001b[?1000h\u001b[?1006h";
|
|
332
|
+
const disable = "\u001b[?1000l\u001b[?1006l";
|
|
333
|
+
stdout.write(enable);
|
|
334
|
+
let buffer = "";
|
|
335
|
+
const pattern = /\u001b\[<(\d+);(\d+);(\d+)([mM])/g;
|
|
336
|
+
const onData = (chunk) => {
|
|
337
|
+
buffer += chunk.toString();
|
|
338
|
+
pattern.lastIndex = 0;
|
|
339
|
+
let consumed = 0;
|
|
340
|
+
let match;
|
|
341
|
+
while ((match = pattern.exec(buffer))) {
|
|
342
|
+
consumed = pattern.lastIndex;
|
|
343
|
+
onMouseRef.current({ button: Number(match[1]), x: Number(match[2]), y: Number(match[3]), release: match[4] === "m" });
|
|
344
|
+
}
|
|
345
|
+
const remainder = buffer.slice(consumed);
|
|
346
|
+
const escape = remainder.lastIndexOf("\u001b");
|
|
347
|
+
buffer = escape >= 0 ? remainder.slice(escape) : "";
|
|
348
|
+
if (buffer.length > 64)
|
|
349
|
+
buffer = buffer.slice(-64);
|
|
350
|
+
};
|
|
351
|
+
stdin.on("data", onData);
|
|
352
|
+
return () => {
|
|
353
|
+
stdin.off("data", onData);
|
|
354
|
+
stdout.write(disable);
|
|
355
|
+
};
|
|
356
|
+
}, [stdin, stdout]);
|
|
357
|
+
}
|
|
358
|
+
function sessionMouseIndex(sessions, selected, rows, y) {
|
|
359
|
+
const visibleCount = Math.max(4, rows - 7);
|
|
360
|
+
const start = Math.max(0, Math.min(Math.max(0, sessions.length - visibleCount), selected - Math.floor(visibleCount / 2)));
|
|
361
|
+
// The session panel starts below the header, panel border, title, and one
|
|
362
|
+
// spacer row. Keep this in sync with SessionList's compact layout.
|
|
363
|
+
let line = 11;
|
|
364
|
+
let previousWorkspace;
|
|
365
|
+
let previousState;
|
|
366
|
+
for (const [offset, session] of sessions.slice(start, start + visibleCount).entries()) {
|
|
367
|
+
if (session.cwd !== previousWorkspace) {
|
|
368
|
+
line += 1;
|
|
369
|
+
previousWorkspace = session.cwd;
|
|
370
|
+
previousState = undefined;
|
|
371
|
+
}
|
|
372
|
+
if (session.state !== previousState) {
|
|
373
|
+
line += 1;
|
|
374
|
+
previousState = session.state;
|
|
375
|
+
}
|
|
376
|
+
if (y === line || y === line + 1)
|
|
377
|
+
return start + offset;
|
|
378
|
+
line += 2;
|
|
379
|
+
}
|
|
380
|
+
return undefined;
|
|
381
|
+
}
|
|
382
|
+
function AgentsApp({ cwd, env, sessions: initialSessions, settings: initialSettings, initialSelectedName, onAction }) {
|
|
246
383
|
const { columns, rows } = useWindowSize();
|
|
247
384
|
const [allSessions, setAllSessions] = useState(initialSessions);
|
|
248
385
|
const [settings, setSettings] = useState(initialSettings);
|
|
249
|
-
const [
|
|
386
|
+
const [selectedName, setSelectedName] = useState(initialSelectedName ?? initialSessions[0]?.name);
|
|
387
|
+
const [selectedWorkspacePath, setSelectedWorkspacePath] = useState(initialSessions[0]?.cwd);
|
|
388
|
+
const [focus, setFocus] = useState("sessions");
|
|
389
|
+
const [newSessionDirectory, setNewSessionDirectory] = useState();
|
|
250
390
|
const [overlay, setOverlay] = useState();
|
|
251
391
|
const [overlaySelected, setOverlaySelected] = useState(0);
|
|
252
392
|
const [draftSettings, setDraftSettings] = useState(initialSettings);
|
|
253
393
|
const { exit } = useApp();
|
|
254
394
|
const sessions = useMemo(() => visibleSessions(allSessions, settings), [allSessions, settings]);
|
|
395
|
+
const selected = Math.max(0, sessions.findIndex(session => session.name === selectedName));
|
|
255
396
|
const selectedSession = sessions[selected];
|
|
397
|
+
const workspaces = useMemo(() => workspaceEntries(sessions), [sessions]);
|
|
398
|
+
const workspaceIndex = selectedWorkspacePath ? Math.max(0, workspaces.findIndex(workspace => workspace.path === selectedWorkspacePath) + 1) : 0;
|
|
256
399
|
const refresh = async () => setAllSessions(await listOmbSessions(cwd, env));
|
|
257
400
|
useEffect(() => {
|
|
258
401
|
const timer = setInterval(() => { void refresh(); }, 3000);
|
|
259
402
|
return () => clearInterval(timer);
|
|
260
403
|
}, [cwd, env]);
|
|
261
|
-
useEffect(() => {
|
|
404
|
+
useEffect(() => {
|
|
405
|
+
if (sessions.length === 0) {
|
|
406
|
+
setSelectedName(undefined);
|
|
407
|
+
}
|
|
408
|
+
else if (!selectedName || !sessions.some(session => session.name === selectedName)) {
|
|
409
|
+
setSelectedName(sessions[0]?.name);
|
|
410
|
+
}
|
|
411
|
+
}, [sessions, selectedName]);
|
|
412
|
+
useEffect(() => {
|
|
413
|
+
if (selectedWorkspacePath && !workspaces.some(workspace => workspace.path === selectedWorkspacePath))
|
|
414
|
+
setSelectedWorkspacePath(workspaces[0]?.path);
|
|
415
|
+
}, [selectedWorkspacePath, workspaces]);
|
|
262
416
|
const finish = (action) => { onAction(action); exit(); };
|
|
417
|
+
const openNewSession = (workspacePath) => {
|
|
418
|
+
setNewSessionDirectory(workspacePath);
|
|
419
|
+
setOverlay("new");
|
|
420
|
+
setFocus("sessions");
|
|
421
|
+
};
|
|
422
|
+
const launchWorkspaceSession = () => {
|
|
423
|
+
finish({ type: "new", projectCwd: selectedWorkspacePath ?? cwd, task: "", sessionId: "" });
|
|
424
|
+
};
|
|
425
|
+
const selectSession = (index) => {
|
|
426
|
+
const session = sessions[index];
|
|
427
|
+
if (!session)
|
|
428
|
+
return;
|
|
429
|
+
setSelectedName(session.name);
|
|
430
|
+
setSelectedWorkspacePath(workspacePath(session));
|
|
431
|
+
};
|
|
432
|
+
const selectWorkspace = (index) => {
|
|
433
|
+
if (index <= 0)
|
|
434
|
+
setSelectedWorkspacePath(undefined);
|
|
435
|
+
else
|
|
436
|
+
setSelectedWorkspacePath(workspaces[index - 1]?.path);
|
|
437
|
+
};
|
|
438
|
+
useTerminalMouse(event => {
|
|
439
|
+
if (overlay || event.release)
|
|
440
|
+
return;
|
|
441
|
+
if (event.button === 64 || event.button === 65) {
|
|
442
|
+
if (event.x <= Math.floor(columns * 0.28)) {
|
|
443
|
+
setFocus("workspaces");
|
|
444
|
+
selectWorkspace(Math.max(0, Math.min(workspaces.length, workspaceIndex + (event.button === 64 ? -1 : 1))));
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
setFocus("sessions");
|
|
448
|
+
selectSession(Math.max(0, Math.min(Math.max(0, sessions.length - 1), selected + (event.button === 64 ? -1 : 1))));
|
|
449
|
+
}
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
if (event.button !== 0)
|
|
453
|
+
return;
|
|
454
|
+
if (event.x <= Math.floor(columns * 0.28)) {
|
|
455
|
+
// Rail rows start at the ALL SESSIONS line and continue one row per
|
|
456
|
+
// workspace. The clamped index also makes clicks on the rail chrome
|
|
457
|
+
// useful instead of silently doing nothing.
|
|
458
|
+
const index = Math.max(0, Math.min(workspaces.length, event.y - 11));
|
|
459
|
+
setFocus("workspaces");
|
|
460
|
+
selectWorkspace(index);
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
const index = sessionMouseIndex(sessions, selected, rows, event.y);
|
|
464
|
+
if (index === undefined)
|
|
465
|
+
return;
|
|
466
|
+
const session = sessions[index];
|
|
467
|
+
if (session?.name === selectedName)
|
|
468
|
+
finish({ type: "attach", session });
|
|
469
|
+
else
|
|
470
|
+
selectSession(index);
|
|
471
|
+
});
|
|
263
472
|
useInput((input, key) => {
|
|
264
473
|
if (overlay)
|
|
265
474
|
return;
|
|
@@ -271,16 +480,30 @@ function AgentsApp({ cwd, env, sessions: initialSessions, settings: initialSetti
|
|
|
271
480
|
finish({ type: "quit" });
|
|
272
481
|
return;
|
|
273
482
|
}
|
|
274
|
-
if (key.
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
483
|
+
if (key.tab) {
|
|
484
|
+
if (focus === "sessions") {
|
|
485
|
+
setFocus("workspaces");
|
|
486
|
+
setSelectedWorkspacePath(selectedSession ? workspacePath(selectedSession) : workspaces[0]?.path);
|
|
487
|
+
}
|
|
488
|
+
else
|
|
489
|
+
setFocus("sessions");
|
|
490
|
+
}
|
|
491
|
+
else if (focus === "workspaces" && (key.upArrow || input === "k"))
|
|
492
|
+
selectWorkspace(Math.max(0, workspaceIndex - 1));
|
|
493
|
+
else if (focus === "workspaces" && (key.downArrow || input === "j"))
|
|
494
|
+
selectWorkspace(Math.min(workspaces.length, workspaceIndex + 1));
|
|
495
|
+
else if (focus === "workspaces" && key.return)
|
|
496
|
+
launchWorkspaceSession();
|
|
497
|
+
else if (focus === "sessions" && (key.upArrow || input === "k"))
|
|
498
|
+
selectSession(Math.max(0, selected - 1));
|
|
499
|
+
else if (focus === "sessions" && (key.downArrow || input === "j"))
|
|
500
|
+
selectSession(Math.min(Math.max(0, sessions.length - 1), selected + 1));
|
|
278
501
|
else if (key.return && selectedSession)
|
|
279
502
|
finish({ type: "attach", session: selectedSession });
|
|
280
503
|
else if (input === "n")
|
|
281
|
-
|
|
504
|
+
openNewSession();
|
|
282
505
|
else if (input === "e" && selectedSession)
|
|
283
|
-
|
|
506
|
+
setOverlay("rename");
|
|
284
507
|
else if (input === "d" && selectedSession) {
|
|
285
508
|
setOverlay("delete");
|
|
286
509
|
}
|
|
@@ -295,16 +518,16 @@ function AgentsApp({ cwd, env, sessions: initialSessions, settings: initialSetti
|
|
|
295
518
|
setOverlay("settings");
|
|
296
519
|
}
|
|
297
520
|
});
|
|
298
|
-
return (_jsxs(Box, { flexDirection: "column", width: "100%", minHeight: Math.max(12, rows - 1), paddingX: 1, children: [_jsx(Header, { cwd: cwd, sessions: sessions }), _jsxs(Box, { flexGrow: 1, marginTop: 1, gap: 1, children: [_jsx(WorkspaceRail, { sessions: sessions,
|
|
299
|
-
if (overlaySelected ===
|
|
521
|
+
return (_jsxs(Box, { flexDirection: "column", width: "100%", minHeight: Math.max(12, rows - 1), paddingX: 1, children: [_jsx(Header, { cwd: cwd, sessions: sessions }), _jsxs(Box, { flexGrow: 1, marginTop: 1, gap: 1, children: [_jsx(WorkspaceRail, { sessions: sessions, selectedPath: selectedWorkspacePath, focused: focus === "workspaces" }), _jsx(SessionList, { sessions: sessions, selected: selected, rows: rows, width: Math.max(34, Math.floor(columns * 0.42)) }), _jsx(Inspector, { session: selectedSession })] }), _jsx(Footer, {}), overlay === "new" && (_jsx(NewSessionModal, { cwd: cwd, sessions: allSessions, projectsFolder: settings.projectsFolder, initialDirectory: newSessionDirectory, selected: selectedSession, columns: columns, rows: rows, onSubmit: value => finish({ type: "new", ...value }), onClose: () => setOverlay(undefined) })), overlay === "help" && _jsx(HelpModal, { columns: columns, onClose: () => setOverlay(undefined) }), overlay === "delete" && selectedSession && _jsx(DeleteModal, { session: selectedSession, columns: columns, onConfirm: () => finish({ type: "delete", session: selectedSession, nextSessionName: sessions[selected - 1]?.name ?? sessions[selected + 1]?.name }), onClose: () => setOverlay(undefined) }), overlay === "rename" && selectedSession && _jsx(RenameModal, { session: selectedSession, columns: columns, onConfirm: value => finish({ type: "rename", session: selectedSession, nextId: value }), onClose: () => setOverlay(undefined) }), overlay === "settings" && (_jsx(SettingsModal, { cwd: cwd, settings: draftSettings, selected: overlaySelected, onMove: setOverlaySelected, columns: columns, onToggle: () => {
|
|
522
|
+
if (overlaySelected === 0)
|
|
300
523
|
return;
|
|
301
|
-
const field = overlaySelected ===
|
|
524
|
+
const field = overlaySelected === 1 ? "autoAttachLatest" : overlaySelected === 2 ? "mouseScrolling" : "showStoppedSessions";
|
|
302
525
|
setDraftSettings(value => ({ ...value, [field]: !value[field] }));
|
|
303
526
|
}, onSetProjectsFolder: value => setDraftSettings(current => ({ ...current, projectsFolder: value })), onSave: () => finish({ type: "settings", settings: draftSettings }), onClose: () => setOverlay(undefined) }))] }));
|
|
304
527
|
}
|
|
305
|
-
export async function runInkAgentsHub(cwd, env, sessions, settings) {
|
|
528
|
+
export async function runInkAgentsHub(cwd, env, sessions, settings, initialSelectedName) {
|
|
306
529
|
let action = { type: "quit" };
|
|
307
|
-
const app = render(_jsx(AgentsApp, { cwd: cwd, env: env, sessions: sessions, settings: settings, onAction: value => { action = value; } }), {
|
|
530
|
+
const app = render(_jsx(AgentsApp, { cwd: cwd, env: env, sessions: sessions, settings: settings, initialSelectedName: initialSelectedName, onAction: value => { action = value; } }), {
|
|
308
531
|
alternateScreen: true,
|
|
309
532
|
});
|
|
310
533
|
await app.waitUntilExit();
|