@alisio/core 0.2.0 → 0.3.0

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.
Files changed (40) hide show
  1. package/dist/application.d.ts +7 -0
  2. package/dist/application.js +90 -6
  3. package/dist/changelog/data.js +36 -0
  4. package/dist/commands/builtins.js +6 -0
  5. package/dist/commands/catalog.d.ts +8 -1
  6. package/dist/commands/catalog.js +62 -0
  7. package/dist/config.d.ts +37 -3
  8. package/dist/config.js +109 -7
  9. package/dist/core/contracts.d.ts +10 -1
  10. package/dist/core/human-wait.d.ts +25 -0
  11. package/dist/core/human-wait.js +54 -0
  12. package/dist/core/run-clock.d.ts +47 -0
  13. package/dist/core/run-clock.js +111 -0
  14. package/dist/core/run-stats.d.ts +2 -0
  15. package/dist/core/runner.d.ts +15 -1
  16. package/dist/core/runner.js +70 -18
  17. package/dist/core/timeout.js +2 -2
  18. package/dist/decisions/bound.d.ts +23 -0
  19. package/dist/decisions/bound.js +48 -0
  20. package/dist/decisions/breaker.d.ts +35 -0
  21. package/dist/decisions/breaker.js +61 -0
  22. package/dist/decisions/format.d.ts +15 -0
  23. package/dist/decisions/format.js +89 -0
  24. package/dist/decisions/index.d.ts +7 -0
  25. package/dist/decisions/index.js +7 -0
  26. package/dist/decisions/metrics.d.ts +45 -0
  27. package/dist/decisions/metrics.js +79 -0
  28. package/dist/decisions/registry.d.ts +22 -0
  29. package/dist/decisions/registry.js +64 -0
  30. package/dist/decisions/service.d.ts +137 -0
  31. package/dist/decisions/service.js +377 -0
  32. package/dist/decisions/validate.d.ts +26 -0
  33. package/dist/decisions/validate.js +182 -0
  34. package/dist/goal/host.d.ts +2 -0
  35. package/dist/goal/host.js +2 -1
  36. package/dist/index.d.ts +5 -2
  37. package/dist/index.js +3 -0
  38. package/dist/plugins/host.d.ts +43 -2
  39. package/dist/plugins/host.js +111 -16
  40. package/package.json +2 -2
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Circuit breaker with an injected clock. The caller reports only the failures that count (the
3
+ * service maps provider errors to `failure()`, `success()` or `neutral()`):
4
+ * - `closed`: calls flow; `threshold` consecutive failures open it.
5
+ * - `open`: `allow()` answers "open" until `openMs` pass, then the next caller is the single probe.
6
+ * - `half-open`: one probe in flight; concurrent callers get "open". The probe's `success()`
7
+ * closes the circuit and its `failure()` re-opens it for another `openMs`.
8
+ * `neutral()` ends a call that says nothing about the provider's health (a fast `not_ready`, a
9
+ * caller abort): it releases the probe without counting or resetting anything.
10
+ */
11
+ export class CircuitBreaker {
12
+ threshold;
13
+ openMs;
14
+ now;
15
+ failures = 0;
16
+ openedAt;
17
+ probing = false;
18
+ constructor(options = {}) {
19
+ this.threshold = options.threshold ?? 3;
20
+ this.openMs = options.openMs ?? 30_000;
21
+ this.now = options.now ?? (() => Date.now());
22
+ }
23
+ /** Asks to run one call. "half-open" means the caller IS the single probe. */
24
+ allow() {
25
+ if (this.openedAt === undefined)
26
+ return "closed";
27
+ if (this.probing)
28
+ return "open";
29
+ if (this.now() - this.openedAt < this.openMs)
30
+ return "open";
31
+ this.probing = true;
32
+ return "half-open";
33
+ }
34
+ success() {
35
+ this.reset();
36
+ }
37
+ failure() {
38
+ this.probing = false;
39
+ if (this.openedAt !== undefined) {
40
+ this.openedAt = this.now();
41
+ return;
42
+ }
43
+ this.failures++;
44
+ if (this.failures >= this.threshold)
45
+ this.openedAt = this.now();
46
+ }
47
+ neutral() {
48
+ this.probing = false;
49
+ }
50
+ reset() {
51
+ this.failures = 0;
52
+ this.openedAt = undefined;
53
+ this.probing = false;
54
+ }
55
+ /** Read-only view for diagnostics; it never consumes the probe. */
56
+ state() {
57
+ if (this.openedAt === undefined)
58
+ return "closed";
59
+ return this.probing || this.now() - this.openedAt >= this.openMs ? "half-open" : "open";
60
+ }
61
+ }
@@ -0,0 +1,15 @@
1
+ import type { DecisionStats } from "@alisio/sdk";
2
+ import type { DecisionMetricsSnapshot } from "./metrics.ts";
3
+ import type { DecisionStatus, DecisionsConfig } from "./service.ts";
4
+ /**
5
+ * The "Decision Intelligence" block of `/stats` (spec §9.2), shared by the core handler and the
6
+ * TUI report. `undefined` when no decision happened, so the section is omitted.
7
+ */
8
+ export declare function formatDecisionStatsSection(stats: DecisionStats): string | undefined;
9
+ /** The `/decisions` report (spec §9.1). */
10
+ export declare function formatDecisionsReport(input: {
11
+ status: DecisionStatus;
12
+ config: Pick<DecisionsConfig, "timeoutMs" | "minConfidence" | "telemetry">;
13
+ session: DecisionMetricsSnapshot;
14
+ process: DecisionMetricsSnapshot;
15
+ }): string;
@@ -0,0 +1,89 @@
1
+ /** `12:04:31 UTC`: deterministic, independent of the viewer's time zone. */
2
+ const clock = (iso) => {
3
+ const date = new Date(iso);
4
+ return Number.isNaN(date.getTime()) ? iso : `${date.toISOString().slice(11, 19)} UTC`;
5
+ };
6
+ const latency = (s) => s.avgLatencyMs === undefined ? undefined : `avg ${s.avgLatencyMs} ms · p95 ${s.p95LatencyMs} ms`;
7
+ const packsLine = (packs) => Object.entries(packs)
8
+ .map(([id, count]) => `${id} (${count})`)
9
+ .join(", ");
10
+ const lastFallbackLine = (f) => `Last fallback: ${f.reason} (${f.decisionId}) at ${clock(f.at)}`;
11
+ /**
12
+ * The "Decision Intelligence" block of `/stats` (spec §9.2), shared by the core handler and the
13
+ * TUI report. `undefined` when no decision happened, so the section is omitted.
14
+ */
15
+ export function formatDecisionStatsSection(stats) {
16
+ if (stats.requests <= 0)
17
+ return undefined;
18
+ const lat = latency(stats);
19
+ const packs = packsLine(stats.packs);
20
+ return [
21
+ "**Decision Intelligence**",
22
+ "",
23
+ ...(stats.provider ? [`- Provider: ${stats.provider}`] : []),
24
+ `- Requests: ${stats.requests} · completed ${stats.completed} · fallbacks ${stats.fallbacks}`,
25
+ ...(lat ? [`- Latency: ${lat}`] : []),
26
+ ...(stats.lastFallback ? [`- ${lastFallbackLine(stats.lastFallback)}`] : []),
27
+ ...(packs ? [`- Packs: ${packs}`] : []),
28
+ ].join("\n");
29
+ }
30
+ const MESSAGE_LIMIT = 200;
31
+ /** The `/decisions` report (spec §9.1). */
32
+ export function formatDecisionsReport(input) {
33
+ const { status, config, session, process } = input;
34
+ let head;
35
+ if (!status.enabled)
36
+ head = "disabled";
37
+ else if (status.configured === null)
38
+ head = "enabled · no provider configured (set decisions.provider)";
39
+ else if (!status.active)
40
+ head = `enabled · provider "${status.configured}" is not registered`;
41
+ else {
42
+ const health = status.health
43
+ ? `${status.health.status}${status.health.detail ? ` (${status.health.detail})` : ""}`
44
+ : "unknown";
45
+ head = `enabled · provider: ${status.active.id} (${status.active.name}) · health: ${health}`;
46
+ }
47
+ const caps = status.active
48
+ ? Object.entries(status.active.capabilities)
49
+ .filter(([, on]) => on === true)
50
+ .map(([name]) => name)
51
+ : [];
52
+ const err = status.lastLifecycleError;
53
+ const row = (label, m) => `| ${label} | ${m.requests} | ${m.completed} | ${m.fallbacks} |`;
54
+ const lat = [
55
+ latency(session) ? `session ${latency(session)}` : undefined,
56
+ latency(process) ? `process ${latency(process)}` : undefined,
57
+ ].filter(Boolean);
58
+ const fallback = session.lastFallback ?? process.lastFallback;
59
+ const packScope = Object.keys(session.packs).length ? session.packs : process.packs;
60
+ const packs = packsLine(packScope);
61
+ return [
62
+ "**Decision Intelligence**",
63
+ "",
64
+ `- Status: ${head}`,
65
+ ...(caps.length ? [`- Capabilities: ${caps.join(", ")}`] : []),
66
+ ...(status.active ? [`- Circuit: ${status.circuit}`] : []),
67
+ `- Timeout: ${config.timeoutMs} ms · min confidence: ${config.minConfidence} · telemetry: ${config.telemetry ? "on" : "off"}`,
68
+ ...(err
69
+ ? [
70
+ `- Lifecycle error: ${err.phase} of ${err.provider} failed: ${err.message.slice(0, MESSAGE_LIMIT)}`,
71
+ ]
72
+ : []),
73
+ "",
74
+ ...(process.requests > 0
75
+ ? [
76
+ "| Scope | Requests | Completed | Fallbacks |",
77
+ "| --- | --- | --- | --- |",
78
+ row("This session", session),
79
+ row("Process", process),
80
+ "",
81
+ ...(lat.length ? [`- Latency: ${lat.join("; ")}`] : []),
82
+ ...(fallback ? [`- ${lastFallbackLine(fallback)}`] : []),
83
+ ...(packs
84
+ ? [`- Packs${Object.keys(session.packs).length ? "" : " (process)"}: ${packs}`]
85
+ : []),
86
+ ]
87
+ : ["No decisions yet in this process."]),
88
+ ].join("\n");
89
+ }
@@ -0,0 +1,7 @@
1
+ export { bindDecisions, type DecisionAnnouncer, outcomeEvent } from "./bound.ts";
2
+ export { CircuitBreaker, type CircuitBreakerOptions, type CircuitState } from "./breaker.ts";
3
+ export { formatDecisionStatsSection, formatDecisionsReport } from "./format.ts";
4
+ export { type DecisionMetricEntry, DecisionMetrics, type DecisionMetricsSnapshot, } from "./metrics.ts";
5
+ export { DecisionRegistry, type RegisteredDecisionProvider } from "./registry.ts";
6
+ export { DEFAULT_DECISIONS_CONFIG, type DecisionAttempt, DecisionError, type DecisionOutcome, DecisionService, type DecisionServiceDeps, type DecisionStatus, type DecisionsConfig, HEALTH_TIMEOUT_MS, } from "./service.ts";
7
+ export { DECISION_LIMITS, splitSupported, validateAnswers, validateRequest } from "./validate.ts";
@@ -0,0 +1,7 @@
1
+ export { bindDecisions, outcomeEvent } from "./bound.js";
2
+ export { CircuitBreaker } from "./breaker.js";
3
+ export { formatDecisionStatsSection, formatDecisionsReport } from "./format.js";
4
+ export { DecisionMetrics, } from "./metrics.js";
5
+ export { DecisionRegistry } from "./registry.js";
6
+ export { DEFAULT_DECISIONS_CONFIG, DecisionError, DecisionService, HEALTH_TIMEOUT_MS, } from "./service.js";
7
+ export { DECISION_LIMITS, splitSupported, validateAnswers, validateRequest } from "./validate.js";
@@ -0,0 +1,45 @@
1
+ import type { DecisionFallbackReason } from "@alisio/sdk";
2
+ export type DecisionMetricEntry = {
3
+ status: "completed";
4
+ decisionId: string;
5
+ pack?: {
6
+ id: string;
7
+ version: number;
8
+ };
9
+ provider: string;
10
+ latencyMs: number;
11
+ } | {
12
+ status: "fallback";
13
+ decisionId: string;
14
+ pack?: {
15
+ id: string;
16
+ version: number;
17
+ };
18
+ provider: string;
19
+ latencyMs: number;
20
+ reason: DecisionFallbackReason;
21
+ };
22
+ export interface DecisionMetricsSnapshot {
23
+ requests: number;
24
+ completed: number;
25
+ fallbacks: number;
26
+ avgLatencyMs?: number;
27
+ p95LatencyMs?: number;
28
+ lastFallback?: {
29
+ reason: DecisionFallbackReason;
30
+ decisionId: string;
31
+ at: string;
32
+ };
33
+ packs: Record<string, number>;
34
+ provider?: string;
35
+ }
36
+ /** In-memory, per-process decision metrics with an optional per-session view. Never persisted. */
37
+ export declare class DecisionMetrics {
38
+ private now;
39
+ private process;
40
+ private sessions;
41
+ constructor(now?: () => number);
42
+ record(entry: DecisionMetricEntry, sessionId?: string): void;
43
+ /** The session's view, or the whole process when no session is given. */
44
+ snapshot(sessionId?: string): DecisionMetricsSnapshot;
45
+ }
@@ -0,0 +1,79 @@
1
+ const LATENCY_RING = 200;
2
+ const MAX_SESSIONS = 200;
3
+ class Tally {
4
+ completed = 0;
5
+ fallbacks = 0;
6
+ provider;
7
+ lastFallback;
8
+ packs = new Map();
9
+ /** Ring of the last completed latencies. */
10
+ latencies = [];
11
+ next = 0;
12
+ add(entry, at) {
13
+ this.provider = entry.provider;
14
+ if (entry.pack)
15
+ this.packs.set(entry.pack.id, (this.packs.get(entry.pack.id) ?? 0) + 1);
16
+ if (entry.status === "completed") {
17
+ this.completed++;
18
+ if (this.latencies.length < LATENCY_RING)
19
+ this.latencies.push(entry.latencyMs);
20
+ else
21
+ this.latencies[this.next] = entry.latencyMs;
22
+ this.next = (this.next + 1) % LATENCY_RING;
23
+ }
24
+ else {
25
+ this.fallbacks++;
26
+ this.lastFallback = { reason: entry.reason, decisionId: entry.decisionId, at };
27
+ }
28
+ }
29
+ snapshot() {
30
+ const out = {
31
+ requests: this.completed + this.fallbacks,
32
+ completed: this.completed,
33
+ fallbacks: this.fallbacks,
34
+ packs: Object.fromEntries(this.packs),
35
+ };
36
+ if (this.latencies.length) {
37
+ const sorted = [...this.latencies].sort((a, b) => a - b);
38
+ out.avgLatencyMs = Math.round(sorted.reduce((sum, v) => sum + v, 0) / sorted.length);
39
+ out.p95LatencyMs = sorted[Math.ceil(0.95 * sorted.length) - 1];
40
+ }
41
+ if (this.lastFallback)
42
+ out.lastFallback = { ...this.lastFallback };
43
+ if (this.provider)
44
+ out.provider = this.provider;
45
+ return out;
46
+ }
47
+ }
48
+ /** In-memory, per-process decision metrics with an optional per-session view. Never persisted. */
49
+ export class DecisionMetrics {
50
+ now;
51
+ process = new Tally();
52
+ sessions = new Map();
53
+ constructor(now = () => Date.now()) {
54
+ this.now = now;
55
+ }
56
+ record(entry, sessionId) {
57
+ const at = new Date(this.now()).toISOString();
58
+ this.process.add(entry, at);
59
+ if (!sessionId)
60
+ return;
61
+ let tally = this.sessions.get(sessionId);
62
+ if (!tally) {
63
+ if (this.sessions.size >= MAX_SESSIONS) {
64
+ const oldest = this.sessions.keys().next().value;
65
+ if (oldest !== undefined)
66
+ this.sessions.delete(oldest);
67
+ }
68
+ tally = new Tally();
69
+ this.sessions.set(sessionId, tally);
70
+ }
71
+ tally.add(entry, at);
72
+ }
73
+ /** The session's view, or the whole process when no session is given. */
74
+ snapshot(sessionId) {
75
+ if (sessionId === undefined)
76
+ return this.process.snapshot();
77
+ return (this.sessions.get(sessionId) ?? new Tally()).snapshot();
78
+ }
79
+ }
@@ -0,0 +1,22 @@
1
+ import type { DecisionProvider } from "@alisio/sdk";
2
+ export interface RegisteredDecisionProvider {
3
+ owner: string;
4
+ provider: DecisionProvider;
5
+ }
6
+ /** Catalog of decision providers. Registering never activates: the configuration picks one. */
7
+ export declare class DecisionRegistry {
8
+ private entries;
9
+ private listeners;
10
+ /** Notified after every successful registration and removal (never throws into callers). */
11
+ onChange(listener: () => void): () => void;
12
+ private changed;
13
+ /** Returns the function that removes this registration (a no-op once it was replaced). */
14
+ register(owner: string, provider: DecisionProvider): () => void;
15
+ get(id: string): RegisteredDecisionProvider | undefined;
16
+ list(): Array<{
17
+ id: string;
18
+ name: string;
19
+ owner: string;
20
+ capabilities: DecisionProvider["capabilities"];
21
+ }>;
22
+ }
@@ -0,0 +1,64 @@
1
+ const PROVIDER_ID = /^[a-z0-9][a-z0-9.-]{0,63}$/;
2
+ /** Catalog of decision providers. Registering never activates: the configuration picks one. */
3
+ export class DecisionRegistry {
4
+ entries = new Map();
5
+ listeners = new Set();
6
+ /** Notified after every successful registration and removal (never throws into callers). */
7
+ onChange(listener) {
8
+ this.listeners.add(listener);
9
+ return () => {
10
+ this.listeners.delete(listener);
11
+ };
12
+ }
13
+ changed() {
14
+ for (const listener of this.listeners) {
15
+ try {
16
+ listener();
17
+ }
18
+ catch {
19
+ /* Observers cannot break registration. */
20
+ }
21
+ }
22
+ }
23
+ /** Returns the function that removes this registration (a no-op once it was replaced). */
24
+ register(owner, provider) {
25
+ if (!provider || typeof provider !== "object")
26
+ throw new Error("Invalid decision provider");
27
+ if (typeof provider.id !== "string" || !PROVIDER_ID.test(provider.id))
28
+ throw new Error(`Invalid decision provider id: ${String(provider.id)}`);
29
+ if (typeof provider.name !== "string" || !provider.name.trim())
30
+ throw new Error(`Decision provider ${provider.id} needs a name`);
31
+ const capabilities = provider.capabilities;
32
+ if (!capabilities ||
33
+ typeof capabilities.select !== "boolean" ||
34
+ typeof capabilities.boolean !== "boolean" ||
35
+ typeof capabilities.ordinal !== "boolean")
36
+ throw new Error(`Decision provider ${provider.id} needs boolean capabilities`);
37
+ if (typeof provider.decide !== "function")
38
+ throw new Error(`Decision provider ${provider.id} needs a decide function`);
39
+ if (this.entries.has(provider.id))
40
+ throw new Error(`Duplicate decision provider: ${provider.id}`);
41
+ const entry = { owner, provider };
42
+ this.entries.set(provider.id, entry);
43
+ this.changed();
44
+ return () => {
45
+ if (this.entries.get(provider.id) !== entry)
46
+ return;
47
+ this.entries.delete(provider.id);
48
+ this.changed();
49
+ };
50
+ }
51
+ get(id) {
52
+ return this.entries.get(id);
53
+ }
54
+ list() {
55
+ return [...this.entries.values()]
56
+ .map(({ owner, provider }) => ({
57
+ id: provider.id,
58
+ name: provider.name,
59
+ owner,
60
+ capabilities: { ...provider.capabilities },
61
+ }))
62
+ .sort((a, b) => a.id.localeCompare(b.id));
63
+ }
64
+ }
@@ -0,0 +1,137 @@
1
+ import { type DecisionFallbackReason, type DecisionOptions, type DecisionProvider, type DecisionRequest, type DecisionResponse, type DecisionsApi } from "@alisio/sdk";
2
+ import { type CircuitBreakerOptions, type CircuitState } from "./breaker.ts";
3
+ import type { DecisionMetrics } from "./metrics.ts";
4
+ import type { DecisionRegistry } from "./registry.ts";
5
+ /** The slice of the `decisions` configuration block the service reads (live, per call). */
6
+ export interface DecisionsConfig {
7
+ enabled: boolean;
8
+ /** Id of the active provider; `null` disables the feature. */
9
+ provider: string | null;
10
+ timeoutMs: number;
11
+ minConfidence: number;
12
+ telemetry: boolean;
13
+ }
14
+ export declare const DEFAULT_DECISIONS_CONFIG: DecisionsConfig;
15
+ /** `/decisions` asks the provider's health with its own short limit. */
16
+ export declare const HEALTH_TIMEOUT_MS = 1000;
17
+ type Pack = {
18
+ id: string;
19
+ version: number;
20
+ };
21
+ export type DecisionOutcome = {
22
+ status: "completed";
23
+ decisionId: string;
24
+ pack?: Pack;
25
+ provider: string;
26
+ latencyMs: number;
27
+ decisionCount: number;
28
+ rejectedCount: number;
29
+ confidenceMin: number;
30
+ } | {
31
+ status: "fallback";
32
+ decisionId: string;
33
+ pack?: Pack;
34
+ provider: string;
35
+ latencyMs: number;
36
+ reason: DecisionFallbackReason;
37
+ };
38
+ export interface DecisionAttempt {
39
+ response: DecisionResponse | null;
40
+ /** Absent when no attempt happened (disabled or no active provider). */
41
+ outcome?: DecisionOutcome;
42
+ }
43
+ /** `decide()` failure: the typed counterpart of a `null` from `tryDecide`. */
44
+ export declare class DecisionError extends Error {
45
+ readonly reason: DecisionFallbackReason | "disabled" | "no_provider";
46
+ constructor(reason: DecisionFallbackReason | "disabled" | "no_provider", message?: string);
47
+ }
48
+ export interface DecisionStatus {
49
+ enabled: boolean;
50
+ /** The configured provider id, whether or not it is registered. */
51
+ configured: string | null;
52
+ active: {
53
+ id: string;
54
+ name: string;
55
+ capabilities: DecisionProvider["capabilities"];
56
+ } | null;
57
+ registered: ReturnType<DecisionRegistry["list"]>;
58
+ health?: {
59
+ status: "ready" | "starting" | "unavailable";
60
+ detail?: string;
61
+ };
62
+ circuit: CircuitState;
63
+ /** Last `activate`/`deactivate` failure of any provider (cleared by a later successful activate). */
64
+ lastLifecycleError?: {
65
+ provider: string;
66
+ phase: "activate" | "deactivate";
67
+ message: string;
68
+ at: number;
69
+ };
70
+ }
71
+ export interface DecisionServiceDeps {
72
+ registry: DecisionRegistry;
73
+ /** Read on every call so configuration changes apply to the next decision. */
74
+ config: () => DecisionsConfig;
75
+ metrics: DecisionMetrics;
76
+ now?: () => number;
77
+ breaker?: CircuitBreakerOptions;
78
+ /** Provider lifecycle (ADR-11): the limit of each call and where failures are logged. */
79
+ lifecycle?: {
80
+ timeoutMs?: () => number;
81
+ log?: (message: string) => void;
82
+ };
83
+ }
84
+ /**
85
+ * Decision Intelligence service: one active provider (chosen by configuration), request and answer
86
+ * validation per decision, a core-enforced time limit and a circuit breaker. Without an active
87
+ * provider it is a silent null object: no metrics, no events.
88
+ */
89
+ export declare class DecisionService implements Omit<DecisionsApi, "registerProvider"> {
90
+ private readonly registry;
91
+ private readonly config;
92
+ private readonly metrics;
93
+ private readonly now;
94
+ private readonly breaker;
95
+ /** Registration the breaker state belongs to; a change of provider resets it. */
96
+ private breakerOwner;
97
+ private readonly lifecycle;
98
+ /** The registration `activate` was last requested for (the one that is active, lifecycle-wise). */
99
+ private lifecycleEntry;
100
+ private lifecycleOn;
101
+ private closed;
102
+ /** Per-registration queue: `deactivate` never crosses a still-running `activate`. */
103
+ private queues;
104
+ private lifecycleError;
105
+ constructor(deps: DecisionServiceDeps);
106
+ /**
107
+ * Lifecycle (ADR-11): makes `activate`/`deactivate` follow what the configuration and the
108
+ * registry say is the active provider. Idempotent, synchronous and never throws: the hooks run
109
+ * in the background (bounded, serialized per provider, failures recorded). The first call (after
110
+ * the plugins loaded) also arms the follow-up on registry changes.
111
+ */
112
+ syncActive(): void;
113
+ /** Close: deactivates the active provider (bounded by `timeoutMs`) and stops following changes. */
114
+ shutdown(timeoutMs?: number): Promise<void>;
115
+ private enqueue;
116
+ /** Runs one hook with a limit; resolves always (a failure is recorded, never propagated). */
117
+ private runLifecycle;
118
+ private active;
119
+ available(): boolean;
120
+ activeProvider(): {
121
+ id: string;
122
+ name: string;
123
+ } | null;
124
+ tryDecide(req: DecisionRequest, opts?: DecisionOptions): Promise<DecisionResponse | null>;
125
+ /** Like `tryDecide`, but a missing response throws a typed `DecisionError`. */
126
+ decide(req: DecisionRequest, opts?: DecisionOptions): Promise<DecisionResponse>;
127
+ /**
128
+ * The full result of one call, with the outcome the run integration turns into an event.
129
+ * Throws `DecisionRequestError` for a malformed request and rethrows the caller's abort.
130
+ */
131
+ attempt(req: DecisionRequest, opts?: DecisionOptions, ctx?: {
132
+ sessionId?: string;
133
+ }): Promise<DecisionAttempt>;
134
+ /** For `/decisions` only: the one place `health()` is called, with its own short limit. */
135
+ status(signal?: AbortSignal): Promise<DecisionStatus>;
136
+ }
137
+ export {};