diff --git a/apps/web/src/agent-deploy.ts b/apps/web/src/agent-deploy.ts index 3d38dc915..60a09e470 100644 --- a/apps/web/src/agent-deploy.ts +++ b/apps/web/src/agent-deploy.ts @@ -19,7 +19,6 @@ const GitTokenMintShape = type({ id: "string", secret: "string" }); const TenantDomainShape = type({ domain: "string" }); const PUSH_TOKEN_LIFETIME_MS = 10 * 60 * 1000; -const AGENT_TURN_TIMEOUT_MS = 2 * 60 * 1000; async function readErrorBody(response: Response): Promise { const body: unknown = await response.json().catch(() => undefined); @@ -111,7 +110,9 @@ export function buildAgentDefinitionJson(args: { toolPackagePins: [], }, drainBehavior: "wait", - timeout: AGENT_TURN_TIMEOUT_MS, + // No `timeout`: it stays armed across an approval park, so any + // finite value aborts a run waiting on a person to answer an + // ask-gated tool call (see agents/myra/src/index.ts). triggers: "unbounded", input: { from: "trigger.payload" }, }, diff --git a/apps/web/src/pages/agents-page.tsx b/apps/web/src/pages/agents-page.tsx index e9f54442f..fa2e38568 100644 --- a/apps/web/src/pages/agents-page.tsx +++ b/apps/web/src/pages/agents-page.tsx @@ -17,14 +17,16 @@ import { TableHead, TableHeader, TableRow, + toast, } from "@corbits/react-ui"; import { Robot } from "@/lib/icons"; import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { reportError } from "@corbits/error-sink"; import { QueryView } from "@/lib/api-query"; import { chatKeys, chatPath } from "../chat-path"; import { isAgentNotRunning, listChatAgents, type ChatAgent } from "@/chat/threads-api"; -import { redeployRoomAgent } from "../workbench-create"; +import { describeRestartFailure, redeployRoomAgent } from "../workbench-create"; import { useBench } from "../bench-context"; import { Link } from "../navigation"; import { useTenantQuery } from "../routines-api"; @@ -57,6 +59,10 @@ export function AgentsRosterList({ onSuccess: () => { void queryClient.invalidateQueries({ queryKey: chatKeys.agents(tenantId) }); }, + onError: (cause) => { + reportError(cause, { operation: "agent_restart", tenantId }); + toast(describeRestartFailure(cause)); + }, }); if (agents.length === 0) { return ( diff --git a/apps/web/src/pages/chat-thread-page.tsx b/apps/web/src/pages/chat-thread-page.tsx index 1e449358e..559822140 100644 --- a/apps/web/src/pages/chat-thread-page.tsx +++ b/apps/web/src/pages/chat-thread-page.tsx @@ -2,10 +2,11 @@ // first message (agent picked in the select, or by @tagging one in the // message itself); `/chats/:id` is the transcript plus a reply box. -import { Button, EmptyState, PageShell, Select } from "@corbits/react-ui"; +import { Button, EmptyState, PageShell, Select, toast } from "@corbits/react-ui"; import { WarningCircle } from "@/lib/icons"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useEffect, useMemo, useState } from "react"; +import { reportError } from "@corbits/error-sink"; import { ApprovalRow } from "@/chat/approval-row"; import { IdentityAvatar } from "@/chat/avatar"; @@ -25,7 +26,7 @@ import { type ChatAgent, } from "@/chat/threads-api"; import { MYRA_SOURCE_CONFIG } from "../myra-source"; -import { redeployRoomAgent } from "../workbench-create"; +import { describeRestartFailure, redeployRoomAgent } from "../workbench-create"; import { useBench } from "../bench-context"; import { chatIdFromPath, chatKeys, chatPath, NEW_CHAT_PATH } from "../chat-path"; import { usePendingApprovals } from "../pending-approvals"; @@ -74,6 +75,10 @@ function NewChat({ onSuccess: () => { void queryClient.invalidateQueries({ queryKey: chatKeys.agents(tenantId) }); }, + onError: (cause) => { + reportError(cause, { operation: "agent_restart", tenantId }); + toast(describeRestartFailure(cause)); + }, }); const defaultAgent = agents.find((agent) => agent.name === MYRA_SOURCE_CONFIG.displayName); @@ -207,6 +212,10 @@ function ChatTranscript({ void queryClient.invalidateQueries({ queryKey: chatKeys.agents(tenantId) }); void queryClient.invalidateQueries({ queryKey: chatKeys.one(tenantId, chatId) }); }, + onError: (cause) => { + reportError(cause, { operation: "agent_restart", tenantId }); + toast(describeRestartFailure(cause)); + }, }); if (chatQuery.isError && chat === undefined) { diff --git a/apps/web/src/shell/first-run-tour.tsx b/apps/web/src/shell/first-run-tour.tsx index 213356bf8..175148b5b 100644 --- a/apps/web/src/shell/first-run-tour.tsx +++ b/apps/web/src/shell/first-run-tour.tsx @@ -86,6 +86,11 @@ export function FirstRunTour({ userId }: { readonly userId: string }) { continuous showSkipButton disableOverlayClose + // The overlay otherwise swallows every click outside the spotlight — + // real app chrome (e.g. a roster row's Chat link) is reachable at + // any point in the shell, tour running or not, so clicks must pass + // through to it rather than land on the tour's own backdrop. + spotlightClicks spotlightPadding={6} callback={handleCallback} styles={{ diff --git a/apps/web/src/workbench-create.ts b/apps/web/src/workbench-create.ts index 65842658c..604917d1d 100644 --- a/apps/web/src/workbench-create.ts +++ b/apps/web/src/workbench-create.ts @@ -124,6 +124,17 @@ export async function createWorkbench(input: CreateWorkbenchInput): Promise