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,258 @@
|
|
|
1
|
+
// Session lifecycle composites (CV22.DS7.US10 slice D).
|
|
2
|
+
//
|
|
3
|
+
// Ports `session_start`, `session_start_fast`, `session_maintenance`,
|
|
4
|
+
// `close_stale_orphans`, `retitle_pending_conversations`, and
|
|
5
|
+
// `_reset_session_orientation` from `src/memory/cli/conversation_logger.py`.
|
|
6
|
+
//
|
|
7
|
+
// These are the composites Pi runs at session boundaries, and their REPORT is
|
|
8
|
+
// the user-visible surface: Pi prints it. Parity is therefore string-exact in
|
|
9
|
+
// grammar, labels, order, and the warning tails — everything except the
|
|
10
|
+
// elapsed seconds, which are wall-clock and cannot be byte-stable across runs
|
|
11
|
+
// or cores.
|
|
12
|
+
//
|
|
13
|
+
// The clock is injected rather than read (Navigator decision 2, 2026-09-03):
|
|
14
|
+
// tests pin durations, and cross-core comparison normalizes the timing token
|
|
15
|
+
// only after validating its exact grammar, so a re-worded or re-punctuated
|
|
16
|
+
// report still fails. Freezing real timings would have faked the evidence
|
|
17
|
+
// instead of isolating it.
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
countCarriedOverConversations,
|
|
21
|
+
countConversationsWithExtractionStatus,
|
|
22
|
+
countQuarantinedConversations,
|
|
23
|
+
extractPending,
|
|
24
|
+
} from "#conversation/extractionDriver.ts";
|
|
25
|
+
import { setMute } from "#conversation/logger.ts";
|
|
26
|
+
import type { WritableDatabase } from "#db/database.ts";
|
|
27
|
+
import { GLOBAL_STICKY_DEFAULTS_SESSION_ID } from "#mirror/runtimeSession.ts";
|
|
28
|
+
import { deactivateOperatingMode } from "#mode/operatingMode.ts";
|
|
29
|
+
import { nowIso } from "#util/pyGenerators.ts";
|
|
30
|
+
|
|
31
|
+
/** Python's `close_stale_orphans` default. */
|
|
32
|
+
export const STALE_ORPHAN_THRESHOLD_MINUTES = 30;
|
|
33
|
+
/** Python's `retitle_pending_conversations` default. */
|
|
34
|
+
export const RETITLE_LIMIT = 5;
|
|
35
|
+
|
|
36
|
+
export interface MaintenanceDeps {
|
|
37
|
+
/** Close one conversation through the full close tail. Throwing is isolated. */
|
|
38
|
+
closeConversation: (db: WritableDatabase, conversationId: string) => Promise<void> | void;
|
|
39
|
+
/** Improve one ended conversation's title; returns true when it changed. */
|
|
40
|
+
retitleConversation: (db: WritableDatabase, conversationId: string) => Promise<boolean> | boolean;
|
|
41
|
+
/** The DS5 orchestration behind replay with its accounting, as the driver injects it. */
|
|
42
|
+
runExtraction: (db: WritableDatabase, conversationId: string) => Promise<void> | void;
|
|
43
|
+
/**
|
|
44
|
+
* `backfillPiSessions` (slice E) bound to its resolved sessions directory.
|
|
45
|
+
* Injected rather than resolved here so the composite never reads the
|
|
46
|
+
* environment itself; the front door owns that resolution.
|
|
47
|
+
*/
|
|
48
|
+
backfillPiSessions: (db: WritableDatabase) => Promise<number> | number;
|
|
49
|
+
/** Python's `MEMORY_MAINTENANCE_MAX_EXTRACTIONS` (AI-05); defaults to the released budget. */
|
|
50
|
+
extractionLimit?: number;
|
|
51
|
+
/** Monotonic seconds, injected so report timings are pinnable. */
|
|
52
|
+
monotonic?: () => number;
|
|
53
|
+
now?: () => string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Python's `_reset_session_orientation`: a new runtime session starts from
|
|
58
|
+
* intention, not stale mode or journey context.
|
|
59
|
+
*/
|
|
60
|
+
export function resetSessionOrientation(db: WritableDatabase, now: () => string = nowIso): void {
|
|
61
|
+
deactivateOperatingMode(db, null, now());
|
|
62
|
+
db.prepare("DELETE FROM runtime_sessions WHERE session_id = ?").run(
|
|
63
|
+
GLOBAL_STICKY_DEFAULTS_SESSION_ID,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Conversation ids bound to an active runtime session. */
|
|
68
|
+
function activeConversationIds(db: WritableDatabase): Set<string> {
|
|
69
|
+
const rows = db
|
|
70
|
+
.prepare(
|
|
71
|
+
"SELECT conversation_id FROM runtime_sessions WHERE active = 1 AND conversation_id IS NOT NULL",
|
|
72
|
+
)
|
|
73
|
+
.all() as Record<string, unknown>[];
|
|
74
|
+
return new Set(rows.map((row) => String(row.conversation_id)));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Python's `get_open_conversations_idle_since`. */
|
|
78
|
+
function openConversationsIdleSince(db: WritableDatabase, threshold: string): string[] {
|
|
79
|
+
const rows = db
|
|
80
|
+
.prepare(
|
|
81
|
+
`SELECT c.id FROM conversations c
|
|
82
|
+
WHERE c.ended_at IS NULL
|
|
83
|
+
AND (
|
|
84
|
+
(SELECT MAX(m.created_at) FROM messages m WHERE m.conversation_id = c.id) < ?
|
|
85
|
+
OR NOT EXISTS (SELECT 1 FROM messages m WHERE m.conversation_id = c.id)
|
|
86
|
+
)`,
|
|
87
|
+
)
|
|
88
|
+
.all(threshold) as Record<string, unknown>[];
|
|
89
|
+
return rows.map((row) => String(row.id));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Python's `close_stale_orphans`: end open conversations idle longer than the
|
|
94
|
+
* threshold, skipping any still bound to an active runtime session.
|
|
95
|
+
*
|
|
96
|
+
* The count increments even when closing raises: `ended_at` is written before
|
|
97
|
+
* the tail, so the orphan IS closed and an extraction failure must not stop the
|
|
98
|
+
* loop or under-report the work. That asymmetry is deliberate in Python.
|
|
99
|
+
*
|
|
100
|
+
* KNOWN UNBOUNDED SPEND (registered for Debt Review): AI-05 caps extraction per
|
|
101
|
+
* maintenance run, but this loop has no cap on orphan count and each orphan
|
|
102
|
+
* runs the full close tail. Ported as-is; naming it here so DS8 prices it.
|
|
103
|
+
*/
|
|
104
|
+
export async function closeStaleOrphans(
|
|
105
|
+
db: WritableDatabase,
|
|
106
|
+
deps: Pick<MaintenanceDeps, "closeConversation" | "now"> & { thresholdMinutes?: number },
|
|
107
|
+
): Promise<number> {
|
|
108
|
+
const thresholdMinutes = deps.thresholdMinutes ?? STALE_ORPHAN_THRESHOLD_MINUTES;
|
|
109
|
+
const now = deps.now ?? nowIso;
|
|
110
|
+
const threshold = new Date(Date.parse(now()) - thresholdMinutes * 60_000).toISOString();
|
|
111
|
+
|
|
112
|
+
const active = activeConversationIds(db);
|
|
113
|
+
let count = 0;
|
|
114
|
+
for (const conversationId of openConversationsIdleSince(db, threshold)) {
|
|
115
|
+
if (active.has(conversationId)) continue;
|
|
116
|
+
try {
|
|
117
|
+
await deps.closeConversation(db, conversationId);
|
|
118
|
+
} catch {
|
|
119
|
+
// The orphan is closed regardless; a failed tail must not stop the loop.
|
|
120
|
+
}
|
|
121
|
+
count += 1;
|
|
122
|
+
}
|
|
123
|
+
return count;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Python's `retitle_pending_conversations`: opportunistically improve a small
|
|
128
|
+
* batch of ended conversation titles at startup.
|
|
129
|
+
*/
|
|
130
|
+
export async function retitlePendingConversations(
|
|
131
|
+
db: WritableDatabase,
|
|
132
|
+
deps: Pick<MaintenanceDeps, "retitleConversation"> & { limit?: number },
|
|
133
|
+
): Promise<number> {
|
|
134
|
+
const limit = deps.limit ?? RETITLE_LIMIT;
|
|
135
|
+
if (limit <= 0) return 0;
|
|
136
|
+
|
|
137
|
+
let candidates: string[];
|
|
138
|
+
try {
|
|
139
|
+
const scanLimit = Math.max(limit * 10, 50);
|
|
140
|
+
const rows = db
|
|
141
|
+
.prepare(
|
|
142
|
+
`SELECT c.id
|
|
143
|
+
FROM conversations c
|
|
144
|
+
WHERE c.ended_at IS NOT NULL
|
|
145
|
+
AND EXISTS (SELECT 1 FROM messages m WHERE m.conversation_id = c.id AND m.role = 'user')
|
|
146
|
+
AND EXISTS (SELECT 1 FROM messages m WHERE m.conversation_id = c.id AND m.role = 'assistant')
|
|
147
|
+
ORDER BY c.ended_at DESC, c.started_at DESC
|
|
148
|
+
LIMIT ?`,
|
|
149
|
+
)
|
|
150
|
+
.all(scanLimit) as Record<string, unknown>[];
|
|
151
|
+
candidates = rows.map((row) => String(row.id));
|
|
152
|
+
} catch {
|
|
153
|
+
return 0;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
let changed = 0;
|
|
157
|
+
for (const conversationId of candidates) {
|
|
158
|
+
if (changed >= limit) break;
|
|
159
|
+
try {
|
|
160
|
+
if (await deps.retitleConversation(db, conversationId)) changed += 1;
|
|
161
|
+
} catch {
|
|
162
|
+
// Python swallows per-conversation failures and continues the batch.
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return changed;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
interface TimedStep {
|
|
169
|
+
label: string;
|
|
170
|
+
count: number;
|
|
171
|
+
elapsed: number;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function timedStep(
|
|
175
|
+
label: string,
|
|
176
|
+
monotonic: () => number,
|
|
177
|
+
run: () => Promise<number> | number,
|
|
178
|
+
): Promise<TimedStep> {
|
|
179
|
+
const started = monotonic();
|
|
180
|
+
const count = await run();
|
|
181
|
+
return { label, count, elapsed: monotonic() - started };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Python's `%.1f` seconds rendering inside the report. */
|
|
185
|
+
function formatElapsed(elapsed: number): string {
|
|
186
|
+
return elapsed.toFixed(1);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Python's `session_maintenance`: four timed steps in a fixed order, then the
|
|
191
|
+
* warning tails. The step order is behavior, not presentation — closing stale
|
|
192
|
+
* orphans first is what makes their conversations eligible for the extraction
|
|
193
|
+
* step in the same run.
|
|
194
|
+
*/
|
|
195
|
+
export async function sessionMaintenance(
|
|
196
|
+
db: WritableDatabase,
|
|
197
|
+
deps: MaintenanceDeps,
|
|
198
|
+
): Promise<string> {
|
|
199
|
+
const monotonic = deps.monotonic ?? (() => performance.now() / 1000);
|
|
200
|
+
|
|
201
|
+
const steps: TimedStep[] = [
|
|
202
|
+
await timedStep("Closed stale conversations", monotonic, () =>
|
|
203
|
+
closeStaleOrphans(db, {
|
|
204
|
+
closeConversation: deps.closeConversation,
|
|
205
|
+
now: deps.now,
|
|
206
|
+
thresholdMinutes: STALE_ORPHAN_THRESHOLD_MINUTES,
|
|
207
|
+
}),
|
|
208
|
+
),
|
|
209
|
+
await timedStep("Backfilled Pi sessions", monotonic, () => deps.backfillPiSessions(db)),
|
|
210
|
+
await timedStep("Retitled pending conversations", monotonic, () =>
|
|
211
|
+
retitlePendingConversations(db, { retitleConversation: deps.retitleConversation }),
|
|
212
|
+
),
|
|
213
|
+
await timedStep("Extracted pending conversations", monotonic, () =>
|
|
214
|
+
extractPending(db, { runExtraction: deps.runExtraction, limit: deps.extractionLimit }),
|
|
215
|
+
),
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
const parts = ["Conversation maintenance complete."];
|
|
219
|
+
for (const step of steps) {
|
|
220
|
+
parts.push(`${step.label}: ${step.count} (${formatElapsed(step.elapsed)}s)`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const quarantined = countQuarantinedConversations(db);
|
|
224
|
+
if (quarantined) {
|
|
225
|
+
parts.push(`⚠ ${quarantined} conversation(s) quarantined after repeated extraction failure`);
|
|
226
|
+
}
|
|
227
|
+
const parseFailed = countConversationsWithExtractionStatus(db, "parse_failed");
|
|
228
|
+
if (parseFailed) {
|
|
229
|
+
parts.push(`⚠ ${parseFailed} conversation(s) with unreadable model output (parse_failed)`);
|
|
230
|
+
}
|
|
231
|
+
const carriedOver = countCarriedOverConversations(db);
|
|
232
|
+
if (carriedOver) {
|
|
233
|
+
parts.push(
|
|
234
|
+
`${carriedOver} conversation(s) carried over to the next run ` +
|
|
235
|
+
"(maintenance extraction budget)",
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
return parts.join("\n");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Python's `session_start_fast`: unmute and reorient, defer the expensive work. */
|
|
242
|
+
export function sessionStartFast(db: WritableDatabase, mirrorHome: string, now = nowIso): string {
|
|
243
|
+
setMute(false, mirrorHome);
|
|
244
|
+
resetSessionOrientation(db, now);
|
|
245
|
+
return "Conversation logging ACTIVE. Maintenance deferred.";
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Python's `session_start`: unmute, reorient, then run full maintenance. */
|
|
249
|
+
export async function sessionStart(
|
|
250
|
+
db: WritableDatabase,
|
|
251
|
+
mirrorHome: string,
|
|
252
|
+
deps: MaintenanceDeps,
|
|
253
|
+
): Promise<string> {
|
|
254
|
+
setMute(false, mirrorHome);
|
|
255
|
+
resetSessionOrientation(db, deps.now ?? nowIso);
|
|
256
|
+
const maintenance = await sessionMaintenance(db, deps);
|
|
257
|
+
return ["Conversation logging ACTIVE.", maintenance].join("\n");
|
|
258
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Atomic closed-session import (CV22.DS7.US10 slice E).
|
|
2
|
+
//
|
|
3
|
+
// Ports `RuntimeSessionService.import_closed_conversation` from
|
|
4
|
+
// `src/memory/services/runtime_session.py`, the seam both backfills write
|
|
5
|
+
// through. It is the sibling of `getOrCreateSessionConversation` in
|
|
6
|
+
// `logger.ts` and shares its transaction discipline for the same reason:
|
|
7
|
+
// a backfill imports against the SAME `runtime_sessions` key a live hook
|
|
8
|
+
// binds (Pi's live session id is the session file path), and the two can
|
|
9
|
+
// interleave.
|
|
10
|
+
//
|
|
11
|
+
// Resolved decision 4B (plan.md): the binding check runs INSIDE the
|
|
12
|
+
// `BEGIN IMMEDIATE` transaction, so a binding a concurrent hook committed
|
|
13
|
+
// while this import waited for the write lock is seen and honored -- the
|
|
14
|
+
// import returns null instead of clobbering a live session with
|
|
15
|
+
// `active=0`/`closed_at`. The same atomicity removes the crash-window
|
|
16
|
+
// duplicate-import path: either the conversation, every message, and the
|
|
17
|
+
// binding all land, or nothing does.
|
|
18
|
+
//
|
|
19
|
+
// Titles are deliberately NOT set here. The two callers title differently
|
|
20
|
+
// (Pi through `setProvisionalTitle`, Codex through a bare column write) and
|
|
21
|
+
// Python applies both after commit; reproducing that asymmetry is the
|
|
22
|
+
// callers' job.
|
|
23
|
+
|
|
24
|
+
import type { LoggerDeps } from "#conversation/logger.ts";
|
|
25
|
+
import { type WritableDatabase, withTransaction } from "#db/database.ts";
|
|
26
|
+
|
|
27
|
+
export interface ImportedMessage {
|
|
28
|
+
role: string;
|
|
29
|
+
content: string;
|
|
30
|
+
createdAt: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ClosedSessionImport {
|
|
34
|
+
interface: string;
|
|
35
|
+
messages: readonly ImportedMessage[];
|
|
36
|
+
endedAt: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Import a closed session transcript as one conversation bound to
|
|
41
|
+
* `sessionId`. Returns the new conversation id, or null when the session is
|
|
42
|
+
* already tracked -- including by a writer that won the binding while this
|
|
43
|
+
* call waited for the lock.
|
|
44
|
+
*/
|
|
45
|
+
export function importClosedConversation(
|
|
46
|
+
db: WritableDatabase,
|
|
47
|
+
sessionId: string,
|
|
48
|
+
session: ClosedSessionImport,
|
|
49
|
+
deps: LoggerDeps,
|
|
50
|
+
): string | null {
|
|
51
|
+
return withTransaction(db, () => {
|
|
52
|
+
const tracked = db
|
|
53
|
+
.prepare("SELECT 1 FROM runtime_sessions WHERE session_id = ?")
|
|
54
|
+
.get(sessionId);
|
|
55
|
+
if (tracked) return null;
|
|
56
|
+
|
|
57
|
+
// Python's Conversation(...) draws its id before its started_at; keep the
|
|
58
|
+
// same generator order so injected sequences line up with the oracle.
|
|
59
|
+
const conversationId = deps.newId();
|
|
60
|
+
const startedAt = deps.nowIso();
|
|
61
|
+
db.prepare(
|
|
62
|
+
`INSERT INTO conversations
|
|
63
|
+
(id, title, started_at, ended_at, interface, persona, journey, summary, tags, metadata)
|
|
64
|
+
VALUES (?, NULL, ?, ?, ?, NULL, NULL, NULL, NULL, NULL)`,
|
|
65
|
+
).run(conversationId, startedAt, session.endedAt, session.interface);
|
|
66
|
+
|
|
67
|
+
const insertMessage = db.prepare(
|
|
68
|
+
`INSERT INTO messages (id, conversation_id, role, content, created_at, token_count, metadata)
|
|
69
|
+
VALUES (?, ?, ?, ?, ?, NULL, NULL)`,
|
|
70
|
+
);
|
|
71
|
+
for (const message of session.messages) {
|
|
72
|
+
insertMessage.run(
|
|
73
|
+
deps.newId(),
|
|
74
|
+
conversationId,
|
|
75
|
+
message.role,
|
|
76
|
+
message.content,
|
|
77
|
+
message.createdAt,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
db.prepare(
|
|
82
|
+
`INSERT INTO runtime_sessions
|
|
83
|
+
(session_id, conversation_id, interface, mirror_active, persona, journey,
|
|
84
|
+
hook_injected, active, started_at, updated_at, closed_at, metadata)
|
|
85
|
+
VALUES (?, ?, ?, 0, NULL, NULL, 0, 0, ?, ?, ?, NULL)`,
|
|
86
|
+
).run(sessionId, conversationId, session.interface, startedAt, startedAt, session.endedAt);
|
|
87
|
+
|
|
88
|
+
return conversationId;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// Assistant-turn backfill from a Claude transcript (CV22.DS7.US10 slice E).
|
|
2
|
+
//
|
|
3
|
+
// Ports `backfill_assistant_messages` from
|
|
4
|
+
// `src/memory/cli/conversation_logger.py` together with the two helpers it
|
|
5
|
+
// borrows from `src/memory/cli/transcript_export.py` (`parse_jsonl`,
|
|
6
|
+
// `_assistant_text`). `hook_session_end` calls it after ending the session --
|
|
7
|
+
// and, when the payload carries no session id at all, INSTEAD of ending one:
|
|
8
|
+
// that session-less route is the path US5's hook port deliberately left out.
|
|
9
|
+
//
|
|
10
|
+
// This is not an import: nothing is created or bound. It appends assistant
|
|
11
|
+
// turns to conversations that overlap the transcript's time window and logged
|
|
12
|
+
// no assistant message of their own, stamping each with `now` (Python's
|
|
13
|
+
// `Message` default), not with the transcript timestamp. Python commits every
|
|
14
|
+
// message as it goes and swallows nothing here; a malformed entry reached
|
|
15
|
+
// mid-walk raises with the earlier appends already committed. The port keeps
|
|
16
|
+
// that sequence so partial state matches, and leaves swallowing to the hook.
|
|
17
|
+
|
|
18
|
+
import { readFileSync } from "node:fs";
|
|
19
|
+
import type { LoggerDeps } from "#conversation/logger.ts";
|
|
20
|
+
import type { WritableDatabase } from "#db/database.ts";
|
|
21
|
+
|
|
22
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
23
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Python `dict.get` on something that must be a dict; see `backfill.ts`. */
|
|
27
|
+
function getOr(container: unknown, key: string, fallback: unknown): unknown {
|
|
28
|
+
if (!isRecord(container)) throw new TypeError(`expected an object with '${key}'`);
|
|
29
|
+
return key in container ? container[key] : fallback;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getFrom(container: unknown, key: string): unknown {
|
|
33
|
+
return getOr(container, key, undefined);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Python `parse_jsonl`: blank lines skipped, unparseable lines skipped. */
|
|
37
|
+
export function parseJsonl(path: string): unknown[] {
|
|
38
|
+
const entries: unknown[] = [];
|
|
39
|
+
for (const raw of readFileSync(path, "utf8").split(/\r?\n|\r/)) {
|
|
40
|
+
const line = raw.trim();
|
|
41
|
+
if (!line) continue;
|
|
42
|
+
try {
|
|
43
|
+
entries.push(JSON.parse(line));
|
|
44
|
+
} catch {
|
|
45
|
+
// json.JSONDecodeError -> continue
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return entries;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Python `_assistant_text`: the stripped, non-empty `text` blocks of an
|
|
53
|
+
* assistant message joined by a blank line. Iterating a string or a dict
|
|
54
|
+
* yields no dict items (so no text); a null or number cannot be iterated and
|
|
55
|
+
* raises, as does a text block whose `text` is not a string.
|
|
56
|
+
*/
|
|
57
|
+
export function assistantText(contentBlocks: unknown): string {
|
|
58
|
+
const iterable = Array.isArray(contentBlocks)
|
|
59
|
+
? contentBlocks
|
|
60
|
+
: typeof contentBlocks === "string" || isRecord(contentBlocks)
|
|
61
|
+
? []
|
|
62
|
+
: null;
|
|
63
|
+
if (iterable === null) throw new TypeError("assistant content is not iterable");
|
|
64
|
+
const parts: string[] = [];
|
|
65
|
+
for (const block of iterable) {
|
|
66
|
+
if (!isRecord(block) || block.type !== "text") continue;
|
|
67
|
+
const text = getOr(block, "text", "");
|
|
68
|
+
if (typeof text !== "string") throw new TypeError("text block is not a string");
|
|
69
|
+
const trimmed = text.trim();
|
|
70
|
+
if (trimmed) parts.push(trimmed);
|
|
71
|
+
}
|
|
72
|
+
return parts.join("\n\n");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Python `str` truthiness on the timestamp: absent, null, or empty is skipped. */
|
|
76
|
+
function timestampOf(entry: unknown): string | null {
|
|
77
|
+
const value = getOr(entry, "timestamp", "");
|
|
78
|
+
return typeof value === "string" && value ? value : null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Python `backfill_assistant_messages`. The transcript window is
|
|
83
|
+
* `[min(timestamp), max(timestamp)]` over entries that carry one; a
|
|
84
|
+
* conversation is in range when `started_at <= window end` and it either
|
|
85
|
+
* ended at or after the window start or is still open. Within an in-range
|
|
86
|
+
* conversation that has no assistant turn, every `assistant` entry whose
|
|
87
|
+
* timestamp falls inside `[started_at, ended_at or window end]` is appended.
|
|
88
|
+
*/
|
|
89
|
+
export function backfillAssistantMessages(
|
|
90
|
+
db: WritableDatabase,
|
|
91
|
+
transcriptPath: string,
|
|
92
|
+
deps: LoggerDeps,
|
|
93
|
+
): void {
|
|
94
|
+
const entries = parseJsonl(transcriptPath);
|
|
95
|
+
if (entries.length === 0) return;
|
|
96
|
+
|
|
97
|
+
const timestamps = entries.map(timestampOf).filter((value): value is string => value !== null);
|
|
98
|
+
if (timestamps.length === 0) return;
|
|
99
|
+
// Python's min()/max() on str compare by code point; ISO timestamps are
|
|
100
|
+
// ASCII, so plain comparison is the same order.
|
|
101
|
+
let startTime = timestamps[0] as string;
|
|
102
|
+
let endTime = timestamps[0] as string;
|
|
103
|
+
for (const value of timestamps) {
|
|
104
|
+
if (value < startTime) startTime = value;
|
|
105
|
+
if (value > endTime) endTime = value;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const conversations = db
|
|
109
|
+
.prepare(
|
|
110
|
+
`SELECT id, started_at, ended_at FROM conversations
|
|
111
|
+
WHERE started_at <= ? AND (ended_at >= ? OR ended_at IS NULL)`,
|
|
112
|
+
)
|
|
113
|
+
.all(endTime, startTime) as { id: string; started_at: string; ended_at: string | null }[];
|
|
114
|
+
|
|
115
|
+
const hasAssistant = db.prepare(
|
|
116
|
+
"SELECT 1 FROM messages WHERE conversation_id = ? AND role = 'assistant' LIMIT 1",
|
|
117
|
+
);
|
|
118
|
+
const insert = db.prepare(
|
|
119
|
+
`INSERT INTO messages (id, conversation_id, role, content, created_at, token_count, metadata)
|
|
120
|
+
VALUES (?, ?, 'assistant', ?, ?, NULL, NULL)`,
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
for (const conversation of conversations) {
|
|
124
|
+
if (hasAssistant.get(conversation.id)) continue;
|
|
125
|
+
const conversationStart = conversation.started_at;
|
|
126
|
+
const conversationEnd = conversation.ended_at ?? endTime;
|
|
127
|
+
|
|
128
|
+
for (const entry of entries) {
|
|
129
|
+
const timestamp = timestampOf(entry);
|
|
130
|
+
if (!timestamp || !(conversationStart <= timestamp && timestamp <= conversationEnd)) continue;
|
|
131
|
+
if (getFrom(entry, "type") !== "assistant") continue;
|
|
132
|
+
const message = getOr(entry, "message", {});
|
|
133
|
+
const text = assistantText(getOr(message, "content", []));
|
|
134
|
+
if (text) {
|
|
135
|
+
// Python: Message(...) draws its id before its created_at.
|
|
136
|
+
const id = deps.newId();
|
|
137
|
+
insert.run(id, conversation.id, text, deps.nowIso());
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|