pulseed 0.4.0 → 0.4.2
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/dist/base/state/state-manager-goal-write.d.ts +22 -0
- package/dist/base/state/state-manager-goal-write.d.ts.map +1 -0
- package/dist/base/state/state-manager-goal-write.js +74 -0
- package/dist/base/state/state-manager-goal-write.js.map +1 -0
- package/dist/base/state/state-manager-wal.d.ts +11 -0
- package/dist/base/state/state-manager-wal.d.ts.map +1 -0
- package/dist/base/state/state-manager-wal.js +89 -0
- package/dist/base/state/state-manager-wal.js.map +1 -0
- package/dist/base/state/state-manager.d.ts +1 -4
- package/dist/base/state/state-manager.d.ts.map +1 -1
- package/dist/base/state/state-manager.js +18 -127
- package/dist/base/state/state-manager.js.map +1 -1
- package/dist/interface/chat/chat-runner.d.ts +1 -1
- package/dist/interface/chat/event-subscriber.d.ts +4 -0
- package/dist/interface/chat/event-subscriber.d.ts.map +1 -1
- package/dist/interface/chat/event-subscriber.js +49 -2
- package/dist/interface/chat/event-subscriber.js.map +1 -1
- package/dist/interface/chat/tend-command.d.ts +1 -1
- package/dist/interface/cli/cli-command-registry.js +1 -1
- package/dist/interface/cli/cli-command-registry.js.map +1 -1
- package/dist/interface/cli/commands/chat.js +2 -2
- package/dist/interface/cli/commands/daemon.d.ts.map +1 -1
- package/dist/interface/cli/commands/daemon.js +87 -44
- package/dist/interface/cli/commands/daemon.js.map +1 -1
- package/dist/interface/cli/commands/schedule.js +2 -2
- package/dist/interface/cli/commands/setup/steps-runtime.js +1 -1
- package/dist/interface/cli/ensure-api-key.d.ts +4 -1
- package/dist/interface/cli/ensure-api-key.d.ts.map +1 -1
- package/dist/interface/cli/ensure-api-key.js +52 -15
- package/dist/interface/cli/ensure-api-key.js.map +1 -1
- package/dist/interface/tui/app.d.ts +1 -1
- package/dist/interface/tui/chat/scroll.d.ts +14 -0
- package/dist/interface/tui/chat/scroll.d.ts.map +1 -0
- package/dist/interface/tui/chat/scroll.js +46 -0
- package/dist/interface/tui/chat/scroll.js.map +1 -0
- package/dist/interface/tui/chat/suggestions.d.ts +8 -0
- package/dist/interface/tui/chat/suggestions.d.ts.map +1 -0
- package/dist/interface/tui/chat/suggestions.js +112 -0
- package/dist/interface/tui/chat/suggestions.js.map +1 -0
- package/dist/interface/tui/chat/types.d.ts +31 -0
- package/dist/interface/tui/chat/types.d.ts.map +1 -0
- package/dist/interface/tui/chat/types.js +2 -0
- package/dist/interface/tui/chat/types.js.map +1 -0
- package/dist/interface/tui/chat/viewport.d.ts +3 -0
- package/dist/interface/tui/chat/viewport.d.ts.map +1 -0
- package/dist/interface/tui/chat/viewport.js +78 -0
- package/dist/interface/tui/chat/viewport.js.map +1 -0
- package/dist/interface/tui/chat.d.ts +5 -49
- package/dist/interface/tui/chat.d.ts.map +1 -1
- package/dist/interface/tui/chat.js +7 -236
- package/dist/interface/tui/chat.js.map +1 -1
- package/dist/interface/tui/entry.js +3 -3
- package/dist/interface/tui/use-loop.d.ts +1 -1
- package/dist/orchestrator/execution/task/task-lifecycle.d.ts +3 -0
- package/dist/orchestrator/execution/task/task-lifecycle.d.ts.map +1 -1
- package/dist/orchestrator/execution/task/task-lifecycle.js +3 -0
- package/dist/orchestrator/execution/task/task-lifecycle.js.map +1 -1
- package/dist/orchestrator/execution/task/task-verifier-rules.d.ts.map +1 -1
- package/dist/orchestrator/execution/task/task-verifier-rules.js +34 -2
- package/dist/orchestrator/execution/task/task-verifier-rules.js.map +1 -1
- package/dist/orchestrator/execution/task/task-verifier-types.d.ts +2 -0
- package/dist/orchestrator/execution/task/task-verifier-types.d.ts.map +1 -1
- package/dist/orchestrator/loop/checkpoint-manager-loop.d.ts +1 -1
- package/dist/orchestrator/loop/checkpoint-manager-loop.d.ts.map +1 -1
- package/dist/orchestrator/loop/core-loop/capability.d.ts +22 -0
- package/dist/orchestrator/loop/core-loop/capability.d.ts.map +1 -0
- package/dist/orchestrator/loop/core-loop/capability.js +151 -0
- package/dist/orchestrator/loop/core-loop/capability.js.map +1 -0
- package/dist/orchestrator/loop/core-loop/contracts.d.ts +245 -0
- package/dist/orchestrator/loop/core-loop/contracts.d.ts.map +1 -0
- package/dist/orchestrator/loop/core-loop/contracts.js +40 -0
- package/dist/orchestrator/loop/core-loop/contracts.js.map +1 -0
- package/dist/orchestrator/loop/core-loop/control.d.ts +27 -0
- package/dist/orchestrator/loop/core-loop/control.d.ts.map +1 -0
- package/dist/orchestrator/loop/core-loop/control.js +72 -0
- package/dist/orchestrator/loop/core-loop/control.js.map +1 -0
- package/dist/orchestrator/loop/core-loop/learning.d.ts +31 -0
- package/dist/orchestrator/loop/core-loop/learning.d.ts.map +1 -0
- package/dist/orchestrator/loop/core-loop/learning.js +92 -0
- package/dist/orchestrator/loop/core-loop/learning.js.map +1 -0
- package/dist/orchestrator/loop/core-loop/preparation.d.ts +63 -0
- package/dist/orchestrator/loop/core-loop/preparation.d.ts.map +1 -0
- package/dist/orchestrator/loop/core-loop/preparation.js +362 -0
- package/dist/orchestrator/loop/core-loop/preparation.js.map +1 -0
- package/dist/orchestrator/loop/core-loop/task-cycle.d.ts +29 -0
- package/dist/orchestrator/loop/core-loop/task-cycle.d.ts.map +1 -0
- package/dist/orchestrator/loop/core-loop/task-cycle.js +674 -0
- package/dist/orchestrator/loop/core-loop/task-cycle.js.map +1 -0
- package/dist/orchestrator/loop/core-loop-capability.d.ts +1 -24
- package/dist/orchestrator/loop/core-loop-capability.d.ts.map +1 -1
- package/dist/orchestrator/loop/core-loop-capability.js +1 -153
- package/dist/orchestrator/loop/core-loop-capability.js.map +1 -1
- package/dist/orchestrator/loop/core-loop-learning.d.ts +1 -34
- package/dist/orchestrator/loop/core-loop-learning.d.ts.map +1 -1
- package/dist/orchestrator/loop/core-loop-learning.js +1 -95
- package/dist/orchestrator/loop/core-loop-learning.js.map +1 -1
- package/dist/orchestrator/loop/core-loop-phases-b.d.ts +1 -31
- package/dist/orchestrator/loop/core-loop-phases-b.d.ts.map +1 -1
- package/dist/orchestrator/loop/core-loop-phases-b.js +1 -669
- package/dist/orchestrator/loop/core-loop-phases-b.js.map +1 -1
- package/dist/orchestrator/loop/core-loop-phases-c.d.ts +1 -26
- package/dist/orchestrator/loop/core-loop-phases-c.d.ts.map +1 -1
- package/dist/orchestrator/loop/core-loop-phases-c.js +1 -71
- package/dist/orchestrator/loop/core-loop-phases-c.js.map +1 -1
- package/dist/orchestrator/loop/core-loop-phases.d.ts +1 -68
- package/dist/orchestrator/loop/core-loop-phases.d.ts.map +1 -1
- package/dist/orchestrator/loop/core-loop-phases.js +1 -367
- package/dist/orchestrator/loop/core-loop-phases.js.map +1 -1
- package/dist/orchestrator/loop/core-loop-types.d.ts +1 -244
- package/dist/orchestrator/loop/core-loop-types.d.ts.map +1 -1
- package/dist/orchestrator/loop/core-loop-types.js +1 -39
- package/dist/orchestrator/loop/core-loop-types.js.map +1 -1
- package/dist/orchestrator/loop/core-loop.d.ts +3 -3
- package/dist/orchestrator/loop/core-loop.d.ts.map +1 -1
- package/dist/orchestrator/loop/core-loop.js +6 -6
- package/dist/orchestrator/loop/core-loop.js.map +1 -1
- package/dist/orchestrator/loop/loop-report-helper.d.ts +1 -1
- package/dist/orchestrator/loop/loop-report-helper.d.ts.map +1 -1
- package/dist/orchestrator/loop/parallel-dispatch.d.ts +2 -2
- package/dist/orchestrator/loop/parallel-dispatch.d.ts.map +1 -1
- package/dist/orchestrator/loop/post-loop-hooks.d.ts +1 -1
- package/dist/orchestrator/loop/post-loop-hooks.d.ts.map +1 -1
- package/dist/orchestrator/loop/tree-loop-runner.d.ts +1 -1
- package/dist/orchestrator/loop/tree-loop-runner.d.ts.map +1 -1
- package/dist/orchestrator/loop/tree-loop-runner.js +1 -1
- package/dist/orchestrator/loop/tree-loop-runner.js.map +1 -1
- package/dist/platform/dream/dream-schedule-suggestions.d.ts +1 -1
- package/dist/platform/drive/drive-system.d.ts +8 -0
- package/dist/platform/drive/drive-system.d.ts.map +1 -1
- package/dist/platform/drive/drive-system.js +39 -22
- package/dist/platform/drive/drive-system.js.map +1 -1
- package/dist/platform/observation/engine/observe-context.d.ts +4 -0
- package/dist/platform/observation/engine/observe-context.d.ts.map +1 -0
- package/dist/platform/observation/engine/observe-context.js +26 -0
- package/dist/platform/observation/engine/observe-context.js.map +1 -0
- package/dist/platform/observation/engine/observe-datasource-stage.d.ts +33 -0
- package/dist/platform/observation/engine/observe-datasource-stage.d.ts.map +1 -0
- package/dist/platform/observation/engine/observe-datasource-stage.js +66 -0
- package/dist/platform/observation/engine/observe-datasource-stage.js.map +1 -0
- package/dist/platform/observation/engine/observe-llm-stage.d.ts +25 -0
- package/dist/platform/observation/engine/observe-llm-stage.d.ts.map +1 -0
- package/dist/platform/observation/engine/observe-llm-stage.js +79 -0
- package/dist/platform/observation/engine/observe-llm-stage.js.map +1 -0
- package/dist/platform/observation/engine/observe-precheck.d.ts +21 -0
- package/dist/platform/observation/engine/observe-precheck.d.ts.map +1 -0
- package/dist/platform/observation/engine/observe-precheck.js +51 -0
- package/dist/platform/observation/engine/observe-precheck.js.map +1 -0
- package/dist/platform/observation/engine/observe-self-report.d.ts +18 -0
- package/dist/platform/observation/engine/observe-self-report.d.ts.map +1 -0
- package/dist/platform/observation/engine/observe-self-report.js +26 -0
- package/dist/platform/observation/engine/observe-self-report.js.map +1 -0
- package/dist/platform/observation/engine/observe-tool-stage.d.ts +21 -0
- package/dist/platform/observation/engine/observe-tool-stage.d.ts.map +1 -0
- package/dist/platform/observation/engine/observe-tool-stage.js +49 -0
- package/dist/platform/observation/engine/observe-tool-stage.js.map +1 -0
- package/dist/platform/observation/observation-engine.d.ts.map +1 -1
- package/dist/platform/observation/observation-engine.js +67 -246
- package/dist/platform/observation/observation-engine.js.map +1 -1
- package/dist/prompt/context-assembler.d.ts +61 -13
- package/dist/prompt/context-assembler.d.ts.map +1 -1
- package/dist/prompt/context-assembler.js +18 -3
- package/dist/prompt/context-assembler.js.map +1 -1
- package/dist/runtime/approval-broker.d.ts.map +1 -1
- package/dist/runtime/approval-broker.js +1 -0
- package/dist/runtime/approval-broker.js.map +1 -1
- package/dist/runtime/command-dispatcher.d.ts +35 -0
- package/dist/runtime/command-dispatcher.d.ts.map +1 -0
- package/dist/runtime/command-dispatcher.js +145 -0
- package/dist/runtime/command-dispatcher.js.map +1 -0
- package/dist/runtime/daemon/client.d.ts +67 -0
- package/dist/runtime/daemon/client.d.ts.map +1 -0
- package/dist/runtime/daemon/client.js +330 -0
- package/dist/runtime/daemon/client.js.map +1 -0
- package/dist/runtime/daemon/health.d.ts +31 -0
- package/dist/runtime/daemon/health.d.ts.map +1 -0
- package/dist/runtime/daemon/health.js +113 -0
- package/dist/runtime/daemon/health.js.map +1 -0
- package/dist/runtime/daemon/index.d.ts +9 -0
- package/dist/runtime/daemon/index.d.ts.map +1 -0
- package/dist/runtime/daemon/index.js +8 -0
- package/dist/runtime/daemon/index.js.map +1 -0
- package/dist/runtime/daemon/maintenance.d.ts +47 -0
- package/dist/runtime/daemon/maintenance.d.ts.map +1 -0
- package/dist/runtime/daemon/maintenance.js +230 -0
- package/dist/runtime/daemon/maintenance.js.map +1 -0
- package/dist/runtime/daemon/persistence.d.ts +20 -0
- package/dist/runtime/daemon/persistence.d.ts.map +1 -0
- package/dist/runtime/daemon/persistence.js +112 -0
- package/dist/runtime/daemon/persistence.js.map +1 -0
- package/dist/runtime/daemon/runner-lifecycle.d.ts +29 -0
- package/dist/runtime/daemon/runner-lifecycle.d.ts.map +1 -0
- package/dist/runtime/daemon/runner-lifecycle.js +56 -0
- package/dist/runtime/daemon/runner-lifecycle.js.map +1 -0
- package/dist/runtime/daemon/runner.d.ts +229 -0
- package/dist/runtime/daemon/runner.d.ts.map +1 -0
- package/dist/runtime/daemon/runner.js +875 -0
- package/dist/runtime/daemon/runner.js.map +1 -0
- package/dist/runtime/daemon/runtime-ownership.d.ts +30 -0
- package/dist/runtime/daemon/runtime-ownership.d.ts.map +1 -0
- package/dist/runtime/daemon/runtime-ownership.js +132 -0
- package/dist/runtime/daemon/runtime-ownership.js.map +1 -0
- package/dist/runtime/daemon/signals.d.ts +17 -0
- package/dist/runtime/daemon/signals.d.ts.map +1 -0
- package/dist/runtime/daemon/signals.js +31 -0
- package/dist/runtime/daemon/signals.js.map +1 -0
- package/dist/runtime/daemon/types.d.ts +8 -0
- package/dist/runtime/daemon/types.d.ts.map +1 -0
- package/dist/runtime/daemon/types.js +2 -0
- package/dist/runtime/daemon/types.js.map +1 -0
- package/dist/runtime/daemon-client.d.ts +1 -55
- package/dist/runtime/daemon-client.d.ts.map +1 -1
- package/dist/runtime/daemon-client.js +1 -297
- package/dist/runtime/daemon-client.js.map +1 -1
- package/dist/runtime/daemon-health.d.ts +1 -30
- package/dist/runtime/daemon-health.d.ts.map +1 -1
- package/dist/runtime/daemon-health.js +1 -112
- package/dist/runtime/daemon-health.js.map +1 -1
- package/dist/runtime/daemon-runner-lifecycle.d.ts +2 -0
- package/dist/runtime/daemon-runner-lifecycle.d.ts.map +1 -0
- package/dist/runtime/daemon-runner-lifecycle.js +2 -0
- package/dist/runtime/daemon-runner-lifecycle.js.map +1 -0
- package/dist/runtime/daemon-runner.d.ts +1 -231
- package/dist/runtime/daemon-runner.d.ts.map +1 -1
- package/dist/runtime/daemon-runner.js +1 -1042
- package/dist/runtime/daemon-runner.js.map +1 -1
- package/dist/runtime/daemon-runtime-ownership.d.ts +2 -0
- package/dist/runtime/daemon-runtime-ownership.d.ts.map +1 -0
- package/dist/runtime/daemon-runtime-ownership.js +2 -0
- package/dist/runtime/daemon-runtime-ownership.js.map +1 -0
- package/dist/runtime/daemon-signals.d.ts +1 -16
- package/dist/runtime/daemon-signals.d.ts.map +1 -1
- package/dist/runtime/daemon-signals.js +1 -30
- package/dist/runtime/daemon-signals.js.map +1 -1
- package/dist/runtime/event/dispatcher.d.ts +34 -0
- package/dist/runtime/event/dispatcher.d.ts.map +1 -0
- package/dist/runtime/event/dispatcher.js +124 -0
- package/dist/runtime/event/dispatcher.js.map +1 -0
- package/dist/runtime/event/index.d.ts +5 -0
- package/dist/runtime/event/index.d.ts.map +1 -0
- package/dist/runtime/event/index.js +5 -0
- package/dist/runtime/event/index.js.map +1 -0
- package/dist/runtime/event/server-snapshot-reader.d.ts +31 -0
- package/dist/runtime/event/server-snapshot-reader.d.ts.map +1 -0
- package/dist/runtime/event/server-snapshot-reader.js +94 -0
- package/dist/runtime/event/server-snapshot-reader.js.map +1 -0
- package/dist/runtime/event/server-sse.d.ts +25 -0
- package/dist/runtime/event/server-sse.d.ts.map +1 -0
- package/dist/runtime/event/server-sse.js +149 -0
- package/dist/runtime/event/server-sse.js.map +1 -0
- package/dist/runtime/event/server.d.ts +114 -0
- package/dist/runtime/event/server.d.ts.map +1 -0
- package/dist/runtime/event/server.js +651 -0
- package/dist/runtime/event/server.js.map +1 -0
- package/dist/runtime/event-dispatcher.d.ts +2 -0
- package/dist/runtime/event-dispatcher.d.ts.map +1 -0
- package/dist/runtime/event-dispatcher.js +2 -0
- package/dist/runtime/event-dispatcher.js.map +1 -0
- package/dist/runtime/event-server-snapshot-reader.d.ts +2 -0
- package/dist/runtime/event-server-snapshot-reader.d.ts.map +1 -0
- package/dist/runtime/event-server-snapshot-reader.js +2 -0
- package/dist/runtime/event-server-snapshot-reader.js.map +1 -0
- package/dist/runtime/event-server-sse.d.ts +2 -0
- package/dist/runtime/event-server-sse.d.ts.map +1 -0
- package/dist/runtime/event-server-sse.js +2 -0
- package/dist/runtime/event-server-sse.js.map +1 -0
- package/dist/runtime/event-server.d.ts +1 -91
- package/dist/runtime/event-server.d.ts.map +1 -1
- package/dist/runtime/event-server.js +1 -698
- package/dist/runtime/event-server.js.map +1 -1
- package/dist/runtime/executor/loop-supervisor.d.ts +9 -5
- package/dist/runtime/executor/loop-supervisor.d.ts.map +1 -1
- package/dist/runtime/executor/loop-supervisor.js +59 -76
- package/dist/runtime/executor/loop-supervisor.js.map +1 -1
- package/dist/runtime/gateway/http-channel-adapter.d.ts +1 -1
- package/dist/runtime/plugin-loader.d.ts +1 -1
- package/dist/runtime/queue/index.d.ts +0 -4
- package/dist/runtime/queue/index.d.ts.map +1 -1
- package/dist/runtime/queue/index.js +0 -2
- package/dist/runtime/queue/index.js.map +1 -1
- package/dist/runtime/queue/journal-backed-queue.d.ts.map +1 -1
- package/dist/runtime/queue/journal-backed-queue.js +2 -0
- package/dist/runtime/queue/journal-backed-queue.js.map +1 -1
- package/dist/runtime/schedule/engine-layers.d.ts +44 -0
- package/dist/runtime/schedule/engine-layers.d.ts.map +1 -0
- package/dist/runtime/schedule/engine-layers.js +433 -0
- package/dist/runtime/schedule/engine-layers.js.map +1 -0
- package/dist/runtime/schedule/engine.d.ts +82 -0
- package/dist/runtime/schedule/engine.d.ts.map +1 -0
- package/dist/runtime/schedule/engine.js +480 -0
- package/dist/runtime/schedule/engine.js.map +1 -0
- package/dist/runtime/schedule/index.d.ts +5 -0
- package/dist/runtime/schedule/index.d.ts.map +1 -0
- package/dist/runtime/schedule/index.js +5 -0
- package/dist/runtime/schedule/index.js.map +1 -0
- package/dist/runtime/schedule/presets.d.ts +536 -0
- package/dist/runtime/schedule/presets.d.ts.map +1 -0
- package/dist/runtime/schedule/presets.js +166 -0
- package/dist/runtime/schedule/presets.js.map +1 -0
- package/dist/runtime/schedule/source.d.ts +65 -0
- package/dist/runtime/schedule/source.d.ts.map +1 -0
- package/dist/runtime/schedule/source.js +16 -0
- package/dist/runtime/schedule/source.js.map +1 -0
- package/dist/runtime/schedule-engine-layers.d.ts +1 -43
- package/dist/runtime/schedule-engine-layers.d.ts.map +1 -1
- package/dist/runtime/schedule-engine-layers.js +1 -432
- package/dist/runtime/schedule-engine-layers.js.map +1 -1
- package/dist/runtime/schedule-engine.d.ts +1 -81
- package/dist/runtime/schedule-engine.d.ts.map +1 -1
- package/dist/runtime/schedule-engine.js +1 -479
- package/dist/runtime/schedule-engine.js.map +1 -1
- package/dist/runtime/schedule-presets.d.ts +1 -535
- package/dist/runtime/schedule-presets.d.ts.map +1 -1
- package/dist/runtime/schedule-presets.js +1 -165
- package/dist/runtime/schedule-presets.js.map +1 -1
- package/dist/runtime/schedule-source.d.ts +1 -64
- package/dist/runtime/schedule-source.d.ts.map +1 -1
- package/dist/runtime/schedule-source.js +1 -15
- package/dist/runtime/schedule-source.js.map +1 -1
- package/dist/runtime/types/daemon.d.ts.map +1 -1
- package/dist/runtime/types/daemon.js +2 -1
- package/dist/runtime/types/daemon.js.map +1 -1
- package/dist/runtime/watchdog.d.ts +44 -0
- package/dist/runtime/watchdog.d.ts.map +1 -0
- package/dist/runtime/watchdog.js +185 -0
- package/dist/runtime/watchdog.js.map +1 -0
- package/dist/tools/builtin/index.d.ts +1 -1
- package/dist/tools/schedule/CreateScheduleTool/CreateScheduleTool.d.ts +1 -1
- package/dist/tools/schedule/CreateScheduleTool/CreateScheduleTool.js +1 -1
- package/dist/tools/schedule/GetScheduleTool/GetScheduleTool.d.ts +1 -1
- package/dist/tools/schedule/ListSchedulesTool/ListSchedulesTool.d.ts +1 -1
- package/dist/tools/schedule/PauseScheduleTool/PauseScheduleTool.d.ts +1 -1
- package/dist/tools/schedule/RemoveScheduleTool/RemoveScheduleTool.d.ts +1 -1
- package/dist/tools/schedule/ResumeScheduleTool/ResumeScheduleTool.d.ts +1 -1
- package/dist/tools/schedule/UpdateScheduleTool/UpdateScheduleTool.d.ts +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,875 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { EventServer } from "../event/server.js";
|
|
3
|
+
import { DaemonConfigSchema, DaemonStateSchema } from "../../base/types/daemon.js";
|
|
4
|
+
import { generateCronEntry } from "./signals.js";
|
|
5
|
+
import { rotateDaemonLog, calculateAdaptiveInterval as calcAdaptiveInterval } from "./health.js";
|
|
6
|
+
import { HttpChannelAdapter } from "../gateway/index.js";
|
|
7
|
+
import { PulSeedEventSchema } from "../../base/types/drive.js";
|
|
8
|
+
import { LoopSupervisor } from "../executor/index.js";
|
|
9
|
+
import { ApprovalStore, OutboxStore, RuntimeHealthStore, createRuntimeStorePaths } from "../store/index.js";
|
|
10
|
+
import { LeaderLockManager } from "../leader-lock-manager.js";
|
|
11
|
+
import { GoalLeaseManager } from "../goal-lease-manager.js";
|
|
12
|
+
import { JournalBackedQueue } from "../queue/journal-backed-queue.js";
|
|
13
|
+
import { QueueClaimSweeper } from "../queue/queue-claim-sweeper.js";
|
|
14
|
+
import { ApprovalBroker } from "../approval-broker.js";
|
|
15
|
+
import { CommandDispatcher } from "../command-dispatcher.js";
|
|
16
|
+
import { EventDispatcher } from "../event/dispatcher.js";
|
|
17
|
+
import { RuntimeOwnershipCoordinator, } from "./runtime-ownership.js";
|
|
18
|
+
import { ProcessShutdownCoordinator, startDaemonStatusHeartbeat, } from "./runner-lifecycle.js";
|
|
19
|
+
import { checkCrashRecoveryMarker, cleanupDaemonRun, collectGoalCycleScheduleSnapshot, deleteShutdownMarkerFile, determineActiveGoalsForCycle, expireOldCronTasks, getMaxGapScoreForGoals, getNextIntervalForGoals, processCronTasksForDaemon, processScheduleEntriesForDaemon, readShutdownMarkerFile, restoreInterruptedGoals, runProactiveMaintenance, runSupervisorMaintenanceCycleForDaemon, saveDaemonStateFile, writeChatMessageEvent, writeShutdownMarkerFile, } from "./index.js";
|
|
20
|
+
// Re-exports for callers that imported these from daemon-runner
|
|
21
|
+
export { generateCronEntry } from "./signals.js";
|
|
22
|
+
export { rotateDaemonLog, calculateAdaptiveInterval } from "./health.js";
|
|
23
|
+
const RUNTIME_LEADER_LEASE_MS = 30_000;
|
|
24
|
+
const RUNTIME_LEADER_HEARTBEAT_MS = 10_000;
|
|
25
|
+
export class DaemonRunner {
|
|
26
|
+
coreLoop;
|
|
27
|
+
driveSystem;
|
|
28
|
+
stateManager;
|
|
29
|
+
pidManager;
|
|
30
|
+
logger;
|
|
31
|
+
config;
|
|
32
|
+
running = false;
|
|
33
|
+
shuttingDown = false;
|
|
34
|
+
state;
|
|
35
|
+
baseDir;
|
|
36
|
+
logDir;
|
|
37
|
+
logPath;
|
|
38
|
+
eventServer;
|
|
39
|
+
approvalFn;
|
|
40
|
+
sleepAbortController = null;
|
|
41
|
+
currentGoalIds = [];
|
|
42
|
+
currentLoopIndex = 0;
|
|
43
|
+
lastProactiveTickAt = 0;
|
|
44
|
+
llmClient;
|
|
45
|
+
reportingEngine;
|
|
46
|
+
cronScheduler;
|
|
47
|
+
scheduleEngine;
|
|
48
|
+
consecutiveIdleCycles = 0;
|
|
49
|
+
gateway;
|
|
50
|
+
supervisor = null;
|
|
51
|
+
cronScheduleInterval = null;
|
|
52
|
+
shutdownResolve = null;
|
|
53
|
+
shutdownCoordinator = null;
|
|
54
|
+
stopStatusHeartbeat = null;
|
|
55
|
+
deps;
|
|
56
|
+
runtimeRoot = null;
|
|
57
|
+
approvalStore = null;
|
|
58
|
+
outboxStore = null;
|
|
59
|
+
runtimeHealthStore = null;
|
|
60
|
+
leaderLockManager = null;
|
|
61
|
+
goalLeaseManager = null;
|
|
62
|
+
journalQueue = null;
|
|
63
|
+
queueClaimSweeper = null;
|
|
64
|
+
approvalBroker = null;
|
|
65
|
+
commandDispatcher = null;
|
|
66
|
+
eventDispatcher = null;
|
|
67
|
+
runtimeOwnership;
|
|
68
|
+
constructor(deps) {
|
|
69
|
+
this.deps = deps;
|
|
70
|
+
this.coreLoop = deps.coreLoop;
|
|
71
|
+
this.driveSystem = deps.driveSystem;
|
|
72
|
+
this.stateManager = deps.stateManager;
|
|
73
|
+
this.pidManager = deps.pidManager;
|
|
74
|
+
this.logger = deps.logger;
|
|
75
|
+
this.eventServer = deps.eventServer;
|
|
76
|
+
this.llmClient = deps.llmClient;
|
|
77
|
+
this.reportingEngine = deps.reportingEngine;
|
|
78
|
+
this.cronScheduler = deps.cronScheduler;
|
|
79
|
+
this.scheduleEngine = deps.scheduleEngine;
|
|
80
|
+
this.gateway = deps.gateway;
|
|
81
|
+
this.supervisor = deps.supervisor ?? null;
|
|
82
|
+
this.lastProactiveTickAt = Date.now();
|
|
83
|
+
// Parse config with defaults via DaemonConfigSchema.parse()
|
|
84
|
+
this.config = DaemonConfigSchema.parse(deps.config ?? {});
|
|
85
|
+
// Resolve base directory from stateManager
|
|
86
|
+
this.baseDir = this.stateManager.getBaseDir();
|
|
87
|
+
// Pre-compute log paths used by rotateLog
|
|
88
|
+
this.logDir = path.join(this.baseDir, this.config.log_dir);
|
|
89
|
+
this.logPath = path.join(this.logDir, "pulseed.log");
|
|
90
|
+
this.runtimeRoot = this.resolveRuntimeRoot();
|
|
91
|
+
const runtimePaths = createRuntimeStorePaths(this.runtimeRoot);
|
|
92
|
+
this.approvalStore = new ApprovalStore(runtimePaths);
|
|
93
|
+
this.outboxStore = new OutboxStore(runtimePaths);
|
|
94
|
+
this.runtimeHealthStore = new RuntimeHealthStore(runtimePaths);
|
|
95
|
+
this.leaderLockManager = new LeaderLockManager(this.runtimeRoot);
|
|
96
|
+
this.goalLeaseManager = new GoalLeaseManager(this.runtimeRoot);
|
|
97
|
+
this.approvalBroker = new ApprovalBroker({
|
|
98
|
+
store: this.approvalStore,
|
|
99
|
+
logger: this.logger,
|
|
100
|
+
});
|
|
101
|
+
this.journalQueue = new JournalBackedQueue({
|
|
102
|
+
journalPath: path.join(this.runtimeRoot, "queue.json"),
|
|
103
|
+
});
|
|
104
|
+
this.queueClaimSweeper = new QueueClaimSweeper({
|
|
105
|
+
queue: this.journalQueue,
|
|
106
|
+
});
|
|
107
|
+
this.runtimeOwnership = new RuntimeOwnershipCoordinator({
|
|
108
|
+
runtimeRoot: this.runtimeRoot,
|
|
109
|
+
logger: this.logger,
|
|
110
|
+
approvalStore: this.approvalStore,
|
|
111
|
+
outboxStore: this.outboxStore,
|
|
112
|
+
runtimeHealthStore: this.runtimeHealthStore,
|
|
113
|
+
leaderLockManager: this.leaderLockManager,
|
|
114
|
+
onLeadershipLost: (reason) => this.failRuntimeLeadership(reason),
|
|
115
|
+
});
|
|
116
|
+
// Initialize daemon state
|
|
117
|
+
this.state = DaemonStateSchema.parse({
|
|
118
|
+
pid: process.pid,
|
|
119
|
+
started_at: new Date().toISOString(),
|
|
120
|
+
last_loop_at: null,
|
|
121
|
+
loop_count: 0,
|
|
122
|
+
active_goals: [],
|
|
123
|
+
status: "stopped",
|
|
124
|
+
crash_count: 0,
|
|
125
|
+
last_error: null,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
resolveRuntimeRoot() {
|
|
129
|
+
const configuredRoot = this.config.runtime_root;
|
|
130
|
+
if (!configuredRoot || configuredRoot.trim() === "") {
|
|
131
|
+
return path.join(this.baseDir, "runtime");
|
|
132
|
+
}
|
|
133
|
+
return path.isAbsolute(configuredRoot)
|
|
134
|
+
? configuredRoot
|
|
135
|
+
: path.resolve(this.baseDir, configuredRoot);
|
|
136
|
+
}
|
|
137
|
+
// ─── Public API ───
|
|
138
|
+
/**
|
|
139
|
+
* Start daemon loop for given goals.
|
|
140
|
+
* Throws if daemon is already running.
|
|
141
|
+
*/
|
|
142
|
+
async start(goalIds) {
|
|
143
|
+
let startupReady = false;
|
|
144
|
+
try {
|
|
145
|
+
// 2. Rotate log if needed, then check for crash recovery marker
|
|
146
|
+
await this.rotateLog();
|
|
147
|
+
await this.checkCrashRecovery();
|
|
148
|
+
await this.initializeRuntimeFoundation();
|
|
149
|
+
await this.acquireRuntimeLeadership();
|
|
150
|
+
// 2c. Start EventServer (always-on) and file watcher
|
|
151
|
+
if (!this.eventServer) {
|
|
152
|
+
const esPort = this.config.event_server_port ?? 41700;
|
|
153
|
+
this.eventServer = new EventServer(this.driveSystem, {
|
|
154
|
+
port: esPort,
|
|
155
|
+
stateManager: this.stateManager,
|
|
156
|
+
outboxStore: this.outboxStore ?? undefined,
|
|
157
|
+
}, this.logger);
|
|
158
|
+
}
|
|
159
|
+
if (this.outboxStore) {
|
|
160
|
+
this.eventServer.setOutboxStore?.(this.outboxStore);
|
|
161
|
+
}
|
|
162
|
+
this.eventServer.setActiveWorkersProvider?.(() => {
|
|
163
|
+
const workers = this.supervisor?.getState().workers ?? [];
|
|
164
|
+
return workers
|
|
165
|
+
.filter((worker) => worker.goalId !== null)
|
|
166
|
+
.map((worker) => ({
|
|
167
|
+
worker_id: worker.workerId,
|
|
168
|
+
goal_id: worker.goalId,
|
|
169
|
+
started_at: worker.startedAt,
|
|
170
|
+
iterations: worker.iterations,
|
|
171
|
+
}));
|
|
172
|
+
});
|
|
173
|
+
if (this.approvalBroker) {
|
|
174
|
+
this.approvalBroker.setBroadcast((eventType, data) => {
|
|
175
|
+
void this.eventServer?.broadcast?.(eventType, data);
|
|
176
|
+
});
|
|
177
|
+
this.eventServer.setApprovalBroker?.(this.approvalBroker);
|
|
178
|
+
}
|
|
179
|
+
this.eventServer.setCommandEnvelopeHook?.(async (envelope) => this.handleInboundEnvelope(envelope));
|
|
180
|
+
if (this.gateway) {
|
|
181
|
+
// Phase A: Route through Gateway → Envelope → writeEvent
|
|
182
|
+
const httpAdapter = new HttpChannelAdapter(this.eventServer);
|
|
183
|
+
this.gateway.registerAdapter(httpAdapter);
|
|
184
|
+
this.gateway.onEnvelope(async (envelope) => this.handleInboundEnvelope(envelope));
|
|
185
|
+
// Wire onHighPriority to abort sleep — done via the abortSleep() public method.
|
|
186
|
+
// Callers who construct buses should pass: onHighPriority: () => daemon.abortSleep()
|
|
187
|
+
// The daemon provides abortSleep() below for this purpose.
|
|
188
|
+
await this.gateway.start();
|
|
189
|
+
this.logger.info("Gateway started with HTTP adapter", { port: this.eventServer.getPort() });
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
await this.eventServer.start();
|
|
193
|
+
this.eventServer.startFileWatcher();
|
|
194
|
+
this.logger.info("EventServer started", { port: this.eventServer.getPort() });
|
|
195
|
+
}
|
|
196
|
+
// Wire approval bridge if not already provided
|
|
197
|
+
if (!this.approvalFn && this.eventServer) {
|
|
198
|
+
const es = this.eventServer;
|
|
199
|
+
this.approvalFn = async (task) => {
|
|
200
|
+
const goalId = String(task["goal_id"] ?? "unknown");
|
|
201
|
+
const description = String(task["description"] ?? "");
|
|
202
|
+
const action = String(task["action"] ?? "");
|
|
203
|
+
const taskId = String(task["id"] ?? "");
|
|
204
|
+
if (this.reportingEngine) {
|
|
205
|
+
try {
|
|
206
|
+
await this.reportingEngine.generateNotification("approval_required", {
|
|
207
|
+
goalId,
|
|
208
|
+
message: description || action || taskId || "Task approval required",
|
|
209
|
+
details: [`task_id: ${taskId || "(none)"}`, `action: ${action || "(none)"}`].join("\n"),
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
catch (err) {
|
|
213
|
+
this.logger.warn("Approval notification dispatch failed", {
|
|
214
|
+
goalId,
|
|
215
|
+
error: err instanceof Error ? err.message : String(err),
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return es.requestApproval(goalId, {
|
|
220
|
+
id: taskId,
|
|
221
|
+
description,
|
|
222
|
+
action,
|
|
223
|
+
});
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
this.stopStatusHeartbeat = startDaemonStatusHeartbeat({
|
|
227
|
+
eventServer: this.eventServer,
|
|
228
|
+
getSnapshot: () => ({
|
|
229
|
+
status: this.state.status,
|
|
230
|
+
activeGoals: this.state.active_goals,
|
|
231
|
+
loopCount: this.state.loop_count,
|
|
232
|
+
startedAt: this.state.started_at,
|
|
233
|
+
}),
|
|
234
|
+
});
|
|
235
|
+
this.driveSystem.startWatcher((event) => this.onEventReceived(event));
|
|
236
|
+
this.shuttingDown = false;
|
|
237
|
+
const shutdownTimeout = this.config.crash_recovery.graceful_shutdown_timeout_ms ?? 30_000;
|
|
238
|
+
this.shutdownCoordinator = new ProcessShutdownCoordinator({
|
|
239
|
+
logger: this.logger,
|
|
240
|
+
gracefulShutdownTimeoutMs: shutdownTimeout,
|
|
241
|
+
onShutdown: () => this.beginGracefulShutdown(),
|
|
242
|
+
onForceStop: () => {
|
|
243
|
+
this.running = false;
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
this.shutdownCoordinator.activate();
|
|
247
|
+
// 4. Restore state from previous interrupted run
|
|
248
|
+
const mergedGoalIds = await this.restoreState(goalIds);
|
|
249
|
+
// 5. Save initial daemon state
|
|
250
|
+
this.running = true;
|
|
251
|
+
this.currentGoalIds = mergedGoalIds;
|
|
252
|
+
this.currentLoopIndex = 0;
|
|
253
|
+
this.state = DaemonStateSchema.parse({
|
|
254
|
+
pid: process.pid,
|
|
255
|
+
started_at: new Date().toISOString(),
|
|
256
|
+
last_loop_at: null,
|
|
257
|
+
loop_count: 0,
|
|
258
|
+
active_goals: mergedGoalIds,
|
|
259
|
+
status: "running",
|
|
260
|
+
crash_count: 0,
|
|
261
|
+
last_error: null,
|
|
262
|
+
});
|
|
263
|
+
await this.saveDaemonState();
|
|
264
|
+
// 5b. Write "running" shutdown marker (crash detection on next startup)
|
|
265
|
+
await this.writeShutdownMarker({
|
|
266
|
+
goal_ids: mergedGoalIds,
|
|
267
|
+
loop_index: 0,
|
|
268
|
+
timestamp: new Date().toISOString(),
|
|
269
|
+
reason: "startup",
|
|
270
|
+
state: "running",
|
|
271
|
+
});
|
|
272
|
+
// 6. Log start
|
|
273
|
+
this.logger.info("Daemon started", {
|
|
274
|
+
pid: process.pid,
|
|
275
|
+
goals: mergedGoalIds,
|
|
276
|
+
check_interval_ms: this.config.check_interval_ms,
|
|
277
|
+
});
|
|
278
|
+
const sweepResult = this.queueClaimSweeper?.sweep();
|
|
279
|
+
if (sweepResult && (sweepResult.reclaimed > 0 || sweepResult.deadlettered > 0)) {
|
|
280
|
+
this.logger.info("Recovered stale runtime claims on startup", {
|
|
281
|
+
reclaimed: sweepResult.reclaimed,
|
|
282
|
+
deadlettered: sweepResult.deadlettered,
|
|
283
|
+
expiredClaimTokens: sweepResult.expiredClaimTokens,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
this.queueClaimSweeper?.start();
|
|
287
|
+
// 7. Create supervisor if not already provided.
|
|
288
|
+
if (!this.supervisor) {
|
|
289
|
+
const factory = this.deps.coreLoopFactory ?? (() => this.coreLoop);
|
|
290
|
+
this.supervisor = new LoopSupervisor({
|
|
291
|
+
coreLoopFactory: factory,
|
|
292
|
+
journalQueue: this.journalQueue,
|
|
293
|
+
goalLeaseManager: this.goalLeaseManager,
|
|
294
|
+
driveSystem: this.driveSystem,
|
|
295
|
+
stateManager: this.stateManager,
|
|
296
|
+
logger: this.logger,
|
|
297
|
+
onGoalComplete: async (goalId, result) => this.handleGoalCompletion(goalId, result),
|
|
298
|
+
onEscalation: (goalId, crashCount, lastError) => {
|
|
299
|
+
this.logger.error(`Goal ${goalId} suspended after ${crashCount} crashes: ${lastError}`);
|
|
300
|
+
},
|
|
301
|
+
}, { iterationsPerCycle: this.config.iterations_per_cycle });
|
|
302
|
+
}
|
|
303
|
+
if (!this.eventDispatcher) {
|
|
304
|
+
this.eventDispatcher = new EventDispatcher({
|
|
305
|
+
journalQueue: this.journalQueue,
|
|
306
|
+
logger: this.logger,
|
|
307
|
+
onGoalActivate: async (goalId) => this.handleGoalStartCommand(goalId),
|
|
308
|
+
onExternalEvent: async (event) => this.driveSystem.writeEvent(PulSeedEventSchema.parse(event)),
|
|
309
|
+
onCronTaskDue: async (task) => this.handleCronTaskDue(task.id),
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
if (!this.commandDispatcher) {
|
|
313
|
+
this.commandDispatcher = new CommandDispatcher({
|
|
314
|
+
journalQueue: this.journalQueue,
|
|
315
|
+
logger: this.logger,
|
|
316
|
+
onGoalStart: async (goalId) => this.handleGoalStartCommand(goalId),
|
|
317
|
+
onGoalStop: async (goalId) => this.handleGoalStopCommand(goalId),
|
|
318
|
+
onChatMessage: async (goalId, message) => this.handleChatMessageCommand(goalId, message),
|
|
319
|
+
onApprovalResponse: async (goalId, requestId, approved) => this.handleApprovalResponseCommand(goalId, requestId, approved),
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
await this.saveRuntimeHealthSnapshot(this.supervisor
|
|
323
|
+
? "execution_ownership_durable"
|
|
324
|
+
: "foundation_only", {
|
|
325
|
+
gateway: this.gateway || this.eventServer ? "ok" : "degraded",
|
|
326
|
+
queue: "ok",
|
|
327
|
+
leases: "ok",
|
|
328
|
+
approval: "ok",
|
|
329
|
+
outbox: "ok",
|
|
330
|
+
supervisor: this.supervisor ? "ok" : "degraded",
|
|
331
|
+
});
|
|
332
|
+
// 8. Run main loop — supervisor mode when supervisor is injected via deps,
|
|
333
|
+
// fallback to sequential runLoop otherwise.
|
|
334
|
+
startupReady = true;
|
|
335
|
+
let cleanupHandled = false;
|
|
336
|
+
try {
|
|
337
|
+
await this.eventDispatcher?.start();
|
|
338
|
+
await this.commandDispatcher?.start();
|
|
339
|
+
if (this.supervisor) {
|
|
340
|
+
// Supervisor handles goal execution; cron/schedule must also run in this mode.
|
|
341
|
+
await this.supervisor.start(mergedGoalIds);
|
|
342
|
+
const maintenanceIntervalMs = this.config.check_interval_ms;
|
|
343
|
+
await this.runSupervisorMaintenanceCycle();
|
|
344
|
+
this.cronScheduleInterval = setInterval(async () => {
|
|
345
|
+
if (this.shuttingDown)
|
|
346
|
+
return;
|
|
347
|
+
await this.runSupervisorMaintenanceCycle();
|
|
348
|
+
}, maintenanceIntervalMs);
|
|
349
|
+
// Block until stop() is called.
|
|
350
|
+
await new Promise((resolve) => {
|
|
351
|
+
this.shutdownResolve = resolve;
|
|
352
|
+
// If already stopped before we get here, resolve immediately.
|
|
353
|
+
if (!this.running)
|
|
354
|
+
resolve();
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
// Fallback: sequential mode
|
|
359
|
+
await this.runLoop();
|
|
360
|
+
cleanupHandled = true;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
finally {
|
|
364
|
+
this.queueClaimSweeper?.stop();
|
|
365
|
+
this.shutdownCoordinator?.dispose();
|
|
366
|
+
this.shutdownCoordinator = null;
|
|
367
|
+
this.stopStatusHeartbeat?.();
|
|
368
|
+
this.stopStatusHeartbeat = null;
|
|
369
|
+
if (this.cronScheduleInterval !== null) {
|
|
370
|
+
clearInterval(this.cronScheduleInterval);
|
|
371
|
+
this.cronScheduleInterval = null;
|
|
372
|
+
}
|
|
373
|
+
await this.supervisor?.shutdown();
|
|
374
|
+
await this.eventDispatcher?.shutdown();
|
|
375
|
+
await this.commandDispatcher?.shutdown();
|
|
376
|
+
this.driveSystem.stopWatcher();
|
|
377
|
+
if (this.gateway) {
|
|
378
|
+
await this.gateway.stop();
|
|
379
|
+
this.logger.info("Gateway stopped");
|
|
380
|
+
}
|
|
381
|
+
else if (this.eventServer) {
|
|
382
|
+
this.eventServer.stopFileWatcher();
|
|
383
|
+
await this.eventServer.stop();
|
|
384
|
+
this.logger.info("EventServer stopped");
|
|
385
|
+
}
|
|
386
|
+
if (!cleanupHandled) {
|
|
387
|
+
await this.cleanup();
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
catch (err) {
|
|
392
|
+
if (!startupReady) {
|
|
393
|
+
await this.releaseStartupOwnership();
|
|
394
|
+
}
|
|
395
|
+
throw err;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
async initializeRuntimeFoundation() {
|
|
399
|
+
await this.runtimeOwnership.initializeFoundation();
|
|
400
|
+
}
|
|
401
|
+
async saveRuntimeHealthSnapshot(phase, components) {
|
|
402
|
+
await this.runtimeOwnership.saveRuntimeHealthSnapshot(phase, components);
|
|
403
|
+
}
|
|
404
|
+
async acquireRuntimeLeadership() {
|
|
405
|
+
await this.runtimeOwnership.acquireLeadership(RUNTIME_LEADER_LEASE_MS, RUNTIME_LEADER_HEARTBEAT_MS);
|
|
406
|
+
}
|
|
407
|
+
failRuntimeLeadership(reason) {
|
|
408
|
+
if (this.state.status === "crashed") {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
this.logger.error("Lost runtime leadership; stopping daemon", {
|
|
412
|
+
error: reason,
|
|
413
|
+
});
|
|
414
|
+
this.state.status = "crashed";
|
|
415
|
+
this.state.last_error = reason;
|
|
416
|
+
this.running = false;
|
|
417
|
+
this.shuttingDown = true;
|
|
418
|
+
this.shutdownResolve?.();
|
|
419
|
+
this.sleepAbortController?.abort();
|
|
420
|
+
void this.saveDaemonState();
|
|
421
|
+
}
|
|
422
|
+
async releaseStartupOwnership() {
|
|
423
|
+
await this.runtimeOwnership.releaseLeadership();
|
|
424
|
+
}
|
|
425
|
+
/** Expose approvalFn for callers (e.g. cmdStart) to wire into TaskLifecycle */
|
|
426
|
+
getApprovalFn() {
|
|
427
|
+
return this.approvalFn;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Abort the current sleep cycle immediately.
|
|
431
|
+
*/
|
|
432
|
+
abortSleep() {
|
|
433
|
+
this.sleepAbortController?.abort();
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Signal daemon to stop after current iteration completes.
|
|
437
|
+
* Saves interrupted_goals so they can be restored on next start.
|
|
438
|
+
*/
|
|
439
|
+
stop() {
|
|
440
|
+
this.running = false;
|
|
441
|
+
this.shutdownResolve?.();
|
|
442
|
+
this.sleepAbortController?.abort();
|
|
443
|
+
this.state.status = "stopping";
|
|
444
|
+
// Save current active_goals as interrupted_goals for state restoration
|
|
445
|
+
this.state.interrupted_goals = [...this.state.active_goals];
|
|
446
|
+
// Do NOT persist here — cleanup() will save the final state after the loop exits.
|
|
447
|
+
// Calling saveDaemonState() here would race with cleanup()'s save and corrupt the file.
|
|
448
|
+
this.logger.info("Stop requested — daemon will stop after current iteration");
|
|
449
|
+
}
|
|
450
|
+
// ─── Private: Main Loop ───
|
|
451
|
+
/**
|
|
452
|
+
* Main daemon loop. Runs until this.running is false or a critical error occurs.
|
|
453
|
+
*/
|
|
454
|
+
async runLoop() {
|
|
455
|
+
while (this.running && !this.shuttingDown) {
|
|
456
|
+
try {
|
|
457
|
+
const goalIds = [...this.currentGoalIds];
|
|
458
|
+
const cycleSnapshot = await this.collectGoalCycleSnapshot(goalIds);
|
|
459
|
+
// 1. Determine which goals need activation
|
|
460
|
+
const activeGoals = await this.determineActiveGoals(goalIds, cycleSnapshot);
|
|
461
|
+
if (activeGoals.length === 0) {
|
|
462
|
+
this.logger.info("No goals need activation this cycle", {
|
|
463
|
+
checked: goalIds.length,
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
// 2. Execute loop for each active goal
|
|
467
|
+
for (const goalId of activeGoals) {
|
|
468
|
+
if (!this.running)
|
|
469
|
+
break;
|
|
470
|
+
this.logger.info(`Running loop for goal: ${goalId}`);
|
|
471
|
+
try {
|
|
472
|
+
const iterationsPerCycle = this.config.iterations_per_cycle ?? 1;
|
|
473
|
+
const result = await this.coreLoop.run(goalId, { maxIterations: iterationsPerCycle });
|
|
474
|
+
this.state.loop_count++;
|
|
475
|
+
this.currentLoopIndex = this.state.loop_count;
|
|
476
|
+
this.state.last_loop_at = new Date().toISOString();
|
|
477
|
+
this.logger.info(`Loop completed for goal: ${goalId}`, {
|
|
478
|
+
status: result.finalStatus,
|
|
479
|
+
iterations: result.totalIterations,
|
|
480
|
+
});
|
|
481
|
+
if (this.eventServer) {
|
|
482
|
+
const goal = await this.stateManager.loadGoal(goalId).catch(() => null);
|
|
483
|
+
void this.eventServer.broadcast?.("iteration_complete", {
|
|
484
|
+
goalId,
|
|
485
|
+
loopCount: this.state.loop_count,
|
|
486
|
+
status: goal?.status ?? "unknown",
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
catch (err) {
|
|
491
|
+
this.handleLoopError(goalId, err);
|
|
492
|
+
}
|
|
493
|
+
// Bail out of goal iteration if crash limit exceeded
|
|
494
|
+
if (!this.running)
|
|
495
|
+
break;
|
|
496
|
+
}
|
|
497
|
+
// 3. Save state
|
|
498
|
+
await this.saveDaemonState();
|
|
499
|
+
if (this.eventServer) {
|
|
500
|
+
void this.eventServer.broadcast?.("daemon_status", {
|
|
501
|
+
status: this.state.status,
|
|
502
|
+
activeGoals: this.state.active_goals,
|
|
503
|
+
loopCount: this.state.loop_count,
|
|
504
|
+
lastLoopAt: this.state.last_loop_at,
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
// 3b. Process due cron-scheduled tasks
|
|
508
|
+
await this.processCronTasks();
|
|
509
|
+
// 3b2. Process schedule engine entries
|
|
510
|
+
await this.processScheduleEntries();
|
|
511
|
+
// 3c. Expire old cron tasks periodically (every 100 cycles)
|
|
512
|
+
if (this.state.loop_count > 0 && this.state.loop_count % 100 === 0) {
|
|
513
|
+
await this.expireCronTasks();
|
|
514
|
+
}
|
|
515
|
+
// 4. Proactive tick: fire every cycle (not only when idle) so long-running goals
|
|
516
|
+
// do not block proactive actions indefinitely.
|
|
517
|
+
if (this.running) {
|
|
518
|
+
await this.proactiveTick();
|
|
519
|
+
}
|
|
520
|
+
// 5. Track idle cycles for adaptive sleep
|
|
521
|
+
if (activeGoals.length > 0) {
|
|
522
|
+
this.consecutiveIdleCycles = 0;
|
|
523
|
+
}
|
|
524
|
+
else {
|
|
525
|
+
this.consecutiveIdleCycles++;
|
|
526
|
+
}
|
|
527
|
+
// 6. Wait for next check interval
|
|
528
|
+
if (this.running) {
|
|
529
|
+
const baseIntervalMs = this.getNextInterval(goalIds);
|
|
530
|
+
const maxGapScore = await this.getMaxGapScore(goalIds, cycleSnapshot);
|
|
531
|
+
const intervalMs = this.calculateAdaptiveInterval(baseIntervalMs, activeGoals.length, maxGapScore, this.consecutiveIdleCycles);
|
|
532
|
+
this.logger.info(`Sleeping for ${intervalMs}ms until next check`);
|
|
533
|
+
await this.sleep(intervalMs);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
catch (err) {
|
|
537
|
+
await this.handleCriticalError(err);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
// Cleanup after loop exits
|
|
541
|
+
await this.cleanup();
|
|
542
|
+
}
|
|
543
|
+
// ─── Private: Goal Activation ───
|
|
544
|
+
/**
|
|
545
|
+
* Determine which goals should be activated this cycle.
|
|
546
|
+
* Uses DriveSystem.shouldActivate() for each goal, then sorts by priority.
|
|
547
|
+
*/
|
|
548
|
+
async determineActiveGoals(goalIds, snapshot) {
|
|
549
|
+
return determineActiveGoalsForCycle(this.driveSystem, goalIds, snapshot);
|
|
550
|
+
}
|
|
551
|
+
async collectGoalCycleSnapshot(goalIds) {
|
|
552
|
+
return collectGoalCycleScheduleSnapshot(this.driveSystem, goalIds);
|
|
553
|
+
}
|
|
554
|
+
// ─── Private: Interval Calculation ───
|
|
555
|
+
/**
|
|
556
|
+
* Calculate the next check interval in milliseconds.
|
|
557
|
+
* Uses per-goal override from config.goal_intervals if configured,
|
|
558
|
+
* otherwise falls back to config.check_interval_ms.
|
|
559
|
+
* Returns the minimum interval across all goals (so the daemon checks
|
|
560
|
+
* as soon as the earliest goal is due).
|
|
561
|
+
*/
|
|
562
|
+
getNextInterval(goalIds) {
|
|
563
|
+
return getNextIntervalForGoals(this.config, goalIds);
|
|
564
|
+
}
|
|
565
|
+
// ─── Private: Error Handling ───
|
|
566
|
+
/**
|
|
567
|
+
* Handle a non-critical loop error for a single goal.
|
|
568
|
+
* Increments crash_count and stops daemon if max_retries exceeded.
|
|
569
|
+
*/
|
|
570
|
+
handleLoopError(goalId, err) {
|
|
571
|
+
this.state.last_error = err instanceof Error ? err.message : String(err);
|
|
572
|
+
this.state.crash_count++;
|
|
573
|
+
this.logger.error(`Loop error for goal ${goalId}`, {
|
|
574
|
+
error: this.state.last_error,
|
|
575
|
+
crash_count: this.state.crash_count,
|
|
576
|
+
max_retries: this.config.crash_recovery.max_retries,
|
|
577
|
+
});
|
|
578
|
+
// If crash count exceeds max_retries, stop daemon
|
|
579
|
+
if (this.state.crash_count >= this.config.crash_recovery.max_retries) {
|
|
580
|
+
this.logger.error(`Max crash retries (${this.config.crash_recovery.max_retries}) exceeded, stopping daemon`);
|
|
581
|
+
this.running = false;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Handle a critical daemon-level error (outer loop catch).
|
|
586
|
+
* Marks state as crashed and stops the loop.
|
|
587
|
+
*/
|
|
588
|
+
async handleCriticalError(err) {
|
|
589
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
590
|
+
this.logger.error("Critical daemon error", { error: msg });
|
|
591
|
+
this.state.status = "crashed";
|
|
592
|
+
this.state.last_error = msg;
|
|
593
|
+
await this.saveDaemonState();
|
|
594
|
+
this.running = false;
|
|
595
|
+
}
|
|
596
|
+
// ─── Private: State Persistence ───
|
|
597
|
+
/**
|
|
598
|
+
* Save daemon state to {baseDir}/daemon-state.json atomically.
|
|
599
|
+
*/
|
|
600
|
+
async saveDaemonState() {
|
|
601
|
+
await saveDaemonStateFile(this.baseDir, this.state, this.logger);
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Restore state from a previous interrupted run.
|
|
605
|
+
* Merges interrupted_goals from daemon-state.json with the given goalIds (deduped).
|
|
606
|
+
* Returns the merged goal ID array.
|
|
607
|
+
*/
|
|
608
|
+
async restoreState(goalIds) {
|
|
609
|
+
return restoreInterruptedGoals(this.baseDir, goalIds, this.logger);
|
|
610
|
+
}
|
|
611
|
+
// ─── Private: Cleanup ───
|
|
612
|
+
/**
|
|
613
|
+
* Perform cleanup after the loop exits and write the final runtime health snapshot.
|
|
614
|
+
* Also writes "clean_shutdown" marker to enable crash-vs-clean detection on next startup.
|
|
615
|
+
*/
|
|
616
|
+
async cleanup() {
|
|
617
|
+
await cleanupDaemonRun({
|
|
618
|
+
baseDir: this.baseDir,
|
|
619
|
+
state: this.state,
|
|
620
|
+
currentGoalIds: this.currentGoalIds,
|
|
621
|
+
currentLoopIndex: this.currentLoopIndex,
|
|
622
|
+
runtimeOwnership: this.runtimeOwnership,
|
|
623
|
+
logger: this.logger,
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
beginGracefulShutdown() {
|
|
627
|
+
if (this.shuttingDown) {
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
this.shuttingDown = true;
|
|
631
|
+
this.logger.info("Shutting down gracefully...");
|
|
632
|
+
this.running = false;
|
|
633
|
+
this.state.status = "stopping";
|
|
634
|
+
this.state.interrupted_goals = [...this.state.active_goals];
|
|
635
|
+
this.shutdownResolve?.();
|
|
636
|
+
this.sleepAbortController?.abort();
|
|
637
|
+
}
|
|
638
|
+
// ─── Private: Cron Scheduler ───
|
|
639
|
+
/**
|
|
640
|
+
* Process due cron-scheduled tasks.
|
|
641
|
+
* Logs each task, executes based on type, and marks as fired.
|
|
642
|
+
*/
|
|
643
|
+
async processCronTasks() {
|
|
644
|
+
await processCronTasksForDaemon({
|
|
645
|
+
cronScheduler: this.cronScheduler,
|
|
646
|
+
logger: this.logger,
|
|
647
|
+
acceptRuntimeEnvelope: (envelope) => this.acceptRuntimeEnvelope(envelope),
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Process due schedule engine entries.
|
|
652
|
+
*/
|
|
653
|
+
async processScheduleEntries() {
|
|
654
|
+
await processScheduleEntriesForDaemon({
|
|
655
|
+
scheduleEngine: this.scheduleEngine,
|
|
656
|
+
logger: this.logger,
|
|
657
|
+
acceptRuntimeEnvelope: (envelope) => this.acceptRuntimeEnvelope(envelope),
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Expire old non-permanent cron tasks.
|
|
662
|
+
*/
|
|
663
|
+
async expireCronTasks() {
|
|
664
|
+
await expireOldCronTasks(this.cronScheduler, this.logger);
|
|
665
|
+
}
|
|
666
|
+
// ─── Private: Sleep ───
|
|
667
|
+
/**
|
|
668
|
+
* Sleep for the given number of milliseconds.
|
|
669
|
+
* Can be aborted early via sleepAbortController (e.g. when an event arrives).
|
|
670
|
+
*/
|
|
671
|
+
sleep(ms) {
|
|
672
|
+
this.sleepAbortController = new AbortController();
|
|
673
|
+
const abortController = this.sleepAbortController;
|
|
674
|
+
return new Promise((resolve) => {
|
|
675
|
+
const timer = setTimeout(resolve, ms);
|
|
676
|
+
abortController.signal.addEventListener("abort", () => {
|
|
677
|
+
clearTimeout(timer);
|
|
678
|
+
resolve();
|
|
679
|
+
});
|
|
680
|
+
}).finally(() => {
|
|
681
|
+
this.sleepAbortController = null;
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
// ─── Private: Event Handling ───
|
|
685
|
+
/**
|
|
686
|
+
* Called when a file-watcher event arrives from DriveSystem.
|
|
687
|
+
* Aborts the current sleep so the loop runs immediately.
|
|
688
|
+
*/
|
|
689
|
+
onEventReceived(event) {
|
|
690
|
+
this.logger.info("Event received, triggering immediate loop", {
|
|
691
|
+
event_type: event.type,
|
|
692
|
+
});
|
|
693
|
+
this.sleepAbortController?.abort();
|
|
694
|
+
}
|
|
695
|
+
acceptRuntimeEnvelope(envelope) {
|
|
696
|
+
if (!this.journalQueue)
|
|
697
|
+
return true;
|
|
698
|
+
const result = this.journalQueue.accept(envelope);
|
|
699
|
+
if (result.accepted) {
|
|
700
|
+
return true;
|
|
701
|
+
}
|
|
702
|
+
this.logger.info("Runtime journal skipped envelope", {
|
|
703
|
+
id: envelope.id,
|
|
704
|
+
name: envelope.name,
|
|
705
|
+
type: envelope.type,
|
|
706
|
+
duplicate: result.duplicate,
|
|
707
|
+
runtime_root: this.runtimeRoot,
|
|
708
|
+
});
|
|
709
|
+
return false;
|
|
710
|
+
}
|
|
711
|
+
async handleInboundEnvelope(envelope) {
|
|
712
|
+
if (!this.acceptRuntimeEnvelope(envelope)) {
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
async handleGoalStartCommand(goalId) {
|
|
717
|
+
if (!this.currentGoalIds.includes(goalId)) {
|
|
718
|
+
this.currentGoalIds.push(goalId);
|
|
719
|
+
}
|
|
720
|
+
this.state.active_goals = [...this.currentGoalIds];
|
|
721
|
+
await this.saveDaemonState();
|
|
722
|
+
this.supervisor?.activateGoal(goalId);
|
|
723
|
+
this.abortSleep();
|
|
724
|
+
}
|
|
725
|
+
async handleGoalStopCommand(goalId) {
|
|
726
|
+
this.currentGoalIds = this.currentGoalIds.filter((id) => id !== goalId);
|
|
727
|
+
this.state.active_goals = [...this.currentGoalIds];
|
|
728
|
+
if (this.state.interrupted_goals) {
|
|
729
|
+
this.state.interrupted_goals = this.state.interrupted_goals.filter((id) => id !== goalId);
|
|
730
|
+
}
|
|
731
|
+
await this.saveDaemonState();
|
|
732
|
+
this.supervisor?.deactivateGoal(goalId);
|
|
733
|
+
this.abortSleep();
|
|
734
|
+
}
|
|
735
|
+
async handleGoalCompletion(goalId, result) {
|
|
736
|
+
this.state.loop_count++;
|
|
737
|
+
this.currentLoopIndex = this.state.loop_count;
|
|
738
|
+
this.state.last_loop_at = new Date().toISOString();
|
|
739
|
+
await this.saveDaemonState();
|
|
740
|
+
if (this.eventServer) {
|
|
741
|
+
const goal = await this.stateManager.loadGoal(goalId).catch(() => null);
|
|
742
|
+
void this.eventServer.broadcast?.("iteration_complete", {
|
|
743
|
+
goalId,
|
|
744
|
+
loopCount: this.state.loop_count,
|
|
745
|
+
status: goal?.status ?? result.status,
|
|
746
|
+
iterations: result.totalIterations,
|
|
747
|
+
});
|
|
748
|
+
void this.eventServer.broadcast?.("daemon_status", {
|
|
749
|
+
status: this.state.status,
|
|
750
|
+
activeGoals: this.state.active_goals,
|
|
751
|
+
loopCount: this.state.loop_count,
|
|
752
|
+
lastLoopAt: this.state.last_loop_at,
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
async runSupervisorMaintenanceCycle() {
|
|
757
|
+
await runSupervisorMaintenanceCycleForDaemon({
|
|
758
|
+
currentGoalIds: this.currentGoalIds,
|
|
759
|
+
driveSystem: this.driveSystem,
|
|
760
|
+
supervisor: this.supervisor,
|
|
761
|
+
processCronTasks: () => this.processCronTasks(),
|
|
762
|
+
processScheduleEntries: () => this.processScheduleEntries(),
|
|
763
|
+
proactiveTick: () => this.proactiveTick(),
|
|
764
|
+
saveDaemonState: () => this.saveDaemonState(),
|
|
765
|
+
eventServer: this.eventServer,
|
|
766
|
+
state: this.state,
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
async handleChatMessageCommand(goalId, message) {
|
|
770
|
+
await writeChatMessageEvent(this.driveSystem, goalId, message);
|
|
771
|
+
this.abortSleep();
|
|
772
|
+
}
|
|
773
|
+
async handleApprovalResponseCommand(goalId, requestId, approved) {
|
|
774
|
+
if (this.approvalBroker) {
|
|
775
|
+
await this.approvalBroker.resolveApproval(requestId, approved, "dispatcher");
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
if (goalId && this.eventServer) {
|
|
779
|
+
await this.eventServer.resolveApproval(requestId, approved);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
async handleCronTaskDue(taskId) {
|
|
783
|
+
if (!this.cronScheduler) {
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
try {
|
|
787
|
+
await this.cronScheduler.markFired(taskId);
|
|
788
|
+
this.logger.info(`Cron task fired: ${taskId}`);
|
|
789
|
+
}
|
|
790
|
+
catch (err) {
|
|
791
|
+
this.logger.warn(`Cron task ${taskId} failed`, {
|
|
792
|
+
error: err instanceof Error ? err.message : String(err),
|
|
793
|
+
});
|
|
794
|
+
await this.cronScheduler.markFired(taskId);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
// ─── Private: Proactive Tick ───
|
|
798
|
+
/**
|
|
799
|
+
* Ask the LLM for a proactive action when no goals were activated this cycle.
|
|
800
|
+
* Fires only if proactive_mode is enabled and enough time has passed since last tick.
|
|
801
|
+
* Errors are caught and logged — they never affect the daemon loop.
|
|
802
|
+
*/
|
|
803
|
+
async proactiveTick() {
|
|
804
|
+
this.lastProactiveTickAt = await runProactiveMaintenance({
|
|
805
|
+
config: this.config,
|
|
806
|
+
llmClient: this.llmClient,
|
|
807
|
+
state: this.state,
|
|
808
|
+
lastProactiveTickAt: this.lastProactiveTickAt,
|
|
809
|
+
logger: this.logger,
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
// ─── Private: Adaptive Sleep ───
|
|
813
|
+
/**
|
|
814
|
+
* Get the highest gap score across all active goals.
|
|
815
|
+
* Falls back to 0 if no gap data is available.
|
|
816
|
+
*/
|
|
817
|
+
async getMaxGapScore(goalIds, snapshot) {
|
|
818
|
+
return getMaxGapScoreForGoals(this.driveSystem, goalIds, snapshot);
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Thin wrapper delegating to the standalone calculateAdaptiveInterval function.
|
|
822
|
+
* Passes this.config.adaptive_sleep as the config parameter.
|
|
823
|
+
* Kept as a class method so tests can call daemon.calculateAdaptiveInterval(...).
|
|
824
|
+
*/
|
|
825
|
+
calculateAdaptiveInterval(baseInterval, goalsActivatedThisCycle, maxGapScore, consecutiveIdleCycles) {
|
|
826
|
+
return calcAdaptiveInterval(baseInterval, goalsActivatedThisCycle, maxGapScore, consecutiveIdleCycles, this.config.adaptive_sleep);
|
|
827
|
+
}
|
|
828
|
+
// ─── Private: Shutdown Marker ───
|
|
829
|
+
/**
|
|
830
|
+
* Write shutdown-state.json to baseDir (async, atomic).
|
|
831
|
+
*/
|
|
832
|
+
async writeShutdownMarker(marker) {
|
|
833
|
+
await writeShutdownMarkerFile(this.baseDir, marker, this.logger);
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Read shutdown-state.json from baseDir.
|
|
837
|
+
* Returns null if file doesn't exist or fails to parse.
|
|
838
|
+
*/
|
|
839
|
+
async readShutdownMarker() {
|
|
840
|
+
return readShutdownMarkerFile(this.baseDir);
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Delete shutdown-state.json (after successful resume).
|
|
844
|
+
*/
|
|
845
|
+
async deleteShutdownMarker() {
|
|
846
|
+
await deleteShutdownMarkerFile(this.baseDir);
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Check for a previous shutdown marker and log recovery information.
|
|
850
|
+
* Called at startup before the main loop begins.
|
|
851
|
+
*/
|
|
852
|
+
async checkCrashRecovery() {
|
|
853
|
+
await checkCrashRecoveryMarker(this.baseDir, this.logger);
|
|
854
|
+
}
|
|
855
|
+
// ─── Log Rotation (delegates to daemon-health) ───
|
|
856
|
+
/**
|
|
857
|
+
* Rotate the main log file if it exceeds the configured size limit.
|
|
858
|
+
* Delegates to rotateDaemonLog() with explicit config params.
|
|
859
|
+
* Called at daemon startup.
|
|
860
|
+
*/
|
|
861
|
+
async rotateLog() {
|
|
862
|
+
const maxSizeBytes = this.config.log_rotation.max_size_mb * 1024 * 1024;
|
|
863
|
+
const maxFiles = this.config.log_rotation.max_files;
|
|
864
|
+
await rotateDaemonLog(this.logPath, this.logDir, maxSizeBytes, maxFiles, this.logger);
|
|
865
|
+
}
|
|
866
|
+
// ─── Static Utilities (delegates to daemon-signals) ───
|
|
867
|
+
/**
|
|
868
|
+
* Generate a crontab entry that runs `pulseed run --goal <goalId>` on a schedule.
|
|
869
|
+
* Delegates to the standalone generateCronEntry() function in daemon-signals.ts.
|
|
870
|
+
*/
|
|
871
|
+
static generateCronEntry(goalId, intervalMinutes = 60) {
|
|
872
|
+
return generateCronEntry(goalId, intervalMinutes);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
//# sourceMappingURL=runner.js.map
|