summaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-10-24 18:47:01 +0900
committerAdam Malczewski <[email protected]>2026-10-24 18:47:01 +0900
commitb666d77df8cf4da6ae40fb5637268926f02cc44d (patch)
tree4208ec0f5f1ef2cf1a9cd9710a442ce10a5f0c61 /src
parent48cdc3d19a319981a8a4128be98db3c283f81790 (diff)
downloaddispatch-web-b666d77df8cf4da6ae40fb5637268926f02cc44d.tar.gz
dispatch-web-b666d77df8cf4da6ae40fb5637268926f02cc44d.zip
test: run frontend verification natively under Bun
Make Bun the explicit Vitest runtime. Detect cyclic tool input before slow native pretty serialization, preserve shared references, and use native abort signals in the live jsdom probe. 2587 tests repeatedly green and2 real HTTP/UI tests pass; Bun-run typecheck, lint and production build pass without NODE_OPTIONS.
Diffstat (limited to 'src')
-rw-r--r--src/features/chat/logic/tool-summary.test.ts15
-rw-r--r--src/features/chat/logic/tool-summary.ts19
2 files changed, 33 insertions, 1 deletions
diff --git a/src/features/chat/logic/tool-summary.test.ts b/src/features/chat/logic/tool-summary.test.ts
index b6c7e0b..c0caa0d 100644
--- a/src/features/chat/logic/tool-summary.test.ts
+++ b/src/features/chat/logic/tool-summary.test.ts
@@ -311,6 +311,21 @@ describe("tool summaries", () => {
});
expect(() => view("mcp", { integer: 1n })).not.toThrow();
});
+ it("rejects ancestor cycles but preserves shared non-cyclic tool values", () => {
+ const nested: Record<string, unknown> = { name: "nested" };
+ const root = { nested };
+ nested.parent = root;
+ expect(view("mcp", { payload: root }).callDetail).toEqual({
+ kind: "fields",
+ fields: [{ key: "payload", value: "[unserializable input]", multiline: false }],
+ });
+ const shared = { name: "shared" };
+ const payload = { first: shared, second: shared, values: [shared, shared] };
+ const detail = view("mcp", { payload }).callDetail;
+ expect(detail.kind).toBe("fields");
+ if (detail.kind !== "fields") throw new Error("Expected fields");
+ expect(detail.fields[0]?.value).toBe(JSON.stringify(payload, null, 2));
+ });
it("caps and whitespace-collapses header strings but retains full detail", () => {
const command = ` echo\n${"x".repeat(300)}`;
const card = view("run_shell", { command });
diff --git a/src/features/chat/logic/tool-summary.ts b/src/features/chat/logic/tool-summary.ts
index f6d1569..2380adc 100644
--- a/src/features/chat/logic/tool-summary.ts
+++ b/src/features/chat/logic/tool-summary.ts
@@ -74,7 +74,24 @@ function asArgs(input: unknown): Args {
function serialize(input: unknown, pretty = false): string {
if (typeof input === "string") return input;
try {
- return JSON.stringify(input, null, pretty ? 2 : undefined) ?? String(input);
+ // Detect ancestor cycles before the native serializer recurses. Bun's
+ // pretty JSON cycle detection can otherwise consume seconds of UI time.
+ // Track ancestors, not every visited object: shared non-cyclic values are valid.
+ const ancestors: object[] = [];
+ return (
+ JSON.stringify(
+ input,
+ function (this: unknown, _key, value: unknown) {
+ if (value !== null && typeof value === "object") {
+ while (ancestors.length && ancestors.at(-1) !== this) ancestors.pop();
+ if (ancestors.includes(value)) throw new TypeError("Circular tool input");
+ ancestors.push(value);
+ }
+ return value;
+ },
+ pretty ? 2 : undefined,
+ ) ?? String(input)
+ );
} catch {
return "[unserializable input]";
}