From d276991c603e6041d20fb5c1e65df8756d35d2ae Mon Sep 17 00:00:00 2001 From: Jiayuan Zhang Date: Thu, 3 Sep 2026 16:13:33 +0800 Subject: [PATCH] MUL-6743 refactor(issues): remove the assignment handoff note (#7626) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(issues): remove the assignment handoff note (MUL-6743) Usage data puts the handoff note at 0.2% of assignments, so the box occupied the confirm dialog's centre for the other 99.8% and bought them nothing. Removing it takes the whole feature with it: the note input and its runtime version gate, the `handoff_note` write field on PATCH/batch issue updates, the task column write, and the daemon's opening-prompt + issue_context.md handoff sections. The confirm dialog itself stays — it still names who is about to run and still offers "Don't start yet" (suppress_run). The `*WithHandoff` enqueue methods only ever varied by that note, so they become `EnqueueTaskForIssueByActor` / `EnqueueTaskForSquadLeaderByActor` — the actor-carrying variants autopilot already called with an empty note. `preview-trigger` drops `handoff_supported`, which nothing read after the gate moved client-side. `agent_task_queue.handoff_note` is deliberately left in place: it holds historical rows and this is the expand half of an expand-contract pair. Nothing reads or writes it now, so a follow-up migration can drop it once no deployed binary selects it. Co-authored-by: multica-agent * docs(issues): reshoot the assignment confirm dialog (MUL-6743) The asset still showed the handoff note box the surrounding prose no longer describes. Recaptured from the app at the same 600x450 frame; the confirm button now carries the send-chord keycaps it has shipped with since MUL-5694. Co-authored-by: multica-agent --------- Co-authored-by: Lambda Co-authored-by: multica-agent --- .../docs/content/docs/assigning-issues.ja.mdx | 9 +- .../docs/content/docs/assigning-issues.ko.mdx | 9 +- apps/docs/content/docs/assigning-issues.mdx | 9 +- .../docs/content/docs/assigning-issues.zh.mdx | 9 +- .../images/docs/assign-confirm-dialog.webp | Bin 9452 -> 7648 bytes packages/core/api/schemas.test.ts | 7 +- packages/core/api/schemas.ts | 2 - packages/core/issues/mutations.ts | 7 +- packages/core/runtimes/cli-version.test.ts | 29 ---- packages/core/runtimes/cli-version.ts | 34 +---- packages/core/types/agent.ts | 7 - packages/core/types/api.ts | 9 +- .../use-chat-project-context-support.ts | 3 +- .../agent-transcript-dialog.tsx | 2 +- .../issues/components/task-run-labels.ts | 6 - .../issues/hooks/use-issue-trigger-preview.ts | 4 - packages/views/locales/en/issues.json | 1 - packages/views/locales/en/modals.json | 3 - packages/views/locales/ja/issues.json | 1 - packages/views/locales/ja/modals.json | 3 - packages/views/locales/ko/issues.json | 1 - packages/views/locales/ko/modals.json | 3 - packages/views/locales/zh-Hans/issues.json | 1 - packages/views/locales/zh-Hans/modals.json | 3 - packages/views/modals/create-issue.test.tsx | 1 - packages/views/modals/run-confirm.test.tsx | 135 +++--------------- packages/views/modals/run-confirm.tsx | 88 ++---------- server/internal/daemon/daemon.go | 1 - server/internal/daemon/execenv/context.go | 8 -- server/internal/daemon/execenv/execenv.go | 1 - .../daemon/execenv/handoff_context_test.go | 33 ----- server/internal/daemon/handoff_prompt_test.go | 38 ----- server/internal/daemon/prompt.go | 7 - server/internal/daemon/prompt_test.go | 1 - server/internal/daemon/types.go | 1 - server/internal/handler/agent.go | 6 - server/internal/handler/daemon.go | 4 - server/internal/handler/issue.go | 9 +- server/internal/handler/issue_trigger.go | 45 ++---- .../handler/issue_trigger_preview_test.go | 43 ------ server/internal/handler/squad.go | 10 +- .../service/attribution_stamp_test.go | 16 +-- server/internal/service/autopilot.go | 7 +- server/internal/service/task.go | 58 ++++---- server/pkg/agent/handoff_version_test.go | 26 ---- server/pkg/agent/version.go | 32 ----- server/pkg/db/generated/agent.sql.go | 24 ++-- server/pkg/db/queries/agent.sql | 6 +- 48 files changed, 126 insertions(+), 636 deletions(-) delete mode 100644 server/internal/daemon/execenv/handoff_context_test.go delete mode 100644 server/internal/daemon/handoff_prompt_test.go delete mode 100644 server/pkg/agent/handoff_version_test.go diff --git a/apps/docs/content/docs/assigning-issues.ja.mdx b/apps/docs/content/docs/assigning-issues.ja.mdx index 12b78e765..a8af58c01 100644 --- a/apps/docs/content/docs/assigning-issues.ja.mdx +++ b/apps/docs/content/docs/assigning-issues.ja.mdx @@ -13,12 +13,11 @@ import { Callout } from "fumadocs-ui/components/callout"; 1. タスクを開き、**担当者**をクリックします。 2. エージェントまたはスクワッドを選択します。 -3. 確認ダイアログで、これから開始するエージェントを確認します。 -4. 必要に応じて引き継ぎメモを記入し、**開始**を選択します。 +3. 確認ダイアログで、これから開始するエージェントを確認し、**開始**を選択します。 -![割り当ての確認ダイアログ:引き継ぎメモを記入するか、まだ開始しないことを選べます](/images/docs/assign-confirm-dialog.webp) +![割り当ての確認ダイアログ:割り当てを確認するか、まだ開始しないことを選べます](/images/docs/assign-confirm-dialog.webp) -引き継ぎメモは今回の実行にのみ適用されます。範囲、順序、今回とくに注目してほしい点の補足に使ってください。長期的に有効な要件は、タスク本文、プロジェクトの説明、またはエージェントの指示に書きます。 +エージェントに伝えたいことは、タスク本文とその議論、あるいはプロジェクトの説明やエージェントの指示に書きます。 スクワッドに割り当てると、タスクの担当者はスクワッドになり、最初に実行を開始するのはスクワッドのリーダーです。リーダーはスクワッドの運用方針に従って、他のメンバーに作業を渡すかどうかを判断します。 @@ -43,7 +42,7 @@ Multica はエージェントのために[作業](/tasks)を作成します。 `backlog` のタスクも、割り当てだけでは実行を開始しません。タスクを `backlog` から移動するか、コメントで担当者に依頼を続けたときに、はじめて後続の実行が生まれます。 -すでにエージェントまたはスカッドの担当者がいる作業を `backlog` から移動すると、同じ確認ダイアログが開きます。実行を開始するのはこのステータス変更そのものなので、同じ引き継ぎメモを書けますし、**まだ開始しない**も同じように使えます(ステータスだけ変えて、エージェントは起こしません)。この確認ダイアログは、`backlog`・`done`・`cancelled` 以外のカテゴリへ移動するときに表示されます。 +すでにエージェントまたはスカッドの担当者がいる作業を `backlog` から移動すると、同じ確認ダイアログが開きます。実行を開始するのはこのステータス変更そのものなので、**まだ開始しない**も同じように使えます(ステータスだけ変えて、エージェントは起こしません)。この確認ダイアログは、`backlog`・`done`・`cancelled` 以外のカテゴリへ移動するときに表示されます。 この挙動は `backlog` カテゴリだけで、そのカテゴリの[カスタムステータス](/issues#カスタムステータス)もまったく同じように振る舞います。すでに `done` や `cancelled` にあるタスクは、エージェントに割り当て・再割り当てすると、すぐに実行が開始されます。 diff --git a/apps/docs/content/docs/assigning-issues.ko.mdx b/apps/docs/content/docs/assigning-issues.ko.mdx index 7ff9581b7..d052303ba 100644 --- a/apps/docs/content/docs/assigning-issues.ko.mdx +++ b/apps/docs/content/docs/assigning-issues.ko.mdx @@ -13,12 +13,11 @@ import { Callout } from "fumadocs-ui/components/callout"; 1. 태스크를 열고 **담당자**를 클릭합니다. 2. 에이전트 또는 스쿼드를 선택합니다. -3. 확인 창에서 시작될 에이전트를 확인합니다. -4. 필요하면 인계 설명을 입력한 뒤 **시작**을 선택합니다. +3. 확인 창에서 시작될 에이전트를 확인한 뒤 **시작**을 선택합니다. -![할당 확인 창: 인계 설명을 입력하거나 지금은 시작하지 않도록 선택할 수 있습니다.](/images/docs/assign-confirm-dialog.webp) +![할당 확인 창: 할당을 확인하거나 지금은 시작하지 않도록 선택할 수 있습니다.](/images/docs/assign-confirm-dialog.webp) -인계 설명은 이번 실행에만 사용됩니다. 범위, 순서, 이번에 특히 주의할 점을 보충하기에 적합합니다. 장기적으로 적용할 요구 사항은 태스크, 프로젝트 설명, 에이전트 지시문에 작성하세요. +에이전트가 알아야 할 내용은 태스크 본문과 그 논의, 또는 프로젝트 설명과 에이전트 지시문에 작성하세요. 스쿼드에 할당하면 태스크 담당자는 스쿼드가 되고 스쿼드 리더가 먼저 실행을 시작합니다. 리더는 스쿼드의 작업 방식에 따라 다른 멤버에게 넘길지 판단합니다. @@ -43,7 +42,7 @@ Multica가 에이전트의 [작업](/tasks)를 만듭니다. 온라인 런타임 `backlog` 상태에서도 에이전트를 할당하는 것만으로 실행이 시작되지 않습니다. 태스크를 `backlog`에서 다른 상태로 옮기거나 댓글에서 담당자에게 추가 요청을 해야 이후 실행이 생성됩니다. -이미 에이전트나 스쿼드 담당자가 있는 작업을 `backlog` 밖으로 옮기면 같은 확인 대화상자가 열립니다. 실행을 시작하는 것이 바로 이 상태 변경이므로 동일한 인계 메모를 남길 수 있고, **아직 시작 안 함**도 그대로 사용할 수 있습니다(상태만 옮기고 에이전트는 깨우지 않습니다). 이 대화상자는 `backlog`·`done`·`cancelled` 이외 카테고리로 옮길 때 나타납니다. +이미 에이전트나 스쿼드 담당자가 있는 작업을 `backlog` 밖으로 옮기면 같은 확인 대화상자가 열립니다. 실행을 시작하는 것이 바로 이 상태 변경이므로 **아직 시작 안 함**도 그대로 사용할 수 있습니다(상태만 옮기고 에이전트는 깨우지 않습니다). 이 대화상자는 `backlog`·`done`·`cancelled` 이외 카테고리로 옮길 때 나타납니다. 이 효과는 `backlog` 카테고리에만 적용되며, 그 카테고리의 [사용자 지정 상태](/issues#사용자-지정-상태)도 똑같이 동작합니다. 이미 `done` 또는 `cancelled` 상태인 태스크도 에이전트에게 할당하거나 다시 할당하면 즉시 실행이 시작됩니다. diff --git a/apps/docs/content/docs/assigning-issues.mdx b/apps/docs/content/docs/assigning-issues.mdx index 770f57937..27da7242e 100644 --- a/apps/docs/content/docs/assigning-issues.mdx +++ b/apps/docs/content/docs/assigning-issues.mdx @@ -13,12 +13,11 @@ When you only need the agent to handle one specific request without changing own 1. Open an issue and click **Assignee**. 2. Pick an agent or a squad. -3. Review the agent about to start in the confirmation dialog. -4. Add a handoff note if needed, then choose **Start**. +3. Review the agent about to start in the confirmation dialog, then choose **Start**. -![Assignment confirmation dialog: add a handoff note, or choose not to start yet](/images/docs/assign-confirm-dialog.webp) +![Assignment confirmation dialog: confirm the assignment, or choose not to start yet](/images/docs/assign-confirm-dialog.webp) -The handoff note applies to this run only — use it for scope, ordering, or what to focus on this time. Requirements that should hold long term belong in the issue, the project description, or the agent instructions. +Everything the agent should know about this work belongs in the issue itself — its description, its discussion, or the project description and agent instructions. When you assign to a squad, the squad becomes the issue's assignee and the squad leader starts first. It decides whether to hand work to other members, following how the squad operates. @@ -47,7 +46,7 @@ When the assignee is a [squad](/squads), the same rule applies: Multica enqueues `backlog` doesn't start a run on assignment either. Execution only follows after you move the issue out of `backlog`, or ask the assignee for something in a follow-up comment. -When the issue already has an agent or squad owner, moving it out of `backlog` opens the same dialog: that status change is what starts the run, so it offers the same handoff note and the same **Don't start yet**, which moves the issue without waking the agent. The dialog appears for every target outside the `backlog`, `done`, and `cancelled` categories. +When the issue already has an agent or squad owner, moving it out of `backlog` opens the same dialog: that status change is what starts the run, so it offers the same **Don't start yet**, which moves the issue without waking the agent. The dialog appears for every target outside the `backlog`, `done`, and `cancelled` categories. Only the `backlog` category works this way, and a [custom status](/issues#custom-statuses) in that category behaves identically. Issues already in `done` or `cancelled` still start a run immediately when assigned or reassigned to an agent. diff --git a/apps/docs/content/docs/assigning-issues.zh.mdx b/apps/docs/content/docs/assigning-issues.zh.mdx index 0b2d0a2cc..0bed38c74 100644 --- a/apps/docs/content/docs/assigning-issues.zh.mdx +++ b/apps/docs/content/docs/assigning-issues.zh.mdx @@ -13,12 +13,11 @@ import { Callout } from "fumadocs-ui/components/callout"; 1. 打开一条任务,点击**负责人**。 2. 选择一个智能体或小队。 -3. 在确认窗口中检查即将启动的智能体。 -4. 如有必要,填写一段交接说明,然后选择**开始**。 +3. 在确认窗口中检查即将启动的智能体,然后选择**开始**。 -![分配确认窗:可填写交接说明,或选择暂不开始](/images/docs/assign-confirm-dialog.webp) +![分配确认窗:确认分配,或选择暂不开始](/images/docs/assign-confirm-dialog.webp) -交接说明只用于这一次执行,适合补充范围、顺序或本次的注意重点。长期有效的要求写进任务、项目说明或智能体指令中。 +这次执行需要智能体知道的信息,都写在任务本身——它的说明、讨论,或者项目说明与智能体指令中。 分配给小队时,任务的负责人是小队,首先开始执行的是小队 leader。它会按照小队的工作方式判断是否需要交给其他成员。 @@ -47,7 +46,7 @@ Multica 会为智能体创建一条[Task](/tasks)。在线运行时领取 task `backlog` 也不会因为分配智能体而启动执行。把任务移出 `backlog`,或者在评论中继续向负责人提出要求后,才会产生后续执行。 -如果任务已经有智能体或小队负责人,把它移出 `backlog` 会弹出同一个确认窗:真正启动执行的正是这次状态变更,所以同样可以填写交接说明,也同样有**暂不开始**——只改状态,不叫醒智能体。目标状态只要不属于 `backlog`、`done`、`cancelled` 分类,都会弹这个确认窗。 +如果任务已经有智能体或小队负责人,把它移出 `backlog` 会弹出同一个确认窗:真正启动执行的正是这次状态变更,所以同样有**暂不开始**——只改状态,不叫醒智能体。目标状态只要不属于 `backlog`、`done`、`cancelled` 分类,都会弹这个确认窗。 只有 `backlog` 分类有这个效果,该分类下的[自定义状态](/issues#自定义状态)表现完全一致。已经处于 `done` 或 `cancelled` 的任务,分配或重新分配给智能体时同样会立即开始执行。 diff --git a/apps/docs/public/images/docs/assign-confirm-dialog.webp b/apps/docs/public/images/docs/assign-confirm-dialog.webp index d0a71fcf284ebad1959f3f54ae71e29989e35fa1..b97f1150f92afdb21fb8b98e33bba8505ffc8a34 100644 GIT binary patch literal 7648 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%0D3to;zAMsVPflaM&Wfc@71XY7QB(E{C zJx=Q^@QuN5bBC2zj6U%ggf zng2fbV&hIaXer!VKJHK%dEowfYIuaRh#w-3MJCF%r%5CZR@*g-`5h=@a$7Y(EPq4F z$;))K)^>ArB8(d9G>E16S8zbmd9CdplG=YkMbX|&8L)TGas4>aJjO#s9TSzjX5akpOHb;&U|M?%O>dm2cZ4usK8J#jnKc?*HyQ5$X_WRd#x z#~nInzEWd2fF-xERo9|n(Jw?HEjA-`s!Kbgy6Z$AH_Ai?YrLh<$uFr0hpvB!>C-uz z{Cq;Vx8fLS*ELx)T_Mw>Q1;t=yawZ<3wEMqQ zU1$-Yi(yC`8u<~cE%jP&r-$e@4#`D{(^K4sZX;rvUilG)(aJsUH@hj|MYh`AO7!hYF_|E9$YK~@~?;Z!9kbNVT-0)pmZ%wUlnwO+L zbWIH673@EK>YRx0vvf9LiBvs46EEyY?>mB32E0jCnNncE4k2CKA6q!)Zpzh`6*jjgIvwS^Qn0PwZj|_P<8UPI}W7E282W23_a4CL0o}(2vRO=*yi+Ru6ZT9$c!|AOZ zPKTK8q#@ldfMo0&l*--p2Sk6<)ffi->IT*o)4JQu6M7J**tY>(90KAROFA4TPo1;= z3sFB*AlRLMt=EH-rFR@(RMy z#!doFkBGdUW{*P}Kyy=Ysm_Gjeu}6Q9&%l85^{CRTKs02zv|7wbJiBmSfEtd=x{oP zMol|xj_sM-AGp6lhQ%nch{ZwUi5 z+wIt@x3VY5pHl#ly;lC1ore0NpUtoWx<>>NZ5o@(KvpK%2Tk|tjQ)OaZ_jVI4qx6^ zz*E9sm}32}87|>L&@mi&j|GY{Bupl-98kFwIs{Z@br|O-D95Yq=MoouwZGl3h+HTL zIFM%ScWII*&&zJqG7s$8h?m`BKGZIg<&Be#`|6kY=Ru@P>vSFSy$3I7vWMn`?8V0j6( z-*f$BC2a4}_i_cq)Ldl(vt)1tPZBd&wo5ttDXd=p8R+XLq{ZL0d(<^6V=JWh-?tG& z-)Y%#{5v`wH9P`@Q*b!^F>;Sg_C5g5ZeTHACNtpuGw3p$6{Joi|J{>RY+vS(Vp^B! z=kXQ3SK;+n@O#=fZK-C5JPYv>-IfQV;N&@GSq5Z49Cz%Xcc1%|uYkvBlIsrJYYH(^ ztE&PCGoE^Y&#RB#rKOLB)Ad%rD_@nxFg=eWNBLZWHFsd3*H_LpVvWEiWDs)-$c z;iwwqopy$5daPM|Q!;Ojly@~8)?;!R_3I?6tvF+O3=!edT|^MBPo>IcZc3f-A#_wu zq=D6HNfM2ivRQoC>5Xg)EY6xL%e@pLpr2|{E^=dVX2Hz~h}lkPMQ7aT5}F=L&yBdz z_D>+mG{3C`xEr1*>N=>RtjMl}Yc$8qIYEtdd`<8Ts>A${sKIB(SKvb~^b6`@sf0`e zLtwk#OaO*R$Z+b*NcFEmmSAIxiA<9U6L@-&%Z|9q@3T|!u2GP@-o%*-2kl!7gL4qt zdt1xbFLxSSM=8;3Vj=r22Uw&7E#vb|PMca)Qi5TuX0NL-(<7C1 z%{Yy+(m5a06Le4uQ(@{cV4XfT${d~n%tfZ|7lBbON;xoI@S1e2NkN7gdDgVbE@bz; zHDZgf{bA^hsvGO{{M3dUgTPbf!BY$b1HaU`z0&8w2=&TewFRJsH1X2#lb8}?QuGLo z^O9EwMS0h$4|+~VcURx+6TwuYP`JC*9uLINLB2O0xv8th0~sq?YohAK`;sTd{cRPB z0Y(cvKX<^RSEwR;BY~TSn)CIPzPXB=8VEVYJj*ysJ)9Bkp}{ap2ET;$-!o=Eu#-%QJty@m zgVv=OK+XuqcrS|u%rs<8!D}3r59DR6cuMHQ2XUZ3Di?$R4CKw;{DBt^MNjCEzEI7P zB4{zUnm93WBap?=xDXLHn#6u~d4jJAf)C)ry+rjDgq@;6VsX5;fMvA#3mltC<9vt^ zzPxX~wscGNzCg>?yJ=e2`FpKOu`S`k)>7zp^Vj1TYj21c#J~n23k~fLX^PNj`q7l| zrzYAu#Ce}a)QjbS6os9v@RcJ0V|pKuBD-b}?0wnybOy9#Hxr!CztR^1W&YCm9`9yB zUJ@42S;n>%ipWlEDBju=8d103)3SF)md*I&SM8g8ak+02)+?Set$NVTGLYo*H_M-e;$NZo;C^rbRp+KZLCz48exSQ+Vvacxpt z1`iriq<0u}k?@ko4cCvRp~Y4c-|A-HS}X*Dv=HQ!A6j(!B7Tf)c%MnoG@LlD{OG}B zb|cOSl*)2}W7zboqpTHCwp7*goBr`#g*80jqCGTTB@t8u?T2N24;37RWe{5d_6U~T zJK%S0#TH7N@;qzM!`|9Ge%3wxvy%b$ntbV)mp7C_8$9sFbc3)Mv#_%&yK+BcpGHt% zouc`V0>qR_W6=d;CDpD=1XI;bS`0M)vyRslesMw{5~Sa~lZs)(S*EdT55$Q2%NeLV zZ^ERx&aGaiU!nSQ(i#Ywk&pFA^Ap!s!{t)(EY4(tMW}ysA7T}V6nreMwm>$JQ}47> zb*ZlLr`xX*%dsrdtyPL37Fdw{_Y7=hJO~p~l&~*4=p2oB< z3|a`M`f>@%E^L(Z1p-60D+q+9PrZZ+jEjztAYt%e;Vs06G;_Oms_ zI1J{m|8^%2{@I?&%C(W=&~l+$!W|Y~lgi1{Q)6=dFB9phfNnU0#zMaBmWk(PXprx< zz7QFwd->~^SO~iYmsBT=K!<4RBx5@rQ=KaTh6B?ratvI7gEW(vpu_TXy%f&vP>-k&*&&2-NAq+I zzf8p=Zsd&a{DpYExvSqn(~l%zx%N7|agtqd^>M*s1`BuP#%(v5ho|^8;!e9G%TJTG~EiE zyBCjIDk)t7`*W?(3NK573o#=&ucJ%r0d(c1p^c}<_c%Rj;eG25w>fqQ%@r`ql~H-e z&8mB(mhz79!9%zoM=T0;X@=#4oTVsVleS)_t}12RN!8^2KYjdc7e)RV$$&c=nq$2$ zhW=1NyhC1GVKFEXJ8y>tL!vb;=$kFPc(-0F3I>~^_Aimh_HrYR6mNVk&pO`{>?Lk^ z&mYzy0AU-?f3*i^2sd!$QF1g&N)-CUJhH$Q%%>cJ4?ZV&by;>-4#aWb;;fU+*@|t@ z)cy1>%S%%Sst(keCX%C*Ep1;sPM18gUKV0;LEgSHo_Rt;wWtT&BdNu-is$FKI0}+@<wl?H+ODa^zIR3kD`HSo~J& z0Z)TAWj%UEU^`4zhR>hakX%F0c(LGqRUTcn!-e#F-y%F&ALC*6D*%~W8AHqvP%(j?&+;xtga*zTgEc0B7xsk2O!BqwXnnS42j!i5JmSg>ra7oSl$tGt8?9DgtQ0AY?0RRInn{zke#3yC z5+G2*mzJA|wnZ|Kr^SAO=qpx*f6YXsQO#%-&($n$yVHlq!xI*Hj}>I!SlU-jZd>wLmOXs9 z0BxsNn#1Wsl&r(q(aL!*1T&(vj7Vl7mxljSeaM(j63`qKa2>1ppVbA8jo05ciRePm z)3x2FNb_fUcTZq#Rp#vMtcL>aCV(aaEro$6RFUEcj-vrGbArrB4gslhU2`q`H(rhP z)^>CFc#0JlXGbMl>9#p+$mPR^!tL031}e&vMggf*AjW0!2t8XFVgC}2Cxi+|I9qNW z9WYI%aJ}2U9=>`^NE_x({A4J!u6CySI?0^bbleLZH|ZV{vvujCU|PM*g9&Y_W5*}O ztFzwFz)iqe!b(3q*k{Y3^KplFk^X|-!Hxa#kj1sDEj8u+jOFRs$>KL7v# literal 9452 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 RxB&rys5JfGNA&;I{{fzUY?J^1 diff --git a/packages/core/api/schemas.test.ts b/packages/core/api/schemas.test.ts index 96442deaa..d22714bc2 100644 --- a/packages/core/api/schemas.test.ts +++ b/packages/core/api/schemas.test.ts @@ -478,14 +478,14 @@ describe("IssueTriggerPreviewSchema", () => { it("parses a well-formed response", () => { const parsed = IssueTriggerPreviewSchema.parse({ triggers: [ - { issue_id: "i1", agent_id: "a1", source: "assign", handoff_supported: true }, - { issue_id: "i2", agent_id: "a2", source: "status", handoff_supported: false }, + { issue_id: "i1", agent_id: "a1", source: "assign" }, + { issue_id: "i2", agent_id: "a2", source: "status" }, ], total_count: 2, }); expect(parsed.total_count).toBe(2); expect(parsed.triggers).toHaveLength(2); - expect(parsed.triggers[0]).toMatchObject({ issue_id: "i1", agent_id: "a1", source: "assign", handoff_supported: true }); + expect(parsed.triggers[0]).toMatchObject({ issue_id: "i1", agent_id: "a1", source: "assign" }); }); it("defaults missing top-level fields (empty / older backend)", () => { @@ -500,7 +500,6 @@ describe("IssueTriggerPreviewSchema", () => { issue_id: "i1", agent_id: "", source: "", - handoff_supported: false, }); }); diff --git a/packages/core/api/schemas.ts b/packages/core/api/schemas.ts index 05350ab69..0bb00c902 100644 --- a/packages/core/api/schemas.ts +++ b/packages/core/api/schemas.ts @@ -1073,7 +1073,6 @@ const IssueTriggerPreviewItemSchema = z.object({ issue_id: z.string(), agent_id: z.string().default(""), source: z.string().default(""), - handoff_supported: z.boolean().default(false), }).loose(); export const IssueTriggerPreviewSchema = z.object({ @@ -1767,7 +1766,6 @@ export const AgentTaskSchema = z.object({ coalesced_comment_ids: OptionalStringArraySchema, delivered_comment_ids: OptionalStringArraySchema, trigger_summary: z.string().optional(), - handoff_note: z.string().optional(), kind: z.string().optional(), work_dir: z.string().optional().catch(undefined), relative_work_dir: z.string().optional().catch(undefined), diff --git a/packages/core/issues/mutations.ts b/packages/core/issues/mutations.ts index a4b825180..4b8365925 100644 --- a/packages/core/issues/mutations.ts +++ b/packages/core/issues/mutations.ts @@ -132,15 +132,14 @@ export function useUpdateIssue() { return api.moveIssue(id, { ...target, ...moveIntent }); }, onMutate: ({ id, move_intent: _moveIntent, ...data }) => { - // suppress_run / handoff_note are write-time control fields, not Issue - // columns. description_base is merge metadata, while description itself + // suppress_run is a write-time control field, not an Issue column. + // description_base is merge metadata, while description itself // is resolved against that base on the server and therefore is not safe // to predict optimistically. Keep the authoritative raw description in // cache so hidden channel-media markers remain available as the base for // a rapid follow-up edit. mutationFn still sends the full payload. const { suppress_run: _suppressRun, - handoff_note: _handoffNote, description: _description, description_base: _descriptionBase, title_base: _titleBase, @@ -251,7 +250,6 @@ export function useUpdateIssue() { // is the plain surgical patch it always was. const { suppress_run: _suppressRun, - handoff_note: _handoffNote, description_base: _descriptionBase, move_intent: _moveIntent, id: _id, @@ -445,7 +443,6 @@ export function useBatchUpdateIssues() { // until a refetch returns the committed result. const { suppress_run: _suppressRun, - handoff_note: _handoffNote, description: _description, description_base: _descriptionBase, ...patch diff --git a/packages/core/runtimes/cli-version.test.ts b/packages/core/runtimes/cli-version.test.ts index 4e77538ac..ccc0f3cc9 100644 --- a/packages/core/runtimes/cli-version.test.ts +++ b/packages/core/runtimes/cli-version.test.ts @@ -3,9 +3,7 @@ import { chatProjectContextSupported, checkQuickCreateCliVersion, checkQuickCreateFieldsCliVersion, - handoffSupported, MIN_CHAT_PROJECT_CONTEXT_CLI_VERSION, - MIN_HANDOFF_CLI_VERSION, runtimeAdvertisesLocalWorktree, } from "./cli-version"; @@ -41,33 +39,6 @@ describe("checkQuickCreateFieldsCliVersion", () => { }); }); -// Mirrors server/pkg/agent/handoff_version_test.go so the frontend soft-gate -// signal and the server's authoritative one agree by construction. -describe("handoffSupported", () => { - it("supports a tagged release at or above the minimum", () => { - expect(handoffSupported(MIN_HANDOFF_CLI_VERSION)).toBe(true); - expect(handoffSupported("0.4.0")).toBe(true); - expect(handoffSupported("v0.3.28")).toBe(true); - }); - - it("does not support a tagged release below the minimum", () => { - expect(handoffSupported("0.3.26")).toBe(false); - expect(handoffSupported("0.2.21")).toBe(false); - }); - - it("fails closed on empty or unparsable input", () => { - expect(handoffSupported("")).toBe(false); - expect(handoffSupported(undefined)).toBe(false); - expect(handoffSupported(null)).toBe(false); - expect(handoffSupported("garbage")).toBe(false); - }); - - it("treats git-describe dev builds as supported regardless of base tag", () => { - expect(handoffSupported("v0.3.0-5-gabc1234")).toBe(true); - expect(handoffSupported("v0.1.0-235-gdaf0e935-dirty")).toBe(true); - }); -}); - describe("chatProjectContextSupported", () => { it("supports a tagged release at or above the minimum", () => { expect(chatProjectContextSupported(MIN_CHAT_PROJECT_CONTEXT_CLI_VERSION)).toBe(true); diff --git a/packages/core/runtimes/cli-version.ts b/packages/core/runtimes/cli-version.ts index bdc46830c..62dd91731 100644 --- a/packages/core/runtimes/cli-version.ts +++ b/packages/core/runtimes/cli-version.ts @@ -88,31 +88,6 @@ export function readRuntimeCliVersion(metadata: Record | undefi return typeof v === "string" ? v : ""; } -/** - * Frontend mirror of the server's `MinHandoffCLIVersion` soft gate - * (`server/pkg/agent/version.go`). The assignment handoff note is only rendered - * into the run's opening prompt by daemons at or above this multica CLI version - * (MUL-3375); older daemons silently drop it. Unlike the quick-create gate this - * never blocks the assignment — the UI just grays out the note box and warns. - * - * Keep in lockstep with the server constant; the two are enforced independently - * (the server is authoritative) but must agree so the warning matches reality. - */ -export const MIN_HANDOFF_CLI_VERSION = "0.3.28"; - -/** - * Whether a daemon-reported CLI version is new enough to render a handoff note. - * Mirrors server `agent.HandoffSupported`: missing / unparsable / below-minimum - * all degrade to `false`, and dev-built daemons (git-describe shape) always - * pass — the version string is the shared signal, so frontend and server agree - * by construction. Pure and synchronous, so the note box can settle from the - * already-warm runtime cache instead of waiting on the trigger-preview - * round-trip, exactly like the quick-create version gate. - */ -export function handoffSupported(detected: string | undefined | null): boolean { - return meetsMinCliVersion(detected, MIN_HANDOFF_CLI_VERSION); -} - /** * First release whose daemon renders the chat session's project context * (description + resources) into the run brief (PR #5765, ships in v0.4.10). @@ -121,16 +96,15 @@ export function handoffSupported(detected: string | undefined | null): boolean { * the Project Context section, so the durable description never reaches the * agent. SOFT gate: selecting a project always works; the UI only warns. * - * Frontend-only constant: unlike handoff there is no server preview endpoint - * computing this, so there is no server twin to keep in lockstep with. + * Frontend-only constant: there is no server preview endpoint computing this, + * so there is no server twin to keep in lockstep with. */ export const MIN_CHAT_PROJECT_CONTEXT_CLI_VERSION = "0.4.10"; /** * Whether a daemon-reported CLI version is new enough to inject a chat - * session's project description into the run brief. Same degrade rules as - * `handoffSupported`: missing / unparsable / below-minimum are `false`, - * dev-built daemons (git-describe shape) always pass. + * session's project description into the run brief. Missing / unparsable / + * below-minimum are `false`; dev-built daemons (git-describe shape) always pass. */ export function chatProjectContextSupported(detected: string | undefined | null): boolean { return meetsMinCliVersion(detected, MIN_CHAT_PROJECT_CONTEXT_CLI_VERSION); diff --git a/packages/core/types/agent.ts b/packages/core/types/agent.ts index b77e4deb6..4d28b5cd6 100644 --- a/packages/core/types/agent.ts +++ b/packages/core/types/agent.ts @@ -347,13 +347,6 @@ export interface AgentTask { * or deleted. */ trigger_summary?: string; - /** - * Handoff instruction the assigner attached when starting this run (MUL-3375). - * Present only on assignment-triggered runs that carried a note; the execution - * log shows it inline as the trigger reason. Absent (legacy / no note) falls - * back to the generic "initial run" label. - */ - handoff_note?: string; /** * Server-computed source discriminator used by the activity row to label * tasks that have no linked issue (so e.g. quick-create tasks render diff --git a/packages/core/types/api.ts b/packages/core/types/api.ts index d45a47a70..4e6b22a54 100644 --- a/packages/core/types/api.ts +++ b/packages/core/types/api.ts @@ -77,10 +77,6 @@ export interface UpdateIssueRequest { * MUL-3375). The assignee/status change still applies. Control field — * strip from optimistic cache patches; never written onto the Issue. */ suppress_run?: boolean; - /** Free-text handoff instruction injected into the started run's opening - * context (MUL-3375). Only consumed when a run actually starts. Control - * field — strip from optimistic cache patches. */ - handoff_note?: string; } /** @@ -112,14 +108,11 @@ export interface IssueTriggerPreviewParams { } /** One issue that WILL start a run under the prospective write. `agent_id` is - * the runnable agent (squad leader for squads). `handoff_supported` is the - * soft-gate signal: false when the target runtime is too old to render a - * handoff note (gray the note box; the assignment still works). */ + * the runnable agent (squad leader for squads). */ export interface IssueTriggerPreviewItem { issue_id: string; agent_id: string; source: string; - handoff_supported: boolean; } export interface IssueTriggerPreview { diff --git a/packages/views/chat/components/use-chat-project-context-support.ts b/packages/views/chat/components/use-chat-project-context-support.ts index b53330d2a..26673f53c 100644 --- a/packages/views/chat/components/use-chat-project-context-support.ts +++ b/packages/views/chat/components/use-chat-project-context-support.ts @@ -12,8 +12,7 @@ import { * project description into the run brief. `null` means "cannot tell" (no * agent, no bound runtime, or the runtime row not in cache yet) and must NOT * warn: this is a soft gate, and a spurious warning is worse than a - * description an old daemon drops — same policy as the handoff note gate in - * run-confirm. + * description an old daemon drops. * * Shared by both send chains (chat tab controller and floating ChatWindow) so * the resolution rule cannot drift between the two surfaces. diff --git a/packages/views/common/task-transcript/agent-transcript-dialog.tsx b/packages/views/common/task-transcript/agent-transcript-dialog.tsx index dddc41528..2cbc1771c 100644 --- a/packages/views/common/task-transcript/agent-transcript-dialog.tsx +++ b/packages/views/common/task-transcript/agent-transcript-dialog.tsx @@ -801,7 +801,7 @@ export function AgentTranscriptDialog({ ? t(($) => $.transcript.trigger_chat) : task.kind === "quick_create" ? t(($) => $.transcript.trigger_quick_create) - : task.kind === "direct" || task.handoff_note + : task.kind === "direct" ? t(($) => $.transcript.trigger_direct) : t(($) => $.transcript.trigger_initial); diff --git a/packages/views/issues/components/task-run-labels.ts b/packages/views/issues/components/task-run-labels.ts index d97863413..fdd06dd11 100644 --- a/packages/views/issues/components/task-run-labels.ts +++ b/packages/views/issues/components/task-run-labels.ts @@ -42,12 +42,6 @@ export function useTriggerText(task: AgentTask): string { } if (task.autopilot_run_id) return t(($) => $.execution_log.trigger_autopilot); if (task.trigger_comment_id) return t(($) => $.execution_log.trigger_comment); - // Assignment-triggered run that carried a handoff note: show the note inline - // (truncated by the caller) the way comment triggers show their text, so the - // row reads as the handoff instead of the generic "initial run". - if (task.handoff_note) { - return retryPrefix + t(($) => $.execution_log.trigger_handoff_prefix) + stripMentionMarkdown(task.handoff_note); - } return t(($) => $.execution_log.trigger_initial); } diff --git a/packages/views/issues/hooks/use-issue-trigger-preview.ts b/packages/views/issues/hooks/use-issue-trigger-preview.ts index 4ec4c03f7..283ddff45 100644 --- a/packages/views/issues/hooks/use-issue-trigger-preview.ts +++ b/packages/views/issues/hooks/use-issue-trigger-preview.ts @@ -22,9 +22,6 @@ export interface UseIssueTriggerPreviewResult { triggers: IssueTriggerPreviewItem[]; totalCount: number; isLoading: boolean; - /** True when every trigger's target runtime can render a handoff note, so - * the note box is safe to enable. False if any started run would drop it. */ - handoffSupported: boolean; } const EMPTY: IssueTriggerPreviewItem[] = []; @@ -90,6 +87,5 @@ export function useIssueTriggerPreview( // Only the first load (no prior data) is "loading"; a background/placeholder // refetch is not, so reveal animations gated on this never collapse mid-fetch. isLoading: enabled && previewQuery.isLoading, - handoffSupported: triggers.length > 0 && triggers.every((t) => t.handoff_supported === true), }; } diff --git a/packages/views/locales/en/issues.json b/packages/views/locales/en/issues.json index 9c69b1f8e..a53ec8024 100644 --- a/packages/views/locales/en/issues.json +++ b/packages/views/locales/en/issues.json @@ -612,7 +612,6 @@ "trigger_autopilot": "Autopilot run", "trigger_comment": "Comment trigger", "trigger_initial": "Initial run", - "trigger_handoff_prefix": "Handoff: ", "included_comments_one": "Includes {{count}} comment", "included_comments_other": "Includes {{count}} comments", "status_queued": "Queued", diff --git a/packages/views/locales/en/modals.json b/packages/views/locales/en/modals.json index 1d2759529..4c2a1a833 100644 --- a/packages/views/locales/en/modals.json +++ b/packages/views/locales/en/modals.json @@ -56,9 +56,6 @@ "assign_batch": "Assign {{count}} issues to {{name}}. They'll start right away if the run conditions are met.", "title_promote": "Start work now?", "promote_single": "Move this issue to {{status}}. {{name}} will start right away if the run conditions are met.", - "note_label": "Handoff note (optional)", - "note_placeholder": "Tell them what matters — scope, focus, or what to avoid…", - "note_unsupported": "Their runtime is too old to receive this handoff note; it may not be read.", "confirm_assign": "Confirm assignment", "confirm_promote": "Move and start", "dont_start": "Don't start yet", diff --git a/packages/views/locales/ja/issues.json b/packages/views/locales/ja/issues.json index c546ba486..4ec7a4503 100644 --- a/packages/views/locales/ja/issues.json +++ b/packages/views/locales/ja/issues.json @@ -589,7 +589,6 @@ "trigger_autopilot": "オートパイロット実行", "trigger_comment": "コメントトリガー", "trigger_initial": "初回実行", - "trigger_handoff_prefix": "引き継ぎ:", "included_comments_other": "{{count}} 件のコメントを含む", "status_queued": "待機中", "status_dispatched": "開始中", diff --git a/packages/views/locales/ja/modals.json b/packages/views/locales/ja/modals.json index 2988f3065..bde60480f 100644 --- a/packages/views/locales/ja/modals.json +++ b/packages/views/locales/ja/modals.json @@ -56,9 +56,6 @@ "assign_batch": "{{count}} 件のタスクを {{name}} に割り当てます。実行条件を満たす場合はすぐに作業を開始します。", "title_promote": "今すぐ作業を開始しますか?", "promote_single": "このタスクを {{status}} に移します。実行条件を満たす場合は {{name}} がすぐに作業を開始します。", - "note_label": "引き継ぎメモ(任意)", - "note_placeholder": "範囲・重点・触れてほしくない点などを一言で…", - "note_unsupported": "相手の実行環境が古いため、この引き継ぎメモは読み取れない可能性があります。", "confirm_assign": "割り当てを確認", "confirm_promote": "移動して開始", "dont_start": "まだ開始しない", diff --git a/packages/views/locales/ko/issues.json b/packages/views/locales/ko/issues.json index af2d5998b..268001d0e 100644 --- a/packages/views/locales/ko/issues.json +++ b/packages/views/locales/ko/issues.json @@ -589,7 +589,6 @@ "trigger_autopilot": "오토파일럿 실행", "trigger_comment": "댓글 트리거", "trigger_initial": "초기 실행", - "trigger_handoff_prefix": "인계: ", "included_comments_other": "댓글 {{count}}개 포함", "status_queued": "대기 중", "status_dispatched": "시작 중", diff --git a/packages/views/locales/ko/modals.json b/packages/views/locales/ko/modals.json index d473df568..82899b8c5 100644 --- a/packages/views/locales/ko/modals.json +++ b/packages/views/locales/ko/modals.json @@ -56,9 +56,6 @@ "assign_batch": "{{count}}개 태스크를 {{name}}에게 할당합니다. 실행 조건을 충족하면 바로 시작합니다.", "title_promote": "지금 작업을 시작할까요?", "promote_single": "이 태스크를 {{status}}(으)로 옮깁니다. 실행 조건을 충족하면 {{name}}이(가) 바로 시작합니다.", - "note_label": "인계 메모 (선택)", - "note_placeholder": "범위·핵심·건드리지 말 것 등을 한마디로…", - "note_unsupported": "상대 런타임이 오래되어 이 인계 메모를 읽지 못할 수 있습니다.", "confirm_assign": "할당 확인", "confirm_promote": "옮기고 시작", "dont_start": "아직 시작 안 함", diff --git a/packages/views/locales/zh-Hans/issues.json b/packages/views/locales/zh-Hans/issues.json index 18fd36d5c..f420718f1 100644 --- a/packages/views/locales/zh-Hans/issues.json +++ b/packages/views/locales/zh-Hans/issues.json @@ -589,7 +589,6 @@ "trigger_autopilot": "自动化运行", "trigger_comment": "评论触发", "trigger_initial": "首次运行", - "trigger_handoff_prefix": "交接:", "included_comments_other": "包含 {{count}} 条评论", "status_queued": "排队中", "status_dispatched": "启动中", diff --git a/packages/views/locales/zh-Hans/modals.json b/packages/views/locales/zh-Hans/modals.json index 12872daef..2fb46e936 100644 --- a/packages/views/locales/zh-Hans/modals.json +++ b/packages/views/locales/zh-Hans/modals.json @@ -56,9 +56,6 @@ "assign_batch": "将 {{count}} 个任务指派给 {{name}};符合运行条件时会立即开始处理。", "title_promote": "现在开始处理?", "promote_single": "把这个任务移到 {{status}};符合运行条件时 {{name}} 会立即开始处理。", - "note_label": "交接说明(可选)", - "note_placeholder": "交代一句——范围、重点、或别碰什么……", - "note_unsupported": "对方的运行环境版本较旧,这条交接说明可能读不到。", "confirm_assign": "确认指派", "confirm_promote": "移动并开始", "dont_start": "暂不开始", diff --git a/packages/views/modals/create-issue.test.tsx b/packages/views/modals/create-issue.test.tsx index 0bd886c2f..4cd4f4180 100644 --- a/packages/views/modals/create-issue.test.tsx +++ b/packages/views/modals/create-issue.test.tsx @@ -227,7 +227,6 @@ vi.mock("../issues/hooks/use-issue-trigger-preview", () => ({ triggers: [], totalCount: 0, isLoading: false, - handoffSupported: false, }), })); diff --git a/packages/views/modals/run-confirm.test.tsx b/packages/views/modals/run-confirm.test.tsx index 1cd6bd106..27526b5c4 100644 --- a/packages/views/modals/run-confirm.test.tsx +++ b/packages/views/modals/run-confirm.test.tsx @@ -8,26 +8,6 @@ import { } from "@multica/core/shortcuts"; import { RunConfirmModal } from "./run-confirm"; -// --- Warm agent / squad / runtime caches (prefetched in the real app) -------- -// The modal resolves the target runtime's cli_version locally — an agent's own -// runtime, or a squad leader's — so nothing in the dialog waits on the network. -// Tests drive the verdict by swapping the runtime's reported cli_version here. -const cache = { - agents: [{ id: "agent-1", runtime_id: "runtime-1" }] as Array<{ id: string; runtime_id: string }>, - runtimes: [{ id: "runtime-1", metadata: { cli_version: "0.4.0" } }] as Array<{ - id: string; - metadata: Record; - }>, - squads: [{ id: "squad-1", leader_id: "agent-1" }] as Array<{ id: string; leader_id: string }>, -}; -vi.mock("@tanstack/react-query", () => ({ - useQuery: ({ queryKey }: { queryKey: string[] }) => { - if (queryKey[0] === "runtimes") return { data: cache.runtimes }; - if (queryKey[0] === "workspaces" && queryKey[2] === "agents") return { data: cache.agents }; - if (queryKey[0] === "workspaces" && queryKey[2] === "squads") return { data: cache.squads }; - return { data: [] }; - }, -})); vi.mock("@multica/core/hooks", () => ({ useWorkspaceId: () => "ws-test" })); vi.mock("@multica/core/issue-statuses/hooks", () => ({ useIssueStatuses: () => @@ -48,26 +28,6 @@ vi.mock("@multica/core/issue-statuses/hooks", () => ({ }, ]), })); -vi.mock("@multica/core/workspace/queries", () => ({ - agentListOptions: (wsId: string) => ({ queryKey: ["workspaces", wsId, "agents"] }), - squadListOptions: (wsId: string) => ({ queryKey: ["workspaces", wsId, "squads"] }), -})); -// Stub the runtimes barrel: the query-options builder would otherwise drag the -// network layer in, and the deep cli-version module isn't an exported subpath. -// `handoffSupported`'s real semver/dev-build logic is exhaustively covered in -// packages/core/runtimes/cli-version.test.ts; here we only need a faithful -// stand-in for the >= 0.3.28 threshold so the cache → version → verdict wiring -// is exercised end to end. -vi.mock("@multica/core/runtimes", () => ({ - runtimeListOptions: (wsId: string) => ({ queryKey: ["runtimes", wsId, "list"] }), - readRuntimeCliVersion: (m?: { cli_version?: unknown }) => - typeof m?.cli_version === "string" ? m.cli_version : "", - handoffSupported: (v?: string | null) => { - const m = /(\d+)\.(\d+)\.(\d+)/.exec((v ?? "").trim()); - if (!m) return false; - return Number(m[1]) * 1e6 + Number(m[2]) * 1e3 + Number(m[3]) >= 3028; // 0.3.28 - }, -})); const mockUpdate = vi.fn().mockResolvedValue({ id: "issue-1" }); const mockBatch = vi.fn().mockResolvedValue({ updated: 2 }); @@ -94,9 +54,6 @@ vi.mock("../i18n", () => ({ title_assign: "Confirm assignment?", assign_single: "assign to {{name}}", assign_batch: "assign {{count}} to {{name}}", - note_label: "Handoff note", - note_placeholder: "scope...", - note_unsupported: "runtime too old", confirm_assign: "Confirm assignment", dont_start: "Don't start yet", toast_failed: "failed", @@ -130,9 +87,6 @@ vi.mock("@multica/ui/components/ui/button", () => ({ ), })); -vi.mock("@multica/ui/components/ui/textarea", () => ({ - Textarea: (props: React.TextareaHTMLAttributes) =>