Improve schema setup and Notion ingest UX

This commit is contained in:
Luca Martial 2026-05-11 12:34:33 -07:00
parent 155613c794
commit 72a4ace13c
21 changed files with 540 additions and 118 deletions

View file

@ -0,0 +1,99 @@
import { describe, expect, it } from 'vitest';
import type { ToolCallLogEntry } from './tool-call-logger.js';
import { createMutableToolTranscriptSummary, recordToolTranscriptEntry } from './tool-transcript-summary.js';
function entry(overrides: Partial<ToolCallLogEntry>): ToolCallLogEntry {
return {
ts: '2026-05-11T00:00:00.000Z',
wuKey: 'wu-1',
toolName: 'wiki_write',
durationMs: 1,
input: {},
...overrides,
};
}
describe('tool transcript summaries', () => {
it('keeps recovered wiki_write structured failures out of fatal failures', () => {
const summary = createMutableToolTranscriptSummary('wu-1', '/tmp/wu-1.jsonl');
recordToolTranscriptEntry(
summary,
entry({
input: { key: 'orbit-customers' },
output: { structured: { success: false, key: 'orbit-customers' } },
}),
);
recordToolTranscriptEntry(
summary,
entry({
input: { key: 'orbit-customers' },
output: { structured: { success: true, key: 'orbit-customers' } },
}),
);
expect(summary.errorCount).toBe(1);
expect(summary.fatalErrorCount).toBe(0);
});
it('keeps unrecovered structured write failures fatal', () => {
const summary = createMutableToolTranscriptSummary('wu-1', '/tmp/wu-1.jsonl');
recordToolTranscriptEntry(
summary,
entry({
input: { key: 'orbit-customers' },
output: { structured: { success: false, key: 'orbit-customers' } },
}),
);
expect(summary.errorCount).toBe(1);
expect(summary.fatalErrorCount).toBe(1);
});
it('treats a later sl_edit_source success as recovery for the same SL source', () => {
const summary = createMutableToolTranscriptSummary('wu-1', '/tmp/wu-1.jsonl');
recordToolTranscriptEntry(
summary,
entry({
toolName: 'sl_write_source',
input: { connectionId: 'warehouse', sourceName: 'orbit_customers' },
output: { structured: { success: false, sourceName: 'orbit_customers' } },
}),
);
recordToolTranscriptEntry(
summary,
entry({
toolName: 'sl_edit_source',
input: { connectionId: 'warehouse', sourceName: 'orbit_customers' },
output: { structured: { success: true, sourceName: 'orbit_customers' } },
}),
);
expect(summary.errorCount).toBe(1);
expect(summary.fatalErrorCount).toBe(0);
});
it('keeps thrown tool errors fatal even after a successful write', () => {
const summary = createMutableToolTranscriptSummary('wu-1', '/tmp/wu-1.jsonl');
recordToolTranscriptEntry(
summary,
entry({
input: { key: 'orbit-customers' },
error: { message: 'tool crashed' },
}),
);
recordToolTranscriptEntry(
summary,
entry({
input: { key: 'orbit-customers' },
output: { structured: { success: true, key: 'orbit-customers' } },
}),
);
expect(summary.errorCount).toBe(1);
expect(summary.fatalErrorCount).toBe(1);
});
});

View file

@ -0,0 +1,130 @@
import type { ToolCallLogEntry } from './tool-call-logger.js';
export interface MutableToolTranscriptSummary {
unitKey: string;
path: string;
toolCallCount: number;
errorCount: number;
fatalErrorCount: number;
toolNames: Set<string>;
hardErrorCount: number;
recoverableFailureCounts: Map<string, number>;
}
export function createMutableToolTranscriptSummary(unitKey: string, path: string): MutableToolTranscriptSummary {
return {
unitKey,
path,
toolCallCount: 0,
errorCount: 0,
fatalErrorCount: 0,
toolNames: new Set<string>(),
hardErrorCount: 0,
recoverableFailureCounts: new Map<string, number>(),
};
}
export function recordToolTranscriptEntry(summary: MutableToolTranscriptSummary, entry: ToolCallLogEntry): void {
summary.toolCallCount += 1;
summary.toolNames.add(entry.toolName);
if (entry.error) {
summary.errorCount += 1;
summary.hardErrorCount += 1;
refreshFatalErrorCount(summary);
return;
}
const recoverableFailureKey = recoverableStructuredFailureKey(entry);
if (recoverableFailureKey) {
summary.errorCount += 1;
summary.recoverableFailureCounts.set(
recoverableFailureKey,
(summary.recoverableFailureCounts.get(recoverableFailureKey) ?? 0) + 1,
);
refreshFatalErrorCount(summary);
return;
}
const recoveryKey = recoverableStructuredSuccessKey(entry);
if (recoveryKey) {
summary.recoverableFailureCounts.delete(recoveryKey);
}
refreshFatalErrorCount(summary);
}
function refreshFatalErrorCount(summary: MutableToolTranscriptSummary): void {
summary.fatalErrorCount =
summary.hardErrorCount + [...summary.recoverableFailureCounts.values()].reduce((sum, count) => sum + count, 0);
}
function recoverableStructuredFailureKey(entry: ToolCallLogEntry): string | null {
if (!isStructuredToolFailure(entry.output)) {
return null;
}
if (entry.toolName === 'wiki_write') {
return wikiTargetKey(entry);
}
if (entry.toolName === 'sl_write_source') {
return slTargetKey(entry);
}
return null;
}
function recoverableStructuredSuccessKey(entry: ToolCallLogEntry): string | null {
if (!isStructuredToolSuccess(entry.output)) {
return null;
}
if (entry.toolName === 'wiki_write') {
return wikiTargetKey(entry);
}
if (entry.toolName === 'sl_write_source' || entry.toolName === 'sl_edit_source') {
return slTargetKey(entry);
}
return null;
}
function isStructuredToolFailure(output: unknown): boolean {
return structuredSuccess(output) === false;
}
function isStructuredToolSuccess(output: unknown): boolean {
return structuredSuccess(output) === true;
}
function structuredSuccess(output: unknown): boolean | null {
const structured = recordField(output, 'structured');
const success = structured?.success;
return typeof success === 'boolean' ? success : null;
}
function wikiTargetKey(entry: ToolCallLogEntry): string | null {
const key = stringField(recordField(entry.output, 'structured'), 'key') ?? stringField(entry.input, 'key');
return key ? `wiki:${key}` : null;
}
function slTargetKey(entry: ToolCallLogEntry): string | null {
const structured = recordField(entry.output, 'structured');
const sourceName = stringField(structured, 'sourceName') ?? stringField(entry.input, 'sourceName');
if (!sourceName) {
return null;
}
const connectionId = stringField(entry.input, 'connectionId') ?? '';
return `sl:${connectionId}:${sourceName}`;
}
function recordField(value: unknown, field: string): Record<string, unknown> | null {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return null;
}
const nested = (value as Record<string, unknown>)[field];
return nested && typeof nested === 'object' && !Array.isArray(nested) ? (nested as Record<string, unknown>) : null;
}
function stringField(value: unknown, field: string): string | null {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return null;
}
const raw = (value as Record<string, unknown>)[field];
return typeof raw === 'string' && raw.length > 0 ? raw : null;
}