()
| 406 | !updateMutation.isPending; |
| 407 | |
| 408 | async function handleSubmit() { |
| 409 | try { |
| 410 | const parsedParallelism = Number.parseInt(parallelism, 10); |
| 411 | const parsedTimeout = Number.parseInt(turnTimeoutSeconds, 10); |
| 412 | const parsedArgs = agentArgs |
| 413 | .split(",") |
| 414 | .map((v) => v.trim()) |
| 415 | .filter((v) => v.length > 0); |
| 416 | const normalizedModel = model.trim() || null; |
| 417 | const normalizedProvider = provider.trim() || null; |
| 418 | |
| 419 | // Harness pin resolution. The backend treats an empty string as the |
| 420 | // "inherit from persona" sentinel (clears the override) and any concrete |
| 421 | // command as an explicit pin. When inheriting, only send the sentinel if |
| 422 | // there's a pin to clear — a name-only edit must leave the record alone. |
| 423 | // When pinning, send the command only if it diverges from the resolved |
| 424 | // value the dialog opened with, so an unchanged save stays a no-op. |
| 425 | const agentCommandUpdate = inheritHarness |
| 426 | ? agent.agentCommandOverride != null |
| 427 | ? "" |
| 428 | : undefined |
| 429 | : agentCommand.trim() !== agent.agentCommand |
| 430 | ? agentCommand.trim() |
| 431 | : undefined; |
| 432 | |
| 433 | // Derive the effective runtime at submit time — the one that will |
| 434 | // actually run AFTER submit. When pinned (inheritHarness=false), it's |
| 435 | // the live dropdown selection. When inheriting, match agent.agentCommand |
| 436 | // first by command (the normal path), then fall back to id-match for |
| 437 | // runtimes where the adapter is missing (command:null in the catalog). |
| 438 | // The id of a known runtime is stable even when its adapter binary is |
| 439 | // absent, so id-fallback lets us classify capability correctly without |
| 440 | // treating a "known adapter missing" as "completely unknown runtime." |
| 441 | const effectiveRuntimeIdForSubmit = inheritHarness |
| 442 | ? (runtimes.find((r) => r.command?.trim() === agent.agentCommand.trim()) |
| 443 | ?.id ?? |
| 444 | // Fallback: id-based match for command:null catalog entries (adapter |
| 445 | // missing but runtime is known and its capability is still static). |
| 446 | runtimes.find((r) => r.id === agent.agentCommand.trim())?.id ?? |
| 447 | "") |
| 448 | : (selectedRuntime?.id ?? selectedRuntimeId); |
| 449 | |
| 450 | // Classify the effective runtime's provider capability as a tri-state so |
| 451 | // the provider submit branch can distinguish "known-locked" (clear) from |
| 452 | // "unknown" (omit). Clearing must ONLY happen when we KNOW the runtime is |
| 453 | // provider-locked (e.g. Claude). When capability is unknown — because the |
| 454 | // catalog is still loading, the query errored, or the inherited command |
| 455 | // matched nothing — we OMIT the field rather than sending null, so a |
| 456 | // transient discovery/loading state never becomes a destructive write. |
| 457 | type ProviderRuntimeCapability = "capable" | "locked" | "unknown"; |
| 458 | const matchedCatalogEntry = |
| 459 | effectiveRuntimeIdForSubmit.length > 0 |
| 460 | ? runtimes.find((r) => r.id === effectiveRuntimeIdForSubmit) |
| 461 | : undefined; |
| 462 | const providerRuntimeCapability: ProviderRuntimeCapability = |
| 463 | matchedCatalogEntry === undefined |
| 464 | ? "unknown" |
| 465 | : runtimeSupportsLlmProviderSelection(matchedCatalogEntry.id) |
no test coverage detected