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
30 changes: 0 additions & 30 deletions apps/hub/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ import {
isWorkbenchHostDefinitionName,
listConnectedProviders,
listDefaultInferencePreferences,
provisionSpaceWorkbench,
startWorkflowCommand,
sendWorkbenchMessage,
settleConnectedService,
Expand Down Expand Up @@ -2849,18 +2848,6 @@ export async function createHub(config: HubConfig) {
return row !== undefined && row.workflowDefinitionId === definitionId;
},
deliveryWorkbenchRequired: routineDeliveryWorkbenchRequired,
// A routine created with no `deliveryWorkbenchId` gets a brand-new
// space of its own, named after it, rather than a dead-end
// 400 — the same workbench-provisioning core `POST /chat/workbenches`
// uses (`@corbits/chat`'s `provisionSpaceWorkbench`), reused here
// instead of reimplemented.
deliverySpace: {
createDeliverySpace: (input) =>
provisionSpaceWorkbench(
{ tenancy: chatTenancy, store: chatStore },
input,
),
},
validateRoutineInput: routineInputValid,
}),
);
Expand Down Expand Up @@ -2938,23 +2925,6 @@ export async function createHub(config: HubConfig) {
);
return hit?.workbenchId;
},
deliverySpace: {
createDeliverySpace: (input) =>
provisionSpaceWorkbench(
{ tenancy: chatTenancy, store: chatStore },
input,
),
},
resolveTenantDomain: async (tenantId) => {
const row = await db.query.tenant.findFirst({
where: eq(tenantTable.id, tenantId),
columns: { domain: true },
});
if (row === undefined) {
throw new Error(`No tenant "${tenantId}"`);
}
return row.domain;
},
validateRoutineInput: routineInputValid,
}),
);
Expand Down
23 changes: 23 additions & 0 deletions packages/agent-directory/src/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,20 @@ const workbenchHostInstance = {
definitionName: "ins-0f1e2d3c4b5a69788796a5b4c3d2e1f0",
};

const dailyDigestDefinition = {
...researcher,
id: "wfd_3",
name: "workbench-digest",
description: null,
};

const last30DaysResearchDefinition = {
...researcher,
id: "wfd_4",
name: "last-30-days-research",
description: null,
};

