Files
open-design/apps/desktop/tests/main/diagnostics-export-delegates.test.ts
T
lefarcenandGitHub 935034cc20 fix(diagnostics): desktop export delegates to daemon so run/AMR logs are captured (#4405)
The desktop "Export diagnostics" path rebuilt the support bundle in the
Electron main process and had to GUESS the daemon data dir from the sidecar
namespace layout (`<namespaceRoot>/data`). That guess is wrong whenever the
daemon's real RUNTIME_DATA_DIR differs: in tools-dev it is `<projectRoot>/.od`
(OD_DATA_DIR unset), and under any OD_DATA_DIR override it is elsewhere. The
exporter then swept the wrong `runs/` and `amr/opencode-home` trees and
silently dropped the per-run `events.jsonl` (which already records every agent
stderr chunk, tool call, and error) and the AMR OpenCode logs — the exact data
needed to explain a stuck/looping run that produced nothing.

The daemon already exposes a complete, correctly-pathed bundle at
`GET /api/diagnostics/export` (it knows its own RUNTIME_DATA_DIR and collects
run events, agent CLI logs, sidecar daemon/web/desktop logs incl. renderer.log,
and host crash reports). So delegate to it instead of duplicating the assembly:

- desktop: new electron-free `diagnostics-fetch.ts` fetches the daemon bundle;
  `diagnostics.ts` keeps only the save dialog + reveal and saves those bytes.
  Removes the data-dir guessing entirely (net -141 lines in diagnostics.ts).
- index.ts: share one `resolveDaemonBaseUrl(runtime, options)` (daemon URL →
  web URL → sidecar web discovery) across app-config menu actions and the
  diagnostics IPC, so the export targets the same daemon as everything else.
- daemon: emit a manifest warning when `runsDir` is set but no per-run event
  logs were found, so an empty bundle reads as "collection gap / persistence
  off" rather than "clean run with nothing to report".

Tests: desktop `fetchDiagnosticsBundle` hits the daemon export endpoint and
surfaces non-200 as an error; daemon export warns on an empty runs dir. Both
red before the change.
2026-06-16 09:26:50 +00:00

28 lines
1.3 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { fetchDiagnosticsBundle } from "../../src/main/diagnostics-fetch.js";
describe("fetchDiagnosticsBundle", () => {
it("GETs the daemon's own diagnostics export endpoint and returns its bytes", async () => {
// Regression for the desktop-ipc export that rebuilt the bundle with a
// GUESSED data dir (`<namespaceRoot>/data`) and so missed the daemon's real
// `runs/<id>/events.jsonl` + AMR logs in dev / OD_DATA_DIR-override setups.
// The fix delegates to the daemon, which knows its real RUNTIME_DATA_DIR.
const bytes = new Uint8Array([0x50, 0x4b, 0x03, 0x04]); // "PK\x03\x04" (zip magic)
const fetchImpl = vi.fn(async () => new Response(bytes, { status: 200 }));
const buffer = await fetchDiagnosticsBundle("http://127.0.0.1:50022", { fetchImpl });
expect(fetchImpl).toHaveBeenCalledWith("http://127.0.0.1:50022/api/diagnostics/export");
expect(Buffer.from(buffer)).toEqual(Buffer.from(bytes));
});
it("throws on a non-200 response so the user sees a real error, not a truncated zip", async () => {
const fetchImpl = vi.fn(async () => new Response("unavailable", { status: 503 }));
await expect(
fetchDiagnosticsBundle("http://127.0.0.1:50022", { fetchImpl }),
).rejects.toThrow(/503/);
});
});