Skip to content
Merged
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
121 changes: 90 additions & 31 deletions scripts/e2e/chat.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
// The path proven: database setup (chat migrations apply) → hub boot
// → sidecar boot → two sign-ups in one tenant (an invited principal
// activated by the owner) → an inference catalog chain seeded with a
// placeholder key → a workbench launched (the anchor instance boots
// in-process, the go/no-go test) → both users posting messages and
// placeholder key → a workbench created (data only since CL-6330 — no
// host run, the go/no-go test) → both users posting messages and
// reading back the converged, decoded timeline with sender identity →
// a second message proving the anchor keeps accepting mail with no
// relaunch → a settings patch that both updates the record and
// appends an audit event to the timeline → independent per-user
// read-state cursors → a second workbench's address mentioned in the
// first, fanning a copy into the mentioned run's mailbox and driving
// that run → the workbench kind filter.
// a second message proving the room keeps accepting mail → a settings
// patch that both updates the record and appends an audit event to
// the timeline → independent per-user read-state cursors → mentioning
// an already-resident agent participant twice, fanning each mention
// into its existing run and never minting a sibling (CL-6451) → the
// workbench kind filter.
//
// Structured as one shared-boot stack (`beforeAll`) with a separate
// `test` per capability, rather than one long test: a real defect
Expand All @@ -29,7 +29,6 @@

import { beforeAll, describe, expect, test } from "bun:test";

import { formatRunAddress } from "@intx/types";
import {
createHubAPI,
seedCatalog,
Expand Down Expand Up @@ -581,40 +580,100 @@
);
});

// CL-6330 deleted the workbench-anchor machinery: a bare `kind:
// "workbench"` room mints only a child tenant and settings rows — no
// host workflow, no run, nothing `/workflows/runs/:runId/events` can
// ever resolve. Only an invited agent (`kind: "chat"` or `POST
// .../invite`) is backed by a real `workflow_run`. This test used to
// mention a second bare workbench and poll its (nonexistent) run,
// which is exactly the 404 CL-6436 tracks. It now proves the CL-6451
// contract this test was actually meant to guard: mentioning a
// participant already resident in the room drives that participant's
// existing run — twice, never minting a sibling.
test("mention fan-out drives the mentioned run", async () => {
const secondWorkbench = await createWorkbench({
kind: "workbench",
name: "mentioned",
const mentioned = await createWorkbench({
kind: "chat",
definitionId: await echoDefinitionId(),
});
expectStatus("create second workbench", secondWorkbench, 201);
const secondWorkbenchId = stringField(
secondWorkbench.data,
expectStatus("create mentioned agent chat", mentioned, 201);
const mentionedWorkbenchId = stringField(
mentioned.data,
"id",
"create second workbench",
"create mentioned agent chat",
);
const mentionedParticipants = arrayField(
mentioned.data,
"participants",
"create mentioned agent chat",
) as { address: string; handle: string }[];
const echoParticipant = mentionedParticipants.find(
(participant) => participant.handle === "echo",
);
const secondWorkbenchAddress = formatRunAddress(secondWorkbenchId, domain);
if (echoParticipant === undefined) {
throw new Error(
`agent chat has no "echo" participant: ${JSON.stringify(mentionedParticipants)}`,
);
}
const echoLocalPart = echoParticipant.address.split("@")[0];
if (echoLocalPart === undefined || echoLocalPart === "") {
throw new Error(`malformed echo address: ${echoParticipant.address}`);
}

const patched = await api(
"PATCH",
`/api/tenants/${tenantId}/chat/workbenches/${workbenchId}/settings`,
{ "chat/participants": [secondWorkbenchAddress] },
const beforeFirst = highestSeq(
await runEvents(user1.cookies, echoLocalPart),
);
await postMessage(
user1.cookies,
mentionedWorkbenchId,
`hey @echo take a look ${crypto.randomUUID()}`,
);
expectStatus("add mentionable participant", patched, 200);

const before = highestSeq(
await runEvents(user1.cookies, secondWorkbenchId),
const afterFirst = await waitForRunProgress(
user1.cookies,
echoLocalPart,
beforeFirst,
);
expect(afterFirst.length).toBeGreaterThan(0);

const mentionText = `hey @${secondWorkbenchId} take a look ${crypto.randomUUID()}`;
await postMessage(user1.cookies, workbenchId, mentionText);
// The resident-reuse claim (CL-6451): a second mention of the same
// already-resident participant drives the SAME run id further —
// never mints a sibling — so this polls that same `echoLocalPart`
// run again rather than any newly-discovered address, and confirms
// the room still lists exactly the one agent participant.
const beforeSecond = highestSeq(
await runEvents(user1.cookies, echoLocalPart),
);
await postMessage(
user1.cookies,
mentionedWorkbenchId,
`hey @echo one more thing ${crypto.randomUUID()}`,
);
const afterSecond = await waitForRunProgress(
user1.cookies,
echoLocalPart,
beforeSecond,
);
expect(afterSecond.length).toBeGreaterThan(0);

const fresh = await waitForRunProgress(
const settingsAfterSecondMention = await api(
"GET",
`/api/tenants/${tenantId}/chat/workbenches/${mentionedWorkbenchId}/settings`,
undefined,
user1.cookies,
secondWorkbenchId,
before,
);
expect(fresh.length).toBeGreaterThan(0);
expectStatus(
"get settings after second mention",
settingsAfterSecondMention,
200,
);
const participantsAfterSecondMention = arrayField(
settingsAfterSecondMention.data,
"participants",
"get settings after second mention",
) as { address: string; handle: string }[];
expect(participantsAfterSecondMention).toHaveLength(1);
expect(participantsAfterSecondMention[0]?.address).toBe(
echoParticipant.address,
);
}, 90_000);

test("inviting the echo agent launches its own run, joins the workbench, and receives @mentions", async () => {
Expand Down Expand Up @@ -812,7 +871,7 @@
expectStatus("re-create existing agent chat", reopened, 200);
expect(
stringField(reopened.data, "id", "re-create existing agent chat"),
).toBe(chatId);

Check failure on line 874 in scripts/e2e/chat.test.ts

View workflow job for this annotation

GitHub Actions / walking-skeleton

error: expect(received).toBe(expected)

Expected: "run_39c5c123b607834fbbd1b936dda8e330" Received: "run_0e9249492f3cab90294c00839783bec1" at <anonymous> (/home/runner/work/workbench/workbench/scripts/e2e/chat.test.ts:874:7)

const workbenchKindListed = await api(
"GET",
Expand Down
Loading