Compare commits

...

27 Commits

Author SHA1 Message Date
1de5a9aac4
ticket: plan workspace panel layout tuning 2026-06-06 15:06:30 +09:00
1c49ea3451
ticket: close workspace orchestration panel 2026-06-06 15:05:38 +09:00
6aeef2d76e
ticket: close workspace panel action dispatch 2026-06-06 15:04:40 +09:00
f4230c7386
merge: workspace panel ticket action dispatch 2026-06-06 15:03:48 +09:00
150e9fc1cd
feat: dispatch workspace panel ticket actions 2026-06-06 15:00:45 +09:00
4f0513996a
ticket: preflight workspace panel action dispatch 2026-06-06 14:30:26 +09:00
ee6fbea177
ticket: add workspace panel action dispatch 2026-06-06 14:29:48 +09:00
a06898ec92
ticket: close intake orchestrator handoff 2026-06-06 14:27:40 +09:00
aaf8391a70
merge: ticket intake orchestrator handoff 2026-06-06 14:26:50 +09:00
adbf3a1e34
feat: add ticket intake handoff 2026-06-06 14:21:37 +09:00
f9745dd7a6
ticket: preflight intake orchestrator handoff 2026-06-06 13:49:16 +09:00
c3cccd8a9d
ticket: close workspace panel composer targets 2026-06-06 13:47:31 +09:00
24d977797d
merge: workspace panel composer targets 2026-06-06 13:46:42 +09:00
a7c155e5c3
feat: add workspace panel composer targets 2026-06-06 13:41:39 +09:00
f23a4a29c6
ticket: preflight workspace panel composer targets 2026-06-06 13:24:53 +09:00
556fd42973
ticket: close workspace panel orchestrator lifecycle 2026-06-06 13:23:48 +09:00
891fe07c6a
merge: workspace panel orchestrator lifecycle 2026-06-06 13:23:01 +09:00
7b29cd6817
feat: add workspace orchestrator panel lifecycle 2026-06-06 09:13:45 +09:00
17970feb21
ticket: preflight workspace panel orchestrator lifecycle 2026-06-06 08:32:34 +09:00
38bf4b0136
ticket: close workspace panel action model 2026-06-06 08:31:28 +09:00
0fce14bcfd
merge: workspace panel action model 2026-06-06 08:30:42 +09:00
11d06df771
Merge branch 'develop' into work/workspace-panel-action-model 2026-06-06 08:30:25 +09:00
cf1cc72255
feat: add workspace panel action model 2026-06-06 08:22:47 +09:00
d5b664b7f4
ticket: remove multi in favor of panel 2026-06-06 08:13:39 +09:00
68e9f5254a
ticket: decide workspace panel launch route 2026-06-06 08:08:49 +09:00
64ca7d7d4b
ticket: unify multi under workspace panel 2026-06-06 08:05:06 +09:00
7965249e23
ticket: update tui composer history persistence 2026-06-06 08:01:38 +09:00
49 changed files with 4882 additions and 314 deletions

View File

@ -2,12 +2,12 @@
id: 20260605-210703-workspace-orchestration-panel id: 20260605-210703-workspace-orchestration-panel
slug: workspace-orchestration-panel slug: workspace-orchestration-panel
title: Workspace orchestration panel title: Workspace orchestration panel
status: open status: closed
kind: task kind: task
priority: P1 priority: P1
labels: [tui, ticket, orchestration, panel] labels: [tui, ticket, orchestration, panel]
created_at: 2026-06-05T21:07:03Z created_at: 2026-06-05T21:07:03Z
updated_at: 2026-06-05T21:09:19Z updated_at: 2026-06-06T06:05:38Z
assignee: null assignee: null
legacy_ticket: null legacy_ticket: null
--- ---

View File

@ -0,0 +1,33 @@
Completed the first-pass workspace orchestration panel implementation.
Delivered child/follow-up tickets:
- `workspace-orchestration-panel-design`: approved design; panel is a unified `yoi panel` workspace view, not a `--multi`/single-Pod split.
- `workspace-panel-action-model`: added thin local-file-first workspace panel ViewModel, Ticket/action rows, no-Ticket Pod-centric fallback, `yoi panel`, and removed `--multi`.
- `workspace-panel-orchestrator-lifecycle`: Ticket-enabled panels ensure/restore/spawn a background workspace Orchestrator and skip Orchestrator lifecycle in no-Ticket workspaces.
- `workspace-panel-composer-targets`: added Companion vs Ticket Intake composer targets and Intake launch through the role launcher.
- `ticket-intake-orchestrator-handoff`: added auditable Intake -> Orchestrator handoff and pre-run peer registration.
- `workspace-panel-ticket-action-dispatch`: added safe dispatch for Go/Defer and safe diagnostics for Review/Close.
Final first-pass behavior:
- `yoi panel` opens the workspace panel for cwd/workspace.
- `--multi` is removed as a user-facing route.
- If `.yoi/ticket.config.toml` is absent, the panel suppresses Ticket UI and behaves as a Pod-centric dashboard.
- If Ticket config is usable, the panel shows Ticket/action rows, background Pod rows, Orchestrator lifecycle state, and composer target selection.
- Ticket Intake composer target launches Intake with the typed message as Intake input, not Companion/current Pod history.
- Intake receives Orchestrator handoff in committed launch input/history and pre-run peer registration is attempted.
- Go records a Ticket decision for Orchestrator routing/preflight and optionally notifies Orchestrator, without starting implementation.
- Defer records a decision and moves open Tickets to pending where applicable.
- Review/Close remain safe explicit-flow diagnostics rather than silent approval/closure.
- Layout/display polish is intentionally deferred to the next pass.
Validation across final slices included:
- `cargo test -p tui workspace_panel`
- `cargo test -p tui multi_pod`
- `cargo test -p client ticket_role`
- `cargo test -p ticket`
- `cargo test -p yoi panel`
- `cargo check --workspace --all-targets`
- `cargo fmt --check`
- `cargo build -p yoi`
- `target/debug/yoi ticket doctor`
- `nix build .#yoi --no-link`

View File

@ -0,0 +1,138 @@
<!-- event: create author: yoi ticket at: 2026-06-05T21:07:03Z -->
## Created
Created by LocalTicketBackend create.
---
<!-- event: plan author: hare at: 2026-06-05T21:09:19Z -->
## Plan
Plan: design the workspace orchestration panel before implementation.
The panel is not a `:ticket` command extension. It is a workspace-scoped orchestration surface with:
- Companion foreground management chat;
- Ticket Intake composer target that sends user input directly to Intake, not Companion history;
- background workspace Orchestrator restored/spawned as `<dir-name>-orchestrator`;
- Intake -> Orchestrator handoff;
- an action model prioritizing human-required Ticket/Intake decisions over raw Pod idle state.
Implementation is split into child tickets:
1. `workspace-orchestration-panel-design`
2. `workspace-panel-orchestrator-lifecycle`
3. `workspace-panel-composer-targets`
4. `ticket-intake-orchestrator-handoff`
5. `workspace-panel-action-model`
Existing `:ticket ...` commands remain as low-level fallback, not the main UX.
---
<!-- event: decision author: hare at: 2026-06-05T23:05:06Z -->
## Decision
Decision/update from UI discussion:
Do not preserve `--multi` as a separate long-term UI surface. The workspace panel should become the unified workspace view.
`--multi`-style Pod list behavior remains useful, but should be treated as the panel's no-Ticket / Pod-centric route rather than a distinct architecture. In workspaces where Ticket config/storage is unavailable or Ticket usage is not desired, opening the panel should still provide the current multi-Pod discovery/attach/send affordances as a fallback view.
Implications:
- Implementation can reuse current `--multi` code/components as the first panel substrate.
- The long-term command/launch model should prefer a panel view option; any existing `--multi` entry point can remain as a compatibility alias or shortcut into the Pod-centric panel route during migration.
- The panel model should tolerate an empty/unavailable Ticket backend and still show useful Pod rows/actions.
- Avoid designing two separate surfaces with duplicated selection, attach, send, and rendering logic.
---
<!-- event: decision author: hare at: 2026-06-05T23:08:49Z -->
## Decision
Decision/update from launch-route discussion:
Current route is still `yoi --multi` -> `LaunchMode::Multi` -> `single_pod::run_multi(...)` -> current multi-Pod dashboard. There is no dedicated `yoi panel` command yet.
Target route should be `yoi panel`: open the workspace panel for the current working directory/workspace. The panel should resolve the workspace from cwd using the same project/config/Ticket root conventions as the rest of Yoi, then:
- show Ticket/action-centric mode when `.yoi/tickets` / Ticket config is available or Ticket use is active;
- fall back to the Pod-centric route (current `--multi` behavior) when the workspace has no Ticket backend/config or no Ticket records;
- keep current `--multi` as a temporary compatibility alias/shortcut into `yoi panel`'s Pod-centric route during migration, rather than a separate long-term surface.
This keeps a single workspace control surface while preserving the existing multi-Pod affordances for non-Ticket workspaces.
---
<!-- event: decision author: hare at: 2026-06-05T23:13:39Z -->
## Decision
Decision/update from launch and no-Ticket workspace discussion:
Remove `--multi` as a user-facing launch option rather than keeping it as a compatibility alias.
Target launch model:
- `yoi panel` opens the workspace panel for the current working directory/workspace.
- The panel resolves Ticket config from the workspace.
- If `.yoi/ticket.config.toml` / Ticket config is defined and usable, the panel shows Ticket/action-centric workspace UI.
- If Ticket config is undefined, the panel suppresses Ticket-related UI entirely:
- no Ticket/action rows;
- no Ticket-specific Pod labels/actions;
- no Intake/Orchestrator/Ticket workflow affordances;
- no Ticket-related diagnostics unless needed to explain an explicitly requested Ticket action.
- In that no-Ticket mode, `yoi panel` should provide the same functional surface as the current `--multi` view: Pod discovery/status, selection, attach/open, and direct send where currently supported.
This keeps one panel implementation while avoiding a permanent `--multi` surface or confusing Ticket UI in workspaces that have not opted into Ticket config.
---
<!-- event: close author: hare at: 2026-06-06T06:05:38Z status: closed -->
## Closed
Completed the first-pass workspace orchestration panel implementation.
Delivered child/follow-up tickets:
- `workspace-orchestration-panel-design`: approved design; panel is a unified `yoi panel` workspace view, not a `--multi`/single-Pod split.
- `workspace-panel-action-model`: added thin local-file-first workspace panel ViewModel, Ticket/action rows, no-Ticket Pod-centric fallback, `yoi panel`, and removed `--multi`.
- `workspace-panel-orchestrator-lifecycle`: Ticket-enabled panels ensure/restore/spawn a background workspace Orchestrator and skip Orchestrator lifecycle in no-Ticket workspaces.
- `workspace-panel-composer-targets`: added Companion vs Ticket Intake composer targets and Intake launch through the role launcher.
- `ticket-intake-orchestrator-handoff`: added auditable Intake -> Orchestrator handoff and pre-run peer registration.
- `workspace-panel-ticket-action-dispatch`: added safe dispatch for Go/Defer and safe diagnostics for Review/Close.
Final first-pass behavior:
- `yoi panel` opens the workspace panel for cwd/workspace.
- `--multi` is removed as a user-facing route.
- If `.yoi/ticket.config.toml` is absent, the panel suppresses Ticket UI and behaves as a Pod-centric dashboard.
- If Ticket config is usable, the panel shows Ticket/action rows, background Pod rows, Orchestrator lifecycle state, and composer target selection.
- Ticket Intake composer target launches Intake with the typed message as Intake input, not Companion/current Pod history.
- Intake receives Orchestrator handoff in committed launch input/history and pre-run peer registration is attempted.
- Go records a Ticket decision for Orchestrator routing/preflight and optionally notifies Orchestrator, without starting implementation.
- Defer records a decision and moves open Tickets to pending where applicable.
- Review/Close remain safe explicit-flow diagnostics rather than silent approval/closure.
- Layout/display polish is intentionally deferred to the next pass.
Validation across final slices included:
- `cargo test -p tui workspace_panel`
- `cargo test -p tui multi_pod`
- `cargo test -p client ticket_role`
- `cargo test -p ticket`
- `cargo test -p yoi panel`
- `cargo check --workspace --all-targets`
- `cargo fmt --check`
- `cargo build -p yoi`
- `target/debug/yoi ticket doctor`
- `nix build .#yoi --no-link`
---

View File

