Files
goodbuddy/src/main/desktop-storage-contracts.ts
T

171 lines
12 KiB
TypeScript

import type { AssistantDatabase, SshProjectWrite } from './assistant/assistant-database'
import type { KnowledgeDatabase } from './knowledge/knowledge-database'
import type { AssistantStorageProgress } from '../shared/assistant-storage-contracts'
import type { GraphExtractionResult } from './knowledge/graph-extractor'
import type { JsonValue } from './knowledge/types'
import type { DesktopFileStorageDomains } from './desktop-storage-files'
import type { DesktopStorageRuntimeOperations } from './desktop-storage-runtime-operations'
import type { replaceDocumentGraph } from './desktop-storage-knowledge-operations'
export { storageDataBytes } from '../shared/storage-data-size'
// Explicit domain allowlists, not a SQL endpoint or a proxy for arbitrary objects.
export const assistantStorageMethods = [
'hasAttachmentOwner', 'clearAssistantData', 'listProjects', 'ensureChannelProjects',
'createProject', 'updateProject', 'createSshProject', 'updateSshProject', 'listProjectsReferencingSshHost',
'listSshHostProjectReferences', 'deleteProjectsReferencingSshHost', 'setProjectArchived',
'deleteProject', 'listConversations', 'listConversationSummaries', 'searchConversations',
'getConversation', 'setConversationPinned', 'isConversationStoryGraphEnabled',
'setConversationStoryGraphEnabled', 'repairConversationRuntimeSelections',
'replaceConversations', 'saveLocalConversations', 'branchLocalConversation',
'deleteLocalConversation', 'getOrCreateRemoteConversation', 'appendConversationMessage',
'appendRemoteConversationMessage', 'claimChannelEvent', 'releaseChannelEvent',
'enqueueChannelResult', 'markChannelResult', 'listUndeliveredChannelResults',
'listMagicNotes', 'listMagicNoteTags', 'renameMagicNoteTag', 'deleteMagicNoteTag',
'getMagicNote', 'getMagicNoteContext', 'createMagicNote', 'updateMagicNote',
'deleteMagicNote', 'createMagicNoteEntry', 'updateMagicNoteEntry', 'deleteMagicNoteEntry',
'getMagicNoteEntry', 'saveMagicNoteAnalysis', 'listMagicTodos', 'getMagicTodoStatus',
'searchMagicNoteSummaries', 'searchMagicNotes', 'getMagicTodo', 'updateMagicTodo',
'saveMagicTodoAnalysis', 'listPendingDelegationResults', 'getDelegationDeliveryStatus',
'saveDelegationResult', 'markDelegationDelivered', 'listTasks', 'getExecutionStats',
'startExecutionTiming', 'endExecutionTiming', 'getActivityHistory', 'replaceActivityHistory',
'updateActivityHistory', 'clearActivityHistory', 'getActivityHistoryPage',
'getActivityHistorySummary', 'reconcileActivityHistory', 'createTask',
'listRecoverableRemoteTasks', 'recordRemoteTaskQuestionArrival', 'recordRemoteTaskQuestionAnswer',
'endRecoverableRemoteTask', 'upsertModelUsageCall', 'recordSystemModelUsage',
'getTokenUsageSummary', 'updateTaskStatus', 'resolveAssistantSuggestionTask', 'appendTaskEvent',
'appendRemoteTaskEventOnce', 'appendRemoteTaskEventsOnce', 'appendRemoteConversationTaskEventOnce',
'appendRemoteConversationTaskEventsBatch', 'appendRemoteTaskEventsBatch',
'getHighestCommittedRemoteTaskEventSequence', 'getHighestCommittedRemoteTaskEventSequenceForTask',
'hasRemoteResponseTextAfterToolFailure', 'getRemoteTaskActivityStates', 'persistComputerControlAudit',
'listRecentComputerControlAudit', 'listArtifacts', 'getArtifact', 'createTextArtifact',
'saveConversationImageOperation', 'saveConversationImageSources',
'markUnfinishedImageOperationsUnconfirmed', 'createImageArtifact', 'createInlineArtifact',
'listMemories', 'createMemory', 'setMemoryStatus', 'removeMemory', 'listConversationQueueItems',
'listPendingScheduleQueueConversationIds', 'listPendingConversationQueueIds',
'getConversationQueueItem', 'getConversationUserQueuePayloadJson',
'isConversationUserQueueItemDispatching', 'enqueueConversationUserInput',
'claimConversationQueueItem', 'completeConversationUserQueueItem', 'releaseConversationUserQueueItem',
'removeConversationUserQueueItem', 'cancelConversationQueueItem', 'listSchedules',
'createSchedule', 'setScheduleEnabled', 'removeSchedule', 'queueDueSchedules',
'queueScheduleNow', 'completeTaskScheduleRun', 'completeScheduleRun', 'listHeartbeatConfigs',
'getHeartbeatConfig', 'createHeartbeatConfig', 'updateHeartbeatConfig', 'setHeartbeatPaused',
'removeHeartbeatConfig', 'listHeartbeatRuns', 'getHeartbeatRun', 'listHeartbeatEntries',
'claimDueHeartbeats', 'claimHeartbeatNow', 'renewHeartbeatLease', 'buildHeartbeatInput',
'collectIncrementalReview', 'resolveReviewScope', 'reviewSummary', 'reviewBackground',
'noChangeSupervisionRun', 'noChangeHeartbeatRun', 'completeHeartbeatTrigger',
'markHeartbeatNoChange', 'setHeartbeatSuggestionStatus', 'supervisionRunForHeartbeat',
'getHeartbeatEntry', 'listSupervisionCandidates', 'startSupervisionRun', 'failSupervisionRun',
'setHeartbeatProjection', 'listSupervisionActivity', 'saveSupervisionResult', 'readStoryGraph',
'getSupervisionResult', 'getSupervisionStories', 'listSupervisionResults', 'getSupervisionGraph',
'getSupervisionSource', 'applySupervisionEntityAction', 'applySupervisionRelationAction',
'completeHeartbeatRun', 'failHeartbeatRun', 'pruneHeartbeatHistory', 'listExperts',
'createExpert', 'updateExpert', 'removeExpert', 'getExpert', 'getProject', 'getTask',
'resolveSupervisionSuggestion'
] as const satisfies readonly (keyof AssistantDatabase)[]
export const knowledgeStorageMethods = [
'createKnowledgeBase', 'listKnowledgeBases', 'getKnowledgeBaseCounts', 'getKnowledgeBase',
'saveExternalBinding', 'updateKnowledgeBase', 'updateKnowledgeSettings',
'markKnowledgeChunkingRebuilt', 'markKnowledgeOntologyRebuilt', 'deleteKnowledgeBase',
'createKnowledgeTask', 'getKnowledgeTask', 'getActiveKnowledgeTaskByDedupeKey',
'getKnowledgeTaskByEmbeddingJobId', 'listKnowledgeTasks', 'listActiveKnowledgeTasks',
'updateKnowledgeTask', 'cancelKnowledgeTask', 'interruptActiveKnowledgeTasks', 'pruneKnowledgeTasks',
'upsertSource', 'listSources', 'listSourcesForSnapshot', 'getSource', 'removeSource',
'upsertDocument', 'getDocumentByParsedResultId', 'getDocument', 'listDocuments',
'listDocumentsForSource', 'listDocumentsForLibraryRebuild', 'listDocumentsForSnapshot',
'getDocumentChunkCounts', 'removeDocument', 'removeEvidenceForDocument',
'pruneUnreferencedGeneratedGraph', 'listChunks', 'listChunksPage', 'getChunkForReference',
'updateChunk', 'deleteChunk', 'listContextChunks', 'replaceDocumentEmbeddings',
'beginDocumentEmbeddingReplacement', 'beginPreparedDocumentEmbeddingReplacement',
'appendDocumentEmbeddingBatch', 'appendPreparedDocumentEmbeddingBatch',
'finishDocumentEmbeddingReplacement', 'discardDocumentEmbeddingReplacement',
'recordEmbeddingIndexError', 'getEmbeddingIndexState', 'getLastEmbeddingIndexJob',
'saveEmbeddingIndexJob', 'listEmbeddingIndexDocumentIds', 'countEmbeddingIndexDocuments',
'getEmbeddingIndexCoverage', 'getEmbeddingIndexDocument', 'vectorSearch', 'graphSearch',
'hybridSearch', 'hybridSearchWithDiagnostics', 'search', 'createEntity', 'getEntity',
'listEntities', 'findEntityByCanonicalName', 'listEntitiesForIdentity', 'updateEntity',
'deleteEntity', 'createRelation', 'getRelation', 'listRelations', 'findRelationByIdentity',
'updateRelation', 'deleteRelation', 'createEvidence', 'listEvidence', 'listGraphSnapshot',
'updateEvidence', 'deleteEvidence', 'mergeEntities'
] as const satisfies readonly (keyof KnowledgeDatabase)[]
export const repositoryStorageMethods = {
review: ['initialize', 'resume', 'load', 'unfinished', 'pause', 'cancel', 'setPhase', 'setStories',
'groups', 'chunk', 'save', 'hasSources', 'batches', 'progress', 'assertComplete', 'omitDeletedConversations', 'navigation', 'saveNavigation'],
stories: ['pending', 'candidates', 'otherFeatures', 'crossMembers', 'directCounts', 'splitInput', 'split', 'apply', 'list', 'unassignedCount', 'act', 'canUndo'],
experiences: ['candidates', 'markSmall', 'existing', 'apply', 'list', 'snapshot', 'act', 'restore'],
suggestions: ['candidates', 'save', 'list', 'get', 'countForHeartbeat'],
external: ['getInstance', 'listInstances', 'saveInstance', 'deleteInstance', 'getBinding', 'hasBinding', 'getBindingsForInstance', 'listBindings', 'saveBinding']
} as const
export type PublishDocumentOptions = Omit<NonNullable<Parameters<KnowledgeDatabase['publishDocument']>[2]>, 'afterChunksInserted'> & {
graph?: GraphExtractionResult
}
// AbortSignals are transport options; callbacks and live handles are never data.
export type StorageData<T> = T extends AbortSignal ? never
: T extends (...args: never[]) => unknown ? never
// JSON is already a serializable recursive type; do not instantiate it again.
: T extends JsonValue ? T
: T extends Date | Uint8Array ? T
: T extends Map<infer K, infer V> ? Map<StorageData<K>, StorageData<V>>
: T extends Set<infer V> ? Set<StorageData<V>>
: T extends object ? { [K in keyof T]: StorageData<T[K]> } : T
export type DesktopStorageDomains = {
assistant: Omit<Pick<AssistantDatabase, typeof assistantStorageMethods[number]>, 'createSshProject' | 'updateSshProject'> & {
supervisionContext(request: Parameters<AssistantDatabase['listSupervisionCandidates']>[0], signal?: AbortSignal): {
summary: ReturnType<AssistantDatabase['reviewSummary']>; background: ReturnType<AssistantDatabase['reviewBackground']>
}
createSshProject(write: Omit<SshProjectWrite, 'assertCurrent'>): ReturnType<AssistantDatabase['createSshProject']>
updateSshProject(projectId: string, expectedUpdatedAt: string, write: Omit<SshProjectWrite, 'assertCurrent'>): ReturnType<AssistantDatabase['updateSshProject']>
}
knowledge: Pick<KnowledgeDatabase, typeof knowledgeStorageMethods[number]> & {
publishDocument(input: Parameters<KnowledgeDatabase['publishDocument']>[0], chunks: Parameters<KnowledgeDatabase['publishDocument']>[1], options?: PublishDocumentOptions): ReturnType<KnowledgeDatabase['publishDocument']>
replaceDocumentGraph(...args: Parameters<typeof replaceDocumentGraph> extends [unknown, ...infer A] ? A : never): void
}
review: Pick<ReturnType<AssistantDatabase['supervisionReviewStore']>, typeof repositoryStorageMethods.review[number]>
stories: Pick<ReturnType<AssistantDatabase['supervisionStories']>, typeof repositoryStorageMethods.stories[number]>
experiences: Pick<ReturnType<AssistantDatabase['supervisionExperiences']>, typeof repositoryStorageMethods.experiences[number]>
suggestions: Pick<ReturnType<AssistantDatabase['supervisionSuggestions']>, typeof repositoryStorageMethods.suggestions[number]>
external: Pick<KnowledgeDatabase['externalStore'], typeof repositoryStorageMethods.external[number]>
runtime: DesktopStorageRuntimeOperations
} & DesktopFileStorageDomains
export type StorageDomain = keyof DesktopStorageDomains
export type StorageMethod<D extends StorageDomain> = keyof DesktopStorageDomains[D] & string
export type StorageArgs<D extends StorageDomain, M extends StorageMethod<D>> =
DesktopStorageDomains[D][M] extends (...args: infer A) => unknown ? StorageData<A> : never
export type StorageResult<D extends StorageDomain, M extends StorageMethod<D>> =
DesktopStorageDomains[D][M] extends (...args: never[]) => infer R ? StorageData<Awaited<R>> : never
export interface DesktopStorageOptions {
assistantPath: string
knowledgePath: string
defaultRootPath: string
userDataPath: string
/** Defaults to packaged sibling entries. Paths are supplied only by Main. */
readerWorkerPath?: string
upgradeWorkerPath?: string
/** In-memory decision retained across an explicit startup retry, never a write receipt. */
confirmedUpgrade?: { migrateNotes: boolean; reclaimSpace: boolean }
}
export type StorageChange = 'magicNotes' | 'magicTodos' | 'modelUsage' | 'executionStats'
export type StorageError = { name: string; message: string; code?: string; issues?: unknown; cause?: StorageError }
export type StorageRequest =
| { type: 'open'; options: DesktopStorageOptions }
| { type: 'call'; id: number; domain: StorageDomain; method: string; args: unknown[] }
| { type: 'cancel'; id: number }
| { type: 'close' }
export type StorageResponse =
| { type: 'ready' }
| { type: 'progress'; progress: AssistantStorageProgress }
| { type: 'upgrade'; upgrade?: DesktopStorageOptions['confirmedUpgrade'] }
| { type: 'changed'; domain: StorageChange }
| { type: 'result'; id: number; result: unknown }
| { type: 'error'; id: number; error: StorageError }
| { type: 'failed'; error: StorageError }
| { type: 'closed' }
/** Per-batch payload guidance for domain writers that split large saves. */
export const STORAGE_MAX_BYTES = 32 * 1024 * 1024