mirror-mind 0.0.1 → 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/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 +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 +102 -6
- 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,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one place a provider-backed family turns its transport decision into
|
|
3
|
+
* actual providers (CV22.DS8.US3).
|
|
4
|
+
*
|
|
5
|
+
* Before this, every route did it inline: `searchRoute` read `replayPath` and
|
|
6
|
+
* built a replay or live embedding provider, `loggerRuntime` did the same for
|
|
7
|
+
* two fixtures plus the pair rule, and `cli.ts` repeated the shape four more
|
|
8
|
+
* times with a hand-written `throw` per family. Six copies of one decision is
|
|
9
|
+
* how the router and the runtime came to disagree (CR077).
|
|
10
|
+
*
|
|
11
|
+
* The spec's declared fixtures double as the family's provider MANIFEST, and
|
|
12
|
+
* that is a real invariant rather than a coincidence: a family must have a
|
|
13
|
+
* replay fixture for every provider kind it touches, or CI would have to reach
|
|
14
|
+
* the network to exercise it. So `replay: { llm, embedding }` says both "these
|
|
15
|
+
* are required to replay" and "these are the providers this family uses".
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
type CreditProvider,
|
|
20
|
+
LiveCreditProvider,
|
|
21
|
+
loadReplayCreditProvider,
|
|
22
|
+
} from "#providers/credits.ts";
|
|
23
|
+
import {
|
|
24
|
+
type EmbeddingProvider,
|
|
25
|
+
LiveEmbeddingProvider,
|
|
26
|
+
loadReplayEmbeddingProvider,
|
|
27
|
+
} from "#providers/embedding.ts";
|
|
28
|
+
import { LiveLlmProvider, type LlmProvider, loadReplayLlmProvider } from "#providers/llm.ts";
|
|
29
|
+
import {
|
|
30
|
+
type ComposedProviderTransportSpec,
|
|
31
|
+
type ProviderTransportDecision,
|
|
32
|
+
type ProviderTransportEnv,
|
|
33
|
+
type ProviderTransportSpec,
|
|
34
|
+
ReplayFixtureIncompleteError,
|
|
35
|
+
resolveComposedProviderTransport,
|
|
36
|
+
resolveProviderTransport,
|
|
37
|
+
} from "#providers/transport.ts";
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* What a family can be: one spec, or a command that composes several
|
|
41
|
+
* (CV22.DS7.US8 item 18b).
|
|
42
|
+
*
|
|
43
|
+
* The factory accepts both because the ROUTE and the RUNTIME must reach the
|
|
44
|
+
* same decision for the same invocation. Leaving the composition to the router
|
|
45
|
+
* and handing this function the owning spec alone would reintroduce CR077 one
|
|
46
|
+
* level up: routing would refuse a half-configured harness that this factory
|
|
47
|
+
* then cheerfully built live providers for.
|
|
48
|
+
*/
|
|
49
|
+
export type FamilyTransport = ProviderTransportSpec | ComposedProviderTransportSpec;
|
|
50
|
+
|
|
51
|
+
/** A composition names its owner; a plain spec names its revert variable. */
|
|
52
|
+
function isComposed(spec: FamilyTransport): spec is ComposedProviderTransportSpec {
|
|
53
|
+
return "owner" in spec;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface FamilyProviders {
|
|
57
|
+
/** Which transport answered. `python` never reaches here -- it returns null. */
|
|
58
|
+
readonly mode: "replay" | "live";
|
|
59
|
+
/** The transport decision's reason, for the front-door log. */
|
|
60
|
+
readonly reason: string;
|
|
61
|
+
/** Present when the family declares an LLM fixture. */
|
|
62
|
+
readonly llm?: LlmProvider;
|
|
63
|
+
/** Present when the family declares an embedding fixture. */
|
|
64
|
+
readonly embedding?: EmbeddingProvider;
|
|
65
|
+
/** Present when the family declares a credits fixture (`consult`). */
|
|
66
|
+
readonly credits?: CreditProvider;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Test seams. The replay loaders and the live constructors are separately
|
|
71
|
+
* injectable because they are separately interesting: replay tests need a
|
|
72
|
+
* fixture that never touches disk, and live tests need a provider that fails
|
|
73
|
+
* on demand without reaching the network.
|
|
74
|
+
*/
|
|
75
|
+
export interface FamilyProviderOverrides {
|
|
76
|
+
loadReplayLlm?: (path: string) => Promise<LlmProvider>;
|
|
77
|
+
loadReplayEmbedding?: (path: string) => Promise<EmbeddingProvider>;
|
|
78
|
+
loadReplayCredits?: (path: string) => Promise<CreditProvider>;
|
|
79
|
+
liveLlm?: (env: ProviderTransportEnv) => LlmProvider;
|
|
80
|
+
liveEmbedding?: (env: ProviderTransportEnv) => EmbeddingProvider;
|
|
81
|
+
liveCredits?: (env: ProviderTransportEnv) => CreditProvider;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Build the providers for one family.
|
|
86
|
+
*
|
|
87
|
+
* Throws `ReplayFixtureIncompleteError` when part of a multi-fixture family is
|
|
88
|
+
* configured: half a fixture must never become a live call.
|
|
89
|
+
*
|
|
90
|
+
* Live providers resolve their config lazily, so a missing key surfaces at the
|
|
91
|
+
* call site -- where each leaf's own failure contract applies -- rather than
|
|
92
|
+
* at construction, where it would break commands that never reach the model.
|
|
93
|
+
*/
|
|
94
|
+
export async function resolveFamilyProviders(
|
|
95
|
+
env: ProviderTransportEnv,
|
|
96
|
+
spec: FamilyTransport,
|
|
97
|
+
overrides: FamilyProviderOverrides = {},
|
|
98
|
+
): Promise<FamilyProviders> {
|
|
99
|
+
// A composed command's PROVIDERS come from its owning family: the fixtures
|
|
100
|
+
// the other families declare answer for their own commands, never for this
|
|
101
|
+
// one. The composition changes which transport is chosen, not whose
|
|
102
|
+
// manifest is built.
|
|
103
|
+
const owner = isComposed(spec) ? spec.owner : spec;
|
|
104
|
+
const decision = isComposed(spec)
|
|
105
|
+
? resolveComposedProviderTransport(env, spec)
|
|
106
|
+
: resolveProviderTransport(env, spec);
|
|
107
|
+
if (decision.mode === "incomplete_replay") throw new ReplayFixtureIncompleteError(decision);
|
|
108
|
+
return decision.mode === "replay"
|
|
109
|
+
? replayProviders(decision, overrides)
|
|
110
|
+
: liveProviders(env, owner, decision, overrides);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function replayProviders(
|
|
114
|
+
decision: ProviderTransportDecision,
|
|
115
|
+
overrides: FamilyProviderOverrides,
|
|
116
|
+
): Promise<FamilyProviders> {
|
|
117
|
+
const paths = decision.replayPaths ?? {};
|
|
118
|
+
const loadLlm = overrides.loadReplayLlm ?? loadReplayLlmProvider;
|
|
119
|
+
const loadEmbedding = overrides.loadReplayEmbedding ?? loadReplayEmbeddingProvider;
|
|
120
|
+
const loadCredits = overrides.loadReplayCredits ?? loadReplayCreditProvider;
|
|
121
|
+
const [llm, embedding, credits] = await Promise.all([
|
|
122
|
+
paths.llm ? loadLlm(paths.llm) : undefined,
|
|
123
|
+
paths.embedding ? loadEmbedding(paths.embedding) : undefined,
|
|
124
|
+
paths.credits ? loadCredits(paths.credits) : undefined,
|
|
125
|
+
]);
|
|
126
|
+
return { mode: "replay", reason: decision.reason, llm, embedding, credits };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function liveProviders(
|
|
130
|
+
env: ProviderTransportEnv,
|
|
131
|
+
spec: ProviderTransportSpec,
|
|
132
|
+
decision: ProviderTransportDecision,
|
|
133
|
+
overrides: FamilyProviderOverrides,
|
|
134
|
+
): FamilyProviders {
|
|
135
|
+
const buildLlm = overrides.liveLlm ?? ((live) => new LiveLlmProvider({ env: { ...live } }));
|
|
136
|
+
const buildEmbedding =
|
|
137
|
+
overrides.liveEmbedding ?? ((live) => new LiveEmbeddingProvider({ env: { ...live } }));
|
|
138
|
+
const buildCredits =
|
|
139
|
+
overrides.liveCredits ?? ((live) => new LiveCreditProvider({ env: { ...live } }));
|
|
140
|
+
return {
|
|
141
|
+
mode: "live",
|
|
142
|
+
reason: decision.reason,
|
|
143
|
+
llm: spec.replay?.llm ? buildLlm(env) : undefined,
|
|
144
|
+
embedding: spec.replay?.embedding ? buildEmbedding(env) : undefined,
|
|
145
|
+
credits: spec.replay?.credits ? buildCredits(env) : undefined,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type LlmTimeoutRole,
|
|
5
|
+
type ProviderConfig,
|
|
6
|
+
resolveExtractionModel,
|
|
7
|
+
resolveLlmMaxRetries,
|
|
8
|
+
resolveLlmTimeoutMs,
|
|
9
|
+
resolveProviderConfig,
|
|
10
|
+
} from "./config.ts";
|
|
11
|
+
import { createOpenRouterClient, LlmTransportError, type OpenRouterClient } from "./openrouter.ts";
|
|
12
|
+
import { loadReplayFixture } from "./replay.ts";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Every role the replay transport understands.
|
|
16
|
+
*
|
|
17
|
+
* The type AND the runtime guard are derived from this one array. They used to
|
|
18
|
+
* be written twice -- a union type plus a hand-maintained `isLlmRole` chain --
|
|
19
|
+
* and CV22.DS7.US11 added three roles to the type while the guard silently
|
|
20
|
+
* kept rejecting them. The unit tests passed; the first end-to-end run through
|
|
21
|
+
* the front door failed with "unsupported role 'journal_classification'".
|
|
22
|
+
* One list, no drift.
|
|
23
|
+
*/
|
|
24
|
+
export const LLM_ROLES = [
|
|
25
|
+
"extraction",
|
|
26
|
+
"task_extraction",
|
|
27
|
+
"summary",
|
|
28
|
+
"curation",
|
|
29
|
+
"consult",
|
|
30
|
+
"reception",
|
|
31
|
+
"consolidation",
|
|
32
|
+
"shadow_scan",
|
|
33
|
+
// CV22.DS7.US10 slice C' -- the close-time metadata surfaces. Role names
|
|
34
|
+
// match Python's `build_llm_logger` roles so the llm_calls ledger agrees.
|
|
35
|
+
"conversation_title",
|
|
36
|
+
"conversation_tags",
|
|
37
|
+
"conversation_summary",
|
|
38
|
+
// CV22.DS7.US11 -- the content & planning tail. Same naming rule, with one
|
|
39
|
+
// deliberate exception: Python's `generate_descriptor` passes no
|
|
40
|
+
// `on_llm_call`, so the `descriptor` role writes NO ledger row on either
|
|
41
|
+
// engine. Parity preserves the gap; closing it is a DS8 plan input.
|
|
42
|
+
"journal_classification",
|
|
43
|
+
"week_plan",
|
|
44
|
+
"descriptor",
|
|
45
|
+
] as const;
|
|
46
|
+
|
|
47
|
+
export type LlmRole = (typeof LLM_ROLES)[number];
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Which timeout tier each role's call is bounded by (CV22.DS8.US3).
|
|
51
|
+
*
|
|
52
|
+
* Python's `send_to_model` defaults to `LLM_TIMEOUT_EXTRACTION` and only
|
|
53
|
+
* `reception` passes anything else (`LLM_TIMEOUT_RECEPTION`, 10s) -- it runs
|
|
54
|
+
* in front of a waiting human on every Mirror Mode activation, so it must give
|
|
55
|
+
* up six times sooner than a background extraction.
|
|
56
|
+
*
|
|
57
|
+
* A full `Record<LlmRole, ...>` rather than a default plus an exception list:
|
|
58
|
+
* the compiler then forces every role added to `LLM_ROLES` to declare its
|
|
59
|
+
* tier, instead of silently inheriting one. That is the US11 lesson -- a type
|
|
60
|
+
* and a hand-maintained companion that drifted apart.
|
|
61
|
+
*/
|
|
62
|
+
const LLM_TIMEOUT_TIER: Readonly<Record<LlmRole, LlmTimeoutRole>> = {
|
|
63
|
+
extraction: "extraction",
|
|
64
|
+
task_extraction: "extraction",
|
|
65
|
+
summary: "extraction",
|
|
66
|
+
curation: "extraction",
|
|
67
|
+
consult: "extraction",
|
|
68
|
+
reception: "reception",
|
|
69
|
+
consolidation: "extraction",
|
|
70
|
+
shadow_scan: "extraction",
|
|
71
|
+
conversation_title: "extraction",
|
|
72
|
+
conversation_tags: "extraction",
|
|
73
|
+
conversation_summary: "extraction",
|
|
74
|
+
journal_classification: "extraction",
|
|
75
|
+
week_plan: "extraction",
|
|
76
|
+
descriptor: "extraction",
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The roles a message in Python's envelope can carry.
|
|
81
|
+
*
|
|
82
|
+
* Closed to `system` and `user` on purpose: those are the only two Python ever
|
|
83
|
+
* sends. An assistant prefill or a second turn would satisfy every prompt
|
|
84
|
+
* digest -- digests pin CONTENT, not STRUCTURE -- while changing what the
|
|
85
|
+
* model hears, so widening this union has to be a deliberate, reviewed edit.
|
|
86
|
+
*/
|
|
87
|
+
export type LlmMessageRole = "system" | "user";
|
|
88
|
+
|
|
89
|
+
export interface LlmMessage {
|
|
90
|
+
role: LlmMessageRole;
|
|
91
|
+
content: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface LlmRequest {
|
|
95
|
+
role: LlmRole;
|
|
96
|
+
/**
|
|
97
|
+
* The assembled prompt: what prompt digests pin and what the `llm_calls`
|
|
98
|
+
* ledger records. For a single-message call it is also what is sent.
|
|
99
|
+
*/
|
|
100
|
+
prompt: string;
|
|
101
|
+
/**
|
|
102
|
+
* Python's exact message array, for the calls that send more than one
|
|
103
|
+
* message (`consult` sends `system` + `user`).
|
|
104
|
+
*
|
|
105
|
+
* Two fields rather than one because they answer different questions.
|
|
106
|
+
* `prompt` is the AUDIT record -- Python logs `json.dumps(messages)` in the
|
|
107
|
+
* ledger's `prompt` column, and the replay transport resolves and digests
|
|
108
|
+
* against it. `messages` is the WIRE format. Collapsing them is precisely
|
|
109
|
+
* the defect this story found: consult's JSON-encoded envelope would have
|
|
110
|
+
* been sent as the text of one user message, passing every digest while the
|
|
111
|
+
* model read a different conversation.
|
|
112
|
+
*/
|
|
113
|
+
messages?: readonly LlmMessage[];
|
|
114
|
+
model?: string;
|
|
115
|
+
temperature?: number;
|
|
116
|
+
maxTokens?: number;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface LlmResponse {
|
|
120
|
+
content: string;
|
|
121
|
+
model?: string;
|
|
122
|
+
generationId?: string | null;
|
|
123
|
+
promptTokens?: number;
|
|
124
|
+
completionTokens?: number;
|
|
125
|
+
latencyMs?: number;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface LlmProvider {
|
|
129
|
+
complete(request: LlmRequest): Promise<LlmResponse>;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface ReplayLlmFixture {
|
|
133
|
+
kind: "llm";
|
|
134
|
+
responses: Partial<Record<LlmRole, LlmResponse | string>>;
|
|
135
|
+
/**
|
|
136
|
+
* Optional per-role SHA-256 of the fully assembled prompt (CV22.DS7.US10
|
|
137
|
+
* slice C′, ai-engineer plan review — blocking).
|
|
138
|
+
*
|
|
139
|
+
* Replay resolves by `role` alone, so a TS prompt that drifted from the
|
|
140
|
+
* Python oracle would replay happily and the divergence would only surface
|
|
141
|
+
* at the DS8 live cutover, against real users. When a fixture pins a digest,
|
|
142
|
+
* a mismatch is a hard, deterministic failure instead.
|
|
143
|
+
*
|
|
144
|
+
* Optional by design: DS5-era fixtures predate prompt assembly and stay
|
|
145
|
+
* valid, while every fixture that pins a digest is strictly enforced.
|
|
146
|
+
*/
|
|
147
|
+
promptDigests?: Partial<Record<LlmRole, string>>;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** SHA-256 of an assembled prompt, as pinned in replay fixtures. */
|
|
151
|
+
export function promptDigest(prompt: string): string {
|
|
152
|
+
return createHash("sha256").update(prompt, "utf8").digest("hex");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export class ReplayLlmProvider implements LlmProvider {
|
|
156
|
+
private readonly fixture: ReplayLlmFixture;
|
|
157
|
+
readonly calls: LlmRequest[] = [];
|
|
158
|
+
|
|
159
|
+
constructor(fixture: ReplayLlmFixture) {
|
|
160
|
+
this.fixture = fixture;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async complete(request: LlmRequest): Promise<LlmResponse> {
|
|
164
|
+
this.calls.push(request);
|
|
165
|
+
const expectedDigest = this.fixture.promptDigests?.[request.role];
|
|
166
|
+
if (expectedDigest !== undefined) {
|
|
167
|
+
const actualDigest = promptDigest(request.prompt);
|
|
168
|
+
if (actualDigest !== expectedDigest) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`replay prompt digest mismatch for role '${request.role}': ` +
|
|
171
|
+
`fixture pins ${expectedDigest}, assembled prompt is ${actualDigest}. ` +
|
|
172
|
+
"The TypeScript prompt drifted from the Python oracle; regrade the " +
|
|
173
|
+
"assembled-prompt golden before changing the fixture.",
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const response = this.fixture.responses[request.role];
|
|
178
|
+
if (response === undefined) {
|
|
179
|
+
throw new Error(`missing replay LLM response for role '${request.role}'`);
|
|
180
|
+
}
|
|
181
|
+
return typeof response === "string" ? { content: response } : response;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export async function loadReplayLlmProvider(path: string): Promise<ReplayLlmProvider> {
|
|
186
|
+
const fixture = await loadReplayFixture(path);
|
|
187
|
+
assertReplayLlmFixture(fixture);
|
|
188
|
+
return new ReplayLlmProvider(fixture);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function assertReplayLlmFixture(value: unknown): asserts value is ReplayLlmFixture {
|
|
192
|
+
if (!isRecord(value) || value.kind !== "llm") {
|
|
193
|
+
throw new Error("LLM replay fixture must declare kind='llm'");
|
|
194
|
+
}
|
|
195
|
+
if (!isRecord(value.responses)) {
|
|
196
|
+
throw new Error("LLM replay fixture must include responses object");
|
|
197
|
+
}
|
|
198
|
+
if (value.promptDigests !== undefined) {
|
|
199
|
+
if (!isRecord(value.promptDigests)) {
|
|
200
|
+
throw new Error("LLM replay fixture promptDigests must be an object");
|
|
201
|
+
}
|
|
202
|
+
for (const [role, digest] of Object.entries(value.promptDigests)) {
|
|
203
|
+
if (!isLlmRole(role)) {
|
|
204
|
+
throw new Error(`LLM replay fixture pins a digest for unsupported role '${role}'`);
|
|
205
|
+
}
|
|
206
|
+
if (typeof digest !== "string" || !/^[0-9a-f]{64}$/.test(digest)) {
|
|
207
|
+
throw new Error(`LLM replay fixture digest for '${role}' must be a sha256 hex string`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
for (const [role, response] of Object.entries(value.responses)) {
|
|
212
|
+
if (!isLlmRole(role)) {
|
|
213
|
+
throw new Error(`LLM replay fixture includes unsupported role '${role}'`);
|
|
214
|
+
}
|
|
215
|
+
if (typeof response === "string") continue;
|
|
216
|
+
if (!isRecord(response) || typeof response.content !== "string") {
|
|
217
|
+
throw new Error(`LLM replay fixture response '${role}' must be a string or { content }`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function isLlmRole(value: string): value is LlmRole {
|
|
223
|
+
return (LLM_ROLES as readonly string[]).includes(value);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
227
|
+
return typeof value === "object" && value !== null;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// --- Live chat completions (CV22.DS8.US2) ----------------------------------
|
|
231
|
+
|
|
232
|
+
/** Python's `send_to_model` defaults, which every un-overridden call inherits. */
|
|
233
|
+
const DEFAULT_TEMPERATURE = 0.7;
|
|
234
|
+
const DEFAULT_MAX_TOKENS = 4096;
|
|
235
|
+
|
|
236
|
+
export interface LiveLlmProviderOptions {
|
|
237
|
+
env?: Record<string, string | undefined>;
|
|
238
|
+
/** Injectable for tests; no test in this repo may reach the network. */
|
|
239
|
+
createClient?: (config: ProviderConfig) => OpenRouterClient;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* The live chat provider (CV22.DS8.US2), mirroring Python's `send_to_model`.
|
|
244
|
+
*
|
|
245
|
+
* Three properties carry the weight:
|
|
246
|
+
*
|
|
247
|
+
* 1. **The envelope is Python's.** Exactly one message, role `user`, content
|
|
248
|
+
* byte-identical to the assembled prompt. US10 pinned SHA-256 digests of
|
|
249
|
+
* every close-tail prompt so a drift fails under replay -- but those
|
|
250
|
+
* digests pin CONTENT, not STRUCTURE. A system message, an assistant
|
|
251
|
+
* prefill, or a second turn would satisfy every digest and still change
|
|
252
|
+
* what the model hears. The envelope is asserted by test for that reason.
|
|
253
|
+
* 2. **Config resolves lazily**, on the first call. A missing key must surface
|
|
254
|
+
* inside the close tail -- where the extraction driver records a failed
|
|
255
|
+
* attempt -- not at construction, where it would crash the session-end hook
|
|
256
|
+
* before any accounting happened.
|
|
257
|
+
* 3. **Model output is not transport output.** An empty or non-JSON completion
|
|
258
|
+
* is a legitimate model outcome that the parser upstream reports as
|
|
259
|
+
* `no_signal`/`parse_failed`. Only a broken HTTP-level SHAPE (no usable
|
|
260
|
+
* `choices[0]`, Python's `IndexError`) is `malformed_output`. Conflating
|
|
261
|
+
* them would retry a deterministic model answer and pay for it each time.
|
|
262
|
+
*/
|
|
263
|
+
export class LiveLlmProvider implements LlmProvider {
|
|
264
|
+
private readonly env: Record<string, string | undefined>;
|
|
265
|
+
private readonly createClient: (config: ProviderConfig) => OpenRouterClient;
|
|
266
|
+
private client: OpenRouterClient | null = null;
|
|
267
|
+
|
|
268
|
+
constructor(options: LiveLlmProviderOptions = {}) {
|
|
269
|
+
this.env = options.env ?? process.env;
|
|
270
|
+
this.createClient = options.createClient ?? ((config) => createOpenRouterClient(config));
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async complete(request: LlmRequest): Promise<LlmResponse> {
|
|
274
|
+
const client = this.resolveClient();
|
|
275
|
+
const model = request.model ?? resolveExtractionModel({ env: this.env });
|
|
276
|
+
const startedAt = Date.now();
|
|
277
|
+
const body = await client.postJson(
|
|
278
|
+
"/chat/completions",
|
|
279
|
+
{
|
|
280
|
+
model,
|
|
281
|
+
// Python sends `[{"role": "user", "content": prompt}]` for every caller
|
|
282
|
+
// that assembles one prompt, and its own array for the callers that do
|
|
283
|
+
// not (`consult`). Nothing else, ever.
|
|
284
|
+
messages: request.messages
|
|
285
|
+
? request.messages.map((message) => ({ role: message.role, content: message.content }))
|
|
286
|
+
: [{ role: "user", content: request.prompt }],
|
|
287
|
+
temperature: request.temperature ?? DEFAULT_TEMPERATURE,
|
|
288
|
+
max_tokens: request.maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
timeoutMs: resolveLlmTimeoutMs(LLM_TIMEOUT_TIER[request.role], { env: this.env }),
|
|
292
|
+
maxRetries: resolveLlmMaxRetries({ env: this.env }),
|
|
293
|
+
},
|
|
294
|
+
);
|
|
295
|
+
return interpretChatResponse(body, model, Date.now() - startedAt);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private resolveClient(): OpenRouterClient {
|
|
299
|
+
this.client ??= this.createClient(resolveProviderConfig("openrouter", { env: this.env }));
|
|
300
|
+
return this.client;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** Mirrors `send_to_model`'s response handling branch for branch. */
|
|
305
|
+
function interpretChatResponse(body: unknown, model: string, latencyMs: number): LlmResponse {
|
|
306
|
+
if (!isRecord(body)) {
|
|
307
|
+
throw new LlmTransportError("malformed_output", "chat response was not an object");
|
|
308
|
+
}
|
|
309
|
+
const choices = body.choices;
|
|
310
|
+
if (!Array.isArray(choices) || choices.length === 0 || !isRecord(choices[0])) {
|
|
311
|
+
// Python indexes `response.choices[0]` unguarded, so this is its
|
|
312
|
+
// IndexError: the response has no usable shape at all.
|
|
313
|
+
throw new LlmTransportError("malformed_output", "chat response carried no usable choice");
|
|
314
|
+
}
|
|
315
|
+
const message = choices[0].message;
|
|
316
|
+
const rawContent = isRecord(message) ? message.content : null;
|
|
317
|
+
// Python: `(response.choices[0].message.content or "").strip()`. An empty or
|
|
318
|
+
// absent completion is data, not a failure.
|
|
319
|
+
const content = typeof rawContent === "string" ? rawContent.trim() : "";
|
|
320
|
+
const usage = isRecord(body.usage) ? body.usage : null;
|
|
321
|
+
return {
|
|
322
|
+
content,
|
|
323
|
+
model,
|
|
324
|
+
generationId: typeof body.id === "string" ? body.id : null,
|
|
325
|
+
promptTokens: readTokenCount(usage?.prompt_tokens),
|
|
326
|
+
completionTokens: readTokenCount(usage?.completion_tokens),
|
|
327
|
+
latencyMs,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** Usage is reported only when the provider gives a real integer; never 0 by default. */
|
|
332
|
+
function readTokenCount(value: unknown): number | undefined {
|
|
333
|
+
return typeof value === "number" && Number.isInteger(value) ? value : undefined;
|
|
334
|
+
}
|