@ -0,0 +1,120 @@
# Delegation intent: Ticket Intake -> Orchestrator handoff
## Classification
`implementation-ready` after Orchestrator lifecycle and composer target slices.
`yoi panel` now opens the workspace panel, ensures a workspace Orchestrator for Ticket-enabled workspaces, and can launch a Ticket Intake Pod from the composer. This ticket connects those pieces with an explicit handoff contract.
## Intent
When the panel launches a Ticket Intake Pod, provide the workspace Orchestrator as the Intake's notification/handoff target so the Intake can report clarified Ticket readiness to the Orchestrator. The handoff must be visible in the destination Pod's committed input/history or durable peer metadata, not hidden context.
The handoff should not authorize implementation automatically. It only tells Intake where to report readiness or follow-up state after Intake has clarified and materialized/agreed a Ticket.
## Worktree / branch
- worktree: `/home/hare/Projects/yoi/.worktree/ticket-intake-orchestrator-handoff`
- branch: `work/ticket-intake-orchestrator-handoff`
This ticket may read tracked `.yoi/tickets` records/design artifacts. Do not read or edit `.yoi/memory/`.
## Requirements
- Define a simple handoff contract from workspace panel to Intake.
- Include Orchestrator Pod name.
- Include that Intake should notify/report to the Orchestrator when it has enough information for routing/preflight.
- State clearly that implementation must not start automatically; human Go / Orchestrator routing gates still apply.
- Carry the handoff through the existing Ticket role launch path where practical.
- Prefer adding explicit fields/options to `TicketRoleLaunchContext` or equivalent over ad hoc string assembly in TUI.
- Do not duplicate role prompt/profile construction in TUI.
- Register Intake and Orchestrator as peers when both are live/reachable if existing `RegisterPeer` semantics make this feasible.
- Registration failure should be a bounded diagnostic/warning, not a reason to lose the Intake launch if the launch itself succeeded.
- Do not rely only on an LLM-internal instruction if a durable peer metadata path is available.
- Include the handoff instruction in the Intake launch input/history so the Intake can explain why it may notify the Orchestrator in later turns.
- Do not inject hidden context only.
- Do not append the Intake request to Companion/current Pod history.
- Add panel success/failure diagnostics for handoff setup separately from Intake launch success.
- No-Ticket workspace must not expose this path.
- Keep Orchestrator as background coordinator; do not make it foreground composer target by default.
- Keep the contract small and typed enough to test, but do not build a scheduler/queue.
## Suggested contract shape
A small data type is preferred over freeform string construction in the panel:
```rust
TicketIntakeHandoff {
orchestrator_pod: String,
workspace_label: String,
}
```
The role launcher can render this into the Intake `user_instruction` as an explicit section, for example:
```text
Panel handoff:
- Workspace Orchestrator Pod: <name>
- When Intake has clarified the request and created/updated the Ticket, notify/report readiness to this Orchestrator.
- Do not start implementation automatically; wait for Orchestrator routing/preflight and human Go gates.
```
If peer registration is implemented, Intake should also receive durable peer metadata for the Orchestrator using the existing Pod peer registration path.
## Non-goals
- Automatic implementation scheduling.
- Queue/lease system.
- Full Ticket action model refinement.
- Final layout/display tuning.
- Generic arbitrary handoff routing.
- Reintroducing `--multi`.
## Current code map
- `crates/tui/src/multi_pod.rs`
- Panel composer target handling and Intake launch path.
- `crates/tui/src/workspace_panel.rs`
- Orchestrator state/name and composer target view state.
- `crates/client/src/ticket_role.rs`
- Role launch context and prompt/input assembly; preferred place for typed handoff fields.
- `crates/protocol/src/lib.rs`
- `Method::RegisterPeer` / `Event::PeerRegistered` semantics.
- `crates/pod/src/controller.rs`
- Peer registration handling for reference.
- `crates/tui/src/socket.rs` / existing client helpers
- Reuse existing socket/method helpers where practical; do not hand-roll protocol loops unnecessarily.
## Validation
Run at least:
- targeted tests for role launch input rendering with handoff;
- targeted tests for panel Intake launch including handoff data;
- targeted tests for peer registration success/failure behavior if implemented;
- `cargo test -p client ticket_role`;
- `cargo test -p tui workspace_panel`;
- `cargo test -p tui multi_pod` or updated panel test names;
- `cargo test -p yoi panel`;
- `cargo check --workspace --all-targets`;
- `cargo fmt --check`;
- `git diff --check`;
- `cargo build -p yoi`;
- `target/debug/yoi ticket doctor`.
Run `nix build .#yoi --no-link` if feasible.
## Completion report
Report:
- worktree path / branch;
- commit hash;
- final handoff contract/data type;
- how Orchestrator target is included in Intake input/history;
- whether/how peer registration is performed;
- launch vs handoff diagnostic behavior;
- proof no Companion/current Pod history mutation is used for Intake body;
- tests updated/added;
- validation results;
- known follow-up layout/display tuning items.

View File

@ -2,12 +2,12 @@
id: 20260605-210704-ticket-intake-orchestrator-handoff id: 20260605-210704-ticket-intake-orchestrator-handoff
slug: ticket-intake-orchestrator-handoff slug: ticket-intake-orchestrator-handoff
title: Ticket intake to orchestrator handoff title: Ticket intake to orchestrator handoff
status: open status: closed
kind: task kind: task
priority: P1 priority: P1
labels: [ticket, intake, orchestrator, handoff] labels: [ticket, intake, orchestrator, handoff]
created_at: 2026-06-05T21:07:04Z created_at: 2026-06-05T21:07:04Z
updated_at: 2026-06-05T21:07:04Z updated_at: 2026-06-06T05:27:40Z
assignee: null assignee: null
legacy_ticket: null legacy_ticket: null
--- ---

View File

@ -0,0 +1,37 @@
Implemented Ticket Intake -> Orchestrator handoff for the workspace panel.
Changes:
- Added typed handoff contract `TicketIntakeHandoff { orchestrator_pod, workspace_label }`.
- Extended `TicketRoleLaunchContext` with optional Intake handoff data.
- Rendered a `Panel handoff:` section into Intake launch input/history through the existing Ticket role launcher.
- The handoff section includes:
- workspace label;
- workspace Orchestrator Pod name;
- expected readiness/report fields;
- an explicit no-auto-implementation gate requiring Orchestrator routing/preflight and human Go before implementation.
- Added `TicketRoleLaunchOptions` / `launch_ticket_role_pod_with_options(...)` for bounded pre-run peer registration.
- Preserved `launch_ticket_role_pod(...)` as the default no-options wrapper.
- Panel Intake launch now passes Orchestrator handoff data and, when applicable, a pre-run peer registration request.
- `RegisterPeer` is sent before the first `Method::Run`, avoiding the controller's running-state rejection path.
- Peer registration warnings are non-fatal when Intake launch/run succeeds.
- No-Ticket workspaces remain Companion-only and expose no handoff path.
- Intake requests do not route through Companion/current Pod history or selected-Pod direct send.
- No automatic implementation scheduling/coder/reviewer spawning was added.
- `--multi` was not reintroduced.
Validation after merge:
- `cargo test -p client ticket_role`
- `cargo test -p tui workspace_panel`
- `cargo test -p tui multi_pod`
- `cargo test -p yoi panel`
- `cargo check --workspace --all-targets`
- `cargo fmt --check`
- `git diff --check HEAD~1..HEAD`
- `cargo build -p yoi`
- `target/debug/yoi ticket doctor`
- `nix build .#yoi --no-link --print-out-paths`
External review approved after one requested-changes cycle.
Known follow-up:
- Final layout/display wording for handoff notices is deferred to panel tuning.

View File

@ -0,0 +1,83 @@
<!-- event: create author: yoi ticket at: 2026-06-05T21:07:04Z -->
## Created
Created by LocalTicketBackend create.
---
<!-- event: plan author: hare at: 2026-06-06T04:49:16Z -->
## Plan
Preflight result: `implementation-ready` after Orchestrator lifecycle and composer target slices.
This ticket should connect panel-launched Intake Pods to the workspace Orchestrator with an explicit, auditable handoff target. The handoff should be carried through the existing Ticket role launch path, preferably as a typed launch-context field rendered into the Intake first-run input/history, and peer metadata should be registered when feasible. It must not authorize automatic implementation.
Detailed delegation intent is recorded in `artifacts/delegation-intent.md`.
---
<!-- event: review author: hare at: 2026-06-06T05:27:40Z status: approve -->
## Review: approve
External reviewer approved current HEAD after requested changes were addressed.
Review summary:
- The previous blocking issue was fixed: `RegisterPeer` is now sent through the Ticket role launcher before the first `Method::Run`.
- Handoff uses a small typed `TicketIntakeHandoff` contract and `TicketRoleLaunchOptions`, not scheduler state.
- Intake launch input includes Orchestrator target and the no-auto-implementation gate.
- No-Ticket workspaces remain Companion-only/no handoff.
- Peer registration failures/skips are bounded warnings, not launch failures.
- Intake request does not route through Companion/current Pod history or selected-Pod direct send.
- No automatic implementation scheduling or `--multi` reintroduction.
---
<!-- event: close author: hare at: 2026-06-06T05:27:40Z status: closed -->
## Closed
Implemented Ticket Intake -> Orchestrator handoff for the workspace panel.
Changes:
- Added typed handoff contract `TicketIntakeHandoff { orchestrator_pod, workspace_label }`.
- Extended `TicketRoleLaunchContext` with optional Intake handoff data.
- Rendered a `Panel handoff:` section into Intake launch input/history through the existing Ticket role launcher.
- The handoff section includes:
- workspace label;
- workspace Orchestrator Pod name;
- expected readiness/report fields;
- an explicit no-auto-implementation gate requiring Orchestrator routing/preflight and human Go before implementation.
- Added `TicketRoleLaunchOptions` / `launch_ticket_role_pod_with_options(...)` for bounded pre-run peer registration.
- Preserved `launch_ticket_role_pod(...)` as the default no-options wrapper.
- Panel Intake launch now passes Orchestrator handoff data and, when applicable, a pre-run peer registration request.
- `RegisterPeer` is sent before the first `Method::Run`, avoiding the controller's running-state rejection path.
- Peer registration warnings are non-fatal when Intake launch/run succeeds.
- No-Ticket workspaces remain Companion-only and expose no handoff path.
- Intake requests do not route through Companion/current Pod history or selected-Pod direct send.
- No automatic implementation scheduling/coder/reviewer spawning was added.
- `--multi` was not reintroduced.
Validation after merge:
- `cargo test -p client ticket_role`
- `cargo test -p tui workspace_panel`
- `cargo test -p tui multi_pod`
- `cargo test -p yoi panel`
- `cargo check --workspace --all-targets`
- `cargo fmt --check`
- `git diff --check HEAD~1..HEAD`
- `cargo build -p yoi`
- `target/debug/yoi ticket doctor`
- `nix build .#yoi --no-link --print-out-paths`
External review approved after one requested-changes cycle.
Known follow-up:
- Final layout/display wording for handoff notices is deferred to panel tuning.
---

View File

@ -2,12 +2,12 @@
id: 20260605-210704-workspace-panel-action-model id: 20260605-210704-workspace-panel-action-model
slug: workspace-panel-action-model slug: workspace-panel-action-model
title: Workspace panel action model title: Workspace panel action model
status: open status: closed
kind: task kind: task
priority: P1 priority: P1
labels: [tui, ticket, orchestration, panel] labels: [tui, ticket, orchestration, panel]
created_at: 2026-06-05T21:07:04Z created_at: 2026-06-05T21:07:04Z
updated_at: 2026-06-05T22:35:56Z updated_at: 2026-06-05T23:31:28Z
assignee: null assignee: null
legacy_ticket: null legacy_ticket: null
--- ---

View File

@ -0,0 +1,36 @@
Implemented the first workspace panel action/model slice.
Changes:
- Added `crates/tui/src/workspace_panel.rs` with a thin UI/action model:
- `WorkspacePanelViewModel`
- `WorkspacePanelHeader`
- `PanelRow`
- `PanelRowKey`
- `TicketPanelEntry`
- `ActionPriority`
- `NextUserAction`
- `TicketPanelPhase`
- Added local-file-first Ticket row derivation through Rust Ticket config/backend APIs.
- Ticket rows are gated on explicit workspace Ticket config. If `.yoi/ticket.config.toml` is absent, the panel suppresses Ticket UI and remains Pod-centric.
- Added simple first-slice heuristics for intake/user reply, ready-for-Go, review needed, close ready, blocked, active work, backlog, and spike needed/running.
- Ordinary open backlog Tickets remain background/non-action and are not promoted to `Go` by default.
- Integrated the model into the current multi-Pod dashboard substrate so Ticket/action rows appear above passive Pod rows while preserving Pod selection, attach/open, and direct-send behavior.
- Added `yoi panel` launch parsing and removed `--multi` as a user-facing launch route.
Validation after merge:
- `cargo test -p tui workspace_panel`
- `cargo test -p tui multi`
- `cargo test -p yoi panel`
- `cargo test -p yoi parse_multi_flag_is_not_a_launch_alias`
- `cargo check --workspace --all-targets`
- `cargo fmt --check`
- `git diff --check HEAD~1..HEAD`
- `cargo build -p yoi`
- `target/debug/yoi ticket doctor`
- `nix build .#yoi --no-link --print-out-paths`
External review approved after fixes.
Known follow-up:
- Surface malformed Ticket config/read failures via panel diagnostics instead of silently falling back to Pod-only display when config exists but loading fails.
- Layout/display tuning is intentionally deferred until the end-to-end panel flow exists.

