Merge branch 'orchestration/yoi-orchestrator' into ticket/orchestrator-progress-companion-notify
This commit is contained in:
commit
724b79f1c0
|
|
@ -0,0 +1 @@
|
||||||
|
{"id":"orch-plan-20260612-145604-1","ticket_id":"00001KTJXS31R","kind":"waiting_capacity_note","note":"Queue review 2026-06-12: leave queued for now because three active in-progress implementation branches are already delegated (`00001KTVJFT6F` Panel focus, `00001KTTW04W2` Companion progress notification, `00001KTVJGC0Y` Ticket language guidance). This Ticket's re-kick / active work-set scope overlaps conceptually and likely in code with Panel lifecycle / Companion progress notification and has duplicate-start / scheduler-boundary risk. Reconsider after at least the Panel/Companion-notify active work is merged or blocked, so implementation can validate active_inprogress suppression against current behavior.","author":"orchestrator","at":"2026-06-12T14:56:04Z"}
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
title: "Orchestrator Idle 時の queued Ticket 見落としを防ぐ"
|
title: "Orchestrator Idle 時の queued Ticket 見落としを防ぐ"
|
||||||
state: 'queued'
|
state: 'queued'
|
||||||
created_at: "2026-06-08T06:12:35Z"
|
created_at: "2026-06-08T06:12:35Z"
|
||||||
updated_at: '2026-06-12T14:49:40Z'
|
updated_at: '2026-06-12T14:56:17Z'
|
||||||
queued_by: 'workspace-panel'
|
queued_by: 'workspace-panel'
|
||||||
queued_at: '2026-06-12T14:49:40Z'
|
queued_at: '2026-06-12T14:49:40Z'
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -126,4 +126,26 @@ Intake refinement により、既存の plan store 実装との差分、current
|
||||||
Ticket を `workspace-panel` が queued にしました。
|
Ticket を `workspace-panel` が queued にしました。
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: decision author: orchestrator at: 2026-06-12T14:56:17Z -->
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
Routing decision: leave queued for now (`waiting_capacity_note`)
|
||||||
|
|
||||||
|
Reason:
|
||||||
|
- This queue review already accepted and delegated three in-progress branches: `00001KTVJFT6F` (Panel focus model), `00001KTTW04W2` (Companion progress notification), and `00001KTVJGC0Y` (Ticket language guidance).
|
||||||
|
- This Ticket concerns Orchestrator idle queued re-kick / active work-set behavior and overlaps conceptually with the active Companion progress notification and Panel lifecycle work. Starting it now would raise duplicate-start / scheduler-boundary / integration-conflict risk rather than safe independent parallelism.
|
||||||
|
- Relation check: dependency relation target `00001KTG3MDFG` is closed, so this is not blocked by an unresolved relation; the current stop reason is capacity/conflict with active work.
|
||||||
|
|
||||||
|
Evidence checked:
|
||||||
|
- Ticket body/thread: active in-progress suppression, clean workspace gate, no queue-drain loop, active-work-set snapshot requirements, and escalation conditions.
|
||||||
|
- TicketRelationQuery: two relation records checked; `depends_on` target `00001KTG3MDFG` is closed.
|
||||||
|
- TicketOrchestrationPlanQuery: no prior records; added `waiting_capacity_note` `orch-plan-20260612-145604-1`.
|
||||||
|
- Workspace/Pod state: Orchestrator worktree clean; active sibling coder Pods are running for Panel focus, Companion progress notification, and Ticket language guidance.
|
||||||
|
|
||||||
|
Next action:
|
||||||
|
- Keep `queued` and reconsider after at least Panel focus / Companion progress notification are merged or blocked, so this Ticket can validate against the current Panel/notification behavior without duplicate scheduler semantics.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
title: 'Orchestrator進捗をAutoKickなしでCompanionへ通知する'
|
title: 'Orchestrator進捗をAutoKickなしでCompanionへ通知する'
|
||||||
state: 'inprogress'
|
state: 'inprogress'
|
||||||
created_at: '2026-06-11T08:15:24Z'
|
created_at: '2026-06-11T08:15:24Z'
|
||||||
updated_at: '2026-06-12T14:51:03Z'
|
updated_at: '2026-06-12T14:52:34Z'
|
||||||
assignee: null
|
assignee: null
|
||||||
queued_by: 'workspace-panel'
|
queued_by: 'workspace-panel'
|
||||||
queued_at: '2026-06-11T10:31:56Z'
|
queued_at: '2026-06-11T10:31:56Z'
|
||||||
|
|
|
||||||
|
|
@ -139,3 +139,35 @@ Decision:
|
||||||
Explicit user follow-up authorized starting the previously accepted implementation plan. Ticket body/thread, relation blockers, accepted orchestration plan, current Orchestrator workspace state, and visible active work were rechecked. No unresolved blocker or missing planning decision remains. Implementation side effects will start only after this accepted `queued -> inprogress` transition is recorded.
|
Explicit user follow-up authorized starting the previously accepted implementation plan. Ticket body/thread, relation blockers, accepted orchestration plan, current Orchestrator workspace state, and visible active work were rechecked. No unresolved blocker or missing planning decision remains. Implementation side effects will start only after this accepted `queued -> inprogress` transition is recorded.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<!-- event: plan author: orchestrator at: 2026-06-12T14:51:31Z -->
|
||||||
|
|
||||||
|
## Plan
|
||||||
|
|
||||||
|
Implementation worktree created for multi-agent handoff.
|
||||||
|
|
||||||
|
- Ticket: `00001KTTW04W2`
|
||||||
|
- Branch: `ticket/orchestrator-progress-companion-notify`
|
||||||
|
- Worktree: `/home/hare/Projects/yoi/.worktree/orchestrator-progress-companion-notify`
|
||||||
|
- Base: Orchestrator workspace HEAD `05fe1f6f` (`orchestration/yoi-orchestrator`), including the accepted routing record and `queued -> inprogress` transition.
|
||||||
|
- Concurrent work note: `00001KTVJFT6F` is active in `/home/hare/Projects/yoi/.worktree/panel-focus-composer-row-selection`; any Panel UI edits should be narrow and integration will recheck conflict risk.
|
||||||
|
- Scope rule: implementation work happens only in this child worktree. Root/original workspace must not be read, written, validated, merged, cleaned, or used for git operations.
|
||||||
|
- Next: spawn sibling coder with narrow write scope to the implementation worktree. Reviewer will be started after coder evidence is available.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: plan author: orchestrator at: 2026-06-12T14:52:34Z -->
|
||||||
|
|
||||||
|
## Plan
|
||||||
|
|
||||||
|
Coder delegated.
|
||||||
|
|
||||||
|
- Coder Pod: `yoi-coder-companion-progress-notify`
|
||||||
|
- Worktree: `/home/hare/Projects/yoi/.worktree/orchestrator-progress-companion-notify`
|
||||||
|
- Branch: `ticket/orchestrator-progress-companion-notify`
|
||||||
|
- Scope: write `/home/hare/Projects/yoi/.worktree/orchestrator-progress-companion-notify`; read child worktree plus minimal non-recursive runtime-root read required by spawn validation.
|
||||||
|
- Task: implement `Notify { auto_run: false }` weak progress notification semantics, bounded Orchestrator progress summary delivery to live/reachable Companion, missing/stopped no spawn/restore behavior, Panel freshness/last-updated indication, focused tests, and required validation.
|
||||||
|
- Concurrent work note: `00001KTVJFT6F` has active coder work in another worktree and may edit Panel UI; coder was instructed to keep Panel changes narrow and report overlap if it becomes a blocker.
|
||||||
|
- Constraint: coder was instructed not to use root/original workspace for decision evidence or git/validation/cleanup, and not to create generated memory/local/runtime/log/lock/secret-like `.yoi` paths.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
---
|
---
|
||||||
title: 'Workspace panel の focus model を composer target と row selection に整理する'
|
title: 'Workspace panel の focus model を composer target と row selection に整理する'
|
||||||
state: 'inprogress'
|
state: 'closed'
|
||||||
created_at: '2026-06-11T14:48:26Z'
|
created_at: '2026-06-11T14:48:26Z'
|
||||||
updated_at: '2026-06-12T14:46:41Z'
|
updated_at: '2026-06-12T15:09:15Z'
|
||||||
assignee: null
|
assignee: null
|
||||||
readiness: 'implementation_ready'
|
readiness: 'implementation_ready'
|
||||||
risk_flags: ['tui-ux', 'input-safety']
|
risk_flags: ['tui-ux', 'input-safety']
|
||||||
|
|
|
||||||
38
.yoi/tickets/00001KTVJFT6F/resolution.md
Normal file
38
.yoi/tickets/00001KTVJFT6F/resolution.md
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
Workspace panel の focus model を composer target と row selection に整理した。
|
||||||
|
|
||||||
|
実装概要:
|
||||||
|
- `yoi panel` の user-visible focus 表示から `item action focus` / `Right action focus` / `global composer` / `PanelFocus` / `ItemAction` 系の不要な focus model を除去した。
|
||||||
|
- composer target は送信先、row selection は空 composer 時の navigation / Enter 対象として扱う表示・挙動へ整理した。
|
||||||
|
- 非空 composer では composer draft / target を優先し、`Enter` は composer send / Intake 起動に向く。
|
||||||
|
- 空 composer では selected row が `Enter` 対象になり、既存 Ticket action dispatch / Pod open 経路を使う。
|
||||||
|
- `Tab` は composer target の切替のみで selected row と draft を保持する。
|
||||||
|
- `Esc` は row selection を解除し、composer draft と target は保持する。
|
||||||
|
- `Left` / `Right` は Panel focus 切替ではなく composer cursor 操作として扱う。
|
||||||
|
- Ticket action dispatch、Pod open、Intake launch、Companion send の authority / safety semantics は維持した。
|
||||||
|
|
||||||
|
Review / integration:
|
||||||
|
- Implementation commit: `c5ef6f79 tui: clarify panel composer target and row selection`
|
||||||
|
- Reviewer: `yoi-reviewer-panel-focus-model` が approve。
|
||||||
|
- Orchestrator merge commit: `d6166c72 merge: panel focus composer row selection`
|
||||||
|
- Ticket completion commit: `e330685e ticket: mark panel focus done`
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
- `cargo test -p tui selected_ticket_row_with_non_empty_composer_shows_composer_enter_behavior`: pass
|
||||||
|
- `cargo test -p tui multi_esc_clears_row_selection_without_quitting_and_preserves_draft`: pass
|
||||||
|
- `cargo test -p tui multi_composer_target_switch_preserves_typed_text`: pass
|
||||||
|
- `cargo test -p tui multi_blank_ticket_intake_enter_uses_selected_row_and_preserves_input`: pass
|
||||||
|
- `cargo fmt --check`: pass
|
||||||
|
- `git diff --check HEAD~1..HEAD`: pass
|
||||||
|
- `./result/bin/yoi ticket doctor`: `doctor: ok`
|
||||||
|
- `nix build .#yoi`: pass
|
||||||
|
|
||||||
|
Known unrelated validation failure:
|
||||||
|
- `cargo test -p tui multi_ --lib` still includes pre-existing failure `multi_pod::tests::orchestrator_launch_context_uses_orchestration_root_for_runtime_workspace`; this was verified on the Orchestrator branch before merge and reviewed as unrelated to this Ticket.
|
||||||
|
|
||||||
|
Cleanup:
|
||||||
|
- coder/reviewer Pods stopped。
|
||||||
|
- child worktree `/home/hare/Projects/yoi/.worktree/panel-focus-composer-row-selection` removed。
|
||||||
|
- branch `ticket/panel-focus-composer-row-selection` deleted。
|
||||||
|
|
||||||
|
Non-blocking risks:
|
||||||
|
- Reviewer found none for this Ticket.
|
||||||
|
|
@ -149,3 +149,216 @@ Coder delegated.
|
||||||
- Constraint: coder was instructed not to use root/original workspace for decision evidence or git/validation/cleanup, and not to create generated memory/local/runtime/log/lock/secret-like `.yoi` paths.
|
- Constraint: coder was instructed not to use root/original workspace for decision evidence or git/validation/cleanup, and not to create generated memory/local/runtime/log/lock/secret-like `.yoi` paths.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<!-- event: implementation_report author: orchestrator at: 2026-06-12T15:01:02Z -->
|
||||||
|
|
||||||
|
## Implementation report
|
||||||
|
|
||||||
|
Coder completed implementation.
|
||||||
|
|
||||||
|
Coder Pod:
|
||||||
|
- `yoi-coder-panel-focus-model`
|
||||||
|
|
||||||
|
Implementation branch/worktree:
|
||||||
|
- Branch: `ticket/panel-focus-composer-row-selection`
|
||||||
|
- Worktree: `/home/hare/Projects/yoi/.worktree/panel-focus-composer-row-selection`
|
||||||
|
- Commit: `c5ef6f79 tui: clarify panel composer target and row selection`
|
||||||
|
- Worktree status: clean
|
||||||
|
|
||||||
|
Changed surfaces:
|
||||||
|
- `crates/tui/src/multi_pod.rs`
|
||||||
|
- `crates/tui/src/workspace_panel.rs`
|
||||||
|
|
||||||
|
Implementation summary:
|
||||||
|
- Removed user-visible `PanelFocus` / `ItemAction` style focus model.
|
||||||
|
- `Tab` switches composer target without clearing selected row.
|
||||||
|
- `Esc` clears row selection while preserving composer draft and target.
|
||||||
|
- Non-empty composer prioritizes draft/text input and `Enter` targets composer send.
|
||||||
|
- Blank composer uses selected row as `Enter` target for Ticket action / Pod open.
|
||||||
|
- Title/status/actionbar/row hints no longer expose `global composer`, `item action`, or `Right action focus` as focus states.
|
||||||
|
- `Left` / `Right` are presented as composer cursor movement, not Panel focus switching.
|
||||||
|
|
||||||
|
Orchestrator-verified evidence from child worktree:
|
||||||
|
- `git status --short --branch`: clean on `ticket/panel-focus-composer-row-selection`
|
||||||
|
- Focused changed tests passed individually:
|
||||||
|
- `cargo test -p tui selected_ticket_row_with_non_empty_composer_shows_composer_enter_behavior`
|
||||||
|
- `cargo test -p tui multi_esc_clears_row_selection_without_quitting_and_preserves_draft`
|
||||||
|
- `cargo test -p tui multi_composer_target_switch_preserves_typed_text`
|
||||||
|
- `cargo test -p tui multi_blank_ticket_intake_enter_uses_selected_row_and_preserves_input`
|
||||||
|
- `cargo fmt --check`: pass
|
||||||
|
- `git diff --check`: pass
|
||||||
|
- `cargo run -p yoi -- ticket doctor`: `doctor: ok`
|
||||||
|
- `nix build .#yoi`: pass
|
||||||
|
|
||||||
|
Known unrelated validation failure:
|
||||||
|
- `cargo test -p tui multi_ --lib` fails one test: `multi_pod::tests::orchestrator_launch_context_uses_orchestration_root_for_runtime_workspace`.
|
||||||
|
- The same focused failing test also fails on current Orchestrator branch before this implementation is merged, with `context.workspace_root` `/repo/yoi` vs expected `/repo/yoi/.worktree/orchestration/yoi-orchestrator`.
|
||||||
|
- The failure is not in the changed focus/composer/row-selection path, but reviewer should still confirm it is unrelated and not introduced by this Ticket.
|
||||||
|
|
||||||
|
Next:
|
||||||
|
- Spawn read-only sibling reviewer against the recorded intent, binding decisions/invariants, acceptance criteria, commit `c5ef6f79`, diff, focused validation evidence, `nix build` evidence, and the known pre-existing unrelated `multi_` failure.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: plan author: orchestrator at: 2026-06-12T15:01:54Z -->
|
||||||
|
|
||||||
|
## Plan
|
||||||
|
|
||||||
|
Reviewer delegated.
|
||||||
|
|
||||||
|
- Reviewer Pod: `yoi-reviewer-panel-focus-model`
|
||||||
|
- Target branch/worktree: `ticket/panel-focus-composer-row-selection` at `/home/hare/Projects/yoi/.worktree/panel-focus-composer-row-selection`
|
||||||
|
- Commit under review: `c5ef6f79 tui: clarify panel composer target and row selection`
|
||||||
|
- Scope: read-only child worktree plus minimal non-recursive runtime-root read required by spawn validation.
|
||||||
|
- Review basis: recorded IntentPacket, binding decisions/invariants, acceptance criteria, implementation summary, diff, focused validation evidence, `nix build` evidence, and known pre-existing unrelated `multi_` failure evidence.
|
||||||
|
- Reviewer focus: composer input protection, blank/non-blank Enter behavior, status/actionbar/key hint consistency, removal of user-visible `item action` / `Right action focus`, Ticket/Pod authority preservation, and staying within Panel scope.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: review author: reviewer:yoi-reviewer-panel-focus-model at: 2026-06-12T15:07:09Z status: approve -->
|
||||||
|
|
||||||
|
## Review: approve
|
||||||
|
|
||||||
|
Approve.
|
||||||
|
|
||||||
|
Review target:
|
||||||
|
- Worktree: `/home/hare/Projects/yoi/.worktree/panel-focus-composer-row-selection`
|
||||||
|
- Commit: `c5ef6f79 tui: clarify panel composer target and row selection`
|
||||||
|
|
||||||
|
Evidence:
|
||||||
|
- 差分は `crates/tui/src/multi_pod.rs` と `crates/tui/src/workspace_panel.rs` に限定され、single-Pod TUI transcript/block navigation は変更していない。
|
||||||
|
- `Right action focus` / `global composer` / `item action` / `PanelFocus` / `ItemAction` は現行ツリー上で user-visible 文言・型として残っていない。
|
||||||
|
- 非空 composer の `Enter` は composer target 送信 / Intake 起動に向き、row selection の Ticket action / Pod open は待機扱いになる。
|
||||||
|
- 空 composer では selected row が `Enter` 対象になり、既存 Ticket action dispatch / Pod open 経路を使う。
|
||||||
|
- `Tab` は composer target の切替のみで selected row と draft を保持する。
|
||||||
|
- `Esc` は row selection を解除し、composer draft と target は保持する。
|
||||||
|
- `Left` / `Right` は Panel focus 切替ではなく composer cursor 操作として扱われる。
|
||||||
|
- Ticket action dispatch は従来通り stale/current authority re-check 経路を通り、`ready -> queued` などの明示 action semantics は弱められていない。
|
||||||
|
- Companion send は workspace Companion 宛て固定の安全確認を維持し、selected Pod へ誤送信する形にはなっていない。
|
||||||
|
|
||||||
|
Reviewer-run validation:
|
||||||
|
- `cargo test -p tui selected_ticket_row_with_non_empty_composer_shows_composer_enter_behavior`: pass
|
||||||
|
- `cargo test -p tui multi_esc_clears_row_selection_without_quitting_and_preserves_draft`: pass
|
||||||
|
- `cargo test -p tui multi_composer_target_switch_preserves_typed_text`: pass
|
||||||
|
- `cargo test -p tui multi_blank_ticket_intake_enter_uses_selected_row_and_preserves_input`: pass
|
||||||
|
- `cargo fmt --check`: pass
|
||||||
|
- `git diff --check orchestration/yoi-orchestrator...HEAD`: pass
|
||||||
|
|
||||||
|
Known broad failure:
|
||||||
|
- `cargo test -p tui multi_ --lib` fails only `multi_pod::tests::orchestrator_launch_context_uses_orchestration_root_for_runtime_workspace`.
|
||||||
|
- The failure is the same `/repo/yoi` vs `/repo/yoi/.worktree/orchestration/yoi-orchestrator` mismatch already confirmed on the current Orchestrator branch, and this implementation does not change that launch-context path. It is pre-existing and not a blocker for this Ticket.
|
||||||
|
|
||||||
|
Non-blocking risks:
|
||||||
|
- None identified. The added tests directly cover key boundaries, draft protection, and target/row-selection display without brittle full UI snapshots.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: implementation_report author: orchestrator at: 2026-06-12T15:08:05Z -->
|
||||||
|
|
||||||
|
## Implementation report
|
||||||
|
|
||||||
|
Integration completed in the Orchestrator workspace.
|
||||||
|
|
||||||
|
Integrated implementation:
|
||||||
|
- Ticket: `00001KTVJFT6F`
|
||||||
|
- Implementation branch: `ticket/panel-focus-composer-row-selection`
|
||||||
|
- Implementation commit: `c5ef6f79 tui: clarify panel composer target and row selection`
|
||||||
|
- Reviewer result: approve, no blockers
|
||||||
|
- Orchestrator merge commit: `d6166c72 merge: panel focus composer row selection`
|
||||||
|
- Integration target: `orchestration/yoi-orchestrator`
|
||||||
|
|
||||||
|
Post-integration validation from Orchestrator worktree:
|
||||||
|
- `cargo test -p tui selected_ticket_row_with_non_empty_composer_shows_composer_enter_behavior`: pass
|
||||||
|
- `cargo test -p tui multi_esc_clears_row_selection_without_quitting_and_preserves_draft`: pass
|
||||||
|
- `cargo test -p tui multi_composer_target_switch_preserves_typed_text`: pass
|
||||||
|
- `cargo test -p tui multi_blank_ticket_intake_enter_uses_selected_row_and_preserves_input`: pass
|
||||||
|
- `cargo fmt --check`: pass
|
||||||
|
- `git diff --check HEAD~1..HEAD`: pass
|
||||||
|
- `./result/bin/yoi ticket doctor`: `doctor: ok`
|
||||||
|
- `nix build .#yoi`: pass
|
||||||
|
|
||||||
|
Known unrelated validation failure remains:
|
||||||
|
- `cargo test -p tui multi_ --lib` still includes pre-existing failure `multi_pod::tests::orchestrator_launch_context_uses_orchestration_root_for_runtime_workspace`; this was verified on the Orchestrator branch before merge and reviewed as unrelated to this Ticket.
|
||||||
|
|
||||||
|
Remaining risks:
|
||||||
|
- None identified by reviewer for this Ticket.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: state_changed author: orchestrator at: 2026-06-12T15:08:11Z from: inprogress to: done reason: implementation_merged_and_validated field: state -->
|
||||||
|
|
||||||
|
## State changed
|
||||||
|
|
||||||
|
Implementation was merged into the Orchestrator branch, reviewer approved with no blockers, and post-integration validation passed. Cleanup and closure will be recorded next.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: implementation_report author: orchestrator at: 2026-06-12T15:08:59Z -->
|
||||||
|
|
||||||
|
## Implementation report
|
||||||
|
|
||||||
|
Cleanup completed.
|
||||||
|
|
||||||
|
- Stopped coder Pod: `yoi-coder-panel-focus-model`
|
||||||
|
- Stopped reviewer Pod: `yoi-reviewer-panel-focus-model`
|
||||||
|
- Removed child worktree: `/home/hare/Projects/yoi/.worktree/panel-focus-composer-row-selection`
|
||||||
|
- Deleted implementation branch: `ticket/panel-focus-composer-row-selection`
|
||||||
|
- Orchestrator worktree status after cleanup: clean
|
||||||
|
|
||||||
|
Cleanup was limited to child implementation worktree/branch and related child Pods. Root/original workspace was not touched.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: state_changed author: hare at: 2026-06-12T15:09:15Z from: done to: closed reason: closed field: state -->
|
||||||
|
|
||||||
|
## State changed
|
||||||
|
|
||||||
|
Ticket を closed にしました。
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: close author: hare at: 2026-06-12T15:09:15Z status: closed -->
|
||||||
|
|
||||||
|
## 完了
|
||||||
|
|
||||||
|
Workspace panel の focus model を composer target と row selection に整理した。
|
||||||
|
|
||||||
|
実装概要:
|
||||||
|
- `yoi panel` の user-visible focus 表示から `item action focus` / `Right action focus` / `global composer` / `PanelFocus` / `ItemAction` 系の不要な focus model を除去した。
|
||||||
|
- composer target は送信先、row selection は空 composer 時の navigation / Enter 対象として扱う表示・挙動へ整理した。
|
||||||
|
- 非空 composer では composer draft / target を優先し、`Enter` は composer send / Intake 起動に向く。
|
||||||
|
- 空 composer では selected row が `Enter` 対象になり、既存 Ticket action dispatch / Pod open 経路を使う。
|
||||||
|
- `Tab` は composer target の切替のみで selected row と draft を保持する。
|
||||||
|
- `Esc` は row selection を解除し、composer draft と target は保持する。
|
||||||
|
- `Left` / `Right` は Panel focus 切替ではなく composer cursor 操作として扱う。
|
||||||
|
- Ticket action dispatch、Pod open、Intake launch、Companion send の authority / safety semantics は維持した。
|
||||||
|
|
||||||
|
Review / integration:
|
||||||
|
- Implementation commit: `c5ef6f79 tui: clarify panel composer target and row selection`
|
||||||
|
- Reviewer: `yoi-reviewer-panel-focus-model` が approve。
|
||||||
|
- Orchestrator merge commit: `d6166c72 merge: panel focus composer row selection`
|
||||||
|
- Ticket completion commit: `e330685e ticket: mark panel focus done`
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
- `cargo test -p tui selected_ticket_row_with_non_empty_composer_shows_composer_enter_behavior`: pass
|
||||||
|
- `cargo test -p tui multi_esc_clears_row_selection_without_quitting_and_preserves_draft`: pass
|
||||||
|
- `cargo test -p tui multi_composer_target_switch_preserves_typed_text`: pass
|
||||||
|
- `cargo test -p tui multi_blank_ticket_intake_enter_uses_selected_row_and_preserves_input`: pass
|
||||||
|
- `cargo fmt --check`: pass
|
||||||
|
- `git diff --check HEAD~1..HEAD`: pass
|
||||||
|
- `./result/bin/yoi ticket doctor`: `doctor: ok`
|
||||||
|
- `nix build .#yoi`: pass
|
||||||
|
|
||||||
|
Known unrelated validation failure:
|
||||||
|
- `cargo test -p tui multi_ --lib` still includes pre-existing failure `multi_pod::tests::orchestrator_launch_context_uses_orchestration_root_for_runtime_workspace`; this was verified on the Orchestrator branch before merge and reviewed as unrelated to this Ticket.
|
||||||
|
|
||||||
|
Cleanup:
|
||||||
|
- coder/reviewer Pods stopped。
|
||||||
|
- child worktree `/home/hare/Projects/yoi/.worktree/panel-focus-composer-row-selection` removed。
|
||||||
|
- branch `ticket/panel-focus-composer-row-selection` deleted。
|
||||||
|
|
||||||
|
Non-blocking risks:
|
||||||
|
- Reviewer found none for this Ticket.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"id":"orch-plan-20260612-145344-1","ticket_id":"00001KTVJGC0Y","kind":"accepted_plan","accepted_plan":{"summary":"`ticket.language` guidance を Ticket role launch 専用から外し、Ticket tools を持つすべての Pod(Companion-style non-role context を含む)に durable/model-visible に届くようにする。既存 Ticket record の rewrite は行わず、language policy 境界と prompt-context principle を維持する。","branch":"ticket/ticket-language-guidance-all-tools","worktree":"/home/hare/Projects/yoi/.worktree/ticket-language-guidance-all-tools","role_plan":"Coder は child worktree に限定して Ticket language guidance の universal Ticket-capable context/tool-surface delivery と focused tests を実装する。Reviewer は read-only で、Ticket role / non-role Companion-style context の両方に guidance が model-visible で届くこと、worker/memory/ticket language 境界、prompt-context safety、tool/feature boundary を確認する。"},"author":"orchestrator","at":"2026-06-12T14:53:44Z"}
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
---
|
---
|
||||||
title: 'Ticket language guidance must apply to all Ticket tool users'
|
title: 'Ticket language guidance must apply to all Ticket tool users'
|
||||||
state: 'queued'
|
state: 'closed'
|
||||||
created_at: '2026-06-11T14:48:44Z'
|
created_at: '2026-06-11T14:48:44Z'
|
||||||
updated_at: '2026-06-12T14:49:39Z'
|
updated_at: '2026-06-12T15:20:11Z'
|
||||||
assignee: null
|
assignee: null
|
||||||
readiness: 'implementation_ready'
|
readiness: 'implementation_ready'
|
||||||
risk_flags: ['prompt-context', 'tool-description', 'feature-boundary', 'ticket-language', 'companion']
|
risk_flags: ['prompt-context', 'tool-description', 'feature-boundary', 'ticket-language', 'companion']
|
||||||
|
|
|
||||||
35
.yoi/tickets/00001KTVJGC0Y/resolution.md
Normal file
35
.yoi/tickets/00001KTVJGC0Y/resolution.md
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
Ticket tool users 全体に `ticket.language` guidance が届くようにした。
|
||||||
|
|
||||||
|
実装概要:
|
||||||
|
- `crates/ticket/src/tool.rs` に shared `ticket_tool_description(name, record_language)` を追加し、configured `ticket.language` がある場合は Ticket tool description に durable Ticket record / Ticket tool body language guidance を追加するようにした。
|
||||||
|
- `crates/pod/src/feature/builtin/ticket.rs` の builtin Ticket feature `ToolDeclaration` descriptions でも同じ helper を使い、read-only Companion-style context と lifecycle/Ticket-role-style context の両方に guidance が届くようにした。
|
||||||
|
- guidance は `worker.language` / `memory.language` / `ticket.language` を区別し、protocol literals、file paths、commands、logs、identifiers、quoted external text は fidelity 優先で保持することを明記した。
|
||||||
|
- guidance は Ticket tool / capability surface で model-visible であり、hidden context injection や `ticket_role` launch prompt fragment ではない。
|
||||||
|
- Companion/read-only authority や mutating tool exposure は拡大していない。
|
||||||
|
|
||||||
|
Review / integration:
|
||||||
|
- Implementation commit: `92c4dee7 ticket: guide Ticket tool language universally`
|
||||||
|
- Reviewer: `yoi-reviewer-ticket-language-guidance` が approve。
|
||||||
|
- Orchestrator merge commit: `ec66cad8 merge: ticket language guidance for tool users`
|
||||||
|
- Ticket completion commit: `2ba97b67 ticket: mark language guidance done`
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
- `cargo test -p ticket ticket_record_language_guidance`: pass
|
||||||
|
- `cargo test -p pod ticket_language_guidance`: pass
|
||||||
|
- `cargo test -p client ticket_record_language_stays_out_of_first_run_text`: pass
|
||||||
|
- `cargo test -p ticket`: pass, 68 tests
|
||||||
|
- `cargo fmt --check`: pass
|
||||||
|
- `git diff --check HEAD~1..HEAD`: pass
|
||||||
|
- `./result/bin/yoi ticket doctor`: `doctor: ok`
|
||||||
|
- `nix build .#yoi`: pass
|
||||||
|
|
||||||
|
Known unrelated validation failure:
|
||||||
|
- `cargo test -p pod` still includes pre-existing failures in Pod orchestration guidance prompt assertion tests. These were verified on the Orchestrator branch before merge and reviewed as unrelated to this Ticket.
|
||||||
|
|
||||||
|
Cleanup:
|
||||||
|
- coder/reviewer Pods stopped。
|
||||||
|
- child worktree `/home/hare/Projects/yoi/.worktree/ticket-language-guidance-all-tools` removed。
|
||||||
|
- branch `ticket/ticket-language-guidance-all-tools` deleted。
|
||||||
|
|
||||||
|
Non-blocking risk:
|
||||||
|
- configured language guidance is appended to every Ticket tool description, including read-only tools. This repeats some prompt text, but it is the accepted universal capability-surface tradeoff for this Ticket.
|
||||||
|
|
@ -29,4 +29,337 @@ LocalTicketBackend によって作成されました。
|
||||||
Ticket を `workspace-panel` が queued にしました。
|
Ticket を `workspace-panel` が queued にしました。
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: decision author: orchestrator at: 2026-06-12T14:54:15Z -->
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
Routing decision: implementation_ready
|
||||||
|
|
||||||
|
Reason:
|
||||||
|
- Ticket body は `ticket.language` guidance を Ticket role launch prompt 専用ではなく、Ticket tools を持つすべての Pod に model-visible に届ける要件を明確に記録している。
|
||||||
|
- `worker.language` / `memory.language` / `ticket.language` の責務分離、hidden context-only injection 禁止、既存 Ticket record の一括 rewrite 禁止が binding invariant として記録済みである。
|
||||||
|
- risk flags は prompt-context / tool-description / feature-boundary / ticket-language / companion だが、bounded context check の結果、具体的な未決定 design/API/authority 判断は残っていない。実装方式は Ticket capability/tool surface または feature-scoped system prompt path の範囲で coder が選べる。
|
||||||
|
- Relation blocker はなく、OrchestrationPlan に accepted plan `orch-plan-20260612-145344-1` を記録済み。
|
||||||
|
- 現在 active coder は `00001KTVJFT6F`(Panel focus)と `00001KTTW04W2`(Companion progress notify)だが、この Ticket の主対象は Ticket language guidance の prompt/tool/feature boundary であり、Panel UI 変更とは独立している。Companion-adjacent確認はあるが、authority強化や progress notify implementation と結合しないように実装・reviewで確認する。
|
||||||
|
|
||||||
|
Evidence checked:
|
||||||
|
- Ticket body / thread: requirements, acceptance criteria, binding decisions, implementation latitude, escalation conditions, validation, intake summary, `ready -> queued` event を確認。
|
||||||
|
- TicketRelationQuery: outgoing/incoming relation なし、blocker なし。
|
||||||
|
- TicketOrchestrationPlanQuery: 既存 record なし、今回 accepted plan を記録。
|
||||||
|
- Code/resource map: `ticket.language` / `Ticket record language` / Ticket tool/backend/feature/prompt surfaces を narrow search で確認。直近の role launch split により Ticket role first-run prompt から language guidance が外れているため、本 Ticket の universal delivery 実装が次の自然な境界であることを確認。
|
||||||
|
- Workspace/Pod state: Orchestrator worktree clean、active child worktree は別 branch/scope。
|
||||||
|
|
||||||
|
IntentPacket:
|
||||||
|
|
||||||
|
Intent:
|
||||||
|
- `ticket.language` guidance を Ticket role 固有の launch prompt ではなく、Ticket-writing tools を持つすべての Pod に durable/model-visible な形で届ける。
|
||||||
|
- Companion-style non-Ticket-role context でも Ticket tool body / durable Ticket record を configured `ticket.language` に従って書く guidance が見えるようにする。
|
||||||
|
|
||||||
|
Binding decisions / invariants:
|
||||||
|
- `ticket.language` は durable Ticket record / Ticket tool body writing policy であり、Ticket role 固有 policy ではない。
|
||||||
|
- `worker.language` は通常 prose、`memory.language` は Memory/Knowledge generation、`ticket.language` は durable Ticket records / Ticket tool bodies の責務に分ける。
|
||||||
|
- `ticket.language` が設定されていても protocol literals、file paths、commands、logs、identifiers、quoted external text を不要に翻訳しない。
|
||||||
|
- hidden context-only injection を作らない。guidance は tool surface / feature-scoped system prompt / committed prompt path など、モデルから見える根拠を残す。
|
||||||
|
- 既存 Ticket records を翻訳・一括 rewrite しない。
|
||||||
|
- Companion default authority を強化しない。
|
||||||
|
|
||||||
|
Requirements / acceptance criteria:
|
||||||
|
- Ticket tools を持つ non-Ticket-role Pod、特に Companion-style context でも guidance が model-visible になる。
|
||||||
|
- Ticket role Pods でも同等 guidance が届き、既存挙動が退行しない。
|
||||||
|
- guidance source は universal Ticket capability / tool surface または feature-scoped system prompt path にあり、Ticket role launch prompt 専用ではない。
|
||||||
|
- `worker.language` が `ticket.language` を override しない。
|
||||||
|
- focused test または snapshot-style verification で Ticket role と generic / Companion-style Ticket-capable context の両方を確認する。
|
||||||
|
- `nix build .#yoi` が通る。
|
||||||
|
|
||||||
|
Implementation latitude:
|
||||||
|
- Ticket tool descriptions/schema text に configured Ticket language instruction を入れる方式、または Ticket feature/capability が有効な Pod への feature-scoped prompt guidance を使う方式を選んでよい。
|
||||||
|
- 既存 architecture を広く作り替えず、現在の ToolRegistry/feature/prompt resource 境界に合う最小実装を選ぶ。
|
||||||
|
- Tests は prompt snapshot 全体に brittle にせず、language guidance の存在/非混同を直接確認する。
|
||||||
|
|
||||||
|
Escalate if:
|
||||||
|
- Tool descriptions が configured Ticket language に依存できず、広い ToolRegistry redesign が必要になる。
|
||||||
|
- feature-scoped prompt guidance が history に残らない context mutation を必要とする。
|
||||||
|
- Companion の Ticket capability path から Ticket config にアクセスできない。
|
||||||
|
- 実装が Ticket record language と worker response language を混同する。
|
||||||
|
- Companion authority を増やす必要が出る。
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
- Ticket role prompt/context の focused test。
|
||||||
|
- generic / Companion-style Ticket-capable context の guidance 確認 test。
|
||||||
|
- relevant focused `cargo test`。
|
||||||
|
- `cargo fmt --check`。
|
||||||
|
- `git diff --check`。
|
||||||
|
- `./result/bin/yoi ticket doctor` または同等。
|
||||||
|
- `nix build .#yoi`。
|
||||||
|
|
||||||
|
Current code map:
|
||||||
|
- `crates/ticket/src/config.rs`: `ticket.language` config parsing / validation。
|
||||||
|
- `crates/ticket/src/tool.rs`: Ticket tool definitions/descriptions and tests。
|
||||||
|
- `crates/pod/src/feature/builtin/ticket.rs`: Ticket feature registration/capability/tool exposure。
|
||||||
|
- `crates/pod/src/prompt/*`: feature/system prompt integration if needed。
|
||||||
|
- `crates/client/src/ticket_role.rs`: ensure Ticket role launch still receives guidance through the new universal path, not first-run Submit prose。
|
||||||
|
|
||||||
|
Critical risks / reviewer focus:
|
||||||
|
- guidance が Ticket role launch prompt 専用に戻っていないこと。
|
||||||
|
- non-role Ticket-capable context にも model-visible に届くこと。
|
||||||
|
- hidden ephemeral context injection ではなく、tool/feature/prompt surface として履歴・prompt根拠が残ること。
|
||||||
|
- `worker.language` と `ticket.language` を混同しないこと。
|
||||||
|
- Companion authority / Ticket mutation authority を強化しないこと。
|
||||||
|
|
||||||
|
Next action:
|
||||||
|
- `queued -> inprogress` を記録してから、branch `ticket/ticket-language-guidance-all-tools` / worktree `/home/hare/Projects/yoi/.worktree/ticket-language-guidance-all-tools` を作成し、sibling coder に narrow write scope で実装を委譲する。Reviewer は coder evidence 後に read-only で起動する。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: state_changed author: orchestrator at: 2026-06-12T14:54:22Z from: queued to: inprogress reason: orchestrator_acceptance field: state -->
|
||||||
|
|
||||||
|
## State changed
|
||||||
|
|
||||||
|
Routing accepted for implementation. Ticket body/thread, relation blockers, orchestration plan, relevant Ticket language / tool / feature / prompt code map, current Orchestrator workspace state, and active parallel work were rechecked. No unresolved blocker or missing planning decision remains. Implementation side effects will start only after this accepted `queued -> inprogress` transition is recorded, using accepted plan `orch-plan-20260612-145344-1`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: plan author: orchestrator at: 2026-06-12T14:54:49Z -->
|
||||||
|
|
||||||
|
## Plan
|
||||||
|
|
||||||
|
Implementation worktree created for multi-agent handoff.
|
||||||
|
|
||||||
|
- Ticket: `00001KTVJGC0Y`
|
||||||
|
- Branch: `ticket/ticket-language-guidance-all-tools`
|
||||||
|
- Worktree: `/home/hare/Projects/yoi/.worktree/ticket-language-guidance-all-tools`
|
||||||
|
- Base: Orchestrator workspace HEAD `76d358e8` (`orchestration/yoi-orchestrator`), including the accepted routing record and `queued -> inprogress` transition.
|
||||||
|
- Parallel work note: active child worktrees are `panel-focus-composer-row-selection` and `orchestrator-progress-companion-notify`; this Ticket should stay within Ticket language/tool/feature/prompt guidance surfaces and avoid coupling to their implementation branches.
|
||||||
|
- Scope rule: implementation work happens only in this child worktree. Root/original workspace must not be read, written, validated, merged, cleaned, or used for git operations.
|
||||||
|
- Next: spawn sibling coder with narrow write scope to the implementation worktree. Reviewer will be started after coder evidence is available.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: plan author: orchestrator at: 2026-06-12T14:55:42Z -->
|
||||||
|
|
||||||
|
## Plan
|
||||||
|
|
||||||
|
Coder delegated.
|
||||||
|
|
||||||
|
- Coder Pod: `yoi-coder-ticket-language-guidance`
|
||||||
|
- Worktree: `/home/hare/Projects/yoi/.worktree/ticket-language-guidance-all-tools`
|
||||||
|
- Branch: `ticket/ticket-language-guidance-all-tools`
|
||||||
|
- Scope: write `/home/hare/Projects/yoi/.worktree/ticket-language-guidance-all-tools`; read child worktree plus minimal non-recursive runtime-root read required by spawn validation.
|
||||||
|
- Task: make configured `ticket.language` guidance model-visible for all Ticket-capable Pods, including non-role Companion-style contexts; preserve worker/memory/ticket language boundaries; avoid hidden context-only injection; add focused tests and required validation.
|
||||||
|
- Parallel work note: active sibling worktrees for Panel focus and Companion progress notification exist; coder was instructed to avoid coupling and report concrete merge/design conflict if found.
|
||||||
|
- Constraint: coder was instructed not to use root/original workspace for decision evidence or git/validation/cleanup, and not to create generated memory/local/runtime/log/lock/secret-like `.yoi` paths.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: implementation_report author: orchestrator at: 2026-06-12T15:10:38Z -->
|
||||||
|
|
||||||
|
## Implementation report
|
||||||
|
|
||||||
|
Coder completed implementation.
|
||||||
|
|
||||||
|
Coder Pod:
|
||||||
|
- `yoi-coder-ticket-language-guidance`
|
||||||
|
|
||||||
|
Implementation branch/worktree:
|
||||||
|
- Branch: `ticket/ticket-language-guidance-all-tools`
|
||||||
|
- Worktree: `/home/hare/Projects/yoi/.worktree/ticket-language-guidance-all-tools`
|
||||||
|
- Commit: `92c4dee7 ticket: guide Ticket tool language universally`
|
||||||
|
- Worktree status: clean
|
||||||
|
|
||||||
|
Changed surfaces:
|
||||||
|
- `crates/ticket/src/tool.rs`
|
||||||
|
- `crates/pod/src/feature/builtin/ticket.rs`
|
||||||
|
|
||||||
|
Implementation summary:
|
||||||
|
- Ticket tool `ToolMeta.description` now derives from configured `LocalTicketBackend::record_language()`.
|
||||||
|
- Added shared `ticket_tool_description(name, record_language)` so all Ticket tool descriptions can include configured Ticket record language guidance.
|
||||||
|
- Guidance distinguishes durable Ticket record / Ticket tool body text from normal `worker.language` prose and `memory.language` Memory/Knowledge generation.
|
||||||
|
- Guidance preserves protocol literals, file paths, commands, logs, identifiers, and quoted external text when fidelity matters.
|
||||||
|
- Feature descriptor `ToolDeclaration` descriptions use the same helper, so Ticket role launch is not the only guidance path.
|
||||||
|
- Focused tests cover configured-language presence, unset-language omission, read-only Companion-style context, and lifecycle/Ticket-role-style context.
|
||||||
|
- Companion/read-only authority was not changed.
|
||||||
|
|
||||||
|
Orchestrator-verified evidence from child worktree:
|
||||||
|
- `git status --short --branch`: clean on `ticket/ticket-language-guidance-all-tools`
|
||||||
|
- `cargo test -p ticket ticket_record_language_guidance`: pass
|
||||||
|
- `cargo test -p pod ticket_language_guidance`: pass
|
||||||
|
- `cargo test -p ticket`: pass, 68 tests
|
||||||
|
- `cargo fmt --check`: pass
|
||||||
|
- `git diff --check`: pass
|
||||||
|
- `cargo run -p yoi -- ticket doctor`: `doctor: ok`
|
||||||
|
- `nix build .#yoi`: pass
|
||||||
|
|
||||||
|
Known unrelated validation failure:
|
||||||
|
- `cargo test -p pod` fails two prompt assertion tests:
|
||||||
|
- `prompt::catalog::tests::pod_orchestration_guidance_section_renders_resource_body`
|
||||||
|
- `prompt::system::tests::pod_orchestration_guidance_is_included_for_pod_management_tools`
|
||||||
|
- The same two focused tests fail on current Orchestrator branch before this implementation is merged, with the same missing phrase `worktree status, diff, and test results`.
|
||||||
|
- These failures are in Pod orchestration guidance prompt assertions, outside the Ticket tool language guidance change, but reviewer should confirm they are unrelated and not introduced by this Ticket.
|
||||||
|
|
||||||
|
Next:
|
||||||
|
- Spawn read-only sibling reviewer against the recorded intent, binding decisions/invariants, acceptance criteria, commit `92c4dee7`, diff, validation evidence, and known pre-existing unrelated `cargo test -p pod` failures.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: plan author: orchestrator at: 2026-06-12T15:11:23Z -->
|
||||||
|
|
||||||
|
## Plan
|
||||||
|
|
||||||
|
Reviewer delegated.
|
||||||
|
|
||||||
|
- Reviewer Pod: `yoi-reviewer-ticket-language-guidance`
|
||||||
|
- Target branch/worktree: `ticket/ticket-language-guidance-all-tools` at `/home/hare/Projects/yoi/.worktree/ticket-language-guidance-all-tools`
|
||||||
|
- Commit under review: `92c4dee7 ticket: guide Ticket tool language universally`
|
||||||
|
- Scope: read-only child worktree plus minimal non-recursive runtime-root read required by spawn validation.
|
||||||
|
- Review basis: recorded IntentPacket, binding decisions/invariants, acceptance criteria, implementation summary, diff, focused validation evidence, `nix build` evidence, and known pre-existing unrelated `cargo test -p pod` failure evidence.
|
||||||
|
- Reviewer focus: universal Ticket tool/capability guidance, Ticket role and generic/Companion-style coverage, worker/memory/ticket language separation, no hidden context-only injection, no authority expansion, and robust focused tests.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: review author: reviewer:yoi-reviewer-ticket-language-guidance at: 2026-06-12T15:16:59Z status: approve -->
|
||||||
|
|
||||||
|
## Review: approve
|
||||||
|
|
||||||
|
Approve.
|
||||||
|
|
||||||
|
Review target:
|
||||||
|
- Worktree: `/home/hare/Projects/yoi/.worktree/ticket-language-guidance-all-tools`
|
||||||
|
- Commit: `92c4dee7 ticket: guide Ticket tool language universally`
|
||||||
|
|
||||||
|
Evidence:
|
||||||
|
- 差分は `crates/ticket/src/tool.rs` と `crates/pod/src/feature/builtin/ticket.rs` に限定されている。
|
||||||
|
- guidance は shared `ticket_tool_description(name, record_language)` で生成され、registered Ticket tools の `ToolMeta.description` と builtin Ticket feature の `ToolDeclaration` descriptions の両方に使われている。
|
||||||
|
- guidance は Ticket tool / capability surface で model-visible であり、hidden context injection でも `ticket_role` launch prompt fragment でもない。
|
||||||
|
- `ticket.language` は `worker.language` / `memory.language` と区別され、protocol literals、paths、commands、logs、identifiers、quoted external text は fidelity 優先で保持する guidance が明記されている。
|
||||||
|
- read-only Companion-style access は read-only のままで、mutating tool exposure は拡大していない。
|
||||||
|
- Ticket role-style lifecycle access も同じ universal tool-description path でカバーされている。
|
||||||
|
- first-run Ticket role text は record-language guidance を launch prose に戻していない。
|
||||||
|
|
||||||
|
Reviewer-run validation:
|
||||||
|
- `cargo test -p ticket ticket_record_language_guidance`: pass
|
||||||
|
- `cargo test -p pod ticket_language_guidance`: pass
|
||||||
|
- `cargo test -p ticket`: pass, 68 tests
|
||||||
|
- `cargo test -p client ticket_record_language_stays_out_of_first_run_text`: pass
|
||||||
|
- `cargo fmt --check`: pass
|
||||||
|
- `git diff --check orchestration/yoi-orchestrator...HEAD`: pass
|
||||||
|
- `cargo run -p yoi -- ticket doctor`: `doctor: ok`
|
||||||
|
- `nix build .#yoi`: pass
|
||||||
|
|
||||||
|
Known broad `cargo test -p pod` failures:
|
||||||
|
- Reviewer reproduced the two prompt assertion failures on HEAD, both for missing `worktree status, diff, and test results`.
|
||||||
|
- This branch has no diff under `crates/pod/src/prompt` or `resources/prompts`, and implementation diff is confined to Ticket tool/feature files.
|
||||||
|
- Together with Orchestrator’s base-branch verification, these failures are pre-existing/unrelated and not a blocker for this Ticket.
|
||||||
|
|
||||||
|
Non-blocking risk:
|
||||||
|
- When configured, language guidance is appended to every Ticket tool description, including read-only tools. This repeats some prompt text, but it is an intentional universal capability-surface tradeoff and satisfies the acceptance criteria.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: implementation_report author: orchestrator at: 2026-06-12T15:19:24Z -->
|
||||||
|
|
||||||
|
## Implementation report
|
||||||
|
|
||||||
|
Integration completed in the Orchestrator workspace.
|
||||||
|
|
||||||
|
Integrated implementation:
|
||||||
|
- Ticket: `00001KTVJGC0Y`
|
||||||
|
- Implementation branch: `ticket/ticket-language-guidance-all-tools`
|
||||||
|
- Implementation commit: `92c4dee7 ticket: guide Ticket tool language universally`
|
||||||
|
- Reviewer result: approve, no blockers
|
||||||
|
- Orchestrator merge commit: `ec66cad8 merge: ticket language guidance for tool users`
|
||||||
|
- Integration target: `orchestration/yoi-orchestrator`
|
||||||
|
|
||||||
|
Post-integration validation from Orchestrator worktree:
|
||||||
|
- `cargo test -p ticket ticket_record_language_guidance`: pass
|
||||||
|
- `cargo test -p pod ticket_language_guidance`: pass
|
||||||
|
- `cargo test -p client ticket_record_language_stays_out_of_first_run_text`: pass
|
||||||
|
- `cargo test -p ticket`: pass, 68 tests
|
||||||
|
- `cargo fmt --check`: pass
|
||||||
|
- `git diff --check HEAD~1..HEAD`: pass
|
||||||
|
- `./result/bin/yoi ticket doctor`: `doctor: ok`
|
||||||
|
- `nix build .#yoi`: pass
|
||||||
|
|
||||||
|
Known unrelated validation failure remains:
|
||||||
|
- `cargo test -p pod` still includes pre-existing failures in Pod orchestration guidance prompt assertion tests. These were verified on the Orchestrator branch before merge and reviewed as unrelated to this Ticket.
|
||||||
|
|
||||||
|
Remaining non-blocking risk:
|
||||||
|
- When configured, Ticket language guidance is appended to every Ticket tool description, including read-only tools. This repeats some prompt text, but it is the accepted universal capability-surface tradeoff for this Ticket.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: state_changed author: orchestrator at: 2026-06-12T15:19:29Z from: inprogress to: done reason: implementation_merged_and_validated field: state -->
|
||||||
|
|
||||||
|
## State changed
|
||||||
|
|
||||||
|
Implementation was merged into the Orchestrator branch, reviewer approved with no blockers, and post-integration validation passed. Cleanup and closure will be recorded next.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: implementation_report author: orchestrator at: 2026-06-12T15:19:54Z -->
|
||||||
|
|
||||||
|
## Implementation report
|
||||||
|
|
||||||
|
Cleanup completed.
|
||||||
|
|
||||||
|
- Stopped coder Pod: `yoi-coder-ticket-language-guidance`
|
||||||
|
- Stopped reviewer Pod: `yoi-reviewer-ticket-language-guidance`
|
||||||
|
- Removed child worktree: `/home/hare/Projects/yoi/.worktree/ticket-language-guidance-all-tools`
|
||||||
|
- Deleted implementation branch: `ticket/ticket-language-guidance-all-tools`
|
||||||
|
- Orchestrator worktree status after cleanup: clean
|
||||||
|
|
||||||
|
Cleanup was limited to child implementation worktree/branch and related child Pods. Root/original workspace was not touched.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: state_changed author: hare at: 2026-06-12T15:20:11Z from: done to: closed reason: closed field: state -->
|
||||||
|
|
||||||
|
## State changed
|
||||||
|
|
||||||
|
Ticket を closed にしました。
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: close author: hare at: 2026-06-12T15:20:11Z status: closed -->
|
||||||
|
|
||||||
|
## 完了
|
||||||
|
|
||||||
|
Ticket tool users 全体に `ticket.language` guidance が届くようにした。
|
||||||
|
|
||||||
|
実装概要:
|
||||||
|
- `crates/ticket/src/tool.rs` に shared `ticket_tool_description(name, record_language)` を追加し、configured `ticket.language` がある場合は Ticket tool description に durable Ticket record / Ticket tool body language guidance を追加するようにした。
|
||||||
|
- `crates/pod/src/feature/builtin/ticket.rs` の builtin Ticket feature `ToolDeclaration` descriptions でも同じ helper を使い、read-only Companion-style context と lifecycle/Ticket-role-style context の両方に guidance が届くようにした。
|
||||||
|
- guidance は `worker.language` / `memory.language` / `ticket.language` を区別し、protocol literals、file paths、commands、logs、identifiers、quoted external text は fidelity 優先で保持することを明記した。
|
||||||
|
- guidance は Ticket tool / capability surface で model-visible であり、hidden context injection や `ticket_role` launch prompt fragment ではない。
|
||||||
|
- Companion/read-only authority や mutating tool exposure は拡大していない。
|
||||||
|
|
||||||
|
Review / integration:
|
||||||
|
- Implementation commit: `92c4dee7 ticket: guide Ticket tool language universally`
|
||||||
|
- Reviewer: `yoi-reviewer-ticket-language-guidance` が approve。
|
||||||
|
- Orchestrator merge commit: `ec66cad8 merge: ticket language guidance for tool users`
|
||||||
|
- Ticket completion commit: `2ba97b67 ticket: mark language guidance done`
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
- `cargo test -p ticket ticket_record_language_guidance`: pass
|
||||||
|
- `cargo test -p pod ticket_language_guidance`: pass
|
||||||
|
- `cargo test -p client ticket_record_language_stays_out_of_first_run_text`: pass
|
||||||
|
- `cargo test -p ticket`: pass, 68 tests
|
||||||
|
- `cargo fmt --check`: pass
|
||||||
|
- `git diff --check HEAD~1..HEAD`: pass
|
||||||
|
- `./result/bin/yoi ticket doctor`: `doctor: ok`
|
||||||
|
- `nix build .#yoi`: pass
|
||||||
|
|
||||||
|
Known unrelated validation failure:
|
||||||
|
- `cargo test -p pod` still includes pre-existing failures in Pod orchestration guidance prompt assertion tests. These were verified on the Orchestrator branch before merge and reviewed as unrelated to this Ticket.
|
||||||
|
|
||||||
|
Cleanup:
|
||||||
|
- coder/reviewer Pods stopped。
|
||||||
|
- child worktree `/home/hare/Projects/yoi/.worktree/ticket-language-guidance-all-tools` removed。
|
||||||
|
- branch `ticket/ticket-language-guidance-all-tools` deleted。
|
||||||
|
|
||||||
|
Non-blocking risk:
|
||||||
|
- configured language guidance is appended to every Ticket tool description, including read-only tools. This repeats some prompt text, but it is the accepted universal capability-surface tradeoff for this Ticket.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ use ticket::{
|
||||||
tool::{
|
tool::{
|
||||||
TICKET_BASE_READ_ONLY_TOOL_NAMES, TICKET_BASE_TOOL_NAMES,
|
TICKET_BASE_READ_ONLY_TOOL_NAMES, TICKET_BASE_TOOL_NAMES,
|
||||||
TICKET_ORCHESTRATION_READ_ONLY_TOOL_NAMES, TICKET_ORCHESTRATION_TOOL_NAMES,
|
TICKET_ORCHESTRATION_READ_ONLY_TOOL_NAMES, TICKET_ORCHESTRATION_TOOL_NAMES,
|
||||||
TICKET_READ_ONLY_TOOL_NAMES, TICKET_TOOL_NAMES, ticket_tools,
|
TICKET_READ_ONLY_TOOL_NAMES, TICKET_TOOL_NAMES, ticket_tool_description, ticket_tools,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -178,7 +178,10 @@ impl FeatureModule for TicketFeature {
|
||||||
));
|
));
|
||||||
let enabled_tool_names = self.enabled_tool_names();
|
let enabled_tool_names = self.enabled_tool_names();
|
||||||
for name in &enabled_tool_names {
|
for name in &enabled_tool_names {
|
||||||
descriptor = descriptor.with_tool(ToolDeclaration::new(*name, tool_description(name)));
|
descriptor = descriptor.with_tool(ToolDeclaration::new(
|
||||||
|
*name,
|
||||||
|
ticket_tool_description(name, self.record_language.as_deref()),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
descriptor
|
descriptor
|
||||||
}
|
}
|
||||||
|
|
@ -227,37 +230,6 @@ impl FeatureModule for TicketFeature {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tool_description(name: &str) -> &'static str {
|
|
||||||
match name {
|
|
||||||
"TicketCreate" => "Create a Ticket through the typed local Ticket backend.",
|
|
||||||
"TicketList" => {
|
|
||||||
"List Tickets as a lightweight bounded overview for id selection; use TicketShow before decisions."
|
|
||||||
}
|
|
||||||
"TicketShow" => {
|
|
||||||
"Show one Ticket through the typed local Ticket backend as the detailed authority."
|
|
||||||
}
|
|
||||||
"TicketComment" => {
|
|
||||||
"Append a comment/plan/decision/implementation_report event to a Ticket."
|
|
||||||
}
|
|
||||||
"TicketReview" => "Append an approve/request_changes review event to a Ticket.",
|
|
||||||
"TicketIntakeReady" => {
|
|
||||||
"Mark an intake Ticket ready and append the typed intake summary/state transition events."
|
|
||||||
}
|
|
||||||
"TicketWorkflowState" => {
|
|
||||||
"Transition Ticket state; queued -> inprogress is the accepted implementation start, so implementation side effects should happen only after that transition is accepted and recorded."
|
|
||||||
}
|
|
||||||
"TicketClose" => "Close a Ticket with a resolution through the typed local Ticket backend.",
|
|
||||||
"TicketOrchestrationPlanRecord" => {
|
|
||||||
"Append a durable typed Ticket orchestration plan record without changing state or starting work."
|
|
||||||
}
|
|
||||||
"TicketOrchestrationPlanQuery" => {
|
|
||||||
"Query durable Ticket orchestration plan records by Ticket and/or relation kind."
|
|
||||||
}
|
|
||||||
"TicketDoctor" => "Run typed local Ticket backend consistency checks.",
|
|
||||||
_ => "Typed Ticket backend tool.",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ticket_tools_feature(workspace: impl AsRef<Path>) -> TicketFeature {
|
pub fn ticket_tools_feature(workspace: impl AsRef<Path>) -> TicketFeature {
|
||||||
TicketFeature::for_workspace(workspace)
|
TicketFeature::for_workspace(workspace)
|
||||||
}
|
}
|
||||||
|
|
@ -298,6 +270,19 @@ mod tests {
|
||||||
std::fs::write(yoi_dir.join("ticket.config.toml"), content).unwrap();
|
std::fs::write(yoi_dir.join("ticket.config.toml"), content).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pending_tool_description(
|
||||||
|
pending_tools: &[llm_worker::tool::ToolDefinition],
|
||||||
|
name: &str,
|
||||||
|
) -> String {
|
||||||
|
pending_tools
|
||||||
|
.iter()
|
||||||
|
.find_map(|definition| {
|
||||||
|
let (meta, _) = definition();
|
||||||
|
(meta.name == name).then_some(meta.description)
|
||||||
|
})
|
||||||
|
.expect("tool exists")
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn descriptor_declares_ticket_tools_and_backend_authority() {
|
fn descriptor_declares_ticket_tools_and_backend_authority() {
|
||||||
let temp = TempDir::new().unwrap();
|
let temp = TempDir::new().unwrap();
|
||||||
|
|
@ -407,6 +392,45 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_only_companion_style_context_exposes_ticket_language_guidance() {
|
||||||
|
let temp = TempDir::new().unwrap();
|
||||||
|
write_ticket_config(
|
||||||
|
temp.path(),
|
||||||
|
r#"
|
||||||
|
[ticket]
|
||||||
|
language = "Japanese"
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
make_ticket_root(&temp.path().join(DEFAULT_TICKET_BACKEND_RELATIVE_PATH));
|
||||||
|
let feature = ticket_tools_feature_with_access(temp.path(), TicketFeatureAccess::ReadOnly);
|
||||||
|
let descriptor = feature.descriptor();
|
||||||
|
let descriptor_description = descriptor
|
||||||
|
.tools
|
||||||
|
.iter()
|
||||||
|
.find(|tool| tool.name == "TicketShow")
|
||||||
|
.expect("TicketShow declared")
|
||||||
|
.description
|
||||||
|
.clone();
|
||||||
|
assert!(descriptor_description.contains("Ticket record language: Japanese"));
|
||||||
|
|
||||||
|
let mut pending_tools = Vec::new();
|
||||||
|
let mut hooks = HookRegistryBuilder::default();
|
||||||
|
let report = FeatureRegistryBuilder::new()
|
||||||
|
.with_module(feature)
|
||||||
|
.install_into_pending(&mut pending_tools, &mut hooks);
|
||||||
|
|
||||||
|
assert_eq!(pending_tools.len(), TICKET_READ_ONLY_TOOL_NAMES.len());
|
||||||
|
assert_eq!(
|
||||||
|
report.reports[0].installed_tools,
|
||||||
|
TICKET_READ_ONLY_TOOL_NAMES
|
||||||
|
);
|
||||||
|
let description = pending_tool_description(&pending_tools, "TicketShow");
|
||||||
|
assert!(description.contains("Ticket record language: Japanese"));
|
||||||
|
assert!(description.contains("distinct from worker.language"));
|
||||||
|
assert!(description.contains("Preserve protocol literals"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn lifecycle_installation_exposes_lifecycle_tools() {
|
fn lifecycle_installation_exposes_lifecycle_tools() {
|
||||||
let temp = TempDir::new().unwrap();
|
let temp = TempDir::new().unwrap();
|
||||||
|
|
@ -444,6 +468,35 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lifecycle_ticket_role_style_context_exposes_ticket_language_guidance() {
|
||||||
|
let temp = TempDir::new().unwrap();
|
||||||
|
write_ticket_config(
|
||||||
|
temp.path(),
|
||||||
|
r#"
|
||||||
|
[ticket]
|
||||||
|
language = "Japanese"
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
make_ticket_root(&temp.path().join(DEFAULT_TICKET_BACKEND_RELATIVE_PATH));
|
||||||
|
let mut pending_tools = Vec::new();
|
||||||
|
let mut hooks = HookRegistryBuilder::default();
|
||||||
|
let report = FeatureRegistryBuilder::new()
|
||||||
|
.with_module(ticket_tools_feature_with_access(
|
||||||
|
temp.path(),
|
||||||
|
TicketFeatureAccess::Lifecycle,
|
||||||
|
))
|
||||||
|
.install_into_pending(&mut pending_tools, &mut hooks);
|
||||||
|
|
||||||
|
assert_eq!(pending_tools.len(), TICKET_TOOL_NAMES.len());
|
||||||
|
assert_eq!(report.reports[0].installed_tools, TICKET_TOOL_NAMES);
|
||||||
|
let description = pending_tool_description(&pending_tools, "TicketComment");
|
||||||
|
assert!(description.contains("Ticket record language: Japanese"));
|
||||||
|
assert!(description.contains("durable Ticket record and Ticket tool body text"));
|
||||||
|
assert!(description.contains("distinct from worker.language"));
|
||||||
|
assert!(description.contains("memory.language"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn installs_ticket_tools_when_default_root_is_usable() {
|
fn installs_ticket_tools_when_default_root_is_usable() {
|
||||||
let temp = TempDir::new().unwrap();
|
let temp = TempDir::new().unwrap();
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,41 @@ explicit state decisions.";
|
||||||
const DOCTOR_DESCRIPTION: &str = "Run typed Ticket backend consistency checks and return bounded \
|
const DOCTOR_DESCRIPTION: &str = "Run typed Ticket backend consistency checks and return bounded \
|
||||||
diagnostics through the typed backend without shelling out to external commands.";
|
diagnostics through the typed backend without shelling out to external commands.";
|
||||||
|
|
||||||
|
fn base_tool_description(name: &str) -> &'static str {
|
||||||
|
match name {
|
||||||
|
"TicketCreate" => CREATE_DESCRIPTION,
|
||||||
|
"TicketList" => LIST_DESCRIPTION,
|
||||||
|
"TicketShow" => SHOW_DESCRIPTION,
|
||||||
|
"TicketComment" => COMMENT_DESCRIPTION,
|
||||||
|
"TicketReview" => REVIEW_DESCRIPTION,
|
||||||
|
"TicketIntakeReady" => INTAKE_READY_DESCRIPTION,
|
||||||
|
"TicketWorkflowState" => WORKFLOW_STATE_DESCRIPTION,
|
||||||
|
"TicketClose" => CLOSE_DESCRIPTION,
|
||||||
|
"TicketRelationRecord" => RELATION_RECORD_DESCRIPTION,
|
||||||
|
"TicketRelationQuery" => RELATION_QUERY_DESCRIPTION,
|
||||||
|
"TicketOrchestrationPlanRecord" => ORCHESTRATION_PLAN_RECORD_DESCRIPTION,
|
||||||
|
"TicketOrchestrationPlanQuery" => ORCHESTRATION_PLAN_QUERY_DESCRIPTION,
|
||||||
|
"TicketDoctor" => DOCTOR_DESCRIPTION,
|
||||||
|
_ => "Ticket backend tool.",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the model-visible Ticket tool description for a configured Ticket backend.
|
||||||
|
///
|
||||||
|
/// `record_language` is the durable Ticket record/tool-body language, distinct from
|
||||||
|
/// worker response language and Memory/Knowledge language. Keeping this on the tool
|
||||||
|
/// surface ensures every Ticket-capable Pod sees the policy without hidden context
|
||||||
|
/// injection or role-launch-only prose.
|
||||||
|
pub fn ticket_tool_description(name: &str, record_language: Option<&str>) -> String {
|
||||||
|
let mut description = base_tool_description(name).to_string();
|
||||||
|
if let Some(language) = record_language.filter(|language| !language.trim().is_empty()) {
|
||||||
|
description.push_str("\n\nTicket record language: ");
|
||||||
|
description.push_str(language.trim());
|
||||||
|
description.push_str(". Use this language for durable Ticket record and Ticket tool body text, including Ticket item bodies, thread comments/plans/decisions/implementation reports, reviews, resolutions, intake summaries, and orchestration plan notes. This policy is distinct from worker.language for normal prose and memory.language for Memory/Knowledge. Preserve protocol literals, file paths, commands, logs, identifiers, and quoted external text when translation would reduce fidelity.");
|
||||||
|
}
|
||||||
|
description
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, schemars::JsonSchema)]
|
#[derive(Debug, Deserialize, schemars::JsonSchema)]
|
||||||
struct TicketCreateParams {
|
struct TicketCreateParams {
|
||||||
/// Ticket title. Must not be empty.
|
/// Ticket title. Must not be empty.
|
||||||
|
|
@ -1273,18 +1308,15 @@ fn json_output(summary: String, value: impl Serialize) -> ToolOutput {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tool_definition<T>(
|
fn tool_definition<T>(name: &'static str, backend: LocalTicketBackend) -> ToolDefinition
|
||||||
name: &'static str,
|
|
||||||
description: &'static str,
|
|
||||||
backend: LocalTicketBackend,
|
|
||||||
) -> ToolDefinition
|
|
||||||
where
|
where
|
||||||
T: Tool + From<LocalTicketBackend> + 'static,
|
T: Tool + From<LocalTicketBackend> + 'static,
|
||||||
{
|
{
|
||||||
|
let description = ticket_tool_description(name, backend.record_language());
|
||||||
Arc::new(move || {
|
Arc::new(move || {
|
||||||
let schema_value = input_schema(name);
|
let schema_value = input_schema(name);
|
||||||
let meta = ToolMeta::new(name)
|
let meta = ToolMeta::new(name)
|
||||||
.description(description)
|
.description(description.clone())
|
||||||
.input_schema(schema_value);
|
.input_schema(schema_value);
|
||||||
let tool: Arc<dyn Tool> = Arc::new(T::from(backend.clone()));
|
let tool: Arc<dyn Tool> = Arc::new(T::from(backend.clone()));
|
||||||
(meta, tool)
|
(meta, tool)
|
||||||
|
|
@ -1348,43 +1380,25 @@ impl_from_backend!(TicketDoctorTool);
|
||||||
/// Build all MVP Ticket tool definitions over one local backend root.
|
/// Build all MVP Ticket tool definitions over one local backend root.
|
||||||
pub fn ticket_tools(backend: LocalTicketBackend) -> Vec<ToolDefinition> {
|
pub fn ticket_tools(backend: LocalTicketBackend) -> Vec<ToolDefinition> {
|
||||||
vec![
|
vec![
|
||||||
tool_definition::<TicketCreateTool>("TicketCreate", CREATE_DESCRIPTION, backend.clone()),
|
tool_definition::<TicketCreateTool>("TicketCreate", backend.clone()),
|
||||||
tool_definition::<TicketListTool>("TicketList", LIST_DESCRIPTION, backend.clone()),
|
tool_definition::<TicketListTool>("TicketList", backend.clone()),
|
||||||
tool_definition::<TicketShowTool>("TicketShow", SHOW_DESCRIPTION, backend.clone()),
|
tool_definition::<TicketShowTool>("TicketShow", backend.clone()),
|
||||||
tool_definition::<TicketCommentTool>("TicketComment", COMMENT_DESCRIPTION, backend.clone()),
|
tool_definition::<TicketCommentTool>("TicketComment", backend.clone()),
|
||||||
tool_definition::<TicketReviewTool>("TicketReview", REVIEW_DESCRIPTION, backend.clone()),
|
tool_definition::<TicketReviewTool>("TicketReview", backend.clone()),
|
||||||
tool_definition::<TicketIntakeReadyTool>(
|
tool_definition::<TicketIntakeReadyTool>("TicketIntakeReady", backend.clone()),
|
||||||
"TicketIntakeReady",
|
tool_definition::<TicketWorkflowStateTool>("TicketWorkflowState", backend.clone()),
|
||||||
INTAKE_READY_DESCRIPTION,
|
tool_definition::<TicketCloseTool>("TicketClose", backend.clone()),
|
||||||
backend.clone(),
|
tool_definition::<TicketRelationRecordTool>("TicketRelationRecord", backend.clone()),
|
||||||
),
|
tool_definition::<TicketRelationQueryTool>("TicketRelationQuery", backend.clone()),
|
||||||
tool_definition::<TicketWorkflowStateTool>(
|
|
||||||
"TicketWorkflowState",
|
|
||||||
WORKFLOW_STATE_DESCRIPTION,
|
|
||||||
backend.clone(),
|
|
||||||
),
|
|
||||||
tool_definition::<TicketCloseTool>("TicketClose", CLOSE_DESCRIPTION, backend.clone()),
|
|
||||||
tool_definition::<TicketRelationRecordTool>(
|
|
||||||
"TicketRelationRecord",
|
|
||||||
RELATION_RECORD_DESCRIPTION,
|
|
||||||
backend.clone(),
|
|
||||||
),
|
|
||||||
tool_definition::<TicketRelationQueryTool>(
|
|
||||||
"TicketRelationQuery",
|
|
||||||
RELATION_QUERY_DESCRIPTION,
|
|
||||||
backend.clone(),
|
|
||||||
),
|
|
||||||
tool_definition::<TicketOrchestrationPlanRecordTool>(
|
tool_definition::<TicketOrchestrationPlanRecordTool>(
|
||||||
"TicketOrchestrationPlanRecord",
|
"TicketOrchestrationPlanRecord",
|
||||||
ORCHESTRATION_PLAN_RECORD_DESCRIPTION,
|
|
||||||
backend.clone(),
|
backend.clone(),
|
||||||
),
|
),
|
||||||
tool_definition::<TicketOrchestrationPlanQueryTool>(
|
tool_definition::<TicketOrchestrationPlanQueryTool>(
|
||||||
"TicketOrchestrationPlanQuery",
|
"TicketOrchestrationPlanQuery",
|
||||||
ORCHESTRATION_PLAN_QUERY_DESCRIPTION,
|
|
||||||
backend.clone(),
|
backend.clone(),
|
||||||
),
|
),
|
||||||
tool_definition::<TicketDoctorTool>("TicketDoctor", DOCTOR_DESCRIPTION, backend),
|
tool_definition::<TicketDoctorTool>("TicketDoctor", backend),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1412,6 +1426,16 @@ mod tests {
|
||||||
.expect("tool exists")
|
.expect("tool exists")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn tool_description_by_name(backend: LocalTicketBackend, name: &str) -> String {
|
||||||
|
ticket_tools(backend)
|
||||||
|
.into_iter()
|
||||||
|
.find_map(|definition| {
|
||||||
|
let (meta, _) = definition();
|
||||||
|
(meta.name == name).then_some(meta.description)
|
||||||
|
})
|
||||||
|
.expect("tool exists")
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ticket_tool_name_partitions_are_explicit() {
|
fn ticket_tool_name_partitions_are_explicit() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -1463,6 +1487,29 @@ mod tests {
|
||||||
assert!(meta.description.contains("implementation side effects"));
|
assert!(meta.description.contains("implementation side effects"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tool_descriptions_include_configured_ticket_record_language_guidance() {
|
||||||
|
let temp = TempDir::new().unwrap();
|
||||||
|
let backend = backend(&temp).with_record_language(Some("Japanese"));
|
||||||
|
let description = tool_description_by_name(backend, "TicketComment");
|
||||||
|
|
||||||
|
assert!(description.contains("Ticket record language: Japanese"));
|
||||||
|
assert!(description.contains("durable Ticket record and Ticket tool body text"));
|
||||||
|
assert!(description.contains("distinct from worker.language"));
|
||||||
|
assert!(description.contains("memory.language"));
|
||||||
|
assert!(description.contains("Preserve protocol literals"));
|
||||||
|
assert!(description.contains("file paths, commands, logs, identifiers"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tool_descriptions_omit_ticket_record_language_guidance_when_unset() {
|
||||||
|
let temp = TempDir::new().unwrap();
|
||||||
|
let description = tool_description_by_name(backend(&temp), "TicketComment");
|
||||||
|
|
||||||
|
assert!(!description.contains("Ticket record language:"));
|
||||||
|
assert!(!description.contains("worker.language"));
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn ticket_tools_create_list_show_and_doctor() {
|
async fn ticket_tools_create_list_show_and_doctor() {
|
||||||
let temp = TempDir::new().unwrap();
|
let temp = TempDir::new().unwrap();
|
||||||
|
|
@ -2256,7 +2303,6 @@ mod tests {
|
||||||
let temp = TempDir::new().unwrap();
|
let temp = TempDir::new().unwrap();
|
||||||
let create = tool(tool_definition::<TicketCreateTool>(
|
let create = tool(tool_definition::<TicketCreateTool>(
|
||||||
"TicketCreate",
|
"TicketCreate",
|
||||||
CREATE_DESCRIPTION,
|
|
||||||
backend(&temp),
|
backend(&temp),
|
||||||
));
|
));
|
||||||
let _ = create;
|
let _ = create;
|
||||||
|
|
|
||||||
|
|
@ -523,13 +523,6 @@ fn commit_intake_registry_update(update: IntakeRegistryUpdate) -> Option<String>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
enum PanelFocus {
|
|
||||||
GlobalComposer,
|
|
||||||
Row,
|
|
||||||
ItemAction,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
struct PanelDiagnostic {
|
struct PanelDiagnostic {
|
||||||
title: String,
|
title: String,
|
||||||
|
|
@ -585,7 +578,6 @@ pub(crate) struct MultiPodApp {
|
||||||
pub(crate) panel: WorkspacePanelViewModel,
|
pub(crate) panel: WorkspacePanelViewModel,
|
||||||
pub(crate) input: InputBuffer,
|
pub(crate) input: InputBuffer,
|
||||||
selected_row: Option<PanelRowKey>,
|
selected_row: Option<PanelRowKey>,
|
||||||
focus: PanelFocus,
|
|
||||||
composer_target: ComposerTarget,
|
composer_target: ComposerTarget,
|
||||||
notice: Option<String>,
|
notice: Option<String>,
|
||||||
panel_diagnostic: Option<PanelDiagnostic>,
|
panel_diagnostic: Option<PanelDiagnostic>,
|
||||||
|
|
@ -618,7 +610,6 @@ impl MultiPodApp {
|
||||||
panel,
|
panel,
|
||||||
input: InputBuffer::new(),
|
input: InputBuffer::new(),
|
||||||
selected_row: None,
|
selected_row: None,
|
||||||
focus: PanelFocus::GlobalComposer,
|
|
||||||
composer_target: ComposerTarget::Companion,
|
composer_target: ComposerTarget::Companion,
|
||||||
notice: None,
|
notice: None,
|
||||||
panel_diagnostic: None,
|
panel_diagnostic: None,
|
||||||
|
|
@ -825,7 +816,7 @@ impl MultiPodApp {
|
||||||
.clone()
|
.clone()
|
||||||
.or_else(|| row.key_hint.clone())
|
.or_else(|| row.key_hint.clone())
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
"Enter dispatches this Ticket action; Right marks action focus; stale Tickets are re-checked before any mutation."
|
"Enter dispatches this Ticket action after re-checking current Ticket authority."
|
||||||
.to_string()
|
.to_string()
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
@ -919,37 +910,11 @@ impl MultiPodApp {
|
||||||
self.list.selected_name = Some(name.clone());
|
self.list.selected_name = Some(name.clone());
|
||||||
}
|
}
|
||||||
self.selected_row = Some(key);
|
self.selected_row = Some(key);
|
||||||
self.focus = PanelFocus::Row;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_panel_focus(&mut self) {
|
fn clear_panel_selection(&mut self) {
|
||||||
self.selected_row = None;
|
self.selected_row = None;
|
||||||
self.list.selected_name = None;
|
self.list.selected_name = None;
|
||||||
self.focus = PanelFocus::GlobalComposer;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn effective_focus(&self) -> PanelFocus {
|
|
||||||
if self.selected_row.is_none() {
|
|
||||||
PanelFocus::GlobalComposer
|
|
||||||
} else {
|
|
||||||
self.focus
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn focus_item_action(&mut self) {
|
|
||||||
if self.selected_row.is_some() {
|
|
||||||
self.focus = PanelFocus::ItemAction;
|
|
||||||
} else {
|
|
||||||
self.notice = Some("No row selected; use ↑/↓ to select a row first.".to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn focus_selected_row(&mut self) {
|
|
||||||
if self.selected_row.is_some() {
|
|
||||||
self.focus = PanelFocus::Row;
|
|
||||||
} else {
|
|
||||||
self.focus = PanelFocus::GlobalComposer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ensure_composer_target_available(&mut self) {
|
fn ensure_composer_target_available(&mut self) {
|
||||||
|
|
@ -1431,15 +1396,16 @@ impl MultiPodApp {
|
||||||
KeyCode::Char('d') if ctrl => MultiPodAction::Quit,
|
KeyCode::Char('d') if ctrl => MultiPodAction::Quit,
|
||||||
KeyCode::Char('c') if ctrl => MultiPodAction::Quit,
|
KeyCode::Char('c') if ctrl => MultiPodAction::Quit,
|
||||||
KeyCode::Esc => {
|
KeyCode::Esc => {
|
||||||
self.clear_panel_focus();
|
self.clear_panel_selection();
|
||||||
self.notice = Some("Focus: global composer target; Ctrl+C quits.".to_string());
|
self.notice = Some(
|
||||||
|
"Row selection cleared; composer draft and target are unchanged.".to_string(),
|
||||||
|
);
|
||||||
MultiPodAction::None
|
MultiPodAction::None
|
||||||
}
|
}
|
||||||
KeyCode::Tab => {
|
KeyCode::Tab => {
|
||||||
// Completion owns Tab before panel target switching when a
|
// Completion owns Tab before panel target switching when a
|
||||||
// completion popup exists. The workspace panel currently has
|
// completion popup exists. The workspace panel currently has
|
||||||
// no completion source, so this is the target switch path.
|
// no completion source, so this is the target switch path.
|
||||||
self.clear_panel_focus();
|
|
||||||
self.cycle_composer_target();
|
self.cycle_composer_target();
|
||||||
MultiPodAction::None
|
MultiPodAction::None
|
||||||
}
|
}
|
||||||
|
|
@ -1451,22 +1417,6 @@ impl MultiPodApp {
|
||||||
self.select_next();
|
self.select_next();
|
||||||
MultiPodAction::None
|
MultiPodAction::None
|
||||||
}
|
}
|
||||||
KeyCode::Left
|
|
||||||
if self.composer_is_blank() && self.effective_focus() == PanelFocus::ItemAction =>
|
|
||||||
{
|
|
||||||
self.focus_selected_row();
|
|
||||||
MultiPodAction::None
|
|
||||||
}
|
|
||||||
KeyCode::Left
|
|
||||||
if self.composer_is_blank() && self.effective_focus() == PanelFocus::Row =>
|
|
||||||
{
|
|
||||||
self.clear_panel_focus();
|
|
||||||
MultiPodAction::None
|
|
||||||
}
|
|
||||||
KeyCode::Right if self.composer_is_blank() => {
|
|
||||||
self.focus_item_action();
|
|
||||||
MultiPodAction::None
|
|
||||||
}
|
|
||||||
KeyCode::Enter
|
KeyCode::Enter
|
||||||
if self.composer_is_blank()
|
if self.composer_is_blank()
|
||||||
&& self.selected_ticket_action() == Some(NextUserAction::Clarify) =>
|
&& self.selected_ticket_action() == Some(NextUserAction::Clarify) =>
|
||||||
|
|
@ -1482,11 +1432,11 @@ impl MultiPodApp {
|
||||||
.map(MultiPodAction::DispatchTicketAction)
|
.map(MultiPodAction::DispatchTicketAction)
|
||||||
.unwrap_or(MultiPodAction::None)
|
.unwrap_or(MultiPodAction::None)
|
||||||
}
|
}
|
||||||
|
KeyCode::Enter if self.composer_is_blank() => MultiPodAction::Open,
|
||||||
KeyCode::Enter if self.composer_target == ComposerTarget::TicketIntake => self
|
KeyCode::Enter if self.composer_target == ComposerTarget::TicketIntake => self
|
||||||
.prepare_intake_launch()
|
.prepare_intake_launch()
|
||||||
.map(MultiPodAction::LaunchIntake)
|
.map(MultiPodAction::LaunchIntake)
|
||||||
.unwrap_or(MultiPodAction::None),
|
.unwrap_or(MultiPodAction::None),
|
||||||
KeyCode::Enter if self.composer_is_blank() => MultiPodAction::Open,
|
|
||||||
KeyCode::Enter => self
|
KeyCode::Enter => self
|
||||||
.prepare_companion_send()
|
.prepare_companion_send()
|
||||||
.map(MultiPodAction::SendCompanion)
|
.map(MultiPodAction::SendCompanion)
|
||||||
|
|
@ -3469,8 +3419,7 @@ fn open_disabled_reason(entry: &PodListEntry) -> String {
|
||||||
}
|
}
|
||||||
return match live.status {
|
return match live.status {
|
||||||
Some(PodStatus::Running) => {
|
Some(PodStatus::Running) => {
|
||||||
"Selected Pod is running; Enter opens/attaches; Right marks action focus."
|
"Selected Pod is running; Enter opens/attaches for inspection.".to_string()
|
||||||
.to_string()
|
|
||||||
}
|
}
|
||||||
Some(PodStatus::Paused) => {
|
Some(PodStatus::Paused) => {
|
||||||
"Selected Pod is paused; open it explicitly to resume or start a new turn."
|
"Selected Pod is paused; open it explicitly to resume or start a new turn."
|
||||||
|
|
@ -3481,8 +3430,7 @@ fn open_disabled_reason(entry: &PodListEntry) -> String {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if entry.stored.is_some() {
|
if entry.stored.is_some() {
|
||||||
return "Selected Pod is stopped; Enter restores/opens; Right marks action focus."
|
return "Selected Pod is stopped; Enter restores/opens for inspection.".to_string();
|
||||||
.to_string();
|
|
||||||
}
|
}
|
||||||
entry
|
entry
|
||||||
.actions
|
.actions
|
||||||
|
|
@ -3496,7 +3444,7 @@ fn selected_ticket_notice(row: Option<&PanelRow>) -> String {
|
||||||
Some(row) if row.is_ticket_action() => {
|
Some(row) if row.is_ticket_action() => {
|
||||||
let action = row.next_action.map(NextUserAction::label).unwrap_or("View");
|
let action = row.next_action.map(NextUserAction::label).unwrap_or("View");
|
||||||
format!(
|
format!(
|
||||||
"Enter dispatches {action} for Ticket '{}' after re-checking current Ticket authority; Right marks action focus.",
|
"Enter dispatches {action} for Ticket '{}' after re-checking current Ticket authority.",
|
||||||
row.title
|
row.title
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -3764,11 +3712,11 @@ fn draw_title(frame: &mut Frame<'_>, app: &MultiPodApp, area: Rect) {
|
||||||
.composer
|
.composer
|
||||||
.is_available(ComposerTarget::TicketIntake)
|
.is_available(ComposerTarget::TicketIntake)
|
||||||
{
|
{
|
||||||
" Row focus: Enter dispatches row action · Right action focus · Tab target"
|
" Row selection: blank Enter opens/dispatches · text Enter uses target · Tab target"
|
||||||
} else if app.panel.header.ticket_configured {
|
} else if app.panel.header.ticket_configured {
|
||||||
" Row focus: Enter opens/dispatches · Right action focus"
|
" Row selection: blank Enter opens/dispatches · text Enter sends to Companion"
|
||||||
} else {
|
} else {
|
||||||
" Pod-centric view · Row focus: Enter opens · Right action focus"
|
" Pod-centric view · Row selection: blank Enter opens · text Enter sends to Companion"
|
||||||
};
|
};
|
||||||
let mut spans = vec![
|
let mut spans = vec![
|
||||||
Span::styled(
|
Span::styled(
|
||||||
|
|
@ -4111,73 +4059,71 @@ fn draw_target_status(frame: &mut Frame<'_>, app: &MultiPodApp, area: Rect) {
|
||||||
fn target_status_line(app: &MultiPodApp) -> Line<'static> {
|
fn target_status_line(app: &MultiPodApp) -> Line<'static> {
|
||||||
if !app.composer_is_blank() {
|
if !app.composer_is_blank() {
|
||||||
return Line::from(vec![
|
return Line::from(vec![
|
||||||
Span::styled("focus ", Style::default().fg(Color::DarkGray)),
|
Span::styled("composer target ", Style::default().fg(Color::DarkGray)),
|
||||||
Span::styled("global composer", Style::default().fg(Color::Cyan)),
|
|
||||||
Span::styled(" · composer ", Style::default().fg(Color::DarkGray)),
|
|
||||||
Span::styled(
|
Span::styled(
|
||||||
app.composer_target().label(),
|
app.composer_target().label(),
|
||||||
Style::default()
|
Style::default()
|
||||||
.fg(Color::Green)
|
.fg(Color::Green)
|
||||||
.add_modifier(Modifier::BOLD),
|
.add_modifier(Modifier::BOLD),
|
||||||
),
|
),
|
||||||
Span::styled(" · Enter ", Style::default().fg(Color::DarkGray)),
|
Span::styled(" · draft Enter ", Style::default().fg(Color::DarkGray)),
|
||||||
Span::styled(
|
Span::styled(
|
||||||
composer_enter_status_text(app),
|
composer_enter_status_text(app),
|
||||||
Style::default().fg(Color::Green),
|
Style::default().fg(Color::Green),
|
||||||
),
|
),
|
||||||
|
Span::styled(
|
||||||
|
" · row selection waits until composer is blank",
|
||||||
|
Style::default().fg(Color::DarkGray),
|
||||||
|
),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let focus_label = match app.effective_focus() {
|
|
||||||
PanelFocus::GlobalComposer => "global composer",
|
|
||||||
PanelFocus::Row => "selected row",
|
|
||||||
PanelFocus::ItemAction => "item action",
|
|
||||||
};
|
|
||||||
if let Some(row) = app
|
if let Some(row) = app
|
||||||
.selected_panel_row()
|
.selected_panel_row()
|
||||||
.filter(|row| row.is_ticket_action())
|
.filter(|row| row.is_ticket_action())
|
||||||
{
|
{
|
||||||
let action = row.next_action.map(NextUserAction::label).unwrap_or("View");
|
let action = row.next_action.map(NextUserAction::label).unwrap_or("View");
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
Span::styled("focus ", Style::default().fg(Color::DarkGray)),
|
Span::styled("composer target ", Style::default().fg(Color::DarkGray)),
|
||||||
Span::styled(focus_label, Style::default().fg(Color::Cyan)),
|
|
||||||
Span::styled(" · composer ", Style::default().fg(Color::DarkGray)),
|
|
||||||
Span::styled(
|
Span::styled(
|
||||||
app.composer_target().label(),
|
app.composer_target().label(),
|
||||||
Style::default()
|
Style::default()
|
||||||
.fg(Color::Magenta)
|
.fg(Color::Magenta)
|
||||||
.add_modifier(Modifier::BOLD),
|
.add_modifier(Modifier::BOLD),
|
||||||
),
|
),
|
||||||
Span::styled(" · ticket ", Style::default().fg(Color::DarkGray)),
|
Span::styled(" · selected Ticket ", Style::default().fg(Color::DarkGray)),
|
||||||
Span::styled(row.status.clone(), panel_priority_style(row.priority)),
|
Span::styled(row.status.clone(), panel_priority_style(row.priority)),
|
||||||
Span::styled(" · action ", Style::default().fg(Color::DarkGray)),
|
Span::styled(" · blank Enter ", Style::default().fg(Color::DarkGray)),
|
||||||
Span::styled(action, Style::default().fg(Color::Magenta)),
|
Span::styled(action, Style::default().fg(Color::Magenta)),
|
||||||
])
|
])
|
||||||
} else if let Some(entry) = app.selected_pod_entry() {
|
} else if let Some(entry) = app.selected_pod_entry() {
|
||||||
let (status, status_style) = row_status_label(entry);
|
let (status, status_style) = row_status_label(entry);
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
Span::styled("focus ", Style::default().fg(Color::DarkGray)),
|
Span::styled("composer target ", Style::default().fg(Color::DarkGray)),
|
||||||
Span::styled(focus_label, Style::default().fg(Color::Cyan)),
|
|
||||||
Span::styled(" · composer ", Style::default().fg(Color::DarkGray)),
|
|
||||||
Span::styled(
|
Span::styled(
|
||||||
app.composer_target().label(),
|
app.composer_target().label(),
|
||||||
Style::default()
|
Style::default()
|
||||||
.fg(Color::Green)
|
.fg(Color::Green)
|
||||||
.add_modifier(Modifier::BOLD),
|
.add_modifier(Modifier::BOLD),
|
||||||
),
|
),
|
||||||
Span::styled(" · pod ", Style::default().fg(Color::DarkGray)),
|
Span::styled(" · selected Pod ", Style::default().fg(Color::DarkGray)),
|
||||||
Span::styled(status.to_string(), status_style),
|
Span::styled(status.to_string(), status_style),
|
||||||
|
Span::styled(
|
||||||
|
" · blank Enter open/attach",
|
||||||
|
Style::default().fg(Color::DarkGray),
|
||||||
|
),
|
||||||
])
|
])
|
||||||
} else {
|
} else {
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
Span::styled("focus ", Style::default().fg(Color::DarkGray)),
|
Span::styled("composer target ", Style::default().fg(Color::DarkGray)),
|
||||||
Span::styled(focus_label, Style::default().fg(Color::Cyan)),
|
|
||||||
Span::styled(" · composer ", Style::default().fg(Color::DarkGray)),
|
|
||||||
Span::styled(
|
Span::styled(
|
||||||
app.composer_target().label(),
|
app.composer_target().label(),
|
||||||
Style::default().fg(Color::DarkGray),
|
Style::default().fg(Color::DarkGray),
|
||||||
),
|
),
|
||||||
Span::styled(" · no selection", Style::default().fg(Color::DarkGray)),
|
Span::styled(
|
||||||
|
" · no row selected · ↑/↓ selects a row",
|
||||||
|
Style::default().fg(Color::DarkGray),
|
||||||
|
),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4311,11 +4257,11 @@ fn actionbar_left_text(app: &MultiPodApp) -> String {
|
||||||
} else {
|
} else {
|
||||||
match app.composer_target() {
|
match app.composer_target() {
|
||||||
ComposerTarget::Companion => {
|
ComposerTarget::Companion => {
|
||||||
"Companion target pending; non-empty Enter keeps draft and reports a diagnostic"
|
"Composer target: Companion; type text to send, or use ↑/↓ then blank Enter for rows"
|
||||||
.to_string()
|
.to_string()
|
||||||
}
|
}
|
||||||
ComposerTarget::TicketIntake => {
|
ComposerTarget::TicketIntake => {
|
||||||
"Ticket Intake target: Enter launches Intake with composer text".to_string()
|
"Composer target: Ticket Intake; type a request, then Enter launches Intake".to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4325,25 +4271,25 @@ fn actionbar_right_text(app: &MultiPodApp) -> &'static str {
|
||||||
if app.panel_diagnostic_open {
|
if app.panel_diagnostic_open {
|
||||||
"F2/Esc close details Ctrl+C quit"
|
"F2/Esc close details Ctrl+C quit"
|
||||||
} else if app.panel_diagnostic.is_some() {
|
} else if app.panel_diagnostic.is_some() {
|
||||||
"F2 details ↑/↓ row Enter row action/open Right action focus Tab target Esc composer Ctrl+C quit"
|
"F2 details ↑/↓ select row Enter selected row Tab target Esc clear selection Left/Right cursor Ctrl+C quit"
|
||||||
} else if !app.composer_is_blank() {
|
} else if !app.composer_is_blank() {
|
||||||
if app
|
if app
|
||||||
.panel
|
.panel
|
||||||
.composer
|
.composer
|
||||||
.is_available(ComposerTarget::TicketIntake)
|
.is_available(ComposerTarget::TicketIntake)
|
||||||
{
|
{
|
||||||
"↑/↓ row Enter composer target Tab target Esc composer Ctrl+C quit"
|
"↑/↓ draft lines Left/Right cursor Enter composer target Tab target Esc clear selection Ctrl+C quit"
|
||||||
} else {
|
} else {
|
||||||
"↑/↓ row Enter composer target Esc composer Ctrl+C quit"
|
"↑/↓ draft lines Left/Right cursor Enter composer target Esc clear selection Ctrl+C quit"
|
||||||
}
|
}
|
||||||
} else if app
|
} else if app
|
||||||
.panel
|
.panel
|
||||||
.composer
|
.composer
|
||||||
.is_available(ComposerTarget::TicketIntake)
|
.is_available(ComposerTarget::TicketIntake)
|
||||||
{
|
{
|
||||||
"↑/↓ row Enter row action/open Right action focus Tab target Esc composer Ctrl+C quit"
|
"↑/↓ select row Enter selected row Tab target Esc clear selection Left/Right cursor Ctrl+C quit"
|
||||||
} else {
|
} else {
|
||||||
"↑/↓ row Enter row action/open Right action focus Esc composer Ctrl+C quit"
|
"↑/↓ select row Enter selected row Esc clear selection Left/Right cursor Ctrl+C quit"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5248,10 +5194,11 @@ mod tests {
|
||||||
assert!(actionbar_left.contains("Companion target: Enter sends composer text"));
|
assert!(actionbar_left.contains("Companion target: Enter sends composer text"));
|
||||||
assert!(actionbar_right.contains("Enter composer target"));
|
assert!(actionbar_right.contains("Enter composer target"));
|
||||||
assert!(!actionbar_left.contains("Queue"));
|
assert!(!actionbar_left.contains("Queue"));
|
||||||
assert!(!actionbar_right.contains("row action/open"));
|
assert!(!actionbar_right.contains("selected row"));
|
||||||
assert!(target_status.contains("focus global composer"));
|
assert!(target_status.contains("composer target Companion"));
|
||||||
assert!(target_status.contains("Enter send composer text to workspace Companion"));
|
assert!(target_status.contains("draft Enter send composer text to workspace Companion"));
|
||||||
assert!(!target_status.contains("action Queue"));
|
assert!(target_status.contains("row selection waits until composer is blank"));
|
||||||
|
assert!(!target_status.contains("blank Enter Queue"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -6162,21 +6109,23 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn multi_esc_clears_panel_focus_without_quitting() {
|
fn multi_esc_clears_row_selection_without_quitting_and_preserves_draft() {
|
||||||
let mut app = ticket_enabled_app(vec![live_info("alpha", PodStatus::Idle)]);
|
let mut app = ticket_enabled_app(vec![live_info("alpha", PodStatus::Idle)]);
|
||||||
|
app.input.insert_str("draft message");
|
||||||
|
|
||||||
assert!(app.selected_row.is_some());
|
assert!(app.selected_row.is_some());
|
||||||
assert!(matches!(
|
|
||||||
app.handle_key(key(KeyCode::Right)),
|
|
||||||
MultiPodAction::None
|
|
||||||
));
|
|
||||||
assert_eq!(app.effective_focus(), PanelFocus::ItemAction);
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
app.handle_key(key(KeyCode::Esc)),
|
app.handle_key(key(KeyCode::Esc)),
|
||||||
MultiPodAction::None
|
MultiPodAction::None
|
||||||
));
|
));
|
||||||
assert!(app.selected_row.is_none());
|
assert!(app.selected_row.is_none());
|
||||||
assert_eq!(app.effective_focus(), PanelFocus::GlobalComposer);
|
assert_eq!(input_text(&app), "draft message");
|
||||||
|
assert!(
|
||||||
|
app.notice
|
||||||
|
.as_deref()
|
||||||
|
.unwrap()
|
||||||
|
.contains("Row selection cleared")
|
||||||
|
);
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
app.handle_key(modified_key(KeyCode::Char('c'), KeyModifiers::CONTROL)),
|
app.handle_key(modified_key(KeyCode::Char('c'), KeyModifiers::CONTROL)),
|
||||||
MultiPodAction::Quit
|
MultiPodAction::Quit
|
||||||
|
|
@ -6189,6 +6138,7 @@ mod tests {
|
||||||
app.input.insert_str("draft intake request");
|
app.input.insert_str("draft intake request");
|
||||||
|
|
||||||
assert!(matches!(app.composer_target(), ComposerTarget::Companion));
|
assert!(matches!(app.composer_target(), ComposerTarget::Companion));
|
||||||
|
let selected_before = app.selected_row.clone();
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
app.handle_key(key(KeyCode::Tab)),
|
app.handle_key(key(KeyCode::Tab)),
|
||||||
MultiPodAction::None
|
MultiPodAction::None
|
||||||
|
|
@ -6198,6 +6148,7 @@ mod tests {
|
||||||
app.composer_target(),
|
app.composer_target(),
|
||||||
ComposerTarget::TicketIntake
|
ComposerTarget::TicketIntake
|
||||||
));
|
));
|
||||||
|
assert_eq!(app.selected_row, selected_before);
|
||||||
assert_eq!(input_text(&app), "draft intake request");
|
assert_eq!(input_text(&app), "draft intake request");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -6233,14 +6184,14 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn multi_ticket_intake_rejects_empty_input() {
|
fn multi_blank_ticket_intake_enter_uses_selected_row_and_preserves_input() {
|
||||||
let mut app = ticket_enabled_app(vec![live_info("idle", PodStatus::Idle)]);
|
let mut app = ticket_enabled_app(vec![live_info("idle", PodStatus::Idle)]);
|
||||||
app.cycle_composer_target();
|
app.cycle_composer_target();
|
||||||
app.input.insert_str(" \n\t");
|
app.input.insert_str(" \n\t");
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
app.handle_key(key(KeyCode::Enter)),
|
app.handle_key(key(KeyCode::Enter)),
|
||||||
MultiPodAction::None
|
MultiPodAction::Open
|
||||||
));
|
));
|
||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
|
|
@ -6249,7 +6200,12 @@ mod tests {
|
||||||
));
|
));
|
||||||
assert!(!app.sending);
|
assert!(!app.sending);
|
||||||
assert_eq!(input_text(&app), " \n\t");
|
assert_eq!(input_text(&app), " \n\t");
|
||||||
assert!(app.notice.as_deref().unwrap().contains("input is empty"));
|
assert!(
|
||||||
|
!app.notice
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.contains("input is empty")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -6531,7 +6487,6 @@ mod tests {
|
||||||
panel,
|
panel,
|
||||||
input: InputBuffer::new(),
|
input: InputBuffer::new(),
|
||||||
selected_row: None,
|
selected_row: None,
|
||||||
focus: PanelFocus::GlobalComposer,
|
|
||||||
composer_target: ComposerTarget::Companion,
|
composer_target: ComposerTarget::Companion,
|
||||||
notice: None,
|
notice: None,
|
||||||
panel_diagnostic: None,
|
panel_diagnostic: None,
|
||||||
|
|
|
||||||
|
|
@ -894,7 +894,7 @@ fn pod_row(entry: &PodListEntry) -> PanelRow {
|
||||||
ticket: None,
|
ticket: None,
|
||||||
related_pods: Vec::new(),
|
related_pods: Vec::new(),
|
||||||
disabled_reason: entry.actions.disabled_reason.clone(),
|
disabled_reason: entry.actions.disabled_reason.clone(),
|
||||||
key_hint: Some("Enter opens/attaches; Right marks action focus".to_string()),
|
key_hint: Some("Enter opens/attaches for inspection".to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user