chore: merge upstream multica-ai/main (19 commits) into fork#32
Merged
Conversation
* feat(skills): structured conflict + overwrite path for local skill re-import
Local-skill re-import previously failed (or silently skipped) on a same-name
collision and, on delete+reimport, changed the skill UUID and dropped agent
bindings. This adds a structured conflict result and a creator-only overwrite
write path so a re-import can update the existing skill in place.
- New terminal import status `conflict` carrying { existing_skill_id,
existing_created_by, can_overwrite }; can_overwrite = requester is the
skill creator (canOverwriteSkillByLocalImport — intentionally narrower than
canManageSkill: admins edit in-app, not via re-import).
- Conflict is detected at daemon-report time (the effective name is only known
once the bundle arrives) via GetSkillByWorkspaceAndName, with the unique
constraint as a race backstop.
- Import requests carry action=overwrite + target_skill_id, persisted through
both the in-memory and Redis LocalSkillImportStore (the heartbeat → daemon
payload is unchanged; overwrite is resolved server-side).
- overwriteSkillWithFiles updates by target_skill_id in one tx: re-checks
existence (workspace-scoped) and creator permission, then replaces
description/content/config and fully replaces files (pruning files absent
from the new bundle). Preserves id, created_by, created_at, name, and
agent_skill bindings. Publishes skill:updated (not skill:created).
- Boundaries: target deleted or permission lost → failed (no fallback to
create-by-name); any mid-write error rolls back the tx, leaving the original
skill untouched. Retrying a terminal request is a no-op.
Tests cover: creator/non-creator conflict (can_overwrite), overwrite preserves
UUID + agent binding + prunes removed files, non-creator overwrite fails,
deleted target fails without create fallback, retry idempotency, and Redis
round-trip of the new fields.
Backend half of MUL-2701. Contract change: same-name local imports now return
status `conflict` instead of `failed` — the Desktop/core client must be updated
to consume it (sibling task).
MUL-2800
Co-authored-by: multica-agent <github@multica.ai>
* fix(skills): gate structured conflict behind client opt-in; guard overwrite target name
Addresses review feedback on PR multica-ai#3498 (MUL-2800).
Backward compatibility: a same-name local import now returns the new `conflict`
status only when the initiating client opts in via `supports_conflict` (an
overwrite request implies it). Older clients — already-installed Desktop builds
whose poll loop only understands `failed`/`timeout` — keep the legacy `failed`
+ "a skill with this name already exists" behavior, so upgrading the backend
ahead of the client no longer regresses the import UX. This is the installed-app
API-compat boundary the repo's CLAUDE.md calls out.
Also: the overwrite write path now verifies the incoming effective name matches
the target skill's current name (errSkillOverwriteNameMismatch -> failed),
preventing a stale/wrong target_skill_id from writing one skill's content onto
another. Creator-only + workspace scoping already prevent privilege escalation;
this narrows the API so it can't be misused.
Refactored LocalSkillImportStore.Create to a LocalSkillImportRequestInput params
struct (the signature had grown to 8 positional args; the opt-in flag pushed it
over). supports_conflict is persisted in both the in-memory and Redis stores.
Tests: conflict tests now opt in; added a legacy-client test (no flag ->
failed + legacy message) and an overwrite name-mismatch test.
MUL-2800
Co-authored-by: multica-agent <github@multica.ai>
* feat(skills): resolve local import conflicts in desktop
Co-authored-by: multica-agent <github@multica.ai>
* fix(skills): preserve bulk flow after conflict resolution
Co-authored-by: multica-agent <github@multica.ai>
* feat(cli): add skill import conflict strategies
Co-authored-by: multica-agent <github@multica.ai>
* fix(i18n): sync skill import locale keys
Co-authored-by: multica-agent <github@multica.ai>
* docs: explain skill import conflict handling
Co-authored-by: multica-agent <github@multica.ai>
* docs: refresh skill import source map anchors
Co-authored-by: multica-agent <github@multica.ai>
---------
Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
…ltica-ai#4027) CLI backends key their session stores to the cwd (Claude Code looks sessions up under ~/.claude/projects/<encoded-cwd>/), so a prior session id can only resolve when the task runs in the exact workdir the session was recorded against. When the prior workdir no longer exists (GC'd after the issue went done, daemon reinstall, manual cleanup), execenv.Reuse falls back to a fresh Prepare but the stale session id was still passed to the backend: claude exited within a second and the run failed before doing any work — permanently, because the failed run records no session_id and the next claim serves the same stale pointer again. Gate ResumeSessionID on the workdir actually being reused, and correct PriorSessionResumed so the runtime brief uses the cold-path wording when the session is dropped. Fixes multica-ai#3854 (MUL-3221) Co-authored-by: J <j@multica.ai> Co-authored-by: multica-agent <github@multica.ai>
Co-authored-by: J <j@multica.ai> Co-authored-by: multica-agent <github@multica.ai>
… [MUL-3216] (multica-ai#4015) * fix(agent): clear stale session id when a resumed ACP session is gone When an agent's stored ACP session no longer exists on the runtime side, session/resume still succeeds — hermes echoes the requested sessionId back — so the failure only surfaces when session/prompt returns JSON-RPC -32603 "Session not found". The backend then reported Status=failed with the stale SessionID still set, which kept the daemon's resume-failure fallback (gated on SessionID == "") from ever firing. The failed task never updates the stored session, so every future mention on the same (agent, issue) dispatched against the same dead id, forever (multica-ai#4010). handleResponse now returns a structured acpRPCError instead of a flat string (rendered text unchanged), and the hermes/kimi/kiro prompt-error paths clear the session id when the error is session-not-found class on a resumed session. The daemon's existing retry then re-executes with a fresh session and stores the replacement id, healing the mapping. * fix(agent): clear stale session id when set_model hits a dead resumed session With a model override, session/set_model runs before session/prompt, so a resumed session that is gone on the agent side surfaces there instead of at the prompt — and the error branch returned the stale SessionID, so the daemon's fresh-session retry (gated on SessionID == "") never fired. Apply the same clear-the-id fix in the set_model error branch of all three backends. Also relax isACPSessionNotFound to accept -32602: kimi-cli raises RequestError.invalid_params({"session_id": "Session not found"}) for every unknown-session path (src/kimi_cli/acp/server.py), so pinning -32603 made the fix dead code for kimi. The wording gate keeps unrelated invalid_params errors (e.g. "model not available") on the preserve-the-id path. Regression tests for all three backends: resumed session + model override + set_model failing with each runtime's observed session-not-found shape must yield status=failed with an empty SessionID.
…#3691) (multica-ai#4033) collectThreadReplies walked the parent_id tree depth-first, so an agent reply forced to nest under its trigger comment rendered before earlier sibling replies (A-D-B-C instead of A-B-C-D) whenever the agent returned late. Sort the collected subtree by created_at (id tie-break) so the thread reads in arrival order — the same order the server already feeds agents via `comment list --thread` (ListThreadCommentsForIssue). All other consumers of the array (resolution derivation, fold bars, counts, deep-link) are order-independent. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Eve <eve@multica-ai.local> Co-authored-by: multica-agent <github@multica.ai>
…ai#4047) Two bugs prevented the Lark binding flow from completing for already-logged-in users: 1. The useEffect ran before AuthInitializer's getMe() returned, setting state to needs-auth; the guard then blocked re-entry once auth loaded. 2. The sign-in redirect used ?redirect= but the login page reads ?next=. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: multica-agent <github@multica.ai>
…ai#3498) * feat(skills): structured conflict + overwrite path for local skill re-import Local-skill re-import previously failed (or silently skipped) on a same-name collision and, on delete+reimport, changed the skill UUID and dropped agent bindings. This adds a structured conflict result and a creator-only overwrite write path so a re-import can update the existing skill in place. - New terminal import status `conflict` carrying { existing_skill_id, existing_created_by, can_overwrite }; can_overwrite = requester is the skill creator (canOverwriteSkillByLocalImport — intentionally narrower than canManageSkill: admins edit in-app, not via re-import). - Conflict is detected at daemon-report time (the effective name is only known once the bundle arrives) via GetSkillByWorkspaceAndName, with the unique constraint as a race backstop. - Import requests carry action=overwrite + target_skill_id, persisted through both the in-memory and Redis LocalSkillImportStore (the heartbeat → daemon payload is unchanged; overwrite is resolved server-side). - overwriteSkillWithFiles updates by target_skill_id in one tx: re-checks existence (workspace-scoped) and creator permission, then replaces description/content/config and fully replaces files (pruning files absent from the new bundle). Preserves id, created_by, created_at, name, and agent_skill bindings. Publishes skill:updated (not skill:created). - Boundaries: target deleted or permission lost → failed (no fallback to create-by-name); any mid-write error rolls back the tx, leaving the original skill untouched. Retrying a terminal request is a no-op. Tests cover: creator/non-creator conflict (can_overwrite), overwrite preserves UUID + agent binding + prunes removed files, non-creator overwrite fails, deleted target fails without create fallback, retry idempotency, and Redis round-trip of the new fields. Backend half of MUL-2701. Contract change: same-name local imports now return status `conflict` instead of `failed` — the Desktop/core client must be updated to consume it (sibling task). MUL-2800 Co-authored-by: multica-agent <github@multica.ai> * fix(skills): gate structured conflict behind client opt-in; guard overwrite target name Addresses review feedback on PR multica-ai#3498 (MUL-2800). Backward compatibility: a same-name local import now returns the new `conflict` status only when the initiating client opts in via `supports_conflict` (an overwrite request implies it). Older clients — already-installed Desktop builds whose poll loop only understands `failed`/`timeout` — keep the legacy `failed` + "a skill with this name already exists" behavior, so upgrading the backend ahead of the client no longer regresses the import UX. This is the installed-app API-compat boundary the repo's CLAUDE.md calls out. Also: the overwrite write path now verifies the incoming effective name matches the target skill's current name (errSkillOverwriteNameMismatch -> failed), preventing a stale/wrong target_skill_id from writing one skill's content onto another. Creator-only + workspace scoping already prevent privilege escalation; this narrows the API so it can't be misused. Refactored LocalSkillImportStore.Create to a LocalSkillImportRequestInput params struct (the signature had grown to 8 positional args; the opt-in flag pushed it over). supports_conflict is persisted in both the in-memory and Redis stores. Tests: conflict tests now opt in; added a legacy-client test (no flag -> failed + legacy message) and an overwrite name-mismatch test. MUL-2800 Co-authored-by: multica-agent <github@multica.ai> * feat(skills): resolve local import conflicts in desktop Co-authored-by: multica-agent <github@multica.ai> * fix(skills): preserve bulk flow after conflict resolution Co-authored-by: multica-agent <github@multica.ai> * fix(skills): show creator name instead of UUID in import conflict UI When a local skill import hits a name conflict with a skill owned by another user, the locked-creator message rendered the raw existing_created_by UUID via the {{creator}} placeholder, which is unreadable. Resolve the UUID against the workspace member list and render the display name instead. When the creator has left the workspace (or the member list hasn't loaded), fall back to the unbranded conflict_locked message rather than leak the UUID. Adds two test cases covering both branches. MUL-2701 Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: J <j@multica.ai> Co-authored-by: multica-agent <github@multica.ai> Co-authored-by: Eve <eve@multica-ai.local>
- suggest other profile workspace roots when disk-usage sees an empty selected root - include the default profile in reverse suggestions and shell-quote profile arguments - keep JSON output and explicit --workspaces-root behavior unchanged MUL-3232
Co-authored-by: J <j@multica.ai> Co-authored-by: multica-agent <github@multica.ai>
Co-authored-by: J <j@multica.ai> Co-authored-by: multica-agent <github@multica.ai>
Fixes GitHub issue multica-ai#3999 by moving the daemon StartTask transition behind workdir provisioning and extending the active env-root guard through completion metadata writes.
* feat(agent): add codebuddyBackend struct and buildCodebuddyArgs Introduces the codebuddy agent backend skeleton with args builder that mirrors claudeBackend's protocol flags (stream-json, bypass permissions, blocked args filtering) for the codebuddy CLI fork. * feat(agent): implement codebuddyBackend.Execute with stream-json parsing * feat(agent): wire codebuddy into New() factory and launchHeaders * feat(agent): add codebuddy dynamic model discovery from --help * feat(agent): add codebuddy thinking/effort discovery and providerThinkingEnums * feat(daemon): add codebuddy CLI probe, env vars, and args support * fix(agent): use len(models)==0 for default model instead of loop index * fix(agent): increase codebuddy --help timeout to 35s for slow CLI startup * fix(agent): address codebuddy PR review feedback - Wire codebuddy into execenv: reuse claude's CLAUDE.md, .claude/skills, and ~/.claude/skills paths since CodeBuddy is a Claude Code fork - Replace hardcoded 20-min timeout with runContext for zero-timeout = no-deadline semantics matching all other backends - Restore runContext regression tests lost in rebase merge - Mirror claude.go execution model: concurrent stdin write to prevent pipe deadlock, sync.Once for stdin closure, keep stdin open for control_request auto-approval mid-run - Add control_request handling with auto-approve behavior - Add RequestID/Request fields to codebuddySDKMessage - Add codebuddy to metrics knownRuntimeProviders - Add codebuddy to provider-logo.tsx (reuses ClaudeLogo) - Consolidate --help discovery: shared codebuddyHelpOutput cache eliminates duplicate cold-start invocations --------- Co-authored-by: krislliu <krislliu@tencent.com>
* Fix chat stop and send recovery Co-authored-by: multica-agent <github@multica.ai> * Fix chat cancel recovery follow-ups Co-authored-by: multica-agent <github@multica.ai> * Guard cancelled chat restore on tx failure Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: multica-agent <github@multica.ai>
Adds migration 119 creating idx_user_created_at on "user"(created_at) using CREATE INDEX CONCURRENTLY, matching the repo convention for index-only migrations (114/115). Co-authored-by: multica-agent <github@multica.ai>
…#4062) * fix: bind quick-create attachments to created issues Co-authored-by: multica-agent <github@multica.ai> * test: use real image markdown in quick-create attachment test Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: J <j@multica.ai> Co-authored-by: multica-agent <github@multica.ai>
* docs: add 2026-06-12 changelog entry Co-authored-by: multica-agent <github@multica.ai> * docs: refine 2026-06-12 changelog copy Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Eve <eve@multica-ai.local> Co-authored-by: multica-agent <github@multica.ai>
multica-ai#4068) The comment card exposed two identical add-reaction affordances: a QuickEmojiPicker in the header's top-right actions and the add button inside the bottom ReactionBar. Keep only the bottom one. - Drop QuickEmojiPicker from the root header and reply-row headers - Always show the ReactionBar add button (it is the only entry point now), removing the isLongContent gating - Remove the now-unused hideAddButton prop from ReactionBar MUL-3262 Co-authored-by: Lambda <lambda@multica.ai> Co-authored-by: multica-agent <github@multica.ai>
# Conflicts: # packages/core/api/client.ts
Upstream added 119_user_created_at_index; the Octo integration also used
119 (119_octo_integration). The custom migrate tool keys schema_migrations
by full basename so two 119s don't hard-collide, but unique sequential
numbering is the convention — renumber the Octo migration to 120 so it
orders cleanly after the upstream 119.
- git mv 119_octo_integration.{up,down}.sql -> 120_octo_integration.*
- update the 'migration 119' references in doc.go, octo.sql header, and
the generated octo.sql.go comment to 120
No schema change. Fresh deploys apply 120_octo_integration; existing
deployments that already applied it as 119 need their schema_migrations
row relabeled 119_octo_integration -> 120_octo_integration (ops note).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Merges the latest upstream
multica-ai/multica@main(19 new commits — CodeBuddy CLI backend, chat stop/send recovery, quick-create attachment binding, skill fixes,user.created_atindex, changelog, etc.) into the fork'smain, which carries the Octo IM integration work.7d719cfbb.Conflict resolved
Only one content conflict:
packages/core/api/client.tsimport block — upstream addedEMPTY_CANCEL_TASK_RESPONSE, the fork added the Octo schema imports. Resolved by keeping both. (schemas.ts / use-realtime-sync.ts / types/index.ts auto-merged.)Migration renumber (119 → 120)
Upstream added
119_user_created_at_index; the Octo integration also used119(119_octo_integration). The custom migrate tool keysschema_migrationsby full basename, so two 119s don't hard-collide — but unique sequential numbering is the convention, so the Octo migration is renumbered to 120 to order after the upstream 119.git mv 119_octo_integration.{up,down}.sql → 120_octo_integration.*doc.go, theocto.sqlheader, and the generatedocto.sql.gocomment.Ops note: fresh deploys apply
120_octo_integration. A deployment that already applied it as119must relabel its row:UPDATE schema_migrations SET version='120_octo_integration' WHERE version='119_octo_integration';(done on the local env).Verification
go build ./...,go vet,gofmtclean; octo + transport + scheduler tests pass.@multica/coretypecheck clean (merged client.ts/schemas).migrate uplocally: applies119_user_created_at_index, skips120_octo_integration(already applied) — clean ordering, no re-run.