@polderlabs/bizar-omp 0.7.6 → 0.7.8

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.
Files changed (59) hide show
  1. package/README.md +16 -10
  2. package/dist/cli/agents-ink.d.ts +34 -0
  3. package/dist/cli/agents-ink.d.ts.map +1 -1
  4. package/dist/cli/agents-ink.js +120 -30
  5. package/dist/cli/agents-ink.js.map +1 -1
  6. package/dist/cli/agents.d.ts +1 -0
  7. package/dist/cli/agents.d.ts.map +1 -1
  8. package/dist/cli/agents.js +37 -13
  9. package/dist/cli/agents.js.map +1 -1
  10. package/dist/cli/doctor.d.ts +4 -4
  11. package/dist/core/types.d.ts +13 -0
  12. package/dist/core/types.d.ts.map +1 -1
  13. package/dist/core/types.js +15 -1
  14. package/dist/core/types.js.map +1 -1
  15. package/dist/extension.d.ts +2 -0
  16. package/dist/extension.d.ts.map +1 -1
  17. package/dist/extension.js +161 -59
  18. package/dist/extension.js.map +1 -1
  19. package/dist/omp/compatibility.d.ts +5 -5
  20. package/dist/omp/compatibility.d.ts.map +1 -1
  21. package/dist/omp/compatibility.js +2 -2
  22. package/dist/omp/compatibility.js.map +1 -1
  23. package/dist/omp/daemon-service.d.ts.map +1 -1
  24. package/dist/omp/daemon-service.js +4 -1
  25. package/dist/omp/daemon-service.js.map +1 -1
  26. package/dist/omp/lifecycle.d.ts +1 -1
  27. package/dist/omp/lifecycle.d.ts.map +1 -1
  28. package/dist/omp/lifecycle.js +2 -1
  29. package/dist/omp/lifecycle.js.map +1 -1
  30. package/dist/omp/omb-sessions.d.ts +60 -3
  31. package/dist/omp/omb-sessions.d.ts.map +1 -1
  32. package/dist/omp/omb-sessions.js +183 -19
  33. package/dist/omp/omb-sessions.js.map +1 -1
  34. package/dist/omp/omb-settings.d.ts +17 -0
  35. package/dist/omp/omb-settings.d.ts.map +1 -1
  36. package/dist/omp/omb-settings.js +10 -0
  37. package/dist/omp/omb-settings.js.map +1 -1
  38. package/dist/omp/terminal-title.d.ts +70 -0
  39. package/dist/omp/terminal-title.d.ts.map +1 -0
  40. package/dist/omp/terminal-title.js +123 -0
  41. package/dist/omp/terminal-title.js.map +1 -0
  42. package/dist/omp/tmux.d.ts +58 -0
  43. package/dist/omp/tmux.d.ts.map +1 -1
  44. package/dist/omp/tmux.js +153 -7
  45. package/dist/omp/tmux.js.map +1 -1
  46. package/dist/storage/journal.d.ts +1 -1
  47. package/dist/storage/journal.d.ts.map +1 -1
  48. package/dist/storage/journal.js +2 -1
  49. package/dist/storage/journal.js.map +1 -1
  50. package/docs/compatibility/baseline.json +8 -7
  51. package/docs/compatibility/supported-surfaces.json +2 -1
  52. package/docs/releases/0.7.7.md +3 -0
  53. package/docs/releases/0.7.8.md +21 -0
  54. package/docs/releases/support-matrix.md +1 -1
  55. package/package.json +1 -1
  56. package/skills/bizar-omp/SKILL.md +24 -0
  57. package/skills/omp-native-development/SKILL.md +1 -1
  58. package/skills/omp-native-development/references/accuracy-and-versioning.md +1 -1
  59. package/skills/omp-native-development/references/source-manifest.json +3 -3
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  <p>
5
5
  <a href="https://www.npmjs.com/package/@polderlabs/bizar-omp"><img src="https://img.shields.io/npm/v/%40polderlabs%2Fbizar-omp?style=flat-square&color=0f766e&label=npm" alt="npm version" /></a>
6
6
  <a href="https://github.com/PolderLabs/BizarHarness-OMP/releases"><img src="https://img.shields.io/github/v/release/PolderLabs/BizarHarness-OMP?style=flat-square&color=2563eb&label=release" alt="latest GitHub release" /></a>
7
- <img src="https://img.shields.io/badge/OMP-18.4.1-2563eb?style=flat-square" alt="OMP 18.4.1" />
7
+ <img src="https://img.shields.io/badge/OMP-18.4.2-2563eb?style=flat-square" alt="OMP 18.4.2" />
8
8
  <img src="https://img.shields.io/badge/Node.js-22%2B-18181b?style=flat-square&logo=nodedotjs&logoColor=5fa04e" alt="Node.js 22 or newer" />
9
9
  <img src="https://img.shields.io/badge/Bun-1.3.14%2B-18181b?style=flat-square&logo=bun&logoColor=fbf0df" alt="Bun 1.3.14 or newer" />
10
10
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-18181b?style=flat-square" alt="MIT license" /></a>
@@ -69,8 +69,8 @@ Bizar does not replace OMP's task system, add a parallel scheduler, or provide a
69
69
 
70
70
  - Node.js 22 or newer
71
71
  - Bun 1.3.14 or newer
