@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,889 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AzureBlobMailroomStore = void 0;
|
|
4
|
+
exports.canonicalMailIndexRecordSnapshot = canonicalMailIndexRecordSnapshot;
|
|
5
|
+
exports.decryptBlobMessages = decryptBlobMessages;
|
|
6
|
+
const node_crypto_1 = require("node:crypto");
|
|
7
|
+
const runtime_1 = require("../nerves/runtime");
|
|
8
|
+
const core_1 = require("./core");
|
|
9
|
+
const search_cache_1 = require("./search-cache");
|
|
10
|
+
const MESSAGE_INDEX_PREFIX = "message-index";
|
|
11
|
+
const MESSAGE_INDEX_SORT_MAX_MS = 9_999_999_999_999;
|
|
12
|
+
const MESSAGE_INDEX_SORT_WIDTH = 13;
|
|
13
|
+
const MESSAGE_INDEX_NO_SOURCE = "~";
|
|
14
|
+
// Bumped from 20s after real HEY-corpus validation revealed that
|
|
15
|
+
// real-world mail bodies (HTML-heavy booking confirmations, MBOX-imported
|
|
16
|
+
// large messages) regularly exceed the original 20s ceiling. 60s with 2
|
|
17
|
+
// attempts = 120s max wait, which is closer to what Azure Blob actually
|
|
18
|
+
// needs for cold reads of a few-MB message body. Index reads still fit
|
|
19
|
+
// comfortably in this budget.
|
|
20
|
+
const DEFAULT_BLOB_OPERATION_TIMEOUT_MS = 60_000;
|
|
21
|
+
const DEFAULT_BLOB_DOWNLOAD_ATTEMPTS = 2;
|
|
22
|
+
const DEFAULT_MESSAGE_FETCH_CONCURRENCY = 20;
|
|
23
|
+
const DEFAULT_MESSAGE_INDEX_BACKFILL_CONCURRENCY = 8;
|
|
24
|
+
const MESSAGE_LIST_SCAN_CONCURRENCY = 32;
|
|
25
|
+
function compareNewestFirst(left, right) {
|
|
26
|
+
return Date.parse(right.receivedAt) - Date.parse(left.receivedAt);
|
|
27
|
+
}
|
|
28
|
+
function compareCandidatesNewestFirst(left, right) {
|
|
29
|
+
return Date.parse(right.lastSeenAt) - Date.parse(left.lastSeenAt);
|
|
30
|
+
}
|
|
31
|
+
function blobText(value) {
|
|
32
|
+
return Buffer.from(`${JSON.stringify(value, null, 2)}\n`, "utf-8");
|
|
33
|
+
}
|
|
34
|
+
function applyOptionalLimit(items, limit) {
|
|
35
|
+
return typeof limit === "number" ? items.slice(0, limit) : items;
|
|
36
|
+
}
|
|
37
|
+
function positiveInteger(value, fallback) {
|
|
38
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
39
|
+
return fallback;
|
|
40
|
+
const normalized = Math.floor(value);
|
|
41
|
+
return normalized > 0 ? normalized : fallback;
|
|
42
|
+
}
|
|
43
|
+
function blobClientName(blob) {
|
|
44
|
+
return typeof blob.name === "string" && blob.name.trim().length > 0 ? blob.name : "<unknown-blob>";
|
|
45
|
+
}
|
|
46
|
+
function timeoutSignal(timeoutMs) {
|
|
47
|
+
if (typeof AbortSignal.timeout === "function") {
|
|
48
|
+
return {
|
|
49
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
50
|
+
dispose() {
|
|
51
|
+
return undefined;
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const controller = new AbortController();
|
|
56
|
+
const timer = setTimeout(() => controller.abort(new Error(`The operation timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
57
|
+
return {
|
|
58
|
+
signal: controller.signal,
|
|
59
|
+
dispose() {
|
|
60
|
+
clearTimeout(timer);
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
async function withBlobOperationTimeout(timeoutMs, operation) {
|
|
65
|
+
const timeout = timeoutSignal(timeoutMs);
|
|
66
|
+
try {
|
|
67
|
+
return await operation(timeout.signal);
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
timeout.dispose();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function normalizeBlobOperationError(action, blob, timeoutMs, error) {
|
|
74
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
75
|
+
if ((error instanceof Error && error.name === "AbortError") || message.toLowerCase().includes("aborted")) {
|
|
76
|
+
return new Error(`${action} ${blobClientName(blob)} timed out after ${timeoutMs}ms`);
|
|
77
|
+
}
|
|
78
|
+
return new Error(`${action} ${blobClientName(blob)} failed: ${message}`);
|
|
79
|
+
}
|
|
80
|
+
function isRetryableBlobDownloadError(error) {
|
|
81
|
+
const message = (error instanceof Error ? error.message : String(error)).toLowerCase();
|
|
82
|
+
return message.includes("timed out") ||
|
|
83
|
+
message.includes("abort") ||
|
|
84
|
+
message.includes("econnreset") ||
|
|
85
|
+
message.includes("etimedout") ||
|
|
86
|
+
message.includes("socket closed");
|
|
87
|
+
}
|
|
88
|
+
function isBlobNotFoundError(error) {
|
|
89
|
+
const maybeStatus = typeof error === "object" && error !== null && "statusCode" in error
|
|
90
|
+
? error.statusCode
|
|
91
|
+
: undefined;
|
|
92
|
+
if (maybeStatus === 404)
|
|
93
|
+
return true;
|
|
94
|
+
const message = (error instanceof Error ? error.message : String(error)).toLowerCase();
|
|
95
|
+
return message.includes("blobnotfound") ||
|
|
96
|
+
message.includes("the specified blob does not exist") ||
|
|
97
|
+
message.includes("missing blob");
|
|
98
|
+
}
|
|
99
|
+
function messageDestinationMatches(message, resolved) {
|
|
100
|
+
return message.agentId === resolved.agentId &&
|
|
101
|
+
message.mailboxId === resolved.mailboxId &&
|
|
102
|
+
message.recipient.toLowerCase() === resolved.address.toLowerCase();
|
|
103
|
+
}
|
|
104
|
+
function isNonEmptyString(value) {
|
|
105
|
+
return typeof value === "string" && value.length > 0;
|
|
106
|
+
}
|
|
107
|
+
function isEncryptedPayload(value) {
|
|
108
|
+
if (typeof value !== "object" || value === null)
|
|
109
|
+
return false;
|
|
110
|
+
const payload = value;
|
|
111
|
+
return payload.algorithm === "RSA-OAEP-SHA256+A256GCM" &&
|
|
112
|
+
isNonEmptyString(payload.keyId) &&
|
|
113
|
+
isNonEmptyString(payload.wrappedKey) &&
|
|
114
|
+
isNonEmptyString(payload.iv) &&
|
|
115
|
+
isNonEmptyString(payload.authTag) &&
|
|
116
|
+
isNonEmptyString(payload.ciphertext);
|
|
117
|
+
}
|
|
118
|
+
function validateCommittedEncryptedMessage(value, expectedId, resolved, label) {
|
|
119
|
+
if (typeof value !== "object" || value === null) {
|
|
120
|
+
throw new Error(`${label} ${expectedId} is invalid: expected an object`);
|
|
121
|
+
}
|
|
122
|
+
const message = value;
|
|
123
|
+
if (message.schemaVersion !== 1 || message.id !== expectedId) {
|
|
124
|
+
throw new Error(`${label} ${expectedId} is invalid: message identity does not match its committed path`);
|
|
125
|
+
}
|
|
126
|
+
if (!isNonEmptyString(message.agentId) || !isNonEmptyString(message.mailboxId) || !isNonEmptyString(message.recipient)) {
|
|
127
|
+
throw new Error(`${label} ${expectedId} is invalid: resolved destination metadata is incomplete`);
|
|
128
|
+
}
|
|
129
|
+
if (!messageDestinationMatches(message, resolved)) {
|
|
130
|
+
throw new Error(`${label} ${expectedId} belongs to a different resolved destination`);
|
|
131
|
+
}
|
|
132
|
+
if (message.bodyForm !== "encrypted" || !isEncryptedPayload(message.privateEnvelope) || "private" in message) {
|
|
133
|
+
throw new Error(`${label} ${expectedId} is invalid: committed body is not a coherent encrypted payload`);
|
|
134
|
+
}
|
|
135
|
+
const rawPrefix = `raw/${expectedId}.`;
|
|
136
|
+
const rawFingerprint = isNonEmptyString(message.rawObject) && message.rawObject.startsWith(rawPrefix) && message.rawObject.endsWith(".json")
|
|
137
|
+
? message.rawObject.slice(rawPrefix.length, -".json".length)
|
|
138
|
+
: "";
|
|
139
|
+
if (!/^[a-f0-9]{64}$/.test(rawFingerprint) ||
|
|
140
|
+
!isNonEmptyString(message.rawSha256) || !/^[a-f0-9]{64}$/.test(message.rawSha256) ||
|
|
141
|
+
typeof message.rawSize !== "number" || !Number.isSafeInteger(message.rawSize) || message.rawSize < 0) {
|
|
142
|
+
throw new Error(`${label} ${expectedId} is invalid: encrypted raw metadata is incoherent`);
|
|
143
|
+
}
|
|
144
|
+
return message;
|
|
145
|
+
}
|
|
146
|
+
async function downloadJson(blob, timeoutMs) {
|
|
147
|
+
if (!await blob.exists())
|
|
148
|
+
return null;
|
|
149
|
+
let lastError = null;
|
|
150
|
+
for (let attempt = 1; attempt <= DEFAULT_BLOB_DOWNLOAD_ATTEMPTS; attempt += 1) {
|
|
151
|
+
try {
|
|
152
|
+
const buffer = await withBlobOperationTimeout(timeoutMs, (abortSignal) => {
|
|
153
|
+
return blob.downloadToBuffer(undefined, undefined, { abortSignal });
|
|
154
|
+
});
|
|
155
|
+
return JSON.parse(buffer.toString("utf-8"));
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
lastError = error;
|
|
159
|
+
if (attempt >= DEFAULT_BLOB_DOWNLOAD_ATTEMPTS || !isRetryableBlobDownloadError(error))
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
throw normalizeBlobOperationError("download", blob, timeoutMs, lastError);
|
|
164
|
+
}
|
|
165
|
+
async function downloadIndexedJson(blob, timeoutMs) {
|
|
166
|
+
let lastError = null;
|
|
167
|
+
for (let attempt = 1; attempt <= DEFAULT_BLOB_DOWNLOAD_ATTEMPTS; attempt += 1) {
|
|
168
|
+
try {
|
|
169
|
+
const buffer = await withBlobOperationTimeout(timeoutMs, (abortSignal) => {
|
|
170
|
+
return blob.downloadToBuffer(undefined, undefined, { abortSignal });
|
|
171
|
+
});
|
|
172
|
+
return JSON.parse(buffer.toString("utf-8"));
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
if (isBlobNotFoundError(error))
|
|
176
|
+
return null;
|
|
177
|
+
lastError = error;
|
|
178
|
+
if (attempt >= DEFAULT_BLOB_DOWNLOAD_ATTEMPTS || !isRetryableBlobDownloadError(error))
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
throw normalizeBlobOperationError("download", blob, timeoutMs, lastError);
|
|
183
|
+
}
|
|
184
|
+
async function uploadJson(blob, value, timeoutMs) {
|
|
185
|
+
try {
|
|
186
|
+
await withBlobOperationTimeout(timeoutMs, (abortSignal) => {
|
|
187
|
+
return blob.uploadData(blobText(value), { abortSignal });
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
throw normalizeBlobOperationError("upload", blob, timeoutMs, error);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async function mapWithConcurrency(items, concurrency, worker) {
|
|
195
|
+
if (items.length === 0)
|
|
196
|
+
return [];
|
|
197
|
+
const results = new Array(items.length);
|
|
198
|
+
let nextIndex = 0;
|
|
199
|
+
const workerLoop = async () => {
|
|
200
|
+
while (true) {
|
|
201
|
+
const current = nextIndex;
|
|
202
|
+
nextIndex += 1;
|
|
203
|
+
if (current >= items.length)
|
|
204
|
+
return;
|
|
205
|
+
results[current] = await worker(items[current], current);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, async () => workerLoop()));
|
|
209
|
+
return results;
|
|
210
|
+
}
|
|
211
|
+
function encodeSourceToken(source) {
|
|
212
|
+
return source ? encodeURIComponent(source.toLowerCase()) : MESSAGE_INDEX_NO_SOURCE;
|
|
213
|
+
}
|
|
214
|
+
function decodeSourceToken(token) {
|
|
215
|
+
return token === MESSAGE_INDEX_NO_SOURCE ? undefined : decodeURIComponent(token);
|
|
216
|
+
}
|
|
217
|
+
function parseSortMs(receivedAt) {
|
|
218
|
+
const parsed = Date.parse(receivedAt);
|
|
219
|
+
if (!Number.isFinite(parsed))
|
|
220
|
+
return 0;
|
|
221
|
+
return Math.max(0, Math.min(MESSAGE_INDEX_SORT_MAX_MS, parsed));
|
|
222
|
+
}
|
|
223
|
+
function messageIndexPrefix(agentId) {
|
|
224
|
+
return `${MESSAGE_INDEX_PREFIX}/${agentId}/`;
|
|
225
|
+
}
|
|
226
|
+
function messageIndexBlobName(message) {
|
|
227
|
+
const sortKey = String(MESSAGE_INDEX_SORT_MAX_MS - parseSortMs(message.receivedAt)).padStart(MESSAGE_INDEX_SORT_WIDTH, "0");
|
|
228
|
+
return `${messageIndexPrefix(message.agentId)}${sortKey}__${message.compartmentKind}__${message.placement}__${encodeSourceToken(message.source)}__${message.id}.json`;
|
|
229
|
+
}
|
|
230
|
+
function messageIndexRecord(message) {
|
|
231
|
+
return {
|
|
232
|
+
schemaVersion: 1,
|
|
233
|
+
id: message.id,
|
|
234
|
+
agentId: message.agentId,
|
|
235
|
+
compartmentKind: message.compartmentKind,
|
|
236
|
+
placement: message.placement,
|
|
237
|
+
...(message.source ? { source: message.source } : {}),
|
|
238
|
+
receivedAt: message.receivedAt,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function parseMessageIndexBlobName(name) {
|
|
242
|
+
if (!name.startsWith(`${MESSAGE_INDEX_PREFIX}/`) || !name.endsWith(".json"))
|
|
243
|
+
return null;
|
|
244
|
+
const parts = name.split("/");
|
|
245
|
+
if (parts.length !== 3)
|
|
246
|
+
return null;
|
|
247
|
+
const agentId = parts[1];
|
|
248
|
+
const stem = parts[2].slice(0, -5);
|
|
249
|
+
const [sortKey, compartmentKind, placement, sourceToken, ...idParts] = stem.split("__");
|
|
250
|
+
const id = idParts.join("__");
|
|
251
|
+
if (!agentId || !sortKey || !compartmentKind || !placement || !sourceToken || !id)
|
|
252
|
+
return null;
|
|
253
|
+
if (!/^\d{13}$/.test(sortKey))
|
|
254
|
+
return null;
|
|
255
|
+
if (compartmentKind !== "native" && compartmentKind !== "delegated")
|
|
256
|
+
return null;
|
|
257
|
+
if (!["imbox", "screener", "discarded", "quarantine", "draft", "sent"].includes(placement))
|
|
258
|
+
return null;
|
|
259
|
+
const decodedSource = decodeSourceToken(sourceToken);
|
|
260
|
+
const receivedAtMs = MESSAGE_INDEX_SORT_MAX_MS - Number(sortKey);
|
|
261
|
+
return {
|
|
262
|
+
schemaVersion: 1,
|
|
263
|
+
id,
|
|
264
|
+
agentId,
|
|
265
|
+
compartmentKind,
|
|
266
|
+
placement: placement,
|
|
267
|
+
...(decodedSource ? { source: decodedSource } : {}),
|
|
268
|
+
receivedAt: new Date(receivedAtMs).toISOString(),
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
function canonicalIndexRecord(record) {
|
|
272
|
+
return {
|
|
273
|
+
id: record.id,
|
|
274
|
+
receivedAt: record.receivedAt,
|
|
275
|
+
placement: record.placement,
|
|
276
|
+
compartmentKind: record.compartmentKind,
|
|
277
|
+
source: record.source?.toLowerCase() ?? null,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function canonicalMailIndexRecordSnapshot(records) {
|
|
281
|
+
const normalizedRecords = records
|
|
282
|
+
.map(canonicalIndexRecord)
|
|
283
|
+
.sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
|
|
284
|
+
const receivedTimes = records
|
|
285
|
+
.map((record) => record.receivedAt)
|
|
286
|
+
.sort((left, right) => Date.parse(left) - Date.parse(right));
|
|
287
|
+
return {
|
|
288
|
+
visibleMessageCount: records.length,
|
|
289
|
+
messageIndexFingerprint: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(normalizedRecords)).digest("hex"),
|
|
290
|
+
...(receivedTimes[0] ? { oldestReceivedAt: receivedTimes[0] } : {}),
|
|
291
|
+
...(receivedTimes.at(-1) ? { newestReceivedAt: receivedTimes.at(-1) } : {}),
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
function canonicalMailIndexRecordOrder(left, right) {
|
|
295
|
+
return JSON.stringify(canonicalIndexRecord(left)).localeCompare(JSON.stringify(canonicalIndexRecord(right)));
|
|
296
|
+
}
|
|
297
|
+
function sourceMatchesFilter(source, filter) {
|
|
298
|
+
if (!filter)
|
|
299
|
+
return true;
|
|
300
|
+
if (!source)
|
|
301
|
+
return false;
|
|
302
|
+
return source.toLowerCase() === filter.toLowerCase();
|
|
303
|
+
}
|
|
304
|
+
function messageMatchesFilters(message, filters) {
|
|
305
|
+
return message.agentId === filters.agentId &&
|
|
306
|
+
(filters.placement ? message.placement === filters.placement : true) &&
|
|
307
|
+
(filters.compartmentKind ? message.compartmentKind === filters.compartmentKind : true) &&
|
|
308
|
+
sourceMatchesFilter(message.source, filters.source);
|
|
309
|
+
}
|
|
310
|
+
class AzureBlobMailroomStore {
|
|
311
|
+
serviceClient;
|
|
312
|
+
containerName;
|
|
313
|
+
mailSearchCache;
|
|
314
|
+
blobOperationTimeoutMs;
|
|
315
|
+
messageFetchConcurrency;
|
|
316
|
+
backfillConcurrency;
|
|
317
|
+
containerReady = null;
|
|
318
|
+
constructor(options) {
|
|
319
|
+
this.serviceClient = options.serviceClient;
|
|
320
|
+
this.containerName = options.containerName;
|
|
321
|
+
this.mailSearchCache = options.mailSearchCache ?? null;
|
|
322
|
+
this.blobOperationTimeoutMs = positiveInteger(options.blobOperationTimeoutMs, DEFAULT_BLOB_OPERATION_TIMEOUT_MS);
|
|
323
|
+
this.messageFetchConcurrency = positiveInteger(options.messageFetchConcurrency, DEFAULT_MESSAGE_FETCH_CONCURRENCY);
|
|
324
|
+
this.backfillConcurrency = positiveInteger(options.backfillConcurrency, DEFAULT_MESSAGE_INDEX_BACKFILL_CONCURRENCY);
|
|
325
|
+
(0, runtime_1.emitNervesEvent)({
|
|
326
|
+
component: "senses",
|
|
327
|
+
event: "senses.mail_blob_store_init",
|
|
328
|
+
message: "azure blob mailroom store initialized",
|
|
329
|
+
meta: { containerName: this.containerName },
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
get container() {
|
|
333
|
+
return this.serviceClient.getContainerClient(this.containerName);
|
|
334
|
+
}
|
|
335
|
+
async ensureContainer() {
|
|
336
|
+
if (!this.containerReady) {
|
|
337
|
+
this.containerReady = this.container.createIfNotExists().then(() => undefined);
|
|
338
|
+
}
|
|
339
|
+
await this.containerReady;
|
|
340
|
+
}
|
|
341
|
+
messageBlob(id) {
|
|
342
|
+
return this.container.getBlockBlobClient(`messages/${id}.json`);
|
|
343
|
+
}
|
|
344
|
+
messageIndexBlob(name) {
|
|
345
|
+
return this.container.getBlockBlobClient(name);
|
|
346
|
+
}
|
|
347
|
+
candidateBlob(id) {
|
|
348
|
+
return this.container.getBlockBlobClient(`candidates/${id}.json`);
|
|
349
|
+
}
|
|
350
|
+
rawBlob(objectName) {
|
|
351
|
+
return this.container.getBlockBlobClient(objectName);
|
|
352
|
+
}
|
|
353
|
+
mailSearchCacheOptions() {
|
|
354
|
+
return this.mailSearchCache ?? {};
|
|
355
|
+
}
|
|
356
|
+
decisionsBlob(agentId) {
|
|
357
|
+
return this.container.getBlockBlobClient(`decisions/${agentId}.json`);
|
|
358
|
+
}
|
|
359
|
+
accessLogBlob(agentId) {
|
|
360
|
+
return this.container.getBlockBlobClient(`access-log/${agentId}.jsonl`);
|
|
361
|
+
}
|
|
362
|
+
outboundBlob(id) {
|
|
363
|
+
return this.container.getBlockBlobClient(`outbound/${id}.json`);
|
|
364
|
+
}
|
|
365
|
+
async putMessageIndex(message) {
|
|
366
|
+
await uploadJson(this.messageIndexBlob(messageIndexBlobName(message)), messageIndexRecord(message), this.blobOperationTimeoutMs);
|
|
367
|
+
}
|
|
368
|
+
async removeMessageIndex(message) {
|
|
369
|
+
await this.messageIndexBlob(messageIndexBlobName(message)).deleteIfExists();
|
|
370
|
+
}
|
|
371
|
+
upsertMailSearchCache(message, privateEnvelope) {
|
|
372
|
+
if (!this.mailSearchCache)
|
|
373
|
+
return;
|
|
374
|
+
(0, search_cache_1.upsertMailSearchCacheDocument)(message, privateEnvelope, this.mailSearchCache);
|
|
375
|
+
}
|
|
376
|
+
syncMailSearchCache(message) {
|
|
377
|
+
if (!this.mailSearchCache)
|
|
378
|
+
return;
|
|
379
|
+
(0, search_cache_1.syncMailSearchCacheMetadata)(message, this.mailSearchCache);
|
|
380
|
+
}
|
|
381
|
+
async listMessagesLegacy(filters) {
|
|
382
|
+
const messageBlobNames = [];
|
|
383
|
+
for await (const item of this.container.listBlobsFlat({ prefix: "messages/" })) {
|
|
384
|
+
messageBlobNames.push(item.name);
|
|
385
|
+
}
|
|
386
|
+
const matches = [];
|
|
387
|
+
let nextIndex = 0;
|
|
388
|
+
const worker = async () => {
|
|
389
|
+
while (nextIndex < messageBlobNames.length) {
|
|
390
|
+
const current = messageBlobNames[nextIndex];
|
|
391
|
+
nextIndex += 1;
|
|
392
|
+
const message = await downloadJson(this.container.getBlockBlobClient(current), this.blobOperationTimeoutMs);
|
|
393
|
+
if (!message || !messageMatchesFilters(message, filters))
|
|
394
|
+
continue;
|
|
395
|
+
matches.push(message);
|
|
396
|
+
matches.sort(compareNewestFirst);
|
|
397
|
+
if (typeof filters.limit === "number" && matches.length > filters.limit)
|
|
398
|
+
matches.length = filters.limit;
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
await Promise.all(Array.from({ length: Math.min(MESSAGE_LIST_SCAN_CONCURRENCY, Math.max(messageBlobNames.length, 1)) }, async () => worker()));
|
|
402
|
+
return applyOptionalLimit(matches.sort(compareNewestFirst), filters.limit);
|
|
403
|
+
}
|
|
404
|
+
async listMessageIndexRecords(filters) {
|
|
405
|
+
await this.ensureContainer();
|
|
406
|
+
const records = [];
|
|
407
|
+
let sawIndex = false;
|
|
408
|
+
for await (const item of this.container.listBlobsFlat({ prefix: messageIndexPrefix(filters.agentId) })) {
|
|
409
|
+
sawIndex = true;
|
|
410
|
+
const parsed = parseMessageIndexBlobName(item.name);
|
|
411
|
+
if (!parsed || !messageMatchesFilters(parsed, filters))
|
|
412
|
+
continue;
|
|
413
|
+
records.push(parsed);
|
|
414
|
+
if (typeof filters.limit === "number" && records.length >= filters.limit)
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
if (!sawIndex)
|
|
418
|
+
return null;
|
|
419
|
+
const sorted = records.sort((left, right) => Date.parse(right.receivedAt) - Date.parse(left.receivedAt));
|
|
420
|
+
(0, runtime_1.emitNervesEvent)({
|
|
421
|
+
component: "senses",
|
|
422
|
+
event: "senses.mail_blob_store_message_indexes_listed",
|
|
423
|
+
message: "azure blob mailroom store listed message indexes",
|
|
424
|
+
meta: { agentId: filters.agentId, count: sorted.length },
|
|
425
|
+
});
|
|
426
|
+
return applyOptionalLimit(sorted, filters.limit);
|
|
427
|
+
}
|
|
428
|
+
async observeMessageIndexAuthority(agentId) {
|
|
429
|
+
const prefix = messageIndexPrefix(agentId);
|
|
430
|
+
const operationTimeout = timeoutSignal(this.blobOperationTimeoutMs);
|
|
431
|
+
const records = [];
|
|
432
|
+
let totalNameCount = 0;
|
|
433
|
+
let parseFailureCount = 0;
|
|
434
|
+
try {
|
|
435
|
+
for await (const item of this.container.listBlobsFlat({ prefix, abortSignal: operationTimeout.signal })) {
|
|
436
|
+
totalNameCount += 1;
|
|
437
|
+
let parsed = null;
|
|
438
|
+
try {
|
|
439
|
+
parsed = parseMessageIndexBlobName(item.name);
|
|
440
|
+
}
|
|
441
|
+
catch {
|
|
442
|
+
parsed = null;
|
|
443
|
+
}
|
|
444
|
+
if (!parsed) {
|
|
445
|
+
parseFailureCount += 1;
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
records.push(parsed);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
catch (error) {
|
|
452
|
+
const message = (error instanceof Error ? error.message : String(error)).toLowerCase();
|
|
453
|
+
if ((error instanceof Error && error.name === "AbortError") || message.includes("aborted")) {
|
|
454
|
+
throw normalizeBlobOperationError("list", { name: prefix }, this.blobOperationTimeoutMs, error);
|
|
455
|
+
}
|
|
456
|
+
throw error;
|
|
457
|
+
}
|
|
458
|
+
finally {
|
|
459
|
+
operationTimeout.dispose();
|
|
460
|
+
}
|
|
461
|
+
records.sort(canonicalMailIndexRecordOrder);
|
|
462
|
+
const counts = new Map();
|
|
463
|
+
for (const record of records)
|
|
464
|
+
counts.set(record.id, (counts.get(record.id) ?? 0) + 1);
|
|
465
|
+
const duplicateIds = [...counts.entries()]
|
|
466
|
+
.filter(([, count]) => count > 1)
|
|
467
|
+
.map(([id]) => id)
|
|
468
|
+
.sort((left, right) => left.localeCompare(right));
|
|
469
|
+
const observation = {
|
|
470
|
+
totalNameCount,
|
|
471
|
+
parsedRecordCount: records.length,
|
|
472
|
+
parseFailureCount,
|
|
473
|
+
duplicateIds,
|
|
474
|
+
records,
|
|
475
|
+
snapshot: canonicalMailIndexRecordSnapshot(records),
|
|
476
|
+
};
|
|
477
|
+
(0, runtime_1.emitNervesEvent)({
|
|
478
|
+
component: "senses",
|
|
479
|
+
event: "senses.mail_blob_store_authority_observed",
|
|
480
|
+
message: "azure blob mailroom index authority observed",
|
|
481
|
+
meta: {
|
|
482
|
+
agentId,
|
|
483
|
+
totalNameCount,
|
|
484
|
+
parsedRecordCount: records.length,
|
|
485
|
+
parseFailureCount,
|
|
486
|
+
duplicateIdCount: duplicateIds.length,
|
|
487
|
+
},
|
|
488
|
+
});
|
|
489
|
+
return observation;
|
|
490
|
+
}
|
|
491
|
+
async listMessagesFromIndexes(filters) {
|
|
492
|
+
const records = await this.listMessageIndexRecords(filters);
|
|
493
|
+
if (records === null)
|
|
494
|
+
return null;
|
|
495
|
+
const messageIds = records.map((record) => record.id);
|
|
496
|
+
const messages = (await mapWithConcurrency(messageIds, this.messageFetchConcurrency, async (id) => {
|
|
497
|
+
return downloadJson(this.messageBlob(id), this.blobOperationTimeoutMs);
|
|
498
|
+
}))
|
|
499
|
+
.filter((message) => message !== null)
|
|
500
|
+
.filter((message) => messageMatchesFilters(message, filters))
|
|
501
|
+
.sort(compareNewestFirst);
|
|
502
|
+
return applyOptionalLimit(messages, filters.limit);
|
|
503
|
+
}
|
|
504
|
+
async backfillMessageIndexes(agentId, onProgress) {
|
|
505
|
+
await this.ensureContainer();
|
|
506
|
+
const messageBlobNames = [];
|
|
507
|
+
for await (const item of this.container.listBlobsFlat({ prefix: "messages/" })) {
|
|
508
|
+
messageBlobNames.push(item.name);
|
|
509
|
+
}
|
|
510
|
+
let indexed = 0;
|
|
511
|
+
const failures = [];
|
|
512
|
+
let scanned = 0;
|
|
513
|
+
let nextIndex = 0;
|
|
514
|
+
const worker = async () => {
|
|
515
|
+
while (nextIndex < messageBlobNames.length) {
|
|
516
|
+
const current = messageBlobNames[nextIndex];
|
|
517
|
+
nextIndex += 1;
|
|
518
|
+
try {
|
|
519
|
+
const message = await downloadJson(this.container.getBlockBlobClient(current), this.blobOperationTimeoutMs);
|
|
520
|
+
if (!message)
|
|
521
|
+
continue;
|
|
522
|
+
if (agentId && message.agentId !== agentId)
|
|
523
|
+
continue;
|
|
524
|
+
await uploadJson(this.messageIndexBlob(messageIndexBlobName(message)), messageIndexRecord(message), this.blobOperationTimeoutMs);
|
|
525
|
+
indexed += 1;
|
|
526
|
+
}
|
|
527
|
+
catch (error) {
|
|
528
|
+
failures.push(error instanceof Error ? error.message : String(error));
|
|
529
|
+
}
|
|
530
|
+
finally {
|
|
531
|
+
scanned += 1;
|
|
532
|
+
onProgress?.({ scanned, indexed, failures: failures.length, total: messageBlobNames.length });
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
await Promise.all(Array.from({ length: Math.min(this.backfillConcurrency, Math.max(messageBlobNames.length, 1)) }, async () => worker()));
|
|
537
|
+
if (failures.length > 0) {
|
|
538
|
+
const sample = failures.slice(0, 3).join("; ");
|
|
539
|
+
throw new Error(`hosted message index backfill incomplete after indexing ${indexed} message(s); ${failures.length} blob operation(s) failed. first failure(s): ${sample}. rerun the command to retry remaining messages.`);
|
|
540
|
+
}
|
|
541
|
+
(0, runtime_1.emitNervesEvent)({
|
|
542
|
+
component: "senses",
|
|
543
|
+
event: "senses.mail_blob_index_backfilled",
|
|
544
|
+
message: "azure blob mailroom message indexes backfilled",
|
|
545
|
+
meta: { agentId: agentId ?? null, indexed },
|
|
546
|
+
});
|
|
547
|
+
return indexed;
|
|
548
|
+
}
|
|
549
|
+
async putRawMessage(input) {
|
|
550
|
+
await this.ensureContainer();
|
|
551
|
+
const { message, rawPayload, privateEnvelope, candidate } = await (0, core_1.buildEncryptedStoredMailMessage)(input);
|
|
552
|
+
const messageBlob = this.messageBlob(message.id);
|
|
553
|
+
let existing = null;
|
|
554
|
+
try {
|
|
555
|
+
existing = await downloadJson(messageBlob, this.blobOperationTimeoutMs);
|
|
556
|
+
}
|
|
557
|
+
catch (error) {
|
|
558
|
+
throw new Error(`committed message ${message.id} could not be read: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
559
|
+
}
|
|
560
|
+
if (existing) {
|
|
561
|
+
const committed = validateCommittedEncryptedMessage(existing, message.id, input.resolved, "committed message");
|
|
562
|
+
this.upsertMailSearchCache(committed, privateEnvelope);
|
|
563
|
+
await this.putMessageIndex(committed);
|
|
564
|
+
(0, runtime_1.emitNervesEvent)({
|
|
565
|
+
component: "senses",
|
|
566
|
+
event: "senses.mail_blob_store_dedupe",
|
|
567
|
+
message: "azure blob mailroom store deduped existing message",
|
|
568
|
+
meta: { id: message.id, agentId: message.agentId },
|
|
569
|
+
});
|
|
570
|
+
return { created: false, message: committed };
|
|
571
|
+
}
|
|
572
|
+
await this.rawBlob(message.rawObject).uploadData(blobText(rawPayload));
|
|
573
|
+
try {
|
|
574
|
+
await messageBlob.uploadData(blobText(message), { conditions: { ifNoneMatch: "*" } });
|
|
575
|
+
}
|
|
576
|
+
catch (error) {
|
|
577
|
+
if (error?.statusCode !== 412)
|
|
578
|
+
throw error;
|
|
579
|
+
let winner;
|
|
580
|
+
try {
|
|
581
|
+
winner = await downloadJson(messageBlob, this.blobOperationTimeoutMs);
|
|
582
|
+
}
|
|
583
|
+
catch (winnerError) {
|
|
584
|
+
throw new Error(`committed winner ${message.id} could not be read: ${winnerError instanceof Error ? winnerError.message : String(winnerError)}`, { cause: winnerError });
|
|
585
|
+
}
|
|
586
|
+
if (!winner)
|
|
587
|
+
throw new Error(`committed winner ${message.id} could not be read: message blob is absent`);
|
|
588
|
+
const committed = validateCommittedEncryptedMessage(winner, message.id, input.resolved, "committed winner");
|
|
589
|
+
if (committed.rawObject !== message.rawObject) {
|
|
590
|
+
try {
|
|
591
|
+
await this.rawBlob(message.rawObject).deleteIfExists();
|
|
592
|
+
}
|
|
593
|
+
catch (cleanupError) {
|
|
594
|
+
(0, runtime_1.emitNervesEvent)({
|
|
595
|
+
level: "warn",
|
|
596
|
+
component: "senses",
|
|
597
|
+
event: "senses.mail_blob_store_loser_raw_cleanup_failed",
|
|
598
|
+
message: "azure blob mailroom store could not clean up a losing encrypted raw object",
|
|
599
|
+
meta: {
|
|
600
|
+
id: message.id,
|
|
601
|
+
agentId: message.agentId,
|
|
602
|
+
rawObject: message.rawObject,
|
|
603
|
+
error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError),
|
|
604
|
+
},
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
this.upsertMailSearchCache(committed, privateEnvelope);
|
|
609
|
+
await this.putMessageIndex(committed);
|
|
610
|
+
(0, runtime_1.emitNervesEvent)({
|
|
611
|
+
component: "senses",
|
|
612
|
+
event: "senses.mail_blob_store_dedupe",
|
|
613
|
+
message: "azure blob mailroom store deduped existing message",
|
|
614
|
+
meta: { id: committed.id, agentId: committed.agentId },
|
|
615
|
+
});
|
|
616
|
+
return { created: false, message: committed };
|
|
617
|
+
}
|
|
618
|
+
await this.putMessageIndex(message);
|
|
619
|
+
this.upsertMailSearchCache(message, privateEnvelope);
|
|
620
|
+
if (candidate) {
|
|
621
|
+
await this.candidateBlob(candidate.id).uploadData(blobText(candidate));
|
|
622
|
+
}
|
|
623
|
+
(0, runtime_1.emitNervesEvent)({
|
|
624
|
+
component: "senses",
|
|
625
|
+
event: "senses.mail_blob_store_message_written",
|
|
626
|
+
message: "azure blob mailroom store wrote message",
|
|
627
|
+
meta: { id: message.id, agentId: message.agentId, candidate: candidate !== undefined },
|
|
628
|
+
});
|
|
629
|
+
return { created: true, message };
|
|
630
|
+
}
|
|
631
|
+
async getMessage(id) {
|
|
632
|
+
await this.ensureContainer();
|
|
633
|
+
const message = await downloadJson(this.messageBlob(id), this.blobOperationTimeoutMs);
|
|
634
|
+
(0, runtime_1.emitNervesEvent)({
|
|
635
|
+
component: "senses",
|
|
636
|
+
event: "senses.mail_blob_store_message_read",
|
|
637
|
+
message: "azure blob mailroom store read message",
|
|
638
|
+
meta: { id, found: message !== null },
|
|
639
|
+
});
|
|
640
|
+
return message;
|
|
641
|
+
}
|
|
642
|
+
async getIndexedMessageById(id) {
|
|
643
|
+
const message = await downloadIndexedJson(this.messageBlob(id), this.blobOperationTimeoutMs);
|
|
644
|
+
(0, runtime_1.emitNervesEvent)({
|
|
645
|
+
component: "senses",
|
|
646
|
+
event: "senses.mail_blob_store_indexed_message_read",
|
|
647
|
+
message: "azure blob mailroom store read message from known index",
|
|
648
|
+
meta: { id, found: message !== null },
|
|
649
|
+
});
|
|
650
|
+
return message;
|
|
651
|
+
}
|
|
652
|
+
async listMessages(filters) {
|
|
653
|
+
await this.ensureContainer();
|
|
654
|
+
let filtered = await this.listMessagesFromIndexes(filters);
|
|
655
|
+
let source = "index";
|
|
656
|
+
if (filtered === null) {
|
|
657
|
+
filtered = await this.listMessagesLegacy(filters);
|
|
658
|
+
source = "legacy";
|
|
659
|
+
}
|
|
660
|
+
(0, runtime_1.emitNervesEvent)({
|
|
661
|
+
component: "senses",
|
|
662
|
+
event: "senses.mail_blob_store_messages_listed",
|
|
663
|
+
message: "azure blob mailroom store listed messages",
|
|
664
|
+
meta: { agentId: filters.agentId, count: filtered.length, source },
|
|
665
|
+
});
|
|
666
|
+
return filtered;
|
|
667
|
+
}
|
|
668
|
+
async updateMessagePlacement(id, placement) {
|
|
669
|
+
await this.ensureContainer();
|
|
670
|
+
const blob = this.messageBlob(id);
|
|
671
|
+
const message = await downloadJson(blob, this.blobOperationTimeoutMs);
|
|
672
|
+
if (!message) {
|
|
673
|
+
(0, runtime_1.emitNervesEvent)({
|
|
674
|
+
component: "senses",
|
|
675
|
+
event: "senses.mail_blob_store_message_placement_updated",
|
|
676
|
+
message: "azure blob mailroom store message placement update missed",
|
|
677
|
+
meta: { id, placement, found: false },
|
|
678
|
+
});
|
|
679
|
+
return null;
|
|
680
|
+
}
|
|
681
|
+
const updated = { ...message, placement };
|
|
682
|
+
await blob.uploadData(blobText(updated));
|
|
683
|
+
await this.removeMessageIndex(message);
|
|
684
|
+
await this.putMessageIndex(updated);
|
|
685
|
+
this.syncMailSearchCache(updated);
|
|
686
|
+
(0, runtime_1.emitNervesEvent)({
|
|
687
|
+
component: "senses",
|
|
688
|
+
event: "senses.mail_blob_store_message_placement_updated",
|
|
689
|
+
message: "azure blob mailroom store updated message placement",
|
|
690
|
+
meta: { id, placement, found: true },
|
|
691
|
+
});
|
|
692
|
+
return updated;
|
|
693
|
+
}
|
|
694
|
+
async readRawMime(message, privateKeys) {
|
|
695
|
+
await this.ensureContainer();
|
|
696
|
+
/* v8 ignore start -- defensive: AzureBlobMailroomStore only writes encrypted messages, but the interface accepts the union @preserve */
|
|
697
|
+
if (message.bodyForm !== "encrypted") {
|
|
698
|
+
throw new Error(`AzureBlobMailroomStore stores encrypted messages, got bodyForm=${message.bodyForm} for ${message.id}`);
|
|
699
|
+
}
|
|
700
|
+
/* v8 ignore stop */
|
|
701
|
+
const payload = await downloadJson(this.rawBlob(message.rawObject), this.blobOperationTimeoutMs);
|
|
702
|
+
if (!payload) {
|
|
703
|
+
(0, runtime_1.emitNervesEvent)({
|
|
704
|
+
component: "senses",
|
|
705
|
+
event: "senses.mail_blob_store_raw_read",
|
|
706
|
+
message: "azure blob mailroom store read raw payload",
|
|
707
|
+
meta: { id: message.id, found: false },
|
|
708
|
+
});
|
|
709
|
+
return null;
|
|
710
|
+
}
|
|
711
|
+
const privateKey = privateKeys[payload.keyId];
|
|
712
|
+
/* v8 ignore start -- runtime config refuses to load without private keys; defensive check for partial vault state @preserve */
|
|
713
|
+
if (!privateKey) {
|
|
714
|
+
throw new Error(`Missing private mail key ${payload.keyId}`);
|
|
715
|
+
}
|
|
716
|
+
/* v8 ignore stop */
|
|
717
|
+
const plaintext = (0, core_1.decryptMailPayload)(payload, privateKey);
|
|
718
|
+
(0, runtime_1.emitNervesEvent)({
|
|
719
|
+
component: "senses",
|
|
720
|
+
event: "senses.mail_blob_store_raw_read",
|
|
721
|
+
message: "azure blob mailroom store read raw payload",
|
|
722
|
+
meta: { id: message.id, found: true },
|
|
723
|
+
});
|
|
724
|
+
return plaintext;
|
|
725
|
+
}
|
|
726
|
+
/** Test helper: read the encrypted raw payload without decrypting. */
|
|
727
|
+
async readEncryptedRawPayload(message) {
|
|
728
|
+
await this.ensureContainer();
|
|
729
|
+
return downloadJson(this.rawBlob(message.rawObject), this.blobOperationTimeoutMs);
|
|
730
|
+
}
|
|
731
|
+
async putScreenerCandidate(candidate) {
|
|
732
|
+
await this.ensureContainer();
|
|
733
|
+
await this.candidateBlob(candidate.id).uploadData(blobText(candidate));
|
|
734
|
+
(0, runtime_1.emitNervesEvent)({
|
|
735
|
+
component: "senses",
|
|
736
|
+
event: "senses.mail_blob_screener_candidate_written",
|
|
737
|
+
message: "azure blob mail screener candidate written",
|
|
738
|
+
meta: { id: candidate.id, agentId: candidate.agentId, status: candidate.status },
|
|
739
|
+
});
|
|
740
|
+
return candidate;
|
|
741
|
+
}
|
|
742
|
+
async updateScreenerCandidate(candidate) {
|
|
743
|
+
return this.putScreenerCandidate(candidate);
|
|
744
|
+
}
|
|
745
|
+
async listScreenerCandidates(filters) {
|
|
746
|
+
await this.ensureContainer();
|
|
747
|
+
const candidates = [];
|
|
748
|
+
for await (const item of this.container.listBlobsFlat({ prefix: "candidates/" })) {
|
|
749
|
+
const candidate = await downloadJson(this.container.getBlockBlobClient(item.name), this.blobOperationTimeoutMs);
|
|
750
|
+
if (candidate)
|
|
751
|
+
candidates.push(candidate);
|
|
752
|
+
}
|
|
753
|
+
const filtered = candidates
|
|
754
|
+
.filter((candidate) => candidate.agentId === filters.agentId)
|
|
755
|
+
.filter((candidate) => filters.status ? candidate.status === filters.status : true)
|
|
756
|
+
.filter((candidate) => filters.placement ? candidate.placement === filters.placement : true)
|
|
757
|
+
.sort(compareCandidatesNewestFirst)
|
|
758
|
+
.slice(0, filters.limit ?? 50);
|
|
759
|
+
(0, runtime_1.emitNervesEvent)({
|
|
760
|
+
component: "senses",
|
|
761
|
+
event: "senses.mail_blob_screener_candidates_listed",
|
|
762
|
+
message: "azure blob mail screener candidates listed",
|
|
763
|
+
meta: { agentId: filters.agentId, count: filtered.length },
|
|
764
|
+
});
|
|
765
|
+
return filtered;
|
|
766
|
+
}
|
|
767
|
+
async recordMailDecision(entry) {
|
|
768
|
+
await this.ensureContainer();
|
|
769
|
+
const complete = {
|
|
770
|
+
schemaVersion: 1,
|
|
771
|
+
...entry,
|
|
772
|
+
id: entry.id ?? `decision_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
773
|
+
createdAt: entry.createdAt ?? new Date().toISOString(),
|
|
774
|
+
};
|
|
775
|
+
const blob = this.decisionsBlob(entry.agentId);
|
|
776
|
+
const existing = await downloadJson(blob, this.blobOperationTimeoutMs).catch(() => null);
|
|
777
|
+
const entries = Array.isArray(existing) ? existing : [];
|
|
778
|
+
entries.push(complete);
|
|
779
|
+
await blob.uploadData(blobText(entries));
|
|
780
|
+
(0, runtime_1.emitNervesEvent)({
|
|
781
|
+
component: "senses",
|
|
782
|
+
event: "senses.mail_blob_decision_recorded",
|
|
783
|
+
message: "azure blob mail decision recorded",
|
|
784
|
+
meta: { agentId: entry.agentId, messageId: entry.messageId, action: entry.action },
|
|
785
|
+
});
|
|
786
|
+
return complete;
|
|
787
|
+
}
|
|
788
|
+
async listMailDecisions(agentId) {
|
|
789
|
+
await this.ensureContainer();
|
|
790
|
+
const entries = await downloadJson(this.decisionsBlob(agentId), this.blobOperationTimeoutMs);
|
|
791
|
+
const safeEntries = Array.isArray(entries) ? entries : [];
|
|
792
|
+
(0, runtime_1.emitNervesEvent)({
|
|
793
|
+
component: "senses",
|
|
794
|
+
event: "senses.mail_blob_decisions_listed",
|
|
795
|
+
message: "azure blob mail decisions listed",
|
|
796
|
+
meta: { agentId, count: safeEntries.length },
|
|
797
|
+
});
|
|
798
|
+
return safeEntries;
|
|
799
|
+
}
|
|
800
|
+
async upsertMailOutbound(record) {
|
|
801
|
+
await this.ensureContainer();
|
|
802
|
+
await this.outboundBlob(record.id).uploadData(blobText(record));
|
|
803
|
+
(0, runtime_1.emitNervesEvent)({
|
|
804
|
+
component: "senses",
|
|
805
|
+
event: "senses.mail_blob_outbound_record_written",
|
|
806
|
+
message: "azure blob mail outbound record written",
|
|
807
|
+
meta: { agentId: record.agentId, id: record.id, status: record.status },
|
|
808
|
+
});
|
|
809
|
+
return record;
|
|
810
|
+
}
|
|
811
|
+
async getMailOutbound(id) {
|
|
812
|
+
await this.ensureContainer();
|
|
813
|
+
const record = await downloadJson(this.outboundBlob(id), this.blobOperationTimeoutMs);
|
|
814
|
+
(0, runtime_1.emitNervesEvent)({
|
|
815
|
+
component: "senses",
|
|
816
|
+
event: "senses.mail_blob_outbound_record_read",
|
|
817
|
+
message: "azure blob mail outbound record read",
|
|
818
|
+
meta: { id, found: record !== null },
|
|
819
|
+
});
|
|
820
|
+
return record;
|
|
821
|
+
}
|
|
822
|
+
async listMailOutbound(agentId, options = {}) {
|
|
823
|
+
await this.ensureContainer();
|
|
824
|
+
// Stream blob NAMES first (no JSON downloads yet), then fan out the
|
|
825
|
+
// downloads with bounded concurrency. The previous loop awaited each
|
|
826
|
+
// download sequentially inside the iterator, which made every Mailbox
|
|
827
|
+
// tab open scale linearly with the total number of outbound records in
|
|
828
|
+
// the container — for an agent with many drafts/sent/failed entries,
|
|
829
|
+
// the UI sat on a loading spinner for minutes (or never finished) while
|
|
830
|
+
// the daemon ground through O(N) sequential network round-trips.
|
|
831
|
+
const outboundBlobNames = [];
|
|
832
|
+
for await (const item of this.container.listBlobsFlat({ prefix: "outbound/" })) {
|
|
833
|
+
outboundBlobNames.push(item.name);
|
|
834
|
+
}
|
|
835
|
+
const downloaded = await mapWithConcurrency(outboundBlobNames, MESSAGE_LIST_SCAN_CONCURRENCY, async (name) => {
|
|
836
|
+
return downloadJson(this.container.getBlockBlobClient(name), this.blobOperationTimeoutMs);
|
|
837
|
+
});
|
|
838
|
+
const filtered = downloaded
|
|
839
|
+
.filter((record) => record !== null)
|
|
840
|
+
.filter((record) => record.agentId === agentId)
|
|
841
|
+
.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
842
|
+
const limited = typeof options.limit === "number" && options.limit > 0
|
|
843
|
+
? filtered.slice(0, options.limit)
|
|
844
|
+
: filtered;
|
|
845
|
+
(0, runtime_1.emitNervesEvent)({
|
|
846
|
+
component: "senses",
|
|
847
|
+
event: "senses.mail_blob_outbound_records_listed",
|
|
848
|
+
message: "azure blob mail outbound records listed",
|
|
849
|
+
meta: { agentId, count: limited.length, scanned: outboundBlobNames.length },
|
|
850
|
+
});
|
|
851
|
+
return limited;
|
|
852
|
+
}
|
|
853
|
+
async recordAccess(entry) {
|
|
854
|
+
await this.ensureContainer();
|
|
855
|
+
const complete = {
|
|
856
|
+
...entry,
|
|
857
|
+
id: `access_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
858
|
+
accessedAt: new Date().toISOString(),
|
|
859
|
+
};
|
|
860
|
+
const blob = this.accessLogBlob(entry.agentId);
|
|
861
|
+
const existing = await downloadJson(blob, this.blobOperationTimeoutMs).catch(() => null);
|
|
862
|
+
const entries = Array.isArray(existing) ? existing : [];
|
|
863
|
+
entries.push(complete);
|
|
864
|
+
await blob.uploadData(blobText(entries));
|
|
865
|
+
(0, runtime_1.emitNervesEvent)({
|
|
866
|
+
component: "senses",
|
|
867
|
+
event: "senses.mail_blob_access_recorded",
|
|
868
|
+
message: "azure blob mail access recorded",
|
|
869
|
+
meta: { agentId: entry.agentId, messageId: entry.messageId ?? null, tool: entry.tool },
|
|
870
|
+
});
|
|
871
|
+
return complete;
|
|
872
|
+
}
|
|
873
|
+
async listAccessLog(agentId) {
|
|
874
|
+
await this.ensureContainer();
|
|
875
|
+
const entries = await downloadJson(this.accessLogBlob(agentId), this.blobOperationTimeoutMs);
|
|
876
|
+
const safeEntries = (Array.isArray(entries) ? entries : []);
|
|
877
|
+
(0, runtime_1.emitNervesEvent)({
|
|
878
|
+
component: "senses",
|
|
879
|
+
event: "senses.mail_blob_access_log_listed",
|
|
880
|
+
message: "azure blob mail access log listed",
|
|
881
|
+
meta: { agentId, count: safeEntries.length },
|
|
882
|
+
});
|
|
883
|
+
return safeEntries;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
exports.AzureBlobMailroomStore = AzureBlobMailroomStore;
|
|
887
|
+
function decryptBlobMessages(messages, privateKeys) {
|
|
888
|
+
return messages.map((message) => (0, core_1.readDecryptedMailMessage)(message, privateKeys));
|
|
889
|
+
}
|