View File

@ -0,0 +1,84 @@
<!-- event: create author: yoi ticket at: 2026-06-05T21:07:04Z -->
## Created
Created by LocalTicketBackend create.
---
<!-- event: plan author: hare at: 2026-06-05T22:35:56Z -->
## Plan
Preflight result: `implementation-ready` as the first implementation slice after design approval.
Implementation should add a thin, testable workspace panel ViewModel/action model and integrate it enough into the current `--multi` dashboard to show Ticket/action rows above passive Pod rows. The model should be local-file-first from `.yoi/tickets/`, reuse existing Pod list data for background Pod state, avoid live socket I/O in the model layer, and leave final layout/display tuning to follow-up adjustments after the first end-to-end pass.
Detailed delegation intent is recorded in `artifacts/delegation-intent.md`.
---
<!-- event: review author: hare at: 2026-06-05T23:31:28Z status: approve -->
## Review: approve
External reviewer approved current HEAD after requested changes were addressed.
Review summary:
- Spike classification is present and tested.
- Ordinary open backlog Tickets remain background/non-action and are not promoted to `Go` by default.
- Integration coverage verifies a Ticket action row above an idle Pod while preserving Pod open/direct-send behavior.
- The model remains plain UI/action data; rendering and live socket I/O stay in existing TUI/Pod paths.
- Ticket loading uses Rust Ticket config/backend APIs and does not shell out.
- `yoi panel` is wired as the launch path; `--multi` is no longer accepted.
- Missing Ticket config suppresses Ticket UI and leaves the panel Pod-centric.
Non-blocking follow-up: malformed Ticket config/read errors are currently silently ignored by the panel model. A later refinement should surface those through the existing diagnostics field when config exists but loading fails.
---
<!-- event: close author: hare at: 2026-06-05T23:31:28Z status: closed -->
## Closed
Implemented the first workspace panel action/model slice.
Changes:
- Added `crates/tui/src/workspace_panel.rs` with a thin UI/action model:
- `WorkspacePanelViewModel`
- `WorkspacePanelHeader`
- `PanelRow`
- `PanelRowKey`
- `TicketPanelEntry`
- `ActionPriority`
- `NextUserAction`
- `TicketPanelPhase`
- Added local-file-first Ticket row derivation through Rust Ticket config/backend APIs.
- Ticket rows are gated on explicit workspace Ticket config. If `.yoi/ticket.config.toml` is absent, the panel suppresses Ticket UI and remains Pod-centric.
- Added simple first-slice heuristics for intake/user reply, ready-for-Go, review needed, close ready, blocked, active work, backlog, and spike needed/running.
- Ordinary open backlog Tickets remain background/non-action and are not promoted to `Go` by default.
- Integrated the model into the current multi-Pod dashboard substrate so Ticket/action rows appear above passive Pod rows while preserving Pod selection, attach/open, and direct-send behavior.
- Added `yoi panel` launch parsing and removed `--multi` as a user-facing launch route.
Validation after merge:
- `cargo test -p tui workspace_panel`
- `cargo test -p tui multi`
- `cargo test -p yoi panel`
- `cargo test -p yoi parse_multi_flag_is_not_a_launch_alias`
- `cargo check --workspace --all-targets`
- `cargo fmt --check`
- `git diff --check HEAD~1..HEAD`
- `cargo build -p yoi`
- `target/debug/yoi ticket doctor`
- `nix build .#yoi --no-link --print-out-paths`
External review approved after fixes.
Known follow-up:
- Surface malformed Ticket config/read failures via panel diagnostics instead of silently falling back to Pod-only display when config exists but loading fails.
- Layout/display tuning is intentionally deferred until the end-to-end panel flow exists.
---

View File

@ -0,0 +1,100 @@
# Delegation intent: workspace panel composer targets
## Classification
`implementation-ready` after action model and Orchestrator lifecycle slices.
The panel now has `yoi panel`, Ticket-config-gated Ticket UI, Pod-centric no-Ticket behavior, and Ticket-enabled Orchestrator lifecycle. This ticket adds the first composer target split so users can send either to the Companion/selected Pod path or directly to a new Intake role Pod.
## Intent
Add workspace panel composer target selection with two first-class targets:
- `Companion` / current Pod-centric send path;
- `Ticket Intake` / launch a new Intake role Pod with the composer body as its first run input.
The Intake path must not append the message to Companion/current Pod history. It should go only to the launched Intake Pod's initial `Method::Run` input/history through the existing Ticket role launcher path.
## Worktree / branch
- worktree: `/home/hare/Projects/yoi/.worktree/workspace-panel-composer-targets`
- branch: `work/workspace-panel-composer-targets`
This ticket may read tracked `.yoi/tickets` records/design artifacts. Do not read or edit `.yoi/memory/`.
## Requirements
- Add a workspace panel composer target model with at least:
- `Companion` / existing selected-Pod send behavior;
- `Ticket Intake`.
- The UI must clearly show the active composer target using existing TUI visual conventions.
- Add a simple keybinding or command path to switch targets without losing typed text where practical.
- `Companion` target should preserve existing panel/Pod-centric send behavior.
- In no-Ticket workspace, this should be the only available target and should behave like the current Pod dashboard send.
- `Ticket Intake` target should be available only when Ticket config is defined/usable.
- Empty Intake messages must be rejected with a bounded diagnostic/actionbar-style message.
- Intake target must call the existing Ticket role launcher (`TicketRole::Intake`) rather than constructing spawn/profile/workflow prompt content inside TUI.
- The composer body must become dynamic user instruction/run input for the Intake launch.
- The Intake body must not be sent to Companion/current Pod history or to any selected Pod.
- After a successful Intake launch, clear the composer and surface a bounded success notice including the launched Pod name.
- On launch failure, keep the composer text and surface a bounded diagnostic.
- Do not implement Intake -> Orchestrator handoff payload in this ticket; that is the next child ticket.
- Do not reintroduce `--multi`.
- Keep layout changes minimal; final display tuning is later.
## Non-goals
- Intake -> Orchestrator handoff contract.
- Generic arbitrary target routing.
- Scheduler/queue.
- Ticket action queue display.
- Final layout/display tuning.
- Companion Pod/session creation beyond preserving existing panel send behavior.
## Current code map
- `crates/tui/src/multi_pod.rs`
- Current panel substrate, composer handling, selected Pod send/open behavior, notices/diagnostics.
- `crates/tui/src/workspace_panel.rs`
- ViewModel/header/diagnostics. Extend with composer target view state only if useful; keep it thin.
- `crates/client/src/ticket_role.rs`
- `TicketRoleLaunchContext`, `TicketRole::Intake`, and `launch_ticket_role_pod(...)`. Use this path for Intake launch.
- `crates/tui/src/command.rs`
- Existing `:ticket intake ...` route for reference only; do not duplicate prompt/profile construction in TUI.
- `crates/tui/src/app.rs`
- Normal composer/history semantics for reference; ensure Intake target does not mutate the wrong history.
## Validation
Run at least:
- targeted TUI tests for composer target switching and Enter behavior;
- a test that Intake target rejects empty input;
- a test that Intake target calls/constructs a Ticket role launch request without sending to selected Pod path;
- a test that no-Ticket workspace exposes only Companion/Pod-centric send;
- `cargo test -p tui workspace_panel`;
- `cargo test -p tui multi` or updated panel test names;
- `cargo test -p client ticket_role` if role launcher code changes;
- `cargo test -p yoi panel`;
- `cargo check --workspace --all-targets`;
- `cargo fmt --check`;
- `git diff --check`;
- `cargo build -p yoi`;
- `target/debug/yoi ticket doctor`.
Run `nix build .#yoi --no-link` if feasible.
## Completion report
Report:
- worktree path / branch;
- commit hash;
- composer target model/keybinding;
- no-Ticket target behavior;
- Intake launch path and how the typed body is routed;
- proof that Intake body does not go to Companion/current Pod history;
- diagnostics/success notices;
- tests updated/added;
- validation results;
- known follow-up layout/display tuning items.

View File

@ -2,12 +2,12 @@
id: 20260605-210704-workspace-panel-composer-targets id: 20260605-210704-workspace-panel-composer-targets
slug: workspace-panel-composer-targets slug: workspace-panel-composer-targets
title: Workspace panel composer targets title: Workspace panel composer targets
status: open status: closed
kind: task kind: task
priority: P1 priority: P1
labels: [tui, composer, intake, panel] labels: [tui, composer, intake, panel]
created_at: 2026-06-05T21:07:04Z created_at: 2026-06-05T21:07:04Z
updated_at: 2026-06-05T21:07:04Z updated_at: 2026-06-06T04:47:31Z
assignee: null assignee: null
legacy_ticket: null legacy_ticket: null
--- ---

View File

@ -0,0 +1,36 @@
Implemented first workspace panel composer targets.
Changes:
- Added workspace panel composer target model with:
- `Companion` / existing selected-Pod send behavior;
- `Ticket Intake`.
- Added `Ctrl+T` target switching without clearing typed composer text.
- Displayed active target in the panel target/status line and actionbar guidance using existing TUI conventions.
- No-Ticket and unusable-Ticket-config workspaces expose only `Companion` / Pod-centric behavior.
- `Ticket Intake` is available only when Ticket config is usable.
- Empty Intake input is rejected with a bounded diagnostic.
- Intake launch uses the existing `TicketRole::Intake` / `launch_ticket_role_pod(...)` path.
- Composer text is passed as `TicketRoleLaunchContext.user_instruction` for the Intake launch.
- Intake text is not sent to Companion/current Pod history or selected-Pod direct send path.
- Successful Intake launch clears composer and reports the launched Pod name.
- Launch failure keeps composer text and reports a bounded diagnostic.
- Intake -> Orchestrator handoff payload was intentionally not added; the follow-up handoff ticket owns that.
- `--multi` was not reintroduced.
Validation after merge:
- `cargo test -p tui workspace_panel`
- `cargo test -p tui multi_pod`
- `cargo test -p client ticket_role`
- `cargo test -p yoi panel`
- `cargo check --workspace --all-targets`
- `cargo fmt --check`
- `git diff --check HEAD~1..HEAD`
- `cargo build -p yoi`
- `target/debug/yoi ticket doctor`
- `nix build .#yoi --no-link --print-out-paths`
External review approved with no requested changes.
Known follow-up:
- Active target display is intentionally minimal; final placement/color/wording tuning is deferred.
- Intake -> Orchestrator handoff remains the next child ticket.

View File

@ -0,0 +1,86 @@
<!-- event: create author: yoi ticket at: 2026-06-05T21:07:04Z -->
## Created
Created by LocalTicketBackend create.
---
<!-- event: plan author: hare at: 2026-06-06T04:24:53Z -->
## Plan
Preflight result: `implementation-ready` after action model and Orchestrator lifecycle.
This ticket should add the first composer target split for `yoi panel`: preserve the Companion/selected-Pod send path, and add a Ticket Intake target that launches an Intake role Pod with the composer body as the initial user instruction. The Intake body must not be appended to Companion/current Pod history or sent to the selected Pod.
Ticket Intake target is available only when Ticket config is defined/usable. No-Ticket workspaces remain Pod-centric and expose only the existing send behavior.
Detailed delegation intent is recorded in `artifacts/delegation-intent.md`.
---
<!-- event: review author: hare at: 2026-06-06T04:47:31Z status: approve -->
## Review: approve
External reviewer approved commit `a7c155e` with no blocking issues.
Review summary:
- Composer targets include `Companion` and `Ticket Intake`.
- No-Ticket/unusable-Ticket-config workspaces remain Companion-only.
- Active target is visible and `Ctrl+T` switching preserves typed input.
- Ticket Intake uses `TicketRole::Intake` via `launch_ticket_role_pod` with composer text as `TicketRoleLaunchContext.user_instruction`.
- Intake text does not route through the selected-Pod direct send path.
- Empty Intake input is rejected locally with a bounded diagnostic.
- Success clears composer and reports launched Pod name; failure keeps composer and reports a bounded diagnostic.
- No Intake -> Orchestrator handoff payload was added.
- `--multi` was not reintroduced.
---
<!-- event: close author: hare at: 2026-06-06T04:47:31Z status: closed -->
## Closed
Implemented first workspace panel composer targets.
Changes:
- Added workspace panel composer target model with:
- `Companion` / existing selected-Pod send behavior;
- `Ticket Intake`.
- Added `Ctrl+T` target switching without clearing typed composer text.
- Displayed active target in the panel target/status line and actionbar guidance using existing TUI conventions.
- No-Ticket and unusable-Ticket-config workspaces expose only `Companion` / Pod-centric behavior.
- `Ticket Intake` is available only when Ticket config is usable.
- Empty Intake input is rejected with a bounded diagnostic.
- Intake launch uses the existing `TicketRole::Intake` / `launch_ticket_role_pod(...)` path.
- Composer text is passed as `TicketRoleLaunchContext.user_instruction` for the Intake launch.
- Intake text is not sent to Companion/current Pod history or selected-Pod direct send path.
- Successful Intake launch clears composer and reports the launched Pod name.
- Launch failure keeps composer text and reports a bounded diagnostic.
- Intake -> Orchestrator handoff payload was intentionally not added; the follow-up handoff ticket owns that.
- `--multi` was not reintroduced.
Validation after merge:
- `cargo test -p tui workspace_panel`
- `cargo test -p tui multi_pod`
- `cargo test -p client ticket_role`
- `cargo test -p yoi panel`
- `cargo check --workspace --all-targets`
- `cargo fmt --check`
- `git diff --check HEAD~1..HEAD`
- `cargo build -p yoi`
- `target/debug/yoi ticket doctor`
- `nix build .#yoi --no-link --print-out-paths`
External review approved with no requested changes.
Known follow-up:
- Active target display is intentionally minimal; final placement/color/wording tuning is deferred.
- Intake -> Orchestrator handoff remains the next child ticket.
---

