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,387 @@
|
|
|
1
|
+
// Session backfills from runtime-written JSONL (CV22.DS7.US10 slice E).
|
|
2
|
+
//
|
|
3
|
+
// Ports `backfill_pi_sessions` and `backfill_codex_session` from
|
|
4
|
+
// `src/memory/cli/conversation_logger.py`. Both import a closed session the
|
|
5
|
+
// live hooks never saw, through the atomic seam in `sessionImport.ts`, and
|
|
6
|
+
// both are graded against `ts/test/goldens/backfill.golden.json`.
|
|
7
|
+
//
|
|
8
|
+
// The two are deliberately NOT unified beyond that seam, because Python's
|
|
9
|
+
// asymmetries are the contract:
|
|
10
|
+
// * Pi titles through `set_provisional_title` (whitespace collapse plus
|
|
11
|
+
// `title_source`/`title_status` provenance in metadata); Codex titles
|
|
12
|
+
// through `_generate_title` and a bare column write with no provenance.
|
|
13
|
+
// * Pi tolerates a corrupt or structurally invalid file by skipping THAT
|
|
14
|
+
// file and continuing the walk; Codex gives up on its single file.
|
|
15
|
+
// * Pi parses every timestamp form (millisecond epoch, string, absent);
|
|
16
|
+
// Codex stores the raw `timestamp` value and lets the model reject a
|
|
17
|
+
// missing one.
|
|
18
|
+
//
|
|
19
|
+
// Nothing here touches a provider or the clock directly: `now` is injected
|
|
20
|
+
// through `LoggerDeps`, which is what makes "no timestamp -> now" pinnable.
|
|
21
|
+
//
|
|
22
|
+
// CR121 (2026-10-11): a home imports only the Pi sessions newer than itself.
|
|
23
|
+
// Python imported every untracked session on the machine, which for a new
|
|
24
|
+
// home meant the person's whole Pi history, copied and spent on before the
|
|
25
|
+
// first prompt. The floor is explicit (`notBefore`), never defaulted, and
|
|
26
|
+
// the rule fails closed: there is no value that means "everything" except
|
|
27
|
+
// the epoch, which says so. The Codex importer is untouched: it imports the
|
|
28
|
+
// one session the wrapper names, not a history.
|
|
29
|
+
|
|
30
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
31
|
+
import { join, relative, sep } from "node:path";
|
|
32
|
+
import { generateTitle, type LoggerDeps, setProvisionalTitle } from "#conversation/logger.ts";
|
|
33
|
+
import { type ImportedMessage, importClosedConversation } from "#conversation/sessionImport.ts";
|
|
34
|
+
import type { WritableDatabase } from "#db/database.ts";
|
|
35
|
+
import { expandHome } from "#util/paths.ts";
|
|
36
|
+
import { comparePathComponents, sliceCodePoints } from "#util/pythonText.ts";
|
|
37
|
+
|
|
38
|
+
/** Python `_DEFAULT_PI_SESSIONS_DIR`: `~/.pi/agent/sessions`. */
|
|
39
|
+
export function defaultPiSessionsDir(homeDir: string): string {
|
|
40
|
+
return join(homeDir, ".pi", "agent", "sessions");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Python's source-directory resolution for the Pi backfill, in order:
|
|
45
|
+
* explicit argument -> `PI_SESSIONS_DIR` -> `~/.pi/agent/sessions`.
|
|
46
|
+
* (Python's module-level `_PI_SESSIONS_DIR` override exists only for its own
|
|
47
|
+
* test monkey-patching and has no runtime input; it is not a contract.)
|
|
48
|
+
*/
|
|
49
|
+
export function resolvePiSessionsDir(
|
|
50
|
+
explicit: string | null | undefined,
|
|
51
|
+
env: { PI_SESSIONS_DIR?: string },
|
|
52
|
+
homeDir: string,
|
|
53
|
+
): string {
|
|
54
|
+
if (explicit) return expandHome(explicit);
|
|
55
|
+
if (env.PI_SESSIONS_DIR) return expandHome(env.PI_SESSIONS_DIR);
|
|
56
|
+
return defaultPiSessionsDir(homeDir);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
60
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Python `dict.get(key, default)` on a value that MUST be a dict: anything
|
|
65
|
+
* else raises AttributeError, which each parser turns into its own failure
|
|
66
|
+
* mode. The default applies only when the key is ABSENT -- a present `null`
|
|
67
|
+
* is returned as null, exactly as Python returns None, so a later `.get` on
|
|
68
|
+
* it raises rather than silently reading from the default.
|
|
69
|
+
*/
|
|
70
|
+
function getOr(container: unknown, key: string, fallback: unknown): unknown {
|
|
71
|
+
if (!isRecord(container)) throw new TypeError(`expected an object with '${key}'`);
|
|
72
|
+
return key in container ? container[key] : fallback;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function getFrom(container: unknown, key: string): unknown {
|
|
76
|
+
return getOr(container, key, undefined);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Python truthiness for JSON values: empty containers are falsy too. */
|
|
80
|
+
function pythonTruthy(value: unknown): boolean {
|
|
81
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
82
|
+
if (isRecord(value)) return Object.keys(value).length > 0;
|
|
83
|
+
return Boolean(value);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Python `_parse_pi_content`: a string as-is, text blocks joined by `\n`, else `""`. */
|
|
87
|
+
export function parsePiContent(content: unknown): string {
|
|
88
|
+
if (typeof content === "string") return content;
|
|
89
|
+
if (Array.isArray(content)) {
|
|
90
|
+
const parts: string[] = [];
|
|
91
|
+
for (const block of content) {
|
|
92
|
+
if (!isRecord(block) || block.type !== "text") continue;
|
|
93
|
+
const text = block.text ?? "";
|
|
94
|
+
// `"\n".join(...)` raises on a non-str item; the file is skipped.
|
|
95
|
+
if (typeof text !== "string") throw new TypeError("text block is not a string");
|
|
96
|
+
parts.push(text);
|
|
97
|
+
}
|
|
98
|
+
return parts.join("\n");
|
|
99
|
+
}
|
|
100
|
+
return "";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Python `datetime.fromtimestamp(ms / 1000, tz=utc).isoformat()`:
|
|
105
|
+
* `+00:00` suffix (not `Z`), microseconds only when non-zero, rounded from
|
|
106
|
+
* the fractional second the way CPython does (`round(frac * 1e6)`).
|
|
107
|
+
*/
|
|
108
|
+
function pythonUtcIsoFromEpochMs(epochMs: number): string {
|
|
109
|
+
const seconds = epochMs / 1000;
|
|
110
|
+
let whole = Math.floor(seconds);
|
|
111
|
+
let micros = Math.round((seconds - whole) * 1e6);
|
|
112
|
+
if (micros >= 1_000_000) {
|
|
113
|
+
whole += 1;
|
|
114
|
+
micros -= 1_000_000;
|
|
115
|
+
}
|
|
116
|
+
const base = new Date(whole * 1000).toISOString().slice(0, 19);
|
|
117
|
+
const fraction = micros === 0 ? "" : `.${String(micros).padStart(6, "0")}`;
|
|
118
|
+
return `${base}${fraction}+00:00`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Python `_parse_pi_timestamp`: a number above 1e10 is a millisecond epoch;
|
|
123
|
+
* a string is stored verbatim; anything else (absent, null, a seconds epoch,
|
|
124
|
+
* a boolean -- which is an int in Python but never exceeds 1e10) is `now`.
|
|
125
|
+
*/
|
|
126
|
+
export function parsePiTimestamp(value: unknown, now: () => string): string {
|
|
127
|
+
if (typeof value === "number" && value > 1e10) return pythonUtcIsoFromEpochMs(value);
|
|
128
|
+
if (typeof value === "string") return value;
|
|
129
|
+
return now();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Parse one Pi session file into importable messages. Throws on any line
|
|
134
|
+
* that is not valid JSON or not the expected shape -- Python wraps the whole
|
|
135
|
+
* file loop in one `try` and skips the file on the first failure, even after
|
|
136
|
+
* earlier lines parsed cleanly.
|
|
137
|
+
*/
|
|
138
|
+
function parsePiSessionFile(path: string, now: () => string): ImportedMessage[] {
|
|
139
|
+
const messages: ImportedMessage[] = [];
|
|
140
|
+
for (const line of readFileSync(path, "utf8").split(/\r?\n|\r/)) {
|
|
141
|
+
if (!line.trim()) continue;
|
|
142
|
+
const entry: unknown = JSON.parse(line);
|
|
143
|
+
if (getFrom(entry, "type") !== "message") continue;
|
|
144
|
+
const message = getOr(entry, "message", {});
|
|
145
|
+
const role = getOr(message, "role", "");
|
|
146
|
+
if (role !== "user" && role !== "assistant") continue;
|
|
147
|
+
const content = parsePiContent(getOr(message, "content", ""));
|
|
148
|
+
if (!content.trim()) continue;
|
|
149
|
+
const createdAt = parsePiTimestamp(getFrom(message, "timestamp"), now);
|
|
150
|
+
messages.push({ role, content, createdAt });
|
|
151
|
+
}
|
|
152
|
+
return messages;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Python `sorted(pi_sessions_dir.rglob("*.jsonl"))`: every `.jsonl` file
|
|
157
|
+
* under the directory, ordered by path components (see
|
|
158
|
+
* `comparePathComponents` for why that is not a string sort).
|
|
159
|
+
*/
|
|
160
|
+
function listPiSessionFiles(sessionsDir: string): string[] {
|
|
161
|
+
const entries = readdirSync(sessionsDir, { recursive: true, withFileTypes: true });
|
|
162
|
+
const found: string[][] = [];
|
|
163
|
+
for (const entry of entries) {
|
|
164
|
+
if (!entry.name.endsWith(".jsonl")) continue;
|
|
165
|
+
const parents = relative(sessionsDir, entry.parentPath).split(sep).filter(Boolean);
|
|
166
|
+
found.push([...parents, entry.name]);
|
|
167
|
+
}
|
|
168
|
+
found.sort(comparePathComponents);
|
|
169
|
+
return found.map((components) => join(sessionsDir, ...components));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface PiBackfillOptions {
|
|
173
|
+
/** Already resolved through `resolvePiSessionsDir`. */
|
|
174
|
+
sessionsDir: string;
|
|
175
|
+
/**
|
|
176
|
+
* An ISO instant: a session that ended at or before it is not imported
|
|
177
|
+
* (CR121). Production passes the home's birth (`homeBornAt`); a caller
|
|
178
|
+
* that wants no floor passes the epoch and says so.
|
|
179
|
+
*/
|
|
180
|
+
notBefore: string;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* When this home was born: the first `applied_at` in the `_migrations`
|
|
185
|
+
* ledger. A fresh database stamps its whole ledger at its first open, and a
|
|
186
|
+
* database from any earlier era carries the ledger from its own first open,
|
|
187
|
+
* so the minimum is the birth of every home there is -- with no new table,
|
|
188
|
+
* column, or marker. Read once per maintenance run.
|
|
189
|
+
*
|
|
190
|
+
* The ledger means "which migrations ran, when"; reading its minimum as a
|
|
191
|
+
* birth is an inference, accepted because every plausible edit to the ledger
|
|
192
|
+
* moves the minimum forward and imports less, never more. `migrations.ts`
|
|
193
|
+
* names this consumer at the ledger's definition. A second consumer of
|
|
194
|
+
* "birth" is the trigger to model it as a row of its own.
|
|
195
|
+
*
|
|
196
|
+
* Fails closed: an empty ledger is an error, not a home born at the epoch.
|
|
197
|
+
*/
|
|
198
|
+
export function homeBornAt(db: WritableDatabase): string {
|
|
199
|
+
const row = db.prepare("SELECT MIN(applied_at) AS born FROM _migrations").get() as
|
|
200
|
+
| { born: string | null }
|
|
201
|
+
| undefined;
|
|
202
|
+
if (typeof row?.born !== "string" || !row.born) {
|
|
203
|
+
throw new Error("the _migrations ledger is empty: this home's birth is unknown");
|
|
204
|
+
}
|
|
205
|
+
return row.born;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** The instant of an ISO timestamp, or NaN for anything `Date.parse` rejects. */
|
|
209
|
+
function instant(iso: string): number {
|
|
210
|
+
return Date.parse(iso);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Python `backfill_pi_sessions`: import every untracked Pi session with at
|
|
215
|
+
* least two messages as a closed `pi` conversation, titled provisionally from
|
|
216
|
+
* the first user line. Returns the number imported.
|
|
217
|
+
*
|
|
218
|
+
* The session id is the session file's path, exactly as Python's
|
|
219
|
+
* `str(session_file)` -- which is also what the live Pi extension binds, so a
|
|
220
|
+
* session the hooks already track is skipped before its file is even read.
|
|
221
|
+
*
|
|
222
|
+
* CR121: a session is Mirror's only if it ended after `notBefore`. Two
|
|
223
|
+
* checks, in cost order. A file whose mtime is at or before the floor is
|
|
224
|
+
* skipped unread: its last message was written at or before its last write,
|
|
225
|
+
* so it cannot be newer. (This is what keeps a skipped session, which is
|
|
226
|
+
* never tracked, from being parsed again at every start for the rest of the
|
|
227
|
+
* home's life.) A file written after the floor is parsed and judged by its
|
|
228
|
+
* last message, the same instant it stores as `endedAt` -- a copy or a sync
|
|
229
|
+
* can touch a file long after its conversation ended. A session whose
|
|
230
|
+
* messages carry no parseable instant dates to `now` under the existing rule
|
|
231
|
+
* and is imported: undated is newer, not older.
|
|
232
|
+
*/
|
|
233
|
+
export function backfillPiSessions(
|
|
234
|
+
db: WritableDatabase,
|
|
235
|
+
options: PiBackfillOptions,
|
|
236
|
+
deps: LoggerDeps,
|
|
237
|
+
): number {
|
|
238
|
+
const floor = instant(options.notBefore);
|
|
239
|
+
if (Number.isNaN(floor)) {
|
|
240
|
+
throw new Error(`notBefore is not an instant: ${JSON.stringify(options.notBefore)}`);
|
|
241
|
+
}
|
|
242
|
+
if (!existsSync(options.sessionsDir)) return 0;
|
|
243
|
+
|
|
244
|
+
let count = 0;
|
|
245
|
+
for (const sessionFile of listPiSessionFiles(options.sessionsDir)) {
|
|
246
|
+
const sessionId = sessionFile;
|
|
247
|
+
if (db.prepare("SELECT 1 FROM runtime_sessions WHERE session_id = ?").get(sessionId)) {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
if (statSync(sessionFile).mtimeMs <= floor) continue;
|
|
251
|
+
let messages: ImportedMessage[];
|
|
252
|
+
try {
|
|
253
|
+
messages = parsePiSessionFile(sessionFile, deps.nowIso);
|
|
254
|
+
} catch {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (messages.length < 2) continue;
|
|
258
|
+
const endedAt = instant((messages.at(-1) as ImportedMessage).createdAt);
|
|
259
|
+
if (endedAt <= floor) continue;
|
|
260
|
+
|
|
261
|
+
const conversationId = importClosedConversation(
|
|
262
|
+
db,
|
|
263
|
+
sessionId,
|
|
264
|
+
{ interface: "pi", messages, endedAt: (messages.at(-1) as ImportedMessage).createdAt },
|
|
265
|
+
deps,
|
|
266
|
+
);
|
|
267
|
+
if (conversationId === null) {
|
|
268
|
+
// A concurrent writer (a live hook) bound this session while the import
|
|
269
|
+
// waited for the write lock -- their binding wins.
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
const firstUser = messages.find((message) => message.role === "user");
|
|
273
|
+
if (firstUser) {
|
|
274
|
+
// Python: `content.strip().split("\n")[0][:60]` -- cut BEFORE the
|
|
275
|
+
// provisional-title whitespace collapse, by code point.
|
|
276
|
+
const title = sliceCodePoints(firstUser.content.trim().split("\n")[0] ?? "", 60);
|
|
277
|
+
setProvisionalTitle(db, conversationId, title);
|
|
278
|
+
}
|
|
279
|
+
count += 1;
|
|
280
|
+
}
|
|
281
|
+
return count;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
interface CodexParse {
|
|
285
|
+
sessionId: string | null;
|
|
286
|
+
messages: { role: string; content: unknown; createdAt: unknown }[];
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Parse a Codex session file. Throws on the first line that is not valid
|
|
291
|
+
* JSON or not the expected shape; Python's single `try` turns that into
|
|
292
|
+
* "return 0". Values are collected raw: Python validates them only when the
|
|
293
|
+
* import constructs its models.
|
|
294
|
+
*/
|
|
295
|
+
function parseCodexSessionFile(path: string): CodexParse {
|
|
296
|
+
const parsed: CodexParse = { sessionId: null, messages: [] };
|
|
297
|
+
for (const line of readFileSync(path, "utf8").split(/\r?\n|\r/)) {
|
|
298
|
+
if (!line.trim()) continue;
|
|
299
|
+
const entry: unknown = JSON.parse(line);
|
|
300
|
+
const entryType = getFrom(entry, "type");
|
|
301
|
+
const payload = getOr(entry, "payload", {});
|
|
302
|
+
if (entryType === "session_meta") {
|
|
303
|
+
// Codex writes a UUID string; a non-string id is treated as absent
|
|
304
|
+
// (the same simplification the hook payloads make).
|
|
305
|
+
const id = getFrom(payload, "id");
|
|
306
|
+
parsed.sessionId = typeof id === "string" ? id : null;
|
|
307
|
+
} else if (entryType === "event_msg") {
|
|
308
|
+
const payloadType = getFrom(payload, "type");
|
|
309
|
+
if (payloadType === "user_message" || payloadType === "agent_message") {
|
|
310
|
+
const content = getOr(payload, "message", "");
|
|
311
|
+
// Python: `if content:` -- any truthy value is kept and validated later.
|
|
312
|
+
if (pythonTruthy(content)) {
|
|
313
|
+
parsed.messages.push({
|
|
314
|
+
role: payloadType === "user_message" ? "user" : "assistant",
|
|
315
|
+
content,
|
|
316
|
+
createdAt: getFrom(entry, "timestamp"),
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return parsed;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export interface CodexBackfillOptions {
|
|
326
|
+
jsonlPath: string;
|
|
327
|
+
interface?: string;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Python `backfill_codex_session`: import one Codex session file as a closed
|
|
332
|
+
* conversation titled through `_generate_title`. Returns 1 on import, else 0.
|
|
333
|
+
*
|
|
334
|
+
* Throws when a kept event has a non-string `message` or `timestamp`: Python's
|
|
335
|
+
* `Message(...)` model rejects those inside the import transaction, so the
|
|
336
|
+
* database is left untouched and the caller sees the failure.
|
|
337
|
+
*/
|
|
338
|
+
export function backfillCodexSession(
|
|
339
|
+
db: WritableDatabase,
|
|
340
|
+
options: CodexBackfillOptions,
|
|
341
|
+
deps: LoggerDeps,
|
|
342
|
+
): number {
|
|
343
|
+
const jsonlPath = expandHome(options.jsonlPath);
|
|
344
|
+
if (!existsSync(jsonlPath) || !statSync(jsonlPath).isFile()) return 0;
|
|
345
|
+
|
|
346
|
+
let parsed: CodexParse;
|
|
347
|
+
try {
|
|
348
|
+
parsed = parseCodexSessionFile(jsonlPath);
|
|
349
|
+
} catch {
|
|
350
|
+
return 0;
|
|
351
|
+
}
|
|
352
|
+
if (!parsed.sessionId || parsed.messages.length === 0) return 0;
|
|
353
|
+
if (db.prepare("SELECT 1 FROM runtime_sessions WHERE session_id = ?").get(parsed.sessionId)) {
|
|
354
|
+
return 0;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const messages: ImportedMessage[] = parsed.messages.map((message) => {
|
|
358
|
+
if (typeof message.content !== "string") {
|
|
359
|
+
throw new TypeError("Codex event message must be a string");
|
|
360
|
+
}
|
|
361
|
+
if (typeof message.createdAt !== "string") {
|
|
362
|
+
throw new TypeError("Codex event timestamp must be a string");
|
|
363
|
+
}
|
|
364
|
+
return { role: message.role, content: message.content, createdAt: message.createdAt };
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
const conversationId = importClosedConversation(
|
|
368
|
+
db,
|
|
369
|
+
parsed.sessionId,
|
|
370
|
+
{
|
|
371
|
+
interface: options.interface ?? "codex",
|
|
372
|
+
messages,
|
|
373
|
+
endedAt: (messages.at(-1) as ImportedMessage).createdAt,
|
|
374
|
+
},
|
|
375
|
+
deps,
|
|
376
|
+
);
|
|
377
|
+
if (conversationId === null) return 0;
|
|
378
|
+
|
|
379
|
+
const firstUser = messages.find((message) => message.role === "user");
|
|
380
|
+
if (firstUser) {
|
|
381
|
+
db.prepare("UPDATE conversations SET title = ? WHERE id = ?").run(
|
|
382
|
+
generateTitle(firstUser.content),
|
|
383
|
+
conversationId,
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
return 1;
|
|
387
|
+
}
|