316 lines
8.9 KiB
TypeScript
316 lines
8.9 KiB
TypeScript
import { randomUUID } from 'node:crypto';
|
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
import { homedir } from 'node:os';
|
|
import { dirname, join } from 'node:path';
|
|
import type { NoteArtifactDoc } from '../modules/note-artifacts/types.js';
|
|
import type { NoteDoc } from '../modules/notes/types.js';
|
|
|
|
export const DEFAULT_PHASE1_ROOT = join(homedir(), '.bytelyst', 'ecosystem', 'phase1');
|
|
|
|
type TranscriptArtifact = {
|
|
id: string;
|
|
title: string | null;
|
|
summary: string | null;
|
|
sourceSurface: string;
|
|
createdAt: string;
|
|
tags: string[];
|
|
ownership: {
|
|
userId: string;
|
|
orgId?: string | null;
|
|
};
|
|
provenance: {
|
|
originProductId: string;
|
|
originActionId?: string | null;
|
|
sessionId?: string | null;
|
|
runId?: string | null;
|
|
approvalId?: string | null;
|
|
correlationId?: string | null;
|
|
lineage: Array<{
|
|
stepType: string;
|
|
productId: string;
|
|
actorType: 'user' | 'agent' | 'system';
|
|
timestamp: string;
|
|
}>;
|
|
};
|
|
payload: {
|
|
transcriptText: string;
|
|
transcriptSource: string;
|
|
language: string;
|
|
durationMs: number;
|
|
};
|
|
};
|
|
|
|
type EcosystemEvent = {
|
|
eventId: string;
|
|
eventName: string;
|
|
eventVersion: 1;
|
|
occurredAt: string;
|
|
productId: string;
|
|
sourceSurface: string;
|
|
userId: string | null;
|
|
orgId?: string | null;
|
|
sessionId?: string | null;
|
|
runId?: string | null;
|
|
artifactId?: string | null;
|
|
actor: {
|
|
actorType: 'user' | 'agent' | 'system' | 'device';
|
|
actorId?: string | null;
|
|
};
|
|
trace: {
|
|
correlationId: string | null;
|
|
causationId: string | null;
|
|
parentEventId: string | null;
|
|
};
|
|
payload: Record<string, unknown>;
|
|
};
|
|
|
|
type TranscriptCaptureEvent = EcosystemEvent & {
|
|
eventName: 'capture.transcript.created';
|
|
artifactId: string;
|
|
};
|
|
|
|
type NoteArtifactEnvelope = {
|
|
id: string;
|
|
artifactType: 'note';
|
|
schemaVersion: 1;
|
|
productId: 'notelett';
|
|
sourceSurface: string;
|
|
title: string;
|
|
summary: string;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
createdBy: {
|
|
actorType: 'agent';
|
|
actorId: string;
|
|
};
|
|
ownership: {
|
|
userId: string;
|
|
orgId?: string | null;
|
|
};
|
|
visibility: {
|
|
scope: 'private';
|
|
};
|
|
status: string;
|
|
tags: string[];
|
|
links: Array<{
|
|
relation: 'summarizes';
|
|
targetArtifactId: string;
|
|
}>;
|
|
provenance: TranscriptArtifact['provenance'];
|
|
payload: {
|
|
noteFormat: 'markdown';
|
|
body: string;
|
|
excerpt: string;
|
|
};
|
|
};
|
|
|
|
export function getPhase1Root(): string {
|
|
return process.env.BYTELYST_ECOSYSTEM_DIR ?? DEFAULT_PHASE1_ROOT;
|
|
}
|
|
|
|
export async function loadLatestTranscriptArtifact(root = getPhase1Root()): Promise<TranscriptArtifact> {
|
|
const raw = await readFile(join(root, 'indexes', 'latest-transcript.json'), 'utf-8');
|
|
return JSON.parse(raw) as TranscriptArtifact;
|
|
}
|
|
|
|
export async function loadLatestTranscriptCaptureEvent(
|
|
root = getPhase1Root()
|
|
): Promise<TranscriptCaptureEvent | null> {
|
|
try {
|
|
const raw = await readFile(join(root, 'indexes', 'latest-transcript-event.json'), 'utf-8');
|
|
return JSON.parse(raw) as TranscriptCaptureEvent;
|
|
} catch (error) {
|
|
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
return null;
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export function buildPhase1NoteImport(params: {
|
|
transcriptArtifact: TranscriptArtifact;
|
|
transcriptCaptureEvent?: TranscriptCaptureEvent | null;
|
|
workspaceId: string;
|
|
userId: string;
|
|
now?: string;
|
|
}): {
|
|
note: NoteDoc;
|
|
noteArtifactDoc: NoteArtifactDoc;
|
|
ecosystemNoteArtifact: NoteArtifactEnvelope;
|
|
createdEvent: EcosystemEvent;
|
|
linkedEvent: EcosystemEvent;
|
|
} {
|
|
const { transcriptArtifact, workspaceId, userId } = params;
|
|
const now = params.now ?? new Date().toISOString();
|
|
const noteId = `note_phase1_${randomUUID().replace(/-/g, '').slice(0, 12)}`;
|
|
const correlationId = transcriptArtifact.provenance.correlationId ?? `corr_${randomUUID().slice(0, 12)}`;
|
|
const runId = `run_note_${randomUUID().replace(/-/g, '').slice(0, 10)}`;
|
|
const title = transcriptArtifact.title || 'Imported transcript note';
|
|
const body = `# ${title}\n\n${transcriptArtifact.payload.transcriptText}`;
|
|
const excerpt = transcriptArtifact.summary || transcriptArtifact.payload.transcriptText.slice(0, 160);
|
|
const noteArtifactId = `art_note_${randomUUID().replace(/-/g, '').slice(0, 12)}`;
|
|
|
|
const note: NoteDoc = {
|
|
id: noteId,
|
|
productId: 'notelett',
|
|
workspaceId,
|
|
userId,
|
|
title,
|
|
body,
|
|
status: 'draft',
|
|
tags: Array.from(new Set([...transcriptArtifact.tags, 'ecosystem', 'phase1'])),
|
|
links: [transcriptArtifact.id],
|
|
sourceType: 'ecosystem-transcript',
|
|
sourceUri: transcriptArtifact.id,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
createdBy: userId,
|
|
updatedBy: userId,
|
|
agentId: 'phase1-transcript-importer',
|
|
};
|
|
|
|
const noteArtifactDoc: NoteArtifactDoc = {
|
|
id: `na_${randomUUID().replace(/-/g, '').slice(0, 12)}`,
|
|
productId: 'notelett',
|
|
workspaceId,
|
|
userId,
|
|
noteId,
|
|
artifactType: 'summary',
|
|
title: 'Imported transcript artifact',
|
|
description: `Transcript source ${transcriptArtifact.id}`,
|
|
blobPath: join(getPhase1Root(), 'artifacts', 'note', `${noteArtifactId}.json`),
|
|
contentType: 'application/json',
|
|
createdAt: now,
|
|
createdBy: userId,
|
|
updatedAt: now,
|
|
updatedBy: userId,
|
|
};
|
|
|
|
const ecosystemNoteArtifact: NoteArtifactEnvelope = {
|
|
id: noteArtifactId,
|
|
artifactType: 'note',
|
|
schemaVersion: 1,
|
|
productId: 'notelett',
|
|
sourceSurface: 'backend',
|
|
title,
|
|
summary: excerpt,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
createdBy: {
|
|
actorType: 'agent',
|
|
actorId: 'phase1-transcript-importer',
|
|
},
|
|
ownership: {
|
|
userId,
|
|
orgId: transcriptArtifact.ownership.orgId ?? null,
|
|
},
|
|
visibility: {
|
|
scope: 'private',
|
|
},
|
|
status: note.status,
|
|
tags: note.tags,
|
|
links: [
|
|
{
|
|
relation: 'summarizes',
|
|
targetArtifactId: transcriptArtifact.id,
|
|
},
|
|
],
|
|
provenance: {
|
|
...transcriptArtifact.provenance,
|
|
runId,
|
|
lineage: [
|
|
...transcriptArtifact.provenance.lineage,
|
|
{
|
|
stepType: 'note-created',
|
|
productId: 'notelett',
|
|
actorType: 'agent',
|
|
timestamp: now,
|
|
},
|
|
],
|
|
},
|
|
payload: {
|
|
noteFormat: 'markdown',
|
|
body,
|
|
excerpt,
|
|
},
|
|
};
|
|
|
|
const createdEvent: EcosystemEvent = {
|
|
eventId: `evt_note_created_${randomUUID().replace(/-/g, '').slice(0, 12)}`,
|
|
eventName: 'artifact.created',
|
|
eventVersion: 1,
|
|
occurredAt: now,
|
|
productId: 'notelett',
|
|
sourceSurface: 'backend',
|
|
userId,
|
|
orgId: transcriptArtifact.ownership.orgId ?? null,
|
|
sessionId: transcriptArtifact.provenance.sessionId ?? null,
|
|
runId,
|
|
artifactId: ecosystemNoteArtifact.id,
|
|
actor: {
|
|
actorType: 'agent',
|
|
actorId: 'phase1-transcript-importer',
|
|
},
|
|
trace: {
|
|
correlationId,
|
|
causationId: params.transcriptCaptureEvent?.eventId ?? null,
|
|
parentEventId: params.transcriptCaptureEvent?.eventId ?? null,
|
|
},
|
|
payload: {
|
|
artifactType: 'note',
|
|
title,
|
|
status: note.status,
|
|
},
|
|
};
|
|
|
|
const linkedEvent: EcosystemEvent = {
|
|
eventId: `evt_note_linked_${randomUUID().replace(/-/g, '').slice(0, 12)}`,
|
|
eventName: 'artifact.linked',
|
|
eventVersion: 1,
|
|
occurredAt: now,
|
|
productId: 'notelett',
|
|
sourceSurface: 'backend',
|
|
userId,
|
|
orgId: transcriptArtifact.ownership.orgId ?? null,
|
|
sessionId: transcriptArtifact.provenance.sessionId ?? null,
|
|
runId,
|
|
artifactId: ecosystemNoteArtifact.id,
|
|
actor: {
|
|
actorType: 'agent',
|
|
actorId: 'phase1-transcript-importer',
|
|
},
|
|
trace: {
|
|
correlationId,
|
|
causationId: createdEvent.eventId,
|
|
parentEventId: createdEvent.eventId,
|
|
},
|
|
payload: {
|
|
sourceArtifactId: ecosystemNoteArtifact.id,
|
|
targetArtifactId: transcriptArtifact.id,
|
|
relation: 'summarizes',
|
|
},
|
|
};
|
|
|
|
return { note, noteArtifactDoc, ecosystemNoteArtifact, createdEvent, linkedEvent };
|
|
}
|
|
|
|
export async function persistPhase1NoteOutputs(params: {
|
|
ecosystemNoteArtifact: NoteArtifactEnvelope;
|
|
createdEvent: EcosystemEvent;
|
|
linkedEvent: EcosystemEvent;
|
|
root?: string;
|
|
}): Promise<void> {
|
|
const root = params.root ?? getPhase1Root();
|
|
await writeJson(join(root, 'artifacts', 'note', `${params.ecosystemNoteArtifact.id}.json`), params.ecosystemNoteArtifact);
|
|
await writeJson(join(root, 'events', 'artifact.created', `${params.createdEvent.eventId}.json`), params.createdEvent);
|
|
await writeJson(join(root, 'events', 'artifact.linked', `${params.linkedEvent.eventId}.json`), params.linkedEvent);
|
|
await writeJson(join(root, 'indexes', 'latest-note.json'), params.ecosystemNoteArtifact);
|
|
await writeJson(join(root, 'indexes', 'latest-note-created-event.json'), params.createdEvent);
|
|
await writeJson(join(root, 'indexes', 'latest-note-linked-event.json'), params.linkedEvent);
|
|
}
|
|
|
|
async function writeJson(path: string, payload: unknown): Promise<void> {
|
|
await mkdir(dirname(path), { recursive: true });
|
|
await writeFile(path, `${JSON.stringify(payload, null, 2)}\n`, 'utf-8');
|
|
}
|