View File

@ -0,0 +1,96 @@
# Delegation intent: workspace panel orchestrator lifecycle
## Classification
`implementation-ready` after the first action-model slice.
The action model slice added `yoi panel`, removed `--multi` as a user-facing launch route, and established no-Ticket behavior: if workspace Ticket config is absent, the panel suppresses Ticket UI and behaves as a Pod-centric dashboard. This lifecycle ticket should respect that boundary.
## Intent
When `yoi panel` opens in a Ticket-enabled workspace, ensure a workspace Orchestrator Pod exists or surface a bounded diagnostic explaining why it cannot be restored/spawned. The Orchestrator remains a background coordinator; closing the panel must not stop it, and Companion remains the foreground management target.
In a no-Ticket workspace, do not spawn/restore an Orchestrator and do not show Orchestrator/Ticket workflow affordances. The panel should remain equivalent to the old Pod-centric `--multi` behavior.
## Worktree / branch
- worktree: `/home/hare/Projects/yoi/.worktree/workspace-panel-orchestrator-lifecycle`
- branch: `work/workspace-panel-orchestrator-lifecycle`
This ticket may read tracked `.yoi/tickets` records/design artifacts. Do not read or edit `.yoi/memory/`.
## Requirements
- Derive the workspace Orchestrator Pod name from the workspace directory, e.g. `<dir-name>-orchestrator`.
- Use a safe/stable Pod-name normalization compatible with existing Pod name constraints.
- Add tests for common workspace names and edge cases.
- On `yoi panel` open in a Ticket-enabled workspace:
- if Orchestrator is already live, observe/report it as live;
- if restorable, restore it through existing Pod restore semantics;
- if missing and permitted, spawn it;
- if restore/spawn is not possible, surface a bounded panel diagnostic.
- Gate Orchestrator lifecycle on explicit Ticket config availability/usability.
- If `.yoi/ticket.config.toml` is absent, skip Orchestrator lifecycle and show Pod-centric panel only.
- If config exists but is malformed/unusable, surface a diagnostic rather than silently behaving as no-Ticket if practical in this slice.
- Use existing Ticket role/profile configuration and role launcher where practical; do not hand-build role prompts/profiles inside TUI.
- Preserve current Pod registry/restore/spawn semantics; do not duplicate registry logic.
- Panel close must not stop the Orchestrator.
- Do not make Orchestrator the foreground composer target by default.
- Keep layout changes minimal; final display tuning is later.
## Non-goals
- Composer target switching / New Intake send.
- Intake -> Orchestrator handoff payload.
- Scheduler/lease/queue.
- Automatic coder/reviewer spawning.
- Final layout/display tuning.
- Reintroducing `--multi`.
## Current code map
- `crates/tui/src/lib.rs`
- `LaunchMode::Panel` path.
- `crates/tui/src/single_pod.rs`
- `run_panel(...)` loop around the panel/dashboard and nested Pod open behavior.
- `crates/tui/src/multi_pod.rs`
- current panel substrate, snapshot loading, rows/rendering/actions.
- `crates/tui/src/workspace_panel.rs`
- thin panel ViewModel / Ticket row model. Extend header/diagnostics as needed without making it a runtime authority.
- `crates/client/src/ticket_role.rs`
- Ticket role launcher path; prefer this for spawning role Pods if it fits panel lifecycle.
- `crates/ticket/src/config.rs`
- Ticket config detection/loading; use this to gate Ticket-enabled vs no-Ticket mode.
- Existing Pod restore/list/spawn client APIs used by picker/multi/role launcher.
## Validation
Run at least:
- targeted TUI tests for name derivation and lifecycle decision logic;
- `cargo test -p tui workspace_panel`;
- `cargo test -p tui multi` or updated panel test names;
- `cargo test -p client ticket_role` if role launcher code changes;
- `cargo test -p yoi panel`;
- `cargo check --workspace --all-targets`;
- `cargo fmt --check`;
- `git diff --check`;
- `cargo build -p yoi`;
- `target/debug/yoi ticket doctor`.
Run `nix build .#yoi --no-link` if feasible.
## Completion report
Report:
- worktree path / branch;
- commit hash;
- final Orchestrator Pod naming rule;
- lifecycle decision behavior for live/restorable/missing/unavailable;
- no-Ticket workspace behavior;
- role launcher/profile usage;
- diagnostics surfaced;
- tests updated/added;
- validation results;
- known follow-up layout/display tuning items.

View File

@ -2,12 +2,12 @@
id: 20260605-210704-workspace-panel-orchestrator-lifecycle id: 20260605-210704-workspace-panel-orchestrator-lifecycle
slug: workspace-panel-orchestrator-lifecycle slug: workspace-panel-orchestrator-lifecycle
title: Workspace panel orchestrator lifecycle title: Workspace panel orchestrator lifecycle
status: open status: closed
kind: task kind: task
priority: P1 priority: P1
labels: [tui, pod, orchestrator, panel] labels: [tui, pod, orchestrator, panel]
created_at: 2026-06-05T21:07:04Z created_at: 2026-06-05T21:07:04Z
updated_at: 2026-06-05T21:07:04Z updated_at: 2026-06-06T04:23:48Z
assignee: null assignee: null
legacy_ticket: null legacy_ticket: null
--- ---

View File

@ -0,0 +1,32 @@
Implemented workspace panel Orchestrator lifecycle.
Changes:
- `yoi panel` now ensures a workspace Orchestrator only in Ticket-enabled workspaces.
- If `.yoi/ticket.config.toml` is absent, the panel skips Orchestrator lifecycle and remains Pod-centric.
- Existing malformed/unusable Ticket config paths surface diagnostics instead of silently falling back to no-Ticket mode.
- Orchestrator Pod name is derived from the workspace directory using ASCII-safe normalization and the `-orchestrator` suffix, capped at 80 chars.
- Already-live Orchestrator is reported as live.
- Restorable Orchestrator is restored through existing Pod restore semantics.
- Missing Orchestrator is spawned through the existing Ticket role launcher as `TicketRole::Orchestrator`.
- Lifecycle decisions use an exact-name authority path and do not depend on the capped UI `PodList` rows.
- Panel close does not stop the Orchestrator.
- Orchestrator is not selected as the default foreground target when it is the only row or when another Pod can be selected.
- Bounded lifecycle/config diagnostics are displayed in the panel.
Validation after merge:
- `cargo test -p tui workspace_panel`
- `cargo test -p tui multi`
- `cargo test -p client ticket_role`
- `cargo test -p yoi panel`
- `cargo check --workspace --all-targets`
- `cargo fmt --check`
- `git diff --check HEAD~1..HEAD`
- `cargo build -p yoi`
- `target/debug/yoi ticket doctor`
- `nix build .#yoi --no-link --print-out-paths`
External review approved after one requested-changes cycle.
Known follow-up:
- Layout/display tuning remains intentionally deferred until the first end-to-end panel flow exists.
- Composer target switching and Intake handoff are covered by follow-up child tickets.

View File

@ -0,0 +1,80 @@
<!-- event: create author: yoi ticket at: 2026-06-05T21:07:04Z -->
## Created
Created by LocalTicketBackend create.
---
<!-- event: plan author: hare at: 2026-06-05T23:32:34Z -->
## Plan
Preflight result: `implementation-ready` after the first action-model slice.
This ticket should add the workspace Orchestrator lifecycle to `yoi panel` only for Ticket-enabled workspaces. If `.yoi/ticket.config.toml` is absent, the panel should skip Orchestrator lifecycle and remain Pod-centric, matching the old `--multi` behavior.
Implementation should use existing Pod restore/spawn semantics and the Ticket role launcher/profile configuration where practical, surface bounded diagnostics, and leave final layout tuning for later.
Detailed delegation intent is recorded in `artifacts/delegation-intent.md`.
---
<!-- event: review author: hare at: 2026-06-06T04:23:48Z status: approve -->
## Review: approve
External reviewer approved current HEAD after requested changes were addressed.
Review summary:
- Orchestrator lifecycle no longer depends on capped UI rows; exact Orchestrator pod presence is checked through an unbounded authority path before lifecycle decisions.
- Existing non-file `.yoi/ticket.config.toml` paths are surfaced as unusable, not absent.
- Tests cover both targeted regressions.
- No `--multi` reintroduction.
- Panel close does not stop the Orchestrator.
- Spawning uses the Ticket role launcher/profile path.
- Default selection avoids making the Orchestrator the foreground target in covered cases.
---
<!-- event: close author: hare at: 2026-06-06T04:23:48Z status: closed -->
## Closed
Implemented workspace panel Orchestrator lifecycle.
Changes:
- `yoi panel` now ensures a workspace Orchestrator only in Ticket-enabled workspaces.
- If `.yoi/ticket.config.toml` is absent, the panel skips Orchestrator lifecycle and remains Pod-centric.
- Existing malformed/unusable Ticket config paths surface diagnostics instead of silently falling back to no-Ticket mode.
- Orchestrator Pod name is derived from the workspace directory using ASCII-safe normalization and the `-orchestrator` suffix, capped at 80 chars.
- Already-live Orchestrator is reported as live.
- Restorable Orchestrator is restored through existing Pod restore semantics.
- Missing Orchestrator is spawned through the existing Ticket role launcher as `TicketRole::Orchestrator`.
- Lifecycle decisions use an exact-name authority path and do not depend on the capped UI `PodList` rows.
- Panel close does not stop the Orchestrator.
- Orchestrator is not selected as the default foreground target when it is the only row or when another Pod can be selected.
- Bounded lifecycle/config diagnostics are displayed in the panel.
Validation after merge:
- `cargo test -p tui workspace_panel`
- `cargo test -p tui multi`
- `cargo test -p client ticket_role`
- `cargo test -p yoi panel`
- `cargo check --workspace --all-targets`
- `cargo fmt --check`
- `git diff --check HEAD~1..HEAD`
- `cargo build -p yoi`
- `target/debug/yoi ticket doctor`
- `nix build .#yoi --no-link --print-out-paths`
External review approved after one requested-changes cycle.
Known follow-up:
- Layout/display tuning remains intentionally deferred until the first end-to-end panel flow exists.
- Composer target switching and Intake handoff are covered by follow-up child tickets.
---

View File

