diff --git a/README.md b/README.md index db38370..2b5974f 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Organize related tasks into named groups, even when they run in different direct ### Resume agent sessions -Start `claude`, `codex`, `grok`, or `omp` normally. When you rerun the task or reload a saved session, `fleetcom` resumes the same conversation automatically. +Start `claude`, `codex`, `grok`, or `omp` normally. When you rerun the task or reload a saved session, `fleetcom` resumes the conversation when its ID is known; otherwise, it runs the authored command. ## Operational model @@ -40,7 +40,7 @@ Running several long-lived commands is pesky once they span terminal panes or ne - Delegates tasks to a daemon, so a disconnecting client stops nothing. - Saves and reloads task recipes: directories, commands, group assignments, and display names. - Reruns a completed task in place, keeping its identity, group, and name. -- Preserves `claude`, `codex`, `grok`, and `omp` conversations, so saved or rerun tasks resume instead of starting fresh. +- Captures `claude`, `codex`, `grok`, and `omp` conversation IDs for saved and rerun tasks. - Automatically snapshots the current task set for recovery. ## Documentation @@ -102,7 +102,7 @@ Every task runs in its own pseudo-terminal, emulated with `alacritty_terminal`. - Several long-lived commands need one place for observation, tagging, and attachment. - Jobs must survive a terminal closing and remain available for reattachment. - The same command set is launched often enough to justify a saved session. -- Agent sessions (`claude`, `codex`, `grok`, `omp`) must resume their conversations on rerun rather than start new ones. +- Captured agent conversations (`claude`, `codex`, `grok`, `omp`) should resume on rerun. ### When to avoid `fleetcom` diff --git a/docs/README.md b/docs/README.md index b1e30a6..bae8e88 100644 --- a/docs/README.md +++ b/docs/README.md @@ -169,7 +169,7 @@ Recipes persist full command lines, which can embed secrets. A token passed as a ### Captured IDs cross a shell boundary -Agent resume writes a captured conversation ID into a command run through `$SHELL -c`, so validation is a security boundary. Accepted IDs contain only lowercase hexadecimal in the `8-4-4-4-12` UUID shape. Hook payloads, terminal scrapes, live session records, filesystem correlation, and the command builder all apply that check. Instrumentation applies only to a bare program word or its canonical resume form, never arbitrary shell text. [Agent session resume](agent-resume.md#validation-boundary) documents both boundaries. +Agent resume writes a captured conversation ID into a command run through `$SHELL -c`, so validation is a security boundary. Accepted IDs contain only lowercase hexadecimal in the `8-4-4-4-12` UUID shape. Capture payloads, live session records, and the command builder all apply that check. Terminal output supplies no session IDs. Instrumentation applies only to a bare program word or its canonical resume form, never arbitrary shell text. [Agent session resume](agent-resume.md#validation-boundary) documents both boundaries. ### Copied text leaves through the terminal @@ -211,7 +211,7 @@ A second `fleetcom` prints a waiting notice, then attaches when the active clien ### Shutdown is graceful-first -`X`, `Q`, `--kill`, and daemon shutdown signals send `SIGTERM` to each task's *process group*, then escalate to `SIGKILL` after two seconds. Removing one task (`X`) keeps an exited leader unreaped through escalation, reserving the process-group ID so background children remain signalable. During full shutdown (`Q`/`--kill`), checking whether a group is empty reaps its exited leader. A `TERM`-ignoring member that outlives the leader can then become unsafe to signal by group ID and survive daemon shutdown. A child created by `cmd &` in a non-interactive shell normally remains in its parent's group. A process that calls `setsid` or otherwise leaves the group is outside the sweep and must be terminated separately. +`X`, `Q`, `--kill`, and daemon shutdown signals send `SIGTERM` to each task's *process group*, then escalate to `SIGKILL` after two seconds. Exited leaders remain unreaped through escalation, reserving the process-group IDs so background children remain signalable. Full shutdown (`Q`/`--kill`) waits one shared grace period even when all listed tasks have finished or exit on `TERM`; with no tasks left to clean up, shutdown returns immediately. Tasks already terminating keep their original escalation timers. A child created by `cmd &` in a non-interactive shell normally remains in its parent's group. A process that calls `setsid` or otherwise leaves the group is outside the sweep and must be terminated separately. ### `--foreground` is ephemeral diff --git a/docs/agent-resume.md b/docs/agent-resume.md index 438ee10..17dcccb 100644 --- a/docs/agent-resume.md +++ b/docs/agent-resume.md @@ -7,11 +7,11 @@ Session files preserve launch commands, not process state. Relaunching a bare `c Start a supported agent without flags: 1. Press `n` and run `claude`, `codex`, `grok`, or `omp`. The task appears in the dashboard under the command you typed. Instrumentation changes only the string executed through `$SHELL -c`, so a direct spawn still displays the requested command. -2. Work in it. `Enter` attaches; `Ctrl-\` returns to the dashboard. Depending on the agent, `fleetcom` pins an ID at launch and may update it from a hook, notifier, or extension while the task runs or from terminal output after it exits. -3. Press `w`, enter a session name, and press `Enter`. If the earlier sources produced no ID, the save also checks the agent's on-disk session store. A captured bare command becomes its canonical resume form, such as `claude --resume ''`. +2. Work in it. `Enter` attaches; `Ctrl-\` returns to the dashboard. Depending on the agent, `fleetcom` pins an ID at launch and may update it from a hook, notifier, extension, or matching live registry record. +3. Press `w`, enter a session name, and press `Enter`. A captured bare command becomes its canonical resume form, such as `claude --resume ''`. Without a known ID, the save preserves the authored command. 4. Run `fleetcom `, or press `o` in the dashboard, to start new processes from the saved commands. A stored resume command reopens its captured conversation. -On a finished agent task, `r` uses the captured launch, hook, notifier, registry, or exit ID without performing save-time filesystem correlation. A registry record remains eligible after exit if it is still present. The replacement keeps the task's ID, tag, group, and name. After a successful rewrite, the row shows the resume command because it has become the task's launch recipe; a [saved session](sessions.md) records the same string. +On a finished agent task, `r` uses the captured launch, hook, notifier, extension, or registry ID. A registry record remains eligible after exit if it is still present. The replacement keeps the task's ID, tag, group, and name. After a successful rewrite, the row shows the resume command because it has become the task's launch recipe; a [saved session](sessions.md) records the same string. Capture is best-effort and narrow by design. A command carrying a prompt, extra flags, or shell syntax stays opaque and saves verbatim. An accepted command with no available ID also saves unchanged. In both cases, loading the recipe reruns the original command. @@ -58,8 +58,6 @@ Claude also publishes one `/sessions/.json` record per session A record counts only when its `kind` is `interactive` and its `pid`, `cwd`, and `startedAt` match the task. The PID must match the filename, the working directories must be identical or resolve to the same path, and the process start must fall within 30 seconds of the task spawn. Missing, malformed, or mismatched records contribute no evidence. The dashboard also maps a matching record's `waiting` status to the top tier of its [preview cascade](commands.md#peek); other statuses do not affect the preview. -After the process exits and the PTY reader reaches EOF, the harness scans the retained terminal text for the last `claude --resume ` hint. Save-time filesystem correlation checks `/projects//.jsonl`, where the slug replaces `/` and `.` in the absolute working directory with `-`. - ### `codex` Codex does not let the caller choose an ID at launch. Both accepted forms instead receive a notify override: @@ -72,14 +70,10 @@ After each turn, the notifier writes the `agent-turn-complete` JSON argument to Replacing a configured notifier would change user behavior. When the effective Codex configuration contains a one-line `notify` array of non-empty basic strings, the capture script executes that notifier after writing the capture file. Its argv is carried in `FLEETCOM_NOTIFY_CHAIN`, joined by newlines, and the notification payload is appended. An empty, multiline, ambiguous, or unsupported `notify` value disables the injected override so the configured route remains unchanged. The line-based configuration reader checks `config.toml` and the profile selected by its first `profile = ...` assignment; the profile's notify assignment takes precedence. -The exit scraper accepts `codex resume ` and `codex resume, then select ()`, using only the UUID. Save-time filesystem correlation checks dated rollout directories under `/sessions/YYYY/MM/DD/`. A rollout matches when its v7 UUID timestamp is within 30 seconds of task spawn and the first record names the task's working directory. The search covers the spawn's UTC date plus or minus two days because the directory date is local time. - ### `grok` Grok accepts a launch-time ID but exposes no injectable live-capture channel. A bare command therefore receives `--session-id ''`, while a canonical resume command needs no instrumentation. -After exit, the harness scans retained terminal text for the last `grok -r ` or `grok --resume ` hint. Save-time filesystem correlation checks `/sessions///`, percent-encoding the canonical working directory, falling back to a group whose `.cwd` file names that path when the encoded name is too long, and ignoring `session_kind: subagent` directories. - ### `omp` omp cannot pin an ID at launch: it has no `--session-id`, and `--resume` requires an existing session. Both accepted forms therefore receive the same injection and no pinned ID: @@ -90,23 +84,19 @@ omp cannot pin an ID at launch: it has no `--session-id`, and `--resume` require `-e` loads the JavaScript module into the agent process and appends it to the user's extensions. Its `session_start` and `session_switch` handlers write `sessionId` as JSON to `FLEETCOM_CAPTURE_FILE`. The second handler follows in-TUI `/resume` changes. Capture writes are best-effort: the module returns when the capture path is empty and ignores write errors. -After exit, the harness scans retained terminal text for the last trusted `omp --resume ` hint. It accepts ordinary exit hints and `Main:` entries in `[Recovery]` blocks. Other labels identify subagent sessions that `omp --resume` cannot open, so they contribute no exit evidence. The aliases `-r`, `--session`, and `-c` remain opaque because `fleetcom` rewrites only the canonical form it detects exactly. - -Save-time filesystem correlation reads `//_.jsonl`, where the sessions root comes from omp's own variable chain rather than one home override; the [environment-variable table](#environment-variables) lists it. `PI_CODING_AGENT_SESSION_DIR` is the exception: omp passes that path straight through as the session file's parent and never computes a bucket, so the store is flat under it. The scan covers the root and one level below without inferring which layout is in play. - -Correlation enumerates buckets instead of deriving their names, then verifies each session header's `cwd` against the task path and its canonical target. A candidate must be the sole UUIDv7 session created within 30 seconds of task spawn. UUIDs found in multiple buckets count once. - -An empty sessions root or bucket contributes no candidate. +The aliases `-r`, `--session`, and `-c` remain opaque because `fleetcom` rewrites only the canonical form it detects exactly. ## ID precedence Several channels can identify different conversations during one task. To make the result deterministic, `fleetcom` chooses the first available ID in this order: -1. The exit hint scraped after process exit and PTY-reader EOF. -2. The current capture-file payload. -3. The live session registry, implemented by `claude`. -4. The ID pinned or targeted at spawn. -5. Save-time filesystem correlation, when exactly one store entry matches the task and the 30-second spawn window. +1. The current capture-file payload. +2. The live session registry, implemented by `claude`. +3. The ID pinned or targeted at spawn. + +Named saves, recovery snapshots, and reruns use this same precedence. `fleetcom` does not scan session stores to infer conversation ownership: a nearby transcript or rollout cannot identify which task owns it. + +Terminal output never supplies a session ID: examples, quoted commands, and tool output can contain another conversation's valid UUID. An ID available only in an exit hint is not recovered. Without a capture, registry, or launch ID, the authored command remains unchanged. The registry outranks the spawn pin because it can contain a session ID selected after launch, including one created by `/clear`. The capture file outranks the registry. @@ -123,7 +113,7 @@ The program word is preserved as typed. If no valid ID is available, the origina ## Validation boundary -Every captured value eventually enters a shell command, which makes validation the security boundary. Accepted IDs contain exactly lowercase hexadecimal characters in the `8-4-4-4-12` UUID shape. Capture payloads, terminal hints, registry records, store names, and the final command builder all apply the same check. Malformed values are ignored rather than interpolated. +Every captured value eventually enters a shell command, which makes validation the security boundary. Accepted IDs contain exactly lowercase hexadecimal characters in the `8-4-4-4-12` UUID shape. Capture payloads, registry records, and the final command builder all apply the same check. Malformed values are ignored rather than interpolated. A valid UUID alone does not establish conversation ownership. ## Extending capture @@ -132,13 +122,11 @@ Each tool implements the `Harness` trait in [`src/harness/mod.rs`](../src/harnes - `shape` supplies the program word and resume selector. The default `detect` and `resume_command` methods derive the accepted and canonical forms from that pair. - `instrument` returns spawn-time arguments, environment entries, and an optional pinned ID. - `parse_capture` reads an ID from hook, notify, or extension JSON. -- `scrape_exit` reads an ID from retained terminal text. - `live_session_id` reads the ID a live session publishes on disk. It defaults to `None` for tools that publish no registry. - `live_blocked_status` reads that same registry for one display fact: whether the tool says it is blocked on the user. It returns preview text, never an ID, and defaults to `None`. -- `correlate_fs` finds one matching on-disk session. -- `resolve_home` turns the task's launch environment into the tool's store root. +- `resolve_home` resolves configuration needed by instrumentation or the live registry. It defaults to `None`. -The supervisor supplies the launch environment and delegates the decision to `resolve_home`. Its default is the two-step rule three of the four tools follow: the tool-specific variable first, then `$HOME` plus the tool's dot directory. omp overrides it, because its store root comes from a chain of variables and a filesystem-conditional XDG branch that no single override can express. Either way, the resolved path remains attached to the task for later filesystem correlation. +The supervisor supplies the launch environment to `resolve_home`. Claude and Codex read their explicit override first, then `$HOME` plus their dot directory. When neither is supplied, configuration reads fall back to the supervisor's platform home. The resolved path stays attached to the task so Claude registry reads continue using its launch-time home after a reconnect. Grok and omp need no home resolution; their environment passes through to the child unchanged. ## Environment variables @@ -147,12 +135,5 @@ The supervisor supplies the launch environment and delegates the decision to `re | `FLEETCOM_RUNTIME_DIR` | Explicit capture-asset root as well as the daemon runtime override. | | `FLEETCOM_CAPTURE_FILE` | Per-run capture file used by the injected hook, notifier, or extension module. | | `FLEETCOM_NOTIFY_CHAIN` | Newline-joined argv for the configured Codex notifier; empty when none is active. | -| `CLAUDE_CONFIG_DIR` | Claude home holding the `sessions/.json` registry and the transcripts used for correlation; defaults to `$HOME/.claude`. | -| `CODEX_HOME` | Codex home used for notify routing and rollout correlation; defaults to `$HOME/.codex`. | -| `GROK_HOME` | Grok home used for session-directory correlation; defaults to `$HOME/.grok`. | -| `PI_CODING_AGENT_SESSION_DIR` | omp sessions root, used verbatim for correlation. The rest of omp's chain builds that path instead of naming it. | -| `PI_CODING_AGENT_DIR` | omp agent directory, whose `sessions` subdirectory is the store. A selected profile ignores it. | -| `PI_CONFIG_DIR` | omp config directory under `$HOME`; defaults to `.omp`. Under `fleetcom`, an absolute value replaces `$HOME`. | -| `OMP_PROFILE` | omp profile, read by presence: it selects a profile when non-empty and suppresses `PI_PROFILE` when empty. | -| `PI_PROFILE` | omp profile used only when `OMP_PROFILE` is absent. A profile inserts `profiles/` under the config directory. | -| `XDG_DATA_HOME` | Redirects the still-default omp agent directory to `/omp`, flattening the `agent/` level, when that target already exists. | +| `CLAUDE_CONFIG_DIR` | Claude home holding the `sessions/.json` registry; defaults to `$HOME/.claude`. | +| `CODEX_HOME` | Codex home used for notify routing; defaults to `$HOME/.codex`. | diff --git a/docs/commands.md b/docs/commands.md index 35864a9..69faa03 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -112,9 +112,7 @@ Rerunning preserves the task's ID, `◆` tag, group, name, and spawn order; its Input meaning depends on the active surface. From the dashboard, `q` or `Ctrl-C` disconnects the client. A daemon and its tasks continue running, so the next `fleetcom` invocation reconnects. Under `--foreground`, the in-process core exits with the client and its tasks die. While attached, `Ctrl-C` belongs to the child. In prompts and pickers, `Esc` cancels without disconnecting. -Uppercase `Q` stops the daemon and terminates each task's process group. Shutdown sends `TERM` first, then `KILL` after a two-second grace period. Processes that have moved into another group are outside this sweep. - -A `TERM`-ignoring member can also survive when its leader exits during shutdown. The group-emptiness check then releases the process-group ID reservation before escalation; [Shutdown is graceful-first](README.md#shutdown-is-graceful-first) explains the tradeoff. +Uppercase `Q` stops the daemon and terminates each task's process group. Shutdown sends `TERM` first, then `KILL` after one shared two-second grace period. Exited leaders remain unreaped until escalation so `TERM`-ignoring descendants in their groups still receive `KILL`. A nonempty fleet waits the grace even when its listed tasks have finished or respect `TERM`; with no tasks left to clean up, shutdown returns immediately. Tasks already terminating keep their original escalation timers. Processes that have moved into another group or session are outside this sweep. ### Task organization diff --git a/docs/how-it-works.md b/docs/how-it-works.md index 2a59a52..6510fc5 100644 --- a/docs/how-it-works.md +++ b/docs/how-it-works.md @@ -10,4 +10,4 @@ Attached input follows the terminal modes reported by the child. Modified Enter ## Grouping follows one activity window -The dashboard groups tasks by state (In use / Running / Idle / Completed), working directory, or names assigned with `g`. One 10-second window drives both idle signals: after 10 s without output, the row glyph changes from `✻` to `∙` and, under state grouping, the task moves to the Idle section in the same refresh. Idle state does not affect row order within directory or custom sections. A tool such as `top`, which prints every 1–2 s, never crosses the window, so it stays `✻` under Running. Preview text is independent: a status from a weaker source must persist for 600 ms before it replaces a stronger one, which absorbs repaint flicker without affecting grouping. Claude's on-disk `waiting` status and screen-derived agent statuses both produce the top-tier `anchor` source. The on-disk status wins when both are present; returning to the screen-derived status does not cross a tier and therefore renders immediately. +The dashboard groups tasks by state (In use / Running / Idle / Completed), working directory, or names assigned with `g`. The lifecycle becomes Idle after more than 10 s without output: the row glyph changes from `✻` to `∙` and, under state grouping, the task moves to the Idle section in the same refresh. Idle state does not affect row order within directory or custom sections. A tool such as `top`, which prints every 1–2 s, never crosses the window, so it stays `✻` under Running. Preview text is independent: a status from a weaker source must persist for 600 ms before it replaces a stronger one, which absorbs repaint flicker without affecting grouping. Claude's on-disk `waiting` status and screen-derived agent statuses both produce the top-tier `anchor` source. The on-disk status wins when both are present; returning to the screen-derived status does not cross a tier and therefore renders immediately. diff --git a/docs/sessions.md b/docs/sessions.md index 0e4e4b9..75390ec 100644 --- a/docs/sessions.md +++ b/docs/sessions.md @@ -51,7 +51,7 @@ The file is plain JSON and practical to edit by hand. Editing the `name` field c Commands with neither a group nor a name use the string form. String and object entries can appear in the same directory array. -A bare agent command does not identify its conversation, so saving it verbatim would start another one on load. When `fleetcom` captures an ID for `claude`, `codex`, `grok`, or `omp`, it stores the resume form instead. The result remains an ordinary command string that can run directly in a shell: +A bare agent command does not identify its conversation, so saving it verbatim would start another one on load. When `fleetcom` captures an ID for `claude`, `codex`, `grok`, or `omp`, it stores the resume form instead. Without a known ID, named saves and recovery snapshots preserve the authored command. The result remains an ordinary command string that can run directly in a shell: ```json { diff --git a/src/app.rs b/src/app.rs index f593fc6..90db75f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -314,23 +314,22 @@ fn step_down(sel: usize, len: usize) -> usize { } /// State-section order: In use, Running, Idle, then Completed. -/// Tags take precedence, completion follows `lifecycle`, and live tasks use -/// `parked` to distinguish Running from Idle. +/// Tags take precedence over lifecycle. fn section_rank(v: &TaskView) -> u8 { if v.tagged { 0 - } else if matches!(v.lifecycle, Lifecycle::Ok | Lifecycle::Failed) { - 3 - } else if v.parked { - 2 } else { - 1 + match v.lifecycle { + Lifecycle::Active => 1, + Lifecycle::Idle => 2, + Lifecycle::Ok | Lifecycle::Failed => 3, + } } } /// Within-section row order: tagged, live, then finished. -/// `parked` does not affect row order, so transitions between active and idle -/// preserve a task's position outside state grouping. +/// Transitions between active and idle preserve a task's position outside +/// state grouping. fn row_rank(v: &TaskView) -> u8 { if v.tagged { 0 diff --git a/src/app_readme_tests.rs b/src/app_readme_tests.rs index 300933b..be4fd22 100644 --- a/src/app_readme_tests.rs +++ b/src/app_readme_tests.rs @@ -127,7 +127,6 @@ fn live_fleet(dirs: &Dirs) -> Vec { group: Some("dashboard".to_string()), name: Some("Dashboard Refine".to_string()), lifecycle: Lifecycle::Active, - parked: false, preview: anchor( "Scope small fixes for dashboard and CLI", "claude:action-row", @@ -144,7 +143,6 @@ fn live_fleet(dirs: &Dirs) -> Vec { group: Some("dashboard".to_string()), name: Some("Summary Refine".to_string()), lifecycle: Lifecycle::Active, - parked: false, preview: anchor("Inferring… · thinking with high effort", "claude:spinner"), started_ago: mins(5), quiet_ago: Some(secs(8)), @@ -158,7 +156,6 @@ fn live_fleet(dirs: &Dirs) -> Vec { group: Some("dashboard".to_string()), name: Some("Grok Language".to_string()), lifecycle: Lifecycle::Active, - parked: false, preview: anchor("Grok 4.5 (xhigh) · Responding…", "grok:spinner"), started_ago: mins(12), quiet_ago: Some(secs(4)), @@ -172,7 +169,6 @@ fn live_fleet(dirs: &Dirs) -> Vec { group: Some("dashboard".to_string()), name: Some("Codex Language".to_string()), lifecycle: Lifecycle::Active, - parked: false, preview: anchor(CODEX_LANGUAGE, "codex:working"), started_ago: mins(18), quiet_ago: Some(secs(2)), @@ -186,7 +182,6 @@ fn live_fleet(dirs: &Dirs) -> Vec { group: Some("dashboard".to_string()), name: Some("Codex Review".to_string()), lifecycle: Lifecycle::Active, - parked: false, preview: anchor(CODEX_REVIEW, "codex:working"), started_ago: mins(24), quiet_ago: Some(secs(6)), @@ -200,7 +195,6 @@ fn live_fleet(dirs: &Dirs) -> Vec { group: Some("tests".to_string()), name: None, lifecycle: Lifecycle::Ok, - parked: false, preview: frozen(FLEETCOM_TESTS), started_ago: mins(2), quiet_ago: None, @@ -214,7 +208,6 @@ fn live_fleet(dirs: &Dirs) -> Vec { group: Some("tests".to_string()), name: None, lifecycle: Lifecycle::Failed, - parked: false, preview: frozen(FLEETCOM_CLIPPY), started_ago: mins(5), quiet_ago: None, @@ -228,7 +221,6 @@ fn live_fleet(dirs: &Dirs) -> Vec { group: Some("desktop".to_string()), name: Some("claude agents".to_string()), lifecycle: Lifecycle::Active, - parked: false, preview: title("2 awaiting input · claude agents"), started_ago: mins(63), quiet_ago: Some(secs(9)), @@ -242,7 +234,6 @@ fn live_fleet(dirs: &Dirs) -> Vec { group: Some("desktop".to_string()), name: Some("Zellij".to_string()), lifecycle: Lifecycle::Active, - parked: false, preview: title("Desktop ¦ Utility"), started_ago: mins(126), quiet_ago: Some(secs(4)), @@ -256,7 +247,6 @@ fn live_fleet(dirs: &Dirs) -> Vec { group: None, name: None, lifecycle: Lifecycle::Idle, - parked: true, preview: floor(">>>"), started_ago: mins(48), quiet_ago: Some(mins(41)), @@ -270,7 +260,6 @@ fn live_fleet(dirs: &Dirs) -> Vec { group: Some("desktop".to_string()), name: None, lifecycle: Lifecycle::Ok, - parked: false, preview: frozen("Already up-to-date."), started_ago: mins(14), quiet_ago: None, @@ -284,7 +273,6 @@ fn live_fleet(dirs: &Dirs) -> Vec { group: Some("turret".to_string()), name: Some("Game Infra Review".to_string()), lifecycle: Lifecycle::Active, - parked: false, preview: title("Turret Game Codebase Organization and Ex… - grok"), started_ago: mins(8), quiet_ago: Some(secs(5)), @@ -298,7 +286,6 @@ fn live_fleet(dirs: &Dirs) -> Vec { group: Some("turret".to_string()), name: Some("Missile Nerf".to_string()), lifecycle: Lifecycle::Active, - parked: false, // omp's spinner phrase carries no model-label prefix: the // adapter's model_label is None. preview: anchor("Tuning missile damage falloff", "omp:spinner"), @@ -314,7 +301,6 @@ fn live_fleet(dirs: &Dirs) -> Vec { group: Some("turret".to_string()), name: Some("EMP Nerf".to_string()), lifecycle: Lifecycle::Active, - parked: false, // At its prompt: the primary-screen title tier renders the // conversation label omp announces as `π >