mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-10 02:16:45 +08:00
* refactor(onboarding): drop the cross-workspace returning-member branch (MUL-5827) Mika's onboarding kickoff carried a `returning` flag, set by the frontend whenever the member was creating an additional workspace. It told the model "this member has onboarded elsewhere; keep the introduction to one line". Every workspace onboards from scratch. A member's second workspace may carry entirely different work, different collaborators, and a different reason for existing — compressing its opening on the strength of an unrelated workspace only makes this one worse. The flag was also the single place where one workspace's state reached into another's first conversation. Removed end to end: the request field, the prompt note, the API client type, the bootstrap input, and the `returning: isNewWorkspace` call site. Older desktop builds that still send `returning` are unaffected — encoding/json ignores the unknown field, which is exactly the new behavior. The built-in skill never branched on this, so SKILL.md needs no change. Co-authored-by: multica-agent <[email protected]> * feat(onboarding): write Mika's opening on the server instead of running an agent (MUL-5827) The first message a new member ever read cost a runtime cold start plus two model round trips: the opening was produced by a full chat task, so they watched a spinner for tens of seconds, and a run that failed introduced Mika as a red error bubble. Nothing in that reply needed an agent. The multica-onboarding skill already pinned it to four beats under a length budget, it reads no workspace state and calls no tool, and every input it personalizes on — language, workspace name, agent display name — is already in the request. It also never generated quick actions (starter cards replace them), so the run bought nothing. The endpoint now writes two rows in one transaction and enqueues nothing: - the opening the member reads, final on arrival, kind onboarding_opening so the starter cards still render under it; - the hidden kickoff, deliberately WITHOUT a task. The member's first real message adopts that kickoff into its input batch. That is what carries the onboarding skill instruction and the profile block into the run that does the first real work, and — because the kickoff quotes the opening verbatim — what stops Mika introducing herself a second time. She has no memory of an opening the server wrote. The cold start does not disappear; it moves to the member's first real request, where they are waiting on something they asked for with content and three one-click starter cards already in front of them. Three shared invariants that a two-row input batch newly exposes: - Both rows are written in one transaction, so DEFAULT now() gives them an identical timestamp. The session-list LATERAL picks the last message with no tiebreaker and ids are random UUIDs, so a tie could select the kickoff — whose kind makes buildChatLastMessage return nil, reporting no last message and bringing back the "Start with Mika" card after a perfect onboarding. The opening is written one microsecond after the kickoff. - DeleteUserChatMessageByTask deleted every user row of a cancelled turn, so cancelling the first message would destroy the only copy of the onboarding context and could hand the member the product's internal prompt as their restored draft. It now excludes the kickoff, and callers release it back to unowned so the next send re-adopts it. - Both reanchor queries would move the kickoff to dispatch time, putting the product's context AFTER the member's message inside one batch. Both now exclude it. The regression test fails without that guard. Also drops the completion-path rule that stamped a kickoff-input turn's reply as the opening: with the kickoff riding into the first real turn, it would render the starter cards a second time under a reply that is not an opening. Co-authored-by: multica-agent <[email protected]> * fix(onboarding): keep stamping the opening for a pre-deploy kickoff turn (MUL-5827) The server writes the opening now, so no new task produces one — but a kickoff task enqueued by the previous server can still be claimed by this one during a rolling deploy, and its reply IS that member's opening. Dropping the stamp outright left it a plain 'message', and message_kind is persisted with nothing to recompute it: that member's onboarding session would render without the starter cards forever. Gated on "the input batch is a kickoff and nothing else", which is exactly the old shape. The new kickoff always rides in alongside a real member message, so the member's first answer still cannot be mistaken for an opening. Deletable once no pre-deploy kickoff task can still be in flight. Co-authored-by: multica-agent <[email protected]> * fix(onboarding): release the kickoff when the first turn fails terminally (MUL-5827) Caught in a live environment, not by a test. The member's first turn died in preparation, the adopted kickoff stayed bound to that dead task, and their next message reached Mika with no onboarding context and no record that she had already greeted them — so she introduced herself a second time, in a conversation she had already opened. That is the exact failure this design exists to prevent. The cancel path already released the kickoff. The failure path did not, and a first-turn failure is entirely ordinary: runtime offline, agent CLI missing, timeout, prepare error. Released in FailTask under the existing retried == nil branch, which is the terminal-failure condition. Deliberately not on the retry path: a retry child inherits the root's chat_input_task_id and the kickoff stays bound to that root, so the retry still reads it — releasing there would strip the context off a turn that is about to run. Co-authored-by: multica-agent <[email protected]> * fix(onboarding): hand the kickoff to a queued successor, not to nobody (MUL-5827) Review catch. Adoption happens inside a send's transaction, so a message the member queues WHILE the kickoff's turn is still running finds nothing to adopt and never gets another chance. Releasing the kickoff to NULL when that turn died therefore left the already-sealed successor to execute with no onboarding skill, no profile block, and no record that Mika had already greeted them — the double introduction this design exists to prevent. Worse, a message sent later could adopt the orphan instead, delivering the context to a turn that had nothing to do with the opening. ReleaseOnboardingKickoffFromTask now re-targets the session's next un-started turn, falling back to NULL only when there is none. Three restrictions on the target, each load-bearing: - status = 'queued' only. A dispatched/running successor has already built its prompt from its input batch, so joining it now would consume the kickoff without ever delivering it; it waits unowned for the next send. - chat_input_task_id = id selects roots that own their input batch. A retry child names its root, and the kickoff is already reachable through that root, so retries must not be re-targeted. - regenerate_quick_actions_for IS NULL skips background suggestion passes, which carry no user input. Ordering matches the shared visible-head selector, so the kickoff lands on the turn the member will actually see run next. Covers both directions: A-owns/B-queued/A-fails hands off in the right order, and an already-dispatched successor is skipped. The first test fails against the previous NULL-only release. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Lambda <[email protected]> Co-authored-by: multica-agent <[email protected]>