From 05fe1f6fb3f568ed2aaba6e5827a35e7d61307d1 Mon Sep 17 00:00:00 2001 From: Hare Date: Fri, 12 Jun 2026 23:51:11 +0900 Subject: [PATCH 01/41] ticket: accept companion progress notify --- .yoi/tickets/00001KTTW04W2/item.md | 4 ++-- .yoi/tickets/00001KTTW04W2/thread.md | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/.yoi/tickets/00001KTTW04W2/item.md b/.yoi/tickets/00001KTTW04W2/item.md index 148b3a92..9ed47c95 100644 --- a/.yoi/tickets/00001KTTW04W2/item.md +++ b/.yoi/tickets/00001KTTW04W2/item.md @@ -1,8 +1,8 @@ --- title: 'Orchestrator進捗をAutoKickなしでCompanionへ通知する' -state: 'queued' +state: 'inprogress' created_at: '2026-06-11T08:15:24Z' -updated_at: '2026-06-11T16:07:34Z' +updated_at: '2026-06-12T14:51:03Z' assignee: null queued_by: 'workspace-panel' queued_at: '2026-06-11T10:31:56Z' diff --git a/.yoi/tickets/00001KTTW04W2/thread.md b/.yoi/tickets/00001KTTW04W2/thread.md index a30e826b..8fbc51ed 100644 --- a/.yoi/tickets/00001KTTW04W2/thread.md +++ b/.yoi/tickets/00001KTTW04W2/thread.md @@ -111,3 +111,31 @@ Next action: - ユーザー/上位 Orchestrator から実装開始の explicit follow-up が来たら、side effect 前に TicketShow / relation / orchestration plan / git/worktree state を再確認し、問題なければ `queued -> inprogress` を記録してから worktree 作成と sibling coder/reviewer routing に進む。 --- + + + +## Decision + +Routing follow-up: implementation start authorized by user. + +Recheck summary: +- Ticket remains `queued` and previously recorded `implementation_ready` still applies. +- TicketRelationQuery: no unresolved `depends_on` / incoming `blocks` for this Ticket. +- TicketOrchestrationPlan: accepted plan `orch-plan-20260611-160703-1` names branch/worktree and defers implementation until explicit follow-up; that follow-up has now arrived. +- Orchestrator worktree is clean. +- Visible active work: `00001KTVJFT6F` has coder `yoi-coder-panel-focus-model` running on a separate branch/worktree. There may be minor overlap in `crates/tui/src/multi_pod.rs` around Panel UI, so coder/reviewer must keep the Panel freshness change narrow and integration will recheck conflicts before merge. + +Decision: +- Accept this Ticket now and proceed to `queued -> inprogress` before worktree/Pod side effects. +- Use existing accepted plan branch `ticket/orchestrator-progress-companion-notify` and worktree `/home/hare/Projects/yoi/.worktree/orchestrator-progress-companion-notify`. +- Continue to avoid root/original workspace operations; implementation side effects are limited to the child worktree and sibling Pods. + +--- + + + +## State changed + +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. + +--- From 1f2bd8a84062f8078f275711add3d9a8d5339791 Mon Sep 17 00:00:00 2001 From: Hare Date: Fri, 12 Jun 2026 23:51:37 +0900 Subject: [PATCH 02/41] ticket: record companion notify worktree --- .yoi/tickets/00001KTTW04W2/thread.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.yoi/tickets/00001KTTW04W2/thread.md b/.yoi/tickets/00001KTTW04W2/thread.md index 8fbc51ed..450bfd67 100644 --- a/.yoi/tickets/00001KTTW04W2/thread.md +++ b/.yoi/tickets/00001KTTW04W2/thread.md @@ -139,3 +139,19 @@ 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. --- + + + +## 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. + +--- From 3d3c6d6d45c261737264c3ec5b0cb10330ce4e20 Mon Sep 17 00:00:00 2001 From: Hare Date: Fri, 12 Jun 2026 23:51:45 +0900 Subject: [PATCH 03/41] ticket: refresh companion notify state --- .yoi/tickets/00001KTTW04W2/item.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KTTW04W2/item.md b/.yoi/tickets/00001KTTW04W2/item.md index 9ed47c95..49e0703f 100644 --- a/.yoi/tickets/00001KTTW04W2/item.md +++ b/.yoi/tickets/00001KTTW04W2/item.md @@ -2,7 +2,7 @@ title: 'Orchestrator進捗をAutoKickなしでCompanionへ通知する' state: 'inprogress' created_at: '2026-06-11T08:15:24Z' -updated_at: '2026-06-12T14:51:03Z' +updated_at: '2026-06-12T14:51:31Z' assignee: null queued_by: 'workspace-panel' queued_at: '2026-06-11T10:31:56Z' From 365051a440b40780ab74305b85b1c71cdf766e7c Mon Sep 17 00:00:00 2001 From: Hare Date: Fri, 12 Jun 2026 23:52:39 +0900 Subject: [PATCH 04/41] ticket: delegate companion notify coder --- .yoi/tickets/00001KTTW04W2/item.md | 2 +- .yoi/tickets/00001KTTW04W2/thread.md | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KTTW04W2/item.md b/.yoi/tickets/00001KTTW04W2/item.md index 49e0703f..c18ec347 100644 --- a/.yoi/tickets/00001KTTW04W2/item.md +++ b/.yoi/tickets/00001KTTW04W2/item.md @@ -2,7 +2,7 @@ title: 'Orchestrator進捗をAutoKickなしでCompanionへ通知する' state: 'inprogress' created_at: '2026-06-11T08:15:24Z' -updated_at: '2026-06-12T14:51:31Z' +updated_at: '2026-06-12T14:52:34Z' assignee: null queued_by: 'workspace-panel' queued_at: '2026-06-11T10:31:56Z' diff --git a/.yoi/tickets/00001KTTW04W2/thread.md b/.yoi/tickets/00001KTTW04W2/thread.md index 450bfd67..b363b9d4 100644 --- a/.yoi/tickets/00001KTTW04W2/thread.md +++ b/.yoi/tickets/00001KTTW04W2/thread.md @@ -155,3 +155,19 @@ Implementation worktree created for multi-agent handoff. - Next: spawn sibling coder with narrow write scope to the implementation worktree. Reviewer will be started after coder evidence is available. --- + + + +## 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. + +--- From 76d358e8249931d8c8fb2fb10a2f57e433fe678d Mon Sep 17 00:00:00 2001 From: Hare Date: Fri, 12 Jun 2026 23:54:31 +0900 Subject: [PATCH 05/41] ticket: accept ticket language guidance --- .../artifacts/orchestration-plan.jsonl | 1 + .yoi/tickets/00001KTVJGC0Y/item.md | 4 +- .yoi/tickets/00001KTVJGC0Y/thread.md | 90 +++++++++++++++++++ 3 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 .yoi/tickets/00001KTVJGC0Y/artifacts/orchestration-plan.jsonl diff --git a/.yoi/tickets/00001KTVJGC0Y/artifacts/orchestration-plan.jsonl b/.yoi/tickets/00001KTVJGC0Y/artifacts/orchestration-plan.jsonl new file mode 100644 index 00000000..d8d43aa1 --- /dev/null +++ b/.yoi/tickets/00001KTVJGC0Y/artifacts/orchestration-plan.jsonl @@ -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"} diff --git a/.yoi/tickets/00001KTVJGC0Y/item.md b/.yoi/tickets/00001KTVJGC0Y/item.md index ecf699a6..f0697ba8 100644 --- a/.yoi/tickets/00001KTVJGC0Y/item.md +++ b/.yoi/tickets/00001KTVJGC0Y/item.md @@ -1,8 +1,8 @@ --- title: 'Ticket language guidance must apply to all Ticket tool users' -state: 'queued' +state: 'inprogress' created_at: '2026-06-11T14:48:44Z' -updated_at: '2026-06-12T14:49:39Z' +updated_at: '2026-06-12T14:54:22Z' assignee: null readiness: 'implementation_ready' risk_flags: ['prompt-context', 'tool-description', 'feature-boundary', 'ticket-language', 'companion'] diff --git a/.yoi/tickets/00001KTVJGC0Y/thread.md b/.yoi/tickets/00001KTVJGC0Y/thread.md index aba14d89..0d8e5385 100644 --- a/.yoi/tickets/00001KTVJGC0Y/thread.md +++ b/.yoi/tickets/00001KTVJGC0Y/thread.md @@ -29,4 +29,94 @@ LocalTicketBackend によって作成されました。 Ticket を `workspace-panel` が queued にしました。 +--- + + + +## 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 で起動する。 + +--- + + + +## 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`. + --- From e34496ff05fe9969133f9529c261575a7e5d66d6 Mon Sep 17 00:00:00 2001 From: Hare Date: Fri, 12 Jun 2026 23:54:54 +0900 Subject: [PATCH 06/41] ticket: record language guidance worktree --- .yoi/tickets/00001KTVJGC0Y/thread.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.yoi/tickets/00001KTVJGC0Y/thread.md b/.yoi/tickets/00001KTVJGC0Y/thread.md index 0d8e5385..57220079 100644 --- a/.yoi/tickets/00001KTVJGC0Y/thread.md +++ b/.yoi/tickets/00001KTVJGC0Y/thread.md @@ -120,3 +120,19 @@ Next action: 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`. --- + + + +## 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. + +--- From 97719f6c475f176a1031a57f503cd4a830bac52f Mon Sep 17 00:00:00 2001 From: Hare Date: Fri, 12 Jun 2026 23:55:05 +0900 Subject: [PATCH 07/41] ticket: refresh language guidance state --- .yoi/tickets/00001KTVJGC0Y/item.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KTVJGC0Y/item.md b/.yoi/tickets/00001KTVJGC0Y/item.md index f0697ba8..54611538 100644 --- a/.yoi/tickets/00001KTVJGC0Y/item.md +++ b/.yoi/tickets/00001KTVJGC0Y/item.md @@ -2,7 +2,7 @@ title: 'Ticket language guidance must apply to all Ticket tool users' state: 'inprogress' created_at: '2026-06-11T14:48:44Z' -updated_at: '2026-06-12T14:54:22Z' +updated_at: '2026-06-12T14:54:49Z' assignee: null readiness: 'implementation_ready' risk_flags: ['prompt-context', 'tool-description', 'feature-boundary', 'ticket-language', 'companion'] From dcd61410ad1543c1b9ca92ea0e8a11aea491f89e Mon Sep 17 00:00:00 2001 From: Hare Date: Fri, 12 Jun 2026 23:55:50 +0900 Subject: [PATCH 08/41] ticket: delegate language guidance coder --- .yoi/tickets/00001KTVJGC0Y/item.md | 2 +- .yoi/tickets/00001KTVJGC0Y/thread.md | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KTVJGC0Y/item.md b/.yoi/tickets/00001KTVJGC0Y/item.md index 54611538..c6e60fd1 100644 --- a/.yoi/tickets/00001KTVJGC0Y/item.md +++ b/.yoi/tickets/00001KTVJGC0Y/item.md @@ -2,7 +2,7 @@ title: 'Ticket language guidance must apply to all Ticket tool users' state: 'inprogress' created_at: '2026-06-11T14:48:44Z' -updated_at: '2026-06-12T14:54:49Z' +updated_at: '2026-06-12T14:55:42Z' assignee: null readiness: 'implementation_ready' risk_flags: ['prompt-context', 'tool-description', 'feature-boundary', 'ticket-language', 'companion'] diff --git a/.yoi/tickets/00001KTVJGC0Y/thread.md b/.yoi/tickets/00001KTVJGC0Y/thread.md index 57220079..c2402c5f 100644 --- a/.yoi/tickets/00001KTVJGC0Y/thread.md +++ b/.yoi/tickets/00001KTVJGC0Y/thread.md @@ -136,3 +136,19 @@ Implementation worktree created for multi-agent handoff. - Next: spawn sibling coder with narrow write scope to the implementation worktree. Reviewer will be started after coder evidence is available. --- + + + +## 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. + +--- From a9b1ab302d4894b6de7ca41cb742b37fe39ae89e Mon Sep 17 00:00:00 2001 From: Hare Date: Fri, 12 Jun 2026 23:56:24 +0900 Subject: [PATCH 09/41] ticket: defer idle rekick for capacity --- .../artifacts/orchestration-plan.jsonl | 1 + .yoi/tickets/00001KTJXS31R/thread.md | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 .yoi/tickets/00001KTJXS31R/artifacts/orchestration-plan.jsonl diff --git a/.yoi/tickets/00001KTJXS31R/artifacts/orchestration-plan.jsonl b/.yoi/tickets/00001KTJXS31R/artifacts/orchestration-plan.jsonl new file mode 100644 index 00000000..12d4e313 --- /dev/null +++ b/.yoi/tickets/00001KTJXS31R/artifacts/orchestration-plan.jsonl @@ -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"} diff --git a/.yoi/tickets/00001KTJXS31R/thread.md b/.yoi/tickets/00001KTJXS31R/thread.md index a392abba..f53dd0d9 100644 --- a/.yoi/tickets/00001KTJXS31R/thread.md +++ b/.yoi/tickets/00001KTJXS31R/thread.md @@ -126,4 +126,26 @@ Intake refinement により、既存の plan store 実装との差分、current Ticket を `workspace-panel` が queued にしました。 +--- + + + +## 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. + --- From 1ead5f2597df883db137b9fa4fa0eecf4cd3536c Mon Sep 17 00:00:00 2001 From: Hare Date: Fri, 12 Jun 2026 23:56:30 +0900 Subject: [PATCH 10/41] ticket: refresh idle rekick queue note --- .yoi/tickets/00001KTJXS31R/item.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KTJXS31R/item.md b/.yoi/tickets/00001KTJXS31R/item.md index e5b6373b..61def2cb 100644 --- a/.yoi/tickets/00001KTJXS31R/item.md +++ b/.yoi/tickets/00001KTJXS31R/item.md @@ -2,7 +2,7 @@ title: "Orchestrator Idle 時の queued Ticket 見落としを防ぐ" state: 'queued' 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_at: '2026-06-12T14:49:40Z' --- From c5ef6f794f310a4bbbaeacfd7fda0aeb7bb13e8e Mon Sep 17 00:00:00 2001 From: Hare Date: Fri, 12 Jun 2026 23:59:05 +0900 Subject: [PATCH 11/41] tui: clarify panel composer target and row selection --- crates/tui/src/multi_pod.rs | 173 +++++++++++------------------- crates/tui/src/workspace_panel.rs | 2 +- 2 files changed, 65 insertions(+), 110 deletions(-) diff --git a/crates/tui/src/multi_pod.rs b/crates/tui/src/multi_pod.rs index 3685086d..d7fcc1ef 100644 --- a/crates/tui/src/multi_pod.rs +++ b/crates/tui/src/multi_pod.rs @@ -516,13 +516,6 @@ fn commit_intake_registry_update(update: IntakeRegistryUpdate) -> Option } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum PanelFocus { - GlobalComposer, - Row, - ItemAction, -} - #[derive(Clone, Debug, PartialEq, Eq)] struct PanelDiagnostic { title: String, @@ -534,7 +527,6 @@ pub(crate) struct MultiPodApp { pub(crate) panel: WorkspacePanelViewModel, pub(crate) input: InputBuffer, selected_row: Option, - focus: PanelFocus, composer_target: ComposerTarget, notice: Option, panel_diagnostic: Option, @@ -566,7 +558,6 @@ impl MultiPodApp { panel, input: InputBuffer::new(), selected_row: None, - focus: PanelFocus::GlobalComposer, composer_target: ComposerTarget::Companion, notice: None, panel_diagnostic: None, @@ -728,7 +719,7 @@ impl MultiPodApp { .clone() .or_else(|| row.key_hint.clone()) .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() }), ); @@ -822,37 +813,11 @@ impl MultiPodApp { self.list.selected_name = Some(name.clone()); } 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.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) { @@ -1334,15 +1299,16 @@ impl MultiPodApp { KeyCode::Char('d') if ctrl => MultiPodAction::Quit, KeyCode::Char('c') if ctrl => MultiPodAction::Quit, KeyCode::Esc => { - self.clear_panel_focus(); - self.notice = Some("Focus: global composer target; Ctrl+C quits.".to_string()); + self.clear_panel_selection(); + self.notice = Some( + "Row selection cleared; composer draft and target are unchanged.".to_string(), + ); MultiPodAction::None } KeyCode::Tab => { // Completion owns Tab before panel target switching when a // completion popup exists. The workspace panel currently has // no completion source, so this is the target switch path. - self.clear_panel_focus(); self.cycle_composer_target(); MultiPodAction::None } @@ -1354,22 +1320,6 @@ impl MultiPodApp { self.select_next(); 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 if self.composer_is_blank() && self.selected_ticket_action() == Some(NextUserAction::Clarify) => @@ -1385,11 +1335,11 @@ impl MultiPodApp { .map(MultiPodAction::DispatchTicketAction) .unwrap_or(MultiPodAction::None) } + KeyCode::Enter if self.composer_is_blank() => MultiPodAction::Open, KeyCode::Enter if self.composer_target == ComposerTarget::TicketIntake => self .prepare_intake_launch() .map(MultiPodAction::LaunchIntake) .unwrap_or(MultiPodAction::None), - KeyCode::Enter if self.composer_is_blank() => MultiPodAction::Open, KeyCode::Enter => self .prepare_companion_send() .map(MultiPodAction::SendCompanion) @@ -3214,8 +3164,7 @@ fn open_disabled_reason(entry: &PodListEntry) -> String { } return match live.status { Some(PodStatus::Running) => { - "Selected Pod is running; Enter opens/attaches; Right marks action focus." - .to_string() + "Selected Pod is running; Enter opens/attaches for inspection.".to_string() } Some(PodStatus::Paused) => { "Selected Pod is paused; open it explicitly to resume or start a new turn." @@ -3226,8 +3175,7 @@ fn open_disabled_reason(entry: &PodListEntry) -> String { }; } if entry.stored.is_some() { - return "Selected Pod is stopped; Enter restores/opens; Right marks action focus." - .to_string(); + return "Selected Pod is stopped; Enter restores/opens for inspection.".to_string(); } entry .actions @@ -3241,7 +3189,7 @@ fn selected_ticket_notice(row: Option<&PanelRow>) -> String { Some(row) if row.is_ticket_action() => { let action = row.next_action.map(NextUserAction::label).unwrap_or("View"); 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 ) } @@ -3509,11 +3457,11 @@ fn draw_title(frame: &mut Frame<'_>, app: &MultiPodApp, area: Rect) { .composer .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 { - " Row focus: Enter opens/dispatches · Right action focus" + " Row selection: blank Enter opens/dispatches · text Enter sends to Companion" } 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![ Span::styled( @@ -3850,73 +3798,71 @@ fn draw_target_status(frame: &mut Frame<'_>, app: &MultiPodApp, area: Rect) { fn target_status_line(app: &MultiPodApp) -> Line<'static> { if !app.composer_is_blank() { return Line::from(vec![ - Span::styled("focus ", Style::default().fg(Color::DarkGray)), - Span::styled("global composer", Style::default().fg(Color::Cyan)), - Span::styled(" · composer ", Style::default().fg(Color::DarkGray)), + Span::styled("composer target ", Style::default().fg(Color::DarkGray)), Span::styled( app.composer_target().label(), Style::default() .fg(Color::Green) .add_modifier(Modifier::BOLD), ), - Span::styled(" · Enter ", Style::default().fg(Color::DarkGray)), + Span::styled(" · draft Enter ", Style::default().fg(Color::DarkGray)), Span::styled( composer_enter_status_text(app), 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 .selected_panel_row() .filter(|row| row.is_ticket_action()) { let action = row.next_action.map(NextUserAction::label).unwrap_or("View"); Line::from(vec![ - Span::styled("focus ", Style::default().fg(Color::DarkGray)), - Span::styled(focus_label, Style::default().fg(Color::Cyan)), - Span::styled(" · composer ", Style::default().fg(Color::DarkGray)), + Span::styled("composer target ", Style::default().fg(Color::DarkGray)), Span::styled( app.composer_target().label(), Style::default() .fg(Color::Magenta) .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(" · action ", Style::default().fg(Color::DarkGray)), + Span::styled(" · blank Enter ", Style::default().fg(Color::DarkGray)), Span::styled(action, Style::default().fg(Color::Magenta)), ]) } else if let Some(entry) = app.selected_pod_entry() { let (status, status_style) = row_status_label(entry); Line::from(vec![ - Span::styled("focus ", Style::default().fg(Color::DarkGray)), - Span::styled(focus_label, Style::default().fg(Color::Cyan)), - Span::styled(" · composer ", Style::default().fg(Color::DarkGray)), + Span::styled("composer target ", Style::default().fg(Color::DarkGray)), Span::styled( app.composer_target().label(), Style::default() .fg(Color::Green) .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( + " · blank Enter open/attach", + Style::default().fg(Color::DarkGray), + ), ]) } else { Line::from(vec![ - Span::styled("focus ", Style::default().fg(Color::DarkGray)), - Span::styled(focus_label, Style::default().fg(Color::Cyan)), - Span::styled(" · composer ", Style::default().fg(Color::DarkGray)), + Span::styled("composer target ", Style::default().fg(Color::DarkGray)), Span::styled( app.composer_target().label(), 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), + ), ]) } } @@ -4050,11 +3996,11 @@ fn actionbar_left_text(app: &MultiPodApp) -> String { } else { match app.composer_target() { 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() } 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() } } } @@ -4064,25 +4010,25 @@ fn actionbar_right_text(app: &MultiPodApp) -> &'static str { if app.panel_diagnostic_open { "F2/Esc close details Ctrl+C quit" } 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() { if app .panel .composer .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 { - "↑/↓ 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 .panel .composer .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 { - "↑/↓ 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" } } @@ -4809,10 +4755,11 @@ mod tests { assert!(actionbar_left.contains("Companion target: Enter sends composer text")); assert!(actionbar_right.contains("Enter composer target")); assert!(!actionbar_left.contains("Queue")); - assert!(!actionbar_right.contains("row action/open")); - assert!(target_status.contains("focus global composer")); - assert!(target_status.contains("Enter send composer text to workspace Companion")); - assert!(!target_status.contains("action Queue")); + assert!(!actionbar_right.contains("selected row")); + assert!(target_status.contains("composer target Companion")); + assert!(target_status.contains("draft Enter send composer text to workspace Companion")); + assert!(target_status.contains("row selection waits until composer is blank")); + assert!(!target_status.contains("blank Enter Queue")); } #[test] @@ -5723,21 +5670,23 @@ mod tests { } #[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)]); + app.input.insert_str("draft message"); 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!( app.handle_key(key(KeyCode::Esc)), MultiPodAction::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!( app.handle_key(modified_key(KeyCode::Char('c'), KeyModifiers::CONTROL)), MultiPodAction::Quit @@ -5750,6 +5699,7 @@ mod tests { app.input.insert_str("draft intake request"); assert!(matches!(app.composer_target(), ComposerTarget::Companion)); + let selected_before = app.selected_row.clone(); assert!(matches!( app.handle_key(key(KeyCode::Tab)), MultiPodAction::None @@ -5759,6 +5709,7 @@ mod tests { app.composer_target(), ComposerTarget::TicketIntake )); + assert_eq!(app.selected_row, selected_before); assert_eq!(input_text(&app), "draft intake request"); } @@ -5794,14 +5745,14 @@ mod tests { } #[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)]); app.cycle_composer_target(); app.input.insert_str(" \n\t"); assert!(matches!( app.handle_key(key(KeyCode::Enter)), - MultiPodAction::None + MultiPodAction::Open )); assert!(matches!( @@ -5810,7 +5761,12 @@ mod tests { )); assert!(!app.sending); 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] @@ -6092,7 +6048,6 @@ mod tests { panel, input: InputBuffer::new(), selected_row: None, - focus: PanelFocus::GlobalComposer, composer_target: ComposerTarget::Companion, notice: None, panel_diagnostic: None, diff --git a/crates/tui/src/workspace_panel.rs b/crates/tui/src/workspace_panel.rs index 80fc3172..4f7b886a 100644 --- a/crates/tui/src/workspace_panel.rs +++ b/crates/tui/src/workspace_panel.rs @@ -894,7 +894,7 @@ fn pod_row(entry: &PodListEntry) -> PanelRow { ticket: None, related_pods: Vec::new(), 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()), } } From 8acf6812a6079e69fc9661e18f20f95da125e549 Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 13 Jun 2026 00:01:13 +0900 Subject: [PATCH 12/41] ticket: record panel focus implementation --- .yoi/tickets/00001KTVJFT6F/item.md | 2 +- .yoi/tickets/00001KTVJFT6F/thread.md | 50 ++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KTVJFT6F/item.md b/.yoi/tickets/00001KTVJFT6F/item.md index 6c520004..3f3ea6b9 100644 --- a/.yoi/tickets/00001KTVJFT6F/item.md +++ b/.yoi/tickets/00001KTVJFT6F/item.md @@ -2,7 +2,7 @@ title: 'Workspace panel の focus model を composer target と row selection に整理する' state: 'inprogress' created_at: '2026-06-11T14:48:26Z' -updated_at: '2026-06-12T14:46:41Z' +updated_at: '2026-06-12T15:01:02Z' assignee: null readiness: 'implementation_ready' risk_flags: ['tui-ux', 'input-safety'] diff --git a/.yoi/tickets/00001KTVJFT6F/thread.md b/.yoi/tickets/00001KTVJFT6F/thread.md index 944adcee..43a46c4a 100644 --- a/.yoi/tickets/00001KTVJFT6F/thread.md +++ b/.yoi/tickets/00001KTVJFT6F/thread.md @@ -149,3 +149,53 @@ 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. --- + + + +## 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. + +--- From 89289379426b4a7bf6a3482a77cde7768834daa8 Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 13 Jun 2026 00:02:02 +0900 Subject: [PATCH 13/41] ticket: delegate panel focus review --- .yoi/tickets/00001KTVJFT6F/item.md | 2 +- .yoi/tickets/00001KTVJFT6F/thread.md | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KTVJFT6F/item.md b/.yoi/tickets/00001KTVJFT6F/item.md index 3f3ea6b9..9d1342f0 100644 --- a/.yoi/tickets/00001KTVJFT6F/item.md +++ b/.yoi/tickets/00001KTVJFT6F/item.md @@ -2,7 +2,7 @@ title: 'Workspace panel の focus model を composer target と row selection に整理する' state: 'inprogress' created_at: '2026-06-11T14:48:26Z' -updated_at: '2026-06-12T15:01:02Z' +updated_at: '2026-06-12T15:01:54Z' assignee: null readiness: 'implementation_ready' risk_flags: ['tui-ux', 'input-safety'] diff --git a/.yoi/tickets/00001KTVJFT6F/thread.md b/.yoi/tickets/00001KTVJFT6F/thread.md index 43a46c4a..4ac38619 100644 --- a/.yoi/tickets/00001KTVJFT6F/thread.md +++ b/.yoi/tickets/00001KTVJFT6F/thread.md @@ -199,3 +199,18 @@ 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. --- + + + +## 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. + +--- From e9b73d987c8fc3d646891bbe614be10225f318e9 Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 13 Jun 2026 00:07:17 +0900 Subject: [PATCH 14/41] ticket: record panel focus review --- .yoi/tickets/00001KTVJFT6F/item.md | 2 +- .yoi/tickets/00001KTVJFT6F/thread.md | 38 ++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KTVJFT6F/item.md b/.yoi/tickets/00001KTVJFT6F/item.md index 9d1342f0..dc64dfd9 100644 --- a/.yoi/tickets/00001KTVJFT6F/item.md +++ b/.yoi/tickets/00001KTVJFT6F/item.md @@ -2,7 +2,7 @@ title: 'Workspace panel の focus model を composer target と row selection に整理する' state: 'inprogress' created_at: '2026-06-11T14:48:26Z' -updated_at: '2026-06-12T15:01:54Z' +updated_at: '2026-06-12T15:07:09Z' assignee: null readiness: 'implementation_ready' risk_flags: ['tui-ux', 'input-safety'] diff --git a/.yoi/tickets/00001KTVJFT6F/thread.md b/.yoi/tickets/00001KTVJFT6F/thread.md index 4ac38619..97020455 100644 --- a/.yoi/tickets/00001KTVJFT6F/thread.md +++ b/.yoi/tickets/00001KTVJFT6F/thread.md @@ -214,3 +214,41 @@ Reviewer delegated. - 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. --- + + + +## 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. + +--- From 92c4dee71a1e3744097a105cf470a7b76dc36fd0 Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 13 Jun 2026 00:08:01 +0900 Subject: [PATCH 15/41] ticket: guide Ticket tool language universally --- crates/pod/src/feature/builtin/ticket.rs | 119 ++++++++++++++++------- crates/ticket/src/tool.rs | 118 +++++++++++++++------- 2 files changed, 168 insertions(+), 69 deletions(-) diff --git a/crates/pod/src/feature/builtin/ticket.rs b/crates/pod/src/feature/builtin/ticket.rs index 471f0e97..f9cf2f05 100644 --- a/crates/pod/src/feature/builtin/ticket.rs +++ b/crates/pod/src/feature/builtin/ticket.rs @@ -12,7 +12,7 @@ use ticket::{ tool::{ TICKET_BASE_READ_ONLY_TOOL_NAMES, TICKET_BASE_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(); 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 } @@ -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) -> TicketFeature { TicketFeature::for_workspace(workspace) } @@ -298,6 +270,19 @@ mod tests { 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] fn descriptor_declares_ticket_tools_and_backend_authority() { 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] fn lifecycle_installation_exposes_lifecycle_tools() { 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] fn installs_ticket_tools_when_default_root_is_usable() { let temp = TempDir::new().unwrap(); diff --git a/crates/ticket/src/tool.rs b/crates/ticket/src/tool.rs index 65354512..c7be9a92 100644 --- a/crates/ticket/src/tool.rs +++ b/crates/ticket/src/tool.rs @@ -131,6 +131,41 @@ explicit state decisions."; const DOCTOR_DESCRIPTION: &str = "Run typed Ticket backend consistency checks and return bounded \ 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)] struct TicketCreateParams { /// Ticket title. Must not be empty. @@ -1273,18 +1308,15 @@ fn json_output(summary: String, value: impl Serialize) -> ToolOutput { } } -fn tool_definition( - name: &'static str, - description: &'static str, - backend: LocalTicketBackend, -) -> ToolDefinition +fn tool_definition(name: &'static str, backend: LocalTicketBackend) -> ToolDefinition where T: Tool + From + 'static, { + let description = ticket_tool_description(name, backend.record_language()); Arc::new(move || { let schema_value = input_schema(name); let meta = ToolMeta::new(name) - .description(description) + .description(description.clone()) .input_schema(schema_value); let tool: Arc = Arc::new(T::from(backend.clone())); (meta, tool) @@ -1348,43 +1380,25 @@ impl_from_backend!(TicketDoctorTool); /// Build all MVP Ticket tool definitions over one local backend root. pub fn ticket_tools(backend: LocalTicketBackend) -> Vec { vec![ - tool_definition::("TicketCreate", CREATE_DESCRIPTION, backend.clone()), - tool_definition::("TicketList", LIST_DESCRIPTION, backend.clone()), - tool_definition::("TicketShow", SHOW_DESCRIPTION, backend.clone()), - tool_definition::("TicketComment", COMMENT_DESCRIPTION, backend.clone()), - tool_definition::("TicketReview", REVIEW_DESCRIPTION, backend.clone()), - tool_definition::( - "TicketIntakeReady", - INTAKE_READY_DESCRIPTION, - backend.clone(), - ), - tool_definition::( - "TicketWorkflowState", - WORKFLOW_STATE_DESCRIPTION, - backend.clone(), - ), - tool_definition::("TicketClose", CLOSE_DESCRIPTION, backend.clone()), - tool_definition::( - "TicketRelationRecord", - RELATION_RECORD_DESCRIPTION, - backend.clone(), - ), - tool_definition::( - "TicketRelationQuery", - RELATION_QUERY_DESCRIPTION, - backend.clone(), - ), + tool_definition::("TicketCreate", backend.clone()), + tool_definition::("TicketList", backend.clone()), + tool_definition::("TicketShow", backend.clone()), + tool_definition::("TicketComment", backend.clone()), + tool_definition::("TicketReview", backend.clone()), + tool_definition::("TicketIntakeReady", backend.clone()), + tool_definition::("TicketWorkflowState", backend.clone()), + tool_definition::("TicketClose", backend.clone()), + tool_definition::("TicketRelationRecord", backend.clone()), + tool_definition::("TicketRelationQuery", backend.clone()), tool_definition::( "TicketOrchestrationPlanRecord", - ORCHESTRATION_PLAN_RECORD_DESCRIPTION, backend.clone(), ), tool_definition::( "TicketOrchestrationPlanQuery", - ORCHESTRATION_PLAN_QUERY_DESCRIPTION, backend.clone(), ), - tool_definition::("TicketDoctor", DOCTOR_DESCRIPTION, backend), + tool_definition::("TicketDoctor", backend), ] } @@ -1412,6 +1426,16 @@ mod tests { .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] fn ticket_tool_name_partitions_are_explicit() { assert_eq!( @@ -1463,6 +1487,29 @@ mod tests { 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] async fn ticket_tools_create_list_show_and_doctor() { let temp = TempDir::new().unwrap(); @@ -2256,7 +2303,6 @@ mod tests { let temp = TempDir::new().unwrap(); let create = tool(tool_definition::( "TicketCreate", - CREATE_DESCRIPTION, backend(&temp), )); let _ = create; From e330685ec30005cb87dff531ec1b2f923f045129 Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 13 Jun 2026 00:08:20 +0900 Subject: [PATCH 16/41] ticket: mark panel focus done --- .yoi/tickets/00001KTVJFT6F/item.md | 4 +-- .yoi/tickets/00001KTVJFT6F/thread.md | 40 ++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/.yoi/tickets/00001KTVJFT6F/item.md b/.yoi/tickets/00001KTVJFT6F/item.md index dc64dfd9..35f5614e 100644 --- a/.yoi/tickets/00001KTVJFT6F/item.md +++ b/.yoi/tickets/00001KTVJFT6F/item.md @@ -1,8 +1,8 @@ --- title: 'Workspace panel の focus model を composer target と row selection に整理する' -state: 'inprogress' +state: 'done' created_at: '2026-06-11T14:48:26Z' -updated_at: '2026-06-12T15:07:09Z' +updated_at: '2026-06-12T15:08:11Z' assignee: null readiness: 'implementation_ready' risk_flags: ['tui-ux', 'input-safety'] diff --git a/.yoi/tickets/00001KTVJFT6F/thread.md b/.yoi/tickets/00001KTVJFT6F/thread.md index 97020455..a3ede57d 100644 --- a/.yoi/tickets/00001KTVJFT6F/thread.md +++ b/.yoi/tickets/00001KTVJFT6F/thread.md @@ -252,3 +252,43 @@ Non-blocking risks: - None identified. The added tests directly cover key boundaries, draft protection, and target/row-selection display without brittle full UI snapshots. --- + + + +## 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. + +--- + + + +## 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. + +--- From 2e7ed31f8733d25df6c497c2f6182387c542a53a Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 13 Jun 2026 00:09:23 +0900 Subject: [PATCH 17/41] ticket: close panel focus model --- .yoi/tickets/00001KTVJFT6F/item.md | 4 +- .yoi/tickets/00001KTVJFT6F/resolution.md | 38 +++++++++++++ .yoi/tickets/00001KTVJFT6F/thread.md | 70 ++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 .yoi/tickets/00001KTVJFT6F/resolution.md diff --git a/.yoi/tickets/00001KTVJFT6F/item.md b/.yoi/tickets/00001KTVJFT6F/item.md index 35f5614e..376de799 100644 --- a/.yoi/tickets/00001KTVJFT6F/item.md +++ b/.yoi/tickets/00001KTVJFT6F/item.md @@ -1,8 +1,8 @@ --- title: 'Workspace panel の focus model を composer target と row selection に整理する' -state: 'done' +state: 'closed' created_at: '2026-06-11T14:48:26Z' -updated_at: '2026-06-12T15:08:11Z' +updated_at: '2026-06-12T15:09:15Z' assignee: null readiness: 'implementation_ready' risk_flags: ['tui-ux', 'input-safety'] diff --git a/.yoi/tickets/00001KTVJFT6F/resolution.md b/.yoi/tickets/00001KTVJFT6F/resolution.md new file mode 100644 index 00000000..182e8483 --- /dev/null +++ b/.yoi/tickets/00001KTVJFT6F/resolution.md @@ -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. \ No newline at end of file diff --git a/.yoi/tickets/00001KTVJFT6F/thread.md b/.yoi/tickets/00001KTVJFT6F/thread.md index a3ede57d..d68be364 100644 --- a/.yoi/tickets/00001KTVJFT6F/thread.md +++ b/.yoi/tickets/00001KTVJFT6F/thread.md @@ -292,3 +292,73 @@ Remaining risks: Implementation was merged into the Orchestrator branch, reviewer approved with no blockers, and post-integration validation passed. Cleanup and closure will be recorded next. --- + + + +## 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. + +--- + + + +## State changed + +Ticket を 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. + +--- From 00127ceffa91a62682e7d5f5a7fddc8300c38ad7 Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 13 Jun 2026 00:10:46 +0900 Subject: [PATCH 18/41] ticket: record language guidance implementation --- .yoi/tickets/00001KTVJGC0Y/item.md | 2 +- .yoi/tickets/00001KTVJGC0Y/thread.md | 50 ++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KTVJGC0Y/item.md b/.yoi/tickets/00001KTVJGC0Y/item.md index c6e60fd1..8a8961e0 100644 --- a/.yoi/tickets/00001KTVJGC0Y/item.md +++ b/.yoi/tickets/00001KTVJGC0Y/item.md @@ -2,7 +2,7 @@ title: 'Ticket language guidance must apply to all Ticket tool users' state: 'inprogress' created_at: '2026-06-11T14:48:44Z' -updated_at: '2026-06-12T14:55:42Z' +updated_at: '2026-06-12T15:10:38Z' assignee: null readiness: 'implementation_ready' risk_flags: ['prompt-context', 'tool-description', 'feature-boundary', 'ticket-language', 'companion'] diff --git a/.yoi/tickets/00001KTVJGC0Y/thread.md b/.yoi/tickets/00001KTVJGC0Y/thread.md index c2402c5f..c526b67d 100644 --- a/.yoi/tickets/00001KTVJGC0Y/thread.md +++ b/.yoi/tickets/00001KTVJGC0Y/thread.md @@ -152,3 +152,53 @@ 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. --- + + + +## 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. + +--- From c400fd5062afe3292da93c8aa9e2c7c34887f397 Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 13 Jun 2026 00:11:32 +0900 Subject: [PATCH 19/41] ticket: delegate language guidance review --- .yoi/tickets/00001KTVJGC0Y/item.md | 2 +- .yoi/tickets/00001KTVJGC0Y/thread.md | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KTVJGC0Y/item.md b/.yoi/tickets/00001KTVJGC0Y/item.md index 8a8961e0..a08fa140 100644 --- a/.yoi/tickets/00001KTVJGC0Y/item.md +++ b/.yoi/tickets/00001KTVJGC0Y/item.md @@ -2,7 +2,7 @@ title: 'Ticket language guidance must apply to all Ticket tool users' state: 'inprogress' created_at: '2026-06-11T14:48:44Z' -updated_at: '2026-06-12T15:10:38Z' +updated_at: '2026-06-12T15:11:23Z' assignee: null readiness: 'implementation_ready' risk_flags: ['prompt-context', 'tool-description', 'feature-boundary', 'ticket-language', 'companion'] diff --git a/.yoi/tickets/00001KTVJGC0Y/thread.md b/.yoi/tickets/00001KTVJGC0Y/thread.md index c526b67d..e42ca4b2 100644 --- a/.yoi/tickets/00001KTVJGC0Y/thread.md +++ b/.yoi/tickets/00001KTVJGC0Y/thread.md @@ -202,3 +202,18 @@ 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. --- + + + +## 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. + +--- From e1a10e4af45dcb4401fa529365ff117ea845df9d Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 13 Jun 2026 00:17:05 +0900 Subject: [PATCH 20/41] ticket: record language guidance review --- .yoi/tickets/00001KTVJGC0Y/item.md | 2 +- .yoi/tickets/00001KTVJGC0Y/thread.md | 39 ++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KTVJGC0Y/item.md b/.yoi/tickets/00001KTVJGC0Y/item.md index a08fa140..137b08f1 100644 --- a/.yoi/tickets/00001KTVJGC0Y/item.md +++ b/.yoi/tickets/00001KTVJGC0Y/item.md @@ -2,7 +2,7 @@ title: 'Ticket language guidance must apply to all Ticket tool users' state: 'inprogress' created_at: '2026-06-11T14:48:44Z' -updated_at: '2026-06-12T15:11:23Z' +updated_at: '2026-06-12T15:16:59Z' assignee: null readiness: 'implementation_ready' risk_flags: ['prompt-context', 'tool-description', 'feature-boundary', 'ticket-language', 'companion'] diff --git a/.yoi/tickets/00001KTVJGC0Y/thread.md b/.yoi/tickets/00001KTVJGC0Y/thread.md index e42ca4b2..c2b51104 100644 --- a/.yoi/tickets/00001KTVJGC0Y/thread.md +++ b/.yoi/tickets/00001KTVJGC0Y/thread.md @@ -217,3 +217,42 @@ Reviewer delegated. - 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. --- + + + +## 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. + +--- From a87d31547172f3a41d0229445ebd8e99b8c65761 Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 13 Jun 2026 00:18:41 +0900 Subject: [PATCH 21/41] feat: weak companion progress notify --- crates/pod/src/controller.rs | 24 +- crates/pod/src/discovery.rs | 12 +- crates/pod/tests/controller_test.rs | 58 ++++ crates/protocol/src/lib.rs | 32 +- crates/tui/src/multi_pod.rs | 456 +++++++++++++++++++++++++++- 5 files changed, 562 insertions(+), 20 deletions(-) diff --git a/crates/pod/src/controller.rs b/crates/pod/src/controller.rs index 425b1eea..450ac4f3 100644 --- a/crates/pod/src/controller.rs +++ b/crates/pod/src/controller.rs @@ -125,6 +125,10 @@ impl PendingRun { } } +fn should_auto_run_notification(status: PodStatus, auto_run: bool) -> bool { + auto_run && status == PodStatus::Idle +} + // --------------------------------------------------------------------------- // PodController — actor that owns a Pod // --------------------------------------------------------------------------- @@ -774,7 +778,7 @@ async fn controller_loop( pending = Some(PendingRun::Run(input)); } - Method::Notify { message } => { + Method::Notify { message, auto_run } => { // Client-side live echo is delivered as `Event::SystemItem` // once the interceptor commits the corresponding // `LogEntry::SystemItem` entry — drained out of the @@ -784,10 +788,10 @@ async fn controller_loop( // RUNNING / Paused: the buffer push is the entire // operation; an in-flight turn (or the next // Resume/Run) will drain it at its next - // pending_history_appends. IDLE: auto-start a turn so the LLM - // sees the buffered notification(s) without a human - // Run. - if shared_state.get_status() == PodStatus::Idle { + // pending_history_appends. IDLE: only `auto_run` + // notifications stage RunForNotification; weak progress + // notices stay queued until an explicit run/resume. + if should_auto_run_notification(shared_state.get_status(), auto_run) { pending = Some(PendingRun::RunForNotification(protocol::InvokeKind::Notify)); } } @@ -1145,7 +1149,7 @@ where .into(), }); } - Some(Method::Notify { message }) => { + Some(Method::Notify { message, .. }) => { // Live echo arrives via `Event::SystemItem` once // the in-flight turn's next `pending_history_appends` // drains this entry through the interceptor. @@ -1337,6 +1341,14 @@ mod tests { ); } + #[test] + fn notification_auto_run_gate_only_allows_idle_auto_run() { + assert!(should_auto_run_notification(PodStatus::Idle, true)); + assert!(!should_auto_run_notification(PodStatus::Idle, false)); + assert!(!should_auto_run_notification(PodStatus::Running, true)); + assert!(!should_auto_run_notification(PodStatus::Paused, true)); + } + struct DriveTurnEnv { // Held to keep the channel alive; without this `method_rx.recv()` // would observe channel-closed and confuse the select! arm. diff --git a/crates/pod/src/discovery.rs b/crates/pod/src/discovery.rs index 8c87a3cb..c5a8500a 100644 --- a/crates/pod/src/discovery.rs +++ b/crates/pod/src/discovery.rs @@ -913,7 +913,14 @@ where } async fn send_peer_notify(socket_path: &Path, message: String) -> io::Result<()> { - connect_and_send(socket_path, &Method::Notify { message }).await + connect_and_send( + socket_path, + &Method::Notify { + message, + auto_run: true, + }, + ) + .await } fn json_content(value: &T) -> Result { @@ -1395,7 +1402,8 @@ mod tests { .await .unwrap(); let method = reader.next::().await.unwrap().unwrap(); - if let Method::Notify { message } = method { + if let Method::Notify { message, auto_run } = method { + assert!(auto_run); tx.send(message).await.unwrap(); } else { panic!("expected Notify, got {method:?}"); diff --git a/crates/pod/tests/controller_test.rs b/crates/pod/tests/controller_test.rs index fd040d53..6e340be6 100644 --- a/crates/pod/tests/controller_test.rs +++ b/crates/pod/tests/controller_test.rs @@ -1025,6 +1025,7 @@ async fn notify_while_idle_auto_starts_turn_and_injects_system_message() { handle .send(Method::Notify { message: "turn finished".into(), + auto_run: true, }) .await .unwrap(); @@ -1105,6 +1106,62 @@ async fn notify_while_idle_auto_starts_turn_and_injects_system_message() { ); } +#[tokio::test] +async fn notify_while_idle_with_auto_run_false_waits_for_explicit_run() { + let client = MockClient::new(simple_text_events()); + let client_for_assert = client.clone(); + let pod = make_pod(client).await; + let handle = spawn_controller(pod).await; + + handle + .send(Method::Notify { + message: "progress snapshot".into(), + auto_run: false, + }) + .await + .unwrap(); + + tokio::time::sleep(std::time::Duration::from_millis(100)).await; + assert_eq!(handle.shared_state.get_status(), PodStatus::Idle); + assert!( + client_for_assert.captured_requests().is_empty(), + "weak Notify must not stage RunForNotification while idle" + ); + + handle.send(Method::run_text("continue")).await.unwrap(); + let deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(2); + loop { + if !client_for_assert.captured_requests().is_empty() { + break; + } + assert!( + tokio::time::Instant::now() < deadline, + "explicit run did not reach the mock LLM" + ); + tokio::time::sleep(std::time::Duration::from_millis(10)).await; + } + wait_for_status(&handle, PodStatus::Idle).await; + let requests = client_for_assert.captured_requests(); + assert_eq!( + requests.len(), + 1, + "explicit run should drain the queued notification" + ); + let notify_in_request = requests[0].items.iter().any(|i| { + i.as_text() + .is_some_and(|t| t.contains("[Notification]") && t.contains("progress snapshot")) + }); + assert!( + notify_in_request, + "queued weak notification must be history-backed on the next explicit run; got items: {:?}", + requests[0] + .items + .iter() + .filter_map(|i| i.as_text()) + .collect::>() + ); +} + #[tokio::test] async fn pod_event_turn_ended_while_idle_auto_starts_turn_and_injects_system_message() { let client = MockClient::new(simple_text_events()); @@ -1259,6 +1316,7 @@ async fn notify_while_running_does_not_emit_already_running_error() { handle .send(Method::Notify { message: "ping".into(), + auto_run: true, }) .await .unwrap(); diff --git a/crates/protocol/src/lib.rs b/crates/protocol/src/lib.rs index 80c4e4bb..526bc60d 100644 --- a/crates/protocol/src/lib.rs +++ b/crates/protocol/src/lib.rs @@ -4,6 +4,14 @@ use std::path::PathBuf; use serde::{Deserialize, Serialize}; +fn default_true() -> bool { + true +} + +fn is_true(value: &bool) -> bool { + *value +} + // --------------------------------------------------------------------------- // Method (Client → Pod via Unix Socket) // --------------------------------------------------------------------------- @@ -15,10 +23,15 @@ pub enum Method { input: Vec, }, /// Human-readable text injected into the target Pod's LLM context - /// as a non-blocking system message. No side effects beyond LLM - /// context; use `PodEvent` for typed lifecycle reports. + /// as a non-blocking system message. `auto_run` controls whether an + /// idle target is kicked into `RunForNotification`; weak notifications + /// (`auto_run: false`) are only queued for the next turn/resume/run. + /// No side effects beyond LLM context; use `PodEvent` for typed + /// lifecycle reports. Notify { message: String, + #[serde(default = "default_true", skip_serializing_if = "is_true")] + auto_run: bool, }, /// Typed lifecycle report from a child Pod to its direct parent. PodEvent(PodEvent), @@ -1027,17 +1040,28 @@ mod tests { } #[test] - fn method_notify_json_roundtrip() { + fn method_notify_json_roundtrip_defaults_to_auto_run() { let json = r#"{"method":"notify","params":{"message":"turn done"}}"#; let method: Method = serde_json::from_str(json).unwrap(); assert!(matches!( method, - Method::Notify { ref message } if message == "turn done" + Method::Notify { ref message, auto_run: true } if message == "turn done" )); let serialized = serde_json::to_string(&method).unwrap(); assert_eq!(serialized, json); } + #[test] + fn method_notify_weak_json_roundtrip_serializes_auto_run_false() { + let json = r#"{"method":"notify","params":{"message":"progress","auto_run":false}}"#; + let method: Method = serde_json::from_str(json).unwrap(); + assert!(matches!( + method, + Method::Notify { ref message, auto_run: false } if message == "progress" + )); + assert_eq!(serde_json::to_string(&method).unwrap(), json); + } + #[test] fn method_list_completions_roundtrip() { let method = Method::ListCompletions { diff --git a/crates/tui/src/multi_pod.rs b/crates/tui/src/multi_pod.rs index 3685086d..f865cf66 100644 --- a/crates/tui/src/multi_pod.rs +++ b/crates/tui/src/multi_pod.rs @@ -49,6 +49,9 @@ use crate::workspace_panel::{ const MAX_ENTRIES: usize = 50; const CLOSED_VISIBLE_ROWS: usize = 3; +const COMPANION_PROGRESS_MAX_TICKETS: usize = 5; +const COMPANION_PROGRESS_MAX_TITLE_CHARS: usize = 80; +const COMPANION_PROGRESS_MAX_MESSAGE_CHARS: usize = 1_800; const SOCKET_OP_TIMEOUT: Duration = Duration::from_secs(3); const MULTI_POD_POLL_INTERVAL: Duration = Duration::from_millis(1_500); const TERMINAL_EVENT_POLL_INTERVAL: Duration = Duration::from_millis(100); @@ -126,6 +129,10 @@ pub(crate) async fn run( loop { if let Some(result) = pending_reload.finish_if_ready().await { app.apply_reload_result(result); + if let Some(request) = app.prepare_companion_progress_notice() { + let result = dispatch_companion_progress_notice(request).await; + app.finish_companion_progress_notice(result); + } } terminal.draw(|f| draw(f, app))?; @@ -529,6 +536,50 @@ struct PanelDiagnostic { details: String, } +#[derive(Debug, Clone, PartialEq, Eq)] +struct CompanionProgressFreshness { + fingerprint: String, + updated_at: String, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct CompanionProgressNotice { + message: String, + fingerprint: String, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct CompanionProgressNoticeRequest { + pod_name: String, + socket_path: PathBuf, + notice: CompanionProgressNotice, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct CompanionProgressNoticeResult { + fingerprint: String, + updated_at: String, + error: Option, +} + +impl CompanionProgressNoticeResult { + fn sent(fingerprint: String, updated_at: String) -> Self { + Self { + fingerprint, + updated_at, + error: None, + } + } + + fn failed(fingerprint: String, error: impl Into) -> Self { + Self { + fingerprint, + updated_at: String::new(), + error: Some(error.into()), + } + } +} + pub(crate) struct MultiPodApp { pub(crate) list: PodList, pub(crate) panel: WorkspacePanelViewModel, @@ -545,6 +596,7 @@ pub(crate) struct MultiPodApp { runtime_command: PodRuntimeCommand, last_companion_lifecycle_failure: Option, last_orchestrator_lifecycle_failure: Option, + companion_progress: Option, } impl MultiPodApp { @@ -579,6 +631,7 @@ impl MultiPodApp { runtime_command, last_companion_lifecycle_failure: None, last_orchestrator_lifecycle_failure: None, + companion_progress: None, } } @@ -629,6 +682,50 @@ impl MultiPodApp { self.selected_row = previous_row.filter(|key| self.panel.row(key).is_some()); self.ensure_selection_visible(); self.ensure_composer_target_available(); + self.apply_companion_progress_freshness(); + } + + fn prepare_companion_progress_notice(&mut self) -> Option { + let target = companion_progress_notice_target(&self.panel, &self.list)?; + let notice = companion_progress_notice(&self.panel, &self.list)?; + if self + .companion_progress + .as_ref() + .is_some_and(|freshness| freshness.fingerprint == notice.fingerprint) + { + self.apply_companion_progress_freshness(); + return None; + } + Some(CompanionProgressNoticeRequest { + pod_name: target.pod_name, + socket_path: target.socket_path, + notice, + }) + } + + fn finish_companion_progress_notice(&mut self, result: CompanionProgressNoticeResult) { + if let Some(error) = result.error { + self.notice = Some(format!("Companion progress notice not delivered: {error}")); + return; + } + self.companion_progress = Some(CompanionProgressFreshness { + fingerprint: result.fingerprint, + updated_at: result.updated_at, + }); + self.apply_companion_progress_freshness(); + } + + fn apply_companion_progress_freshness(&mut self) { + let Some(freshness) = self.companion_progress.as_ref() else { + return; + }; + let Some(companion) = self.panel.header.companion.as_mut() else { + return; + }; + companion.detail = Some(format!( + "progress context updated at {} (weak notify)", + freshness.updated_at + )); } fn apply_companion_lifecycle_memory(&mut self, panel: &mut WorkspacePanelViewModel) { @@ -2338,6 +2435,157 @@ struct OrchestratorNotifyTarget { socket_path: PathBuf, } +#[derive(Debug, Clone, PartialEq, Eq)] +struct CompanionProgressNoticeTarget { + pod_name: String, + socket_path: PathBuf, +} + +fn companion_progress_notice_target( + panel: &WorkspacePanelViewModel, + list: &PodList, +) -> Option { + let companion = panel.header.companion.as_ref()?; + if !companion_status_is_peer_reachable(companion.status) { + return None; + } + let entry = list + .entries + .iter() + .find(|entry| entry.name == companion.pod_name)?; + let live = entry.live.as_ref()?; + if !live.reachable { + return None; + } + Some(CompanionProgressNoticeTarget { + pod_name: companion.pod_name.clone(), + socket_path: live.socket_path.clone(), + }) +} + +fn companion_status_is_peer_reachable(status: CompanionPanelStatus) -> bool { + matches!( + status, + CompanionPanelStatus::Live | CompanionPanelStatus::Restored | CompanionPanelStatus::Spawned + ) +} + +fn companion_progress_notice( + panel: &WorkspacePanelViewModel, + list: &PodList, +) -> Option { + let companion = panel.header.companion.as_ref()?; + let orchestrator = panel.header.orchestrator.as_ref()?; + let mut lines = vec![ + "Orchestrator progress context (read-only weak notification; no auto-run).".to_string(), + "Reason: workspace Panel refreshed bounded orchestration progress for Companion explanation." + .to_string(), + format!( + "Roles: Companion {} is {}; Orchestrator {} is {}.", + companion.pod_name, + companion.status.label(), + orchestrator.pod_name, + orchestrator.status.label() + ), + ]; + + let mut ticket_lines = Vec::new(); + for row in panel.rows.iter().take(COMPANION_PROGRESS_MAX_TICKETS) { + let ticket_id = row + .ticket + .as_ref() + .map(|ticket| ticket.id.as_str()) + .unwrap_or("unknown-ticket"); + ticket_lines.push(format!( + "- {} [{}] {} (ref: .yoi/tickets/{})", + ticket_id, + row.status, + bounded_progress_text(&row.title, COMPANION_PROGRESS_MAX_TITLE_CHARS), + ticket_id + )); + } + if ticket_lines.is_empty() { + lines.push("Tickets: none visible in the current Panel snapshot.".to_string()); + } else { + lines.push(format!( + "Tickets (first {} visible, bounded):", + ticket_lines.len() + )); + lines.extend(ticket_lines); + if panel.rows.len() > COMPANION_PROGRESS_MAX_TICKETS { + lines.push(format!( + "- … {} more ticket(s) omitted from this bounded notice.", + panel.rows.len() - COMPANION_PROGRESS_MAX_TICKETS + )); + } + } + + let role_pod_lines = bounded_role_pod_lines(list, companion, orchestrator); + if !role_pod_lines.is_empty() { + lines.push("Role pod status snapshot:".to_string()); + lines.extend(role_pod_lines); + } + + let message = bounded_progress_text(&lines.join("\n"), COMPANION_PROGRESS_MAX_MESSAGE_CHARS); + let fingerprint = message.clone(); + Some(CompanionProgressNotice { + message, + fingerprint, + }) +} + +fn bounded_role_pod_lines( + list: &PodList, + companion: &CompanionPanelState, + orchestrator: &OrchestratorPanelState, +) -> Vec { + let mut lines = Vec::new(); + for name in [&companion.pod_name, &orchestrator.pod_name] { + let Some(entry) = list.entries.iter().find(|entry| entry.name == *name) else { + continue; + }; + lines.push(format!("- {}: {}", entry.name, row_status_label(entry).0)); + } + lines +} + +fn bounded_progress_text(input: &str, max_chars: usize) -> String { + let mut output = String::new(); + for (idx, ch) in input.chars().enumerate() { + if idx >= max_chars { + output.push('…'); + return output; + } + let sanitized = if ch.is_control() && ch != '\n' && ch != '\t' { + ' ' + } else { + ch + }; + output.push(sanitized); + } + output +} + +fn progress_notice_timestamp() -> String { + match SystemTime::now().duration_since(UNIX_EPOCH) { + Ok(duration) => format!("unix:{}", duration.as_secs()), + Err(_) => "unix:0".to_string(), + } +} + +async fn dispatch_companion_progress_notice( + request: CompanionProgressNoticeRequest, +) -> CompanionProgressNoticeResult { + let fingerprint = request.notice.fingerprint.clone(); + match send_notify_only(&request.socket_path, request.notice.message, false).await { + Ok(()) => CompanionProgressNoticeResult::sent(fingerprint, progress_notice_timestamp()), + Err(err) => CompanionProgressNoticeResult::failed( + fingerprint, + format!("{}: {}", request.pod_name, err), + ), + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct TicketActionOutcome { notice: String, @@ -3133,7 +3381,7 @@ async fn notify_workspace_orchestrator( ); }; let message = orchestrator_queue_notification_message(ticket); - match send_notify_only(&target.socket_path, message).await { + match send_notify_only(&target.socket_path, message, true).await { Ok(()) => OrchestratorNotificationOutcome::Sent { pod_name: target.pod_name, }, @@ -3146,7 +3394,11 @@ async fn notify_workspace_orchestrator( } } -async fn send_notify_only(socket: &Path, message: String) -> Result<(), NotifySendError> { +async fn send_notify_only( + socket: &Path, + message: String, + auto_run: bool, +) -> Result<(), NotifySendError> { let stream = tokio::time::timeout(SOCKET_OP_TIMEOUT, UnixStream::connect(socket)) .await .map_err(|_| NotifySendError::Io("connect timed out".into()))? @@ -3175,10 +3427,13 @@ async fn send_notify_only(socket: &Path, message: String) -> Result<(), NotifySe } } - tokio::time::timeout(SOCKET_OP_TIMEOUT, writer.write(&Method::Notify { message })) - .await - .map_err(|_| NotifySendError::Io("write timed out".into()))? - .map_err(|e| NotifySendError::Io(format!("write: {e}"))) + tokio::time::timeout( + SOCKET_OP_TIMEOUT, + writer.write(&Method::Notify { message, auto_run }), + ) + .await + .map_err(|_| NotifySendError::Io("write timed out".into()))? + .map_err(|e| NotifySendError::Io(format!("write: {e}"))) } #[derive(Debug, Clone, PartialEq, Eq)] @@ -3531,6 +3786,12 @@ fn draw_title(frame: &mut Frame<'_>, app: &MultiPodApp, area: Rect) { companion.status.label(), companion_status_style(companion.status), )); + if let Some(detail) = companion.detail.as_deref() { + spans.push(Span::styled( + format!(" ({detail})"), + Style::default().fg(Color::DarkGray), + )); + } } if let Some(orchestrator) = &app.panel.header.orchestrator { spans.push(Span::styled( @@ -4692,16 +4953,194 @@ mod tests { reader.next::().await.unwrap().unwrap() }); - send_notify_only(&socket_path, "panel Queue".to_string()) + send_notify_only(&socket_path, "panel Queue".to_string(), true) .await .unwrap(); let method = server.await.unwrap(); assert!(matches!( method, - Method::Notify { message } if message == "panel Queue" + Method::Notify { message, auto_run: true } if message == "panel Queue" )); } + #[tokio::test] + async fn send_notify_only_can_deliver_weak_notification_without_auto_run() { + let temp = TempDir::new().unwrap(); + let socket_path = temp.path().join("companion.sock"); + let listener = tokio::net::UnixListener::bind(&socket_path).unwrap(); + let server = tokio::spawn(async move { + let (stream, _) = listener.accept().await.unwrap(); + let (reader, writer) = stream.into_split(); + let mut reader = JsonLineReader::new(reader); + let mut writer = JsonLineWriter::new(writer); + writer + .write(&Event::Snapshot { + entries: Vec::new(), + greeting: protocol::Greeting { + pod_name: "yoi".to_string(), + cwd: temp.path().display().to_string(), + provider: "test".to_string(), + model: "test".to_string(), + scope_summary: "test".to_string(), + tools: Vec::new(), + context_window: 0, + context_tokens: 0, + }, + status: PodStatus::Idle, + }) + .await + .unwrap(); + reader.next::().await.unwrap().unwrap() + }); + + send_notify_only(&socket_path, "panel progress".to_string(), false) + .await + .unwrap(); + let method = server.await.unwrap(); + assert!(matches!( + method, + Method::Notify { message, auto_run: false } if message == "panel progress" + )); + } + + #[test] + fn companion_progress_notice_target_skips_missing_stopped_and_unreachable_without_spawn_restore() + { + let missing_app = ticket_enabled_app(vec![live_info("test-orchestrator", PodStatus::Idle)]); + assert!(companion_progress_notice_target(&missing_app.panel, &missing_app.list).is_none()); + + let mut stopped_panel = WorkspacePanelViewModel::empty(Path::new("test")); + stopped_panel.header.companion = Some(CompanionPanelState::new( + "yoi", + CompanionPanelStatus::Stopped, + None, + )); + stopped_panel.header.orchestrator = Some(OrchestratorPanelState::new( + "test-orchestrator", + OrchestratorPanelStatus::Live, + None, + )); + let stopped_list = PodList::from_sources( + PodVisibilitySource::ResumePicker, + vec![stopped_info("yoi")], + vec![live_info("test-orchestrator", PodStatus::Idle)], + None, + 10, + ); + assert!(companion_progress_notice_target(&stopped_panel, &stopped_list).is_none()); + + let mut unreachable = live_info("yoi", PodStatus::Idle); + unreachable.reachable = false; + let unreachable_app = ticket_enabled_app(vec![ + unreachable, + live_info("test-orchestrator", PodStatus::Idle), + ]); + assert!( + companion_progress_notice_target(&unreachable_app.panel, &unreachable_app.list) + .is_none() + ); + } + + #[test] + fn companion_progress_notice_is_bounded_and_excludes_sensitive_unbounded_fields() { + let mut app = ticket_enabled_app(vec![ + live_info("yoi", PodStatus::Idle), + live_info("test-orchestrator", PodStatus::Running), + ]); + app.panel.rows = (0..12) + .map(|index| { + let mut row = panel_test_ticket_row( + &format!("TICKET-{index}"), + &format!("Visible title {index} {}", "x".repeat(140)), + ActionPriority::Background, + NextUserAction::Wait, + "inprogress", + ); + if let Some(ticket) = row.ticket.as_mut() { + ticket.latest_event_excerpt = Some( + "SECRET_PROVIDER_ERROR_TOKEN should never be copied into progress notices" + .to_string(), + ); + } + row.subtitle = Some("private thread excerpt should stay out".to_string()); + row + }) + .collect(); + app.panel + .header + .diagnostics + .push("diagnostic with SECRET_PROVIDER_ERROR_TOKEN should stay out".to_string()); + + let notice = companion_progress_notice(&app.panel, &app.list).unwrap(); + assert!(notice.message.contains("TICKET-0")); + assert!(notice.message.contains("ref: .yoi/tickets/TICKET-0")); + assert!(notice.message.contains("more ticket(s) omitted")); + assert!(notice.message.chars().count() <= COMPANION_PROGRESS_MAX_MESSAGE_CHARS + 1); + assert!(!notice.message.contains("SECRET_PROVIDER_ERROR_TOKEN")); + assert!(!notice.message.contains("private thread excerpt")); + assert_eq!(notice.fingerprint, notice.message); + } + + #[test] + fn companion_progress_notice_success_sets_panel_freshness_without_persisting_snapshot() { + let mut app = ticket_enabled_app(vec![ + live_info("yoi", PodStatus::Idle), + live_info("test-orchestrator", PodStatus::Idle), + ]); + app.panel.rows.push(panel_test_ticket_row( + "TICKET-1", + "Implement progress notices", + ActionPriority::Background, + NextUserAction::Wait, + "inprogress", + )); + + let request = app.prepare_companion_progress_notice().unwrap(); + assert_eq!(request.pod_name, "yoi"); + app.finish_companion_progress_notice(CompanionProgressNoticeResult::sent( + request.notice.fingerprint, + "unix:42".to_string(), + )); + + let detail = app + .panel + .header + .companion + .as_ref() + .and_then(|companion| companion.detail.as_deref()) + .unwrap(); + assert!(detail.contains("unix:42")); + assert!(detail.contains("weak notify")); + assert!(app.prepare_companion_progress_notice().is_none()); + } + + #[test] + fn companion_progress_notice_target_accepts_live_running_companion() { + let app = ticket_enabled_app(vec![ + live_info("yoi", PodStatus::Running), + live_info("test-orchestrator", PodStatus::Running), + ]); + let target = companion_progress_notice_target(&app.panel, &app.list).unwrap(); + assert_eq!(target.pod_name, "yoi"); + assert_eq!(target.socket_path, PathBuf::from("/tmp/yoi.sock")); + } + + #[test] + fn companion_progress_failure_is_best_effort_and_does_not_mark_freshness() { + let mut app = ticket_enabled_app(vec![ + live_info("yoi", PodStatus::Idle), + live_info("test-orchestrator", PodStatus::Idle), + ]); + let request = app.prepare_companion_progress_notice().unwrap(); + app.finish_companion_progress_notice(CompanionProgressNoticeResult::failed( + request.notice.fingerprint, + "socket closed", + )); + + assert!(app.companion_progress.is_none()); + assert!(app.notice.as_deref().unwrap().contains("not delivered")); + } + #[test] fn no_ticket_selection_keeps_enter_pod_centric() { let mut app = test_app(vec![live_info("alpha", PodStatus::Idle)]); @@ -6103,6 +6542,7 @@ mod tests { runtime_command: PodRuntimeCommand::for_executable("/tmp/yoi"), last_companion_lifecycle_failure, last_orchestrator_lifecycle_failure, + companion_progress: None, }; app.ensure_selection_visible(); app.ensure_composer_target_available(); From 2ba97b674e8db67c0281eb54a245e8841cd14e69 Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 13 Jun 2026 00:19:35 +0900 Subject: [PATCH 22/41] ticket: mark language guidance done --- .yoi/tickets/00001KTVJGC0Y/item.md | 4 +-- .yoi/tickets/00001KTVJGC0Y/thread.md | 40 ++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/.yoi/tickets/00001KTVJGC0Y/item.md b/.yoi/tickets/00001KTVJGC0Y/item.md index 137b08f1..535758e9 100644 --- a/.yoi/tickets/00001KTVJGC0Y/item.md +++ b/.yoi/tickets/00001KTVJGC0Y/item.md @@ -1,8 +1,8 @@ --- title: 'Ticket language guidance must apply to all Ticket tool users' -state: 'inprogress' +state: 'done' created_at: '2026-06-11T14:48:44Z' -updated_at: '2026-06-12T15:16:59Z' +updated_at: '2026-06-12T15:19:29Z' assignee: null readiness: 'implementation_ready' risk_flags: ['prompt-context', 'tool-description', 'feature-boundary', 'ticket-language', 'companion'] diff --git a/.yoi/tickets/00001KTVJGC0Y/thread.md b/.yoi/tickets/00001KTVJGC0Y/thread.md index c2b51104..c98c97b8 100644 --- a/.yoi/tickets/00001KTVJGC0Y/thread.md +++ b/.yoi/tickets/00001KTVJGC0Y/thread.md @@ -256,3 +256,43 @@ 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. --- + + + +## 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. + +--- + + + +## 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. + +--- From 3bcf6777687882f7e0d2b0d03586e47f6d045a5c Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 13 Jun 2026 00:20:20 +0900 Subject: [PATCH 23/41] ticket: close language guidance --- .yoi/tickets/00001KTVJGC0Y/item.md | 4 +- .yoi/tickets/00001KTVJGC0Y/resolution.md | 35 +++++++++++++ .yoi/tickets/00001KTVJGC0Y/thread.md | 67 ++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 .yoi/tickets/00001KTVJGC0Y/resolution.md diff --git a/.yoi/tickets/00001KTVJGC0Y/item.md b/.yoi/tickets/00001KTVJGC0Y/item.md index 535758e9..c59f1589 100644 --- a/.yoi/tickets/00001KTVJGC0Y/item.md +++ b/.yoi/tickets/00001KTVJGC0Y/item.md @@ -1,8 +1,8 @@ --- title: 'Ticket language guidance must apply to all Ticket tool users' -state: 'done' +state: 'closed' created_at: '2026-06-11T14:48:44Z' -updated_at: '2026-06-12T15:19:29Z' +updated_at: '2026-06-12T15:20:11Z' assignee: null readiness: 'implementation_ready' risk_flags: ['prompt-context', 'tool-description', 'feature-boundary', 'ticket-language', 'companion'] diff --git a/.yoi/tickets/00001KTVJGC0Y/resolution.md b/.yoi/tickets/00001KTVJGC0Y/resolution.md new file mode 100644 index 00000000..fa7a6ab4 --- /dev/null +++ b/.yoi/tickets/00001KTVJGC0Y/resolution.md @@ -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. \ No newline at end of file diff --git a/.yoi/tickets/00001KTVJGC0Y/thread.md b/.yoi/tickets/00001KTVJGC0Y/thread.md index c98c97b8..aa02127a 100644 --- a/.yoi/tickets/00001KTVJGC0Y/thread.md +++ b/.yoi/tickets/00001KTVJGC0Y/thread.md @@ -296,3 +296,70 @@ Remaining non-blocking risk: Implementation was merged into the Orchestrator branch, reviewer approved with no blockers, and post-integration validation passed. Cleanup and closure will be recorded next. --- + + + +## 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. + +--- + + + +## State changed + +Ticket を 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. + +--- From 839b241c2cccb8b0ecf6708499717e2cfa170928 Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 13 Jun 2026 00:23:08 +0900 Subject: [PATCH 24/41] ticket: record companion notify implementation --- .yoi/tickets/00001KTTW04W2/item.md | 2 +- .yoi/tickets/00001KTTW04W2/thread.md | 55 ++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KTTW04W2/item.md b/.yoi/tickets/00001KTTW04W2/item.md index c18ec347..963484cb 100644 --- a/.yoi/tickets/00001KTTW04W2/item.md +++ b/.yoi/tickets/00001KTTW04W2/item.md @@ -2,7 +2,7 @@ title: 'Orchestrator進捗をAutoKickなしでCompanionへ通知する' state: 'inprogress' created_at: '2026-06-11T08:15:24Z' -updated_at: '2026-06-12T14:52:34Z' +updated_at: '2026-06-12T15:23:02Z' assignee: null queued_by: 'workspace-panel' queued_at: '2026-06-11T10:31:56Z' diff --git a/.yoi/tickets/00001KTTW04W2/thread.md b/.yoi/tickets/00001KTTW04W2/thread.md index b363b9d4..05b3b5b1 100644 --- a/.yoi/tickets/00001KTTW04W2/thread.md +++ b/.yoi/tickets/00001KTTW04W2/thread.md @@ -171,3 +171,58 @@ 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. --- + + + +## Implementation report + +Coder completed implementation and branch was synchronized with current Orchestrator HEAD before review. + +Coder Pod: +- `yoi-coder-companion-progress-notify` + +Implementation branch/worktree: +- Branch: `ticket/orchestrator-progress-companion-notify` +- Worktree: `/home/hare/Projects/yoi/.worktree/orchestrator-progress-companion-notify` +- Implementation commit: `a87d3154 feat: weak companion progress notify` +- Sync merge commit on implementation branch: `724b79f1 Merge branch 'orchestration/yoi-orchestrator' into ticket/orchestrator-progress-companion-notify` +- Worktree status: clean + +Changed surfaces: +- `crates/protocol/src/lib.rs` +- `crates/pod/src/controller.rs` +- `crates/pod/src/discovery.rs` +- `crates/pod/tests/controller_test.rs` +- `crates/tui/src/multi_pod.rs` + +Implementation summary: +- Added `Method::Notify { auto_run: bool }`; missing `auto_run` defaults to `true`, `true` serializes like the old shape, and `false` is explicit. +- Idle Pod `Notify { auto_run: false }` does not stage `RunForNotification`; explicit next run drains notification history-backed through existing paths. +- Existing peer notify/discovery paths keep `auto_run: true`. +- Panel sends bounded progress notices to live/reachable Companion only via weak `Notify { auto_run: false }`. +- Missing/stopped/unreachable Companion is best-effort no-op; no spawn/restore. +- Progress summary is bounded to Ticket id/title/state, role pod status, and `.yoi/tickets/` refs; no full thread, long output, diagnostics, provider errors, or secret-like content. +- Panel title shows Companion progress freshness/last-updated without adding a persistent snapshot store. +- Companion profile/tool authority is unchanged. + +Orchestrator-verified evidence from child worktree after sync with current Orchestrator branch: +- `git status --short --branch`: clean on `ticket/orchestrator-progress-companion-notify` +- `cargo test -p protocol`: pass, 39 tests +- `cargo test -p pod --test controller_test`: pass, 36 tests +- `cargo test -p tui companion_progress -- --nocapture`: pass, 5 tests +- `cargo test -p tui send_notify_only_can_deliver_weak_notification_without_auto_run -- --nocapture`: pass, 1 test +- `cargo check -p protocol -p pod -p tui`: 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 unrelated validation failures from coder full-suite notes: +- `cargo test -p pod` still fails existing prompt-resource assertion tests: `worker_instruction_override_from_resources`, `worker_instruction_uses_resource_template`. +- `cargo test -p tui` still fails existing tests: `multi_pod::tests::orchestrator_launch_context_includes_ticket_and_workspace_facts`, `multi_pod::tests::spawn_or_restore_companion_restores_live_pod`, `multi_pod::tests::spawn_or_restore_companion_spawns_missing_pod`. +- Focused weak-notify/progress tests pass; reviewer should confirm the broad failures are unrelated/pre-existing and not introduced by this Ticket. + +Next: +- Spawn read-only sibling reviewer against the recorded intent, binding decisions/invariants, acceptance criteria, implementation commit `a87d3154`, synced review head `724b79f1`, diff, validation evidence, and known broad-suite failures. + +--- From 46da9523ed4d807d50e4ed2631ced9245f7e40fd Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 13 Jun 2026 00:24:00 +0900 Subject: [PATCH 25/41] ticket: delegate companion notify review --- .yoi/tickets/00001KTTW04W2/item.md | 2 +- .yoi/tickets/00001KTTW04W2/thread.md | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KTTW04W2/item.md b/.yoi/tickets/00001KTTW04W2/item.md index 963484cb..2f24d21c 100644 --- a/.yoi/tickets/00001KTTW04W2/item.md +++ b/.yoi/tickets/00001KTTW04W2/item.md @@ -2,7 +2,7 @@ title: 'Orchestrator進捗をAutoKickなしでCompanionへ通知する' state: 'inprogress' created_at: '2026-06-11T08:15:24Z' -updated_at: '2026-06-12T15:23:02Z' +updated_at: '2026-06-12T15:23:52Z' assignee: null queued_by: 'workspace-panel' queued_at: '2026-06-11T10:31:56Z' diff --git a/.yoi/tickets/00001KTTW04W2/thread.md b/.yoi/tickets/00001KTTW04W2/thread.md index 05b3b5b1..eb76c255 100644 --- a/.yoi/tickets/00001KTTW04W2/thread.md +++ b/.yoi/tickets/00001KTTW04W2/thread.md @@ -226,3 +226,19 @@ Next: - Spawn read-only sibling reviewer against the recorded intent, binding decisions/invariants, acceptance criteria, implementation commit `a87d3154`, synced review head `724b79f1`, diff, validation evidence, and known broad-suite failures. --- + + + +## Plan + +Reviewer delegated. + +- Reviewer Pod: `yoi-reviewer-companion-progress-notify` +- Target branch/worktree: `ticket/orchestrator-progress-companion-notify` at `/home/hare/Projects/yoi/.worktree/orchestrator-progress-companion-notify` +- Implementation commit: `a87d3154 feat: weak companion progress notify` +- Synced review head: `724b79f1 Merge branch 'orchestration/yoi-orchestrator' into ticket/orchestrator-progress-companion-notify` +- 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 after syncing with current Orchestrator branch, focused validation evidence, `nix build` evidence, and known broad-suite failures. +- Reviewer focus: no AutoKick/re-kick/scheduler behavior, legacy notify compatibility, bounded/history-backed progress notices, missing/stopped Companion no-op, no authority expansion, Panel freshness UI compatibility with merged focus model, and robust focused tests. + +--- From 4ad7ae9b8fab3ea07a5d606dd29dae99f6225657 Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 13 Jun 2026 00:30:37 +0900 Subject: [PATCH 26/41] ticket: request companion notify changes --- .yoi/tickets/00001KTTW04W2/item.md | 2 +- .yoi/tickets/00001KTTW04W2/thread.md | 42 ++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KTTW04W2/item.md b/.yoi/tickets/00001KTTW04W2/item.md index 2f24d21c..5c241c7b 100644 --- a/.yoi/tickets/00001KTTW04W2/item.md +++ b/.yoi/tickets/00001KTTW04W2/item.md @@ -2,7 +2,7 @@ title: 'Orchestrator進捗をAutoKickなしでCompanionへ通知する' state: 'inprogress' created_at: '2026-06-11T08:15:24Z' -updated_at: '2026-06-12T15:23:52Z' +updated_at: '2026-06-12T15:30:03Z' assignee: null queued_by: 'workspace-panel' queued_at: '2026-06-11T10:31:56Z' diff --git a/.yoi/tickets/00001KTTW04W2/thread.md b/.yoi/tickets/00001KTTW04W2/thread.md index eb76c255..7c67eda4 100644 --- a/.yoi/tickets/00001KTTW04W2/thread.md +++ b/.yoi/tickets/00001KTTW04W2/thread.md @@ -242,3 +242,45 @@ Reviewer delegated. - Reviewer focus: no AutoKick/re-kick/scheduler behavior, legacy notify compatibility, bounded/history-backed progress notices, missing/stopped Companion no-op, no authority expansion, Panel freshness UI compatibility with merged focus model, and robust focused tests. --- + + + +## Review: request changes + +Request changes. + +Blocker: +- `crates/tui/src/multi_pod.rs` の `companion_progress_notice()` が、Companion の model context に入る progress notice の framing 文言を Rust に直書きしている。 +- この notice は `dispatch_companion_progress_notice()` から `Notify { auto_run: false }` として送られ、NotifyBuffer 経由で history-backed な SystemItem / LLM context になるため、単なる Panel UI 文言ではなく LLM-facing framing と判断する。 +- Ticket の明示要件「Prompt / workflow 文言を Rust code に直書きしない」「LLM-facing summary framing が必要な場合は `resources/prompts` 側に置く」に反している。 + +Requested change: +- Rust 側は bounded runtime values の組み立てに留める。 +- progress notice の framing/template は `resources/prompts` 配下へ移す。 +- template/resource rendering の focused test を追加または既存 `companion_progress` tests を更新し、framing が resource 側から来ることと bounded/sensitive-safe values が維持されることを確認する。 + +Good evidence confirmed: +- `auto_run:false` は idle Pod に `RunForNotification` を staged しない。 +- `auto_run:true` と legacy missing field の挙動は維持されている。 +- live/reachable Companion の socket に `Notify { auto_run: false }` を送るだけで、missing/stopped/unreachable では spawn/restore されない。 +- 通知内容は Ticket id/title/state、role pod status、short reason、`.yoi/tickets/` ref に絞られ、thread excerpt / diagnostics / provider error token はテストで除外確認されている。 +- Companion profile / authority 強化は diff 上ない。 +- Panel freshness は header detail 表示で、composer target / row selection の focus model には干渉していない。 + +Reviewer-run validation: +- `git diff --check orchestration/yoi-orchestrator...HEAD`: pass +- `cargo test -p protocol`: pass, 39 tests +- `cargo test -p pod --test controller_test`: pass, 36 tests +- `cargo test -p tui companion_progress -- --nocapture`: pass, 5 tests +- `cargo test -p tui send_notify_only_can_deliver_weak_notification_without_auto_run -- --nocapture`: pass, 1 test +- `cargo check -p protocol -p pod -p tui`: pass +- `cargo fmt --check`: pass +- `cargo run -p yoi -- ticket doctor`: `doctor: ok` +- `nix build .#yoi`: pass + +Broad-suite failures: +- `cargo test -p pod` currently fails 2 prompt-resource assertion tests. +- `cargo test -p tui` currently fails 3 unrelated-looking tests. +- These broad-suite failures do not appear caused by the progress-notify diff and are not the requested-change blocker. + +--- From 042da1bcfd993ff996e1fdd09d95fcd933123d2b Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 13 Jun 2026 00:31:08 +0900 Subject: [PATCH 27/41] ticket: delegate companion notify fix --- .yoi/tickets/00001KTTW04W2/item.md | 2 +- .yoi/tickets/00001KTTW04W2/thread.md | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KTTW04W2/item.md b/.yoi/tickets/00001KTTW04W2/item.md index 5c241c7b..653c093d 100644 --- a/.yoi/tickets/00001KTTW04W2/item.md +++ b/.yoi/tickets/00001KTTW04W2/item.md @@ -2,7 +2,7 @@ title: 'Orchestrator進捗をAutoKickなしでCompanionへ通知する' state: 'inprogress' created_at: '2026-06-11T08:15:24Z' -updated_at: '2026-06-12T15:30:03Z' +updated_at: '2026-06-12T15:31:01Z' assignee: null queued_by: 'workspace-panel' queued_at: '2026-06-11T10:31:56Z' diff --git a/.yoi/tickets/00001KTTW04W2/thread.md b/.yoi/tickets/00001KTTW04W2/thread.md index 7c67eda4..9a14589b 100644 --- a/.yoi/tickets/00001KTTW04W2/thread.md +++ b/.yoi/tickets/00001KTTW04W2/thread.md @@ -284,3 +284,25 @@ Broad-suite failures: - These broad-suite failures do not appear caused by the progress-notify diff and are not the requested-change blocker. --- + + + +## Plan + +Fix loop delegated to coder. + +Reviewer blocker: +- `companion_progress_notice()` hard-coded LLM-facing progress notice framing in Rust. +- The notice reaches Companion model context through `Notify { auto_run: false }` / NotifyBuffer, so it must follow the prompt-resource boundary. + +Coder fix request: +- Move progress notice framing/template to `resources/prompts` using existing resource conventions. +- Keep Rust limited to bounded runtime values and resource rendering/loading. +- Preserve weak notify semantics, live/reachable-only delivery, missing/stopped no spawn/restore, bounded/sensitive-safe summary, no persistent snapshot store, and no authority expansion. +- Add/update focused tests proving resource-backed framing and bounded/sensitive-safe runtime values. + +Status: +- Sent fix request to `yoi-coder-companion-progress-notify` on existing branch/worktree. +- Reviewer verdict remains `request_changes` until coder fix and second review. + +--- From 61e6c0683c955776a239ebe09754b054d0cc4a81 Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 13 Jun 2026 00:38:52 +0900 Subject: [PATCH 28/41] fix: resource-back companion progress notice --- Cargo.lock | 1 + crates/tui/Cargo.toml | 1 + crates/tui/src/multi_pod.rs | 178 ++++++++++++------ package.nix | 2 +- .../panel/companion_progress_notice.md | 12 ++ 5 files changed, 137 insertions(+), 57 deletions(-) create mode 100644 resources/prompts/panel/companion_progress_notice.md diff --git a/Cargo.lock b/Cargo.lock index 6db38a35..ec748fa1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3962,6 +3962,7 @@ dependencies = [ "crossterm 0.28.1", "llm-worker", "manifest", + "minijinja", "pod-registry", "pod-store", "protocol", diff --git a/crates/tui/Cargo.toml b/crates/tui/Cargo.toml index 0fc0b5de..14911805 100644 --- a/crates/tui/Cargo.toml +++ b/crates/tui/Cargo.toml @@ -22,6 +22,7 @@ pod-registry = { workspace = true } provider = { workspace = true } ticket = { workspace = true } serde = { workspace = true, features = ["derive"] } +minijinja = "2.19.0" pulldown-cmark = { version = "0.13.3", default-features = false } llm-worker.workspace = true diff --git a/crates/tui/src/multi_pod.rs b/crates/tui/src/multi_pod.rs index c5540282..c3b45b5b 100644 --- a/crates/tui/src/multi_pod.rs +++ b/crates/tui/src/multi_pod.rs @@ -21,6 +21,7 @@ use ratatui::layout::{Constraint, Layout, Position, Rect}; use ratatui::style::{Color, Modifier, Style}; use ratatui::text::{Line, Span}; use ratatui::widgets::{Block, Borders, Clear, Paragraph, Widget, Wrap}; +use serde::Serialize; use session_store::FsStore; use ticket::config::TicketConfig; use ticket::{LocalTicketBackend, TicketBackend, TicketIdOrSlug, TicketWorkflowState}; @@ -52,6 +53,8 @@ const CLOSED_VISIBLE_ROWS: usize = 3; const COMPANION_PROGRESS_MAX_TICKETS: usize = 5; const COMPANION_PROGRESS_MAX_TITLE_CHARS: usize = 80; const COMPANION_PROGRESS_MAX_MESSAGE_CHARS: usize = 1_800; +const COMPANION_PROGRESS_NOTICE_TEMPLATE: &str = + include_str!("../../../resources/prompts/panel/companion_progress_notice.md"); const SOCKET_OP_TIMEOUT: Duration = Duration::from_secs(3); const MULTI_POD_POLL_INTERVAL: Duration = Duration::from_millis(1_500); const TERMINAL_EVENT_POLL_INTERVAL: Duration = Duration::from_millis(100); @@ -573,6 +576,35 @@ impl CompanionProgressNoticeResult { } } +#[derive(Debug, Serialize)] +struct CompanionProgressTemplateContext { + companion: CompanionProgressTemplateRole, + orchestrator: CompanionProgressTemplateRole, + tickets: Vec, + omitted_ticket_count: usize, + role_pods: Vec, +} + +#[derive(Debug, Serialize)] +struct CompanionProgressTemplateRole { + pod_name: String, + status: String, +} + +#[derive(Debug, Serialize)] +struct CompanionProgressTemplateTicket { + id: String, + state: String, + title: String, + reference: String, +} + +#[derive(Debug, Serialize)] +struct CompanionProgressTemplateRolePod { + name: String, + status: String, +} + pub(crate) struct MultiPodApp { pub(crate) list: PodList, pub(crate) panel: WorkspacePanelViewModel, @@ -2426,57 +2458,44 @@ fn companion_progress_notice( ) -> Option { let companion = panel.header.companion.as_ref()?; let orchestrator = panel.header.orchestrator.as_ref()?; - let mut lines = vec![ - "Orchestrator progress context (read-only weak notification; no auto-run).".to_string(), - "Reason: workspace Panel refreshed bounded orchestration progress for Companion explanation." - .to_string(), - format!( - "Roles: Companion {} is {}; Orchestrator {} is {}.", - companion.pod_name, - companion.status.label(), - orchestrator.pod_name, - orchestrator.status.label() - ), - ]; - - let mut ticket_lines = Vec::new(); - for row in panel.rows.iter().take(COMPANION_PROGRESS_MAX_TICKETS) { - let ticket_id = row - .ticket - .as_ref() - .map(|ticket| ticket.id.as_str()) - .unwrap_or("unknown-ticket"); - ticket_lines.push(format!( - "- {} [{}] {} (ref: .yoi/tickets/{})", - ticket_id, - row.status, - bounded_progress_text(&row.title, COMPANION_PROGRESS_MAX_TITLE_CHARS), - ticket_id - )); - } - if ticket_lines.is_empty() { - lines.push("Tickets: none visible in the current Panel snapshot.".to_string()); - } else { - lines.push(format!( - "Tickets (first {} visible, bounded):", - ticket_lines.len() - )); - lines.extend(ticket_lines); - if panel.rows.len() > COMPANION_PROGRESS_MAX_TICKETS { - lines.push(format!( - "- … {} more ticket(s) omitted from this bounded notice.", - panel.rows.len() - COMPANION_PROGRESS_MAX_TICKETS - )); - } - } - - let role_pod_lines = bounded_role_pod_lines(list, companion, orchestrator); - if !role_pod_lines.is_empty() { - lines.push("Role pod status snapshot:".to_string()); - lines.extend(role_pod_lines); - } - - let message = bounded_progress_text(&lines.join("\n"), COMPANION_PROGRESS_MAX_MESSAGE_CHARS); + let ticket_rows = panel + .rows + .iter() + .filter_map(|row| row.ticket.as_ref().map(|ticket| (row, ticket))) + .collect::>(); + let tickets = ticket_rows + .iter() + .take(COMPANION_PROGRESS_MAX_TICKETS) + .map(|(row, ticket)| CompanionProgressTemplateTicket { + id: bounded_progress_text(&ticket.id, COMPANION_PROGRESS_MAX_TITLE_CHARS), + state: bounded_progress_text(&row.status, COMPANION_PROGRESS_MAX_TITLE_CHARS), + title: bounded_progress_text(&row.title, COMPANION_PROGRESS_MAX_TITLE_CHARS), + reference: format!(".yoi/tickets/{}", ticket.id), + }) + .collect::>(); + let context = CompanionProgressTemplateContext { + companion: CompanionProgressTemplateRole { + pod_name: bounded_progress_text( + &companion.pod_name, + COMPANION_PROGRESS_MAX_TITLE_CHARS, + ), + status: companion.status.label().to_string(), + }, + orchestrator: CompanionProgressTemplateRole { + pod_name: bounded_progress_text( + &orchestrator.pod_name, + COMPANION_PROGRESS_MAX_TITLE_CHARS, + ), + status: orchestrator.status.label().to_string(), + }, + tickets, + omitted_ticket_count: ticket_rows + .len() + .saturating_sub(COMPANION_PROGRESS_MAX_TICKETS), + role_pods: bounded_role_pod_values(list, companion, orchestrator), + }; + let rendered = render_companion_progress_notice_template(&context).ok()?; + let message = bounded_progress_text(&rendered, COMPANION_PROGRESS_MAX_MESSAGE_CHARS); let fingerprint = message.clone(); Some(CompanionProgressNotice { message, @@ -2484,19 +2503,35 @@ fn companion_progress_notice( }) } -fn bounded_role_pod_lines( +fn render_companion_progress_notice_template( + context: &CompanionProgressTemplateContext, +) -> Result { + let mut env = minijinja::Environment::new(); + env.set_undefined_behavior(minijinja::UndefinedBehavior::Strict); + env.add_template( + "companion_progress_notice", + COMPANION_PROGRESS_NOTICE_TEMPLATE, + )?; + env.get_template("companion_progress_notice")? + .render(context) +} + +fn bounded_role_pod_values( list: &PodList, companion: &CompanionPanelState, orchestrator: &OrchestratorPanelState, -) -> Vec { - let mut lines = Vec::new(); +) -> Vec { + let mut role_pods = Vec::new(); for name in [&companion.pod_name, &orchestrator.pod_name] { let Some(entry) = list.entries.iter().find(|entry| entry.name == *name) else { continue; }; - lines.push(format!("- {}: {}", entry.name, row_status_label(entry).0)); + role_pods.push(CompanionProgressTemplateRolePod { + name: bounded_progress_text(&entry.name, COMPANION_PROGRESS_MAX_TITLE_CHARS), + status: row_status_label(entry).0.to_string(), + }); } - lines + role_pods } fn bounded_progress_text(input: &str, max_chars: usize) -> String { @@ -4987,6 +5022,37 @@ mod tests { ); } + #[test] + fn companion_progress_notice_uses_prompt_resource_template() { + let first_resource_line = COMPANION_PROGRESS_NOTICE_TEMPLATE.lines().next().unwrap(); + let context = CompanionProgressTemplateContext { + companion: CompanionProgressTemplateRole { + pod_name: "yoi".to_string(), + status: "Live".to_string(), + }, + orchestrator: CompanionProgressTemplateRole { + pod_name: "test-orchestrator".to_string(), + status: "Live".to_string(), + }, + tickets: vec![CompanionProgressTemplateTicket { + id: "RESOURCE-TICKET".to_string(), + state: "inprogress".to_string(), + title: "Rendered from runtime values".to_string(), + reference: ".yoi/tickets/RESOURCE-TICKET".to_string(), + }], + omitted_ticket_count: 0, + role_pods: vec![CompanionProgressTemplateRolePod { + name: "yoi".to_string(), + status: "idle".to_string(), + }], + }; + + let rendered = render_companion_progress_notice_template(&context).unwrap(); + assert!(rendered.contains(first_resource_line)); + assert!(rendered.contains("RESOURCE-TICKET")); + assert!(rendered.contains("Rendered from runtime values")); + } + #[test] fn companion_progress_notice_is_bounded_and_excludes_sensitive_unbounded_fields() { let mut app = ticket_enabled_app(vec![ diff --git a/package.nix b/package.nix index d21b5ce9..ae0eb0c0 100644 --- a/package.nix +++ b/package.nix @@ -40,7 +40,7 @@ rustPlatform.buildRustPackage rec { filter = sourceFilter; }; - cargoHash = "sha256-WvMpHbTswYeRrkw5I4V4E1RnG7j13PbuQCbeas/XILs="; + cargoHash = "sha256-o47Erp9UrS2Rgwd0JNpuYPO4pZmv62DkzY9KXMQpyAM="; depsExtraArgs = { # Older fetchCargoVendor utilities used crates.io's API download endpoint, diff --git a/resources/prompts/panel/companion_progress_notice.md b/resources/prompts/panel/companion_progress_notice.md new file mode 100644 index 00000000..7c52ee05 --- /dev/null +++ b/resources/prompts/panel/companion_progress_notice.md @@ -0,0 +1,12 @@ +Orchestrator progress context (read-only weak notification; no auto-run). +Reason: workspace Panel refreshed bounded orchestration progress for Companion explanation. +Roles: Companion {{ companion.pod_name }} is {{ companion.status }}; Orchestrator {{ orchestrator.pod_name }} is {{ orchestrator.status }}. + +{% if tickets %}Tickets (first {{ tickets | length }} visible, bounded): +{% for ticket in tickets %}- {{ ticket.id }} [{{ ticket.state }}] {{ ticket.title }} (ref: {{ ticket.reference }}) +{% endfor %}{% if omitted_ticket_count > 0 %}- … {{ omitted_ticket_count }} more ticket(s) omitted from this bounded notice. +{% endif %}{% else %}Tickets: none visible in the current Panel snapshot. +{% endif %}{% if role_pods %} +Role pod status snapshot: +{% for role_pod in role_pods %}- {{ role_pod.name }}: {{ role_pod.status }} +{% endfor %}{% endif %} From 40bdb90243d7f7998a44875ea4b9f89cadf0949f Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 13 Jun 2026 00:40:21 +0900 Subject: [PATCH 29/41] ticket: record companion notify fix --- .yoi/tickets/00001KTTW04W2/item.md | 2 +- .yoi/tickets/00001KTTW04W2/thread.md | 42 ++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KTTW04W2/item.md b/.yoi/tickets/00001KTTW04W2/item.md index 653c093d..e9d81924 100644 --- a/.yoi/tickets/00001KTTW04W2/item.md +++ b/.yoi/tickets/00001KTTW04W2/item.md @@ -2,7 +2,7 @@ title: 'Orchestrator進捗をAutoKickなしでCompanionへ通知する' state: 'inprogress' created_at: '2026-06-11T08:15:24Z' -updated_at: '2026-06-12T15:31:01Z' +updated_at: '2026-06-12T15:40:14Z' assignee: null queued_by: 'workspace-panel' queued_at: '2026-06-11T10:31:56Z' diff --git a/.yoi/tickets/00001KTTW04W2/thread.md b/.yoi/tickets/00001KTTW04W2/thread.md index 9a14589b..e29717e3 100644 --- a/.yoi/tickets/00001KTTW04W2/thread.md +++ b/.yoi/tickets/00001KTTW04W2/thread.md @@ -306,3 +306,45 @@ Status: - Reviewer verdict remains `request_changes` until coder fix and second review. --- + + + +## Implementation report + +Coder completed requested fix. + +Coder Pod: +- `yoi-coder-companion-progress-notify` + +Implementation branch/worktree: +- Branch: `ticket/orchestrator-progress-companion-notify` +- Worktree: `/home/hare/Projects/yoi/.worktree/orchestrator-progress-companion-notify` +- Original implementation commit: `a87d3154 feat: weak companion progress notify` +- Sync merge commit: `724b79f1 Merge branch 'orchestration/yoi-orchestrator' into ticket/orchestrator-progress-companion-notify` +- Fix commit: `61e6c068 fix: resource-back companion progress notice` +- Worktree status: clean + +Fix summary: +- Added `resources/prompts/panel/companion_progress_notice.md` for model-facing Companion progress notice framing. +- Removed hard-coded progress notice framing from Rust. +- Rust now builds bounded runtime values and renders the prompt resource via `minijinja`. +- Added focused test proving the notice renders from the prompt resource. +- Preserved live/reachable-only delivery, missing/stopped no-op, `auto_run:false`, bounded output, sensitive-field exclusion, and no persistent snapshot store. +- Added `minijinja` to `crates/tui/Cargo.toml`, updated `Cargo.lock`, and updated `package.nix` `cargoHash` for Nix packaging. + +Orchestrator-verified evidence from child worktree after fix: +- `git status --short --branch`: clean on `ticket/orchestrator-progress-companion-notify` +- `cargo test -p protocol`: pass, 39 tests +- `cargo test -p pod --test controller_test`: pass, 36 tests +- `cargo test -p tui companion_progress -- --nocapture`: pass, 6 tests +- `cargo test -p tui send_notify_only_can_deliver_weak_notification_without_auto_run -- --nocapture`: pass, 1 test +- `cargo check -p protocol -p pod -p tui`: 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 + +Next: +- Request reviewer re-review of fix commit `61e6c068`, focusing on the prompt-resource boundary blocker, retention of weak notify safety invariants, dependency/package updates, and validation evidence. + +--- From 85f4bafca625191c313a3f0a17533f98b3f555ed Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 13 Jun 2026 00:42:28 +0900 Subject: [PATCH 30/41] ticket: approve companion notify review --- .yoi/tickets/00001KTTW04W2/item.md | 2 +- .yoi/tickets/00001KTTW04W2/thread.md | 47 ++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KTTW04W2/item.md b/.yoi/tickets/00001KTTW04W2/item.md index e9d81924..48b83ea4 100644 --- a/.yoi/tickets/00001KTTW04W2/item.md +++ b/.yoi/tickets/00001KTTW04W2/item.md @@ -2,7 +2,7 @@ title: 'Orchestrator進捗をAutoKickなしでCompanionへ通知する' state: 'inprogress' created_at: '2026-06-11T08:15:24Z' -updated_at: '2026-06-12T15:40:14Z' +updated_at: '2026-06-12T15:42:21Z' assignee: null queued_by: 'workspace-panel' queued_at: '2026-06-11T10:31:56Z' diff --git a/.yoi/tickets/00001KTTW04W2/thread.md b/.yoi/tickets/00001KTTW04W2/thread.md index e29717e3..956006e6 100644 --- a/.yoi/tickets/00001KTTW04W2/thread.md +++ b/.yoi/tickets/00001KTTW04W2/thread.md @@ -348,3 +348,50 @@ Next: - Request reviewer re-review of fix commit `61e6c068`, focusing on the prompt-resource boundary blocker, retention of weak notify safety invariants, dependency/package updates, and validation evidence. --- + + + +## Review: approve + +Approve. + +The previous requested-change blocker is resolved. + +Review findings: +- Prompt/resource boundary is now satisfied: + - Companion progress notice framing lives in `resources/prompts/panel/companion_progress_notice.md`. + - Rust only builds bounded runtime values and renders the prompt resource via `minijinja`. + - `COMPANION_PROGRESS_NOTICE_TEMPLATE` uses `include_str!` to load the resource. + - `render_companion_progress_notice_template()` renders with strict `minijinja` undefined behavior. + - Focused test `companion_progress_notice_uses_prompt_resource_template` confirms rendering comes from the resource. + - Grep for prior hard-coded framing found no Rust production copy of the prompt text; only test assertion remains. +- Weak notify invariants remain intact: + - Companion progress dispatch still uses `send_notify_only(..., false)`. + - `auto_run:false` controller behavior remains covered by pod controller tests. + - `auto_run:true` legacy behavior remains covered by protocol/controller tests. +- Missing/stopped/unreachable Companion remains no-op: + - Target selection still requires Companion panel state to be peer-reachable and a live reachable socket. + - No spawn/restore path is introduced for progress delivery. +- No Companion authority expansion found: + - The fix touches prompt rendering, TUI dependency metadata, `Cargo.lock`, and `package.nix`; it does not alter Companion profile/tool authority. +- Bounded/sensitive-safe summary remains preserved: + - Runtime values are bounded before/after rendering. + - The summary still uses ticket id/state/title/ref and role pod status, not thread excerpts, diagnostics, Pod output, or provider error details. +- Dependency/package updates are appropriate: + - `minijinja = "2.19.0"` is already used elsewhere in the workspace (`crates/pod`), so adding it to `crates/tui` does not introduce an unexpected new dependency family. + - `Cargo.lock` and `package.nix` `cargoHash` were updated, and `nix build .#yoi` passes. + +Reviewer-run validation: +- `git diff --check orchestration/yoi-orchestrator...HEAD`: pass +- `cargo test -p tui companion_progress -- --nocapture`: pass, 6 tests +- `cargo test -p tui send_notify_only_can_deliver_weak_notification_without_auto_run -- --nocapture`: pass, 1 test +- `cargo check -p protocol -p pod -p tui`: pass +- `cargo fmt --check`: pass +- `cargo run -p yoi -- ticket doctor`: `doctor: ok` +- `nix build .#yoi`: pass +- `cargo test -p protocol`: pass, 39 tests +- `cargo test -p pod --test controller_test`: pass, 36 tests + +No blocking issues remain. + +--- From 2b64f42854c86778eca5e840a3176a96050a0170 Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 13 Jun 2026 00:44:04 +0900 Subject: [PATCH 31/41] ticket: mark companion notify done --- .yoi/tickets/00001KTTW04W2/item.md | 4 +-- .yoi/tickets/00001KTTW04W2/thread.md | 46 ++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/.yoi/tickets/00001KTTW04W2/item.md b/.yoi/tickets/00001KTTW04W2/item.md index 48b83ea4..4111343f 100644 --- a/.yoi/tickets/00001KTTW04W2/item.md +++ b/.yoi/tickets/00001KTTW04W2/item.md @@ -1,8 +1,8 @@ --- title: 'Orchestrator進捗をAutoKickなしでCompanionへ通知する' -state: 'inprogress' +state: 'done' created_at: '2026-06-11T08:15:24Z' -updated_at: '2026-06-12T15:42:21Z' +updated_at: '2026-06-12T15:43:57Z' assignee: null queued_by: 'workspace-panel' queued_at: '2026-06-11T10:31:56Z' diff --git a/.yoi/tickets/00001KTTW04W2/thread.md b/.yoi/tickets/00001KTTW04W2/thread.md index 956006e6..63f8de22 100644 --- a/.yoi/tickets/00001KTTW04W2/thread.md +++ b/.yoi/tickets/00001KTTW04W2/thread.md @@ -395,3 +395,49 @@ Reviewer-run validation: No blocking issues remain. --- + + + +## Implementation report + +Integration completed in the Orchestrator workspace. + +Integrated implementation: +- Ticket: `00001KTTW04W2` +- Implementation branch: `ticket/orchestrator-progress-companion-notify` +- Implementation commits: + - `a87d3154 feat: weak companion progress notify` + - `61e6c068 fix: resource-back companion progress notice` +- Reviewer result: approve after fix-loop, no blockers remain +- Orchestrator merge commit: `56b10a2d merge: companion weak progress notify` +- Integration target: `orchestration/yoi-orchestrator` + +Post-integration validation from Orchestrator worktree: +- `cargo test -p protocol`: pass, 39 tests +- `cargo test -p pod --test controller_test`: pass, 36 tests +- `cargo test -p tui companion_progress -- --nocapture`: pass, 6 tests +- `cargo test -p tui send_notify_only_can_deliver_weak_notification_without_auto_run -- --nocapture`: pass, 1 test +- `cargo check -p protocol -p pod -p tui`: pass +- `cargo fmt --check`: pass +- `git diff --check HEAD~1..HEAD`: pass +- `./result/bin/yoi ticket doctor`: `doctor: ok` +- `nix build .#yoi`: pass + +Fix-loop resolution: +- Reviewer blocker about Rust-hard-coded LLM-facing progress notice framing was resolved by moving framing to `resources/prompts/panel/companion_progress_notice.md` and rendering it from bounded runtime values. + +Remaining known broad-suite failures: +- Existing prompt/TUI broad-suite failures noted earlier remain outside this Ticket and were not blockers for the focused implementation/review. + +Remaining risks: +- None blocking. Added `minijinja` dependency to `crates/tui`, already used elsewhere in workspace; `Cargo.lock` and `package.nix` were updated and Nix build passed. + +--- + + + +## State changed + +Implementation was merged into the Orchestrator branch after reviewer-requested fix, reviewer approved with no blockers, and post-integration validation passed. Cleanup and closure will be recorded next. + +--- From 6a1d60f9a53c1d2af1c814d486fbe3234c37038a Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 13 Jun 2026 00:44:49 +0900 Subject: [PATCH 32/41] ticket: close companion notify --- .yoi/tickets/00001KTTW04W2/item.md | 4 +- .yoi/tickets/00001KTTW04W2/resolution.md | 41 +++++++++++++ .yoi/tickets/00001KTTW04W2/thread.md | 73 ++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 .yoi/tickets/00001KTTW04W2/resolution.md diff --git a/.yoi/tickets/00001KTTW04W2/item.md b/.yoi/tickets/00001KTTW04W2/item.md index 4111343f..09797e95 100644 --- a/.yoi/tickets/00001KTTW04W2/item.md +++ b/.yoi/tickets/00001KTTW04W2/item.md @@ -1,8 +1,8 @@ --- title: 'Orchestrator進捗をAutoKickなしでCompanionへ通知する' -state: 'done' +state: 'closed' created_at: '2026-06-11T08:15:24Z' -updated_at: '2026-06-12T15:43:57Z' +updated_at: '2026-06-12T15:44:42Z' assignee: null queued_by: 'workspace-panel' queued_at: '2026-06-11T10:31:56Z' diff --git a/.yoi/tickets/00001KTTW04W2/resolution.md b/.yoi/tickets/00001KTTW04W2/resolution.md new file mode 100644 index 00000000..320420eb --- /dev/null +++ b/.yoi/tickets/00001KTTW04W2/resolution.md @@ -0,0 +1,41 @@ +Orchestrator progress を AutoKick なしで live/reachable Companion に通知する仕組みを実装した。 + +実装概要: +- `Method::Notify { auto_run: bool }` を追加し、`auto_run: false` では idle Pod に `RunForNotification` を stage しない weak notification にした。 +- `auto_run: true` と legacy missing-field behavior は既存 Notify と互換にした。 +- Panel から live/reachable Companion へ bounded progress notice を `Notify { auto_run: false }` で送るようにした。 +- missing/stopped/unreachable Companion は best-effort no-op とし、spawn/restore しない。 +- Progress summary は Ticket id/title/state、role pod status、short reason、`.yoi/tickets/` refs に限定し、full thread、Pod output、diagnostics、provider errors、secret-like content を含めない。 +- Panel に Companion progress freshness / last-updated indication を追加した。 +- Reviewer request_changes を受け、Companion progress notice の LLM-facing framing を Rust 直書きから `resources/prompts/panel/companion_progress_notice.md` へ移し、Rust は bounded runtime values の rendering に限定した。 +- Companion profile/tool authority は変更していない。 + +Review / integration: +- Implementation commits: + - `a87d3154 feat: weak companion progress notify` + - `61e6c068 fix: resource-back companion progress notice` +- Reviewer: `yoi-reviewer-companion-progress-notify` が初回 request_changes、fix 後 approve。 +- Orchestrator merge commit: `56b10a2d merge: companion weak progress notify` +- Ticket completion commit: `2b64f428 ticket: mark companion notify done` + +Validation: +- `cargo test -p protocol`: pass, 39 tests +- `cargo test -p pod --test controller_test`: pass, 36 tests +- `cargo test -p tui companion_progress -- --nocapture`: pass, 6 tests +- `cargo test -p tui send_notify_only_can_deliver_weak_notification_without_auto_run -- --nocapture`: pass, 1 test +- `cargo check -p protocol -p pod -p tui`: 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 broad-suite failures: +- Existing prompt/TUI broad-suite failures noted in thread remain outside this Ticket and were not blockers for focused implementation/review. + +Cleanup: +- coder/reviewer Pods stopped。 +- child worktree `/home/hare/Projects/yoi/.worktree/orchestrator-progress-companion-notify` removed。 +- branch `ticket/orchestrator-progress-companion-notify` deleted。 + +Non-blocking risk: +- Added `minijinja` dependency to `crates/tui`; it is already used elsewhere in the workspace, and `Cargo.lock` / `package.nix` were updated with passing Nix build. \ No newline at end of file diff --git a/.yoi/tickets/00001KTTW04W2/thread.md b/.yoi/tickets/00001KTTW04W2/thread.md index 63f8de22..b6ca1dbf 100644 --- a/.yoi/tickets/00001KTTW04W2/thread.md +++ b/.yoi/tickets/00001KTTW04W2/thread.md @@ -441,3 +441,76 @@ Remaining risks: Implementation was merged into the Orchestrator branch after reviewer-requested fix, reviewer approved with no blockers, and post-integration validation passed. Cleanup and closure will be recorded next. --- + + + +## Implementation report + +Cleanup completed. + +- Stopped coder Pod: `yoi-coder-companion-progress-notify` +- Stopped reviewer Pod: `yoi-reviewer-companion-progress-notify` +- Removed child worktree: `/home/hare/Projects/yoi/.worktree/orchestrator-progress-companion-notify` +- Deleted implementation branch: `ticket/orchestrator-progress-companion-notify` +- Orchestrator worktree status after cleanup: clean + +Cleanup was limited to child implementation worktree/branch and related child Pods. Root/original workspace was not touched. + +--- + + + +## State changed + +Ticket を closed にしました。 + + +--- + + + +## 完了 + +Orchestrator progress を AutoKick なしで live/reachable Companion に通知する仕組みを実装した。 + +実装概要: +- `Method::Notify { auto_run: bool }` を追加し、`auto_run: false` では idle Pod に `RunForNotification` を stage しない weak notification にした。 +- `auto_run: true` と legacy missing-field behavior は既存 Notify と互換にした。 +- Panel から live/reachable Companion へ bounded progress notice を `Notify { auto_run: false }` で送るようにした。 +- missing/stopped/unreachable Companion は best-effort no-op とし、spawn/restore しない。 +- Progress summary は Ticket id/title/state、role pod status、short reason、`.yoi/tickets/` refs に限定し、full thread、Pod output、diagnostics、provider errors、secret-like content を含めない。 +- Panel に Companion progress freshness / last-updated indication を追加した。 +- Reviewer request_changes を受け、Companion progress notice の LLM-facing framing を Rust 直書きから `resources/prompts/panel/companion_progress_notice.md` へ移し、Rust は bounded runtime values の rendering に限定した。 +- Companion profile/tool authority は変更していない。 + +Review / integration: +- Implementation commits: + - `a87d3154 feat: weak companion progress notify` + - `61e6c068 fix: resource-back companion progress notice` +- Reviewer: `yoi-reviewer-companion-progress-notify` が初回 request_changes、fix 後 approve。 +- Orchestrator merge commit: `56b10a2d merge: companion weak progress notify` +- Ticket completion commit: `2b64f428 ticket: mark companion notify done` + +Validation: +- `cargo test -p protocol`: pass, 39 tests +- `cargo test -p pod --test controller_test`: pass, 36 tests +- `cargo test -p tui companion_progress -- --nocapture`: pass, 6 tests +- `cargo test -p tui send_notify_only_can_deliver_weak_notification_without_auto_run -- --nocapture`: pass, 1 test +- `cargo check -p protocol -p pod -p tui`: 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 broad-suite failures: +- Existing prompt/TUI broad-suite failures noted in thread remain outside this Ticket and were not blockers for focused implementation/review. + +Cleanup: +- coder/reviewer Pods stopped。 +- child worktree `/home/hare/Projects/yoi/.worktree/orchestrator-progress-companion-notify` removed。 +- branch `ticket/orchestrator-progress-companion-notify` deleted。 + +Non-blocking risk: +- Added `minijinja` dependency to `crates/tui`; it is already used elsewhere in the workspace, and `Cargo.lock` / `package.nix` were updated with passing Nix build. + +--- From e72a4536b4038b402e8a997a6f58b51acb0546dd Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 13 Jun 2026 00:46:37 +0900 Subject: [PATCH 33/41] ticket: accept idle queued rekick --- .../artifacts/orchestration-plan.jsonl | 1 + .yoi/tickets/00001KTJXS31R/item.md | 4 +- .yoi/tickets/00001KTJXS31R/thread.md | 98 +++++++++++++++++++ 3 files changed, 101 insertions(+), 2 deletions(-) diff --git a/.yoi/tickets/00001KTJXS31R/artifacts/orchestration-plan.jsonl b/.yoi/tickets/00001KTJXS31R/artifacts/orchestration-plan.jsonl index 12d4e313..c190a8d6 100644 --- a/.yoi/tickets/00001KTJXS31R/artifacts/orchestration-plan.jsonl +++ b/.yoi/tickets/00001KTJXS31R/artifacts/orchestration-plan.jsonl @@ -1 +1,2 @@ {"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"} +{"id":"orch-plan-20260612-154541-2","ticket_id":"00001KTJXS31R","kind":"accepted_plan","accepted_plan":{"summary":"`queued` Ticket の見落としを防ぐ starvation-prevention layer を実装する。Orchestrator が Idle かつ active_inprogress が導出されない場合だけ bounded work-list attention / re-kick を行い、active coder/reviewer/merge/cleanup 待ち中は queued があるだけでは re-kick しない。session-lifetime work set / role-session claims / visible Pods/worktrees / Ticket state を使い、scheduler・queue drain loop・blind spawn は作らない。","branch":"ticket/orchestrator-idle-queued-rekick","worktree":"/home/hare/Projects/yoi/.worktree/orchestrator-idle-queued-rekick","role_plan":"Coder は child worktree に限定して idle Orchestrator queued work discovery / re-kick policy と session-lifetime active-work suppression を実装し、Panel/lifecycle/Ticket routing 周辺の focused tests を追加する。Reviewer は read-only で、scheduler 化していないこと、`queued -> inprogress` acceptance gate、duplicate-start prevention、active_inprogress suppression、waiting reason visibility、recent Companion progress notification / Panel focus behaviorとの整合を確認する。"},"author":"orchestrator","at":"2026-06-12T15:45:41Z"} diff --git a/.yoi/tickets/00001KTJXS31R/item.md b/.yoi/tickets/00001KTJXS31R/item.md index 61def2cb..f5e41fd2 100644 --- a/.yoi/tickets/00001KTJXS31R/item.md +++ b/.yoi/tickets/00001KTJXS31R/item.md @@ -1,8 +1,8 @@ --- title: "Orchestrator Idle 時の queued Ticket 見落としを防ぐ" -state: 'queued' +state: 'inprogress' created_at: "2026-06-08T06:12:35Z" -updated_at: '2026-06-12T14:56:17Z' +updated_at: '2026-06-12T15:46:28Z' queued_by: 'workspace-panel' queued_at: '2026-06-12T14:49:40Z' --- diff --git a/.yoi/tickets/00001KTJXS31R/thread.md b/.yoi/tickets/00001KTJXS31R/thread.md index f53dd0d9..709a51de 100644 --- a/.yoi/tickets/00001KTJXS31R/thread.md +++ b/.yoi/tickets/00001KTJXS31R/thread.md @@ -149,3 +149,101 @@ 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. --- + + + +## Decision + +Routing decision: implementation_ready + +Reason: +- 前回の waiting reason は、Panel focus / Companion progress notification / Ticket language guidance の active in-progress work と重なる capacity/conflict だった。現在それらは merge/close/cleanup 済みで、visible child Pod は 0、Orchestrator worktree は clean。 +- Relation blocker はない。`depends_on` target `00001KTG3MDFG` は closed 済みで、incoming `related` は non-blocking context である。 +- Ticket body/thread は、session-lifetime work set discovery / re-kick policy、active_inprogress suppression、duplicate-start prevention、no scheduler/no polling/no blind spawn、`queued -> inprogress` acceptance gate を binding decisions として明確にしている。 +- risk flags は orchestration-policy / panel-lifecycle / persistence / role-session / authority-boundary / duplicate-start だが、bounded context check の結果、具体的な未決定 design/API/authority 判断は残っていない。実装方式は Panel/lifecycle/runtime boundary と local session work-set representation の local tactic に閉じている。 +- OrchestrationPlan に accepted plan `orch-plan-20260612-154541-2` を記録済み。 + +Evidence checked: +- Ticket body / thread / artifacts: requirements, acceptance criteria, binding decisions, previous waiting_capacity_note, and accepted plan を確認。 +- TicketRelationQuery: `depends_on` target `00001KTG3MDFG` closed、incoming `related` non-blocking。 +- Related Ticket: `00001KTG3MDFG` closed。OrchestrationPlan record/tool surface は実装済みで、この Ticket では再実装しない前提を確認。 +- Recent merged context: Panel focus model、Companion weak progress notify、Ticket language guidance は closed/merged/cleaned up 済み。 +- Code map: `crates/tui/src/workspace_panel.rs`, `crates/tui/src/multi_pod.rs`, `crates/tui/src/role_session_registry.rs`, Ticket state/action surfaces, Pod visibility/state surfaces, recent `companion_progress` weak notify path を確認。 +- Workspace/Pod state: Orchestrator branch `orchestration/yoi-orchestrator` is clean; visible child Pods 0。 + +IntentPacket: + +Intent: +- Orchestrator が idle で actionable queued work を見落とすことを防ぐ bounded starvation-prevention layer を実装する。 +- `active_inprogress` が導出されている間は queued/planned work があるだけでは re-kick しない。 +- re-kick は inspection / work-set incorporation / next planned acceptance を促す attention であり、scheduler や blind implementation start ではない。 + +Binding decisions / invariants: +- `new_queued` / `planned_queued` / `active_inprogress` / `actionable_inprogress` は core Ticket state に追加しない。Ticket `state`、session-lifetime work set、role/session claims、visible Pod/worktree state、OrchestrationPlan records から導出する分類に留める。 +- Work set は session-lifetime runtime state であり、Ticket ごとの durable artifact log として積まない。durable に残すべき判断だけ Ticket comment / state transition / OrchestrationPlan record に残す。 +- 常時 polling / unattended scheduler loop / queue drain loop を作らない。 +- `queued -> inprogress` acceptance なしに coder/reviewer Pod spawn、worktree 作成、implementation side effect を行わない。 +- active coder/reviewer/planning-sync/merge/cleanup 等の完了待ち中は queued/planned work の存在だけで re-kick しない。 +- duplicate Orchestrator/coder/reviewer/worktree start を防ぐ。 +- Panel は authority/backend/scheduler にならず、bounded attention surface に留まる。 + +Requirements / acceptance criteria: +- Orchestrator Pod が `Idle` で `new_queued` work があるとき、bounded work-list attention または session work set incorporation に進める。 +- `active_inprogress` がなく、planned queued work が unblocked/capacity-allowed のとき、next acceptance/routing に進める。 +- `active_inprogress` が導出される間は queued/planned work の存在だけで re-kick しない。 +- 開始しない planned queued work には session work set 上でユーザーに提示できる bounded waiting/blocking reason を保持する。 +- Ticket state、session work set、role/session claims、visible Pod/worktree state を使って duplicate start を避ける。 +- session work set が失われても `queued` Ticket から安全に再検出・再 inspection できる。 + +Implementation latitude: +- Panel open / Orchestrator restore/spawn / explicit user action のどの boundary で idle detection と attention payload を組むかは実装側で選んでよい。 +- Session work-set の具体的な runtime representation は既存 local role/session registry や Panel app state に合わせて選んでよい。 +- Bounded attention payload の具体形は実装側で選んでよいが、full Ticket thread / unbounded Pod output / hidden context-only injection は避ける。 +- OrchestrationPlan types の最小拡張は必要なら検討してよいが、core Ticket lifecycle state や旧 `workflow_state` は復活させない。 + +Escalate if: +- Durable scheduler state / persistent queue runner / polling loop が必要になる。 +- `queued -> inprogress` acceptance gate を迂回しないと実装できない。 +- Duplicate start prevention に新しい global lock/lease authority が必要になる。 +- Panel が lifecycle authority / scheduler になる必要が出る。 +- Role/session claims から active_inprogress を安全に導出できない。 + +Validation: +- idle Orchestrator + queued detection。 +- active_inprogress 導出時の re-kick suppression。 +- planned queued waiting reason retention。 +- duplicate-start prevention。 +- session work set loss から queued Ticket 再検出。 +- relevant `cargo test` for `tui` / panel / role session / ticket routing paths。 +- `cargo fmt --check`。 +- `git diff --check`。 +- `cargo run -p yoi -- ticket doctor` または同等。 +- `nix build .#yoi`。 + +Current code map: +- `crates/tui/src/workspace_panel.rs`: Ticket rows/actions and state-first Panel view model。 +- `crates/tui/src/multi_pod.rs`: Panel lifecycle, Companion/Orchestrator interactions, recent progress notice / header behavior。 +- `crates/tui/src/role_session_registry.rs`: local role/session claims。 +- `crates/pod-store`, `crates/pod-registry`, and protocol Pod status surfaces only if needed for visible Pod/worktree state derivation。 +- Ticket relation / OrchestrationPlan tool surfaces are already implemented and should be reused/read, not reimplemented as plan store。 + +Critical risks / reviewer focus: +- No scheduler / queue drain loop / background polling。 +- No blind worktree/Pod spawn before `queued -> inprogress` acceptance。 +- active_inprogress suppression works and avoids re-kick while coder/reviewer/merge/cleanup is active。 +- duplicate start prevention uses actual Ticket state + claims + visible Pods/worktrees, not heuristic title/labels。 +- session work set is runtime/session scoped, not a new durable per-Ticket artifact log。 +- Recent Companion weak progress notify and Panel focus behavior remain coherent. + +Next action: +- `queued -> inprogress` を記録してから、branch `ticket/orchestrator-idle-queued-rekick` / worktree `/home/hare/Projects/yoi/.worktree/orchestrator-idle-queued-rekick` を作成し、sibling coder に narrow write scope で実装を委譲する。Reviewer は coder evidence 後に read-only で起動する。 + +--- + + + +## State changed + +Routing accepted for implementation. Ticket body/thread, dependency relation target, incoming non-blocking relation, previous waiting-capacity note, current Orchestrator workspace state, visible Pods, recent merged Panel/Companion/Ticket-language context, and relevant Panel/lifecycle/role-session code map 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-154541-2`. + +--- From 3b3e786a0b7a10afd7b65592e3b9abe4a6ceb89c Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 13 Jun 2026 00:47:08 +0900 Subject: [PATCH 34/41] ticket: record idle queued worktree --- .yoi/tickets/00001KTJXS31R/item.md | 2 +- .yoi/tickets/00001KTJXS31R/thread.md | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KTJXS31R/item.md b/.yoi/tickets/00001KTJXS31R/item.md index f5e41fd2..ae8a3e8d 100644 --- a/.yoi/tickets/00001KTJXS31R/item.md +++ b/.yoi/tickets/00001KTJXS31R/item.md @@ -2,7 +2,7 @@ title: "Orchestrator Idle 時の queued Ticket 見落としを防ぐ" state: 'inprogress' created_at: "2026-06-08T06:12:35Z" -updated_at: '2026-06-12T15:46:28Z' +updated_at: '2026-06-12T15:47:00Z' queued_by: 'workspace-panel' queued_at: '2026-06-12T14:49:40Z' --- diff --git a/.yoi/tickets/00001KTJXS31R/thread.md b/.yoi/tickets/00001KTJXS31R/thread.md index 709a51de..d422d5e9 100644 --- a/.yoi/tickets/00001KTJXS31R/thread.md +++ b/.yoi/tickets/00001KTJXS31R/thread.md @@ -247,3 +247,18 @@ Next action: Routing accepted for implementation. Ticket body/thread, dependency relation target, incoming non-blocking relation, previous waiting-capacity note, current Orchestrator workspace state, visible Pods, recent merged Panel/Companion/Ticket-language context, and relevant Panel/lifecycle/role-session code map 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-154541-2`. --- + + + +## Plan + +Implementation worktree created for multi-agent handoff. + +- Ticket: `00001KTJXS31R` +- Branch: `ticket/orchestrator-idle-queued-rekick` +- Worktree: `/home/hare/Projects/yoi/.worktree/orchestrator-idle-queued-rekick` +- Base: Orchestrator workspace HEAD `e72a4536` (`orchestration/yoi-orchestrator`), including the accepted routing record and `queued -> inprogress` transition. +- 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. + +--- From a85826e82db1833a207bb3bc5b4770ae692de07f Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 13 Jun 2026 00:47:58 +0900 Subject: [PATCH 35/41] ticket: delegate idle queued coder --- .yoi/tickets/00001KTJXS31R/item.md | 2 +- .yoi/tickets/00001KTJXS31R/thread.md | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KTJXS31R/item.md b/.yoi/tickets/00001KTJXS31R/item.md index ae8a3e8d..5e9862b8 100644 --- a/.yoi/tickets/00001KTJXS31R/item.md +++ b/.yoi/tickets/00001KTJXS31R/item.md @@ -2,7 +2,7 @@ title: "Orchestrator Idle 時の queued Ticket 見落としを防ぐ" state: 'inprogress' created_at: "2026-06-08T06:12:35Z" -updated_at: '2026-06-12T15:47:00Z' +updated_at: '2026-06-12T15:47:50Z' queued_by: 'workspace-panel' queued_at: '2026-06-12T14:49:40Z' --- diff --git a/.yoi/tickets/00001KTJXS31R/thread.md b/.yoi/tickets/00001KTJXS31R/thread.md index d422d5e9..099e3eba 100644 --- a/.yoi/tickets/00001KTJXS31R/thread.md +++ b/.yoi/tickets/00001KTJXS31R/thread.md @@ -262,3 +262,18 @@ Implementation worktree created for multi-agent handoff. - Next: spawn sibling coder with narrow write scope to the implementation worktree. Reviewer will be started after coder evidence is available. --- + + + +## Plan + +Coder delegated. + +- Coder Pod: `yoi-coder-idle-queued-rekick` +- Worktree: `/home/hare/Projects/yoi/.worktree/orchestrator-idle-queued-rekick` +- Branch: `ticket/orchestrator-idle-queued-rekick` +- Scope: write `/home/hare/Projects/yoi/.worktree/orchestrator-idle-queued-rekick`; read child worktree plus minimal non-recursive runtime-root read required by spawn validation. +- Task: implement bounded idle-Orchestrator queued-work attention / session work-set rediscovery without scheduler/polling/queue-drain behavior, preserve `queued -> inprogress` acceptance gate, suppress re-kick while active_inprogress work exists, prevent duplicate starts, and add focused tests plus required validation. +- 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. + +--- From d2fae81a360a04cb99169ece414d4907ff2f4efc Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 13 Jun 2026 01:02:51 +0900 Subject: [PATCH 36/41] tui: add idle queued orchestrator attention --- crates/tui/src/multi_pod.rs | 695 ++++++++++++++++++ .../panel/orchestrator_idle_queue_notice.md | 24 + 2 files changed, 719 insertions(+) create mode 100644 resources/prompts/panel/orchestrator_idle_queue_notice.md diff --git a/crates/tui/src/multi_pod.rs b/crates/tui/src/multi_pod.rs index c3b45b5b..d96adc09 100644 --- a/crates/tui/src/multi_pod.rs +++ b/crates/tui/src/multi_pod.rs @@ -1,3 +1,4 @@ +use std::collections::BTreeSet; use std::fmt; use std::io; use std::path::{Path, PathBuf}; @@ -55,6 +56,11 @@ const COMPANION_PROGRESS_MAX_TITLE_CHARS: usize = 80; const COMPANION_PROGRESS_MAX_MESSAGE_CHARS: usize = 1_800; const COMPANION_PROGRESS_NOTICE_TEMPLATE: &str = include_str!("../../../resources/prompts/panel/companion_progress_notice.md"); +const ORCHESTRATOR_IDLE_QUEUE_NOTICE_TEMPLATE: &str = + include_str!("../../../resources/prompts/panel/orchestrator_idle_queue_notice.md"); +const ORCHESTRATOR_QUEUE_ATTENTION_MAX_TICKETS: usize = 6; +const ORCHESTRATOR_QUEUE_ATTENTION_MAX_TEXT_CHARS: usize = 120; +const ORCHESTRATOR_QUEUE_ATTENTION_MAX_MESSAGE_CHARS: usize = 2_400; const SOCKET_OP_TIMEOUT: Duration = Duration::from_secs(3); const MULTI_POD_POLL_INTERVAL: Duration = Duration::from_millis(1_500); const TERMINAL_EVENT_POLL_INTERVAL: Duration = Duration::from_millis(100); @@ -132,6 +138,10 @@ pub(crate) async fn run( loop { if let Some(result) = pending_reload.finish_if_ready().await { app.apply_reload_result(result); + if let Some(request) = app.prepare_orchestrator_queue_attention_notice() { + let result = dispatch_orchestrator_queue_attention_notice(request).await; + app.finish_orchestrator_queue_attention_notice(result); + } if let Some(request) = app.prepare_companion_progress_notice() { let result = dispatch_companion_progress_notice(request).await; app.finish_companion_progress_notice(result); @@ -605,6 +615,124 @@ struct CompanionProgressTemplateRolePod { status: String, } +#[derive(Debug, Clone, PartialEq, Eq, Default)] +struct OrchestratorWorkSet { + active_inprogress: Vec, + queued: Vec, + fingerprint: String, +} + +impl OrchestratorWorkSet { + fn is_empty(&self) -> bool { + self.active_inprogress.is_empty() && self.queued.is_empty() + } + + fn has_active_inprogress(&self) -> bool { + !self.active_inprogress.is_empty() + } + + fn planned_queued_ids(&self) -> BTreeSet { + self.queued.iter().map(|item| item.id.clone()).collect() + } + + fn actionable_queued(&self) -> Vec<&OrchestratorQueuedWorkItem> { + self.queued + .iter() + .filter(|item| item.waiting_reason.is_none()) + .collect() + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct OrchestratorActiveWorkItem { + id: String, + title: String, + status: String, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct OrchestratorQueuedWorkItem { + id: String, + title: String, + classification: OrchestratorQueuedClassification, + waiting_reason: Option, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum OrchestratorQueuedClassification { + NewQueued, + PlannedQueued, +} + +impl OrchestratorQueuedClassification { + fn as_str(self) -> &'static str { + match self { + Self::NewQueued => "new_queued", + Self::PlannedQueued => "planned_queued", + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct OrchestratorQueueAttentionFreshness { + fingerprint: String, + updated_at: String, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct OrchestratorQueueAttentionNotice { + message: String, + fingerprint: String, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct OrchestratorQueueAttentionNoticeRequest { + pod_name: String, + socket_path: PathBuf, + notice: OrchestratorQueueAttentionNotice, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct OrchestratorQueueAttentionNoticeResult { + fingerprint: String, + updated_at: String, + error: Option, +} + +impl OrchestratorQueueAttentionNoticeResult { + fn sent(fingerprint: String, updated_at: String) -> Self { + Self { + fingerprint, + updated_at, + error: None, + } + } + + fn failed(fingerprint: String, error: impl Into) -> Self { + Self { + fingerprint, + updated_at: String::new(), + error: Some(error.into()), + } + } +} + +#[derive(Debug, Serialize)] +struct OrchestratorQueueTemplateContext { + workspace: String, + actionable_tickets: Vec, + waiting_tickets: Vec, + omitted_ticket_count: usize, +} + +#[derive(Debug, Serialize)] +struct OrchestratorQueueTemplateTicket { + id: String, + title: String, + classification: &'static str, + waiting_reason: Option, +} + pub(crate) struct MultiPodApp { pub(crate) list: PodList, pub(crate) panel: WorkspacePanelViewModel, @@ -621,6 +749,8 @@ pub(crate) struct MultiPodApp { last_companion_lifecycle_failure: Option, last_orchestrator_lifecycle_failure: Option, companion_progress: Option, + orchestrator_work_set: OrchestratorWorkSet, + orchestrator_queue_attention: Option, } impl MultiPodApp { @@ -655,6 +785,8 @@ impl MultiPodApp { last_companion_lifecycle_failure: None, last_orchestrator_lifecycle_failure: None, companion_progress: None, + orchestrator_work_set: OrchestratorWorkSet::default(), + orchestrator_queue_attention: None, } } @@ -705,9 +837,64 @@ impl MultiPodApp { self.selected_row = previous_row.filter(|key| self.panel.row(key).is_some()); self.ensure_selection_visible(); self.ensure_composer_target_available(); + self.refresh_orchestrator_work_set(); + self.apply_orchestrator_work_set_detail(); self.apply_companion_progress_freshness(); } + fn prepare_orchestrator_queue_attention_notice( + &mut self, + ) -> Option { + let target = orchestrator_queue_attention_notice_target(&self.panel, &self.list)?; + if self.orchestrator_work_set.is_empty() { + self.refresh_orchestrator_work_set(); + } + let notice = orchestrator_queue_attention_notice(&self.panel, &self.orchestrator_work_set)?; + if self + .orchestrator_queue_attention + .as_ref() + .is_some_and(|freshness| freshness.fingerprint == notice.fingerprint) + { + self.apply_orchestrator_work_set_detail(); + return None; + } + Some(OrchestratorQueueAttentionNoticeRequest { + pod_name: target.pod_name, + socket_path: target.socket_path, + notice, + }) + } + + fn finish_orchestrator_queue_attention_notice( + &mut self, + result: OrchestratorQueueAttentionNoticeResult, + ) { + if let Some(error) = result.error { + self.notice = Some(format!( + "Orchestrator queued-work attention not delivered: {error}" + )); + return; + } + self.orchestrator_queue_attention = Some(OrchestratorQueueAttentionFreshness { + fingerprint: result.fingerprint, + updated_at: result.updated_at, + }); + self.apply_orchestrator_work_set_detail(); + } + + fn refresh_orchestrator_work_set(&mut self) { + let previous_planned = self.orchestrator_work_set.planned_queued_ids(); + self.orchestrator_work_set = derive_orchestrator_work_set(&self.panel, previous_planned); + } + + fn apply_orchestrator_work_set_detail(&mut self) { + let detail = orchestrator_work_set_detail( + &self.orchestrator_work_set, + self.orchestrator_queue_attention.as_ref(), + ); + apply_orchestrator_detail(&mut self.panel, detail); + } + fn prepare_companion_progress_notice(&mut self) -> Option { let target = companion_progress_notice_target(&self.panel, &self.list)?; let notice = companion_progress_notice(&self.panel, &self.list)?; @@ -2417,6 +2604,312 @@ struct OrchestratorNotifyTarget { socket_path: PathBuf, } +fn orchestrator_queue_attention_notice_target( + panel: &WorkspacePanelViewModel, + list: &PodList, +) -> Option { + let orchestrator = panel.header.orchestrator.as_ref()?; + if !matches!(orchestrator.status, OrchestratorPanelStatus::Live) { + return None; + } + let entry = list + .entries + .iter() + .find(|entry| entry.name == orchestrator.pod_name)?; + if !entry.actions.can_open { + return None; + } + let live = entry.live.as_ref()?; + if !live.reachable || live.status != Some(PodStatus::Idle) { + return None; + } + Some(OrchestratorNotifyTarget { + pod_name: orchestrator.pod_name.clone(), + socket_path: live.socket_path.clone(), + }) +} + +fn derive_orchestrator_work_set( + panel: &WorkspacePanelViewModel, + previous_planned: BTreeSet, +) -> OrchestratorWorkSet { + let active_inprogress = panel + .rows + .iter() + .filter_map(|row| { + let ticket = row.ticket.as_ref()?; + if ticket.workflow_state == TicketWorkflowState::InProgress { + Some(OrchestratorActiveWorkItem { + id: ticket.id.clone(), + title: ticket.title.clone(), + status: ticket.workflow_state.as_str().to_string(), + }) + } else { + None + } + }) + .collect::>(); + let active_wait = if active_inprogress.is_empty() { + None + } else { + Some(format!( + "waiting for active_inprogress: {}", + active_inprogress + .iter() + .map(|item| item.id.as_str()) + .collect::>() + .join(", ") + )) + }; + let queued = panel + .rows + .iter() + .filter_map(|row| { + let ticket = row.ticket.as_ref()?; + if ticket.workflow_state != TicketWorkflowState::Queued { + return None; + } + let duplicate_guard = queued_duplicate_guard(ticket, row); + let waiting_reason = active_wait + .clone() + .or_else(|| { + ticket + .blocked_reason + .as_ref() + .map(|reason| format!("blocked by Ticket relation diagnostics: {reason}")) + }) + .or(duplicate_guard); + let classification = + if waiting_reason.is_some() || previous_planned.contains(&ticket.id) { + OrchestratorQueuedClassification::PlannedQueued + } else { + OrchestratorQueuedClassification::NewQueued + }; + Some(OrchestratorQueuedWorkItem { + id: ticket.id.clone(), + title: ticket.title.clone(), + classification, + waiting_reason, + }) + }) + .collect::>(); + let fingerprint = orchestrator_work_set_fingerprint(&active_inprogress, &queued); + OrchestratorWorkSet { + active_inprogress, + queued, + fingerprint, + } +} + +fn queued_duplicate_guard( + ticket: &crate::workspace_panel::TicketPanelEntry, + row: &PanelRow, +) -> Option { + let mut guards = Vec::new(); + if let Some(claim) = ticket.local_claim.as_ref().filter(|claim| { + matches!( + claim.status, + TicketLocalClaimStatus::Live | TicketLocalClaimStatus::Restorable + ) + }) { + guards.push(format!( + "local {} claim {} ({})", + claim.role, + claim.pod_name, + claim.status.label() + )); + } + for pod in ticket.related_pods.iter().chain(row.related_pods.iter()) { + if !guards.iter().any(|guard| guard.contains(pod)) { + guards.push(format!("related pod/worktree {pod}")); + } + } + if guards.is_empty() { + None + } else { + Some(format!( + "waiting on existing role/session or visible pod/worktree before duplicate start: {}", + guards.join(", ") + )) + } +} + +fn orchestrator_work_set_fingerprint( + active: &[OrchestratorActiveWorkItem], + queued: &[OrchestratorQueuedWorkItem], +) -> String { + let active = active + .iter() + .map(|item| format!("active:{}:{}", item.id, item.status)) + .collect::>() + .join("|"); + let queued = queued + .iter() + .map(|item| { + format!( + "queued:{}:{}:{}", + item.id, + item.classification.as_str(), + item.waiting_reason.as_deref().unwrap_or("actionable") + ) + }) + .collect::>() + .join("|"); + format!("active=[{active}];queued=[{queued}]") +} + +fn orchestrator_queue_attention_notice( + panel: &WorkspacePanelViewModel, + work_set: &OrchestratorWorkSet, +) -> Option { + if work_set.has_active_inprogress() { + return None; + } + let actionable = work_set.actionable_queued(); + if actionable.is_empty() { + return None; + } + let waiting = work_set + .queued + .iter() + .filter(|item| item.waiting_reason.is_some()) + .collect::>(); + let ticket_count = actionable.len() + waiting.len(); + let actionable_tickets = actionable + .iter() + .take(ORCHESTRATOR_QUEUE_ATTENTION_MAX_TICKETS) + .map(|item| orchestrator_queue_template_ticket(item)) + .collect::>(); + let remaining_capacity = + ORCHESTRATOR_QUEUE_ATTENTION_MAX_TICKETS.saturating_sub(actionable_tickets.len()); + let waiting_tickets = waiting + .iter() + .take(remaining_capacity) + .map(|item| orchestrator_queue_template_ticket(item)) + .collect::>(); + let rendered = + render_orchestrator_queue_attention_template(&OrchestratorQueueTemplateContext { + workspace: bounded_progress_text( + &panel.header.workspace_label, + ORCHESTRATOR_QUEUE_ATTENTION_MAX_TEXT_CHARS, + ), + actionable_tickets, + waiting_tickets, + omitted_ticket_count: ticket_count + .saturating_sub(ORCHESTRATOR_QUEUE_ATTENTION_MAX_TICKETS), + }) + .ok()?; + let message = bounded_progress_text(&rendered, ORCHESTRATOR_QUEUE_ATTENTION_MAX_MESSAGE_CHARS); + let fingerprint = format!("idle-queue:{}", work_set.fingerprint); + Some(OrchestratorQueueAttentionNotice { + message, + fingerprint, + }) +} + +fn orchestrator_queue_template_ticket( + item: &&OrchestratorQueuedWorkItem, +) -> OrchestratorQueueTemplateTicket { + OrchestratorQueueTemplateTicket { + id: bounded_progress_text(&item.id, ORCHESTRATOR_QUEUE_ATTENTION_MAX_TEXT_CHARS), + title: bounded_progress_text(&item.title, ORCHESTRATOR_QUEUE_ATTENTION_MAX_TEXT_CHARS), + classification: item.classification.as_str(), + waiting_reason: item.waiting_reason.as_ref().map(|reason| { + bounded_progress_text(reason, ORCHESTRATOR_QUEUE_ATTENTION_MAX_TEXT_CHARS) + }), + } +} + +fn render_orchestrator_queue_attention_template( + context: &OrchestratorQueueTemplateContext, +) -> Result { + let mut env = minijinja::Environment::new(); + env.set_undefined_behavior(minijinja::UndefinedBehavior::Strict); + env.add_template( + "orchestrator_idle_queue_notice", + ORCHESTRATOR_IDLE_QUEUE_NOTICE_TEMPLATE, + )?; + env.get_template("orchestrator_idle_queue_notice")? + .render(context) +} + +fn orchestrator_work_set_detail( + work_set: &OrchestratorWorkSet, + freshness: Option<&OrchestratorQueueAttentionFreshness>, +) -> Option { + if work_set.is_empty() { + return freshness.map(|freshness| { + format!( + "queued-work attention last sent at {} (idle auto-run notify)", + freshness.updated_at + ) + }); + } + if work_set.has_active_inprogress() { + let active = work_set + .active_inprogress + .iter() + .take(3) + .map(|item| item.id.as_str()) + .collect::>() + .join(", "); + let queued = work_set + .queued + .iter() + .take(3) + .map(|item| match item.waiting_reason.as_deref() { + Some(reason) => format!("{} ({reason})", item.id), + None => item.id.clone(), + }) + .collect::>() + .join(", "); + return Some(bounded_panel_diagnostic(format!( + "queued-work attention suppressed; active_inprogress: {active}; planned queued: {queued}" + ))); + } + let actionable = work_set.actionable_queued(); + let waiting = work_set.queued.len().saturating_sub(actionable.len()); + if !actionable.is_empty() { + let classes = actionable + .iter() + .take(3) + .map(|item| format!("{} ({})", item.id, item.classification.as_str())) + .collect::>() + .join(", "); + let sent = freshness + .map(|freshness| format!("; last sent at {}", freshness.updated_at)) + .unwrap_or_default(); + return Some(bounded_panel_diagnostic(format!( + "queued-work attention pending: {classes}; waiting queued: {waiting}{sent}" + ))); + } + let waiting = work_set + .queued + .iter() + .take(3) + .map(|item| match item.waiting_reason.as_deref() { + Some(reason) => format!("{} ({reason})", item.id), + None => item.id.clone(), + }) + .collect::>() + .join(", "); + Some(bounded_panel_diagnostic(format!( + "queued-work attention waiting: {waiting}" + ))) +} + +fn apply_orchestrator_detail(panel: &mut WorkspacePanelViewModel, detail: Option) { + let Some(orchestrator) = panel.header.orchestrator.as_mut() else { + return; + }; + if matches!(orchestrator.status, OrchestratorPanelStatus::Unavailable) { + return; + } + if let Some(detail) = detail { + orchestrator.detail = Some(detail); + } +} + #[derive(Debug, Clone, PartialEq, Eq)] struct CompanionProgressNoticeTarget { pod_name: String, @@ -2571,6 +3064,21 @@ async fn dispatch_companion_progress_notice( } } +async fn dispatch_orchestrator_queue_attention_notice( + request: OrchestratorQueueAttentionNoticeRequest, +) -> OrchestratorQueueAttentionNoticeResult { + let fingerprint = request.notice.fingerprint.clone(); + match send_notify_only(&request.socket_path, request.notice.message, true).await { + Ok(()) => { + OrchestratorQueueAttentionNoticeResult::sent(fingerprint, progress_notice_timestamp()) + } + Err(err) => OrchestratorQueueAttentionNoticeResult::failed( + fingerprint, + format!("{}: {}", request.pod_name, err), + ), + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct TicketActionOutcome { notice: String, @@ -6476,6 +6984,189 @@ mod tests { assert!(app.notice.as_deref().unwrap().contains("cannot be opened")); } + #[test] + fn idle_orchestrator_gets_bounded_attention_for_new_queued_work() { + let mut app = ticket_enabled_app(vec![live_info("test-orchestrator", PodStatus::Idle)]); + app.panel.rows = vec![panel_test_ticket_row( + "00001QUEUE", + "Queued work", + ActionPriority::Background, + NextUserAction::Wait, + "queued", + )]; + app.refresh_orchestrator_work_set(); + + let request = app + .prepare_orchestrator_queue_attention_notice() + .expect("idle orchestrator should receive queued-work attention"); + + assert_eq!(request.pod_name, "test-orchestrator"); + assert!(request.notice.message.contains("00001QUEUE")); + assert!(request.notice.message.contains("new_queued")); + assert!(request.notice.message.contains("queued -> inprogress")); + } + + #[test] + fn active_inprogress_suppresses_queued_attention_and_retains_waiting_reason() { + let mut app = ticket_enabled_app(vec![live_info("test-orchestrator", PodStatus::Idle)]); + app.panel.rows = vec![ + panel_test_ticket_row( + "00001ACTIVE", + "Active work", + ActionPriority::Background, + NextUserAction::Wait, + "inprogress", + ), + panel_test_ticket_row( + "00001QUEUE", + "Queued work", + ActionPriority::Background, + NextUserAction::Wait, + "queued", + ), + ]; + app.refresh_orchestrator_work_set(); + app.apply_orchestrator_work_set_detail(); + + assert!(app.prepare_orchestrator_queue_attention_notice().is_none()); + let queued = app + .orchestrator_work_set + .queued + .iter() + .find(|item| item.id == "00001QUEUE") + .expect("queued item retained"); + assert_eq!( + queued.classification, + OrchestratorQueuedClassification::PlannedQueued + ); + assert!( + queued + .waiting_reason + .as_deref() + .unwrap() + .contains("active_inprogress") + ); + assert!( + app.panel + .header + .orchestrator + .as_ref() + .unwrap() + .detail + .as_deref() + .unwrap() + .contains("suppressed") + ); + } + + #[test] + fn planned_queued_prompts_when_active_work_clears() { + let mut app = ticket_enabled_app(vec![live_info("test-orchestrator", PodStatus::Idle)]); + app.panel.rows = vec![ + panel_test_ticket_row( + "00001ACTIVE", + "Active work", + ActionPriority::Background, + NextUserAction::Wait, + "inprogress", + ), + panel_test_ticket_row( + "00001QUEUE", + "Queued work", + ActionPriority::Background, + NextUserAction::Wait, + "queued", + ), + ]; + app.refresh_orchestrator_work_set(); + assert!(app.prepare_orchestrator_queue_attention_notice().is_none()); + + app.panel.rows = vec![panel_test_ticket_row( + "00001QUEUE", + "Queued work", + ActionPriority::Background, + NextUserAction::Wait, + "queued", + )]; + app.refresh_orchestrator_work_set(); + let request = app + .prepare_orchestrator_queue_attention_notice() + .expect("planned queued work should prompt after active work clears"); + + assert!(request.notice.message.contains("planned_queued")); + assert!( + !request + .notice + .message + .contains("waiting for active_inprogress") + ); + } + + #[test] + fn queued_attention_is_suppressed_when_existing_claim_prevents_duplicate_start() { + let mut app = ticket_enabled_app(vec![live_info("test-orchestrator", PodStatus::Idle)]); + let mut row = panel_test_ticket_row( + "00001QUEUE", + "Queued work", + ActionPriority::Background, + NextUserAction::Wait, + "queued", + ); + row.ticket.as_mut().unwrap().local_claim = + Some(crate::workspace_panel::TicketLocalClaimEntry { + pod_name: "coder-00001QUEUE".to_string(), + role: "coder".to_string(), + status: TicketLocalClaimStatus::Live, + }); + row.related_pods.push("reviewer-00001QUEUE".to_string()); + app.panel.rows = vec![row]; + app.refresh_orchestrator_work_set(); + app.apply_orchestrator_work_set_detail(); + + assert!(app.prepare_orchestrator_queue_attention_notice().is_none()); + let waiting = app.orchestrator_work_set.queued[0] + .waiting_reason + .as_deref() + .unwrap(); + assert!(waiting.contains("duplicate start")); + assert!(waiting.contains("coder-00001QUEUE")); + } + + #[test] + fn rediscovered_queued_work_is_actionable_when_session_work_set_is_empty() { + let mut app = ticket_enabled_app(vec![live_info("test-orchestrator", PodStatus::Idle)]); + app.orchestrator_work_set = OrchestratorWorkSet::default(); + app.panel.rows = vec![panel_test_ticket_row( + "00001QUEUE", + "Queued work", + ActionPriority::Background, + NextUserAction::Wait, + "queued", + )]; + + let request = app + .prepare_orchestrator_queue_attention_notice() + .expect("queued ticket state should be rediscovered safely"); + + assert!(request.notice.message.contains("new_queued")); + assert!(request.notice.message.contains("00001QUEUE")); + } + + #[test] + fn queued_attention_requires_idle_orchestrator_to_avoid_duplicate_rekick() { + let mut app = ticket_enabled_app(vec![live_info("test-orchestrator", PodStatus::Running)]); + app.panel.rows = vec![panel_test_ticket_row( + "00001QUEUE", + "Queued work", + ActionPriority::Background, + NextUserAction::Wait, + "queued", + )]; + app.refresh_orchestrator_work_set(); + + assert!(app.prepare_orchestrator_queue_attention_notice().is_none()); + } + fn test_app(live: Vec) -> MultiPodApp { app_with_list(PodList::from_sources( PodVisibilitySource::ResumePicker, @@ -6564,9 +7255,13 @@ mod tests { last_companion_lifecycle_failure, last_orchestrator_lifecycle_failure, companion_progress: None, + orchestrator_work_set: OrchestratorWorkSet::default(), + orchestrator_queue_attention: None, }; app.ensure_selection_visible(); app.ensure_composer_target_available(); + app.refresh_orchestrator_work_set(); + app.apply_orchestrator_work_set_detail(); app } diff --git a/resources/prompts/panel/orchestrator_idle_queue_notice.md b/resources/prompts/panel/orchestrator_idle_queue_notice.md new file mode 100644 index 00000000..3753fba5 --- /dev/null +++ b/resources/prompts/panel/orchestrator_idle_queue_notice.md @@ -0,0 +1,24 @@ + +Workspace panel observed that this Orchestrator Pod is idle while queued Ticket work is present. + +This is bounded attention only, not scheduler authority. Do not drain the queue automatically. Before implementation side effects, verify the Ticket state and record the normal `queued -> inprogress` acceptance through Ticket tools. + +Workspace: {{ workspace }} + +Actionable queued Tickets: +{% for ticket in actionable_tickets -%} +- {{ ticket.id }} — {{ ticket.title }} [{{ ticket.classification }}] +{% endfor -%} + +{% if waiting_tickets | length > 0 -%} +Queued Tickets retained in the session work set but currently waiting: +{% for ticket in waiting_tickets -%} +- {{ ticket.id }} — {{ ticket.title }} [{{ ticket.classification }}]: {{ ticket.waiting_reason }} +{% endfor -%} +{% endif -%} +{% if omitted_ticket_count > 0 -%} +Additional queued Tickets omitted from this bounded notice: {{ omitted_ticket_count }} +{% endif -%} + +Preserve the existing human gate, dependency/conflict/capacity/dirty-workspace checks, and duplicate-start checks using actual Ticket state, role/session claims, visible Pods, and worktrees. + From 81e666d1ee59ef952893400b6053eb46a8742f82 Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 13 Jun 2026 01:04:26 +0900 Subject: [PATCH 37/41] ticket: record idle queued implementation --- .yoi/tickets/00001KTJXS31R/item.md | 2 +- .yoi/tickets/00001KTJXS31R/thread.md | 57 ++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KTJXS31R/item.md b/.yoi/tickets/00001KTJXS31R/item.md index 5e9862b8..0983107f 100644 --- a/.yoi/tickets/00001KTJXS31R/item.md +++ b/.yoi/tickets/00001KTJXS31R/item.md @@ -2,7 +2,7 @@ title: "Orchestrator Idle 時の queued Ticket 見落としを防ぐ" state: 'inprogress' created_at: "2026-06-08T06:12:35Z" -updated_at: '2026-06-12T15:47:50Z' +updated_at: '2026-06-12T16:04:17Z' queued_by: 'workspace-panel' queued_at: '2026-06-12T14:49:40Z' --- diff --git a/.yoi/tickets/00001KTJXS31R/thread.md b/.yoi/tickets/00001KTJXS31R/thread.md index 099e3eba..badc1e46 100644 --- a/.yoi/tickets/00001KTJXS31R/thread.md +++ b/.yoi/tickets/00001KTJXS31R/thread.md @@ -277,3 +277,60 @@ 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. --- + + + +## Implementation report + +Coder completed implementation. + +Coder Pod: +- `yoi-coder-idle-queued-rekick` + +Implementation branch/worktree: +- Branch: `ticket/orchestrator-idle-queued-rekick` +- Worktree: `/home/hare/Projects/yoi/.worktree/orchestrator-idle-queued-rekick` +- Commit: `d2fae81a tui: add idle queued orchestrator attention` +- Worktree status: clean + +Changed surfaces: +- `crates/tui/src/multi_pod.rs` +- `resources/prompts/panel/orchestrator_idle_queue_notice.md` + +Implementation summary: +- Adds session-scoped `OrchestratorWorkSet` derived at Panel reload boundary. +- Derives `active_inprogress`, new/actionable queued, planned queued, waiting reasons, and duplicate guards from Panel Ticket rows, local role-session claims, related Pods, blocked reasons, and live Orchestrator status. +- Sends bounded Orchestrator attention only when Orchestrator is live/reachable/idle and queued work is actionable. +- Suppresses attention when active_inprogress exists or duplicate-start guard indicates existing claim/Pod/worktree activity. +- Retains waiting reason / planned queued information in Panel header detail for session-visible explanation. +- Adds prompt resource `resources/prompts/panel/orchestrator_idle_queue_notice.md` for the attention payload, preserving the prompt-resource boundary. +- Does not add scheduler loop, polling loop, queue drain loop, core Ticket states, or persistent work-set artifact store. + +Implementation note for reviewer attention: +- Coder report used the phrase `workflow_state` for `TicketPanelEntry.workflow_state`; this is existing Panel model naming over the typed Ticket state enum, not reintroduction of obsolete Ticket frontmatter `workflow_state`. + +Orchestrator-verified evidence from child worktree: +- `git status --short --branch`: clean on `ticket/orchestrator-idle-queued-rekick` +- `cargo test -p tui queued_attention`: pass, 3 tests +- `cargo test -p tui planned_queued_prompts`: pass, 1 test +- `cargo test -p tui rediscovered_queued_work`: pass, 1 test +- `cargo test -p tui active_inprogress_suppresses`: pass, 1 test +- `cargo test -p tui idle_orchestrator_gets_bounded_attention`: pass, 1 test +- `cargo test -p tui workspace_panel`: pass, 12 tests +- `cargo check -p tui`: 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-suite note from coder: +- `cargo test -p tui` still fails unrelated existing/neighboring expectation tests: + - `multi_pod::tests::orchestrator_launch_context_uses_orchestration_root_for_runtime_workspace` + - `spawn::tests::profile_choices_use_project_registry_default` + - `spawn::tests::profile_choices_include_builtin_and_project_default_marker` +- Focused tests for this Ticket pass; reviewer should confirm broad failures are unrelated and not introduced by this Ticket. + +Next: +- Spawn read-only sibling reviewer against recorded intent, binding decisions/invariants, acceptance criteria, commit `d2fae81a`, diff, validation evidence, and known broad-suite failures. + +--- From 3005884032454884e665cfb8ccca39c156f08631 Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 13 Jun 2026 01:05:16 +0900 Subject: [PATCH 38/41] ticket: delegate idle queued review --- .yoi/tickets/00001KTJXS31R/item.md | 2 +- .yoi/tickets/00001KTJXS31R/thread.md | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KTJXS31R/item.md b/.yoi/tickets/00001KTJXS31R/item.md index 0983107f..cdd8a8cd 100644 --- a/.yoi/tickets/00001KTJXS31R/item.md +++ b/.yoi/tickets/00001KTJXS31R/item.md @@ -2,7 +2,7 @@ title: "Orchestrator Idle 時の queued Ticket 見落としを防ぐ" state: 'inprogress' created_at: "2026-06-08T06:12:35Z" -updated_at: '2026-06-12T16:04:17Z' +updated_at: '2026-06-12T16:05:08Z' queued_by: 'workspace-panel' queued_at: '2026-06-12T14:49:40Z' --- diff --git a/.yoi/tickets/00001KTJXS31R/thread.md b/.yoi/tickets/00001KTJXS31R/thread.md index badc1e46..ac5753bb 100644 --- a/.yoi/tickets/00001KTJXS31R/thread.md +++ b/.yoi/tickets/00001KTJXS31R/thread.md @@ -334,3 +334,18 @@ Next: - Spawn read-only sibling reviewer against recorded intent, binding decisions/invariants, acceptance criteria, commit `d2fae81a`, diff, validation evidence, and known broad-suite failures. --- + + + +## Plan + +Reviewer delegated. + +- Reviewer Pod: `yoi-reviewer-idle-queued-rekick` +- Target branch/worktree: `ticket/orchestrator-idle-queued-rekick` at `/home/hare/Projects/yoi/.worktree/orchestrator-idle-queued-rekick` +- Commit under review: `d2fae81a tui: add idle queued orchestrator attention` +- 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 broad-suite failures. +- Reviewer focus: no scheduler/polling/queue-drain behavior, no acceptance-gate bypass, active_inprogress suppression, duplicate-start prevention, session-scoped work set, prompt-resource boundary, recent Panel/Companion behavior compatibility, and robust focused tests. + +--- From 255a212af97998c1a9617dc4998b69cd9eb0a9e7 Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 13 Jun 2026 01:10:31 +0900 Subject: [PATCH 39/41] ticket: record idle queued review --- .yoi/tickets/00001KTJXS31R/item.md | 2 +- .yoi/tickets/00001KTJXS31R/thread.md | 47 ++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/.yoi/tickets/00001KTJXS31R/item.md b/.yoi/tickets/00001KTJXS31R/item.md index cdd8a8cd..a19b2bd6 100644 --- a/.yoi/tickets/00001KTJXS31R/item.md +++ b/.yoi/tickets/00001KTJXS31R/item.md @@ -2,7 +2,7 @@ title: "Orchestrator Idle 時の queued Ticket 見落としを防ぐ" state: 'inprogress' created_at: "2026-06-08T06:12:35Z" -updated_at: '2026-06-12T16:05:08Z' +updated_at: '2026-06-12T16:10:24Z' queued_by: 'workspace-panel' queued_at: '2026-06-12T14:49:40Z' --- diff --git a/.yoi/tickets/00001KTJXS31R/thread.md b/.yoi/tickets/00001KTJXS31R/thread.md index ac5753bb..949299b3 100644 --- a/.yoi/tickets/00001KTJXS31R/thread.md +++ b/.yoi/tickets/00001KTJXS31R/thread.md @@ -349,3 +349,50 @@ Reviewer delegated. - Reviewer focus: no scheduler/polling/queue-drain behavior, no acceptance-gate bypass, active_inprogress suppression, duplicate-start prevention, session-scoped work set, prompt-resource boundary, recent Panel/Companion behavior compatibility, and robust focused tests. --- + + + +## Review: approve + +Approve. + +Review target: +- Worktree: `/home/hare/Projects/yoi/.worktree/orchestrator-idle-queued-rekick` +- Commit: `d2fae81a tui: add idle queued orchestrator attention` + +Evidence: +- 差分は `crates/tui/src/multi_pod.rs` と `resources/prompts/panel/orchestrator_idle_queue_notice.md` のみ。 +- queued work への通知は idle かつ reachable な Orchestrator Pod に限定されている。 +- `Method::Notify { auto_run: true }` は bounded attention / re-kick として使われるだけで、Ticket 状態変更・Pod spawn・worktree 作成・queue drain は追加されていない。 +- Scheduler / queue drain / background worker は追加されていない。既存 Panel reload 後に fingerprint で重複抑制された通知を送る構成。 +- `queued -> inprogress` acceptance は bypass されていない。通知 payload は Ticket state verification と通常 acceptance 記録を明示し、implementation side effect は行わない。 +- `active_inprogress` suppression は実装されている。`TicketWorkflowState::InProgress` が存在する場合、queued attention は送られず、waiting reason として session work set に残る。 +- Duplicate-start guard は title/label ではなく、Ticket id に紐づく local claim / related visible Pods / worktree 表示情報から導出している。 +- Session work set は `MultiPodApp` 内の runtime/session-scoped state のみで、永続 per-Ticket artifact は追加されていない。 +- Prompt 本文は `resources/prompts/panel/orchestrator_idle_queue_notice.md` に置かれており、Rust 側は include/render の境界に留まっている。 +- `workflow_state` 命名は既存の typed `TicketWorkflowState` / Panel internal naming の利用であり、この差分で obsolete frontmatter authority を再導入しているものではない。 + +Reviewer-run validation: +- `cargo test -p tui queued_attention -- --nocapture`: pass, 3 tests +- `cargo test -p tui planned_queued_prompts -- --nocapture`: pass +- `cargo test -p tui rediscovered_queued_work -- --nocapture`: pass +- `cargo test -p tui idle_orchestrator_gets_bounded_attention -- --nocapture`: pass +- `cargo test -p tui active_inprogress_suppresses -- --nocapture`: pass +- `cargo test -p tui workspace_panel -- --nocapture`: pass, 12 tests +- `cargo check -p tui`: pass +- `git diff --check orchestration/yoi-orchestrator...HEAD && cargo fmt --check`: pass + +Known broad-suite failures: +- `cargo test -p tui`: expected broad-suite failure, 312 passed / 3 failed. +- Failing tests: + - `multi_pod::tests::orchestrator_launch_context_uses_orchestration_root_for_runtime_workspace` + - `spawn::tests::profile_choices_use_project_registry_default` + - `spawn::tests::profile_choices_include_builtin_and_project_default_marker` +- The two `spawn` failures are in untouched `crates/tui/src/spawn.rs`. +- The `orchestrator_launch_context...` failure is in `multi_pod.rs`, but this diff does not touch `build_orchestrator_launch_context` or that test area; it remains the existing/neighboring expectation mismatch reported by coder. Not a blocker for this Ticket. + +Non-blocking risks: +- Failed delivery is not fingerprint-marked, so a repeatedly reachable-but-rejecting socket can retry on subsequent Panel reloads; this is consistent with existing notice behavior but may be noisy. +- Duplicate guard depends on the Panel’s existing visible claim/pod/worktree derivation, so it prevents duplicate starts for locally visible state but is not a full global scheduler/lock. This matches the Ticket’s bounded Panel-attention scope. + +--- From 60cf2d9f09b529ddd7d41d15e212e100ec0f68d1 Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 13 Jun 2026 01:11:57 +0900 Subject: [PATCH 40/41] ticket: mark idle queued done --- .yoi/tickets/00001KTJXS31R/item.md | 4 +-- .yoi/tickets/00001KTJXS31R/thread.md | 44 ++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/.yoi/tickets/00001KTJXS31R/item.md b/.yoi/tickets/00001KTJXS31R/item.md index a19b2bd6..38e5d2ef 100644 --- a/.yoi/tickets/00001KTJXS31R/item.md +++ b/.yoi/tickets/00001KTJXS31R/item.md @@ -1,8 +1,8 @@ --- title: "Orchestrator Idle 時の queued Ticket 見落としを防ぐ" -state: 'inprogress' +state: 'done' created_at: "2026-06-08T06:12:35Z" -updated_at: '2026-06-12T16:10:24Z' +updated_at: '2026-06-12T16:11:51Z' queued_by: 'workspace-panel' queued_at: '2026-06-12T14:49:40Z' --- diff --git a/.yoi/tickets/00001KTJXS31R/thread.md b/.yoi/tickets/00001KTJXS31R/thread.md index 949299b3..f7da9af5 100644 --- a/.yoi/tickets/00001KTJXS31R/thread.md +++ b/.yoi/tickets/00001KTJXS31R/thread.md @@ -396,3 +396,47 @@ Non-blocking risks: - Duplicate guard depends on the Panel’s existing visible claim/pod/worktree derivation, so it prevents duplicate starts for locally visible state but is not a full global scheduler/lock. This matches the Ticket’s bounded Panel-attention scope. --- + + + +## Implementation report + +Integration completed in the Orchestrator workspace. + +Integrated implementation: +- Ticket: `00001KTJXS31R` +- Implementation branch: `ticket/orchestrator-idle-queued-rekick` +- Implementation commit: `d2fae81a tui: add idle queued orchestrator attention` +- Reviewer result: approve, no blockers +- Orchestrator merge commit: `9538feb1 merge: idle queued orchestrator attention` +- Integration target: `orchestration/yoi-orchestrator` + +Post-integration validation from Orchestrator worktree: +- `cargo test -p tui queued_attention`: pass, 3 tests +- `cargo test -p tui planned_queued_prompts`: pass, 1 test +- `cargo test -p tui rediscovered_queued_work`: pass, 1 test +- `cargo test -p tui active_inprogress_suppresses`: pass, 1 test +- `cargo test -p tui idle_orchestrator_gets_bounded_attention`: pass, 1 test +- `cargo test -p tui workspace_panel`: pass, 12 tests +- `cargo check -p tui`: pass +- `cargo fmt --check`: pass +- `git diff --check HEAD~1..HEAD`: pass +- `./result/bin/yoi ticket doctor`: `doctor: ok` +- `nix build .#yoi`: pass + +Known broad-suite failures: +- Existing broad `cargo test -p tui` failures noted by coder/reviewer remain outside this Ticket and were not blockers for focused implementation/review. + +Remaining non-blocking risks: +- Failed attention delivery is not fingerprint-marked, so a repeatedly reachable-but-rejecting socket can retry on subsequent Panel reloads; this is consistent with existing notice behavior but may be noisy. +- Duplicate guard is bounded to Panel-visible claim/Pod/worktree derivation and is not a full global scheduler/lock, matching this Ticket's scoped attention model. + +--- + + + +## 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. + +--- From f235fd18b2830e09f2c46e38164e260bef8c6efe Mon Sep 17 00:00:00 2001 From: Hare Date: Sat, 13 Jun 2026 01:13:08 +0900 Subject: [PATCH 41/41] ticket: close idle queued attention --- .yoi/tickets/00001KTJXS31R/item.md | 4 +- .yoi/tickets/00001KTJXS31R/resolution.md | 41 +++++++++++++ .yoi/tickets/00001KTJXS31R/thread.md | 73 ++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 .yoi/tickets/00001KTJXS31R/resolution.md diff --git a/.yoi/tickets/00001KTJXS31R/item.md b/.yoi/tickets/00001KTJXS31R/item.md index 38e5d2ef..0d95edf2 100644 --- a/.yoi/tickets/00001KTJXS31R/item.md +++ b/.yoi/tickets/00001KTJXS31R/item.md @@ -1,8 +1,8 @@ --- title: "Orchestrator Idle 時の queued Ticket 見落としを防ぐ" -state: 'done' +state: 'closed' created_at: "2026-06-08T06:12:35Z" -updated_at: '2026-06-12T16:11:51Z' +updated_at: '2026-06-12T16:12:42Z' queued_by: 'workspace-panel' queued_at: '2026-06-12T14:49:40Z' --- diff --git a/.yoi/tickets/00001KTJXS31R/resolution.md b/.yoi/tickets/00001KTJXS31R/resolution.md new file mode 100644 index 00000000..e9cebf5e --- /dev/null +++ b/.yoi/tickets/00001KTJXS31R/resolution.md @@ -0,0 +1,41 @@ +Orchestrator Idle 時に queued Ticket を見落とさない bounded attention layer を実装した。 + +実装概要: +- `crates/tui/src/multi_pod.rs` に session-scoped `OrchestratorWorkSet` を追加し、Panel reload 境界で queued/actionable/planned/active_inprogress を導出するようにした。 +- Idle かつ reachable な Orchestrator Pod にだけ bounded queued-work attention を送る。 +- `active_inprogress` がある場合は re-kick を抑制し、waiting reason を session work set / Panel header detail に保持する。 +- Duplicate-start guard は Ticket id に紐づく local claim / related visible Pods / worktree 表示情報から導出する。 +- Session work set は `MultiPodApp` 内 runtime/session state に留め、durable per-Ticket artifact store は追加していない。 +- `resources/prompts/panel/orchestrator_idle_queue_notice.md` を追加し、Orchestrator attention payload の prompt 本文を resource 化した。 +- Scheduler loop、polling loop、queue drain loop、core Ticket state、implementation side effect、acceptance gate bypass は追加していない。 + +Review / integration: +- Implementation commit: `d2fae81a tui: add idle queued orchestrator attention` +- Reviewer: `yoi-reviewer-idle-queued-rekick` が approve。 +- Orchestrator merge commit: `9538feb1 merge: idle queued orchestrator attention` +- Ticket completion commit: `60cf2d9f ticket: mark idle queued done` + +Validation: +- `cargo test -p tui queued_attention`: pass, 3 tests +- `cargo test -p tui planned_queued_prompts`: pass, 1 test +- `cargo test -p tui rediscovered_queued_work`: pass, 1 test +- `cargo test -p tui active_inprogress_suppresses`: pass, 1 test +- `cargo test -p tui idle_orchestrator_gets_bounded_attention`: pass, 1 test +- `cargo test -p tui workspace_panel`: pass, 12 tests +- `cargo check -p tui`: pass +- `cargo fmt --check`: pass +- `git diff --check HEAD~1..HEAD`: pass +- `./result/bin/yoi ticket doctor`: `doctor: ok` +- `nix build .#yoi`: pass + +Known broad-suite failures: +- Existing broad `cargo test -p tui` failures noted in thread remain outside this Ticket and were not blockers for focused implementation/review. + +Cleanup: +- coder/reviewer Pods stopped。 +- child worktree `/home/hare/Projects/yoi/.worktree/orchestrator-idle-queued-rekick` removed。 +- branch `ticket/orchestrator-idle-queued-rekick` deleted。 + +Non-blocking risks: +- Failed attention delivery is not fingerprint-marked, so a repeatedly reachable-but-rejecting socket can retry on subsequent Panel reloads; this is consistent with existing notice behavior but may be noisy。 +- Duplicate guard is bounded to Panel-visible claim/Pod/worktree derivation and is not a full global scheduler/lock, matching this Ticket の scoped attention model。 \ No newline at end of file diff --git a/.yoi/tickets/00001KTJXS31R/thread.md b/.yoi/tickets/00001KTJXS31R/thread.md index f7da9af5..c3e7676b 100644 --- a/.yoi/tickets/00001KTJXS31R/thread.md +++ b/.yoi/tickets/00001KTJXS31R/thread.md @@ -440,3 +440,76 @@ Remaining non-blocking risks: Implementation was merged into the Orchestrator branch, reviewer approved with no blockers, and post-integration validation passed. Cleanup and closure will be recorded next. --- + + + +## Implementation report + +Cleanup completed. + +- Stopped coder Pod: `yoi-coder-idle-queued-rekick` +- Stopped reviewer Pod: `yoi-reviewer-idle-queued-rekick` +- Removed child worktree: `/home/hare/Projects/yoi/.worktree/orchestrator-idle-queued-rekick` +- Deleted implementation branch: `ticket/orchestrator-idle-queued-rekick` +- Orchestrator worktree status after cleanup: clean + +Cleanup was limited to child implementation worktree/branch and related child Pods. Root/original workspace was not touched. + +--- + + + +## State changed + +Ticket を closed にしました。 + + +--- + + + +## 完了 + +Orchestrator Idle 時に queued Ticket を見落とさない bounded attention layer を実装した。 + +実装概要: +- `crates/tui/src/multi_pod.rs` に session-scoped `OrchestratorWorkSet` を追加し、Panel reload 境界で queued/actionable/planned/active_inprogress を導出するようにした。 +- Idle かつ reachable な Orchestrator Pod にだけ bounded queued-work attention を送る。 +- `active_inprogress` がある場合は re-kick を抑制し、waiting reason を session work set / Panel header detail に保持する。 +- Duplicate-start guard は Ticket id に紐づく local claim / related visible Pods / worktree 表示情報から導出する。 +- Session work set は `MultiPodApp` 内 runtime/session state に留め、durable per-Ticket artifact store は追加していない。 +- `resources/prompts/panel/orchestrator_idle_queue_notice.md` を追加し、Orchestrator attention payload の prompt 本文を resource 化した。 +- Scheduler loop、polling loop、queue drain loop、core Ticket state、implementation side effect、acceptance gate bypass は追加していない。 + +Review / integration: +- Implementation commit: `d2fae81a tui: add idle queued orchestrator attention` +- Reviewer: `yoi-reviewer-idle-queued-rekick` が approve。 +- Orchestrator merge commit: `9538feb1 merge: idle queued orchestrator attention` +- Ticket completion commit: `60cf2d9f ticket: mark idle queued done` + +Validation: +- `cargo test -p tui queued_attention`: pass, 3 tests +- `cargo test -p tui planned_queued_prompts`: pass, 1 test +- `cargo test -p tui rediscovered_queued_work`: pass, 1 test +- `cargo test -p tui active_inprogress_suppresses`: pass, 1 test +- `cargo test -p tui idle_orchestrator_gets_bounded_attention`: pass, 1 test +- `cargo test -p tui workspace_panel`: pass, 12 tests +- `cargo check -p tui`: pass +- `cargo fmt --check`: pass +- `git diff --check HEAD~1..HEAD`: pass +- `./result/bin/yoi ticket doctor`: `doctor: ok` +- `nix build .#yoi`: pass + +Known broad-suite failures: +- Existing broad `cargo test -p tui` failures noted in thread remain outside this Ticket and were not blockers for focused implementation/review. + +Cleanup: +- coder/reviewer Pods stopped。 +- child worktree `/home/hare/Projects/yoi/.worktree/orchestrator-idle-queued-rekick` removed。 +- branch `ticket/orchestrator-idle-queued-rekick` deleted。 + +Non-blocking risks: +- Failed attention delivery is not fingerprint-marked, so a repeatedly reachable-but-rejecting socket can retry on subsequent Panel reloads; this is consistent with existing notice behavior but may be noisy。 +- Duplicate guard is bounded to Panel-visible claim/Pod/worktree derivation and is not a full global scheduler/lock, matching this Ticket の scoped attention model。 + +---