@aitne/daemon 0.1.10 → 0.1.11
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/adapters/adapter-watchdog.d.ts +70 -0
- package/dist/adapters/adapter-watchdog.js +115 -0
- package/dist/adapters/discord.d.ts +17 -1
- package/dist/adapters/discord.js +33 -0
- package/dist/adapters/notification-manager.d.ts +27 -1
- package/dist/adapters/notification-manager.js +54 -39
- package/dist/adapters/slack-adapter.d.ts +26 -1
- package/dist/adapters/slack-adapter.js +41 -0
- package/dist/adapters/telegram-adapter.d.ts +18 -1
- package/dist/adapters/telegram-adapter.js +41 -2
- package/dist/adapters/types.d.ts +20 -0
- package/dist/adapters/whatsapp-adapter.d.ts +26 -7
- package/dist/adapters/whatsapp-adapter.js +74 -21
- package/dist/api/env-writer.js +8 -5
- package/dist/api/helpers/agent-errors-registry.d.ts +5 -5
- package/dist/api/helpers/agent-errors-registry.js +5 -5
- package/dist/api/routes/agent.js +33 -12
- package/dist/api/routes/agents/index.js +75 -16
- package/dist/api/routes/agents/views.d.ts +37 -2
- package/dist/api/routes/agents/views.js +64 -2
- package/dist/api/routes/background-task.d.ts +22 -0
- package/dist/api/routes/background-task.js +338 -0
- package/dist/api/routes/browser-history.js +9 -1
- package/dist/api/routes/context/permissions.js +3 -2
- package/dist/api/routes/context/snapshots.js +0 -3
- package/dist/api/routes/context/write.js +3 -17
- package/dist/api/routes/dashboard/config.js +48 -12
- package/dist/api/routes/dashboard/cost-approvals.js +66 -0
- package/dist/api/routes/dashboard/notifications.js +9 -9
- package/dist/api/routes/integrations/crud-patch.js +5 -1
- package/dist/api/routes/integrations-reconcile.js +2 -2
- package/dist/api/routes/notion.d.ts +1 -1
- package/dist/api/routes/observations.js +7 -7
- package/dist/api/routes/obsidian.d.ts +1 -1
- package/dist/api/routes/receipts.js +5 -1
- package/dist/api/routes/setup-migrate.js +1 -1
- package/dist/api/routes/setup.js +1 -1
- package/dist/api/routes/task-flows.d.ts +1 -1
- package/dist/api/routes/task-flows.js +1 -1
- package/dist/api/routes/tuning.d.ts +29 -0
- package/dist/api/routes/tuning.js +304 -0
- package/dist/api/server.d.ts +44 -16
- package/dist/api/server.js +9 -0
- package/dist/bootstrap/adapters.d.ts +19 -0
- package/dist/bootstrap/adapters.js +61 -0
- package/dist/bootstrap/api.d.ts +5 -3
- package/dist/bootstrap/api.js +45 -13
- package/dist/bootstrap/catchup.d.ts +1 -1
- package/dist/bootstrap/catchup.js +11 -11
- package/dist/bootstrap/event-pipeline.d.ts +11 -0
- package/dist/bootstrap/event-pipeline.js +245 -7
- package/dist/bootstrap/observers.js +9 -6
- package/dist/bootstrap/schedule-helpers.d.ts +104 -6
- package/dist/bootstrap/schedule-helpers.js +172 -19
- package/dist/config.js +26 -12
- package/dist/core/agent-core.d.ts +33 -1
- package/dist/core/agent-core.js +36 -1
- package/dist/core/agents/activity-scan-cadence.d.ts +103 -0
- package/dist/core/agents/activity-scan-cadence.js +127 -0
- package/dist/core/agents/agent-route-override.d.ts +53 -0
- package/dist/core/agents/agent-route-override.js +69 -0
- package/dist/core/agents/builtin-registry.d.ts +51 -14
- package/dist/core/agents/builtin-registry.js +92 -15
- package/dist/core/agents/config-gate-reconcile.d.ts +38 -0
- package/dist/core/agents/config-gate-reconcile.js +51 -0
- package/dist/core/agents/cron-substitute.d.ts +1 -1
- package/dist/core/agents/cron-substitute.js +1 -1
- package/dist/core/agents/custom-routine-migration.d.ts +60 -0
- package/dist/core/agents/custom-routine-migration.js +149 -0
- package/dist/core/agents/firing-blocked.d.ts +1 -1
- package/dist/core/agents/hourly-cadence.d.ts +102 -0
- package/dist/core/agents/hourly-cadence.js +126 -0
- package/dist/core/agents/loader-boot.js +23 -0
- package/dist/core/agents/loader.d.ts +19 -0
- package/dist/core/agents/loader.js +34 -2
- package/dist/core/agents/override-merge.d.ts +1 -1
- package/dist/core/agents/override-merge.js +9 -1
- package/dist/core/agents/recurrence-convert.d.ts +1 -1
- package/dist/core/agents/recurrence-convert.js +1 -1
- package/dist/core/agents/recurring-schedule-adapter.js +8 -0
- package/dist/core/alerts.js +6 -6
- package/dist/core/backends/auth-health-monitor.d.ts +2 -2
- package/dist/core/backends/auth-health-monitor.js +1 -1
- package/dist/core/backends/backend-router.d.ts +27 -1
- package/dist/core/backends/backend-router.js +165 -1
- package/dist/core/backends/claude-code-core.d.ts +71 -31
- package/dist/core/backends/claude-code-core.js +282 -54
- package/dist/core/backends/cli-quota-guards.d.ts +29 -1
- package/dist/core/backends/cli-quota-guards.js +40 -5
- package/dist/core/backends/codex-core.d.ts +6 -0
- package/dist/core/backends/codex-core.js +22 -6
- package/dist/core/backends/failure-spend.d.ts +58 -0
- package/dist/core/backends/failure-spend.js +137 -0
- package/dist/core/backends/gemini-cli-core.d.ts +6 -0
- package/dist/core/backends/gemini-cli-core.js +25 -6
- package/dist/core/backends/model-registry.d.ts +1 -1
- package/dist/core/backends/model-registry.js +4 -4
- package/dist/core/backends/opencode-core.d.ts +1 -1
- package/dist/core/backends/opencode-core.js +5 -5
- package/dist/core/backends/plan-presets.js +39 -15
- package/dist/core/bang-commands/commands-cost.js +3 -1
- package/dist/core/bang-commands/commands-report.js +4 -3
- package/dist/core/bang-commands/commands-research.js +4 -1
- package/dist/core/bang-commands/commands-revert-tuning.d.ts +18 -0
- package/dist/core/bang-commands/commands-revert-tuning.js +63 -0
- package/dist/core/bang-commands/commands-stop-start.js +3 -3
- package/dist/core/bang-commands/commands-task-control.d.ts +19 -0
- package/dist/core/bang-commands/commands-task-control.js +147 -0
- package/dist/core/bang-commands/commands-wiki.js +5 -5
- package/dist/core/bang-commands/index.d.ts +2 -0
- package/dist/core/bang-commands/index.js +12 -0
- package/dist/core/bang-commands/registry.d.ts +12 -0
- package/dist/core/browser-history/research-cluster-fanout.d.ts +28 -14
- package/dist/core/browser-history/research-cluster-fanout.js +39 -16
- package/dist/core/channel-timeline.d.ts +5 -1
- package/dist/core/channel-timeline.js +13 -0
- package/dist/core/context/index-reconciler.js +5 -2
- package/dist/core/context/policy-index-reconciler.d.ts +6 -4
- package/dist/core/context/policy-index-runner.js +25 -6
- package/dist/core/context-builder-calendar.js +10 -2
- package/dist/core/context-builder-conversation.d.ts +8 -1
- package/dist/core/context-builder-conversation.js +41 -7
- package/dist/core/context-builder-yesterday.js +4 -3
- package/dist/core/context-builder.d.ts +7 -2
- package/dist/core/context-builder.js +62 -20
- package/dist/core/context-file-serializer.d.ts +1 -1
- package/dist/core/context-file-serializer.js +1 -1
- package/dist/core/context-health.js +2 -2
- package/dist/core/context-paths.d.ts +1 -1
- package/dist/core/context-paths.js +1 -1
- package/dist/core/context-validation/prepare-write.js +1 -1
- package/dist/core/context-validation/routine-rulebook.d.ts +1 -1
- package/dist/core/context-vault-aliases.d.ts +0 -13
- package/dist/core/context-vault-aliases.js +37 -0
- package/dist/core/custom-routines.d.ts +99 -0
- package/dist/core/custom-routines.js +187 -0
- package/dist/core/daemon-api-cli.js +49 -0
- package/dist/core/day-boundary.d.ts +46 -0
- package/dist/core/day-boundary.js +40 -0
- package/dist/core/dispatcher-activity-scan.d.ts +221 -0
- package/dist/core/dispatcher-activity-scan.js +775 -0
- package/dist/core/dispatcher-error-handling.d.ts +6 -11
- package/dist/core/dispatcher-error-handling.js +38 -62
- package/dist/core/dispatcher-hourly-check.js +6 -1
- package/dist/core/dispatcher-message-handler.d.ts +10 -0
- package/dist/core/dispatcher-message-handler.js +17 -0
- package/dist/core/dispatcher-morning-routine.d.ts +6 -6
- package/dist/core/dispatcher-morning-routine.js +13 -13
- package/dist/core/dispatcher-result-processor.d.ts +33 -0
- package/dist/core/dispatcher-result-processor.js +167 -11
- package/dist/core/dispatcher-scheduled-background-task.d.ts +42 -0
- package/dist/core/dispatcher-scheduled-background-task.js +89 -0
- package/dist/core/dispatcher-scheduled-tasks.d.ts +63 -1
- package/dist/core/dispatcher-scheduled-tasks.js +213 -6
- package/dist/core/dispatcher-task-delivery.d.ts +105 -0
- package/dist/core/dispatcher-task-delivery.js +555 -0
- package/dist/core/dispatcher-types.d.ts +48 -9
- package/dist/core/dispatcher-types.js +3 -3
- package/dist/core/dispatcher.d.ts +112 -31
- package/dist/core/dispatcher.js +284 -59
- package/dist/core/dm-freshness-metrics.d.ts +1 -1
- package/dist/core/drift-effects.js +2 -2
- package/dist/core/feedback/consolidation-prep.js +17 -5
- package/dist/core/feedback/eviction-scorer.js +6 -2
- package/dist/core/feedback/lesson-format.js +9 -4
- package/dist/core/feedback/lesson-injection.d.ts +1 -1
- package/dist/core/feedback/lesson-injection.js +17 -2
- package/dist/core/feedback/lesson-store-overview.d.ts +8 -4
- package/dist/core/feedback/lesson-store-overview.js +8 -4
- package/dist/core/feedback/regeneralization-prep.js +29 -16
- package/dist/core/feedback/self-performance-prep.d.ts +186 -0
- package/dist/core/feedback/self-performance-prep.js +541 -0
- package/dist/core/feedback/tuning-actuator.d.ts +198 -0
- package/dist/core/feedback/tuning-actuator.js +432 -0
- package/dist/core/feedback/tuning-recommender.d.ts +247 -0
- package/dist/core/feedback/tuning-recommender.js +580 -0
- package/dist/core/feedback/tuning-revert-monitor.d.ts +90 -0
- package/dist/core/feedback/tuning-revert-monitor.js +213 -0
- package/dist/core/health-monitor.d.ts +6 -0
- package/dist/core/health-monitor.js +1 -1
- package/dist/core/injection-policy.d.ts +4 -4
- package/dist/core/injection-policy.js +4 -4
- package/dist/core/integration-main-backend.js +4 -0
- package/dist/core/management-md.d.ts +2 -2
- package/dist/core/management-md.js +51 -13
- package/dist/core/morning/orchestrator.d.ts +2 -2
- package/dist/core/morning/orchestrator.js +2 -2
- package/dist/core/notification-gate.d.ts +64 -0
- package/dist/core/notification-gate.js +51 -0
- package/dist/core/notification-rate-limit.d.ts +40 -0
- package/dist/core/notification-rate-limit.js +50 -0
- package/dist/core/policy-files.d.ts +1 -1
- package/dist/core/policy-files.js +2 -2
- package/dist/core/pre-pass-freshness.d.ts +4 -4
- package/dist/core/retention.d.ts +5 -0
- package/dist/core/retention.js +20 -4
- package/dist/core/review-context.d.ts +1 -1
- package/dist/core/review-context.js +10 -5
- package/dist/core/roadmap-write-lock.d.ts +2 -1
- package/dist/core/roadmap-write-lock.js +15 -10
- package/dist/core/routine-acquisition-plan.d.ts +47 -1
- package/dist/core/routine-acquisition-plan.js +78 -20
- package/dist/core/routine-fetch-window-retry.js +7 -4
- package/dist/core/routine-fetch-window-runner.d.ts +39 -3
- package/dist/core/routine-fetch-window-runner.js +264 -13
- package/dist/core/routine-windows.d.ts +2 -2
- package/dist/core/routine-windows.js +8 -5
- package/dist/core/scheduler.d.ts +175 -16
- package/dist/core/scheduler.js +559 -102
- package/dist/core/signal-detector.d.ts +12 -0
- package/dist/core/signal-detector.js +53 -9
- package/dist/core/skills-compiler-denied-tools.js +2 -2
- package/dist/core/skills-compiler-skill-index.d.ts +2 -2
- package/dist/core/skills-compiler-skill-index.js +2 -2
- package/dist/core/skills-compiler-variants.d.ts +1 -1
- package/dist/core/skills-compiler-variants.js +8 -0
- package/dist/core/skills-compiler.d.ts +29 -26
- package/dist/core/skills-compiler.js +117 -81
- package/dist/core/skills-manifest.d.ts +37 -0
- package/dist/core/skills-manifest.js +73 -2
- package/dist/core/sleep-inhibitor.d.ts +79 -0
- package/dist/core/sleep-inhibitor.js +132 -0
- package/dist/core/slim-system-prompt-loader.d.ts +77 -0
- package/dist/core/slim-system-prompt-loader.js +141 -0
- package/dist/core/spawn-gates.d.ts +126 -0
- package/dist/core/spawn-gates.js +180 -0
- package/dist/core/today-direct-writer.d.ts +2 -2
- package/dist/core/today-direct-writer.js +1 -1
- package/dist/core/today-write-lock.d.ts +4 -2
- package/dist/core/today-write-lock.js +30 -20
- package/dist/core/wake-detector.d.ts +55 -0
- package/dist/core/wake-detector.js +80 -0
- package/dist/core/wiki/compile-lock.d.ts +1 -1
- package/dist/core/wiki/compile-lock.js +1 -1
- package/dist/core/workdir.js +15 -6
- package/dist/db/activity-scan-signals.d.ts +77 -0
- package/dist/db/activity-scan-signals.js +378 -0
- package/dist/db/agents-store.d.ts +28 -0
- package/dist/db/agents-store.js +62 -0
- package/dist/db/background-task-clarifications-store.d.ts +81 -0
- package/dist/db/background-task-clarifications-store.js +152 -0
- package/dist/db/background-task-store.d.ts +207 -0
- package/dist/db/background-task-store.js +380 -0
- package/dist/db/browser-history-store.d.ts +39 -6
- package/dist/db/browser-history-store.js +51 -7
- package/dist/db/browser-task-clarifications-store.d.ts +12 -0
- package/dist/db/browser-task-clarifications-store.js +35 -5
- package/dist/db/browser-task-store.d.ts +3 -0
- package/dist/db/browser-task-store.js +29 -4
- package/dist/db/deferred-dm.d.ts +86 -0
- package/dist/db/deferred-dm.js +199 -0
- package/dist/db/migrations.js +330 -0
- package/dist/db/observations.d.ts +2 -2
- package/dist/db/observations.js +3 -3
- package/dist/db/schema.js +217 -16
- package/dist/db/voice-transcripts-store.d.ts +1 -1
- package/dist/index.js +86 -29
- package/dist/messaging/browser-task-mcp-notifier.d.ts +12 -70
- package/dist/messaging/browser-task-mcp-notifier.js +30 -151
- package/dist/messaging/browser-task-screenshot-attachment.d.ts +15 -0
- package/dist/messaging/browser-task-screenshot-attachment.js +63 -0
- package/dist/observers/delegated-sync-worker.d.ts +6 -6
- package/dist/observers/delegated-sync-worker.js +10 -10
- package/dist/observers/git-delegated-cron.d.ts +1 -1
- package/dist/observers/git-delegated-cron.js +2 -2
- package/dist/observers/github-poller-classifier.d.ts +3 -3
- package/dist/observers/github-poller-classifier.js +3 -3
- package/dist/observers/imminent-event-scheduler.d.ts +1 -1
- package/dist/observers/imminent-event-scheduler.js +1 -1
- package/dist/observers/mail-poller.d.ts +1 -0
- package/dist/observers/mail-poller.js +42 -3
- package/dist/observers/observation-summarizer/summarizer-client.d.ts +2 -2
- package/dist/observers/observation-summarizer/summarizer-client.js +2 -2
- package/dist/observers/observation-summarizer/worker.d.ts +2 -2
- package/dist/observers/observation-summarizer/worker.js +4 -4
- package/dist/observers/obsidian-watcher.d.ts +1 -1
- package/dist/observers/obsidian-watcher.js +1 -1
- package/dist/safety/agent-write-tracker.d.ts +4 -4
- package/dist/safety/agent-write-tracker.js +4 -4
- package/dist/safety/audit.d.ts +43 -5
- package/dist/safety/audit.js +86 -18
- package/dist/safety/risk-classifier.d.ts +6 -0
- package/dist/safety/risk-classifier.js +75 -11
- package/dist/scheduler/activity-scan-gate.d.ts +86 -0
- package/dist/scheduler/activity-scan-gate.js +132 -0
- package/dist/services/background-task/background-task-budget.d.ts +80 -0
- package/dist/services/background-task/background-task-budget.js +91 -0
- package/dist/services/background-task/background-task-driver.d.ts +105 -0
- package/dist/services/background-task/background-task-driver.js +416 -0
- package/dist/services/background-task/background-task-runner.d.ts +96 -0
- package/dist/services/background-task/background-task-runner.js +673 -0
- package/dist/services/background-task/background-task-tools.d.ts +84 -0
- package/dist/services/background-task/background-task-tools.js +247 -0
- package/dist/services/background-task/background-task-transition-events.d.ts +43 -0
- package/dist/services/background-task/background-task-transition-events.js +54 -0
- package/dist/services/browser-history/automation/egress-denylist.d.ts +1 -1
- package/dist/services/browser-history/automation/egress-denylist.js +16 -6
- package/dist/services/browser-history/managed-chromium/sandbox-launcher.js +0 -1
- package/dist/services/browser-task/browser-task-runner.js +53 -8
- package/dist/services/observations-batch.d.ts +1 -1
- package/dist/services/observations-batch.js +2 -2
- package/dist/settings/runtime-settings.d.ts +38 -11
- package/dist/settings/runtime-settings.js +203 -40
- package/dist/settings/settings-store.js +11 -3
- package/package.json +4 -4
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { AdapterConnectionState } from "./types.js";
|
|
2
|
+
/** Probe cadence. */
|
|
3
|
+
export declare const ADAPTER_WATCHDOG_INTERVAL_MS: number;
|
|
4
|
+
/**
|
|
5
|
+
* Consecutive "down" observations before forcing a restart. Two ticks
|
|
6
|
+
* (~10 min at the default cadence) gives every library's own reconnect
|
|
7
|
+
* machinery (discord.js gateway resume, @slack/socket-mode backoff,
|
|
8
|
+
* telegraf poll retry) ample time to self-heal a transient blip — the
|
|
9
|
+
* watchdog only steps in for the gave-up / wedged cases.
|
|
10
|
+
*/
|
|
11
|
+
export declare const ADAPTER_WATCHDOG_DOWN_TICKS_BEFORE_RESTART = 2;
|
|
12
|
+
export interface WatchedAdapter {
|
|
13
|
+
platform: string;
|
|
14
|
+
/**
|
|
15
|
+
* Live transport probe. Closures typically read the mutable
|
|
16
|
+
* `AdapterState` slot so a reload that swaps the instance is picked up
|
|
17
|
+
* automatically. Return "unknown" when there is nothing to assess
|
|
18
|
+
* (adapter not configured / not started) — the watchdog takes no action
|
|
19
|
+
* and resets the down counter.
|
|
20
|
+
*/
|
|
21
|
+
getConnectionState: () => AdapterConnectionState;
|
|
22
|
+
/**
|
|
23
|
+
* Full stop→start cycle (bootstrap's `reload*Adapter(true)`). Expected
|
|
24
|
+
* to manage MessageHub status transitions itself. Errors are caught and
|
|
25
|
+
* logged; the down counter is preserved so the next tick retries.
|
|
26
|
+
*/
|
|
27
|
+
restart: () => Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Invoked on observed state transitions (down ↔ ok) so the caller can
|
|
30
|
+
* surface them (e.g. `messageHub.setPlatformRuntimeStatus`). Optional.
|
|
31
|
+
*/
|
|
32
|
+
onStateChange?: (state: AdapterConnectionState) => void;
|
|
33
|
+
}
|
|
34
|
+
export interface AdapterWatchdogOptions {
|
|
35
|
+
intervalMs?: number;
|
|
36
|
+
downTicksBeforeRestart?: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Daemon-level liveness watchdog for the messaging adapters.
|
|
40
|
+
*
|
|
41
|
+
* Every adapter relies on library-internal reconnection that can end in a
|
|
42
|
+
* permanently dead transport while the daemon still reports "connected":
|
|
43
|
+
* a Slack reconnect chain killed by an unrecoverable start error, a
|
|
44
|
+
* discord.js session invalidation, a telegraf poll loop exited on a
|
|
45
|
+
* non-retryable error — all typically after machine sleep kills the TCP
|
|
46
|
+
* sockets. The pre-watchdog behavior was a silent, indefinite outage that
|
|
47
|
+
* only a daemon restart cleared.
|
|
48
|
+
*
|
|
49
|
+
* The watchdog polls each adapter's `getConnectionState()` and, after
|
|
50
|
+
* {@link ADAPTER_WATCHDOG_DOWN_TICKS_BEFORE_RESTART} consecutive "down"
|
|
51
|
+
* observations, forces a full adapter restart through the bootstrap
|
|
52
|
+
* reloader. Restarts are serialized per adapter (no overlapping cycles)
|
|
53
|
+
* and a failed restart retries on the following tick.
|
|
54
|
+
*/
|
|
55
|
+
export declare class AdapterWatchdog {
|
|
56
|
+
private readonly intervalMs;
|
|
57
|
+
private readonly downTicksBeforeRestart;
|
|
58
|
+
private readonly watched;
|
|
59
|
+
private readonly downCounts;
|
|
60
|
+
private readonly lastObserved;
|
|
61
|
+
private readonly restartInFlight;
|
|
62
|
+
private timer;
|
|
63
|
+
constructor(options?: AdapterWatchdogOptions);
|
|
64
|
+
register(adapter: WatchedAdapter): void;
|
|
65
|
+
start(): void;
|
|
66
|
+
stop(): void;
|
|
67
|
+
/** One probe pass over all watched adapters. Exposed for tests. */
|
|
68
|
+
tick(): Promise<void>;
|
|
69
|
+
private probe;
|
|
70
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { createLogger } from "../logging.js";
|
|
2
|
+
const logger = createLogger("adapter-watchdog");
|
|
3
|
+
/** Probe cadence. */
|
|
4
|
+
export const ADAPTER_WATCHDOG_INTERVAL_MS = 5 * 60_000;
|
|
5
|
+
/**
|
|
6
|
+
* Consecutive "down" observations before forcing a restart. Two ticks
|
|
7
|
+
* (~10 min at the default cadence) gives every library's own reconnect
|
|
8
|
+
* machinery (discord.js gateway resume, @slack/socket-mode backoff,
|
|
9
|
+
* telegraf poll retry) ample time to self-heal a transient blip — the
|
|
10
|
+
* watchdog only steps in for the gave-up / wedged cases.
|
|
11
|
+
*/
|
|
12
|
+
export const ADAPTER_WATCHDOG_DOWN_TICKS_BEFORE_RESTART = 2;
|
|
13
|
+
/**
|
|
14
|
+
* Daemon-level liveness watchdog for the messaging adapters.
|
|
15
|
+
*
|
|
16
|
+
* Every adapter relies on library-internal reconnection that can end in a
|
|
17
|
+
* permanently dead transport while the daemon still reports "connected":
|
|
18
|
+
* a Slack reconnect chain killed by an unrecoverable start error, a
|
|
19
|
+
* discord.js session invalidation, a telegraf poll loop exited on a
|
|
20
|
+
* non-retryable error — all typically after machine sleep kills the TCP
|
|
21
|
+
* sockets. The pre-watchdog behavior was a silent, indefinite outage that
|
|
22
|
+
* only a daemon restart cleared.
|
|
23
|
+
*
|
|
24
|
+
* The watchdog polls each adapter's `getConnectionState()` and, after
|
|
25
|
+
* {@link ADAPTER_WATCHDOG_DOWN_TICKS_BEFORE_RESTART} consecutive "down"
|
|
26
|
+
* observations, forces a full adapter restart through the bootstrap
|
|
27
|
+
* reloader. Restarts are serialized per adapter (no overlapping cycles)
|
|
28
|
+
* and a failed restart retries on the following tick.
|
|
29
|
+
*/
|
|
30
|
+
export class AdapterWatchdog {
|
|
31
|
+
intervalMs;
|
|
32
|
+
downTicksBeforeRestart;
|
|
33
|
+
watched = [];
|
|
34
|
+
downCounts = new Map();
|
|
35
|
+
lastObserved = new Map();
|
|
36
|
+
restartInFlight = new Set();
|
|
37
|
+
timer = null;
|
|
38
|
+
constructor(options) {
|
|
39
|
+
this.intervalMs = options?.intervalMs ?? ADAPTER_WATCHDOG_INTERVAL_MS;
|
|
40
|
+
this.downTicksBeforeRestart =
|
|
41
|
+
options?.downTicksBeforeRestart
|
|
42
|
+
?? ADAPTER_WATCHDOG_DOWN_TICKS_BEFORE_RESTART;
|
|
43
|
+
}
|
|
44
|
+
register(adapter) {
|
|
45
|
+
this.watched.push(adapter);
|
|
46
|
+
}
|
|
47
|
+
start() {
|
|
48
|
+
if (this.timer)
|
|
49
|
+
return;
|
|
50
|
+
this.timer = setInterval(() => void this.tick(), this.intervalMs);
|
|
51
|
+
this.timer.unref?.();
|
|
52
|
+
logger.info({
|
|
53
|
+
platforms: this.watched.map((w) => w.platform),
|
|
54
|
+
intervalMs: this.intervalMs,
|
|
55
|
+
}, "Adapter watchdog started");
|
|
56
|
+
}
|
|
57
|
+
stop() {
|
|
58
|
+
if (!this.timer)
|
|
59
|
+
return;
|
|
60
|
+
clearInterval(this.timer);
|
|
61
|
+
this.timer = null;
|
|
62
|
+
}
|
|
63
|
+
/** One probe pass over all watched adapters. Exposed for tests. */
|
|
64
|
+
async tick() {
|
|
65
|
+
for (const adapter of this.watched) {
|
|
66
|
+
await this.probe(adapter).catch((err) => {
|
|
67
|
+
logger.error({ err, platform: adapter.platform }, "Adapter watchdog probe threw");
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async probe(adapter) {
|
|
72
|
+
if (this.restartInFlight.has(adapter.platform))
|
|
73
|
+
return;
|
|
74
|
+
let state;
|
|
75
|
+
try {
|
|
76
|
+
state = adapter.getConnectionState();
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
logger.warn({ err, platform: adapter.platform }, "Adapter connection probe threw; treating as unknown");
|
|
80
|
+
state = "unknown";
|
|
81
|
+
}
|
|
82
|
+
const previous = this.lastObserved.get(adapter.platform);
|
|
83
|
+
if (previous !== state) {
|
|
84
|
+
this.lastObserved.set(adapter.platform, state);
|
|
85
|
+
if (state === "down" || previous === "down") {
|
|
86
|
+
logger.warn({ platform: adapter.platform, from: previous ?? "unobserved", to: state }, "Adapter connection state changed");
|
|
87
|
+
adapter.onStateChange?.(state);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (state !== "down") {
|
|
91
|
+
this.downCounts.set(adapter.platform, 0);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const downCount = (this.downCounts.get(adapter.platform) ?? 0) + 1;
|
|
95
|
+
this.downCounts.set(adapter.platform, downCount);
|
|
96
|
+
if (downCount < this.downTicksBeforeRestart) {
|
|
97
|
+
logger.warn({ platform: adapter.platform, downCount }, "Adapter connection down — waiting for library self-recovery before restart");
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
logger.warn({ platform: adapter.platform, downCount }, "Adapter connection still down — forcing restart");
|
|
101
|
+
this.restartInFlight.add(adapter.platform);
|
|
102
|
+
try {
|
|
103
|
+
await adapter.restart();
|
|
104
|
+
this.downCounts.set(adapter.platform, 0);
|
|
105
|
+
logger.info({ platform: adapter.platform }, "Adapter watchdog restart completed");
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
// Counter is left at/above the threshold so the next tick retries.
|
|
109
|
+
logger.error({ err, platform: adapter.platform }, "Adapter watchdog restart failed; will retry on next tick");
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
this.restartInFlight.delete(adapter.platform);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MessageAdapter, OnMessageCallback, OutboundAttachmentRef } from "./types.js";
|
|
1
|
+
import type { AdapterConnectionState, MessageAdapter, OnMessageCallback, OutboundAttachmentRef } from "./types.js";
|
|
2
2
|
import type { AttachmentStore } from "../services/attachments/store.js";
|
|
3
3
|
export interface DiscordBotInfo {
|
|
4
4
|
id: string;
|
|
@@ -48,6 +48,14 @@ export declare class DiscordAdapter implements MessageAdapter {
|
|
|
48
48
|
private readonly onOwnerDetected;
|
|
49
49
|
private botUserId;
|
|
50
50
|
private botInfo;
|
|
51
|
+
/**
|
|
52
|
+
* Set when the gateway emits `Events.Invalidated` — discord.js stops
|
|
53
|
+
* reconnecting entirely on an invalidated session, so without external
|
|
54
|
+
* intervention the adapter is permanently deaf. Cleared on `start()`.
|
|
55
|
+
*/
|
|
56
|
+
private sessionInvalidated;
|
|
57
|
+
/** True once `client.login()` has resolved; cleared in `stop()`. */
|
|
58
|
+
private startCompleted;
|
|
51
59
|
private pairingChallenge;
|
|
52
60
|
private readonly attachmentStore;
|
|
53
61
|
constructor(opts: DiscordAdapterOptions);
|
|
@@ -64,6 +72,14 @@ export declare class DiscordAdapter implements MessageAdapter {
|
|
|
64
72
|
static fetchBotInfo(botToken: string): Promise<DiscordBotInfo>;
|
|
65
73
|
start(): Promise<void>;
|
|
66
74
|
stop(): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Live gateway liveness for the adapter watchdog. discord.js resumes /
|
|
77
|
+
* reconnects on its own for transient closes, so a momentary not-ready
|
|
78
|
+
* state is normal — the watchdog only acts on consecutive "down"
|
|
79
|
+
* observations. An invalidated session is reported "down" immediately
|
|
80
|
+
* because the client never recovers from it without a fresh login.
|
|
81
|
+
*/
|
|
82
|
+
getConnectionState(): AdapterConnectionState;
|
|
67
83
|
resolveUserChannel(): Promise<string | null>;
|
|
68
84
|
sendMessage(params: {
|
|
69
85
|
channel: string;
|
package/dist/adapters/discord.js
CHANGED
|
@@ -34,6 +34,14 @@ export class DiscordAdapter {
|
|
|
34
34
|
onOwnerDetected;
|
|
35
35
|
botUserId = null;
|
|
36
36
|
botInfo = null;
|
|
37
|
+
/**
|
|
38
|
+
* Set when the gateway emits `Events.Invalidated` — discord.js stops
|
|
39
|
+
* reconnecting entirely on an invalidated session, so without external
|
|
40
|
+
* intervention the adapter is permanently deaf. Cleared on `start()`.
|
|
41
|
+
*/
|
|
42
|
+
sessionInvalidated = false;
|
|
43
|
+
/** True once `client.login()` has resolved; cleared in `stop()`. */
|
|
44
|
+
startCompleted = false;
|
|
37
45
|
pairingChallenge = null;
|
|
38
46
|
attachmentStore;
|
|
39
47
|
constructor(opts) {
|
|
@@ -113,6 +121,7 @@ export class DiscordAdapter {
|
|
|
113
121
|
// Remove all listeners first to prevent duplicates on re-start
|
|
114
122
|
this.client.removeAllListeners(Events.Error);
|
|
115
123
|
this.client.removeAllListeners(Events.Warn);
|
|
124
|
+
this.client.removeAllListeners(Events.Invalidated);
|
|
116
125
|
rawClient.removeAllListeners("raw");
|
|
117
126
|
this.client.on(Events.Error, (err) => {
|
|
118
127
|
logger.error({ error: err.message }, "Discord client error");
|
|
@@ -120,6 +129,14 @@ export class DiscordAdapter {
|
|
|
120
129
|
this.client.on(Events.Warn, (info) => {
|
|
121
130
|
logger.warn({ info }, "Discord client warn");
|
|
122
131
|
});
|
|
132
|
+
// Session invalidation is terminal for discord.js — the client stops
|
|
133
|
+
// reconnecting. Record it so getConnectionState reports "down" and the
|
|
134
|
+
// adapter watchdog performs a full stop→start cycle.
|
|
135
|
+
this.client.on(Events.Invalidated, () => {
|
|
136
|
+
this.sessionInvalidated = true;
|
|
137
|
+
logger.error("Discord gateway session invalidated — client will not reconnect on its own");
|
|
138
|
+
});
|
|
139
|
+
this.sessionInvalidated = false;
|
|
123
140
|
// We deliberately do NOT subscribe to Events.MessageCreate.
|
|
124
141
|
//
|
|
125
142
|
// discord.js 14.26.2 has a bug where MessageCreateAction silently
|
|
@@ -154,12 +171,28 @@ export class DiscordAdapter {
|
|
|
154
171
|
avatarUrl: this.client.user.avatarURL() ?? null,
|
|
155
172
|
};
|
|
156
173
|
}
|
|
174
|
+
this.startCompleted = true;
|
|
157
175
|
logger.info({ botUser: this.client.user?.tag }, "Discord adapter connected");
|
|
158
176
|
}
|
|
159
177
|
async stop() {
|
|
178
|
+
this.startCompleted = false;
|
|
160
179
|
this.client.destroy();
|
|
161
180
|
logger.info("Discord adapter disconnected");
|
|
162
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* Live gateway liveness for the adapter watchdog. discord.js resumes /
|
|
184
|
+
* reconnects on its own for transient closes, so a momentary not-ready
|
|
185
|
+
* state is normal — the watchdog only acts on consecutive "down"
|
|
186
|
+
* observations. An invalidated session is reported "down" immediately
|
|
187
|
+
* because the client never recovers from it without a fresh login.
|
|
188
|
+
*/
|
|
189
|
+
getConnectionState() {
|
|
190
|
+
if (!this.startCompleted)
|
|
191
|
+
return "unknown";
|
|
192
|
+
if (this.sessionInvalidated)
|
|
193
|
+
return "down";
|
|
194
|
+
return this.client.isReady() ? "ok" : "down";
|
|
195
|
+
}
|
|
163
196
|
async resolveUserChannel() {
|
|
164
197
|
if (!this.mutableOwnerId)
|
|
165
198
|
return null;
|
|
@@ -5,6 +5,7 @@ import type { INotificationManager, ReplyActivityHandle } from "../core/dispatch
|
|
|
5
5
|
import type { MessageReplyTarget } from "../core/dispatcher-types.js";
|
|
6
6
|
import type { SignalDetector } from "../core/signal-detector.js";
|
|
7
7
|
import type { MessageHub } from "./message-hub.js";
|
|
8
|
+
import type { OutboundAttachmentRef } from "./types.js";
|
|
8
9
|
export interface NotificationManagerOptions {
|
|
9
10
|
/**
|
|
10
11
|
* Maximum number of times `deliverReply` will attempt the originating
|
|
@@ -27,6 +28,24 @@ export interface NotificationManagerOptions {
|
|
|
27
28
|
* `setTimeout(...).unref()` so the timer never blocks daemon shutdown.
|
|
28
29
|
*/
|
|
29
30
|
sleep?: (ms: number) => Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* BACKGROUND_TASK_RUNNER_DESIGN.md §2.3 / §13 Decision 4 (Phase 4,
|
|
33
|
+
* opt-in) — router that hands a proactive autonomous forward to the
|
|
34
|
+
* task-delivery gate + activity-branch machinery so the REAL DM agent
|
|
35
|
+
* weaves it into the live conversation. Only invoked when the owner is
|
|
36
|
+
* active AND `config.autonomousForwardNaturalDelivery` is on; returns
|
|
37
|
+
* `true` when it enqueued the weave (this `deliverProactive` then
|
|
38
|
+
* returns without the verbatim send), `false` to fall through to the
|
|
39
|
+
* verbatim path. Wired from the event bus in bootstrap; absent in tests.
|
|
40
|
+
* The IDLE path is never rerouted — it keeps the richer verbatim
|
|
41
|
+
* delivery (multi-destination fan-out, notification_log telemetry,
|
|
42
|
+
* batching cooldown) unchanged.
|
|
43
|
+
*/
|
|
44
|
+
routeAutonomousForwardNaturally?: (input: {
|
|
45
|
+
content: string;
|
|
46
|
+
event: import("@aitne/shared").Event;
|
|
47
|
+
originSessionId?: number;
|
|
48
|
+
}) => Promise<boolean>;
|
|
30
49
|
}
|
|
31
50
|
type DestinationMode = "default" | "configured_only";
|
|
32
51
|
export declare class NotificationManager implements INotificationManager {
|
|
@@ -61,6 +80,7 @@ export declare class NotificationManager implements INotificationManager {
|
|
|
61
80
|
private readonly replyRetryAttempts;
|
|
62
81
|
private readonly replyRetryBackoffBaseMs;
|
|
63
82
|
private readonly sleep;
|
|
83
|
+
private readonly routeAutonomousForwardNaturally?;
|
|
64
84
|
constructor(messageHub: MessageHub, db: Database.Database, config: AgentConfig, options?: NotificationManagerOptions);
|
|
65
85
|
/** Set the SignalDetector for implicit feedback tracking. */
|
|
66
86
|
setSignalDetector(detector: SignalDetector): void;
|
|
@@ -84,6 +104,7 @@ export declare class NotificationManager implements INotificationManager {
|
|
|
84
104
|
destinationMode?: DestinationMode;
|
|
85
105
|
originSessionId?: number;
|
|
86
106
|
replyTo?: MessageReplyTarget;
|
|
107
|
+
attachments?: readonly OutboundAttachmentRef[];
|
|
87
108
|
}): Promise<void>;
|
|
88
109
|
/**
|
|
89
110
|
* Direct reply to a MessageEvent — bypasses quiet-hours, rate-limits,
|
|
@@ -188,7 +209,12 @@ export declare class NotificationManager implements INotificationManager {
|
|
|
188
209
|
* cache invalidates naturally on every call (config can hot-reload
|
|
189
210
|
* via PATCH /api/config). */
|
|
190
211
|
private quietHoursWindow;
|
|
191
|
-
/**
|
|
212
|
+
/**
|
|
213
|
+
* Check if hourly or daily notification limits have been reached.
|
|
214
|
+
* Counting lives in the shared `core/notification-rate-limit.ts` helper
|
|
215
|
+
* so this gate and the `/api/notify` route gate
|
|
216
|
+
* (QUIET_HOURS_HARDENING_PLAN.md Phase 1) cannot drift.
|
|
217
|
+
*/
|
|
192
218
|
private isRateLimited;
|
|
193
219
|
/**
|
|
194
220
|
* Per-type gate. True when this event type has hit
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { isMessageEvent
|
|
1
|
+
import { isMessageEvent } from "@aitne/shared";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { isInQuietHoursAt, nextQuietHoursEndMs as computeNextQuietHoursEndMs, } from "../core/quiet-hours.js";
|
|
4
|
+
import { evaluateNotificationRateLimit } from "../core/notification-rate-limit.js";
|
|
5
|
+
import { SAFETY_CATEGORIES } from "../core/notification-gate.js";
|
|
4
6
|
import { MessageDeliveryError } from "./message-hub.js";
|
|
5
7
|
import { recordProactiveForwardDeliveries } from "../core/channel-timeline.js";
|
|
8
|
+
import { classifyOwnerDmActivity } from "../core/context-builder-conversation.js";
|
|
6
9
|
import { createLogger } from "../logging.js";
|
|
7
10
|
const logger = createLogger("notification-manager");
|
|
8
11
|
/**
|
|
@@ -26,13 +29,6 @@ function logDeliveryFailure(err, msg) {
|
|
|
26
29
|
const NOOP_REPLY_ACTIVITY = {
|
|
27
30
|
stop: async () => { },
|
|
28
31
|
};
|
|
29
|
-
/** Safety categories bypass quiet hours and user preferences */
|
|
30
|
-
const SAFETY_CATEGORIES = [
|
|
31
|
-
"security",
|
|
32
|
-
"deadline",
|
|
33
|
-
"error",
|
|
34
|
-
"critical",
|
|
35
|
-
];
|
|
36
32
|
/**
|
|
37
33
|
* Default bounded-retry shape for `deliverReply`. Sized so the operator's
|
|
38
34
|
* worst-case wait for an acknowledged DM is small (~600 ms across two
|
|
@@ -96,6 +92,7 @@ export class NotificationManager {
|
|
|
96
92
|
replyRetryAttempts;
|
|
97
93
|
replyRetryBackoffBaseMs;
|
|
98
94
|
sleep;
|
|
95
|
+
routeAutonomousForwardNaturally;
|
|
99
96
|
constructor(messageHub, db, config, options = {}) {
|
|
100
97
|
this.messageHub = messageHub;
|
|
101
98
|
this.db = db;
|
|
@@ -108,6 +105,7 @@ export class NotificationManager {
|
|
|
108
105
|
this.replyRetryAttempts = Math.max(1, options.replyRetryAttempts ?? DEFAULT_REPLY_RETRY_ATTEMPTS);
|
|
109
106
|
this.replyRetryBackoffBaseMs = Math.max(0, options.replyRetryBackoffBaseMs ?? DEFAULT_REPLY_RETRY_BACKOFF_BASE_MS);
|
|
110
107
|
this.sleep = options.sleep ?? defaultSleep;
|
|
108
|
+
this.routeAutonomousForwardNaturally = options.routeAutonomousForwardNaturally;
|
|
111
109
|
}
|
|
112
110
|
/** Set the SignalDetector for implicit feedback tracking. */
|
|
113
111
|
setSignalDetector(detector) {
|
|
@@ -150,7 +148,7 @@ export class NotificationManager {
|
|
|
150
148
|
// a `failed` notification row, and we fall through to the proactive
|
|
151
149
|
// path so the reply still reaches the operator.
|
|
152
150
|
if (options?.replyTo) {
|
|
153
|
-
const delivered = await this.deliverDirect(message, event, priority, options.replyTo);
|
|
151
|
+
const delivered = await this.deliverDirect(message, event, priority, options.replyTo, options.attachments);
|
|
154
152
|
if (delivered)
|
|
155
153
|
return;
|
|
156
154
|
logger.info({
|
|
@@ -254,11 +252,17 @@ export class NotificationManager {
|
|
|
254
252
|
* (`send`'s explicit-replyTo path) to drive the §3.4-bis fallback to
|
|
255
253
|
* the proactive destinations.
|
|
256
254
|
*/
|
|
257
|
-
async deliverDirect(message, event, priority, target) {
|
|
255
|
+
async deliverDirect(message, event, priority, target, attachments) {
|
|
258
256
|
const dispatchId = randomUUID();
|
|
259
257
|
const summary = truncateSummary(message);
|
|
260
258
|
try {
|
|
261
|
-
|
|
259
|
+
// Keep the no-attachment call shape identical to the historical
|
|
260
|
+
// 4-arg form so existing adapters/tests that assert the exact send
|
|
261
|
+
// signature are unaffected; only widen to 5 args when media is
|
|
262
|
+
// present (the task-delivery idle screenshot path).
|
|
263
|
+
const delivery = attachments && attachments.length > 0
|
|
264
|
+
? await this.messageHub.sendToPlatform(target.platform, target.channel, message, target.threadId ?? undefined, [...attachments])
|
|
265
|
+
: await this.messageHub.sendToPlatform(target.platform, target.channel, message, target.threadId ?? undefined);
|
|
262
266
|
this.logNotificationRows({
|
|
263
267
|
dispatchId,
|
|
264
268
|
event,
|
|
@@ -299,6 +303,33 @@ export class NotificationManager {
|
|
|
299
303
|
const isSafety = this.isSafetyCategory(category, priority);
|
|
300
304
|
const dispatchId = randomUUID();
|
|
301
305
|
const summary = truncateSummary(message);
|
|
306
|
+
// BACKGROUND_TASK_RUNNER_DESIGN.md §2.3 / §13 Decision 4 (Phase 4,
|
|
307
|
+
// opt-in) — when the owner is ACTIVE mid-conversation, hand a
|
|
308
|
+
// non-safety, non-batched proactive forward to the delivery machinery
|
|
309
|
+
// so the real DM agent weaves it into the live thread instead of this
|
|
310
|
+
// verbatim dump. The IDLE path is deliberately left on the richer
|
|
311
|
+
// verbatim delivery below (multi-destination fan-out + telemetry +
|
|
312
|
+
// batching), so this only changes the active case. The router enqueues
|
|
313
|
+
// the weave and returns true; a false (no owner-DM channel / disabled)
|
|
314
|
+
// falls through to the unchanged verbatim path.
|
|
315
|
+
if (!isSafety
|
|
316
|
+
&& !fromBatch
|
|
317
|
+
&& this.config.autonomousForwardNaturalDelivery
|
|
318
|
+
&& this.routeAutonomousForwardNaturally
|
|
319
|
+
&& classifyOwnerDmActivity({ db: this.db, config: this.config }) === "active") {
|
|
320
|
+
try {
|
|
321
|
+
const routed = await this.routeAutonomousForwardNaturally({
|
|
322
|
+
content: message,
|
|
323
|
+
event,
|
|
324
|
+
...(originSessionId !== undefined ? { originSessionId } : {}),
|
|
325
|
+
});
|
|
326
|
+
if (routed)
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
catch (err) {
|
|
330
|
+
logger.warn({ err, eventType: event.type }, "autonomous-forward natural delivery routing threw; falling back to verbatim proactive send");
|
|
331
|
+
}
|
|
332
|
+
}
|
|
302
333
|
if (!isSafety && this.isQuietHours()) {
|
|
303
334
|
this.logNotificationRows({
|
|
304
335
|
dispatchId,
|
|
@@ -613,35 +644,19 @@ export class NotificationManager {
|
|
|
613
644
|
timezone: this.config.timezone || undefined,
|
|
614
645
|
};
|
|
615
646
|
}
|
|
616
|
-
/**
|
|
647
|
+
/**
|
|
648
|
+
* Check if hourly or daily notification limits have been reached.
|
|
649
|
+
* Counting lives in the shared `core/notification-rate-limit.ts` helper
|
|
650
|
+
* so this gate and the `/api/notify` route gate
|
|
651
|
+
* (QUIET_HOURS_HARDENING_PLAN.md Phase 1) cannot drift.
|
|
652
|
+
*/
|
|
617
653
|
isRateLimited() {
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
.
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
FROM notification_log
|
|
625
|
-
WHERE status = 'delivered'
|
|
626
|
-
AND COALESCE(notification_type, '') != 'message.received'
|
|
627
|
-
AND created_at > datetime('now', '-1 hour')`)
|
|
628
|
-
.get();
|
|
629
|
-
if (hourlyCount.cnt >= this.config.maxNotificationsPerHour) {
|
|
630
|
-
return true;
|
|
631
|
-
}
|
|
632
|
-
// Daily check (timezone-aware agent day)
|
|
633
|
-
const bounds = getAgentDayBoundsUtc(this.config.timezone, this.config.dayBoundaryHour);
|
|
634
|
-
const dailyCount = this.db
|
|
635
|
-
.prepare(`SELECT COUNT(DISTINCT CASE
|
|
636
|
-
WHEN dispatch_id != '' THEN dispatch_id
|
|
637
|
-
ELSE CAST(id AS TEXT)
|
|
638
|
-
END) as cnt
|
|
639
|
-
FROM notification_log
|
|
640
|
-
WHERE status = 'delivered'
|
|
641
|
-
AND COALESCE(notification_type, '') != 'message.received'
|
|
642
|
-
AND created_at >= ? AND created_at < ?`)
|
|
643
|
-
.get(bounds.start, bounds.end);
|
|
644
|
-
return dailyCount.cnt >= this.config.maxNotificationsPerDay;
|
|
654
|
+
return evaluateNotificationRateLimit(this.db, {
|
|
655
|
+
maxNotificationsPerHour: this.config.maxNotificationsPerHour,
|
|
656
|
+
maxNotificationsPerDay: this.config.maxNotificationsPerDay,
|
|
657
|
+
timezone: this.config.timezone,
|
|
658
|
+
dayBoundaryHour: this.config.dayBoundaryHour,
|
|
659
|
+
}).limited;
|
|
645
660
|
}
|
|
646
661
|
/**
|
|
647
662
|
* Per-type gate. True when this event type has hit
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MessageAdapter, OnMessageCallback, OutboundAttachmentRef } from "./types.js";
|
|
1
|
+
import type { AdapterConnectionState, MessageAdapter, OnMessageCallback, OutboundAttachmentRef } from "./types.js";
|
|
2
2
|
import type { AttachmentStore } from "../services/attachments/store.js";
|
|
3
3
|
export interface SlackBotInfo {
|
|
4
4
|
botUserId: string | null;
|
|
@@ -56,6 +56,12 @@ export declare class SlackAdapter implements MessageAdapter {
|
|
|
56
56
|
private readonly onMessage;
|
|
57
57
|
private readonly onOwnerDetected;
|
|
58
58
|
private app;
|
|
59
|
+
/**
|
|
60
|
+
* True once `app.start()` has resolved (cleared in `stop()`). Gates the
|
|
61
|
+
* watchdog probe: before the first successful start there is no socket
|
|
62
|
+
* to assess, and reporting "down" then would trigger pointless restarts.
|
|
63
|
+
*/
|
|
64
|
+
private startCompleted;
|
|
59
65
|
private botUserId;
|
|
60
66
|
private botInfo;
|
|
61
67
|
private pairingChallenge;
|
|
@@ -75,6 +81,25 @@ export declare class SlackAdapter implements MessageAdapter {
|
|
|
75
81
|
static fetchBotInfo(botToken: string): Promise<SlackBotInfo>;
|
|
76
82
|
start(): Promise<void>;
|
|
77
83
|
stop(): Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* Live Socket Mode liveness for the adapter watchdog.
|
|
86
|
+
*
|
|
87
|
+
* Bolt's `App` keeps its `SocketModeReceiver` private, but the receiver's
|
|
88
|
+
* `client` (a `@slack/socket-mode` `SocketModeClient`) and the client's
|
|
89
|
+
* `websocket` (a `SlackWebSocket` with a public `isActive()`) are public
|
|
90
|
+
* fields — only the first hop needs a cast. `@slack/socket-mode` v2
|
|
91
|
+
* reconnects on `close` with an UNBOUNDED linear backoff, but a reconnect
|
|
92
|
+
* chain can still die permanently (`UnrecoverableSocketModeStartError`,
|
|
93
|
+
* or an exception escaping the recursive retry), and after machine sleep
|
|
94
|
+
* the ping-pong watchdog may take minutes to notice a dead socket. The
|
|
95
|
+
* adapter watchdog uses this probe to force a full stop→start cycle when
|
|
96
|
+
* the socket stays dead.
|
|
97
|
+
*
|
|
98
|
+
* Returns "unknown" (watchdog: no action) when the receiver shape is not
|
|
99
|
+
* what we expect — a Bolt upgrade must degrade to "no watchdog" rather
|
|
100
|
+
* than to restart loops.
|
|
101
|
+
*/
|
|
102
|
+
getConnectionState(): AdapterConnectionState;
|
|
78
103
|
resolveUserChannel(): Promise<string | null>;
|
|
79
104
|
sendMessage(params: {
|
|
80
105
|
channel: string;
|
|
@@ -34,6 +34,12 @@ export class SlackAdapter {
|
|
|
34
34
|
onMessage;
|
|
35
35
|
onOwnerDetected;
|
|
36
36
|
app = null;
|
|
37
|
+
/**
|
|
38
|
+
* True once `app.start()` has resolved (cleared in `stop()`). Gates the
|
|
39
|
+
* watchdog probe: before the first successful start there is no socket
|
|
40
|
+
* to assess, and reporting "down" then would trigger pointless restarts.
|
|
41
|
+
*/
|
|
42
|
+
startCompleted = false;
|
|
37
43
|
botUserId = null;
|
|
38
44
|
botInfo = null;
|
|
39
45
|
pairingChallenge = null;
|
|
@@ -147,15 +153,50 @@ export class SlackAdapter {
|
|
|
147
153
|
await this.handleMessage(message);
|
|
148
154
|
});
|
|
149
155
|
await this.app.start();
|
|
156
|
+
this.startCompleted = true;
|
|
150
157
|
logger.info({ botUserId: this.botUserId }, "Slack adapter connected (Socket Mode)");
|
|
151
158
|
}
|
|
152
159
|
async stop() {
|
|
160
|
+
this.startCompleted = false;
|
|
153
161
|
if (this.app) {
|
|
154
162
|
await this.app.stop();
|
|
155
163
|
this.app = null;
|
|
156
164
|
}
|
|
157
165
|
logger.info("Slack adapter disconnected");
|
|
158
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Live Socket Mode liveness for the adapter watchdog.
|
|
169
|
+
*
|
|
170
|
+
* Bolt's `App` keeps its `SocketModeReceiver` private, but the receiver's
|
|
171
|
+
* `client` (a `@slack/socket-mode` `SocketModeClient`) and the client's
|
|
172
|
+
* `websocket` (a `SlackWebSocket` with a public `isActive()`) are public
|
|
173
|
+
* fields — only the first hop needs a cast. `@slack/socket-mode` v2
|
|
174
|
+
* reconnects on `close` with an UNBOUNDED linear backoff, but a reconnect
|
|
175
|
+
* chain can still die permanently (`UnrecoverableSocketModeStartError`,
|
|
176
|
+
* or an exception escaping the recursive retry), and after machine sleep
|
|
177
|
+
* the ping-pong watchdog may take minutes to notice a dead socket. The
|
|
178
|
+
* adapter watchdog uses this probe to force a full stop→start cycle when
|
|
179
|
+
* the socket stays dead.
|
|
180
|
+
*
|
|
181
|
+
* Returns "unknown" (watchdog: no action) when the receiver shape is not
|
|
182
|
+
* what we expect — a Bolt upgrade must degrade to "no watchdog" rather
|
|
183
|
+
* than to restart loops.
|
|
184
|
+
*/
|
|
185
|
+
getConnectionState() {
|
|
186
|
+
if (!this.app || !this.startCompleted)
|
|
187
|
+
return "unknown";
|
|
188
|
+
const receiver = this.app.receiver;
|
|
189
|
+
const websocket = receiver?.client?.websocket;
|
|
190
|
+
if (!websocket || typeof websocket.isActive !== "function") {
|
|
191
|
+
return "unknown";
|
|
192
|
+
}
|
|
193
|
+
try {
|
|
194
|
+
return websocket.isActive() ? "ok" : "down";
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return "unknown";
|
|
198
|
+
}
|
|
199
|
+
}
|
|
159
200
|
async resolveUserChannel() {
|
|
160
201
|
if (!this.app || !this.mutableOwnerId)
|
|
161
202
|
return null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MessageAdapter, OnMessageCallback, OutboundAttachmentRef } from "./types.js";
|
|
1
|
+
import type { AdapterConnectionState, MessageAdapter, OnMessageCallback, OutboundAttachmentRef } from "./types.js";
|
|
2
2
|
import type { AttachmentStore } from "../services/attachments/store.js";
|
|
3
3
|
export interface TelegramBotInfo {
|
|
4
4
|
id: number;
|
|
@@ -59,6 +59,15 @@ export declare class TelegramAdapter implements MessageAdapter {
|
|
|
59
59
|
private readonly attachmentStore;
|
|
60
60
|
private bot;
|
|
61
61
|
private botInfo;
|
|
62
|
+
/**
|
|
63
|
+
* Non-null when the long-poll loop has died. telegraf's `launch()`
|
|
64
|
+
* promise stays pending while polling runs and settles when the loop
|
|
65
|
+
* exits: it self-retries network errors / 429 / 5xx internally, but any
|
|
66
|
+
* other failure (401 invalid token, 409 conflicting consumer, an
|
|
67
|
+
* unexpected throw) kills the loop for good. Without observing that
|
|
68
|
+
* settlement the daemon would sit "connected" while receiving nothing.
|
|
69
|
+
*/
|
|
70
|
+
private pollingDeadError;
|
|
62
71
|
/** Active pairing challenge (null when pairing isn't in progress). */
|
|
63
72
|
private pairingChallenge;
|
|
64
73
|
/** Buffers for Telegram media albums (media_group_id → accumulated items). */
|
|
@@ -84,6 +93,14 @@ export declare class TelegramAdapter implements MessageAdapter {
|
|
|
84
93
|
start(): Promise<void>;
|
|
85
94
|
stop(): Promise<void>;
|
|
86
95
|
resolveUserChannel(): Promise<string | null>;
|
|
96
|
+
/**
|
|
97
|
+
* Live long-poll liveness for the adapter watchdog. "ok" means the
|
|
98
|
+
* polling loop is still running (telegraf retries transient network /
|
|
99
|
+
* 429 / 5xx failures internally, so an offline laptop stays "ok" and
|
|
100
|
+
* recovers on its own); "down" means the loop has exited and only a
|
|
101
|
+
* full stop→start cycle brings messages back.
|
|
102
|
+
*/
|
|
103
|
+
getConnectionState(): AdapterConnectionState;
|
|
87
104
|
sendMessage(params: {
|
|
88
105
|
channel: string;
|
|
89
106
|
text: string;
|