@ouro.bot/cli 0.1.0-alpha.49 → 0.1.0-alpha.491
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 +3 -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 +3126 -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 +989 -0
- 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 +426 -0
- package/dist/heart/background-operations.js +281 -0
- package/dist/heart/bridges/manager.js +37 -0
- package/dist/heart/bridges/state-machine.js +20 -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 +845 -229
- package/dist/heart/cross-chat-delivery.js +131 -0
- package/dist/heart/daemon/agent-config-check.js +490 -0
- package/dist/heart/daemon/agent-discovery.js +79 -3
- package/dist/heart/daemon/agent-service.js +360 -0
- package/dist/heart/daemon/agentic-repair.js +216 -0
- package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
- package/dist/heart/daemon/cadence.js +70 -0
- package/dist/heart/daemon/cli-defaults.js +640 -0
- package/dist/heart/daemon/cli-exec.js +7239 -0
- package/dist/heart/daemon/cli-help.js +493 -0
- package/dist/heart/daemon/cli-parse.js +1533 -0
- package/dist/heart/daemon/cli-render-doctor.js +57 -0
- package/dist/heart/daemon/cli-render.js +561 -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 -1616
- package/dist/heart/daemon/daemon-entry.js +345 -3
- package/dist/heart/daemon/daemon-health.js +141 -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 +677 -58
- package/dist/heart/daemon/dns-workflow.js +394 -0
- package/dist/heart/daemon/doctor-types.js +8 -0
- package/dist/heart/daemon/doctor.js +486 -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 +89 -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 +214 -0
- 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 +178 -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 +264 -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 +62 -0
- 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 +3 -3
- 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 +201 -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 +59 -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 +644 -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/progress-story.js +42 -0
- package/dist/heart/provider-attempt.js +134 -0
- package/dist/heart/provider-binding-resolver.js +255 -0
- package/dist/heart/provider-credentials.js +424 -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 +193 -55
- package/dist/heart/providers/azure.js +104 -13
- package/dist/heart/providers/error-classification.js +63 -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 +29 -7
- package/dist/heart/providers/openai-codex.js +63 -39
- package/dist/heart/runtime-capability-check.js +170 -0
- package/dist/heart/runtime-credentials.js +260 -0
- package/dist/heart/sense-truth.js +11 -4
- package/dist/heart/session-activity.js +190 -0
- package/dist/heart/session-events.js +981 -0
- package/dist/heart/session-transcript.js +167 -0
- package/dist/heart/start-of-turn-packet.js +345 -0
- package/dist/heart/streaming.js +48 -28
- package/dist/heart/sync.js +332 -0
- package/dist/heart/target-resolution.js +127 -0
- package/dist/heart/tempo.js +93 -0
- package/dist/heart/temporal-view.js +41 -0
- package/dist/heart/tool-activity-callbacks.js +36 -0
- package/dist/heart/tool-description.js +135 -0
- package/dist/heart/tool-friction.js +55 -0
- package/dist/heart/tool-loop.js +200 -0
- package/dist/heart/turn-context.js +372 -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 +600 -0
- package/dist/mailroom/core.js +658 -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 +219 -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/travel-extract.js +89 -0
- package/dist/mind/bundle-manifest.js +7 -1
- package/dist/mind/context.js +164 -93
- 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/group-context.js +144 -0
- package/dist/mind/friends/resolver.js +54 -2
- package/dist/mind/friends/store-file.js +39 -3
- package/dist/mind/friends/trust-explanation.js +74 -0
- 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 +56 -8
- package/dist/mind/prompt-refresh.js +3 -2
- package/dist/mind/prompt.js +973 -168
- 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 +93 -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/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 +774 -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 +46 -842
- package/dist/repertoire/tools-bluebubbles.js +1 -0
- package/dist/repertoire/tools-bridge.js +141 -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 +105 -0
- package/dist/repertoire/tools-github.js +1 -7
- package/dist/repertoire/tools-mail.js +1281 -0
- package/dist/repertoire/tools-notes.js +376 -0
- package/dist/repertoire/tools-session.js +749 -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 +356 -0
- package/dist/repertoire/tools-user-profile.js +144 -0
- package/dist/repertoire/tools-vault.js +40 -0
- package/dist/repertoire/tools.js +144 -115
- 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 +73 -0
- package/dist/senses/{bluebubbles-inbound-log.js → bluebubbles/inbound-log.js} +20 -3
- package/dist/senses/bluebubbles/index.js +1835 -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 +605 -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 +83 -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 +515 -211
- package/dist/senses/commands.js +66 -3
- package/dist/senses/habit-turn-message.js +108 -0
- package/dist/senses/inner-dialog-worker.js +110 -20
- package/dist/senses/inner-dialog.js +408 -21
- package/dist/senses/mail-entry.js +66 -0
- package/dist/senses/mail.js +379 -0
- package/dist/senses/pipeline.js +588 -81
- 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 +412 -163
- 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 -1032
- package/dist/senses/debug-activity.js +0 -127
- 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,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pruneDaemonLogs = pruneDaemonLogs;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const crypto_1 = require("crypto");
|
|
7
|
+
const nerves_1 = require("../../nerves");
|
|
8
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
9
|
+
const identity_1 = require("../identity");
|
|
10
|
+
function isActiveLogStream(name) {
|
|
11
|
+
if (name.endsWith(".ndjson")) {
|
|
12
|
+
return !/\.\d+\.ndjson$/.test(name);
|
|
13
|
+
}
|
|
14
|
+
if (name.endsWith(".log")) {
|
|
15
|
+
return !/\.\d+\.log$/.test(name);
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
function pruneDaemonLogs(options = {}) {
|
|
20
|
+
/* v8 ignore next -- defensive: tests always pass logsDir to avoid prod paths @preserve */
|
|
21
|
+
const logsDir = options.logsDir ?? (0, identity_1.getAgentDaemonLogsDir)(options.agentName);
|
|
22
|
+
const maxSizeBytes = options.maxSizeBytes ?? nerves_1.DEFAULT_MAX_LOG_SIZE_BYTES;
|
|
23
|
+
const maxGenerations = options.maxGenerations ?? nerves_1.DEFAULT_MAX_GENERATIONS;
|
|
24
|
+
const traceId = (0, crypto_1.randomUUID)();
|
|
25
|
+
(0, runtime_1.emitNervesEvent)({
|
|
26
|
+
component: "nerves",
|
|
27
|
+
event: "nerves.logs_prune_start",
|
|
28
|
+
trace_id: traceId,
|
|
29
|
+
message: "pruning daemon logs",
|
|
30
|
+
meta: { logsDir, maxSizeBytes, maxGenerations },
|
|
31
|
+
});
|
|
32
|
+
let completed = false;
|
|
33
|
+
try {
|
|
34
|
+
if (!(0, fs_1.existsSync)(logsDir)) {
|
|
35
|
+
completed = true;
|
|
36
|
+
(0, runtime_1.emitNervesEvent)({
|
|
37
|
+
component: "nerves",
|
|
38
|
+
event: "nerves.logs_prune_end",
|
|
39
|
+
trace_id: traceId,
|
|
40
|
+
message: "daemon logs dir does not exist",
|
|
41
|
+
meta: { logsDir, filesCompacted: 0, bytesFreed: 0 },
|
|
42
|
+
});
|
|
43
|
+
return { filesCompacted: 0, bytesFreed: 0 };
|
|
44
|
+
}
|
|
45
|
+
let filesCompacted = 0;
|
|
46
|
+
let bytesFreed = 0;
|
|
47
|
+
// Enumerate and rotate each active structured stream plus legacy launchd
|
|
48
|
+
// .log streams. We explicitly skip .gz and other files — only the active
|
|
49
|
+
// stream can be a rotation candidate. Legacy generation files are handled
|
|
50
|
+
// inside rotateIfNeeded's generation-shift step, so we skip them here to
|
|
51
|
+
// avoid double-rotating.
|
|
52
|
+
for (const name of (0, fs_1.readdirSync)(logsDir)) {
|
|
53
|
+
if (!isActiveLogStream(name))
|
|
54
|
+
continue;
|
|
55
|
+
const filePath = (0, path_1.join)(logsDir, name);
|
|
56
|
+
let sizeBefore;
|
|
57
|
+
try {
|
|
58
|
+
sizeBefore = (0, fs_1.statSync)(filePath).size;
|
|
59
|
+
/* v8 ignore start -- defensive: file disappears between readdir and stat @preserve */
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
/* v8 ignore stop */
|
|
65
|
+
if (sizeBefore < maxSizeBytes)
|
|
66
|
+
continue;
|
|
67
|
+
// rotateIfNeeded returns true because we pre-checked sizeBefore >=
|
|
68
|
+
// maxSizeBytes above. The false branch of `if (rotated)` is defensive
|
|
69
|
+
// and unreachable under normal flow; we skip it from coverage so a
|
|
70
|
+
// future refactor that weakens the pre-check still reports correct
|
|
71
|
+
// counts without needing a contrived test.
|
|
72
|
+
const rotated = (0, nerves_1.rotateIfNeeded)(filePath, {
|
|
73
|
+
maxSizeBytes,
|
|
74
|
+
maxGenerations,
|
|
75
|
+
compress: true,
|
|
76
|
+
});
|
|
77
|
+
/* v8 ignore next 3 -- defensive: pre-check guarantees rotated=true @preserve */
|
|
78
|
+
if (!rotated) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
filesCompacted += 1;
|
|
82
|
+
bytesFreed += sizeBefore;
|
|
83
|
+
}
|
|
84
|
+
completed = true;
|
|
85
|
+
(0, runtime_1.emitNervesEvent)({
|
|
86
|
+
component: "nerves",
|
|
87
|
+
event: "nerves.logs_prune_end",
|
|
88
|
+
trace_id: traceId,
|
|
89
|
+
message: "daemon logs pruned",
|
|
90
|
+
meta: { logsDir, filesCompacted, bytesFreed },
|
|
91
|
+
});
|
|
92
|
+
return { filesCompacted, bytesFreed };
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
/* v8 ignore next -- defensive: completed=true only reached after try returns @preserve */
|
|
96
|
+
if (!completed) {
|
|
97
|
+
/* v8 ignore next -- defensive: rotation always throws real Errors @preserve */
|
|
98
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
99
|
+
(0, runtime_1.emitNervesEvent)({
|
|
100
|
+
component: "nerves",
|
|
101
|
+
event: "nerves.logs_prune_error",
|
|
102
|
+
trace_id: traceId,
|
|
103
|
+
level: "error",
|
|
104
|
+
message: "daemon logs prune failed",
|
|
105
|
+
meta: { logsDir, error: reason },
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
throw err;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -35,9 +35,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.FileMessageRouter = void 0;
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
|
-
const os = __importStar(require("os"));
|
|
39
38
|
const path = __importStar(require("path"));
|
|
40
39
|
const runtime_1 = require("../../nerves/runtime");
|
|
40
|
+
const identity_1 = require("../identity");
|
|
41
41
|
function messageId(nowIso) {
|
|
42
42
|
return `msg-${nowIso.replace(/[^0-9]/g, "")}`;
|
|
43
43
|
}
|
|
@@ -45,7 +45,7 @@ class FileMessageRouter {
|
|
|
45
45
|
baseDir;
|
|
46
46
|
now;
|
|
47
47
|
constructor(options = {}) {
|
|
48
|
-
this.baseDir = options.baseDir ??
|
|
48
|
+
this.baseDir = options.baseDir ?? (0, identity_1.getAgentMessagesRoot)();
|
|
49
49
|
this.now = options.now ?? (() => new Date().toISOString());
|
|
50
50
|
fs.mkdirSync(this.baseDir, { recursive: true });
|
|
51
51
|
}
|
|
@@ -0,0 +1,134 @@
|
|
|
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.createRealOsCronDeps = createRealOsCronDeps;
|
|
37
|
+
exports.createRealCrontabDeps = createRealCrontabDeps;
|
|
38
|
+
exports.resolveOuroBinaryPath = resolveOuroBinaryPath;
|
|
39
|
+
const child_process_1 = require("child_process");
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const os = __importStar(require("os"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
44
|
+
function createRealOsCronDeps() {
|
|
45
|
+
(0, runtime_1.emitNervesEvent)({
|
|
46
|
+
component: "daemon",
|
|
47
|
+
event: "daemon.os_cron_deps_created",
|
|
48
|
+
message: "created real OS cron deps",
|
|
49
|
+
meta: { platform: process.platform },
|
|
50
|
+
});
|
|
51
|
+
return {
|
|
52
|
+
exec: (cmd) => {
|
|
53
|
+
try {
|
|
54
|
+
(0, child_process_1.execSync)(cmd, { stdio: "ignore" });
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
/* best effort */
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
writeFile: (p, c) => fs.writeFileSync(p, c, "utf-8"),
|
|
61
|
+
removeFile: (p) => {
|
|
62
|
+
try {
|
|
63
|
+
fs.unlinkSync(p);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
/* best effort — file may already be gone */
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
existsFile: (p) => fs.existsSync(p),
|
|
70
|
+
listDir: (dir) => {
|
|
71
|
+
try {
|
|
72
|
+
return fs.readdirSync(dir);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
mkdirp: (dir) => fs.mkdirSync(dir, { recursive: true }),
|
|
79
|
+
homeDir: os.homedir(),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function createRealCrontabDeps() {
|
|
83
|
+
(0, runtime_1.emitNervesEvent)({
|
|
84
|
+
component: "daemon",
|
|
85
|
+
event: "daemon.crontab_deps_created",
|
|
86
|
+
message: "created real crontab deps",
|
|
87
|
+
meta: {},
|
|
88
|
+
});
|
|
89
|
+
/* v8 ignore start -- crontab exec closures: calling these in tests would modify the real system crontab @preserve */
|
|
90
|
+
return {
|
|
91
|
+
execOutput: (cmd) => (0, child_process_1.execSync)(cmd, { encoding: "utf-8" }),
|
|
92
|
+
execWrite: (cmd, stdin) => {
|
|
93
|
+
(0, child_process_1.execSync)(cmd, { input: stdin, stdio: ["pipe", "ignore", "ignore"] });
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
/* v8 ignore stop */
|
|
97
|
+
}
|
|
98
|
+
/* v8 ignore start -- ouro path resolution: probes process.argv, filesystem layout, and PATH; branches depend on install method and runtime environment @preserve */
|
|
99
|
+
function resolveOuroBinaryPath() {
|
|
100
|
+
// Try to resolve from process.argv[1] — the script being run
|
|
101
|
+
const scriptPath = process.argv[1];
|
|
102
|
+
if (scriptPath) {
|
|
103
|
+
// If running via node dist/heart/daemon/daemon-entry.js, resolve the ouro wrapper
|
|
104
|
+
// The ouro binary is typically at the package root's bin
|
|
105
|
+
const distDir = path.resolve(path.dirname(scriptPath));
|
|
106
|
+
const packageBin = path.resolve(distDir, "..", "..", "..", "node_modules", ".bin", "ouro");
|
|
107
|
+
if (fs.existsSync(packageBin)) {
|
|
108
|
+
return packageBin;
|
|
109
|
+
}
|
|
110
|
+
// Try the repo-local scripts/ouro.sh
|
|
111
|
+
const repoOuro = path.resolve(distDir, "..", "..", "..", "scripts", "ouro.sh");
|
|
112
|
+
if (fs.existsSync(repoOuro)) {
|
|
113
|
+
return repoOuro;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Try which ouro
|
|
117
|
+
try {
|
|
118
|
+
const result = (0, child_process_1.execSync)("which ouro", { encoding: "utf-8" }).trim();
|
|
119
|
+
if (result.length > 0)
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
/* not on PATH */
|
|
124
|
+
}
|
|
125
|
+
// Fallback: use "ouro" and rely on PATH
|
|
126
|
+
(0, runtime_1.emitNervesEvent)({
|
|
127
|
+
component: "daemon",
|
|
128
|
+
event: "daemon.ouro_path_fallback",
|
|
129
|
+
message: "could not resolve full ouro binary path, falling back to 'ouro'",
|
|
130
|
+
meta: {},
|
|
131
|
+
});
|
|
132
|
+
return "ouro";
|
|
133
|
+
}
|
|
134
|
+
/* v8 ignore stop */
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
const runtime_1 = require("../../nerves/runtime");
|
|
5
5
|
const runtime_logging_1 = require("./runtime-logging");
|
|
6
|
-
const ouro_bot_wrapper_1 = require("
|
|
6
|
+
const ouro_bot_wrapper_1 = require("../versioning/ouro-bot-wrapper");
|
|
7
7
|
(0, runtime_logging_1.configureDaemonRuntimeLogger)("ouro-bot");
|
|
8
8
|
(0, runtime_1.emitNervesEvent)({
|
|
9
9
|
component: "daemon",
|
|
@@ -12,12 +12,14 @@ const ouro_bot_wrapper_1 = require("./ouro-bot-wrapper");
|
|
|
12
12
|
meta: { args: process.argv.slice(2) },
|
|
13
13
|
});
|
|
14
14
|
void (0, ouro_bot_wrapper_1.runOuroBotWrapper)(process.argv.slice(2)).catch((error) => {
|
|
15
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
16
|
+
process.stderr.write(`${message}\n`);
|
|
15
17
|
(0, runtime_1.emitNervesEvent)({
|
|
16
18
|
level: "error",
|
|
17
19
|
component: "daemon",
|
|
18
20
|
event: "daemon.ouro_bot_entry_error",
|
|
19
21
|
message: "ouro.bot wrapper entrypoint failed",
|
|
20
|
-
meta: { error:
|
|
22
|
+
meta: { error: message },
|
|
21
23
|
});
|
|
22
24
|
process.exit(1);
|
|
23
25
|
});
|
|
@@ -12,12 +12,14 @@ const runtime_logging_1 = require("./runtime-logging");
|
|
|
12
12
|
meta: { args: process.argv.slice(2) },
|
|
13
13
|
});
|
|
14
14
|
void (0, daemon_cli_1.runOuroCli)(process.argv.slice(2)).catch((error) => {
|
|
15
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
16
|
+
process.stderr.write(`${message}\n`);
|
|
15
17
|
(0, runtime_1.emitNervesEvent)({
|
|
16
18
|
level: "error",
|
|
17
19
|
component: "daemon",
|
|
18
20
|
event: "daemon.cli_entry_error",
|
|
19
21
|
message: "ouro CLI entrypoint failed",
|
|
20
|
-
meta: { error:
|
|
22
|
+
meta: { error: message },
|
|
21
23
|
});
|
|
22
24
|
process.exit(1);
|
|
23
25
|
});
|
|
@@ -51,15 +51,70 @@ class DaemonProcessManager {
|
|
|
51
51
|
now;
|
|
52
52
|
setTimeoutFn;
|
|
53
53
|
clearTimeoutFn;
|
|
54
|
+
cooldownRecoveryMs;
|
|
55
|
+
maxCooldownRetries;
|
|
56
|
+
existsSyncFn;
|
|
57
|
+
configCheckFn;
|
|
58
|
+
statusWriterFn;
|
|
59
|
+
onSnapshotChangeFn;
|
|
60
|
+
/**
|
|
61
|
+
* Notify the snapshot-change observer (if registered). Swallows any
|
|
62
|
+
* errors from the observer so process lifecycle code never fails
|
|
63
|
+
* because the observer threw.
|
|
64
|
+
*/
|
|
65
|
+
notifySnapshotChange(snapshot) {
|
|
66
|
+
if (!this.onSnapshotChangeFn)
|
|
67
|
+
return;
|
|
68
|
+
try {
|
|
69
|
+
this.onSnapshotChangeFn(snapshot);
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
(0, runtime_1.emitNervesEvent)({
|
|
73
|
+
level: "warn",
|
|
74
|
+
component: "daemon",
|
|
75
|
+
event: "daemon.snapshot_change_observer_error",
|
|
76
|
+
message: "snapshot-change observer threw",
|
|
77
|
+
meta: {
|
|
78
|
+
agent: snapshot.name,
|
|
79
|
+
error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error),
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
writeStatus(agent, text) {
|
|
85
|
+
try {
|
|
86
|
+
this.statusWriterFn(text);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
(0, runtime_1.emitNervesEvent)({
|
|
90
|
+
level: "warn",
|
|
91
|
+
component: "daemon",
|
|
92
|
+
event: "daemon.status_writer_error",
|
|
93
|
+
message: "daemon status writer threw",
|
|
94
|
+
meta: {
|
|
95
|
+
agent,
|
|
96
|
+
error: error instanceof Error ? error.message : String(error),
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
54
101
|
constructor(options) {
|
|
55
102
|
this.maxRestartsPerHour = options.maxRestartsPerHour ?? 10;
|
|
56
103
|
this.stabilityThresholdMs = options.stabilityThresholdMs ?? 60_000;
|
|
57
104
|
this.initialBackoffMs = options.initialBackoffMs ?? 1_000;
|
|
58
105
|
this.maxBackoffMs = options.maxBackoffMs ?? 60_000;
|
|
106
|
+
this.cooldownRecoveryMs = options.cooldownRecoveryMs ?? 5 * 60 * 1_000;
|
|
107
|
+
this.maxCooldownRetries = options.maxCooldownRetries ?? 3;
|
|
59
108
|
this.spawnFn = options.spawn ?? ((command, args, spawnOptions) => (0, child_process_1.spawn)(command, args, spawnOptions));
|
|
60
109
|
this.now = options.now ?? (() => Date.now());
|
|
61
110
|
this.setTimeoutFn = options.setTimeoutFn ?? ((cb, delay) => setTimeout(cb, delay));
|
|
62
111
|
this.clearTimeoutFn = options.clearTimeoutFn ?? ((timer) => clearTimeout(timer));
|
|
112
|
+
this.existsSyncFn = options.existsSync ?? null;
|
|
113
|
+
this.configCheckFn = options.configCheck ?? null;
|
|
114
|
+
this.statusWriterFn = options.statusWriter ?? ((text) => {
|
|
115
|
+
process.stderr.write(text);
|
|
116
|
+
});
|
|
117
|
+
this.onSnapshotChangeFn = options.onSnapshotChange ?? null;
|
|
63
118
|
for (const agent of options.agents) {
|
|
64
119
|
this.agents.set(agent.name, {
|
|
65
120
|
config: agent,
|
|
@@ -67,6 +122,9 @@ class DaemonProcessManager {
|
|
|
67
122
|
restartTimer: null,
|
|
68
123
|
crashTimestamps: [],
|
|
69
124
|
stopRequested: false,
|
|
125
|
+
cooldownTimer: null,
|
|
126
|
+
cooldownRetryCount: 0,
|
|
127
|
+
fastCrashCount: 0,
|
|
70
128
|
snapshot: {
|
|
71
129
|
name: agent.name,
|
|
72
130
|
channel: agent.channel,
|
|
@@ -76,6 +134,10 @@ class DaemonProcessManager {
|
|
|
76
134
|
startedAt: null,
|
|
77
135
|
lastCrashAt: null,
|
|
78
136
|
backoffMs: this.initialBackoffMs,
|
|
137
|
+
lastExitCode: null,
|
|
138
|
+
lastSignal: null,
|
|
139
|
+
errorReason: null,
|
|
140
|
+
fixHint: null,
|
|
79
141
|
},
|
|
80
142
|
});
|
|
81
143
|
}
|
|
@@ -94,14 +156,75 @@ class DaemonProcessManager {
|
|
|
94
156
|
this.clearRestartTimer(state);
|
|
95
157
|
state.stopRequested = false;
|
|
96
158
|
state.snapshot.status = "starting";
|
|
159
|
+
if (this.configCheckFn) {
|
|
160
|
+
const result = await this.configCheckFn(agent);
|
|
161
|
+
if (result.skip) {
|
|
162
|
+
state.snapshot.status = "stopped";
|
|
163
|
+
state.snapshot.errorReason = null;
|
|
164
|
+
state.snapshot.fixHint = null;
|
|
165
|
+
(0, runtime_1.emitNervesEvent)({
|
|
166
|
+
component: "daemon",
|
|
167
|
+
event: "daemon.agent_config_skipped",
|
|
168
|
+
message: result.error ?? "agent start skipped by config check",
|
|
169
|
+
meta: { agent, fix: result.fix ?? null },
|
|
170
|
+
});
|
|
171
|
+
this.notifySnapshotChange(state.snapshot);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (!result.ok) {
|
|
175
|
+
state.snapshot.status = "crashed";
|
|
176
|
+
// Surface the error and fix to the snapshot so sibling agents can
|
|
177
|
+
// read it via the pulse. Without this, the diagnosis stayed
|
|
178
|
+
// trapped in the nerves event and stderr — visible to humans
|
|
179
|
+
// running `ouro status` or grepping logs, but invisible to
|
|
180
|
+
// peer agents trying to coordinate around the broken state.
|
|
181
|
+
state.snapshot.errorReason = result.error ?? "agent config validation failed";
|
|
182
|
+
state.snapshot.fixHint = result.fix ?? null;
|
|
183
|
+
(0, runtime_1.emitNervesEvent)({
|
|
184
|
+
level: "error",
|
|
185
|
+
component: "daemon",
|
|
186
|
+
event: "daemon.agent_config_invalid",
|
|
187
|
+
message: result.error ?? "agent config validation failed",
|
|
188
|
+
meta: { agent, fix: result.fix ?? null },
|
|
189
|
+
});
|
|
190
|
+
this.writeStatus(agent, `[daemon] ${agent}: ${result.error}\n` +
|
|
191
|
+
(result.fix ? ` Fix: ${result.fix}\n` : ""));
|
|
192
|
+
this.notifySnapshotChange(state.snapshot);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
// Config check passed — clear any prior error so the pulse stops
|
|
196
|
+
// reporting the broken state. This is the recovery path: the user
|
|
197
|
+
// fixed their secrets/config, the next startAgent attempt sees a
|
|
198
|
+
// valid config, and the pulse goes quiet.
|
|
199
|
+
state.snapshot.errorReason = null;
|
|
200
|
+
state.snapshot.fixHint = null;
|
|
201
|
+
}
|
|
97
202
|
const runCwd = (0, identity_1.getRepoRoot)();
|
|
98
203
|
const entryScript = path.join((0, identity_1.getRepoRoot)(), "dist", state.config.entry);
|
|
204
|
+
if (this.existsSyncFn && !this.existsSyncFn(entryScript)) {
|
|
205
|
+
state.snapshot.status = "crashed";
|
|
206
|
+
(0, runtime_1.emitNervesEvent)({
|
|
207
|
+
level: "error",
|
|
208
|
+
component: "daemon",
|
|
209
|
+
event: "daemon.agent_entry_missing",
|
|
210
|
+
message: "agent entry script does not exist — cannot spawn. Run 'ouro daemon install' from the correct location.",
|
|
211
|
+
meta: { agent, entryScript },
|
|
212
|
+
});
|
|
213
|
+
this.notifySnapshotChange(state.snapshot);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
99
216
|
const args = [entryScript, "--agent", state.config.agentArg ?? agent, ...(state.config.args ?? [])];
|
|
100
217
|
const child = this.spawnFn("node", args, {
|
|
101
218
|
cwd: runCwd,
|
|
102
219
|
env: state.config.env ? { ...process.env, ...state.config.env } : process.env,
|
|
103
220
|
stdio: ["ignore", "ignore", "ignore", "ipc"],
|
|
104
221
|
});
|
|
222
|
+
/* v8 ignore next 7 -- defensive: spawn should always return a ChildProcess @preserve */
|
|
223
|
+
if (!child) {
|
|
224
|
+
state.snapshot.status = "crashed";
|
|
225
|
+
(0, runtime_1.emitNervesEvent)({ level: "error", component: "daemon", event: "daemon.agent_spawn_failed", message: "spawn returned null", meta: { agent } });
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
105
228
|
state.process = child;
|
|
106
229
|
state.snapshot.status = "running";
|
|
107
230
|
state.snapshot.pid = child.pid ?? null;
|
|
@@ -112,6 +235,18 @@ class DaemonProcessManager {
|
|
|
112
235
|
message: "daemon started managed agent process",
|
|
113
236
|
meta: { agent, pid: child.pid ?? null, cwd: runCwd },
|
|
114
237
|
});
|
|
238
|
+
this.notifySnapshotChange(state.snapshot);
|
|
239
|
+
/* v8 ignore start — child process error handler; requires real spawn to trigger */
|
|
240
|
+
child.on("error", (err) => {
|
|
241
|
+
(0, runtime_1.emitNervesEvent)({
|
|
242
|
+
level: "warn",
|
|
243
|
+
component: "daemon",
|
|
244
|
+
event: "daemon.agent_process_error",
|
|
245
|
+
message: "managed agent process emitted error",
|
|
246
|
+
meta: { agent, error: err.message },
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
/* v8 ignore stop */
|
|
115
250
|
child.once("exit", (code, signal) => {
|
|
116
251
|
this.onExit(state, code, signal);
|
|
117
252
|
});
|
|
@@ -119,10 +254,12 @@ class DaemonProcessManager {
|
|
|
119
254
|
async stopAgent(agent) {
|
|
120
255
|
const state = this.requireAgent(agent);
|
|
121
256
|
this.clearRestartTimer(state);
|
|
257
|
+
this.clearCooldownTimer(state);
|
|
122
258
|
state.stopRequested = true;
|
|
123
259
|
if (!state.process) {
|
|
124
260
|
state.snapshot.status = "stopped";
|
|
125
261
|
state.snapshot.pid = null;
|
|
262
|
+
this.notifySnapshotChange(state.snapshot);
|
|
126
263
|
return;
|
|
127
264
|
}
|
|
128
265
|
const child = state.process;
|
|
@@ -141,6 +278,7 @@ class DaemonProcessManager {
|
|
|
141
278
|
meta: { agent },
|
|
142
279
|
});
|
|
143
280
|
}
|
|
281
|
+
this.notifySnapshotChange(state.snapshot);
|
|
144
282
|
}
|
|
145
283
|
async restartAgent(agent) {
|
|
146
284
|
await this.stopAgent(agent);
|
|
@@ -179,6 +317,8 @@ class DaemonProcessManager {
|
|
|
179
317
|
return;
|
|
180
318
|
state.process = null;
|
|
181
319
|
state.snapshot.pid = null;
|
|
320
|
+
state.snapshot.lastExitCode = code;
|
|
321
|
+
state.snapshot.lastSignal = signal;
|
|
182
322
|
const crashed = !state.stopRequested && code !== 0;
|
|
183
323
|
const now = this.now();
|
|
184
324
|
const startedAt = state.snapshot.startedAt ? Date.parse(state.snapshot.startedAt) : now;
|
|
@@ -195,13 +335,45 @@ class DaemonProcessManager {
|
|
|
195
335
|
if (runDuration >= this.stabilityThresholdMs) {
|
|
196
336
|
state.snapshot.backoffMs = this.initialBackoffMs;
|
|
197
337
|
}
|
|
338
|
+
this.notifySnapshotChange(state.snapshot);
|
|
198
339
|
return;
|
|
199
340
|
}
|
|
200
341
|
state.snapshot.lastCrashAt = new Date(now).toISOString();
|
|
342
|
+
// Fast-crash detection: if the agent dies within 5 seconds of starting, it's likely
|
|
343
|
+
// a configuration issue (missing credentials, bad provider, etc.) not a transient failure.
|
|
344
|
+
// After 3 consecutive fast crashes, stop retrying and mark as config-failed.
|
|
345
|
+
const FAST_CRASH_THRESHOLD_MS = 5000;
|
|
346
|
+
const FAST_CRASH_MAX = 3;
|
|
347
|
+
if (runDuration < FAST_CRASH_THRESHOLD_MS) {
|
|
348
|
+
state.fastCrashCount = state.fastCrashCount + 1;
|
|
349
|
+
if (state.fastCrashCount >= FAST_CRASH_MAX) {
|
|
350
|
+
state.snapshot.status = "crashed";
|
|
351
|
+
// Capture the fast-crash diagnosis on the snapshot so it surfaces
|
|
352
|
+
// via the pulse. The error message is prescriptive: it tells the
|
|
353
|
+
// user (and their sibling agents) exactly what to do.
|
|
354
|
+
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).`;
|
|
355
|
+
state.snapshot.fixHint = `Fix the config and run \`ouro up\` to restart, or check daemon logs for the underlying error.`;
|
|
356
|
+
(0, runtime_1.emitNervesEvent)({
|
|
357
|
+
level: "error",
|
|
358
|
+
component: "daemon",
|
|
359
|
+
event: "daemon.agent_config_failure",
|
|
360
|
+
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.`,
|
|
361
|
+
meta: { agent: state.config.name, fastCrashCount: state.fastCrashCount, avgRunDurationMs: runDuration },
|
|
362
|
+
});
|
|
363
|
+
this.notifySnapshotChange(state.snapshot);
|
|
364
|
+
return; // Don't schedule cooldown recovery — this needs human/agent intervention
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
// Reset fast-crash counter on a stable run
|
|
369
|
+
state.fastCrashCount = 0;
|
|
370
|
+
}
|
|
201
371
|
state.crashTimestamps = state.crashTimestamps.filter((crashTs) => crashTs >= startOfHour(now));
|
|
202
372
|
state.crashTimestamps.push(now);
|
|
203
373
|
if (state.crashTimestamps.length > this.maxRestartsPerHour) {
|
|
204
374
|
state.snapshot.status = "crashed";
|
|
375
|
+
state.snapshot.errorReason = `agent exceeded restart limit (${this.maxRestartsPerHour}/hr) — entering cooldown`;
|
|
376
|
+
state.snapshot.fixHint = "investigate why the agent keeps crashing; cooldown will retry shortly";
|
|
205
377
|
(0, runtime_1.emitNervesEvent)({
|
|
206
378
|
level: "error",
|
|
207
379
|
component: "daemon",
|
|
@@ -209,6 +381,8 @@ class DaemonProcessManager {
|
|
|
209
381
|
message: "managed agent exceeded restart limit and is marked crashed",
|
|
210
382
|
meta: { agent: state.config.name, maxRestartsPerHour: this.maxRestartsPerHour },
|
|
211
383
|
});
|
|
384
|
+
this.notifySnapshotChange(state.snapshot);
|
|
385
|
+
this.scheduleCooldownRecovery(state);
|
|
212
386
|
return;
|
|
213
387
|
}
|
|
214
388
|
state.snapshot.status = "starting";
|
|
@@ -219,6 +393,7 @@ class DaemonProcessManager {
|
|
|
219
393
|
state.restartTimer = this.setTimeoutFn(() => {
|
|
220
394
|
void this.startAgent(state.config.name);
|
|
221
395
|
}, waitMs);
|
|
396
|
+
this.notifySnapshotChange(state.snapshot);
|
|
222
397
|
}
|
|
223
398
|
clearRestartTimer(state) {
|
|
224
399
|
if (state.restartTimer === null)
|
|
@@ -226,6 +401,45 @@ class DaemonProcessManager {
|
|
|
226
401
|
this.clearTimeoutFn(state.restartTimer);
|
|
227
402
|
state.restartTimer = null;
|
|
228
403
|
}
|
|
404
|
+
scheduleCooldownRecovery(state) {
|
|
405
|
+
if (state.cooldownRetryCount >= this.maxCooldownRetries) {
|
|
406
|
+
(0, runtime_1.emitNervesEvent)({
|
|
407
|
+
level: "error",
|
|
408
|
+
component: "daemon",
|
|
409
|
+
event: "daemon.agent_permanent_failure",
|
|
410
|
+
message: "managed agent exhausted all cooldown retries — permanently stopped",
|
|
411
|
+
meta: { agent: state.config.name, cooldownRetryCount: state.cooldownRetryCount, maxCooldownRetries: this.maxCooldownRetries },
|
|
412
|
+
});
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
this.clearCooldownTimer(state);
|
|
416
|
+
state.cooldownTimer = this.setTimeoutFn(() => {
|
|
417
|
+
state.cooldownRetryCount += 1;
|
|
418
|
+
state.crashTimestamps = [];
|
|
419
|
+
state.snapshot.backoffMs = this.initialBackoffMs;
|
|
420
|
+
state.snapshot.status = "starting";
|
|
421
|
+
state.snapshot.restartCount += 1;
|
|
422
|
+
(0, runtime_1.emitNervesEvent)({
|
|
423
|
+
component: "daemon",
|
|
424
|
+
event: "daemon.agent_cooldown_recovery",
|
|
425
|
+
message: "attempting cooldown recovery for managed agent",
|
|
426
|
+
meta: { agent: state.config.name, cooldownRetryCount: state.cooldownRetryCount },
|
|
427
|
+
});
|
|
428
|
+
void this.startAgent(state.config.name);
|
|
429
|
+
}, this.cooldownRecoveryMs);
|
|
430
|
+
(0, runtime_1.emitNervesEvent)({
|
|
431
|
+
component: "daemon",
|
|
432
|
+
event: "daemon.agent_cooldown_scheduled",
|
|
433
|
+
message: `scheduled cooldown recovery in ${this.cooldownRecoveryMs}ms`,
|
|
434
|
+
meta: { agent: state.config.name, cooldownRecoveryMs: this.cooldownRecoveryMs, cooldownRetryCount: state.cooldownRetryCount },
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
clearCooldownTimer(state) {
|
|
438
|
+
if (state.cooldownTimer === null)
|
|
439
|
+
return;
|
|
440
|
+
this.clearTimeoutFn(state.cooldownTimer);
|
|
441
|
+
state.cooldownTimer = null;
|
|
442
|
+
}
|
|
229
443
|
requireAgent(agent) {
|
|
230
444
|
const state = this.agents.get(agent);
|
|
231
445
|
if (!state) {
|