项目文件夹

文件
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

1908 行
68 KiB
TypeScript

import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { reportRunCompletedFromDaemon } from '../src/langfuse-bridge.js';
import { buildPromptStackTelemetry } from '../src/prompt-telemetry.js';
interface FakeMessage {
id: string;
role: 'user' | 'assistant';
content: string;
attachments?: Array<Record<string, unknown>>;
producedFiles?: Array<Record<string, unknown>>;
traceObjectFiles?: Array<Record<string, unknown>>;
}
function makeDb(messagesByConvo: Record<string, FakeMessage[]> = {}) {
return {
__messages: messagesByConvo,
prepare() {
throw new Error('listMessages should be the only DB call in tests');
},
};
}
function makeRun(over: Partial<Parameters<typeof reportRunCompletedFromDaemon>[0]['run']> = {}) {
const now = Date.now();
return {
id: 'run-id-1',
projectId: 'proj-1',
conversationId: 'conv-1',
assistantMessageId: 'msg-1',
agentId: 'claude',
status: 'succeeded',
createdAt: now - 4500,
updatedAt: now,
events: [
{
id: 1,
event: 'agent',
timestamp: now - 4000,
data: {
type: 'tool_use',
id: 'tool-1',
name: 'Bash',
input: { command: 'ls -la' },
},
},
{
id: 2,
event: 'agent',
timestamp: now - 3500,
data: {
type: 'tool_result',
toolUseId: 'tool-1',
content: 'total 0',
isError: false,
},
},
{
id: 3,
event: 'agent',
timestamp: now - 3000,
data: {
type: 'tool_use',
id: 'tool-2',
name: 'Write',
input: { path: 'index.html' },
},
},
{
id: 4,
event: 'agent',
timestamp: now - 2500,
data: {
type: 'tool_result',
toolUseId: 'tool-2',
content: 'wrote index.html',
isError: false,
},
},
{
id: 5,
event: 'agent',
timestamp: now - 2000,
data: {
type: 'usage',
usage: { input_tokens: 100, output_tokens: 200 },
},
},
] as Array<{ id: number; event: string; data: unknown; timestamp?: number }>,
userPrompt: 'design a coffee landing page',
...over,
};
}
function bodyOf(
batch: unknown[],
type: string,
name?: string,
): Record<string, any> {
const event = (batch as Array<{ type: string; body: Record<string, any> }>).find(
(item) => item.type === type && (name === undefined || item.body.name === name),
);
expect(event).toBeTruthy();
return event!.body;
}
describe('langfuse-bridge.reportRunCompletedFromDaemon', () => {
let dataDir: string;
beforeEach(async () => {
dataDir = await mkdtemp(path.join(tmpdir(), 'od-bridge-'));
});
afterEach(async () => {
await rm(dataDir, { recursive: true, force: true });
vi.restoreAllMocks();
});
async function writeAppCfg(cfg: Record<string, unknown>) {
await writeFile(path.join(dataDir, 'app-config.json'), JSON.stringify(cfg));
}
it('does nothing when telemetry.metrics is off', async () => {
await writeAppCfg({
installationId: 'install-1',
telemetry: { metrics: false },
});
const fetchSpy = vi.fn();
await reportRunCompletedFromDaemon({
db: makeDb(),
dataDir,
run: makeRun() as any,
fetchImpl: fetchSpy as any,
});
expect(fetchSpy).not.toHaveBeenCalled();
});
it('does nothing when conversation/tool content reporting is off', async () => {
await writeAppCfg({
installationId: 'install-1',
telemetry: { metrics: true, content: false, artifactManifest: true },
});
const fetchSpy = vi.fn();
process.env.LANGFUSE_PUBLIC_KEY = 'pk';
process.env.LANGFUSE_SECRET_KEY = 'sk';
try {
await reportRunCompletedFromDaemon({
db: makeDbWithListMessages({
'conv-1': [
{
id: 'msg-1',
role: 'assistant',
content: 'sensitive output',
producedFiles: [{ name: 'secret.html', kind: 'html', size: 1 }],
},
],
}),
dataDir,
run: makeRun() as any,
fetchImpl: fetchSpy as any,
});
} finally {
delete process.env.LANGFUSE_PUBLIC_KEY;
delete process.env.LANGFUSE_SECRET_KEY;
}
expect(fetchSpy).not.toHaveBeenCalled();
});
it('does nothing when no app-config.json exists (fresh install)', async () => {
const fetchSpy = vi.fn();
await reportRunCompletedFromDaemon({
db: makeDb(),
dataDir,
run: makeRun() as any,
fetchImpl: fetchSpy as any,
});
expect(fetchSpy).not.toHaveBeenCalled();
});
it('builds a ReportContext from db + app-config and POSTs the trace', async () => {
await writeAppCfg({
installationId: 'install-uuid-1',
telemetry: { metrics: true, content: true, artifactManifest: true },
});
const messages: FakeMessage[] = [
{
id: 'user-1',
role: 'user',
content: 'Use this reference.',
attachments: [
{
path: 'uploads/brand.pdf',
name: 'brand.pdf',
kind: 'file',
size: 2_481_032,
mime: 'application/pdf',
sha256: '1234abcd',
},
],
},
{
id: 'msg-1',
role: 'assistant',
content: 'Here is a draft …',
producedFiles: [
{
name: 'index.html',
path: '/Users/alice/private/project/index.html',
kind: 'html',
mime: 'text/html',
size: 4096,
hash: 'sha256:artifacthash',
artifactManifest: {
version: 1,
kind: 'html',
status: 'complete',
exports: ['html'],
},
},
{ name: 'style.css', kind: 'code', size: 800 },
],
},
];
const fetchSpy = vi
.fn()
.mockResolvedValue(new Response('{}', { status: 207 }));
process.env.LANGFUSE_PUBLIC_KEY = 'pk';
process.env.LANGFUSE_SECRET_KEY = 'sk';
try {
await reportRunCompletedFromDaemon({
db: makeDbWithListMessages({ 'conv-1': messages }),
dataDir,
run: makeRun({ agentId: 'qoder' }) as any,
fetchImpl: fetchSpy as any,
});
} finally {
delete process.env.LANGFUSE_PUBLIC_KEY;
delete process.env.LANGFUSE_SECRET_KEY;
}
expect(fetchSpy).toHaveBeenCalledTimes(1);
const init = fetchSpy.mock.calls[0]![1] as RequestInit;
const batch = JSON.parse(init.body as string).batch as any[];
expect(batch.map((item) => item.type)).toEqual([
'trace-create',
'span-create',
'generation-create',
'event-create',
'span-create',
'span-create',
'event-create',
]);
const trace = batch[0].body;
const span = bodyOf(batch, 'span-create', 'agent-run');
const generation = bodyOf(batch, 'generation-create', 'llm');
const bash = bodyOf(batch, 'span-create', 'tool:Bash');
const write = bodyOf(batch, 'span-create', 'tool:Write');
const usage = bodyOf(batch, 'event-create', 'agent-usage');
const artifacts = bodyOf(batch, 'event-create', 'artifact-summary');
expect(trace.userId).toBe('install-uuid-1');
expect(trace.sessionId).toBe('conv-1');
expect(trace.input).toBe('design a coffee landing page');
expect(trace.output).toBe('Here is a draft …');
expect(span.id).toBe('run-id-1-agent');
expect(span.traceId).toBe('run-id-1');
expect(span.input).toBe('design a coffee landing page');
expect(span.output).toBe('Here is a draft …');
expect(generation.parentObservationId).toBe('run-id-1-agent');
expect(bash.parentObservationId).toBe('run-id-1-agent');
expect(bash.input).toMatch(/ls -la/);
expect(bash.output).toBe('total 0');
expect(write.parentObservationId).toBe('run-id-1-agent');
expect(write.metadata.toolName).toBe('Write');
expect(usage.parentObservationId).toBe('run-id-1-agent');
expect(usage.input).toEqual({
source: 'qoder',
event_type: 'usage',
});
expect(usage.output.usage).toEqual({ input_tokens: 100, output_tokens: 200 });
expect(artifacts.parentObservationId).toBe('run-id-1-agent');
expect(artifacts.input).toEqual({
source: 'agent_generated_artifacts',
artifact_count: 2,
artifact_manifest_enabled: true,
});
expect(artifacts.output).toEqual({
artifacts: [
{ slug: 'index.html', type: 'html', sizeBytes: 4096 },
{ slug: 'style.css', type: 'code', sizeBytes: 800 },
],
manifest_completeness: 'complete',
});
expect(artifacts.metadata.artifacts).toEqual([
{ slug: 'index.html', type: 'html', sizeBytes: 4096 },
{ slug: 'style.css', type: 'code', sizeBytes: 800 },
]);
expect(trace.metadata.attachment_manifest).toEqual([
expect.objectContaining({
attachment_id: expect.stringMatching(/^att_[0-9a-f]{16}$/),
object_class: 'attachment',
storage_ref: expect.stringMatching(
/^od:\/\/objects\/workspaces\/unknown\/projects\/proj-1\/runs\/run-id-1\/attachment\/att_[0-9a-f]{16}$/,
),
project_id: 'proj-1',
run_id: 'run-id-1',
workspace_id: null,
status: 'ok',
size_bytes: 2_481_032,
sha256: 'sha256:1234abcd',
mime_type: 'application/pdf',
extension: 'pdf',
redacted: false,
truncated: false,
stored_in_open_design: true,
retention_policy: 'project_lifetime',
access_scope: 'project',
sensitivity: 'private',
source: 'user_upload',
expires_at: null,
approved_by: null,
}),
]);
expect(trace.metadata.artifact_manifest).toEqual([
expect.objectContaining({
artifact_id: expect.stringMatching(/^art_[0-9a-f]{16}$/),
object_class: 'artifact',
type: 'html',
storage_ref: expect.stringMatching(
/^od:\/\/objects\/workspaces\/unknown\/projects\/proj-1\/runs\/run-id-1\/artifact\/art_[0-9a-f]{16}$/,
),
project_id: 'proj-1',
run_id: 'run-id-1',
workspace_id: null,
status: 'ok',
size_bytes: 4096,
sha256: 'sha256:artifacthash',
mime_type: 'text/html',
extension: 'html',
build_status: 'complete',
preview_status: 'unavailable',
export_status: 'available',
redacted: false,
truncated: false,
stored_in_open_design: true,
retention_policy: 'project_lifetime',
access_scope: 'project',
sensitivity: 'private',
source: 'agent_generated',
expires_at: null,
approved_by: null,
}),
expect.objectContaining({
object_class: 'artifact',
type: 'code',
size_bytes: 800,
extension: 'css',
export_status: 'unavailable',
}),
]);
expect(trace.metadata.manifest_completeness).toBe('complete');
expect(JSON.stringify(batch)).not.toContain('/Users/alice/private/project');
expect(JSON.stringify(batch)).not.toContain('brand.pdf');
// Core tags must be present. The bridge also tacks on an `os:<...>`
// tag derived from the host (`darwin` / `linux` / `win32`), which is
// useful telemetry but varies between dev / CI environments — assert
// its presence by prefix rather than pinning a value.
expect(trace.tags).toEqual(
expect.arrayContaining(['open-design', 'project:proj-1', 'agent:qoder']),
);
expect((trace.tags as string[]).some((t) => t.startsWith('os:'))).toBe(true);
expect(trace.metadata.eventsSummary.toolCalls).toBe(2);
expect(trace.metadata.eventsSummary.errors).toBe(0);
expect(trace.metadata.tokens).toEqual({
input: 100,
inputProvider: 100,
inputEffective: 100,
output: 200,
total: 300,
estimatedContext: 93,
cacheTokenSource: 'unavailable',
});
expect(trace.metadata.artifacts).toEqual([
{ slug: 'index.html', type: 'html', sizeBytes: 4096 },
{ slug: 'style.css', type: 'code', sizeBytes: 800 },
]);
expect(trace.metadata.success).toBe(true);
});
it('collects runtime diagnostic agent events into Langfuse observations', async () => {
await writeAppCfg({
installationId: 'install-uuid-1',
telemetry: { metrics: true, content: true, artifactManifest: false },
});
const fetchSpy = vi
.fn()
.mockResolvedValue(new Response('{}', { status: 207 }));
process.env.LANGFUSE_PUBLIC_KEY = 'pk';
process.env.LANGFUSE_SECRET_KEY = 'sk';
try {
await reportRunCompletedFromDaemon({
db: makeDbWithListMessages({
'conv-1': [
{
id: 'msg-1',
role: 'assistant',
content: '',
producedFiles: [],
},
],
}),
dataDir,
run: makeRun({
agentId: 'amr',
events: [
{
id: 1,
event: 'agent',
timestamp: Date.now() - 100,
data: {
type: 'diagnostic',
name: 'acp_artifact_text_suppression',
source: 'acp-json-rpc',
reason: 'artifact_echo',
suppressedChars: 4096,
openedBlocks: 1,
closedBlocks: 1,
fileCount: 1,
files: ['index.html'],
},
},
] as any,
}) as any,
fetchImpl: fetchSpy as any,
});
} finally {
delete process.env.LANGFUSE_PUBLIC_KEY;
delete process.env.LANGFUSE_SECRET_KEY;
}
const init = fetchSpy.mock.calls[0]![1] as RequestInit;
const batch = JSON.parse(init.body as string).batch as any[];
expect(
bodyOf(batch, 'event-create', 'agent-diagnostic:acp_artifact_text_suppression'),
).toMatchObject({
input: {
source: 'amr',
event_type: 'diagnostic',
},
output: {
name: 'acp_artifact_text_suppression',
source: 'acp-json-rpc',
reason: 'artifact_echo',
suppressed_chars: 4096,
opened_blocks: 1,
closed_blocks: 1,
file_count: 1,
files: ['index.html'],
},
metadata: {
diagnostic_name: 'acp_artifact_text_suppression',
},
});
});
it('marks trace-safe object manifests partial when object accounting is incomplete', async () => {
await writeAppCfg({
installationId: 'install-uuid-1',
telemetry: { metrics: true, content: true, artifactManifest: true },
});
const fetchSpy = vi
.fn()
.mockResolvedValue(new Response('{}', { status: 207 }));
process.env.LANGFUSE_PUBLIC_KEY = 'pk';
process.env.LANGFUSE_SECRET_KEY = 'sk';
try {
await reportRunCompletedFromDaemon({
db: makeDbWithListMessages({
'conv-1': [
{
id: 'user-1',
role: 'user',
content: 'Use this reference.',
attachments: [{ path: 'uploads/brand.pdf', kind: 'file' }],
},
{
id: 'msg-1',
role: 'assistant',
content: 'Here is a draft …',
producedFiles: [{ name: 'index.html', kind: 'html' }],
},
],
}),
dataDir,
run: makeRun() as any,
fetchImpl: fetchSpy as any,
});
} finally {
delete process.env.LANGFUSE_PUBLIC_KEY;
delete process.env.LANGFUSE_SECRET_KEY;
}
const init = fetchSpy.mock.calls[0]![1] as RequestInit;
const batch = JSON.parse(init.body as string).batch as any[];
const trace = batch[0].body;
expect(trace.metadata.manifest_completeness).toBe('partial');
expect(trace.metadata.attachment_manifest[0]).toMatchObject({
object_class: 'attachment',
status: 'partial',
reason: 'size_unavailable',
});
expect(trace.metadata.artifact_manifest[0]).toMatchObject({
object_class: 'artifact',
status: 'partial',
reason: 'size_unavailable',
});
});
it('derives production object uploads from the telemetry relay while keeping bodies out of Langfuse', async () => {
await writeAppCfg({
installationId: 'install-uuid-1',
telemetry: { metrics: true, content: true, artifactManifest: true },
});
const projectDir = path.join(dataDir, 'projects', 'proj-1');
await mkdir(projectDir, { recursive: true });
await writeFile(path.join(projectDir, 'brief.txt'), 'private attachment body');
await writeFile(path.join(projectDir, 'index.html'), '<!doctype html><h1>private artifact</h1>');
const fetchSpy = vi.fn(async (url: string, init: RequestInit) => {
if (url.includes('/api/objects/authorize')) {
const parsed = JSON.parse(init.body as string) as {
objects: Array<{ storage_ref: string; sha256: string; size_bytes: number }>;
};
expect(parsed.objects).toHaveLength(2);
return new Response(JSON.stringify({ upload_token: 'upload-token' }), { status: 200 });
}
if (url.includes('/api/objects/batch')) {
const parsed = JSON.parse(init.body as string) as {
upload_token: string;
objects: Array<{ storage_ref: string; content_base64: string }>;
};
expect(parsed.upload_token).toBe('upload-token');
expect(parsed.objects).toHaveLength(2);
return new Response(
JSON.stringify({
objects: parsed.objects.map((object) => ({
storage_ref: object.storage_ref,
status: 'available',
size_bytes: Buffer.from(object.content_base64, 'base64').byteLength,
sha256: `sha256:${object.storage_ref.split('/').at(-1)}`,
})),
}),
{ status: 200 },
);
}
return new Response('{}', { status: 207 });
});
const priorNodeEnv = process.env.NODE_ENV;
process.env.NODE_ENV = 'production';
process.env.OPEN_DESIGN_TELEMETRY_RELAY_URL = 'https://telemetry.open-design.ai/api/langfuse';
process.env.LANGFUSE_PUBLIC_KEY = 'pk';
process.env.LANGFUSE_SECRET_KEY = 'sk';
try {
await reportRunCompletedFromDaemon({
db: makeDbWithListMessages({
'conv-1': [
{
id: 'user-1',
role: 'user',
content: 'Use this reference.',
attachments: [
{
path: 'brief.txt',
name: 'brief.txt',
size: 'private attachment body'.length,
},
],
},
{
id: 'msg-1',
role: 'assistant',
content: 'Done.',
producedFiles: [{ name: 'index.html', kind: 'html', size: 41 }],
},
],
}),
dataDir,
run: makeRun({
userPrompt: 'Use this reference.',
projectAttachmentPaths: ['brief.txt'],
}) as any,
fetchImpl: fetchSpy as any,
});
} finally {
if (priorNodeEnv === undefined) {
delete process.env.NODE_ENV;
} else {
process.env.NODE_ENV = priorNodeEnv;
}
delete process.env.OPEN_DESIGN_TELEMETRY_RELAY_URL;
delete process.env.LANGFUSE_PUBLIC_KEY;
delete process.env.LANGFUSE_SECRET_KEY;
}
expect(fetchSpy).toHaveBeenCalledTimes(4);
expect(fetchSpy.mock.calls[0]![0]).toContain('/api/langfuse');
expect(fetchSpy.mock.calls[1]![0]).toBe('https://telemetry.open-design.ai/api/objects/authorize');
expect(fetchSpy.mock.calls[2]![0]).toBe('https://telemetry.open-design.ai/api/objects/batch');
expect(fetchSpy.mock.calls[3]![0]).toContain('/api/langfuse');
const init = fetchSpy.mock.calls[3]![1] as RequestInit;
const langfuseBody = init.body as string;
expect(langfuseBody).not.toContain('private attachment body');
expect(langfuseBody).not.toContain('<!doctype html><h1>private artifact</h1>');
const batch = JSON.parse(langfuseBody).batch as any[];
const trace = batch[0].body;
expect(trace.metadata.manifest_completeness).toBe('complete');
expect(trace.metadata.attachment_manifest[0]).toMatchObject({
object_class: 'attachment',
status: 'ok',
stored_in_open_design: true,
size_bytes: 'private attachment body'.length,
});
expect(trace.metadata.artifact_manifest[0]).toMatchObject({
object_class: 'artifact',
status: 'ok',
stored_in_open_design: true,
size_bytes: '<!doctype html><h1>private artifact</h1>'.length,
});
});
it('uploads trace objects with worker-issued authority before reporting Langfuse manifests', async () => {
await writeAppCfg({
installationId: 'install-uuid-1',
telemetry: { metrics: true, content: true, artifactManifest: true },
});
const projectDir = path.join(dataDir, 'projects', 'proj-1');
await mkdir(projectDir, { recursive: true });
await writeFile(
path.join(projectDir, 'brief.txt'),
'attachment body should stay out of langfuse',
);
await writeFile(
path.join(projectDir, 'index.html'),
'<!doctype html><h1>artifact body</h1>',
);
const tailMarker = 'TAIL_MARKER_SHOULD_NOT_REACH_LANGFUSE';
const prompt = `${'长'.repeat(70 * 1024)}${tailMarker}`;
const fetchSpy = vi.fn(async (url: string, init: RequestInit) => {
if (url.includes('/api/objects/authorize')) {
const parsed = JSON.parse(init.body as string) as {
objects: Array<{ storage_ref: string; sha256: string; size_bytes: number }>;
};
expect(parsed.objects).toHaveLength(3);
expect(parsed.objects.every((object) => /^sha256:[a-f0-9]{64}$/.test(object.sha256)))
.toBe(true);
expect(parsed.objects.map((object) => object.size_bytes)).toEqual([
'attachment body should stay out of langfuse'.length,
'<!doctype html><h1>artifact body</h1>'.length,
Buffer.byteLength(prompt, 'utf8'),
]);
return new Response(JSON.stringify({ upload_token: 'upload-token' }), { status: 200 });
}
if (url.includes('/api/objects/batch')) {
const parsed = JSON.parse(init.body as string) as {
upload_token: string;
objects: Array<{ storage_ref: string; content_base64: string }>;
};
expect(parsed.upload_token).toBe('upload-token');
expect(parsed.objects).toHaveLength(3);
return new Response(
JSON.stringify({
objects: parsed.objects.map((object) => ({
storage_ref: object.storage_ref,
status: 'available',
size_bytes: Buffer.from(object.content_base64, 'base64').byteLength,
sha256: `sha256:${object.storage_ref.split('/').at(-1)}`,
})),
}),
{ status: 200 },
);
}
return new Response('{}', { status: 207 });
});
process.env.OPEN_DESIGN_TELEMETRY_RELAY_URL = 'https://telemetry.open-design.ai/api/langfuse';
process.env.LANGFUSE_PUBLIC_KEY = 'pk';
process.env.LANGFUSE_SECRET_KEY = 'sk';
try {
await reportRunCompletedFromDaemon({
db: makeDbWithListMessages({
'conv-1': [
{
id: 'user-1',
role: 'user',
content: prompt,
attachments: [
{
path: 'brief.txt',
name: 'brief.txt',
size: 'attachment body should stay out of langfuse'.length,
},
],
},
{
id: 'msg-1',
role: 'assistant',
content: 'done',
producedFiles: [{ name: 'index.html', kind: 'html', size: 35 }],
},
],
}),
dataDir,
run: makeRun({
userPrompt: prompt,
projectAttachmentPaths: ['brief.txt'],
}) as any,
fetchImpl: fetchSpy as any,
});
} finally {
delete process.env.OPEN_DESIGN_TELEMETRY_RELAY_URL;
delete process.env.LANGFUSE_PUBLIC_KEY;
delete process.env.LANGFUSE_SECRET_KEY;
}
expect(fetchSpy).toHaveBeenCalledTimes(4);
expect(fetchSpy.mock.calls[0]![0]).toContain('/api/langfuse');
const registrationBody = JSON.parse(fetchSpy.mock.calls[0]![1]!.body as string).batch as any[];
const registrationTrace = registrationBody[0].body;
expect(registrationTrace.metadata.attachment_manifest[0]).not.toHaveProperty('reason');
expect(registrationTrace.metadata.artifact_manifest[0]).not.toHaveProperty('reason');
expect(registrationTrace.metadata.input_text_snapshot_manifest[0]).not.toHaveProperty('reason');
expect(fetchSpy.mock.calls[1]![0]).toContain('/api/objects/authorize');
expect(fetchSpy.mock.calls[2]![0]).toContain('/api/objects/batch');
expect(fetchSpy.mock.calls[3]![0]).toContain('/api/langfuse');
const langfuseInit = fetchSpy.mock.calls[3]![1] as RequestInit;
const langfuseBody = langfuseInit.body as string;
expect(langfuseBody).not.toContain('attachment body should stay out of langfuse');
expect(langfuseBody).not.toContain('<!doctype html><h1>artifact body</h1>');
expect(langfuseBody).not.toContain(tailMarker);
const batch = JSON.parse(langfuseBody).batch as any[];
const trace = batch[0].body;
expect(trace.metadata.manifest_completeness).toBe('complete');
expect(trace.metadata.attachment_manifest).toHaveLength(1);
expect(trace.metadata.artifact_manifest).toHaveLength(1);
expect(trace.metadata.input_text_snapshot_manifest).toHaveLength(1);
expect(trace.metadata.attachment_manifest[0]).toMatchObject({
object_class: 'attachment',
status: 'ok',
stored_in_open_design: true,
source: 'user_upload',
retention_policy: 'observability_90d',
access_scope: 'project',
sensitivity: 'private',
});
expect(trace.metadata.attachment_manifest[0]).not.toHaveProperty('reason');
expect(trace.metadata.artifact_manifest[0]).toMatchObject({
object_class: 'artifact',
status: 'ok',
stored_in_open_design: true,
source: 'agent_generated',
retention_policy: 'observability_90d',
});
expect(trace.metadata.artifact_manifest[0]).not.toHaveProperty('reason');
expect(trace.metadata.input_text_snapshot_manifest[0]).toMatchObject({
object_class: 'input_text_snapshot',
status: 'ok',
stored_in_open_design: true,
source: 'user_prompt',
});
expect(trace.metadata.input_text_snapshot_manifest[0]).not.toHaveProperty('reason');
expect(JSON.stringify(trace.metadata)).toContain(
'od://objects/workspaces/unknown/projects/proj-1/runs/run-id-1',
);
});
it('registers object upload authority through the object relay when traces use direct Langfuse', async () => {
await writeAppCfg({
installationId: 'install-uuid-1',
telemetry: { metrics: true, content: true, artifactManifest: true },
});
const projectDir = path.join(dataDir, 'projects', 'proj-1');
await mkdir(projectDir, { recursive: true });
await writeFile(path.join(projectDir, 'index.html'), '<!doctype html><h1>artifact body</h1>');
const fetchSpy = vi.fn(async (url: string, init: RequestInit) => {
if (url.includes('/api/objects/authorize')) {
const parsed = JSON.parse(init.body as string) as {
objects: Array<{ storage_ref: string; object_class: string }>;
};
expect(parsed.objects).toHaveLength(1);
expect(parsed.objects[0]).toMatchObject({ object_class: 'artifact' });
return new Response(JSON.stringify({ upload_token: 'upload-token' }), { status: 200 });
}
if (url.includes('/api/objects/batch')) {
const parsed = JSON.parse(init.body as string) as {
objects: Array<{ storage_ref: string; content_base64: string }>;
};
return new Response(
JSON.stringify({
objects: parsed.objects.map((object) => ({
storage_ref: object.storage_ref,
status: 'available',
size_bytes: Buffer.from(object.content_base64, 'base64').byteLength,
sha256: 'sha256:uploaded-artifact',
})),
}),
{ status: 200 },
);
}
return new Response('{}', { status: 207 });
});
process.env.OPEN_DESIGN_OBJECT_RELAY_URL = 'https://telemetry.open-design.ai/api/objects/batch';
process.env.LANGFUSE_PUBLIC_KEY = 'pk';
process.env.LANGFUSE_SECRET_KEY = 'sk';
try {
await reportRunCompletedFromDaemon({
db: makeDbWithListMessages({
'conv-1': [
{ id: 'user-1', role: 'user', content: 'Build it.' },
{
id: 'msg-1',
role: 'assistant',
content: 'done',
producedFiles: [{ name: 'index.html', kind: 'html', size: 35 }],
},
],
}),
dataDir,
run: makeRun() as any,
fetchImpl: fetchSpy as any,
});
} finally {
delete process.env.OPEN_DESIGN_OBJECT_RELAY_URL;
delete process.env.LANGFUSE_PUBLIC_KEY;
delete process.env.LANGFUSE_SECRET_KEY;
}
expect(fetchSpy).toHaveBeenCalledTimes(4);
expect(fetchSpy.mock.calls[0]![0]).toBe('https://telemetry.open-design.ai/api/langfuse');
expect(fetchSpy.mock.calls[1]![0]).toBe('https://telemetry.open-design.ai/api/objects/authorize');
expect(fetchSpy.mock.calls[2]![0]).toBe('https://telemetry.open-design.ai/api/objects/batch');
expect(fetchSpy.mock.calls[3]![0]).toBe('https://us.cloud.langfuse.com/api/public/ingestion');
const registrationBatch = JSON.parse(fetchSpy.mock.calls[0]![1]!.body as string).batch as any[];
const finalBatch = JSON.parse(fetchSpy.mock.calls[3]![1]!.body as string).batch as any[];
expect(registrationBatch[0].body.metadata.artifact_manifest[0]).toMatchObject({
object_class: 'artifact',
storage_ref: expect.stringContaining(
'od://objects/workspaces/unknown/projects/proj-1/runs/run-id-1/artifact/',
),
});
expect(finalBatch[0].body.metadata.artifact_manifest[0]).toMatchObject({
object_class: 'artifact',
status: 'ok',
stored_in_open_design: true,
});
});
it('uploads modified existing files from traceObjectFiles and records object summary metadata', async () => {
await writeAppCfg({
installationId: 'install-uuid-1',
telemetry: { metrics: true, content: true, artifactManifest: true },
});
const projectDir = path.join(dataDir, 'projects', 'proj-1');
await mkdir(projectDir, { recursive: true });
await writeFile(path.join(projectDir, 'existing.html'), '<!doctype html><h1>modified</h1>');
const uploadedFilenames: string[] = [];
const fetchSpy = vi.fn(async (url: string, init: RequestInit) => {
if (url.includes('/api/objects/authorize')) {
return new Response(JSON.stringify({ upload_token: 'upload-token' }), { status: 200 });
}
if (url.includes('/api/objects/batch')) {
const parsed = JSON.parse(init.body as string) as {
objects: Array<{ storage_ref: string; filename: string; content_base64: string }>;
};
uploadedFilenames.push(...parsed.objects.map((object) => object.filename));
return new Response(
JSON.stringify({
objects: parsed.objects.map((object) => ({
storage_ref: object.storage_ref,
status: 'available',
size_bytes: Buffer.from(object.content_base64, 'base64').byteLength,
sha256: 'sha256:uploaded-artifact',
})),
}),
{ status: 200 },
);
}
return new Response('{}', { status: 207 });
});
process.env.OPEN_DESIGN_OBJECT_RELAY_URL = 'https://telemetry.open-design.ai/api/objects/batch';
process.env.LANGFUSE_PUBLIC_KEY = 'pk';
process.env.LANGFUSE_SECRET_KEY = 'sk';
try {
await reportRunCompletedFromDaemon({
db: makeDbWithListMessages({
'conv-1': [
{ id: 'user-1', role: 'user', content: 'Update the existing page.' },
{
id: 'msg-1',
role: 'assistant',
content: 'done',
producedFiles: [],
traceObjectFiles: [
{
name: 'existing.html',
kind: 'html',
size: 34,
traceObjectReason: 'modified',
},
],
},
],
}),
dataDir,
run: makeRun() as any,
fetchImpl: fetchSpy as any,
});
} finally {
delete process.env.OPEN_DESIGN_OBJECT_RELAY_URL;
delete process.env.LANGFUSE_PUBLIC_KEY;
delete process.env.LANGFUSE_SECRET_KEY;
}
expect(uploadedFilenames).toEqual(['existing.html']);
const finalBatch = JSON.parse(fetchSpy.mock.calls.at(-1)![1]!.body as string).batch as any[];
expect(finalBatch[0].body.metadata.trace_object_summary).toEqual({
new_file_count: 0,
modified_file_count: 1,
recovered_file_count: 0,
candidate_file_count: 1,
uploaded_file_count: 1,
skipped_file_count: 0,
skip_reasons: {},
});
expect(finalBatch[0].body.metadata.artifacts).toEqual([
{ slug: 'existing.html', type: 'html', sizeBytes: 34 },
]);
});
it('derives manifest completeness from merged uploaded and fallback manifests', async () => {
await writeAppCfg({
installationId: 'install-uuid-1',
telemetry: { metrics: true, content: true, artifactManifest: true },
});
const projectDir = path.join(dataDir, 'projects', 'proj-1');
await mkdir(projectDir, { recursive: true });
await writeFile(path.join(projectDir, 'index.html'), '<!doctype html><h1>artifact body</h1>');
const fetchSpy = vi.fn(async (url: string, init: RequestInit) => {
if (url.includes('/api/objects/authorize')) {
const parsed = JSON.parse(init.body as string) as {
objects: Array<{ storage_ref: string; object_class: string }>;
};
expect(parsed.objects).toHaveLength(1);
expect(parsed.objects[0]).toMatchObject({ object_class: 'artifact' });
return new Response(JSON.stringify({ upload_token: 'upload-token' }), { status: 200 });
}
if (url.includes('/api/objects/batch')) {
const parsed = JSON.parse(init.body as string) as {
objects: Array<{ storage_ref: string; content_base64: string }>;
};
return new Response(
JSON.stringify({
objects: parsed.objects.map((object) => ({
storage_ref: object.storage_ref,
status: 'available',
size_bytes: Buffer.from(object.content_base64, 'base64').byteLength,
sha256: 'sha256:uploaded-artifact',
})),
}),
{ status: 200 },
);
}
return new Response('{}', { status: 207 });
});
process.env.OPEN_DESIGN_OBJECT_RELAY_URL = 'https://telemetry.open-design.ai/api/objects/batch';
process.env.OPEN_DESIGN_TELEMETRY_RELAY_URL = 'https://telemetry.open-design.ai/api/langfuse';
process.env.LANGFUSE_PUBLIC_KEY = 'pk';
process.env.LANGFUSE_SECRET_KEY = 'sk';
try {
await reportRunCompletedFromDaemon({
db: makeDbWithListMessages({
'conv-1': [
{
id: 'user-1',
role: 'user',
content: 'Use the attached reference.',
attachments: [{ path: 'uploads/brand.pdf', kind: 'file' }],
},
{
id: 'msg-1',
role: 'assistant',
content: 'done',
producedFiles: [{ name: 'index.html', kind: 'html', size: 35 }],
},
],
}),
dataDir,
run: makeRun() as any,
fetchImpl: fetchSpy as any,
});
} finally {
delete process.env.OPEN_DESIGN_OBJECT_RELAY_URL;
delete process.env.OPEN_DESIGN_TELEMETRY_RELAY_URL;
delete process.env.LANGFUSE_PUBLIC_KEY;
delete process.env.LANGFUSE_SECRET_KEY;
}
expect(fetchSpy).toHaveBeenCalledTimes(4);
expect(fetchSpy.mock.calls[0]![0]).toContain('/api/langfuse');
expect(fetchSpy.mock.calls[1]![0]).toContain('/api/objects/authorize');
expect(fetchSpy.mock.calls[2]![0]).toContain('/api/objects/batch');
const langfuseInit = fetchSpy.mock.calls[3]![1] as RequestInit;
const batch = JSON.parse(langfuseInit.body as string).batch as any[];
const trace = batch[0].body;
expect(trace.metadata.manifest_completeness).toBe('partial');
expect(trace.metadata.attachment_manifest[0]).toMatchObject({
object_class: 'attachment',
status: 'partial',
reason: 'size_unavailable',
});
expect(trace.metadata.artifact_manifest[0]).toMatchObject({
object_class: 'artifact',
status: 'ok',
stored_in_open_design: true,
});
});
it('reports imported project nested artifact manifests without leaking raw paths to Langfuse', async () => {
await writeAppCfg({
installationId: 'install-uuid-1',
telemetry: { metrics: true, content: true, artifactManifest: true },
});
const importedRoot = path.join(dataDir, 'imported-project');
const managedRoot = path.join(dataDir, 'projects', 'proj-1');
await mkdir(path.join(importedRoot, 'dist'), { recursive: true });
await mkdir(managedRoot, { recursive: true });
await writeFile(path.join(importedRoot, 'dist', 'index.html'), '<!doctype html><h1>imported</h1>');
await writeFile(path.join(managedRoot, 'index.html'), '<!doctype html><h1>managed</h1>');
const fetchSpy = vi.fn().mockResolvedValue(new Response('{}', { status: 207 }));
process.env.LANGFUSE_PUBLIC_KEY = 'pk';
process.env.LANGFUSE_SECRET_KEY = 'sk';
try {
await reportRunCompletedFromDaemon({
db: makeDbWithListMessages({
'conv-1': [
{ id: 'user-1', role: 'user', content: 'Create a nested artifact.' },
{
id: 'msg-1',
role: 'assistant',
content: 'Done.',
producedFiles: [
{ name: 'index.html', path: 'dist/index.html', kind: 'html', size: 31 },
],
},
],
}),
dataDir,
run: makeRun({
projectMetadata: { baseDir: importedRoot },
}) as any,
fetchImpl: fetchSpy as any,
});
} finally {
delete process.env.LANGFUSE_PUBLIC_KEY;
delete process.env.LANGFUSE_SECRET_KEY;
}
expect(fetchSpy).toHaveBeenCalledTimes(1);
expect(fetchSpy.mock.calls[0]![0]).not.toContain('/api/objects/batch');
const langfuseInit = fetchSpy.mock.calls[0]![1] as RequestInit;
const langfuseBody = langfuseInit.body as string;
expect(langfuseBody).not.toContain(importedRoot);
expect(langfuseBody).not.toContain('dist/index.html');
expect(langfuseBody).not.toContain('<!doctype html><h1>imported</h1>');
const batch = JSON.parse(langfuseBody).batch as any[];
const trace = batch[0].body;
expect(trace.metadata.artifacts).toEqual([
{ slug: 'index.html', type: 'html', sizeBytes: 31 },
]);
expect(trace.metadata.artifact_manifest[0]).toMatchObject({
object_class: 'artifact',
status: 'ok',
stored_in_open_design: true,
extension: 'html',
});
});
it('carries prior user attachments into follow-up generation traces', async () => {
await writeAppCfg({
installationId: 'install-uuid-1',
telemetry: { metrics: true, content: true, artifactManifest: true },
});
const fetchSpy = vi
.fn()
.mockResolvedValue(new Response('{}', { status: 207 }));
process.env.LANGFUSE_PUBLIC_KEY = 'pk';
process.env.LANGFUSE_SECRET_KEY = 'sk';
try {
await reportRunCompletedFromDaemon({
db: makeDbWithListMessages({
'conv-1': [
{
id: 'user-1',
role: 'user',
content: 'Use this private reference.',
attachments: [
{
path: 'uploads/private-reference.md',
name: 'private-reference.md',
size: 562,
},
],
},
{
id: 'assistant-1',
role: 'assistant',
content: 'Please answer the discovery form.',
},
{
id: 'user-2',
role: 'user',
content: '[form answers — discovery]\nBuild the artifact.',
},
{
id: 'msg-1',
role: 'assistant',
content: 'Done.',
producedFiles: [
{ name: 'index.html', kind: 'html', size: 4096 },
],
},
],
}),
dataDir,
run: (() => {
const now = Date.now();
return makeRun({
analyticsTelemetry: {
promptBuildStartAt: now - 4300,
promptBuildEndAt: now - 4200,
},
promptTelemetry: buildPromptStackTelemetry({
composedPrompt:
'# Instructions\n\nBuild the artifact.\n\n---\n# User request\n\n[form answers — discovery]\nBuild the artifact.',
sections: [
{ kind: 'daemonSystemPrompt', content: 'Build the artifact.' },
{
kind: 'userRequest',
content: '[form answers — discovery]\nBuild the artifact.',
},
],
}),
});
})() as any,
fetchImpl: fetchSpy as any,
});
} finally {
delete process.env.LANGFUSE_PUBLIC_KEY;
delete process.env.LANGFUSE_SECRET_KEY;
}
const init = fetchSpy.mock.calls[0]![1] as RequestInit;
const batch = JSON.parse(init.body as string).batch as any[];
const trace = batch[0].body;
const promptBuild = bodyOf(batch, 'span-create', 'prompt-build');
expect(trace.metadata.attachment_manifest).toHaveLength(1);
expect(trace.metadata.attachment_manifest[0]).toMatchObject({
object_class: 'attachment',
status: 'ok',
size_bytes: 562,
extension: 'md',
source: 'user_upload',
project_id: 'proj-1',
run_id: 'run-id-1',
});
expect(promptBuild.input.ingredients.attachment_refs).toEqual([
expect.objectContaining({
object_class: 'attachment',
status: 'ok',
size_bytes: 562,
extension: 'md',
source: 'user_upload',
attachment_id: trace.metadata.attachment_manifest[0].attachment_id,
}),
]);
const payload = JSON.stringify(batch);
expect(payload).not.toContain('private-reference.md');
expect(payload).not.toContain('uploads/private-reference.md');
});
it('counts duplicate streamed tool_use records by unique tool id', async () => {
await writeAppCfg({
installationId: 'install-uuid-1',
telemetry: { metrics: true, content: true, artifactManifest: false },
});
const fetchSpy = vi
.fn()
.mockResolvedValue(new Response('{}', { status: 207 }));
process.env.LANGFUSE_PUBLIC_KEY = 'pk';
process.env.LANGFUSE_SECRET_KEY = 'sk';
try {
await reportRunCompletedFromDaemon({
db: makeDbWithListMessages({
'conv-1': [
{ id: 'user-1', role: 'user', content: 'Create a file.' },
{ id: 'msg-1', role: 'assistant', content: 'Done.', producedFiles: [] },
],
}),
dataDir,
run: makeRun({
events: [
{
id: 1,
event: 'agent',
timestamp: Date.now() - 3000,
data: {
type: 'tool_use',
id: 'write-1',
name: 'Write',
input: { path: 'index.html', content: '<!doctype html>' },
},
},
{
id: 2,
event: 'agent',
timestamp: Date.now() - 2900,
data: {
type: 'tool_use',
id: 'write-1',
name: 'Write',
input: { path: 'index.html', content: '<!doctype html>' },
},
},
{
id: 3,
event: 'agent',
timestamp: Date.now() - 2500,
data: {
type: 'tool_result',
toolUseId: 'write-1',
content: 'wrote index.html',
isError: false,
},
},
],
}) as any,
fetchImpl: fetchSpy as any,
});
} finally {
delete process.env.LANGFUSE_PUBLIC_KEY;
delete process.env.LANGFUSE_SECRET_KEY;
}
const init = fetchSpy.mock.calls[0]![1] as RequestInit;
const batch = JSON.parse(init.body as string).batch as any[];
const trace = batch[0].body;
const toolSpans = batch.filter(
(item) => item.type === 'span-create' && item.body.name === 'tool:Write',
);
expect(trace.metadata.eventsSummary.toolCalls).toBe(1);
expect(trace.metadata.eventsSummary.errors).toBe(0);
expect(toolSpans).toHaveLength(1);
});
it('redacts content-tool payloads and local paths from tool observations', async () => {
await writeAppCfg({
installationId: 'install-uuid-1',
telemetry: { metrics: true, content: true, artifactManifest: true },
});
const fetchSpy = vi
.fn()
.mockResolvedValue(new Response('{}', { status: 207 }));
process.env.LANGFUSE_PUBLIC_KEY = 'pk';
process.env.LANGFUSE_SECRET_KEY = 'sk';
try {
await reportRunCompletedFromDaemon({
db: makeDbWithListMessages({
'conv-1': [
{
id: 'user-1',
role: 'user',
content: 'Use this private reference.',
attachments: [{ path: 'uploads/private-brand-reference.txt', name: 'private-brand-reference.txt', size: 247 }],
},
{
id: 'msg-1',
role: 'assistant',
content: 'Done.',
producedFiles: [],
},
],
}),
dataDir,
run: makeRun({
events: [
{
id: 1,
event: 'agent',
data: {
type: 'tool_use',
id: 'read-1',
name: 'Read',
input: { file_path: '/Users/alice/project/private-brand-reference.txt' },
},
},
{
id: 2,
event: 'agent',
data: {
type: 'tool_result',
toolUseId: 'read-1',
content: 'Private brand reference: do not upload raw content',
isError: false,
},
},
{
id: 3,
event: 'agent',
data: {
type: 'tool_use',
id: 'write-1',
name: 'Write',
input: { file_path: '/Users/alice/project/index.html', content: '<!doctype html><html>heavy</html>' },
},
},
{
id: 4,
event: 'agent',
data: {
type: 'tool_result',
toolUseId: 'write-1',
content: 'File created successfully at: /Users/alice/project/index.html',
isError: false,
},
},
],
}) as any,
fetchImpl: fetchSpy as any,
});
} finally {
delete process.env.LANGFUSE_PUBLIC_KEY;
delete process.env.LANGFUSE_SECRET_KEY;
}
const init = fetchSpy.mock.calls[0]![1] as RequestInit;
const batch = JSON.parse(init.body as string).batch as any[];
const read = bodyOf(batch, 'span-create', 'tool:Read');
const write = bodyOf(batch, 'span-create', 'tool:Write');
expect(read.input).toBe('[REDACTED:tool_input:content_tool:Read]');
expect(read.output).toBe('[REDACTED:tool_output:content_tool:Read]');
expect(write.input).toBe('[REDACTED:tool_input:content_tool:Write]');
expect(write.output).toBe('[REDACTED:tool_output:content_tool:Write]');
const payload = JSON.stringify(batch);
expect(payload).not.toContain('Private brand reference');
expect(payload).not.toContain('/Users/alice/project');
expect(payload).not.toContain('<!doctype html>');
});
it('forwards run prompt telemetry into trace and generation metadata', async () => {
await writeAppCfg({
installationId: 'install-uuid-1',
telemetry: { metrics: true, content: true, artifactManifest: false },
});
const promptTelemetry = buildPromptStackTelemetry({
composedPrompt:
'# Instructions\n\nUse /Users/alice/project\n\n---\n# User request\n\ndesign a coffee landing page',
sections: [
{ kind: 'daemonSystemPrompt', content: 'Use /Users/alice/project' },
{ kind: 'userRequest', content: 'design a coffee landing page' },
],
});
const fetchSpy = vi
.fn()
.mockResolvedValue(new Response('{}', { status: 207 }));
process.env.LANGFUSE_PUBLIC_KEY = 'pk';
process.env.LANGFUSE_SECRET_KEY = 'sk';
try {
await reportRunCompletedFromDaemon({
db: makeDbWithListMessages({
'conv-1': [{ id: 'msg-1', role: 'assistant', content: 'Here is a draft …' }],
}),
dataDir,
run: makeRun({ promptTelemetry } as any) as any,
fetchImpl: fetchSpy as any,
});
} finally {
delete process.env.LANGFUSE_PUBLIC_KEY;
delete process.env.LANGFUSE_SECRET_KEY;
}
const init = fetchSpy.mock.calls[0]![1] as RequestInit;
const batch = JSON.parse(init.body as string).batch as any[];
const trace = batch[0].body;
const generation = bodyOf(batch, 'generation-create', 'llm');
expect(trace.input).toBe('design a coffee landing page');
expect(generation.input).toMatchObject({
type: 'open-design.prompt-stack',
sections: [
expect.objectContaining({
kind: 'daemonSystemPrompt',
redactedContent: expect.stringContaining('[REDACTED:path]'),
}),
expect.objectContaining({
kind: 'userRequest',
redactedContent: 'design a coffee landing page',
}),
],
});
expect(trace.metadata.promptStack).toBeUndefined();
expect(generation.metadata.promptStack).toBeUndefined();
expect(trace.metadata.promptStack_sectionCount).toBe(2);
expect(generation.metadata.promptStack_sectionCount).toBe(2);
expect(trace.metadata.promptStack_promptFingerprint).toEqual(
generation.metadata.promptStack_promptFingerprint,
);
});
it('attaches turn-level config (model / reasoning / skill / DS) to trace + generation', async () => {
await writeAppCfg({
installationId: 'install-uuid-1',
telemetry: { metrics: true, content: true, artifactManifest: false },
});
const fetchSpy = vi
.fn()
.mockResolvedValue(new Response('{}', { status: 207 }));
process.env.LANGFUSE_PUBLIC_KEY = 'pk';
process.env.LANGFUSE_SECRET_KEY = 'sk';
try {
await reportRunCompletedFromDaemon({
db: makeDbWithListMessages({ 'conv-1': [] }),
dataDir,
run: makeRun({
model: 'claude-sonnet-4-5',
reasoning: 'high',
skillId: 'landing-page',
designSystemId: 'mission-control',
designSystemDigest: 'digest-abc',
designSystemSelectionSource: 'project',
promptCache: {
stablePromptHash: 'stable-hash-1',
hit: true,
missReason: null,
},
clientType: 'desktop',
}) as any,
appVersion: {
version: '0.5.0',
channel: 'beta',
packaged: true,
platform: 'darwin',
arch: 'arm64',
},
fetchImpl: fetchSpy as any,
});
} finally {
delete process.env.LANGFUSE_PUBLIC_KEY;
delete process.env.LANGFUSE_SECRET_KEY;
}
const init = fetchSpy.mock.calls[0]![1] as RequestInit;
const batch = JSON.parse(init.body as string).batch as any[];
const trace = batch[0].body;
const generation = bodyOf(batch, 'generation-create', 'llm');
// Turn-level: trace metadata + tags carry it for filtering / grouping.
expect(trace.metadata.model).toBe('claude-sonnet-4-5');
expect(trace.metadata.reasoning).toBe('high');
expect(trace.metadata.skillId).toBe('landing-page');
expect(trace.metadata.designSystemId).toBe('mission-control');
expect(trace.metadata.designSystemDigest).toBe('digest-abc');
expect(trace.metadata.designSystemSelectionSource).toBe('project');
expect(trace.metadata.stablePromptHash).toBe('stable-hash-1');
expect(trace.metadata.stablePromptCacheHit).toBe(true);
expect(trace.metadata.stablePromptCacheMissReason).toBeNull();
expect(trace.tags).toEqual(
expect.arrayContaining([
'model:claude-sonnet-4-5',
'skill:landing-page',
'ds:mission-control',
'client:desktop',
]),
);
// Runtime / build info on every trace.
expect(trace.metadata.appVersion).toBe('0.5.0');
expect(trace.metadata.appChannel).toBe('beta');
expect(trace.metadata.packaged).toBe(true);
expect(trace.metadata.clientType).toBe('desktop');
expect(typeof trace.metadata.os).toBe('string');
expect(typeof trace.metadata.nodeVersion).toBe('string');
// Generation: model is a first-class Langfuse field (not just metadata),
// and reasoning lands on modelParameters where Langfuse expects it.
expect(generation.model).toBe('claude-sonnet-4-5');
expect(generation.modelParameters).toEqual({ reasoning: 'high' });
});
it('labels turn.model with the agent-reported model on default-model runs', async () => {
await writeAppCfg({
installationId: 'install-uuid-2',
telemetry: { metrics: true, content: true, artifactManifest: false },
});
const fetchSpy = vi
.fn()
.mockResolvedValue(new Response('{}', { status: 207 }));
process.env.LANGFUSE_PUBLIC_KEY = 'pk';
process.env.LANGFUSE_SECRET_KEY = 'sk';
try {
const run = makeRun({
// Request did not pin a model (the `default` placeholder), so the
// resolved model must come from the agent's reported status event —
// matching the agent-reported fallback server.ts uses for PostHog.
model: 'default',
}) as any;
run.events.unshift({
id: 0,
event: 'agent',
timestamp: run.createdAt + 10,
data: { type: 'status', label: 'model', model: 'claude-opus-4-1' },
});
await reportRunCompletedFromDaemon({
db: makeDbWithListMessages({ 'conv-1': [] }),
dataDir,
run,
fetchImpl: fetchSpy as any,
});
} finally {
delete process.env.LANGFUSE_PUBLIC_KEY;
delete process.env.LANGFUSE_SECRET_KEY;
}
const init = fetchSpy.mock.calls[0]![1] as RequestInit;
const batch = JSON.parse(init.body as string).batch as any[];
const trace = batch[0].body;
const generation = bodyOf(batch, 'generation-create', 'llm');
expect(trace.metadata.model).toBe('claude-opus-4-1');
expect(trace.tags).toEqual(
expect.arrayContaining(['model:claude-opus-4-1']),
);
expect(generation.model).toBe('claude-opus-4-1');
});
it('forwards token usage for a totalTokens-only usage event', async () => {
await writeAppCfg({
installationId: 'install-uuid-3',
telemetry: { metrics: true, content: true, artifactManifest: false },
});
const fetchSpy = vi
.fn()
.mockResolvedValue(new Response('{}', { status: 207 }));
process.env.LANGFUSE_PUBLIC_KEY = 'pk';
process.env.LANGFUSE_SECRET_KEY = 'sk';
try {
const run = makeRun() as any;
// A provider that only reports an aggregate total — no input/output
// breakdown. scanRunEventsForUsageAnalytics still surfaces total_tokens,
// and the bridge must carry it through to Langfuse so the per-trace UI
// does not lose token visibility and stays consistent with PostHog.
run.events = [
{
id: 1,
event: 'agent',
timestamp: run.createdAt + 1000,
data: { type: 'usage', usage: { total_tokens: 512 } },
},
];
await reportRunCompletedFromDaemon({
db: makeDbWithListMessages({ 'conv-1': [] }),
dataDir,
run,
fetchImpl: fetchSpy as any,
});
} finally {
delete process.env.LANGFUSE_PUBLIC_KEY;
delete process.env.LANGFUSE_SECRET_KEY;
}
const init = fetchSpy.mock.calls[0]![1] as RequestInit;
const batch = JSON.parse(init.body as string).batch as any[];
const trace = batch[0].body;
const generation = bodyOf(batch, 'generation-create', 'llm');
// total_tokens must reach the trace metadata even with no input/output.
expect(trace.metadata.tokens).toBeTruthy();
expect(trace.metadata.tokens.total).toBe(512);
expect(trace.metadata.tokens.input).toBeUndefined();
expect(trace.metadata.tokens.output).toBeUndefined();
// …and onto the Langfuse generation usage so cost/token views populate.
expect(generation.usage.total).toBe(512);
});
it('uses the default model bucket for a default-model run with no status/model event', async () => {
await writeAppCfg({
installationId: 'install-uuid-4',
telemetry: { metrics: true, content: true, artifactManifest: false },
});
const fetchSpy = vi
.fn()
.mockResolvedValue(new Response('{}', { status: 207 }));
process.env.LANGFUSE_PUBLIC_KEY = 'pk';
process.env.LANGFUSE_SECRET_KEY = 'sk';
try {
// Request never pinned a concrete model (the `default` placeholder) and
// the agent never reported one. Keep this aligned with PostHog's
// model_id bucket so Langfuse traces remain filterable by model state.
await reportRunCompletedFromDaemon({
db: makeDbWithListMessages({ 'conv-1': [] }),
dataDir,
run: makeRun({ model: 'default' }) as any,
fetchImpl: fetchSpy as any,
});
} finally {
delete process.env.LANGFUSE_PUBLIC_KEY;
delete process.env.LANGFUSE_SECRET_KEY;
}
const init = fetchSpy.mock.calls[0]![1] as RequestInit;
const batch = JSON.parse(init.body as string).batch as any[];
const trace = batch[0].body;
const generation = bodyOf(batch, 'generation-create', 'llm');
expect(trace.metadata.model).toBe('default');
expect(trace.tags).toEqual(expect.arrayContaining(['model:default']));
expect(generation.model).toBe('default');
});
it('includes artifacts when content telemetry is on', async () => {
await writeAppCfg({
installationId: 'install-1',
telemetry: { metrics: true, content: true },
});
const messages: FakeMessage[] = [
{
id: 'msg-1',
role: 'assistant',
content: 'sensitive output',
producedFiles: [{ name: 'secret.html', kind: 'html', size: 1 }],
},
];
const fetchSpy = vi
.fn()
.mockResolvedValue(new Response('{}', { status: 207 }));
process.env.LANGFUSE_PUBLIC_KEY = 'pk';
process.env.LANGFUSE_SECRET_KEY = 'sk';
try {
await reportRunCompletedFromDaemon({
db: makeDbWithListMessages({ 'conv-1': messages }),
dataDir,
run: makeRun() as any,
fetchImpl: fetchSpy as any,
});
} finally {
delete process.env.LANGFUSE_PUBLIC_KEY;
delete process.env.LANGFUSE_SECRET_KEY;
}
const init = fetchSpy.mock.calls[0]![1] as RequestInit;
const trace = JSON.parse(init.body as string).batch[0].body;
expect(trace.input).toBe('design a coffee landing page');
expect(trace.output).toBe('sensitive output');
expect(trace.metadata.artifacts).toEqual([
{ slug: 'secret.html', type: 'html', sizeBytes: 1 },
]);
// tokens + eventsSummary are still in metadata since they're metrics
expect(trace.metadata.tokens).toEqual({
input: 100,
inputProvider: 100,
inputEffective: 100,
output: 200,
total: 300,
estimatedContext: 93,
cacheTokenSource: 'unavailable',
});
});
it('reports only the current user turn captured on the run', async () => {
await writeAppCfg({
installationId: 'install-1',
telemetry: { metrics: true, content: true },
});
const fetchSpy = vi
.fn()
.mockResolvedValue(new Response('{}', { status: 207 }));
process.env.LANGFUSE_PUBLIC_KEY = 'pk';
process.env.LANGFUSE_SECRET_KEY = 'sk';
try {
await reportRunCompletedFromDaemon({
db: makeDbWithListMessages({
'conv-1': [
{
id: 'msg-1',
role: 'assistant',
content: 'latest answer',
},
],
}),
dataDir,
run: makeRun({ userPrompt: 'post-consent revision' }) as any,
fetchImpl: fetchSpy as any,
});
} finally {
delete process.env.LANGFUSE_PUBLIC_KEY;
delete process.env.LANGFUSE_SECRET_KEY;
}
const init = fetchSpy.mock.calls[0]![1] as RequestInit;
const payload = init.body as string;
const batch = JSON.parse(payload).batch as any[];
expect(batch[0].body.input).toBe('post-consent revision');
expect(bodyOf(batch, 'span-create', 'agent-run').input).toBe(
'post-consent revision',
);
expect(payload).not.toContain('pre-consent brief');
});
it('passes status=failed and a clipped error message through', async () => {
await writeAppCfg({
installationId: 'install-1',
telemetry: { metrics: true, content: true },
});
const fetchSpy = vi
.fn()
.mockResolvedValue(new Response('{}', { status: 207 }));
process.env.LANGFUSE_PUBLIC_KEY = 'pk';
process.env.LANGFUSE_SECRET_KEY = 'sk';
try {
await reportRunCompletedFromDaemon({
db: makeDbWithListMessages({ 'conv-1': [] }),
dataDir,
run: makeRun({
status: 'failed',
events: [
{
id: 1,
event: 'error',
data: { error: { message: 'agent stream blew up' } },
},
],
}) as any,
fetchImpl: fetchSpy as any,
});
} finally {
delete process.env.LANGFUSE_PUBLIC_KEY;
delete process.env.LANGFUSE_SECRET_KEY;
}
const init = fetchSpy.mock.calls[0]![1] as RequestInit;
const batch = JSON.parse(init.body as string).batch as any[];
expect(batch[0].body.metadata.status).toBe('failed');
expect(batch[0].body.metadata.success).toBe(false);
expect(batch[0].body.metadata.error).toBe('agent stream blew up');
expect(bodyOf(batch, 'span-create', 'agent-run').level).toBe('ERROR');
expect(bodyOf(batch, 'generation-create', 'llm').level).toBe('ERROR');
expect(bodyOf(batch, 'generation-create', 'llm').statusMessage).toBe(
'agent stream blew up',
);
expect(bodyOf(batch, 'event-create', 'run-error').statusMessage).toBe(
'agent stream blew up',
);
});
it('adds redacted stderr tail metadata for failed runs', async () => {
await writeAppCfg({
installationId: 'install-1',
telemetry: { metrics: true, content: true },
});
const fetchSpy = vi
.fn()
.mockResolvedValue(new Response('{}', { status: 207 }));
const rawKey = `sk-${'a'.repeat(48)}`;
process.env.LANGFUSE_PUBLIC_KEY = 'pk';
process.env.LANGFUSE_SECRET_KEY = 'sk';
try {
await reportRunCompletedFromDaemon({
db: makeDbWithListMessages({ 'conv-1': [] }),
dataDir,
run: makeRun({
status: 'failed',
events: [
{ id: 1, event: 'stderr', data: { chunk: `provider 429 OPENAI_API_KEY=${rawKey}\n` } },
{ id: 2, event: 'error', data: { error: { message: 'provider failed' } } },
],
}) as any,
fetchImpl: fetchSpy as any,
});
} finally {
delete process.env.LANGFUSE_PUBLIC_KEY;
delete process.env.LANGFUSE_SECRET_KEY;
}
const init = fetchSpy.mock.calls[0]![1] as RequestInit;
const payload = init.body as string;
const batch = JSON.parse(payload).batch as any[];
expect(batch[0].body.metadata.stderr).toEqual({
tail: 'provider 429 OPENAI_API_KEY=[REDACTED:sk_key]',
lineCount: 1,
truncated: false,
});
expect(payload).not.toContain(rawKey);
});
it('survives a missing assistant message (web has not PUT yet)', async () => {
await writeAppCfg({
installationId: 'install-1',
telemetry: { metrics: true, content: true },
});
const fetchSpy = vi
.fn()
.mockResolvedValue(new Response('{}', { status: 207 }));
process.env.LANGFUSE_PUBLIC_KEY = 'pk';
process.env.LANGFUSE_SECRET_KEY = 'sk';
try {
await reportRunCompletedFromDaemon({
db: makeDbWithListMessages({ 'conv-1': [] }),
dataDir,
run: makeRun() as any,
fetchImpl: fetchSpy as any,
});
} finally {
delete process.env.LANGFUSE_PUBLIC_KEY;
delete process.env.LANGFUSE_SECRET_KEY;
}
const init = fetchSpy.mock.calls[0]![1] as RequestInit;
const trace = JSON.parse(init.body as string).batch[0].body;
expect(trace.input).toBe('design a coffee landing page');
// truncate() drops empty strings, so output is omitted entirely.
expect(trace.output).toBeUndefined();
});
it('uses the persisted terminal status when the in-memory run has not settled yet', async () => {
await writeAppCfg({
installationId: 'install-uuid-1',
telemetry: { metrics: true, content: true, artifactManifest: false },
});
const run = makeRun({
status: 'cancelRequested',
updatedAt: 1_700_000_009_000,
});
const fetchSpy = vi
.fn()
.mockResolvedValue(new Response('{}', { status: 207 }));
process.env.LANGFUSE_PUBLIC_KEY = 'pk';
process.env.LANGFUSE_SECRET_KEY = 'sk';
try {
await reportRunCompletedFromDaemon({
db: makeDbWithListMessages({ 'conv-1': [] }),
dataDir,
run: run as any,
persistedRunStatus: 'canceled',
persistedEndedAt: run.createdAt + 2500,
fetchImpl: fetchSpy as any,
});
} finally {
delete process.env.LANGFUSE_PUBLIC_KEY;
delete process.env.LANGFUSE_SECRET_KEY;
}
const init = fetchSpy.mock.calls[0]![1] as RequestInit;
const batch = JSON.parse(init.body as string).batch as any[];
const trace = batch[0].body;
const span = bodyOf(batch, 'span-create', 'agent-run');
expect(trace.metadata.status).toBe('canceled');
expect(trace.metadata.eventsSummary.durationMs).toBe(2500);
expect(span.metadata.status).toBe('canceled');
expect(span.endTime).toBe(new Date(run.createdAt + 2500).toISOString());
});
});
// listMessages reads from a `prepare(...).all(cid)` call against
// better-sqlite3. To avoid spinning up SQLite in unit tests we provide a
// stub that satisfies the same shape used in `apps/daemon/src/db.ts`.
function makeDbWithListMessages(messagesByConvo: Record<string, FakeMessage[]>) {
// Mirror db.ts: SELECT returns *Json columns and listMessages runs them
// through normalizeMessage which JSON.parses producedFilesJson into
// producedFiles. Tests pass producedFiles directly, so we round-trip
// through JSON.stringify to match the real-world shape.
return {
prepare(_sql: string) {
return {
all(cid: string) {
return (messagesByConvo[cid] ?? []).map((m) => ({
id: m.id,
role: m.role,
content: m.content,
agentId: null,
agentName: null,
runId: null,
runStatus: null,
lastRunEventId: null,
eventsJson: null,
attachmentsJson: m.attachments
? JSON.stringify(m.attachments)
: null,
commentAttachmentsJson: null,
producedFilesJson: m.producedFiles
? JSON.stringify(m.producedFiles)
: null,
traceObjectFilesJson: m.traceObjectFiles
? JSON.stringify(m.traceObjectFiles)
: null,
createdAt: 0,
startedAt: null,
endedAt: null,
position: 0,
}));
},
};
},
};
}