fix(ui): wait for current guided runner settings

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-12 19:51:12 -05:00
1 parent 4440df2e44
commit 0d998fad49
3 files changed
+101 -3

No files matched your search

@@ -614,3 +614,38 @@ assertions remain unchanged. The full fake-transport suite passes 72 with one
existing skip; runner noemit and maps pass. Fresh exact-head runner CI remains
required after this test-only correction. No package campaign, paid provider
call, local native build, Docker build or upstream prerequisite edit.
## Guided creation with refreshed settings — 2026-10-12
Review 4253594222 on behavior head `4440df2e44e346080308e40ec3287c94c7f776a2`
identified stale cached environment selection during a background instance-settings
read. The mounted reproduction actually calls setup against cached SSH/Legacy
before the pending current settings select local/native, rather than merely
observing a loading label. Guided Claude/Codex setup now requires current completed
environment, settings, execution policy, experimental policy and matching Auto
runner discovery reads before testing or hiring. Existing connection controls stay
mounted inside a disabled fieldset while refreshing, preserving an API-key draft.
Both the cached SSH-to-local case and same-environment discovery refresh pass;
all 65 guided setup controls, UI noemit, token gates and structural maps pass.
Logs: `/private/tmp/claude-guided-current-settings-before.log`,
`/private/tmp/claude-guided-current-settings-draft-controls.log`, and
`/private/tmp/claude-guided-current-settings-final-{types,tokens,maps}.log`.
This repair is prepared locally while the source owner integrates the common
transport correction; published behavior head remains the literal 4440 revision.
No paid turns, local native builds or Docker builds were performed.
Platform head `173a73ec95386d7b9eac50d87699a16ec27da3b3` failed a credential
fixture during its two-port reservation (EADDRINUSE before the production staging
call). The existing bounded three-port fixture reservation helper is extended to
hold two candidates, retrying fixture preparation only and never a production
staging call. A foreign-collision control verifies that foreign ownership survives,
the auth sentinel stays unchanged, and preparation stops within eight attempts.
All 42 credential controls and runner noemit pass locally after supplying the
worktree's declared tsx test loader. Logs:
`/private/tmp/claude-quorum-fixture-complete-controls.log` and
`/private/tmp/claude-quorum-fixture-final-types.log`. This test-only correction
is prepared in the protocol worktree and has not been published during the parent
integration hold. The independent serialized issue-route timeout remains under
phase diagnosis; a subsequent full mocked suite passes 36 controls but does not
waive the cloud failure or prove its latency cause. Final source composition,
required CI and the provider-free cloud packaging campaign remain pending.
+11 -3
View File
@@ -340,7 +340,7 @@ function Setup({
!adapter.disabled &&
!getAdapterDisplay(adapterType).comingSoon,
) && (requestAdapterType !== "paperclip_runner" || adapterType === "paperclip_runner" || adapterInventory.data?.some(adapter => adapter.type === "paperclip_runner" && adapter.loaded && !adapter.disabled));
const ready = Boolean(
const connectionSettingsReady = Boolean(
available &&
(!["codex_local", "claude_local"].includes(adapterType) || runner !== "auto" || Boolean(adapters.data)) &&
!environmentError &&
@@ -358,6 +358,14 @@ function Setup({
!caps.error &&
(!(managedOnly || forced.forced) || environmentId),
);
const needsCurrentSetup = ["codex_local", "claude_local"].includes(adapterType);
const ready = connectionSettingsReady && (!needsCurrentSetup || (
envs.isSuccess && !envs.isFetching
&& settings.isSuccess && !settings.isFetching
&& experimental.isSuccess && !experimental.isFetching
&& general.isSuccess && !general.isFetching
&& (runner !== "auto" || (adapters.isSuccess && !adapters.isFetching))
));
const busy = testState === "running" || saving;
function buildConfig(
@@ -762,7 +770,7 @@ function Setup({
center
/>
</div>
{ready ? <AgentProviderConnection
{connectionSettingsReady ? <fieldset disabled={!ready} className="contents"><AgentProviderConnection
key={environmentId ?? "local"}
companyId={companyId}
adapterType={connectionAdapter}
@@ -814,7 +822,7 @@ function Setup({
setRuntimeAiBinding(undefined);
setScreen("runtime");
}}
/> : <p role="status" className="text-sm text-muted-foreground">Loading connection settings…</p>}
/></fieldset> : <p role="status" className="text-sm text-muted-foreground">Loading connection settings…</p>}
{["codex_local", "claude_local"].includes(adapterType) && <details className="mt-5 space-y-3">
<summary className="cursor-pointer text-sm text-muted-foreground">Advanced</summary>
<AgentRunnerSelect value={runner} disabled={busy}
+55
View File
@@ -200,6 +200,61 @@ afterEach(async () => {
container.remove();
});
describe("New agent setup", () => {
it("waits for the current instance default instead of testing cached SSH discovery", async () => {
let finish!: (value: object) => void;
settings.get.mockReturnValue(new Promise<object>(resolve => { finish = resolve; }));
const environments = [
{ id: "local-1", name: "Local", driver: "local", config: {} },
{ id: "linux-ssh", name: "SSH", driver: "ssh", config: {} },
];
envApi.list.mockResolvedValue(environments);
cache.setQueryData(queryKeys.environments.list("company-1"), environments);
cache.setQueryData(queryKeys.instance.settings, { defaultEnvironmentId: "linux-ssh" });
adapterApi.list.mockImplementation(async (context?: { environmentId?: string | null }) => state.adapters.map((adapter: any) => adapter.type === "claude_local"
? { ...adapter, supportedRunners: ["paperclip", "legacy"], defaultRunner: context?.environmentId === "local-1" ? "paperclip" : "legacy" } : adapter));
await render("claude_local");
await connect("Claude");
expect(api.testEnvironment).not.toHaveBeenCalled();
expect(api.hire).not.toHaveBeenCalled();
await act(async () => finish({ defaultEnvironmentId: "local-1" }));
await settle();
await connect("Claude");
await click("Finish setup");
expect(api.testEnvironment).toHaveBeenCalledTimes(1);
expect(api.testEnvironment.mock.calls[0][2].environmentId).toBe("local-1");
expect(api.hire.mock.calls[0][1].adapterConfig).not.toHaveProperty("maxTurnsPerRun");
});
it("keeps the API key draft while waiting for refreshed discovery", async () => {
state.adapters = state.adapters.map((adapter: any) => adapter.type === "claude_local"
? { ...adapter, defaultRunner: "paperclip", supportedRunners: ["paperclip", "legacy"] } : adapter);
await render("claude_local");
await click("ClaudeAPI key");
await fill("API key", "example-test-secret");
const draft = container.querySelector('[aria-label="API key"]') as HTMLInputElement;
let finish!: (value: object[]) => void;
adapterApi.list.mockReturnValue(new Promise<object[]>(resolve => { finish = resolve; }));
let refresh!: Promise<void>;
await act(async () => {
refresh = cache.invalidateQueries({ queryKey: queryKeys.adapters.availability("company-1", "local-1") });
});
await settle();
expect(container.querySelector('[aria-label="API key"]')).toBe(draft);
expect(draft.value).toBe("example-test-secret");
await click("Connect");
expect(managedApi.create).not.toHaveBeenCalled();
expect(api.testEnvironment).not.toHaveBeenCalled();
await act(async () => { finish(state.adapters); await refresh; });
await settle();
expect(container.querySelector('[aria-label="API key"]')).toBe(draft);
expect(draft.value).toBe("example-test-secret");
await click("Connect");
await click("Finish setup");
expect(api.testEnvironment).toHaveBeenCalledTimes(1);
expect(api.hire).toHaveBeenCalledTimes(1);
expect(api.hire.mock.calls[0][1].adapterConfig).not.toHaveProperty("maxTurnsPerRun");
});
it.each(["paperclip", "legacy", undefined] as const)("uses Claude's discovered %s default for Auto setup and hire", async (defaultRunner) => {
settings.getExperimental.mockResolvedValue({ enableNativeRunner: false });
state.adapters = (state.adapters as any[]).map(adapter => adapter.type === "claude_local"