Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions apps/web/src/agent-deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> {
const body: unknown = await response.json().catch(() => undefined);
Expand Down Expand Up @@ -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" },
},
Expand Down
8 changes: 7 additions & 1 deletion apps/web/src/pages/agents-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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 (
Expand Down
13 changes: 11 additions & 2 deletions apps/web/src/pages/chat-thread-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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";
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down
5 changes: 5 additions & 0 deletions apps/web/src/shell/first-run-tour.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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={{
Expand Down
11 changes: 11 additions & 0 deletions apps/web/src/workbench-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,17 @@ export async function createWorkbench(input: CreateWorkbenchInput): Promise<stri
return tenantId;
}

const GENERIC_RESTART_FAILURE = "Couldn't restart this agent. Try again.";

/** Allow-lists what is safe to show verbatim: only the authored stage
* copy, never a raw request path or schema summary. */
export function describeRestartFailure(cause: unknown): string {
if (!(cause instanceof WorkbenchCreateError)) return GENERIC_RESTART_FAILURE;
return cause.stage === "deploy"
? "This agent couldn't be deployed. Try again."
: GENERIC_RESTART_FAILURE;
}

/** A hub restart releases every process-provisioned deployment; this
* redeploys one room agent through the same path `createWorkbench` used,
* re-running it against the tenant its asset already lives in. */
Expand Down
Loading