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,333 @@
|
|
|
1
|
+
import { metadataDict } from "#conversation/closeTail.ts";
|
|
2
|
+
import type { WritableDatabase } from "#db/database.ts";
|
|
3
|
+
import { embeddingToBytes } from "#db/decode.ts";
|
|
4
|
+
import { optionalString, requireString } from "#db/rowDecode.ts";
|
|
5
|
+
import {
|
|
6
|
+
curateAgainstExisting,
|
|
7
|
+
type ExistingMemoryForCuration,
|
|
8
|
+
type ExtractedMemory,
|
|
9
|
+
type ExtractionMessage,
|
|
10
|
+
type ExtractionStatus,
|
|
11
|
+
extractMemoriesWithStatus,
|
|
12
|
+
extractTasks,
|
|
13
|
+
formatTranscript,
|
|
14
|
+
naiveSummary,
|
|
15
|
+
type OnLlmCall,
|
|
16
|
+
} from "#extraction/conversation.ts";
|
|
17
|
+
import { createMemoryRow } from "#memory/memoryWrite.ts";
|
|
18
|
+
import { chatLedgerHook, embeddingLedgerHook } from "#observability/ledgerHooks.ts";
|
|
19
|
+
import { resolveExtractionModel } from "#providers/config.ts";
|
|
20
|
+
import {
|
|
21
|
+
addEmbeddingProvenance,
|
|
22
|
+
type EmbeddingAttemptInfo,
|
|
23
|
+
type EmbeddingProvider,
|
|
24
|
+
generateEmbeddingSafely,
|
|
25
|
+
} from "#providers/embedding.ts";
|
|
26
|
+
import type { LlmProvider } from "#providers/llm.ts";
|
|
27
|
+
import { newId, nowIso, pythonJsonDumps, pythonJsonDumpsEnsureAscii } from "#util/pyGenerators.ts";
|
|
28
|
+
|
|
29
|
+
export interface ConversationExtractionOptions {
|
|
30
|
+
llm: LlmProvider;
|
|
31
|
+
embeddings: EmbeddingProvider;
|
|
32
|
+
now?: () => string;
|
|
33
|
+
id?: () => string;
|
|
34
|
+
summarize?: boolean;
|
|
35
|
+
twoPass?: boolean;
|
|
36
|
+
curationExisting?: readonly ExistingMemoryForCuration[];
|
|
37
|
+
/**
|
|
38
|
+
* Model-call ledger (CV9.E2.S13/S14). Python's `_make_logger(role,
|
|
39
|
+
* conversation_id)` writes one `llm_calls` row per successful call in
|
|
40
|
+
* `extraction`, `curation`, `task_extraction`, and `summary`; the embedding
|
|
41
|
+
* rows were already logged. Off by default so the DS5 unit surface stays
|
|
42
|
+
* ledger-free; the front door turns it on.
|
|
43
|
+
*/
|
|
44
|
+
ledger?: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ConversationExtractionResult {
|
|
48
|
+
memoryIds: string[];
|
|
49
|
+
taskIds: string[];
|
|
50
|
+
extracted: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface ConversationRow {
|
|
54
|
+
id: string;
|
|
55
|
+
persona: string | null;
|
|
56
|
+
journey: string | null;
|
|
57
|
+
metadata: string | null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function runConversationExtraction(
|
|
61
|
+
db: WritableDatabase,
|
|
62
|
+
conversationId: string,
|
|
63
|
+
options: ConversationExtractionOptions,
|
|
64
|
+
): Promise<ConversationExtractionResult> {
|
|
65
|
+
const conv = getConversation(db, conversationId);
|
|
66
|
+
const messages = getMessages(db, conversationId);
|
|
67
|
+
if (!conv?.journey || messages.length < 4) {
|
|
68
|
+
return { memoryIds: [], taskIds: [], extracted: 0 };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const now = options.now ?? nowIso;
|
|
72
|
+
const id = options.id ?? newId;
|
|
73
|
+
const userName = resolveUserName(db);
|
|
74
|
+
const ledger = options.ledger ? llmLedger(db, conversationId, now) : undefined;
|
|
75
|
+
|
|
76
|
+
// A failure anywhere below propagates unmodified. Python records it -- the
|
|
77
|
+
// `llm_failed` status, the attempt counter, and quarantine at the max --
|
|
78
|
+
// OUTSIDE this orchestration, in `_run_extraction`; the port keeps that
|
|
79
|
+
// accounting in `extractionRun.ts` for the same reason: the stored metadata
|
|
80
|
+
// key order depends on it being written once, there, never partly here.
|
|
81
|
+
const outcome = await extractMemoriesWithStatus(options.llm, messages, {
|
|
82
|
+
persona: conv.persona,
|
|
83
|
+
journey: conv.journey,
|
|
84
|
+
userName,
|
|
85
|
+
onLlmCall: ledger?.("extraction"),
|
|
86
|
+
});
|
|
87
|
+
let extractedMemories: ExtractedMemory[] = outcome.memories;
|
|
88
|
+
const extractionStatus: ExtractionStatus = outcome.status;
|
|
89
|
+
|
|
90
|
+
if (options.twoPass && extractedMemories.length > 0) {
|
|
91
|
+
extractedMemories = await curateAgainstExisting(
|
|
92
|
+
options.llm,
|
|
93
|
+
extractedMemories,
|
|
94
|
+
options.curationExisting ?? [],
|
|
95
|
+
{ onLlmCall: ledger?.("curation") },
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const taskIds = await persistExtractedTasks(db, options.llm, messages, {
|
|
100
|
+
journey: conv.journey,
|
|
101
|
+
userName,
|
|
102
|
+
now,
|
|
103
|
+
id,
|
|
104
|
+
onLlmCall: ledger?.("task_extraction"),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const summaryText =
|
|
108
|
+
options.summarize === false
|
|
109
|
+
? naiveSummary(messages)
|
|
110
|
+
: await replayedSummary(options.llm, messages, userName, ledger?.("summary"));
|
|
111
|
+
const finalSummary = summaryText || naiveSummary(messages);
|
|
112
|
+
|
|
113
|
+
// Stage EVERY network embedding first -- the summary and each memory -- so a
|
|
114
|
+
// failure leaves nothing persisted. The retry then starts clean and does not
|
|
115
|
+
// duplicate rows or re-spend on embeddings (AI-03 / CV9.E2.S9).
|
|
116
|
+
//
|
|
117
|
+
// This mirrors Python's `_extract_and_persist` deliberately. The port
|
|
118
|
+
// originally interleaved embed -> insert per memory, which is invisible
|
|
119
|
+
// under replay -- replayed embeddings never fail -- and becomes a partial
|
|
120
|
+
// write the moment a live provider is on the other end: memories 1..n-1
|
|
121
|
+
// persisted, `extracted` unset, and a retry that inserts them a second time
|
|
122
|
+
// with a second paid embedding each (CV22.DS8.US2 plan review).
|
|
123
|
+
const summaryEmbeddingBytes = finalSummary
|
|
124
|
+
? embeddingToBytes(
|
|
125
|
+
// Logged to the ledger (AI-09/D-003), but NOT provenance-stamped:
|
|
126
|
+
// conversation_embeddings has no metadata column, matching Python's own
|
|
127
|
+
// narrower scope (add_embedding_provenance is called from
|
|
128
|
+
// add_memory/add_attachment only, never from the summary path).
|
|
129
|
+
await generateEmbeddingSafely(options.embeddings, finalSummary, {
|
|
130
|
+
onAttempt: logEmbeddingAttempt(db, conversationId, now),
|
|
131
|
+
}),
|
|
132
|
+
)
|
|
133
|
+
: null;
|
|
134
|
+
|
|
135
|
+
const stagedMemories: { memory: (typeof extractedMemories)[number]; embedding: Uint8Array }[] =
|
|
136
|
+
[];
|
|
137
|
+
for (const memory of extractedMemories) {
|
|
138
|
+
const embeddingText = `${memory.title}. ${memory.content}${memory.context ? ` Context: ${memory.context}` : ""}`;
|
|
139
|
+
const embedding = await generateEmbeddingSafely(options.embeddings, embeddingText, {
|
|
140
|
+
onAttempt: logEmbeddingAttempt(db, conversationId, now),
|
|
141
|
+
});
|
|
142
|
+
stagedMemories.push({ memory, embedding: embeddingToBytes(embedding) });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// All embeddings succeeded -- only local writes remain.
|
|
146
|
+
if (summaryEmbeddingBytes !== null && finalSummary) {
|
|
147
|
+
db.prepare(
|
|
148
|
+
`INSERT INTO conversation_embeddings (conversation_id, summary_embedding) VALUES (?, ?) ` +
|
|
149
|
+
`ON CONFLICT(conversation_id) DO UPDATE SET summary_embedding = excluded.summary_embedding`,
|
|
150
|
+
).run(conversationId, summaryEmbeddingBytes);
|
|
151
|
+
db.prepare("UPDATE conversations SET summary = ? WHERE id = ?").run(
|
|
152
|
+
finalSummary.slice(0, 1000),
|
|
153
|
+
conversationId,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const memoryIds: string[] = [];
|
|
158
|
+
for (const { memory, embedding } of stagedMemories) {
|
|
159
|
+
const memoryId = id();
|
|
160
|
+
insertMemory(db, memoryId, conversationId, memory, embedding, now());
|
|
161
|
+
memoryIds.push(memoryId);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const metadata = metadataDict(conv.metadata);
|
|
165
|
+
metadata.extracted = true;
|
|
166
|
+
metadata.extraction_status = extractionStatus.status;
|
|
167
|
+
if (
|
|
168
|
+
extractionStatus.dropped &&
|
|
169
|
+
Object.values(extractionStatus.dropped).some((count) => count > 0)
|
|
170
|
+
) {
|
|
171
|
+
metadata.extraction_dropped = extractionStatus.dropped;
|
|
172
|
+
}
|
|
173
|
+
db.prepare("UPDATE conversations SET metadata = ? WHERE id = ?").run(
|
|
174
|
+
pythonJsonDumps(metadata),
|
|
175
|
+
conversationId,
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
return { memoryIds, taskIds, extracted: memoryIds.length };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function persistExtractedTasks(
|
|
182
|
+
db: WritableDatabase,
|
|
183
|
+
llm: LlmProvider,
|
|
184
|
+
messages: readonly ExtractionMessage[],
|
|
185
|
+
options: {
|
|
186
|
+
journey: string;
|
|
187
|
+
userName: string;
|
|
188
|
+
now: () => string;
|
|
189
|
+
id: () => string;
|
|
190
|
+
onLlmCall?: OnLlmCall;
|
|
191
|
+
},
|
|
192
|
+
): Promise<string[]> {
|
|
193
|
+
try {
|
|
194
|
+
const tasks = await extractTasks(llm, messages, {
|
|
195
|
+
journey: options.journey,
|
|
196
|
+
userName: options.userName,
|
|
197
|
+
onLlmCall: options.onLlmCall,
|
|
198
|
+
});
|
|
199
|
+
const inserted: string[] = [];
|
|
200
|
+
for (const task of tasks) {
|
|
201
|
+
const existing = db
|
|
202
|
+
.prepare("SELECT id FROM tasks WHERE title LIKE ? AND journey = ? ORDER BY created_at DESC")
|
|
203
|
+
.all(`%${task.title}%`, task.journey ?? options.journey);
|
|
204
|
+
if (existing.length > 0) continue;
|
|
205
|
+
const taskId = options.id();
|
|
206
|
+
const now = options.now();
|
|
207
|
+
db.prepare(
|
|
208
|
+
`INSERT INTO tasks ` +
|
|
209
|
+
`(id, journey, title, status, due_date, scheduled_at, time_hint, stage, context, source, created_at, updated_at, completed_at, metadata) ` +
|
|
210
|
+
`VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
211
|
+
).run(
|
|
212
|
+
taskId,
|
|
213
|
+
task.journey,
|
|
214
|
+
task.title,
|
|
215
|
+
"todo",
|
|
216
|
+
task.due_date,
|
|
217
|
+
null,
|
|
218
|
+
null,
|
|
219
|
+
task.stage,
|
|
220
|
+
task.context,
|
|
221
|
+
"conversation",
|
|
222
|
+
now,
|
|
223
|
+
now,
|
|
224
|
+
null,
|
|
225
|
+
null,
|
|
226
|
+
);
|
|
227
|
+
inserted.push(taskId);
|
|
228
|
+
}
|
|
229
|
+
return inserted;
|
|
230
|
+
} catch {
|
|
231
|
+
return [];
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function replayedSummary(
|
|
236
|
+
llm: LlmProvider,
|
|
237
|
+
messages: readonly ExtractionMessage[],
|
|
238
|
+
userName: string,
|
|
239
|
+
onLlmCall?: OnLlmCall,
|
|
240
|
+
): Promise<string> {
|
|
241
|
+
try {
|
|
242
|
+
const prompt = formatTranscript(messages, userName);
|
|
243
|
+
const response = await llm.complete({
|
|
244
|
+
role: "summary",
|
|
245
|
+
prompt,
|
|
246
|
+
model: resolveExtractionModel(),
|
|
247
|
+
temperature: 0.3,
|
|
248
|
+
});
|
|
249
|
+
onLlmCall?.(response, prompt);
|
|
250
|
+
return response.content.trim();
|
|
251
|
+
} catch {
|
|
252
|
+
return naiveSummary(messages);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function getConversation(db: WritableDatabase, conversationId: string): ConversationRow | null {
|
|
257
|
+
const row = db
|
|
258
|
+
.prepare("SELECT id, persona, journey, metadata FROM conversations WHERE id = ?")
|
|
259
|
+
.get(conversationId);
|
|
260
|
+
if (!row) return null;
|
|
261
|
+
return {
|
|
262
|
+
id: requireString(row, "id"),
|
|
263
|
+
persona: optionalString(row, "persona"),
|
|
264
|
+
journey: optionalString(row, "journey"),
|
|
265
|
+
metadata: optionalString(row, "metadata"),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function getMessages(db: WritableDatabase, conversationId: string): ExtractionMessage[] {
|
|
270
|
+
return db
|
|
271
|
+
.prepare("SELECT role, content FROM messages WHERE conversation_id = ? ORDER BY created_at ASC")
|
|
272
|
+
.all(conversationId)
|
|
273
|
+
.map((row) => ({ role: requireString(row, "role"), content: requireString(row, "content") }));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function resolveUserName(db: WritableDatabase): string {
|
|
277
|
+
const row = db
|
|
278
|
+
.prepare("SELECT content FROM identity WHERE layer = ? AND key = ? LIMIT 1")
|
|
279
|
+
.get("user", "identity");
|
|
280
|
+
const content = row ? optionalString(row, "content") : null;
|
|
281
|
+
const match = content?.match(
|
|
282
|
+
/(?:You are talking to|Você está falando com) ([A-Z][a-zA-Záéíóúãõ]+)/,
|
|
283
|
+
);
|
|
284
|
+
return match?.[1] ?? "User";
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* The close tail's two ledger hooks. Roles match Python's `build_llm_logger`
|
|
289
|
+
* roles so the `llm_calls` ledger agrees across engines; the orchestration's
|
|
290
|
+
* clock stamps every row.
|
|
291
|
+
*/
|
|
292
|
+
function llmLedger(
|
|
293
|
+
db: WritableDatabase,
|
|
294
|
+
conversationId: string,
|
|
295
|
+
now: () => string,
|
|
296
|
+
): (role: string) => OnLlmCall {
|
|
297
|
+
return (role) => chatLedgerHook(db, role, { conversationId, now });
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function logEmbeddingAttempt(
|
|
301
|
+
db: WritableDatabase,
|
|
302
|
+
conversationId: string,
|
|
303
|
+
now: () => string,
|
|
304
|
+
): (info: EmbeddingAttemptInfo) => void {
|
|
305
|
+
return embeddingLedgerHook(db, { conversationId, now });
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function insertMemory(
|
|
309
|
+
db: WritableDatabase,
|
|
310
|
+
id: string,
|
|
311
|
+
conversationId: string,
|
|
312
|
+
memory: ExtractedMemory,
|
|
313
|
+
embedding: Uint8Array,
|
|
314
|
+
createdAt: string,
|
|
315
|
+
): void {
|
|
316
|
+
createMemoryRow(db, {
|
|
317
|
+
id,
|
|
318
|
+
conversationId,
|
|
319
|
+
memoryType: memory.memory_type,
|
|
320
|
+
layer: memory.layer,
|
|
321
|
+
title: memory.title,
|
|
322
|
+
content: memory.content,
|
|
323
|
+
context: memory.context,
|
|
324
|
+
journey: memory.journey,
|
|
325
|
+
persona: memory.persona,
|
|
326
|
+
// Memory tags go through `services/memory.py`'s plain `json.dumps(tags)`,
|
|
327
|
+
// which keeps `ensure_ascii=True` -- unlike conversation tags above.
|
|
328
|
+
tags: memory.tags.length > 0 ? pythonJsonDumpsEnsureAscii(memory.tags) : null,
|
|
329
|
+
createdAt,
|
|
330
|
+
embedding,
|
|
331
|
+
metadata: addEmbeddingProvenance(null),
|
|
332
|
+
});
|
|
333
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// CV22.DS7.US5 slice C — the budgeted extraction driver.
|
|
2
|
+
//
|
|
3
|
+
// Ports `extract_pending` and the maintenance counters from
|
|
4
|
+
// `src/memory/cli/conversation_logger.py`, plus the eligibility query in
|
|
5
|
+
// `ConversationStore.get_unextracted_conversations`.
|
|
6
|
+
//
|
|
7
|
+
// The extraction call is injected rather than imported: it is the DS5
|
|
8
|
+
// orchestration (`runConversationExtraction`) behind the replay transport, so
|
|
9
|
+
// this module — the part that decides *what* gets extracted and *how failure
|
|
10
|
+
// behaves* — stays deterministic and provider-free. Two released properties
|
|
11
|
+
// live here and must not regress:
|
|
12
|
+
//
|
|
13
|
+
// * AI-05 (CV9.E2.S26): a backlog can never turn one session start into an
|
|
14
|
+
// unbounded spend loop. At most `limit` conversations per call,
|
|
15
|
+
// oldest-ended first, remainder visibly carried over.
|
|
16
|
+
// * CV9.E2.S7: one poison-pill conversation is recorded and skipped, never
|
|
17
|
+
// allowed to crash the batch or block the queue behind it.
|
|
18
|
+
|
|
19
|
+
import type { WritableDatabase } from "#db/database.ts";
|
|
20
|
+
|
|
21
|
+
/** Python's `MEMORY_MAINTENANCE_MAX_EXTRACTIONS` default. */
|
|
22
|
+
export const DEFAULT_MAINTENANCE_MAX_EXTRACTIONS = 10;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Eligibility, byte-for-byte from `ConversationStore._UNEXTRACTED_WHERE`.
|
|
26
|
+
* `json_extract` returns NULL for malformed metadata, and `IS NOT 1` keeps
|
|
27
|
+
* such a row eligible rather than silently dropping it from the queue.
|
|
28
|
+
*/
|
|
29
|
+
const UNEXTRACTED_WHERE = `
|
|
30
|
+
WHERE c.ended_at IS NOT NULL
|
|
31
|
+
AND c.journey IS NOT NULL
|
|
32
|
+
AND (c.metadata IS NULL OR json_extract(c.metadata, '$.extracted') IS NOT 1)
|
|
33
|
+
AND (c.metadata IS NULL
|
|
34
|
+
OR json_extract(c.metadata, '$.extraction_quarantined') IS NOT 1)
|
|
35
|
+
AND (SELECT COUNT(*) FROM messages m WHERE m.conversation_id = c.id) >= 4
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
export interface UnextractedConversation {
|
|
39
|
+
id: string;
|
|
40
|
+
journey: string | null;
|
|
41
|
+
persona: string | null;
|
|
42
|
+
metadata: string | null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Ended, eligible conversations, oldest-ended first (FIFO drain). */
|
|
46
|
+
export function selectUnextractedConversations(
|
|
47
|
+
db: WritableDatabase,
|
|
48
|
+
limit: number | null,
|
|
49
|
+
): UnextractedConversation[] {
|
|
50
|
+
const base = `SELECT c.id, c.journey, c.persona, c.metadata FROM conversations c${UNEXTRACTED_WHERE}ORDER BY c.ended_at ASC`;
|
|
51
|
+
const rows = limit === null ? db.prepare(base).all() : db.prepare(`${base} LIMIT ?`).all(limit);
|
|
52
|
+
return rows.map((row) => ({
|
|
53
|
+
id: String(row.id),
|
|
54
|
+
journey: typeof row.journey === "string" ? row.journey : null,
|
|
55
|
+
persona: typeof row.persona === "string" ? row.persona : null,
|
|
56
|
+
metadata: typeof row.metadata === "string" ? row.metadata : null,
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface ExtractPendingOptions {
|
|
61
|
+
/** Defaults to the released maintenance budget. */
|
|
62
|
+
limit?: number;
|
|
63
|
+
/**
|
|
64
|
+
* The DS5 orchestration behind replay, wrapped in Python's `_run_extraction`
|
|
65
|
+
* accounting (`extractionRun.ts`); a throw or a rejection marks this one
|
|
66
|
+
* failed. Async because the orchestration awaits providers -- a synchronous
|
|
67
|
+
* signature here would let a rejected extraction escape the isolation below
|
|
68
|
+
* and count as a success.
|
|
69
|
+
*/
|
|
70
|
+
runExtraction: (db: WritableDatabase, conversationId: string) => Promise<void> | void;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Extract memories from ended conversations not yet processed, one at a time
|
|
75
|
+
* in queue order.
|
|
76
|
+
*
|
|
77
|
+
* Returns the number of conversations successfully extracted. Each is isolated:
|
|
78
|
+
* a failure is skipped so it cannot crash the batch or block the queue behind
|
|
79
|
+
* it, matching Python's bare `except Exception: continue`.
|
|
80
|
+
*/
|
|
81
|
+
export async function extractPending(
|
|
82
|
+
db: WritableDatabase,
|
|
83
|
+
options: ExtractPendingOptions,
|
|
84
|
+
): Promise<number> {
|
|
85
|
+
const limit = options.limit ?? DEFAULT_MAINTENANCE_MAX_EXTRACTIONS;
|
|
86
|
+
if (limit <= 0) return 0;
|
|
87
|
+
|
|
88
|
+
let extracted = 0;
|
|
89
|
+
for (const conversation of selectUnextractedConversations(db, limit)) {
|
|
90
|
+
try {
|
|
91
|
+
await options.runExtraction(db, conversation.id);
|
|
92
|
+
extracted += 1;
|
|
93
|
+
} catch {}
|
|
94
|
+
}
|
|
95
|
+
return extracted;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function countScalar(db: WritableDatabase, sql: string, ...params: string[]): number {
|
|
99
|
+
const row = db.prepare(sql).get(...params);
|
|
100
|
+
const value = row?.n;
|
|
101
|
+
if (typeof value === "bigint") return Number(value);
|
|
102
|
+
return typeof value === "number" ? value : 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Conversations quarantined after repeated extraction failure (CV9.E2.S7). */
|
|
106
|
+
export function countQuarantinedConversations(db: WritableDatabase): number {
|
|
107
|
+
return countScalar(
|
|
108
|
+
db,
|
|
109
|
+
`SELECT COUNT(*) AS n FROM conversations c
|
|
110
|
+
WHERE json_extract(c.metadata, '$.extraction_quarantined') IS 1`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Conversations whose recorded extraction status matches (AI-10). */
|
|
115
|
+
export function countConversationsWithExtractionStatus(
|
|
116
|
+
db: WritableDatabase,
|
|
117
|
+
status: string,
|
|
118
|
+
): number {
|
|
119
|
+
return countScalar(
|
|
120
|
+
db,
|
|
121
|
+
`SELECT COUNT(*) AS n FROM conversations c
|
|
122
|
+
WHERE json_extract(c.metadata, '$.extraction_status') = ?`,
|
|
123
|
+
status,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Conversations still eligible after a capped run (CV9.E2.S26, AI-05). */
|
|
128
|
+
export function countCarriedOverConversations(db: WritableDatabase): number {
|
|
129
|
+
return countScalar(db, `SELECT COUNT(*) AS n FROM conversations c${UNEXTRACTED_WHERE}`);
|
|
130
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Extraction failure accounting (CV22.DS7.US10 slice F).
|
|
2
|
+
//
|
|
3
|
+
// Ports `ConversationService._run_extraction` -- the wrapper around the DS5
|
|
4
|
+
// orchestration that Python's `extract_conversation` and `end_conversation`
|
|
5
|
+
// both go through -- and `_record_failed_extraction_attempt` (CV9.E2.S7).
|
|
6
|
+
//
|
|
7
|
+
// The orchestration itself (`runConversationExtraction`) is DS5's; what it
|
|
8
|
+
// lacked was the accounting Python keeps OUTSIDE it: every failed attempt
|
|
9
|
+
// increments `extraction_attempts`, stamps `extraction_status=llm_failed`,
|
|
10
|
+
// and quarantines the conversation at `EXTRACTION_MAX_ATTEMPTS` so a
|
|
11
|
+
// poison-pill transcript stops being retried at every session start. Without
|
|
12
|
+
// this wrapper a conversation whose extraction failed under TypeScript would
|
|
13
|
+
// be retried forever and the maintenance report's quarantine tail would never
|
|
14
|
+
// appear -- a divergence `session-maintenance` parity cannot tolerate.
|
|
15
|
+
//
|
|
16
|
+
// The metadata key ORDER is part of the stored bytes: Python appends
|
|
17
|
+
// `extraction_attempts` before `extraction_status` when neither exists yet, so
|
|
18
|
+
// the write happens here, once, rather than partly inside the orchestration.
|
|
19
|
+
|
|
20
|
+
import { metadataDict } from "#conversation/closeTail.ts";
|
|
21
|
+
import type { WritableDatabase } from "#db/database.ts";
|
|
22
|
+
import { pythonJsonDumps } from "#util/pyGenerators.ts";
|
|
23
|
+
|
|
24
|
+
/** Python's `MEMORY_EXTRACTION_MAX_ATTEMPTS` default. */
|
|
25
|
+
export const DEFAULT_EXTRACTION_MAX_ATTEMPTS = 3;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Python `_record_failed_extraction_attempt`: bump the counter in the
|
|
29
|
+
* conversation's metadata JSON, mark the failure, quarantine at the max.
|
|
30
|
+
* Returns the new attempt count (0 when the conversation does not exist).
|
|
31
|
+
*/
|
|
32
|
+
export function recordFailedExtractionAttempt(
|
|
33
|
+
db: WritableDatabase,
|
|
34
|
+
conversationId: string,
|
|
35
|
+
maxAttempts: number = DEFAULT_EXTRACTION_MAX_ATTEMPTS,
|
|
36
|
+
): number {
|
|
37
|
+
const row = db.prepare("SELECT metadata FROM conversations WHERE id = ?").get(conversationId);
|
|
38
|
+
if (!row) return 0;
|
|
39
|
+
const metadata = metadataDict(typeof row.metadata === "string" ? row.metadata : null);
|
|
40
|
+
// Python: `int(meta.get("extraction_attempts", 0)) + 1`.
|
|
41
|
+
const attempts = Math.trunc(Number(metadata.extraction_attempts ?? 0)) + 1;
|
|
42
|
+
metadata.extraction_attempts = attempts;
|
|
43
|
+
metadata.extraction_status = "llm_failed";
|
|
44
|
+
if (attempts >= maxAttempts) metadata.extraction_quarantined = true;
|
|
45
|
+
db.prepare("UPDATE conversations SET metadata = ? WHERE id = ?").run(
|
|
46
|
+
pythonJsonDumps(metadata),
|
|
47
|
+
conversationId,
|
|
48
|
+
);
|
|
49
|
+
return attempts;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Python `_run_extraction`: run the orchestration; on ANY failure record the
|
|
54
|
+
* attempt and re-raise, so the caller's per-conversation loop can isolate it.
|
|
55
|
+
* The eligibility gate (journey, four messages) lives in the orchestration and
|
|
56
|
+
* returns before touching the row, exactly as Python's does.
|
|
57
|
+
*/
|
|
58
|
+
export async function runExtractionWithAccounting(
|
|
59
|
+
db: WritableDatabase,
|
|
60
|
+
conversationId: string,
|
|
61
|
+
run: (db: WritableDatabase, conversationId: string) => Promise<unknown> | unknown,
|
|
62
|
+
maxAttempts: number = DEFAULT_EXTRACTION_MAX_ATTEMPTS,
|
|
63
|
+
): Promise<void> {
|
|
64
|
+
try {
|
|
65
|
+
await run(db, conversationId);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
recordFailedExtractionAttempt(db, conversationId, maxAttempts);
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// The fields a Claude-schema hook payload carries, in both spellings
|
|
2
|
+
// (CV21.DS2.US8).
|
|
3
|
+
//
|
|
4
|
+
// Three runtimes hand the shared hooks a payload: Claude Code and Codex in
|
|
5
|
+
// snake_case, Grok in camelCase with a snake_case alias on some fields but
|
|
6
|
+
// not all (DS12.TS4 L8). The turn is correlated under three names: Claude
|
|
7
|
+
// Code's `prompt_id`, Codex's `turn_id`, Grok's `promptId`. One reader, so
|
|
8
|
+
// the logger and the hook entries cannot disagree about where a field lives.
|
|
9
|
+
//
|
|
10
|
+
// Every reader returns "" for a missing or non-string field, and the parser
|
|
11
|
+
// returns null rather than throwing: a hook that threw on a payload it did
|
|
12
|
+
// not recognize would break the user's turn (the `extract_prompt` posture).
|
|
13
|
+
|
|
14
|
+
export type HookPayloadObject = Record<string, unknown>;
|
|
15
|
+
|
|
16
|
+
/** Parse a hook payload. Null for anything but a JSON object. */
|
|
17
|
+
export function parseHookPayloadObject(raw: string): HookPayloadObject | null {
|
|
18
|
+
try {
|
|
19
|
+
const value: unknown = JSON.parse(raw);
|
|
20
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
|
|
21
|
+
return value as HookPayloadObject;
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The first of the named fields that holds a string, else "". */
|
|
28
|
+
export function payloadText(payload: HookPayloadObject, ...names: string[]): string {
|
|
29
|
+
for (const name of names) {
|
|
30
|
+
const value = payload[name];
|
|
31
|
+
if (typeof value === "string") return value;
|
|
32
|
+
}
|
|
33
|
+
return "";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const sessionIdOf = (payload: HookPayloadObject): string =>
|
|
37
|
+
payloadText(payload, "session_id", "sessionId");
|
|
38
|
+
|
|
39
|
+
export const promptOf = (payload: HookPayloadObject): string => payloadText(payload, "prompt");
|
|
40
|
+
|
|
41
|
+
/** The runtime's correlation id for the turn, whatever it calls it. */
|
|
42
|
+
export const turnIdOf = (payload: HookPayloadObject): string =>
|
|
43
|
+
payloadText(payload, "prompt_id", "turn_id", "promptId");
|
|
44
|
+
|
|
45
|
+
/** The turn's final answer, as the Stop hook receives it. */
|
|
46
|
+
export const lastAssistantMessageOf = (payload: HookPayloadObject): string =>
|
|
47
|
+
payloadText(payload, "last_assistant_message", "lastAssistantMessage");
|
|
48
|
+
|
|
49
|
+
/** Set only inside a subagent (Grok names it; Claude Code and Codex fire SubagentStop instead). */
|
|
50
|
+
export const subagentTypeOf = (payload: HookPayloadObject): string =>
|
|
51
|
+
payloadText(payload, "subagentType", "subagent_type");
|