mirror-mind 0.0.1 → 0.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/hooks/log-assistant-reply.sh +85 -0
- package/.claude/hooks/log-session-end.sh +85 -0
- package/.claude/hooks/log-user-prompt.sh +85 -0
- package/.claude/hooks/mirror-inject.sh +85 -0
- package/.claude/hooks/session-start.sh +85 -0
- package/.gemini/hooks/log-assistant.sh +85 -0
- package/.gemini/hooks/log-user.sh +85 -0
- package/.gemini/hooks/session-end.sh +85 -0
- package/.gemini/hooks/session-start.sh +85 -0
- package/.pi/extensions/mirror-logger.ts +646 -0
- package/.pi/skills/mm-backup/SKILL.md +21 -0
- package/.pi/skills/mm-build/SKILL.md +1206 -0
- package/.pi/skills/mm-consolidate/SKILL.md +112 -0
- package/.pi/skills/mm-consult/SKILL.md +70 -0
- package/.pi/skills/mm-conversations/SKILL.md +16 -0
- package/.pi/skills/mm-discard/SKILL.md +39 -0
- package/.pi/skills/mm-explore/SKILL.md +255 -0
- package/.pi/skills/mm-help/SKILL.md +74 -0
- package/.pi/skills/mm-identity/SKILL.md +96 -0
- package/.pi/skills/mm-journal/SKILL.md +16 -0
- package/.pi/skills/mm-journey/SKILL.md +23 -0
- package/.pi/skills/mm-journeys/SKILL.md +15 -0
- package/.pi/skills/mm-memories/SKILL.md +17 -0
- package/.pi/skills/mm-mirror/SKILL.md +98 -0
- package/.pi/skills/mm-mute/SKILL.md +18 -0
- package/.pi/skills/mm-new/SKILL.md +18 -0
- package/.pi/skills/mm-recall/SKILL.md +17 -0
- package/.pi/skills/mm-release-notes/SKILL.md +52 -0
- package/.pi/skills/mm-seed/SKILL.md +19 -0
- package/.pi/skills/mm-shadow/SKILL.md +117 -0
- package/.pi/skills/mm-soul/SKILL.md +502 -0
- package/.pi/skills/mm-tasks/SKILL.md +24 -0
- package/.pi/skills/mm-update/SKILL.md +32 -0
- package/.pi/skills/mm-week/SKILL.md +28 -0
- package/.pi/skills/mm-welcome/SKILL.md +24 -0
- package/AGENTS.md +188 -0
- package/README.md +126 -9
- package/bin/loader.js +67 -0
- package/bin/mirror-hook.js +7 -0
- package/bin/mirror.js +7 -0
- package/docs/releases/index.md +100 -0
- package/docs/releases/pending-cutoffs.md +510 -0
- package/docs/releases/v0.10.0.md +123 -0
- package/docs/releases/v0.10.1.md +66 -0
- package/docs/releases/v0.10.10.md +51 -0
- package/docs/releases/v0.10.11.md +39 -0
- package/docs/releases/v0.10.12.md +57 -0
- package/docs/releases/v0.10.13.md +35 -0
- package/docs/releases/v0.10.14.md +42 -0
- package/docs/releases/v0.10.15.md +53 -0
- package/docs/releases/v0.10.16.md +35 -0
- package/docs/releases/v0.10.17.md +43 -0
- package/docs/releases/v0.10.2.md +64 -0
- package/docs/releases/v0.10.3.md +78 -0
- package/docs/releases/v0.10.4.md +42 -0
- package/docs/releases/v0.10.5.md +57 -0
- package/docs/releases/v0.10.6.md +47 -0
- package/docs/releases/v0.10.7.md +53 -0
- package/docs/releases/v0.10.8.md +49 -0
- package/docs/releases/v0.10.9.md +37 -0
- package/docs/releases/v0.11.0.md +86 -0
- package/docs/releases/v0.12.0.md +67 -0
- package/docs/releases/v0.13.0.md +64 -0
- package/docs/releases/v0.14.0.md +68 -0
- package/docs/releases/v0.15.0.md +48 -0
- package/docs/releases/v0.16.0.md +50 -0
- package/docs/releases/v0.16.1.md +55 -0
- package/docs/releases/v0.16.2.md +36 -0
- package/docs/releases/v0.17.0.md +50 -0
- package/docs/releases/v0.17.1.md +58 -0
- package/docs/releases/v0.17.2.md +42 -0
- package/docs/releases/v0.17.3.md +58 -0
- package/docs/releases/v0.18.0.md +84 -0
- package/docs/releases/v0.18.1.md +51 -0
- package/docs/releases/v0.18.2.md +69 -0
- package/docs/releases/v0.19.0.md +85 -0
- package/docs/releases/v0.19.1.md +46 -0
- package/docs/releases/v0.20.0.md +85 -0
- package/docs/releases/v0.21.0.md +61 -0
- package/docs/releases/v0.21.1.md +47 -0
- package/docs/releases/v0.22.0.md +77 -0
- package/docs/releases/v0.22.1.md +42 -0
- package/docs/releases/v0.23.0.md +105 -0
- package/docs/releases/v0.24.0.md +125 -0
- package/docs/releases/v0.25.0.md +107 -0
- package/docs/releases/v0.26.0.md +139 -0
- package/docs/releases/v0.26.1.md +65 -0
- package/docs/releases/v0.26.2.md +43 -0
- package/docs/releases/v0.27.0.md +63 -0
- package/docs/releases/v0.28.0.md +59 -0
- package/docs/releases/v0.29.0.md +61 -0
- package/docs/releases/v0.29.1.md +59 -0
- package/docs/releases/v0.29.2.md +59 -0
- package/docs/releases/v0.29.3.md +93 -0
- package/docs/releases/v0.30.0.md +75 -0
- package/docs/releases/v0.30.1.md +93 -0
- package/docs/releases/v0.30.2.md +66 -0
- package/docs/releases/v0.31.0.md +122 -0
- package/docs/releases/v0.31.1.md +142 -0
- package/docs/releases/v0.31.10.md +118 -0
- package/docs/releases/v0.31.11.md +88 -0
- package/docs/releases/v0.31.12.md +105 -0
- package/docs/releases/v0.31.13.md +99 -0
- package/docs/releases/v0.31.14.md +97 -0
- package/docs/releases/v0.31.2.md +115 -0
- package/docs/releases/v0.31.3.md +142 -0
- package/docs/releases/v0.31.4.md +117 -0
- package/docs/releases/v0.31.5.md +155 -0
- package/docs/releases/v0.31.6.md +132 -0
- package/docs/releases/v0.31.7.md +132 -0
- package/docs/releases/v0.31.8.md +120 -0
- package/docs/releases/v0.31.9.md +120 -0
- package/docs/releases/v0.32.0.md +261 -0
- package/docs/releases/v0.32.1.md +59 -0
- package/docs/releases/v0.33.0.md +164 -0
- package/docs/releases/v0.8.0.md +118 -0
- package/docs/releases/v0.9.0.md +98 -0
- package/docs/releases/v0.9.1.md +83 -0
- package/package.json +102 -6
- package/plugins/mirror-mind/.claude-plugin/plugin.json +17 -0
- package/plugins/mirror-mind/hooks/hooks.json +62 -0
- package/plugins/mirror-mind/hooks/log-assistant-reply.sh +98 -0
- package/plugins/mirror-mind/hooks/log-session-end.sh +98 -0
- package/plugins/mirror-mind/hooks/log-user-prompt.sh +98 -0
- package/plugins/mirror-mind/hooks/mirror-inject.sh +98 -0
- package/plugins/mirror-mind/hooks/session-start.sh +98 -0
- package/plugins/mirror-mind/skills/mm-backup/SKILL.md +27 -0
- package/plugins/mirror-mind/skills/mm-build/SKILL.md +1206 -0
- package/plugins/mirror-mind/skills/mm-consolidate/SKILL.md +112 -0
- package/plugins/mirror-mind/skills/mm-consult/SKILL.md +83 -0
- package/plugins/mirror-mind/skills/mm-conversations/SKILL.md +17 -0
- package/plugins/mirror-mind/skills/mm-discard/SKILL.md +40 -0
- package/plugins/mirror-mind/skills/mm-explore/SKILL.md +241 -0
- package/plugins/mirror-mind/skills/mm-help/SKILL.md +104 -0
- package/plugins/mirror-mind/skills/mm-identity/SKILL.md +96 -0
- package/plugins/mirror-mind/skills/mm-journal/SKILL.md +21 -0
- package/plugins/mirror-mind/skills/mm-journey/SKILL.md +37 -0
- package/plugins/mirror-mind/skills/mm-journeys/SKILL.md +15 -0
- package/plugins/mirror-mind/skills/mm-memories/SKILL.md +20 -0
- package/plugins/mirror-mind/skills/mm-mirror/SKILL.md +67 -0
- package/plugins/mirror-mind/skills/mm-mute/SKILL.md +18 -0
- package/plugins/mirror-mind/skills/mm-new/SKILL.md +18 -0
- package/plugins/mirror-mind/skills/mm-recall/SKILL.md +17 -0
- package/plugins/mirror-mind/skills/mm-release-notes/SKILL.md +31 -0
- package/plugins/mirror-mind/skills/mm-seed/SKILL.md +15 -0
- package/plugins/mirror-mind/skills/mm-shadow/SKILL.md +117 -0
- package/plugins/mirror-mind/skills/mm-soul/SKILL.md +488 -0
- package/plugins/mirror-mind/skills/mm-tasks/SKILL.md +36 -0
- package/plugins/mirror-mind/skills/mm-update/SKILL.md +30 -0
- package/plugins/mirror-mind/skills/mm-week/SKILL.md +46 -0
- package/plugins/mirror-mind/skills/mm-welcome/SKILL.md +22 -0
- package/scripts/codex-hooks/session-end.sh +85 -0
- package/scripts/codex-hooks/session-start.sh +85 -0
- package/scripts/codex-mirror.sh +48 -0
- package/templates/identity/README.md +119 -0
- package/templates/identity/ego/behavior.yaml +52 -0
- package/templates/identity/ego/constraints.yaml +15 -0
- package/templates/identity/ego/identity.yaml +43 -0
- package/templates/identity/journeys/personal-growth.yaml +39 -0
- package/templates/identity/organization/identity.yaml +24 -0
- package/templates/identity/organization/principles.yaml +17 -0
- package/templates/identity/personas/coach.yaml +94 -0
- package/templates/identity/personas/designer.yaml +93 -0
- package/templates/identity/personas/doctor.yaml +96 -0
- package/templates/identity/personas/engineer.yaml +77 -0
- package/templates/identity/personas/financial.yaml +97 -0
- package/templates/identity/personas/prompt-engineer.yaml +122 -0
- package/templates/identity/personas/researcher.yaml +88 -0
- package/templates/identity/personas/strategist.yaml +91 -0
- package/templates/identity/personas/teacher.yaml +95 -0
- package/templates/identity/personas/therapist.yaml +91 -0
- package/templates/identity/personas/thinker.yaml +74 -0
- package/templates/identity/personas/writer.yaml +76 -0
- package/templates/identity/self/config.yaml +16 -0
- package/templates/identity/self/soul.yaml +105 -0
- package/templates/identity/user/identity.yaml +30 -0
- package/ts/src/backup/staging.ts +62 -0
- package/ts/src/backup/zipBackup.ts +267 -0
- package/ts/src/backup/zipReader.ts +109 -0
- package/ts/src/backup/zipWriter.ts +148 -0
- package/ts/src/builder/activeCheckpoint.ts +134 -0
- package/ts/src/builder/aggregateStatus.ts +34 -0
- package/ts/src/builder/approve.ts +149 -0
- package/ts/src/builder/argv.ts +292 -0
- package/ts/src/builder/ariadMethod.ts +536 -0
- package/ts/src/builder/artifacts/artifactSurfaces.ts +106 -0
- package/ts/src/builder/artifacts/artifactWriter.ts +113 -0
- package/ts/src/builder/artifacts/closureArtifacts.ts +199 -0
- package/ts/src/builder/artifacts/deliveryStoryArtifacts.ts +316 -0
- package/ts/src/builder/artifacts/planArtifacts.ts +242 -0
- package/ts/src/builder/artifacts/scaffoldSections.ts +273 -0
- package/ts/src/builder/artifacts/scaffoldState.ts +227 -0
- package/ts/src/builder/artifacts/storyFiles.ts +61 -0
- package/ts/src/builder/artifacts/storyIndex.ts +166 -0
- package/ts/src/builder/cadence.ts +59 -0
- package/ts/src/builder/card.ts +192 -0
- package/ts/src/builder/checkpointRefused.ts +66 -0
- package/ts/src/builder/closure.ts +964 -0
- package/ts/src/builder/commands.ts +2053 -0
- package/ts/src/builder/cursorPosition.ts +176 -0
- package/ts/src/builder/cursorTransitions.ts +77 -0
- package/ts/src/builder/deliveryCursor.ts +577 -0
- package/ts/src/builder/deliveryStoryClosure.ts +521 -0
- package/ts/src/builder/deliveryStoryPlan.ts +536 -0
- package/ts/src/builder/deliveryStoryReady.ts +160 -0
- package/ts/src/builder/deliveryStoryRoadmapClosure.ts +151 -0
- package/ts/src/builder/expand.ts +336 -0
- package/ts/src/builder/flowUnit.ts +339 -0
- package/ts/src/builder/homeSurface.ts +122 -0
- package/ts/src/builder/implementationGuard.ts +125 -0
- package/ts/src/builder/index.ts +11 -0
- package/ts/src/builder/lifecycleRefusal.ts +92 -0
- package/ts/src/builder/lifecycleRibbon.ts +159 -0
- package/ts/src/builder/load.ts +313 -0
- package/ts/src/builder/methodAdoption.ts +113 -0
- package/ts/src/builder/methodDefinition.ts +429 -0
- package/ts/src/builder/methodInspection.ts +371 -0
- package/ts/src/builder/plan.ts +408 -0
- package/ts/src/builder/planPreauthorization.ts +265 -0
- package/ts/src/builder/prepare.ts +225 -0
- package/ts/src/builder/projectPaths.ts +64 -0
- package/ts/src/builder/pull.ts +356 -0
- package/ts/src/builder/pullCandidates.ts +463 -0
- package/ts/src/builder/pullCandidatesRender.ts +238 -0
- package/ts/src/builder/refinementField.ts +93 -0
- package/ts/src/builder/releaseIntent.ts +173 -0
- package/ts/src/builder/resumeState.ts +84 -0
- package/ts/src/builder/resumeSurface.ts +149 -0
- package/ts/src/builder/roadmapGrammar.ts +300 -0
- package/ts/src/builder/roadmapScan.ts +97 -0
- package/ts/src/builder/roadmapScope.ts +210 -0
- package/ts/src/builder/scopePhrases.ts +146 -0
- package/ts/src/builder/storyPaths.ts +196 -0
- package/ts/src/builder/storyPlanPreauthorization.ts +287 -0
- package/ts/src/builder/surfaceProtocol.ts +42 -0
- package/ts/src/builder/templateGeneration.ts +148 -0
- package/ts/src/builder/transition.ts +178 -0
- package/ts/src/consult/args.ts +99 -0
- package/ts/src/consult/core.ts +122 -0
- package/ts/src/consult/modelCatalog.ts +55 -0
- package/ts/src/consult/render.ts +47 -0
- package/ts/src/conversation/append.ts +414 -0
- package/ts/src/conversation/backfill.ts +387 -0
- package/ts/src/conversation/closeTail.ts +467 -0
- package/ts/src/conversation/extraction.ts +333 -0
- package/ts/src/conversation/extractionDriver.ts +130 -0
- package/ts/src/conversation/extractionRun.ts +70 -0
- package/ts/src/conversation/hookPayload.ts +51 -0
- package/ts/src/conversation/journeyInference.ts +165 -0
- package/ts/src/conversation/journeyRepair.ts +114 -0
- package/ts/src/conversation/lifecycleFaces.ts +197 -0
- package/ts/src/conversation/lifecycleWrites.ts +313 -0
- package/ts/src/conversation/listing.ts +77 -0
- package/ts/src/conversation/logger.ts +851 -0
- package/ts/src/conversation/loggerCli.ts +321 -0
- package/ts/src/conversation/loggerRuntime.ts +259 -0
- package/ts/src/conversation/metadataLifecycle.ts +443 -0
- package/ts/src/conversation/recall.ts +66 -0
- package/ts/src/conversation/sessionComposites.ts +258 -0
- package/ts/src/conversation/sessionImport.ts +90 -0
- package/ts/src/conversation/transcriptBackfill.ts +141 -0
- package/ts/src/cultivation/applyActions.ts +241 -0
- package/ts/src/cultivation/cluster.ts +86 -0
- package/ts/src/cultivation/consolidationStore.ts +281 -0
- package/ts/src/cultivation/promptContext.ts +62 -0
- package/ts/src/cultivation/propose.ts +305 -0
- package/ts/src/cultivation/scan.ts +155 -0
- package/ts/src/db/backupGate.ts +47 -0
- package/ts/src/db/bootstrap.ts +108 -0
- package/ts/src/db/bootstrapLock.ts +223 -0
- package/ts/src/db/copyGuard.ts +60 -0
- package/ts/src/db/database.ts +397 -0
- package/ts/src/db/decode.ts +66 -0
- package/ts/src/db/ftsIntegrity.ts +41 -0
- package/ts/src/db/migrateOnOpen.ts +214 -0
- package/ts/src/db/migrations.ts +576 -0
- package/ts/src/db/readyOnOpen.ts +35 -0
- package/ts/src/db/rowDecode.ts +42 -0
- package/ts/src/db/schema.ts +432 -0
- package/ts/src/db/schemaInventory.ts +225 -0
- package/ts/src/db/schemaInventorySnapshot.ts +2107 -0
- package/ts/src/db/schemaState.ts +91 -0
- package/ts/src/db/schemaTsDivergence.ts +114 -0
- package/ts/src/descriptor/descriptorRead.ts +49 -0
- package/ts/src/docs/docsLint.ts +265 -0
- package/ts/src/explorer/handoff.ts +522 -0
- package/ts/src/explorer/render.ts +335 -0
- package/ts/src/explorer/story.ts +769 -0
- package/ts/src/explorer/transition.ts +72 -0
- package/ts/src/extensions/bindings.ts +170 -0
- package/ts/src/extensions/catalog.ts +249 -0
- package/ts/src/extensions/catalogCommands.ts +513 -0
- package/ts/src/extensions/catalogWrites.ts +652 -0
- package/ts/src/extensions/contextRuntime.ts +325 -0
- package/ts/src/extensions/dispatch.ts +374 -0
- package/ts/src/extensions/errors.ts +24 -0
- package/ts/src/extensions/manifest.ts +356 -0
- package/ts/src/extensions/migrations.ts +350 -0
- package/ts/src/extraction/conversation.ts +304 -0
- package/ts/src/extraction/conversationMetadata.ts +206 -0
- package/ts/src/extraction/fencing.ts +106 -0
- package/ts/src/extraction/json.ts +17 -0
- package/ts/src/extraction/prompts.ts +470 -0
- package/ts/src/frontDoor/args.ts +30 -0
- package/ts/src/frontDoor/argvShape.ts +106 -0
- package/ts/src/frontDoor/buildLoadRuntime.ts +139 -0
- package/ts/src/frontDoor/buildRoute.ts +398 -0
- package/ts/src/frontDoor/cli.ts +1872 -0
- package/ts/src/frontDoor/cloneRoleGuard.ts +98 -0
- package/ts/src/frontDoor/consultRoute.ts +50 -0
- package/ts/src/frontDoor/contentTailRoute.ts +239 -0
- package/ts/src/frontDoor/conversationLoggerRoute.ts +83 -0
- package/ts/src/frontDoor/cultivationRoute.ts +210 -0
- package/ts/src/frontDoor/dbPath.ts +131 -0
- package/ts/src/frontDoor/dbSafetyToolsRoute.ts +240 -0
- package/ts/src/frontDoor/exploreRoute.ts +572 -0
- package/ts/src/frontDoor/extensionCatalogRoute.ts +252 -0
- package/ts/src/frontDoor/frontDoorLog.ts +57 -0
- package/ts/src/frontDoor/identityWrite.ts +36 -0
- package/ts/src/frontDoor/journeyWriteRoute.ts +26 -0
- package/ts/src/frontDoor/lifecycleWriteRoute.ts +110 -0
- package/ts/src/frontDoor/liveBackup.ts +100 -0
- package/ts/src/frontDoor/mirrorModeRoute.ts +163 -0
- package/ts/src/frontDoor/namedRefusal.ts +35 -0
- package/ts/src/frontDoor/nodeSupport.ts +24 -0
- package/ts/src/frontDoor/render/consolidate.ts +199 -0
- package/ts/src/frontDoor/render/conversations.ts +33 -0
- package/ts/src/frontDoor/render/cultivationShared.ts +21 -0
- package/ts/src/frontDoor/render/descriptor.ts +20 -0
- package/ts/src/frontDoor/render/detectPersona.ts +50 -0
- package/ts/src/frontDoor/render/icons.ts +13 -0
- package/ts/src/frontDoor/render/identity.ts +59 -0
- package/ts/src/frontDoor/render/identityRows.ts +17 -0
- package/ts/src/frontDoor/render/inspectPersona.ts +53 -0
- package/ts/src/frontDoor/render/journeyStatus.ts +78 -0
- package/ts/src/frontDoor/render/journeys.ts +79 -0
- package/ts/src/frontDoor/render/list.ts +41 -0
- package/ts/src/frontDoor/render/memories.ts +67 -0
- package/ts/src/frontDoor/render/recall.ts +49 -0
- package/ts/src/frontDoor/render/shadow.ts +155 -0
- package/ts/src/frontDoor/render/tasks.ts +99 -0
- package/ts/src/frontDoor/render/tasksImportSync.ts +63 -0
- package/ts/src/frontDoor/render/week.ts +113 -0
- package/ts/src/frontDoor/routing.ts +1001 -0
- package/ts/src/frontDoor/runtimeRoute.ts +623 -0
- package/ts/src/frontDoor/searchRoute.ts +166 -0
- package/ts/src/frontDoor/seedPaths.ts +98 -0
- package/ts/src/frontDoor/sessionStartWelcomeRoute.ts +166 -0
- package/ts/src/frontDoor/soulRoute.ts +433 -0
- package/ts/src/frontDoor/stdinSource.ts +37 -0
- package/ts/src/frontDoor/tasksImportSyncRoute.ts +110 -0
- package/ts/src/frontDoor/tasksWriteRoute.ts +101 -0
- package/ts/src/frontDoor/usage.ts +176 -0
- package/ts/src/guards/claudePlugin.ts +298 -0
- package/ts/src/guards/packContents.ts +306 -0
- package/ts/src/guards/piSourcedSkills.ts +70 -0
- package/ts/src/guards/retiredRouteShadows.ts +57 -0
- package/ts/src/guards/retiredSurfaces.ts +564 -0
- package/ts/src/hooks/claude.ts +214 -0
- package/ts/src/hooks/codex.ts +45 -0
- package/ts/src/hooks/gemini.ts +126 -0
- package/ts/src/hooks/main.ts +109 -0
- package/ts/src/hooks/mirrorState.ts +71 -0
- package/ts/src/hooks/payload.ts +73 -0
- package/ts/src/hooks/runtime.ts +179 -0
- package/ts/src/hooks/runtimeDetection.ts +34 -0
- package/ts/src/identity/applyConsolidationIdentityUpdate.ts +67 -0
- package/ts/src/identity/descriptorGenerate.ts +162 -0
- package/ts/src/identity/identityEdit.ts +143 -0
- package/ts/src/identity/identityKey.ts +77 -0
- package/ts/src/identity/identityRead.ts +69 -0
- package/ts/src/identity/identityStore.ts +80 -0
- package/ts/src/identity/journeyListing.ts +53 -0
- package/ts/src/identity/personaInspect.ts +44 -0
- package/ts/src/identity/personaListing.ts +37 -0
- package/ts/src/identity/setIdentity.ts +64 -0
- package/ts/src/index.ts +231 -0
- package/ts/src/init/init.ts +111 -0
- package/ts/src/journey/journeyOptions.ts +131 -0
- package/ts/src/journey/journeyRemoval.ts +158 -0
- package/ts/src/journey/journeyStatus.ts +149 -0
- package/ts/src/journey/journeySyncFile.ts +87 -0
- package/ts/src/journey/journeyWrite.ts +203 -0
- package/ts/src/journey/parentJourney.ts +41 -0
- package/ts/src/journey/validateParentJourney.ts +55 -0
- package/ts/src/mcp/boundary.ts +151 -0
- package/ts/src/mcp/guards.ts +244 -0
- package/ts/src/mcp/main.ts +121 -0
- package/ts/src/mcp/payload.ts +141 -0
- package/ts/src/mcp/protocol.ts +141 -0
- package/ts/src/mcp/readModels.ts +241 -0
- package/ts/src/mcp/registry.ts +201 -0
- package/ts/src/mcp/serve.ts +120 -0
- package/ts/src/mcp/tools/deterministic.ts +137 -0
- package/ts/src/mcp/tools/providerCrossing.ts +187 -0
- package/ts/src/mcp/wire.ts +8 -0
- package/ts/src/memory/journal.ts +180 -0
- package/ts/src/memory/listing.ts +107 -0
- package/ts/src/memory/memoryWrite.ts +88 -0
- package/ts/src/memory/reinforcement.ts +35 -0
- package/ts/src/mirror/context.ts +161 -0
- package/ts/src/mirror/defaultResolution.ts +199 -0
- package/ts/src/mirror/mirrorForm.ts +43 -0
- package/ts/src/mirror/orchestration.ts +364 -0
- package/ts/src/mirror/reception.ts +194 -0
- package/ts/src/mirror/render.ts +75 -0
- package/ts/src/mirror/runtimeSession.ts +205 -0
- package/ts/src/mode/operatingMode.ts +148 -0
- package/ts/src/observability/callOutcome.ts +115 -0
- package/ts/src/observability/inspectLedger.ts +329 -0
- package/ts/src/observability/ledgerHooks.ts +126 -0
- package/ts/src/observability/llmCalls.ts +154 -0
- package/ts/src/persona/detectPersona.ts +81 -0
- package/ts/src/planning/promptAssembly.ts +111 -0
- package/ts/src/planning/weekPending.ts +81 -0
- package/ts/src/planning/weekPlan.ts +183 -0
- package/ts/src/planning/weekSave.ts +117 -0
- package/ts/src/providers/config.ts +189 -0
- package/ts/src/providers/cost.ts +64 -0
- package/ts/src/providers/credits.ts +204 -0
- package/ts/src/providers/embedding.ts +321 -0
- package/ts/src/providers/familyProviders.ts +147 -0
- package/ts/src/providers/llm.ts +334 -0
- package/ts/src/providers/openrouter.ts +244 -0
- package/ts/src/providers/redaction.ts +110 -0
- package/ts/src/providers/replay.ts +15 -0
- package/ts/src/providers/transport.ts +410 -0
- package/ts/src/repair/encodingRepair.ts +253 -0
- package/ts/src/runtime/backup.ts +151 -0
- package/ts/src/runtime/channel.ts +146 -0
- package/ts/src/runtime/config.ts +168 -0
- package/ts/src/runtime/diagnose.ts +686 -0
- package/ts/src/runtime/git.ts +510 -0
- package/ts/src/runtime/installKind.ts +220 -0
- package/ts/src/runtime/migrate.ts +154 -0
- package/ts/src/runtime/packageIdentity.ts +102 -0
- package/ts/src/runtime/releaseNotes.ts +244 -0
- package/ts/src/runtime/status.ts +557 -0
- package/ts/src/runtime/strategies/clone.ts +200 -0
- package/ts/src/runtime/strategies/package.ts +328 -0
- package/ts/src/runtime/treeRoot.ts +30 -0
- package/ts/src/runtime/update.ts +445 -0
- package/ts/src/runtime/updateGate.ts +40 -0
- package/ts/src/runtime/updatePipeline.ts +159 -0
- package/ts/src/runtime/version.ts +36 -0
- package/ts/src/runtime/wiring.ts +130 -0
- package/ts/src/search/memorySearch.ts +338 -0
- package/ts/src/search/ranker.ts +154 -0
- package/ts/src/seed/seed.ts +256 -0
- package/ts/src/seed/seedYaml.ts +133 -0
- package/ts/src/soul/apply.ts +218 -0
- package/ts/src/soul/harvest.ts +267 -0
- package/ts/src/soul/prompts/soul_beauty_voice.md +103 -0
- package/ts/src/soul/prompts/soul_self_voice.md +219 -0
- package/ts/src/soul/prompts/soul_wisdom_voice.md +121 -0
- package/ts/src/soul/prompts.ts +85 -0
- package/ts/src/soul/render.ts +358 -0
- package/ts/src/soul/state.ts +212 -0
- package/ts/src/soul/transition.ts +37 -0
- package/ts/src/tasks/journeyPathParse.ts +113 -0
- package/ts/src/tasks/taskImportSync.ts +142 -0
- package/ts/src/tasks/taskStore.ts +312 -0
- package/ts/src/tasks/weekView.ts +120 -0
- package/ts/src/util/clipCodePoints.ts +26 -0
- package/ts/src/util/paths.ts +63 -0
- package/ts/src/util/program.ts +9 -0
- package/ts/src/util/pyGenerators.ts +144 -0
- package/ts/src/util/pythonText.ts +317 -0
- package/ts/src/util/semver.ts +45 -0
- package/ts/src/util/shellWord.ts +25 -0
- package/ts/src/util/slug.ts +64 -0
- package/ts/src/welcome/card.ts +339 -0
- package/ts/src/welcome/statusLine.ts +101 -0
- package/ts/src/welcome/updateCache.ts +129 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
// The conversation close tail (CV22.DS7.US10 slice C′).
|
|
2
|
+
//
|
|
3
|
+
// Ports `end_conversation`, `finalize_metadata_on_close`,
|
|
4
|
+
// `apply_generated_metadata_lifecycle`, and
|
|
5
|
+
// `_apply_force_generated_metadata_lifecycle` from
|
|
6
|
+
// `src/memory/services/conversation.py`.
|
|
7
|
+
//
|
|
8
|
+
// This is the path every ended session runs through, and its shape carries two
|
|
9
|
+
// properties that must not be lost:
|
|
10
|
+
//
|
|
11
|
+
// 1. `ended_at` is written BEFORE extraction, and finalization runs in a
|
|
12
|
+
// `finally`. An orphan therefore closes and still gets its non-manual
|
|
13
|
+
// metadata finalized even when extraction raises — the comment in Python
|
|
14
|
+
// calls this out explicitly, so it is behavior, not an accident.
|
|
15
|
+
// 2. The decision engine, not this module, decides whether each field is
|
|
16
|
+
// generated. How many LLM calls the tail makes is therefore a function of
|
|
17
|
+
// the dry-run decisions and the execution profile, which is why the
|
|
18
|
+
// ledger is graded as an ordered call sequence rather than a row count.
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
type ConversationLike,
|
|
22
|
+
dryRunMetadataLifecycle,
|
|
23
|
+
type FieldReport,
|
|
24
|
+
type MessageLike,
|
|
25
|
+
messagesAreTitleable,
|
|
26
|
+
metadataExecutionProfile,
|
|
27
|
+
metadataProfileAction,
|
|
28
|
+
titleNeedsImprovement,
|
|
29
|
+
} from "#conversation/metadataLifecycle.ts";
|
|
30
|
+
import type { WritableDatabase } from "#db/database.ts";
|
|
31
|
+
import type { ExtractionMessage } from "#extraction/conversation.ts";
|
|
32
|
+
import {
|
|
33
|
+
generateConversationSummary,
|
|
34
|
+
generateConversationTags,
|
|
35
|
+
generateConversationTitle,
|
|
36
|
+
} from "#extraction/conversationMetadata.ts";
|
|
37
|
+
import type { LlmProvider, LlmResponse } from "#providers/llm.ts";
|
|
38
|
+
import { pythonJsonDumps } from "#util/pyGenerators.ts";
|
|
39
|
+
|
|
40
|
+
export interface CloseTailDeps {
|
|
41
|
+
llm: LlmProvider;
|
|
42
|
+
/**
|
|
43
|
+
* Fires per successful LLM call, in call order — the ledger seam. Receives
|
|
44
|
+
* the assembled prompt so a `full`-mode ledger can store it, as Python's
|
|
45
|
+
* `LLMResponse.prompt` does.
|
|
46
|
+
*/
|
|
47
|
+
onLlmCall?: (role: string, response: LlmResponse, conversationId: string, prompt: string) => void;
|
|
48
|
+
now?: () => string;
|
|
49
|
+
userName?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface MetadataLifecycleApplyReport {
|
|
53
|
+
conversation_id: string;
|
|
54
|
+
mode: string;
|
|
55
|
+
mutated: boolean;
|
|
56
|
+
changed: Record<string, unknown>;
|
|
57
|
+
skipped: Record<string, string>;
|
|
58
|
+
dry_run: unknown;
|
|
59
|
+
profile: string;
|
|
60
|
+
actions: Record<string, string>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface ConversationRow extends ConversationLike {
|
|
64
|
+
id: string;
|
|
65
|
+
title: string | null;
|
|
66
|
+
summary: string | null;
|
|
67
|
+
tags: string | null;
|
|
68
|
+
metadata: string | null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Python's `_clean_title`: collapse whitespace, require non-empty, cap 160. */
|
|
72
|
+
export function cleanTitle(title: string): string {
|
|
73
|
+
const clean = title.trim().split(/\s+/u).filter(Boolean).join(" ");
|
|
74
|
+
if (!clean) throw new Error("title is required");
|
|
75
|
+
if (clean.length > 160) throw new Error("title must be at most 160 characters");
|
|
76
|
+
return clean;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Python's `_clean_summary`: collapse whitespace inside each blank-line-separated
|
|
81
|
+
* paragraph, drop empty paragraphs, rejoin with a blank line, then truncate to
|
|
82
|
+
* 1000 characters and right-strip.
|
|
83
|
+
*/
|
|
84
|
+
export function cleanSummary(summary: string): string {
|
|
85
|
+
const paragraphs = summary
|
|
86
|
+
.trim()
|
|
87
|
+
.split("\n\n")
|
|
88
|
+
.filter((part) => part.trim())
|
|
89
|
+
.map((part) => part.split(/\s+/u).filter(Boolean).join(" "));
|
|
90
|
+
let clean = paragraphs.join("\n\n");
|
|
91
|
+
if (clean.length > 1000) clean = clean.slice(0, 1000).replace(/\s+$/u, "");
|
|
92
|
+
return clean;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Python's `_looks_like_artifact`: digits, hashes, and CSS sizes are not tags. */
|
|
96
|
+
export function looksLikeArtifact(term: string): boolean {
|
|
97
|
+
// Python's `str.isdigit()` is Unicode-aware (superscripts included); the last
|
|
98
|
+
// clause already rejects anything containing an ASCII digit, so the practical
|
|
99
|
+
// gate here is the same.
|
|
100
|
+
if (/^\d+$/u.test(term)) return true;
|
|
101
|
+
if (/^[0-9a-f]{7,}$/u.test(term)) return true;
|
|
102
|
+
if (/^\d+px$/u.test(term)) return true;
|
|
103
|
+
return /\d/u.test(term);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Python's `_title_metadata`. */
|
|
107
|
+
export function titleMetadata(
|
|
108
|
+
conversation: ConversationLike,
|
|
109
|
+
options: { source: string; status: string; previousTitle: string | null },
|
|
110
|
+
): Record<string, unknown> {
|
|
111
|
+
const metadata = metadataDict(conversation.metadata);
|
|
112
|
+
if (options.previousTitle && options.previousTitle !== metadata.previous_title) {
|
|
113
|
+
metadata.previous_title = options.previousTitle;
|
|
114
|
+
}
|
|
115
|
+
metadata.title_source = options.source;
|
|
116
|
+
metadata.title_status = options.status;
|
|
117
|
+
return metadata;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function metadataDict(raw: string | null | undefined): Record<string, unknown> {
|
|
121
|
+
if (!raw) return {};
|
|
122
|
+
try {
|
|
123
|
+
const value: unknown = JSON.parse(raw);
|
|
124
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
125
|
+
? (value as Record<string, unknown>)
|
|
126
|
+
: {};
|
|
127
|
+
} catch {
|
|
128
|
+
return {};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Generate and apply the safe updates the lifecycle report exposes.
|
|
134
|
+
*
|
|
135
|
+
* KNOWN WASTED CALL, preserved for parity (registered for Debt Review): when
|
|
136
|
+
* the tags action is apply/regenerate, no summary was generated, and the
|
|
137
|
+
* summary decision is `refine_candidate`, Python calls `suggest_summary` a
|
|
138
|
+
* second time and passes the result to `_suggest_tags` — which declares the
|
|
139
|
+
* parameter and never reads it. That is a real LLM call whose output is
|
|
140
|
+
* discarded. Porting it preserves the call sequence the ledger records; it is
|
|
141
|
+
* named here so DS8 does not price it as intentional.
|
|
142
|
+
*/
|
|
143
|
+
export async function applyGeneratedMetadataLifecycle(
|
|
144
|
+
db: WritableDatabase,
|
|
145
|
+
conversationId: string,
|
|
146
|
+
options: CloseTailDeps & { source?: string; profileName?: string },
|
|
147
|
+
): Promise<MetadataLifecycleApplyReport> {
|
|
148
|
+
const source = options.source ?? "metadata_lifecycle_apply";
|
|
149
|
+
const profileName = options.profileName ?? "manual_safe";
|
|
150
|
+
|
|
151
|
+
const conversation = getConversationForTitleOperation(db, conversationId);
|
|
152
|
+
const messages = getMessages(db, conversation.id);
|
|
153
|
+
const metadata = metadataDict(conversation.metadata);
|
|
154
|
+
const dryRun = dryRunMetadataLifecycle(conversation, messages, metadata, {
|
|
155
|
+
titleNeedsImprovement,
|
|
156
|
+
});
|
|
157
|
+
const fields = dryRun.fields;
|
|
158
|
+
const profile = metadataExecutionProfile(profileName);
|
|
159
|
+
const actions: Record<string, string> = Object.fromEntries(
|
|
160
|
+
Object.entries(fields).map(([field, report]) => [
|
|
161
|
+
field,
|
|
162
|
+
metadataProfileAction(profile, field, report as FieldReport),
|
|
163
|
+
]),
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
const suggest = buildSuggesters(conversation, messages, options);
|
|
167
|
+
|
|
168
|
+
let generatedTitle: string | null = null;
|
|
169
|
+
let generatedSummary: string | null = null;
|
|
170
|
+
let generatedTags: string[] | null = null;
|
|
171
|
+
|
|
172
|
+
if (actions.title === "apply" || actions.title === "regenerate") {
|
|
173
|
+
generatedTitle = await suggest.title();
|
|
174
|
+
}
|
|
175
|
+
if (actions.summary === "apply" || actions.summary === "regenerate") {
|
|
176
|
+
generatedSummary = await suggest.summary();
|
|
177
|
+
}
|
|
178
|
+
if (actions.tags === "apply" || actions.tags === "regenerate") {
|
|
179
|
+
let tagSourceSummary = generatedSummary;
|
|
180
|
+
if (tagSourceSummary === null && fields.summary.decision === "refine_candidate") {
|
|
181
|
+
// The discarded call documented above. Kept so the ledger sequence matches.
|
|
182
|
+
tagSourceSummary = await suggest.summary();
|
|
183
|
+
}
|
|
184
|
+
generatedTags = await suggest.tags(tagSourceSummary);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!profile.forceRegenerate) {
|
|
188
|
+
throw new Error(
|
|
189
|
+
`metadata execution profile '${profileName}' is not force_regenerate; ` +
|
|
190
|
+
"the non-force apply path (apply_metadata_lifecycle) is not ported yet " +
|
|
191
|
+
"— close_time is the profile the close tail uses.",
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const report = applyForceGeneratedMetadataLifecycle(db, conversation, {
|
|
196
|
+
dryRun,
|
|
197
|
+
actions,
|
|
198
|
+
title: generatedTitle,
|
|
199
|
+
summary: generatedSummary,
|
|
200
|
+
tags: generatedTags,
|
|
201
|
+
source,
|
|
202
|
+
});
|
|
203
|
+
return { ...report, profile: profile.name, actions };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Python's `_apply_force_generated_metadata_lifecycle`. */
|
|
207
|
+
function applyForceGeneratedMetadataLifecycle(
|
|
208
|
+
db: WritableDatabase,
|
|
209
|
+
conversation: ConversationRow,
|
|
210
|
+
input: {
|
|
211
|
+
dryRun: unknown;
|
|
212
|
+
actions: Record<string, string>;
|
|
213
|
+
title: string | null;
|
|
214
|
+
summary: string | null;
|
|
215
|
+
tags: string[] | null;
|
|
216
|
+
source: string;
|
|
217
|
+
},
|
|
218
|
+
): Omit<MetadataLifecycleApplyReport, "profile" | "actions"> {
|
|
219
|
+
const { actions, source } = input;
|
|
220
|
+
// Rebound below by the title branch, exactly as Python rebinds `metadata`
|
|
221
|
+
// to `_title_metadata(...)`'s fresh dict — later branches mutate the new one.
|
|
222
|
+
let metadata = metadataDict(conversation.metadata);
|
|
223
|
+
const updates: Record<string, string> = {};
|
|
224
|
+
const changed: Record<string, unknown> = {};
|
|
225
|
+
const skipped: Record<string, string> = {};
|
|
226
|
+
|
|
227
|
+
if (actions.title === "preserve_manual") {
|
|
228
|
+
skipped.title = "manual_lock_preserved";
|
|
229
|
+
} else if (actions.title === "regenerate" && input.title) {
|
|
230
|
+
const clean = cleanTitle(input.title);
|
|
231
|
+
updates.title = clean;
|
|
232
|
+
metadata = titleMetadata(conversation, {
|
|
233
|
+
source,
|
|
234
|
+
status: "generated",
|
|
235
|
+
previousTitle: conversation.title,
|
|
236
|
+
});
|
|
237
|
+
changed.title = clean;
|
|
238
|
+
} else if (actions.title === "regenerate") {
|
|
239
|
+
skipped.title = "generation_failed";
|
|
240
|
+
} else {
|
|
241
|
+
skipped.title = actions.title as string;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (actions.summary === "regenerate" && metadata.summary_source === "manual") {
|
|
245
|
+
skipped.summary = "manual_summary_preserved";
|
|
246
|
+
} else if (actions.summary === "regenerate" && input.summary) {
|
|
247
|
+
const clean = cleanSummary(input.summary);
|
|
248
|
+
if (clean) {
|
|
249
|
+
updates.summary = clean;
|
|
250
|
+
metadata.summary_status = "generated";
|
|
251
|
+
metadata.summary_source = source;
|
|
252
|
+
changed.summary = clean;
|
|
253
|
+
} else {
|
|
254
|
+
skipped.summary = "blank_value";
|
|
255
|
+
}
|
|
256
|
+
} else if (actions.summary === "regenerate") {
|
|
257
|
+
skipped.summary = "generation_failed";
|
|
258
|
+
} else {
|
|
259
|
+
skipped.summary = actions.summary as string;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (actions.tags === "regenerate" && metadata.tags_source === "manual") {
|
|
263
|
+
skipped.tags = "manual_tags_preserved";
|
|
264
|
+
} else if (actions.tags === "regenerate" && input.tags && input.tags.length > 0) {
|
|
265
|
+
// `pythonJsonDumps`, not `JSON.stringify`: Python writes `["a", "b"]` with a
|
|
266
|
+
// space after the comma, and these bytes land in a TEXT column that both
|
|
267
|
+
// cores read and compare.
|
|
268
|
+
updates.tags = pythonJsonDumps(input.tags);
|
|
269
|
+
metadata.tags_status = "generated";
|
|
270
|
+
metadata.tags_source = source;
|
|
271
|
+
changed.tags = input.tags;
|
|
272
|
+
} else if (actions.tags === "regenerate") {
|
|
273
|
+
skipped.tags = "generation_failed";
|
|
274
|
+
} else {
|
|
275
|
+
skipped.tags = actions.tags as string;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (Object.keys(changed).length > 0) {
|
|
279
|
+
metadata.metadata_lifecycle_version = 1;
|
|
280
|
+
metadata.last_metadata_update_source = source;
|
|
281
|
+
updates.metadata = pythonJsonDumps(metadata);
|
|
282
|
+
updateConversation(db, conversation.id, updates);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
conversation_id: conversation.id,
|
|
287
|
+
mode: "apply",
|
|
288
|
+
mutated: Object.keys(changed).length > 0,
|
|
289
|
+
changed,
|
|
290
|
+
skipped,
|
|
291
|
+
dry_run: input.dryRun,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Python's `maybe_generate_title`: improve one title when it is safe to replace.
|
|
297
|
+
*
|
|
298
|
+
* Distinct from the close tail's finalization: this is the startup-maintenance
|
|
299
|
+
* path, it touches only the title, and it returns whether the title actually
|
|
300
|
+
* changed so `retitle_pending_conversations` can count real work. Provider
|
|
301
|
+
* failure leaves the conversation untouched, exactly as Python's bare `except`
|
|
302
|
+
* does.
|
|
303
|
+
*/
|
|
304
|
+
export async function maybeGenerateTitle(
|
|
305
|
+
db: WritableDatabase,
|
|
306
|
+
conversationId: string,
|
|
307
|
+
deps: CloseTailDeps & { source?: string },
|
|
308
|
+
): Promise<boolean> {
|
|
309
|
+
const source = deps.source ?? "llm_auto";
|
|
310
|
+
const conversation = getConversationForTitleOperation(db, conversationId);
|
|
311
|
+
if (!titleNeedsImprovement(conversation)) return false;
|
|
312
|
+
|
|
313
|
+
const messages = getMessages(db, conversation.id);
|
|
314
|
+
if (!messagesAreTitleable(messages)) return false;
|
|
315
|
+
|
|
316
|
+
try {
|
|
317
|
+
const suggestion = await generateConversationTitle(deps.llm, messages, {
|
|
318
|
+
userName: deps.userName ?? "User",
|
|
319
|
+
onLlmCall: (response, prompt) =>
|
|
320
|
+
deps.onLlmCall?.("conversation_title", response, conversation.id, prompt),
|
|
321
|
+
});
|
|
322
|
+
if (!suggestion) return false;
|
|
323
|
+
const clean = cleanTitle(suggestion);
|
|
324
|
+
const metadata = titleMetadata(conversation, {
|
|
325
|
+
source,
|
|
326
|
+
status: "generated",
|
|
327
|
+
previousTitle: conversation.title,
|
|
328
|
+
});
|
|
329
|
+
updateConversation(db, conversation.id, {
|
|
330
|
+
title: clean,
|
|
331
|
+
metadata: pythonJsonDumps(metadata),
|
|
332
|
+
});
|
|
333
|
+
return clean !== conversation.title;
|
|
334
|
+
} catch {
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/** Python's `finalize_metadata_on_close`. */
|
|
340
|
+
export async function finalizeMetadataOnClose(
|
|
341
|
+
db: WritableDatabase,
|
|
342
|
+
conversationId: string,
|
|
343
|
+
deps: CloseTailDeps,
|
|
344
|
+
): Promise<MetadataLifecycleApplyReport> {
|
|
345
|
+
return applyGeneratedMetadataLifecycle(db, conversationId, {
|
|
346
|
+
...deps,
|
|
347
|
+
source: "close_time_metadata_finalization",
|
|
348
|
+
profileName: "close_time",
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Build the `CloseHooks` that `logger.ts`'s `endConversation` invokes.
|
|
354
|
+
*
|
|
355
|
+
* The ordering contract (`ended_at` -> extraction -> `finally` finalize) lives
|
|
356
|
+
* in exactly one place, the logger's close seam that US5 slice A built and
|
|
357
|
+
* tested. This module supplies the two tail implementations that seam was
|
|
358
|
+
* designed to receive; it deliberately does NOT re-implement the ordering,
|
|
359
|
+
* because two copies of that contract would eventually disagree.
|
|
360
|
+
*/
|
|
361
|
+
export function createCloseHooks(
|
|
362
|
+
deps: CloseTailDeps & {
|
|
363
|
+
runExtraction?: (db: WritableDatabase, conversationId: string) => Promise<void> | void;
|
|
364
|
+
},
|
|
365
|
+
): {
|
|
366
|
+
runExtraction?: (db: WritableDatabase, conversationId: string) => Promise<void> | void;
|
|
367
|
+
finalizeMetadata: (db: WritableDatabase, conversationId: string) => Promise<void>;
|
|
368
|
+
} {
|
|
369
|
+
return {
|
|
370
|
+
runExtraction: deps.runExtraction,
|
|
371
|
+
finalizeMetadata: async (db, conversationId) => {
|
|
372
|
+
await finalizeMetadataOnClose(db, conversationId, deps);
|
|
373
|
+
},
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function buildSuggesters(
|
|
378
|
+
conversation: ConversationRow,
|
|
379
|
+
messages: ExtractionMessage[],
|
|
380
|
+
deps: CloseTailDeps,
|
|
381
|
+
) {
|
|
382
|
+
const userName = deps.userName ?? "User";
|
|
383
|
+
const record = (role: string) => (response: LlmResponse, prompt: string) =>
|
|
384
|
+
deps.onLlmCall?.(role, response, conversation.id, prompt);
|
|
385
|
+
|
|
386
|
+
return {
|
|
387
|
+
async title(): Promise<string | null> {
|
|
388
|
+
// Python's `suggest_title` raises ValueError on no messages or an empty
|
|
389
|
+
// suggestion, and the caller maps that to None.
|
|
390
|
+
if (messages.length === 0) return null;
|
|
391
|
+
const suggestion = await generateConversationTitle(deps.llm, messages, {
|
|
392
|
+
userName,
|
|
393
|
+
onLlmCall: record("conversation_title"),
|
|
394
|
+
});
|
|
395
|
+
return suggestion ? suggestion : null;
|
|
396
|
+
},
|
|
397
|
+
async summary(): Promise<string | null> {
|
|
398
|
+
if (messages.length === 0) return null;
|
|
399
|
+
const suggestion = await generateConversationSummary(deps.llm, messages, {
|
|
400
|
+
userName,
|
|
401
|
+
onLlmCall: record("conversation_summary"),
|
|
402
|
+
});
|
|
403
|
+
const clean = cleanSummary(suggestion);
|
|
404
|
+
return clean ? clean : null;
|
|
405
|
+
},
|
|
406
|
+
async tags(_generatedSummary: string | null): Promise<string[]> {
|
|
407
|
+
// `_generatedSummary` is accepted and ignored, mirroring Python's
|
|
408
|
+
// `_suggest_tags(conversation_id, generated_summary)`.
|
|
409
|
+
const tags = await generateConversationTags(deps.llm, messages, {
|
|
410
|
+
userName,
|
|
411
|
+
onLlmCall: record("conversation_tags"),
|
|
412
|
+
});
|
|
413
|
+
return tags.filter((tag) => !looksLikeArtifact(tag));
|
|
414
|
+
},
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function getConversationForTitleOperation(
|
|
419
|
+
db: WritableDatabase,
|
|
420
|
+
conversationId: string,
|
|
421
|
+
): ConversationRow {
|
|
422
|
+
if (typeof conversationId !== "string" || !conversationId.trim()) {
|
|
423
|
+
throw new Error("conversationId is required");
|
|
424
|
+
}
|
|
425
|
+
const row =
|
|
426
|
+
(db
|
|
427
|
+
.prepare("SELECT id, title, summary, tags, metadata FROM conversations WHERE id = ?")
|
|
428
|
+
.get(conversationId) as Record<string, unknown> | undefined) ??
|
|
429
|
+
(db
|
|
430
|
+
.prepare(
|
|
431
|
+
"SELECT id, title, summary, tags, metadata FROM conversations WHERE id LIKE ? " +
|
|
432
|
+
"ORDER BY started_at DESC LIMIT 1",
|
|
433
|
+
)
|
|
434
|
+
.get(`${conversationId}%`) as Record<string, unknown> | undefined);
|
|
435
|
+
if (!row) throw new Error(`Conversation '${conversationId}' not found`);
|
|
436
|
+
return {
|
|
437
|
+
id: String(row.id),
|
|
438
|
+
title: typeof row.title === "string" ? row.title : null,
|
|
439
|
+
summary: typeof row.summary === "string" ? row.summary : null,
|
|
440
|
+
tags: typeof row.tags === "string" ? row.tags : null,
|
|
441
|
+
metadata: typeof row.metadata === "string" ? row.metadata : null,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function getMessages(
|
|
446
|
+
db: WritableDatabase,
|
|
447
|
+
conversationId: string,
|
|
448
|
+
): (ExtractionMessage & MessageLike)[] {
|
|
449
|
+
const rows = db
|
|
450
|
+
.prepare("SELECT role, content FROM messages WHERE conversation_id = ? ORDER BY created_at")
|
|
451
|
+
.all(conversationId) as Record<string, unknown>[];
|
|
452
|
+
return rows.map((row) => ({ role: String(row.role), content: String(row.content ?? "") }));
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function updateConversation(
|
|
456
|
+
db: WritableDatabase,
|
|
457
|
+
conversationId: string,
|
|
458
|
+
updates: Record<string, string>,
|
|
459
|
+
): void {
|
|
460
|
+
const columns = Object.keys(updates);
|
|
461
|
+
if (columns.length === 0) return;
|
|
462
|
+
const assignments = columns.map((column) => `${column} = ?`).join(", ");
|
|
463
|
+
db.prepare(`UPDATE conversations SET ${assignments} WHERE id = ?`).run(
|
|
464
|
+
...columns.map((column) => updates[column] as string),
|
|
465
|
+
conversationId,
|
|
466
|
+
);
|
|
467
|
+
}
|