mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-27 13:06:20 +02:00
Chat project context (MUL-5150, #5765) is rendered into the run brief by the daemon, so daemons older than the release that ships it silently drop the project description while still honoring the server-extracted repos. Mirror the handoff-note soft gate: resolve the active agent's runtime cli_version from the warm runtime cache and, when it is a stale release build, surface a warning next to the composer chip and inside the project submenu. Selection is never blocked — dev-describe builds and unknown runtimes stay silent. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
147 lines
6.2 KiB
TypeScript
147 lines
6.2 KiB
TypeScript
/**
|
|
* 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, drop quick-create attachment
|
|
* bindings, 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.21";
|
|
export const MIN_QUICK_CREATE_FIELDS_CLI_VERSION = "0.4.3";
|
|
|
|
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+)/;
|
|
|
|
// Matches the `git describe --tags --always --dirty` output for a build past
|
|
// the latest tag, e.g. `v0.2.15-235-gdaf0e935` or `v0.2.15-235-gdaf0e935-dirty`.
|
|
// Daemons built from source (Makefile `make build` / `make daemon`) report this
|
|
// shape; tagged releases are bare semver. Treating dev-described daemons as OK
|
|
// is what keeps `pnpm dev:desktop` + `make daemon` unblocked without weakening
|
|
// the gate for staging or production users running stale stable releases.
|
|
const DEV_DESCRIBE_RE = /^v?\d+\.\d+\.\d+-\d+-g[0-9a-fA-F]+/;
|
|
|
|
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.
|
|
* Dev-built daemons (git-describe shape) are always OK — the version string
|
|
* itself is the shared signal, so frontend and server agree by construction.
|
|
*/
|
|
export function checkQuickCreateCliVersion(detected: string | undefined | null): CliVersionCheck {
|
|
return checkCliVersion(detected, MIN_QUICK_CREATE_CLI_VERSION);
|
|
}
|
|
|
|
/** Capability gate for explicit quick-create priority and due-date fields. */
|
|
export function checkQuickCreateFieldsCliVersion(
|
|
detected: string | undefined | null,
|
|
): CliVersionCheck {
|
|
return checkCliVersion(detected, MIN_QUICK_CREATE_FIELDS_CLI_VERSION);
|
|
}
|
|
|
|
function checkCliVersion(
|
|
detected: string | undefined | null,
|
|
minimum: string,
|
|
): CliVersionCheck {
|
|
const current = (detected ?? "").trim();
|
|
if (DEV_DESCRIBE_RE.test(current)) {
|
|
return { state: "ok", current, min: minimum };
|
|
}
|
|
const parsed = current ? parseSemver(current) : null;
|
|
if (!parsed) {
|
|
return { state: "missing", current, min: minimum };
|
|
}
|
|
const min = parseSemver(minimum)!;
|
|
if (lessThan(parsed, min)) {
|
|
return { state: "too_old", current, min: minimum };
|
|
}
|
|
return { state: "ok", current, min: minimum };
|
|
}
|
|
|
|
/** Pull `cli_version` off a runtime row's loosely-typed metadata bag. */
|
|
export function readRuntimeCliVersion(metadata: Record<string, unknown> | undefined): string {
|
|
const v = metadata?.cli_version;
|
|
return typeof v === "string" ? v : "";
|
|
}
|
|
|
|
/**
|
|
* Frontend mirror of the server's `MinHandoffCLIVersion` soft gate
|
|
* (`server/pkg/agent/version.go`). The assignment handoff note is only rendered
|
|
* into the run's opening prompt by daemons at or above this multica CLI version
|
|
* (MUL-3375); older daemons silently drop it. Unlike the quick-create gate this
|
|
* never blocks the assignment — the UI just grays out the note box and warns.
|
|
*
|
|
* Keep in lockstep with the server constant; the two are enforced independently
|
|
* (the server is authoritative) but must agree so the warning matches reality.
|
|
*/
|
|
export const MIN_HANDOFF_CLI_VERSION = "0.3.28";
|
|
|
|
/**
|
|
* Whether a daemon-reported CLI version is new enough to render a handoff note.
|
|
* Mirrors server `agent.HandoffSupported`: missing / unparsable / below-minimum
|
|
* all degrade to `false`, and dev-built daemons (git-describe shape) always
|
|
* pass — the version string is the shared signal, so frontend and server agree
|
|
* by construction. Pure and synchronous, so the note box can settle from the
|
|
* already-warm runtime cache instead of waiting on the trigger-preview
|
|
* round-trip, exactly like the quick-create version gate.
|
|
*/
|
|
export function handoffSupported(detected: string | undefined | null): boolean {
|
|
return meetsMinCliVersion(detected, MIN_HANDOFF_CLI_VERSION);
|
|
}
|
|
|
|
/**
|
|
* First release whose daemon renders the chat session's project context
|
|
* (description + resources) into the run brief (PR #5765, ships in v0.4.10).
|
|
* Older daemons still receive and honor the project's repos — the server
|
|
* pre-extracts those into the generic `repos` claim field — but silently skip
|
|
* the Project Context section, so the durable description never reaches the
|
|
* agent. SOFT gate: selecting a project always works; the UI only warns.
|
|
*
|
|
* Frontend-only constant: unlike handoff there is no server preview endpoint
|
|
* computing this, so there is no server twin to keep in lockstep with.
|
|
*/
|
|
export const MIN_CHAT_PROJECT_CONTEXT_CLI_VERSION = "0.4.10";
|
|
|
|
/**
|
|
* Whether a daemon-reported CLI version is new enough to inject a chat
|
|
* session's project description into the run brief. Same degrade rules as
|
|
* `handoffSupported`: missing / unparsable / below-minimum are `false`,
|
|
* dev-built daemons (git-describe shape) always pass.
|
|
*/
|
|
export function chatProjectContextSupported(detected: string | undefined | null): boolean {
|
|
return meetsMinCliVersion(detected, MIN_CHAT_PROJECT_CONTEXT_CLI_VERSION);
|
|
}
|
|
|
|
function meetsMinCliVersion(detected: string | undefined | null, minimum: string): boolean {
|
|
const current = (detected ?? "").trim();
|
|
if (!current) return false;
|
|
if (DEV_DESCRIBE_RE.test(current)) return true;
|
|
const parsed = parseSemver(current);
|
|
if (!parsed) return false;
|
|
return !lessThan(parsed, parseSemver(minimum)!);
|
|
}
|