{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { mkdtemp, readdir, rm } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { compileBundle, loadCompiledBundle } from \"./bundle/compile.ts\";\nimport { isDigest } from \"./bundle/schema.ts\";\nimport { storeTrialComparison, trialEvidenceDigest } from \"./comparison.ts\";\nimport { ensureEvolutionConfiguration } from \"./evolve/config.ts\";\nimport {\n\tapplyInboxDecisions,\n\tgarbageCollectInbox,\n\ttype InboxGcResult,\n\tinitializeInboxLifecycle,\n\tlinkProposalInbox,\n\treadInboxEntry,\n\treadInboxLifecycleStates,\n\treopenProposalInbox,\n\tresolveInboxEntry,\n\tsettleProposalInbox,\n} from \"./inbox.ts\";\nimport { deterministicPreferenceId, PREFERENCES_PATH, readBundlePreferenceMemory } from \"./memory/preferences.ts\";\nimport {\n\ttype EvoBundleMigrationOptions,\n\tinferAgentDirectoryForEvoRoot,\n\tmigratePiDataToBundleSource,\n} from \"./migration.ts\";\nimport { type EvoPaths, ensureEvoLayout, getEvoPaths } from \"./paths.ts\";\nimport {\n\tapproveProposal,\n\tdeferProposal,\n\tloadProposal,\n\tproposalApproval,\n\trejectProposal,\n\treopenProposal,\n\tstageProposal,\n} from \"./proposal.ts\";\nimport { createRecorderStore, readSessionLog, resolveStoredPayload, type StoredInboxEntry } from \"./recorder/store.ts\";\nimport { collectEvidenceCorpus } from \"./reflect/evidence.ts\";\nimport { BundleRegistry, type BundleRegistryOptions } from \"./registry/registry.ts\";\nimport { atomicWriteJson, canonicalJson, durableUnlink, readJsonIfExists, sha256, withFileLock } from \"./storage.ts\";\nimport type { CompiledBundle, DataApprovalActivation, EvoStatus, Proposal, ProposalApproval } from \"./types.ts\";\n\ntype ServiceIntentAction = \"approve\" | \"reject\" | \"defer\" | \"reopen\" | \"keep\" | \"direct-keep\" | \"rollback\";\n\ninterface ServiceIntent {\n\tschemaVersion: 2;\n\taction: ServiceIntentAction;\n\tpayloadDigest: string;\n\toperationId: string;\n\tstateBeforeDigest: string;\n\tcreatedAt: string;\n\tcompletedAt?: string;\n\tcompletedStateDigest?: string;\n}\n\nfunction assertServiceIntent(\n\tvalue: unknown,\n\taction: ServiceIntentAction,\n\tpayloadDigest: string,\n): asserts value is ServiceIntent {\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n\t\tthrow new Error(\"Stored Evo-Pi service intent is invalid\");\n\t}\n\tconst intent = value as Record<string, unknown>;\n\tif (\n\t\tintent.schemaVersion !== 2 ||\n\t\tintent.action !== action ||\n\t\tintent.payloadDigest !== payloadDigest ||\n\t\ttypeof intent.operationId !== \"string\" ||\n\t\t!/^[A-Za-z0-9._:-]{1,256}$/.test(intent.operationId) ||\n\t\ttypeof intent.stateBeforeDigest !== \"string\" ||\n\t\t!isDigest(intent.stateBeforeDigest) ||\n\t\ttypeof intent.createdAt !== \"string\" ||\n\t\t!Number.isFinite(Date.parse(intent.createdAt)) ||\n\t\t(intent.completedAt !== undefined &&\n\t\t\t(typeof intent.completedAt !== \"string\" || !Number.isFinite(Date.parse(intent.completedAt)))) ||\n\t\t(intent.completedStateDigest !== undefined &&\n\t\t\t(typeof intent.completedStateDigest !== \"string\" || !isDigest(intent.completedStateDigest))) ||\n\t\t(intent.completedAt === undefined && intent.completedStateDigest !== undefined)\n\t) {\n\t\tthrow new Error(\"Stored Evo-Pi service intent is invalid\");\n\t}\n}\n\nexport class EvoService {\n\treadonly paths: EvoPaths;\n\treadonly registry: BundleRegistry;\n\n\tconstructor(paths: EvoPaths = getEvoPaths(), registryOptions: BundleRegistryOptions = {}) {\n\t\tthis.paths = paths;\n\t\tthis.registry = new BundleRegistry(paths, registryOptions);\n\t}\n\n\tasync init(enabledTools?: string[], migrationSources?: EvoBundleMigrationOptions): Promise<CompiledBundle> {\n\t\tawait this.registry.recoverPendingTransition();\n\t\tawait ensureEvoLayout(this.paths);\n\t\tawait ensureEvolutionConfiguration(this.paths);\n\t\tconst existingDigest = await this.registry.readStableDigest();\n\t\tif (existingDigest) return loadCompiledBundle(this.paths, existingDigest);\n\n\t\tconst sourceDirectory = await mkdtemp(join(this.paths.root, \".seed-\"));\n\t\ttry {\n\t\t\tconst migration = await migratePiDataToBundleSource({\n\t\t\t\tbundleSourceDirectory: sourceDirectory,\n\t\t\t\tsources: migrationSources,\n\t\t\t\tdefaultAgentDirectory: inferAgentDirectoryForEvoRoot(this.paths.root),\n\t\t\t});\n\t\t\tconst corePromptPaths = migration.assets\n\t\t\t\t.filter((asset) => asset.kind === \"custom-prompt\" || asset.kind === \"append-prompt\")\n\t\t\t\t.map((asset) => asset.targetPath);\n\t\t\tawait atomicWriteJson(join(sourceDirectory, \"policy.json\"), {\n\t\t\t\tschemaVersion: 1,\n\t\t\t\t...(enabledTools ? { enabledTools: [...new Set(enabledTools)].sort() } : {}),\n\t\t\t\tenabledFeatures: [],\n\t\t\t\t...(migration.promptPaths.length > 0\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tpromptOrder: migration.promptPaths,\n\t\t\t\t\t\t\tstablePromptPaths: migration.promptPaths,\n\t\t\t\t\t\t}\n\t\t\t\t\t: {}),\n\t\t\t\tcoreAssets: corePromptPaths,\n\t\t\t\tmodelRouting: {},\n\t\t\t\tvalidation: { requiredChecks: [] },\n\t\t\t\t...(migration.assets.length > 0 ? { managedSources: migration.assets } : {}),\n\t\t\t});\n\t\t\tconst bundle = await compileBundle({\n\t\t\t\tpaths: this.paths,\n\t\t\t\tsourceDirectory,\n\t\t\t\tparentDigest: null,\n\t\t\t\tsummary:\n\t\t\t\t\tmigration.assets.length > 0\n\t\t\t\t\t\t? \"Initial Evo-Pi data bundle migrated from Pi resources\"\n\t\t\t\t\t\t: \"Initial empty Evo-Pi data bundle\",\n\t\t\t});\n\t\t\tawait this.registry.initialize(bundle.digest);\n\t\t\treturn bundle;\n\t\t} finally {\n\t\t\tawait rm(sourceDirectory, { recursive: true, force: true });\n\t\t}\n\t}\n\n\tasync status(): Promise<EvoStatus> {\n\t\treturn this.registry.getStatus();\n\t}\n\n\tasync listProposals(): Promise<Proposal[]> {\n\t\tawait ensureEvoLayout(this.paths);\n\t\tconst proposals: Proposal[] = [];\n\t\tfor (const entry of await readdir(this.paths.proposals, { withFileTypes: true })) {\n\t\t\tif (!entry.isDirectory() || !entry.name.startsWith(\"p-\")) continue;\n\t\t\tproposals.push(await loadProposal(this.paths, entry.name));\n\t\t}\n\t\treturn proposals.sort((left, right) => right.createdAt.localeCompare(left.createdAt));\n\t}\n\n\tasync getProposal(id: string): Promise<Proposal> {\n\t\treturn loadProposal(this.paths, id);\n\t}\n\n\tasync note(sessionId: string, text: string): Promise<StoredInboxEntry> {\n\t\treturn this.writeInbox(sessionId, \"NOTE\", \"note\", text);\n\t}\n\n\tasync request(sessionId: string, text: string): Promise<StoredInboxEntry> {\n\t\treturn this.writeInbox(sessionId, \"REQUEST\", \"request\", text);\n\t}\n\n\tasync preference(sessionId: string, text: string): Promise<StoredInboxEntry> {\n\t\tconst normalized = text.trim();\n\t\tconst inbox = await this.writeInbox(sessionId, \"PREFERENCE\", \"preference\", normalized);\n\t\tawait applyInboxDecisions(\n\t\t\tthis.paths,\n\t\t\t[\n\t\t\t\t{\n\t\t\t\t\tfile: inbox.fileName,\n\t\t\t\t\tkind: \"preference\",\n\t\t\t\t\tinstruction: normalized,\n\t\t\t\t\treason: \"User explicitly recorded a durable preference\",\n\t\t\t\t},\n\t\t\t],\n\t\t\tnew Set([inbox.fileName]),\n\t\t);\n\t\tawait this.materializePreference(inbox.fileName);\n\t\treturn inbox;\n\t}\n\n\tasync materializePreference(file: string): Promise<Proposal | undefined> {\n\t\tconst [entry, states, stable] = await Promise.all([\n\t\t\treadInboxEntry(this.paths, file),\n\t\t\treadInboxLifecycleStates(this.paths),\n\t\t\tthis.registry.readStableDigest(),\n\t\t]);\n\t\tif (!stable) throw new Error(\"Evo-Pi registry is not initialized\");\n\t\tconst state = states.get(file) ?? (await initializeInboxLifecycle(this.paths, file));\n\t\tif (state.kind !== \"preference\" || !state.instruction) {\n\t\t\tthrow new Error(\"Inbox entry is not an explicitly classified durable preference\");\n\t\t}\n\t\tif (!entry.text.includes(state.instruction)) {\n\t\t\tthrow new Error(\"Durable preference instruction is not an exact inbox substring\");\n\t\t}\n\t\tconst events = await readSessionLog(this.paths, entry.sessionId);\n\t\tconst feedback = [...events]\n\t\t\t.reverse()\n\t\t\t.find((event) => event.type === \"explicit_feedback\" && event.inboxFile === file);\n\t\tif (!feedback || feedback.type !== \"explicit_feedback\") {\n\t\t\tthrow new Error(\"Explicit durable preference has no recorder evidence event\");\n\t\t}\n\t\tconst quote = await resolveStoredPayload(this.paths, feedback.text);\n\t\tif (typeof quote !== \"string\" || !quote.includes(state.instruction)) {\n\t\t\tthrow new Error(\"Recorder evidence does not contain the durable preference instruction\");\n\t\t}\n\t\tconst parent = await loadCompiledBundle(this.paths, stable);\n\t\tconst memory = await readBundlePreferenceMemory(parent);\n\t\tif (memory.preferences.some((preference) => preference.instruction === state.instruction)) {\n\t\t\tawait resolveInboxEntry(this.paths, file, \"Preference was already active\", \"materialized\");\n\t\t\tawait garbageCollectInbox(this.paths);\n\t\t\treturn undefined;\n\t\t}\n\t\tconst id = deterministicPreferenceId(state.instruction);\n\t\tif (memory.preferences.some((preference) => preference.id === id)) {\n\t\t\tthrow new Error(`Durable preference id collision: ${id}`);\n\t\t}\n\t\tconst candidateMemory = {\n\t\t\tschemaVersion: 1 as const,\n\t\t\tpreferences: [\n\t\t\t\t...memory.preferences,\n\t\t\t\t{\n\t\t\t\t\tid,\n\t\t\t\t\tinstruction: state.instruction,\n\t\t\t\t\tsource: { sessionId: entry.sessionId, sequence: feedback.sequence, quote },\n\t\t\t\t\taddedAt: new Date().toISOString(),\n\t\t\t\t},\n\t\t\t],\n\t\t};\n\t\tconst proposal = await stageProposal({\n\t\t\tpaths: this.paths,\n\t\t\tparentDigest: stable,\n\t\t\tdraft: {\n\t\t\t\tmotivation: \"Materialize an explicit user-authored durable preference.\",\n\t\t\t\texpectedEffect: \"The trusted harness applies the preference across future sessions.\",\n\t\t\t\trisk: \"The user can remove the preference through the same trusted control path.\",\n\t\t\t\tverifyPlan: \"Compile the bundle and verify exact append-only preference provenance.\",\n\t\t\t\ttrialPlan: \"No semantic experiment is required for an explicit user configuration change.\",\n\t\t\t\tsource: \"explicit-request\",\n\t\t\t\tevidence: [{ sessionId: entry.sessionId, sequence: feedback.sequence, quote }],\n\t\t\t\tinboxReferences: [file],\n\t\t\t\treplayScenarios: [],\n\t\t\t\tchanges: [\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: PREFERENCES_PATH,\n\t\t\t\t\t\tcontent: `${JSON.stringify(candidateMemory, undefined, \"\\t\")}\\n`,\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t},\n\t\t\tobservationsMarkdown: \"# Explicit durable preference\\n\\nMaterialized directly from exact user evidence.\",\n\t\t});\n\t\tif (proposal.tier !== \"T0\") throw new Error(\"Explicit durable preference did not classify as T0\");\n\t\treturn this.approve(proposal.id, proposalApproval(proposal));\n\t}\n\n\tasync forgetPreference(sessionId: string, preferenceId: string): Promise<StoredInboxEntry> {\n\t\tif (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(preferenceId)) {\n\t\t\tthrow new Error(\"Preference id is invalid\");\n\t\t}\n\t\treturn this.writeInbox(\n\t\t\tsessionId,\n\t\t\t\"REQUEST\",\n\t\t\t\"request\",\n\t\t\t`Remove the active durable preference with id ${preferenceId}`,\n\t\t);\n\t}\n\n\tasync gcInbox(dryRun = false): Promise<InboxGcResult> {\n\t\treturn garbageCollectInbox(this.paths, dryRun);\n\t}\n\n\tasync resolveInbox(file: string, reason: string): Promise<void> {\n\t\tawait resolveInboxEntry(this.paths, file, reason);\n\t\tawait garbageCollectInbox(this.paths);\n\t}\n\n\tasync approve(id: string, expected: ProposalApproval): Promise<Proposal> {\n\t\tconst proposal = await this.withIntent(\"approve\", { id, expected }, (idempotencyKey, expectedStateDigest) =>\n\t\t\tapproveProposal(this.paths, id, expected, undefined, this.registry, idempotencyKey, expectedStateDigest),\n\t\t);\n\t\tif (proposal.status === \"kept\") {\n\t\t\tawait settleProposalInbox(this.paths, proposal);\n\t\t\tawait garbageCollectInbox(this.paths);\n\t\t} else {\n\t\t\tawait linkProposalInbox(this.paths, proposal);\n\t\t}\n\t\treturn proposal;\n\t}\n\n\tasync approveComponent(\n\t\tid: string,\n\t\texpected: ProposalApproval,\n\t\tactivation: DataApprovalActivation,\n\t): Promise<Proposal> {\n\t\tconst proposal = await this.withIntent(\n\t\t\t\"approve\",\n\t\t\t{ id, expected, activation },\n\t\t\t(idempotencyKey, expectedStateDigest) =>\n\t\t\t\tapproveProposal(\n\t\t\t\t\tthis.paths,\n\t\t\t\t\tid,\n\t\t\t\t\texpected,\n\t\t\t\t\tundefined,\n\t\t\t\t\tthis.registry,\n\t\t\t\t\tidempotencyKey,\n\t\t\t\t\texpectedStateDigest,\n\t\t\t\t\tactivation,\n\t\t\t\t),\n\t\t);\n\t\tif (proposal.status === \"kept\") {\n\t\t\tawait settleProposalInbox(this.paths, proposal);\n\t\t\tawait garbageCollectInbox(this.paths);\n\t\t} else {\n\t\t\tawait linkProposalInbox(this.paths, proposal);\n\t\t}\n\t\treturn proposal;\n\t}\n\n\tasync reject(id: string, reason: string): Promise<Proposal> {\n\t\tconst proposal = await this.withIntent(\"reject\", { id, reason }, (idempotencyKey, expectedStateDigest) =>\n\t\t\trejectProposal(this.paths, id, reason, this.registry, idempotencyKey, expectedStateDigest),\n\t\t);\n\t\tawait reopenProposalInbox(this.paths, proposal, \"Linked proposal was rejected; input remains open\");\n\t\treturn proposal;\n\t}\n\n\tasync defer(id: string, reason: string, until?: string): Promise<Proposal> {\n\t\treturn this.withIntent(\"defer\", { id, reason, until: until ?? null }, (idempotencyKey, expectedStateDigest) =>\n\t\t\tdeferProposal(this.paths, id, reason, until, this.registry, idempotencyKey, expectedStateDigest),\n\t\t);\n\t}\n\n\tasync reopen(id: string, reason: string): Promise<Proposal> {\n\t\treturn this.withIntent(\"reopen\", { id, reason }, (idempotencyKey, expectedStateDigest) =>\n\t\t\treopenProposal(this.paths, id, reason, this.registry, idempotencyKey, expectedStateDigest),\n\t\t);\n\t}\n\n\tasync rollback(targetDigest: string | undefined, reason: string): Promise<{ from: string; to: string }> {\n\t\treturn this.withIntent(\n\t\t\t\"rollback\",\n\t\t\t{ targetDigest: targetDigest ?? null, reason },\n\t\t\tasync (idempotencyKey, expectedStateDigest) => {\n\t\t\t\tconst trial = await this.registry.readTrial();\n\t\t\t\tif (trial) {\n\t\t\t\t\tawait storeTrialComparison(this.paths, await loadProposal(this.paths, trial.proposalId), trial);\n\t\t\t\t}\n\t\t\t\tconst { from, to } = await this.registry.rollbackProposal(\n\t\t\t\t\ttargetDigest,\n\t\t\t\t\treason,\n\t\t\t\t\tidempotencyKey,\n\t\t\t\t\texpectedStateDigest,\n\t\t\t\t);\n\t\t\t\tif (trial) {\n\t\t\t\t\tawait reopenProposalInbox(\n\t\t\t\t\t\tthis.paths,\n\t\t\t\t\t\tawait loadProposal(this.paths, trial.proposalId),\n\t\t\t\t\t\t\"Trial rolled back; linked input reopened\",\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn { from, to };\n\t\t\t},\n\t\t);\n\t}\n\n\tasync directKeepComponentTrial(proposalId: string, reason: string): Promise<Proposal> {\n\t\tconst proposal = await this.withIntent(\n\t\t\t\"direct-keep\",\n\t\t\t{ proposalId, reason },\n\t\t\t(idempotencyKey, expectedStateDigest) =>\n\t\t\t\tthis.registry.directKeepComponentTrial({\n\t\t\t\t\tproposalId,\n\t\t\t\t\treason,\n\t\t\t\t\tidempotencyKey,\n\t\t\t\t\texpectedStateDigest,\n\t\t\t\t}),\n\t\t);\n\t\tawait settleProposalInbox(this.paths, proposal);\n\t\tawait garbageCollectInbox(this.paths);\n\t\treturn proposal;\n\t}\n\n\tasync keep(reason: string): Promise<Proposal> {\n\t\treturn this.withIntent(\"keep\", { reason }, async (idempotencyKey, expectedStateDigest) => {\n\t\t\tconst trial = await this.registry.readTrial();\n\t\t\tlet evidenceDigest: string | undefined;\n\t\t\tif (trial) {\n\t\t\t\tconst proposal = await loadProposal(this.paths, trial.proposalId);\n\t\t\t\tconst [corpus, storedComparison] = await Promise.all([\n\t\t\t\t\tcollectEvidenceCorpus(this.paths, { mode: \"full\", completedSessionsOnly: true }),\n\t\t\t\t\tstoreTrialComparison(this.paths, proposal, trial),\n\t\t\t\t]);\n\t\t\t\tevidenceDigest = trialEvidenceDigest(corpus.evidenceDigest, storedComparison.comparison.evidenceDigest);\n\t\t\t} else {\n\t\t\t\tconst stable = await this.registry.readStableDigest();\n\t\t\t\tconst completed = (await this.listProposals()).find(\n\t\t\t\t\t(proposal) => proposal.status === \"kept\" && proposal.candidateDigest === stable,\n\t\t\t\t);\n\t\t\t\tevidenceDigest = completed?.artifacts.retrospective?.evidence?.digest;\n\t\t\t}\n\t\t\tconst proposal = await this.registry.keepProposal(reason, idempotencyKey, expectedStateDigest, evidenceDigest);\n\t\t\tawait settleProposalInbox(this.paths, proposal);\n\t\t\tawait garbageCollectInbox(this.paths);\n\t\t\treturn proposal;\n\t\t});\n\t}\n\n\tasync pause(reason: string): Promise<void> {\n\t\tawait this.registry.pause(reason);\n\t}\n\n\tasync resume(reason: string): Promise<void> {\n\t\tawait this.registry.resume(reason);\n\t}\n\n\tprivate async withIntent<T>(\n\t\taction: ServiceIntentAction,\n\t\tpayload: unknown,\n\t\toperation: (idempotencyKey: string, expectedStateDigest: string) => Promise<T>,\n\t): Promise<T> {\n\t\tconst payloadDigest = sha256(canonicalJson({ action, payload }));\n\t\tconst intentPath = join(this.paths.intents, `${action}-${payloadDigest}.json`);\n\t\tconst lockName = `intent-${action}-${payloadDigest}`;\n\t\tconst selected = await withFileLock(this.paths, lockName, async () => {\n\t\t\tconst operationState = await this.registry.getOperationState();\n\t\t\tconst existing = await readJsonIfExists<ServiceIntent>(intentPath);\n\t\t\tif (existing) {\n\t\t\t\tassertServiceIntent(existing, action, payloadDigest);\n\t\t\t\tconst matchesState =\n\t\t\t\t\texisting.completedAt === undefined\n\t\t\t\t\t\t? existing.stateBeforeDigest === operationState.digest ||\n\t\t\t\t\t\t\texisting.operationId === operationState.latestOperationId\n\t\t\t\t\t\t: existing.completedStateDigest === operationState.digest;\n\t\t\t\tif (matchesState) {\n\t\t\t\t\treturn { intent: existing, expectedStateDigest: operationState.digest };\n\t\t\t\t}\n\t\t\t\tawait durableUnlink(intentPath);\n\t\t\t}\n\t\t\tconst created: ServiceIntent = {\n\t\t\t\tschemaVersion: 2,\n\t\t\t\taction,\n\t\t\t\tpayloadDigest,\n\t\t\t\toperationId: randomUUID(),\n\t\t\t\tstateBeforeDigest: operationState.digest,\n\t\t\t\tcreatedAt: new Date().toISOString(),\n\t\t\t};\n\t\t\tawait atomicWriteJson(intentPath, created);\n\t\t\treturn { intent: created, expectedStateDigest: operationState.digest };\n\t\t});\n\t\tconst result = await operation(selected.intent.operationId, selected.expectedStateDigest);\n\t\tconst completedState = await this.registry.getOperationState();\n\t\tawait withFileLock(this.paths, lockName, async () => {\n\t\t\tconst current = await readJsonIfExists<ServiceIntent>(intentPath);\n\t\t\tconst completion = {\n\t\t\t\tcompletedAt: new Date().toISOString(),\n\t\t\t\t...(completedState.latestOperationId === selected.intent.operationId\n\t\t\t\t\t? { completedStateDigest: completedState.digest }\n\t\t\t\t\t: {}),\n\t\t\t};\n\t\t\tif (!current) {\n\t\t\t\tawait atomicWriteJson(intentPath, { ...selected.intent, ...completion });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tassertServiceIntent(current, action, payloadDigest);\n\t\t\tif (current.operationId !== selected.intent.operationId) {\n\t\t\t\tthrow new Error(\"Evo-Pi service intent changed while the operation was running\");\n\t\t\t}\n\t\t\tif (!current.completedAt) {\n\t\t\t\tawait atomicWriteJson(intentPath, { ...current, ...completion });\n\t\t\t}\n\t\t});\n\t\treturn result;\n\t}\n\n\tprivate async writeInbox(\n\t\tsessionId: string,\n\t\tlabel: \"NOTE\" | \"REQUEST\" | \"PREFERENCE\",\n\t\tkind: \"note\" | \"request\" | \"preference\",\n\t\ttext: string,\n\t): Promise<StoredInboxEntry> {\n\t\tconst normalized = text.trim();\n\t\tif (!normalized) throw new Error(`${label.toLowerCase()} text must not be empty`);\n\t\tconst store = await createRecorderStore({\n\t\t\tpaths: this.paths,\n\t\t\tsessionId,\n\t\t\tbundleDigest: await this.registry.readStableDigest(),\n\t\t});\n\t\tconst inbox = await store.writeInbox(`${label}: ${normalized}`, \"extension\", kind);\n\t\tawait initializeInboxLifecycle(this.paths, inbox.fileName);\n\t\tif (kind === \"preference\") {\n\t\t\tawait store.append({\n\t\t\t\ttype: \"explicit_feedback\",\n\t\t\t\tsource: \"extension\",\n\t\t\t\ttext: await store.storePayload(normalized),\n\t\t\t\tinboxFile: inbox.fileName,\n\t\t\t});\n\t\t}\n\t\treturn inbox;\n\t}\n}\n"]}