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,646 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mirror Logger Extension
|
|
3
|
+
*
|
|
4
|
+
* Integrates Pi with the Mirror Mind memory system.
|
|
5
|
+
*
|
|
6
|
+
* Events handled:
|
|
7
|
+
* - session_start → unmute + welcome card + status line, in ONE front-door
|
|
8
|
+
* process (CV9.E2.S32); maintenance detached
|
|
9
|
+
* - before_agent_start → log user prompt, NOT awaited: queued on a per-session
|
|
10
|
+
* chain so the turn starts at once (CV9.E2.S32)
|
|
11
|
+
* - agent_end → log assistant response, after the chain settles
|
|
12
|
+
* - session_shutdown → close conversation (after the chain settles) + backup
|
|
13
|
+
*
|
|
14
|
+
* All heavy logic lives in the TypeScript core, reached through the `mirror`
|
|
15
|
+
* bin that ships beside this file (`bin/mirror.js`). This extension is a thin
|
|
16
|
+
* dispatcher. Failures are swallowed to never block Pi — but logged to
|
|
17
|
+
* mirror-logger.log in the resolved mirror home (homes root only as bootstrap
|
|
18
|
+
* fallback when no mirror home is resolvable).
|
|
19
|
+
*
|
|
20
|
+
* Since CV22.DS10.US3 this extension assumes nothing about the cwd: the core it
|
|
21
|
+
* runs is the one it ships with, located from this file (D6); configuration
|
|
22
|
+
* is the core's own (D3); and for a session Pi did not open inside the tree,
|
|
23
|
+
* it supplies the Operating Instructions (`AGENTS.md`) itself (D13), and says
|
|
24
|
+
* so once when `mirror` is not on the PATH.
|
|
25
|
+
*
|
|
26
|
+
* External skill prep note:
|
|
27
|
+
* Pi reads installed external skills from
|
|
28
|
+
* ~/.mirror-minds/<user>/runtime/skills/pi/extensions.json
|
|
29
|
+
* rather than from source manifests under ~/.mirror-minds/<user>/extensions/.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
33
|
+
import { accessSync, appendFileSync, closeSync, constants, existsSync, mkdirSync, openSync, readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
34
|
+
import { homedir } from "node:os";
|
|
35
|
+
import { delimiter, dirname, join } from "node:path";
|
|
36
|
+
import { fileURLToPath } from "node:url";
|
|
37
|
+
import { spawn } from "node:child_process";
|
|
38
|
+
|
|
39
|
+
/** The tree this extension ships in: `.pi/extensions/` sits two below the root. */
|
|
40
|
+
const TREE_ROOT = fileURLToPath(new URL("../../", import.meta.url)).replace(/\/$/, "");
|
|
41
|
+
/** The core this extension runs: the bin beside it, never one found by the cwd (D6). */
|
|
42
|
+
const MIRROR_BIN = join(TREE_ROOT, "bin", "mirror.js");
|
|
43
|
+
/** The Operating Instructions that ship with the core (D13). */
|
|
44
|
+
const AGENTS_FILE = join(TREE_ROOT, "AGENTS.md");
|
|
45
|
+
|
|
46
|
+
// Mirror home directory names. Mirrors the Python core contract in
|
|
47
|
+
// src/memory/config.py (_DEFAULT_USER_HOMES_DIR_NAME / _LEGACY_USER_HOMES_DIR_NAME).
|
|
48
|
+
// The core migrated the default homes root from ~/.mirror to ~/.mirror-minds; the
|
|
49
|
+
// legacy layout stays supported indefinitely. This extension is TypeScript and cannot
|
|
50
|
+
// import the Python constants, so it duplicates the contract — keep the names here.
|
|
51
|
+
const NEW_HOMES_DIR = ".mirror-minds";
|
|
52
|
+
const LEGACY_HOMES_DIR = ".mirror";
|
|
53
|
+
|
|
54
|
+
// Respect MEMORY_DIR so Pi session files land in the same directory Python reads.
|
|
55
|
+
// Fallback to ~/.mirror-minds if unset (matches config.DEFAULT_MEMORY_DIR).
|
|
56
|
+
function _resolveMemoryDir(): string {
|
|
57
|
+
// CV9.E2.S6 — log containment. Precedence mirrors the Python core:
|
|
58
|
+
// explicit MEMORY_DIR > resolved mirror home (shell env > .env, with the
|
|
59
|
+
// legacy ~/.mirror/<user> location honored when only it exists) > homes
|
|
60
|
+
// root. The homes-root fallback is deliberate: it is the bootstrap error
|
|
61
|
+
// channel that records "could not resolve the mirror home" itself.
|
|
62
|
+
const raw = process.env.MEMORY_DIR;
|
|
63
|
+
if (raw) return raw.startsWith("~") ? join(homedir(), raw.slice(2)) : raw;
|
|
64
|
+
const { home, user } = _effectiveMirrorEnv();
|
|
65
|
+
if (home) return home.startsWith("~") ? join(homedir(), home.slice(2)) : home;
|
|
66
|
+
if (user) {
|
|
67
|
+
const newHome = join(homedir(), NEW_HOMES_DIR, user);
|
|
68
|
+
const legacyHome = join(homedir(), LEGACY_HOMES_DIR, user);
|
|
69
|
+
if (!_isDir(newHome) && _isDir(legacyHome)) return legacyHome;
|
|
70
|
+
return newHome;
|
|
71
|
+
}
|
|
72
|
+
return join(homedir(), NEW_HOMES_DIR);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** True when the path is an existing directory. */
|
|
76
|
+
function _isDir(path: string): boolean {
|
|
77
|
+
try {
|
|
78
|
+
return statSync(path).isDirectory();
|
|
79
|
+
} catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** True when a user home has an installed Pi external-skill catalog. */
|
|
85
|
+
function _hasPiCatalog(userHome: string): boolean {
|
|
86
|
+
try {
|
|
87
|
+
return existsSync(join(userHome, "runtime", "skills", "pi", "extensions.json"));
|
|
88
|
+
} catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** User homes under a homes root that carry an installed Pi external-skill catalog. */
|
|
94
|
+
function _piCatalogHomes(homesDirName: string): string[] {
|
|
95
|
+
const root = join(homedir(), homesDirName);
|
|
96
|
+
try {
|
|
97
|
+
return readdirSync(root)
|
|
98
|
+
.map((name) => join(root, name))
|
|
99
|
+
.filter((path) => _isDir(path) && _hasPiCatalog(path));
|
|
100
|
+
} catch {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// One KEY=VALUE file, as the core reads it (no quote stripping, comments
|
|
106
|
+
// skipped). Pi (Node) never loads .env on its own, so without this the
|
|
107
|
+
// extension cannot see the MIRROR_USER the core will resolve.
|
|
108
|
+
function _readEnvFile(path: string): Record<string, string> {
|
|
109
|
+
try {
|
|
110
|
+
if (!statSync(path).isFile()) return {};
|
|
111
|
+
const out: Record<string, string> = {};
|
|
112
|
+
for (const rawLine of readFileSync(path, "utf-8").split(/\r?\n/)) {
|
|
113
|
+
const line = rawLine.trim();
|
|
114
|
+
if (!line || line.startsWith("#") || !line.includes("=")) continue;
|
|
115
|
+
const eq = line.indexOf("=");
|
|
116
|
+
const key = line.slice(0, eq).trim();
|
|
117
|
+
if (key) out[key] = line.slice(eq + 1).trim();
|
|
118
|
+
}
|
|
119
|
+
return out;
|
|
120
|
+
} catch {
|
|
121
|
+
return {};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** The user's config file, where the core reads it: `${XDG_CONFIG_HOME:-~/.config}/mirror/env`. */
|
|
126
|
+
function _userConfigFile(): string {
|
|
127
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
128
|
+
return join(xdg && xdg.length > 0 ? xdg : join(homedir(), ".config"), "mirror", "env");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Effective Mirror env, in the core's own order (CV22.DS10.US3 D3): the real
|
|
132
|
+
// shell env wins; then the TREE's `.env` -- a clone's, located from this file,
|
|
133
|
+
// never the cwd's; then the user's config file. The core resolves the database
|
|
134
|
+
// the same way, so the two cannot disagree about which mirror a session logs to.
|
|
135
|
+
function _effectiveMirrorEnv(): { home?: string; user?: string } {
|
|
136
|
+
const fromFiles = { ..._readEnvFile(_userConfigFile()), ..._readEnvFile(join(TREE_ROOT, ".env")) };
|
|
137
|
+
const rawHome = "MIRROR_HOME" in process.env ? process.env.MIRROR_HOME : fromFiles.MIRROR_HOME;
|
|
138
|
+
const rawUser = "MIRROR_USER" in process.env ? process.env.MIRROR_USER : fromFiles.MIRROR_USER;
|
|
139
|
+
return { home: rawHome?.trim() || undefined, user: rawUser?.trim() || undefined };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Where `mirror` resolves on this PATH, or null. The skills say `mirror`
|
|
144
|
+
* (CV22.DS10.US3 D7), so an agent in a session where it does not resolve
|
|
145
|
+
* meets `command not found` and improvises -- which is why session start says
|
|
146
|
+
* so once, naming the fix, instead of leaving the agent to guess.
|
|
147
|
+
*/
|
|
148
|
+
function _mirrorOnPath(): string | null {
|
|
149
|
+
const dirs = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
|
|
150
|
+
for (const dir of dirs) {
|
|
151
|
+
const candidate = join(dir, process.platform === "win32" ? "mirror.cmd" : "mirror");
|
|
152
|
+
try {
|
|
153
|
+
accessSync(candidate, constants.X_OK);
|
|
154
|
+
return candidate;
|
|
155
|
+
} catch {
|
|
156
|
+
// keep looking
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** True when `path` is the same file as this tree's AGENTS.md (symlinks resolved). */
|
|
163
|
+
function _isOurAgentsFile(path: string): boolean {
|
|
164
|
+
try {
|
|
165
|
+
return realpathSync(path) === realpathSync(AGENTS_FILE);
|
|
166
|
+
} catch {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const MIRROR_DIR = _resolveMemoryDir();
|
|
172
|
+
const LOG_FILE = join(MIRROR_DIR, "mirror-logger.log");
|
|
173
|
+
|
|
174
|
+
// Content size limit for CLI arguments (~50KB, safe for macOS ARG_MAX)
|
|
175
|
+
const MAX_CONTENT_SIZE = 50_000;
|
|
176
|
+
|
|
177
|
+
type RuntimeCatalogEntry = {
|
|
178
|
+
id?: string;
|
|
179
|
+
command_name?: string;
|
|
180
|
+
installed_skill_path?: string;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
type RuntimeCatalog = {
|
|
184
|
+
schema_version?: string;
|
|
185
|
+
runtime?: string;
|
|
186
|
+
target_root?: string;
|
|
187
|
+
generated_at?: string;
|
|
188
|
+
extensions?: RuntimeCatalogEntry[];
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
type MirrorStatusContext = {
|
|
192
|
+
hasUI: boolean;
|
|
193
|
+
sessionManager: {
|
|
194
|
+
getSessionFile(): string | undefined;
|
|
195
|
+
};
|
|
196
|
+
ui: {
|
|
197
|
+
setStatus(key: string, value: string | undefined): void;
|
|
198
|
+
};
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* One registration per Pi process. Pi can reach this file twice -- as the
|
|
203
|
+
* project's `.pi/extensions/` inside the checkout and as a personal package
|
|
204
|
+
* installed from `npm root -g`, which `npm link` makes the same file under
|
|
205
|
+
* two paths -- and two registrations would log every turn twice. The guard is
|
|
206
|
+
* process-scoped, not module-scoped, because two paths are two module
|
|
207
|
+
* instances (CV22.DS10.US3 plateau 2, the quality-assurance finding).
|
|
208
|
+
*/
|
|
209
|
+
const LOADED_KEY = "__mirrorLoggerRegistered";
|
|
210
|
+
|
|
211
|
+
export default function (pi: ExtensionAPI) {
|
|
212
|
+
const globals = globalThis as Record<string, unknown>;
|
|
213
|
+
if (globals[LOADED_KEY]) {
|
|
214
|
+
try {
|
|
215
|
+
appendFileSync(LOG_FILE, `${new Date().toISOString()} [INFO] second registration skipped: ${import.meta.url}\n`);
|
|
216
|
+
} catch {
|
|
217
|
+
// never block Pi
|
|
218
|
+
}
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
globals[LOADED_KEY] = import.meta.url;
|
|
222
|
+
|
|
223
|
+
// --- Helpers ---
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Every Mirror command this extension runs enters the TypeScript front
|
|
227
|
+
* door through the `mirror` bin beside this file (`bin/mirror.js`), the
|
|
228
|
+
* same entry the skills reach through the PATH, so `routing.ts` decides
|
|
229
|
+
* the route and `front-door.log` records it. The bin, not `cli.ts`
|
|
230
|
+
* directly: under `npm root -g` Node will not strip types (D15), and the
|
|
231
|
+
* bin is the one entry that runs in a checkout and an install alike.
|
|
232
|
+
* It reads configuration and silences warnings itself (D3), so no flag
|
|
233
|
+
* and no `.env` path travels here. Nothing depends on the cwd.
|
|
234
|
+
*/
|
|
235
|
+
const FRONT_DOOR_ARGV = [MIRROR_BIN];
|
|
236
|
+
|
|
237
|
+
function log(level: string, msg: string): void {
|
|
238
|
+
try {
|
|
239
|
+
const ts = new Date().toISOString();
|
|
240
|
+
mkdirSync(MIRROR_DIR, { recursive: true });
|
|
241
|
+
appendFileSync(LOG_FILE, `${ts} [${level}] ${msg}\n`);
|
|
242
|
+
} catch {
|
|
243
|
+
// Logging failure must never break anything
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function runMirrorBackground(args: string[], label: string): void {
|
|
248
|
+
let logFd: number | undefined;
|
|
249
|
+
try {
|
|
250
|
+
mkdirSync(MIRROR_DIR, { recursive: true });
|
|
251
|
+
logFd = openSync(LOG_FILE, "a");
|
|
252
|
+
const child = spawn("node", [...FRONT_DOOR_ARGV, ...args], {
|
|
253
|
+
cwd: process.cwd(),
|
|
254
|
+
stdio: ["ignore", logFd, logFd],
|
|
255
|
+
// Windows: a detached console child materializes as a visible
|
|
256
|
+
// console (or a Windows Terminal tab, when WT is the default
|
|
257
|
+
// terminal, which does not honor CREATE_NO_WINDOW for detached
|
|
258
|
+
// sessions). Sharing Pi's console makes it truly invisible.
|
|
259
|
+
// POSIX keeps detached so the write survives Pi exiting.
|
|
260
|
+
detached: process.platform !== "win32",
|
|
261
|
+
windowsHide: true,
|
|
262
|
+
});
|
|
263
|
+
child.unref();
|
|
264
|
+
log("INFO", `${label} started in detached background process ${child.pid ?? "(unknown pid)"}`);
|
|
265
|
+
} catch (err: unknown) {
|
|
266
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
267
|
+
log("ERROR", `${label} failed: ${message.slice(0, 500)}`);
|
|
268
|
+
} finally {
|
|
269
|
+
if (logFd !== undefined) {
|
|
270
|
+
try {
|
|
271
|
+
closeSync(logFd);
|
|
272
|
+
} catch {
|
|
273
|
+
// Ignore close failure.
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* CV9.E2.S32 test knob: a delay before each awaited front-door exec, so the
|
|
281
|
+
* quit-mid-write and ordering cases can be driven deterministically.
|
|
282
|
+
* Honored ONLY under `MEMORY_ENV=test`; a production session never sleeps.
|
|
283
|
+
*/
|
|
284
|
+
function testFrontDoorDelayMs(): number {
|
|
285
|
+
if (process.env.MEMORY_ENV !== "test") return 0;
|
|
286
|
+
const raw = Number.parseInt(process.env.MIRROR_TEST_FRONT_DOOR_DELAY_MS ?? "", 10);
|
|
287
|
+
return Number.isFinite(raw) && raw > 0 ? raw : 0;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async function runMirror(args: string[]): Promise<string> {
|
|
291
|
+
try {
|
|
292
|
+
const delay = testFrontDoorDelayMs();
|
|
293
|
+
if (delay > 0) await new Promise((resolve) => setTimeout(resolve, delay));
|
|
294
|
+
const result = await pi.exec("node", [...FRONT_DOOR_ARGV, ...args], {
|
|
295
|
+
timeout: 30_000,
|
|
296
|
+
});
|
|
297
|
+
const stderr = (result?.stderr ?? "").trim();
|
|
298
|
+
if (stderr) {
|
|
299
|
+
log("WARN", `stderr from [${args.slice(0, 2).join(" ")}]: ${stderr.slice(0, 500)}`);
|
|
300
|
+
}
|
|
301
|
+
return (result?.stdout ?? "").trim();
|
|
302
|
+
} catch (err: unknown) {
|
|
303
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
304
|
+
log("ERROR", `runMirror failed [${args.slice(0, 2).join(" ")}]: ${message.slice(0, 500)}`);
|
|
305
|
+
return "";
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* The `session-start --fast --welcome --format json` envelope (CV9.E2.S32).
|
|
311
|
+
* One process answers what used to take three: the unmute, the welcome
|
|
312
|
+
* card, and the status line. A parse failure is a WARN and the same empty
|
|
313
|
+
* result the three calls used to yield when one of them failed.
|
|
314
|
+
*/
|
|
315
|
+
interface SessionOpenEnvelope {
|
|
316
|
+
sessionStart: string;
|
|
317
|
+
welcome: string;
|
|
318
|
+
statusLine: string;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function parseSessionOpenEnvelope(stdout: string): SessionOpenEnvelope | null {
|
|
322
|
+
if (!stdout.trim()) return null;
|
|
323
|
+
try {
|
|
324
|
+
const parsed: unknown = JSON.parse(stdout);
|
|
325
|
+
if (!parsed || typeof parsed !== "object") throw new Error("not an object");
|
|
326
|
+
const record = parsed as Record<string, unknown>;
|
|
327
|
+
const field = (name: string): string => (typeof record[name] === "string" ? (record[name] as string) : "");
|
|
328
|
+
return { sessionStart: field("sessionStart"), welcome: field("welcome"), statusLine: field("statusLine") };
|
|
329
|
+
} catch (err: unknown) {
|
|
330
|
+
log("WARN", `session-open envelope unreadable: ${err instanceof Error ? err.message : String(err)}`);
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* CV9.E2.S32: the per-session chain of pending `log-user` writes.
|
|
337
|
+
*
|
|
338
|
+
* The invariant protected: the `log-user` process has EXITED before the
|
|
339
|
+
* `log-assistant` process for the same turn is SPAWNED, and before
|
|
340
|
+
* `session-end-pi` runs. Message reads are `ORDER BY created_at, id` and
|
|
341
|
+
* `created_at` is stamped by the writing process at insert time, so order
|
|
342
|
+
* in the database is wall-clock order of the inserts; the chain is what
|
|
343
|
+
* makes that order the prompt order. Consecutive prompts serialize on the
|
|
344
|
+
* same chain. A link that fails (or times out at `pi.exec`'s 30 s) settles
|
|
345
|
+
* the chain all the same: `runMirror` swallows, and the `.then(\u2026, \u2026)` is
|
|
346
|
+
* belt and braces so a rejection can never wedge it.
|
|
347
|
+
*/
|
|
348
|
+
const pendingUserLog = new Map<string, Promise<void>>();
|
|
349
|
+
|
|
350
|
+
function enqueueUserLog(sessionId: string, args: string[]): void {
|
|
351
|
+
const prior = pendingUserLog.get(sessionId) ?? Promise.resolve();
|
|
352
|
+
const next = prior.then(() => runMirror(args)).then(
|
|
353
|
+
() => undefined,
|
|
354
|
+
() => undefined,
|
|
355
|
+
);
|
|
356
|
+
pendingUserLog.set(sessionId, next);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async function settleUserLog(sessionId: string): Promise<void> {
|
|
360
|
+
await (pendingUserLog.get(sessionId) ?? Promise.resolve());
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/** Extract readable text from a content blocks array or plain string. */
|
|
364
|
+
function extractText(content: unknown): string {
|
|
365
|
+
if (typeof content === "string") return content;
|
|
366
|
+
if (!Array.isArray(content)) return "";
|
|
367
|
+
return content
|
|
368
|
+
.filter((b: Record<string, unknown>) => b && b.type === "text" && typeof b.text === "string")
|
|
369
|
+
.map((b: Record<string, unknown>) => b.text as string)
|
|
370
|
+
.join("\n");
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/** Truncate content to fit in CLI arguments. */
|
|
374
|
+
function truncate(text: string): string {
|
|
375
|
+
if (text.length <= MAX_CONTENT_SIZE) return text;
|
|
376
|
+
return text.slice(0, MAX_CONTENT_SIZE) + "\n[… truncated]";
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/** The status bar line: the front door's compact status, or `?`, plus the external-skill count. */
|
|
380
|
+
function setMirrorStatus(ctx: MirrorStatusContext, compactStatus: string): void {
|
|
381
|
+
if (!ctx.hasUI) return;
|
|
382
|
+
const externalCatalog = loadInstalledPiExternalSkills();
|
|
383
|
+
const externalSkills = externalCatalog?.extensions ?? [];
|
|
384
|
+
const status = compactStatus || "◇ Mirror · ?";
|
|
385
|
+
ctx.ui.setStatus(
|
|
386
|
+
"mirror",
|
|
387
|
+
externalSkills.length > 0 ? `${status} · ext ${externalSkills.length}` : status,
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
async function refreshMirrorStatus(ctx: MirrorStatusContext): Promise<void> {
|
|
392
|
+
if (!ctx.hasUI) return;
|
|
393
|
+
const sessionId = ctx.sessionManager.getSessionFile() ?? null;
|
|
394
|
+
const statusArgs = ["welcome", "--status-line"];
|
|
395
|
+
if (sessionId) {
|
|
396
|
+
statusArgs.push("--session-id", sessionId);
|
|
397
|
+
}
|
|
398
|
+
setMirrorStatus(ctx, (await runMirror(statusArgs)).trim());
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Resolve the active Mirror home the same way the Python core does
|
|
402
|
+
// (src/memory/config.py resolve_mirror_home), including the per-workspace
|
|
403
|
+
// .env that Node would otherwise never see.
|
|
404
|
+
function resolveMirrorHome(): string | null {
|
|
405
|
+
const { home: explicitHome, user: mirrorUser } = _effectiveMirrorEnv();
|
|
406
|
+
|
|
407
|
+
if (explicitHome) {
|
|
408
|
+
return explicitHome.startsWith("~") ? join(homedir(), explicitHome.slice(2)) : explicitHome;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Prefer the new ~/.mirror-minds/<user> location, and fall back to the
|
|
412
|
+
// legacy ~/.mirror/<user> only when the new home does not exist.
|
|
413
|
+
if (mirrorUser) {
|
|
414
|
+
const newUserHome = join(homedir(), NEW_HOMES_DIR, mirrorUser);
|
|
415
|
+
const legacyUserHome = join(homedir(), LEGACY_HOMES_DIR, mirrorUser);
|
|
416
|
+
if (!_isDir(newUserHome) && _isDir(legacyUserHome)) {
|
|
417
|
+
return legacyUserHome;
|
|
418
|
+
}
|
|
419
|
+
return newUserHome;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// No explicit home/user from shell or .env: infer the active Mirror home
|
|
423
|
+
// from a single installed catalog. Prefer the new homes root; only fall
|
|
424
|
+
// back to legacy when the new root has none. Ambiguity (more than one
|
|
425
|
+
// candidate under a root) requires MIRROR_USER/MIRROR_HOME — never guess.
|
|
426
|
+
for (const homesDir of [NEW_HOMES_DIR, LEGACY_HOMES_DIR]) {
|
|
427
|
+
const candidates = _piCatalogHomes(homesDir);
|
|
428
|
+
if (candidates.length === 1) return candidates[0];
|
|
429
|
+
if (candidates.length > 1) {
|
|
430
|
+
log(
|
|
431
|
+
"WARN",
|
|
432
|
+
`multiple Mirror homes with Pi external skill catalogs under ${homesDir}; set MIRROR_USER or MIRROR_HOME`,
|
|
433
|
+
);
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function loadInstalledPiExternalSkills(): RuntimeCatalog | null {
|
|
441
|
+
try {
|
|
442
|
+
const mirrorHome = resolveMirrorHome();
|
|
443
|
+
if (!mirrorHome) return null;
|
|
444
|
+
const catalogPath = join(mirrorHome, "runtime", "skills", "pi", "extensions.json");
|
|
445
|
+
if (!existsSync(catalogPath)) return null;
|
|
446
|
+
|
|
447
|
+
const raw = readFileSync(catalogPath, "utf-8");
|
|
448
|
+
const data = JSON.parse(raw) as RuntimeCatalog;
|
|
449
|
+
if (data.schema_version !== "1") {
|
|
450
|
+
log("WARN", `unsupported Pi external skill catalog schema: ${String(data.schema_version ?? "(missing)")}`);
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
if (data.runtime !== "pi") {
|
|
454
|
+
log("WARN", `unexpected Pi external skill catalog runtime: ${String(data.runtime ?? "(missing)")}`);
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
if (!Array.isArray(data.extensions)) {
|
|
458
|
+
log("WARN", "invalid Pi external skill catalog: extensions must be an array");
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
return data;
|
|
462
|
+
} catch (err: unknown) {
|
|
463
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
464
|
+
log("WARN", `failed to load Pi external skill catalog: ${message.slice(0, 500)}`);
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function getInstalledPiSkillPaths(): string[] {
|
|
470
|
+
const catalog = loadInstalledPiExternalSkills();
|
|
471
|
+
const items = catalog?.extensions ?? [];
|
|
472
|
+
const skillPaths = items
|
|
473
|
+
.map((item) => item.installed_skill_path)
|
|
474
|
+
.filter((path): path is string => typeof path === "string" && path.length > 0)
|
|
475
|
+
.filter((path) => existsSync(path));
|
|
476
|
+
return [...new Set(skillPaths)];
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// --- dynamic resources → installed external Pi skills ---
|
|
480
|
+
|
|
481
|
+
pi.on("resources_discover", async () => {
|
|
482
|
+
const skillPaths = getInstalledPiSkillPaths();
|
|
483
|
+
if (skillPaths.length > 0) {
|
|
484
|
+
log("INFO", `resources_discover: loaded ${skillPaths.length} installed Pi external skill(s)`);
|
|
485
|
+
}
|
|
486
|
+
return { skillPaths };
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
// --- 0. the Operating Instructions, for a session Pi did not open in the tree ---
|
|
490
|
+
//
|
|
491
|
+
// Pi loads one context file per directory from the cwd upward. Inside the
|
|
492
|
+
// checkout that is this tree's AGENTS.md; from anywhere else -- an npm
|
|
493
|
+
// user's project, /tmp -- it is not, and a session that answers /mm-mirror
|
|
494
|
+
// without the modes, the persona signature, or the Builder boundary is not
|
|
495
|
+
// a Mirror session (CV22.DS10.US3 D13). So the extension appends the file
|
|
496
|
+
// it ships with, unless Pi already loaded that very file.
|
|
497
|
+
|
|
498
|
+
pi.on("before_agent_start", async (event) => {
|
|
499
|
+
const files = event.systemPromptOptions.contextFiles;
|
|
500
|
+
if (files.some((file) => _isOurAgentsFile(file.path))) return;
|
|
501
|
+
let content: string;
|
|
502
|
+
try {
|
|
503
|
+
content = readFileSync(AGENTS_FILE, "utf-8");
|
|
504
|
+
} catch (err: unknown) {
|
|
505
|
+
log("WARN", `AGENTS.md not readable at ${AGENTS_FILE}: ${err instanceof Error ? err.message : String(err)}`);
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
if (files.some((file) => file.content === content)) return;
|
|
509
|
+
files.push({ path: AGENTS_FILE, content });
|
|
510
|
+
log("INFO", `operating instructions appended from ${AGENTS_FILE}`);
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
// --- 1. session_start → unmute + welcome + status line in one process ---
|
|
514
|
+
//
|
|
515
|
+
// CV9.E2.S32. `session-start --fast` WRITES the orientation that `welcome`
|
|
516
|
+
// and `welcome --status-line` READ, so the three cannot run in parallel;
|
|
517
|
+
// the composite runs them in one process, write first. Maintenance stays
|
|
518
|
+
// detached. Fires on startup, reload, new, resume, and fork alike.
|
|
519
|
+
|
|
520
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
521
|
+
log("INFO", "session_start fired");
|
|
522
|
+
if (ctx.hasUI) {
|
|
523
|
+
ctx.ui.setStatus("mirror", "◇ Mirror · starting… maintenance will continue in background");
|
|
524
|
+
}
|
|
525
|
+
// The skills say `mirror`. Say once, visibly, when that will not work.
|
|
526
|
+
if (_mirrorOnPath() === null) {
|
|
527
|
+
const fix = existsSync(join(TREE_ROOT, ".git"))
|
|
528
|
+
? `run \`npm link\` once in ${TREE_ROOT}`
|
|
529
|
+
: "add `$(npm prefix -g)/bin` to the PATH";
|
|
530
|
+
const line = `Mirror: \`mirror\` is not on the PATH — ${fix}. Skills will fail until it is.`;
|
|
531
|
+
log("WARN", line);
|
|
532
|
+
if (ctx.hasUI) ctx.ui.notify(line, "warning");
|
|
533
|
+
}
|
|
534
|
+
const sessionId = ctx.sessionManager.getSessionFile() ?? null;
|
|
535
|
+
const openArgs = ["conversation-logger", "session-start", "--fast", "--welcome", "--format", "json"];
|
|
536
|
+
if (sessionId) openArgs.push("--session-id", sessionId);
|
|
537
|
+
const envelope = parseSessionOpenEnvelope(await runMirror(openArgs));
|
|
538
|
+
const summary = envelope?.sessionStart ?? "";
|
|
539
|
+
const externalCatalog = loadInstalledPiExternalSkills();
|
|
540
|
+
const externalSkills = externalCatalog?.extensions ?? [];
|
|
541
|
+
const externalSkillSummary = externalSkills.length
|
|
542
|
+
? `External skills: ${externalSkills.map((item) => item.command_name ?? item.id ?? "(unknown)").join(", ")}`
|
|
543
|
+
: "External skills: none";
|
|
544
|
+
log("INFO", `session-start result: ${summary || "(empty)"}`);
|
|
545
|
+
log("INFO", externalSkillSummary);
|
|
546
|
+
|
|
547
|
+
const welcome = (envelope?.welcome ?? "").trim();
|
|
548
|
+
if (welcome) {
|
|
549
|
+
log("INFO", `welcome: ${welcome.split("\n")[0]}`);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
if (ctx.hasUI) {
|
|
553
|
+
if (welcome) {
|
|
554
|
+
ctx.ui.notify(welcome, "info");
|
|
555
|
+
}
|
|
556
|
+
setMirrorStatus(ctx, (envelope?.statusLine ?? "").trim());
|
|
557
|
+
}
|
|
558
|
+
runMirrorBackground(["conversation-logger", "session-maintenance"], "session-maintenance");
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
// --- 2. before_agent_start → log user prompt with explicit session id ---
|
|
562
|
+
|
|
563
|
+
pi.on("before_agent_start", async (event, ctx) => {
|
|
564
|
+
const sessionId = ctx.sessionManager.getSessionFile() ?? null;
|
|
565
|
+
if (!sessionId) return;
|
|
566
|
+
|
|
567
|
+
const prompt = event.prompt ?? "";
|
|
568
|
+
if (!prompt || prompt.startsWith("/")) return;
|
|
569
|
+
|
|
570
|
+
log("INFO", `log-user: ${prompt.slice(0, 80)}...`);
|
|
571
|
+
// Not awaited (CV9.E2.S32): the turn starts now; `agent_end` and
|
|
572
|
+
// `session_shutdown` settle the chain before anything that must follow.
|
|
573
|
+
enqueueUserLog(sessionId, [
|
|
574
|
+
"conversation-logger",
|
|
575
|
+
"log-user",
|
|
576
|
+
sessionId,
|
|
577
|
+
truncate(prompt),
|
|
578
|
+
"--interface",
|
|
579
|
+
"pi",
|
|
580
|
+
]);
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
// --- 3. agent_end → log assistant response ---
|
|
584
|
+
//
|
|
585
|
+
// agent_end fires once per user prompt, with ALL messages in the cycle
|
|
586
|
+
// (assistant + tool calls + tool results). Extract only assistant text
|
|
587
|
+
// and log as a single consolidated message.
|
|
588
|
+
|
|
589
|
+
pi.on("agent_end", async (event, ctx) => {
|
|
590
|
+
const sessionId = ctx.sessionManager.getSessionFile() ?? null;
|
|
591
|
+
if (!sessionId) return;
|
|
592
|
+
|
|
593
|
+
const messages = (event as unknown as Record<string, unknown>).messages;
|
|
594
|
+
if (!Array.isArray(messages) || messages.length === 0) return;
|
|
595
|
+
|
|
596
|
+
const assistantTexts: string[] = [];
|
|
597
|
+
for (const msg of messages) {
|
|
598
|
+
if (
|
|
599
|
+
msg &&
|
|
600
|
+
typeof msg === "object" &&
|
|
601
|
+
"role" in msg &&
|
|
602
|
+
(msg as Record<string, unknown>).role === "assistant"
|
|
603
|
+
) {
|
|
604
|
+
const text = extractText((msg as Record<string, unknown>).content);
|
|
605
|
+
if (text.trim()) {
|
|
606
|
+
assistantTexts.push(text);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
if (assistantTexts.length === 0) return;
|
|
612
|
+
|
|
613
|
+
log("INFO", `log-assistant: ${assistantTexts.length} block(s), ${assistantTexts.join("").length} chars`);
|
|
614
|
+
|
|
615
|
+
const combined = assistantTexts.join("\n\n---\n\n");
|
|
616
|
+
const content = truncate(combined);
|
|
617
|
+
|
|
618
|
+
// The user message's process has exited before this one is spawned.
|
|
619
|
+
await settleUserLog(sessionId);
|
|
620
|
+
runMirrorBackground(
|
|
621
|
+
["conversation-logger", "log-assistant", sessionId, content, "--interface", "pi"],
|
|
622
|
+
"log-assistant",
|
|
623
|
+
);
|
|
624
|
+
await refreshMirrorStatus(ctx);
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
// --- 4. session_shutdown → close conversation + backup ---
|
|
628
|
+
//
|
|
629
|
+
// Uses extract=False because extraction calls the LLM and can take 30s+.
|
|
630
|
+
// Extraction happens at the next session_start via extract_pending.
|
|
631
|
+
|
|
632
|
+
pi.on("session_shutdown", async (_event, ctx) => {
|
|
633
|
+
const sessionId = ctx.sessionManager.getSessionFile() ?? null;
|
|
634
|
+
|
|
635
|
+
if (sessionId) {
|
|
636
|
+
// A prompt still in flight must land before the conversation closes.
|
|
637
|
+
await settleUserLog(sessionId);
|
|
638
|
+
pendingUserLog.delete(sessionId);
|
|
639
|
+
await runMirror(["conversation-logger", "session-end-pi", sessionId]);
|
|
640
|
+
log("INFO", `session closed: ${sessionId}`);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// The dated zip backup answers from TypeScript (CV22.DS7.TS1).
|
|
644
|
+
await runMirror(["backup", "--silent"]);
|
|
645
|
+
});
|
|
646
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "mm-backup"
|
|
3
|
+
description: Backs up the production memory database
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Backup
|
|
8
|
+
|
|
9
|
+
When receiving `/mm-backup`: answered by the TS core (CV22.DS7.TS1).
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
mirror backup
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The backup succeeded only when the command exits 0 and prints a
|
|
16
|
+
`Backup created:` line. Then tell the user: "Memory database backed up." and show
|
|
17
|
+
that line.
|
|
18
|
+
|
|
19
|
+
If the command exits non-zero, or prints a line starting with
|
|
20
|
+
`backup: no archive was written:`, the backup failed. Tell the user so, quote
|
|
21
|
+
that line, and never say the database was backed up.
|