@ouro.bot/cli 0.1.0-alpha.83 → 0.1.0-alpha.831
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/LICENSE +21 -0
- package/README.md +154 -23
- package/RepairGuide.ouro/agent.json +5 -0
- package/RepairGuide.ouro/psyche/IDENTITY.md +19 -0
- package/RepairGuide.ouro/psyche/SOUL.md +55 -0
- package/RepairGuide.ouro/skills/diagnose-broken-remote.md +63 -0
- package/RepairGuide.ouro/skills/diagnose-stacked-typed-issues.md +35 -0
- package/RepairGuide.ouro/skills/diagnose-sync-blocked.md +54 -0
- package/RepairGuide.ouro/skills/diagnose-vault-expired.md +60 -0
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +4 -2
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +2 -2
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +1 -1
- package/assets/bluebubbles-host +264 -0
- package/assets/sanctuary-host-launcher.sh +16 -0
- package/changelog.json +5308 -16
- package/deploy/unraid/Dockerfile +54 -0
- package/deploy/unraid/README.txt +2983 -0
- package/deploy/unraid/audit-container-spec.sh +4 -0
- package/deploy/unraid/container-runtime.json +1 -0
- package/deploy/unraid/docker-man-template-transaction.mjs +711 -0
- package/deploy/unraid/docker-man-template-xml.cjs +105 -0
- package/deploy/unraid/migrate-sanctuary-bundle.mjs +60 -0
- package/deploy/unraid/ouro-events/bootstrap-spool.sh +92 -0
- package/deploy/unraid/ouro-events/emit-event.mjs +310 -0
- package/deploy/unraid/ouro-events/emit-usenet-event.sh +54 -0
- package/deploy/unraid/ouro-events/install-usenet-guard.sh +435 -0
- package/deploy/unraid/ouro-events/usenet-health.sh +176 -0
- package/deploy/unraid/sanctuary-acceptance-adapter.sh +43 -0
- package/deploy/unraid/sanctuary-acceptance-contract.json +198 -0
- package/deploy/unraid/sanctuary-acceptance-harness.sh +23 -0
- package/deploy/unraid/sanctuary-authority-installation.json +36 -0
- package/deploy/unraid/sanctuary-authority-service.sh +30 -0
- package/deploy/unraid/sanctuary-deployment-target.mjs +710 -0
- package/deploy/unraid/sanctuary-safe-rename.py +24 -0
- package/deploy/unraid/sanctuary-unit16-host-broker.mjs +2084 -0
- package/deploy/unraid/sanctuary-unit16-run.sh +560 -0
- package/deploy/unraid/sanctuary-unit18-target-audit.sh +27 -0
- package/deploy/unraid/sanctuary.ouro/agent.json +66 -0
- package/deploy/unraid/sanctuary.ouro/arc/README.md +3 -0
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +5 -0
- package/deploy/unraid/sanctuary.ouro/habits/sanctuary-health.md +8 -0
- package/deploy/unraid/sanctuary.ouro/mcp/media-mcp.mjs +795 -0
- package/deploy/unraid/sanctuary.ouro/provider-readiness.json +11 -0
- package/deploy/unraid/sanctuary.ouro/psyche/ASPIRATIONS.md +5 -0
- package/deploy/unraid/sanctuary.ouro/psyche/IDENTITY.md +5 -0
- package/deploy/unraid/sanctuary.ouro/psyche/LORE.md +15 -0
- package/deploy/unraid/sanctuary.ouro/psyche/SOUL.md +25 -0
- package/deploy/unraid/sanctuary.ouro/psyche/TACIT.md +15 -0
- package/deploy/unraid/sanctuary.ouro/state/policy/steward.json +7 -0
- package/deploy/unraid/sanctuary.ouro/tool-profiles.json +135 -0
- package/deploy/unraid/sanctuary.xml +28 -0
- package/dist/a2a/card.js +57 -0
- package/dist/a2a/client.js +180 -0
- package/dist/a2a/config.js +50 -0
- package/dist/a2a/delegation-stores.js +56 -0
- package/dist/a2a/did-resolution.js +93 -0
- package/dist/a2a/identity.js +170 -0
- package/dist/a2a/inbound-share.js +107 -0
- package/dist/a2a/mission-result-wire.js +196 -0
- package/dist/a2a/onboarding.js +115 -0
- package/dist/a2a/pin-store.js +132 -0
- package/dist/a2a/seen-ledger.js +120 -0
- package/dist/a2a/server.js +604 -0
- package/dist/a2a/task-store.js +69 -0
- package/dist/a2a/transport.js +45 -0
- package/dist/a2a/types.js +3 -0
- package/dist/arc/attention-types.js +8 -0
- package/dist/arc/cares.js +360 -0
- package/dist/arc/episodes.js +118 -0
- package/dist/arc/evolution.js +490 -0
- package/dist/arc/flight-recorder.js +1034 -0
- package/dist/arc/intentions.js +134 -0
- package/dist/arc/json-store.js +117 -0
- package/dist/arc/obligations.js +386 -0
- package/dist/arc/packets.js +336 -0
- package/dist/arc/presence.js +185 -0
- package/dist/arc/task-lifecycle.js +57 -0
- package/dist/commerce/store.js +755 -0
- package/dist/commerce/types.js +3 -0
- package/dist/heart/active-work.js +798 -40
- package/dist/heart/agent-entry.js +252 -3
- package/dist/heart/approval-files.js +113 -0
- package/dist/heart/approval-store.js +960 -0
- package/dist/heart/attachments/image-normalize.js +194 -0
- package/dist/heart/attachments/materialize.js +97 -0
- package/dist/heart/attachments/originals.js +88 -0
- package/dist/heart/attachments/render.js +29 -0
- package/dist/heart/attachments/sources/bluebubbles.js +156 -0
- package/dist/heart/attachments/sources/cli-local-file.js +78 -0
- package/dist/heart/attachments/sources/index.js +18 -0
- package/dist/heart/attachments/sources/telegram.js +71 -0
- package/dist/heart/attachments/store.js +132 -0
- package/dist/heart/attachments/types.js +93 -0
- package/dist/heart/auth/auth-flow.js +495 -0
- package/dist/heart/autonomy-budget.js +478 -0
- package/dist/heart/awaiting/await-alert.js +148 -0
- package/dist/heart/awaiting/await-expiry.js +143 -0
- package/dist/heart/awaiting/await-loader.js +91 -0
- package/dist/heart/awaiting/await-parser.js +149 -0
- package/dist/heart/awaiting/await-runtime-state.js +111 -0
- package/dist/heart/awaiting/await-scheduler.js +382 -0
- package/dist/heart/background-operations.js +281 -0
- package/dist/heart/bridges/manager.js +137 -17
- package/dist/heart/bridges/store.js +14 -2
- package/dist/heart/bundle-state.js +168 -0
- package/dist/heart/commitments.js +44 -18
- package/dist/heart/config-registry.js +331 -0
- package/dist/heart/config.js +174 -131
- package/dist/heart/context-loss-gauntlet.js +387 -0
- package/dist/heart/context-loss-sentinel.js +1157 -0
- package/dist/heart/core.js +2169 -581
- package/dist/heart/cross-chat-delivery.js +9 -22
- package/dist/heart/daemon/agent-config-check.js +451 -0
- package/dist/heart/daemon/agent-discovery.js +185 -3
- package/dist/heart/daemon/agent-service.js +542 -0
- package/dist/heart/daemon/agentic-repair.js +547 -0
- package/dist/heart/daemon/await-private-wake.js +59 -0
- package/dist/heart/daemon/bluebubbles-cli-integration.js +71 -0
- package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
- package/dist/heart/daemon/bluebubbles-host-protocol.js +433 -0
- package/dist/heart/daemon/bluebubbles-host.js +273 -0
- package/dist/heart/daemon/boot-sync-probe.js +197 -0
- package/dist/heart/daemon/cadence.js +180 -0
- package/dist/heart/daemon/cli-defaults.js +854 -0
- package/dist/heart/daemon/cli-desk.js +322 -0
- package/dist/heart/daemon/cli-exec.js +8822 -0
- package/dist/heart/daemon/cli-help.js +716 -0
- package/dist/heart/daemon/cli-parse.js +2771 -0
- package/dist/heart/daemon/cli-render-doctor.js +57 -0
- package/dist/heart/daemon/cli-render.js +777 -0
- package/dist/heart/daemon/cli-types.js +8 -0
- package/dist/heart/daemon/connect-bay.js +323 -0
- package/dist/heart/daemon/container-credential-bootstrap.js +273 -0
- package/dist/heart/daemon/container-healthcheck.js +112 -0
- package/dist/heart/daemon/container-runtime.js +97 -0
- package/dist/heart/daemon/container-spec-auditor-main.js +171 -0
- package/dist/heart/daemon/container-spec-auditor.js +229 -0
- package/dist/heart/daemon/daemon-bootstrap-startup.js +115 -0
- package/dist/heart/daemon/daemon-cli.js +29 -2349
- package/dist/heart/daemon/daemon-entry.js +930 -11
- package/dist/heart/daemon/daemon-health.js +186 -0
- package/dist/heart/daemon/daemon-rollup.js +57 -0
- package/dist/heart/daemon/daemon-runtime-sync.js +88 -13
- package/dist/heart/daemon/daemon-tombstone.js +236 -0
- package/dist/heart/daemon/daemon.js +2084 -105
- package/dist/heart/daemon/dns-workflow.js +394 -0
- package/dist/heart/daemon/doctor-types.js +8 -0
- package/dist/heart/daemon/doctor.js +1755 -0
- package/dist/heart/daemon/freshness.js +345 -0
- package/dist/heart/daemon/habit-private-wake.js +61 -0
- package/dist/heart/daemon/health-monitor.js +142 -11
- package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
- package/dist/heart/daemon/hooks/bundle-meta.js +135 -1
- package/dist/heart/daemon/http-health-probe.js +80 -0
- package/dist/heart/daemon/human-command-screens.js +234 -0
- package/dist/heart/daemon/human-readiness.js +114 -0
- package/dist/heart/daemon/inner-status.js +78 -0
- package/dist/heart/daemon/interactive-repair.js +394 -0
- package/dist/heart/daemon/launchd.js +37 -8
- package/dist/heart/daemon/log-tailer.js +79 -10
- package/dist/heart/daemon/logs-prune.js +132 -0
- package/dist/heart/daemon/mcp-canary.js +524 -0
- package/dist/heart/daemon/message-router.js +65 -5
- package/dist/heart/daemon/migrate-to-desk.js +848 -0
- package/dist/heart/daemon/os-cron-deps.js +135 -0
- package/dist/heart/daemon/os-cron.js +22 -14
- package/dist/heart/daemon/ouro-bot-entry.js +4 -2
- package/dist/heart/daemon/ouro-entry.js +3 -1
- package/dist/heart/daemon/plugin-cli.js +432 -0
- package/dist/heart/daemon/process-manager.js +733 -61
- package/dist/heart/daemon/provider-discovery.js +137 -0
- package/dist/heart/daemon/provider-ping-progress.js +83 -0
- package/dist/heart/daemon/prunable-bundle.js +144 -0
- package/dist/heart/daemon/pulse.js +511 -0
- package/dist/heart/daemon/readiness-repair.js +365 -0
- package/dist/heart/daemon/run-hooks.js +2 -0
- package/dist/heart/daemon/runtime-logging.js +47 -14
- package/dist/heart/daemon/runtime-metadata.js +2 -30
- package/dist/heart/daemon/runtime-mode.js +13 -2
- package/dist/heart/daemon/safe-mode.js +161 -0
- package/dist/heart/daemon/sanctuary-acceptance-adapter.js +2654 -0
- package/dist/heart/daemon/sanctuary-acceptance-harness.js +1830 -0
- package/dist/heart/daemon/sanctuary-acceptance-marker.js +267 -0
- package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +955 -0
- package/dist/heart/daemon/sanctuary-authority-codec.js +86 -0
- package/dist/heart/daemon/sanctuary-authority-epoch.js +256 -0
- package/dist/heart/daemon/sanctuary-authority-installation.js +140 -0
- package/dist/heart/daemon/sanctuary-authority-ledger.js +241 -0
- package/dist/heart/daemon/sanctuary-authority-root-lifecycle.js +819 -0
- package/dist/heart/daemon/sanctuary-authority-vault-migration.js +79 -0
- package/dist/heart/daemon/sanctuary-bundle-migration.js +729 -0
- package/dist/heart/daemon/sanctuary-host-authority.js +1008 -0
- package/dist/heart/daemon/sanctuary-host-detached-supervisor.js +494 -0
- package/dist/heart/daemon/sanctuary-host-executor.js +670 -0
- package/dist/heart/daemon/sanctuary-host-linux-kernel.js +334 -0
- package/dist/heart/daemon/sanctuary-host-supervisor-entry.js +207 -0
- package/dist/heart/daemon/sanctuary-host-supervisor.js +95 -0
- package/dist/heart/daemon/sanctuary-package-management.js +103 -0
- package/dist/heart/daemon/sanctuary-scheduler-liveness.js +227 -0
- package/dist/heart/daemon/sanctuary-scheduler-origin.js +132 -0
- package/dist/heart/daemon/sanctuary-telegram-authority-entry.js +445 -0
- package/dist/heart/daemon/sanctuary-telegram-authority-gateway.js +585 -0
- package/dist/heart/daemon/sanctuary-telegram-authority-service.js +630 -0
- package/dist/heart/daemon/sense-manager.js +699 -48
- package/dist/heart/daemon/session-id-resolver.js +131 -0
- package/dist/heart/daemon/skill-management-installer.js +1 -1
- package/dist/heart/daemon/socket-client.js +180 -12
- package/dist/heart/daemon/stale-bundle-prune.js +113 -0
- package/dist/heart/daemon/startup-tui.js +339 -0
- package/dist/heart/daemon/supercronic-supervisor.js +282 -0
- package/dist/heart/daemon/task-scheduler.js +117 -39
- package/dist/heart/daemon/terminal-ui.js +499 -0
- package/dist/heart/daemon/thoughts.js +138 -54
- package/dist/heart/daemon/up-progress.js +366 -0
- package/dist/heart/daemon/vault-items.js +56 -0
- package/dist/heart/delegation.js +1 -4
- package/dist/heart/external-events/router.js +1430 -0
- package/dist/heart/frontend-approval-runtime.js +506 -0
- package/dist/heart/frontend-journal.js +215 -0
- package/dist/heart/frontend-session-service.js +237 -0
- package/dist/heart/frontend-socket-client.js +164 -0
- package/dist/heart/frontend-socket.js +400 -0
- package/dist/heart/habits/habit-cancel.js +810 -0
- package/dist/heart/habits/habit-lifecycle.js +1327 -0
- package/dist/heart/habits/habit-migration.js +194 -0
- package/dist/heart/habits/habit-parser.js +278 -0
- package/dist/heart/habits/habit-runtime-state.js +93 -0
- package/dist/heart/habits/habit-scheduler.js +653 -0
- package/dist/heart/habits/habit-session-summary.js +318 -0
- package/dist/heart/habits/habit-session.js +636 -0
- package/dist/heart/{daemon → hatch}/hatch-flow.js +55 -62
- package/dist/heart/{daemon → hatch}/hatch-specialist.js +6 -8
- package/dist/heart/{daemon → hatch}/specialist-prompt.js +12 -9
- package/dist/heart/{daemon → hatch}/specialist-tools.js +53 -18
- package/dist/heart/identity.js +196 -59
- package/dist/heart/kept-notes.js +289 -0
- package/dist/heart/kicks.js +1 -1
- package/dist/heart/machine-identity.js +161 -0
- package/dist/heart/mail-import-discovery.js +356 -0
- package/dist/heart/mailbox/mailbox-http-hooks.js +98 -0
- package/dist/heart/mailbox/mailbox-http-response.js +7 -0
- package/dist/heart/mailbox/mailbox-http-routes.js +398 -0
- package/dist/heart/mailbox/mailbox-http-static.js +103 -0
- package/dist/heart/mailbox/mailbox-http-transport.js +129 -0
- package/dist/heart/mailbox/mailbox-http.js +99 -0
- package/dist/heart/mailbox/mailbox-read.js +39 -0
- package/dist/heart/mailbox/mailbox-types.js +27 -0
- package/dist/heart/mailbox/mailbox-view.js +197 -0
- package/dist/heart/mailbox/readers/agent-machine.js +428 -0
- package/dist/heart/mailbox/readers/continuity-readers.js +329 -0
- package/dist/heart/mailbox/readers/mail.js +375 -0
- package/dist/heart/mailbox/readers/runtime-readers.js +823 -0
- package/dist/heart/mailbox/readers/sessions.js +231 -0
- package/dist/heart/mailbox/readers/shared.js +111 -0
- package/dist/heart/mcp/mcp-server.js +696 -0
- package/dist/heart/migrate-config.js +100 -0
- package/dist/heart/model-capabilities.js +11 -0
- package/dist/heart/orientation-frame.js +345 -0
- package/dist/heart/platform.js +81 -0
- package/dist/heart/primitives.js +1 -1
- package/dist/heart/private-runtime/decision-renderer.js +158 -0
- package/dist/heart/private-runtime/index.js +18 -0
- package/dist/heart/private-runtime/ledger.js +221 -0
- package/dist/heart/private-runtime/policy.js +252 -0
- package/dist/heart/private-runtime/types.js +2 -0
- package/dist/heart/progress-story.js +1 -1
- package/dist/heart/provider-attempt.js +137 -0
- package/dist/heart/provider-binding-resolver.js +273 -0
- package/dist/heart/provider-credentials.js +449 -0
- package/dist/heart/provider-failover.js +311 -0
- package/dist/heart/provider-models.js +96 -0
- package/dist/heart/provider-ping.js +265 -0
- package/dist/heart/provider-readiness-cache.js +119 -0
- package/dist/heart/provider-visibility.js +188 -0
- package/dist/heart/providers/anthropic-token.js +131 -0
- package/dist/heart/providers/anthropic.js +173 -60
- package/dist/heart/providers/azure.js +23 -11
- package/dist/heart/providers/error-classification.js +127 -0
- package/dist/heart/providers/github-copilot.js +34 -38
- package/dist/heart/providers/minimax-vlm.js +189 -0
- package/dist/heart/providers/minimax.js +26 -8
- package/dist/heart/providers/openai-codex-token.js +349 -0
- package/dist/heart/providers/openai-codex.js +55 -40
- package/dist/heart/providers/openai-compatible.js +177 -0
- package/dist/heart/run-ledger.js +247 -0
- package/dist/heart/runtime-capability-check.js +170 -0
- package/dist/heart/runtime-credentials.js +607 -0
- package/dist/heart/runtime-cwd.js +87 -0
- package/dist/heart/sense-truth.js +22 -4
- package/dist/heart/session-activity.js +92 -24
- package/dist/heart/session-events.js +1504 -0
- package/dist/heart/session-playback-cli-main.js +5 -0
- package/dist/heart/session-playback-cli.js +36 -0
- package/dist/heart/session-playback.js +231 -0
- package/dist/heart/session-redaction-repair-cli-main.js +31 -0
- package/dist/heart/session-redaction-repair.js +552 -0
- package/dist/heart/session-stats-cli-main.js +5 -0
- package/dist/heart/session-stats.js +183 -0
- package/dist/heart/session-transcript.js +133 -0
- package/dist/heart/start-of-turn-packet.js +372 -0
- package/dist/heart/steward-policy.js +618 -0
- package/dist/heart/streaming.js +604 -207
- package/dist/heart/structured-output.js +196 -0
- package/dist/heart/sync-classification.js +176 -0
- package/dist/heart/sync.js +449 -0
- package/dist/heart/target-resolution.js +11 -7
- package/dist/heart/tempo.js +93 -0
- package/dist/heart/temporal-view.js +41 -0
- package/dist/heart/timeouts.js +101 -0
- package/dist/heart/tool-activity-callbacks.js +59 -0
- package/dist/heart/tool-approval.js +382 -0
- package/dist/heart/tool-description.js +155 -0
- package/dist/heart/tool-friction.js +55 -0
- package/dist/heart/tool-loop.js +200 -0
- package/dist/heart/turn-context.js +482 -0
- package/dist/heart/turn-execution-lease.js +30 -0
- package/dist/heart/{daemon → versioning}/ouro-bot-global-installer.js +6 -5
- package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
- package/dist/heart/versioning/ouro-path-installer.js +432 -0
- package/dist/heart/versioning/ouro-recovery-launcher.js +76 -0
- package/dist/heart/versioning/ouro-version-manager.js +409 -0
- package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
- package/dist/heart/{daemon → versioning}/update-checker.js +6 -1
- package/dist/heart/versioning/update-hooks.js +154 -0
- package/dist/heart/versioning/version-intent.js +114 -0
- package/dist/heart/work-card.js +380 -0
- package/dist/mailbox-ui/assets/index-Cyv4mw3Y.js +1 -0
- package/dist/mailbox-ui/assets/index-Du_9G9WO.css +1 -0
- package/dist/mailbox-ui/assets/vendor-CcN1XpQ9.js +61 -0
- package/dist/mailbox-ui/index.html +16 -0
- package/dist/mailroom/attention.js +167 -0
- package/dist/mailroom/autonomy.js +209 -0
- package/dist/mailroom/blob-store.js +889 -0
- package/dist/mailroom/body-cache.js +61 -0
- package/dist/mailroom/cache-sync-cli.js +58 -0
- package/dist/mailroom/core.js +803 -0
- package/dist/mailroom/entry.js +160 -0
- package/dist/mailroom/file-store.js +568 -0
- package/dist/mailroom/hosted-cache-sync.js +384 -0
- package/dist/mailroom/mbox-import.js +393 -0
- package/dist/mailroom/migration.js +164 -0
- package/dist/mailroom/outbound.js +380 -0
- package/dist/mailroom/policy.js +263 -0
- package/dist/mailroom/reader.js +292 -0
- package/dist/mailroom/search-cache.js +513 -0
- package/dist/mailroom/search-relevance.js +319 -0
- package/dist/mailroom/smtp-ingress.js +176 -0
- package/dist/mailroom/source-state.js +176 -0
- package/dist/mailroom/thread.js +109 -0
- package/dist/mailroom/travel-extract.js +89 -0
- package/dist/mind/bundle-manifest.js +24 -2
- package/dist/mind/context.js +309 -111
- package/dist/mind/desk-section.js +362 -0
- package/dist/mind/diary-integrity.js +60 -0
- package/dist/mind/{memory.js → diary.js} +68 -77
- package/dist/mind/embedding-provider.js +60 -0
- package/dist/mind/file-state.js +179 -0
- package/dist/mind/{associative-recall.js → note-search.js} +47 -58
- package/dist/mind/obligation-steering.js +199 -3
- package/dist/mind/pending.js +36 -10
- package/dist/mind/prompt-budget.js +479 -0
- package/dist/mind/prompt-refresh.js +3 -2
- package/dist/mind/prompt.js +1050 -189
- package/dist/mind/provenance-trust.js +26 -0
- package/dist/mind/record-paths.js +312 -0
- package/dist/mind/scrutiny.js +173 -0
- package/dist/mind/session-transaction.js +564 -0
- package/dist/nerves/cli-logging.js +7 -1
- package/dist/nerves/coverage/audit-rules.js +15 -6
- package/dist/nerves/coverage/audit.js +28 -2
- package/dist/nerves/coverage/cli.js +1 -1
- package/dist/nerves/coverage/contract.js +5 -5
- package/dist/nerves/coverage/file-completeness.js +139 -5
- package/dist/nerves/coverage/run-artifacts.js +55 -35
- package/dist/nerves/coverage/source-scanner.js +4 -4
- package/dist/nerves/event-buffer.js +111 -0
- package/dist/nerves/index.js +293 -9
- package/dist/nerves/observation.js +20 -0
- package/dist/nerves/redact.js +79 -0
- package/dist/nerves/review/cli-main.js +5 -0
- package/dist/nerves/review/cli.js +156 -0
- package/dist/nerves/review/core.js +159 -0
- package/dist/nerves/runtime.js +17 -1
- package/dist/repertoire/ado-client.js +15 -56
- package/dist/repertoire/ado-semantic.js +16 -10
- package/dist/repertoire/api-client.js +97 -0
- package/dist/repertoire/bitwarden-store.js +1045 -0
- package/dist/repertoire/bundle-templates.js +71 -0
- package/dist/repertoire/bw-installer.js +180 -0
- package/dist/repertoire/coding/codex-jsonl.js +64 -0
- package/dist/repertoire/coding/context-pack.js +331 -0
- package/dist/repertoire/coding/feedback.js +174 -32
- package/dist/repertoire/coding/index.js +15 -3
- package/dist/repertoire/coding/manager.js +159 -10
- package/dist/repertoire/coding/spawner.js +58 -11
- package/dist/repertoire/coding/tools.js +221 -15
- package/dist/repertoire/coding/workbench-client.js +272 -0
- package/dist/repertoire/coding/workbench-manager.js +346 -0
- package/dist/repertoire/commerce-errors.js +109 -0
- package/dist/repertoire/commerce-self-test.js +156 -0
- package/dist/repertoire/credential-access.js +178 -0
- package/dist/repertoire/desk/classifier.js +362 -0
- package/dist/repertoire/duffel-client.js +185 -0
- package/dist/repertoire/github-client.js +14 -55
- package/dist/repertoire/graph-client.js +11 -52
- package/dist/repertoire/guardrails.js +811 -18
- package/dist/repertoire/mcp-client.js +44 -3
- package/dist/repertoire/mcp-manager.js +427 -139
- package/dist/repertoire/mcp-tools.js +124 -0
- package/dist/repertoire/plugin-mcp.js +175 -0
- package/dist/repertoire/plugins.js +253 -0
- package/dist/repertoire/relationship-authorization.js +322 -0
- package/dist/repertoire/shell-sessions.js +134 -0
- package/dist/repertoire/skills.js +48 -4
- package/dist/repertoire/stripe-client.js +131 -0
- package/dist/repertoire/tool-arguments.js +170 -0
- package/dist/repertoire/tool-results.js +29 -0
- package/dist/repertoire/tools-a2a.js +699 -0
- package/dist/repertoire/tools-attachments.js +322 -0
- package/dist/repertoire/tools-awaiting.js +575 -0
- package/dist/repertoire/tools-base.js +77 -1138
- package/dist/repertoire/tools-bluebubbles.js +2 -0
- package/dist/repertoire/tools-bridge.js +144 -0
- package/dist/repertoire/tools-bundle.js +993 -0
- package/dist/repertoire/tools-commerce.js +253 -0
- package/dist/repertoire/tools-config.js +186 -0
- package/dist/repertoire/tools-continuity.js +575 -0
- package/dist/repertoire/tools-credential.js +383 -0
- package/dist/repertoire/tools-evolution.js +527 -0
- package/dist/repertoire/tools-files.js +344 -0
- package/dist/repertoire/tools-flight.js +290 -0
- package/dist/repertoire/tools-flow.js +119 -0
- package/dist/repertoire/tools-github.js +3 -8
- package/dist/repertoire/tools-habits.js +103 -0
- package/dist/repertoire/tools-mail.js +1828 -0
- package/dist/repertoire/tools-notes.js +485 -0
- package/dist/repertoire/tools-obligations.js +182 -0
- package/dist/repertoire/tools-orientation.js +31 -0
- package/dist/repertoire/tools-record.js +482 -0
- package/dist/repertoire/tools-rsvp.js +139 -0
- package/dist/repertoire/tools-runtime.js +150 -0
- package/dist/repertoire/tools-sanctuary-host.js +202 -0
- package/dist/repertoire/tools-session.js +1133 -0
- package/dist/repertoire/tools-shell.js +149 -0
- package/dist/repertoire/tools-steward-policy.js +119 -0
- package/dist/repertoire/tools-stripe.js +224 -0
- package/dist/repertoire/tools-surface.js +369 -0
- package/dist/repertoire/tools-teams.js +12 -39
- package/dist/repertoire/tools-telegram-contacts.js +37 -0
- package/dist/repertoire/tools-travel.js +125 -0
- package/dist/repertoire/tools-trip.js +982 -0
- package/dist/repertoire/tools-unraid.js +531 -0
- package/dist/repertoire/tools-user-profile.js +146 -0
- package/dist/repertoire/tools-vault.js +40 -0
- package/dist/repertoire/tools-voice.js +145 -0
- package/dist/repertoire/tools.js +494 -148
- package/dist/repertoire/travel-api-client.js +360 -0
- package/dist/repertoire/unraid-client.js +155 -0
- package/dist/repertoire/unraid-restart.js +339 -0
- package/dist/repertoire/user-profile.js +131 -0
- package/dist/repertoire/vault-setup.js +246 -0
- package/dist/repertoire/vault-unlock.js +628 -0
- package/dist/rsvp/aisleplanner-client.js +354 -0
- package/dist/rsvp/cli.js +926 -0
- package/dist/rsvp/config.js +327 -0
- package/dist/rsvp/cutover.js +433 -0
- package/dist/rsvp/diagnostics.js +232 -0
- package/dist/rsvp/diff-renderer.js +176 -0
- package/dist/rsvp/habit-policy.js +110 -0
- package/dist/rsvp/habit-stage.js +123 -0
- package/dist/rsvp/incident-bundle.js +97 -0
- package/dist/rsvp/migration.js +204 -0
- package/dist/rsvp/native-habit-runner.js +630 -0
- package/dist/rsvp/outbound-state.js +628 -0
- package/dist/rsvp/query.js +118 -0
- package/dist/rsvp/replay.js +233 -0
- package/dist/rsvp/snapshot.js +232 -0
- package/dist/rsvp/spend-ledger.js +175 -0
- package/dist/scripts/claude-code-hook.js +41 -0
- package/dist/scripts/claude-code-stop-hook.js +47 -0
- package/dist/senses/a2a-entry.js +115 -0
- package/dist/senses/acp-server.js +386 -0
- package/dist/senses/attention-queue.js +186 -0
- package/dist/senses/await-turn-message.js +58 -0
- package/dist/senses/bluebubbles/active-turns.js +216 -0
- package/dist/senses/bluebubbles/attachment-cache.js +53 -0
- package/dist/senses/bluebubbles/attachment-download.js +137 -0
- package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +355 -40
- package/dist/senses/bluebubbles/context-packet.js +207 -0
- package/dist/senses/bluebubbles/context-smoke.js +144 -0
- package/dist/senses/bluebubbles/entry.js +90 -0
- package/dist/senses/{bluebubbles-inbound-log.js → bluebubbles/inbound-log.js} +21 -3
- package/dist/senses/bluebubbles/index.js +3841 -0
- package/dist/senses/bluebubbles/latest-turn.js +311 -0
- package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -71
- package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +159 -28
- package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +24 -13
- package/dist/senses/bluebubbles/outbound-state.js +308 -0
- package/dist/senses/bluebubbles/processed-log.js +133 -0
- package/dist/senses/bluebubbles/reaction-policy.js +47 -0
- package/dist/senses/bluebubbles/replay.js +284 -0
- package/dist/senses/{bluebubbles-runtime-state.js → bluebubbles/runtime-state.js} +30 -2
- package/dist/senses/bluebubbles/semantic-receipts.js +1986 -0
- package/dist/senses/{bluebubbles-session-cleanup.js → bluebubbles/session-cleanup.js} +1 -1
- package/dist/senses/bluebubbles/webhook-registration.js +228 -0
- package/dist/senses/bluebubbles-meta-guard.js +39 -0
- package/dist/senses/cli/bracketed-paste.js +82 -0
- package/dist/senses/cli/image-paste.js +287 -0
- package/dist/senses/cli/image-ref-navigation.js +75 -0
- package/dist/senses/cli/ink-app.js +156 -0
- package/dist/senses/cli/inline-diff.js +64 -0
- package/dist/senses/cli/input-keys.js +174 -0
- package/dist/senses/cli/kill-ring.js +86 -0
- package/dist/senses/cli/message-list.js +51 -0
- package/dist/senses/cli/ouro-tui.js +607 -0
- package/dist/senses/cli/spinner-imperative.js +135 -0
- package/dist/senses/cli/spinner.js +101 -0
- package/dist/senses/cli/status-line.js +60 -0
- package/dist/senses/cli/streaming-markdown.js +526 -0
- package/dist/senses/cli/tool-display.js +85 -0
- package/dist/senses/cli/tool-render.js +85 -0
- package/dist/senses/cli/tui-store.js +241 -0
- package/dist/senses/cli/virtual-list.js +35 -0
- package/dist/senses/cli-entry.js +60 -8
- package/dist/senses/cli.js +604 -320
- package/dist/senses/commands.js +66 -3
- package/dist/senses/context-packet-ledger.js +371 -0
- package/dist/senses/context-packets.js +154 -0
- package/dist/senses/habit-lifecycle-guard.js +24 -0
- package/dist/senses/habit-turn-message.js +160 -0
- package/dist/senses/ingress-evidence.js +2 -0
- package/dist/senses/mail-entry.js +66 -0
- package/dist/senses/mail.js +524 -0
- package/dist/senses/pipeline.js +1115 -204
- package/dist/senses/private-runtime-worker.js +859 -0
- package/dist/senses/private-runtime.js +1517 -0
- package/dist/senses/proactive-content-guard.js +51 -0
- package/dist/senses/root-host-approval-port.js +345 -0
- package/dist/senses/root-host-approval-runtime.js +393 -0
- package/dist/senses/sanctuary-authority-resident.js +102 -0
- package/dist/senses/sanctuary-download-credit-presentation.js +31 -0
- package/dist/senses/sanctuary-full-visibility-contract.js +302 -0
- package/dist/senses/sanctuary-grounding.js +118 -0
- package/dist/senses/sanctuary-health-acceptance-probe-entry.js +12 -0
- package/dist/senses/sanctuary-health-acceptance-probe.js +774 -0
- package/dist/senses/sanctuary-health-runner.js +100 -0
- package/dist/senses/sanctuary-health.js +408 -0
- package/dist/senses/sanctuary-install-state-contract.js +126 -0
- package/dist/senses/sanctuary-interactive-control.js +410 -0
- package/dist/senses/sanctuary-media-catalog-contract.js +297 -0
- package/dist/senses/sanctuary-media-optimization.js +514 -0
- package/dist/senses/sanctuary-runtime.js +340 -0
- package/dist/senses/sanctuary-sab.js +85 -0
- package/dist/senses/sanctuary-storage-optimization-contract.js +32 -0
- package/dist/senses/shared-turn.js +772 -0
- package/dist/senses/surface-tool.js +109 -0
- package/dist/senses/teams-entry.js +60 -8
- package/dist/senses/teams.js +519 -218
- package/dist/senses/telegram-admission.js +718 -0
- package/dist/senses/telegram-approval-runtime.js +793 -0
- package/dist/senses/telegram-attachments.js +186 -0
- package/dist/senses/telegram-audit-ledger.js +220 -0
- package/dist/senses/telegram-authority-transport.js +231 -0
- package/dist/senses/telegram-client.js +1749 -0
- package/dist/senses/telegram-effect-adapter.js +896 -0
- package/dist/senses/telegram-entry.js +83 -0
- package/dist/senses/telegram.js +2324 -0
- package/dist/senses/trust-gate.js +104 -9
- package/dist/senses/voice/audio-playback.js +237 -0
- package/dist/senses/voice/audio-routing.js +119 -0
- package/dist/senses/voice/elevenlabs.js +202 -0
- package/dist/senses/voice/floor-control.js +431 -0
- package/dist/senses/voice/floor-controller.js +115 -0
- package/dist/senses/voice/golden-path.js +116 -0
- package/dist/senses/voice/index.js +29 -0
- package/dist/senses/voice/meeting.js +113 -0
- package/dist/senses/voice/outbound.js +190 -0
- package/dist/senses/voice/phone.js +33 -0
- package/dist/senses/voice/playback.js +139 -0
- package/dist/senses/voice/realtime-eval.js +496 -0
- package/dist/senses/voice/realtime-trace.js +531 -0
- package/dist/senses/voice/transcript.js +70 -0
- package/dist/senses/voice/turn.js +191 -0
- package/dist/senses/voice/twilio-phone-runtime.js +807 -0
- package/dist/senses/voice/twilio-phone.js +5031 -0
- package/dist/senses/voice/types.js +2 -0
- package/dist/senses/voice/whisper.js +161 -0
- package/dist/senses/voice-entry.js +81 -0
- package/dist/senses/voice-realtime-eval-command.js +99 -0
- package/dist/senses/voice-realtime-eval-entry.js +21 -0
- package/dist/senses/voice-twilio-entry.js +87 -0
- package/dist/trips/core.js +138 -0
- package/dist/trips/store.js +265 -0
- package/dist/util/frontmatter.js +69 -0
- package/npm-shrinkwrap.json +8138 -0
- package/package.json +72 -13
- package/skills/agent-commerce.md +113 -0
- package/skills/browser-navigation.md +117 -0
- package/skills/commerce-setup-guide.md +116 -0
- package/skills/commerce-setup.md +84 -0
- package/skills/configure-dev-tools.md +99 -0
- package/skills/travel-planning.md +138 -0
- package/dist/heart/daemon/auth-flow.js +0 -430
- package/dist/heart/daemon/ouro-path-installer.js +0 -260
- package/dist/heart/daemon/ouro-version-manager.js +0 -171
- package/dist/heart/daemon/update-hooks.js +0 -138
- package/dist/heart/obligations.js +0 -191
- package/dist/heart/safe-workspace.js +0 -228
- package/dist/heart/session-recall.js +0 -116
- package/dist/mind/friends/channel.js +0 -84
- package/dist/mind/friends/group-context.js +0 -144
- package/dist/mind/friends/resolver.js +0 -84
- package/dist/mind/friends/store-file.js +0 -190
- package/dist/mind/friends/store.js +0 -4
- package/dist/mind/friends/tokens.js +0 -26
- package/dist/mind/friends/trust-explanation.js +0 -74
- package/dist/mind/friends/types.js +0 -29
- package/dist/repertoire/tasks/board.js +0 -134
- package/dist/repertoire/tasks/index.js +0 -224
- package/dist/repertoire/tasks/lifecycle.js +0 -80
- package/dist/repertoire/tasks/middleware.js +0 -65
- package/dist/repertoire/tasks/parser.js +0 -173
- package/dist/repertoire/tasks/scanner.js +0 -132
- package/dist/repertoire/tasks/transitions.js +0 -144
- package/dist/senses/bluebubbles-entry.js +0 -13
- package/dist/senses/bluebubbles.js +0 -1181
- package/dist/senses/debug-activity.js +0 -154
- package/dist/senses/inner-dialog-worker.js +0 -90
- package/dist/senses/inner-dialog.js +0 -525
- package/subagents/README.md +0 -7
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
- /package/dist/{repertoire/tasks/types.js → heart/attachments/sources/adapter.js} +0 -0
- /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
- /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
- /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
- /package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +0 -0
|
@@ -0,0 +1,1504 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.EVENT_CONTENT_MAX_CHARS = void 0;
|
|
37
|
+
exports.materializeApprovalTerminal = materializeApprovalTerminal;
|
|
38
|
+
exports.attachApprovalSuspensionCheckpoint = attachApprovalSuspensionCheckpoint;
|
|
39
|
+
exports.listApprovalSuspensionCheckpoints = listApprovalSuspensionCheckpoints;
|
|
40
|
+
exports.removeApprovalSuspensionCheckpoint = removeApprovalSuspensionCheckpoint;
|
|
41
|
+
exports.truncateLargeEventContent = truncateLargeEventContent;
|
|
42
|
+
exports.capStructuredRecordString = capStructuredRecordString;
|
|
43
|
+
exports.capStructuredRecordStringArray = capStructuredRecordStringArray;
|
|
44
|
+
exports.capStructuredRecordStringLeaves = capStructuredRecordStringLeaves;
|
|
45
|
+
exports.normalizeContinuityState = normalizeContinuityState;
|
|
46
|
+
exports.validateSessionMessages = validateSessionMessages;
|
|
47
|
+
exports.detectDuplicateToolCallIds = detectDuplicateToolCallIds;
|
|
48
|
+
exports.repairSessionMessages = repairSessionMessages;
|
|
49
|
+
exports.migrateToolNames = migrateToolNames;
|
|
50
|
+
exports.sanitizeProviderMessages = sanitizeProviderMessages;
|
|
51
|
+
exports.stampIngressTime = stampIngressTime;
|
|
52
|
+
exports.getIngressTime = getIngressTime;
|
|
53
|
+
exports.stampIngressRelations = stampIngressRelations;
|
|
54
|
+
exports.getIngressRelations = getIngressRelations;
|
|
55
|
+
exports.projectedSessionEventIds = projectedSessionEventIds;
|
|
56
|
+
exports.projectProviderMessages = projectProviderMessages;
|
|
57
|
+
exports.annotateMessageTimestamps = annotateMessageTimestamps;
|
|
58
|
+
exports.bestEventTimestamp = bestEventTimestamp;
|
|
59
|
+
exports.formatSessionEventTimestamp = formatSessionEventTimestamp;
|
|
60
|
+
exports.extractEventText = extractEventText;
|
|
61
|
+
exports.deriveSessionChronology = deriveSessionChronology;
|
|
62
|
+
exports.describeCurrentSessionTiming = describeCurrentSessionTiming;
|
|
63
|
+
exports.migrateLegacySessionEnvelope = migrateLegacySessionEnvelope;
|
|
64
|
+
exports.isExactRawSessionRedactionMarker = isExactRawSessionRedactionMarker;
|
|
65
|
+
exports.selectEffectiveSessionEvents = selectEffectiveSessionEvents;
|
|
66
|
+
exports.parseSessionEnvelope = parseSessionEnvelope;
|
|
67
|
+
exports.loadSessionEnvelopeFile = loadSessionEnvelopeFile;
|
|
68
|
+
exports.buildCanonicalSessionEnvelope = buildCanonicalSessionEnvelope;
|
|
69
|
+
exports.appendSyntheticAssistantEvent = appendSyntheticAssistantEvent;
|
|
70
|
+
const fs = __importStar(require("fs"));
|
|
71
|
+
const node_crypto_1 = require("node:crypto");
|
|
72
|
+
const node_util_1 = require("node:util");
|
|
73
|
+
const runtime_1 = require("../nerves/runtime");
|
|
74
|
+
const structured_output_1 = require("./structured-output");
|
|
75
|
+
const APPROVAL_TERMINAL_TEXT = {
|
|
76
|
+
denied: "approval denied by requester; the protected action was not executed",
|
|
77
|
+
expired: "approval expired before execution; the protected action was not executed",
|
|
78
|
+
drifted: "approval became invalid before execution; the protected action was not executed",
|
|
79
|
+
abandoned_before_attempt: "approval was abandoned before execution; the protected action was not executed; request a fresh approval",
|
|
80
|
+
attempted_indeterminate: "execution may have occurred; do not retry automatically",
|
|
81
|
+
};
|
|
82
|
+
function messagesRevision(messages) {
|
|
83
|
+
return (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(messages), "utf8").digest("hex");
|
|
84
|
+
}
|
|
85
|
+
function hasExactMaterializedApprovalPair(messages, checkpoint, toolCallId, content) {
|
|
86
|
+
if (messages.length < 2)
|
|
87
|
+
return false;
|
|
88
|
+
const assistant = messages.at(-2);
|
|
89
|
+
const tool = messages.at(-1);
|
|
90
|
+
const expectedTool = {
|
|
91
|
+
role: "tool",
|
|
92
|
+
tool_call_id: toolCallId,
|
|
93
|
+
content,
|
|
94
|
+
};
|
|
95
|
+
return assistant.role === "assistant"
|
|
96
|
+
&& tool.role === "tool"
|
|
97
|
+
&& messageFingerprint(assistant) === messageFingerprint(checkpoint.frozenAssistantMessage)
|
|
98
|
+
&& messageFingerprint(tool) === messageFingerprint(expectedTool);
|
|
99
|
+
}
|
|
100
|
+
function materializeApprovalTerminal(input) {
|
|
101
|
+
const messages = structuredClone(input.messages);
|
|
102
|
+
if (input.record.state === "session_head_changed") {
|
|
103
|
+
return {
|
|
104
|
+
messages,
|
|
105
|
+
materialized: false,
|
|
106
|
+
resumeProvider: false,
|
|
107
|
+
directNotice: "the session changed before this approval could be applied; the protected action was not executed",
|
|
108
|
+
revision: messagesRevision(messages),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const content = input.record.state === "succeeded" || input.record.state === "failed"
|
|
112
|
+
? input.record.result
|
|
113
|
+
: APPROVAL_TERMINAL_TEXT[input.record.state];
|
|
114
|
+
if (!content)
|
|
115
|
+
throw new Error(`approval state ${input.record.state} cannot be materialized`);
|
|
116
|
+
const resumeProvider = input.record.state === "succeeded" || input.record.state === "failed" || input.record.state === "denied";
|
|
117
|
+
const exactPair = hasExactMaterializedApprovalPair(messages, input.checkpoint, input.record.toolCallId, content);
|
|
118
|
+
if (exactPair) {
|
|
119
|
+
return {
|
|
120
|
+
messages,
|
|
121
|
+
materialized: false,
|
|
122
|
+
resumeProvider,
|
|
123
|
+
...(resumeProvider ? {} : { directNotice: content }),
|
|
124
|
+
...(input.record.state === "attempted_indeterminate" ? { severity: "high" } : {}),
|
|
125
|
+
revision: messagesRevision(messages),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const advanced = input.currentSessionRevision !== input.checkpoint.suspendedSessionRevision;
|
|
129
|
+
if (advanced) {
|
|
130
|
+
const notice = input.record.state === "succeeded"
|
|
131
|
+
? "the approved action completed, but the session changed; no model continuation was run"
|
|
132
|
+
: input.record.state === "failed"
|
|
133
|
+
? "the approved action failed, but the session changed; no model continuation was run"
|
|
134
|
+
: input.record.state === "attempted_indeterminate"
|
|
135
|
+
? `${APPROVAL_TERMINAL_TEXT.attempted_indeterminate}; the session changed`
|
|
136
|
+
: "the session changed before this approval could be applied; the protected action was not executed";
|
|
137
|
+
return {
|
|
138
|
+
messages,
|
|
139
|
+
materialized: false,
|
|
140
|
+
resumeProvider: false,
|
|
141
|
+
directNotice: notice,
|
|
142
|
+
revision: messagesRevision(messages),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
if (input.materializedApprovalIds?.includes(input.record.approvalId)) {
|
|
146
|
+
return {
|
|
147
|
+
messages,
|
|
148
|
+
materialized: false,
|
|
149
|
+
resumeProvider,
|
|
150
|
+
...(resumeProvider ? {} : { directNotice: content }),
|
|
151
|
+
...(input.record.state === "attempted_indeterminate" ? { severity: "high" } : {}),
|
|
152
|
+
revision: messagesRevision(messages),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
const frozen = structuredClone(input.checkpoint.frozenAssistantMessage);
|
|
156
|
+
messages.push(frozen, { role: "tool", tool_call_id: input.record.toolCallId, content });
|
|
157
|
+
const directNotice = resumeProvider ? undefined : content;
|
|
158
|
+
(0, runtime_1.emitNervesEvent)({
|
|
159
|
+
component: "mind",
|
|
160
|
+
event: "mind.approval_terminal_materialized",
|
|
161
|
+
message: "materialized approval terminal transcript pair",
|
|
162
|
+
meta: { approvalId: input.record.approvalId, state: input.record.state, resumeProvider },
|
|
163
|
+
});
|
|
164
|
+
return {
|
|
165
|
+
messages,
|
|
166
|
+
materialized: true,
|
|
167
|
+
resumeProvider,
|
|
168
|
+
...(directNotice ? { directNotice } : {}),
|
|
169
|
+
...(input.record.state === "attempted_indeterminate" ? { severity: "high" } : {}),
|
|
170
|
+
revision: messagesRevision(messages),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
const APPROVAL_HASH = /^[a-f0-9]{64}$/;
|
|
174
|
+
function normalizeApprovalSuspensions(value) {
|
|
175
|
+
if (!Array.isArray(value))
|
|
176
|
+
return [];
|
|
177
|
+
return value.flatMap((item) => {
|
|
178
|
+
if (!item || typeof item !== "object" || Array.isArray(item))
|
|
179
|
+
return [];
|
|
180
|
+
const record = item;
|
|
181
|
+
if (typeof record.approvalId !== "string"
|
|
182
|
+
|| typeof record.checkpointDigest !== "string"
|
|
183
|
+
|| !APPROVAL_HASH.test(record.checkpointDigest)
|
|
184
|
+
|| typeof record.baseSessionRevision !== "string"
|
|
185
|
+
|| !APPROVAL_HASH.test(record.baseSessionRevision)
|
|
186
|
+
|| typeof record.suspendedSessionRevision !== "string"
|
|
187
|
+
|| !APPROVAL_HASH.test(record.suspendedSessionRevision)
|
|
188
|
+
|| typeof record.argumentDigest !== "string"
|
|
189
|
+
|| !APPROVAL_HASH.test(record.argumentDigest)
|
|
190
|
+
|| typeof record.schemaDigest !== "string"
|
|
191
|
+
|| !APPROVAL_HASH.test(record.schemaDigest)
|
|
192
|
+
|| typeof record.toolDigest !== "string"
|
|
193
|
+
|| !APPROVAL_HASH.test(record.toolDigest)
|
|
194
|
+
|| typeof record.policyDigest !== "string"
|
|
195
|
+
|| !APPROVAL_HASH.test(record.policyDigest)
|
|
196
|
+
|| typeof record.preCallDigest !== "string"
|
|
197
|
+
|| !APPROVAL_HASH.test(record.preCallDigest)
|
|
198
|
+
|| !Array.isArray(record.preCallMessages)
|
|
199
|
+
|| !record.frozenAssistantMessage
|
|
200
|
+
|| typeof record.frozenAssistantMessage !== "object"
|
|
201
|
+
|| Array.isArray(record.frozenAssistantMessage))
|
|
202
|
+
return [];
|
|
203
|
+
return [{
|
|
204
|
+
approvalId: record.approvalId,
|
|
205
|
+
checkpointDigest: record.checkpointDigest,
|
|
206
|
+
baseSessionRevision: record.baseSessionRevision,
|
|
207
|
+
suspendedSessionRevision: record.suspendedSessionRevision,
|
|
208
|
+
argumentDigest: record.argumentDigest,
|
|
209
|
+
schemaDigest: record.schemaDigest,
|
|
210
|
+
toolDigest: record.toolDigest,
|
|
211
|
+
policyDigest: record.policyDigest,
|
|
212
|
+
preCallDigest: record.preCallDigest,
|
|
213
|
+
preCallMessages: structuredClone(record.preCallMessages),
|
|
214
|
+
frozenAssistantMessage: structuredClone(record.frozenAssistantMessage),
|
|
215
|
+
}];
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
function attachApprovalSuspensionCheckpoint(envelope, checkpoint) {
|
|
219
|
+
const existing = envelope.approvalSuspensions ?? [];
|
|
220
|
+
const retained = existing.filter((item) => item.approvalId !== checkpoint.approvalId);
|
|
221
|
+
(0, runtime_1.emitNervesEvent)({
|
|
222
|
+
component: "engine",
|
|
223
|
+
event: "engine.approval_checkpoint_attached",
|
|
224
|
+
message: "approval suspension checkpoint attached outside provider projection",
|
|
225
|
+
meta: { approvalId: checkpoint.approvalId },
|
|
226
|
+
});
|
|
227
|
+
return { ...envelope, approvalSuspensions: [...retained, structuredClone(checkpoint)] };
|
|
228
|
+
}
|
|
229
|
+
function listApprovalSuspensionCheckpoints(envelope) {
|
|
230
|
+
return structuredClone(envelope.approvalSuspensions ?? []);
|
|
231
|
+
}
|
|
232
|
+
function removeApprovalSuspensionCheckpoint(envelope, approvalId) {
|
|
233
|
+
const approvalSuspensions = (envelope.approvalSuspensions ?? []).filter((item) => item.approvalId !== approvalId);
|
|
234
|
+
(0, runtime_1.emitNervesEvent)({
|
|
235
|
+
component: "engine",
|
|
236
|
+
event: "engine.approval_checkpoint_removed",
|
|
237
|
+
message: "approval suspension checkpoint removed",
|
|
238
|
+
meta: { approvalId },
|
|
239
|
+
});
|
|
240
|
+
return { ...envelope, approvalSuspensions };
|
|
241
|
+
}
|
|
242
|
+
// Native origins survive replay normalization, but never enter serialized provider or session data.
|
|
243
|
+
const messageSourceEventIds = Symbol("messageSourceEventIds");
|
|
244
|
+
function withMessageSourceEventIds(message, eventIds) {
|
|
245
|
+
if (eventIds)
|
|
246
|
+
Object.defineProperty(message, messageSourceEventIds, { value: eventIds });
|
|
247
|
+
return message;
|
|
248
|
+
}
|
|
249
|
+
exports.EVENT_CONTENT_MAX_CHARS = 256 * 1024;
|
|
250
|
+
function truncateLargeEventContent(content, maxChars) {
|
|
251
|
+
if (typeof content !== "string") {
|
|
252
|
+
return { content, truncated: false, originalLength: 0 };
|
|
253
|
+
}
|
|
254
|
+
if (content.length <= maxChars) {
|
|
255
|
+
return { content, truncated: false, originalLength: content.length };
|
|
256
|
+
}
|
|
257
|
+
const marker = `[truncated — event content exceeded ${maxChars} chars; original length ${content.length} chars]`;
|
|
258
|
+
const remainingBudget = Math.max(0, maxChars - marker.length);
|
|
259
|
+
const headLength = Math.ceil(remainingBudget * 0.75);
|
|
260
|
+
const tailLength = Math.max(0, remainingBudget - headLength);
|
|
261
|
+
return {
|
|
262
|
+
content: `${content.slice(0, headLength)}${marker}${tailLength > 0 ? content.slice(-tailLength) : ""}`,
|
|
263
|
+
truncated: true,
|
|
264
|
+
originalLength: content.length,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
function capStructuredRecordString(value) {
|
|
268
|
+
return truncateLargeEventContent(value, exports.EVENT_CONTENT_MAX_CHARS).content;
|
|
269
|
+
}
|
|
270
|
+
function capStructuredRecordStringArray(values) {
|
|
271
|
+
return values.map((value) => capStructuredRecordString(value));
|
|
272
|
+
}
|
|
273
|
+
function capStructuredRecordStringLeaves(value) {
|
|
274
|
+
if (typeof value === "string") {
|
|
275
|
+
return capStructuredRecordString(value);
|
|
276
|
+
}
|
|
277
|
+
if (Array.isArray(value)) {
|
|
278
|
+
return value.map((item) => capStructuredRecordStringLeaves(item));
|
|
279
|
+
}
|
|
280
|
+
if (!value || typeof value !== "object") {
|
|
281
|
+
return value;
|
|
282
|
+
}
|
|
283
|
+
const capped = {};
|
|
284
|
+
for (const [key, child] of Object.entries(value)) {
|
|
285
|
+
capped[key] = capStructuredRecordStringLeaves(child);
|
|
286
|
+
}
|
|
287
|
+
return capped;
|
|
288
|
+
}
|
|
289
|
+
function formatElapsed(ms) {
|
|
290
|
+
const minutes = Math.max(0, Math.floor(ms / 60000));
|
|
291
|
+
if (minutes < 60)
|
|
292
|
+
return `${minutes}m ago`;
|
|
293
|
+
const hours = Math.floor(minutes / 60);
|
|
294
|
+
if (hours < 24)
|
|
295
|
+
return `${hours}h ago`;
|
|
296
|
+
const days = Math.floor(hours / 24);
|
|
297
|
+
return `${days}d ago`;
|
|
298
|
+
}
|
|
299
|
+
const LEGACY_WRITTEN_NOTE_PREFIX = "mem" + "ory";
|
|
300
|
+
const TOOL_NAME_MIGRATIONS = {
|
|
301
|
+
final_answer: "settle",
|
|
302
|
+
no_response: "observe",
|
|
303
|
+
go_inward: "ponder",
|
|
304
|
+
descend: "ponder",
|
|
305
|
+
[`${LEGACY_WRITTEN_NOTE_PREFIX}_save`]: "diary_write",
|
|
306
|
+
[`${LEGACY_WRITTEN_NOTE_PREFIX}_search`]: "search_facts",
|
|
307
|
+
};
|
|
308
|
+
function normalizeUsage(usage) {
|
|
309
|
+
if (!usage || typeof usage !== "object")
|
|
310
|
+
return null;
|
|
311
|
+
const record = usage;
|
|
312
|
+
if (typeof record.input_tokens !== "number"
|
|
313
|
+
|| typeof record.output_tokens !== "number"
|
|
314
|
+
|| typeof record.reasoning_tokens !== "number"
|
|
315
|
+
|| typeof record.total_tokens !== "number") {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
input_tokens: record.input_tokens,
|
|
320
|
+
output_tokens: record.output_tokens,
|
|
321
|
+
reasoning_tokens: record.reasoning_tokens,
|
|
322
|
+
total_tokens: record.total_tokens,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
function normalizeContinuityState(state) {
|
|
326
|
+
const record = state && typeof state === "object"
|
|
327
|
+
? state
|
|
328
|
+
: null;
|
|
329
|
+
return {
|
|
330
|
+
mustResolveBeforeHandoff: record?.mustResolveBeforeHandoff === true,
|
|
331
|
+
lastFriendActivityAt: typeof record?.lastFriendActivityAt === "string" ? record.lastFriendActivityAt : null,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
function normalizeContent(content) {
|
|
335
|
+
if (content == null)
|
|
336
|
+
return null;
|
|
337
|
+
if (typeof content === "string")
|
|
338
|
+
return content;
|
|
339
|
+
if (!Array.isArray(content))
|
|
340
|
+
return null;
|
|
341
|
+
return content
|
|
342
|
+
.filter((part) => part != null && typeof part === "object")
|
|
343
|
+
.map((part) => ({ ...part }));
|
|
344
|
+
}
|
|
345
|
+
const SYNTHETIC_TIMESTAMP_PREFIX_RE = /^(?:(?:\[(?:just now|-\d+[mhd])\])\s*)+/i;
|
|
346
|
+
function stripSyntheticTimestampPrefix(text) {
|
|
347
|
+
return text.replace(SYNTHETIC_TIMESTAMP_PREFIX_RE, "");
|
|
348
|
+
}
|
|
349
|
+
function sanitizeConversationContent(role, content) {
|
|
350
|
+
if (role !== "user" && role !== "assistant")
|
|
351
|
+
return content;
|
|
352
|
+
if (typeof content === "string")
|
|
353
|
+
return stripSyntheticTimestampPrefix(content);
|
|
354
|
+
if (!Array.isArray(content))
|
|
355
|
+
return content;
|
|
356
|
+
return content.map((part) => {
|
|
357
|
+
if (part.type === "text" && typeof part.text === "string") {
|
|
358
|
+
return { ...part, text: stripSyntheticTimestampPrefix(part.text) };
|
|
359
|
+
}
|
|
360
|
+
return part;
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
function normalizeToolCalls(rawToolCalls) {
|
|
364
|
+
if (!Array.isArray(rawToolCalls))
|
|
365
|
+
return [];
|
|
366
|
+
return rawToolCalls
|
|
367
|
+
.filter((call) => call != null && typeof call === "object")
|
|
368
|
+
.map((call) => {
|
|
369
|
+
const fn = call.function;
|
|
370
|
+
const originalName = typeof fn?.name === "string" ? fn.name : "unknown";
|
|
371
|
+
const migratedName = TOOL_NAME_MIGRATIONS[originalName] ?? originalName;
|
|
372
|
+
return {
|
|
373
|
+
id: typeof call.id === "string" ? call.id : "",
|
|
374
|
+
type: typeof call.type === "string" ? call.type : "function",
|
|
375
|
+
function: {
|
|
376
|
+
name: migratedName,
|
|
377
|
+
arguments: typeof fn?.arguments === "string" ? fn.arguments : JSON.stringify(fn?.arguments ?? ""),
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
function normalizeRole(role) {
|
|
383
|
+
if (role === "developer")
|
|
384
|
+
return "system";
|
|
385
|
+
return role === "system" || role === "user" || role === "assistant" || role === "tool"
|
|
386
|
+
? role
|
|
387
|
+
: "user";
|
|
388
|
+
}
|
|
389
|
+
function normalizeMessage(message) {
|
|
390
|
+
const record = message;
|
|
391
|
+
const role = normalizeRole(record.role);
|
|
392
|
+
const normalizedContent = sanitizeConversationContent(role, normalizeContent(record.content));
|
|
393
|
+
const sourceEventIds = message[messageSourceEventIds];
|
|
394
|
+
if (role === "assistant") {
|
|
395
|
+
return {
|
|
396
|
+
role,
|
|
397
|
+
content: normalizedContent,
|
|
398
|
+
name: typeof record.name === "string" ? record.name : null,
|
|
399
|
+
toolCallId: null,
|
|
400
|
+
toolCalls: normalizeToolCalls(record.tool_calls),
|
|
401
|
+
hadToolCallsField: Array.isArray(record.tool_calls),
|
|
402
|
+
sourceEventIds,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
if (role === "tool") {
|
|
406
|
+
return {
|
|
407
|
+
role,
|
|
408
|
+
content: typeof record.content === "string" ? record.content : "",
|
|
409
|
+
name: null,
|
|
410
|
+
toolCallId: typeof record.tool_call_id === "string" ? record.tool_call_id : null,
|
|
411
|
+
toolCalls: [],
|
|
412
|
+
hadToolCallsField: false,
|
|
413
|
+
sourceEventIds,
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
return {
|
|
417
|
+
role,
|
|
418
|
+
content: normalizedContent ?? "",
|
|
419
|
+
name: typeof record.name === "string" ? record.name : null,
|
|
420
|
+
toolCallId: null,
|
|
421
|
+
toolCalls: [],
|
|
422
|
+
hadToolCallsField: false,
|
|
423
|
+
sourceEventIds,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
function contentText(content) {
|
|
427
|
+
if (typeof content === "string")
|
|
428
|
+
return content;
|
|
429
|
+
if (!Array.isArray(content))
|
|
430
|
+
return "";
|
|
431
|
+
return content
|
|
432
|
+
.map((part) => (part.type === "text" && typeof part.text === "string"
|
|
433
|
+
? part.text
|
|
434
|
+
: ""))
|
|
435
|
+
.filter((text) => text.length > 0)
|
|
436
|
+
.join("");
|
|
437
|
+
}
|
|
438
|
+
function toProviderMessage(message) {
|
|
439
|
+
if (message.role === "assistant") {
|
|
440
|
+
const assistant = {
|
|
441
|
+
role: "assistant",
|
|
442
|
+
content: message.content,
|
|
443
|
+
};
|
|
444
|
+
if (message.name)
|
|
445
|
+
assistant.name = message.name;
|
|
446
|
+
if (message.hadToolCallsField || message.toolCalls.length > 0) {
|
|
447
|
+
assistant.tool_calls = message.toolCalls.map((call) => ({
|
|
448
|
+
id: call.id,
|
|
449
|
+
type: call.type,
|
|
450
|
+
function: {
|
|
451
|
+
name: call.function.name,
|
|
452
|
+
arguments: call.function.arguments,
|
|
453
|
+
},
|
|
454
|
+
}));
|
|
455
|
+
}
|
|
456
|
+
return withMessageSourceEventIds(assistant, message.sourceEventIds);
|
|
457
|
+
}
|
|
458
|
+
if (message.role === "tool") {
|
|
459
|
+
return withMessageSourceEventIds({
|
|
460
|
+
role: "tool",
|
|
461
|
+
content: typeof message.content === "string" ? message.content : contentText(message.content),
|
|
462
|
+
tool_call_id: message.toolCallId ?? "",
|
|
463
|
+
}, message.sourceEventIds);
|
|
464
|
+
}
|
|
465
|
+
if (message.role === "system") {
|
|
466
|
+
return withMessageSourceEventIds({
|
|
467
|
+
role: "system",
|
|
468
|
+
content: typeof message.content === "string" ? message.content : contentText(message.content),
|
|
469
|
+
...(message.name ? { name: message.name } : {}),
|
|
470
|
+
}, message.sourceEventIds);
|
|
471
|
+
}
|
|
472
|
+
return withMessageSourceEventIds({
|
|
473
|
+
role: "user",
|
|
474
|
+
content: (typeof message.content === "string" || Array.isArray(message.content) ? message.content : ""),
|
|
475
|
+
...(message.name ? { name: message.name } : {}),
|
|
476
|
+
}, message.sourceEventIds);
|
|
477
|
+
}
|
|
478
|
+
function messageSourcesAgree(left, right) {
|
|
479
|
+
const leftIds = left[messageSourceEventIds];
|
|
480
|
+
const rightIds = right[messageSourceEventIds];
|
|
481
|
+
return !leftIds || !rightIds || leftIds.length === rightIds.length && leftIds.every((id, index) => id === rightIds[index]);
|
|
482
|
+
}
|
|
483
|
+
function messageFingerprint(message, captureContent = false) {
|
|
484
|
+
const normalized = normalizeMessage(message);
|
|
485
|
+
return JSON.stringify({
|
|
486
|
+
role: normalized.role,
|
|
487
|
+
content: captureContent ? truncateLargeEventContent(normalized.content, exports.EVENT_CONTENT_MAX_CHARS).content : normalized.content,
|
|
488
|
+
name: normalized.name,
|
|
489
|
+
tool_call_id: normalized.toolCallId,
|
|
490
|
+
tool_calls: normalized.toolCalls,
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
function makeEventId(sequence) {
|
|
494
|
+
return `evt-${String(sequence).padStart(6, "0")}`;
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Collapse duplicate event ids to a single entry, last-occurrence-wins.
|
|
498
|
+
*
|
|
499
|
+
* Concurrent writers in older versions of postTurnPersist could each load the
|
|
500
|
+
* envelope, compute `events.length + 1` for the next sequence, and both write
|
|
501
|
+
* an event with the same id. The duplicates would persist in the saved JSON
|
|
502
|
+
* and confuse downstream replay (the same outbound message could appear to
|
|
503
|
+
* have been sent twice from the agent's perspective without the agent knowing
|
|
504
|
+
* it sent it). We dedupe defensively on every load so corrupted sessions
|
|
505
|
+
* self-heal on the next save and so any future race produces a consistent
|
|
506
|
+
* view.
|
|
507
|
+
*/
|
|
508
|
+
function dedupeEventsByIdLastWins(events) {
|
|
509
|
+
// Index id → last position so we can preserve original order while
|
|
510
|
+
// collapsing duplicates to their final occurrence.
|
|
511
|
+
const lastIndexById = new Map();
|
|
512
|
+
for (let i = 0; i < events.length; i++) {
|
|
513
|
+
lastIndexById.set(events[i].id, i);
|
|
514
|
+
}
|
|
515
|
+
return events.filter((event, index) => lastIndexById.get(event.id) === index);
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* The next sequence to assign for a freshly-built event. Uses max(existing
|
|
519
|
+
* sequences) + 1 rather than `events.length + 1` so that gaps from earlier
|
|
520
|
+
* pruning, archive replay, or self-heal dedup never produce a colliding id.
|
|
521
|
+
*/
|
|
522
|
+
function nextEventSequence(existing) {
|
|
523
|
+
return existing.reduce((max, event) => Math.max(max, event.sequence), 0) + 1;
|
|
524
|
+
}
|
|
525
|
+
function validateSessionMessages(messages) {
|
|
526
|
+
const violations = [];
|
|
527
|
+
let prevNonToolRole = null;
|
|
528
|
+
let prevAssistantHadToolCalls = false;
|
|
529
|
+
let sawToolResultSincePrevAssistant = false;
|
|
530
|
+
for (let i = 0; i < messages.length; i++) {
|
|
531
|
+
const msg = normalizeMessage(messages[i]);
|
|
532
|
+
if (msg.role === "system")
|
|
533
|
+
continue;
|
|
534
|
+
if (msg.role === "tool") {
|
|
535
|
+
sawToolResultSincePrevAssistant = true;
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
538
|
+
if (msg.role === "assistant" && prevNonToolRole === "assistant") {
|
|
539
|
+
if (!(prevAssistantHadToolCalls && sawToolResultSincePrevAssistant)) {
|
|
540
|
+
violations.push(`back-to-back assistant at index ${i}`);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
prevAssistantHadToolCalls = msg.role === "assistant" && msg.toolCalls.length > 0;
|
|
544
|
+
sawToolResultSincePrevAssistant = false;
|
|
545
|
+
prevNonToolRole = msg.role;
|
|
546
|
+
}
|
|
547
|
+
for (const collision of detectDuplicateToolCallIds(messages)) {
|
|
548
|
+
violations.push(`duplicate tool_call_id '${collision.id}' across assistant messages at indices ${collision.indices.join(", ")} — provider may reject (MiniMax canonicalizes call_function_<hash>_<n> across turns)`);
|
|
549
|
+
}
|
|
550
|
+
return violations;
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Detect tool_call_ids that appear in more than one assistant message
|
|
554
|
+
* within the conversation. MiniMax-M2.7 in particular emits canonical
|
|
555
|
+
* ids of the form `call_function_<hash>_<n>` and reuses the same id
|
|
556
|
+
* across turns when the same function is called — which causes provider
|
|
557
|
+
* rejections on replay because tool_call_id is supposed to be unique
|
|
558
|
+
* per request. We don't (yet) rewrite these here; this function exists
|
|
559
|
+
* so the sanitize pipeline can surface the collision through nerves
|
|
560
|
+
* (`mind.session_invariant_violation`) and operators can decide.
|
|
561
|
+
*
|
|
562
|
+
* Same-message duplicates (one assistant calling the same id twice)
|
|
563
|
+
* are not collisions — they're a legitimate parallel call shape and
|
|
564
|
+
* would be handled by the assistant's own emit logic. We only flag
|
|
565
|
+
* cross-message reuse.
|
|
566
|
+
*/
|
|
567
|
+
function detectDuplicateToolCallIds(messages) {
|
|
568
|
+
const idsByFirstIndex = new Map();
|
|
569
|
+
for (let i = 0; i < messages.length; i++) {
|
|
570
|
+
const msg = normalizeMessage(messages[i]);
|
|
571
|
+
if (msg.role !== "assistant")
|
|
572
|
+
continue;
|
|
573
|
+
const seenInThisMessage = new Set();
|
|
574
|
+
for (const call of msg.toolCalls) {
|
|
575
|
+
if (!call.id || seenInThisMessage.has(call.id))
|
|
576
|
+
continue;
|
|
577
|
+
seenInThisMessage.add(call.id);
|
|
578
|
+
const indices = idsByFirstIndex.get(call.id) ?? [];
|
|
579
|
+
indices.push(i);
|
|
580
|
+
idsByFirstIndex.set(call.id, indices);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
const collisions = [];
|
|
584
|
+
for (const [id, indices] of idsByFirstIndex) {
|
|
585
|
+
if (indices.length > 1)
|
|
586
|
+
collisions.push({ id, indices });
|
|
587
|
+
}
|
|
588
|
+
return collisions;
|
|
589
|
+
}
|
|
590
|
+
function repairSessionMessages(messages) {
|
|
591
|
+
const normalized = messages.map(normalizeMessage);
|
|
592
|
+
const violations = validateSessionMessages(messages);
|
|
593
|
+
if (violations.length === 0)
|
|
594
|
+
return normalized.map(toProviderMessage);
|
|
595
|
+
const result = [];
|
|
596
|
+
let duplicateAssistantsDropped = 0;
|
|
597
|
+
for (const msg of normalized) {
|
|
598
|
+
if (msg.role === "assistant" && result.length > 0) {
|
|
599
|
+
const prev = result[result.length - 1];
|
|
600
|
+
if (prev.role === "assistant" && prev.toolCalls.length === 0) {
|
|
601
|
+
if (prev.sourceEventIds)
|
|
602
|
+
prev.sourceEventIds = [...new Set([...prev.sourceEventIds, ...(msg.sourceEventIds ?? [])])];
|
|
603
|
+
const prevContent = contentText(prev.content);
|
|
604
|
+
const curContent = contentText(msg.content);
|
|
605
|
+
// Drop the second of two consecutive assistants when the content is
|
|
606
|
+
// byte-identical (after trim) — that's a retry/double-persist artifact,
|
|
607
|
+
// not legitimate continuation. Concatenating them produced visible
|
|
608
|
+
// duplicate text in surfaces. Empty strings still concatenate (could
|
|
609
|
+
// be "" + real content).
|
|
610
|
+
if (prevContent.trim().length > 0 && prevContent.trim() === curContent.trim() && msg.toolCalls.length === 0) {
|
|
611
|
+
duplicateAssistantsDropped += 1;
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
prev.content = `${prevContent}\n\n${curContent}`;
|
|
615
|
+
prev.toolCalls = msg.toolCalls;
|
|
616
|
+
continue;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
result.push(msg);
|
|
620
|
+
}
|
|
621
|
+
if (duplicateAssistantsDropped > 0) {
|
|
622
|
+
(0, runtime_1.emitNervesEvent)({
|
|
623
|
+
level: "info",
|
|
624
|
+
event: "mind.session_duplicate_assistant_dropped",
|
|
625
|
+
component: "mind",
|
|
626
|
+
message: "dropped consecutive assistant messages with identical content (retry/double-persist artifact)",
|
|
627
|
+
meta: { count: duplicateAssistantsDropped },
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
(0, runtime_1.emitNervesEvent)({
|
|
631
|
+
level: "info",
|
|
632
|
+
event: "mind.session_invariant_repair",
|
|
633
|
+
component: "mind",
|
|
634
|
+
message: "repaired session invariant violations",
|
|
635
|
+
meta: { violations },
|
|
636
|
+
});
|
|
637
|
+
return result.map(toProviderMessage);
|
|
638
|
+
}
|
|
639
|
+
function repairToolCallSequences(messages, inlineReasoningStrippedCallIds = new Set()) {
|
|
640
|
+
const normalized = messages.map(normalizeMessage);
|
|
641
|
+
// Position-aware orphan detection. A tool result is orphaned if there is
|
|
642
|
+
// no preceding assistant message in the array whose tool_calls contain the
|
|
643
|
+
// matching id. (The previous logic checked all assistant messages
|
|
644
|
+
// globally, which kept tool results that appeared BEFORE their matching
|
|
645
|
+
// assistant — invalid order — and triggered MiniMax error 2013 on replay.)
|
|
646
|
+
let removed = 0;
|
|
647
|
+
const seenCallIds = new Set();
|
|
648
|
+
const repaired = normalized.filter((msg) => {
|
|
649
|
+
if (msg.role === "assistant") {
|
|
650
|
+
for (const tc of msg.toolCalls)
|
|
651
|
+
seenCallIds.add(tc.id);
|
|
652
|
+
return true;
|
|
653
|
+
}
|
|
654
|
+
if (msg.role !== "tool")
|
|
655
|
+
return true;
|
|
656
|
+
const keep = msg.toolCallId !== null && seenCallIds.has(msg.toolCallId);
|
|
657
|
+
if (!keep)
|
|
658
|
+
removed++;
|
|
659
|
+
return keep;
|
|
660
|
+
});
|
|
661
|
+
if (removed > 0) {
|
|
662
|
+
(0, runtime_1.emitNervesEvent)({
|
|
663
|
+
level: "info",
|
|
664
|
+
event: "mind.session_orphan_tool_result_repair",
|
|
665
|
+
component: "mind",
|
|
666
|
+
message: "removed orphaned tool results from session history",
|
|
667
|
+
meta: { removed },
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
let injected = 0;
|
|
671
|
+
for (let i = 0; i < repaired.length; i++) {
|
|
672
|
+
const msg = repaired[i];
|
|
673
|
+
if (msg.role !== "assistant" || msg.toolCalls.length === 0)
|
|
674
|
+
continue;
|
|
675
|
+
const resultIds = new Set();
|
|
676
|
+
for (let j = i + 1; j < repaired.length; j++) {
|
|
677
|
+
const following = repaired[j];
|
|
678
|
+
if (following.role === "tool" && following.toolCallId !== null) {
|
|
679
|
+
resultIds.add(following.toolCallId);
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
if (following.role === "assistant" || following.role === "user")
|
|
683
|
+
break;
|
|
684
|
+
}
|
|
685
|
+
const missing = msg.toolCalls.filter((toolCall) => !resultIds.has(toolCall.id));
|
|
686
|
+
if (missing.length === 0)
|
|
687
|
+
continue;
|
|
688
|
+
const syntheticResults = missing.map((toolCall) => ({
|
|
689
|
+
role: "tool",
|
|
690
|
+
content: buildSyntheticToolResultMessage(toolCall.id, inlineReasoningStrippedCallIds),
|
|
691
|
+
name: null,
|
|
692
|
+
toolCallId: toolCall.id,
|
|
693
|
+
toolCalls: [],
|
|
694
|
+
hadToolCallsField: false,
|
|
695
|
+
}));
|
|
696
|
+
let insertAt = i + 1;
|
|
697
|
+
while (insertAt < repaired.length && repaired[insertAt].role === "tool")
|
|
698
|
+
insertAt++;
|
|
699
|
+
repaired.splice(insertAt, 0, ...syntheticResults);
|
|
700
|
+
injected += syntheticResults.length;
|
|
701
|
+
}
|
|
702
|
+
if (injected > 0) {
|
|
703
|
+
(0, runtime_1.emitNervesEvent)({
|
|
704
|
+
level: "info",
|
|
705
|
+
event: "mind.session_orphan_tool_call_repair",
|
|
706
|
+
component: "mind",
|
|
707
|
+
message: "injected synthetic tool results for orphaned tool calls",
|
|
708
|
+
meta: { injected },
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
return repaired.map(toProviderMessage);
|
|
712
|
+
}
|
|
713
|
+
function canonicalizeSystemMessageSequence(messages) {
|
|
714
|
+
const normalized = messages.map(normalizeMessage);
|
|
715
|
+
const firstSystemIndex = normalized.findIndex((msg) => msg.role === "system");
|
|
716
|
+
if (firstSystemIndex === -1)
|
|
717
|
+
return normalized.map(toProviderMessage);
|
|
718
|
+
const extraSystemCount = normalized.filter((msg) => msg.role === "system").length - 1;
|
|
719
|
+
if (firstSystemIndex === 0 && extraSystemCount === 0) {
|
|
720
|
+
return normalized.map(toProviderMessage);
|
|
721
|
+
}
|
|
722
|
+
const primarySystem = normalized[firstSystemIndex];
|
|
723
|
+
const nonSystemMessages = normalized.filter((msg) => msg.role !== "system");
|
|
724
|
+
const repaired = [primarySystem, ...nonSystemMessages].map(toProviderMessage);
|
|
725
|
+
(0, runtime_1.emitNervesEvent)({
|
|
726
|
+
level: "info",
|
|
727
|
+
event: "mind.session_system_prompt_repair",
|
|
728
|
+
component: "mind",
|
|
729
|
+
message: "canonicalized session system prompt sequence",
|
|
730
|
+
meta: {
|
|
731
|
+
firstSystemIndex,
|
|
732
|
+
extraSystemCount,
|
|
733
|
+
finalMessageCount: repaired.length,
|
|
734
|
+
},
|
|
735
|
+
});
|
|
736
|
+
return repaired;
|
|
737
|
+
}
|
|
738
|
+
function migrateToolNames(messages) {
|
|
739
|
+
const safeMessages = messages.filter((message) => Boolean(message) && typeof message === "object");
|
|
740
|
+
let migrated = 0;
|
|
741
|
+
for (const message of safeMessages) {
|
|
742
|
+
const record = message;
|
|
743
|
+
if (record.role !== "assistant" || !Array.isArray(record.tool_calls))
|
|
744
|
+
continue;
|
|
745
|
+
for (const toolCall of record.tool_calls) {
|
|
746
|
+
if (!toolCall || typeof toolCall !== "object")
|
|
747
|
+
continue;
|
|
748
|
+
const toolRecord = toolCall;
|
|
749
|
+
if (toolRecord.type !== "function")
|
|
750
|
+
continue;
|
|
751
|
+
const originalName = toolRecord.function?.name;
|
|
752
|
+
if (typeof originalName !== "string")
|
|
753
|
+
continue;
|
|
754
|
+
if (TOOL_NAME_MIGRATIONS[originalName])
|
|
755
|
+
migrated += 1;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
if (migrated > 0) {
|
|
759
|
+
(0, runtime_1.emitNervesEvent)({
|
|
760
|
+
level: "info",
|
|
761
|
+
event: "mind.session_tool_name_migration",
|
|
762
|
+
component: "mind",
|
|
763
|
+
message: "migrated deprecated tool names in session history",
|
|
764
|
+
meta: { migrated },
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
return safeMessages.map(normalizeMessage).map(toProviderMessage);
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Strip inline `<think>...</think>` blocks from a string. Mirrors the
|
|
771
|
+
* helper at senses/shared-turn.ts (operator-facing) and core.ts
|
|
772
|
+
* (live-turn) — kept inline here because session-events.ts is the load-
|
|
773
|
+
* time repair path and needs its own copy to avoid sense/heart import
|
|
774
|
+
* cycles. If the close tag is missing, drops everything from the open
|
|
775
|
+
* tag onward.
|
|
776
|
+
*/
|
|
777
|
+
function stripInlineThinkBlocks(input) {
|
|
778
|
+
let out = input;
|
|
779
|
+
for (;;) {
|
|
780
|
+
const open = out.indexOf("<think>");
|
|
781
|
+
if (open === -1)
|
|
782
|
+
break;
|
|
783
|
+
const close = out.indexOf("</think>", open + "<think>".length);
|
|
784
|
+
if (close === -1) {
|
|
785
|
+
out = out.slice(0, open);
|
|
786
|
+
break;
|
|
787
|
+
}
|
|
788
|
+
out = out.slice(0, open) + out.slice(close + "</think>".length);
|
|
789
|
+
}
|
|
790
|
+
return out.trim();
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Strip inline `<think>` content from any assistant message that ALSO has
|
|
794
|
+
* tool_calls. MiniMax-style models persist think-content + tool_calls on
|
|
795
|
+
* the same assistant turn; replaying that combination triggers MiniMax
|
|
796
|
+
* error 2013 ("tool result's tool id not found") and stalls the session.
|
|
797
|
+
*
|
|
798
|
+
* AX requirement: the agent MUST see that this happened. We don't silently
|
|
799
|
+
* paper over their previous turn — we strip for replay correctness AND
|
|
800
|
+
* collect the affected tool_call_ids in `inlineReasoningStrippedCallIds`
|
|
801
|
+
* so the downstream synthetic-tool-result repair can produce an
|
|
802
|
+
* explanatory message addressed to those specific calls. The agent sees:
|
|
803
|
+
* "your previous tool call's result was lost because the assistant message
|
|
804
|
+
* had inline reasoning blocks the provider couldn't replay — here's what
|
|
805
|
+
* happened, retry if needed." Full awareness, no silent corrections.
|
|
806
|
+
*
|
|
807
|
+
* This load-time repair self-heals existing sessions that were saved
|
|
808
|
+
* before the persist-time strip in core.ts landed.
|
|
809
|
+
*/
|
|
810
|
+
function repairInlineReasoningOnReplay(messages, inlineReasoningStrippedCallIds) {
|
|
811
|
+
let repaired = 0;
|
|
812
|
+
const result = messages.map((msg) => {
|
|
813
|
+
if (msg.role !== "assistant")
|
|
814
|
+
return msg;
|
|
815
|
+
const a = msg;
|
|
816
|
+
if (!a.tool_calls || a.tool_calls.length === 0)
|
|
817
|
+
return msg;
|
|
818
|
+
if (typeof a.content !== "string")
|
|
819
|
+
return msg;
|
|
820
|
+
if (!a.content.includes("<think>"))
|
|
821
|
+
return msg;
|
|
822
|
+
const stripped = stripInlineThinkBlocks(a.content);
|
|
823
|
+
repaired++;
|
|
824
|
+
for (const tc of a.tool_calls)
|
|
825
|
+
inlineReasoningStrippedCallIds.add(tc.id);
|
|
826
|
+
return withMessageSourceEventIds({ ...a, content: stripped.length > 0 ? stripped : null }, msg[messageSourceEventIds]);
|
|
827
|
+
});
|
|
828
|
+
if (repaired > 0) {
|
|
829
|
+
(0, runtime_1.emitNervesEvent)({
|
|
830
|
+
level: "info",
|
|
831
|
+
event: "mind.session_inline_reasoning_repair",
|
|
832
|
+
component: "mind",
|
|
833
|
+
message: "stripped inline <think> blocks from assistant messages with tool_calls so replay is valid; agent will see explanatory tool-result messages",
|
|
834
|
+
meta: { repaired, affectedCallIds: inlineReasoningStrippedCallIds.size },
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
return result;
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Compose the synthetic tool-result message the agent sees when their
|
|
841
|
+
* previous turn's tool call has no matching tool result. The default
|
|
842
|
+
* message tells the agent what happened (turn ended early, result lost)
|
|
843
|
+
* and what to do (retry if the work isn't done). When the parent
|
|
844
|
+
* assistant message had inline `<think>` reasoning that the provider
|
|
845
|
+
* rejected, the message is more specific so the agent can adjust.
|
|
846
|
+
*
|
|
847
|
+
* AX rule: every repair must produce a message the agent can read and
|
|
848
|
+
* act on. Silent strips are never OK.
|
|
849
|
+
*/
|
|
850
|
+
function buildSyntheticToolResultMessage(toolCallId, inlineReasoningStrippedCallIds) {
|
|
851
|
+
if (inlineReasoningStrippedCallIds.has(toolCallId)) {
|
|
852
|
+
return [
|
|
853
|
+
"error: this tool call's result was lost.",
|
|
854
|
+
"your previous assistant turn included inline `<think>...</think>` reasoning alongside tool_calls,",
|
|
855
|
+
"and the provider (likely MiniMax) rejects that combination on replay (error 2013).",
|
|
856
|
+
"the harness has stripped the inline reasoning from the persisted content so the next replay is valid;",
|
|
857
|
+
"your reasoning trace itself is preserved out-of-band and not lost.",
|
|
858
|
+
"if the underlying work still needs to be done, retry the tool call now —",
|
|
859
|
+
"the call may not have run, or it ran but the result didn't reach you.",
|
|
860
|
+
].join(" ");
|
|
861
|
+
}
|
|
862
|
+
return [
|
|
863
|
+
"error: this tool call's result was lost — the previous turn ended before the tool finished",
|
|
864
|
+
"(provider rejection, daemon interrupt, or the tool itself errored).",
|
|
865
|
+
"if the work needs to be done, retry the tool call now.",
|
|
866
|
+
].join(" ");
|
|
867
|
+
}
|
|
868
|
+
function sanitizeProviderMessages(messages) {
|
|
869
|
+
const safeMessages = messages.filter((message) => Boolean(message) && typeof message === "object");
|
|
870
|
+
const normalized = safeMessages.map(normalizeMessage);
|
|
871
|
+
const violations = validateSessionMessages(safeMessages);
|
|
872
|
+
if (violations.length > 0) {
|
|
873
|
+
(0, runtime_1.emitNervesEvent)({
|
|
874
|
+
level: "info",
|
|
875
|
+
event: "mind.session_invariant_violation",
|
|
876
|
+
component: "mind",
|
|
877
|
+
message: "session invariant violated",
|
|
878
|
+
meta: { violations },
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
// Track which tool_call_ids belonged to assistant messages whose inline
|
|
882
|
+
// reasoning we just stripped. The synthetic-tool-result repair downstream
|
|
883
|
+
// uses this set to produce an explanatory message for those calls so the
|
|
884
|
+
// agent has full awareness of what happened.
|
|
885
|
+
const inlineReasoningStrippedCallIds = new Set();
|
|
886
|
+
return canonicalizeSystemMessageSequence(migrateToolNames(repairToolCallSequences(repairInlineReasoningOnReplay(repairSessionMessages(normalized.map(toProviderMessage)), inlineReasoningStrippedCallIds), inlineReasoningStrippedCallIds)));
|
|
887
|
+
}
|
|
888
|
+
function stampIngressTime(msg) {
|
|
889
|
+
msg._ingressAt = new Date().toISOString();
|
|
890
|
+
}
|
|
891
|
+
function getIngressTime(msg) {
|
|
892
|
+
const value = msg._ingressAt;
|
|
893
|
+
return typeof value === "string" ? value : null;
|
|
894
|
+
}
|
|
895
|
+
function stampIngressRelations(msg, relations) {
|
|
896
|
+
;
|
|
897
|
+
msg._ingressRelations = structuredClone(relations);
|
|
898
|
+
}
|
|
899
|
+
function getIngressRelations(msg) {
|
|
900
|
+
const value = msg._ingressRelations;
|
|
901
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
902
|
+
return null;
|
|
903
|
+
const relations = value;
|
|
904
|
+
return {
|
|
905
|
+
replyToEventId: typeof relations.replyToEventId === "string" ? relations.replyToEventId : null,
|
|
906
|
+
threadRootEventId: typeof relations.threadRootEventId === "string" ? relations.threadRootEventId : null,
|
|
907
|
+
references: Array.isArray(relations.references) ? relations.references.filter((item) => typeof item === "string") : [],
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
function createEventTime(role, recordedAt, captureKind, ingressAt) {
|
|
911
|
+
if (captureKind === "migration") {
|
|
912
|
+
return {
|
|
913
|
+
authoredAt: null,
|
|
914
|
+
authoredAtSource: "migration",
|
|
915
|
+
observedAt: null,
|
|
916
|
+
observedAtSource: "migration",
|
|
917
|
+
recordedAt,
|
|
918
|
+
recordedAtSource: "migration",
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
if (role === "user") {
|
|
922
|
+
return {
|
|
923
|
+
authoredAt: null,
|
|
924
|
+
authoredAtSource: "unknown",
|
|
925
|
+
observedAt: ingressAt ?? recordedAt,
|
|
926
|
+
observedAtSource: "ingest",
|
|
927
|
+
recordedAt,
|
|
928
|
+
recordedAtSource: "save",
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
return {
|
|
932
|
+
authoredAt: recordedAt,
|
|
933
|
+
authoredAtSource: "local",
|
|
934
|
+
observedAt: recordedAt,
|
|
935
|
+
observedAtSource: "local",
|
|
936
|
+
recordedAt,
|
|
937
|
+
recordedAtSource: "save",
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
function buildEventFromMessage(message, sequence, recordedAt, captureKind, sourceMessageIndex, legacyVersion, ingressAt, ingressRelations) {
|
|
941
|
+
const normalized = normalizeMessage(message);
|
|
942
|
+
const role = normalized.role;
|
|
943
|
+
const cappedContent = truncateLargeEventContent(normalized.content, exports.EVENT_CONTENT_MAX_CHARS).content;
|
|
944
|
+
return {
|
|
945
|
+
id: makeEventId(sequence),
|
|
946
|
+
sequence,
|
|
947
|
+
role,
|
|
948
|
+
content: cappedContent,
|
|
949
|
+
name: normalized.name,
|
|
950
|
+
toolCallId: role === "tool" ? normalized.toolCallId : null,
|
|
951
|
+
toolCalls: role === "assistant" ? normalized.toolCalls : [],
|
|
952
|
+
attachments: [],
|
|
953
|
+
time: createEventTime(role, recordedAt, captureKind, ingressAt),
|
|
954
|
+
relations: {
|
|
955
|
+
replyToEventId: ingressRelations?.replyToEventId ?? null,
|
|
956
|
+
threadRootEventId: ingressRelations?.threadRootEventId ?? null,
|
|
957
|
+
references: ingressRelations?.references ?? [],
|
|
958
|
+
toolCallId: role === "tool" ? normalized.toolCallId : null,
|
|
959
|
+
supersedesEventId: null,
|
|
960
|
+
redactsEventId: null,
|
|
961
|
+
},
|
|
962
|
+
provenance: {
|
|
963
|
+
captureKind,
|
|
964
|
+
legacyVersion,
|
|
965
|
+
sourceMessageIndex,
|
|
966
|
+
},
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
function projectedSessionEventIds(envelope) {
|
|
970
|
+
return providerProjectionEvents(envelope).map((event) => event.id);
|
|
971
|
+
}
|
|
972
|
+
function providerProjectionEvents(envelope) {
|
|
973
|
+
const events = selectEffectiveSessionEvents(envelope.events);
|
|
974
|
+
if (envelope.projection.eventIds.length === 0)
|
|
975
|
+
return envelope.projection.trimmed ? [] : events;
|
|
976
|
+
const byId = new Map(events.map((event) => [event.id, event]));
|
|
977
|
+
return envelope.projection.eventIds
|
|
978
|
+
.map((id) => byId.get(id))
|
|
979
|
+
.filter((event) => Boolean(event));
|
|
980
|
+
}
|
|
981
|
+
function providerMessageFromEvent(event) {
|
|
982
|
+
return toProviderMessage({
|
|
983
|
+
role: event.role,
|
|
984
|
+
content: event.content,
|
|
985
|
+
name: event.name,
|
|
986
|
+
toolCallId: event.toolCallId,
|
|
987
|
+
toolCalls: event.toolCalls,
|
|
988
|
+
hadToolCallsField: event.toolCalls.length > 0,
|
|
989
|
+
sourceEventIds: [event.id],
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
function projectProviderMessages(envelope) {
|
|
993
|
+
return providerProjectionEvents(envelope).map(providerMessageFromEvent);
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* Annotate user and assistant messages with a relative time offset tag.
|
|
997
|
+
* System and tool messages are untouched.
|
|
998
|
+
*/
|
|
999
|
+
function annotateMessageTimestamps(envelope, messages, nowMs = Date.now()) {
|
|
1000
|
+
const events = providerProjectionEvents(envelope);
|
|
1001
|
+
return messages.map((msg, i) => {
|
|
1002
|
+
const event = events[i];
|
|
1003
|
+
if (!event)
|
|
1004
|
+
return msg;
|
|
1005
|
+
if (event.role !== "user" && event.role !== "assistant")
|
|
1006
|
+
return msg;
|
|
1007
|
+
const ts = bestEventTimestamp(event);
|
|
1008
|
+
const elapsed = nowMs - Date.parse(ts);
|
|
1009
|
+
if (elapsed < 0)
|
|
1010
|
+
return msg;
|
|
1011
|
+
const tag = elapsed < 60000 ? "[just now]" : `[-${formatElapsedCompact(elapsed)}]`;
|
|
1012
|
+
if (typeof msg.content === "string" && msg.content.length > 0) {
|
|
1013
|
+
return { ...msg, content: `${tag} ${msg.content}` };
|
|
1014
|
+
}
|
|
1015
|
+
return msg;
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
/** Compact elapsed format for message annotations: "3m", "2h", "1d". */
|
|
1019
|
+
function formatElapsedCompact(ms) {
|
|
1020
|
+
const minutes = Math.max(1, Math.floor(ms / 60000));
|
|
1021
|
+
if (minutes < 60)
|
|
1022
|
+
return `${minutes}m`;
|
|
1023
|
+
const hours = Math.floor(minutes / 60);
|
|
1024
|
+
if (hours < 24)
|
|
1025
|
+
return `${hours}h`;
|
|
1026
|
+
const days = Math.floor(hours / 24);
|
|
1027
|
+
return `${days}d`;
|
|
1028
|
+
}
|
|
1029
|
+
function bestEventTimestamp(event) {
|
|
1030
|
+
return event.time.authoredAt ?? event.time.observedAt ?? event.time.recordedAt;
|
|
1031
|
+
}
|
|
1032
|
+
function formatSessionEventTimestamp(event) {
|
|
1033
|
+
const iso = bestEventTimestamp(event);
|
|
1034
|
+
const date = new Date(iso);
|
|
1035
|
+
const year = date.getUTCFullYear();
|
|
1036
|
+
const month = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
1037
|
+
const day = String(date.getUTCDate()).padStart(2, "0");
|
|
1038
|
+
const hour = String(date.getUTCHours()).padStart(2, "0");
|
|
1039
|
+
const minute = String(date.getUTCMinutes()).padStart(2, "0");
|
|
1040
|
+
return `${year}-${month}-${day} ${hour}:${minute}`;
|
|
1041
|
+
}
|
|
1042
|
+
function extractEventText(event) {
|
|
1043
|
+
return contentText(event.content);
|
|
1044
|
+
}
|
|
1045
|
+
function deriveSessionChronology(events) {
|
|
1046
|
+
events = selectEffectiveSessionEvents(events);
|
|
1047
|
+
let lastInboundAt = null;
|
|
1048
|
+
let lastOutboundAt = null;
|
|
1049
|
+
let lastActivityAt = null;
|
|
1050
|
+
let lastAssistantSequence = -1;
|
|
1051
|
+
for (const event of events) {
|
|
1052
|
+
if (event.role === "system")
|
|
1053
|
+
continue;
|
|
1054
|
+
const at = bestEventTimestamp(event);
|
|
1055
|
+
lastActivityAt = at;
|
|
1056
|
+
if (event.role === "user") {
|
|
1057
|
+
lastInboundAt = at;
|
|
1058
|
+
}
|
|
1059
|
+
if (event.role === "assistant") {
|
|
1060
|
+
lastOutboundAt = at;
|
|
1061
|
+
lastAssistantSequence = event.sequence;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
const unansweredInboundCount = events.filter((event) => event.role === "user" && event.sequence > lastAssistantSequence).length;
|
|
1065
|
+
return {
|
|
1066
|
+
lastInboundAt,
|
|
1067
|
+
lastOutboundAt,
|
|
1068
|
+
lastActivityAt,
|
|
1069
|
+
unansweredInboundCount,
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
function describeCurrentSessionTiming(events, nowMs = Date.now()) {
|
|
1073
|
+
const chronology = deriveSessionChronology(events);
|
|
1074
|
+
const parts = [];
|
|
1075
|
+
if (chronology.lastInboundAt) {
|
|
1076
|
+
parts.push(`last inbound ${formatElapsed(nowMs - Date.parse(chronology.lastInboundAt))}`);
|
|
1077
|
+
}
|
|
1078
|
+
if (chronology.lastOutboundAt) {
|
|
1079
|
+
parts.push(`i last replied ${formatElapsed(nowMs - Date.parse(chronology.lastOutboundAt))}`);
|
|
1080
|
+
}
|
|
1081
|
+
if (chronology.unansweredInboundCount > 0) {
|
|
1082
|
+
const count = chronology.unansweredInboundCount;
|
|
1083
|
+
parts.push(`${count} unanswered inbound message${count === 1 ? "" : "s"}`);
|
|
1084
|
+
}
|
|
1085
|
+
return parts.length > 0 ? `current thread: ${parts.join("; ")}` : "";
|
|
1086
|
+
}
|
|
1087
|
+
function migrateLegacySessionEnvelope(raw, options) {
|
|
1088
|
+
if (!raw || typeof raw !== "object")
|
|
1089
|
+
return null;
|
|
1090
|
+
const legacy = raw;
|
|
1091
|
+
const looksLegacy = legacy.version === 1
|
|
1092
|
+
|| (legacy.version == null && ("messages" in legacy || "lastUsage" in legacy || "state" in legacy));
|
|
1093
|
+
if (!looksLegacy)
|
|
1094
|
+
return null;
|
|
1095
|
+
const messages = Array.isArray(legacy.messages)
|
|
1096
|
+
? sanitizeProviderMessages(legacy.messages)
|
|
1097
|
+
: [];
|
|
1098
|
+
const recordedAt = options.fileMtimeAt ?? options.recordedAt;
|
|
1099
|
+
const events = messages.map((message, index) => buildEventFromMessage(message, index + 1, recordedAt, "migration", index, 1));
|
|
1100
|
+
return {
|
|
1101
|
+
version: 2,
|
|
1102
|
+
events,
|
|
1103
|
+
projection: {
|
|
1104
|
+
eventIds: events.map((event) => event.id),
|
|
1105
|
+
trimmed: false,
|
|
1106
|
+
maxTokens: null,
|
|
1107
|
+
contextMargin: null,
|
|
1108
|
+
inputTokens: null,
|
|
1109
|
+
projectedAt: recordedAt,
|
|
1110
|
+
},
|
|
1111
|
+
structuredOutputs: (0, structured_output_1.extractStructuredOutputsFromEvents)(events, { emitTelemetry: false }),
|
|
1112
|
+
lastUsage: normalizeUsage(legacy.lastUsage),
|
|
1113
|
+
state: normalizeContinuityState(legacy.state),
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
function normalizeSessionEvent(event, index, recordedAt) {
|
|
1117
|
+
const role = normalizeRole(event.role);
|
|
1118
|
+
const time = event.time;
|
|
1119
|
+
const relations = event.relations;
|
|
1120
|
+
const provenance = event.provenance;
|
|
1121
|
+
const content = sanitizeConversationContent(role, normalizeContent(event.content));
|
|
1122
|
+
return {
|
|
1123
|
+
id: typeof event.id === "string" ? event.id : makeEventId(index + 1),
|
|
1124
|
+
sequence: typeof event.sequence === "number" ? event.sequence : index + 1,
|
|
1125
|
+
role,
|
|
1126
|
+
content,
|
|
1127
|
+
name: typeof event.name === "string" ? event.name : null,
|
|
1128
|
+
toolCallId: typeof event.toolCallId === "string" ? event.toolCallId : null,
|
|
1129
|
+
toolCalls: normalizeToolCalls(event.toolCalls),
|
|
1130
|
+
attachments: Array.isArray(event.attachments) ? event.attachments.filter((item) => typeof item === "string") : [],
|
|
1131
|
+
time: {
|
|
1132
|
+
authoredAt: typeof time?.authoredAt === "string" ? time.authoredAt : null,
|
|
1133
|
+
authoredAtSource: typeof time?.authoredAtSource === "string" ? time.authoredAtSource : "unknown",
|
|
1134
|
+
observedAt: typeof time?.observedAt === "string" ? time.observedAt : null,
|
|
1135
|
+
observedAtSource: typeof time?.observedAtSource === "string" ? time.observedAtSource : "unknown",
|
|
1136
|
+
recordedAt: typeof time?.recordedAt === "string" ? time.recordedAt : recordedAt,
|
|
1137
|
+
recordedAtSource: typeof time?.recordedAtSource === "string" ? time.recordedAtSource : "save",
|
|
1138
|
+
},
|
|
1139
|
+
relations: {
|
|
1140
|
+
replyToEventId: typeof relations?.replyToEventId === "string" ? relations.replyToEventId : null,
|
|
1141
|
+
threadRootEventId: typeof relations?.threadRootEventId === "string" ? relations.threadRootEventId : null,
|
|
1142
|
+
references: Array.isArray(relations?.references) ? relations.references.filter((item) => typeof item === "string") : [],
|
|
1143
|
+
toolCallId: typeof relations?.toolCallId === "string" ? relations.toolCallId : null,
|
|
1144
|
+
supersedesEventId: typeof relations?.supersedesEventId === "string" ? relations.supersedesEventId : null,
|
|
1145
|
+
redactsEventId: typeof relations?.redactsEventId === "string" ? relations.redactsEventId : null,
|
|
1146
|
+
},
|
|
1147
|
+
provenance: {
|
|
1148
|
+
captureKind: typeof provenance?.captureKind === "string" ? provenance.captureKind : "live",
|
|
1149
|
+
legacyVersion: typeof provenance?.legacyVersion === "number" ? provenance.legacyVersion : null,
|
|
1150
|
+
sourceMessageIndex: typeof provenance?.sourceMessageIndex === "number" ? provenance.sourceMessageIndex : null,
|
|
1151
|
+
},
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
function exactKeys(value, keys) {
|
|
1155
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
1156
|
+
&& Reflect.ownKeys(value).length === keys.length
|
|
1157
|
+
&& keys.every((key) => Object.prototype.hasOwnProperty.call(value, key));
|
|
1158
|
+
}
|
|
1159
|
+
function nonblank(value) {
|
|
1160
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
1161
|
+
}
|
|
1162
|
+
function exactIsoTime(value) {
|
|
1163
|
+
return typeof value === "string" && Number.isFinite(Date.parse(value)) && new Date(value).toISOString() === value;
|
|
1164
|
+
}
|
|
1165
|
+
function exactRawEvent(value, index) {
|
|
1166
|
+
if (!exactKeys(value, ["id", "sequence", "role", "content", "name", "toolCallId", "toolCalls", "attachments", "time", "relations", "provenance"]))
|
|
1167
|
+
return false;
|
|
1168
|
+
if (!nonblank(value.id) || !Number.isSafeInteger(value.sequence) || Number(value.sequence) <= 0
|
|
1169
|
+
|| typeof value.role !== "string" || !["system", "user", "assistant", "tool"].includes(value.role)
|
|
1170
|
+
|| !(value.name === null || typeof value.name === "string") || !(value.toolCallId === null || typeof value.toolCallId === "string")
|
|
1171
|
+
|| !(value.content === null || typeof value.content === "string" || Array.isArray(value.content))
|
|
1172
|
+
|| !Array.isArray(value.attachments) || !value.attachments.every((item) => typeof item === "string")
|
|
1173
|
+
|| !Array.isArray(value.toolCalls))
|
|
1174
|
+
return false;
|
|
1175
|
+
for (const call of value.toolCalls) {
|
|
1176
|
+
if (!exactKeys(call, ["id", "type", "function"]) || !nonblank(call.id) || call.type !== "function"
|
|
1177
|
+
|| !exactKeys(call.function, ["name", "arguments"]) || !nonblank(call.function.name) || typeof call.function.arguments !== "string")
|
|
1178
|
+
return false;
|
|
1179
|
+
}
|
|
1180
|
+
const time = value.time;
|
|
1181
|
+
if (!exactKeys(time, ["authoredAt", "authoredAtSource", "observedAt", "observedAtSource", "recordedAt", "recordedAtSource"])
|
|
1182
|
+
|| !(time.authoredAt === null || exactIsoTime(time.authoredAt))
|
|
1183
|
+
|| !(time.observedAt === null || exactIsoTime(time.observedAt)) || !exactIsoTime(time.recordedAt))
|
|
1184
|
+
return false;
|
|
1185
|
+
for (const key of ["authoredAtSource", "observedAtSource", "recordedAtSource"]) {
|
|
1186
|
+
if (typeof time[key] !== "string" || !["unknown", "local", "ingest", "migration", "save"].includes(time[key]))
|
|
1187
|
+
return false;
|
|
1188
|
+
}
|
|
1189
|
+
const relations = value.relations;
|
|
1190
|
+
if (!exactKeys(relations, ["replyToEventId", "threadRootEventId", "references", "toolCallId", "supersedesEventId", "redactsEventId"])
|
|
1191
|
+
|| !Array.isArray(relations.references) || !relations.references.every((item) => typeof item === "string"))
|
|
1192
|
+
return false;
|
|
1193
|
+
for (const key of ["replyToEventId", "threadRootEventId", "toolCallId", "supersedesEventId", "redactsEventId"]) {
|
|
1194
|
+
if (relations[key] !== null && typeof relations[key] !== "string")
|
|
1195
|
+
return false;
|
|
1196
|
+
}
|
|
1197
|
+
const provenance = value.provenance;
|
|
1198
|
+
if (!exactKeys(provenance, ["captureKind", "legacyVersion", "sourceMessageIndex"])
|
|
1199
|
+
|| typeof provenance.captureKind !== "string" || !["live", "synthetic", "migration"].includes(provenance.captureKind)
|
|
1200
|
+
|| !(provenance.legacyVersion === null || Number.isSafeInteger(provenance.legacyVersion) && Number(provenance.legacyVersion) > 0)
|
|
1201
|
+
|| !(provenance.sourceMessageIndex === null || Number.isSafeInteger(provenance.sourceMessageIndex) && Number(provenance.sourceMessageIndex) >= 0))
|
|
1202
|
+
return false;
|
|
1203
|
+
return (0, node_util_1.isDeepStrictEqual)(value, normalizeSessionEvent(value, index, time.recordedAt));
|
|
1204
|
+
}
|
|
1205
|
+
function isExactRawSessionRedactionMarker(candidate, rawEvents) {
|
|
1206
|
+
if (!candidate || typeof candidate !== "object" || candidate.role !== "system"
|
|
1207
|
+
|| !exactRawEvent(candidate, 0) || candidate.content !== null || candidate.name !== null || candidate.toolCallId !== null
|
|
1208
|
+
|| candidate.toolCalls.length !== 0 || candidate.attachments.length !== 0
|
|
1209
|
+
|| candidate.time.authoredAt !== null || candidate.time.observedAt !== null
|
|
1210
|
+
|| candidate.time.authoredAtSource !== "migration" || candidate.time.observedAtSource !== "migration" || candidate.time.recordedAtSource !== "migration"
|
|
1211
|
+
|| candidate.provenance.captureKind !== "migration" || candidate.provenance.legacyVersion !== 2 || candidate.provenance.sourceMessageIndex !== null
|
|
1212
|
+
|| candidate.relations.replyToEventId !== null || candidate.relations.threadRootEventId !== null || candidate.relations.references.length !== 0
|
|
1213
|
+
|| candidate.relations.toolCallId !== null || candidate.relations.supersedesEventId !== null || !nonblank(candidate.relations.redactsEventId)
|
|
1214
|
+
|| !Array.isArray(rawEvents))
|
|
1215
|
+
return false;
|
|
1216
|
+
const ids = new Set();
|
|
1217
|
+
let previousSequence = 0;
|
|
1218
|
+
for (const raw of rawEvents) {
|
|
1219
|
+
if (!raw || typeof raw !== "object")
|
|
1220
|
+
return false;
|
|
1221
|
+
const event = raw;
|
|
1222
|
+
if (!nonblank(event.id) || ids.has(event.id) || !Number.isSafeInteger(event.sequence) || Number(event.sequence) <= previousSequence)
|
|
1223
|
+
return false;
|
|
1224
|
+
ids.add(event.id);
|
|
1225
|
+
previousSequence = Number(event.sequence);
|
|
1226
|
+
}
|
|
1227
|
+
const candidateIndex = rawEvents.findIndex((raw) => raw.id === candidate.id);
|
|
1228
|
+
if (candidateIndex < 0 || !(0, node_util_1.isDeepStrictEqual)(rawEvents[candidateIndex], candidate))
|
|
1229
|
+
return false;
|
|
1230
|
+
const targetIndex = rawEvents.findIndex((raw) => raw.id === candidate.relations.redactsEventId);
|
|
1231
|
+
if (targetIndex < 0 || targetIndex >= candidateIndex)
|
|
1232
|
+
return false;
|
|
1233
|
+
const target = rawEvents[targetIndex];
|
|
1234
|
+
if (!exactRawEvent(target, targetIndex) || target.role !== "user" || target.relations.redactsEventId !== null)
|
|
1235
|
+
return false;
|
|
1236
|
+
const attempts = rawEvents.map((raw) => raw.relations?.redactsEventId);
|
|
1237
|
+
return attempts.filter((id) => id === target.id).length === 1 && !attempts.includes(candidate.id);
|
|
1238
|
+
}
|
|
1239
|
+
function selectEffectiveSessionEvents(events) {
|
|
1240
|
+
const hidden = new Set();
|
|
1241
|
+
for (const event of events) {
|
|
1242
|
+
if (isExactRawSessionRedactionMarker(event, events)) {
|
|
1243
|
+
hidden.add(event.id);
|
|
1244
|
+
hidden.add(event.relations.redactsEventId);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
return events.filter((event) => !hidden.has(event.id));
|
|
1248
|
+
}
|
|
1249
|
+
function parseSessionEnvelope(raw, options = {}) {
|
|
1250
|
+
const recordedAt = options.recordedAt ?? new Date().toISOString();
|
|
1251
|
+
const fileMtimeAt = options.fileMtimeAt ?? null;
|
|
1252
|
+
const migrated = migrateLegacySessionEnvelope(raw, { recordedAt, fileMtimeAt });
|
|
1253
|
+
if (migrated)
|
|
1254
|
+
return migrated;
|
|
1255
|
+
if (!raw || typeof raw !== "object")
|
|
1256
|
+
return null;
|
|
1257
|
+
const record = raw;
|
|
1258
|
+
if (record.version !== 2 || !Array.isArray(record.events) || !record.projection || typeof record.projection !== "object") {
|
|
1259
|
+
return null;
|
|
1260
|
+
}
|
|
1261
|
+
const validMarkers = new Set(record.events.filter((event) => isExactRawSessionRedactionMarker(event, record.events)));
|
|
1262
|
+
let invalidMarkers = 0;
|
|
1263
|
+
const rawEvents = record.events
|
|
1264
|
+
.filter((event) => event != null && typeof event === "object")
|
|
1265
|
+
.map((event, index) => {
|
|
1266
|
+
const normalized = normalizeSessionEvent(event, index, recordedAt);
|
|
1267
|
+
const attempted = event.relations?.redactsEventId;
|
|
1268
|
+
if (attempted !== undefined && attempted !== null && !validMarkers.has(event)) {
|
|
1269
|
+
normalized.relations.redactsEventId = null;
|
|
1270
|
+
invalidMarkers++;
|
|
1271
|
+
}
|
|
1272
|
+
return normalized;
|
|
1273
|
+
});
|
|
1274
|
+
if (invalidMarkers > 0)
|
|
1275
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1276
|
+
level: "warn", component: "heart", event: "session.redaction_marker_invalid",
|
|
1277
|
+
message: "invalid raw redaction authority ignored", meta: { count: invalidMarkers },
|
|
1278
|
+
});
|
|
1279
|
+
// Self-heal duplicate event ids that may have been written by concurrent
|
|
1280
|
+
// writers in older harness versions. Last-occurrence-wins by id (later
|
|
1281
|
+
// entries in the persisted file are the more recent state for that id).
|
|
1282
|
+
// We preserve the original document order otherwise, so projection.eventIds
|
|
1283
|
+
// still resolves predictably.
|
|
1284
|
+
const events = dedupeEventsByIdLastWins(rawEvents);
|
|
1285
|
+
const projection = record.projection;
|
|
1286
|
+
const envelope = {
|
|
1287
|
+
version: 2,
|
|
1288
|
+
events,
|
|
1289
|
+
projection: {
|
|
1290
|
+
eventIds: Array.isArray(projection.eventIds) ? projection.eventIds.filter((item) => typeof item === "string") : [],
|
|
1291
|
+
trimmed: projection.trimmed === true,
|
|
1292
|
+
maxTokens: typeof projection.maxTokens === "number" ? projection.maxTokens : null,
|
|
1293
|
+
contextMargin: typeof projection.contextMargin === "number" ? projection.contextMargin : null,
|
|
1294
|
+
inputTokens: typeof projection.inputTokens === "number" ? projection.inputTokens : null,
|
|
1295
|
+
projectedAt: typeof projection.projectedAt === "string" ? projection.projectedAt : null,
|
|
1296
|
+
},
|
|
1297
|
+
lastUsage: normalizeUsage(record.lastUsage),
|
|
1298
|
+
state: normalizeContinuityState(record.state),
|
|
1299
|
+
approvalSuspensions: normalizeApprovalSuspensions(record.approvalSuspensions),
|
|
1300
|
+
};
|
|
1301
|
+
envelope.structuredOutputs = (0, structured_output_1.extractStructuredOutputsFromEvents)(providerProjectionEvents(envelope), { emitTelemetry: false });
|
|
1302
|
+
return envelope;
|
|
1303
|
+
}
|
|
1304
|
+
function loadSessionEnvelopeFile(filePath) {
|
|
1305
|
+
try {
|
|
1306
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
1307
|
+
let mtime;
|
|
1308
|
+
try {
|
|
1309
|
+
mtime = fs.statSync(filePath).mtime.toISOString();
|
|
1310
|
+
}
|
|
1311
|
+
catch {
|
|
1312
|
+
mtime = new Date().toISOString();
|
|
1313
|
+
}
|
|
1314
|
+
return parseSessionEnvelope(JSON.parse(raw), {
|
|
1315
|
+
recordedAt: mtime,
|
|
1316
|
+
fileMtimeAt: mtime,
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
catch {
|
|
1320
|
+
return null;
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
function messageRole(msg) {
|
|
1324
|
+
return normalizeRole(msg.role);
|
|
1325
|
+
}
|
|
1326
|
+
function filterNonSystem(messages) {
|
|
1327
|
+
return messages.filter((msg) => messageRole(msg) !== "system");
|
|
1328
|
+
}
|
|
1329
|
+
/**
|
|
1330
|
+
* Compare two message arrays by their non-system messages only.
|
|
1331
|
+
* Returns the number of matching non-system messages from the start.
|
|
1332
|
+
* System messages (whose content changes every turn due to live world-state)
|
|
1333
|
+
* are excluded so that prefix matching is not defeated by system prompt updates.
|
|
1334
|
+
*/
|
|
1335
|
+
function findCommonPrefixLength(a, b, captureContent = false) {
|
|
1336
|
+
const aNonSys = filterNonSystem(a);
|
|
1337
|
+
const bNonSys = filterNonSystem(b);
|
|
1338
|
+
const max = Math.min(aNonSys.length, bNonSys.length);
|
|
1339
|
+
for (let i = 0; i < max; i++) {
|
|
1340
|
+
if (!messageSourcesAgree(aNonSys[i], bNonSys[i]))
|
|
1341
|
+
return i;
|
|
1342
|
+
if (messageFingerprint(aNonSys[i], captureContent) !== messageFingerprint(bNonSys[i], captureContent))
|
|
1343
|
+
return i;
|
|
1344
|
+
}
|
|
1345
|
+
return max;
|
|
1346
|
+
}
|
|
1347
|
+
function selectProjectedEventIds(currentMessages, currentEventIds, trimmedMessages) {
|
|
1348
|
+
if (trimmedMessages.length === 0)
|
|
1349
|
+
return [];
|
|
1350
|
+
const result = [];
|
|
1351
|
+
const currentIdentities = new Set(currentMessages);
|
|
1352
|
+
let needle = 0;
|
|
1353
|
+
let trimmedFingerprint = null;
|
|
1354
|
+
for (let i = 0; i < currentMessages.length && needle < trimmedMessages.length; i++) {
|
|
1355
|
+
const currentMessage = currentMessages[i];
|
|
1356
|
+
const trimmedMessage = trimmedMessages[needle];
|
|
1357
|
+
if (currentMessage !== trimmedMessage) {
|
|
1358
|
+
if (currentIdentities.has(trimmedMessage))
|
|
1359
|
+
continue;
|
|
1360
|
+
if (!messageSourcesAgree(currentMessage, trimmedMessage))
|
|
1361
|
+
continue;
|
|
1362
|
+
trimmedFingerprint ??= messageFingerprint(trimmedMessage);
|
|
1363
|
+
if (messageFingerprint(currentMessage) !== trimmedFingerprint)
|
|
1364
|
+
continue;
|
|
1365
|
+
}
|
|
1366
|
+
result.push(...currentEventIds[i]);
|
|
1367
|
+
needle++;
|
|
1368
|
+
trimmedFingerprint = null;
|
|
1369
|
+
}
|
|
1370
|
+
return result;
|
|
1371
|
+
}
|
|
1372
|
+
function retainedSourceEventIds(message, existing, used) {
|
|
1373
|
+
const ids = message[messageSourceEventIds];
|
|
1374
|
+
if (!ids?.length || ids.some((id) => !existing.has(id) || used.has(id)))
|
|
1375
|
+
return null;
|
|
1376
|
+
const originals = ids.map((id) => providerMessageFromEvent(existing.get(id)));
|
|
1377
|
+
const fingerprint = messageFingerprint(message, true);
|
|
1378
|
+
const matches = (candidate) => (candidate[messageSourceEventIds] !== undefined
|
|
1379
|
+
&& messageSourcesAgree(candidate, message)
|
|
1380
|
+
&& messageFingerprint(candidate, true) === fingerprint);
|
|
1381
|
+
return originals.some(matches) || sanitizeProviderMessages(originals).some(matches) ? ids : null;
|
|
1382
|
+
}
|
|
1383
|
+
function buildCanonicalSessionEnvelope(options) {
|
|
1384
|
+
const existing = options.existing;
|
|
1385
|
+
// Callers pass pre-sanitized messages + pre-captured ingress times.
|
|
1386
|
+
const currentIngressTimes = options.currentIngressTimes ?? options.currentMessages.map(getIngressTime);
|
|
1387
|
+
const currentIngressRelations = options.currentIngressRelations ?? options.currentMessages.map(getIngressRelations);
|
|
1388
|
+
let previousMessages = options.previousMessages;
|
|
1389
|
+
const currentMessages = options.currentMessages;
|
|
1390
|
+
const trimmedMessages = options.trimmedMessages;
|
|
1391
|
+
let previousProjectionGroups = existing ? providerProjectionEvents(existing).map((event) => [event.id]) : [];
|
|
1392
|
+
// Compare only non-system messages to find the common prefix.
|
|
1393
|
+
// System messages change every turn (live world-state in system prompt)
|
|
1394
|
+
// and must not defeat prefix matching of the actual conversation.
|
|
1395
|
+
let nonSystemPrefix = findCommonPrefixLength(previousMessages, currentMessages, true);
|
|
1396
|
+
// A repaired provider message can represent several original native records.
|
|
1397
|
+
const repairedPrevious = sanitizeProviderMessages(previousMessages.map((message, index) => withMessageSourceEventIds({ ...message }, previousProjectionGroups[index])));
|
|
1398
|
+
const repairedPrefix = findCommonPrefixLength(repairedPrevious, currentMessages);
|
|
1399
|
+
if (repairedPrefix > nonSystemPrefix) {
|
|
1400
|
+
previousMessages = repairedPrevious;
|
|
1401
|
+
previousProjectionGroups = repairedPrevious.map((message) => message[messageSourceEventIds] ?? []);
|
|
1402
|
+
nonSystemPrefix = repairedPrefix;
|
|
1403
|
+
}
|
|
1404
|
+
// Build a lookup of non-system previous projection IDs.
|
|
1405
|
+
const prevNonSystemIds = [];
|
|
1406
|
+
for (let i = 0; i < previousMessages.length; i++) {
|
|
1407
|
+
if (messageRole(previousMessages[i]) !== "system") {
|
|
1408
|
+
prevNonSystemIds.push(previousProjectionGroups[i]);
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
// Walk currentMessages and build currentEventIds + new events.
|
|
1412
|
+
// Non-system messages within the prefix reuse old event IDs.
|
|
1413
|
+
// Other messages reuse a matching native origin or create a new event.
|
|
1414
|
+
const events = [...(existing?.events ?? [])];
|
|
1415
|
+
const sourceEvents = new Map(selectEffectiveSessionEvents(events).map((event) => [event.id, event]));
|
|
1416
|
+
const excludedSourceIds = new Set(events.filter((event) => !sourceEvents.has(event.id)).map((event) => event.id));
|
|
1417
|
+
const usedEventIds = new Set();
|
|
1418
|
+
const currentEventIds = [];
|
|
1419
|
+
let nonSystemSeen = 0;
|
|
1420
|
+
for (let i = 0; i < currentMessages.length; i++) {
|
|
1421
|
+
const sourceIds = currentMessages[i][messageSourceEventIds];
|
|
1422
|
+
if (sourceIds?.some((id) => excludedSourceIds.has(id))) {
|
|
1423
|
+
throw new Error("cannot persist messages derived from redacted session events");
|
|
1424
|
+
}
|
|
1425
|
+
const role = messageRole(currentMessages[i]);
|
|
1426
|
+
const isSystem = role === "system";
|
|
1427
|
+
const inPrefix = !isSystem && nonSystemSeen < nonSystemPrefix;
|
|
1428
|
+
if (inPrefix && prevNonSystemIds[nonSystemSeen].every((id) => !usedEventIds.has(id))) {
|
|
1429
|
+
// Reuse existing event ID for this matched non-system message
|
|
1430
|
+
currentEventIds.push(prevNonSystemIds[nonSystemSeen]);
|
|
1431
|
+
nonSystemSeen++;
|
|
1432
|
+
}
|
|
1433
|
+
else if (isSystem && i < previousMessages.length
|
|
1434
|
+
&& messageRole(previousMessages[i]) === "system"
|
|
1435
|
+
&& messageSourcesAgree(currentMessages[i], previousMessages[i])
|
|
1436
|
+
&& previousProjectionGroups[i].every((id) => !usedEventIds.has(id))
|
|
1437
|
+
&& messageFingerprint(currentMessages[i], true) === messageFingerprint(previousMessages[i], true)) {
|
|
1438
|
+
// System message at same position with identical content -- reuse event ID
|
|
1439
|
+
currentEventIds.push(previousProjectionGroups[i]);
|
|
1440
|
+
}
|
|
1441
|
+
else {
|
|
1442
|
+
if (!isSystem)
|
|
1443
|
+
nonSystemSeen++;
|
|
1444
|
+
const retainedIds = retainedSourceEventIds(currentMessages[i], sourceEvents, usedEventIds);
|
|
1445
|
+
if (retainedIds) {
|
|
1446
|
+
currentEventIds.push(retainedIds);
|
|
1447
|
+
for (const id of retainedIds)
|
|
1448
|
+
usedEventIds.add(id);
|
|
1449
|
+
continue;
|
|
1450
|
+
}
|
|
1451
|
+
// Create a new event. Use nextEventSequence(events) instead of
|
|
1452
|
+
// `events.length + 1` so that any gap (from pruning, archive replay,
|
|
1453
|
+
// or self-heal dedup) cannot collide with an existing id.
|
|
1454
|
+
const event = buildEventFromMessage(currentMessages[i], nextEventSequence(events), options.recordedAt, "live", null, null, currentIngressTimes[i], currentIngressRelations[i]);
|
|
1455
|
+
events.push(event);
|
|
1456
|
+
currentEventIds.push([event.id]);
|
|
1457
|
+
}
|
|
1458
|
+
for (const id of currentEventIds.at(-1))
|
|
1459
|
+
usedEventIds.add(id);
|
|
1460
|
+
}
|
|
1461
|
+
const projectionEventIds = selectProjectedEventIds(currentMessages, currentEventIds, trimmedMessages);
|
|
1462
|
+
// Projection omissions are not authority to delete native history.
|
|
1463
|
+
const projectionIdSet = new Set(projectionEventIds);
|
|
1464
|
+
const effectiveEvents = selectEffectiveSessionEvents(events);
|
|
1465
|
+
const effectiveIds = new Set(effectiveEvents.map((event) => event.id));
|
|
1466
|
+
const evictedEvents = effectiveEvents.filter((event) => !projectionIdSet.has(event.id));
|
|
1467
|
+
const envelope = {
|
|
1468
|
+
version: 2,
|
|
1469
|
+
events,
|
|
1470
|
+
projection: {
|
|
1471
|
+
eventIds: projectionEventIds.filter((id) => effectiveIds.has(id)),
|
|
1472
|
+
trimmed: evictedEvents.length > 0,
|
|
1473
|
+
maxTokens: options.projectionBasis.maxTokens,
|
|
1474
|
+
contextMargin: options.projectionBasis.contextMargin,
|
|
1475
|
+
inputTokens: options.projectionBasis.inputTokens,
|
|
1476
|
+
projectedAt: options.recordedAt,
|
|
1477
|
+
},
|
|
1478
|
+
lastUsage: normalizeUsage(options.lastUsage),
|
|
1479
|
+
state: normalizeContinuityState(options.state),
|
|
1480
|
+
approvalSuspensions: structuredClone(options.existing?.approvalSuspensions ?? []),
|
|
1481
|
+
};
|
|
1482
|
+
envelope.structuredOutputs = (0, structured_output_1.extractStructuredOutputsFromEvents)(providerProjectionEvents(envelope));
|
|
1483
|
+
return { envelope, evictedEvents };
|
|
1484
|
+
}
|
|
1485
|
+
function appendSyntheticAssistantEvent(envelope, content, recordedAt) {
|
|
1486
|
+
// Use nextEventSequence(events) instead of `events.length + 1` so any gap
|
|
1487
|
+
// (from pruning, archive replay, or self-heal dedup) cannot collide with
|
|
1488
|
+
// an existing event id. Same fix pattern as line 1046.
|
|
1489
|
+
const sequence = nextEventSequence(envelope.events);
|
|
1490
|
+
const event = buildEventFromMessage({ role: "assistant", content }, sequence, recordedAt, "synthetic", null, null);
|
|
1491
|
+
const priorIds = projectedSessionEventIds(envelope);
|
|
1492
|
+
const updated = {
|
|
1493
|
+
...envelope,
|
|
1494
|
+
events: [...envelope.events, event],
|
|
1495
|
+
projection: {
|
|
1496
|
+
...envelope.projection,
|
|
1497
|
+
eventIds: [...priorIds, event.id],
|
|
1498
|
+
projectedAt: recordedAt,
|
|
1499
|
+
trimmed: new Set(priorIds).size < selectEffectiveSessionEvents(envelope.events).length,
|
|
1500
|
+
},
|
|
1501
|
+
};
|
|
1502
|
+
updated.structuredOutputs = (0, structured_output_1.extractStructuredOutputsFromEvents)(providerProjectionEvents(updated));
|
|
1503
|
+
return updated;
|
|
1504
|
+
}
|