Files
kandev/apps/web/lib/api/domains/workspace-api.ts
T
José Almeida 89332e1cbb fix: render passthrough terminal in quick chat (#744)
* fix(backend): start agent immediately for passthrough quick-chat sessions

Passthrough profiles need a running PTY for the terminal to attach. When
launchPrepare is called with a passthrough profile, upgrade the request to
launchStart so the PTY exists by the time the frontend renders the terminal.

* fix(web): render passthrough terminal in quick chat for passthrough profiles

Quick chat used MessageList + ChatInputArea unconditionally, so passthrough
profiles showed an empty chat with no agent output (PTY never attached).
Render PassthroughTerminal when the active session is passthrough, mirroring
the task-page branch. Passthrough is detected via session.is_passthrough or,
before WS hydrates, via the agent profile's cli_passthrough flag — looked up
through a new agentProfileId stored on quickChat session entries (set by the
picker on selection and seeded by SSR for restored sessions).

* style(web): apply prettier formatting

* fix(web): address review feedback on quick-chat passthrough rendering

- Narrow `metadata.agent_profile_id` to string at runtime via a
  `readAgentProfileId` helper instead of a blind type cast.
- Collapse `useIsQuickChatPassthrough` into a single `useShallow`
  selector to avoid three separate store subscriptions.

* fix(backend): prevent launchPrepare ↔ launchStart recursion for passthrough

When passthrough quick-chat sessions hit a workflow step that blocks
auto-start, launchStart's downgrade to launchPrepare would have re-upgraded
back into launchStart and looped forever. Skip the passthrough upgrade when
AutoStart=true, since that flag uniquely identifies the downgrade path.

Adds tests for `isPassthroughProfile` and a regression test that runs the
launchStart→launchPrepare path under a deadline so future recursions surface.

* test(web): cover existing agentProfileId preservation on reopen

* style(web): apply prettier formatting

* test(backend): seed task with blocked workflow step in recursion regression

Without persisted task and step, shouldBlockAutoStart short-circuited to
false, the test bypassed the launchStart→launchPrepare downgrade entirely,
and removing the recursion guard would still pass. Seed the task with a
WorkflowStepID mapping to a step lacking auto_start_agent so the guard is
the only thing preventing the bounce. Verified by temporarily removing the
guard — the test now times out as expected.

* test(backend): cover ResolveAgentProfile error path in isPassthroughProfile
2026-04-30 18:36:26 +01:00

117 lines
3.2 KiB
TypeScript

import { fetchJson, type ApiRequestOptions } from "../client";
import type {
ListWorkspacesResponse,
ListRepositoriesResponse,
RepositoryBranchesResponse,
ListRepositoryScriptsResponse,
Workspace,
} from "@/lib/types/http";
// Workspace operations
export async function createWorkspace(
payload: { name: string; description?: string },
options?: ApiRequestOptions,
) {
return fetchJson<Workspace>("/api/v1/workspaces", {
...options,
init: { method: "POST", body: JSON.stringify(payload), ...(options?.init ?? {}) },
});
}
export async function listWorkspaces(options?: ApiRequestOptions) {
return fetchJson<ListWorkspacesResponse>("/api/v1/workspaces", options);
}
// Repository operations
export async function listRepositories(
workspaceId: string,
params?: { includeScripts?: boolean },
options?: ApiRequestOptions,
) {
const searchParams = new URLSearchParams();
if (params?.includeScripts) {
searchParams.set("include_scripts", "true");
}
const queryString = searchParams.toString();
const url = `/api/v1/workspaces/${workspaceId}/repositories${queryString ? `?${queryString}` : ""}`;
return fetchJson<ListRepositoriesResponse>(url, options);
}
export async function listRepositoryBranches(repositoryId: string, options?: ApiRequestOptions) {
return fetchJson<RepositoryBranchesResponse>(
`/api/v1/repositories/${repositoryId}/branches`,
options,
);
}
export async function listRepositoryScripts(repositoryId: string, options?: ApiRequestOptions) {
return fetchJson<ListRepositoryScriptsResponse>(
`/api/v1/repositories/${repositoryId}/scripts`,
options,
);
}
// Quick Chat operations
export type StartQuickChatRequest = {
title?: string;
repository_id?: string;
agent_profile_id?: string;
executor_id?: string;
prompt?: string;
local_path?: string;
repository_name?: string;
default_branch?: string;
base_branch?: string;
};
export type StartQuickChatResponse = {
task_id: string;
session_id: string;
};
export async function startQuickChat(
workspaceId: string,
payload: StartQuickChatRequest,
options?: ApiRequestOptions,
) {
return fetchJson<StartQuickChatResponse>(`/api/v1/workspaces/${workspaceId}/quick-chat`, {
...options,
init: { method: "POST", body: JSON.stringify(payload), ...(options?.init ?? {}) },
});
}
export async function listQuickChatSessions(workspaceId: string, options?: ApiRequestOptions) {
return fetchJson<{
tasks: Array<{
id: string;
title: string;
workspace_id: string;
primary_session_id?: string | null;
metadata?: Record<string, unknown> | null;
}>;
}>(`/api/v1/workspaces/${workspaceId}/tasks?only_ephemeral=true&exclude_config=true`, options);
}
// Config Chat operations
export type StartConfigChatRequest = {
agent_profile_id?: string;
executor_id?: string;
prompt?: string;
};
export type StartConfigChatResponse = {
task_id: string;
session_id: string;
};
export async function startConfigChat(
workspaceId: string,
payload: StartConfigChatRequest,
options?: ApiRequestOptions,
) {
return fetchJson<StartConfigChatResponse>(`/api/v1/workspaces/${workspaceId}/config-chat`, {
...options,
init: { method: "POST", body: JSON.stringify(payload), ...(options?.init ?? {}) },
});
}