@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,1157 @@
|
|
|
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.contextLossSentinelPaths = contextLossSentinelPaths;
|
|
37
|
+
exports.deriveContextLossSentinelProviderSignals = deriveContextLossSentinelProviderSignals;
|
|
38
|
+
exports.refreshContextLossSentinel = refreshContextLossSentinel;
|
|
39
|
+
exports.readContextLossSentinelView = readContextLossSentinelView;
|
|
40
|
+
exports.formatContextLossSentinelText = formatContextLossSentinelText;
|
|
41
|
+
exports.formatContextLossSentinelJson = formatContextLossSentinelJson;
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const child_process_1 = require("child_process");
|
|
45
|
+
const crypto_1 = require("crypto");
|
|
46
|
+
const flight_recorder_1 = require("../arc/flight-recorder");
|
|
47
|
+
const runtime_1 = require("../nerves/runtime");
|
|
48
|
+
const context_loss_gauntlet_1 = require("./context-loss-gauntlet");
|
|
49
|
+
const provider_visibility_1 = require("./provider-visibility");
|
|
50
|
+
const CONTEXT_LOSS_SENTINEL_RECEIPT_FILE_LIMIT = 512;
|
|
51
|
+
const CONTEXT_LOSS_SENTINEL_GIT_STATUS_TIMEOUT_MS = 5_000;
|
|
52
|
+
const REQUIRED_LANES = ["outward", "inner"];
|
|
53
|
+
function logicalLocator(...parts) {
|
|
54
|
+
return path.posix.join(...parts);
|
|
55
|
+
}
|
|
56
|
+
function relativeSentinelRoot() {
|
|
57
|
+
return logicalLocator("arc", "flight-recorder", "context-loss-sentinel");
|
|
58
|
+
}
|
|
59
|
+
function latestLocator() {
|
|
60
|
+
return logicalLocator(relativeSentinelRoot(), "latest.json");
|
|
61
|
+
}
|
|
62
|
+
function latestReadyLocator() {
|
|
63
|
+
return logicalLocator(relativeSentinelRoot(), "latest-ready.json");
|
|
64
|
+
}
|
|
65
|
+
function receiptLocator(receiptId) {
|
|
66
|
+
return logicalLocator(relativeSentinelRoot(), "receipts", `${receiptId}.json`);
|
|
67
|
+
}
|
|
68
|
+
function historyDay(generatedAt) {
|
|
69
|
+
return generatedAt.slice(0, 10);
|
|
70
|
+
}
|
|
71
|
+
function historyLocator(generatedAt) {
|
|
72
|
+
return logicalLocator(relativeSentinelRoot(), "history", `${historyDay(generatedAt)}.jsonl`);
|
|
73
|
+
}
|
|
74
|
+
function contextLossSentinelPaths(agentRoot) {
|
|
75
|
+
const rootDir = path.join(agentRoot, relativeSentinelRoot());
|
|
76
|
+
return {
|
|
77
|
+
rootDir,
|
|
78
|
+
latest: path.join(rootDir, "latest.json"),
|
|
79
|
+
latestReady: path.join(rootDir, "latest-ready.json"),
|
|
80
|
+
historyDir: path.join(rootDir, "history"),
|
|
81
|
+
receiptsDir: path.join(rootDir, "receipts"),
|
|
82
|
+
lock: path.join(rootDir, ".write.lock"),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function atomicWriteJson(filePath, value) {
|
|
86
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
87
|
+
const tmpPath = `${filePath}.${process.pid}.${Date.now()}.${(0, crypto_1.randomUUID)()}.tmp`;
|
|
88
|
+
fs.writeFileSync(tmpPath, `${JSON.stringify(value, null, 2)}\n`, "utf-8");
|
|
89
|
+
fs.renameSync(tmpPath, filePath);
|
|
90
|
+
}
|
|
91
|
+
function sleep(ms) {
|
|
92
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
93
|
+
}
|
|
94
|
+
async function withFileLock(lockPath, timeoutMs, fn) {
|
|
95
|
+
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
96
|
+
const startedAt = Date.now();
|
|
97
|
+
while (true) {
|
|
98
|
+
try {
|
|
99
|
+
const fd = fs.openSync(lockPath, "wx");
|
|
100
|
+
try {
|
|
101
|
+
fs.writeFileSync(fd, `${process.pid}\n`, "utf-8");
|
|
102
|
+
return await fn();
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
fs.closeSync(fd);
|
|
106
|
+
fs.rmSync(lockPath, { force: true });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
const code = String(error.code);
|
|
111
|
+
if (code !== "EEXIST") {
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
if (Date.now() - startedAt > timeoutMs) {
|
|
115
|
+
throw new Error(`context-loss Sentinel lock timed out: ${lockPath}`);
|
|
116
|
+
}
|
|
117
|
+
await sleep(5);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function compareReceiptOrder(left, right) {
|
|
122
|
+
const leftTime = Date.parse(left.generatedAt);
|
|
123
|
+
const rightTime = Date.parse(right.generatedAt);
|
|
124
|
+
if (leftTime !== rightTime)
|
|
125
|
+
return leftTime - rightTime;
|
|
126
|
+
return left.id.localeCompare(right.id);
|
|
127
|
+
}
|
|
128
|
+
function shouldReplaceReceipt(existing, candidate) {
|
|
129
|
+
return existing === null || compareReceiptOrder(candidate, existing) >= 0;
|
|
130
|
+
}
|
|
131
|
+
function orderFromReceipt(receipt) {
|
|
132
|
+
return { generatedAt: receipt.generatedAt, id: receipt.id };
|
|
133
|
+
}
|
|
134
|
+
function compareOrder(left, right) {
|
|
135
|
+
const leftTime = Date.parse(left.generatedAt);
|
|
136
|
+
const rightTime = Date.parse(right.generatedAt);
|
|
137
|
+
if (leftTime !== rightTime)
|
|
138
|
+
return leftTime - rightTime;
|
|
139
|
+
return left.id.localeCompare(right.id);
|
|
140
|
+
}
|
|
141
|
+
function maxOrder(left, right) {
|
|
142
|
+
if (!left)
|
|
143
|
+
return right;
|
|
144
|
+
if (!right)
|
|
145
|
+
return left;
|
|
146
|
+
return compareOrder(left, right) >= 0 ? left : right;
|
|
147
|
+
}
|
|
148
|
+
function isOrder(value) {
|
|
149
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
150
|
+
return false;
|
|
151
|
+
const record = value;
|
|
152
|
+
return typeof record.generatedAt === "string"
|
|
153
|
+
&& isValidTimestamp(record.generatedAt)
|
|
154
|
+
&& typeof record.id === "string";
|
|
155
|
+
}
|
|
156
|
+
function sentinelWatermarkPath(agentRoot) {
|
|
157
|
+
return path.join(agentRoot, "state", "arc", "context-loss-sentinel-watermark.json");
|
|
158
|
+
}
|
|
159
|
+
function readWatermark(agentRoot) {
|
|
160
|
+
const filePath = sentinelWatermarkPath(agentRoot);
|
|
161
|
+
try {
|
|
162
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
163
|
+
return {
|
|
164
|
+
schemaVersion: 1,
|
|
165
|
+
latest: isOrder(parsed.latest) ? parsed.latest : null,
|
|
166
|
+
latestReady: isOrder(parsed.latestReady) ? parsed.latestReady : null,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return { schemaVersion: 1, latest: null, latestReady: null };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function writeWatermark(agentRoot, watermark) {
|
|
174
|
+
atomicWriteJson(sentinelWatermarkPath(agentRoot), watermark);
|
|
175
|
+
}
|
|
176
|
+
function updateWatermarkOrder(watermark, key, candidate) {
|
|
177
|
+
watermark[key] = maxOrder(watermark[key], orderFromReceipt(candidate));
|
|
178
|
+
}
|
|
179
|
+
function stableReceiptState(receipt) {
|
|
180
|
+
return JSON.stringify({
|
|
181
|
+
verdict: receipt.verdict,
|
|
182
|
+
summary: receipt.summary,
|
|
183
|
+
latestReadyLocator: receipt.latestReadyLocator,
|
|
184
|
+
recoveryAnchor: receipt.recoveryAnchor,
|
|
185
|
+
gauntlet: receipt.gauntlet,
|
|
186
|
+
signals: receipt.signals,
|
|
187
|
+
resumeSnapshot: receipt.resumeSnapshot,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
function sameRecoveryState(left, right) {
|
|
191
|
+
return stableReceiptState(left) === stableReceiptState(right);
|
|
192
|
+
}
|
|
193
|
+
function setReceiptFileMtime(filePath, receipt) {
|
|
194
|
+
const timestamp = new Date(receipt.generatedAt);
|
|
195
|
+
fs.utimesSync(filePath, timestamp, timestamp);
|
|
196
|
+
}
|
|
197
|
+
function selectLatestReadyReceipt(existingReady, candidate, latestReadyOrder) {
|
|
198
|
+
if (candidate.verdict !== "ready")
|
|
199
|
+
return existingReady;
|
|
200
|
+
if (latestReadyOrder && compareOrder(orderFromReceipt(candidate), latestReadyOrder) < 0)
|
|
201
|
+
return existingReady;
|
|
202
|
+
return candidate;
|
|
203
|
+
}
|
|
204
|
+
function shouldReplaceLatestReceipt(existingLatest, candidate, latestOrder) {
|
|
205
|
+
if (latestOrder && compareOrder(orderFromReceipt(candidate), latestOrder) < 0)
|
|
206
|
+
return false;
|
|
207
|
+
if (!existingLatest)
|
|
208
|
+
return true;
|
|
209
|
+
return shouldReplaceReceipt(existingLatest, candidate);
|
|
210
|
+
}
|
|
211
|
+
function coalescedDaemonHealthReceipt(agentRoot, existingLatest, existingReady, candidate, watermark) {
|
|
212
|
+
if (candidate.trigger !== "daemon_health")
|
|
213
|
+
return null;
|
|
214
|
+
if (!existingLatest || !sameRecoveryState(existingLatest, candidate))
|
|
215
|
+
return null;
|
|
216
|
+
if (candidate.verdict === "ready" && (!existingReady || !sameRecoveryState(existingReady, candidate)))
|
|
217
|
+
return null;
|
|
218
|
+
const candidateOrder = orderFromReceipt(candidate);
|
|
219
|
+
if (watermark.latest && compareOrder(candidateOrder, watermark.latest) <= 0)
|
|
220
|
+
return existingLatest;
|
|
221
|
+
updateWatermarkOrder(watermark, "latest", candidate);
|
|
222
|
+
if (candidate.verdict === "ready" && existingReady) {
|
|
223
|
+
updateWatermarkOrder(watermark, "latestReady", candidate);
|
|
224
|
+
}
|
|
225
|
+
writeWatermark(agentRoot, watermark);
|
|
226
|
+
return existingLatest;
|
|
227
|
+
}
|
|
228
|
+
function readJson(filePath) {
|
|
229
|
+
try {
|
|
230
|
+
return { ok: true, value: JSON.parse(fs.readFileSync(filePath, "utf-8")) };
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
return { ok: false, reason: String(error) };
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function isStringArray(value) {
|
|
237
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
238
|
+
}
|
|
239
|
+
function isValidTimestamp(value) {
|
|
240
|
+
return Number.isFinite(Date.parse(value));
|
|
241
|
+
}
|
|
242
|
+
function isSource(value) {
|
|
243
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
244
|
+
return false;
|
|
245
|
+
const record = value;
|
|
246
|
+
return typeof record.kind === "string" && typeof record.locator === "string";
|
|
247
|
+
}
|
|
248
|
+
function isRepair(value) {
|
|
249
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
250
|
+
return false;
|
|
251
|
+
const record = value;
|
|
252
|
+
return (record.actor === "agent-runnable" || record.actor === "human-required" || record.actor === "human-choice")
|
|
253
|
+
&& typeof record.kind === "string"
|
|
254
|
+
&& (record.command === undefined || typeof record.command === "string")
|
|
255
|
+
&& typeof record.detail === "string";
|
|
256
|
+
}
|
|
257
|
+
function isSignal(value) {
|
|
258
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
259
|
+
return false;
|
|
260
|
+
const record = value;
|
|
261
|
+
return typeof record.id === "string"
|
|
262
|
+
&& (record.kind === "gauntlet" || record.kind === "provider_lane" || record.kind === "sense" || record.kind === "daemon" || record.kind === "bundle")
|
|
263
|
+
&& (record.status === "pass" || record.status === "warn" || record.status === "fail")
|
|
264
|
+
&& (record.severity === "info" || record.severity === "warn" || record.severity === "critical")
|
|
265
|
+
&& (record.verdictImpact === "none" || record.verdictImpact === "watch" || record.verdictImpact === "blocked")
|
|
266
|
+
&& typeof record.summary === "string"
|
|
267
|
+
&& isSource(record.source)
|
|
268
|
+
&& (record.repair === undefined || isRepair(record.repair));
|
|
269
|
+
}
|
|
270
|
+
function isRecoveryAnchor(value) {
|
|
271
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
272
|
+
return false;
|
|
273
|
+
const record = value;
|
|
274
|
+
return (record.kind === "flight-recorder" || record.kind === "latest-ready")
|
|
275
|
+
&& (record.currentAsk === null || typeof record.currentAsk === "string")
|
|
276
|
+
&& (record.nextSafeAction === null || typeof record.nextSafeAction === "string")
|
|
277
|
+
&& typeof record.flightRecorderLatestLocator === "string"
|
|
278
|
+
&& isStringArray(record.sourceEventIds)
|
|
279
|
+
&& (record.recordedAt === null || typeof record.recordedAt === "string");
|
|
280
|
+
}
|
|
281
|
+
function isGauntletSummary(value) {
|
|
282
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
283
|
+
return false;
|
|
284
|
+
const record = value;
|
|
285
|
+
return (record.verdict === "ready" || record.verdict === "watch" || record.verdict === "blocked")
|
|
286
|
+
&& typeof record.scorePercentage === "number"
|
|
287
|
+
&& isStringArray(record.failedChecks)
|
|
288
|
+
&& isStringArray(record.warnedChecks)
|
|
289
|
+
&& typeof record.sourceLocator === "string";
|
|
290
|
+
}
|
|
291
|
+
function isResumeSnapshot(value) {
|
|
292
|
+
return (0, flight_recorder_1.isFlightRecorderResume)(value);
|
|
293
|
+
}
|
|
294
|
+
function isReceipt(value) {
|
|
295
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
296
|
+
return false;
|
|
297
|
+
const record = value;
|
|
298
|
+
return record.schemaVersion === 1
|
|
299
|
+
&& typeof record.id === "string"
|
|
300
|
+
&& typeof record.agent === "string"
|
|
301
|
+
&& (record.trigger === "post_turn" || record.trigger === "provider_failover" || record.trigger === "daemon_startup" || record.trigger === "daemon_health" || record.trigger === "session_start" || record.trigger === "manual_cli")
|
|
302
|
+
&& typeof record.generatedAt === "string"
|
|
303
|
+
&& isValidTimestamp(record.generatedAt)
|
|
304
|
+
&& (record.verdict === "ready" || record.verdict === "watch" || record.verdict === "blocked")
|
|
305
|
+
&& typeof record.summary === "string"
|
|
306
|
+
&& typeof record.receiptLocator === "string"
|
|
307
|
+
&& (record.latestReadyLocator === null || typeof record.latestReadyLocator === "string")
|
|
308
|
+
&& isRecoveryAnchor(record.recoveryAnchor)
|
|
309
|
+
&& isGauntletSummary(record.gauntlet)
|
|
310
|
+
&& Array.isArray(record.signals)
|
|
311
|
+
&& record.signals.every(isSignal)
|
|
312
|
+
&& isStringArray(record.sourceLocators)
|
|
313
|
+
&& isResumeSnapshot(record.resumeSnapshot);
|
|
314
|
+
}
|
|
315
|
+
function readReceiptFile(filePath, label, issues) {
|
|
316
|
+
if (!fs.existsSync(filePath))
|
|
317
|
+
return null;
|
|
318
|
+
const parsed = readJson(filePath);
|
|
319
|
+
if (!parsed.ok) {
|
|
320
|
+
issues.push(`${label} unreadable: ${parsed.reason}`);
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
if (!isReceipt(parsed.value)) {
|
|
324
|
+
issues.push(`${label} malformed`);
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
return parsed.value;
|
|
328
|
+
}
|
|
329
|
+
function receiptIdFromFileName(fileName) {
|
|
330
|
+
return fileName.endsWith(".json") ? fileName.slice(0, -".json".length) : null;
|
|
331
|
+
}
|
|
332
|
+
function receiptOrderFromDetailFile(filePath, fileName) {
|
|
333
|
+
try {
|
|
334
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
335
|
+
if (isReceipt(parsed))
|
|
336
|
+
return orderFromReceipt(parsed);
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
// Malformed legacy detail files should not block retention cleanup.
|
|
340
|
+
}
|
|
341
|
+
const stat = fs.statSync(filePath);
|
|
342
|
+
return {
|
|
343
|
+
generatedAt: new Date(stat.mtimeMs).toISOString(),
|
|
344
|
+
id: receiptIdFromFileName(fileName),
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
function pruneReceiptDetailFiles(paths, protectedReceiptIds) {
|
|
348
|
+
const fileNames = fs.readdirSync(paths.receiptsDir)
|
|
349
|
+
.filter((entry) => receiptIdFromFileName(entry) !== null);
|
|
350
|
+
if (fileNames.length <= CONTEXT_LOSS_SENTINEL_RECEIPT_FILE_LIMIT)
|
|
351
|
+
return;
|
|
352
|
+
const candidates = fileNames
|
|
353
|
+
.map((fileName) => ({
|
|
354
|
+
fileName,
|
|
355
|
+
filePath: path.join(paths.receiptsDir, fileName),
|
|
356
|
+
receiptId: receiptIdFromFileName(fileName),
|
|
357
|
+
order: receiptOrderFromDetailFile(path.join(paths.receiptsDir, fileName), fileName),
|
|
358
|
+
}))
|
|
359
|
+
.filter((entry) => !protectedReceiptIds.has(entry.receiptId))
|
|
360
|
+
.sort((left, right) => compareOrder(left.order, right.order));
|
|
361
|
+
const removalCount = fileNames.length - CONTEXT_LOSS_SENTINEL_RECEIPT_FILE_LIMIT;
|
|
362
|
+
for (const entry of candidates.slice(0, removalCount)) {
|
|
363
|
+
fs.rmSync(entry.filePath, { force: true });
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function isNonEmptyText(value) {
|
|
367
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
368
|
+
}
|
|
369
|
+
function hasSafeResumeSnapshot(receipt) {
|
|
370
|
+
const resume = receipt.resumeSnapshot;
|
|
371
|
+
return resume.canContinue
|
|
372
|
+
&& resume.hasCompleteState
|
|
373
|
+
&& resume.recorderHealth.status === "ok"
|
|
374
|
+
&& resume.blockedBecause.length === 0
|
|
375
|
+
&& isNonEmptyText(resume.currentAsk.value)
|
|
376
|
+
&& isNonEmptyText(resume.nextSafeAction.value);
|
|
377
|
+
}
|
|
378
|
+
function readLatestReadyFile(filePath, label, issues) {
|
|
379
|
+
const receipt = readReceiptFile(filePath, label, issues);
|
|
380
|
+
if (receipt && receipt.verdict !== "ready") {
|
|
381
|
+
issues.push(`${label} is not a ready receipt`);
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
if (receipt && (receipt.gauntlet.verdict !== "ready"
|
|
385
|
+
|| receipt.signals.some((signal) => signal.verdictImpact !== "none")
|
|
386
|
+
|| !hasSafeResumeSnapshot(receipt))) {
|
|
387
|
+
issues.push(`${label} is not a semantically ready receipt`);
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
return receipt;
|
|
391
|
+
}
|
|
392
|
+
function readLatestReady(agentRoot) {
|
|
393
|
+
const receipt = readLatestReadyFile(contextLossSentinelPaths(agentRoot).latestReady, "latest-ready.json", []);
|
|
394
|
+
return receipt?.verdict === "ready" ? receipt : null;
|
|
395
|
+
}
|
|
396
|
+
function signalStatusForImpact(impact) {
|
|
397
|
+
if (impact === "blocked")
|
|
398
|
+
return { status: "fail", severity: "critical" };
|
|
399
|
+
if (impact === "watch")
|
|
400
|
+
return { status: "warn", severity: "warn" };
|
|
401
|
+
return { status: "pass", severity: "info" };
|
|
402
|
+
}
|
|
403
|
+
function providerCheckCommand(agentName, lane) {
|
|
404
|
+
return `ouro provider check --agent ${agentName} --lane ${lane}`;
|
|
405
|
+
}
|
|
406
|
+
function sourceForLane(lane) {
|
|
407
|
+
return {
|
|
408
|
+
kind: "provider-visibility",
|
|
409
|
+
locator: `agent.json#providers.${lane}`,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
function repair(actor, kind, detail, command) {
|
|
413
|
+
return {
|
|
414
|
+
actor,
|
|
415
|
+
kind,
|
|
416
|
+
detail,
|
|
417
|
+
command,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
function providerSignal(visibility, lane, impact, summary, repairValue, meta = {}) {
|
|
421
|
+
return {
|
|
422
|
+
id: `provider:${lane}`,
|
|
423
|
+
kind: "provider_lane",
|
|
424
|
+
...signalStatusForImpact(impact),
|
|
425
|
+
verdictImpact: impact,
|
|
426
|
+
summary,
|
|
427
|
+
source: sourceForLane(lane),
|
|
428
|
+
...(repairValue ? { repair: repairValue } : {}),
|
|
429
|
+
meta: {
|
|
430
|
+
agentName: visibility.agentName,
|
|
431
|
+
lane,
|
|
432
|
+
...meta,
|
|
433
|
+
},
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
function configuredProviderSignal(visibility, lane) {
|
|
437
|
+
if (lane.credential.status === "missing") {
|
|
438
|
+
return providerSignal(visibility, lane.lane, "blocked", `${lane.lane} credentials missing for ${lane.provider}`, repair("human-required", "provider-credential", `${lane.provider} credentials must be added to the agent vault.`, lane.credential.repairCommand), { provider: lane.provider, model: lane.model, credentialStatus: lane.credential.status });
|
|
439
|
+
}
|
|
440
|
+
if (lane.credential.status === "invalid-pool") {
|
|
441
|
+
return providerSignal(visibility, lane.lane, "blocked", `${lane.lane} credential vault unavailable for ${lane.provider}`, repair("human-required", "vault-unavailable", "The agent credential vault must be unlocked or repaired.", lane.credential.repairCommand), { provider: lane.provider, model: lane.model, credentialStatus: lane.credential.status });
|
|
442
|
+
}
|
|
443
|
+
if (lane.credential.status === "not-loaded") {
|
|
444
|
+
return providerSignal(visibility, lane.lane, "watch", `${lane.lane} credentials not loaded for ${lane.provider}`, repair("agent-runnable", "provider-credential-cache", "Refresh the in-process provider credential cache.", lane.credential.repairCommand ?? `ouro provider refresh --agent ${visibility.agentName}`), { provider: lane.provider, model: lane.model, credentialStatus: lane.credential.status });
|
|
445
|
+
}
|
|
446
|
+
if (lane.readiness.status === "failed") {
|
|
447
|
+
return providerSignal(visibility, lane.lane, "blocked", `${lane.lane} live check failed for ${lane.provider}${lane.readiness.error ? `: ${lane.readiness.error}` : ""}`, repair("agent-runnable", "provider-live-check", "Run a fresh provider capability check and inspect the failure.", providerCheckCommand(visibility.agentName, lane.lane)), { provider: lane.provider, model: lane.model, readinessStatus: lane.readiness.status, checkedAt: lane.readiness.checkedAt ?? null, attempts: lane.readiness.attempts ?? null });
|
|
448
|
+
}
|
|
449
|
+
if (lane.readiness.status === "stale" && (lane.readiness.checkedAt || lane.readiness.reason || lane.readiness.error)) {
|
|
450
|
+
return providerSignal(visibility, lane.lane, "watch", `${lane.lane} readiness stale for ${lane.provider}${lane.readiness.reason ? `: ${lane.readiness.reason}` : ""}`, repair("agent-runnable", "provider-live-check", "Run a fresh provider capability check before relying on this lane.", providerCheckCommand(visibility.agentName, lane.lane)), { provider: lane.provider, model: lane.model, readinessStatus: lane.readiness.status, checkedAt: lane.readiness.checkedAt ?? null });
|
|
451
|
+
}
|
|
452
|
+
if (lane.readiness.status === "unknown" || lane.readiness.status === "stale") {
|
|
453
|
+
return providerSignal(visibility, lane.lane, "watch", `${lane.lane} readiness unknown for ${lane.provider}${lane.readiness.reason ? `: ${lane.readiness.reason}` : ""}`, repair("agent-runnable", "provider-live-check", "Run a provider capability check; Sentinel will not invent stale readiness without evidence.", providerCheckCommand(visibility.agentName, lane.lane)), { provider: lane.provider, model: lane.model, readinessStatus: "unknown" });
|
|
454
|
+
}
|
|
455
|
+
return providerSignal(visibility, lane.lane, "none", `${lane.lane} provider ready: ${lane.provider} / ${lane.model}`, undefined, { provider: lane.provider, model: lane.model, readinessStatus: lane.readiness.status, checkedAt: lane.readiness.checkedAt ?? null });
|
|
456
|
+
}
|
|
457
|
+
function missingProviderLaneSignal(visibility, lane) {
|
|
458
|
+
return providerSignal(visibility, lane, "blocked", `${lane} provider visibility missing from deterministic provider report`, repair("agent-runnable", "provider-visibility", "Refresh Sentinel from a complete provider visibility source.", `ouro work sentinel refresh --agent ${visibility.agentName}`), { laneStatus: "missing-from-report" });
|
|
459
|
+
}
|
|
460
|
+
function deriveContextLossSentinelProviderSignals(visibility) {
|
|
461
|
+
return REQUIRED_LANES.map((laneName) => {
|
|
462
|
+
const lane = visibility.lanes.find((entry) => entry.lane === laneName);
|
|
463
|
+
if (!lane) {
|
|
464
|
+
return missingProviderLaneSignal(visibility, laneName);
|
|
465
|
+
}
|
|
466
|
+
if (lane.status === "unconfigured") {
|
|
467
|
+
return providerSignal(visibility, lane.lane, "blocked", `${lane.lane} provider unconfigured: ${lane.reason}`, repair("human-choice", "provider-selection", "Choose a provider and model for this lane.", lane.repairCommand), { laneStatus: lane.status, reason: lane.reason });
|
|
468
|
+
}
|
|
469
|
+
return configuredProviderSignal(visibility, lane);
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
function gauntletSignal(report) {
|
|
473
|
+
const impact = report.verdict === "blocked"
|
|
474
|
+
? "blocked"
|
|
475
|
+
: report.verdict === "watch"
|
|
476
|
+
? "watch"
|
|
477
|
+
: "none";
|
|
478
|
+
return {
|
|
479
|
+
id: "gauntlet:context-loss",
|
|
480
|
+
kind: "gauntlet",
|
|
481
|
+
...signalStatusForImpact(impact),
|
|
482
|
+
verdictImpact: impact,
|
|
483
|
+
summary: report.summary,
|
|
484
|
+
source: {
|
|
485
|
+
kind: "context-loss-gauntlet",
|
|
486
|
+
locator: "arc/flight-recorder/latest.json",
|
|
487
|
+
},
|
|
488
|
+
meta: {
|
|
489
|
+
scorePercentage: report.score.percentage,
|
|
490
|
+
failedChecks: report.checks.filter((check) => check.status === "fail").map((check) => check.id),
|
|
491
|
+
warnedChecks: report.checks.filter((check) => check.status === "warn").map((check) => check.id),
|
|
492
|
+
},
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
function healthSignalKind(result) {
|
|
496
|
+
return result.name.startsWith("sense-probe:") ? "sense" : "daemon";
|
|
497
|
+
}
|
|
498
|
+
function healthSignals(results) {
|
|
499
|
+
return results
|
|
500
|
+
.filter((result) => result.name.startsWith("sense-probe:") || result.status !== "ok")
|
|
501
|
+
.map((result) => {
|
|
502
|
+
const impact = result.status === "critical"
|
|
503
|
+
? "blocked"
|
|
504
|
+
: result.status === "warn"
|
|
505
|
+
? "watch"
|
|
506
|
+
: "none";
|
|
507
|
+
const kind = healthSignalKind(result);
|
|
508
|
+
return {
|
|
509
|
+
id: `${kind}:${result.name}`,
|
|
510
|
+
kind,
|
|
511
|
+
...signalStatusForImpact(impact),
|
|
512
|
+
verdictImpact: impact,
|
|
513
|
+
summary: result.message,
|
|
514
|
+
source: {
|
|
515
|
+
kind: "daemon-health",
|
|
516
|
+
locator: `daemon.health:${result.name}`,
|
|
517
|
+
},
|
|
518
|
+
meta: { healthStatus: result.status },
|
|
519
|
+
};
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
function defaultGitStatus(agentRoot) {
|
|
523
|
+
try {
|
|
524
|
+
if (!fs.existsSync(path.join(agentRoot, ".git")))
|
|
525
|
+
return { ok: true, porcelain: "" };
|
|
526
|
+
const porcelain = (0, child_process_1.execFileSync)("git", ["status", "--porcelain=v1", "-uall"], {
|
|
527
|
+
cwd: agentRoot,
|
|
528
|
+
encoding: "utf-8",
|
|
529
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
530
|
+
timeout: CONTEXT_LOSS_SENTINEL_GIT_STATUS_TIMEOUT_MS,
|
|
531
|
+
});
|
|
532
|
+
return { ok: true, porcelain };
|
|
533
|
+
}
|
|
534
|
+
catch (error) {
|
|
535
|
+
return { ok: false, error: String(error) };
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
function gitStatusEntries(porcelain) {
|
|
539
|
+
return porcelain.split(/\r?\n/).filter((line) => line.trim().length > 0);
|
|
540
|
+
}
|
|
541
|
+
function normalizeGitStatusPath(entryPath) {
|
|
542
|
+
return entryPath.trim().replace(/^"|"$/g, "");
|
|
543
|
+
}
|
|
544
|
+
function gitStatusPaths(entry) {
|
|
545
|
+
const rawPath = entry.slice(3).trim();
|
|
546
|
+
const paths = [];
|
|
547
|
+
let start = 0;
|
|
548
|
+
let inQuote = false;
|
|
549
|
+
let escaped = false;
|
|
550
|
+
for (let index = 0; index < rawPath.length; index += 1) {
|
|
551
|
+
const char = rawPath[index];
|
|
552
|
+
if (escaped) {
|
|
553
|
+
escaped = false;
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
if (inQuote && char === "\\") {
|
|
557
|
+
escaped = true;
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
if (char === "\"") {
|
|
561
|
+
inQuote = !inQuote;
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
if (!inQuote && rawPath.startsWith(" -> ", index)) {
|
|
565
|
+
paths.push(rawPath.slice(start, index));
|
|
566
|
+
start = index + 4;
|
|
567
|
+
index += 3;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
paths.push(rawPath.slice(start));
|
|
571
|
+
return paths.map(normalizeGitStatusPath);
|
|
572
|
+
}
|
|
573
|
+
function isSentinelGitStatusEntry(entry) {
|
|
574
|
+
const sentinelRoot = relativeSentinelRoot();
|
|
575
|
+
return gitStatusPaths(entry).every((entryPath) => {
|
|
576
|
+
const normalizedPath = entryPath.replace(/\/$/, "");
|
|
577
|
+
return normalizedPath === sentinelRoot || normalizedPath.startsWith(`${sentinelRoot}/`);
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
function isContextLossSentinelBlockerText(value) {
|
|
581
|
+
return value.startsWith("context-loss Sentinel blocked:");
|
|
582
|
+
}
|
|
583
|
+
function hasContextLossSentinelCheckpointEvents(agentRoot, resume) {
|
|
584
|
+
const events = readFlightRecorderEventsByIds(agentRoot, resume.lastSafeCheckpoint.sourceEventIds);
|
|
585
|
+
const foundEventIds = new Set(events.map((event) => event.id));
|
|
586
|
+
if (resume.lastSafeCheckpoint.sourceEventIds.some((eventId) => !foundEventIds.has(eventId)))
|
|
587
|
+
return false;
|
|
588
|
+
return resume.blockedBecause.length > 0
|
|
589
|
+
? events.every(isContextLossSentinelBlockerEvent)
|
|
590
|
+
: events.every(isContextLossSentinelRecoveryEvent);
|
|
591
|
+
}
|
|
592
|
+
function isContextLossSentinelCheckpoint(agentRoot, resume) {
|
|
593
|
+
return resume.lastSafeCheckpoint.sourceEventIds.length > 0
|
|
594
|
+
&& resume.lastSafeCheckpoint.sourceEventIds.every((eventId) => eventId.startsWith("fr-sentinel-"))
|
|
595
|
+
&& resume.blockedBecause.every(isContextLossSentinelBlockerText)
|
|
596
|
+
&& hasContextLossSentinelCheckpointEvents(agentRoot, resume);
|
|
597
|
+
}
|
|
598
|
+
function neutralizeSentinelCheckpoint(resume) {
|
|
599
|
+
return {
|
|
600
|
+
...resume,
|
|
601
|
+
canContinue: true,
|
|
602
|
+
blockedBecause: [],
|
|
603
|
+
lastSafeCheckpoint: {
|
|
604
|
+
turnId: null,
|
|
605
|
+
sessionRef: null,
|
|
606
|
+
recordedAt: null,
|
|
607
|
+
sourceEventIds: [],
|
|
608
|
+
},
|
|
609
|
+
recorderHealth: { status: "ok", issues: [] },
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
function isContextLossSentinelProducedRef(value) {
|
|
613
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
614
|
+
return false;
|
|
615
|
+
const record = value;
|
|
616
|
+
if (record.kind !== "arc" || typeof record.locator !== "string")
|
|
617
|
+
return false;
|
|
618
|
+
const sentinelRoot = relativeSentinelRoot();
|
|
619
|
+
return record.locator === sentinelRoot || record.locator.startsWith(`${sentinelRoot}/`);
|
|
620
|
+
}
|
|
621
|
+
function isContextLossSentinelFlightRecorderEvent(value) {
|
|
622
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
623
|
+
return false;
|
|
624
|
+
const record = value;
|
|
625
|
+
const meta = record.meta;
|
|
626
|
+
const producedRefs = record.producedRefs;
|
|
627
|
+
return typeof record.id === "string"
|
|
628
|
+
&& record.id.startsWith("fr-sentinel-")
|
|
629
|
+
&& (record.kind === "blocker_detected" || record.kind === "agent_note")
|
|
630
|
+
&& meta?.source === "context-loss-sentinel"
|
|
631
|
+
&& Array.isArray(producedRefs)
|
|
632
|
+
&& producedRefs.length > 0
|
|
633
|
+
&& producedRefs.every(isContextLossSentinelProducedRef);
|
|
634
|
+
}
|
|
635
|
+
function isContextLossSentinelBlockerEvent(event) {
|
|
636
|
+
return isContextLossSentinelFlightRecorderEvent(event)
|
|
637
|
+
&& event.kind === "blocker_detected"
|
|
638
|
+
&& Array.isArray(event.blockedBecause)
|
|
639
|
+
&& event.blockedBecause.length > 0
|
|
640
|
+
&& event.blockedBecause.every(isContextLossSentinelBlockerText);
|
|
641
|
+
}
|
|
642
|
+
function isContextLossSentinelRecoveryEvent(event) {
|
|
643
|
+
return isContextLossSentinelFlightRecorderEvent(event)
|
|
644
|
+
&& event.kind === "agent_note"
|
|
645
|
+
&& Array.isArray(event.blockedBecause)
|
|
646
|
+
&& event.blockedBecause.length === 0
|
|
647
|
+
&& event.meta?.resolution === "blocked-signals-recovered";
|
|
648
|
+
}
|
|
649
|
+
function gitShowHeadFile(agentRoot, entryPath) {
|
|
650
|
+
try {
|
|
651
|
+
return (0, child_process_1.execFileSync)("git", ["show", `HEAD:${entryPath}`], {
|
|
652
|
+
cwd: agentRoot,
|
|
653
|
+
encoding: "utf-8",
|
|
654
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
655
|
+
timeout: CONTEXT_LOSS_SENTINEL_GIT_STATUS_TIMEOUT_MS,
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
catch {
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
function nonEmptyLines(text) {
|
|
663
|
+
return text.split(/\r?\n/).filter((line) => line.trim().length > 0);
|
|
664
|
+
}
|
|
665
|
+
function isFlightRecorderEventsPath(entryPath) {
|
|
666
|
+
return /^arc\/flight-recorder\/events\/[^/]+\.jsonl$/.test(entryPath);
|
|
667
|
+
}
|
|
668
|
+
function isFlightRecorderLatestPath(entryPath) {
|
|
669
|
+
return entryPath === "arc/flight-recorder/latest.json";
|
|
670
|
+
}
|
|
671
|
+
function isDeletedGitStatusEntry(entry) {
|
|
672
|
+
return entry.slice(0, 2).includes("D");
|
|
673
|
+
}
|
|
674
|
+
function isUntrackedGitStatusEntry(entry) {
|
|
675
|
+
return entry.startsWith("??");
|
|
676
|
+
}
|
|
677
|
+
function isWorktreeOnlyModifiedGitStatusEntry(entry) {
|
|
678
|
+
return entry.slice(0, 2) === " M";
|
|
679
|
+
}
|
|
680
|
+
function parseJsonLine(line) {
|
|
681
|
+
try {
|
|
682
|
+
return JSON.parse(line);
|
|
683
|
+
}
|
|
684
|
+
catch {
|
|
685
|
+
return null;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
function isContextLossSentinelEventFileDirty(agentRoot, entry, entryPath) {
|
|
689
|
+
if (isDeletedGitStatusEntry(entry))
|
|
690
|
+
return false;
|
|
691
|
+
const filePath = path.join(agentRoot, entryPath);
|
|
692
|
+
if (isUntrackedGitStatusEntry(entry)) {
|
|
693
|
+
const candidateLines = fs.existsSync(filePath) ? nonEmptyLines(fs.readFileSync(filePath, "utf-8")) : [];
|
|
694
|
+
return candidateLines.length > 0
|
|
695
|
+
&& candidateLines.every((line) => isContextLossSentinelFlightRecorderEvent(parseJsonLine(line)));
|
|
696
|
+
}
|
|
697
|
+
if (!isWorktreeOnlyModifiedGitStatusEntry(entry) || !fs.existsSync(filePath))
|
|
698
|
+
return false;
|
|
699
|
+
const headText = gitShowHeadFile(agentRoot, entryPath);
|
|
700
|
+
if (headText === null)
|
|
701
|
+
return false;
|
|
702
|
+
const headLines = nonEmptyLines(headText);
|
|
703
|
+
const currentLines = nonEmptyLines(fs.readFileSync(filePath, "utf-8"));
|
|
704
|
+
if (currentLines.length <= headLines.length)
|
|
705
|
+
return false;
|
|
706
|
+
if (!headLines.every((line, index) => currentLines[index] === line))
|
|
707
|
+
return false;
|
|
708
|
+
const appendedLines = currentLines.slice(headLines.length);
|
|
709
|
+
return appendedLines.every((line) => isContextLossSentinelFlightRecorderEvent(parseJsonLine(line)));
|
|
710
|
+
}
|
|
711
|
+
function isContextLossSentinelLatestDirty(agentRoot, entry, entryPath) {
|
|
712
|
+
if (isDeletedGitStatusEntry(entry) || isUntrackedGitStatusEntry(entry))
|
|
713
|
+
return false;
|
|
714
|
+
if (!isWorktreeOnlyModifiedGitStatusEntry(entry))
|
|
715
|
+
return false;
|
|
716
|
+
const current = readJson(path.join(agentRoot, entryPath));
|
|
717
|
+
if (!current.ok || !(0, flight_recorder_1.isFlightRecorderResume)(current.value) || !isContextLossSentinelCheckpoint(agentRoot, current.value)) {
|
|
718
|
+
return false;
|
|
719
|
+
}
|
|
720
|
+
const headText = gitShowHeadFile(agentRoot, entryPath);
|
|
721
|
+
if (headText === null)
|
|
722
|
+
return false;
|
|
723
|
+
const head = parseJsonLine(headText);
|
|
724
|
+
if (!(0, flight_recorder_1.isFlightRecorderResume)(head))
|
|
725
|
+
return false;
|
|
726
|
+
return JSON.stringify(neutralizeSentinelCheckpoint(current.value))
|
|
727
|
+
=== JSON.stringify(neutralizeSentinelCheckpoint(head));
|
|
728
|
+
}
|
|
729
|
+
function isContextLossSentinelFlightRecorderGitStatusEntry(agentRoot, entry) {
|
|
730
|
+
return gitStatusPaths(entry).every((entryPath) => (isFlightRecorderEventsPath(entryPath)
|
|
731
|
+
? isContextLossSentinelEventFileDirty(agentRoot, entry, entryPath)
|
|
732
|
+
: isFlightRecorderLatestPath(entryPath)
|
|
733
|
+
? isContextLossSentinelLatestDirty(agentRoot, entry, entryPath)
|
|
734
|
+
: false));
|
|
735
|
+
}
|
|
736
|
+
function isContextLossSentinelOwnedGitStatusEntry(entry, options) {
|
|
737
|
+
return isSentinelGitStatusEntry(entry)
|
|
738
|
+
|| isContextLossSentinelFlightRecorderGitStatusEntry(options.agentRoot, entry);
|
|
739
|
+
}
|
|
740
|
+
function bundleSignal(status, options) {
|
|
741
|
+
const gitStatusCommand = "git status --porcelain=v1 -uall";
|
|
742
|
+
if (!status.ok) {
|
|
743
|
+
return {
|
|
744
|
+
id: "bundle:git",
|
|
745
|
+
kind: "bundle",
|
|
746
|
+
status: "warn",
|
|
747
|
+
severity: "warn",
|
|
748
|
+
verdictImpact: "watch",
|
|
749
|
+
summary: `bundle git status unavailable: ${status.error}`,
|
|
750
|
+
source: { kind: "git", locator: gitStatusCommand },
|
|
751
|
+
repair: repair("agent-runnable", "bundle-cleanup", "Inspect bundle git state before assuming the local state is clean.", gitStatusCommand),
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
const dirtyEntries = gitStatusEntries(status.porcelain)
|
|
755
|
+
.filter((entry) => !(options.ignoreSentinelDirtyEntries && isContextLossSentinelOwnedGitStatusEntry(entry, options)));
|
|
756
|
+
if (dirtyEntries.length > 0) {
|
|
757
|
+
return {
|
|
758
|
+
id: "bundle:git",
|
|
759
|
+
kind: "bundle",
|
|
760
|
+
status: "warn",
|
|
761
|
+
severity: "warn",
|
|
762
|
+
verdictImpact: "watch",
|
|
763
|
+
summary: `bundle has ${dirtyEntries.length} uncommitted git status entr${dirtyEntries.length === 1 ? "y" : "ies"}`,
|
|
764
|
+
source: { kind: "git", locator: gitStatusCommand },
|
|
765
|
+
repair: repair("agent-runnable", "bundle-cleanup", "Resolve or intentionally preserve local bundle changes before handoff.", gitStatusCommand),
|
|
766
|
+
meta: { dirtyEntries },
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
return {
|
|
770
|
+
id: "bundle:git",
|
|
771
|
+
kind: "bundle",
|
|
772
|
+
status: "pass",
|
|
773
|
+
severity: "info",
|
|
774
|
+
verdictImpact: "none",
|
|
775
|
+
summary: "bundle git status clean",
|
|
776
|
+
source: { kind: "git", locator: gitStatusCommand },
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
function sentinelVerdict(signals) {
|
|
780
|
+
if (signals.some((entry) => entry.verdictImpact === "blocked"))
|
|
781
|
+
return "blocked";
|
|
782
|
+
if (signals.some((entry) => entry.verdictImpact === "watch"))
|
|
783
|
+
return "watch";
|
|
784
|
+
return "ready";
|
|
785
|
+
}
|
|
786
|
+
function summaryForVerdict(verdict) {
|
|
787
|
+
if (verdict === "ready")
|
|
788
|
+
return "ready: deterministic recovery state is current and last-known-good is safe";
|
|
789
|
+
if (verdict === "watch")
|
|
790
|
+
return "watch: deterministic recovery can continue, but one or more signals need attention";
|
|
791
|
+
return "blocked: deterministic recovery failed and must use latest-ready or repair before continuing";
|
|
792
|
+
}
|
|
793
|
+
function gauntletSummary(report) {
|
|
794
|
+
return {
|
|
795
|
+
verdict: report.verdict,
|
|
796
|
+
scorePercentage: report.score.percentage,
|
|
797
|
+
failedChecks: report.checks.filter((check) => check.status === "fail").map((check) => check.id),
|
|
798
|
+
warnedChecks: report.checks.filter((check) => check.status === "warn").map((check) => check.id),
|
|
799
|
+
sourceLocator: "arc/flight-recorder/latest.json",
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
function anchorFromResume(kind, resume) {
|
|
803
|
+
return {
|
|
804
|
+
kind,
|
|
805
|
+
currentAsk: resume.currentAsk.value,
|
|
806
|
+
nextSafeAction: resume.nextSafeAction.value,
|
|
807
|
+
flightRecorderLatestLocator: "arc/flight-recorder/latest.json",
|
|
808
|
+
sourceEventIds: [...resume.currentAsk.sourceEventIds, ...resume.nextSafeAction.sourceEventIds],
|
|
809
|
+
recordedAt: resume.lastSafeCheckpoint.recordedAt,
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
function readFlightRecorderEventsByIds(agentRoot, eventIds) {
|
|
813
|
+
const wanted = new Set(eventIds);
|
|
814
|
+
const eventsRoot = path.join(agentRoot, "arc", "flight-recorder", "events");
|
|
815
|
+
if (!fs.existsSync(eventsRoot))
|
|
816
|
+
return [];
|
|
817
|
+
return fs.readdirSync(eventsRoot)
|
|
818
|
+
.filter((entry) => entry.endsWith(".jsonl"))
|
|
819
|
+
.flatMap((entry) => fs.readFileSync(path.join(eventsRoot, entry), "utf-8").split(/\r?\n/))
|
|
820
|
+
.filter((line) => line.trim().length > 0)
|
|
821
|
+
.flatMap((line) => {
|
|
822
|
+
try {
|
|
823
|
+
const parsed = JSON.parse(line);
|
|
824
|
+
return wanted.has(parsed.id) ? [parsed] : [];
|
|
825
|
+
}
|
|
826
|
+
catch {
|
|
827
|
+
return [];
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
function isSentinelAuthoredBlockerEvent(event) {
|
|
832
|
+
return event.kind === "blocker_detected"
|
|
833
|
+
&& (event.meta?.source === "context-loss-sentinel"
|
|
834
|
+
|| Boolean(event.producedRefs?.some((ref) => ref.kind === "arc" && ref.locator.startsWith(relativeSentinelRoot()))));
|
|
835
|
+
}
|
|
836
|
+
function hasOnlySentinelAuthoredBlockers(agentRoot, resume) {
|
|
837
|
+
if (resume.blockedBecause.length === 0)
|
|
838
|
+
return false;
|
|
839
|
+
if (resume.lastSafeCheckpoint.sourceEventIds.length === 0)
|
|
840
|
+
return false;
|
|
841
|
+
const events = readFlightRecorderEventsByIds(agentRoot, resume.lastSafeCheckpoint.sourceEventIds);
|
|
842
|
+
const foundEventIds = new Set(events.map((event) => event.id));
|
|
843
|
+
if (resume.lastSafeCheckpoint.sourceEventIds.some((eventId) => !foundEventIds.has(eventId)))
|
|
844
|
+
return false;
|
|
845
|
+
const blockerEvents = events.filter((event) => (event.blockedBecause?.length ?? 0) > 0);
|
|
846
|
+
if (blockerEvents.length === 0)
|
|
847
|
+
return false;
|
|
848
|
+
return blockerEvents.every(isSentinelAuthoredBlockerEvent);
|
|
849
|
+
}
|
|
850
|
+
function selectGauntletResume(agentRoot) {
|
|
851
|
+
const resume = (0, flight_recorder_1.readFlightRecorderResume)(agentRoot);
|
|
852
|
+
const latestReady = readLatestReady(agentRoot);
|
|
853
|
+
if (hasOnlySentinelAuthoredBlockers(agentRoot, resume) && latestReady) {
|
|
854
|
+
return { resume: latestReady.resumeSnapshot, anchorKind: "latest-ready" };
|
|
855
|
+
}
|
|
856
|
+
return { resume, anchorKind: "flight-recorder" };
|
|
857
|
+
}
|
|
858
|
+
function resolveProviderVisibility(agentName, agentRoot, options) {
|
|
859
|
+
return options.providerVisibility ?? (0, provider_visibility_1.buildAgentProviderVisibility)({
|
|
860
|
+
agentName,
|
|
861
|
+
agentRoot,
|
|
862
|
+
homeDir: options.homeDir,
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
function shouldUseLatestReadyRecovery(anchorKind, verdict, signals) {
|
|
866
|
+
if (anchorKind === "latest-ready")
|
|
867
|
+
return true;
|
|
868
|
+
if (verdict === "ready")
|
|
869
|
+
return false;
|
|
870
|
+
const hasNonGauntletRisk = signals.some((signal) => signal.kind !== "gauntlet" && signal.verdictImpact !== "none");
|
|
871
|
+
const hasGauntletBlocker = signals.some((signal) => signal.kind === "gauntlet" && signal.verdictImpact === "blocked");
|
|
872
|
+
return hasNonGauntletRisk && !hasGauntletBlocker;
|
|
873
|
+
}
|
|
874
|
+
function makeReceipt(agentName, agentRoot, options, generatedAt) {
|
|
875
|
+
const receiptId = options.createReceiptId?.() ?? `sentinel-${(0, crypto_1.randomUUID)()}`;
|
|
876
|
+
const selectedResume = selectGauntletResume(agentRoot);
|
|
877
|
+
const report = (0, context_loss_gauntlet_1.runContextLossGauntlet)(agentName, agentRoot, {
|
|
878
|
+
now: options.now,
|
|
879
|
+
homeDir: options.homeDir,
|
|
880
|
+
flightRecorderResume: selectedResume.resume,
|
|
881
|
+
});
|
|
882
|
+
const providerVisibility = resolveProviderVisibility(agentName, agentRoot, options);
|
|
883
|
+
const signals = [
|
|
884
|
+
gauntletSignal(report),
|
|
885
|
+
...deriveContextLossSentinelProviderSignals(providerVisibility),
|
|
886
|
+
...healthSignals(options.daemonHealthResults ?? []),
|
|
887
|
+
bundleSignal((options.gitStatus ?? (() => defaultGitStatus(agentRoot)))(), {
|
|
888
|
+
ignoreSentinelDirtyEntries: options.trigger === "daemon_health",
|
|
889
|
+
agentRoot,
|
|
890
|
+
}),
|
|
891
|
+
];
|
|
892
|
+
const verdict = sentinelVerdict(signals);
|
|
893
|
+
const latestReady = readLatestReady(agentRoot);
|
|
894
|
+
const recoverySource = latestReady && shouldUseLatestReadyRecovery(selectedResume.anchorKind, verdict, signals)
|
|
895
|
+
? { resume: latestReady.resumeSnapshot, anchorKind: "latest-ready" }
|
|
896
|
+
: selectedResume;
|
|
897
|
+
const readyLocator = verdict === "ready" || latestReady ? latestReadyLocator() : null;
|
|
898
|
+
return {
|
|
899
|
+
schemaVersion: 1,
|
|
900
|
+
id: receiptId,
|
|
901
|
+
agent: agentName,
|
|
902
|
+
trigger: options.trigger,
|
|
903
|
+
generatedAt,
|
|
904
|
+
verdict,
|
|
905
|
+
summary: summaryForVerdict(verdict),
|
|
906
|
+
receiptLocator: receiptLocator(receiptId),
|
|
907
|
+
latestReadyLocator: readyLocator,
|
|
908
|
+
recoveryAnchor: anchorFromResume(recoverySource.anchorKind, recoverySource.resume),
|
|
909
|
+
gauntlet: gauntletSummary(report),
|
|
910
|
+
signals,
|
|
911
|
+
sourceLocators: [
|
|
912
|
+
"arc/flight-recorder/latest.json",
|
|
913
|
+
latestLocator(),
|
|
914
|
+
historyLocator(generatedAt),
|
|
915
|
+
receiptLocator(receiptId),
|
|
916
|
+
...(readyLocator ? [readyLocator] : []),
|
|
917
|
+
],
|
|
918
|
+
resumeSnapshot: recoverySource.resume,
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
function ensureSentinelDirs(paths) {
|
|
922
|
+
fs.mkdirSync(paths.rootDir, { recursive: true });
|
|
923
|
+
fs.mkdirSync(paths.historyDir, { recursive: true });
|
|
924
|
+
fs.mkdirSync(paths.receiptsDir, { recursive: true });
|
|
925
|
+
}
|
|
926
|
+
function appendHistory(paths, receipt) {
|
|
927
|
+
fs.mkdirSync(paths.historyDir, { recursive: true });
|
|
928
|
+
fs.appendFileSync(path.join(paths.historyDir, `${historyDay(receipt.generatedAt)}.jsonl`), `${JSON.stringify(receipt)}\n`, "utf-8");
|
|
929
|
+
}
|
|
930
|
+
function syncLatestReadyLocator(receipt, hasLatestReady) {
|
|
931
|
+
receipt.latestReadyLocator = hasLatestReady ? latestReadyLocator() : null;
|
|
932
|
+
const locator = latestReadyLocator();
|
|
933
|
+
receipt.sourceLocators = hasLatestReady
|
|
934
|
+
? Array.from(new Set([...receipt.sourceLocators, locator]))
|
|
935
|
+
: receipt.sourceLocators.filter((entry) => entry !== locator);
|
|
936
|
+
return receipt;
|
|
937
|
+
}
|
|
938
|
+
function syncLatestReadyState(receipt, latestReady) {
|
|
939
|
+
syncLatestReadyLocator(receipt, latestReady !== null);
|
|
940
|
+
if (latestReady && shouldUseLatestReadyRecovery(receipt.recoveryAnchor.kind, receipt.verdict, receipt.signals)) {
|
|
941
|
+
receipt.recoveryAnchor = anchorFromResume("latest-ready", latestReady.resumeSnapshot);
|
|
942
|
+
receipt.resumeSnapshot = latestReady.resumeSnapshot;
|
|
943
|
+
}
|
|
944
|
+
return receipt;
|
|
945
|
+
}
|
|
946
|
+
function blockedSignalSummaries(receipt) {
|
|
947
|
+
return receipt.signals
|
|
948
|
+
.filter((signal) => signal.verdictImpact === "blocked")
|
|
949
|
+
.map((signal) => `${signal.id}: ${signal.summary}`);
|
|
950
|
+
}
|
|
951
|
+
function recordBlockedReceiptEvent(agentRoot, receipt) {
|
|
952
|
+
if (receipt.verdict !== "blocked")
|
|
953
|
+
return;
|
|
954
|
+
if (receipt.signals.some((signal) => signal.kind === "gauntlet" && signal.verdictImpact === "blocked"))
|
|
955
|
+
return;
|
|
956
|
+
(0, flight_recorder_1.recordFlightRecorderEvent)(agentRoot, {
|
|
957
|
+
id: `fr-${receipt.id}`,
|
|
958
|
+
kind: "blocker_detected",
|
|
959
|
+
recordedAt: receipt.generatedAt,
|
|
960
|
+
summary: "context-loss Sentinel blocked recovery",
|
|
961
|
+
blockedBecause: blockedSignalSummaries(receipt).map((summary) => `context-loss Sentinel blocked: ${summary}`),
|
|
962
|
+
producedRefs: [{
|
|
963
|
+
kind: "arc",
|
|
964
|
+
locator: receipt.receiptLocator,
|
|
965
|
+
}],
|
|
966
|
+
meta: {
|
|
967
|
+
source: "context-loss-sentinel",
|
|
968
|
+
receiptId: receipt.id,
|
|
969
|
+
trigger: receipt.trigger,
|
|
970
|
+
},
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
function recordRecoveredReceiptEvent(agentRoot, receipt) {
|
|
974
|
+
if (receipt.verdict === "blocked")
|
|
975
|
+
return;
|
|
976
|
+
const resume = (0, flight_recorder_1.readFlightRecorderResume)(agentRoot);
|
|
977
|
+
if (!hasOnlySentinelAuthoredBlockers(agentRoot, resume))
|
|
978
|
+
return;
|
|
979
|
+
(0, flight_recorder_1.recordFlightRecorderEvent)(agentRoot, {
|
|
980
|
+
id: `fr-${receipt.id}-recovered`,
|
|
981
|
+
kind: "agent_note",
|
|
982
|
+
recordedAt: receipt.generatedAt,
|
|
983
|
+
summary: "context-loss Sentinel cleared its prior recovery blocker",
|
|
984
|
+
blockedBecause: [],
|
|
985
|
+
producedRefs: [{
|
|
986
|
+
kind: "arc",
|
|
987
|
+
locator: receipt.receiptLocator,
|
|
988
|
+
}],
|
|
989
|
+
meta: {
|
|
990
|
+
source: "context-loss-sentinel",
|
|
991
|
+
receiptId: receipt.id,
|
|
992
|
+
trigger: receipt.trigger,
|
|
993
|
+
resolution: "blocked-signals-recovered",
|
|
994
|
+
},
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
async function persistReceipt(agentRoot, receipt, lockTimeoutMs) {
|
|
998
|
+
const paths = contextLossSentinelPaths(agentRoot);
|
|
999
|
+
return withFileLock(paths.lock, lockTimeoutMs, async () => {
|
|
1000
|
+
ensureSentinelDirs(paths);
|
|
1001
|
+
const existingLatest = readReceiptFile(paths.latest, "latest.json", []);
|
|
1002
|
+
const existingReady = readLatestReady(agentRoot);
|
|
1003
|
+
const watermark = readWatermark(agentRoot);
|
|
1004
|
+
const latestOrder = maxOrder(maxOrder(maxOrder(existingLatest ? orderFromReceipt(existingLatest) : null, existingReady ? orderFromReceipt(existingReady) : null), watermark.latest), watermark.latestReady);
|
|
1005
|
+
const latestReadyOrder = maxOrder(maxOrder(existingReady ? orderFromReceipt(existingReady) : null, watermark.latestReady), existingLatest?.verdict === "ready" ? orderFromReceipt(existingLatest) : null);
|
|
1006
|
+
const nextReady = selectLatestReadyReceipt(existingReady, receipt, latestReadyOrder);
|
|
1007
|
+
syncLatestReadyState(receipt, nextReady);
|
|
1008
|
+
const coalesced = coalescedDaemonHealthReceipt(agentRoot, existingLatest, existingReady, receipt, watermark);
|
|
1009
|
+
if (coalesced)
|
|
1010
|
+
return coalesced;
|
|
1011
|
+
const receiptPath = path.join(paths.receiptsDir, `${receipt.id}.json`);
|
|
1012
|
+
atomicWriteJson(receiptPath, receipt);
|
|
1013
|
+
setReceiptFileMtime(receiptPath, receipt);
|
|
1014
|
+
appendHistory(paths, receipt);
|
|
1015
|
+
let latestAfterWrite = existingLatest ?? receipt;
|
|
1016
|
+
if (shouldReplaceLatestReceipt(existingLatest, receipt, latestOrder)) {
|
|
1017
|
+
atomicWriteJson(paths.latest, receipt);
|
|
1018
|
+
updateWatermarkOrder(watermark, "latest", receipt);
|
|
1019
|
+
writeWatermark(agentRoot, watermark);
|
|
1020
|
+
setReceiptFileMtime(paths.latest, receipt);
|
|
1021
|
+
latestAfterWrite = receipt;
|
|
1022
|
+
recordBlockedReceiptEvent(agentRoot, receipt);
|
|
1023
|
+
recordRecoveredReceiptEvent(agentRoot, receipt);
|
|
1024
|
+
}
|
|
1025
|
+
else if (existingLatest && nextReady === receipt) {
|
|
1026
|
+
const updatedLatest = syncLatestReadyState(existingLatest, nextReady);
|
|
1027
|
+
atomicWriteJson(paths.latest, updatedLatest);
|
|
1028
|
+
latestAfterWrite = updatedLatest;
|
|
1029
|
+
}
|
|
1030
|
+
if (receipt.verdict === "ready" && nextReady === receipt) {
|
|
1031
|
+
atomicWriteJson(paths.latestReady, receipt);
|
|
1032
|
+
updateWatermarkOrder(watermark, "latestReady", receipt);
|
|
1033
|
+
writeWatermark(agentRoot, watermark);
|
|
1034
|
+
setReceiptFileMtime(paths.latestReady, receipt);
|
|
1035
|
+
}
|
|
1036
|
+
const protectedReceiptIds = new Set([receipt.id, latestAfterWrite.id]);
|
|
1037
|
+
if (nextReady)
|
|
1038
|
+
protectedReceiptIds.add(nextReady.id);
|
|
1039
|
+
pruneReceiptDetailFiles(paths, protectedReceiptIds);
|
|
1040
|
+
return receipt;
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
async function refreshContextLossSentinel(agentName, agentRoot, options) {
|
|
1044
|
+
const generatedAt = (options.now ?? (() => new Date()))().toISOString();
|
|
1045
|
+
const receipt = makeReceipt(agentName, agentRoot, options, generatedAt);
|
|
1046
|
+
if (options.delayBeforeWriteMs && options.delayBeforeWriteMs > 0) {
|
|
1047
|
+
await sleep(options.delayBeforeWriteMs);
|
|
1048
|
+
}
|
|
1049
|
+
const persistedReceipt = await persistReceipt(agentRoot, receipt, options.lockTimeoutMs ?? 5_000);
|
|
1050
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1051
|
+
component: "engine",
|
|
1052
|
+
event: "engine.context_loss_sentinel_refreshed",
|
|
1053
|
+
message: "context-loss Sentinel refreshed deterministic recovery state",
|
|
1054
|
+
meta: {
|
|
1055
|
+
agentName,
|
|
1056
|
+
trigger: options.trigger,
|
|
1057
|
+
verdict: persistedReceipt.verdict,
|
|
1058
|
+
receiptId: persistedReceipt.id,
|
|
1059
|
+
blockedSignals: persistedReceipt.signals.filter((entry) => entry.verdictImpact === "blocked").map((entry) => entry.id),
|
|
1060
|
+
watchSignals: persistedReceipt.signals.filter((entry) => entry.verdictImpact === "watch").map((entry) => entry.id),
|
|
1061
|
+
},
|
|
1062
|
+
});
|
|
1063
|
+
return persistedReceipt;
|
|
1064
|
+
}
|
|
1065
|
+
function readHistory(paths, limit, issues) {
|
|
1066
|
+
if (!fs.existsSync(paths.historyDir))
|
|
1067
|
+
return [];
|
|
1068
|
+
const files = fs.readdirSync(paths.historyDir)
|
|
1069
|
+
.filter((entry) => entry.endsWith(".jsonl"))
|
|
1070
|
+
.sort();
|
|
1071
|
+
const receipts = [];
|
|
1072
|
+
for (const fileName of files) {
|
|
1073
|
+
const filePath = path.join(paths.historyDir, fileName);
|
|
1074
|
+
const lines = fs.readFileSync(filePath, "utf-8").split(/\r?\n/);
|
|
1075
|
+
lines.forEach((line, index) => {
|
|
1076
|
+
if (line.trim().length === 0)
|
|
1077
|
+
return;
|
|
1078
|
+
try {
|
|
1079
|
+
const parsed = JSON.parse(line);
|
|
1080
|
+
if (isReceipt(parsed)) {
|
|
1081
|
+
receipts.push(parsed);
|
|
1082
|
+
}
|
|
1083
|
+
else {
|
|
1084
|
+
issues.push(`history/${fileName} line ${index + 1} malformed`);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
catch (error) {
|
|
1088
|
+
issues.push(`history/${fileName} line ${index + 1} unreadable: ${String(error)}`);
|
|
1089
|
+
}
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
return receipts.slice(Math.max(0, receipts.length - limit));
|
|
1093
|
+
}
|
|
1094
|
+
function readContextLossSentinelView(agentRoot, options = {}) {
|
|
1095
|
+
const paths = contextLossSentinelPaths(agentRoot);
|
|
1096
|
+
const issues = [];
|
|
1097
|
+
const limit = Math.max(0, options.limit ?? 20);
|
|
1098
|
+
return {
|
|
1099
|
+
schemaVersion: 1,
|
|
1100
|
+
latest: readReceiptFile(paths.latest, "latest.json", issues),
|
|
1101
|
+
latestReady: readLatestReadyFile(paths.latestReady, "latest-ready.json", issues),
|
|
1102
|
+
history: readHistory(paths, limit, issues),
|
|
1103
|
+
degraded: { issues },
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
function renderSignal(signalEntry) {
|
|
1107
|
+
const repairText = signalEntry.repair?.command ? ` repair: ${signalEntry.repair.command}` : "";
|
|
1108
|
+
return ` - ${signalEntry.status.toUpperCase()} ${signalEntry.id}: ${signalEntry.summary}${repairText}`;
|
|
1109
|
+
}
|
|
1110
|
+
function formatReceipt(receipt) {
|
|
1111
|
+
const blockedGuidance = receipt.verdict === "blocked"
|
|
1112
|
+
? receipt.latestReadyLocator
|
|
1113
|
+
? "guidance: current latest is blocked; repair blocked signals or use latest-ready before continuing."
|
|
1114
|
+
: "guidance: current latest is blocked; repair blocked signals before continuing."
|
|
1115
|
+
: null;
|
|
1116
|
+
return [
|
|
1117
|
+
`Recovery Sentinel - ${receipt.agent}`,
|
|
1118
|
+
`generated: ${receipt.generatedAt}`,
|
|
1119
|
+
`receipt: ${receipt.receiptLocator}`,
|
|
1120
|
+
`trigger: ${receipt.trigger}`,
|
|
1121
|
+
`verdict: ${receipt.verdict}`,
|
|
1122
|
+
`latest-ready: ${receipt.latestReadyLocator ?? "unavailable"}`,
|
|
1123
|
+
`summary: ${receipt.summary}`,
|
|
1124
|
+
...(blockedGuidance ? [blockedGuidance] : []),
|
|
1125
|
+
"",
|
|
1126
|
+
"Recovery anchor",
|
|
1127
|
+
` kind: ${receipt.recoveryAnchor.kind}`,
|
|
1128
|
+
` current ask: ${receipt.recoveryAnchor.currentAsk ?? "unavailable"}`,
|
|
1129
|
+
` next action: ${receipt.recoveryAnchor.nextSafeAction ?? "unavailable"}`,
|
|
1130
|
+
"",
|
|
1131
|
+
"Signals",
|
|
1132
|
+
...receipt.signals.map(renderSignal),
|
|
1133
|
+
];
|
|
1134
|
+
}
|
|
1135
|
+
function formatContextLossSentinelText(input) {
|
|
1136
|
+
if (input === null)
|
|
1137
|
+
return "Recovery Sentinel - unavailable";
|
|
1138
|
+
if ("latest" in input) {
|
|
1139
|
+
const displayReceipt = input.latest ?? input.latestReady;
|
|
1140
|
+
if (!displayReceipt) {
|
|
1141
|
+
return [
|
|
1142
|
+
"Recovery Sentinel - unavailable",
|
|
1143
|
+
...input.degraded.issues.map((issue) => `degraded: ${issue}`),
|
|
1144
|
+
].join("\n").trim();
|
|
1145
|
+
}
|
|
1146
|
+
return [
|
|
1147
|
+
...formatReceipt(displayReceipt),
|
|
1148
|
+
"",
|
|
1149
|
+
`history: ${input.history.length} receipt${input.history.length === 1 ? "" : "s"}`,
|
|
1150
|
+
...(input.degraded.issues.length > 0 ? ["", ...input.degraded.issues.map((issue) => `degraded: ${issue}`)] : []),
|
|
1151
|
+
].join("\n").trim();
|
|
1152
|
+
}
|
|
1153
|
+
return formatReceipt(input).join("\n").trim();
|
|
1154
|
+
}
|
|
1155
|
+
function formatContextLossSentinelJson(input) {
|
|
1156
|
+
return `${JSON.stringify(input, null, 2)}\n`;
|
|
1157
|
+
}
|