72
- - OMP 18.4.1 on `PATH`
73
- - OMP 18.3.2, 18.3.0, 18.2.11, 18.2.8, 18.2.7, 18.2.6, 18.2.5, 18.2.4 remain regression-qualified
72
+ - OMP 18.4.2 on `PATH`
73
+ - OMP 18.4.1, 18.3.2, 18.3.0, 18.2.11, 18.2.8, 18.2.7, 18.2.6, 18.2.5, 18.2.4 remain regression-qualified
74
74
  - tmux on Linux and macOS, or [psmux](https://github.com/psmux/psmux) on Windows for durable sessions
75
75
 
76
76
  Check the host before installing:
@@ -238,10 +238,11 @@ omb agents --new # open the new-session picker immediately
238
238
  omb hub # alias for omb agents
239
239
  ```
240
240
 
241
- The hub shows every Bizar-owned session across projects. It includes:
241
+ By default the hub shows the sessions of the current directory, including sessions whose host has stopped. Press `Tab` to switch to every project, and again to return. Press `f` to cycle the state filter between `Active`, `Stopped`, and `Both`. It includes:
242
242
 
243
+ - A `Cwd`/`All` scope row and an `Active`/`Stopped`/`Both` state row in the header, and matching filters in the plain-text view.
243
244
  - One full-width, workspace-grouped session list with a compact selected-session strip.
244
- - Attach, rename, delete, refresh, settings, help, and reconnect controls.
245
+ - Attach, continue, rename, delete, refresh, settings, help, and reconnect controls.
245
246
  - A new-session selector opened with `n`.
246
247
  - Recursive search inside the configured Project directory root, parent navigation, recent paths, autocomplete, and direct absolute paths.
247
248
  - Optional task text and an explicit session name.
@@ -252,7 +253,9 @@ The hub shows every Bizar-owned session across projects. It includes:
252
253
 
253
254
  | Key | Action |
254
255
  | --- | --- |
255
- | `Enter` | Attach to the selected live session |
256
+ | `Tab` | Switch the list between this directory (`Cwd`) and every project (`All`) |
257
+ | `f` | Cycle the state filter between `Active`, `Stopped`, and `Both` |
258
+ | `Enter` | Attach to the selected live session, or continue a stopped one by restarting its host |
256
259
  | `n` | Open the full new-session menu |
257
260
  | `e` | Rename the selected session inline |
258
261
  | `d` | Delete the selected Bizar session |
@@ -263,16 +266,16 @@ The hub shows every Bizar-owned session across projects. It includes:
263
266
 
264
267
  Mouse clicks select sessions and a second click opens a live one. Mouse scrolling moves through the session list while the hub is open. The hub enables terminal SGR mouse reporting only for its own lifetime. Mouse behavior inside an attached OMP session follows the session's OMP and multiplexer settings.
265
268
 
266
- To configure project discovery, open `s`, select `Project directory root`, press `Enter`, enter a path such as `~/projects`, and save. Use `Show stopped sessions in this view` when you need to inspect sessions whose host is no longer running. Deleting a session also tombstones its native journal so a recent history entry cannot reappear as an unopenable session; deleting an already unhosted row removes that stale history entry.
269
+ To configure project discovery, open `s`, select `Project directory root`, press `Enter`, enter a path such as `~/projects`, and save. Use `Agents view scope` to make `Cwd` or `All` the opening view; the choice is shared by every hub window. Use `Sessions shown` to keep only active sessions, only stopped ones, or both; stopped sessions are shown by default so a conversation whose host exited stays reachable. Pressing `Enter` on a stopped session restarts a host bound to that exact conversation and attaches to it. Deleting a session also tombstones its native journal so a recent history entry cannot reappear as an unopenable session; deleting an already unhosted row removes that stale history entry.
267
270
 
268
271
  ### Session states
269
272
 
270
273
  | State | Meaning |
271
274
  | --- | --- |
272
275
  | `working` | The journal shows unfinished work. |
273
- | `waiting` | The agent finished its turn and is waiting for input. |
276
+ | `waiting` | The agent finished its turn and is waiting for input, whether or not a client is attached. |
274
277
  | `connected` | A client is attached and no structured activity has been journaled yet. |
275
- | `disconnected` | The host is live and unattached, with no work in progress. |
278
+ | `disconnected` | The host is live and unattached, and the journal gives no evidence either way. |
276
279
  | `stopped` | No host is observed and the journal is silent. |
277
280
 
278
281
  State follows journal activity and host liveness, not tmux attachment alone. A detached session can remain `working`. Activity that stops advancing is marked `stalled`; old journal data is marked `stale`.
@@ -386,6 +389,9 @@ The role names are selectors, not guarantees of speed, cost, provider, or model
386
389
  - OMP keeps ownership of credentials, providers, model selections, approval policies, tool policies, and project overrides.
387
390
  - Managed worker changes stay isolated and require explicit parent integration. Bizar never silently applies worker patches.
388
391
  - Git worktree cleanup is preview-first, ownership-aware, and non-forced. Ignored or uncertain content blocks cleanup.
392
+ - A journal has at most one writer. A host that resumes a different conversation probes the target for a competing writer, then moves its own generation binding and journal manifest together; if either cannot be written, the host is left deliberately unrecoverable rather than still bound to the conversation it left, and a dead pane refuses to respawn instead of resuming the wrong one. The probe is point-in-time — OMP emits no cancellation event for a switch that does not commit, so ownership is re-established on the confirmed switch rather than held across that gap.
393
+ - The agents hub never presents an unanswered multiplexer scan as an empty session list. A refused scan keeps the last confirmed rows and marks the view degraded; a refused scan at startup is reported rather than opening an empty hub.
394
+ - Managed-workflow refusals name the run, its phase, and the cancel that releases it. A run left mid-flight otherwise blocks every `task` call and every non-allowlisted tool for the rest of the session with no in-band way out.
389
395
  - Install only in projects and environments you trust. Review `setup --dry-run` and `bizar-omp install-doctor` before making changes.
390
396
 
391
397
  ### Qualification boundary
@@ -398,7 +404,7 @@ The release does not claim OS sandboxing, provider model quality, universal nati
398
404
 
399
405
  | Component | Qualified baseline |
400
406
  | --- | --- |
401
- | OMP | 18.4.1 at release commit `2e07c170b514df32f45b3e84cb8660c3ed86a93d`; 18.3.0, 18.2.11, 18.2.8, 18.2.7, 18.2.6, 18.2.5, and 18.2.4 remain regression-qualified |
407
+ | OMP | 18.4.2 at release commit `4620bb8338e0ecace7ea237da9d5088d16068617`; 18.3.0, 18.2.11, 18.2.8, 18.2.7, 18.2.6, 18.2.5, and 18.2.4 remain regression-qualified |
402
408
  | Node.js | 22.x and 24.x |
403
409
  | Bun | 1.3.14 compatibility job |
404
410
  | Package | `@polderlabs/bizar-omp@0.7.3` |
@@ -5,6 +5,15 @@ export type AgentsHubAction = {
5
5
  } | {
6
6
  type: "attach";
7
7
  session: OmbSessionInfo;
8
+ }
9
+ /**
10
+ * Restart a host for a session whose tmux session exited, so its journal can
11
+ * be picked up again. Attaching is impossible while nothing hosts it, so
12
+ * this is the only way back into a stopped conversation.
13
+ */
14
+ | {
15
+ type: "continue";
16
+ session: OmbSessionInfo;
8
17
  } | {
9
18
  type: "new";
10
19
  projectCwd: string;
@@ -22,5 +31,30 @@ export type AgentsHubAction = {
22
31
  type: "settings";
23
32
  settings: OmbSettings;
24
33
  };
34
+ /** Terminal lines consumed by the hub chrome above the first workspace heading. */
35
+ export declare const FIRST_ROW_LINE = 12;
36
+ /**
37
+ * Slice the window of rows that fits beside the hub's fixed chrome.
38
+ *
39
+ * The chrome (header with scope toolbar, list frame, selected strip, footer)
40
+ * costs `CHROME_ROWS`; the remainder is the row budget, where every session
41
+ * also spends a line on a workspace heading. Exported because both this and the
42
+ * mouse hit-test below must agree on where the first row lands, and both are
43
+ * easy to break by changing the header without changing a constant.
44
+ */
45
+ export declare function sessionWindow(sessions: OmbSessionInfo[], selected: number, rows: number): {
46
+ start: number;
47
+ visible: OmbSessionInfo[];
48
+ };
49
+ /**
50
+ * Map a terminal row to the session rendered there, or `undefined` for chrome.
51
+ *
52
+ * `FIRST_ROW_LINE` counts the fixed chrome above the first workspace heading:
53
+ * outer padding, header border, title, scope filter row, header border, a
54
+ * one-row gap, list border, list title, and a one-row gap. Measured against a
55
+ * live pane; a stale value silently breaks row clicks without failing any
56
+ * rendering test, so it is pinned by `tests/agents-ink.test.ts`.
57
+ */
58
+ export declare function sessionMouseIndex(sessions: OmbSessionInfo[], selected: number, rows: number, y: number): number | undefined;
25
59
  export declare function runInkAgentsHub(cwd: string, env: NodeJS.ProcessEnv, sessions: OmbSessionInfo[], settings: OmbSettings, initialSelectedName?: string, openNewSession?: boolean): Promise<AgentsHubAction>;
26
60
  //# 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;AAE3H,OAAO,EAAoB,KAAK,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAI5E,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;AA0qBhD,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,EAAE,cAAc,UAAQ,GAAG,OAAO,CAAC,eAAe,CAAC,CAO5M"}
1
+ {"version":3,"file":"agents-ink.d.ts","sourceRoot":"","sources":["../../src/cli/agents-ink.tsx"],"names":[],"mappings":"AAEA,OAAO,EAA+E,KAAK,cAAc,EAAwB,MAAM,wBAAwB,CAAC;AAEhK,OAAO,EAAiF,KAAK,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAIzI,MAAM,MAAM,eAAe,GACvB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,cAAc,CAAA;CAAE;AAC7C;;;;GAIG;GACD;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,cAAc,CAAA;CAAE,GAC7C;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;AA4HhD,mFAAmF;AACnF,eAAO,MAAM,cAAc,KAAK,CAAC;AAKjC;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,cAAc,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,cAAc,EAAE,CAAA;CAAE,CActI;AAybD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,cAAc,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAU3H;AA8KD,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,EAAE,cAAc,UAAQ,GAAG,OAAO,CAAC,eAAe,CAAC,CAO5M"}
@@ -1,9 +1,9 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useRef, useState } from "react";
3
3
  import { Box, Text, useApp, useInput, useStdin, useStdout, useWindowSize, render } from "ink";
4
- import { listOmbSessions, ombSessionDisplayName } from "../omp/omb-sessions.js";
4
+ import { filterOmbSessions, listOmbSessions, ombScanAuthority, ombSessionDisplayName } from "../omp/omb-sessions.js";
5
5
  import { activitySummary, compactAge } from "../omp/session-activity.js";
6
- import { watchOmbSettings } from "../omp/omb-settings.js";
6
+ import { saveOmbSettings, watchOmbSettings } from "../omp/omb-settings.js";
7
7
  import { directoryKeyAction, expandDirectoryPath, filterDirectoryOptions, readDirectoryCompletions, readDirectoryOptions } from "./directory-selector.js";
8
8
  import { OMP_THEME } from "./omp-theme.js";
9
9
  const STATUS_STYLE = {
@@ -19,9 +19,9 @@ const HUB_STATES = ["working", "waiting", "connected", "disconnected", "stopped"
19
19
  /** One-line meaning of each state, used by the help overlay. */
20
20
  const STATUS_HINT = {
21
21
  working: "the journal shows unfinished work",
22
- waiting: "attached and waiting for your input",
23
- connected: "attached with nothing in flight",
24
- disconnected: "no client attached and nothing in flight",
22
+ waiting: "finished its turn and is waiting for your input",
23
+ connected: "attached, with no journal evidence of a phase",
24
+ disconnected: "live, with no client attached and no journal evidence either way",
25
25
  stopped: "no host observed and the journal went silent",
26
26
  unknown: "recent journal exists, but no live host or process was observed",
27
27
  };
@@ -49,14 +49,24 @@ function age(session) {
49
49
  function workspacePath(session) {
50
50
  return session.cwd === "?" ? session.project : session.cwd;
51
51
  }
52
- function visibleSessions(allSessions, settings) {
53
- return [...(settings.showStoppedSessions ? allSessions : allSessions.filter(session => session.state !== "stopped"))]
54
- .sort((a, b) => workspacePath(a).localeCompare(workspacePath(b)) || HUB_STATES.indexOf(a.state) - HUB_STATES.indexOf(b.state) || a.name.localeCompare(b.name));
52
+ function visibleSessions(allSessions, settings, cwd) {
53
+ return filterOmbSessions(allSessions, cwd, {
54
+ scope: settings.agentsScope,
55
+ includeStopped: settings.showStoppedSessions,
56
+ sessionFilter: settings.sessionFilter,
57
+ }).sort((a, b) => workspacePath(a).localeCompare(workspacePath(b)) || HUB_STATES.indexOf(a.state) - HUB_STATES.indexOf(b.state) || a.name.localeCompare(b.name));
55
58
  }
56
- function Header({ sessions }) {
59
+ function FilterTabs({ active, color, background }) {
60
+ return _jsxs(Text, { bold: true, color: active ? OMP_THEME.text : OMP_THEME.muted, backgroundColor: active ? background : undefined, children: [" ", color, " "] });
61
+ }
62
+ function Header({ sessions, scope, filter, cwd, scanIssue }) {
57
63
  const working = sessions.filter(session => session.state === "working").length;
58
64
  const waiting = sessions.filter(session => session.state === "waiting").length;
59
- return (_jsxs(Box, { borderStyle: "round", borderColor: OMP_THEME.accent, backgroundColor: OMP_THEME.surface, paddingX: 2, justifyContent: "space-between", children: [_jsxs(Text, { bold: true, color: OMP_THEME.accent, children: ["\u03C0 ", _jsx(Text, { color: OMP_THEME.text, children: "BIZAR SESSIONS" })] }), _jsxs(Text, { color: OMP_THEME.muted, children: [sessions.length, " total ", _jsxs(Text, { color: OMP_THEME.success, children: ["\u00B7 ", working, " working"] }), " ", _jsxs(Text, { color: OMP_THEME.warning, children: ["\u00B7 ", waiting, " waiting"] })] })] }));
65
+ const connected = sessions.filter(session => session.state === "connected").length;
66
+ const disconnected = sessions.filter(session => session.state === "disconnected").length;
67
+ const stopped = sessions.filter(session => session.state === "stopped").length;
68
+ const cwdLabel = scope === "cwd" ? short(cwd, 32) : "every project";
69
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: OMP_THEME.accent, backgroundColor: OMP_THEME.surface, paddingX: 2, paddingY: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Text, { bold: true, color: OMP_THEME.accent, children: ["\u03C0 ", _jsx(Text, { color: OMP_THEME.text, children: "BIZAR SESSIONS" })] }), _jsxs(Text, { color: OMP_THEME.muted, children: [sessions.length, " shown ", _jsxs(Text, { color: OMP_THEME.success, children: ["\u00B7 ", working, " working"] }), " ", _jsxs(Text, { color: OMP_THEME.warning, children: ["\u00B7 ", waiting, " waiting"] }), stopped > 0 && _jsxs(Text, { color: OMP_THEME.muted, children: [" \u00B7 ", stopped, " stopped"] })] })] }), _jsxs(Box, { marginTop: 1, justifyContent: "space-between", children: [_jsxs(Box, { children: [_jsx(Text, { color: OMP_THEME.muted, children: "Scope:" }), _jsx(FilterTabs, { active: scope === "cwd", color: `Cwd (${cwdLabel})`, background: OMP_THEME.selectedBackground }), _jsx(FilterTabs, { active: scope === "all", color: "All", background: OMP_THEME.selectedBackground }), _jsx(Text, { children: " " }), _jsx(Text, { color: OMP_THEME.muted, children: "State:" }), _jsx(FilterTabs, { active: filter === "active", color: "Active", background: OMP_THEME.selectedBackground }), _jsx(FilterTabs, { active: filter === "stopped", color: "Stopped", background: OMP_THEME.selectedBackground }), _jsx(FilterTabs, { active: filter === "all", color: "Both", background: OMP_THEME.selectedBackground })] }), _jsxs(Text, { color: OMP_THEME.muted, children: [working + waiting + connected + disconnected, " live \u00B7 ", stopped, " stopped"] })] }), scanIssue && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: OMP_THEME.warning, children: ["! Showing the last confirmed session list \u2014 ", scanIssue, "."] }) }))] }));
60
70
  }
61
71
  function SessionRow({ session, selected, width }) {
62
72
  const status = STATUS_STYLE[session.state];
@@ -64,10 +74,23 @@ function SessionRow({ session, selected, width }) {
64
74
  const displayName = ombSessionDisplayName(session);
65
75
  return (_jsxs(Box, { width: "100%", paddingX: 1, backgroundColor: selected ? OMP_THEME.selectedBackground : undefined, justifyContent: "space-between", children: [_jsx(Box, { justifyContent: "space-between", children: _jsxs(Text, { color: selected ? OMP_THEME.text : status.color, bold: selected, children: [selected ? _jsx(Text, { color: OMP_THEME.accent, children: "\u203A" }) : " ", " ", status.mark, " ", short(displayName, Math.max(18, width - (width >= 100 ? 58 : 24)))] }) }), _jsxs(Text, { color: selected ? OMP_THEME.accent : status.color, children: [" ", status.label] }), width >= 100 && _jsxs(Text, { color: activityColor(activity), children: [" \u00B7 ", short(activitySummary(activity), 24), " ", _jsxs(Text, { color: OMP_THEME.muted, children: ["\u00B7 ", activity ? compactAge(activity.ageSeconds) : age(session), " \u00B7 ", session.nativeOutcome, session.uncertainLiveness ? " · liveness unknown" : ""] })] })] }));
66
76
  }
67
- function sessionWindow(sessions, selected, rows) {
77
+ /** Terminal lines consumed by the hub chrome above the first workspace heading. */
78
+ export const FIRST_ROW_LINE = 12;
79
+ /** Terminal lines the hub chrome consumes in total, leaving the row budget. */
80
+ const CHROME_ROWS = 16;
81
+ /**
82
+ * Slice the window of rows that fits beside the hub's fixed chrome.
83
+ *
84
+ * The chrome (header with scope toolbar, list frame, selected strip, footer)
85
+ * costs `CHROME_ROWS`; the remainder is the row budget, where every session
86
+ * also spends a line on a workspace heading. Exported because both this and the
87
+ * mouse hit-test below must agree on where the first row lands, and both are
88
+ * easy to break by changing the header without changing a constant.
89
+ */
90
+ export function sessionWindow(sessions, selected, rows) {
68
91
  if (!sessions.length)
69
92
  return { start: 0, visible: [] };
70
- const budget = Math.max(3, rows - 14);
93
+ const budget = Math.max(3, rows - CHROME_ROWS);
71
94
  const lineCost = (items) => items.reduce((cost, session, index) => cost + 1 + (index === 0 || workspacePath(items[index - 1]) !== workspacePath(session) ? 1 : 0), 0);
72
95
  let start = Math.max(0, Math.min(selected, sessions.length - 1));
73
96
  let end = start + 1;
@@ -106,7 +129,7 @@ function SelectedSummary({ session, width }) {
106
129
  return (_jsxs(Box, { borderStyle: "round", borderColor: OMP_THEME.border, paddingX: 1, justifyContent: "space-between", children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: short(ombSessionDisplayName(session), Math.max(16, Math.floor(width * .3))) }), " ", _jsxs(Text, { color: status.color, children: [status.mark, " ", status.label] })] }), _jsx(Text, { color: activityColor(activity), children: short(activitySummary(activity), Math.max(16, Math.floor(width * .3))) }), _jsxs(Text, { color: OMP_THEME.muted, children: [session.hosted ? short(session.cwd, Math.max(18, Math.floor(width * .28))) : session.processObserved ? "process observed; no host" : "liveness unknown", " \u00B7 ", session.attached ? "attached" : "detached", " \u00B7 ", session.nativeOutcome] })] }));
107
130
  }
108
131
  function Footer() {
109
- return (_jsx(Box, { borderStyle: "round", borderColor: OMP_THEME.border, paddingX: 1, children: _jsxs(Text, { color: OMP_THEME.muted, children: [_jsx(Text, { color: OMP_THEME.accent, children: "Enter" }), " open ", _jsx(Text, { color: OMP_THEME.accent, children: "n" }), " new ", _jsx(Text, { color: OMP_THEME.accent, children: "e" }), " rename ", _jsx(Text, { color: OMP_THEME.accent, children: "d" }), " delete ", _jsx(Text, { color: OMP_THEME.accent, children: "s" }), " settings ", _jsx(Text, { color: OMP_THEME.accent, children: "?" }), " more ", _jsx(Text, { color: OMP_THEME.accent, children: "q" }), " quit"] }) }));
132
+ return (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { color: OMP_THEME.muted, children: [_jsx(Text, { color: OMP_THEME.accent, children: "Tab" }), " scope ", _jsx(Text, { color: OMP_THEME.accent, children: "Enter" }), " open ", _jsx(Text, { color: OMP_THEME.accent, children: "n" }), " new ", _jsx(Text, { color: OMP_THEME.accent, children: "e" }), " rename ", _jsx(Text, { color: OMP_THEME.accent, children: "d" }), " delete ", _jsx(Text, { color: OMP_THEME.accent, children: "s" }), " settings ", _jsx(Text, { color: OMP_THEME.accent, children: "?" }), " help ", _jsx(Text, { color: OMP_THEME.accent, children: "q" }), " quit"] }) }));
110
133
  }
111
134
  function Modal({ title, children, width, columns }) {
112
135
  return (_jsxs(Box, { position: "absolute", top: 2, left: Math.max(0, Math.floor((columns - width) / 2)), width: width, flexDirection: "column", borderStyle: "round", borderColor: OMP_THEME.accent, backgroundColor: OMP_THEME.surface, paddingX: 2, paddingY: 1, children: [_jsxs(Text, { bold: true, color: OMP_THEME.accent, children: ["\u03C0 \u203A ", title] }), _jsx(Box, { marginTop: 1, width: "100%", flexDirection: "column", backgroundColor: OMP_THEME.surface, children: children })] }));
@@ -253,9 +276,10 @@ function NewSessionModal({ cwd, sessions, projectsFolder, initialDirectory: work
253
276
  function SettingsModal({ cwd, settings, selected, columns, onToggle, onMove, onSetProjectsFolder, onSave, onClose }) {
254
277
  const options = [
255
278
  ["Project directory root", settings.projectsFolder || "not set"],
279
+ ["Agents view scope", settings.agentsScope === "cwd" ? "Cwd" : "All"],
256
280
  ["Attach plain omb to latest live session", settings.autoAttachLatest],
257
281
  ["Enable mouse scrolling in OMB sessions", settings.mouseScrolling],
258
- ["Show stopped sessions in this view", settings.showStoppedSessions],
282
+ ["Sessions shown (active/stopped/both)", settings.sessionFilter],
259
283
  ["Enable system tray icon", settings.trayEnabled],
260
284
  ];
261
285
  const [editingFolder, setEditingFolder] = useState(false);
@@ -318,12 +342,12 @@ function SettingsModal({ cwd, settings, selected, columns, onToggle, onMove, onS
318
342
  onToggle();
319
343
  }
320
344
  });
321
- return (_jsxs(Modal, { title: "OMB SETTINGS", width: Math.min(70, Math.max(46, columns - 6)), columns: columns, children: [options.map(([label, enabled], index) => (_jsx(Box, { width: "100%", backgroundColor: index === selected ? OMP_THEME.selectedBackground : OMP_THEME.surface, children: _jsxs(Text, { color: index === selected ? OMP_THEME.text : OMP_THEME.muted, children: [index === selected ? _jsx(Text, { color: OMP_THEME.accent, children: "\u203A" }) : " ", " ", typeof enabled === "boolean" ? `[${enabled ? "on " : "off"}]` : enabled, " ", label] }) }, label))), selected === 0 && (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: editingFolder ? OMP_THEME.accent : OMP_THEME.border, backgroundColor: OMP_THEME.surface, paddingX: 1, children: [_jsx(Text, { color: OMP_THEME.accent, 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, { color: OMP_THEME.muted, children: "Autocomplete" }), folderSuggestions.slice(0, 5).map((suggestion, index) => (_jsx(Box, { width: "100%", backgroundColor: index === folderSuggestionSelected ? OMP_THEME.selectedBackground : OMP_THEME.surface, children: _jsxs(Text, { color: index === folderSuggestionSelected ? OMP_THEME.text : OMP_THEME.muted, children: [index === folderSuggestionSelected ? _jsx(Text, { color: OMP_THEME.accent, children: "\u203A" }) : " ", " ", suggestion.path] }) }, suggestion.path)))] })), _jsx(Text, { children: " " }), _jsx(Text, { color: OMP_THEME.muted, children: editingFolder ? "Type path Enter apply Ctrl-U clear Esc cancel" : "Up/Down select Space/Enter edit s save Esc cancel" }), _jsx(Text, { color: OMP_THEME.warning, 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." : selected === 4 ? "The tray starts a native menu for supported Linux, macOS, and Windows hosts." : "The hub always shows every Bizar session across projects." })] }));
345
+ return (_jsxs(Modal, { title: "OMB SETTINGS", width: Math.min(70, Math.max(46, columns - 6)), columns: columns, children: [options.map(([label, enabled], index) => (_jsx(Box, { width: "100%", backgroundColor: index === selected ? OMP_THEME.selectedBackground : OMP_THEME.surface, children: _jsxs(Text, { color: index === selected ? OMP_THEME.text : OMP_THEME.muted, children: [index === selected ? _jsx(Text, { color: OMP_THEME.accent, children: "\u203A" }) : " ", " ", typeof enabled === "boolean" ? `[${enabled ? "on " : "off"}]` : enabled, " ", label] }) }, label))), selected === 0 && (_jsxs(Box, { marginTop: 1, borderStyle: "round", borderColor: editingFolder ? OMP_THEME.accent : OMP_THEME.border, backgroundColor: OMP_THEME.surface, paddingX: 1, children: [_jsx(Text, { color: OMP_THEME.accent, 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, { color: OMP_THEME.muted, children: "Autocomplete" }), folderSuggestions.slice(0, 5).map((suggestion, index) => (_jsx(Box, { width: "100%", backgroundColor: index === folderSuggestionSelected ? OMP_THEME.selectedBackground : OMP_THEME.surface, children: _jsxs(Text, { color: index === folderSuggestionSelected ? OMP_THEME.text : OMP_THEME.muted, children: [index === folderSuggestionSelected ? _jsx(Text, { color: OMP_THEME.accent, children: "\u203A" }) : " ", " ", suggestion.path] }) }, suggestion.path)))] })), _jsx(Text, { children: " " }), _jsx(Text, { color: OMP_THEME.muted, children: editingFolder ? "Type path Enter apply Ctrl-U clear Esc cancel" : "Up/Down select Space/Enter edit s save Esc cancel" }), _jsx(Text, { color: OMP_THEME.warning, children: selected === 0 ? "Child directories from this folder appear in the new-session picker." : selected === 1 ? "Cwd shows this directory by default; All shows sessions from every project." : selected === 3 ? "Mouse mode applies to existing OMB sessions when saved." : selected === 5 ? "The tray starts a native menu for supported Linux, macOS, and Windows hosts." : "Settings apply to this shared agents hub." })] }));
322
346
  }
323
347
  function HelpModal({ columns, onClose }) {
324
348
  useInput((input, key) => { if (key.escape || key.return || input === "q")
325
349
  onClose(); });
326
- 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: OMP_THEME.accent, children: "Enter" }), " open the selected live session"] }), _jsxs(Text, { children: [_jsx(Text, { color: OMP_THEME.accent, children: "n" }), " create a session"] }), _jsxs(Text, { children: [_jsx(Text, { color: OMP_THEME.accent, children: "Mouse" }), " click rows and scroll the list"] }), _jsxs(Text, { children: [_jsx(Text, { color: OMP_THEME.accent, children: "e" }), " rename the selected session"] }), _jsxs(Text, { children: [_jsx(Text, { color: OMP_THEME.accent, children: "d" }), " delete after confirmation"] }), _jsxs(Text, { children: [_jsx(Text, { color: OMP_THEME.accent, children: "s" }), " open launcher settings"] }), _jsxs(Text, { children: [_jsx(Text, { color: OMP_THEME.accent, children: "r" }), " refresh the session list"] }), _jsxs(Text, { children: [_jsx(Text, { color: OMP_THEME.accent, children: "q / Esc" }), " leave this view"] }), _jsx(Text, { children: " " }), _jsx(Text, { color: OMP_THEME.muted, children: "STATES" }), HUB_STATES.map(state => (_jsxs(Text, { color: OMP_THEME.muted, children: [_jsxs(Text, { color: STATUS_STYLE[state].color, children: [STATUS_STYLE[state].mark, " ", state] }), " ", STATUS_HINT[state]] }, state))), _jsx(Text, { color: OMP_THEME.muted, children: "Enter or Esc returns." })] }));
350
+ 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: OMP_THEME.accent, children: "Enter" }), " open the selected live session"] }), _jsxs(Text, { children: [_jsx(Text, { color: OMP_THEME.accent, children: "Tab / Shift-Tab" }), " switch between Cwd and All sessions"] }), _jsxs(Text, { children: [_jsx(Text, { color: OMP_THEME.accent, children: "n" }), " create a session"] }), _jsxs(Text, { children: [_jsx(Text, { color: OMP_THEME.accent, children: "Mouse" }), " click rows and scroll the list"] }), _jsxs(Text, { children: [_jsx(Text, { color: OMP_THEME.accent, children: "e" }), " rename the selected session"] }), _jsxs(Text, { children: [_jsx(Text, { color: OMP_THEME.accent, children: "d" }), " delete after confirmation"] }), _jsxs(Text, { children: [_jsx(Text, { color: OMP_THEME.accent, children: "s" }), " open launcher settings"] }), _jsxs(Text, { children: [_jsx(Text, { color: OMP_THEME.accent, children: "r" }), " refresh the session list"] }), _jsxs(Text, { children: [_jsx(Text, { color: OMP_THEME.accent, children: "q / Esc" }), " leave this view"] }), _jsx(Text, { children: " " }), _jsx(Text, { color: OMP_THEME.muted, children: "STATES" }), HUB_STATES.map(state => (_jsxs(Text, { color: OMP_THEME.muted, children: [_jsxs(Text, { color: STATUS_STYLE[state].color, children: [STATUS_STYLE[state].mark, " ", state] }), " ", STATUS_HINT[state]] }, state))), _jsx(Text, { color: OMP_THEME.muted, children: "Enter or Esc returns." })] }));
327
351
  }
328
352
  function DeleteModal({ session, columns, onConfirm, onClose }) {
329
353
  useInput((input, key) => {
@@ -386,10 +410,18 @@ function useTerminalMouse(onMouse) {
386
410
  };
387
411
  }, [stdin, stdout]);
388
412
  }
389
- function sessionMouseIndex(sessions, selected, rows, y) {
413
+ /**
414
+ * Map a terminal row to the session rendered there, or `undefined` for chrome.
415
+ *
416
+ * `FIRST_ROW_LINE` counts the fixed chrome above the first workspace heading:
417
+ * outer padding, header border, title, scope filter row, header border, a
418
+ * one-row gap, list border, list title, and a one-row gap. Measured against a
419
+ * live pane; a stale value silently breaks row clicks without failing any
420
+ * rendering test, so it is pinned by `tests/agents-ink.test.ts`.
421
+ */
422
+ export function sessionMouseIndex(sessions, selected, rows, y) {
390
423
  const { start, visible } = sessionWindow(sessions, selected, rows);
391
- // Header, one-row gap, list border, and list title precede the first group.
392
- let line = 7;
424
+ let line = FIRST_ROW_LINE;
393
425
  let previousWorkspace;
394
426
  for (const [offset, session] of visible.entries()) {
395
427
  if (workspacePath(session) !== previousWorkspace) {
@@ -411,21 +443,31 @@ function AgentsApp({ cwd, env, sessions: initialSessions, settings: initialSetti
411
443
  const [overlay, setOverlay] = useState(openNewSessionOnStart ? "new" : undefined);
412
444
  const [overlaySelected, setOverlaySelected] = useState(0);
413
445
  const [draftSettings, setDraftSettings] = useState(initialSettings);
446
+ const [scanIssue, setScanIssue] = useState();
414
447
  const refreshSequence = useRef(0);
415
448
  const { exit } = useApp();
416
- const sessions = useMemo(() => visibleSessions(allSessions, settings), [allSessions, settings]);
449
+ const sessions = useMemo(() => visibleSessions(allSessions, settings, cwd), [allSessions, settings, cwd]);
417
450
  const selected = Math.max(0, sessions.findIndex(session => session.name === selectedName));
418
451
  const selectedSession = sessions[selected];
419
- const refresh = async (current = settings) => {
452
+ const refresh = async () => {
420
453
  const sequence = ++refreshSequence.current;
421
- const next = await listOmbSessions(cwd, env, { includeStopped: current.showStoppedSessions });
454
+ // A scan the multiplexer could not answer is not evidence that no sessions
455
+ // exist. Applying it would blank every row on a transient tmux restart, so
456
+ // the last good list is kept and the hub is marked degraded instead.
457
+ const authority = await ombScanAuthority(cwd, env);
458
+ if (sequence !== refreshSequence.current)
459
+ return;
460
+ setScanIssue(authority.known ? undefined : authority.reason);
461
+ if (!authority.known)
462
+ return;
463
+ const next = await listOmbSessions(cwd, env, { includeStopped: true });
422
464
  if (sequence === refreshSequence.current)
423
465
  setAllSessions(next);
424
466
  };
425
467
  useEffect(() => {
426
468
  const timer = setInterval(() => { void refresh(); }, 3000);
427
469
  return () => clearInterval(timer);
428
- }, [cwd, env, settings.showStoppedSessions]);
470
+ }, [cwd, env]);
429
471
  useEffect(() => {
430
472
  // Reload settings when another process (the dashboard, another `omb agents`
431
473
  // window, or `omp config set`) rewrites the shared `omb.json`. The watch
@@ -452,6 +494,37 @@ function AgentsApp({ cwd, env, sessions: initialSessions, settings: initialSetti
452
494
  return;
453
495
  setSelectedName(session.name);
454
496
  };
497
+ const toggleScope = () => {
498
+ const next = { ...settings, agentsScope: settings.agentsScope === "cwd" ? "all" : "cwd" };
499
+ try {
500
+ setSettings(saveOmbSettings(next, env));
501
+ }
502
+ catch (error) {
503
+ if (error instanceof Error && error.name === "OmbSettingsConflictError")
504
+ return;
505
+ throw error;
506
+ }
507
+ };
508
+ const toggleFilter = () => {
509
+ const order = ["all", "active", "stopped"];
510
+ const next = { ...settings, sessionFilter: order[(order.indexOf(settings.sessionFilter) + 1) % order.length] ?? "all" };
511
+ try {
512
+ setSettings(saveOmbSettings(next, env));
513
+ }
514
+ catch (error) {
515
+ if (error instanceof Error && error.name === "OmbSettingsConflictError")
516
+ return;
517
+ throw error;
518
+ }
519
+ };
520
+ /**
521
+ * Enter on a stopped session restarts a host for it instead of attaching:
522
+ * nothing is attached to a session tmux no longer hosts, so attaching is
523
+ * the one action guaranteed not to work.
524
+ */
525
+ const sessionAction = (session) => session.state === "stopped" || session.hosted === false
526
+ ? { type: "continue", session }
527
+ : { type: "attach", session };
455
528
  useTerminalMouse(event => {
456
529
  if (overlay || event.release)
457
530
  return;
@@ -465,14 +538,22 @@ function AgentsApp({ cwd, env, sessions: initialSessions, settings: initialSetti
465
538
  if (index === undefined)
466
539
  return;
467
540
  const session = sessions[index];
468
- if (session?.name === selectedName && session.hosted !== false && session.state !== "stopped")
469
- finish({ type: "attach", session });
541
+ if (session?.name === selectedName)
542
+ finish(sessionAction(session));
470
543
  else
471
544
  selectSession(index);
472
545
  });
473
546
  useInput((input, key) => {
474
547
  if (overlay)
475
548
  return;
549
+ if (key.tab) {
550
+ toggleScope();
551
+ return;
552
+ }
553
+ if (input === "f") {
554
+ toggleFilter();
555
+ return;
556
+ }
476
557
  if (key.ctrl && input === "c") {
477
558
  finish({ type: "quit" });
478
559
  return;
@@ -485,8 +566,8 @@ function AgentsApp({ cwd, env, sessions: initialSessions, settings: initialSetti
485
566
  selectSession(Math.max(0, selected - 1));
486
567
  else if (key.downArrow || input === "j")
487
568
  selectSession(Math.min(Math.max(0, sessions.length - 1), selected + 1));
488
- else if (key.return && selectedSession && selectedSession.hosted !== false && selectedSession.state !== "stopped")
489
- finish({ type: "attach", session: selectedSession });
569
+ else if (key.return && selectedSession)
570
+ finish(sessionAction(selectedSession));
490
571
  else if (input === "n")
491
572
  openNewSession(selectedSession?.cwd === "?" ? undefined : selectedSession?.cwd);
492
573
  else if (input === "e" && selectedSession && selectedSession.hosted !== false && selectedSession.state !== "stopped")
@@ -505,14 +586,23 @@ function AgentsApp({ cwd, env, sessions: initialSessions, settings: initialSetti
505
586
  setOverlay("settings");
506
587
  }
507
588
  });
508
- return (_jsxs(Box, { flexDirection: "column", width: "100%", minHeight: Math.max(12, rows - 1), paddingX: 1, children: [_jsx(Header, { sessions: sessions }), _jsx(Box, { flexGrow: 1, marginTop: 1, children: _jsx(SessionList, { sessions: sessions, selected: selected, rows: rows, width: Math.max(34, columns - 4) }) }), _jsx(SelectedSummary, { session: selectedSession, width: Math.max(34, columns - 4) }), _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: () => {
589
+ return (_jsxs(Box, { flexDirection: "column", width: "100%", minHeight: Math.max(12, rows - 1), paddingX: 1, children: [_jsx(Header, { sessions: sessions, scope: settings.agentsScope, filter: settings.sessionFilter, cwd: cwd, scanIssue: scanIssue }), _jsx(Box, { flexGrow: 1, marginTop: 1, children: _jsx(SessionList, { sessions: sessions, selected: selected, rows: rows, width: Math.max(34, columns - 4) }) }), _jsx(SelectedSummary, { session: selectedSession, width: Math.max(34, columns - 4) }), _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: () => {
509
590
  if (overlaySelected === 0)
510
591
  return;
511
- const field = overlaySelected === 1 ? "autoAttachLatest" : overlaySelected === 2 ? "mouseScrolling" : overlaySelected === 3 ? "showStoppedSessions" : "trayEnabled";
592
+ if (overlaySelected === 1) {
593
+ setDraftSettings(value => ({ ...value, agentsScope: value.agentsScope === "cwd" ? "all" : "cwd" }));
594
+ return;
595
+ }
596
+ if (overlaySelected === 4) {
597
+ const order = ["all", "active", "stopped"];
598
+ setDraftSettings(value => ({ ...value, sessionFilter: order[(order.indexOf(value.sessionFilter) + 1) % order.length] ?? "all" }));
599
+ return;
600
+ }
601
+ const field = overlaySelected === 2 ? "autoAttachLatest" : overlaySelected === 3 ? "mouseScrolling" : "trayEnabled";
512
602
  setDraftSettings(value => ({ ...value, [field]: !value[field] }));
513
603
  }, onSetProjectsFolder: value => setDraftSettings(current => ({ ...current, projectsFolder: value })), onSave: () => {
514
604
  setSettings(draftSettings);
515
- void refresh(draftSettings);
605
+ void refresh();
516
606
  finish({ type: "settings", settings: draftSettings });
517
607
  }, onClose: () => setOverlay(undefined) }))] }));
518
608
  }