@ -0,0 +1,93 @@
# Delegation intent: workspace panel Ticket action dispatch
## Classification
`implementation-ready` as the final first-pass panel slice before layout/display tuning.
The panel now supports `yoi panel`, Ticket/action display, Ticket-gated Orchestrator lifecycle, composer targets, and Intake -> Orchestrator handoff. Ticket action rows are still mostly display-only. This ticket should make the core human decision affordances actionable without creating a scheduler.
## Intent
Implement minimal safe Ticket action dispatch from selected workspace panel Ticket rows. The first pass should prioritize `Go` / Intake-approved routing and `Defer`; other displayed actions may be actionable if safe or remain explicitly disabled with a clear bounded diagnostic.
## Worktree / branch
- worktree: `/home/hare/Projects/yoi/.worktree/workspace-panel-ticket-action-dispatch`
- branch: `work/workspace-panel-ticket-action-dispatch`
This ticket may read tracked `.yoi/tickets` records/design artifacts. Do not read or edit `.yoi/memory/`.
## Requirements
- Replace blanket display-only Ticket action behavior with an action dispatcher for selected Ticket rows.
- Re-check current Ticket authority before mutation; do not mutate based only on stale `WorkspacePanelViewModel` data.
- Use Rust Ticket backend APIs / existing Ticket tool logic; do not shell out.
- Implement `Go` at minimum:
- record a Ticket decision/comment indicating human Go for Orchestrator routing/preflight;
- notify the workspace Orchestrator when live/reachable, if an existing peer/client path is available;
- report notification failure as bounded warning, not lost Ticket decision.
- Implement `Defer` where existing Ticket backend semantics make it safe, likely by moving/open->pending or recording a pending/defer decision.
- `Close` must not close without a resolution; either provide a safe explicit resolution path or show a clear diagnostic that close requires a resolution.
- `Review` should not silently approve; either route to existing review command/role flow or show a clear diagnostic guiding the user to inspect evidence/use review path.
- No-Ticket workspaces must expose no Ticket actions and preserve Pod-centric behavior.
- Preserve existing selected-Pod open/direct-send and composer target behavior.
- Keep UI layout changes minimal; final visual tuning comes later.
- Do not reintroduce `--multi`.
- Do not add scheduler/queue/automatic coder/reviewer spawning.
## Suggested action semantics
- `Go`: append a `decision` or equivalent Ticket thread entry such as `Panel Go: user authorized Orchestrator routing/preflight`, then attempt to notify Orchestrator with a concise message naming the Ticket id/slug and action. Do not start implementation directly.
- `Defer`: if backend status `pending` is the established defer state, move Ticket to pending and append a decision/comment explaining panel defer; otherwise append a decision only and leave status unchanged with a diagnostic.
- `Close`: require explicit resolution text; for this first slice, a disabled diagnostic is acceptable.
- `Review`: disabled/guided diagnostic is acceptable unless there is already a safe review UI path.
## Current code map
- `crates/tui/src/multi_pod.rs`
- Current row selection, Enter/send/open behavior, composer targets, actionbar notices.
- `crates/tui/src/workspace_panel.rs`
- `PanelRow`, `PanelRowKey`, `TicketPanelEntry`, `NextUserAction`, row derivation. Add stable action keys/data if needed.
- `crates/ticket/src/lib.rs`
- Ticket backend status/comment/review/close APIs.
- `crates/ticket/src/tool.rs`
- Existing tool behavior for Ticket mutations may be reusable/referenceable.
- `crates/client/src/ticket_role.rs`
- Existing Orchestrator/Intake launch context and role naming.
- Pod peer/client send helpers used by handoff/composer implementation.
## Validation
Run at least:
- targeted TUI tests for `Go` action success;
- targeted TUI tests for stale/invalid Ticket action rejection;
- targeted TUI tests for `Defer` behavior or explicit disabled reason;
- targeted tests that `Close` requires resolution / does not close silently;
- tests that no-Ticket workspace has no Ticket action dispatch;
- tests that Pod open/direct-send and composer Intake paths still work;
- `cargo test -p tui workspace_panel`;
- `cargo test -p tui multi_pod`;
- `cargo test -p ticket` if backend APIs change;
- `cargo test -p yoi panel`;
- `cargo check --workspace --all-targets`;
- `cargo fmt --check`;
- `git diff --check`;
- `cargo build -p yoi`;
- `target/debug/yoi ticket doctor`.
Run `nix build .#yoi --no-link` if feasible.
## Completion report
Report:
- worktree path / branch;
- commit hash;
- exact action semantics implemented for Go/Defer/Review/Close;
- how Ticket authority is re-checked;
- how Orchestrator notification is attempted/reported;
- proof no automatic implementation scheduling is introduced;
- tests updated/added;
- validation results;
- remaining layout/display tuning items.

View File

@ -0,0 +1,55 @@
---
id: 20260606-052903-workspace-panel-ticket-action-dispatch
slug: workspace-panel-ticket-action-dispatch
title: Workspace panel Ticket action dispatch
status: closed
kind: task
priority: P1
labels: [tui, ticket, orchestration, panel]
created_at: 2026-06-06T05:29:03Z
updated_at: 2026-06-06T06:04:40Z
assignee: null
legacy_ticket: null
---
## Background
The first workspace panel implementation now has `yoi panel`, Ticket/action rows, Ticket-gated Orchestrator lifecycle, composer targets, and Intake -> Orchestrator handoff.
Ticket rows currently expose Go/Review/Close/Defer-style actions as display affordances only. To complete the first end-to-end panel before layout/display tuning, the panel needs minimal action dispatch for the user decision points it already displays.
## Goal
Implement simple workspace panel Ticket action dispatch so selected Ticket/action rows can record the intended human decision through the Ticket backend and, where appropriate, notify the workspace Orchestrator.
## Requirements
- Keep the UI/action model thin; do not build a scheduler/queue or a second Ticket state machine.
- Implement minimal dispatch for visible Ticket actions where safe:
- `Go` / approve Intake-ready Ticket for Orchestrator routing/preflight;
- `Defer` / move or mark pending where existing Ticket backend semantics support it;
- `Review` / open or guide to existing review flow if full inline review is not safe in this first slice;
- `Close` / do not close without a resolution; show a bounded diagnostic or require explicit resolution path.
- Re-check Ticket authority immediately before mutation; do not mutate based only on stale `WorkspacePanelViewModel` data.
- Record actions in `.yoi/tickets` through Rust Ticket APIs / Ticket tools; do not shell out.
- If an Orchestrator is live/reachable and the action is a routing/Go signal, notify it through existing Pod/peer/client mechanisms where feasible.
- No-Ticket workspaces must remain Pod-centric and expose no Ticket actions.
- Preserve existing selected-Pod send/open behavior.
- Keep layout changes minimal; final visual tuning is a later pass.
## Non-goals
- Automatic implementation scheduling.
- Automatic coder/reviewer spawning.
- Full inline review editor.
- Final layout/display tuning.
- Reintroducing `--multi`.
## Acceptance criteria
- Ticket/action rows no longer merely say display-only for every action; at least Go/Defer are actionable or explicitly disabled with a clear reason.
- Action dispatch re-checks current Ticket state before writing.
- Ticket mutations use Rust backend APIs and are visible in `.yoi/tickets` thread/status state.
- Orchestrator notification is attempted for Go/routing actions when feasible and reported as success/warning.
- No-Ticket panel remains functionally equivalent to Pod-centric dashboard.
- Tests cover action dispatch success, stale/invalid action rejection, no-Ticket behavior, and Pod actions remaining intact.

View File

@ -0,0 +1,31 @@
Implemented workspace panel Ticket action dispatch.
Changes:
- Added dispatch for selected Ticket action rows with current authority re-checks.
- Mutation is gated by the same Ticket config availability logic used by panel display; absent/unusable config rejects before backend load/mutation.
- `Go` records a `decision` event authorizing Orchestrator routing/preflight, explicitly without starting implementation, and attempts to notify the workspace Orchestrator when reachable.
- `Defer` records a `decision` event and moves open Tickets to pending where applicable.
- `Review` does not silently approve/request changes; it emits a diagnostic requiring the explicit review flow.
- `Close` does not close without a resolution; it emits a diagnostic requiring an explicit resolution path.
- No-Ticket workspaces expose no Ticket action dispatch and stay Pod-centric.
- Existing selected-Pod open/direct-send and composer Intake paths are preserved.
- No scheduler/queue/automatic coder/reviewer spawning was introduced.
- `--multi` was not reintroduced.
Validation after merge:
- `cargo test -p tui workspace_panel`
- `cargo test -p tui multi_pod`
- `cargo test -p ticket`
- `cargo test -p yoi panel`
- `cargo check --workspace --all-targets`
- `cargo fmt --check`
- `git diff --check HEAD~1..HEAD`
- `cargo build -p yoi`
- `target/debug/yoi ticket doctor`
- `nix build .#yoi --no-link --print-out-paths`
External review approved after one requested-changes cycle.
Known follow-up:
- Final layout/display tuning remains the next pass.
- Close/review could later gain richer inline workflows, but the first pass is intentionally safe and non-destructive.

View File

@ -0,0 +1,88 @@
<!-- event: create author: yoi ticket at: 2026-06-06T05:29:03Z -->
## Created
Created by LocalTicketBackend create.
---
<!-- event: plan author: hare at: 2026-06-06T05:29:48Z -->
## Plan
Created this follow-up because the first panel slices now provide Ticket/action rows, Orchestrator lifecycle, Intake launch, and Intake handoff, but Ticket row actions are still mostly display affordances.
Before layout/display tuning, the panel should support a minimal safe action dispatch path for the human decision points it already displays, especially Go/Defer. The implementation should re-check Ticket authority before mutation, use Rust Ticket APIs, and notify Orchestrator for Go/routing actions when feasible.
---
<!-- event: plan author: hare at: 2026-06-06T05:30:26Z -->
## Plan
Preflight result: `implementation-ready` as the final first-pass panel slice before layout/display tuning.
The first panel slices now provide display, Orchestrator lifecycle, Intake launch, and handoff. This ticket should replace blanket display-only Ticket actions with minimal safe dispatch, especially Go and Defer, while keeping Review/Close safe if a full inline flow is not yet available.
Detailed delegation intent is recorded in `artifacts/delegation-intent.md`.
---
<!-- event: review author: hare at: 2026-06-06T06:04:40Z status: approve -->
## Review: approve
External reviewer approved current HEAD after requested changes were addressed.
Review summary:
- Ticket action dispatch now re-checks Ticket config availability before backend mutation, matching panel display gating.
- Stale/no-config action dispatch is rejected before mutation and covered by tests.
- Go records a decision and attempts Orchestrator notification without starting implementation.
- Defer safely records a decision and moves open Tickets to pending.
- Review and Close remain safe diagnostics rather than silent approval/closure.
- No-Ticket workspaces remain Pod-centric.
- Existing Pod open/direct-send and composer Intake behavior remain covered.
---
<!-- event: close author: hare at: 2026-06-06T06:04:40Z status: closed -->
## Closed
Implemented workspace panel Ticket action dispatch.
Changes:
- Added dispatch for selected Ticket action rows with current authority re-checks.
- Mutation is gated by the same Ticket config availability logic used by panel display; absent/unusable config rejects before backend load/mutation.
- `Go` records a `decision` event authorizing Orchestrator routing/preflight, explicitly without starting implementation, and attempts to notify the workspace Orchestrator when reachable.
- `Defer` records a `decision` event and moves open Tickets to pending where applicable.
- `Review` does not silently approve/request changes; it emits a diagnostic requiring the explicit review flow.
- `Close` does not close without a resolution; it emits a diagnostic requiring an explicit resolution path.
- No-Ticket workspaces expose no Ticket action dispatch and stay Pod-centric.
- Existing selected-Pod open/direct-send and composer Intake paths are preserved.
- No scheduler/queue/automatic coder/reviewer spawning was introduced.
- `--multi` was not reintroduced.
Validation after merge:
- `cargo test -p tui workspace_panel`
- `cargo test -p tui multi_pod`
- `cargo test -p ticket`
- `cargo test -p yoi panel`
- `cargo check --workspace --all-targets`
- `cargo fmt --check`
- `git diff --check HEAD~1..HEAD`
- `cargo build -p yoi`
- `target/debug/yoi ticket doctor`
- `nix build .#yoi --no-link --print-out-paths`
External review approved after one requested-changes cycle.
Known follow-up:
- Final layout/display tuning remains the next pass.
- Close/review could later gain richer inline workflows, but the first pass is intentionally safe and non-destructive.
---

View File

