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
32 changes: 16 additions & 16 deletions VENDORED.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions apps/web/src/agents-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) });
},
});
}
2 changes: 1 addition & 1 deletion apps/web/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
54 changes: 27 additions & 27 deletions apps/web/src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -3613,56 +3613,56 @@ 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;
margin: 0;
padding: 0;
list-style: none;
}
.room-info-approval-row {
.workbench-info-approval-row {
display: flex;
flex-direction: column;
gap: 0.4rem;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/auto-workbench-title.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/bench-context-value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
28 changes: 14 additions & 14 deletions apps/web/src/bench-context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,31 @@ function membership(overrides: Partial<Principal> & { 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<string, string | null>): ReadonlyMap<string, string | null> {
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);

Expand All @@ -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");
});
Expand All @@ -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);

Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/bench-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
14 changes: 7 additions & 7 deletions apps/web/src/chat-path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
10 changes: 5 additions & 5 deletions apps/web/src/chat/approval-row.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -36,15 +36,15 @@ export function ApprovalRow({
const pending = resolveMutation.isPending ? resolveMutation.variables : null;

return (
<li className="room-info-approval-row">
<li className="workbench-info-approval-row">
<div>
<span className="room-info-cell-primary">{item.headline}</span>
<span className="workbench-info-cell-primary">{item.headline}</span>
<br />
<span className="room-info-cell-context">
<span className="workbench-info-cell-context">
{item.agentName} · {formatRelativeTime(item.createdAt)}
</span>
</div>
<div className="room-info-row-actions">
<div className="workbench-info-row-actions">
<Button
variant="ghost"
size="sm"
Expand Down
Loading
Loading