describe("purposeAgentDefinitions", () => {
test("drops the chat anchor machinery's workbench-host definitions", () => {
const result = purposeAgentDefinitions([
Expand All @@ -48,6 +62,15 @@ describe("purposeAgentDefinitions", () => {
]);
expect(result).toEqual([researcher]);
});

test("drops routine-only workflow catalog utilities (Daily digest, Last 30 days research) — they are non-conversational, seeded as routines, and belong on the Routines page, not the Agents list", () => {
const result = purposeAgentDefinitions([
researcher,
dailyDigestDefinition,
last30DaysResearchDefinition,
]);
expect(result).toEqual([researcher]);
});
});

const invitedAgentInstance = {
Expand Down
26 changes: 19 additions & 7 deletions packages/agent-directory/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
withDisplayNames,
type WithDisplayName,
} from "@corbits/chat/display-name";
import { isConversationalWorkflowName } from "@corbits/workflow-catalog";

// `deriveDisplayName`/`humanizeSlug` (CL-6413) live in `@corbits/chat`
// itself, not here: this package already depends on `@corbits/chat` (for
Expand Down Expand Up @@ -42,16 +43,27 @@ export type UserFacingAgentInstance = {
};

/** Every definition, minus the chat anchor machinery's workbench hosts —
* those are internal plumbing, never a user-facing agent. Definitions
* never need the run-id filter `purposeAgentInstances` below takes:
* definition rows aren't run rows, so a folded run's own id can never
* match here — an invited agent's real `definitionId` stays a
* legitimate, reusable template even though its *instance* is chat
* plumbing. */
* those are internal plumbing, never a user-facing agent — and minus every
* non-conversational workflow-catalog utility (`workbench-digest`/"Daily
* digest", `last-30-days-research`/"Last 30 days research", …): those are
* mail-triggered automations a routine schedules, never something a person
* opens a chat with, so they belong on the Routines page only. The one
* distinguishing property is `isConversationalWorkflowName` — "can this be
* DMed" — the same test `listVisibleAgentDefinitions` already applies to the
* sidebar's DM list; `automatable` (schedulable as a routine) is orthogonal
* and never decides this. Definitions never need the run-id filter
* `purposeAgentInstances` below takes: definition rows aren't run rows, so a
* folded run's own id can never match here — an invited agent's real
* `definitionId` stays a legitimate, reusable template even though its
* *instance* is chat plumbing. */
export function purposeAgentDefinitions<T extends UserFacingAgentDefinition>(
definitions: readonly T[],
): readonly T[] {
return definitions.filter((d) => !isWorkbenchHostDefinitionName(d.name));
return definitions.filter(
(d) =>
!isWorkbenchHostDefinitionName(d.name) &&
isConversationalWorkflowName(d.name),
);
}

/**
Expand Down
18 changes: 18 additions & 0 deletions packages/hub-client/src/default-routines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,24 @@ export async function ensureDefaultRoutines(
body,
cookies,
);
if (
created.status === 400 &&
/deliveryWorkbenchId is required/.test(JSON.stringify(created.data))
) {
// This preset's definition needs somewhere to deliver to, and
// seeding names no workbench — a person hasn't picked one yet, and
// seeding must never invent one and name it after the routine
// (that's exactly the "Daily digest"/"New Workbench" pollution this
// preset-planting flow used to cause). The routine simply isn't
// planted until a member creates it by hand and picks a real
// destination.
log(
`routine "${preset.name}" skipped: its workflow needs a delivery ` +
`workbench and this preset names none — create it by hand and ` +
`pick one`,
);
continue;
}
if (
created.status === 400 &&
/is required/.test(JSON.stringify(created.data))
Expand Down
39 changes: 39 additions & 0 deletions packages/hub-client/test/default-routines.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,45 @@ describe("ensureDefaultRoutines", () => {
);
});

// The hub never auto-provisions a delivery workbench (that pollution —
// a workbench literally named "Daily digest" — is exactly what this
// fix removes): a delivery-required preset seeded with no workbench
// named gets this 400, and seeding must skip it honestly rather than
// failing the whole seed or fabricating a destination.
test("skips a delivery-required preset honestly when the hub 400s for lacking a named workbench", async () => {
const { lines, log } = collector();
const handler: FakeHandler = (method, path) => {
if (
method === "GET" &&
path === `/api/tenants/${TENANT_ID}/workflows/definitions`
) {
return deployedDefinitionsResponse();
}
if (method === "GET" && path === `/api/tenants/${TENANT_ID}/routines`) {
return { status: 200, data: { items: [] } };
}
if (method === "POST" && path === `/api/tenants/${TENANT_ID}/routines`) {
return {
status: 400,
data: {
error: {
code: "bad_request",
message: "deliveryWorkbenchId is required for this workflow",
},
},
};
}
return undefined;
};

await ensureDefaultRoutines(fakeAPI(handler), [], TENANT_ID, log);

const output = lines.join("\n");
expect(output).toContain(
'routine "Daily digest" skipped: its workflow needs a delivery workbench and this preset names none — create it by hand and pick one',
);
});

test("a re-seed matches every preset by presetKey — even renamed — and creates nothing twice", async () => {
const { lines, log } = collector();
const handler: FakeHandler = (method, path) => {
Expand Down
1 change: 0 additions & 1 deletion packages/routines/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ export type {
RoutineLauncher,
LaunchedRoutineRun,
RunSummaryResolver,
DeliverySpacePort,
} from "./routes";

export { createWorkflowRoutineRoutes } from "./workflow-routine-routes";
Expand Down
Loading
Loading