@ -7,7 +7,7 @@ kind: task
priority: P2 priority: P2
labels: [tui, composer, history, persistence] labels: [tui, composer, history, persistence]
created_at: 2026-06-01T02:11:04Z created_at: 2026-06-01T02:11:04Z
updated_at: 2026-06-01T02:11:04Z updated_at: 2026-06-05T23:01:38Z
assignee: null assignee: null
legacy_ticket: null legacy_ticket: null
--- ---
@ -20,28 +20,36 @@ TUI composer では上下キーで過去に送信・queue した入力を recall
## Storage decision ## Storage decision
永続化先は workspace 配下の `./.insomnia/` ではなく、ユーザー data dir既定では `~/.insomnia`、実装上は既存の data-dir 解決に従う)を使う方針とする。 永続化先は workspace 配下の `./.yoi/` ではなく、ユーザー data dir既定では `~/.yoi`、実装上は既存の data-dir 解決に従う)を使う方針とする。
保存 layout は、canonical workspace path / pwd から作る path-safe key を使って workspace ごとに分ける。デフォルト表示としては次のような形にする。
```text
~/.yoi/<path-to-pwd>/composer-history.json
```
実際の `<path-to-pwd>` は path separator や衝突を安全に扱える encoded key / stable key にしてよいが、metadata には元の workspace path / display name を持たせる。
理由: 理由:
- composer input history は個人の操作履歴であり、project-authored asset ではない。 - composer input history は個人の操作履歴であり、project-authored asset ではない。
- 入力には secret / private context が混ざり得るため、workspace に書くと git 追跡・共有・公開監査のリスクが上がる。 - 入力には secret / private context が混ざり得るため、workspace に書くと git 追跡・共有・公開監査のリスクが上がる。
- `./.insomnia/` は workflow / knowledge / manifest assets など project/workspace 側の明示的な資産に寄せ、生成された個人履歴は user data 側に置く方が境界が明確。 - `./.yoi/` は workflow / knowledge / Ticket records / manifest assets など project/workspace 側の明示的な資産に寄せ、生成された個人履歴は user data 側に置く方が境界が明確。
- 「どの workspace の履歴か」は、user data 側で workspace identitycanonical workspace root / git root などから作る stable keyと display metadata を持てば表現できる。 - 「どの workspace の履歴か」は、user data 側で workspace identitycanonical workspace root / git root などから作る stable keyと display metadata を持てば表現できる。
## Requirements ## Requirements
- 上下キーで呼び出す composer input history を、TUI 再起動後も利用できるよう永続化すること。 - 上下キーで呼び出す composer input history を、TUI 再起動後も利用できるよう永続化すること。
- 履歴は workspace ごとに分離すること。別 workspace の入力履歴が通常操作で混ざらないこと。 - 履歴は workspace ごとに分離すること。別 workspace の入力履歴が通常操作で混ざらないこと。
- 保存先は既存の user data dir 配下にすること。デフォルト表示としては `~/.insomnia` 相当だが、実装は data-dir override / path resolution に従うこと。 - 保存先は既存の user data dir 配下にすること。デフォルト表示としては `~/.yoi/<path-to-pwd>/composer-history.json` 相当だが、実装は data-dir override / path resolution に従うこと。
- `./.insomnia/` には composer history を作成しないこと。 - `./.yoi/` には composer history を作成しないこと。
- 保存 record は、どの workspace の履歴か判別できる metadata / key を持つこと。 - 保存 record は、どの workspace の履歴か判別できる metadata / key を持つこと。
- 既存の TUI-local / non-destructive recall semantics を維持すること。 - 既存の TUI-local / non-destructive recall semantics を維持すること。
- Pod protocol を変えない。 - Pod protocol を変えない。
- transcript / session history を mutate しない。 - transcript / session history を mutate しない。
- recalled input は、ユーザーが再送信するまでは conversation state に影響しない。 - recalled input は、ユーザーが再送信するまでは conversation state に影響しない。
- 入力は typed `Segment` vector として保存し、structured input の recall を壊さないこと。 - 入力は typed `Segment` vector として保存し、structured input の recall を壊さないこと。
- non-blank input のみ保存し、連続重複を抑止し、履歴件数は bounded にすること。既存挙動の 100 件 bound を尊重する - non-blank input のみ保存し、連続重複を抑止し、履歴件数は workspace ごとに最大 30 件程度に bound すること
- secret 値が混ざり得る前提で、保存内容を diagnostics / logs / tickets / tests snapshot / model context に不用意に出さないこと。 - secret 値が混ざり得る前提で、保存内容を diagnostics / logs / tickets / tests snapshot / model context に不用意に出さないこと。
- 破損した履歴ファイルがあっても TUI startup を致命的に壊さず、必要なら warning と空履歴 fallback にすること。 - 破損した履歴ファイルがあっても TUI startup を致命的に壊さず、必要なら warning と空履歴 fallback にすること。
- 既存の multiline cursor navigation、Up/Down browse、draft restore、edit-on-recall の挙動を regression させないこと。 - 既存の multiline cursor navigation、Up/Down browse、draft restore、edit-on-recall の挙動を regression させないこと。
@ -51,6 +59,7 @@ TUI composer では上下キーで過去に送信・queue した入力を recall
- 同じ workspace で TUI を再起動しても、以前送信した composer input を上下キーで recall できる。 - 同じ workspace で TUI を再起動しても、以前送信した composer input を上下キーで recall できる。
- 別 workspace では履歴が分離される。 - 別 workspace では履歴が分離される。
- workspace 配下に新しい composer history file が作られない。 - workspace 配下に新しい composer history file が作られない。
- user data dir 配下に workspace-scoped な composer history が保存される。 - user data dir 配下、既定では `~/.yoi/<path-to-pwd>/composer-history.json` 相当の workspace-scoped path に composer history が保存される。
- 保存件数は workspace ごとに最大 30 件程度に bound される。
- Pod session log / Worker history / transcript には、recall 操作だけでは何も追加されない。 - Pod session log / Worker history / transcript には、recall 操作だけでは何も追加されない。
- focused test または明確な手動確認手順で、永続化・workspace 分離・既存 recall semantics を検証できる。 - focused test または明確な手動確認手順で、永続化・workspace 分離・既存 recall semantics を検証できる。

View File

@ -4,4 +4,19 @@
Created by tickets.sh create. Created by tickets.sh create.
---
<!-- event: decision author: hare at: 2026-06-05T23:01:38Z -->
## Decision
Updated based on user direction:
- keep this as the existing `tui-composer-history-persistence` ticket rather than creating a duplicate;
- default user-data shape should be like `~/.yoi/<path-to-pwd>/composer-history.json` using a path-safe/stable workspace key;
- do not create composer history under workspace `./.yoi/`;
- bound persisted recall history to about 30 entries per workspace instead of the older 100-entry note;
- keep typed `Segment` storage, non-destructive recall semantics, and no Pod/session transcript mutation.
--- ---

View File

@ -1,34 +0,0 @@
<!-- event: create author: yoi ticket at: 2026-06-05T21:07:03Z -->
## Created
Created by LocalTicketBackend create.
---
<!-- event: plan author: hare at: 2026-06-05T21:09:19Z -->
## Plan
Plan: design the workspace orchestration panel before implementation.
The panel is not a `:ticket` command extension. It is a workspace-scoped orchestration surface with:
- Companion foreground management chat;
- Ticket Intake composer target that sends user input directly to Intake, not Companion history;
- background workspace Orchestrator restored/spawned as `<dir-name>-orchestrator`;
- Intake -> Orchestrator handoff;
- an action model prioritizing human-required Ticket/Intake decisions over raw Pod idle state.
Implementation is split into child tickets:
1. `workspace-orchestration-panel-design`
2. `workspace-panel-orchestrator-lifecycle`
3. `workspace-panel-composer-targets`
4. `ticket-intake-orchestrator-handoff`
5. `workspace-panel-action-model`
Existing `:ticket ...` commands remain as low-level fallback, not the main UX.
---

View File

@ -1,7 +0,0 @@
<!-- event: create author: yoi ticket at: 2026-06-05T21:07:04Z -->
## Created
Created by LocalTicketBackend create.
---

View File

@ -1,20 +0,0 @@
<!-- event: create author: yoi ticket at: 2026-06-05T21:07:04Z -->
## Created
Created by LocalTicketBackend create.
---
<!-- event: plan author: hare at: 2026-06-05T22:35:56Z -->
## Plan
Preflight result: `implementation-ready` as the first implementation slice after design approval.
Implementation should add a thin, testable workspace panel ViewModel/action model and integrate it enough into the current `--multi` dashboard to show Ticket/action rows above passive Pod rows. The model should be local-file-first from `.yoi/tickets/`, reuse existing Pod list data for background Pod state, avoid live socket I/O in the model layer, and leave final layout/display tuning to follow-up adjustments after the first end-to-end pass.
Detailed delegation intent is recorded in `artifacts/delegation-intent.md`.
---

View File

@ -1,7 +0,0 @@
<!-- event: create author: yoi ticket at: 2026-06-05T21:07:04Z -->
## Created
Created by LocalTicketBackend create.
---

View File

@ -1,7 +0,0 @@
<!-- event: create author: yoi ticket at: 2026-06-05T21:07:04Z -->
## Created
Created by LocalTicketBackend create.
---

View File

@ -0,0 +1,58 @@
---
id: 20260606-060548-workspace-panel-layout-display-tuning
slug: workspace-panel-layout-display-tuning
title: Workspace panel layout and display tuning
status: open
kind: task
priority: P2
labels: [tui, ticket, orchestration, panel, ux]
created_at: 2026-06-06T06:05:48Z
updated_at: 2026-06-06T06:06:30Z
assignee: null
legacy_ticket: null
---
## Background
The first-pass workspace panel implementation is complete. It provides `yoi panel`, Ticket-config-gated Ticket/action rows, no-Ticket Pod-centric fallback, background Orchestrator lifecycle, Companion vs Ticket Intake composer targets, Intake -> Orchestrator handoff, and minimal Ticket action dispatch.
The implementation intentionally kept layout and displayed content simple. The next pass should tune what the user sees and how the panel is organized while preserving the existing TUI visual language and the thin ViewModel/action-dispatch boundaries.
## Goal
Refine the workspace panel layout, labels, ordering, and displayed detail so the first-pass functionality is easy to understand and operate in real use.
## Requirements
- Follow existing TUI visual conventions/components.
- Preserve the single `yoi panel` route and no-Ticket Pod-centric behavior.
- Keep the UI intermediate representation thin; do not move authority into rendering code.
- Improve Ticket/action row labels, status markers, and key hints.
- Improve detail pane content for selected Ticket/action rows:
- current phase;
- next action;
- latest plan/report/review excerpt;
- related Pods;
- Orchestrator/handoff/action diagnostics.
- Improve composer target display so Companion vs Ticket Intake is obvious.
- Improve diagnostics/notices for Orchestrator lifecycle, Intake launch/handoff, and Ticket Go/Defer actions.
- Add or refine the phase/dependency/timeline view if it is useful in the first tuning pass.
- Keep Pod-centric no-Ticket panel functionally equivalent to the old multi-Pod dashboard.
- Avoid broad refactors or new scheduling behavior.
## Non-goals
- New scheduling/queue system.
- Automatic implementation authorization.
- Replacing the single-Pod TUI.
- Reintroducing `--multi`.
- Changing Ticket backend storage/config semantics.
## Acceptance criteria
- `yoi panel` remains functional in both Ticket-enabled and no-Ticket workspaces.
- The primary row list makes user-action-required items visually distinguishable from passive background Pods.
- The active composer target is unambiguous.
- Common diagnostics are concise and actionable.
- Layout/display tests or snapshot-like unit tests cover key row/detail rendering decisions where practical.
- Existing validation continues to pass.

View File

@ -0,0 +1,18 @@
<!-- event: create author: yoi ticket at: 2026-06-06T06:05:48Z -->
## Created
Created by LocalTicketBackend create.
---
<!-- event: plan author: hare at: 2026-06-06T06:06:30Z -->
## Plan
Created after closing the first-pass workspace orchestration panel implementation.
The first pass deliberately prioritized end-to-end behavior over visual/layout polish. This ticket owns the next tuning pass: row labels, key hints, detail pane content, composer target visibility, concise diagnostics, and optional phase/dependency/timeline display, while preserving existing TUI conventions and the thin ViewModel/action-dispatch boundaries.
---

1
Cargo.lock generated
View File

