Files
wehub-resource-sync 070959e133
landing-page-staging / Deploy landing page to staging (push) Has been skipped
landing-page-ci / Validate landing page (push) Failing after 4s
visual-baseline / Capture visual baselines (push) Has been cancelled
bake-plugin-previews / Bake plugin previews (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:00:47 +08:00

346 lines
14 KiB
TypeScript

import { mkdir, rm, writeFile } from 'node:fs/promises';
import { tmpdir, userInfo } from 'node:os';
import { dirname, join } from 'node:path';
import { randomUUID } from 'node:crypto';
import { describe, expect, it } from 'vitest';
import JSZip from 'jszip';
import {
APP_KEYS,
SIDECAR_MODES,
SIDECAR_SOURCES,
type SidecarStamp,
} from '@open-design/sidecar-proto';
import type { SidecarRuntimeContext } from '@open-design/sidecar';
import {
STANDALONE_LAUNCH_WARNING,
createDiagnosticsExportHandler,
} from '../src/diagnostics-export.js';
interface MockResponse {
status(code: number): MockResponse;
setHeader(name: string, value: string): MockResponse;
end(payload: Buffer): void;
json(payload: unknown): void;
capturedStatus?: number;
capturedPayload?: Buffer;
capturedJson?: unknown;
}
function mockResponse(): MockResponse {
const res: MockResponse = {
status(code) { res.capturedStatus = code; return res; },
setHeader() { return res; },
end(payload) { res.capturedPayload = payload; },
json(payload) { res.capturedJson = payload; },
};
return res;
}
interface DiagnosticsManifestFile {
name: string;
}
describe('diagnostics export handler — non-sidecar launch', () => {
// Reviewer-requested regression spec: `runDaemonCliStartup()` calls
// `startDaemonRuntime()` without a runtime context, so plain `od` users
// hit the diagnostics handler with `options.runtime == null`. The bundle
// must still produce a valid zip AND surface a manifest warning that
// file-based logs were not captured, so the operator can tell the
// diff between "no logs because plain launch" and "no logs because
// something genuinely broke."
it('emits a standalone-launch warning when runtime is null', async () => {
const handler = createDiagnosticsExportHandler({ runtime: null, projectRoot: '/tmp/test-project' });
const res = mockResponse();
// Express RequestHandler signature wants three args; the handler only
// reads `res`, so casting through `unknown` keeps the test focused.
await handler({} as never, res as never, () => undefined);
expect(res.capturedStatus).toBe(200);
expect(res.capturedPayload).toBeInstanceOf(Buffer);
const zip = await JSZip.loadAsync(res.capturedPayload!);
const manifestRaw = await zip.file('summary/manifest.json')!.async('string');
const manifest = JSON.parse(manifestRaw) as {
warnings: string[];
files: DiagnosticsManifestFile[];
extra?: {
browserUse?: {
registryPath?: string;
socketCount?: number;
candidateCount?: number;
staleCount?: number;
probeFailureCategory?: string;
};
};
};
expect(manifest.warnings).toContain(STANDALONE_LAUNCH_WARNING);
expect(manifest.extra?.browserUse).toMatchObject({
registryPath: expect.stringContaining('codex-browser-use'),
probeFailureCategory: expect.any(String),
});
expect(typeof manifest.extra?.browserUse?.socketCount).toBe('number');
expect(typeof manifest.extra?.browserUse?.candidateCount).toBe('number');
expect(typeof manifest.extra?.browserUse?.staleCount).toBe('number');
// Standalone launches intentionally omit sidecar-managed daemon/web/desktop
// log files, but real developer machines may still contribute matching
// macOS crash reports from /Library/Logs/DiagnosticReports. Keep the test
// focused on the contract that no sidecar log files are bundled.
expect(
manifest.files.filter((file) => file.name.startsWith('logs/')),
).toEqual([]);
});
});
describe('diagnostics export handler — packaged (runtime) layout', () => {
// Regression for the namespaceRoot off-by-one that left every packaged
// bundle without daemon/web logs (the agent-run flow lives in the daemon
// log). In packaged builds the orchestrator launches each child with
// `base = <namespaceRoot>/runtime` while the logs live a level up at
// `<namespaceRoot>/logs`. The old `resolveNamespaceRoot(base, namespace)`
// resolved the daemon log to `<namespaceRoot>/runtime/<namespace>/logs/...`
// → ENOENT, so the bundle silently captured nothing.
it('captures the daemon log from the real <namespaceRoot>/logs tree', async () => {
const root = join(tmpdir(), `od-diag-${randomUUID()}`);
const namespaceRoot = join(root, 'namespaces', 'release-stable');
const daemonLogPath = join(namespaceRoot, 'logs', APP_KEYS.DAEMON, 'latest.log');
const marker = 'DAEMON-LOG-MARKER critique runId=rc100-poster';
try {
await mkdir(dirname(daemonLogPath), { recursive: true });
await writeFile(daemonLogPath, `${marker}\n`, 'utf8');
const runtime: SidecarRuntimeContext<SidecarStamp> = {
app: APP_KEYS.DAEMON,
// packaged launches children with base == <namespaceRoot>/runtime
base: join(namespaceRoot, 'runtime'),
ipc: '/tmp/od-diag-test-daemon.sock',
mode: SIDECAR_MODES.RUNTIME,
namespace: 'release-stable',
source: SIDECAR_SOURCES.PACKAGED,
};
const handler = createDiagnosticsExportHandler({ runtime, projectRoot: '/tmp/test-project' });
const res = mockResponse();
await handler({} as never, res as never, () => undefined);
expect(res.capturedStatus).toBe(200);
const zip = await JSZip.loadAsync(res.capturedPayload!);
// The log must be present with its real contents, not a missing-file
// placeholder.
const daemonEntry = zip.file('logs/daemon/latest.log');
expect(daemonEntry).not.toBeNull();
expect(await daemonEntry!.async('string')).toContain(marker);
const manifest = JSON.parse(await zip.file('summary/manifest.json')!.async('string')) as {
files: { name: string; bytes: number; error?: string }[];
};
const daemonFile = manifest.files.find((f) => f.name === 'logs/daemon/latest.log');
expect(daemonFile?.error).toBeUndefined();
expect(daemonFile?.bytes ?? 0).toBeGreaterThan(0);
} finally {
await rm(root, { recursive: true, force: true });
}
});
it('reports missing packaged log files under logical log paths without duplicating runtime segments', async () => {
const root = join(tmpdir(), `od-diag-missing-${randomUUID()}`);
const namespaceRoot = join(root, 'namespaces', 'release-beta');
const daemonLogPath = join(namespaceRoot, 'logs', APP_KEYS.DAEMON, 'latest.log');
try {
await mkdir(dirname(daemonLogPath), { recursive: true });
await writeFile(daemonLogPath, 'daemon ok\n', 'utf8');
const runtime: SidecarRuntimeContext<SidecarStamp> = {
app: APP_KEYS.DAEMON,
base: join(namespaceRoot, 'runtime'),
ipc: '/tmp/od-diag-missing.sock',
mode: SIDECAR_MODES.RUNTIME,
namespace: 'release-beta',
source: SIDECAR_SOURCES.PACKAGED,
};
const handler = createDiagnosticsExportHandler({ runtime, projectRoot: '/tmp/test-project' });
const res = mockResponse();
await handler({} as never, res as never, () => undefined);
expect(res.capturedStatus).toBe(200);
const zip = await JSZip.loadAsync(res.capturedPayload!);
const manifest = JSON.parse(await zip.file('summary/manifest.json')!.async('string')) as {
files: Array<{ name: string; bytes?: number; error?: string }>;
};
const fileNames = manifest.files.map((file) => file.name);
expect(fileNames).toContain('logs/daemon/latest.log');
expect(fileNames).toContain('logs/web/latest.log');
expect(fileNames).toContain('logs/desktop/latest.log');
expect(fileNames.some((name) => name.includes('runtime/release-beta/logs'))).toBe(false);
const webLog = manifest.files.find((file) => file.name === 'logs/web/latest.log');
const desktopLog = manifest.files.find((file) => file.name === 'logs/desktop/latest.log');
expect(webLog?.error).toBeTruthy();
expect(desktopLog?.error).toBeTruthy();
} finally {
await rm(root, { recursive: true, force: true });
}
});
});
describe('diagnostics export handler — run event logs', () => {
it('bundles recent per-run events.jsonl logs for agent stream forensics', async () => {
const root = join(tmpdir(), `od-diag-runs-${randomUUID()}`);
const runsDir = join(root, 'runs');
const runLogPath = join(runsDir, 'run-3165', 'events.jsonl');
const marker = 'Agent stalled without emitting any new output for 600s';
try {
await mkdir(dirname(runLogPath), { recursive: true });
await writeFile(
runLogPath,
JSON.stringify({
event: 'agent',
data: { type: 'raw', line: marker },
}) + '\n',
'utf8',
);
const handler = createDiagnosticsExportHandler({
runtime: null,
projectRoot: '/tmp/test-project',
runsDir,
});
const res = mockResponse();
await handler({} as never, res as never, () => undefined);
expect(res.capturedStatus).toBe(200);
const zip = await JSZip.loadAsync(res.capturedPayload!);
const runEntry = zip.file('runs/run-3165/events.jsonl');
expect(runEntry).not.toBeNull();
expect(await runEntry!.async('string')).toContain(marker);
const manifest = JSON.parse(await zip.file('summary/manifest.json')!.async('string')) as {
files: { name: string; bytes: number; error?: string }[];
};
const runFile = manifest.files.find((file) => file.name === 'runs/run-3165/events.jsonl');
expect(runFile?.error).toBeUndefined();
expect(runFile?.bytes ?? 0).toBeGreaterThan(0);
} finally {
await rm(root, { recursive: true, force: true });
}
});
it('keeps failed run forensics useful while redacting credentials and user paths', async () => {
const root = join(tmpdir(), `od-diag-runs-sensitive-${randomUUID()}`);
const runsDir = join(root, 'runs');
const runLogPath = join(runsDir, 'run-sensitive', 'events.jsonl');
const username = userInfo().username;
const homePath = `/Users/${username}/open-design/project`;
const secretBearer = 'od_bearer_secret_12345';
const secretQuery = 'query-token-secret';
const secretApiKey = 'api-key-secret';
const stderrMarker = 'Agent failed while calling upstream provider';
try {
await mkdir(dirname(runLogPath), { recursive: true });
await writeFile(
runLogPath,
[
{
event: 'stderr',
data: {
chunk: stderrMarker,
},
},
{
event: 'stderr',
data: {
chunk: `Authorization: Bearer ${secretBearer}`,
},
},
{
event: 'stderr',
data: {
chunk: `GET https://provider.example/v1/models?access_token=${secretQuery}`,
},
},
{
event: 'stderr',
data: {
chunk: `provider api_key=${secretApiKey}`,
},
},
{
event: 'stderr',
data: {
chunk: `cwd ${homePath}`,
},
},
{
event: 'error',
data: {
message: 'Provider rejected the request after auth forwarding.',
code: 'AGENT_EXECUTION_FAILED',
},
},
{
event: 'diagnostic',
data: {
type: 'agent_runtime',
phase: 'agent-call',
stderr_present: true,
},
},
].map((entry) => JSON.stringify(entry)).join('\n') + '\n',
'utf8',
);
const handler = createDiagnosticsExportHandler({
runtime: null,
projectRoot: '/tmp/test-project',
runsDir,
});
const res = mockResponse();
await handler({} as never, res as never, () => undefined);
expect(res.capturedStatus).toBe(200);
const zip = await JSZip.loadAsync(res.capturedPayload!);
const runEntry = zip.file('runs/run-sensitive/events.jsonl');
expect(runEntry).not.toBeNull();
const runLog = await runEntry!.async('string');
expect(runLog).toContain(stderrMarker);
expect(runLog).toContain('AGENT_EXECUTION_FAILED');
expect(runLog).toContain('agent_runtime');
expect(runLog).toContain('Bearer [REDACTED]');
expect(runLog).toContain('access_token=[REDACTED]');
expect(runLog).toContain('api_key=[REDACTED]');
expect(runLog).toContain('/Users/<USER>/open-design/project');
expect(runLog).not.toContain(secretBearer);
expect(runLog).not.toContain(secretQuery);
expect(runLog).not.toContain(secretApiKey);
expect(runLog).not.toContain(homePath);
} finally {
await rm(root, { recursive: true, force: true });
}
});
it('warns when runsDir is set but no per-run event logs were found', async () => {
// An empty/absent runs dir adds no manifest file entries, so without an
// explicit warning an empty bundle is indistinguishable from a healthy run
// — exactly the gap that made an AMR loop look like "nothing happened."
const runsDir = join(tmpdir(), `od-diag-empty-runs-${randomUUID()}`);
const handler = createDiagnosticsExportHandler({
runtime: null,
projectRoot: '/tmp/test-project',
runsDir,
});
const res = mockResponse();
await handler({} as never, res as never, () => undefined);
expect(res.capturedStatus).toBe(200);
const zip = await JSZip.loadAsync(res.capturedPayload!);
const manifest = JSON.parse(await zip.file('summary/manifest.json')!.async('string')) as {
warnings: string[];
};
expect(manifest.warnings.some((w) => w.includes('No per-run event logs found'))).toBe(true);
});
});