Files
multica/packages/core/runtimes/mutations.ts
Bohan Jiang fd58e13bec feat(runtimes): custom runtime names + searchable machine-grouped picker (MUL-4217) (#5070)
* feat(runtimes): custom runtime names + searchable machine-grouped picker

MUL-4217. Runtime names were daemon-generated ("Claude (host)") and
uneditable, so picking one at agent-create time was painful once a
workspace had many machines.

Phase 1 — create-agent RuntimePicker: add a search box (>6 runtimes) and
group options by machine (Local/Remote/Cloud, online-first, current
machine first) reusing buildRuntimeMachines/filterRuntimeMachines. Rows
show the provider under a machine header instead of a flat repeated list.

Phase 2 — custom names: new nullable agent_runtime.custom_name column,
never written by the registration/heartbeat upsert so the daemon can't
clobber it; display is coalesce(custom_name, name) via runtimeDisplayName.
PATCH /api/runtimes/:id gains custom_name (+ apply_to_machine to name every
runtime sharing a daemon_id in one action, owner-scoped for non-admins).
Rename UI on the runtime detail page; `multica runtime rename` CLI command.

Verified: go build/vet, sqlc, handler tests (incl. new custom-name single
+ machine-fanout), 1650 views + 764 core TS tests, typecheck, locale parity.

Co-authored-by: multica-agent <github@multica.ai>

* fix(runtimes): address review — persist machine name on new registrations, keep custom_name in register response

Elon's review on #5070 (MUL-4217):

1. Machine name looked "lost" when a new provider registered on an
   already-named machine — the new row landed with custom_name=null and
   broke sharedCustomName. Now a fresh runtime inherits the machine's shared
   custom name at register time (ListDaemonCustomNames + sharedDaemonCustomName),
   so the machine title stays stable as providers come and go.

2. DaemonRegister rebuilt the response row by hand and dropped custom_name,
   so register returned custom_name:null — inconsistent with list/get/update.
   Both branches now carry CustomName.

Also: tighten the updateRuntime patch type to custom_name?: string (drop the
misleading `| null`, since the server treats null as "unchanged", not "clear").

Tests: register response preserves custom_name; new runtime inherits machine name.
Co-authored-by: multica-agent <github@multica.ai>

* fix(runtimes): inherit machine name for failed-profile registrations too

Elon's re-review of #5070 (MUL-4217): the machine-name inheritance added
last round only covered the normal req.Runtimes path. The req.FailedProfiles
branch also upserts a daemon_id-scoped agent_runtime row (offline, profile
registration error), which shows up in the runtime list / machine grouping —
so on a named machine a failed custom-profile row landed with custom_name=NULL
and dragged the machine title back to the hostname.

Extract the inheritance into h.inheritMachineCustomName and call it from both
the normal runtime path and the failed-profile path. Add a test: named daemon
+ failed profile upsert -> the failed row's persisted custom_name is inherited.

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: J <j@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-08 16:00:17 +08:00

68 lines
2.4 KiB
TypeScript

import { useMutation, useQueryClient } from "@tanstack/react-query";
import { api } from "../api";
import { runtimeKeys } from "./queries";
import { workspaceKeys } from "../workspace/queries";
import { agentTaskSnapshotKeys } from "../agents/queries";
export function useDeleteRuntime(wsId: string) {
const qc = useQueryClient();
return useMutation({
mutationFn: (runtimeId: string) => api.deleteRuntime(runtimeId),
onSettled: () => {
qc.invalidateQueries({ queryKey: runtimeKeys.all(wsId) });
},
});
}
// Cascade-mode counterpart to useDeleteRuntime. The dialog routes here when
// the strict DELETE refused with `runtime_has_active_agents` (or when the
// caller already knows the runtime has active agents and wants to skip the
// pre-flight refusal). Mutation fn returns the server-reported counts so
// the caller can render a richer success toast.
//
// Invalidates runtimes (the list / detail), workspace agents (the cascade
// archives them) and the agent presence snapshot (cascade also cancels
// queued/running tasks). Without the agent-side invalidation the Agents
// page would keep showing the just-archived rows as live until a refetch.
export function useArchiveAgentsAndDeleteRuntime(wsId: string) {
const qc = useQueryClient();
return useMutation({
mutationFn: ({
runtimeId,
expectedActiveAgentIds,
}: {
runtimeId: string;
expectedActiveAgentIds: string[];
}) => api.archiveAgentsAndDeleteRuntime(runtimeId, expectedActiveAgentIds),
onSettled: () => {
qc.invalidateQueries({ queryKey: runtimeKeys.all(wsId) });
qc.invalidateQueries({ queryKey: workspaceKeys.agents(wsId) });
qc.invalidateQueries({ queryKey: agentTaskSnapshotKeys.all(wsId) });
},
});
}
// useUpdateRuntime patches editable fields on a runtime (visibility, custom
// name). Invalidates the runtime list so the picker disabled-state and
// display names recompute.
export function useUpdateRuntime(wsId: string) {
const qc = useQueryClient();
return useMutation({
mutationFn: ({
runtimeId,
patch,
}: {
runtimeId: string;
patch: {
visibility?: "private" | "public";
// Empty string clears the custom name; omit to leave unchanged.
custom_name?: string;
apply_to_machine?: boolean;
};
}) => api.updateRuntime(runtimeId, patch),
onSettled: () => {
qc.invalidateQueries({ queryKey: runtimeKeys.all(wsId) });
},
});
}