@ -3956,6 +3956,7 @@ dependencies = [
"serde_json", "serde_json",
"session-store", "session-store",
"tempfile", "tempfile",
"ticket",
"tokio", "tokio",
"toml", "toml",
"unicode-width", "unicode-width",

View File

@ -18,7 +18,8 @@ pub use runtime_command::PodRuntimeCommand;
pub use pod_client::PodClient; pub use pod_client::PodClient;
pub use spawn::{SpawnConfig, SpawnError, SpawnReady, spawn_pod}; pub use spawn::{SpawnConfig, SpawnError, SpawnReady, spawn_pod};
pub use ticket_role::{ pub use ticket_role::{
TicketRef, TicketRoleLaunchContext, TicketRoleLaunchError, TicketRoleLaunchPlan, TicketRef, TicketRoleLaunchContext, TicketRoleLaunchError, TicketRoleLaunchOptions,
TicketRoleLaunchResult, launch_ticket_role_pod, plan_ticket_role_launch, TicketRoleLaunchPlan, TicketRoleLaunchResult, TicketRolePreRunWarning, launch_ticket_role_pod,
launch_ticket_role_pod_with_options, plan_ticket_role_launch,
plan_ticket_role_launch_with_config, plan_ticket_role_launch_with_config,
}; };

View File

@ -18,6 +18,7 @@ use crate::{PodClient, PodRuntimeCommand, SpawnConfig, SpawnError, SpawnReady, s
const MAX_FIELD_CHARS: usize = 8_000; const MAX_FIELD_CHARS: usize = 8_000;
const MAX_POD_NAME_CHARS: usize = 80; const MAX_POD_NAME_CHARS: usize = 80;
const RUN_ACCEPTANCE_TIMEOUT: Duration = Duration::from_secs(10); const RUN_ACCEPTANCE_TIMEOUT: Duration = Duration::from_secs(10);
const PRE_RUN_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
/// Ticket identifier carried by a role launch request. /// Ticket identifier carried by a role launch request.
#[derive(Debug, Clone, PartialEq, Eq, Default)] #[derive(Debug, Clone, PartialEq, Eq, Default)]
@ -71,6 +72,31 @@ impl TicketRef {
} }
} }
/// Auditable panel handoff target included in a Ticket Intake launch.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TicketIntakeHandoff {
pub orchestrator_pod: String,
pub workspace_label: String,
}
impl TicketIntakeHandoff {
pub fn new(orchestrator_pod: impl Into<String>, workspace_label: impl Into<String>) -> Self {
Self {
orchestrator_pod: orchestrator_pod.into(),
workspace_label: workspace_label.into(),
}
}
fn append_prompt_lines(&self, out: &mut String) {
out.push_str("\nPanel handoff:\n");
push_bounded_bullet(out, "workspace", &self.workspace_label);
push_bounded_bullet(out, "workspace_orchestrator_pod", &self.orchestrator_pod);
out.push_str("- When Intake has clarified the request and created/updated the Ticket, notify/report readiness to this Orchestrator.\n");
out.push_str("- Handoff report fields: created_or_updated_ticket_id_or_slug, readiness, needs_preflight, risk_flags, user_go_required, intake_summary.\n");
out.push_str("- Do not start implementation automatically; wait for Orchestrator routing/preflight and human Go gates.\n");
}
}
/// Typed input for constructing a Ticket role launch. /// Typed input for constructing a Ticket role launch.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub struct TicketRoleLaunchContext { pub struct TicketRoleLaunchContext {
@ -79,6 +105,7 @@ pub struct TicketRoleLaunchContext {
pub pod_name: Option<String>, pub pod_name: Option<String>,
pub ticket: Option<TicketRef>, pub ticket: Option<TicketRef>,
pub user_instruction: Option<String>, pub user_instruction: Option<String>,
pub intake_handoff: Option<TicketIntakeHandoff>,
pub intent_packet: Option<String>, pub intent_packet: Option<String>,
pub worktree_path: Option<PathBuf>, pub worktree_path: Option<PathBuf>,
pub branch: Option<String>, pub branch: Option<String>,
@ -94,6 +121,7 @@ impl TicketRoleLaunchContext {
pod_name: None, pod_name: None,
ticket: None, ticket: None,
user_instruction: None, user_instruction: None,
intake_handoff: None,
intent_packet: None, intent_packet: None,
worktree_path: None, worktree_path: None,
branch: None, branch: None,
@ -145,6 +173,26 @@ impl TicketRoleLaunchPlan {
pub struct TicketRoleLaunchResult { pub struct TicketRoleLaunchResult {
pub plan: TicketRoleLaunchPlan, pub plan: TicketRoleLaunchPlan,
pub ready: SpawnReady, pub ready: SpawnReady,
pub pre_run_warnings: Vec<TicketRolePreRunWarning>,
}
/// Non-fatal diagnostic produced by bounded pre-run launch actions.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TicketRolePreRunWarning {
pub message: String,
}
/// Optional bounded actions executed after spawn readiness and before the first Run.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct TicketRoleLaunchOptions {
pub pre_run_peer_registrations: Vec<String>,
}
impl TicketRoleLaunchOptions {
pub fn with_pre_run_peer_registration(mut self, pod_name: impl Into<String>) -> Self {
self.pre_run_peer_registrations.push(pod_name.into());
self
}
} }
#[derive(Debug, Error)] #[derive(Debug, Error)]
@ -228,6 +276,26 @@ pub async fn launch_ticket_role_pod<F>(
runtime_command: PodRuntimeCommand, runtime_command: PodRuntimeCommand,
progress: F, progress: F,
) -> Result<TicketRoleLaunchResult, TicketRoleLaunchError> ) -> Result<TicketRoleLaunchResult, TicketRoleLaunchError>
where
F: FnMut(&str),
{
launch_ticket_role_pod_with_options(
context,
runtime_command,
progress,
TicketRoleLaunchOptions::default(),
)
.await
}
/// Spawn the Pod, run bounded pre-run launch options while it is still idle,
/// then send the first `Method::Run` input and wait for acceptance evidence.
pub async fn launch_ticket_role_pod_with_options<F>(
context: TicketRoleLaunchContext,
runtime_command: PodRuntimeCommand,
progress: F,
options: TicketRoleLaunchOptions,
) -> Result<TicketRoleLaunchResult, TicketRoleLaunchError>
where where
F: FnMut(&str), F: FnMut(&str),
{ {
@ -239,12 +307,95 @@ where
socket_path: ready.socket_path.clone(), socket_path: ready.socket_path.clone(),
source, source,
})?; })?;
let pre_run_warnings = run_pre_run_options_then_send_run(&mut client, &plan, &options).await?;
wait_for_run_acceptance(&mut client, &plan.run_segments, RUN_ACCEPTANCE_TIMEOUT).await?;
Ok(TicketRoleLaunchResult {
plan,
ready,
pre_run_warnings,
})
}
async fn run_pre_run_options_then_send_run(
client: &mut PodClient,
plan: &TicketRoleLaunchPlan,
options: &TicketRoleLaunchOptions,
) -> Result<Vec<TicketRolePreRunWarning>, TicketRoleLaunchError> {
let pre_run_warnings = perform_pre_run_peer_registrations(
client,
&options.pre_run_peer_registrations,
PRE_RUN_ACTION_TIMEOUT,
)
.await;
client client
.send(&plan.run_method()) .send(&plan.run_method())
.await .await
.map_err(|source| TicketRoleLaunchError::SendRun { source })?; .map_err(|source| TicketRoleLaunchError::SendRun { source })?;
wait_for_run_acceptance(&mut client, &plan.run_segments, RUN_ACCEPTANCE_TIMEOUT).await?; Ok(pre_run_warnings)
Ok(TicketRoleLaunchResult { plan, ready }) }
async fn perform_pre_run_peer_registrations(
client: &mut PodClient,
peer_names: &[String],
timeout: Duration,
) -> Vec<TicketRolePreRunWarning> {
let mut warnings = Vec::new();
for peer_name in peer_names {
if peer_name.trim().is_empty() {
warnings.push(TicketRolePreRunWarning {
message: "pre-run peer registration skipped: peer Pod name is empty".to_string(),
});
continue;
}
if let Err(message) = pre_run_register_peer(client, peer_name, timeout).await {
warnings.push(TicketRolePreRunWarning { message });
}
}
warnings
}
async fn pre_run_register_peer(
client: &mut PodClient,
peer_name: &str,
timeout: Duration,
) -> Result<(), String> {
if let Err(source) = client
.send(&Method::RegisterPeer {
name: peer_name.to_string(),
})
.await
{
return Err(format!(
"pre-run peer registration for {peer_name} failed while sending request: {source}"
));
}
let wait = async {
loop {
let Some(event) = client.next_event().await else {
return Err(format!(
"pre-run peer registration for {peer_name} failed: connection closed before response"
));
};
match event {
Event::PeerRegistered { .. } => return Ok(()),
Event::Error { code, message } => {
return Err(format!(
"pre-run peer registration for {peer_name} failed with {code:?}: {message}"
));
}
_ => {}
}
}
};
tokio::time::timeout(timeout, wait)
.await
.unwrap_or_else(|_| {
Err(format!(
"pre-run peer registration for {peer_name} timed out before first Run"
))
})
} }
async fn wait_for_run_acceptance( async fn wait_for_run_acceptance(
@ -309,6 +460,10 @@ fn build_launch_prompt(
None => out.push_str("\nUser/action instruction: not specified\n"), None => out.push_str("\nUser/action instruction: not specified\n"),
} }
if let Some(handoff) = &context.intake_handoff {
handoff.append_prompt_lines(&mut out);
}
if let Some(intent_packet) = non_empty(context.intent_packet.as_deref()) { if let Some(intent_packet) = non_empty(context.intent_packet.as_deref()) {
push_bounded_section(&mut out, "Intent packet", intent_packet); push_bounded_section(&mut out, "Intent packet", intent_packet);
} }
@ -431,7 +586,10 @@ fn non_empty(value: Option<&str>) -> Option<&str> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use protocol::{Greeting, PodStatus};
use tempfile::TempDir; use tempfile::TempDir;
use tokio::io::{AsyncBufReadExt, AsyncWrite, AsyncWriteExt, BufReader};
use tokio::net::UnixListener;
fn write_config(workspace: &std::path::Path, content: &str) { fn write_config(workspace: &std::path::Path, content: &str) {
let dir = workspace.join(".yoi"); let dir = workspace.join(".yoi");
@ -446,6 +604,152 @@ mod tests {
} }
} }
async fn write_test_event<W>(writer: &mut W, event: Event)
where
W: AsyncWrite + Unpin,
{
writer
.write_all(serde_json::to_string(&event).unwrap().as_bytes())
.await
.unwrap();
writer.write_all(b"\n").await.unwrap();
}
fn test_snapshot() -> Event {
Event::Snapshot {
entries: vec![],
greeting: Greeting {
pod_name: "ticket-intake".to_string(),
cwd: "/tmp".to_string(),
provider: "test".to_string(),
model: "test".to_string(),
scope_summary: "test".to_string(),
tools: vec![],
context_window: 0,
context_tokens: 0,
},
status: PodStatus::Idle,
}
}
fn test_launch_plan(workspace: &std::path::Path) -> TicketRoleLaunchPlan {
TicketRoleLaunchPlan {
workspace_root: workspace.to_path_buf(),
role: TicketRole::Intake,
pod_name: "ticket-intake".to_string(),
profile: "project:intake".to_string(),
workflow: "ticket-intake-workflow".to_string(),
launch_prompt_ref: None,
run_segments: vec![Segment::Text {
content: "intake request".to_string(),
}],
}
}
#[tokio::test]
async fn pre_run_peer_registration_is_sent_before_first_run_submission() {
let temp = TempDir::new().unwrap();
let socket_path = temp.path().join("pod.sock");
let listener = UnixListener::bind(&socket_path).unwrap();
let server = tokio::spawn(async move {
let (stream, _) = listener.accept().await.unwrap();
let (reader, mut writer) = stream.into_split();
write_test_event(&mut writer, test_snapshot()).await;
let mut reader = BufReader::new(reader);
let mut first = String::new();
reader.read_line(&mut first).await.unwrap();
match serde_json::from_str::<Method>(&first).unwrap() {
Method::RegisterPeer { name } => assert_eq!(name, "workspace-orchestrator"),
method => panic!("expected RegisterPeer before Run, got {method:?}"),
}
write_test_event(
&mut writer,
Event::PeerRegistered {
result: serde_json::json!({"peer": "workspace-orchestrator"}),
},
)
.await;
let mut second = String::new();
reader.read_line(&mut second).await.unwrap();
match serde_json::from_str::<Method>(&second).unwrap() {
Method::Run { input } => {
assert_eq!(
input,
test_launch_plan(std::path::Path::new("/tmp")).run_segments
)
}
method => panic!("expected Run after pre-run RegisterPeer, got {method:?}"),
}
});
let mut client = PodClient::connect(&socket_path).await.unwrap();
let options = TicketRoleLaunchOptions::default()
.with_pre_run_peer_registration("workspace-orchestrator");
let warnings = run_pre_run_options_then_send_run(
&mut client,
&test_launch_plan(std::path::Path::new("/tmp")),
&options,
)
.await
.unwrap();
server.await.unwrap();
assert!(warnings.is_empty());
}
#[tokio::test]
async fn pre_run_peer_registration_failure_warns_but_still_sends_run() {
let temp = TempDir::new().unwrap();
let socket_path = temp.path().join("pod.sock");
let listener = UnixListener::bind(&socket_path).unwrap();
let server = tokio::spawn(async move {
let (stream, _) = listener.accept().await.unwrap();
let (reader, mut writer) = stream.into_split();
write_test_event(&mut writer, test_snapshot()).await;
let mut reader = BufReader::new(reader);
let mut first = String::new();
reader.read_line(&mut first).await.unwrap();
assert!(matches!(
serde_json::from_str::<Method>(&first).unwrap(),
Method::RegisterPeer { .. }
));
write_test_event(
&mut writer,
Event::Error {
code: protocol::ErrorCode::InvalidRequest,
message: "peer metadata unavailable".to_string(),
},
)
.await;
let mut second = String::new();
reader.read_line(&mut second).await.unwrap();
assert!(matches!(
serde_json::from_str::<Method>(&second).unwrap(),
Method::Run { .. }
));
});
let mut client = PodClient::connect(&socket_path).await.unwrap();
let options = TicketRoleLaunchOptions::default()
.with_pre_run_peer_registration("workspace-orchestrator");
let warnings = run_pre_run_options_then_send_run(
&mut client,
&test_launch_plan(std::path::Path::new("/tmp")),
&options,
)
.await
.unwrap();
server.await.unwrap();
assert_eq!(warnings.len(), 1);
assert!(warnings[0].message.contains("InvalidRequest"));
assert!(warnings[0].message.contains("workspace-orchestrator"));
}
#[test] #[test]
fn default_config_role_launch_plan_uses_defaults() { fn default_config_role_launch_plan_uses_defaults() {
let temp = TempDir::new().unwrap(); let temp = TempDir::new().unwrap();
@ -534,6 +838,20 @@ workflow = "ticket-review-workflow"
assert!(intake_text.contains("Clarify and materialize")); assert!(intake_text.contains("Clarify and materialize"));
assert!(intake_text.contains("Workflow: ticket-intake-workflow")); assert!(intake_text.contains("Workflow: ticket-intake-workflow"));
let mut handoff_intake = TicketRoleLaunchContext::new(temp.path(), TicketRole::Intake);
handoff_intake.intake_handoff = Some(TicketIntakeHandoff::new(
"panel-orchestrator-demo",
"Demo workspace",
));
let handoff_plan = plan_ticket_role_launch(handoff_intake).unwrap();
let handoff_text = text_segment(&handoff_plan);
assert!(handoff_text.contains("Panel handoff:"));
assert!(handoff_text.contains("workspace_orchestrator_pod: panel-orchestrator-demo"));
assert!(handoff_text.contains("workspace: Demo workspace"));
assert!(handoff_text.contains("created_or_updated_ticket_id_or_slug"));
assert!(handoff_text.contains("Do not start implementation automatically"));
assert!(handoff_text.contains("human Go gates"));
let mut orchestrator = TicketRoleLaunchContext::new(temp.path(), TicketRole::Orchestrator); let mut orchestrator = TicketRoleLaunchContext::new(temp.path(), TicketRole::Orchestrator);
orchestrator.ticket = Some(TicketRef::slug("launcher")); orchestrator.ticket = Some(TicketRef::slug("launcher"));
orchestrator.intent_packet = Some("Route to implementation after preflight.".into()); orchestrator.intent_packet = Some("Route to implementation after preflight.".into());

View File

@ -19,6 +19,7 @@ secrets = { workspace = true }
session-store = { workspace = true } session-store = { workspace = true }
pod-store = { workspace = true } pod-store = { workspace = true }
pod-registry = { workspace = true } pod-registry = { workspace = true }
ticket = { workspace = true }
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }
pulldown-cmark = { version = "0.13.3", default-features = false } pulldown-cmark = { version = "0.13.3", default-features = false }
llm-worker.workspace = true llm-worker.workspace = true

View File

@ -15,6 +15,7 @@ mod task;
mod tool; mod tool;
mod ui; mod ui;
mod view_mode; mod view_mode;
mod workspace_panel;
use std::io; use std::io;
use std::path::PathBuf; use std::path::PathBuf;
@ -51,10 +52,8 @@ pub enum LaunchMode {
/// `yoi --session <UUID>`: skip the picker, go straight to the /// `yoi --session <UUID>`: skip the picker, go straight to the
/// resume name dialog with `id` baked in. /// resume name dialog with `id` baked in.
ResumeWithSession(SegmentId), ResumeWithSession(SegmentId),
/// `yoi --multi`: open the multi-Pod dashboard. This is intentionally /// `yoi panel`: open the workspace panel from the current workspace.
/// separate from `-r`/`--resume`, which keeps its single-Pod picker Panel,
/// meaning.
Multi,
} }
pub async fn launch(options: LaunchOptions) -> ExitCode { pub async fn launch(options: LaunchOptions) -> ExitCode {
@ -85,7 +84,7 @@ pub async fn launch(options: LaunchOptions) -> ExitCode {
LaunchMode::ResumeWithSession(id) => { LaunchMode::ResumeWithSession(id) => {
single_pod::run_spawn(Some(id), None, runtime_command).await single_pod::run_spawn(Some(id), None, runtime_command).await
} }
LaunchMode::Multi => single_pod::run_multi(runtime_command).await, LaunchMode::Panel => single_pod::run_panel(runtime_command).await,
}; };
// Always restore the terminal first so any pending eprintln below // Always restore the terminal first so any pending eprintln below

File diff suppressed because it is too large Load Diff

View File

@ -184,10 +184,10 @@ pub(crate) async fn run_resume(
run_pod_name(pod_name, socket_override, runtime_command).await run_pod_name(pod_name, socket_override, runtime_command).await
} }
pub(crate) async fn run_multi( pub(crate) async fn run_panel(
runtime_command: PodRuntimeCommand, runtime_command: PodRuntimeCommand,
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<(), Box<dyn std::error::Error>> {
let mut app = multi_pod::load_app().await?; let mut app = multi_pod::load_app(runtime_command.clone()).await?;
let mut terminal = enter_fullscreen()?; let mut terminal = enter_fullscreen()?;
loop { loop {

File diff suppressed because it is too large Load Diff

View File

@ -118,6 +118,12 @@ fn parse_args_slice(args: &[String]) -> Result<Mode, ParseError> {
ticket_cli::parse_ticket_args(&args[1..]).map_err(|e| ParseError(e.to_string()))?; ticket_cli::parse_ticket_args(&args[1..]).map_err(|e| ParseError(e.to_string()))?;
return Ok(Mode::Ticket(ticket_cli)); return Ok(Mode::Ticket(ticket_cli));
} }
"panel" => {
if args.len() != 1 {
return Err(ParseError("yoi panel does not accept arguments".into()));
}
return Ok(Mode::Tui(LaunchMode::Panel));
}
"keys" => { "keys" => {
if args.len() != 1 { if args.len() != 1 {
return Err(ParseError("yoi keys does not accept arguments".into())); return Err(ParseError("yoi keys does not accept arguments".into()));
@ -147,7 +153,6 @@ fn parse_args_slice(args: &[String]) -> Result<Mode, ParseError> {
let mut pod_name = None; let mut pod_name = None;
let mut socket_override = None; let mut socket_override = None;
let mut profile = None; let mut profile = None;
let mut multi = false;
let mut positional = None; let mut positional = None;
let mut i = 0; let mut i = 0;
@ -158,10 +163,6 @@ fn parse_args_slice(args: &[String]) -> Result<Mode, ParseError> {
resume = true; resume = true;
i += 1; i += 1;
} }
"--multi" => {
multi = true;
i += 1;
}
"--session" => { "--session" => {
let value = args let value = args
.get(i + 1) .get(i + 1)
@ -256,38 +257,12 @@ fn parse_args_slice(args: &[String]) -> Result<Mode, ParseError> {
|| session.is_some() || session.is_some()
|| pod_name.is_some() || pod_name.is_some()
|| positional.is_some() || positional.is_some()
|| socket_override.is_some() || socket_override.is_some())
|| multi)
{ {
return Err(ParseError( return Err(ParseError(
"--profile can only be used for fresh spawn".to_string(), "--profile can only be used for fresh spawn".to_string(),
)); ));
} }
if multi && resume {
return Err(ParseError(
"--multi and --resume are mutually exclusive".to_string(),
));
}
if multi && session.is_some() {
return Err(ParseError(
"--multi and --session are mutually exclusive".to_string(),
));
}
if multi && pod_name.is_some() {
return Err(ParseError(
"--multi and --pod are mutually exclusive".to_string(),
));
}
if multi && positional.is_some() {
return Err(ParseError(
"--multi cannot be used with a positional Pod name".to_string(),
));
}
if multi && socket_override.is_some() {
return Err(ParseError(
"--multi and --socket are mutually exclusive".to_string(),
));
}
if pod_name.is_some() && session.is_some() { if pod_name.is_some() && session.is_some() {
return Err(ParseError( return Err(ParseError(
"--pod and --session are mutually exclusive".to_string(), "--pod and --session are mutually exclusive".to_string(),
@ -314,9 +289,6 @@ fn parse_args_slice(args: &[String]) -> Result<Mode, ParseError> {
)); ));
} }
if multi {
return Ok(Mode::Tui(LaunchMode::Multi));
}
let pod_name = pod_name.or(positional); let pod_name = pod_name.or(positional);
if let Some(pod_name) = pod_name { if let Some(pod_name) = pod_name {
return Ok(Mode::Tui(LaunchMode::PodName { return Ok(Mode::Tui(LaunchMode::PodName {
@ -342,7 +314,7 @@ fn parse_session_id(value: &str) -> Result<SegmentId, ParseError> {
fn print_help() { fn print_help() {
println!( println!(
"yoi\n\nUsage:\n yoi [OPTIONS] [POD_NAME]\n yoi keys\n yoi pod [POD_OPTIONS]\n yoi ticket <COMMAND> [OPTIONS]\n yoi memory lint [OPTIONS]\n\nOptions:\n -r, --resume Open the Pod picker and resume/attach a Pod\n --multi Open the multi-Pod dashboard\n --pod <NAME> Attach/restore/create a Pod by name\n --socket <PATH> Attach to a specific Pod socket with --pod\n --session <UUID> Resume a specific session segment\n --profile <REF> Start a fresh Pod from a profile\n -h, --help Print help\n" "yoi\n\nUsage:\n yoi [OPTIONS] [POD_NAME]\n yoi panel\n yoi keys\n yoi pod [POD_OPTIONS]\n yoi ticket <COMMAND> [OPTIONS]\n yoi memory lint [OPTIONS]\n\nOptions:\n -r, --resume Open the Pod picker and resume/attach a Pod\n --pod <NAME> Attach/restore/create a Pod by name\n --socket <PATH> Attach to a specific Pod socket with --pod\n --session <UUID> Resume a specific session segment\n --profile <REF> Start a fresh Pod from a profile\n -h, --help Print help\n"
); );
} }
@ -581,13 +553,19 @@ mod tests {
} }
#[test] #[test]
fn parse_multi_mode() { fn parse_panel_mode() {
match parse_args_from(["--multi"]).unwrap() { match parse_args_from(["panel"]).unwrap() {
Mode::Tui(LaunchMode::Multi) => {} Mode::Tui(LaunchMode::Panel) => {}
_ => panic!("expected Multi mode"), _ => panic!("expected Panel mode"),
} }
} }
#[test]
fn parse_multi_flag_is_not_a_launch_alias() {
let err = parse_args_from(["--multi"]).unwrap_err();
assert_eq!(err.to_string(), "unknown argument: --multi");
}
#[test] #[test]
fn parse_top_level_help() { fn parse_top_level_help() {
match parse_args_from(["--help"]).unwrap() { match parse_args_from(["--help"]).unwrap() {
@ -603,44 +581,4 @@ mod tests {
_ => panic!("expected MemoryLintHelp mode"), _ => panic!("expected MemoryLintHelp mode"),
} }
} }
#[test]
fn parse_multi_conflicts_are_clear() {
let segment_id = session_store::new_segment_id().to_string();
let cases = [
(
vec!["--multi".to_string(), "--resume".to_string()],
"--multi and --resume are mutually exclusive",
),
(
vec!["--multi".to_string(), "--session".to_string(), segment_id],
"--multi and --session are mutually exclusive",
),
(
vec![
"--multi".to_string(),
"--pod".to_string(),
"agent".to_string(),
],
"--multi and --pod are mutually exclusive",
),
(
vec!["--multi".to_string(), "agent".to_string()],
"--multi cannot be used with a positional Pod name",
),
(
vec![
"--multi".to_string(),
"--socket".to_string(),
"/tmp/a.sock".to_string(),
],
"--multi and --socket are mutually exclusive",
),
];
for (args, message) in cases {
let err = parse_args_from(args).unwrap_err();
assert_eq!(err.to_string(), message);
}
}
} }

View File

@ -40,7 +40,7 @@ rustPlatform.buildRustPackage rec {
filter = sourceFilter; filter = sourceFilter;
}; };
cargoHash = "sha256-+eIKCBT0NR8OJn8IxuJl2nc7M6OxlPQ+9RHncSz9K2M="; cargoHash = "sha256-aG07L64sHxGKYou7dzuNuYt6xoHjIgGhlsnI5kxGmUg=";
depsExtraArgs = { depsExtraArgs = {
# Older fetchCargoVendor utilities used crates.io's API download endpoint, # Older fetchCargoVendor utilities used crates.io's API download endpoint,