mirror-mind 0.0.0-stage → 0.32.1
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-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 +561 -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/LICENSE +21 -0
- package/README.md +128 -2
- package/bin/loader.js +67 -0
- package/bin/mirror-hook.js +7 -0
- package/bin/mirror.js +7 -0
- package/docs/releases/index.md +99 -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.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 +110 -4
- package/plugins/mirror-mind/.claude-plugin/plugin.json +13 -0
- package/plugins/mirror-mind/hooks/hooks.json +50 -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/mcp/launch.sh +52 -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 +65 -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/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 +576 -0
- package/ts/src/conversation/loggerCli.ts +282 -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 +1865 -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 +73 -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 +999 -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/soulRoute.ts +433 -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 +252 -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 +176 -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 +68 -0
- package/ts/src/hooks/runtime.ts +159 -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/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,85 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Mirror Mind hook wrapper — GENERATED by scripts/generate_hook_wrappers.sh.
|
|
3
|
+
# Edit the generator, never this file: every wrapper shares these bytes and
|
|
4
|
+
# differs only in the hook name and in how it finds the Mirror entry
|
|
5
|
+
# (CV22.DS10.TS5 decision D4; CV22.DS10.US3 decision D5).
|
|
6
|
+
set -uo pipefail
|
|
7
|
+
HOOK="gemini:session-start"
|
|
8
|
+
|
|
9
|
+
# Resolve the tree from THIS FILE, never from the cwd: a runtime spawns hooks
|
|
10
|
+
# from whatever directory the session is in. The tree is a checkout or the
|
|
11
|
+
# installed package; the wrapper's depth to its root is the same in both.
|
|
12
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
13
|
+
REPO_ROOT="$(cd "$HERE/../.." && pwd -P)"
|
|
14
|
+
|
|
15
|
+
# A hook never fails the user's turn, and never fails in silence: when it
|
|
16
|
+
# cannot do its job it appends ONE line to <mirror home>/hooks.log and exits 0.
|
|
17
|
+
# The home is found the way the core finds it -- MIRROR_HOME, else
|
|
18
|
+
# MIRROR_USER -- from the environment when set there, else from the tree's
|
|
19
|
+
# .env when the wrapper is in a tree, else from the user's config file
|
|
20
|
+
# (~/.config/mirror/env), in the core's order (US3, D3). A GUI launch carries
|
|
21
|
+
# neither variable, and the first wrappers then wrote to a home nobody reads.
|
|
22
|
+
env_file_value() {
|
|
23
|
+
[ -f "$2" ] || return 0
|
|
24
|
+
sed -n -E "s/^[[:space:]]*(export[[:space:]]+)?$1[[:space:]]*=[[:space:]]*//p" "$2" \
|
|
25
|
+
| tail -n 1 | tr -d "\"'\r"
|
|
26
|
+
}
|
|
27
|
+
env_value() {
|
|
28
|
+
local value=""
|
|
29
|
+
if [ -n "${!1+set}" ]; then printf '%s' "${!1}"; return; fi
|
|
30
|
+
[ -z "$REPO_ROOT" ] || value="$(env_file_value "$1" "$REPO_ROOT/.env")"
|
|
31
|
+
[ -n "$value" ] || value="$(env_file_value "$1" "${XDG_CONFIG_HOME:-$HOME/.config}/mirror/env")"
|
|
32
|
+
printf '%s' "$value"
|
|
33
|
+
}
|
|
34
|
+
note() {
|
|
35
|
+
local home user
|
|
36
|
+
home="$(env_value MIRROR_HOME)"
|
|
37
|
+
user="$(env_value MIRROR_USER)"
|
|
38
|
+
if [ -n "$home" ]; then
|
|
39
|
+
home="${home/#\~/$HOME}"
|
|
40
|
+
elif [ -n "$user" ]; then
|
|
41
|
+
home="$HOME/.mirror-minds/$user"
|
|
42
|
+
[ -d "$home" ] || [ ! -d "$HOME/.mirror/$user" ] || home="$HOME/.mirror/$user"
|
|
43
|
+
else
|
|
44
|
+
return 0
|
|
45
|
+
fi
|
|
46
|
+
mkdir -p "$home" 2>/dev/null \
|
|
47
|
+
&& printf '%s %s: %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$HOOK" "$1" >> "$home/hooks.log" 2>/dev/null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
ENTRY="$REPO_ROOT/bin/mirror-hook.js"
|
|
51
|
+
|
|
52
|
+
# Find Node. /usr/bin/python3 exists on every macOS; node usually lives under
|
|
53
|
+
# nvm or Homebrew and is NOT on the PATH of a GUI-launched runtime. The Python
|
|
54
|
+
# hooks could end in `|| true` because they always found their interpreter;
|
|
55
|
+
# the same silence here would hide a new failure — no logging, no inject, no
|
|
56
|
+
# close tail — that a user would meet weeks later as "Mirror stopped
|
|
57
|
+
# remembering". So an unresolvable Node is RECORDED, never swallowed. The
|
|
58
|
+
# directory the entry was found in is searched first: an npm global bin holds
|
|
59
|
+
# the `node` that installed the package beside `mirror-hook`, while a system
|
|
60
|
+
# `/usr/local/bin/node` can be years older (CI's Ubuntu runner has one).
|
|
61
|
+
NODE="${MIRROR_NODE:-}"
|
|
62
|
+
if [ -z "$NODE" ] || [ ! -x "$NODE" ]; then
|
|
63
|
+
NODE="$(command -v node 2>/dev/null || true)"
|
|
64
|
+
fi
|
|
65
|
+
for candidate in "$(dirname "$ENTRY")/node" "$HOME/.nvm/current/bin/node" /opt/homebrew/bin/node /usr/local/bin/node; do
|
|
66
|
+
[ -n "$NODE" ] && break
|
|
67
|
+
[ -x "$candidate" ] && NODE="$candidate"
|
|
68
|
+
done
|
|
69
|
+
|
|
70
|
+
if [ -z "$NODE" ]; then
|
|
71
|
+
note "node not found on PATH; hook skipped. Set MIRROR_NODE."
|
|
72
|
+
exit 0
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
# Not `exec`: the entry exits 0 whatever happens inside it, so a non-zero exit
|
|
76
|
+
# means Node itself could not run the hook -- too old for the loader shim, or
|
|
77
|
+
# it crashed. That used to vanish into a stderr no runtime shows (TS5 handoff
|
|
78
|
+
# review, finding N1). No flags: the entry reads configuration and silences
|
|
79
|
+
# its own warnings (US3, D3).
|
|
80
|
+
"$NODE" "$ENTRY" "$HOOK" "$@"
|
|
81
|
+
status=$?
|
|
82
|
+
if [ "$status" -ne 0 ]; then
|
|
83
|
+
note "node exited $status before the hook finished ($NODE, $("$NODE" --version 2>/dev/null || echo 'version unknown')); Mirror needs Node 24 or later. Set MIRROR_NODE."
|
|
84
|
+
fi
|
|
85
|
+
exit 0
|
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mirror Logger Extension
|
|
3
|
+
*
|
|
4
|
+
* Integrates Pi with the Mirror Mind memory system.
|
|
5
|
+
*
|
|
6
|
+
* Events handled:
|
|
7
|
+
* - session_start → unmute + close stale orphans + extract pending memories
|
|
8
|
+
* - before_agent_start → log user prompt with explicit session id
|
|
9
|
+
* - agent_end → log assistant response (all messages in the turn)
|
|
10
|
+
* - session_shutdown → close conversation + backup database
|
|
11
|
+
*
|
|
12
|
+
* All heavy logic lives in the TypeScript core, reached through the `mirror`
|
|
13
|
+
* bin that ships beside this file (`bin/mirror.js`). This extension is a thin
|
|
14
|
+
* dispatcher. Failures are swallowed to never block Pi — but logged to
|
|
15
|
+
* mirror-logger.log in the resolved mirror home (homes root only as bootstrap
|
|
16
|
+
* fallback when no mirror home is resolvable).
|
|
17
|
+
*
|
|
18
|
+
* Since CV22.DS10.US3 this extension assumes nothing about the cwd: the core it
|
|
19
|
+
* runs is the one it ships with, located from this file (D6); configuration
|
|
20
|
+
* is the core's own (D3); and for a session Pi did not open inside the tree,
|
|
21
|
+
* it supplies the Operating Instructions (`AGENTS.md`) itself (D13), and says
|
|
22
|
+
* so once when `mirror` is not on the PATH.
|
|
23
|
+
*
|
|
24
|
+
* External skill prep note:
|
|
25
|
+
* Pi reads installed external skills from
|
|
26
|
+
* ~/.mirror-minds/<user>/runtime/skills/pi/extensions.json
|
|
27
|
+
* rather than from source manifests under ~/.mirror-minds/<user>/extensions/.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
31
|
+
import { accessSync, appendFileSync, closeSync, constants, existsSync, mkdirSync, openSync, readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
32
|
+
import { homedir } from "node:os";
|
|
33
|
+
import { delimiter, dirname, join } from "node:path";
|
|
34
|
+
import { fileURLToPath } from "node:url";
|
|
35
|
+
import { spawn } from "node:child_process";
|
|
36
|
+
|
|
37
|
+
/** The tree this extension ships in: `.pi/extensions/` sits two below the root. */
|
|
38
|
+
const TREE_ROOT = fileURLToPath(new URL("../../", import.meta.url)).replace(/\/$/, "");
|
|
39
|
+
/** The core this extension runs: the bin beside it, never one found by the cwd (D6). */
|
|
40
|
+
const MIRROR_BIN = join(TREE_ROOT, "bin", "mirror.js");
|
|
41
|
+
/** The Operating Instructions that ship with the core (D13). */
|
|
42
|
+
const AGENTS_FILE = join(TREE_ROOT, "AGENTS.md");
|
|
43
|
+
|
|
44
|
+
// Mirror home directory names. Mirrors the Python core contract in
|
|
45
|
+
// src/memory/config.py (_DEFAULT_USER_HOMES_DIR_NAME / _LEGACY_USER_HOMES_DIR_NAME).
|
|
46
|
+
// The core migrated the default homes root from ~/.mirror to ~/.mirror-minds; the
|
|
47
|
+
// legacy layout stays supported indefinitely. This extension is TypeScript and cannot
|
|
48
|
+
// import the Python constants, so it duplicates the contract — keep the names here.
|
|
49
|
+
const NEW_HOMES_DIR = ".mirror-minds";
|
|
50
|
+
const LEGACY_HOMES_DIR = ".mirror";
|
|
51
|
+
|
|
52
|
+
// Respect MEMORY_DIR so Pi session files land in the same directory Python reads.
|
|
53
|
+
// Fallback to ~/.mirror-minds if unset (matches config.DEFAULT_MEMORY_DIR).
|
|
54
|
+
function _resolveMemoryDir(): string {
|
|
55
|
+
// CV9.E2.S6 — log containment. Precedence mirrors the Python core:
|
|
56
|
+
// explicit MEMORY_DIR > resolved mirror home (shell env > .env, with the
|
|
57
|
+
// legacy ~/.mirror/<user> location honored when only it exists) > homes
|
|
58
|
+
// root. The homes-root fallback is deliberate: it is the bootstrap error
|
|
59
|
+
// channel that records "could not resolve the mirror home" itself.
|
|
60
|
+
const raw = process.env.MEMORY_DIR;
|
|
61
|
+
if (raw) return raw.startsWith("~") ? join(homedir(), raw.slice(2)) : raw;
|
|
62
|
+
const { home, user } = _effectiveMirrorEnv();
|
|
63
|
+
if (home) return home.startsWith("~") ? join(homedir(), home.slice(2)) : home;
|
|
64
|
+
if (user) {
|
|
65
|
+
const newHome = join(homedir(), NEW_HOMES_DIR, user);
|
|
66
|
+
const legacyHome = join(homedir(), LEGACY_HOMES_DIR, user);
|
|
67
|
+
if (!_isDir(newHome) && _isDir(legacyHome)) return legacyHome;
|
|
68
|
+
return newHome;
|
|
69
|
+
}
|
|
70
|
+
return join(homedir(), NEW_HOMES_DIR);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** True when the path is an existing directory. */
|
|
74
|
+
function _isDir(path: string): boolean {
|
|
75
|
+
try {
|
|
76
|
+
return statSync(path).isDirectory();
|
|
77
|
+
} catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** True when a user home has an installed Pi external-skill catalog. */
|
|
83
|
+
function _hasPiCatalog(userHome: string): boolean {
|
|
84
|
+
try {
|
|
85
|
+
return existsSync(join(userHome, "runtime", "skills", "pi", "extensions.json"));
|
|
86
|
+
} catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** User homes under a homes root that carry an installed Pi external-skill catalog. */
|
|
92
|
+
function _piCatalogHomes(homesDirName: string): string[] {
|
|
93
|
+
const root = join(homedir(), homesDirName);
|
|
94
|
+
try {
|
|
95
|
+
return readdirSync(root)
|
|
96
|
+
.map((name) => join(root, name))
|
|
97
|
+
.filter((path) => _isDir(path) && _hasPiCatalog(path));
|
|
98
|
+
} catch {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// One KEY=VALUE file, as the core reads it (no quote stripping, comments
|
|
104
|
+
// skipped). Pi (Node) never loads .env on its own, so without this the
|
|
105
|
+
// extension cannot see the MIRROR_USER the core will resolve.
|
|
106
|
+
function _readEnvFile(path: string): Record<string, string> {
|
|
107
|
+
try {
|
|
108
|
+
if (!statSync(path).isFile()) return {};
|
|
109
|
+
const out: Record<string, string> = {};
|
|
110
|
+
for (const rawLine of readFileSync(path, "utf-8").split(/\r?\n/)) {
|
|
111
|
+
const line = rawLine.trim();
|
|
112
|
+
if (!line || line.startsWith("#") || !line.includes("=")) continue;
|
|
113
|
+
const eq = line.indexOf("=");
|
|
114
|
+
const key = line.slice(0, eq).trim();
|
|
115
|
+
if (key) out[key] = line.slice(eq + 1).trim();
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
} catch {
|
|
119
|
+
return {};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** The user's config file, where the core reads it: `${XDG_CONFIG_HOME:-~/.config}/mirror/env`. */
|
|
124
|
+
function _userConfigFile(): string {
|
|
125
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
126
|
+
return join(xdg && xdg.length > 0 ? xdg : join(homedir(), ".config"), "mirror", "env");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Effective Mirror env, in the core's own order (CV22.DS10.US3 D3): the real
|
|
130
|
+
// shell env wins; then the TREE's `.env` -- a clone's, located from this file,
|
|
131
|
+
// never the cwd's; then the user's config file. The core resolves the database
|
|
132
|
+
// the same way, so the two cannot disagree about which mirror a session logs to.
|
|
133
|
+
function _effectiveMirrorEnv(): { home?: string; user?: string } {
|
|
134
|
+
const fromFiles = { ..._readEnvFile(_userConfigFile()), ..._readEnvFile(join(TREE_ROOT, ".env")) };
|
|
135
|
+
const rawHome = "MIRROR_HOME" in process.env ? process.env.MIRROR_HOME : fromFiles.MIRROR_HOME;
|
|
136
|
+
const rawUser = "MIRROR_USER" in process.env ? process.env.MIRROR_USER : fromFiles.MIRROR_USER;
|
|
137
|
+
return { home: rawHome?.trim() || undefined, user: rawUser?.trim() || undefined };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Where `mirror` resolves on this PATH, or null. The skills say `mirror`
|
|
142
|
+
* (CV22.DS10.US3 D7), so an agent in a session where it does not resolve
|
|
143
|
+
* meets `command not found` and improvises -- which is why session start says
|
|
144
|
+
* so once, naming the fix, instead of leaving the agent to guess.
|
|
145
|
+
*/
|
|
146
|
+
function _mirrorOnPath(): string | null {
|
|
147
|
+
const dirs = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
|
|
148
|
+
for (const dir of dirs) {
|
|
149
|
+
const candidate = join(dir, process.platform === "win32" ? "mirror.cmd" : "mirror");
|
|
150
|
+
try {
|
|
151
|
+
accessSync(candidate, constants.X_OK);
|
|
152
|
+
return candidate;
|
|
153
|
+
} catch {
|
|
154
|
+
// keep looking
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** True when `path` is the same file as this tree's AGENTS.md (symlinks resolved). */
|
|
161
|
+
function _isOurAgentsFile(path: string): boolean {
|
|
162
|
+
try {
|
|
163
|
+
return realpathSync(path) === realpathSync(AGENTS_FILE);
|
|
164
|
+
} catch {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const MIRROR_DIR = _resolveMemoryDir();
|
|
170
|
+
const LOG_FILE = join(MIRROR_DIR, "mirror-logger.log");
|
|
171
|
+
|
|
172
|
+
// Content size limit for CLI arguments (~50KB, safe for macOS ARG_MAX)
|
|
173
|
+
const MAX_CONTENT_SIZE = 50_000;
|
|
174
|
+
|
|
175
|
+
type RuntimeCatalogEntry = {
|
|
176
|
+
id?: string;
|
|
177
|
+
command_name?: string;
|
|
178
|
+
installed_skill_path?: string;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
type RuntimeCatalog = {
|
|
182
|
+
schema_version?: string;
|
|
183
|
+
runtime?: string;
|
|
184
|
+
target_root?: string;
|
|
185
|
+
generated_at?: string;
|
|
186
|
+
extensions?: RuntimeCatalogEntry[];
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
type MirrorStatusContext = {
|
|
190
|
+
hasUI: boolean;
|
|
191
|
+
sessionManager: {
|
|
192
|
+
getSessionFile(): string | undefined;
|
|
193
|
+
};
|
|
194
|
+
ui: {
|
|
195
|
+
setStatus(key: string, value: string | undefined): void;
|
|
196
|
+
};
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* One registration per Pi process. Pi can reach this file twice -- as the
|
|
201
|
+
* project's `.pi/extensions/` inside the checkout and as a personal package
|
|
202
|
+
* installed from `npm root -g`, which `npm link` makes the same file under
|
|
203
|
+
* two paths -- and two registrations would log every turn twice. The guard is
|
|
204
|
+
* process-scoped, not module-scoped, because two paths are two module
|
|
205
|
+
* instances (CV22.DS10.US3 plateau 2, the quality-assurance finding).
|
|
206
|
+
*/
|
|
207
|
+
const LOADED_KEY = "__mirrorLoggerRegistered";
|
|
208
|
+
|
|
209
|
+
export default function (pi: ExtensionAPI) {
|
|
210
|
+
const globals = globalThis as Record<string, unknown>;
|
|
211
|
+
if (globals[LOADED_KEY]) {
|
|
212
|
+
try {
|
|
213
|
+
appendFileSync(LOG_FILE, `${new Date().toISOString()} [INFO] second registration skipped: ${import.meta.url}\n`);
|
|
214
|
+
} catch {
|
|
215
|
+
// never block Pi
|
|
216
|
+
}
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
globals[LOADED_KEY] = import.meta.url;
|
|
220
|
+
|
|
221
|
+
// --- Helpers ---
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Every Mirror command this extension runs enters the TypeScript front
|
|
225
|
+
* door through the `mirror` bin beside this file (`bin/mirror.js`), the
|
|
226
|
+
* same entry the skills reach through the PATH, so `routing.ts` decides
|
|
227
|
+
* the route and `front-door.log` records it. The bin, not `cli.ts`
|
|
228
|
+
* directly: under `npm root -g` Node will not strip types (D15), and the
|
|
229
|
+
* bin is the one entry that runs in a checkout and an install alike.
|
|
230
|
+
* It reads configuration and silences warnings itself (D3), so no flag
|
|
231
|
+
* and no `.env` path travels here. Nothing depends on the cwd.
|
|
232
|
+
*/
|
|
233
|
+
const FRONT_DOOR_ARGV = [MIRROR_BIN];
|
|
234
|
+
|
|
235
|
+
function log(level: string, msg: string): void {
|
|
236
|
+
try {
|
|
237
|
+
const ts = new Date().toISOString();
|
|
238
|
+
mkdirSync(MIRROR_DIR, { recursive: true });
|
|
239
|
+
appendFileSync(LOG_FILE, `${ts} [${level}] ${msg}\n`);
|
|
240
|
+
} catch {
|
|
241
|
+
// Logging failure must never break anything
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function runMirrorBackground(args: string[], label: string): void {
|
|
246
|
+
let logFd: number | undefined;
|
|
247
|
+
try {
|
|
248
|
+
mkdirSync(MIRROR_DIR, { recursive: true });
|
|
249
|
+
logFd = openSync(LOG_FILE, "a");
|
|
250
|
+
const child = spawn("node", [...FRONT_DOOR_ARGV, ...args], {
|
|
251
|
+
cwd: process.cwd(),
|
|
252
|
+
stdio: ["ignore", logFd, logFd],
|
|
253
|
+
// Windows: a detached console child materializes as a visible
|
|
254
|
+
// console (or a Windows Terminal tab, when WT is the default
|
|
255
|
+
// terminal, which does not honor CREATE_NO_WINDOW for detached
|
|
256
|
+
// sessions). Sharing Pi's console makes it truly invisible.
|
|
257
|
+
// POSIX keeps detached so the write survives Pi exiting.
|
|
258
|
+
detached: process.platform !== "win32",
|
|
259
|
+
windowsHide: true,
|
|
260
|
+
});
|
|
261
|
+
child.unref();
|
|
262
|
+
log("INFO", `${label} started in detached background process ${child.pid ?? "(unknown pid)"}`);
|
|
263
|
+
} catch (err: unknown) {
|
|
264
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
265
|
+
log("ERROR", `${label} failed: ${message.slice(0, 500)}`);
|
|
266
|
+
} finally {
|
|
267
|
+
if (logFd !== undefined) {
|
|
268
|
+
try {
|
|
269
|
+
closeSync(logFd);
|
|
270
|
+
} catch {
|
|
271
|
+
// Ignore close failure.
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async function runMirror(args: string[]): Promise<string> {
|
|
278
|
+
try {
|
|
279
|
+
const result = await pi.exec("node", [...FRONT_DOOR_ARGV, ...args], {
|
|
280
|
+
timeout: 30_000,
|
|
281
|
+
});
|
|
282
|
+
const stderr = (result?.stderr ?? "").trim();
|
|
283
|
+
if (stderr) {
|
|
284
|
+
log("WARN", `stderr from [${args.slice(0, 2).join(" ")}]: ${stderr.slice(0, 500)}`);
|
|
285
|
+
}
|
|
286
|
+
return (result?.stdout ?? "").trim();
|
|
287
|
+
} catch (err: unknown) {
|
|
288
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
289
|
+
log("ERROR", `runMirror failed [${args.slice(0, 2).join(" ")}]: ${message.slice(0, 500)}`);
|
|
290
|
+
return "";
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** Extract readable text from a content blocks array or plain string. */
|
|
295
|
+
function extractText(content: unknown): string {
|
|
296
|
+
if (typeof content === "string") return content;
|
|
297
|
+
if (!Array.isArray(content)) return "";
|
|
298
|
+
return content
|
|
299
|
+
.filter((b: Record<string, unknown>) => b && b.type === "text" && typeof b.text === "string")
|
|
300
|
+
.map((b: Record<string, unknown>) => b.text as string)
|
|
301
|
+
.join("\n");
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** Truncate content to fit in CLI arguments. */
|
|
305
|
+
function truncate(text: string): string {
|
|
306
|
+
if (text.length <= MAX_CONTENT_SIZE) return text;
|
|
307
|
+
return text.slice(0, MAX_CONTENT_SIZE) + "\n[… truncated]";
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async function refreshMirrorStatus(ctx: MirrorStatusContext): Promise<void> {
|
|
311
|
+
if (!ctx.hasUI) return;
|
|
312
|
+
const sessionId = ctx.sessionManager.getSessionFile() ?? null;
|
|
313
|
+
const statusArgs = ["welcome", "--status-line"];
|
|
314
|
+
if (sessionId) {
|
|
315
|
+
statusArgs.push("--session-id", sessionId);
|
|
316
|
+
}
|
|
317
|
+
const compactStatus = (await runMirror(statusArgs)).trim();
|
|
318
|
+
const externalCatalog = loadInstalledPiExternalSkills();
|
|
319
|
+
const externalSkills = externalCatalog?.extensions ?? [];
|
|
320
|
+
const status = compactStatus || "◇ Mirror · ?";
|
|
321
|
+
ctx.ui.setStatus(
|
|
322
|
+
"mirror",
|
|
323
|
+
externalSkills.length > 0 ? `${status} · ext ${externalSkills.length}` : status,
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Resolve the active Mirror home the same way the Python core does
|
|
328
|
+
// (src/memory/config.py resolve_mirror_home), including the per-workspace
|
|
329
|
+
// .env that Node would otherwise never see.
|
|
330
|
+
function resolveMirrorHome(): string | null {
|
|
331
|
+
const { home: explicitHome, user: mirrorUser } = _effectiveMirrorEnv();
|
|
332
|
+
|
|
333
|
+
if (explicitHome) {
|
|
334
|
+
return explicitHome.startsWith("~") ? join(homedir(), explicitHome.slice(2)) : explicitHome;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Prefer the new ~/.mirror-minds/<user> location, and fall back to the
|
|
338
|
+
// legacy ~/.mirror/<user> only when the new home does not exist.
|
|
339
|
+
if (mirrorUser) {
|
|
340
|
+
const newUserHome = join(homedir(), NEW_HOMES_DIR, mirrorUser);
|
|
341
|
+
const legacyUserHome = join(homedir(), LEGACY_HOMES_DIR, mirrorUser);
|
|
342
|
+
if (!_isDir(newUserHome) && _isDir(legacyUserHome)) {
|
|
343
|
+
return legacyUserHome;
|
|
344
|
+
}
|
|
345
|
+
return newUserHome;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// No explicit home/user from shell or .env: infer the active Mirror home
|
|
349
|
+
// from a single installed catalog. Prefer the new homes root; only fall
|
|
350
|
+
// back to legacy when the new root has none. Ambiguity (more than one
|
|
351
|
+
// candidate under a root) requires MIRROR_USER/MIRROR_HOME — never guess.
|
|
352
|
+
for (const homesDir of [NEW_HOMES_DIR, LEGACY_HOMES_DIR]) {
|
|
353
|
+
const candidates = _piCatalogHomes(homesDir);
|
|
354
|
+
if (candidates.length === 1) return candidates[0];
|
|
355
|
+
if (candidates.length > 1) {
|
|
356
|
+
log(
|
|
357
|
+
"WARN",
|
|
358
|
+
`multiple Mirror homes with Pi external skill catalogs under ${homesDir}; set MIRROR_USER or MIRROR_HOME`,
|
|
359
|
+
);
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function loadInstalledPiExternalSkills(): RuntimeCatalog | null {
|
|
367
|
+
try {
|
|
368
|
+
const mirrorHome = resolveMirrorHome();
|
|
369
|
+
if (!mirrorHome) return null;
|
|
370
|
+
const catalogPath = join(mirrorHome, "runtime", "skills", "pi", "extensions.json");
|
|
371
|
+
if (!existsSync(catalogPath)) return null;
|
|
372
|
+
|
|
373
|
+
const raw = readFileSync(catalogPath, "utf-8");
|
|
374
|
+
const data = JSON.parse(raw) as RuntimeCatalog;
|
|
375
|
+
if (data.schema_version !== "1") {
|
|
376
|
+
log("WARN", `unsupported Pi external skill catalog schema: ${String(data.schema_version ?? "(missing)")}`);
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
if (data.runtime !== "pi") {
|
|
380
|
+
log("WARN", `unexpected Pi external skill catalog runtime: ${String(data.runtime ?? "(missing)")}`);
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
if (!Array.isArray(data.extensions)) {
|
|
384
|
+
log("WARN", "invalid Pi external skill catalog: extensions must be an array");
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
return data;
|
|
388
|
+
} catch (err: unknown) {
|
|
389
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
390
|
+
log("WARN", `failed to load Pi external skill catalog: ${message.slice(0, 500)}`);
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function getInstalledPiSkillPaths(): string[] {
|
|
396
|
+
const catalog = loadInstalledPiExternalSkills();
|
|
397
|
+
const items = catalog?.extensions ?? [];
|
|
398
|
+
const skillPaths = items
|
|
399
|
+
.map((item) => item.installed_skill_path)
|
|
400
|
+
.filter((path): path is string => typeof path === "string" && path.length > 0)
|
|
401
|
+
.filter((path) => existsSync(path));
|
|
402
|
+
return [...new Set(skillPaths)];
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// --- dynamic resources → installed external Pi skills ---
|
|
406
|
+
|
|
407
|
+
pi.on("resources_discover", async () => {
|
|
408
|
+
const skillPaths = getInstalledPiSkillPaths();
|
|
409
|
+
if (skillPaths.length > 0) {
|
|
410
|
+
log("INFO", `resources_discover: loaded ${skillPaths.length} installed Pi external skill(s)`);
|
|
411
|
+
}
|
|
412
|
+
return { skillPaths };
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
// --- 0. the Operating Instructions, for a session Pi did not open in the tree ---
|
|
416
|
+
//
|
|
417
|
+
// Pi loads one context file per directory from the cwd upward. Inside the
|
|
418
|
+
// checkout that is this tree's AGENTS.md; from anywhere else -- an npm
|
|
419
|
+
// user's project, /tmp -- it is not, and a session that answers /mm-mirror
|
|
420
|
+
// without the modes, the persona signature, or the Builder boundary is not
|
|
421
|
+
// a Mirror session (CV22.DS10.US3 D13). So the extension appends the file
|
|
422
|
+
// it ships with, unless Pi already loaded that very file.
|
|
423
|
+
|
|
424
|
+
pi.on("before_agent_start", async (event) => {
|
|
425
|
+
const files = event.systemPromptOptions.contextFiles;
|
|
426
|
+
if (files.some((file) => _isOurAgentsFile(file.path))) return;
|
|
427
|
+
let content: string;
|
|
428
|
+
try {
|
|
429
|
+
content = readFileSync(AGENTS_FILE, "utf-8");
|
|
430
|
+
} catch (err: unknown) {
|
|
431
|
+
log("WARN", `AGENTS.md not readable at ${AGENTS_FILE}: ${err instanceof Error ? err.message : String(err)}`);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
if (files.some((file) => file.content === content)) return;
|
|
435
|
+
files.push({ path: AGENTS_FILE, content });
|
|
436
|
+
log("INFO", `operating instructions appended from ${AGENTS_FILE}`);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
// --- 1. session_start → unmute + close stale orphans + extract pending ---
|
|
440
|
+
|
|
441
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
442
|
+
log("INFO", "session_start fired");
|
|
443
|
+
if (ctx.hasUI) {
|
|
444
|
+
ctx.ui.setStatus("mirror", "◇ Mirror · starting… maintenance will continue in background");
|
|
445
|
+
}
|
|
446
|
+
// The skills say `mirror`. Say once, visibly, when that will not work.
|
|
447
|
+
if (_mirrorOnPath() === null) {
|
|
448
|
+
const fix = existsSync(join(TREE_ROOT, ".git"))
|
|
449
|
+
? `run \`npm link\` once in ${TREE_ROOT}`
|
|
450
|
+
: "add `$(npm prefix -g)/bin` to the PATH";
|
|
451
|
+
const line = `Mirror: \`mirror\` is not on the PATH — ${fix}. Skills will fail until it is.`;
|
|
452
|
+
log("WARN", line);
|
|
453
|
+
if (ctx.hasUI) ctx.ui.notify(line, "warning");
|
|
454
|
+
}
|
|
455
|
+
const summary = await runMirror(["conversation-logger", "session-start", "--fast"]);
|
|
456
|
+
if (ctx.hasUI) {
|
|
457
|
+
ctx.ui.setStatus("mirror", "◇ Mirror · checking release status…");
|
|
458
|
+
}
|
|
459
|
+
const externalCatalog = loadInstalledPiExternalSkills();
|
|
460
|
+
const externalSkills = externalCatalog?.extensions ?? [];
|
|
461
|
+
const externalSkillSummary = externalSkills.length
|
|
462
|
+
? `External skills: ${externalSkills.map((item) => item.command_name ?? item.id ?? "(unknown)").join(", ")}`
|
|
463
|
+
: "External skills: none";
|
|
464
|
+
log("INFO", `session-start result: ${summary || "(empty)"}`);
|
|
465
|
+
log("INFO", externalSkillSummary);
|
|
466
|
+
|
|
467
|
+
const welcome = (await runMirror(["welcome"])).trim();
|
|
468
|
+
if (welcome) {
|
|
469
|
+
log("INFO", `welcome: ${welcome.split("\n")[0]}`);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (ctx.hasUI) {
|
|
473
|
+
if (welcome) {
|
|
474
|
+
ctx.ui.notify(welcome, "info");
|
|
475
|
+
}
|
|
476
|
+
await refreshMirrorStatus(ctx);
|
|
477
|
+
runMirrorBackground(["conversation-logger", "session-maintenance"], "session-maintenance");
|
|
478
|
+
} else {
|
|
479
|
+
runMirrorBackground(["conversation-logger", "session-maintenance"], "session-maintenance");
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
// --- 2. before_agent_start → log user prompt with explicit session id ---
|
|
484
|
+
|
|
485
|
+
pi.on("before_agent_start", async (event, ctx) => {
|
|
486
|
+
const sessionId = ctx.sessionManager.getSessionFile() ?? null;
|
|
487
|
+
if (!sessionId) return;
|
|
488
|
+
|
|
489
|
+
const prompt = event.prompt ?? "";
|
|
490
|
+
if (!prompt || prompt.startsWith("/")) return;
|
|
491
|
+
|
|
492
|
+
log("INFO", `log-user: ${prompt.slice(0, 80)}...`);
|
|
493
|
+
await runMirror([
|
|
494
|
+
"conversation-logger",
|
|
495
|
+
"log-user",
|
|
496
|
+
sessionId,
|
|
497
|
+
truncate(prompt),
|
|
498
|
+
"--interface",
|
|
499
|
+
"pi",
|
|
500
|
+
]);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
// --- 3. agent_end → log assistant response ---
|
|
504
|
+
//
|
|
505
|
+
// agent_end fires once per user prompt, with ALL messages in the cycle
|
|
506
|
+
// (assistant + tool calls + tool results). Extract only assistant text
|
|
507
|
+
// and log as a single consolidated message.
|
|
508
|
+
|
|
509
|
+
pi.on("agent_end", async (event, ctx) => {
|
|
510
|
+
const sessionId = ctx.sessionManager.getSessionFile() ?? null;
|
|
511
|
+
if (!sessionId) return;
|
|
512
|
+
|
|
513
|
+
const messages = (event as unknown as Record<string, unknown>).messages;
|
|
514
|
+
if (!Array.isArray(messages) || messages.length === 0) return;
|
|
515
|
+
|
|
516
|
+
const assistantTexts: string[] = [];
|
|
517
|
+
for (const msg of messages) {
|
|
518
|
+
if (
|
|
519
|
+
msg &&
|
|
520
|
+
typeof msg === "object" &&
|
|
521
|
+
"role" in msg &&
|
|
522
|
+
(msg as Record<string, unknown>).role === "assistant"
|
|
523
|
+
) {
|
|
524
|
+
const text = extractText((msg as Record<string, unknown>).content);
|
|
525
|
+
if (text.trim()) {
|
|
526
|
+
assistantTexts.push(text);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (assistantTexts.length === 0) return;
|
|
532
|
+
|
|
533
|
+
log("INFO", `log-assistant: ${assistantTexts.length} block(s), ${assistantTexts.join("").length} chars`);
|
|
534
|
+
|
|
535
|
+
const combined = assistantTexts.join("\n\n---\n\n");
|
|
536
|
+
const content = truncate(combined);
|
|
537
|
+
|
|
538
|
+
runMirrorBackground(
|
|
539
|
+
["conversation-logger", "log-assistant", sessionId, content, "--interface", "pi"],
|
|
540
|
+
"log-assistant",
|
|
541
|
+
);
|
|
542
|
+
await refreshMirrorStatus(ctx);
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
// --- 4. session_shutdown → close conversation + backup ---
|
|
546
|
+
//
|
|
547
|
+
// Uses extract=False because extraction calls the LLM and can take 30s+.
|
|
548
|
+
// Extraction happens at the next session_start via extract_pending.
|
|
549
|
+
|
|
550
|
+
pi.on("session_shutdown", async (_event, ctx) => {
|
|
551
|
+
const sessionId = ctx.sessionManager.getSessionFile() ?? null;
|
|
552
|
+
|
|
553
|
+
if (sessionId) {
|
|
554
|
+
await runMirror(["conversation-logger", "session-end-pi", sessionId]);
|
|
555
|
+
log("INFO", `session closed: ${sessionId}`);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// The dated zip backup answers from TypeScript (CV22.DS7.TS1).
|
|
559
|
+
await runMirror(["backup", "--silent"]);
|
|
560
|
+
});
|
|
561
|
+
}
|
|
@@ -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.
|