@ouro.bot/cli 0.1.0-alpha.55 → 0.1.0-alpha.550
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/README.md +133 -19
- 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-bootstrap-drift.md +54 -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/changelog.json +3555 -0
- package/dist/arc/attention-types.js +8 -0
- package/dist/arc/cares.js +140 -0
- package/dist/arc/episodes.js +117 -0
- package/dist/arc/intentions.js +133 -0
- package/dist/arc/json-store.js +117 -0
- package/dist/arc/obligations.js +237 -0
- package/dist/arc/packets.js +193 -0
- package/dist/arc/presence.js +185 -0
- package/dist/arc/task-lifecycle.js +65 -0
- package/dist/heart/active-work.js +837 -26
- package/dist/heart/agent-entry.js +58 -3
- 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/adapter.js +2 -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 +16 -0
- package/dist/heart/attachments/store.js +103 -0
- package/dist/heart/attachments/types.js +93 -0
- package/dist/heart/auth/auth-flow.js +479 -0
- package/dist/heart/background-operations.js +281 -0
- package/dist/heart/bundle-state.js +168 -0
- package/dist/heart/commitments.js +111 -0
- package/dist/heart/config-registry.js +304 -0
- package/dist/heart/config.js +114 -118
- package/dist/heart/core.js +925 -246
- package/dist/heart/cross-chat-delivery.js +3 -18
- package/dist/heart/daemon/agent-config-check.js +512 -0
- package/dist/heart/daemon/agent-discovery.js +102 -3
- package/dist/heart/daemon/agent-service.js +522 -0
- package/dist/heart/daemon/agentic-repair.js +554 -0
- package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
- package/dist/heart/daemon/boot-sync-probe.js +197 -0
- package/dist/heart/daemon/cadence.js +70 -0
- package/dist/heart/daemon/cli-defaults.js +665 -0
- package/dist/heart/daemon/cli-exec.js +7565 -0
- package/dist/heart/daemon/cli-help.js +498 -0
- package/dist/heart/daemon/cli-parse.js +1590 -0
- package/dist/heart/daemon/cli-render-doctor.js +57 -0
- package/dist/heart/daemon/cli-render.js +775 -0
- package/dist/heart/daemon/cli-types.js +8 -0
- package/dist/heart/daemon/connect-bay.js +323 -0
- package/dist/heart/daemon/daemon-cli.js +29 -1672
- package/dist/heart/daemon/daemon-entry.js +417 -2
- package/dist/heart/daemon/daemon-health.js +183 -0
- package/dist/heart/daemon/daemon-rollup.js +58 -0
- package/dist/heart/daemon/daemon-runtime-sync.js +87 -13
- package/dist/heart/daemon/daemon-tombstone.js +236 -0
- package/dist/heart/daemon/daemon.js +758 -71
- package/dist/heart/daemon/dns-workflow.js +394 -0
- package/dist/heart/daemon/doctor-types.js +8 -0
- package/dist/heart/daemon/doctor.js +844 -0
- package/dist/heart/daemon/drift-detection.js +146 -0
- package/dist/heart/daemon/health-monitor.js +122 -1
- package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
- package/dist/heart/daemon/hooks/bundle-meta.js +115 -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 +102 -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 +82 -12
- package/dist/heart/daemon/logs-prune.js +110 -0
- package/dist/heart/daemon/mcp-canary.js +297 -0
- package/dist/heart/daemon/message-router.js +2 -2
- package/dist/heart/daemon/os-cron-deps.js +135 -0
- package/dist/heart/daemon/os-cron.js +14 -12
- package/dist/heart/daemon/ouro-bot-entry.js +4 -2
- package/dist/heart/daemon/ouro-entry.js +3 -1
- package/dist/heart/daemon/process-manager.js +375 -33
- package/dist/heart/daemon/provider-discovery.js +137 -0
- package/dist/heart/daemon/provider-ping-progress.js +83 -0
- package/dist/heart/daemon/pulse.js +475 -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 +67 -16
- package/dist/heart/daemon/runtime-metadata.js +3 -31
- package/dist/heart/daemon/safe-mode.js +161 -0
- package/dist/heart/daemon/sense-manager.js +353 -38
- package/dist/heart/daemon/session-id-resolver.js +131 -0
- package/dist/heart/daemon/skill-management-installer.js +94 -0
- package/dist/heart/daemon/socket-client.js +158 -11
- package/dist/heart/daemon/stale-bundle-prune.js +96 -0
- package/dist/heart/daemon/startup-tui.js +330 -0
- package/dist/heart/daemon/task-scheduler.js +3 -25
- package/dist/heart/daemon/terminal-ui.js +499 -0
- package/dist/heart/daemon/thoughts.js +162 -17
- package/dist/heart/daemon/up-progress.js +366 -0
- package/dist/heart/daemon/vault-items.js +56 -0
- package/dist/heart/delegation.js +1 -1
- package/dist/heart/habits/habit-migration.js +189 -0
- package/dist/heart/habits/habit-parser.js +140 -0
- package/dist/heart/habits/habit-runtime-state.js +100 -0
- package/dist/heart/habits/habit-scheduler.js +372 -0
- package/dist/heart/{daemon → hatch}/hatch-flow.js +52 -117
- 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 +35 -12
- package/dist/heart/identity.js +200 -51
- package/dist/heart/kept-notes.js +357 -0
- package/dist/heart/kicks.js +1 -1
- package/dist/heart/machine-identity.js +161 -0
- package/dist/heart/mail-import-discovery.js +353 -0
- package/dist/heart/mailbox/mailbox-http-hooks.js +66 -0
- package/dist/heart/mailbox/mailbox-http-response.js +7 -0
- package/dist/heart/mailbox/mailbox-http-routes.js +246 -0
- package/dist/heart/mailbox/mailbox-http-static.js +103 -0
- package/dist/heart/mailbox/mailbox-http-transport.js +116 -0
- package/dist/heart/mailbox/mailbox-http.js +99 -0
- package/dist/heart/mailbox/mailbox-read.js +31 -0
- package/dist/heart/mailbox/mailbox-types.js +27 -0
- package/dist/heart/mailbox/mailbox-view.js +195 -0
- package/dist/heart/mailbox/readers/agent-machine.js +382 -0
- package/dist/heart/mailbox/readers/continuity-readers.js +338 -0
- package/dist/heart/mailbox/readers/mail.js +362 -0
- package/dist/heart/mailbox/readers/runtime-readers.js +651 -0
- package/dist/heart/mailbox/readers/sessions.js +232 -0
- package/dist/heart/mailbox/readers/shared.js +111 -0
- package/dist/heart/mcp/mcp-server.js +683 -0
- package/dist/heart/migrate-config.js +100 -0
- package/dist/heart/model-capabilities.js +19 -0
- package/dist/heart/platform.js +81 -0
- package/dist/heart/provider-attempt.js +134 -0
- package/dist/heart/provider-binding-resolver.js +255 -0
- package/dist/heart/provider-credentials.js +425 -0
- package/dist/heart/provider-failover.js +301 -0
- package/dist/heart/provider-models.js +81 -0
- package/dist/heart/provider-ping.js +262 -0
- package/dist/heart/provider-state.js +216 -0
- package/dist/heart/provider-visibility.js +188 -0
- package/dist/heart/providers/anthropic-token.js +131 -0
- package/dist/heart/providers/anthropic.js +139 -52
- package/dist/heart/providers/azure.js +97 -13
- package/dist/heart/providers/error-classification.js +127 -0
- package/dist/heart/providers/github-copilot.js +145 -0
- package/dist/heart/providers/minimax-vlm.js +189 -0
- package/dist/heart/providers/minimax.js +26 -8
- package/dist/heart/providers/openai-codex.js +55 -40
- package/dist/heart/runtime-capability-check.js +170 -0
- package/dist/heart/runtime-credentials.js +367 -0
- package/dist/heart/runtime-cwd.js +87 -0
- package/dist/heart/sense-truth.js +11 -4
- package/dist/heart/session-activity.js +43 -22
- package/dist/heart/session-events.js +1149 -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-stats-cli-main.js +5 -0
- package/dist/heart/session-stats.js +182 -0
- package/dist/heart/session-transcript.js +243 -0
- package/dist/heart/start-of-turn-packet.js +345 -0
- package/dist/heart/streaming.js +44 -27
- package/dist/heart/sync-classification.js +176 -0
- package/dist/heart/sync.js +449 -0
- package/dist/heart/target-resolution.js +9 -5
- 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-description.js +139 -0
- package/dist/heart/tool-friction.js +55 -0
- package/dist/heart/tool-loop.js +200 -0
- package/dist/heart/turn-context.js +381 -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 +426 -0
- package/dist/heart/versioning/ouro-version-manager.js +295 -0
- package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
- package/dist/heart/{daemon → versioning}/update-checker.js +6 -1
- package/dist/heart/{daemon → versioning}/update-hooks.js +63 -59
- package/dist/mailbox-ui/assets/index-BPr5vNuM.css +1 -0
- package/dist/mailbox-ui/assets/index-Cm51CY9W.js +61 -0
- package/dist/mailbox-ui/index.html +15 -0
- package/dist/mailroom/attention.js +167 -0
- package/dist/mailroom/autonomy.js +209 -0
- package/dist/mailroom/blob-store.js +674 -0
- package/dist/mailroom/body-cache.js +61 -0
- package/dist/mailroom/core.js +720 -0
- package/dist/mailroom/entry.js +160 -0
- package/dist/mailroom/file-store.js +430 -0
- package/dist/mailroom/mbox-import.js +383 -0
- package/dist/mailroom/outbound.js +380 -0
- package/dist/mailroom/policy.js +263 -0
- package/dist/mailroom/reader.js +233 -0
- package/dist/mailroom/search-cache.js +256 -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 +7 -1
- package/dist/mind/context.js +165 -101
- package/dist/mind/diary-integrity.js +60 -0
- package/dist/mind/{memory.js → diary.js} +62 -75
- package/dist/mind/embedding-provider.js +60 -0
- package/dist/mind/file-state.js +179 -0
- package/dist/mind/friends/channel.js +30 -0
- package/dist/mind/friends/resolver.js +54 -2
- package/dist/mind/friends/store-file.js +39 -3
- package/dist/mind/friends/types.js +2 -2
- package/dist/mind/journal-index.js +161 -0
- package/dist/mind/note-search.js +268 -0
- package/dist/mind/obligation-steering.js +221 -0
- package/dist/mind/pending.js +4 -0
- package/dist/mind/prompt-refresh.js +3 -2
- package/dist/mind/prompt.js +995 -123
- package/dist/mind/provenance-trust.js +26 -0
- package/dist/mind/scrutiny.js +173 -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 +1 -1
- package/dist/nerves/event-buffer.js +111 -0
- package/dist/nerves/index.js +224 -4
- 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 +152 -0
- package/dist/nerves/runtime.js +5 -1
- package/dist/repertoire/ado-client.js +15 -56
- package/dist/repertoire/ado-semantic.js +11 -10
- package/dist/repertoire/api-client.js +97 -0
- package/dist/repertoire/bitwarden-store.js +816 -0
- package/dist/repertoire/bundle-templates.js +72 -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 +330 -0
- package/dist/repertoire/coding/feedback.js +197 -30
- package/dist/repertoire/coding/manager.js +158 -9
- package/dist/repertoire/coding/spawner.js +55 -9
- package/dist/repertoire/coding/tools.js +170 -7
- package/dist/repertoire/commerce-errors.js +109 -0
- package/dist/repertoire/commerce-self-test.js +156 -0
- package/dist/repertoire/credential-access.js +111 -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 +396 -0
- package/dist/repertoire/mcp-client.js +295 -0
- package/dist/repertoire/mcp-manager.js +362 -0
- package/dist/repertoire/mcp-tools.js +63 -0
- package/dist/repertoire/shell-sessions.js +133 -0
- package/dist/repertoire/skills.js +15 -24
- package/dist/repertoire/stripe-client.js +131 -0
- package/dist/repertoire/tasks/board.js +31 -5
- package/dist/repertoire/tasks/fix.js +182 -0
- package/dist/repertoire/tasks/index.js +16 -4
- package/dist/repertoire/tasks/lifecycle.js +2 -2
- package/dist/repertoire/tasks/parser.js +3 -2
- package/dist/repertoire/tasks/scanner.js +194 -37
- package/dist/repertoire/tasks/transitions.js +16 -78
- package/dist/repertoire/tool-results.js +29 -0
- package/dist/repertoire/tools-attachments.js +317 -0
- package/dist/repertoire/tools-base.js +47 -1075
- package/dist/repertoire/tools-bluebubbles.js +1 -0
- package/dist/repertoire/tools-bridge.js +142 -0
- package/dist/repertoire/tools-bundle.js +984 -0
- package/dist/repertoire/tools-config.js +185 -0
- package/dist/repertoire/tools-continuity.js +248 -0
- package/dist/repertoire/tools-credential.js +381 -0
- package/dist/repertoire/tools-files.js +342 -0
- package/dist/repertoire/tools-flight.js +224 -0
- package/dist/repertoire/tools-flow.js +119 -0
- package/dist/repertoire/tools-github.js +1 -7
- package/dist/repertoire/tools-mail.js +1857 -0
- package/dist/repertoire/tools-notes.js +421 -0
- package/dist/repertoire/tools-session.js +750 -0
- package/dist/repertoire/tools-shell.js +120 -0
- package/dist/repertoire/tools-stripe.js +180 -0
- package/dist/repertoire/tools-surface.js +243 -0
- package/dist/repertoire/tools-teams.js +9 -39
- package/dist/repertoire/tools-travel.js +125 -0
- package/dist/repertoire/tools-trip.js +604 -0
- package/dist/repertoire/tools-user-profile.js +144 -0
- package/dist/repertoire/tools-vault.js +40 -0
- package/dist/repertoire/tools.js +108 -100
- package/dist/repertoire/travel-api-client.js +360 -0
- package/dist/repertoire/user-profile.js +131 -0
- package/dist/repertoire/vault-setup.js +246 -0
- package/dist/repertoire/vault-unlock.js +561 -0
- package/dist/scripts/claude-code-hook.js +41 -0
- package/dist/scripts/claude-code-stop-hook.js +47 -0
- package/dist/senses/attention-queue.js +116 -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} +219 -18
- package/dist/senses/bluebubbles/entry.js +77 -0
- package/dist/senses/{bluebubbles-inbound-log.js → bluebubbles/inbound-log.js} +20 -3
- package/dist/senses/bluebubbles/index.js +2305 -0
- package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -70
- package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +33 -12
- package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +3 -3
- package/dist/senses/bluebubbles/processed-log.js +133 -0
- package/dist/senses/bluebubbles/replay.js +137 -0
- package/dist/senses/{bluebubbles-runtime-state.js → bluebubbles/runtime-state.js} +30 -2
- package/dist/senses/{bluebubbles-session-cleanup.js → bluebubbles/session-cleanup.js} +1 -1
- 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 +240 -0
- package/dist/senses/cli/virtual-list.js +35 -0
- package/dist/senses/cli-entry.js +60 -8
- package/dist/senses/cli-layout.js +187 -0
- package/dist/senses/cli.js +520 -209
- package/dist/senses/commands.js +66 -3
- package/dist/senses/habit-turn-message.js +108 -0
- package/dist/senses/inner-dialog-worker.js +175 -21
- package/dist/senses/inner-dialog.js +330 -27
- package/dist/senses/mail-entry.js +66 -0
- package/dist/senses/mail.js +379 -0
- package/dist/senses/pipeline.js +569 -182
- package/dist/senses/proactive-content-guard.js +51 -0
- package/dist/senses/shared-turn.js +248 -0
- package/dist/senses/surface-tool.js +68 -0
- package/dist/senses/teams-entry.js +60 -8
- package/dist/senses/teams.js +387 -98
- package/dist/senses/trust-gate.js +100 -5
- package/dist/trips/core.js +138 -0
- package/dist/trips/store.js +146 -0
- package/package.json +38 -7
- package/skills/agent-commerce.md +106 -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 +101 -0
- package/skills/travel-planning.md +138 -0
- package/dist/heart/daemon/ouro-path-installer.js +0 -178
- package/dist/heart/daemon/subagent-installer.js +0 -166
- package/dist/heart/session-recall.js +0 -116
- package/dist/mind/associative-recall.js +0 -209
- package/dist/senses/bluebubbles-entry.js +0 -13
- package/dist/senses/bluebubbles.js +0 -1177
- package/dist/senses/debug-activity.js +0 -148
- package/subagents/README.md +0 -86
- package/subagents/work-doer.md +0 -237
- package/subagents/work-merger.md +0 -618
- package/subagents/work-planner.md +0 -390
- /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/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,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatProviderAttemptProgress = formatProviderAttemptProgress;
|
|
4
|
+
exports.formatProviderRetryProgress = formatProviderRetryProgress;
|
|
5
|
+
exports.createProviderPingProgressReporter = createProviderPingProgressReporter;
|
|
6
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
7
|
+
function formatProviderPingLabel(context) {
|
|
8
|
+
return context.model ? `${context.provider} / ${context.model}` : context.provider;
|
|
9
|
+
}
|
|
10
|
+
function providerRetryReason(record) {
|
|
11
|
+
switch (record.classification) {
|
|
12
|
+
case "auth-failure":
|
|
13
|
+
return "credentials were rejected";
|
|
14
|
+
case "usage-limit":
|
|
15
|
+
return "usage limit hit";
|
|
16
|
+
case "rate-limit":
|
|
17
|
+
return "provider asked us to slow down";
|
|
18
|
+
case "server-error":
|
|
19
|
+
return record.httpStatus === 529 ? "provider is busy right now" : "provider is having trouble right now";
|
|
20
|
+
case "network-error":
|
|
21
|
+
return "network connection dropped";
|
|
22
|
+
case "unknown":
|
|
23
|
+
return "last check failed";
|
|
24
|
+
default:
|
|
25
|
+
return "last check failed";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function providerRetryTiming(delayMs) {
|
|
29
|
+
if (!delayMs || delayMs <= 0)
|
|
30
|
+
return "now";
|
|
31
|
+
const seconds = delayMs / 1000;
|
|
32
|
+
const rounded = Number.isInteger(seconds) ? `${seconds.toFixed(0)}s` : `${seconds.toFixed(1).replace(/\.0$/, "")}s`;
|
|
33
|
+
return `in ${rounded}`;
|
|
34
|
+
}
|
|
35
|
+
function formatProviderAttemptProgress(context, attempt, maxAttempts) {
|
|
36
|
+
const prefix = context.subject ? `${context.subject}: ` : "";
|
|
37
|
+
if (maxAttempts <= 1)
|
|
38
|
+
return `${prefix}checking ${formatProviderPingLabel(context)}...`;
|
|
39
|
+
return `${prefix}checking ${formatProviderPingLabel(context)} (attempt ${attempt} of ${maxAttempts})...`;
|
|
40
|
+
}
|
|
41
|
+
function formatProviderRetryProgress(context, record, maxAttempts) {
|
|
42
|
+
const nextAttempt = Math.min(record.attempt + 1, maxAttempts);
|
|
43
|
+
const retryDetail = `${providerRetryReason(record)}; retrying ${providerRetryTiming(record.delayMs)} (attempt ${nextAttempt} of ${maxAttempts})`;
|
|
44
|
+
if (context.subject) {
|
|
45
|
+
return `${context.subject}: ${retryDetail} while checking ${formatProviderPingLabel(context)}`;
|
|
46
|
+
}
|
|
47
|
+
return `${formatProviderPingLabel(record)}: ${retryDetail}`;
|
|
48
|
+
}
|
|
49
|
+
function createProviderPingProgressReporter(context, onProgress) {
|
|
50
|
+
return {
|
|
51
|
+
onAttemptStart: async (attempt, maxAttempts) => {
|
|
52
|
+
(0, runtime_1.emitNervesEvent)({
|
|
53
|
+
component: "daemon",
|
|
54
|
+
event: "daemon.provider_ping_progress_reported",
|
|
55
|
+
message: "reported provider ping attempt progress",
|
|
56
|
+
meta: {
|
|
57
|
+
provider: context.provider,
|
|
58
|
+
model: context.model ?? null,
|
|
59
|
+
kind: "attempt",
|
|
60
|
+
attempt,
|
|
61
|
+
maxAttempts,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
onProgress(formatProviderAttemptProgress(context, attempt, maxAttempts));
|
|
65
|
+
},
|
|
66
|
+
onRetry: async (record, maxAttempts) => {
|
|
67
|
+
(0, runtime_1.emitNervesEvent)({
|
|
68
|
+
component: "daemon",
|
|
69
|
+
event: "daemon.provider_ping_progress_reported",
|
|
70
|
+
message: "reported provider ping retry progress",
|
|
71
|
+
meta: {
|
|
72
|
+
provider: record.provider,
|
|
73
|
+
model: record.model,
|
|
74
|
+
kind: "retry",
|
|
75
|
+
attempt: record.attempt,
|
|
76
|
+
maxAttempts,
|
|
77
|
+
classification: record.classification ?? "unknown",
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
onProgress(formatProviderRetryProgress(context, record, maxAttempts));
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -0,0 +1,475 @@
|
|
|
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.getPulsePath = getPulsePath;
|
|
37
|
+
exports.getPulseDeliveredPath = getPulseDeliveredPath;
|
|
38
|
+
exports.buildAlertId = buildAlertId;
|
|
39
|
+
exports.readAgentActivity = readAgentActivity;
|
|
40
|
+
exports.buildPulseState = buildPulseState;
|
|
41
|
+
exports.findNovelBrokenAgents = findNovelBrokenAgents;
|
|
42
|
+
exports.findRecoveredAgents = findRecoveredAgents;
|
|
43
|
+
exports.buildRecoveryAlertId = buildRecoveryAlertId;
|
|
44
|
+
exports.pickWakeRecipient = pickWakeRecipient;
|
|
45
|
+
exports.writePulse = writePulse;
|
|
46
|
+
exports.readPulse = readPulse;
|
|
47
|
+
exports.readDeliveredState = readDeliveredState;
|
|
48
|
+
exports.writeDeliveredState = writeDeliveredState;
|
|
49
|
+
exports.pruneDeliveredState = pruneDeliveredState;
|
|
50
|
+
exports.flushPulse = flushPulse;
|
|
51
|
+
const fs = __importStar(require("fs"));
|
|
52
|
+
const os = __importStar(require("os"));
|
|
53
|
+
const path = __importStar(require("path"));
|
|
54
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
55
|
+
const provider_visibility_1 = require("../provider-visibility");
|
|
56
|
+
/* v8 ignore next 3 -- path defaults: tests always inject @preserve */
|
|
57
|
+
function defaultPulsePath() {
|
|
58
|
+
return path.join(os.homedir(), ".ouro-cli", "pulse.json");
|
|
59
|
+
}
|
|
60
|
+
/* v8 ignore next 3 -- path defaults: tests always inject @preserve */
|
|
61
|
+
function defaultDeliveredPath() {
|
|
62
|
+
return path.join(os.homedir(), ".ouro-cli", "pulse-delivered.json");
|
|
63
|
+
}
|
|
64
|
+
function getPulsePath() {
|
|
65
|
+
return defaultPulsePath();
|
|
66
|
+
}
|
|
67
|
+
function getPulseDeliveredPath() {
|
|
68
|
+
return defaultDeliveredPath();
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Hash an error reason into a short stable identifier. We use a simple
|
|
72
|
+
* non-cryptographic hash because we only need stability across daemon
|
|
73
|
+
* restarts on the same machine, not collision resistance against
|
|
74
|
+
* adversaries. djb2 is fine.
|
|
75
|
+
*/
|
|
76
|
+
function hashErrorReason(reason) {
|
|
77
|
+
let h = 5381;
|
|
78
|
+
for (let i = 0; i < reason.length; i++) {
|
|
79
|
+
h = ((h << 5) + h + reason.charCodeAt(i)) >>> 0;
|
|
80
|
+
}
|
|
81
|
+
return h.toString(16);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Build a stable alert ID for a (agent, error) pair. Same agent + same
|
|
85
|
+
* error → same ID, even across daemon restarts. Different error → new ID.
|
|
86
|
+
*/
|
|
87
|
+
function buildAlertId(agent, errorReason) {
|
|
88
|
+
return `${agent}:${hashErrorReason(errorReason)}`;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Read an agent's inner-dialog runtime state and format it as a short
|
|
92
|
+
* activity hint string suitable for display in another agent's pulse
|
|
93
|
+
* section. Returns null when the file is missing or malformed — both
|
|
94
|
+
* cases are silent (the pulse just doesn't include activity for that
|
|
95
|
+
* agent).
|
|
96
|
+
*
|
|
97
|
+
* Pure: takes a file reader so tests can inject content without touching
|
|
98
|
+
* fs. Defaults to fs.readFileSync for the production daemon caller.
|
|
99
|
+
*/
|
|
100
|
+
function readAgentActivity(bundlePath, readFile = (p) => fs.readFileSync(p, "utf-8")) {
|
|
101
|
+
const runtimePath = path.join(bundlePath, "state", "sessions", "self", "inner", "runtime.json");
|
|
102
|
+
let raw;
|
|
103
|
+
try {
|
|
104
|
+
raw = readFile(runtimePath);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
let parsed;
|
|
110
|
+
try {
|
|
111
|
+
parsed = JSON.parse(raw);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
const status = typeof parsed.status === "string" ? parsed.status : null;
|
|
117
|
+
const reason = typeof parsed.reason === "string" ? parsed.reason : null;
|
|
118
|
+
const startedAt = typeof parsed.startedAt === "string" ? parsed.startedAt : null;
|
|
119
|
+
if (!status)
|
|
120
|
+
return null;
|
|
121
|
+
// Format compactly. Examples:
|
|
122
|
+
// "running (instinct since 23:44)"
|
|
123
|
+
// "idle since 21:30"
|
|
124
|
+
// "running"
|
|
125
|
+
const sinceStr = startedAt ? ` since ${startedAt.slice(11, 16)}` : "";
|
|
126
|
+
if (status === "running" && reason) {
|
|
127
|
+
return `running (${reason}${sinceStr})`;
|
|
128
|
+
}
|
|
129
|
+
return `${status}${sinceStr}`;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Convert daemon process-manager snapshots into the pulse state shape.
|
|
133
|
+
* Pure: no fs side effects (activity reading goes through the injected
|
|
134
|
+
* `readActivity` callback so tests can pin everything).
|
|
135
|
+
*
|
|
136
|
+
* The bundlePath comes from the bundlesRoot — we don't read the bundle
|
|
137
|
+
* directly, we just compute where it lives so sibling agents have a
|
|
138
|
+
* starting point if they want to navigate there manually.
|
|
139
|
+
*/
|
|
140
|
+
function buildPulseState(snapshots, bundlesRoot, daemonVersion, now, readActivity = readAgentActivity, readProviderVisibility = () => null) {
|
|
141
|
+
const agents = snapshots.map((snap) => {
|
|
142
|
+
const errorReason = snap.errorReason;
|
|
143
|
+
const bundlePath = path.join(bundlesRoot, `${snap.name}.ouro`);
|
|
144
|
+
return {
|
|
145
|
+
name: snap.name,
|
|
146
|
+
bundlePath,
|
|
147
|
+
status: snap.status,
|
|
148
|
+
lastSeenAt: snap.startedAt,
|
|
149
|
+
errorReason,
|
|
150
|
+
fixHint: snap.fixHint,
|
|
151
|
+
alertId: errorReason ? buildAlertId(snap.name, errorReason) : null,
|
|
152
|
+
// Only read activity for agents that are actually running. For
|
|
153
|
+
// crashed/stopped agents, the runtime.json is stale at best.
|
|
154
|
+
currentActivity: snap.status === "running" ? readActivity(bundlePath) : null,
|
|
155
|
+
providerVisibility: readProviderVisibility(snap.name, bundlePath),
|
|
156
|
+
};
|
|
157
|
+
});
|
|
158
|
+
return {
|
|
159
|
+
generatedAt: now.toISOString(),
|
|
160
|
+
daemonVersion,
|
|
161
|
+
agents,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* A "novel broken transition" is an agent that is currently broken AND
|
|
166
|
+
* either (a) wasn't broken in the previous pulse state, OR (b) was broken
|
|
167
|
+
* but with a different alertId. Used to decide when to fire an inner.wake
|
|
168
|
+
* for proactive notification.
|
|
169
|
+
*
|
|
170
|
+
* Pure: takes prev and next states, returns the list of newly-broken
|
|
171
|
+
* agents that warrant a wake.
|
|
172
|
+
*/
|
|
173
|
+
function findNovelBrokenAgents(prev, next) {
|
|
174
|
+
const novel = [];
|
|
175
|
+
for (const agent of next.agents) {
|
|
176
|
+
if (!agent.alertId)
|
|
177
|
+
continue;
|
|
178
|
+
const prevAgent = prev?.agents.find((a) => a.name === agent.name);
|
|
179
|
+
const wasBrokenWithSameAlert = prevAgent?.alertId === agent.alertId;
|
|
180
|
+
if (wasBrokenWithSameAlert)
|
|
181
|
+
continue;
|
|
182
|
+
novel.push(agent);
|
|
183
|
+
}
|
|
184
|
+
return novel;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* A "recovery transition" is an agent that was broken in the previous
|
|
188
|
+
* pulse state but is healthy now. The pulse fires a wake on these too,
|
|
189
|
+
* so the user gets a positive notification when their fix takes effect.
|
|
190
|
+
*
|
|
191
|
+
* Pure: takes prev and next states, returns the list of newly-recovered
|
|
192
|
+
* agents that warrant a wake.
|
|
193
|
+
*/
|
|
194
|
+
function findRecoveredAgents(prev, next) {
|
|
195
|
+
if (!prev)
|
|
196
|
+
return [];
|
|
197
|
+
const recovered = [];
|
|
198
|
+
for (const agent of next.agents) {
|
|
199
|
+
// Healthy in next.
|
|
200
|
+
if (agent.alertId !== null)
|
|
201
|
+
continue;
|
|
202
|
+
if (agent.status !== "running")
|
|
203
|
+
continue;
|
|
204
|
+
// Was broken in prev.
|
|
205
|
+
const prevAgent = prev.agents.find((a) => a.name === agent.name);
|
|
206
|
+
if (!prevAgent || prevAgent.alertId === null)
|
|
207
|
+
continue;
|
|
208
|
+
recovered.push(agent);
|
|
209
|
+
}
|
|
210
|
+
return recovered;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Build a stable alert ID for a recovery event. Used so the recovery
|
|
214
|
+
* wake is also at-most-once (no re-paging on every snapshot change after
|
|
215
|
+
* recovery). Includes the recovery timestamp so a later break+heal cycle
|
|
216
|
+
* generates a fresh ID.
|
|
217
|
+
*/
|
|
218
|
+
function buildRecoveryAlertId(agent, recoveredAt) {
|
|
219
|
+
return `recovery:${agent}:${recoveredAt}`;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Pick which agent should receive an inner.wake for a fleet alert. Heuristic:
|
|
223
|
+
* the most-recently-active running agent (by `lastSeenAt`), excluding the
|
|
224
|
+
* broken agent itself and any agents that aren't currently running. Returns
|
|
225
|
+
* null if there's no eligible recipient (e.g., the only other agent on the
|
|
226
|
+
* machine is also broken).
|
|
227
|
+
*
|
|
228
|
+
* Pure: takes the pulse state and the alert target, returns the chosen
|
|
229
|
+
* recipient name or null.
|
|
230
|
+
*/
|
|
231
|
+
function pickWakeRecipient(state, brokenAgent) {
|
|
232
|
+
const candidates = state.agents
|
|
233
|
+
.filter((a) => a.name !== brokenAgent)
|
|
234
|
+
.filter((a) => a.status === "running")
|
|
235
|
+
.filter((a) => a.lastSeenAt !== null)
|
|
236
|
+
.sort((a, b) => {
|
|
237
|
+
// Most-recent first. lastSeenAt is non-null per the filter above.
|
|
238
|
+
const aMs = Date.parse(a.lastSeenAt);
|
|
239
|
+
const bMs = Date.parse(b.lastSeenAt);
|
|
240
|
+
return bMs - aMs;
|
|
241
|
+
});
|
|
242
|
+
return candidates[0]?.name ?? null;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Write the pulse state to disk. Best-effort: if the write fails, emit a
|
|
246
|
+
* nerves event but do not throw. The pulse is a notification mechanism;
|
|
247
|
+
* the daemon's primary work should not be blocked by it.
|
|
248
|
+
*/
|
|
249
|
+
function writePulse(state, deps = {}) {
|
|
250
|
+
/* v8 ignore start -- dep defaults: production-only paths; tests inject all four explicitly @preserve */
|
|
251
|
+
const filePath = deps.pulsePath ?? defaultPulsePath();
|
|
252
|
+
const writeFile = deps.writeFile ?? ((p, c) => fs.writeFileSync(p, c, "utf-8"));
|
|
253
|
+
const mkdirp = deps.mkdirp ?? ((d) => fs.mkdirSync(d, { recursive: true }));
|
|
254
|
+
/* v8 ignore stop */
|
|
255
|
+
try {
|
|
256
|
+
mkdirp(path.dirname(filePath));
|
|
257
|
+
writeFile(filePath, JSON.stringify(state, null, 2) + "\n");
|
|
258
|
+
(0, runtime_1.emitNervesEvent)({
|
|
259
|
+
component: "daemon",
|
|
260
|
+
event: "daemon.pulse_written",
|
|
261
|
+
message: "wrote machine pulse state",
|
|
262
|
+
meta: { filePath, agentCount: state.agents.length },
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
(0, runtime_1.emitNervesEvent)({
|
|
267
|
+
level: "warn",
|
|
268
|
+
component: "daemon",
|
|
269
|
+
event: "daemon.pulse_write_error",
|
|
270
|
+
message: "failed to write pulse state",
|
|
271
|
+
meta: {
|
|
272
|
+
filePath,
|
|
273
|
+
error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error),
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Read the pulse state from disk. Returns null if the file doesn't exist
|
|
280
|
+
* or is malformed — pulse readers (like the prompt assembler) should
|
|
281
|
+
* gracefully degrade to "no pulse, render nothing" rather than crash.
|
|
282
|
+
*/
|
|
283
|
+
function readPulse(deps = {}) {
|
|
284
|
+
const filePath = deps.pulsePath ?? defaultPulsePath();
|
|
285
|
+
/* v8 ignore next -- dep default: tests always inject @preserve */
|
|
286
|
+
const readFile = deps.readFile ?? ((p) => fs.readFileSync(p, "utf-8"));
|
|
287
|
+
try {
|
|
288
|
+
const raw = readFile(filePath);
|
|
289
|
+
const parsed = JSON.parse(raw);
|
|
290
|
+
if (typeof parsed.generatedAt !== "string")
|
|
291
|
+
return null;
|
|
292
|
+
if (typeof parsed.daemonVersion !== "string")
|
|
293
|
+
return null;
|
|
294
|
+
if (!Array.isArray(parsed.agents))
|
|
295
|
+
return null;
|
|
296
|
+
return {
|
|
297
|
+
generatedAt: parsed.generatedAt,
|
|
298
|
+
daemonVersion: parsed.daemonVersion,
|
|
299
|
+
agents: parsed.agents.filter(isValidPulseAgentEntry).map((agent) => {
|
|
300
|
+
const rawAgent = agent;
|
|
301
|
+
if (!Object.prototype.hasOwnProperty.call(rawAgent, "providerVisibility"))
|
|
302
|
+
return agent;
|
|
303
|
+
return {
|
|
304
|
+
...agent,
|
|
305
|
+
providerVisibility: (0, provider_visibility_1.isAgentProviderVisibility)(rawAgent.providerVisibility)
|
|
306
|
+
? rawAgent.providerVisibility
|
|
307
|
+
: null,
|
|
308
|
+
};
|
|
309
|
+
}),
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
function isValidPulseAgentEntry(value) {
|
|
317
|
+
if (value === null || typeof value !== "object")
|
|
318
|
+
return false;
|
|
319
|
+
const v = value;
|
|
320
|
+
return (typeof v.name === "string"
|
|
321
|
+
&& typeof v.bundlePath === "string"
|
|
322
|
+
&& (v.status === "running" || v.status === "starting" || v.status === "stopped" || v.status === "crashed")
|
|
323
|
+
&& (v.lastSeenAt === null || typeof v.lastSeenAt === "string")
|
|
324
|
+
&& (v.errorReason === null || typeof v.errorReason === "string")
|
|
325
|
+
&& (v.fixHint === null || typeof v.fixHint === "string")
|
|
326
|
+
&& (v.alertId === null || typeof v.alertId === "string"));
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Read the persistent delivered-alerts state. Returns an empty set if the
|
|
330
|
+
* file doesn't exist or is malformed.
|
|
331
|
+
*/
|
|
332
|
+
function readDeliveredState(deps = {}) {
|
|
333
|
+
/* v8 ignore start -- dep defaults: production-only paths; tests inject all explicitly @preserve */
|
|
334
|
+
const filePath = deps.deliveredPath ?? defaultDeliveredPath();
|
|
335
|
+
const readFile = deps.readFile ?? ((p) => fs.readFileSync(p, "utf-8"));
|
|
336
|
+
/* v8 ignore stop */
|
|
337
|
+
try {
|
|
338
|
+
const raw = readFile(filePath);
|
|
339
|
+
const parsed = JSON.parse(raw);
|
|
340
|
+
if (!Array.isArray(parsed.delivered))
|
|
341
|
+
return new Set();
|
|
342
|
+
return new Set(parsed.delivered.filter((id) => typeof id === "string"));
|
|
343
|
+
}
|
|
344
|
+
catch {
|
|
345
|
+
return new Set();
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Persist the delivered-alerts state to disk. Best-effort.
|
|
350
|
+
*/
|
|
351
|
+
function writeDeliveredState(delivered, deps = {}) {
|
|
352
|
+
/* v8 ignore start -- dep defaults: production-only paths; tests inject all four explicitly @preserve */
|
|
353
|
+
const filePath = deps.deliveredPath ?? defaultDeliveredPath();
|
|
354
|
+
const writeFile = deps.writeFile ?? ((p, c) => fs.writeFileSync(p, c, "utf-8"));
|
|
355
|
+
const mkdirp = deps.mkdirp ?? ((d) => fs.mkdirSync(d, { recursive: true }));
|
|
356
|
+
/* v8 ignore stop */
|
|
357
|
+
try {
|
|
358
|
+
mkdirp(path.dirname(filePath));
|
|
359
|
+
const state = { delivered: [...delivered].sort() };
|
|
360
|
+
writeFile(filePath, JSON.stringify(state, null, 2) + "\n");
|
|
361
|
+
}
|
|
362
|
+
catch (error) {
|
|
363
|
+
(0, runtime_1.emitNervesEvent)({
|
|
364
|
+
level: "warn",
|
|
365
|
+
component: "daemon",
|
|
366
|
+
event: "daemon.pulse_delivered_write_error",
|
|
367
|
+
message: "failed to write pulse delivered state",
|
|
368
|
+
meta: {
|
|
369
|
+
filePath,
|
|
370
|
+
error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error),
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Prune delivered alert IDs that no longer correspond to an active broken
|
|
377
|
+
* agent. When the user fixes ouroboros's config, the next pulse state has
|
|
378
|
+
* no alertId for ouroboros, so we drop the old delivered entry — meaning
|
|
379
|
+
* if ouroboros breaks AGAIN later (with the same error), we re-page.
|
|
380
|
+
*/
|
|
381
|
+
function pruneDeliveredState(delivered, state) {
|
|
382
|
+
const liveAlertIds = new Set();
|
|
383
|
+
for (const agent of state.agents) {
|
|
384
|
+
if (agent.alertId)
|
|
385
|
+
liveAlertIds.add(agent.alertId);
|
|
386
|
+
}
|
|
387
|
+
const pruned = new Set();
|
|
388
|
+
for (const id of delivered) {
|
|
389
|
+
if (liveAlertIds.has(id))
|
|
390
|
+
pruned.add(id);
|
|
391
|
+
}
|
|
392
|
+
return pruned;
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Single entry point the daemon's onSnapshotChange callback uses. Builds
|
|
396
|
+
* the new pulse state, diffs against the previous, writes the file, and
|
|
397
|
+
* fires inner.wake on novel broken transitions to the most-recently-active
|
|
398
|
+
* sibling. Persistent at-most-once delivery via the delivered state file.
|
|
399
|
+
*
|
|
400
|
+
* Pure-ish: all I/O goes through dep callbacks so tests can pin every
|
|
401
|
+
* input and assert every effect. Defaults wire to the real fs functions
|
|
402
|
+
* for production callers.
|
|
403
|
+
*/
|
|
404
|
+
function flushPulse(deps) {
|
|
405
|
+
const state = buildPulseState(deps.snapshots, deps.bundlesRoot, deps.daemonVersion, deps.now, readAgentActivity, (agentName, bundlePath) => (0, provider_visibility_1.buildAgentProviderVisibility)({ agentName, agentRoot: bundlePath }));
|
|
406
|
+
/* v8 ignore start -- dep defaults: production daemon path; the arrow functions only fire when the corresponding dep is omitted, which only happens in production code paths. Tests inject all deps explicitly. @preserve */
|
|
407
|
+
const readPrev = deps.readPrev ?? (() => readPulse());
|
|
408
|
+
const writeNext = deps.writeNext ?? ((s) => writePulse(s));
|
|
409
|
+
const readDelivered = deps.readDelivered ?? (() => readDeliveredState());
|
|
410
|
+
const writeDelivered = deps.writeDelivered ?? ((d) => writeDeliveredState(d));
|
|
411
|
+
const fireInnerWake = deps.fireInnerWake ?? null;
|
|
412
|
+
/* v8 ignore stop */
|
|
413
|
+
const prev = readPrev();
|
|
414
|
+
let delivered = readDelivered();
|
|
415
|
+
// Write the new pulse state first so any reader (including the
|
|
416
|
+
// wake-recipient agent's prompt assembly on its next turn) sees the
|
|
417
|
+
// current state. Doing this before firing the wake matters: if the
|
|
418
|
+
// wake races against a fast prompt build on the recipient, we want
|
|
419
|
+
// the recipient to read the NEW state, not the old one.
|
|
420
|
+
writeNext(state);
|
|
421
|
+
// Find agents that newly transitioned to broken (or to a different
|
|
422
|
+
// error than before).
|
|
423
|
+
const novelBroken = findNovelBrokenAgents(prev, state);
|
|
424
|
+
// Of those, the ones we haven't already delivered an alert for.
|
|
425
|
+
const undeliveredBroken = novelBroken.filter((a) => a.alertId !== null && !delivered.has(a.alertId));
|
|
426
|
+
// Find agents that newly recovered (were broken in prev, healthy now).
|
|
427
|
+
const recovered = findRecoveredAgents(prev, state);
|
|
428
|
+
// For recovery wakes, build a fresh alert ID per recovery event so
|
|
429
|
+
// we don't re-page on every subsequent flush.
|
|
430
|
+
const recoveryAlertIds = recovered.map((a) => buildRecoveryAlertId(a.name, state.generatedAt));
|
|
431
|
+
const undeliveredRecovered = recovered.filter((_, i) => !delivered.has(recoveryAlertIds[i]));
|
|
432
|
+
// Fire wakes and update the delivered set.
|
|
433
|
+
const wakeFiredFor = [];
|
|
434
|
+
const newlyDelivered = [];
|
|
435
|
+
for (const broken of undeliveredBroken) {
|
|
436
|
+
/* v8 ignore next -- defensive: undeliveredBroken already filtered to non-null alertId; this is a TS narrowing helper @preserve */
|
|
437
|
+
if (broken.alertId === null)
|
|
438
|
+
continue;
|
|
439
|
+
const recipient = pickWakeRecipient(state, broken.name);
|
|
440
|
+
if (recipient !== null && fireInnerWake !== null) {
|
|
441
|
+
fireInnerWake(recipient);
|
|
442
|
+
wakeFiredFor.push(recipient);
|
|
443
|
+
}
|
|
444
|
+
// Mark delivered even if no recipient was available — otherwise
|
|
445
|
+
// the daemon will keep trying to wake every time the snapshot
|
|
446
|
+
// changes, which would spam logs without producing any user value.
|
|
447
|
+
// The passive prompt section still surfaces the broken state to
|
|
448
|
+
// the recipient when they eventually have a turn for any reason.
|
|
449
|
+
delivered.add(broken.alertId);
|
|
450
|
+
newlyDelivered.push(broken.alertId);
|
|
451
|
+
}
|
|
452
|
+
for (let i = 0; i < undeliveredRecovered.length; i++) {
|
|
453
|
+
const recoveredAgent = undeliveredRecovered[i];
|
|
454
|
+
const recoveryAlertId = buildRecoveryAlertId(recoveredAgent.name, state.generatedAt);
|
|
455
|
+
const recipient = pickWakeRecipient(state, recoveredAgent.name);
|
|
456
|
+
if (recipient !== null && fireInnerWake !== null) {
|
|
457
|
+
fireInnerWake(recipient);
|
|
458
|
+
wakeFiredFor.push(recipient);
|
|
459
|
+
}
|
|
460
|
+
delivered.add(recoveryAlertId);
|
|
461
|
+
newlyDelivered.push(recoveryAlertId);
|
|
462
|
+
}
|
|
463
|
+
// Drop delivered ids for agents that have healed since (this drops
|
|
464
|
+
// the original `agent_config_failure:...` IDs once the agent recovers,
|
|
465
|
+
// so a future relapse re-pages). Recovery alert IDs are NOT pruned by
|
|
466
|
+
// pruneDeliveredState because they're tied to a healthy state, not a
|
|
467
|
+
// broken state — they live in the delivered set until the same agent
|
|
468
|
+
// breaks and recovers again, at which point a new recovery ID is
|
|
469
|
+
// generated and the old one becomes harmless cruft. We could clean
|
|
470
|
+
// them up too, but the cost is bounded by the number of recovery
|
|
471
|
+
// cycles, which is small.
|
|
472
|
+
delivered = pruneDeliveredState(delivered, state);
|
|
473
|
+
writeDelivered(delivered);
|
|
474
|
+
return { state, wakeFiredFor, newlyDelivered };
|
|
475
|
+
}
|