Skip to content

Commit d80ac22

Browse files
committed
fix(dashboard-agent): refuse a view carrying two investigations instead of filing them as one card
1 parent bf3c480 commit d80ac22

2 files changed

Lines changed: 40 additions & 1 deletion

File tree

internal-packages/dashboard-agent/src/dashboard-agent.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1701,6 +1701,35 @@ describe("buildDashboardAgentTools", () => {
17011701
expect(upserts[1]).toMatchObject({ id: first.investigationId });
17021702
});
17031703

1704+
it("render_view refuses two investigations in one view and writes neither", async () => {
1705+
const { capability, rows, upserts } = fakeInvestigations();
1706+
const tools = buildDashboardAgentTools({ ...SCOPE, investigations: capability });
1707+
const renderView = tools.render_view as {
1708+
inputSchema: { parse: (input: unknown) => unknown };
1709+
execute: (input: unknown, opts: unknown) => Promise<any>;
1710+
};
1711+
1712+
const output = await renderView.execute(
1713+
renderView.inputSchema.parse({
1714+
blocks: [
1715+
{ type: "investigation", investigation: investigationState },
1716+
{
1717+
type: "investigation",
1718+
investigation: { ...concludedState, title: "A different question entirely" },
1719+
},
1720+
],
1721+
}),
1722+
{}
1723+
);
1724+
1725+
// One id is assigned per call, so committing both would file the second subject as
1726+
// the first's next revision.
1727+
expect(typeof output.error).toBe("string");
1728+
expect(output.blocks).toBeUndefined();
1729+
expect(upserts).toEqual([]);
1730+
expect(rows.size).toBe(0);
1731+
});
1732+
17041733
it("render_view errors on an unknown investigationId and writes nothing", async () => {
17051734
const { capability, rows } = fakeInvestigations();
17061735
const tools = buildDashboardAgentTools({ ...SCOPE, investigations: capability });

internal-packages/dashboard-agent/src/tool-investigations.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,17 @@ export function createInvestigationRenderer(
149149
* back. `continueId` is only a pointer; the turn's own closure wins when set.
150150
*/
151151
return async function renderInvestigations(blocks: ViewBlockInput[], continueId?: string) {
152-
if (!blocks.some((block) => block.type === "investigation")) return { blocks };
152+
const investigationBlocks = blocks.filter((block) => block.type === "investigation").length;
153+
if (investigationBlocks === 0) return { blocks };
154+
155+
// One id is assigned per call, so a second block in the same view would be written
156+
// as the next revision of the first: one card carrying two subjects.
157+
if (investigationBlocks > 1) {
158+
return {
159+
error:
160+
"A view holds at most one investigation block, and this one has more than one. Render one investigation per call, passing its own investigationId back each time.",
161+
};
162+
}
153163

154164
if (!ctx.investigations) {
155165
return { error: "Investigations aren't available on this turn, so I can't render one." };

0 commit comments

Comments
 (0)