mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-08 17:36:45 +08:00
374b6f3db45962b3be4f520e42e5b05de205a607
350
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
46d4e26712 |
MUL-8347: fix Hermes compatibility with gateway-backed and older ACP (#9666)
* fix(hermes): support gateway-backed and in-process ACP Co-authored-by: multica-agent <[email protected]> * fix(hermes): interrupt active task gateways during cleanup Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
ca593b3103 |
MUL-8302: fix(daemon): preserve task environment in Hermes tool shells (#9607)
* fix(daemon): preserve task environment in Hermes tool shells * fix(daemon): complete Hermes task env and preserve derived isolation (MUL-8302) Co-authored-by: multica-agent <[email protected]> * fix(deps): patch x/net HTML vulnerability (MUL-8302) Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Sol-Boy <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
a008d893dd |
MUL-8292: refactor(brief): move Available Commands detail behind --help (#9596)
* MUL-8292: refactor(brief): move Available Commands detail behind --help pointers Each bullet keeps its signature and the behaviour needed at the call site; wakeup condition catalogue, PR snapshot mechanics, actor filters and the repeated file rules now live only in --help and the multica-platform references. Issue brief 17,300 -> 16,372 bytes (-928); chat and autopilot briefs drop by the same amount. New TestAvailableCommandsKeepsCompressedContracts pins the kept contracts and guards the moved detail from drifting back. Co-authored-by: multica-agent <[email protected]> * MUL-8292: fix(brief): keep Available Commands rules self-contained in every kind Review follow-up on #9596: - comment list / issue create / comment add pointed at Workflow step 2 and Comment Formatting, which chat and autopilot briefs do not have; the bounded-read recipe and the workdir file rule are inline again. - repo checkout restores both keep conditions and the clean-checkout case. - wakeup scopes last_error to missing PR links/snapshots; a pending check or unmet condition records nothing. Contract test now runs every kind x provider, and a new test fails when the shared section references a heading or workflow step the kind lacks. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
ac963e9b08 |
MUL-7981: fix(execenv): fork past a re-dispatched task's fallback branch (#9547)
* fix(execenv): let a re-dispatched task fork past its own busy-branch fallback (#9198) When the conversation branch is held by another worktree, a local_directory worktree task forks onto <branch>-<task key>. That name is deterministic, so the same task re-dispatched while the branch is still busy hit 'a branch named ... already exists' and failed every attempt. Fork a fresh timestamped branch instead, leaving the earlier attempt's branch (and any partial work on it) untouched. Co-authored-by: multica-agent <[email protected]> * fix(execenv): keep picking an unused fallback branch name on repeated re-dispatch A second-granular suffix still collides when the same task is prepared more than twice within one second. Try a bounded sequence of fallback names (<alt>, <alt>-<unix>, <alt>-<unix>-N) instead of failing on the first collision; earlier attempts' branches stay untouched. Addresses review on #9547. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-eve <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
e8fdcfd226 |
MUL-6623 feat(agent): add host-local Devin CLI over ACP (#7485)
* feat(agent): add host-local Devin CLI over ACP Spawn `devin acp` via the shared ACP client. Local CLI only — not cloud Devin VMs/Playbooks/Secrets. Model via `devin acp --model`. Catalog from `devin models list --format json` (families/variants). * fix: skip Devin ACP handshake when browser auth is also offered * fix: close dim case string so Devin compiles Missing quote after dim broke runtime_config.go and every Go CI job. * fix: stop runtimeConfigPath recursion on Devin Devin is a builtin whose ProtocolFamily is itself. Recursing on that overflowed the stack and killed backend-tests. * test(agent): cover Devin model failures and final-answer delivery Co-authored-by: multica-agent <[email protected]> * fix(agent): honor Devin model aliases and stored login (MUL-6623) Co-authored-by: multica-agent <[email protected]> * fix(agent): drain Kiro panic details before terminating runtime (MUL-6623) Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Sol-Boy <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
90a88519df |
fix(daemon): defer issue status rules to project workflow (MUL-8145) (#9415)
* fix(daemon): defer issue status rules to the project workflow (MUL-8145) When the claim carries a project workflow, the runtime brief no longer tells the agent to set in_progress first and in_review on delivery. Those statuses may be outside the workflow (the server rejects them), and entering a handoff step reassigns the issue. The brief now points at the per-turn [Project workflow] block, and the workflow brief carries the entry/exit checks in workflow terms. Briefs without a workflow are byte-identical to before. Co-authored-by: multica-agent <[email protected]> * fix(squad): word the owning leader's status moves by the project workflow (MUL-8145) The squad protocol (Agent Identity, which outranks the runtime brief) still told an owning leader to move the parent to in_progress and then in_review, which a workflow may not list and which can hand the parent to another step's handler. When the claim carries a project workflow, responsibility 6 now keeps the ownership grant but moves the parent only through the [Project workflow] commands. The claim resolves the workflow before the briefing to choose the wording. Guest leaders and issues without a workflow are unchanged. Co-authored-by: multica-agent <[email protected]> * refactor(daemon): list no status keys in the brief for a workflow issue (MUL-8145) With a project workflow, [Project workflow] already lists the issue's statuses and the commands that move it, and the built-in keys may not be accepted there. The status command line now keeps only the command and points at that block; other issues learn valid statuses from the error. Co-authored-by: multica-agent <[email protected]> * refactor(workflow): stop forbidding status moves nothing asks for (MUL-8145) Under a project workflow the current runtime brief no longer states the built-in in_progress / in_review moves, so the lines that forbade them (in [Project workflow], the runtime brief and the squad protocol) only named statuses the agent had no reason to use. Drop them, and the exit check the runtime brief already states. Older daemons still state the built-in rules, so the daemon now advertises workflow-status-rules-v1; without it the claim keeps the override lines in [Project workflow] and the squad protocol. The multica-platform skill notes that [Project workflow] supersedes its built-in status arc. Co-authored-by: multica-agent <[email protected]> * refactor(workflow): shorten the step's moves in the workflow brief (MUL-8145) Replace the handoff preamble with a plain "Your moves:" heading, name who each move hands off to on its own line, and drop the "this workflow has no blocked status" explanations from Can't proceed. Co-authored-by: multica-agent <[email protected]> * refactor(workflow): state each step outcome as the status to set (MUL-8145) The moves block read like raw CLI ('Step done → multica issue status MUL-7 x'). Each outcome now says which status to set and that the server assigns the next handler; the CLI form is given once. Co-authored-by: multica-agent <[email protected]> * Revert "refactor(workflow): state each step outcome as the status to set (MUL-8145)" This reverts commit 2d82d2d342a99afe88cc40c48516b7ea8e860d1e. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-eve <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
ba1a795882 |
MUL-8114: fix(agent): preserve runtime config boundaries after formatting (#9382)
* fix(agent): preserve runtime config boundaries after formatting Keep original trailing newlines inside the managed block so formatter normalization does not leak cleanup changes into task commits. Constraint: Preserve user edits outside the managed brief and legacy block behavior. Rejected: Restore the entire original file | Discards agent-authored changes. Confidence: high Scope-risk: narrow Tested: execenv suite with race and shuffle; go vet; Prettier 3.6.2 and Oxfmt 0.40.0 file round trips. Not-tested: Full daemon auto-finalize integration. * fix(agent): don't restore boundary into replaced body; keep CRLF consistent Follow-up to the formatter-boundary fix (MUL-8114, review on #9382): - Record whether the pre-existing file was blank in the boundary metadata. An empty prefix at cleanup only means "blank original" when that flag is set; otherwise the agent removed or moved the original content, and re-adding the old trailing newline produced "\n# New\n". - Treat any two line breaks (LF or CRLF) as the collapsed separator and restore the recorded boundary in the formatter's line-ending style, so CRLF files no longer end up mixed or with extra CRLFs. - locateMarkerBlock consumes a CRLF after the end marker. - Restore the removed test doc comment and update the inject/cleanup docs to describe the metadata line. Co-authored-by: multica-agent <[email protected]> * fix(agent): don't restore blank-file boundary in front of new content A newline-only CLAUDE.md that the formatter stripped and the agent then wrote a body into (after the managed block) got the recorded newline restored in front of the new body, failing prettier --check. Only restore a blank file's boundary when nothing follows the block (MUL-8114). Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: shaun0927 <[email protected]> Co-authored-by: Multica Eve <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
c0b46688f2 |
fix(daemon): self-repair worktree deliveries moved off their branch (MUL-8139) (#9454)
* fix(daemon): let a worktree turn repair a delivery it moved off its branch (MUL-8139) Worktree-mode turns now get told which branch and starting commit their delivery must stay on. When a turn still ends with its work squashed past the baseline or committed off the task branch, the daemon runs one repair turn asking the agent to put the work back before Finalize decides. The MUL-6881 delivery guard is unchanged; resets past the baseline and repairs that would revert the baseline's content are never offered. Refs #9404 Co-authored-by: multica-agent <[email protected]> * fix(daemon): refuse repairs that would revert baseline edits; no supplements in repair turn (MUL-8139) Review follow-up on the worktree delivery self-repair: - repairWouldRevertBase now proves at the content level that the agent's files already carry every change the baseline made relative to its merge base with tip (equal content, or a clean three-way merge that leaves the agent's file unchanged). A path-level "both sides touched it" check let a repair silently revert a user's edit in a file the agent also changed. Deletions, non-regular or binary files, conflicts and git errors refuse. - The repair turn no longer accepts additional messages: its reply is discarded, so an accepted message would be acked as delivered and its answer lost. Unclaimed, it settles as turn_ended and the receipt says so. Co-authored-by: multica-agent <[email protected]> * fix(daemon): keep file modes in the worktree repair preservation check (MUL-8139) repairWouldRevertBase compared only text, so a user's chmod +x recorded in the baseline was lost when the agent worked from an older checkout: the repair re-committed run.sh as 100644. The agent's side is now read from the tree `git add -A` would stage (built in a private copy of the worktree index), which is exactly what RepairSteps commits, and every path the baseline changed must keep the baseline's mode. A mismatch, or an index that cannot be copied, refuses the repair. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-eve <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
e720fe2219 |
MUL-8135: feat(wakeups): wake on linked PR merge conflicts (--until-pr conflicts) (#9406)
* MUL-8135: feat(wakeups): wake on linked PR merge conflicts (--until-pr conflicts) Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> * MUL-8135: fix(wakeups): conflict wakes per PR identity, rotate the watch sweep - A linked-PR condition only fires for a PR key the rule has not seen; a PR leaving the satisfying set no longer re-wakes the agent for the others. - Conflict keys use the PR id, so same-numbered fork PRs on one head stay distinct. - The conflict-watch sweep rotates an address cursor so failing fetches cannot starve the rest. Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> * MUL-8135: fix(wakeups): count repeated linked-PR keys when deduping Checks and merged keys carry only the PR number, so same-numbered PRs from two repositories share a key. Compare occurrence counts so the second one satisfying still wakes the agent. Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
cfc165ce6a |
MUL-8095: fix(execenv): normalize required metadata for Codex user skills (#9346)
* fix(execenv): normalize required metadata for Codex user skills Co-authored-by: multica-agent <[email protected]> * fix(execenv): preserve user skill frontmatter formatting when filling metadata Inject missing name/description lines instead of re-marshalling the map, so comments, key order and CRLF endings survive; present-but-empty values are replaced on the yaml.Node tree. Fix stale expected values in test messages. MUL-8095 Co-authored-by: multica-agent <[email protected]> * fix(execenv): only repair what Codex rejects and verify every repair Codex falls back to the directory name for a missing/blank name and only requires a non-empty description, so stop injecting name. The one-line insert is kept for plain block mappings but now re-parsed; flow, indented and other mappings go through the node rewrite, which expands aliases of a replaced anchored value and keeps its comments. Every repair must parse and leave all other fields equal, otherwise the skill is skipped. MUL-8095 Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: JC的AI分身 <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
88f6d6272b |
MUL-8057: feat(agent): run Meta Muse Code as an MSP runtime (#9309)
* feat(agent): run Meta Muse Code as an MSP runtime Muse Code speaks MSP (Muse Session Protocol) over `muse serve`: JSON-RPC 2.0 on stdio, like ACP, but with its own method vocabulary. A session opens with session/start or session/resume, work is submitted with turn/start, and assistant text, reasoning and tool calls arrive as transcript items. The adapter maps those items onto the Backend contract, reads the terminal turn outcome, and reports a notFound resume as ResumeRejected so the daemon retries on a fresh session instead of losing continuity silently. Approval is a session property, not a launch flag: `muse serve` has no --yolo, and the CLI documents --yolo as TUI-only. A fresh session is created with approvalMode=allowAll and a resumed session re-asserts it with session/setApprovalMode, which is what lets a claimed task run without waiting for a human. The container and per-task worktree remain the isolation boundary, as they are for every other backend. commandId must be UUIDv7 — Muse derives a turn id from it and rejects anything else — so the adapter mints time-ordered ids rather than v4. The model catalog comes from the session-free model/list query and the pick is applied through session/start modelId. * feat(runtime-profiles): allow muse as a protocol family A custom runtime profile may only be based on a protocol family the daemon supports, and the database enforces the same list. Widening both keeps the create flow, the server validation and the CHECK constraint in lockstep with agent.SupportedTypes. * fix(muse): complete runtime integration and lifecycle handling (MUL-8057) Co-authored-by: multica-agent <[email protected]> * docs(muse): document runtime setup and support (MUL-8057) Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: yushen <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
700d25eef5 |
MUL-7420: render the workflow brief once on the server, for the assignee or an observer (#9293)
- issueworkflow.Brief is the only renderer: the claim carries it into every run's per-turn prompt; the editor preview and timeline show the same text. - Workflow handoffs stop writing the brief into handoff_note, so a person's note no longer drops the brief and the handoff framing fits again. - Agents that are not the issue's assignee get an observer brief without the step's commands. - Clearer rule override and narrower wakeup guidance; user text is stripped of control characters. - multica-platform skill documents project workflows. Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
da1671ca20 |
MUL-7420: Project workflows — columns and handoffs on the workflow (#8814)
* feat(workflows): workspace workflows with step handoffs (MUL-7420)
A workflow is a named, workspace-level selection of statuses from the
shared status catalog, in board order, with an optional handoff on each
step. Projects opt in through project.workflow_id; a project without one
keeps the whole catalog (the implicit Default workflow), so existing
workspaces see no change.
- issue_workflow table + project.workflow_id, CRUD API under
/api/issue-workflows, POST /api/projects/{id}/workflow with a dry-run
status mapping plan. Only issues on statuses the target workflow does
not list are remapped, and remapping never hands issues off.
- Issue writes keep the status inside the project's workflow. Entering a
step with a handler through a status change assigns the issue to that
handler and starts its run with the step's brief as the handoff note;
an explicit assignee in the same write wins. Creating an issue starts
at the workflow's starting step.
- Task claims carry the issue's project workflow, and the brief renders
a Project Workflow section so agents move along this project's
statuses instead of the built-in ones.
- Statuses a workflow lists cannot be archived.
- Adoption is gated by the project_workflows_v1 flag; existing
workflows keep working and projects can always switch back to Default.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): workflow settings, project picker and workflow-aware boards (MUL-7420)
- Settings → Workflows (behind project_workflows_v1): the Default
workflow plus named workflows, each shown as its step chain with
handlers and the projects using it; an editor to pick statuses from the
library, order them, mark the starting step, and set each step's
handoff, instructions and next/back statuses.
- Project Workflow property: switching dry-runs first and asks where
issues on unlisted statuses go; nothing else moves and nothing is
handed off.
- A project board's columns are its workflow's steps, and each column
head names who entering it hands the issue to.
- Status pickers offer only the issue's workflow steps and say who each
hands off to; boards refuse drops onto statuses the dragged card's
workflow does not list.
- New issues in a workflow project start on its starting step, with a
hint naming who they will be handed to.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(workflows): name built-in steps once in the brief; roomier picker hints (MUL-7420)
Built-in statuses have no stored name, so the handoff brief printed
`todo` todo. Print the key alone when the name adds nothing.
The status picker widens for workflow projects and shows the handoff as
an arrow and name, so step names no longer truncate to one letter.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): handoff previews and a single timeline entry per handoff (MUL-7420)
- GET /api/issues/{id}/workflow-handoff?status= says what moving an issue
would do: who it goes to, which runs of its current agent are still
active, and the brief the handler receives. Backs the confirmation.
- POST /api/issue-workflows/preview-brief renders a step's brief from an
unsaved workflow, for the editor's preview.
- Mapping plans list the statuses that stay, so the switch dialog can say
which issues are untouched.
- A handoff is recorded on the status_changed activity (workflow, handler,
whether a run started, the brief) instead of a separate assignee row.
- The brief now reads like the one shown in the design: the workflow, the
current step, its instructions, the statuses, and the commands to hand
off.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): rebuild the workflow UI to the reviewed design (MUL-7420)
The first cut used generic controls where the design had purpose-built
ones. This brings every surface up to the mockup:
- Settings → Workflows is one page: the workflows (step chain with
handler avatars, handoff count, projects using it; Default first) and,
below, the shared status library with each status's workflow usage.
- The workflow editor is a full page instead of a dialog: draggable step
cards summarising each handoff, and a panel for the selected step —
swap its status, pick the handler with a segmented control and a
searchable target, handoff instructions, next/back statuses, a preview
of the brief the handler receives, duplicate and delete.
- The project Workflow picker lists each workflow's statuses, handoffs and
projects, links to settings, and always confirms the switch with what
moves and what stays.
- Moving an issue into a handoff step (status picker, table, board drop)
asks first: who it goes from and to, a default-on option to stop the
previous agent's run, and the brief.
- Status pickers name the workflow and project, show each step's handler
with an avatar or that the assignee stays, and explain themselves.
- Boards: the handler chip in project column heads, hatched columns a
dragged card cannot enter with the reason, and a drop hint naming who
the card will go to.
- The timeline records a handoff under its status change, with the brief
the run received; the issue sidebar shows the workflow it follows.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(workflows): word a queued previous run as queued in the handoff confirm
The confirm dialog said the previous agent was "still running (for N
minutes)" even when its task had only been queued. Queued runs now read
as queued, and the checkbox cancels them instead of stopping them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): refuse a run's status change once the issue left its step
A run decides where the issue goes next from the step it was queued for.
When someone else moved the issue while the run worked, applying that
decision undid their move: in the live Grok run, a PM parked NW-1 as
blocked and the still-running agent moved it straight back to code
review.
Tasks now record the issue's status when they are queued
(agent_task_queue.workflow_step, written by the task INSERTs; automatic
retries keep their parent's step), and a run's own status changes move
it along. In a workflow project, an agent status change from a run whose
step no longer matches the issue is refused with 409
workflow_step_moved, naming where the issue is now and who moved it, so
the agent stops instead of retrying.
A handoff away from a squad now also stops the runs made on the squad's
behalf, and stopped runs record who stopped them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(workflows): keep every status entry point inside the issue's workflow
The issue context menu listed every catalog status and applied handoff
steps without the confirmation the status picker and board show, so a
right-click could pick a status the workflow refuses or start an agent
unasked. It now offers the workflow's steps and opens the handoff
confirmation through a shared modal.
One-click status actions no longer assume built-in statuses a workflow
may not list: "mark done" lands on the workflow's done step (hidden when
it has none), un-marking a duplicate returns to the workflow's start,
and "mark duplicate" is offered only when the workflow lists cancelled.
The batch toolbar offers a single project's workflow statuses when the
selection sits in one project.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(workflows): keep issue wakeups consistent with workflow steps
Three places where project workflows and issue wakeups met:
- A wakeup run is queued when it fires, so it took the issue's status at
that moment as its workflow step. A wakeup an agent registered then
bypassed the stale-step refusal: parked by a PM, the issue could be
moved on by the agent's "check CI, then hand to review" wakeup. It now
keeps the registering run's step; a member's wakeup still starts from
the issue's status.
- Retries carried handoff_note only for wakeup runs, so a retried
workflow handoff lost its brief. Rows with a workflow step keep it.
- Switching or editing a workflow remaps statuses in bulk without the
close rule, so an issue remapped into a done status kept its wakeups
and pending wakeup runs. The remap now ends them like any status write.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(wakeups): don't also wake the agent a write hands the issue to
A workflow handoff or an assignment already starts the receiving agent's
run. If that agent also subscribed an event wakeup to the issue's status
or assignee, the same write queued a second run for one fact, and
workflows made such writes frequent.
The write now names the agent it hands the issue to in the
transaction-local multica.handed_to_agent (the new agent assignee, a
squad's leader, or a step's handler even when it already owned the
issue), and wakeup capture skips that agent's subscriptions for the
write's issue.* events. Other agents' wakeups, and task, comment and
reaction events, are captured as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): tell agents not to watch workflow issues with wakeups
The handoff brief and the runtime's project workflow section now say
that each step's handler gets a run when the issue enters the step, so
wakeups on a workflow issue are for waiting on time or on something
outside it, not on its status, assignee or runs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(workflows): refresh project usage after project changes
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): MUL-7420 split columns from per-project handoffs
Workflows now only define a board's columns between a fixed head
(backlog, todo) and tail (blocked, done, cancelled). Handoffs move to
project.workflow_handoffs, so projects can share columns and still hand
off to different people. Default becomes a real, editable row that every
project without a named workflow follows. Adds project handoff, add-column
(project fork or shared) and project brief preview endpoints; same-agent
adjacent columns re-run the agent.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(core): MUL-7420 project handoffs and Default workflow client model
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(views): MUL-7420 column-header handoffs and project workflow menu
Board column headers set the project's handoff for that column; the
project toolbar gets a workflow menu (full flow, switch, templates) and an
add-column action. Views outside a project show Default's columns, with
other statuses reachable through a status filter. Adds the issue flow card,
create-project workflow choice, a columns-only workflow editor and a
lighter status picker.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(views): MUL-7420 wrap create-project workflow label in a menu group
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): MUL-7420 keep workflow structure in settings; no squad handoffs
Projects no longer add columns or create workflows: the add-column
endpoint is removed and creating a workflow is admin-only. Handoffs go
to one agent, a member or the project lead; squads route work themselves,
so a handoff to one is rejected, as is the issue-creator handler.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* refactor(core): MUL-7420 drop the project add-column client
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(views): MUL-7420 simpler handoff picker and workflow editor form
The handoff form is one searchable "Hand to" picker (no handoff, the
project lead, an agent or a member); the next status defaults to the next
column and next/back overrides sit under "More options". The workflow
editor gets labelled name and description fields and shows the fixed
statuses as one start and one end row. Templates move from the project
menu to Settings → Workflows → New, and the project board loses its
add-column action.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* refactor(server): MUL-7420 keep handoffs on the workflow, make backlog and blocked optional
Handoffs move back onto workflow steps: a workflow is the whole pipeline,
and a project only picks one. The project lead stays a relative handler, so
projects can still share a workflow. Handoff edits are admin-only, through
the workflow write or PUT /api/issue-workflows/{id}/handoffs/{status}; the
brief preview moves to the workflow too. Drops the unreleased
project.workflow_handoffs migration.
Todo, done and cancelled are required; backlog and blocked are optional and
keep their fixed places. Without blocked, the brief points agents at the
step's back status.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
Co-authored-by: Cursor <[email protected]>
* feat(views): MUL-7420 edit handoffs on the workflow, optional backlog and blocked
The workflow editor sets each column's handoff in its side panel, and
Backlog and Blocked switch on and off. The board's column popover and the
flow panel edit the workflow's handoff, say how many projects it reaches,
and are read-only for non-admins. Drops the per-project handoff client
code.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(wakeups): MUL-7420 keep platform rules on writes that hand off nobody
Migration 579 skips an issue.* event for the agent a write hands the issue
to. Its filter compared agent_id with the transaction's handed_to_agent
setting using IS DISTINCT FROM, which is false when both are NULL. Main's
platform rules (child_done) have no agent_id, so every issue.* event that
handed the issue to nobody was dropped for them: a parent leaving backlog
never woke its agent for sub-issues that closed meanwhile
(TestChildDoneHeldWhileParentInBacklog).
Rules without an agent are never the handoff target, so they always
capture. Extends TestHandoffDoesNotAlsoWakeTheHandler with a platform rule
on a handoff and on a plain status change; the latter fails on the old
filter.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(views): MUL-7420 three-way handoff choice; quieter move confirm
The handoff form picks one of three directions, as in the mockup: no
handoff, hand to an agent (starts a run), or hand to a member, project lead
included (assignee only). What the step does, where it goes next and the
back status only reach an agent's brief, so they show and save for agent
handoffs alone.
The board's move confirmation keeps the agent's brief collapsed. Create
project shows the workflow picker even when Default is the only workflow,
with a note on where to make more. The hidden-columns panel says a board's
columns come from its workflow, with a link to manage workflows for admins.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): confirm handoff on project move, per-project handoff copy, two-pane editor (MUL-7420)
- A single issue moved into a workflow project from issue detail confirms
the handoff of the column it lands on; the write opts in with
enter_workflow_step. Batch, API and CLI moves stay administrative.
- The handoff preview takes project_id to preview such a move.
- Saving a shared workflow's handoff from a board can keep the change to
that project: the server copies the workflow for it and switches the
project in one transaction (copy_for_project_id).
- The column popover says which workflow it edits and who else uses it.
- The workflow editor is a step list with a detail panel for the
selected step.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* style: gofmt workflow tests
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(views): MUL-7420 show next/back always; edit workflow name and description in place
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(views): MUL-7420 trim copy on the workflows settings page
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix: MUL-7420 keep pre-workflow behavior for workspaces that never adopt workflows
The run step guard now applies only to workflows that hand off, and the
standalone Statuses settings page keeps its original header.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(views): MUL-7420 use a solid placeholder tone on the workflow header
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(workflows): keep the runtime brief cache-stable; fix project board and batch status gaps (MUL-7420)
- Move the project workflow out of the runtime brief into the per-turn
prompt so CLAUDE.md stays byte-identical across steps and issues.
- An explicit status filter overrides the project workflow's columns; the
hidden-columns panel lists statuses outside the workflow.
- Batch status offers only statuses every selected workflow allows; a
partial batch rolls back, refetches and warns.
- setProjectWorkflow returns void instead of an unparsed body.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
Co-authored-by: Lambda <[email protected]>
Co-authored-by: Cursor <[email protected]>
|
||
|
|
231dedb552 |
MUL-8034: chore: clean historical comments across the codebase (#9274)
* chore: clean historical comments (MUL-8034) Co-authored-by: multica-agent <[email protected]> * docs: preserve pending Composio expiry contract Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
8352787263 |
MUL-7658: fix(daemon): recover undelivered batch claims after timeout (ZIC-260) (#8774)
* fix(daemon): recover undelivered batch claims after request timeout Co-authored-by: multica-agent <[email protected]> * test(daemon): avoid copying task service locks in recovery fixture Co-authored-by: multica-agent <[email protected]> * test(tasks): isolate delegated recovery assertions (MUL-7658) Co-authored-by: multica-agent <[email protected]> * test(daemon): use one clock for deferred claim hints (MUL-7658) Co-authored-by: multica-agent <[email protected]> * fix(daemon): keep claim recovery within client deadlines (MUL-7658) Co-authored-by: multica-agent <[email protected]> * fix(execenv): preserve racy Git index timestamps in snapshots (MUL-7658) Co-authored-by: multica-agent <[email protected]> * fix(daemon): deliver claim responses before recovery side effects (MUL-7658) Co-authored-by: multica-agent <[email protected]> * fix(daemon): isolate HTTP claim retries from recovery (MUL-7658) Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> Co-authored-by: Sol-Boy <[email protected]> |
||
|
|
6d5cdcbed2 |
MUL-7488: fix(execenv): exclude committed local HEAD changes from resume replay (#8549)
* fix(execenv): exclude clean HEAD advances from resume replay * fix(execenv): normalize carried WIP before replaying local edits Co-authored-by: multica-agent <[email protected]> * fix(execenv): resume after conflicting local draft commits Co-authored-by: multica-agent <[email protected]> * fix(execenv): limit snapshot conflict handling to affected paths Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Sol-Boy <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
55c40d5db9 |
MUL-7982: resume Codex across continued local worktrees (#9218)
* fix(daemon): resume Codex across continued local worktrees MUL-7982 Co-authored-by: multica-agent <[email protected]> * fix(daemon): serialize Codex session store writers (cherry picked from commit 7cbe282e1ef5fa8d44e1cfd04def683ad259a360) Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Yushen <[email protected]> Co-authored-by: multica-agent <[email protected]> Co-authored-by: mameikagou <[email protected]> |
||
|
|
92d0b6666d |
MUL-7812: fix(agent): deliver MCP servers to Pi tasks (#9063)
* fix(agent): deliver MCP servers to Pi tasks Pi skipped untrusted project config in JSON mode, and Multica never wrote .pi/mcp.json, so composed MCP servers never reached the runtime. Co-authored-by: Cursor <[email protected]> * fix(agent): hand Pi its MCP servers through a temporary extension Writing .pi/mcp.json and passing --approve trusted the whole project directory and failed when that file already existed. An explicit --extension loads the servers without project trust. Co-authored-by: Cursor <[email protected]> * docs: mark Pi as reading agent MCP configuration The provider comparison still said Pi skipped the MCP field. Pi now receives that configuration. Co-authored-by: Cursor <[email protected]> * fix(agent): harden Pi MCP registration Co-authored-by: multica-agent <[email protected]> * fix(agent): allow empty Pi MCP configuration Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Cursor <[email protected]> Co-authored-by: Yushen <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
756cdd915c |
MUL-7794 fix(wakeups): surface recoverable PR condition diagnostics [ZIC-302] (#9066)
* fix(wakeups): surface recoverable PR condition diagnostics Co-authored-by: multica-agent <[email protected]> * fix(wakeups): preserve condition diagnostic ownership Co-authored-by: multica-agent <[email protected]> * test(wakeups): isolate receipt cleanup limit Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> Co-authored-by: Yushen <[email protected]> |
||
|
|
fbd3470eac |
MUL-7875 fix(daemon): preserve Hermes sessions across worktrees (ZIC-301) (#9065)
* fix(daemon): resume Hermes sessions across task worktrees Co-authored-by: multica-agent <[email protected]> * fix(hermes): preserve unreadable history during migration Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
b4ca5b4a23 |
refactor(skills): retire legacy issue skill redirect (#9046)
Co-authored-by: Multica Eve <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
43b0571f99 |
MUL-7821: feat(cli): mark duplicates with issue status/update --duplicate-of (#8979)
* feat(cli): mark duplicates from issue status and issue update (MUL-7821) Add --duplicate-of to `multica issue status` and `multica issue update`. It resolves an issue key or UUID and sends duplicate_of_issue_id, which the server already accepts (MUL-7349). The CLI rejects a status other than cancelled, and description or attachment changes, before any request or upload. It fails when the response has no duplicate_of field, so a server older than v0.5.2 cannot silently drop the mark. Closes #8974 Co-authored-by: multica-agent <[email protected]> * docs: teach agents to mark duplicates with --duplicate-of (MUL-7821) Add the command to the agent runtime brief, the multica-platform issues reference, CLI_AND_DAEMON.md and the CLI docs tables, so agents record the mark instead of cancelling a duplicate and explaining it in a comment. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
5ed09342af |
MUL-7680: issue wakeup v2 — conditions, runaway protection, check-ins and visibility (#8807)
* feat(wakeups): add rule deadlines and expose the child-done system rule (MUL-7680)
Wakeups can now end: an absolute deadline (expires_at) or a relative wait
(expires_in_seconds) that restarts when the rule is re-enabled. When the
deadline passes first, the scheduler ends the rule; event rules with
on_timeout=wake run the target once with a wakeup.timeout fact. A timed-out
rule keeps disabled_at NULL so its timeout run stays claimable.
The implicit "wake the parent's assignee when a stage of sub-issues
finishes" behavior is now described by GET /api/issues/{id}/system-wakeups
and can be turned off or given a supplementary instruction per issue via
PUT /api/issues/{id}/system-wakeups/child_done. Rule reads fail open to the
previous behavior.
List responses add the creator (member or agent) and the new expiry fields.
The CLI gains --expires-in, --expires-at and --on-timeout.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(issues): let members create wakeups and show the child-done system rule (MUL-7680)
The issue sidebar's Wakeups section is always available on open issues and
gains a New wakeup popover. Members pick a condition (a time, a recurring
check with an end date, someone's reply, an agent's run ending, or raw
events), the agent to wake, the instruction, how often it fires, how long
to wait and what happens on timeout. It posts the same configuration agents
create with the CLI.
The parent's child-done wake appears as a System rule with its stage,
remaining sub-issues and target, a per-issue toggle and a supplementary
instruction. Rows show when a rule ends, timed-out rules read as such, and
details name who created the rule. Rule titles wrap to two lines instead of
truncating.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* docs(wakeups): document deadlines, member-created rules and the system rule (MUL-7680)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(wakeups): platform conditions, runaway protection and check-ins (MUL-7680)
Conditions: a wakeup can now wait for a fact the platform checks itself
(a status, assignee, label or property value; sub-issues or one stage
finishing; a linked PR's checks finishing or merging; another issue's
status). Related events only prompt an early evaluation; the rule wakes
its agent when the predicate becomes true, and a repeating rule fires
again only after it turned false or its facts changed. Finished PR checks
from before registration are ignored.
Runaway protection: repeating event rules stop after max_fires runs
(default 20), a rule pauses when its causal chain passes through it a
third time without a person in between, or when it starts 12 runs in an
hour. The reason is stored and shown.
Also: silent check-ins for every/cron checks (the run then posts no
fallback comment), wake now, delete, a per-rule run history, paused-rule
lists, timeline entries for created/triggered/timed-out/paused/check-in,
and source, paused scope, 7-day runs and child-done system rows in the
workspace list. The CLI gains --until-* conditions, --max-fires and the
trigger/delete/checkin/runs commands; the brief and wakeup prompt state
the check-in exception.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(issues): record child-done transitions in the status write (MUL-7680)
The child-done system rule ran after the status write committed and gave
up on any error, so a crash, deploy or transient failure lost the
parent's wake. A trigger now records each child's move into a closed
status in the writing transaction, for every writer. The request that
wrote it processes the rows right away and a scheduler sweep retries
anything left over; claims make the two paths process a transition
once.
The rule also follows a workspace default (settings key
system_wakeup_child_done) until an issue sets its own, the per-issue
update accepts partial bodies, and each trigger adds a timeline entry
that names its system comment.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(views): wakeup conditions, history, timeline and workspace list (MUL-7680)
Issue sidebar: members can create the platform-checked conditions (a field
reaching a value, sub-issues or a stage finishing, a linked PR's CI or merge,
another issue's status) and cap how often a repeating wait fires. Rule
details show the fire count, why the platform paused a rule, the latest runs
with silent-check notes, and offer wake now and delete.
The issue header says what the issue is waiting for ("Emacs 在等 Jiayuan 回复
+2") and opens the Wakeups section. Board and list cards use the same short
sentence, or flag a paused rule. Wakeup runs read as "由唤醒触发 · <condition>"
in the execution log, transcript and on the comments they post.
The timeline shows created, triggered, timed-out, paused and check-in entries
with the rule they came from; consecutive check-ins merge, and the child-done
entry stands in for its system comment, which it can reveal.
Automation → Issue wakeups adds a source column and filter, 7-day runs, a
paused scope with a banner, the child-done system rule on each waiting parent,
and "New wakeup" with an issue picker. Settings → Issue statuses sets the
system rule's workspace default. Copy in all five languages.
The server records the watched issue's identifier in other-issue conditions
and the rule's creator in timeline snapshots, and returns board summaries with
their condition.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* docs(wakeups): document conditions, runaway protection and check-ins (MUL-7680)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(wakeups): list system rules without a revision and localize statuses (MUL-7680)
System rule rows returned revision 0, which clients reject as a rule
revision; they now return null, and a list row with an invalid revision no
longer fails the whole page. Built-in statuses in conditions and the status
picker read in the viewer's language. Adds a browser test for a condition
created from the form, the header summary, the scheduler waking the agent
and the workspace list.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(views): say why a rule paused without repeating "paused" (MUL-7680)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(i18n): use the product's task terms in wakeup copy (MUL-7680)
ja, ko and fr wakeup strings said イシュー / 이슈 / ticket; the product term
for an issue in those languages is タスク / 태스크 / tâche (and sub-tasks
accordingly), per the conventions page. French strings are rewritten for
the feminine noun.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* test(views): pin the shortcut platform in the wakeup form test (MUL-7680)
The send shortcut is primary+Enter, which is Ctrl on Linux CI runners, so
pressing Cmd+Enter only submitted on macOS.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(views): simplify the workspace wakeup list
Put the filters on one row: scope as a segmented control on the left,
source/trigger/agent and a search-as-you-type box on the right. "New
wakeup" moves to the page header, which drops the separate Search button.
Rows are single-line: the identifier sits before the title, frequency and
timezone move into the trigger tooltip, and selection, prompt edit and
the last run's transcript show on row hover or focus. The issue column
takes the remaining width and truncates, so the table fits the page.
Also fix creating from the list: the issue picker closes itself right
after reporting the selection, and that close cancelled the flow before
the form could open.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* refactor(server): run the child_done rule as a system issue wakeup
The parent-assignee wake on closed sub-issue stages was its own code path:
a system comment, a mention-style run, an override table and endpoints.
It is now an ordinary issue_wakeup row with system_rule set, sharing the
condition, receipts, runaway protection, timeline entries and run model of
people's rules.
- One children_done evaluator: a stage wakes the assignee when it closes
while a later stage waits; the wrap-up waits for every sub-issue,
unstaged ones included. People's sub-issue conditions read the same set.
- issue_child_event records closing, reopening, re-parenting and restaging
for every writer; the request processes it right after commit and the
sweep retries. User sub-issue rules become immediate too.
- The target is resolved when it fires: an agent run, a squad leader run,
an inbox notification for a member, or only the timeline entry.
- A parked (backlog) parent catches up when it leaves backlog; a waiting
run of the same agent is joined instead of queuing a second one; the
hourly limit pauses a runaway rule. No system comment is posted.
- The run gets the issue's instruction, else the workspace's, else the
built-in one, plus each stage's counts and the next stage.
- Workspace defaults move to GET/PUT /api/system-wakeups (owners and
admins) and apply to every rule nobody customized; turning a rule on
records what already holds so old facts do not fire.
- Existing open parents get their rule from a one-time backfill.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(views): workspace wakeup settings and sub-issue notifications
- Settings → Wakeups holds the sub-issue rule's workspace default and
default instruction; the toggle leaves Issue statuses.
- The issue's system rule shows who it reaches (a member is notified),
a platform pause, and the instruction it will use.
- Timeline entries say whether the assignee was woken, notified or joined
a waiting run. The folding of the old system comment is gone.
- Inbox renders the new children_done notification on web, desktop and
mobile.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(server): stop wakeups from repeating their agent's runs
Runs are serialized per issue and agent, so a wakeup never ran beside
another run of its agent, but it queued behind one and repeated it. Each
rule now checks two things before it starts a run:
- Acknowledged: every input came from the agent itself. Its own comments
and issue changes never wake it, and a condition its own unfinished run
on the issue satisfied does not wake it when the platform or the agent
set the rule up. This fixes #8849: a coordinator closing its own stage is
not woken again while that run is going. A person's condition rule still
runs afterwards, since the running agent lacks its instruction.
- Merged: when a run of the agent is already waiting to start on the issue
(assignment, comment, another rule), the rule's instruction and facts
join that run (context.wakeup_joined, sent to the daemon as
wakeup_joined and rendered for every prompt kind) instead of queuing
another. Turning the rule off or replacing it withdraws what it joined.
Sub-issue changes now record the run that made them, and hints and
condition.met inputs carry those runs so a satisfied condition knows
whether the agent caused it. A rule that names the agent itself as the
actor keeps waking it on its own changes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(views): show merged and acknowledged wakeups, hint assignee comment rules
- Timeline entries say when a wakeup joined the agent's waiting run or was
the agent's own doing, for people's rules and the sub-issue rule.
- The create form tells a member that a reply or comment rule for the
issue's agent assignee joins the run a comment already starts.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(server): hand waiting wakeups to a run when it is claimed
A rule that fired while its agent had a run waiting used to write its
instruction into that run right away and consume its inputs. That let a
member's rule run under another member's identity, kept deleted rules'
instructions in the run, lost the inputs when the run was cancelled or
claimed by an older daemon, and skipped the fire cap and loop check.
Now the rule only waits for a run that runs as the same person its own
run would, keeping its inputs. When a daemon advertising
joined-wakeups-v1 claims that run, JoinWaitingWakeups rechecks each rule
(on, same agent and person, creator still allowed, not the agent's own
input, no loop), consumes its inputs, counts the fire toward max_fires,
adds its chain and records the merge. A re-claim drops entries of rules
turned off or changed since. Anything else leaves the rule its inputs to
start its own run.
A child_done rule created by the backfill or by processing a change now
treats sub-issues with unprocessed close events as still open, so the
backfill no longer swallows a close waiting for the sweep.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(server): consume joined wakeup inputs only once the run starts
A claim used to consume the inputs of the rules that joined the run. If
that claim did not go through, the run went back to the queue carrying
inputs the rules no longer had: turning the sub-issue rule off on the
issue or for the workspace (which only changes enabled) did not remove
them from the next claim, and cancelling the run lost a once rule that
had already ended.
A claim now reserves the inputs for the run (receipt task_id, still
unprocessed). The rule's next dispatch settles them: a run that started
consumes them as a merged firing (once ends, max_fires counts and can
pause, timeline entry); a run that ended without starting gives them
back; one that has not started keeps them. Turning a rule off or
changing it discards its pending inputs, reserved ones included, and a
later claim of the same run rechecks every entry and drops the ones
without reserved inputs or no longer allowed to reach the run.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
|
||
|
|
7dac88a6a8 |
MUL-7649: issue deliverables and dynamic blocks — sidebar section, overview grid, viewer info panel, html/mermaid block frame (#8779)
* feat(attachments): issue deliverables model and sequence block ids (MUL-7649) collectDeliverableFiles groups every comment upload into deliverables, merging same-name, same-type re-uploads into versions (newest version first). Description attachments are inputs and never enter the model. Sequence items now carry the id of the block they came from, so a viewer can say which comment (or the description) a file was posted in. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * feat(viewer): host details, overview and info panel slots (MUL-7649) PreviewSequenceProvider takes an optional describeItem (title accessory, info panel, locate action) and onOpenOverview. The viewer renders only the slots it is handed: a locate button, a grid button (G) and an info toggle (I) whose panel sits beside the stage. Letter keys stand down in fields, and any key pressed inside an open menu stays with the menu. A zoom canvas still at fit now follows a viewport resize (the info panel opening), while a zoom the reader chose is only clamped, as before. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * feat(issues): deliverables section, overview grid and info panel (MUL-7649) The sidebar's "Deliverables" section shows what the issue delivered as a whole: its pull requests and the files its comments uploaded, re-uploads merged into one entry marked v2. It lists the three newest images and four newest files, plus "View all N deliverables". The former Pull requests section becomes the code group, keeping its MUL-7429 actions (link a PR by URL, the auto-complete menu) beside the group label. While the workspace shows PRs, the section stays up even with nothing delivered, since that is where a PR gets linked by hand. The overview (from "view all" or G in the viewer) shows code first, then files grouped by posting comment, filterable by kind, each group one click from its comment. Its count always equals the sidebar's. In the viewer, deliverables get a version switcher, and every file gets an info panel (source excerpt, sibling files, uploader, time, size) and "Show in comments", which unfolds a resolved thread if needed and reuses the timeline's jump-and-flash highlight. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * feat(comments): standalone attachments as cards and an image row (MUL-7649) The files under a comment (and a chat message) no longer stack as one full-width row each. Other files become cards in a grid: type glyph, name, "TYPE · size", download / remove on hover, and the whole card opens the file. Several images sit in one row at a shared height; a lone image keeps its full size. HTML keeps its embedded preview (MUL-2330). On the issue page a re-uploaded file's card carries its version (v1, v2) from the deliverables model, through a small context. Standalone attachments are grouped images, then HTML, then files, and the preview sequence walks them in that same order, so paging follows the screen. The file-type glyph moves to editor/utils/file-icon so comment cards and the deliverables surfaces share one mapping. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * refactor(issues): pull requests keep their own section (MUL-7649) Since MUL-7429 the PR block is issue workflow — linking a PR by hand, the auto-complete switch, "won't auto-complete: missing Closes" — rather than output, so it goes back to main's own Pull requests section, unchanged, directly above Deliverables. Deliverables is now the delivered files only: hidden until a comment delivers one, and the sidebar number, "View all N" and the overview's "All N" all count files. The overview drops its code group. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(comments): lay several images out as justified rows (MUL-7649) Fixed-height tiles wrapped as soon as the column narrowed — three screenshots became two plus an orphan, with a ragged right edge. Rows are now justified from each image's real aspect ratio: every row shares one height and a full row runs edge to edge. A row takes as many images as fit above a minimum height, never grows past a maximum, and a last row that does not fill keeps the height of the row above it. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * feat(rich-content): dynamic block frame for html and mermaid fences (MUL-7649) A fenced ```html or ```mermaid block is part of the message body, so it now renders in one shared frame instead of two unrelated widgets: - Always-visible title bar: kind icon, the fence's title="..." (or the kind name), a kind chip, Preview | Source, fullscreen and copy. Previously the controls only appeared on hover and floated over the content. - The body takes its content's height (at least 120px) and collapses past 480px behind a fade and "Show all". An HTML block used to be a fixed 480px. - Loading keeps the height this block had earlier in the session; a script error or a Mermaid parse error is explained inside the frame with "View source" and "Copy error". - HTML gets the app's theme tokens (--foreground, --chart-1 ...); HTML that uses one also gets the app's color-scheme, so it follows dark mode. HTML that uses none keeps its own look. The sandboxed document reports its height and first uncaught error through a one-line postMessage bridge; the page checks the message source and clamps every value, and stops a document whose height follows the frame. rehype-raw drops the fence meta, so the title is read in the closed-fence parse. The Mermaid sandbox now also declares the app's color-scheme and font: in dark mode it was painted on an opaque white canvas, and its labels were drawn in a serif font that did not match the layout Mermaid measured. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * feat(attachments): HTML files show as cards, not inline previews (MUL-7649) Reverses the MUL-2330 pin. An uploaded HTML file is a deliverable to open, not part of the text: like every other non-image file it shows as a card (a row inline in the body, a grid card under it) that opens in the viewer, and its contents are no longer fetched to embed a 480px iframe. HTML meant to be read in place is written as a ```html block, which renders as a dynamic block. - Attachment drops its html branch; HtmlAttachmentPreview is deleted and HtmlPreviewBody keeps only the inline source the viewer uses. - orderStandaloneAttachments groups images, then files (HTML included), so AttachmentList and the viewer sequence still walk the same order. - The MUL-2330 regression pins now assert the card and that no text fetch happens. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * docs: charts go in html/mermaid blocks, files show as cards (MUL-7649) Agents that uploaded an HTML chart expecting it to render in the comment now get a file card instead. Tell them where each kind of content goes: - multica-platform skill (issues reference, routed from the table): a fenced ```html / ```mermaid block renders in place with a title bar and content height; an attached file, HTML included, is a card that opens the viewer. Covers title="...", the sandbox, the theme variables and their opt-in color scheme, and sizing to content rather than the viewport. - `multica issue comment add --attachment` help says the same in one line. - Comments docs (all five languages) describe both. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * feat(daemon): runtime brief says where charts and diagrams go (MUL-7649) The chart/file rule lived only in the multica-platform skill's issues reference, which an agent opens on demand, and the skill's description did not mention comment formatting, so an agent that just wanted a chart in its result had no reason to read it. Agents that uploaded report.html expecting an inline chart now get a card. - The brief's Output section carries one line, beside the file-delivery line, on the surfaces the web renders (issue comments and web/mobile chat): put charts and diagrams in the text as a fenced html or mermaid block, name it with title="...", and an attached file, HTML included, shows as a card. Theming and sizing stay in the reference. - Channel chats, autopilot run results and quick-create stdout do not render these blocks and do not get the line; the delivery tests pin both sides. - The skill description names "charts and files in comments" so the reference is picked for that task. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(issues): locate a file in a collapsed thread (MUL-7649) "Show in comments" for a file posted in a reply only unfolded resolved threads and gave up after 30 frames, so a thread the reader had collapsed stayed shut and the reply never mounted. A reply now goes through the quick-jump rail's jumpToReply, which already undoes every kind of folding and waits for the reply to land. A root comment gets the same treatment for its own thread (the reader's collapse, or a resolved bar), then the jump. Found in review by Emacs. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(viewer): the info panel's "Show in comments" closes the viewer (MUL-7649) The top bar's locate went through the viewer, which closes itself first; the same button in the info panel called the host's callback directly, so the page scrolled underneath a viewer that still covered it. Controls handed to describeItem gain close(), and the issue page builds one close-then-locate action for both entry points. Found in review by Emacs. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(issues): the deliverables overview is a real modal dialog (MUL-7649) The overview was a portaled layer with role="dialog" and nothing else: focus stayed on the opener, Tab walked into the page underneath, and closing left focus wherever it was. It now renders through the shared Dialog, restyled to cover the window, so the primitive moves focus in, keeps it inside, returns it to the opener, and handles Escape. `G` back to the viewer's file stays. The dialog is named by its title ("MUL-123 deliverables"). Found in review by Emacs. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
e909e9c89d |
MUL-7587 fix(daemon): show on-behalf-of identity in agent context (#8688)
* fix(daemon): expose original requester in agent context * fix(daemon): render run originator as on-behalf-of identity * docs(daemon): update stale Task Initiator comments to On Behalf Of Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Bohan-J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
df2e86cf3a |
fix(daemon): retry Windows task root record rename (#8786)
Refs #8785 |
||
|
|
62efe26aa0 |
MUL-7620 feat(telegram): send and receive photos, videos and files (#8727)
* feat(telegram): send and receive photos, videos and files Telegram was text-only in both directions: dispatch dropped every non-text update with an "unsupported" notice, the outbound subscriber only knew sendMessage/editMessageText, and the channel was never declared to DeclareChannelFileDelivery, so agents were told they cannot attach files. Inbound: photos, documents, video, video notes, animations, audio and voice notes become chat attachments through the engine's MediaResolver seam (getFile, file host download, object storage, intent-ledger row before the PUT, as Slack does). The caption is the message text and a placeholder marker leads the sender's own text; the engine swaps it for the attachment link once the bind commits. Quoted and recent group context still go in front of it. Stickers and other non-file kinds keep the unsupported notice. Outbound: files the agent bound to its reply are sent into the chat as their own messages once the text settles (sendPhoto for common images up to 10 MB, sendVideo/sendAudio for mp4 and common audio, sendDocument for the rest up to 50 MB). A photo Telegram refuses to process is resent as a document. Both halves hang off the same store != nil branch that declares file delivery, so the agent is promised the hop only where it exists. Compared with the WeCom reference this drops relay routing, the three-state delivery classification, per-reply metrics and the pending/admitted counters: Telegram outbound is stateless HTTP and the delivery lease already guarantees a single sender per turn. Co-Authored-By: Claude Fable 5.1 <[email protected]> * fix(telegram): address media review — deliver files once, admit before spawning, keep the notice without storage Review on #8727 raised five points; each lands here with a test. 1. A file-only reply could be delivered twice. closeTurn returned true whether this call ended the turn, a deeper attempt owned it, or it was settled already, and the empty-reply branch delivered files on all three. closeTurn now reports closedNow / closedAlready / closeHeld, and files go out only on closedNow. Regression tests: a replayed chat:done on another replica sends the files once; a completion an automatic retry has superseded sends nothing and the retry's answer still lands. 2. Admission sat behind the goroutine and the lookup. The attachment lookup now runs on the terminal worker, so a reply with nothing bound costs one indexed read and no goroutine; a delivery is spawned only for files known to exist, under a slot claimed first (non-blocking, four slots). Every slot busy sheds the reply with the notice instead of parking it. 3. Rebased on main over MUL-7585: recent group context, /new isolation and the privacy rule are kept; unaddressed group media is buffered and never a turn, as before; the media placeholder leads the sender's own text with quoted and recent context still in front. 4. Without object storage the member got no signal. The polling loop now knows whether the deployment stores media (ChannelDeps.AcceptsMedia, set from the same store != nil branch as the resolver) and keeps the old unsupported notice when it does not. A fetch that fails — over the 20 MB bot download limit, or a failed download — tells the sender so, instead of leaving the agent a placeholder nothing stands behind. 5. The failure notice claimed more than the code knew: a lost response may mean Telegram accepted the file. It now says delivery could not be confirmed and that the file stays attached in Multica, which holds either way. Co-Authored-By: Claude Fable 5.1 <[email protected]> * fix(telegram): offset the media placeholder past prepended context; report a failed attachment lookup Second review round on #8727. 1. With quoted or recent group context ahead of the sender's text, the MediaRef carried only the placeholder, so the engine replaced occurrence 0: a member who typed "[Image]" in the window received the sender's file and the sender's own marker stayed bare. inboundMedia now carries PlaceholderIndex — the enrichers only prepend, so it is the count of the marker ahead of the sender's segment, literals included (the DingTalk rule) — and the resolver sets it as InlineIndex. Regression tests cover the recent-context and the quoted-message paths. 2. A failed ListAttachmentsByChatMessage dropped the files silently while the text already on screen referred to them. The read has no side effects, so it is retried three times 250 ms apart; if it still fails the member gets a notice worded for what is known — whether the reply had files could not be checked — never the one that presumes a file existed. Co-Authored-By: Claude Fable 5.1 <[email protected]> --------- Co-authored-by: Claude Fable 5.1 <[email protected]> |
||
|
|
81f0fb418a |
test(execenv): stop the test repo template racing git auto maintenance (#8730)
The shared template repo is built once per test binary and handed to every test as an os.CopyFS copy, so nothing may write inside it after buildTestRepoTemplate returns. git commit breaks that: it forks a detached `git maintenance run --auto` and returns without waiting, leaving a background process that creates and removes lock files under .git/. CopyFS then lists .git/objects/maintenance.lock and fails to open it once maintenance has removed it, failing whichever test happened to call newTestRepo at that moment — most recently TestGitRootLockTimeoutDoesNotAdviseDeletingTheLock on an auth-only PR (#8706), which touches nothing in this package. Disable auto maintenance in the template's config. Copies inherit it, so the git commands tests run against their own copy cannot spawn a background process that races their assertions either. Co-authored-by: multica-agent <[email protected]> |
||
|
|
d2423aa35c |
MUL-7590: fix(execenv): support OpenClaw agents.entries per-agent workspaces (#8678)
* fix(execenv): pin per-agent workspaces under the OpenClaw agents.entries schema OpenClaw 2026.8 retired the agents.list config path and reports it as "Unknown config path: agents.list", moving the agents to a map keyed by agent id under agents.entries. The per-task wrapper only ever wrote agents.defaults.workspace, and an agents.entries.<id>.workspace entry outranks it verbatim: on a host where adding a second agent pinned the gateway agent to an absolute workspace, every task ran in that pinned directory instead of its prepared workdir, so the workdir skills/ never loaded and the agent edited the wrong tree. Read agents.entries when agents.list is gone, pin every entry workspace to the task workdir on write-back, and keep the registry fallback for the generations that have neither config path. The stdout-envelope branch now matches the path-aware wording, so a 2026.8 host that prints its envelope without exiting is read instead of failed closed, and the entries probe shares the agents.list deadline so the worst case stays three CLI deadlines. Co-authored-by: multica-agent <[email protected]> * test(execenv): cover the agents.entries probe, write-back and shared deadline Adds the regressions the review of the earlier attempt asked for: the current "Unknown config path" envelope with err == nil selects the entries probe and never the registry, both the plain and the managed-MCP flows pin every agents.entries.<id>.workspace to the task workdir, a valid-but-unset entries path falls back to the registry, and an envelope naming an unrelated path still fails closed. The worst-case budget test now drives the five-invocation chain and pins the config-schema pair to one shared deadline, so a fourth budget fails loudly. Co-authored-by: multica-agent <[email protected]> * fix(execenv): pin agents.entries workspaces without copying the entry Review feedback on the write-back: `config get` redacts the config before it returns the path it was asked for, so every sensitive per-agent field comes back as `__OPENCLAW_REDACTED__`. The wrapper is a sibling of the user's config in the $include merge and siblings win, so copying the resolved entry would write that sentinel over the user's real secret — memory search API key, web fetch headers, sandbox SSH credentials — for every task on the host, and nothing on OpenClaw's load path restores it. `agents.entries` is an object keyed by id and objects merge recursively, so `{"<id>": {"workspace": "<workdir>"}}` is enough to pin the workspace while every other field still arrives from the user's own config. The same reasoning drops the resolved payload from the row representation: an entries row now carries the id marker alone, so no redacted copy of the user's agent config is held in memory or written to the discovery cache file. Since only the source still decides the write-back, it is now carried explicitly (`openclawAgentsSource`: list / entries / registry) rather than as a boolean that effectively meant "not agents.list", and the source is recorded in the cache entry so a hit rebuilds the same wrapper. The cache shape version is bumped, so an entry written by an older daemon is a clean miss. Co-authored-by: multica-agent <[email protected]> * test(execenv): assert the entries write-back emits workspace alone Review feedback: the tests encoded the old contract (name / model carried into the wrapper verbatim). They now assert the wrapper carries exactly one key per entry — `workspace` — and that nothing else from the resolved payload reaches the file OpenClaw reads. The new regression drives the redaction shapes upstream's own redact-snapshot suite covers (memory search apiKey, web fetch headers, sandbox SSH credentials) through the entries probe of a real preparation, and fails if any of them ends up in the wrapper: with the copying write-back restored it reports the sentinel in `agents.entries.main`. The unit test for the write-back gate also covers a half-marked list, which a quiet per-row filter would have pinned anyway. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: tianshuai1 <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
5140b99d86 |
MUL-7577: remove no-start guidance from runtime and platform skill (#8680)
* fix(prompts): remove no-start guidance from runtime briefs Co-authored-by: multica-agent <[email protected]> * fix(skills): remove no-start guidance from platform instructions Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
8c9f865e3e |
MUL-7480: Revert comment steering feature (#8648)
* Revert "MUL-7480: Link delivered steers to final replies (#8623)" This reverts commit |
||
|
|
624648cc14 |
fix(daemon): offer a Git Bash variant of the Windows reply cookbook (MUL-7554) (#8634)
The Windows brief only knew PowerShell: the per-turn reply cookbook and the Comment Formatting cleanup line assumed every Windows agent pastes into PowerShell. The daemon knows the host OS, not the shell behind the agent's command tool. Claude Code runs Git Bash for its Bash tool, and a user who turns off its PowerShell tool (CLAUDE_CODE_USE_POWERSHELL_TOOL=0, GitHub #8627) gets a cookbook whose 'if ($LASTEXITCODE ...)' line is a bash syntax error that fires AFTER the post succeeded, so the agent sees a failure and may re-post. Render one variant per shell on Windows (PowerShell with the $LASTEXITCODE gate, Git Bash with '&&') and tell the agent to pick the one its command tool runs. The shell-execution test now selects the block by shell and adds a windows/git-bash case that runs the bash variant under sh. Co-authored-by: multica-agent <[email protected]> |
||
|
|
2b62b2b52e |
fix(cli): compare workdir paths in one namespace (MUL-4252, MUL-7193) (#8028)
* fix(cli): compare workdir paths in one namespace (MUL-4252)
The containment guard behind --content-file, --description-file and --attachment
canonicalizes the workdir but could not canonicalize a candidate path that does
not exist yet, and compared the two sides in different namespaces. That is wrong
in three ways, and only the first is merely a wrong answer.
## A path inside the workdir read as outside
filepath.EvalSymlinks fails outright when any component is missing. The fallback
resolved one level up (filepath.Dir) and otherwise called filepath.Clean, which
does not follow symlinks at all.
os.Getwd() returns the LOGICAL working directory whenever $PWD names the current
directory, and everything that writes $PWD carries the path as typed: a shell's
`cd`, and the PWD=<workdir> this repo exports to agent processes
(pkg/agent/opencode.go, deveco.go, codearts.go). So on a host whose workdir path
crosses a symlink, the base was /private/var/... while the candidate stayed
/var/..., and `--content-file reports/summary.md` was rejected as a guardrail
violation when the real problem was that `reports/` did not exist. The only
remedy the error offers is --allow-external-file, so the caller's next move is to
disable the guard and collect an unrelated not-found error on the retry.
## A path outside the workdir read as inside
With an already-canonical workdir the same fallback inverts. `escape/sub/x.md`,
where `escape` is a symlink pointing out of the workdir, read as INSIDE it: the
unresolvable tail fell back to a lexical clean that never sees the symlink.
## ".." was applied to the string, not to the followed path
filepath.Join and filepath.Clean collapse "escape/.." before any symlink is
followed, while the kernel follows `escape` out of the workdir first and then
goes up. So the guard judged a different path than os.ReadFile then opened:
guard says within=true ; os.ReadFile got "another run's file"
That is the MUL-4252 failure this guard exists to prevent — reading a file left
behind by another run — reachable with a file that exists and no race.
## A path on another volume read as an internal failure
Windows only: filepath.Rel cannot relate two absolute paths on different volumes
and returns an error, which surfaced as `Rel: can't make Z:\report.md relative to
C:\Users\...\workdir`. Two absolute paths Rel cannot relate are on different
volumes, which is as far outside the workdir as a path gets, so the guard now
answers that instead.
## The fix
util.ResolveSymlinksBestEffort canonicalizes as far as the filesystem allows: it
resolves the path as given first — the only step that can see ".." the way the
kernel does — then walks up to the deepest existing ancestor, resolves that, and
re-attaches the missing tail lexically. A mismatch inside the missing tail is
inert: a path whose intermediate directories do not exist cannot be opened at
all. Both sides of the comparison go through it.
internal/daemon/execenv already had this algorithm as resolvePathBestEffort,
unexported and documented as Python's Path.resolve(strict=False). It now
delegates, so there is one owner, and the ".." handling makes it more faithful to
that documented contract rather than less.
## Coverage
TestFileWithinWorkingDir runs its table under two working-directory shapes, and
both are load-bearing: under a symlinked workdir an unresolved candidate always
looks outside, so the symlink-escape rows can only fail under a canonical
workdir. Reverting one part of the mechanism at a time:
candidate unresolved (Clean) 5 tests / 15 subtests fail
one-level fallback 3 tests / 8 subtests fail
base side left unresolved 5 tests / 9 subtests fail
clean before resolve ("..") 2 tests / 5 subtests fail
helper walks only one level 4 tests / 9 subtests fail
The lint side is covered too: classifyLocalPathTarget uses the same predicate,
and there the same false negative flips direction — a workdir-local path with a
missing intermediate directory matched neither of its signals, so the link was
published instead of blocked.
cmd/multica has no Windows job, so the guard's Windows-only behaviour ran
nowhere: a candidate on another volume, and a directory junction, which
filepath.EvalSymlinks resolves to its own name rather than descending through it
(internal/daemon/config_windows_test.go asserts that refusal directly). A
junction inside the workdir pointing out of it therefore still reads as inside,
before this change and after it. Failing closed on a component that exists but
cannot be canonicalized would reject every path under a pnpm-style node_modules
junction, and on a host whose workdir is itself reached through a junction it
would reject every --content-file and --attachment on that host — a larger break
than the one it prevents. The windows-tagged test pins the gap with the
observations it rests on and fails with instructions if the platform starts
resolving junctions. Its CI step is scoped and runs with -v so a silent skip is
visible rather than passing as "ok".
* fix(cli): lead with the missing file when a rejected path does not exist (MUL-4252)
The workdir containment guard answers one question and reports it as if it
answered two. A path outside the workdir that does not exist got the same error as
a stale file sitting in /tmp:
--content-file path "/tmp/desc.md" resolves outside the current working
directory; write agent temp files inside the task workdir ... Pass
--allow-external-file to override.
The only remedy that error offers is the flag, so the caller's reasonable next
move is to disable the guard and retry — earning a second, unrelated "no such
file or directory". The diagnosis points away from the actual problem, and the
lesson it teaches an agent is to turn the guard off.
MUL-4252 was about reading a file that exists and belongs to another run. When
the path does not exist there is no such file to protect anyone from, so the
missing file leads, with the containment problem kept as the second clause since
the caller still needs to know the flag would not help.
pathExists treats any stat error other than "not exists" as "exists", so a
permission wall or a broken mount keeps the guard's own wording and stays
fail-closed rather than being reported as a missing file.
Both halves are pinned by mutation: dropping the branch fails
TestExternalFileErrorLeadsWithMissingFile on the missing-file assertion, and
making pathExists report everything as missing fails the same test on the
assertion that an existing external file must still be told about
--allow-external-file.
* fix(cli): preserve Windows path kinds and resolve junctions in the containment guard (MUL-4252)
Addresses the two blockers in the review on 3ec1781e:
1. absNoClean prefixed the working directory onto every non-IsAbs path,
but on Windows a root-relative path (\tmp\desc.md) resolves against
the volume ROOT and a drive-relative path (C:tmp\desc.md) against the
current directory on that drive. The guard inspected
C:\task\workdir\tmp\desc.md while os.ReadFile opened C:\tmp\desc.md,
so an in-workdir shadow could pass an outside read. absNoClean now
splits per platform (path_other.go / path_windows.go) and preserves
the kinds; a drive-relative path on an unobservable drive is handed
back uncanonicalized and fails closed through the Rel branch.
2. The junction case in workdir_guard_windows_test.go pinned a known
contract violation (EvalSymlinks refuses to descend through junctions
under the winsymlink semantics this module ships with). Resolution is
now component-wise on Windows (evalPath) and follows junctions via
os.Readlink — which answers under both winsymlink settings — with a
bounded follow budget; reparse points Readlink cannot speak for
(OneDrive placeholders) stay opaque. A junction out of the workdir is
rejected, one into it (the pnpm shape) is still admitted, and the
known-gap test is replaced by cases asserting both directions plus a
chain and a dead target.
The shared walk in ResolveSymlinksBestEffort is now component-wise on
both platforms: it applies ".." to the resolved prefix even when the
tail is missing (previously the second phase started from filepath.Abs,
which cleans first), and it never depends on what EvalSymlinks does at
a filesystem root. Unix behaviour is unchanged for every shape the
existing tests cover; the new dot-dot-in-the-walk case pins the
difference.
* fix(cli): classify link targets with the full Windows path grammar and resolve unknown reparse points by handle (MUL-4252)
Addresses the two blockers in the review on d3d370377:
1. Link targets were spliced onto the link's parent unless
filepath.IsAbs said otherwise, but the kernel applies the full path
grammar to targets: a root-relative target (\outside) resolves from
the root of the volume the link sits on, and a volume-GUID target
(\\?\Volume{GUID}\...) is absolute in the device namespace. The
resolver now classifies targets through classifyTarget, which reuses
the same grammar as absNoClean; device-namespace targets without a
Win32 spelling are kept verbatim so the containment comparison fails
closed across the namespace instead of splicing a mangled relative
path (the prefix rules mirror internal/daemon's #6883 handling).
2. An os.Readlink failure on a ModeIrregular reparse point no longer
means "does not redirect". Readlink decodes only the symlink and
mount-point tags; other name-surrogates (e.g. container-isolation
links) redirect through a filter while the lexical location stays
behind. Such components are now resolved through
GetFinalPathNameByHandle (zero desired access, so no hydration, and
FILE_FLAG_BACKUP_SEMANTICS for directories); if the handle names the
same path the component is opaque, otherwise the walk continues from
the final path. When even the handle cannot name the location the
resolver returns the new util.ErrUnresolvablePath instead of
guessing: fileWithinWorkingDir treats it as outside (fail closed)
and execenv's delegate falls back to the input, which its
containment comparison rejects.
ResolveSymlinksBestEffort now returns an error so the fail-closed case
is explicit rather than a look-alike path. Negative tests cover a
root-relative symlink target with an in-workdir shadow (the kernel's
target is verified by content before asserting rejection) and a
volume-GUID junction target with the prefix-stripped shadow.
* fix(cli): classify drive-absolute link targets before the volume check (MUL-4252)
classifyTarget asked VolumeName before IsAbs, but VolumeName is
non-empty for a drive-absolute path (C:\dir) too. A junction's absolute
target was therefore misread as drive-relative and resolved against the
working directory — a nonexistent spelling — which errored the
resolution and left the escape to be judged lexically inside the
workdir. The IsAbs branch is asked first, and the load-bearing ordering
is pinned by TestClassifyTarget, a fixture-free table over the target
grammar that would have caught this without a filesystem.
* ci: run the util link-target classification table on Windows
TestClassifyTarget pins the branch ordering classifyTarget's link-target
grammar depends on, but windows-tagged tests in internal/util had no
execution point: the windows-execenv job only ran the guard tests in
cmd/multica. Scoped to the table by name, matching the job's pattern.
* test(cli): correct classifyTarget expectations for root and base shapes
The walk base is the volume root and the working directory's components
live in the segments for a drive-relative target, and the plain-relative
branch returns the link's directory verbatim without a trailing
separator. The expectations said otherwise; the code was right.
---------
Co-authored-by: weiweiwei <[email protected]>
|
||
|
|
c375f83c2e |
MUL-7418: add issue event and time wakeups (#8467)
* feat: add issue event and time wakeups (MUL-7418) Co-authored-by: multica-agent <[email protected]> * feat(wakeups): capture issue collaboration event lifecycle Co-authored-by: multica-agent <[email protected]> * fix(wakeups): preserve reply source when reopening threads Co-authored-by: multica-agent <[email protected]> * feat(wakeups): summarize sidebar and issue activity cues Co-authored-by: multica-agent <[email protected]> * fix(wakeups): restore disabled configurations with a two-way toggle Co-authored-by: multica-agent <[email protected]> * feat(wakeups): manage workspace wakeups in Autopilot Co-authored-by: multica-agent <[email protected]> * chore(wakeups): reserve migrations after current main Co-authored-by: multica-agent <[email protected]> * test(wakeups): align inventory fixtures with shared contracts Co-authored-by: multica-agent <[email protected]> * fix(wakeups): distinguish trigger conditions and execution results Co-authored-by: multica-agent <[email protected]> * fix(wakeups): suppress registration feedback and drain bounded evidence Co-authored-by: multica-agent <[email protected]> * fix(wakeups): align sidebar actions with titles Co-authored-by: multica-agent <[email protected]> * fix(wakeups): bound database work and unify shared issue visibility Co-authored-by: multica-agent <[email protected]> * feat(wakeups): filter mutation events by member or agent actor Co-authored-by: multica-agent <[email protected]> * refactor(wakeups): merge structured evidence and narrow receipt locks Co-authored-by: multica-agent <[email protected]> * feat(wakeups): let people edit wakeup instructions Co-authored-by: multica-agent <[email protected]> * fix(wakeups): align inventory controls and compact prompt editing Co-authored-by: multica-agent <[email protected]> * fix(mobile): handle deferred agent run status Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Lambda <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
8ce795b007 |
MUL-7480: Steer active agent turns from comments (#8605)
* feat: steer active agent turns from comments (MUL-7480) Co-authored-by: multica-agent <[email protected]> * fix: make comment steering hint driven Co-authored-by: multica-agent <[email protected]> * fix: harden active comment steering races Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Forge-Boy <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
9018df3efb |
feat(projects): set a repository's checkout ref from the UI (MUL-7504) (#8592)
* feat(projects): set a repository's checkout ref from the UI (MUL-7504) A project that tracks one delivery line could already pin the branch its tasks start from — `github_repo.resource_ref.ref` has been stored, sent to the daemon and honored at checkout since #4467. The UI only ever displayed it. Anyone working through the web, desktop or mobile app had to drop to `multica project resource update --ref` to set something the interface already showed as a property of the resource, and until they did, every task silently started from the remote default branch. This adds the missing input, on the three paths a repository is configured: - Create-project modal: a "Branch, tag, or commit" field per selected repo, shown once a URL is entered so the common default-branch case still reads as one field. - Resource panel: the same field in the attach form, plus a dialog on each attached repo — the row previously offered only delete. - Mobile: the field in the attach sheet, and the ref rendered in the list, which never showed it at all. Three details the shape of the existing code forced: - The ref renders on its own line rather than appended to the repo label. A custom name took that slot, so naming a repo hid the one signal that its tasks do not start from the default branch — and mobile is the only client that collects a name. - Edits spread the stored ref before overwriting. The server replaces resource_ref wholesale, so a payload carrying only `ref` is rejected for a missing URL. - Pasting a `.../tree/<branch>` URL now splits into the two fields. Mobile's URL check accepted that whole string and stored it as the clone URL, so the gap was already producing targets that cannot be cloned. Server-side, `ref` was stored with no validation beyond a trim, so a typo survived to checkout and surfaced as a daemon 500 naming its local repo cache, minutes later and to whoever ran the task rather than whoever typed it. validateGitRef applies the shape rules from `git check-ref-format` plus a length cap. Existence on the remote is deliberately still not checked: the server cannot see the repository, and an offline daemon or a private repo must not block saving configuration. Free text rather than a branch picker, by design. Nothing in the product can list a repository's branches today, and a picker could not express a tag or a commit SHA anyway. A "list remote branches" endpoint remains the follow-up #8572 proposes; it should stay an input aid, never the only way. Co-authored-by: multica-agent <[email protected]> * feat(projects): make the pinned branch the delivery target too (MUL-7504) Pinning a repository's starting point only solved half the problem. Nothing in Multica creates pull requests — `gh pr create` is the agent's own call, and without `--base` it targets the repository's default branch. So a project pinned to `release/2026-09` had its agents start in the right place and then open pull requests into `main`, carrying every commit the release line had that `main` lacked. Product review asked for the two halves to move together, so the brief now states both. The Repositories section names each repo's starting point and, when anything is pinned, says to deliver back to it. The Project Context bullet stops implying `--ref` is how you reach the configured revision — the daemon already applied it, and `--ref` is the override. The delivery rule is stated conditionally, because a pin is not necessarily a branch. The field accepts anything git resolves, and neither the server nor the daemon can tell a branch from a tag or a commit without asking the remote — which the product deliberately does not do. A tag has nothing to merge back into, so the agent is told to confirm rather than guess a base. That same ambiguity shapes how the UI narrows to branches. The field now says "Starting branch" and explains that tasks both start and deliver there, but the promise cannot be enforced: `v1.2.3` is a legal branch name and `main` is a legal tag. Only a full-length object id is unambiguous, and only that is declined, pointing at `multica repo checkout --ref` where a one-off revision belongs. The stored grammar is unchanged, so the CLI and API still accept tags and commits — the daemon has always resolved all three, and this is a UI-level product choice, not a data restriction. Two fixes from the same review: - An unpinned repository now reads "Default branch" instead of showing nothing. Clearing a branch used to make the line vanish, which looks identical to the setting never having existed — there was no way to confirm from the panel that a repo was deliberately on its default, or that the row had a branch setting at all. - The edit dialog says the change applies to newly started work. The ref is read when a task is claimed, so work already underway keeps the branch it started on, and the copy now says so rather than leaving it to be discovered. Co-authored-by: multica-agent <[email protected]> * fix(projects): close three gaps in the starting-branch flow (MUL-7504) All three found in review of the two commits before this one. **A resumed task could be told to deliver somewhere new.** The starting point in the brief is the project's CURRENT setting, read when the run was claimed. A task that started from `release/a`, left uncommitted work and resumed after the project moved to `release/b` kept its old checkout — the work is still branched off `release/a` — while the brief now named `release/b` and told it to deliver there. Following that would push the old line's work into the new one, and would break what the edit dialog promises: work already underway keeps the branch it started on. The brief cannot settle this alone, because whether a checkout gets reused is only known once `repo checkout` runs. It defers to the checkout instead, which already reports `Kept` and names the branch it is on. **Create Project accepted a branch it had already rejected.** The submit gate only checked the add-a-repo field, so a branch edited on an already-selected repo reached the payload with nothing but an inline error to show for it — and the server accepts commit ids by design, so nothing downstream caught it. Both the button and handleSubmit now check every selected repo; the button alone was never enough, since TitleEditor's onSubmit calls handleSubmit directly. **A second pasted browse URL was stored whole.** Splitting `/tree/<branch>` was gated on the branch field being empty, so pasting another repository's browse URL after one was already filled in saved the entire `/tree/...` string as the clone URL — a target that cannot be cloned. Normalising the URL is now unconditional in all three entry points. Whether to overwrite the BRANCH is the separate question, and the pasted pair wins: the branch field only appears once a URL is present, so a value sitting in it came from the previous URL rather than from something typed ahead of time. One defect the review surfaced indirectly: GithubRefField took an optional `id` and the per-repo instance in the create-project modal passed none, which detached the label from its input — leaving the field unnamed for a screen reader, and unreachable by getByLabelText, which is why the gap had no test. The id is now generated when omitted. Co-authored-by: multica-agent <[email protected]> * fix(execenv): stop naming the checkout as the resumed delivery target (MUL-7504) The resume warning added in the previous commit told the agent to deliver kept work "to the branch it actually started from, which the checkout names". The second clause is wrong. A kept checkout reports the branch the worktree is ON — resolved by `git symbolic-ref`, so the task's own `agent/<name>/<task>` branch. That is the HEAD of a pull request, never its base, and nothing in WorktreeResult carries the ref it was cut from. An agent following that sentence would pass its own branch as `--base` and open a pull request whose base equals its head. The warning now says what the reported branch actually is, and points at the sources that do settle the question: the base of the work's existing pull request, or the target the task states, and ask when neither does. It also moves out of the `if pinned` branch. A project cleared back to its default branch renders no starting point at all, yet a task resumed after that change still holds a checkout cut from the old one — gating the warning on a pin dropped it exactly where the mismatch is invisible. The one sentence that only makes sense alongside a listed starting point ("do not retarget it to a starting point listed above") stays conditional, so the cleared variant does not point at a line that is not there. Regressions cover both resume shapes — pinned A to B, and A cleared to the default branch — plus the specific wrong phrasing, so it cannot come back. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
6b69b3710c |
Revert "MUL-7517: clarify squad leader identity framing (#8597)" (#8600)
This reverts commit
|
||
|
|
a51fd83072 |
MUL-7517: clarify squad leader identity framing (#8597)
* fix(squads): clarify leader identity framing (MUL-7517) Co-authored-by: multica-agent <[email protected]> * fix(squads): address briefing review feedback (MUL-7517) Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Forge-Boy <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
d7009df8cc |
Revert "MUL-7300 fix(issues): preserve Quick Create original input (#8519)" (#8546)
This reverts commit
|
||
|
|
a6472044c3 |
MUL-7300 fix(issues): preserve Quick Create original input (#8519)
* fix(issues): preserve quick-create original input Co-authored-by: multica-agent <[email protected]> * fix(issues): validate quick-create origins Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Sol-Boy <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
29987fdda5 |
MUL-7461 test(execenv): clear MULTICA_TASK_CONFIG_ROOT so the suite is environment-independent (#8512)
Four tests in this package failed for every agent running the suite from
inside a Multica task, and passed in CI:
TestHermesMemoryStorePathLayout
TestPruneHermesMemoryStores
TestHermesSessionStorePathLayout
TestPruneHermesSessionStores
They isolate themselves by pointing HOME at a t.TempDir() and then assert a
path under $HOME/.multica. But HermesMemoryStorePath / HermesSessionStorePath
resolve through cli.ProfileDir, and multicaConfigRoot consults
MULTICA_TASK_CONFIG_ROOT before it ever reaches HOME — returning that root
directly, without the .multica segment. The daemon sets that variable for
every task it runs (taskMulticaEnvironment), so overriding HOME had no effect
and the tests were asserting against a branch they never took. CI never sets
the variable, so it stayed green and the trap only ever hit agents.
Clearing the variable once in TestMain makes the package resolve profile dirs
the same way in both environments. Preferred over a per-test
t.Setenv(cli.TaskConfigRootEnv, "") for three reasons:
- It also covers TestPruneHermesMemoryStoresDisabled and
TestPruneHermesSessionStoresDisabled, which passed for the wrong reason:
their stores were never created under the root the pruner scans, and the
assertions held only because retention <= 0 disables the pruner outright.
- t.Setenv cannot be called from a parallel test, so the per-test fix would
force future tests that resolve a profile dir to choose between running in
parallel and running in the right environment.
- New tests in the package are correct by default rather than by remembering
to copy a line.
A test that wants the task-local branch still sets the variable itself.
Verified on the same commit, whole package:
go test ./internal/daemon/execenv/ -count=1 ok
env -u MULTICA_TASK_CONFIG_ROOT go test ./internal/daemon/execenv/ ok
go test ./internal/daemon/execenv/ -count=1 -race ok
go vet ./internal/daemon/execenv/ ok
Co-authored-by: multica-agent <[email protected]>
|
||
|
|
d54300abaa |
refactor(claim): compare only title and description (MUL-7344) (#8488)
Status leaves the compared set, so the snapshot is now just the two body fields. The comparison answers one question — must the agent re-read the issue BODY? — and status never helped answer it: its current value ships on every claim, so the agent learns it without a read. Worse, comparing it worked against the feature. The workflow has the agent set in_progress when it starts and in_review when it delivers, so the agent's OWN bookkeeping came back the next turn as "changed: status" and demanded a body re-read that nothing in the body justified. Replaying this issue's 21 follow-ups, that alone cut "unchanged" from 17 to 4. The earlier rationale for keeping it — a free signal that somebody intervened — was wrong on both counts: it is not free, and the agent can see an intervention by comparing the shipped status against the value it last set itself. issueSnapshotVersion stays 1, and this time rows in the old shape DO exist. That is still correct: removing a field is backward compatible, because every field the new code reads is present in an old row and still means the same thing, and the stale "status" key is ignored. The comment now states the actual bump rule rather than "the set changed" — bump when an old row can no longer answer the current question, which means adding a field or changing how a value is derived, not removing one. A test pins the legacy shape comparing correctly in both the unchanged and the changed direction, so that claim is checked rather than asserted. No migration: the column is JSONB and the schema is unchanged. TestClaimTaskByRuntime_AssigneeAndPriorityAreOutOfScope becomes NonBodyFieldsAreOutOfScope and now moves status alongside assignee and priority, asserting the current status still ships with its NEW value — without that, the fixture's UPDATE could silently not apply and the empty changed-list would prove nothing. Co-authored-by: multica-agent <[email protected]> |
||
|
|
f39debf8d1 |
MUL-7344: skip the issue read and the comment scan when the server already answered them (#8377)
* feat(daemon): read the comment delta with one --since, not a roots scan (MUL-7344) A resumed follow-up turn whose claim carried a server-computed issue-wide comment delta was still told to run the workflow's two-step scan: a `--roots-only --summary` pass to discover which threads moved, then a `--thread ... --since ...` expansion per thread. That rediscovers, in two or more calls, an answer the server already has. `--since <anchor>` without `--thread` IS that answer. ListCommentsSinceForIssue returns every comment on the issue created after the anchor, in every thread and at every nesting level, with no folding, on the same `created_at > since` predicate CountNewCommentsSince counted with. It is a strict superset of what the roots scan could surface: roots-only `last_activity_at` is MAX(created_at) over a thread, so any comment able to move a thread's activity is already in the `--since` result. So BuildNewCommentsHint now hands over exactly one wide read and says it answers step 2's scan — the same rule BuildResumedCommentsHint already applies to a server-computed EMPTY delta. The optional second command stays a THREAD read on `--tail 30` for the reply itself; it is never the wide read, and it never combines `--thread` with `--since`, which drops the thread root. Brief step 2 and the multica-platform skill's bounded-reads rule both gain the matching static sentence, so an agent is not left choosing between the hint and two surfaces that contradict it. Cold start, dropped resume and unknown-delta paths are untouched, byte for byte: only an affirmative server report may stand in for the scan. Co-authored-by: multica-agent <[email protected]> * feat(claim): compare the issue against the last run so a resumed turn can skip the read (MUL-7344) A follow-up turn that resumes its provider session already holds the issue it read minutes ago, and was still told, unconditionally, to run `multica issue get` before doing anything. On an issue that did not move, that read returns bytes the session already has. The claim now answers the question instead. Each claim records the comparable state of the issue it built its payload from — status, assignee, priority, and sha256 of title and description — in a new `agent_task_queue.issue_snapshot` JSONB column, and compares against the snapshot the SAME agent's previous run on the SAME issue recorded. The response carries `issue_state_delta_known`, `issue_changed_fields`, and the current `issue_status` / assignee. Why a per-claim snapshot and not a `content_revision` on `issue`: a revision column has to be bumped by every writer that touches one of those five fields, and the first one missed reports "unchanged" forever with no symptom. A snapshot taken at claim time has exactly one writer. It also rides the anchor the comment delta already uses (agent_id, issue_id, started_at DESC), so both deltas on a claim mean "since your last run" in the same sense. The daemon renders one of three things, gated on a REAL resume: - unchanged → reported as workflow step 1's answer; the read survives only as "if resumed memory is not enough". - changed → the changed field NAMES plus the read, so the agent knows what to look for instead of diffing blind. - otherwise → the pre-existing instruction, byte for byte. `issue_state_delta_known` carries the same contract as `new_comments_delta_known` (MUL-6984), and for the same reason: a cold start, a missing prior snapshot, a read error, a foreign shape version and an old server all produce an empty changed-field list, identical to a genuine "nothing changed". Only the flag separates them, and only the affirmative one may stand in for the read. The compared set is named in full in the prompt rather than summarised as "the issue": labels, parent, due date, stage, project and metadata are NOT compared, and an agent told "unchanged" has to be able to see that. The snapshot write is NOT gated on the task being comment-backed — an assignment run that recorded nothing would leave the next run with no baseline. It shares the receipt's CAS, so a stale handler cannot write over a newer reclaim or after execution starts, and a snapshot that fails to encode is simply not recorded: the next run reports "not compared" and reads the issue. Co-authored-by: multica-agent <[email protected]> * fix(claim): renumber the migration past main and fold in review nits (MUL-7344) CI caught a real collision: main landed 471_comment_deleted_at through 474 while this branch was open, so 471_agent_task_issue_snapshot shared a numeric prefix and TestMigrationNumericPrefixesAreUnique failed. Renumbered to 475 after merging main; no other migration references it. Review nits from the PR review, all non-blocking, all cheap to take now: 1. Brief step 2's closing sentence told the agent that a resumed run's scan surfaces moved threads via `last_activity_at`. That is now true only on the path where the delta is UNKNOWN — when the server hands over a `--since` delta there is no scan to read it from. Rewritten to attach the `last_activity_at` guidance to the scan itself, wherever the scan happens. Done in this PR rather than deferred because this PR already moves brief bytes, so it costs no extra prompt-cache invalidation. 2. The two per-claim anchor reads are now one. GetLastTaskStartedAtForIssueAndAgent and GetLastTaskIssueSnapshotForIssueAndAgent selected the same row with the same WHERE and ORDER, differing only in projection; they are replaced by GetLastRunAnchorForIssueAndAgent returning started_at and issue_snapshot together. A comment-triggered claim is back to ONE anchor read, as before this branch. The saved round trip is the smaller half. The real gain is that "since your last run" is now a single resolved fact on a claim instead of two independently-resolved ones that could disagree. The shared read also shares a failure mode, and both consumers already degrade the same safe way: comment scan required, issue comparison unknown. 3. The hint's `%d new comment(s)` excludes the injected trigger and the agent's own comments; the `--since` read it hands over has no such filter, so the row count is legitimately higher. Narrowing the read to match would mean a second count-shaped query for no gain, so the text now states the mismatch and its direction outright instead of leaving the agent to reconcile two numbers. New test pins the merge: one claim, one prior run, both deltas resolved from that single anchor row. Co-authored-by: multica-agent <[email protected]> * fix(migrations): renumber the issue-snapshot migration to 478 (MUL-7344) main landed 475_issue_status_key_not_reserved through 477 while the previous renumber was in flight, so 475_agent_task_issue_snapshot collided in turn. Merged main again and moved to 478. TestMigrationNumericPrefixesAreUnique passes, and the full ledger applies cleanly from scratch through 478. Co-authored-by: multica-agent <[email protected]> * fix(claim): measure both deltas from the run whose session is resumed (MUL-7344) Review found a real correctness bug, not a performance one: the claim could tell a resumed agent "the issue is unchanged" while the session it handed back predated the change. Both deltas were anchored on the most recently STARTED run for (agent, issue). The session the daemon resumes is chosen by a different rule, and the two are not the same row: - GetLastTaskSession skips poisoned and retired sessions, so a newer run that died on an unprocessable conversation is passed over for an OLDER healthy one. - A manual rerun resumes the exact source task the operator clicked. So: an old session remembers description A, the user edits it to B, a newer run records B and then dies unresumable. The next follow-up resumes the OLD session but compares B against B, reports "unchanged", and the agent keeps working from a context that still says A. PriorSessionResumeUnavailable does not catch this — it flags a missing Codex rollout, which is only one of the ways the resumed session can be older than the newest run. The fix binds both deltas to the run actually being resumed. GetLastTaskSession now returns that row's started_at and issue_snapshot alongside its session, the rerun path takes them from its source task, and the delta computation moves after resume resolution so it can only ever read the adopted row. Where no session is adopted — no prior session, a session on another runtime, a forced-fresh retry, a rerun whose source failure poisoned the conversation — no delta is reported and the daemon performs the reads it always has. That makes the standalone anchor query unnecessary: the anchor is now a projection on a row the claim already fetched, so a claim reads no extra row at all (GetLastRunAnchorForIssueAndAgent is removed). The precondition is now visible in the fixtures: a prior run has to be resumable for a delta to exist, so the comment-delta tests seed a session_id. The regression test covers both paths and was verified to fail against the previous anchor — both sub-cases report an empty changed-field list, which is exactly the wrong "unchanged". Also renumbers the migration 478 → 491; main landed 478_issue_status_category_expand through 490 while the branch was open. Co-authored-by: multica-agent <[email protected]> * fix(claim): a never-started anchor row may date neither delta (MUL-7344) Final review found the last corner of the resumed-run bug. Binding the anchor to the resumed SESSION was necessary but not sufficient: GetLastTaskSession returns that session's latest TERMINAL row, and that row is not always a run that ran. A retry child can inherit the session, be claimed — which writes its snapshot — and then fail before the agent ever starts: runtime offline during prepare, cancelled while dispatched. Its snapshot describes the issue as of ITS claim, while the session's memory stops at the last run that actually executed. If the description was edited in between, the current issue matches that snapshot and the claim reports "unchanged" to an agent whose context predates the edit. The started_at gate now covers both deltas rather than only the comment one. The comment delta was already immune because it read StartedAt directly; the issue delta read only the snapshot, so it had no such guard. Hoisting the check makes the row's requirement explicit — a row that never ran dates nothing — and the inner duplicate is dropped so no reader has to wonder what each guards. Regression test reproduces the exact fixture and was verified to fail without the gate: known=true with an empty changed-field list, i.e. the false "unchanged". Also from review: - Adopt the no-adopted-session table (missing session, session on another runtime, force-fresh retry, poisoned rerun source). Each seeds a usable snapshot AND an unread comment, so a regression has something to report and the assertion cannot pass on an empty fixture. - The resumed-run regression test asserted a comment anchor that never executed: both sub-cases created only the trigger comment, which the count excludes, so NewCommentsSince was empty and the branch was dead. A comment between the two runs now makes it run and pins known/count/since. - Rewrite the migration header, which still described the old "(agent_id, issue_id, started_at DESC)" anchor and an index rationale for a column that is never a lookup key. Co-authored-by: multica-agent <[email protected]> * refactor(claim): compare title, description and status only (MUL-7344) The snapshot compared five fields. The comparison answers exactly one question — must the agent run `issue get` again? — so a field earns a place only if changing it alters what the agent does AND its current value is not already in the per-turn message. Assignee fails the second test: the claim ships the current assignee outright, and "is this mine now" is answered by that value, not by whether it moved. Priority fails the first: it is reachable only by reading the issue, but it does not change what the agent does. Both are dropped. Status stays even though it is also shipped as a current value, so comparing it is strictly redundant for "what is it now". It earns its place as a signal rather than a value: "changed: status" is the clearest evidence that somebody intervened between the runs — a push back from in_review to todo means the delivery was rejected — and it costs nothing to carry. Narrowing the set means a narrower claim, so the prompt now says "compared title, description and status". An issue whose only change is assignee or priority is reported as unchanged, which is correct and now pinned by a test that also asserts the current assignee still ships — without that second assertion the fixture's UPDATE could silently not apply and the test would pass on nothing. Snapshot version goes to 2, because the rule is that the version tracks the compared SET. No deployment has written a v1 row, but the gate is what makes narrowing safe in general: a v1 row read by v2 code is "not compared", which degrades to the unconditional read. Test fixtures now build snapshots at issueSnapshotVersion rather than a literal 1, so a future bump cannot quietly turn them into not-compared tests while they keep passing. The foreign-version case stays explicit at +1. Also renumbers the migration 491 -> 495; main landed 491 through 494 while the branch was open. Fifth renumber. Co-authored-by: multica-agent <[email protected]> * refactor(claim): keep the snapshot at v1 (MUL-7344) The previous commit bumped issueSnapshotVersion to 2 when it narrowed the compared set. That was the version rule applied past its purpose. The rule — "bump when the compared SET changes" — exists to stop a stored snapshot from being compared against a differently-shaped one. It protects DEPLOYED rows. This column is introduced by this same unmerged PR: the migration exists only on this branch, main has neither the column nor the writer, and no environment has ever stored a row with the wider set. There is nothing for the bump to protect, and shipping v2 as the first version the column has ever held would leave a reader hunting for a v1 that never existed. Back to 1. The `v` field and its unknown-version gate stay exactly as they were — those are what make a real narrowing safe later. The comment now says which version this is and why the review-time narrowing did not earn a bump, so the rule still has a stated precedent without a burned number. No behaviour change: the compared set is still title, description and status, and fixtures build at issueSnapshotVersion rather than a literal, so they followed the constant back down. Co-authored-by: multica-agent <[email protected]> * fix(claim): only a completed run may date a delta (MUL-7344) Final review found a window one step later than the never-started case. A snapshot is written at CLAIM time. The daemon then writes started_at (TaskService.StartTask) and only afterwards launches the provider. So a run can carry both a snapshot and a started_at while its prompt never reached the session: the daemon exits during prepare, the runtime drops, orphan recovery marks the row failed. Anchoring there compares the current issue against a snapshot describing an edit that session never saw — reporting "unchanged" across a real change, and dating the comment delta late enough to hide every comment older than that row's start. Reproduced on the real CreateRetryTask -> claim -> StartTask -> RecoverOrphanedTasksForRuntime -> follow-up claim path. Nothing on the row separates "failed after the provider ran" from "failed before it ran". session_id is inherited by CreateRetryTask at insert, so it does not mean the session was fed; started_at precedes the launch, so it does not mean the turn began. The only terminal status that PROVES delivery is 'completed' — a run cannot finish a turn it never started. So failed and cancelled sources now date neither delta. Their SESSION is still resumed exactly as before, and GetLastTaskSession still accepts them on purpose; those runs simply perform the context reads they always did. That is the cheap side of the trade: a follow-up after a failure or a stop is already recovering from something, while the case the optimization exists for — an ordinary follow-up after a healthy run — is untouched. Both conditions now live in newResumedRunAnchor rather than at the use site, so "which run may date a delta" is answered once, and GetLastTaskSession carries the status needed to answer it. Regression test is the reviewer's repro, verified to fail without the rule: known=true with an empty changed list AND a computed zero comment count, i.e. both a false "unchanged" and a false "no new comments". Also: - Renumber the migration 495 -> 499; main landed 495 through 498. Sixth renumber. - The migration's content_revision rationale named the old five-field set while arguing about hypothetical issue-row writers; it now refers to "any compared field", which is what the argument actually depends on. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
080b118f07 |
fix(daemon): isolate cached worktree Git identities (MUL-7423) (#8470)
* fix(daemon): isolate Git identities in cached worktrees (MUL-7423) Co-authored-by: multica-agent <[email protected]> * fix(repocache): preserve config lock ownership on handoff Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
d665f0ac60 |
MUL-7379 docs: retranslate the issue status model and correct category-wide behavior claims (#8438)
Documentation half of MUL-7379, after #8435 fixed the code. The stale pages did not merely use outdated wording — they promised behavior the platform stopped providing, in the three languages nobody re-read. zh/ja/ko issues.mdx were left on the retired seven-category inheritance model when MUL-7240 rewrote the English page. Each still said a new status "inherits that category's behavior in full", with a seven-row table promising that an in_review-category status finishes the autopilot run and auto-archives past failure notices, an in_progress-category status rolls back to todo on failure, and a backlog-category status parks — plus the worked example naming `Code Review`. An admin following the Chinese page would create that status and their autopilot runs would silently never finish. All three now mirror the English source, including the installed-client wire-enum paragraph they lacked entirely; ja and ko also gained the agent status-writing paragraph only en and zh carried. Status keys and category names stay lowercase English per the i18n glossary, which already listed the four values correctly. assigning-issues (all four locales) said only the `backlog` CATEGORY parks and a custom status in it behaves identically. WillEnqueueRun parks on the literal key, so a reader following that page parks work on their own unstarted status and gets exactly the run the page promises they will not. The same sentence named a `cancelled` category, which is now `closed`. inbox (all four locales) attributed failure-notice archiving to an `in_review` category that does not exist; it now states the handoff rule #8435 implemented, including why blocked is excluded. The Korean settings subtitle said five lifecycle categories directly above the four groups. CLI_AND_DAEMON.md presented the seven built-ins as the complete enum while validateIssueStatus is deliberately shape-only. One behavioral change, in its own commit with its test: the agent brief rendered each lifecycle group as "- `unstarted`: `backlog`, `todo` (built-in)". That leading backticked token used to always be a valid status key, so sharing the formatting was harmless; `unstarted`, `started` and `closed` are now category-only names that Resolve refuses, so the one line teaching agents how to write status invited `multica issue status <id> started` — a 400. The category label is now plain text and only settable keys keep backticks. Review caught two overclaims, both corrected: filtering resolves on the concrete status like boards, lists and sorting do, not on the category; and `done` is both a lifecycle category and a built-in key, so it is not among the names Resolve refuses. |
||
|
|
b62cce56da |
MUL-7359 perf(runtime): avoid echoing final comment bodies (#8390)
* perf(runtime): avoid echoing final comment bodies * refactor(daemon): keep the file-first guardrail as the Comment Formatting lede The receipt-mode rule opened the section, pushing the file-first guardrail (MUL-2904 / #4182 / MUL-4252) out of the lede. The guardrail is the section's correctness red line — a shell-mangled body is a wrong comment — while the receipt mode only decides how much of an already-correct comment is echoed back. Move the rule after the guardrail in both OS variants; it is now a single const so the two branches cannot drift. Co-authored-by: multica-agent <[email protected]> * docs(skills): drop the duplicated final-comment-receipt section issues.md states its own charter as "Product contracts the runtime brief does not fully encode", and SKILL.md gives the brief ownership of "when to comment". The brief's ## Comment Formatting now carries this rule in full, so the copy here duplicates a brief-owned instruction against the single-owner convention (MUL-6984 #7971, MUL-5825 #6517). It was also unreachable by the documented routing: the section was added to the file's own table of contents, but SKILL.md's Routing row for issues.md was not updated, so no task would route here for this topic. Co-authored-by: multica-agent <[email protected]> * fix(daemon): gate final-comment cleanup on the post succeeding Agents hand the reply cookbook to one shell call. With cleanup as its own statement, a failed post is followed by a cleanup that SUCCEEDS, so the call exits 0 and ./reply.md is deleted. Under `--output table` a failed and a successful post both write nothing to stdout, which leaves the exit status as the only machine-checkable signal — masking it lets an agent end its turn believing an unposted final result was delivered, and drops the body it would need to retry with. The brief already told agents to check the posting command's exit status; the rendered snippet made that impossible. POSIX chains with `&&`. PowerShell 5.1 has no `&&` (it landed in 7), so the Windows variant propagates $LASTEXITCODE instead. Both keep ./reply.md on disk when the post fails. The guard EXECUTES the rendered snippet against a stub CLI: this is a shell-composition property, and the handler-level test can only prove the command returns an error, not that the pasted block preserves it. Verified to fail on the previous two-statement form. The pwsh case runs wherever PowerShell is installed, including Linux runners, and skips otherwise. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
d77a1c5ea7 |
fix(prompts): remove status wire details from operational guidance (#8401)
Co-authored-by: Lambda <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
aac85ab263 |
fix(daemon): preserve issue status catalog wire compatibility (#8378)
Co-authored-by: Lambda <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
686957e743 |
MUL-7271: test(server): cut slow Go test time further (follow-up to #8295) (#8318)
* test(server): reduce slow unit test waits Co-authored-by: multica-agent <[email protected]> * test(server): cut slow Go unit test time further Follow-up to the first pass on MUL-7271. Test-only changes plus a few package-private seams whose production values are unchanged. - Re-executed race-built test binaries no longer pay the race runtime's 1s atexit sleep (GORACE=atexit_sleep_ms=0 for child processes only). - pkg/agent: event-driven or shorter test-only bounds for handshake, drain and idle graces; redundant timeout variants merged. - daemon/execenv: git fixtures copied from a template repo built once; tests that re-ran the same worktree lifecycle folded into their supersets; ForkLock-guarded fake-CLI writes so parallel tests cannot hit ETXTBSY. - handler/service: cap and paging tests shrink their caps instead of seeding thousands of rows; lock waits probe pg_locks instead of sleeping; one shared pgxpool per service test binary; cheaper cron and RSA fixtures. - wecom: the phishing-link sweep reuses one parser and derives CRLF from LF with a sampled cross-check; fake sockets acknowledge instead of waiting out the ack timeout. - migrate: the migration corpus is parsed once; isolated DB tests run in parallel. Co-authored-by: multica-agent <[email protected]> * test(server): remove transport timeout unit tests Co-authored-by: multica-agent <[email protected]> * test(server): restore two non-timeout tests and make a bound load-bearing Follow-up to the review of #8318. All test-only; no production code changes. - daemon: restore the three read-liveness cases that the transport-timeout sweep took with it. They assert that a ping, a pong and an application frame each EXTEND the read deadline, so a healthy wakeup socket survives without traffic of our own — the deadline never fires on the happy path, which is why they are not transport-timeout tests. Verified they fail (all three) when the extension calls are removed from wakeup.go, and pass in 0.57s together. The pure "times out without peer traffic" case stays deleted. - pkg/agent: restore the parent-DEADLINE half of the opaque-env redaction pair. codex.go treats a parent deadline and a parent cancellation as one ending; only the Canceled arm was left covered. The fake now writes the secret to stderr before reading the handshake, so the check no longer races our own initialize write, and the budget is 2s against a measured 430-465ms launch. Verified it fails when the !contextEnded guard is removed. - pkg/agent: TestRunCollectQuietDoesNotWaitWhenTheAnswerIsIn had its bound widened to a literal 5s, which the shrunk 750ms drain grace can never reach — the test could no longer fail. It now raises the drain grace to 10s for its own duration and asserts under 3s: healthy runs take 0.19-0.44s, and a broken short-circuit takes 10.19s and fails. - wecom: gofmt the import block in relay_settle_budget_test.go. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Sol-Boy <[email protected]> Co-authored-by: multica-agent <[email protected]> Co-authored-by: J <[email protected]> |
||
|
|
7dafc0cd62 |
MUL-7240: Unify issue lifecycle into four stored categories (#8261)
* feat: simplify issue status categories Co-authored-by: multica-agent <[email protected]> * feat: adopt four stored issue lifecycle categories Co-authored-by: multica-agent <[email protected]> * fix: preserve visible work when upgrading saved category preferences Co-authored-by: multica-agent <[email protected]> * docs: align lifecycle comments with stored and wire contracts Co-authored-by: multica-agent <[email protected]> * test: align mobile grouping expectations with four categories Co-authored-by: multica-agent <[email protected]> * fix(issues): group user views by concrete status keys Co-authored-by: multica-agent <[email protected]> * fix(settings): simplify status editor copy and category selector Co-authored-by: multica-agent <[email protected]> * feat(statuses): allow ordering built-ins and unify row actions Co-authored-by: multica-agent <[email protected]> * feat(statuses): configure custom status icons independently of category Co-authored-by: multica-agent <[email protected]> * fix(statuses): dismiss built-in status notice Co-authored-by: multica-agent <[email protected]> * fix(issue-status): require moving issues before archive and polish controls Co-authored-by: multica-agent <[email protected]> * fix(ci): renumber issue status migrations after main updates Co-authored-by: multica-agent <[email protected]> * fix(statuses): isolate archive inspection and align catalog behavior Co-authored-by: multica-agent <[email protected]> * fix(statuses): respect injectable catalog for table grouping Co-authored-by: multica-agent <[email protected]> * refactor(statuses): simplify built-in skill and runtime guidance Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Lambda <[email protected]> Co-authored-by: multica-agent <[email protected]> |