diff --git a/VENDORED.md b/VENDORED.md index cceef1cc7..dced992a5 100644 --- a/VENDORED.md +++ b/VENDORED.md @@ -27,22 +27,22 @@ Pinned to [faremeter/interchange](https://github.com/faremeter/interchange) | Vendored path | What it is | Owner | Kill date | | ------------------------------ | ------------------------------------------------------------------------ | ------ | ---------- | -| `apps/sidecar` | Copy of upstream's sidecar app; apps are never npm-published. Two local deltas: `workflow-substrate-factory.ts` registers `@corbits/credential-header`'s and `@corbits/credential-mcp`'s providers alongside `builtinCredentialProviders()`; `bin/workflow-child` and `bin/workflow-probe-child` run under plain `bun` instead of upstream's `intx-src` resolve condition | sawyer | 2026-11-03 | -| `vendor/intx/agent` | `@intx/agent` at a newer commit than npm has published | sawyer | 2026-11-03 | -| `vendor/intx/db` | `@intx/db`, with one local serving-time credential-refresh delta | sawyer | 2026-11-03 | -| `vendor/intx/harness` | `@intx/harness` at a newer commit than npm has published | sawyer | 2026-11-03 | -| `vendor/intx/hub-agent` | `@intx/hub-agent`, with a local OAuth-login-over-websocket delta | sawyer | 2026-11-03 | -| `vendor/intx/hub-api` | `@intx/hub-api`, with local approval and workflow-deploy-bearer routes | sawyer | 2026-11-03 | -| `vendor/intx/hub-sessions` | `@intx/hub-sessions`, with several local run/pack-acceptance fixes | sawyer | 2026-11-03 | -| `vendor/intx/inference` | `@intx/inference`, with one local Google file-upload compile fix | sawyer | 2026-11-03 | -| `vendor/intx/mail-memory` | `@intx/mail-memory` at a newer commit than npm has published | sawyer | 2026-11-03 | -| `vendor/intx/mailbox` | `@intx/mailbox`, never yet published to npm | sawyer | 2026-11-03 | -| `vendor/intx/mime` | `@intx/mime` at a newer commit than npm has published | sawyer | 2026-11-03 | -| `vendor/intx/types` | `@intx/types`, with one local model-provider-plugin enum addition | sawyer | 2026-11-03 | -| `vendor/intx/hub-common` | `@intx/hub-common` at a newer commit than npm has published | sawyer | 2026-11-03 | -| `vendor/intx/workflow` | `@intx/workflow`, with one local step-timeout-budget delta | sawyer | 2026-11-03 | -| `vendor/intx/workflow-deploy` | `@intx/workflow-deploy` at a newer commit than npm has published | sawyer | 2026-11-03 | -| `vendor/intx/workflow-host` | `@intx/workflow-host`, with one local step-grants-collapse delta | sawyer | 2026-11-03 | +| `apps/sidecar` | Copy of upstream's sidecar app; apps are never npm-published. Two local deltas: `workflow-substrate-factory.ts` registers `@corbits/credential-header`'s and `@corbits/credential-mcp`'s providers alongside `builtinCredentialProviders()`; `bin/workflow-child` and `bin/workflow-probe-child` run under plain `bun` instead of upstream's `intx-src` resolve condition | maintainers | 2026-11-03 | +| `vendor/intx/agent` | `@intx/agent` at a newer commit than npm has published | maintainers | 2026-11-03 | +| `vendor/intx/db` | `@intx/db`, with one local serving-time credential-refresh delta | maintainers | 2026-11-03 | +| `vendor/intx/harness` | `@intx/harness` at a newer commit than npm has published | maintainers | 2026-11-03 | +| `vendor/intx/hub-agent` | `@intx/hub-agent`, with a local OAuth-login-over-websocket delta | maintainers | 2026-11-03 | +| `vendor/intx/hub-api` | `@intx/hub-api`, with local approval and workflow-deploy-bearer routes | maintainers | 2026-11-03 | +| `vendor/intx/hub-sessions` | `@intx/hub-sessions`, with several local run/pack-acceptance fixes | maintainers | 2026-11-03 | +| `vendor/intx/inference` | `@intx/inference`, with one local Google file-upload compile fix | maintainers | 2026-11-03 | +| `vendor/intx/mail-memory` | `@intx/mail-memory` at a newer commit than npm has published | maintainers | 2026-11-03 | +| `vendor/intx/mailbox` | `@intx/mailbox`, never yet published to npm | maintainers | 2026-11-03 | +| `vendor/intx/mime` | `@intx/mime` at a newer commit than npm has published | maintainers | 2026-11-03 | +| `vendor/intx/types` | `@intx/types`, with one local model-provider-plugin enum addition | maintainers | 2026-11-03 | +| `vendor/intx/hub-common` | `@intx/hub-common` at a newer commit than npm has published | maintainers | 2026-11-03 | +| `vendor/intx/workflow` | `@intx/workflow`, with one local step-timeout-budget delta | maintainers | 2026-11-03 | +| `vendor/intx/workflow-deploy` | `@intx/workflow-deploy` at a newer commit than npm has published | maintainers | 2026-11-03 | +| `vendor/intx/workflow-host` | `@intx/workflow-host`, with one local step-grants-collapse delta | maintainers | 2026-11-03 | npm's published `0.3.0` predates this pin for every row above, so each is re-vendored at the same commit rather than mixed pins; the root diff --git a/apps/web/src/agents-api.ts b/apps/web/src/agents-api.ts index 13c080835..a31c4cdba 100644 --- a/apps/web/src/agents-api.ts +++ b/apps/web/src/agents-api.ts @@ -12,7 +12,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { ApiQueryError } from "@/lib/api-query"; import { deployAgentSource, type DeployedAgent, type NewAgentInput } from "./agent-deploy"; -import { chatKeys, roomKeys } from "./chat-path"; +import { chatKeys, workbenchKeys } from "./chat-path"; import { tenantKeys } from "./query-client"; export type AgentDefinition = typeof WorkflowDefinitionResponse.infer; @@ -91,8 +91,8 @@ export function useDeployAgentMutation(tenantId: string) { void queryClient.invalidateQueries({ queryKey: tenantKeys.agentDirectory(tenantId) }); void queryClient.invalidateQueries({ queryKey: tenantKeys.visibleAgents(tenantId) }); void queryClient.invalidateQueries({ queryKey: chatKeys.agents(tenantId) }); - // A deploy from a room transcript adds a participant to that room. - void queryClient.invalidateQueries({ queryKey: roomKeys.participants(tenantId) }); + // A deploy from a workbench transcript adds a participant to that workbench. + void queryClient.invalidateQueries({ queryKey: workbenchKeys.participants(tenantId) }); }, }); } diff --git a/apps/web/src/api.ts b/apps/web/src/api.ts index 2fe9517af..328a326e6 100644 --- a/apps/web/src/api.ts +++ b/apps/web/src/api.ts @@ -186,7 +186,7 @@ export function rejectApproval( /** * One-shot fetch of `GET /api/tenants/:id` — the only place `parentId` - * comes from. A bench is a top-level tenant (`parentId === null`); a room + * comes from. A bench is a top-level tenant (`parentId === null`); a workbench * is a named child tenant, so the raw-id/name heuristic can never tell them * apart. `bench-context.tsx` fans this out per membership with * `useQueries` to decide which memberships are benches. diff --git a/apps/web/src/app.css b/apps/web/src/app.css index 9252f4811..1abf0f6d7 100644 --- a/apps/web/src/app.css +++ b/apps/web/src/app.css @@ -2540,7 +2540,7 @@ tr.insights-row-clickable:hover { } /* Dashboard-density header/cell treatment for the insights data tables — - scoped to this wrapper class so react-ui's roomier Table defaults stand + scoped to this wrapper class so react-ui's more spacious Table defaults stand everywhere else. */ .insights-data-table th { font-size: 0.68rem; @@ -3613,48 +3613,48 @@ tr.insights-row-clickable:hover { } } -/* A workbench room's left info column: activity, artifacts, approvals and +/* A workbench workbench's left info column: activity, artifacts, approvals and participants, stacked as quiet panels beside the running chat. */ -.room-info-panel { +.workbench-info-panel { padding: 0.9rem 1rem; border: 1px solid color-mix(in srgb, var(--foreground) 10%, transparent); background: var(--card, var(--background)); min-width: 0; } -.room-info-panel + .room-info-panel { +.workbench-info-panel + .workbench-info-panel { margin-top: 1rem; } -.room-info-panel-header { +.workbench-info-panel-header { display: flex; align-items: baseline; gap: 0.6rem; margin-bottom: 0.6rem; } -.room-info-panel-header h2 { +.workbench-info-panel-header h2 { margin: 0; font-size: 0.85rem; font-weight: 700; } -.room-info-empty-note { +.workbench-info-empty-note { margin: 0; font-size: 0.8rem; color: var(--muted-foreground); } -.room-info-cell-primary { +.workbench-info-cell-primary { font-size: 0.85rem; font-weight: 700; } -.room-info-cell-context { +.workbench-info-cell-context { font-size: 0.7rem; color: var(--muted-foreground); } -.room-info-row-actions { +.workbench-info-row-actions { display: flex; justify-content: flex-end; gap: 0.4rem; } -.room-info-approval-list, -.room-info-artifact-list { +.workbench-info-approval-list, +.workbench-info-artifact-list { display: flex; flex-direction: column; gap: 0.6rem; @@ -3662,7 +3662,7 @@ tr.insights-row-clickable:hover { padding: 0; list-style: none; } -.room-info-approval-row { +.workbench-info-approval-row { display: flex; flex-direction: column; gap: 0.4rem; @@ -3870,15 +3870,15 @@ tr.insights-row-clickable:hover { } } -/* A workbench room: a left info column (activity, artifacts, approvals, +/* A workbench workbench: a left info column (activity, artifacts, approvals, participants), the running chat in the main column, and an optional sub-thread panel on the right. */ -.room-layout { +.workbench-layout { display: flex; min-height: 0; flex: 1; } -.room-info-column { +.workbench-info-column { display: flex; flex-direction: column; width: 18rem; @@ -3889,28 +3889,28 @@ tr.insights-row-clickable:hover { border-right: 1px solid var(--border); } @media (max-width: 1100px) { - .room-info-column { + .workbench-info-column { display: none; } } -.room-main { +.workbench-main { display: flex; flex-direction: column; flex: 1; min-width: 0; min-height: 0; } -.room-main-scroll { +.workbench-main-scroll { flex: 1; min-height: 0; overflow-y: auto; } -.room-main-composer { +.workbench-main-composer { flex-shrink: 0; border-top: 1px solid var(--border); padding: 0.75rem 0; } -.room-participants { +.workbench-participants { display: flex; flex-wrap: wrap; gap: 0.5rem; @@ -3920,12 +3920,12 @@ tr.insights-row-clickable:hover { font-size: 0.82rem; color: var(--muted-foreground); } -.room-participants > li { +.workbench-participants > li { display: flex; align-items: center; gap: 0.35rem; } -.room-replies-link { +.workbench-replies-link { display: block; margin-top: 0.35rem; padding: 0; @@ -3935,10 +3935,10 @@ tr.insights-row-clickable:hover { color: var(--muted-foreground); cursor: pointer; } -.room-replies-link:hover { +.workbench-replies-link:hover { color: var(--foreground); } -.room-subthread { +.workbench-subthread { display: flex; flex-direction: column; width: 22rem; @@ -3947,13 +3947,13 @@ tr.insights-row-clickable:hover { padding: 1rem; border-left: 1px solid var(--border); } -.room-subthread-head { +.workbench-subthread-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.75rem; } -.room-subthread-head > h2 { +.workbench-subthread-head > h2 { margin: 0; font-size: 0.95rem; font-weight: 600; diff --git a/apps/web/src/auto-workbench-title.ts b/apps/web/src/auto-workbench-title.ts index fed5ef361..ab08374ea 100644 --- a/apps/web/src/auto-workbench-title.ts +++ b/apps/web/src/auto-workbench-title.ts @@ -1,6 +1,6 @@ // ad-hoc ("Just start talking") benches mint as "New Workbench" and // used to stay that way in the sidebar. Prefab templates already name the -// room after the template title; this helper turns the person's first message +// workbench after the template title; this helper turns the person's first message // into a short sidebar title so blank benches get the same treatment once // that message is known (at create) — without inventing a second rename API. // Callers apply the result through `patchWorkbenchSettings` the same way the @@ -34,7 +34,7 @@ export function titleFromFirstMessage( /** * What an ad-hoc auto-name should PATCH as `chat/name`: the derived title when - * the room is still the generic "New Workbench" placeholder, otherwise + * the workbench is still the generic "New Workbench" placeholder, otherwise * `undefined` so callers leave prefab (and already-renamed) titles alone. */ export function autoNameFromFirstMessage( diff --git a/apps/web/src/bench-context-value.ts b/apps/web/src/bench-context-value.ts index 53ff8f923..9fdc44238 100644 --- a/apps/web/src/bench-context-value.ts +++ b/apps/web/src/bench-context-value.ts @@ -17,7 +17,7 @@ export type BenchState = { * top-level tenant (`parentId === null`, per `GET /api/tenants/:id`). * Empty while any membership's tenant detail is still loading — every * consumer that used to filter `memberships` with `isBenchMembership` - * reads this instead, so a room can never sneak into a bench list. */ + * reads this instead, so a workbench can never sneak into a bench list. */ readonly benchMemberships: readonly Principal[]; readonly selectedTenantId: string | null; readonly selectedPrincipalId: string | null; diff --git a/apps/web/src/bench-context.test.ts b/apps/web/src/bench-context.test.ts index 0de020047..3a26dbe0b 100644 --- a/apps/web/src/bench-context.test.ts +++ b/apps/web/src/bench-context.test.ts @@ -16,31 +16,31 @@ function membership(overrides: Partial & { tenantId: string }): Princ } /** `null` = a top-level tenant (a bench); a string = its parent's id (a - * room, or any other child tenant). Missing entries model a tenant whose + * workbench, or any other child tenant). Missing entries model a tenant whose * detail hasn't loaded yet. */ function parents(entries: Record): ReadonlyMap { return new Map(Object.entries(entries)); } describe("resolveSelection", () => { - test("a bench sorting first wins over a room, regardless of name", () => { + test("a bench sorting first wins over a workbench, regardless of name", () => { const memberships = [ membership({ tenantId: "tnt_bench", tenantName: "Growth Team" }), - membership({ tenantId: "tnt_room", tenantName: "Launch Planning" }), + membership({ tenantId: "tnt_workbench", tenantName: "Launch Planning" }), ]; - const parentByTenantId = parents({ tnt_bench: null, tnt_room: "tnt_bench" }); + const parentByTenantId = parents({ tnt_bench: null, tnt_workbench: "tnt_bench" }); const resolved = resolveSelection(memberships, null, parentByTenantId); expect(resolved?.tenantId).toBe("tnt_bench"); }); - test("a room sorting first is skipped in favor of the first bench", () => { + test("a workbench sorting first is skipped in favor of the first bench", () => { const memberships = [ - membership({ tenantId: "tnt_room", tenantName: "Launch Planning" }), + membership({ tenantId: "tnt_workbench", tenantName: "Launch Planning" }), membership({ tenantId: "tnt_bench", tenantName: "Growth Team" }), ]; - const parentByTenantId = parents({ tnt_room: "tnt_bench", tnt_bench: null }); + const parentByTenantId = parents({ tnt_workbench: "tnt_bench", tnt_bench: null }); const resolved = resolveSelection(memberships, null, parentByTenantId); @@ -59,14 +59,14 @@ describe("resolveSelection", () => { expect(resolved?.tenantId).toBe("tnt_bench_b"); }); - test("a stored selection naming a room falls through to the first bench — a room can never be selected even when it is the stored id", () => { + test("a stored selection naming a workbench falls through to the first bench — a workbench can never be selected even when it is the stored id", () => { const memberships = [ - membership({ tenantId: "tnt_room" }), + membership({ tenantId: "tnt_workbench" }), membership({ tenantId: "tnt_bench" }), ]; - const parentByTenantId = parents({ tnt_room: "tnt_bench", tnt_bench: null }); + const parentByTenantId = parents({ tnt_workbench: "tnt_bench", tnt_bench: null }); - const resolved = resolveSelection(memberships, "tnt_room", parentByTenantId); + const resolved = resolveSelection(memberships, "tnt_workbench", parentByTenantId); expect(resolved?.tenantId).toBe("tnt_bench"); }); @@ -80,9 +80,9 @@ describe("resolveSelection", () => { expect(resolved?.tenantId).toBe("tnt_bench"); }); - test("undefined when every membership is a room", () => { - const memberships = [membership({ tenantId: "tnt_room" })]; - const parentByTenantId = parents({ tnt_room: "tnt_primary" }); + test("undefined when every membership is a workbench", () => { + const memberships = [membership({ tenantId: "tnt_workbench" })]; + const parentByTenantId = parents({ tnt_workbench: "tnt_primary" }); const resolved = resolveSelection(memberships, null, parentByTenantId); diff --git a/apps/web/src/bench-context.tsx b/apps/web/src/bench-context.tsx index 3494844f2..3fa92952d 100644 --- a/apps/web/src/bench-context.tsx +++ b/apps/web/src/bench-context.tsx @@ -38,8 +38,8 @@ function writeStoredTenantId(tenantId: string): void { /** True for a membership the shell may treat as a bench: a top-level * tenant, i.e. one whose `GET /api/tenants/:id` reports `parentId: null`. - * Rooms are named child tenants (`needs-converge.ts`'s `POST /api/tenants - * { parentId }`), so a name-based heuristic can never tell a room from a + * Workbenches are named child tenants (`needs-converge.ts`'s `POST /api/tenants + * { parentId }`), so a name-based heuristic can never tell a workbench from a * bench — only the tenant's own parent can. `parentByTenantId` holds * `undefined` for a tenant whose detail hasn't loaded yet, which this * treats as "not (yet known to be) a bench" rather than guessing. */ diff --git a/apps/web/src/chat-path.ts b/apps/web/src/chat-path.ts index 0c9faf4a3..4f89d8751 100644 --- a/apps/web/src/chat-path.ts +++ b/apps/web/src/chat-path.ts @@ -12,13 +12,13 @@ export const chatKeys = { childTenants: (tenantId: string) => ["tenant", tenantId, "child-tenants"] as const, }; -/** One key factory per room (a workbench child tenant), so a send - * invalidates the room's timeline and roster together. */ -export const roomKeys = { - scope: (tenantId: string) => ["room", tenantId] as const, - tenant: (tenantId: string) => ["room", tenantId, "tenant"] as const, - participants: (tenantId: string) => ["room", tenantId, "participants"] as const, - timeline: (tenantId: string) => ["room", tenantId, "timeline"] as const, +/** One key factory per workbench (a workbench child tenant), so a send + * invalidates the workbench's timeline and roster together. */ +export const workbenchKeys = { + scope: (tenantId: string) => ["workbench", tenantId] as const, + tenant: (tenantId: string) => ["workbench", tenantId, "tenant"] as const, + participants: (tenantId: string) => ["workbench", tenantId, "participants"] as const, + timeline: (tenantId: string) => ["workbench", tenantId, "timeline"] as const, }; export const CHATS_PATH_PREFIX = "/chats"; diff --git a/apps/web/src/chat/approval-row.tsx b/apps/web/src/chat/approval-row.tsx index e20a6c46e..506f060ea 100644 --- a/apps/web/src/chat/approval-row.tsx +++ b/apps/web/src/chat/approval-row.tsx @@ -1,5 +1,5 @@ // One pending approval with its approve/deny buttons. Shared by the -// workbench room's info column and the chat transcript, so a person can +// workbench workbench's info column and the chat transcript, so a person can // answer an agent's ask wherever they are looking when it lands. import { Button, formatRelativeTime, toast } from "@corbits/react-ui"; @@ -36,15 +36,15 @@ export function ApprovalRow({ const pending = resolveMutation.isPending ? resolveMutation.variables : null; return ( -
  • +
  • - {item.headline} + {item.headline}
    - + {item.agentName} · {formatRelativeTime(item.createdAt)}
    -
    +
    {approvals.length === 0 ? null : ( -
      +
        {approvals.map((item) => ( ))} diff --git a/apps/web/src/pages/new-workbench-picker.tsx b/apps/web/src/pages/new-workbench-picker.tsx index 99ce17d3c..89106f53f 100644 --- a/apps/web/src/pages/new-workbench-picker.tsx +++ b/apps/web/src/pages/new-workbench-picker.tsx @@ -1,6 +1,6 @@ // A new workbench is a child tenant plus the agents you put in it. This // screen is the prompt box: say what you want and hit Enter, or open an -// empty room and add people and agents as you go. +// empty workbench and add people and agents as you go. import { Button, toast } from "@corbits/react-ui"; import { useDismissablePopover } from "@corbits/react-ui/hooks/use-dismissable-popover"; @@ -37,7 +37,7 @@ export function describeWorkbenchCreateFailure(cause: unknown, refId?: string): const PROMPT_PLACEHOLDER = "What do you want your Workbench to do?"; const AGENT_LISTBOX_ID = "new-workbench-agent-listbox"; -/** A room's name is its opening ask, trimmed — an empty room is just +/** A workbench's name is its opening ask, trimmed — an empty workbench is just * "Workbench" until it is renamed. */ function workbenchName(prompt: string): string { const trimmed = prompt.trim(); @@ -129,8 +129,8 @@ export function NewWorkbenchPickerRoute() { ...(selectedTenantId !== null ? { tenantId: selectedTenantId } : {}), }); toast(describeWorkbenchCreateFailure(cause, refId)); - // The room exists once a later stage fails; the toast says to retry - // from the room, so go there. + // The workbench exists once a later stage fails; the toast says to retry + // from the workbench, so go there. if (cause instanceof WorkbenchCreateError && cause.tenantId !== undefined) { void queryClient.invalidateQueries({ queryKey: chatKeys.childTenants(variables.benchTenantId), diff --git a/apps/web/src/pages/workbench-room-page.tsx b/apps/web/src/pages/workbench-page.tsx similarity index 68% rename from apps/web/src/pages/workbench-room-page.tsx rename to apps/web/src/pages/workbench-page.tsx index 27b68d286..d6c9096fd 100644 --- a/apps/web/src/pages/workbench-room-page.tsx +++ b/apps/web/src/pages/workbench-page.tsx @@ -1,6 +1,6 @@ -// A workbench is a child tenant, and its room is that tenant's mailbox: +// A workbench is a child tenant, and its workbench is that tenant's mailbox: // the timeline is the tenant's mail threads, participants are its -// principals, and a message is addressed to every agent in the room. The +// principals, and a message is addressed to every agent in the workbench. The // left info column folds in what Mission Control used to show for a // bench overall — here scoped to this one workbench: latest activity, // relevant artifacts, and pending approvals with approve/deny. Sub-threads @@ -24,43 +24,43 @@ import { Composer } from "@/chat/composer"; import { Markdown } from "@/chat/markdown"; import { MessageAttachments } from "@/chat/message-attachments"; import { resolveMessagePackage } from "@/chat/deployable-package"; -import { stripRoster } from "@/chat/room-roster"; +import { stripRoster } from "@/chat/workbench-roster"; import { ancestorChain, - listRoomParticipants, - readRoom, + listWorkbenchParticipants, + readWorkbench, resolveAvatarName, resolveParticipantName, sameAddress, - sendToRoom, + sendToWorkbench, subscribeToInbox, - type RoomMessage, - type RoomParticipant, + type WorkbenchMessage, + type WorkbenchParticipant, } from "@/chat/threads-api"; import { ArtifactListPageSchema, useAPIQuery } from "../api"; import { useBench } from "../bench-context"; import { createFetchStockHub } from "../needs-converge"; import { usePendingApprovals } from "../pending-approvals"; -import { roomKeys } from "../chat-path"; +import { workbenchKeys } from "../chat-path"; import { StageTopBar } from "../shell/stage-top-bar"; -import { redeployRoomAgent } from "../workbench-create"; +import { redeployWorkbenchAgent } from "../workbench-create"; import { workbenchIdFromPath } from "../workbench-path"; function errorText(cause: unknown): string { return cause instanceof Error ? cause.message : String(cause); } -function RoomMessageRow({ +function WorkbenchMessageRow({ message, participants, - roomTenantId, + workbenchTenantId, onReply, }: { - readonly message: RoomMessage; - readonly participants: readonly RoomParticipant[]; - readonly roomTenantId: string; + readonly message: WorkbenchMessage; + readonly participants: readonly WorkbenchParticipant[]; + readonly workbenchTenantId: string; /** Undefined in the sub-thread panel, where a row is read-only context. */ - readonly onReply?: (message: RoomMessage) => void; + readonly onReply?: (message: WorkbenchMessage) => void; }) { // Avatars read off the participant's real name — the person's own // included, never the "You" transcript label — falling back to the @@ -71,7 +71,7 @@ function RoomMessageRow({ ); const kind = message.author !== "me" && matched?.kind === "agent" ? "agent" : "person"; // The person's own send carries a trailing roster block so agents in the - // room can hand off to each other; it's never something a person should + // workbench can hand off to each other; it's never something a person should // see echoed back at them. const body = message.author === "me" ? stripRoster(message.body) : message.body; const { pkg, renderedBody } = resolveMessagePackage(message.attachments, body); @@ -86,9 +86,13 @@ function RoomMessageRow({
        - + {onReply === undefined ? null : ( - )} @@ -97,9 +101,13 @@ function RoomMessageRow({ ); } -function ParticipantList({ participants }: { readonly participants: readonly RoomParticipant[] }) { +function ParticipantList({ + participants, +}: { + readonly participants: readonly WorkbenchParticipant[]; +}) { return ( -
          +
            {participants.map((participant) => (
          • @@ -118,88 +126,88 @@ function ParticipantList({ participants }: { readonly participants: readonly Roo /** The left info column: what Mission Control used to show, scoped to this * one workbench — its latest activity, its relevant artifacts, and what's - * pending on it — plus the participant roster already read for the room. */ -function RoomInfoColumn({ - roomTenantId, + * pending on it — plus the participant roster already read for the workbench. */ +function WorkbenchInfoColumn({ + workbenchTenantId, latestMessage, participants, }: { - readonly roomTenantId: string; - readonly latestMessage: RoomMessage | undefined; - readonly participants: readonly RoomParticipant[]; + readonly workbenchTenantId: string; + readonly latestMessage: WorkbenchMessage | undefined; + readonly participants: readonly WorkbenchParticipant[]; }) { const anyAgentLive = participants.some((p) => p.kind === "agent" && p.address !== ""); - const approvalsQuery = usePendingApprovals(roomTenantId, { + const approvalsQuery = usePendingApprovals(workbenchTenantId, { refetchInterval: anyAgentLive ? 3000 : false, }); const artifactsQuery = useAPIQuery( - `/api/tenants/${roomTenantId}/artifacts`, + `/api/tenants/${workbenchTenantId}/artifacts`, ArtifactListPageSchema, ); const pendingApprovals = approvalsQuery.kind === "ready" ? approvalsQuery.data : null; return ( -