diff --git a/packages/core/runtimes/cli-version.ts b/packages/core/runtimes/cli-version.ts deleted file mode 100644 index c36ad67983..0000000000 --- a/packages/core/runtimes/cli-version.ts +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Frontend mirror of the server's MinQuickCreateCLIVersion gate. The - * agent-create flow (Quick Create modal) requires the daemon's bundled - * multica CLI to be at least this version — older daemons either - * double-create issues on partial CLI failures or mishandle pasted - * screenshot URLs (see PR #1851 / MUL-1496). - * - * Both the frontend pre-validation in the modal and the server's - * `/api/issues/quick-create` handler enforce this; the server is the - * authoritative trust boundary, the frontend just lets us tell the user - * "your daemon needs an upgrade" before they hit submit. - */ -export const MIN_QUICK_CREATE_CLI_VERSION = "0.2.20"; - -export type CliVersionState = "ok" | "too_old" | "missing"; - -export interface CliVersionCheck { - state: CliVersionState; - /** What the daemon reported, or empty if missing/unparsable. */ - current: string; - /** The hard minimum we gate on. */ - min: string; -} - -const SEMVER_RE = /v?(\d+)\.(\d+)\.(\d+)/; - -function parseSemver(raw: string): [number, number, number] | null { - const m = SEMVER_RE.exec(raw.trim()); - if (!m) return null; - return [Number(m[1]), Number(m[2]), Number(m[3])]; -} - -function lessThan(a: [number, number, number], b: [number, number, number]) { - if (a[0] !== b[0]) return a[0] < b[0]; - if (a[1] !== b[1]) return a[1] < b[1]; - return a[2] < b[2]; -} - -/** - * Check a daemon-reported CLI version string against the minimum. Returns - * `"missing"` for empty/unparsable input (fail closed — same policy as the - * server) and `"too_old"` for a parsable version below the threshold. - */ -export function checkQuickCreateCliVersion(detected: string | undefined | null): CliVersionCheck { - const current = (detected ?? "").trim(); - const parsed = current ? parseSemver(current) : null; - if (!parsed) { - return { state: "missing", current, min: MIN_QUICK_CREATE_CLI_VERSION }; - } - const min = parseSemver(MIN_QUICK_CREATE_CLI_VERSION)!; - if (lessThan(parsed, min)) { - return { state: "too_old", current, min: MIN_QUICK_CREATE_CLI_VERSION }; - } - return { state: "ok", current, min: MIN_QUICK_CREATE_CLI_VERSION }; -} - -/** Pull `cli_version` off a runtime row's loosely-typed metadata bag. */ -export function readRuntimeCliVersion(metadata: Record | undefined): string { - const v = metadata?.cli_version; - return typeof v === "string" ? v : ""; -} diff --git a/packages/core/runtimes/index.ts b/packages/core/runtimes/index.ts index e16b10bef5..59c6af048d 100644 --- a/packages/core/runtimes/index.ts +++ b/packages/core/runtimes/index.ts @@ -6,4 +6,3 @@ export * from "./local-skills"; export * from "./types"; export * from "./derive-health"; export * from "./use-runtime-health"; -export * from "./cli-version"; diff --git a/packages/views/modals/quick-create-issue.test.tsx b/packages/views/modals/quick-create-issue.test.tsx index 786a6c2f38..7814769710 100644 --- a/packages/views/modals/quick-create-issue.test.tsx +++ b/packages/views/modals/quick-create-issue.test.tsx @@ -30,8 +30,6 @@ vi.mock("@tanstack/react-query", () => ({ return { data: [{ id: "agent-1", name: "Bohan", archived_at: null, runtime_id: "runtime-1" }], }; - case "runtimes": - return { data: [{ id: "runtime-1", metadata: { cli_version: "1.2.3" } }] }; default: return { data: [] }; } @@ -75,13 +73,6 @@ vi.mock("@multica/core/auth", () => ({ (selector ? selector({ user: { id: "user-1" } }) : { user: { id: "user-1" } }), })); -vi.mock("@multica/core/runtimes", () => ({ - runtimeListOptions: () => ({ queryKey: ["runtimes"] }), - checkQuickCreateCliVersion: () => ({ state: "ok", min: "1.0.0" }), - readRuntimeCliVersion: () => "1.2.3", - MIN_QUICK_CREATE_CLI_VERSION: "1.0.0", -})); - vi.mock("@multica/core/hooks/use-file-upload", () => ({ useFileUpload: () => ({ uploadWithToast: vi.fn(), uploading: false }), })); diff --git a/packages/views/modals/quick-create-issue.tsx b/packages/views/modals/quick-create-issue.tsx index fb50148b65..cd8a04ffd5 100644 --- a/packages/views/modals/quick-create-issue.tsx +++ b/packages/views/modals/quick-create-issue.tsx @@ -20,12 +20,6 @@ import { agentListOptions } from "@multica/core/workspace/queries"; import { useQuickCreateStore } from "@multica/core/issues/stores/quick-create-store"; import { useIssueDraftStore } from "@multica/core/issues/stores/draft-store"; import { useCreateModeStore } from "@multica/core/issues/stores/create-mode-store"; -import { - runtimeListOptions, - checkQuickCreateCliVersion, - readRuntimeCliVersion, - MIN_QUICK_CREATE_CLI_VERSION, -} from "@multica/core/runtimes"; import { useFileUpload } from "@multica/core/hooks/use-file-upload"; import { formatShortcut, modKey, enterKey } from "@multica/core/platform"; import type { Agent } from "@multica/core/types"; @@ -111,26 +105,6 @@ export function AgentCreatePanel({ [visibleAgents, agentId], ); - // Daemon CLI version gate. The agent-create flow needs the runtime's - // bundled multica CLI to be ≥ MIN_QUICK_CREATE_CLI_VERSION; older - // daemons handle attachments and partial-failure retries incorrectly - // (see PR #1851 / MUL-1496). Pre-check on the picker so the user gets - // immediate feedback instead of waiting for the inbox failure; the - // server re-validates as the trust boundary. - const { data: runtimes = [] } = useQuery(runtimeListOptions(wsId)); - const selectedRuntime = useMemo( - () => - selectedAgent?.runtime_id - ? runtimes.find((r) => r.id === selectedAgent.runtime_id) - : undefined, - [runtimes, selectedAgent?.runtime_id], - ); - const versionCheck = useMemo( - () => checkQuickCreateCliVersion(readRuntimeCliVersion(selectedRuntime?.metadata)), - [selectedRuntime?.metadata], - ); - const versionBlocked = versionCheck.state !== "ok"; - const initialPrompt = (data?.prompt as string) || promptDraft; // The editor is uncontrolled — we read the latest markdown via the ref at // submit/switch time. `hasContent` mirrors emptiness so the Create button @@ -164,7 +138,7 @@ export function AgentCreatePanel({ const submit = async () => { const md = editorRef.current?.getMarkdown()?.trim() ?? ""; - if (!md || !agentId || submitting || versionBlocked || uploading) return; + if (!md || !agentId || submitting || uploading) return; setSubmitting(true); setError(null); try { @@ -190,32 +164,14 @@ export function AgentCreatePanel({ } catch (e) { // Server returns 422 with { code, ... } for the structured rejection // paths the modal cares about. Surface the reason in-modal so the - // user can switch to a live agent / upgrade their daemon without - // leaving the flow. + // user can switch to a live agent without leaving the flow. if (e instanceof ApiError && e.body && typeof e.body === "object") { - const body = e.body as { - code?: string; - reason?: string; - current_version?: string; - min_version?: string; - }; + const body = e.body as { code?: string; reason?: string }; if (body.code === "agent_unavailable") { setError(body.reason || "Agent is unavailable. Pick another agent."); setSubmitting(false); return; } - if (body.code === "daemon_version_unsupported") { - // Race fallback: the picker pre-check should normally catch this, - // but a runtime can silently re-register with an older CLI between - // pre-check and submit. Same wording as the inline notice for - // consistency. - const cur = body.current_version || "unknown"; - setError( - `This agent's daemon CLI (${cur}) is below the required ${body.min_version || MIN_QUICK_CREATE_CLI_VERSION}. Upgrade the daemon to use Create with agent.`, - ); - setSubmitting(false); - return; - } } setError("Failed to submit. Try again."); } finally { @@ -324,14 +280,6 @@ export function AgentCreatePanel({ - {selectedAgent && versionBlocked && ( -
- {versionCheck.state === "missing" - ? `This agent's daemon doesn't report a CLI version. Create with agent needs multica CLI ≥ ${versionCheck.min}. Upgrade the daemon and reconnect, or switch to manual create.` - : `This agent's daemon CLI is ${versionCheck.current} — Create with agent needs ≥ ${versionCheck.min}. Upgrade the daemon, or switch to manual create.`} -
- )} - {/* Prompt — same rich editor Advanced uses, so paste/drop images, mentions, and formatting all work. The dropZone wrapper enables drag-and-drop file uploads alongside paste. */} @@ -397,12 +345,7 @@ export function AgentCreatePanel({