@ouro.bot/cli 0.1.0-alpha.52 → 0.1.0-alpha.520
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/{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 +3353 -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 +427 -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 +119 -129
- package/dist/heart/core.js +948 -243
- 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 +360 -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 +643 -0
- package/dist/heart/daemon/cli-exec.js +7476 -0
- package/dist/heart/daemon/cli-help.js +493 -0
- package/dist/heart/daemon/cli-parse.js +1557 -0
- package/dist/heart/daemon/cli-render-doctor.js +57 -0
- package/dist/heart/daemon/cli-render.js +649 -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 -1631
- package/dist/heart/daemon/daemon-entry.js +404 -3
- 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 +190 -12
- package/dist/heart/daemon/daemon-tombstone.js +236 -0
- package/dist/heart/daemon/daemon.js +758 -60
- package/dist/heart/daemon/dns-workflow.js +394 -0
- package/dist/heart/daemon/doctor-types.js +8 -0
- package/dist/heart/daemon/doctor.js +837 -0
- package/dist/heart/daemon/drift-detection.js +146 -0
- package/dist/heart/daemon/health-monitor.js +92 -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 +25 -5
- package/dist/heart/daemon/log-tailer.js +82 -12
- package/dist/heart/daemon/logs-prune.js +110 -0
- package/dist/heart/daemon/message-router.js +2 -2
- package/dist/heart/daemon/os-cron-deps.js +134 -0
- 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 +381 -26
- 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 +73 -0
- package/dist/heart/daemon/runtime-mode.js +67 -0
- package/dist/heart/daemon/safe-mode.js +161 -0
- package/dist/heart/daemon/sense-manager.js +259 -37
- 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 +109 -4
- 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 +205 -66
- 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/mcp/mcp-server.js +653 -0
- package/dist/heart/migrate-config.js +100 -0
- package/dist/heart/model-capabilities.js +19 -0
- package/dist/heart/outlook/outlook-http-hooks.js +66 -0
- package/dist/heart/outlook/outlook-http-response.js +7 -0
- package/dist/heart/outlook/outlook-http-routes.js +244 -0
- package/dist/heart/outlook/outlook-http-static.js +103 -0
- package/dist/heart/outlook/outlook-http-transport.js +116 -0
- package/dist/heart/outlook/outlook-http.js +99 -0
- package/dist/heart/outlook/outlook-read.js +31 -0
- package/dist/heart/outlook/outlook-types.js +27 -0
- package/dist/heart/outlook/outlook-view.js +195 -0
- package/dist/heart/outlook/readers/agent-machine.js +382 -0
- package/dist/heart/outlook/readers/continuity-readers.js +336 -0
- package/dist/heart/outlook/readers/mail.js +362 -0
- package/dist/heart/outlook/readers/runtime-readers.js +650 -0
- package/dist/heart/outlook/readers/sessions.js +232 -0
- package/dist/heart/outlook/readers/shared.js +111 -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/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 +36 -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 +1 -1
- package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
- package/dist/heart/versioning/ouro-path-installer.js +425 -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 +5 -1
- package/dist/heart/{daemon → versioning}/update-hooks.js +63 -59
- package/dist/mailroom/attention.js +167 -0
- package/dist/mailroom/autonomy.js +209 -0
- package/dist/mailroom/blob-store.js +606 -0
- package/dist/mailroom/body-cache.js +61 -0
- package/dist/mailroom/core.js +672 -0
- package/dist/mailroom/entry.js +160 -0
- package/dist/mailroom/file-store.js +426 -0
- package/dist/mailroom/mbox-import.js +382 -0
- package/dist/mailroom/outbound.js +380 -0
- package/dist/mailroom/policy.js +263 -0
- package/dist/mailroom/reader.js +228 -0
- package/dist/mailroom/search-cache.js +182 -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} +74 -93
- 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 +942 -122
- 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/outlook-ui/assets/index-BPr5vNuM.css +1 -0
- package/dist/outlook-ui/assets/index-Cm51CY9W.js +61 -0
- package/dist/outlook-ui/index.html +15 -0
- 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 +255 -0
- package/dist/repertoire/mcp-manager.js +305 -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 +1477 -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 +422 -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/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 +1947 -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 +111 -0
- package/dist/senses/bluebubbles/replay.js +129 -0
- package/dist/senses/{bluebubbles-runtime-state.js → bluebubbles/runtime-state.js} +2 -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 +37 -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
|
@@ -51,22 +51,108 @@ class DaemonProcessManager {
|
|
|
51
51
|
now;
|
|
52
52
|
setTimeoutFn;
|
|
53
53
|
clearTimeoutFn;
|
|
54
|
+
cooldownRecoveryMs;
|
|
55
|
+
maxCooldownRetries;
|
|
56
|
+
startupStaleAfterMs;
|
|
57
|
+
existsSyncFn;
|
|
58
|
+
configCheckFn;
|
|
59
|
+
statusWriterFn;
|
|
60
|
+
onSnapshotChangeFn;
|
|
61
|
+
/**
|
|
62
|
+
* Notify the snapshot-change observer (if registered). Swallows any
|
|
63
|
+
* errors from the observer so process lifecycle code never fails
|
|
64
|
+
* because the observer threw.
|
|
65
|
+
*/
|
|
66
|
+
notifySnapshotChange(snapshot) {
|
|
67
|
+
if (!this.onSnapshotChangeFn)
|
|
68
|
+
return;
|
|
69
|
+
try {
|
|
70
|
+
this.onSnapshotChangeFn(snapshot);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
(0, runtime_1.emitNervesEvent)({
|
|
74
|
+
level: "warn",
|
|
75
|
+
component: "daemon",
|
|
76
|
+
event: "daemon.snapshot_change_observer_error",
|
|
77
|
+
message: "snapshot-change observer threw",
|
|
78
|
+
meta: {
|
|
79
|
+
agent: snapshot.name,
|
|
80
|
+
error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error),
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
writeStatus(agent, text) {
|
|
86
|
+
try {
|
|
87
|
+
this.statusWriterFn(text);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
(0, runtime_1.emitNervesEvent)({
|
|
91
|
+
level: "warn",
|
|
92
|
+
component: "daemon",
|
|
93
|
+
event: "daemon.status_writer_error",
|
|
94
|
+
message: "daemon status writer threw",
|
|
95
|
+
meta: {
|
|
96
|
+
agent,
|
|
97
|
+
error: error instanceof Error ? error.message : String(error),
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
currentTimeMs() {
|
|
103
|
+
const value = this.now();
|
|
104
|
+
return Number.isFinite(value) ? value : Date.now();
|
|
105
|
+
}
|
|
106
|
+
isStartAttemptCurrent(state, attemptId) {
|
|
107
|
+
return state.startAttemptId === attemptId;
|
|
108
|
+
}
|
|
109
|
+
markConfigCheckFailed(state, errorReason, fixHint) {
|
|
110
|
+
const agent = state.config.name;
|
|
111
|
+
state.snapshot.status = "crashed";
|
|
112
|
+
state.snapshot.errorReason = errorReason;
|
|
113
|
+
state.snapshot.fixHint = fixHint;
|
|
114
|
+
(0, runtime_1.emitNervesEvent)({
|
|
115
|
+
level: "error",
|
|
116
|
+
component: "daemon",
|
|
117
|
+
event: "daemon.agent_config_invalid",
|
|
118
|
+
message: errorReason,
|
|
119
|
+
meta: { agent, fix: fixHint },
|
|
120
|
+
});
|
|
121
|
+
this.writeStatus(agent, `[daemon] ${agent}: ${errorReason}\n` +
|
|
122
|
+
(fixHint ? ` Fix: ${fixHint}\n` : ""));
|
|
123
|
+
this.notifySnapshotChange(state.snapshot);
|
|
124
|
+
}
|
|
54
125
|
constructor(options) {
|
|
55
126
|
this.maxRestartsPerHour = options.maxRestartsPerHour ?? 10;
|
|
56
127
|
this.stabilityThresholdMs = options.stabilityThresholdMs ?? 60_000;
|
|
57
128
|
this.initialBackoffMs = options.initialBackoffMs ?? 1_000;
|
|
58
129
|
this.maxBackoffMs = options.maxBackoffMs ?? 60_000;
|
|
130
|
+
this.cooldownRecoveryMs = options.cooldownRecoveryMs ?? 5 * 60 * 1_000;
|
|
131
|
+
this.maxCooldownRetries = options.maxCooldownRetries ?? 3;
|
|
132
|
+
this.startupStaleAfterMs = options.startupStaleAfterMs ?? 45_000;
|
|
59
133
|
this.spawnFn = options.spawn ?? ((command, args, spawnOptions) => (0, child_process_1.spawn)(command, args, spawnOptions));
|
|
60
134
|
this.now = options.now ?? (() => Date.now());
|
|
61
135
|
this.setTimeoutFn = options.setTimeoutFn ?? ((cb, delay) => setTimeout(cb, delay));
|
|
62
136
|
this.clearTimeoutFn = options.clearTimeoutFn ?? ((timer) => clearTimeout(timer));
|
|
137
|
+
this.existsSyncFn = options.existsSync ?? null;
|
|
138
|
+
this.configCheckFn = options.configCheck ?? null;
|
|
139
|
+
this.statusWriterFn = options.statusWriter ?? ((text) => {
|
|
140
|
+
process.stderr.write(text);
|
|
141
|
+
});
|
|
142
|
+
this.onSnapshotChangeFn = options.onSnapshotChange ?? null;
|
|
63
143
|
for (const agent of options.agents) {
|
|
64
144
|
this.agents.set(agent.name, {
|
|
65
145
|
config: agent,
|
|
66
146
|
process: null,
|
|
147
|
+
startInFlight: false,
|
|
148
|
+
startAttemptedAtMs: null,
|
|
149
|
+
startAttemptId: 0,
|
|
67
150
|
restartTimer: null,
|
|
68
151
|
crashTimestamps: [],
|
|
69
152
|
stopRequested: false,
|
|
153
|
+
cooldownTimer: null,
|
|
154
|
+
cooldownRetryCount: 0,
|
|
155
|
+
fastCrashCount: 0,
|
|
70
156
|
snapshot: {
|
|
71
157
|
name: agent.name,
|
|
72
158
|
channel: agent.channel,
|
|
@@ -76,6 +162,10 @@ class DaemonProcessManager {
|
|
|
76
162
|
startedAt: null,
|
|
77
163
|
lastCrashAt: null,
|
|
78
164
|
backoffMs: this.initialBackoffMs,
|
|
165
|
+
lastExitCode: null,
|
|
166
|
+
lastSignal: null,
|
|
167
|
+
errorReason: null,
|
|
168
|
+
fixHint: null,
|
|
79
169
|
},
|
|
80
170
|
});
|
|
81
171
|
}
|
|
@@ -87,42 +177,203 @@ class DaemonProcessManager {
|
|
|
87
177
|
}
|
|
88
178
|
}
|
|
89
179
|
}
|
|
180
|
+
triggerAutoStartAgents() {
|
|
181
|
+
for (const state of this.agents.values()) {
|
|
182
|
+
if (!state.config.autoStart)
|
|
183
|
+
continue;
|
|
184
|
+
void this.startAgent(state.config.name).catch((error) => {
|
|
185
|
+
const errorReason = error instanceof Error ? error.message : String(error);
|
|
186
|
+
this.markConfigCheckFailed(state, `agent startup threw before the worker could run: ${errorReason}`, "Run 'ouro doctor' for diagnostics, then retry 'ouro up'.");
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
90
190
|
async startAgent(agent) {
|
|
91
191
|
const state = this.requireAgent(agent);
|
|
92
|
-
if (state.process)
|
|
192
|
+
if (state.process || state.startInFlight)
|
|
93
193
|
return;
|
|
94
|
-
|
|
95
|
-
state.
|
|
96
|
-
state.
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
194
|
+
const attemptId = state.startAttemptId + 1;
|
|
195
|
+
state.startAttemptId = attemptId;
|
|
196
|
+
state.startInFlight = true;
|
|
197
|
+
state.startAttemptedAtMs = this.currentTimeMs();
|
|
198
|
+
try {
|
|
199
|
+
this.clearRestartTimer(state);
|
|
200
|
+
state.stopRequested = false;
|
|
201
|
+
state.snapshot.status = "starting";
|
|
202
|
+
if (this.configCheckFn) {
|
|
203
|
+
let result;
|
|
204
|
+
try {
|
|
205
|
+
result = await this.configCheckFn(agent);
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
const errorReason = error instanceof Error ? error.message : String(error);
|
|
209
|
+
this.markConfigCheckFailed(state, `agent config validation threw: ${errorReason}`, "Run 'ouro doctor' for diagnostics, then retry 'ouro up'.");
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (!this.isStartAttemptCurrent(state, attemptId))
|
|
213
|
+
return;
|
|
214
|
+
if (state.stopRequested) {
|
|
215
|
+
state.snapshot.status = "stopped";
|
|
216
|
+
state.snapshot.pid = null;
|
|
217
|
+
this.notifySnapshotChange(state.snapshot);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (result.skip) {
|
|
221
|
+
state.snapshot.status = "stopped";
|
|
222
|
+
state.snapshot.errorReason = null;
|
|
223
|
+
state.snapshot.fixHint = null;
|
|
224
|
+
(0, runtime_1.emitNervesEvent)({
|
|
225
|
+
component: "daemon",
|
|
226
|
+
event: "daemon.agent_config_skipped",
|
|
227
|
+
message: result.error ?? "agent start skipped by config check",
|
|
228
|
+
meta: { agent, fix: result.fix ?? null },
|
|
229
|
+
});
|
|
230
|
+
this.notifySnapshotChange(state.snapshot);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (!result.ok) {
|
|
234
|
+
this.markConfigCheckFailed(state, result.error ?? "agent config validation failed", result.fix ?? null);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
/* v8 ignore next -- defensive duplicate stale-start guard before spawn preparation @preserve */
|
|
238
|
+
if (!this.isStartAttemptCurrent(state, attemptId))
|
|
239
|
+
return;
|
|
240
|
+
// Config check passed — clear any prior error so the pulse stops
|
|
241
|
+
// reporting the broken state. This is the recovery path: the user
|
|
242
|
+
// fixed their secrets/config, the next startAgent attempt sees a
|
|
243
|
+
// valid config, and the pulse goes quiet.
|
|
244
|
+
state.snapshot.errorReason = null;
|
|
245
|
+
state.snapshot.fixHint = null;
|
|
246
|
+
}
|
|
247
|
+
const runCwd = (0, identity_1.getRepoRoot)();
|
|
248
|
+
const entryScript = path.join((0, identity_1.getRepoRoot)(), "dist", state.config.entry);
|
|
249
|
+
if (this.existsSyncFn && !this.existsSyncFn(entryScript)) {
|
|
250
|
+
state.snapshot.status = "crashed";
|
|
251
|
+
(0, runtime_1.emitNervesEvent)({
|
|
252
|
+
level: "error",
|
|
253
|
+
component: "daemon",
|
|
254
|
+
event: "daemon.agent_entry_missing",
|
|
255
|
+
message: "agent entry script does not exist — cannot spawn. Run 'ouro daemon install' from the correct location.",
|
|
256
|
+
meta: { agent, entryScript },
|
|
257
|
+
});
|
|
258
|
+
this.notifySnapshotChange(state.snapshot);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
/* v8 ignore next -- defensive duplicate stale-start guard immediately before spawn @preserve */
|
|
262
|
+
if (!this.isStartAttemptCurrent(state, attemptId))
|
|
263
|
+
return;
|
|
264
|
+
if (state.stopRequested) {
|
|
265
|
+
state.snapshot.status = "stopped";
|
|
266
|
+
state.snapshot.pid = null;
|
|
267
|
+
this.notifySnapshotChange(state.snapshot);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const args = [entryScript, "--agent", state.config.agentArg ?? agent, ...(state.config.args ?? [])];
|
|
271
|
+
const child = this.spawnFn("node", args, {
|
|
272
|
+
cwd: runCwd,
|
|
273
|
+
env: state.config.env ? { ...process.env, ...state.config.env } : process.env,
|
|
274
|
+
stdio: ["ignore", "ignore", "ignore", "ipc"],
|
|
275
|
+
});
|
|
276
|
+
/* v8 ignore next 7 -- defensive: spawn should always return a ChildProcess @preserve */
|
|
277
|
+
if (!child) {
|
|
278
|
+
state.snapshot.status = "crashed";
|
|
279
|
+
(0, runtime_1.emitNervesEvent)({ level: "error", component: "daemon", event: "daemon.agent_spawn_failed", message: "spawn returned null", meta: { agent } });
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (!this.isStartAttemptCurrent(state, attemptId)) {
|
|
283
|
+
try {
|
|
284
|
+
child.kill("SIGTERM");
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
(0, runtime_1.emitNervesEvent)({
|
|
288
|
+
level: "warn",
|
|
289
|
+
component: "daemon",
|
|
290
|
+
event: "daemon.agent_stop_error",
|
|
291
|
+
message: "failed to stop stale managed agent startup",
|
|
292
|
+
meta: { agent },
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
state.process = child;
|
|
298
|
+
state.snapshot.status = "running";
|
|
299
|
+
state.snapshot.pid = child.pid ?? null;
|
|
300
|
+
state.snapshot.startedAt = new Date(this.currentTimeMs()).toISOString();
|
|
301
|
+
const bootstrap = state.config.getRuntimeCredentialBootstrap?.() ?? null;
|
|
302
|
+
if (bootstrap) {
|
|
303
|
+
const message = {
|
|
304
|
+
type: "ouro.runtimeCredentialBootstrap",
|
|
305
|
+
agentName: bootstrap.agentName,
|
|
306
|
+
};
|
|
307
|
+
if (bootstrap.runtimeConfig)
|
|
308
|
+
message.runtimeConfig = bootstrap.runtimeConfig;
|
|
309
|
+
if (bootstrap.machineRuntimeConfig)
|
|
310
|
+
message.machineRuntimeConfig = bootstrap.machineRuntimeConfig;
|
|
311
|
+
if (bootstrap.machineId)
|
|
312
|
+
message.machineId = bootstrap.machineId;
|
|
313
|
+
if (bootstrap.providerCredentialRecords)
|
|
314
|
+
message.providerCredentialRecords = bootstrap.providerCredentialRecords;
|
|
315
|
+
try {
|
|
316
|
+
child.send?.(message);
|
|
317
|
+
(0, runtime_1.emitNervesEvent)({
|
|
318
|
+
component: "daemon",
|
|
319
|
+
event: "daemon.agent_runtime_credentials_bootstrap_sent",
|
|
320
|
+
message: "sent runtime credential bootstrap to managed agent process",
|
|
321
|
+
meta: {
|
|
322
|
+
agent,
|
|
323
|
+
runtimeConfig: !!bootstrap.runtimeConfig,
|
|
324
|
+
machineRuntimeConfig: !!bootstrap.machineRuntimeConfig,
|
|
325
|
+
providerCredentialRecords: bootstrap.providerCredentialRecords?.length ?? 0,
|
|
326
|
+
},
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
(0, runtime_1.emitNervesEvent)({
|
|
331
|
+
level: "warn",
|
|
332
|
+
component: "daemon",
|
|
333
|
+
event: "daemon.agent_ipc_send_error",
|
|
334
|
+
message: "failed to send runtime credential bootstrap to managed agent process",
|
|
335
|
+
meta: { agent, error: error instanceof Error ? error.message : String(error) },
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
(0, runtime_1.emitNervesEvent)({
|
|
340
|
+
component: "daemon",
|
|
341
|
+
event: "daemon.agent_started",
|
|
342
|
+
message: "daemon started managed agent process",
|
|
343
|
+
meta: { agent, pid: child.pid ?? null, cwd: runCwd },
|
|
344
|
+
});
|
|
345
|
+
this.notifySnapshotChange(state.snapshot);
|
|
346
|
+
/* v8 ignore start — child process error handler; requires real spawn to trigger */
|
|
347
|
+
child.on("error", (err) => {
|
|
348
|
+
(0, runtime_1.emitNervesEvent)({
|
|
349
|
+
level: "warn",
|
|
350
|
+
component: "daemon",
|
|
351
|
+
event: "daemon.agent_process_error",
|
|
352
|
+
message: "managed agent process emitted error",
|
|
353
|
+
meta: { agent, error: err.message },
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
/* v8 ignore stop */
|
|
357
|
+
child.once("exit", (code, signal) => {
|
|
358
|
+
this.onExit(state, code, signal);
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
finally {
|
|
362
|
+
if (this.isStartAttemptCurrent(state, attemptId)) {
|
|
363
|
+
state.startInFlight = false;
|
|
364
|
+
state.startAttemptedAtMs = null;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
118
367
|
}
|
|
119
368
|
async stopAgent(agent) {
|
|
120
369
|
const state = this.requireAgent(agent);
|
|
121
370
|
this.clearRestartTimer(state);
|
|
371
|
+
this.clearCooldownTimer(state);
|
|
122
372
|
state.stopRequested = true;
|
|
123
373
|
if (!state.process) {
|
|
124
374
|
state.snapshot.status = "stopped";
|
|
125
375
|
state.snapshot.pid = null;
|
|
376
|
+
this.notifySnapshotChange(state.snapshot);
|
|
126
377
|
return;
|
|
127
378
|
}
|
|
128
379
|
const child = state.process;
|
|
@@ -141,8 +392,34 @@ class DaemonProcessManager {
|
|
|
141
392
|
meta: { agent },
|
|
142
393
|
});
|
|
143
394
|
}
|
|
395
|
+
this.notifySnapshotChange(state.snapshot);
|
|
144
396
|
}
|
|
145
397
|
async restartAgent(agent) {
|
|
398
|
+
const state = this.requireAgent(agent);
|
|
399
|
+
if (state.startInFlight && !state.process) {
|
|
400
|
+
const startedAt = state.startAttemptedAtMs;
|
|
401
|
+
/* v8 ignore next -- defensive: startInFlight always records a start timestamp @preserve */
|
|
402
|
+
const elapsedMs = startedAt === null ? 0 : this.currentTimeMs() - startedAt;
|
|
403
|
+
if (elapsedMs < this.startupStaleAfterMs) {
|
|
404
|
+
(0, runtime_1.emitNervesEvent)({
|
|
405
|
+
component: "daemon",
|
|
406
|
+
event: "daemon.agent_restart_deferred",
|
|
407
|
+
message: "managed agent restart skipped while startup is already in flight",
|
|
408
|
+
meta: { agent, elapsedMs, startupStaleAfterMs: this.startupStaleAfterMs },
|
|
409
|
+
});
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
(0, runtime_1.emitNervesEvent)({
|
|
413
|
+
level: "warn",
|
|
414
|
+
component: "daemon",
|
|
415
|
+
event: "daemon.agent_startup_stale_recovered",
|
|
416
|
+
message: "managed agent startup was stale; clearing the pending attempt before restart",
|
|
417
|
+
meta: { agent, elapsedMs, startupStaleAfterMs: this.startupStaleAfterMs },
|
|
418
|
+
});
|
|
419
|
+
state.startAttemptId += 1;
|
|
420
|
+
state.startInFlight = false;
|
|
421
|
+
state.startAttemptedAtMs = null;
|
|
422
|
+
}
|
|
146
423
|
await this.stopAgent(agent);
|
|
147
424
|
await this.startAgent(agent);
|
|
148
425
|
}
|
|
@@ -178,9 +455,13 @@ class DaemonProcessManager {
|
|
|
178
455
|
if (!state.process)
|
|
179
456
|
return;
|
|
180
457
|
state.process = null;
|
|
458
|
+
state.startInFlight = false;
|
|
459
|
+
state.startAttemptedAtMs = null;
|
|
181
460
|
state.snapshot.pid = null;
|
|
461
|
+
state.snapshot.lastExitCode = code;
|
|
462
|
+
state.snapshot.lastSignal = signal;
|
|
182
463
|
const crashed = !state.stopRequested && code !== 0;
|
|
183
|
-
const now = this.
|
|
464
|
+
const now = this.currentTimeMs();
|
|
184
465
|
const startedAt = state.snapshot.startedAt ? Date.parse(state.snapshot.startedAt) : now;
|
|
185
466
|
const runDuration = Math.max(0, now - startedAt);
|
|
186
467
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -195,13 +476,45 @@ class DaemonProcessManager {
|
|
|
195
476
|
if (runDuration >= this.stabilityThresholdMs) {
|
|
196
477
|
state.snapshot.backoffMs = this.initialBackoffMs;
|
|
197
478
|
}
|
|
479
|
+
this.notifySnapshotChange(state.snapshot);
|
|
198
480
|
return;
|
|
199
481
|
}
|
|
200
482
|
state.snapshot.lastCrashAt = new Date(now).toISOString();
|
|
483
|
+
// Fast-crash detection: if the agent dies within 5 seconds of starting, it's likely
|
|
484
|
+
// a configuration issue (missing credentials, bad provider, etc.) not a transient failure.
|
|
485
|
+
// After 3 consecutive fast crashes, stop retrying and mark as config-failed.
|
|
486
|
+
const FAST_CRASH_THRESHOLD_MS = 5000;
|
|
487
|
+
const FAST_CRASH_MAX = 3;
|
|
488
|
+
if (runDuration < FAST_CRASH_THRESHOLD_MS) {
|
|
489
|
+
state.fastCrashCount = state.fastCrashCount + 1;
|
|
490
|
+
if (state.fastCrashCount >= FAST_CRASH_MAX) {
|
|
491
|
+
state.snapshot.status = "crashed";
|
|
492
|
+
// Capture the fast-crash diagnosis on the snapshot so it surfaces
|
|
493
|
+
// via the pulse. The error message is prescriptive: it tells the
|
|
494
|
+
// user (and their sibling agents) exactly what to do.
|
|
495
|
+
state.snapshot.errorReason = `agent crashed ${FAST_CRASH_MAX} times within ${FAST_CRASH_THRESHOLD_MS}ms of startup — likely a configuration issue (missing credentials, bad provider).`;
|
|
496
|
+
state.snapshot.fixHint = `Fix the config and run \`ouro up\` to restart, or check daemon logs for the underlying error.`;
|
|
497
|
+
(0, runtime_1.emitNervesEvent)({
|
|
498
|
+
level: "error",
|
|
499
|
+
component: "daemon",
|
|
500
|
+
event: "daemon.agent_config_failure",
|
|
501
|
+
message: `agent crashed ${FAST_CRASH_MAX} times within ${FAST_CRASH_THRESHOLD_MS}ms of startup — likely a configuration issue (missing credentials, bad provider). Not retrying. Fix the config and run \`ouro up\` to restart.`,
|
|
502
|
+
meta: { agent: state.config.name, fastCrashCount: state.fastCrashCount, avgRunDurationMs: runDuration },
|
|
503
|
+
});
|
|
504
|
+
this.notifySnapshotChange(state.snapshot);
|
|
505
|
+
return; // Don't schedule cooldown recovery — this needs human/agent intervention
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
// Reset fast-crash counter on a stable run
|
|
510
|
+
state.fastCrashCount = 0;
|
|
511
|
+
}
|
|
201
512
|
state.crashTimestamps = state.crashTimestamps.filter((crashTs) => crashTs >= startOfHour(now));
|
|
202
513
|
state.crashTimestamps.push(now);
|
|
203
514
|
if (state.crashTimestamps.length > this.maxRestartsPerHour) {
|
|
204
515
|
state.snapshot.status = "crashed";
|
|
516
|
+
state.snapshot.errorReason = `agent exceeded restart limit (${this.maxRestartsPerHour}/hr) — entering cooldown`;
|
|
517
|
+
state.snapshot.fixHint = "investigate why the agent keeps crashing; cooldown will retry shortly";
|
|
205
518
|
(0, runtime_1.emitNervesEvent)({
|
|
206
519
|
level: "error",
|
|
207
520
|
component: "daemon",
|
|
@@ -209,6 +522,8 @@ class DaemonProcessManager {
|
|
|
209
522
|
message: "managed agent exceeded restart limit and is marked crashed",
|
|
210
523
|
meta: { agent: state.config.name, maxRestartsPerHour: this.maxRestartsPerHour },
|
|
211
524
|
});
|
|
525
|
+
this.notifySnapshotChange(state.snapshot);
|
|
526
|
+
this.scheduleCooldownRecovery(state);
|
|
212
527
|
return;
|
|
213
528
|
}
|
|
214
529
|
state.snapshot.status = "starting";
|
|
@@ -219,6 +534,7 @@ class DaemonProcessManager {
|
|
|
219
534
|
state.restartTimer = this.setTimeoutFn(() => {
|
|
220
535
|
void this.startAgent(state.config.name);
|
|
221
536
|
}, waitMs);
|
|
537
|
+
this.notifySnapshotChange(state.snapshot);
|
|
222
538
|
}
|
|
223
539
|
clearRestartTimer(state) {
|
|
224
540
|
if (state.restartTimer === null)
|
|
@@ -226,6 +542,45 @@ class DaemonProcessManager {
|
|
|
226
542
|
this.clearTimeoutFn(state.restartTimer);
|
|
227
543
|
state.restartTimer = null;
|
|
228
544
|
}
|
|
545
|
+
scheduleCooldownRecovery(state) {
|
|
546
|
+
if (state.cooldownRetryCount >= this.maxCooldownRetries) {
|
|
547
|
+
(0, runtime_1.emitNervesEvent)({
|
|
548
|
+
level: "error",
|
|
549
|
+
component: "daemon",
|
|
550
|
+
event: "daemon.agent_permanent_failure",
|
|
551
|
+
message: "managed agent exhausted all cooldown retries — permanently stopped",
|
|
552
|
+
meta: { agent: state.config.name, cooldownRetryCount: state.cooldownRetryCount, maxCooldownRetries: this.maxCooldownRetries },
|
|
553
|
+
});
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
this.clearCooldownTimer(state);
|
|
557
|
+
state.cooldownTimer = this.setTimeoutFn(() => {
|
|
558
|
+
state.cooldownRetryCount += 1;
|
|
559
|
+
state.crashTimestamps = [];
|
|
560
|
+
state.snapshot.backoffMs = this.initialBackoffMs;
|
|
561
|
+
state.snapshot.status = "starting";
|
|
562
|
+
state.snapshot.restartCount += 1;
|
|
563
|
+
(0, runtime_1.emitNervesEvent)({
|
|
564
|
+
component: "daemon",
|
|
565
|
+
event: "daemon.agent_cooldown_recovery",
|
|
566
|
+
message: "attempting cooldown recovery for managed agent",
|
|
567
|
+
meta: { agent: state.config.name, cooldownRetryCount: state.cooldownRetryCount },
|
|
568
|
+
});
|
|
569
|
+
void this.startAgent(state.config.name);
|
|
570
|
+
}, this.cooldownRecoveryMs);
|
|
571
|
+
(0, runtime_1.emitNervesEvent)({
|
|
572
|
+
component: "daemon",
|
|
573
|
+
event: "daemon.agent_cooldown_scheduled",
|
|
574
|
+
message: `scheduled cooldown recovery in ${this.cooldownRecoveryMs}ms`,
|
|
575
|
+
meta: { agent: state.config.name, cooldownRecoveryMs: this.cooldownRecoveryMs, cooldownRetryCount: state.cooldownRetryCount },
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
clearCooldownTimer(state) {
|
|
579
|
+
if (state.cooldownTimer === null)
|
|
580
|
+
return;
|
|
581
|
+
this.clearTimeoutFn(state.cooldownTimer);
|
|
582
|
+
state.cooldownTimer = null;
|
|
583
|
+
}
|
|
229
584
|
requireAgent(agent) {
|
|
230
585
|
const state = this.agents.get(agent);
|
|
231
586
|
if (!state) {
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared provider discovery for repair.
|
|
4
|
+
*
|
|
5
|
+
* Runtime repair only trusts the agent vault. First-run conveniences may still
|
|
6
|
+
* inspect env vars before credentials are stored, but once an agent exists the
|
|
7
|
+
* vault is the source of truth.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.scanEnvVarCredentials = scanEnvVarCredentials;
|
|
11
|
+
exports.discoverInstalledAgentCredentials = discoverInstalledAgentCredentials;
|
|
12
|
+
exports.describeDiscoveredCredentialSource = describeDiscoveredCredentialSource;
|
|
13
|
+
exports.discoverWorkingProvider = discoverWorkingProvider;
|
|
14
|
+
const identity_1 = require("../identity");
|
|
15
|
+
const provider_credentials_1 = require("../provider-credentials");
|
|
16
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
17
|
+
/**
|
|
18
|
+
* Scan environment variables for API keys during first-run bootstrap.
|
|
19
|
+
* This does not participate in runtime provider repair.
|
|
20
|
+
*/
|
|
21
|
+
function scanEnvVarCredentials(env) {
|
|
22
|
+
const results = [];
|
|
23
|
+
for (const [provider, desc] of Object.entries(identity_1.PROVIDER_CREDENTIALS)) {
|
|
24
|
+
const cred = {};
|
|
25
|
+
for (const [envVar, credKey] of Object.entries(desc.envVars)) {
|
|
26
|
+
const value = env[envVar];
|
|
27
|
+
if (value) {
|
|
28
|
+
cred[credKey] = value;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const hasRequired = desc.required.some((key) => !!cred[key]);
|
|
32
|
+
if (hasRequired) {
|
|
33
|
+
results.push({
|
|
34
|
+
provider,
|
|
35
|
+
agentName: "env",
|
|
36
|
+
credentials: cred,
|
|
37
|
+
providerConfig: { ...cred },
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return results;
|
|
42
|
+
}
|
|
43
|
+
function stringifyProviderFields(fields) {
|
|
44
|
+
const result = {};
|
|
45
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
46
|
+
result[key] = String(value);
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
function discoveredFromVaultRecord(record, agentName = "vault") {
|
|
51
|
+
return {
|
|
52
|
+
provider: record.provider,
|
|
53
|
+
agentName,
|
|
54
|
+
credentials: stringifyProviderFields(record.credentials),
|
|
55
|
+
providerConfig: stringifyProviderFields(record.config),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
async function discoverInstalledAgentCredentials(agentNames) {
|
|
59
|
+
const discovered = [];
|
|
60
|
+
for (const agentName of agentNames) {
|
|
61
|
+
if (agentName === "SerpentGuide")
|
|
62
|
+
continue;
|
|
63
|
+
const poolResult = await (0, provider_credentials_1.refreshProviderCredentialPool)(agentName, { preserveCachedOnFailure: true });
|
|
64
|
+
if (!poolResult.ok)
|
|
65
|
+
continue;
|
|
66
|
+
for (const record of Object.values(poolResult.pool.providers)) {
|
|
67
|
+
if (!record)
|
|
68
|
+
continue;
|
|
69
|
+
discovered.push(discoveredFromVaultRecord(record, agentName));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return discovered;
|
|
73
|
+
}
|
|
74
|
+
function describeDiscoveredCredentialSource(credential, envVar) {
|
|
75
|
+
if (credential.agentName === "env") {
|
|
76
|
+
return envVar ? `from env: $${envVar}` : "from env";
|
|
77
|
+
}
|
|
78
|
+
return `from ${credential.agentName}'s vault`;
|
|
79
|
+
}
|
|
80
|
+
async function discoverWorkingProvider(deps) {
|
|
81
|
+
const poolResult = await (0, provider_credentials_1.refreshProviderCredentialPool)(deps.agentName);
|
|
82
|
+
if (!poolResult.ok) {
|
|
83
|
+
(0, runtime_1.emitNervesEvent)({
|
|
84
|
+
level: "warn",
|
|
85
|
+
component: "daemon",
|
|
86
|
+
event: "daemon.provider_discovery_none",
|
|
87
|
+
message: "provider discovery could not read agent vault",
|
|
88
|
+
meta: { agentName: deps.agentName, reason: poolResult.reason },
|
|
89
|
+
});
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
const candidates = Object.entries(poolResult.pool.providers)
|
|
93
|
+
.map(([, record]) => discoveredFromVaultRecord(record));
|
|
94
|
+
if (candidates.length === 0) {
|
|
95
|
+
(0, runtime_1.emitNervesEvent)({
|
|
96
|
+
level: "info",
|
|
97
|
+
component: "daemon",
|
|
98
|
+
event: "daemon.provider_discovery_none",
|
|
99
|
+
message: "no provider credentials found in agent vault",
|
|
100
|
+
meta: { agentName: deps.agentName },
|
|
101
|
+
});
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
for (const candidate of candidates) {
|
|
105
|
+
const config = { ...candidate.providerConfig, ...candidate.credentials };
|
|
106
|
+
(0, runtime_1.emitNervesEvent)({
|
|
107
|
+
level: "info",
|
|
108
|
+
component: "daemon",
|
|
109
|
+
event: "daemon.provider_discovery_ping",
|
|
110
|
+
message: `pinging provider: ${candidate.provider}`,
|
|
111
|
+
meta: { agentName: deps.agentName, provider: candidate.provider, source: candidate.agentName },
|
|
112
|
+
});
|
|
113
|
+
const result = await deps.pingProvider(candidate.provider, config);
|
|
114
|
+
if (result.ok) {
|
|
115
|
+
(0, runtime_1.emitNervesEvent)({
|
|
116
|
+
level: "info",
|
|
117
|
+
component: "daemon",
|
|
118
|
+
event: "daemon.provider_discovery_ok",
|
|
119
|
+
message: `provider discovery succeeded: ${candidate.provider}`,
|
|
120
|
+
meta: { agentName: deps.agentName, provider: candidate.provider, source: candidate.agentName },
|
|
121
|
+
});
|
|
122
|
+
return {
|
|
123
|
+
provider: candidate.provider,
|
|
124
|
+
credentials: candidate.credentials,
|
|
125
|
+
providerConfig: candidate.providerConfig,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
(0, runtime_1.emitNervesEvent)({
|
|
130
|
+
level: "warn",
|
|
131
|
+
component: "daemon",
|
|
132
|
+
event: "daemon.provider_discovery_all_failed",
|
|
133
|
+
message: "all vault provider candidates failed ping",
|
|
134
|
+
meta: { agentName: deps.agentName, candidateCount: candidates.length },
|
|
135
|
+
});
|
|
136
|
+
return null;
|
|
137
|
+
}
|