@bastani/atomic 0.9.39 → 0.9.40-alpha.1
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/CHANGELOG.md +28 -0
- package/dist/builtin/intercom/CHANGELOG.md +14 -0
- package/dist/builtin/intercom/broker/broker.ts +523 -72
- package/dist/builtin/intercom/broker/client.ts +136 -5
- package/dist/builtin/intercom/broker/database-wake-up-bus.ts +274 -0
- package/dist/builtin/intercom/broker/group-membership.ts +2 -2
- package/dist/builtin/intercom/broker/multinode-router.ts +1500 -0
- package/dist/builtin/intercom/broker/multinode-state-store.ts +569 -0
- package/dist/builtin/intercom/broker/open-broker-state.ts +160 -0
- package/dist/builtin/intercom/broker/paths.ts +6 -0
- package/dist/builtin/intercom/broker/pending-send-registry.ts +14 -0
- package/dist/builtin/intercom/broker/recipient-receipts.ts +93 -0
- package/dist/builtin/intercom/broker/restoring-recipients.ts +263 -0
- package/dist/builtin/intercom/broker/send-handler.ts +241 -25
- package/dist/builtin/intercom/broker/session-id-reclaim.ts +41 -0
- package/dist/builtin/intercom/broker/socket-writes.ts +47 -0
- package/dist/builtin/intercom/broker/spawn.ts +10 -2
- package/dist/builtin/intercom/broker/sql-held-send-store.ts +96 -0
- package/dist/builtin/intercom/broker/sql-known-session-store.ts +129 -0
- package/dist/builtin/intercom/broker/sql-lease-store.ts +194 -0
- package/dist/builtin/intercom/broker/sql-outbox-store.ts +249 -0
- package/dist/builtin/intercom/broker/sql-pending-ask-store.ts +105 -0
- package/dist/builtin/intercom/broker/sql-restoring-reservation-store.ts +295 -0
- package/dist/builtin/intercom/broker/sql-sent-message-authority.ts +126 -0
- package/dist/builtin/intercom/broker/supervisor-channel.ts +10 -0
- package/dist/builtin/intercom/broker/wake-up-bus.ts +60 -0
- package/dist/builtin/intercom/index.bundle.mjs +793 -91
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/intercom/retry-policy.ts +4 -0
- package/dist/builtin/intercom/session-target.ts +24 -0
- package/dist/builtin/intercom/types.ts +57 -3
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +31 -0
- package/dist/builtin/subagents/README.md +18 -12
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/skills/agent-browser/SKILL.md +1 -1
- package/dist/builtin/subagents/skills/cua-driver/BROWSER.md +55 -8
- package/dist/builtin/subagents/skills/cua-driver/EMBEDDING.md +7 -2
- package/dist/builtin/subagents/skills/cua-driver/LINUX.md +7 -1
- package/dist/builtin/subagents/skills/cua-driver/MACOS.md +151 -27
- package/dist/builtin/subagents/skills/cua-driver/RUNTIME.md +1 -1
- package/dist/builtin/subagents/skills/cua-driver/SKILL.md +33 -17
- package/dist/builtin/subagents/skills/cua-driver/WINDOWS.md +5 -0
- package/dist/builtin/subagents/skills/cua-driver/WORKFLOW.md +46 -8
- package/dist/builtin/subagents/skills/subagent/SKILL.md +18 -20
- package/dist/builtin/subagents/src/extension/index.bundle.mjs +8032 -6034
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +18 -0
- package/dist/builtin/workflows/README.md +1 -1
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/skills/impeccable/SKILL.md +8 -8
- package/dist/builtin/workflows/skills/impeccable/reference/adapt.md +3 -3
- package/dist/builtin/workflows/skills/impeccable/reference/adapt.native.md +1 -1
- package/dist/builtin/workflows/skills/impeccable/reference/audit.md +4 -2
- package/dist/builtin/workflows/skills/impeccable/reference/audit.native.md +2 -2
- package/dist/builtin/workflows/skills/impeccable/reference/craft-floor.md +1 -1
- package/dist/builtin/workflows/skills/impeccable/reference/critique.md +22 -9
- package/dist/builtin/workflows/skills/impeccable/reference/degraded/finish-reviewer.md +2 -2
- package/dist/builtin/workflows/skills/impeccable/reference/distill.md +3 -3
- package/dist/builtin/workflows/skills/impeccable/reference/extract.md +3 -3
- package/dist/builtin/workflows/skills/impeccable/reference/harden.md +2 -4
- package/dist/builtin/workflows/skills/impeccable/reference/hooks.md +4 -4
- package/dist/builtin/workflows/skills/impeccable/reference/live.md +1 -1
- package/dist/builtin/workflows/skills/impeccable/reference/new-work.md +6 -6
- package/dist/builtin/workflows/skills/impeccable/reference/onboard.md +3 -3
- package/dist/builtin/workflows/skills/impeccable/reference/optimize.md +3 -3
- package/dist/builtin/workflows/skills/impeccable/reference/overdrive.md +3 -3
- package/dist/builtin/workflows/skills/impeccable/reference/quieter.md +3 -3
- package/dist/builtin/workflows/skills/impeccable/reference/typeset.md +10 -0
- package/dist/builtin/workflows/skills/impeccable/reference/visualize.md +1 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/VERSION +1 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/impeccable +6 -0
- package/dist/builtin/workflows/skills/impeccable/scripts/impeccable.cmd +47 -22
- package/dist/builtin/workflows/skills/impeccable/scripts/live-browser-dom.js +34 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/live-browser-session.js +25 -1
- package/dist/builtin/workflows/skills/impeccable/scripts/live-browser.js +33 -8
- package/dist/builtin/workflows/src/durable/journal/resolution.d.ts +7 -7
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +22855 -22308
- package/dist/builtin/workflows/src/index.js +106 -35
- package/dist/cli/args.d.ts +2 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +16 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session-extension-bindings.d.ts +2 -0
- package/dist/core/agent-session-extension-bindings.d.ts.map +1 -1
- package/dist/core/agent-session-extension-bindings.js +6 -6
- package/dist/core/agent-session-extension-bindings.js.map +1 -1
- package/dist/core/agent-session-methods.d.ts +6 -3
- package/dist/core/agent-session-methods.d.ts.map +1 -1
- package/dist/core/agent-session-methods.js.map +1 -1
- package/dist/core/agent-session-persistent-custom-messages.d.ts +7 -2
- package/dist/core/agent-session-persistent-custom-messages.d.ts.map +1 -1
- package/dist/core/agent-session-persistent-custom-messages.js +29 -12
- package/dist/core/agent-session-persistent-custom-messages.js.map +1 -1
- package/dist/core/agent-session-recovery.d.ts +14 -0
- package/dist/core/agent-session-recovery.d.ts.map +1 -0
- package/dist/core/agent-session-recovery.js +83 -0
- package/dist/core/agent-session-recovery.js.map +1 -0
- package/dist/core/agent-session-tasks.d.ts.map +1 -1
- package/dist/core/agent-session-tasks.js +4 -6
- package/dist/core/agent-session-tasks.js.map +1 -1
- package/dist/core/agent-session-types.d.ts +13 -0
- package/dist/core/agent-session-types.d.ts.map +1 -1
- package/dist/core/agent-session-types.js.map +1 -1
- package/dist/core/agent-session.d.ts +2 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +4 -1
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/api-types.d.ts +6 -2
- package/dist/core/extensions/api-types.d.ts.map +1 -1
- package/dist/core/extensions/api-types.js.map +1 -1
- package/dist/core/extensions/event-results.d.ts +21 -0
- package/dist/core/extensions/event-results.d.ts.map +1 -1
- package/dist/core/extensions/event-results.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader-api.d.ts.map +1 -1
- package/dist/core/extensions/loader-api.js +4 -0
- package/dist/core/extensions/loader-api.js.map +1 -1
- package/dist/core/extensions/runner-events.d.ts +9 -2
- package/dist/core/extensions/runner-events.d.ts.map +1 -1
- package/dist/core/extensions/runner-events.js +29 -0
- package/dist/core/extensions/runner-events.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +5 -3
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +7 -2
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/runtime-types.d.ts +1 -0
- package/dist/core/extensions/runtime-types.d.ts.map +1 -1
- package/dist/core/extensions/runtime-types.js.map +1 -1
- package/dist/core/extensions/session-events.d.ts +30 -2
- package/dist/core/extensions/session-events.d.ts.map +1 -1
- package/dist/core/extensions/session-events.js.map +1 -1
- package/dist/core/sdk-types.d.ts +3 -2
- package/dist/core/sdk-types.d.ts.map +1 -1
- package/dist/core/sdk-types.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +3 -0
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-storage.d.ts.map +1 -1
- package/dist/core/session-storage.js +15 -9
- package/dist/core/session-storage.js.map +1 -1
- package/dist/core/storage/process-selection.d.ts +39 -0
- package/dist/core/storage/process-selection.d.ts.map +1 -0
- package/dist/core/storage/process-selection.js +154 -0
- package/dist/core/storage/process-selection.js.map +1 -0
- package/dist/core/storage/provider.d.ts +6 -1
- package/dist/core/storage/provider.d.ts.map +1 -1
- package/dist/core/storage/provider.js +3 -2
- package/dist/core/storage/provider.js.map +1 -1
- package/dist/core/storage/selection.d.ts +1 -1
- package/dist/core/storage/selection.d.ts.map +1 -1
- package/dist/core/storage/selection.js.map +1 -1
- package/dist/core/storage/session/jsonl-session-store.d.ts +2 -0
- package/dist/core/storage/session/jsonl-session-store.d.ts.map +1 -1
- package/dist/core/storage/session/jsonl-session-store.js +38 -2
- package/dist/core/storage/session/jsonl-session-store.js.map +1 -1
- package/dist/core/tasks/completion.d.ts +11 -2
- package/dist/core/tasks/completion.d.ts.map +1 -1
- package/dist/core/tasks/completion.js +40 -16
- package/dist/core/tasks/completion.js.map +1 -1
- package/dist/core/tasks/pause.d.ts +4 -1
- package/dist/core/tasks/pause.d.ts.map +1 -1
- package/dist/core/tasks/pause.js +8 -5
- package/dist/core/tasks/pause.js.map +1 -1
- package/dist/index-extensions.d.ts +1 -1
- package/dist/index-extensions.d.ts.map +1 -1
- package/dist/index-extensions.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +7 -1
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-rendering.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-rendering.js +11 -1
- package/dist/modes/interactive/components/chat-session-host-rendering.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-state.d.ts +2 -0
- package/dist/modes/interactive/components/chat-session-host-state.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-state.js +2 -0
- package/dist/modes/interactive/components/chat-session-host-state.js.map +1 -1
- package/dist/modes/interactive/interactive-deferred-startup.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-deferred-startup.js +4 -0
- package/dist/modes/interactive/interactive-deferred-startup.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-surface.d.ts +2 -1
- package/dist/modes/interactive/interactive-mode-surface.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-surface.js.map +1 -1
- package/dist/modes/interactive/interactive-render-chat.js +3 -0
- package/dist/modes/interactive/interactive-render-chat.js.map +1 -1
- package/dist/modes/interactive/interactive-session-routing.js +2 -0
- package/dist/modes/interactive/interactive-session-routing.js.map +1 -1
- package/dist/modes/interactive/interactive-session-runtime.d.ts +2 -1
- package/dist/modes/interactive/interactive-session-runtime.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-session-runtime.js +29 -0
- package/dist/modes/interactive/interactive-session-runtime.js.map +1 -1
- package/dist/modes/interactive/interactive-startup.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-startup.js +4 -1
- package/dist/modes/interactive/interactive-startup.js.map +1 -1
- package/dist/modes/interactive-engine/engine-args.d.ts.map +1 -1
- package/dist/modes/interactive-engine/engine-args.js +4 -0
- package/dist/modes/interactive-engine/engine-args.js.map +1 -1
- package/dist/modes/interactive-engine/engine-render-service.d.ts.map +1 -1
- package/dist/modes/interactive-engine/engine-render-service.js +4 -4
- package/dist/modes/interactive-engine/engine-render-service.js.map +1 -1
- package/dist/modes/interactive-engine/isolated-runtime.d.ts.map +1 -1
- package/dist/modes/interactive-engine/isolated-runtime.js +4 -0
- package/dist/modes/interactive-engine/isolated-runtime.js.map +1 -1
- package/dist/modes/interactive-engine/remote-renderer.js +1 -1
- package/dist/modes/interactive-engine/remote-renderer.js.map +1 -1
- package/dist/modes/rpc/rpc-client-api.d.ts +4 -1
- package/dist/modes/rpc/rpc-client-api.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client-api.js +4 -0
- package/dist/modes/rpc/rpc-client-api.js.map +1 -1
- package/dist/modes/rpc/rpc-command-handler.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-command-handler.js +3 -0
- package/dist/modes/rpc/rpc-command-handler.js.map +1 -1
- package/dist/modes/rpc/rpc-command-timeouts.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-command-timeouts.js +1 -0
- package/dist/modes/rpc/rpc-command-timeouts.js.map +1 -1
- package/dist/modes/rpc/rpc-input-scheduler.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-input-scheduler.js +6 -1
- package/dist/modes/rpc/rpc-input-scheduler.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +12 -1
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +44 -11
- package/dist/utils/shell.js.map +1 -1
- package/dist/utils/tools-manager.js +1 -1
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/background-tasks.md +5 -3
- package/docs/computer-use.md +2 -2
- package/docs/environment-variables.md +4 -1
- package/docs/extensions/events.md +29 -2
- package/docs/getting-started/first-session.md +1 -1
- package/docs/guides/intercom.md +1 -1
- package/docs/intercom/operations.md +103 -1
- package/docs/intercom/reference.md +10 -3
- package/docs/intercom.md +8 -6
- package/docs/keybindings.md +1 -1
- package/docs/models.md +1 -1
- package/docs/providers.md +4 -2
- package/docs/reference/cli.md +5 -0
- package/docs/rpc/protocol.md +19 -0
- package/docs/rpc.md +3 -0
- package/docs/sdk/web-server.md +1 -1
- package/docs/sdk.md +5 -2
- package/docs/sessions.md +17 -1
- package/docs/settings.md +5 -3
- package/docs/skills.md +1 -1
- package/docs/storage.md +54 -3
- package/docs/subagents.md +49 -9
- package/docs/usage.md +1 -1
- package/docs/windows.md +14 -4
- package/docs/workflows/api-reference.md +1 -1
- package/docs/workflows/authoring.md +1 -1
- package/docs/workflows/operations.md +10 -6
- package/npm-shrinkwrap.json +37 -37
- package/package.json +4 -4
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import type { SqliteDatabase } from "@bastani/atomic/storage";
|
|
3
|
+
import type { BrokerMessage, ClientMessage, SessionInfo } from "../types.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Multi-node broker state seams (specs/2026-10-19-intercom-multi-node.md). Brokers on different hosts share this
|
|
7
|
+
* state through the selected `storage` adapter; the database is the source of truth and wake-ups are only hints.
|
|
8
|
+
*
|
|
9
|
+
* Shared conventions:
|
|
10
|
+
* - Every operation reports its outcome as a discriminated result; an empty identifier yields `invalid`.
|
|
11
|
+
* - Ages are measured on the store clock (the database clock for SQL stores), never the caller's clock.
|
|
12
|
+
* - A lease or claim is live while `ageMs <= staleAfterMs`; a negative `staleAfterMs` treats it as expired.
|
|
13
|
+
* - Expiry only makes a lease or claim takeable. Its token is fenced (rejected as `stale`) once another
|
|
14
|
+
* acquisition supersedes it; until then the holder may still renew it.
|
|
15
|
+
* - Text, frames and requests are stored and returned verbatim, and returned values are detached copies.
|
|
16
|
+
* - Lists come back in insertion order unless an operation says otherwise.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** How often a broker renews its membership and presence leases. */
|
|
20
|
+
export const BROKER_LEASE_RENEW_INTERVAL_MS = 10_000;
|
|
21
|
+
/** Age after which another broker may take a membership or presence lease over. */
|
|
22
|
+
export const BROKER_LEASE_STALE_AFTER_MS = 30_000;
|
|
23
|
+
/** Age after which another broker may claim an outbox row whose claimant stopped renewing. */
|
|
24
|
+
export const OUTBOX_CLAIM_STALE_AFTER_MS = 30_000;
|
|
25
|
+
/** Safety-net outbox scan interval for brokers whose wake-ups were lost. */
|
|
26
|
+
export const OUTBOX_BACKUP_POLL_INTERVAL_MS = 30_000;
|
|
27
|
+
/** How long a session that left stays known, so a send to it is queued for its return (spec §14, S5 amendment). */
|
|
28
|
+
export const KNOWN_SESSION_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
|
|
29
|
+
|
|
30
|
+
/** The exact frame a broker writes to a recipient's socket; a cross-node delivery writes the same frame. */
|
|
31
|
+
export type IntercomDeliveryFrame = Extract<BrokerMessage, { type: "message" }>;
|
|
32
|
+
|
|
33
|
+
/** A send held for a recipient being restored (#3760), dispatched again verbatim when the hold ends. */
|
|
34
|
+
export type HeldSendRequest = Extract<ClientMessage, { type: "send" | "supervisor_send" }>;
|
|
35
|
+
|
|
36
|
+
export type StateStoreInvalidReason =
|
|
37
|
+
| "empty-broker-id"
|
|
38
|
+
| "empty-holder-id"
|
|
39
|
+
| "empty-claimant-id"
|
|
40
|
+
| "empty-target-identity"
|
|
41
|
+
| "empty-message-id"
|
|
42
|
+
| "empty-reservation-key"
|
|
43
|
+
| "empty-session-id"
|
|
44
|
+
| "empty-child-name"
|
|
45
|
+
| "empty-target"
|
|
46
|
+
| "empty-group"
|
|
47
|
+
| "subject-key-mismatch";
|
|
48
|
+
|
|
49
|
+
export interface InvalidStateInput {
|
|
50
|
+
readonly status: "invalid";
|
|
51
|
+
readonly reason: StateStoreInvalidReason;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type LeaseKind = "broker" | "presence";
|
|
55
|
+
|
|
56
|
+
/** Broker membership: one row per running broker process, keyed by its random `brokerId`. */
|
|
57
|
+
export interface BrokerMembershipSubject {
|
|
58
|
+
readonly kind: "broker";
|
|
59
|
+
readonly brokerId: string;
|
|
60
|
+
/** Host label for diagnostics and future direct forwarding; not used for routing. */
|
|
61
|
+
readonly nodeId: string;
|
|
62
|
+
readonly startedAt: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Recipient presence: which broker currently hosts the recipient's socket. */
|
|
66
|
+
export interface RecipientPresenceSubject {
|
|
67
|
+
readonly kind: "presence";
|
|
68
|
+
/** Stable recipient authority (`deliveryTargetIdentity`), which survives reconnects. */
|
|
69
|
+
readonly targetIdentity: string;
|
|
70
|
+
readonly session: SessionInfo;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export type LeaseSubject = BrokerMembershipSubject | RecipientPresenceSubject;
|
|
74
|
+
|
|
75
|
+
export type LeaseKey =
|
|
76
|
+
| { readonly kind: "broker"; readonly brokerId: string }
|
|
77
|
+
| { readonly kind: "presence"; readonly targetIdentity: string };
|
|
78
|
+
|
|
79
|
+
export interface Lease {
|
|
80
|
+
readonly key: LeaseKey;
|
|
81
|
+
/** The `brokerId` holding the lease. */
|
|
82
|
+
readonly holderId: string;
|
|
83
|
+
/** Positive safe integer, strictly greater than every earlier token for the same key, including released ones. */
|
|
84
|
+
readonly fencingToken: number;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface LeaseState extends Lease {
|
|
88
|
+
readonly subject: LeaseSubject;
|
|
89
|
+
/** Store-clock milliseconds since the lease was acquired or last renewed; a non-negative safe integer. */
|
|
90
|
+
readonly ageMs: number;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type LeaseAcquireResult =
|
|
94
|
+
| { readonly status: "acquired"; readonly lease: Lease }
|
|
95
|
+
| { readonly status: "held"; readonly holderId: string; readonly fencingToken: number }
|
|
96
|
+
| InvalidStateInput;
|
|
97
|
+
|
|
98
|
+
export type LeaseStaleResult = {
|
|
99
|
+
readonly status: "stale";
|
|
100
|
+
/** Present while the key has a holder. */
|
|
101
|
+
readonly currentFencingToken?: number;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export type LeaseRenewResult = { readonly status: "renewed" } | LeaseStaleResult | InvalidStateInput;
|
|
105
|
+
export type LeaseReleaseResult = { readonly status: "released" } | LeaseStaleResult | InvalidStateInput;
|
|
106
|
+
export type LeaseReadResult =
|
|
107
|
+
| { readonly status: "found"; readonly lease: LeaseState }
|
|
108
|
+
| { readonly status: "missing" }
|
|
109
|
+
| InvalidStateInput;
|
|
110
|
+
|
|
111
|
+
/** S1: broker membership and recipient presence leases with fencing tokens. */
|
|
112
|
+
export interface BrokerLeaseStore {
|
|
113
|
+
/**
|
|
114
|
+
* Take the subject's lease when it is unheld or older than `staleAfterMs`. The current holder re-acquiring keeps
|
|
115
|
+
* its token, resets the age and replaces the subject. Otherwise the live holder is reported as `held`.
|
|
116
|
+
*/
|
|
117
|
+
acquire(subject: LeaseSubject, holderId: string, staleAfterMs: number): Promise<LeaseAcquireResult>;
|
|
118
|
+
/** Reset the age, replacing the subject when one is given. A superseded token is `stale`. */
|
|
119
|
+
renew(lease: Lease, subject?: LeaseSubject): Promise<LeaseRenewResult>;
|
|
120
|
+
/** Give the lease up; the next acquisition still gets a greater token. A non-holder's release is `stale`. */
|
|
121
|
+
release(lease: Lease): Promise<LeaseReleaseResult>;
|
|
122
|
+
/** Current holder of the key at any age; `missing` when unheld. */
|
|
123
|
+
read(key: LeaseKey): Promise<LeaseReadResult>;
|
|
124
|
+
/** Live leases of one kind, in the order their keys were first acquired. */
|
|
125
|
+
listLive(kind: LeaseKind, staleAfterMs: number): Promise<LeaseState[]>;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export type OutboxRowState = "queued" | "claimed" | "delivered" | "failed";
|
|
129
|
+
|
|
130
|
+
export interface OutboxEnqueueRequest {
|
|
131
|
+
/** Stable recipient authority the row is addressed to. */
|
|
132
|
+
readonly targetIdentity: string;
|
|
133
|
+
/** Broker holding the sender's socket; it is woken with `outcome` once the row settles. */
|
|
134
|
+
readonly originBrokerId: string;
|
|
135
|
+
/** Caller-computed send digest; the same message id with another digest is a conflict. */
|
|
136
|
+
readonly digest: string;
|
|
137
|
+
/** Written verbatim to the recipient; its `message.id` is the row's message id. */
|
|
138
|
+
readonly frame: IntercomDeliveryFrame;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface OutboxRow {
|
|
142
|
+
/** Store-assigned positive safe integer, strictly increasing in insertion order across the outbox. */
|
|
143
|
+
readonly sequence: number;
|
|
144
|
+
readonly messageId: string;
|
|
145
|
+
readonly targetIdentity: string;
|
|
146
|
+
readonly originBrokerId: string;
|
|
147
|
+
readonly digest: string;
|
|
148
|
+
readonly frame: IntercomDeliveryFrame;
|
|
149
|
+
readonly state: OutboxRowState;
|
|
150
|
+
/** Number of claims taken on this row so far. */
|
|
151
|
+
readonly claimAttempts: number;
|
|
152
|
+
/** Present only when `state` is `failed`; verbatim. */
|
|
153
|
+
readonly failureReason?: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface OutboxClaim {
|
|
157
|
+
readonly messageId: string;
|
|
158
|
+
/** The `brokerId` that claimed the row. */
|
|
159
|
+
readonly claimantId: string;
|
|
160
|
+
/** Random token, different for every claim of the row. */
|
|
161
|
+
readonly token: string;
|
|
162
|
+
/** Equals the row's `claimAttempts` when this claim was taken. */
|
|
163
|
+
readonly attempt: number;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface ClaimedOutboxRow {
|
|
167
|
+
readonly row: OutboxRow;
|
|
168
|
+
readonly claim: OutboxClaim;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export interface OutboxClaimRequest {
|
|
172
|
+
readonly claimantId: string;
|
|
173
|
+
readonly targetIdentity: string;
|
|
174
|
+
readonly staleAfterMs: number;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export type OutboxEnqueueResult =
|
|
178
|
+
| { readonly status: "inserted"; readonly row: OutboxRow }
|
|
179
|
+
/** Same id and digest: no second row is written and the existing row reports its current state. */
|
|
180
|
+
| { readonly status: "duplicate"; readonly row: OutboxRow }
|
|
181
|
+
| { readonly status: "conflict"; readonly messageId: string }
|
|
182
|
+
| InvalidStateInput;
|
|
183
|
+
|
|
184
|
+
export type OutboxClaimResult =
|
|
185
|
+
/** The recipient's newly claimed rows in insertion order; empty when there is nothing to deliver. */
|
|
186
|
+
| { readonly status: "claimed"; readonly rows: ClaimedOutboxRow[] }
|
|
187
|
+
/** Another claimant holds a live claim on this recipient, so its rows stay in order with that claimant. */
|
|
188
|
+
| { readonly status: "held"; readonly claimantId: string }
|
|
189
|
+
| InvalidStateInput;
|
|
190
|
+
|
|
191
|
+
/** The row is already `delivered` or `failed`; every later write for it reports this, whatever its token. */
|
|
192
|
+
export type OutboxSettledResult = { readonly status: "settled"; readonly row: OutboxRow };
|
|
193
|
+
export type OutboxClaimWriteRejection =
|
|
194
|
+
| OutboxSettledResult
|
|
195
|
+
| { readonly status: "stale" }
|
|
196
|
+
| { readonly status: "unknown" }
|
|
197
|
+
| InvalidStateInput;
|
|
198
|
+
|
|
199
|
+
export type OutboxRenewClaimResult = { readonly status: "renewed" } | OutboxClaimWriteRejection;
|
|
200
|
+
export type OutboxReleaseClaimResult = { readonly status: "released" } | OutboxClaimWriteRejection;
|
|
201
|
+
export type OutboxDeliveredResult =
|
|
202
|
+
| { readonly status: "delivered"; readonly row: OutboxRow }
|
|
203
|
+
| OutboxClaimWriteRejection;
|
|
204
|
+
export type OutboxFailedResult = { readonly status: "failed"; readonly row: OutboxRow } | OutboxClaimWriteRejection;
|
|
205
|
+
export type OutboxReadResult =
|
|
206
|
+
| { readonly status: "found"; readonly row: OutboxRow }
|
|
207
|
+
| { readonly status: "missing" }
|
|
208
|
+
| InvalidStateInput;
|
|
209
|
+
export type OutboxListResult = { readonly status: "listed"; readonly rows: OutboxRow[] } | InvalidStateInput;
|
|
210
|
+
|
|
211
|
+
/** S2: the durable outbox, deduplicated by message id, with exclusive claim leases. */
|
|
212
|
+
export interface OutboxStore {
|
|
213
|
+
enqueue(request: OutboxEnqueueRequest): Promise<OutboxEnqueueResult>;
|
|
214
|
+
/**
|
|
215
|
+
* Claim the recipient's queued rows and rows whose claim is older than `staleAfterMs`, in insertion order.
|
|
216
|
+
* Rows the same claimant already holds live stay in flight and are not returned again.
|
|
217
|
+
*/
|
|
218
|
+
claim(request: OutboxClaimRequest): Promise<OutboxClaimResult>;
|
|
219
|
+
renewClaim(claim: OutboxClaim): Promise<OutboxRenewClaimResult>;
|
|
220
|
+
/** Return the row to `queued`, for example when its recipient disconnected before acknowledging. */
|
|
221
|
+
releaseClaim(claim: OutboxClaim): Promise<OutboxReleaseClaimResult>;
|
|
222
|
+
/**
|
|
223
|
+
* The recipient acknowledged `received`; only now may its sender be told `delivered`. For an ask, record its
|
|
224
|
+
* route with `PendingAskStore.record` first: a settled row is never claimed again, so a route written after it
|
|
225
|
+
* would be lost if the broker stopped between the two writes.
|
|
226
|
+
*/
|
|
227
|
+
markDelivered(claim: OutboxClaim): Promise<OutboxDeliveredResult>;
|
|
228
|
+
/** The recipient's broker refused the row; `reason` is reported to the sender verbatim. */
|
|
229
|
+
markFailed(claim: OutboxClaim, reason: string): Promise<OutboxFailedResult>;
|
|
230
|
+
read(messageId: string): Promise<OutboxReadResult>;
|
|
231
|
+
/** The recipient's `queued` and `claimed` rows in insertion order, without claiming them. */
|
|
232
|
+
listUndelivered(targetIdentity: string): Promise<OutboxListResult>;
|
|
233
|
+
/** Delete `delivered` and `failed` rows settled more than `settledOlderThanMs` ago; returns the count. */
|
|
234
|
+
prune(settledOlderThanMs: number): Promise<number>;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* The shared key of a restoring-recipient reservation: the lowercase hex SHA-256 of the supervisor capability.
|
|
239
|
+
* The capability is a bearer secret with a random nonce, so the shared database stores only this digest.
|
|
240
|
+
*/
|
|
241
|
+
export function reservationKeyFor(capability: string): string {
|
|
242
|
+
return createHash("sha256").update(capability, "utf8").digest("hex");
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export interface HeldSend {
|
|
246
|
+
/** `reservationKeyFor(capability)` of the restoring-recipient reservation the send waits on; never the capability. */
|
|
247
|
+
readonly reservationKey: string;
|
|
248
|
+
/** The broker holding the sender's socket and wait; it dispatches or refuses the send when the hold ends. */
|
|
249
|
+
readonly originBrokerId: string;
|
|
250
|
+
readonly senderSessionId: string;
|
|
251
|
+
/** Its `message.id` identifies the held send. */
|
|
252
|
+
readonly request: HeldSendRequest;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export type HeldSendHoldResult = { readonly status: "held" } | { readonly status: "duplicate" } | InvalidStateInput;
|
|
256
|
+
export type HeldSendListResult = { readonly status: "listed"; readonly sends: HeldSend[] } | InvalidStateInput;
|
|
257
|
+
export type HeldSendTakeResult = { readonly status: "taken"; readonly sends: HeldSend[] } | InvalidStateInput;
|
|
258
|
+
export type HeldSendDropResult = { readonly status: "dropped" } | { readonly status: "missing" } | InvalidStateInput;
|
|
259
|
+
|
|
260
|
+
/** S3: sends held for recipients being restored, so any broker can dispatch them when the hold ends. */
|
|
261
|
+
export interface HeldSendStore {
|
|
262
|
+
/** A message id already held, under any reservation, is a `duplicate` and is not held twice. */
|
|
263
|
+
hold(send: HeldSend): Promise<HeldSendHoldResult>;
|
|
264
|
+
list(reservationKey: string): Promise<HeldSendListResult>;
|
|
265
|
+
/** Atomically remove and return the reservation's held sends in insertion order, so each is handed over once. */
|
|
266
|
+
take(reservationKey: string): Promise<HeldSendTakeResult>;
|
|
267
|
+
/** Remove only this send; `dropped` tells exactly one caller that it now owns the send's dispatch. */
|
|
268
|
+
drop(messageId: string): Promise<HeldSendDropResult>;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export interface RestoringReservationRequest {
|
|
272
|
+
/** `reservationKeyFor(capability)`. */
|
|
273
|
+
readonly reservationKey: string;
|
|
274
|
+
readonly ownerSessionId: string;
|
|
275
|
+
/** The broker holding the owner's socket. */
|
|
276
|
+
readonly ownerBrokerId: string;
|
|
277
|
+
/** Verbatim, for refusal texts. */
|
|
278
|
+
readonly childName: string;
|
|
279
|
+
/** The normalized child name: a lookup target, and the identity a newer restoration or a registration matches. */
|
|
280
|
+
readonly childTarget: string;
|
|
281
|
+
/** Further normalized targets, such as the child's Atomic session id and pre-restart Intercom id. */
|
|
282
|
+
readonly aliases: readonly string[];
|
|
283
|
+
/** The child's normalized group; senders in it are admitted. */
|
|
284
|
+
readonly group: string;
|
|
285
|
+
/** Kept only until the supervisor that restores the child reserves it or the child registers. */
|
|
286
|
+
readonly dormant: boolean;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export interface RestoringReservation {
|
|
290
|
+
readonly reservationKey: string;
|
|
291
|
+
readonly ownerSessionId: string;
|
|
292
|
+
readonly ownerBrokerId: string;
|
|
293
|
+
readonly childName: string;
|
|
294
|
+
readonly childTarget: string;
|
|
295
|
+
/** `childTarget` first, then every other target once, in the order it was added. */
|
|
296
|
+
readonly targets: string[];
|
|
297
|
+
readonly group: string;
|
|
298
|
+
readonly dormant: boolean;
|
|
299
|
+
readonly fencingToken: number;
|
|
300
|
+
/** Set once the owner reports the child gone; verbatim. */
|
|
301
|
+
readonly gone?: string;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export interface RestoringReservationLease {
|
|
305
|
+
readonly reservationKey: string;
|
|
306
|
+
readonly ownerSessionId: string;
|
|
307
|
+
/** Positive safe integer, greater than every token issued earlier for the key, including ended or pruned ones. */
|
|
308
|
+
readonly fencingToken: number;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** How a reservation ended; brokers holding sends for it read this to dispatch or refuse them. */
|
|
312
|
+
export type RestoringReservationEnd =
|
|
313
|
+
/** The child registered: held sends are routed again. */
|
|
314
|
+
| { readonly outcome: "consumed" }
|
|
315
|
+
/** The owner ended it: held sends are refused with `refusal` when given, otherwise routed again. */
|
|
316
|
+
| { readonly outcome: "released"; readonly refusal?: string }
|
|
317
|
+
/** A newer restoration of the same child took it over: held sends keep waiting on `by`. */
|
|
318
|
+
| { readonly outcome: "superseded"; readonly by: string };
|
|
319
|
+
|
|
320
|
+
export interface RestoringReservationSender {
|
|
321
|
+
readonly sessionId: string;
|
|
322
|
+
/** The sender's normalized groups. */
|
|
323
|
+
readonly groups: readonly string[];
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export interface RestoringReservationRegistration {
|
|
327
|
+
/** The registered child's normalized name. */
|
|
328
|
+
readonly childTarget: string;
|
|
329
|
+
/** The registered child's normalized groups. */
|
|
330
|
+
readonly groups: readonly string[];
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export interface RestoringReservationReleaseOutcome {
|
|
334
|
+
/** Keep the reservation as a gone marker that refuses held and later sends with this text. */
|
|
335
|
+
readonly gone?: string;
|
|
336
|
+
/** End the reservation and refuse only the sends held now with this text. Ignored when `gone` is given. */
|
|
337
|
+
readonly refusal?: string;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export type RestoringReservationReserveResult =
|
|
341
|
+
/** `superseded` lists the reservations this one took over, in insertion order. */
|
|
342
|
+
| { readonly status: "reserved"; readonly lease: RestoringReservationLease; readonly superseded: string[] }
|
|
343
|
+
| InvalidStateInput;
|
|
344
|
+
export type RestoringReservationLookupResult =
|
|
345
|
+
| { readonly status: "none" }
|
|
346
|
+
| { readonly status: "reserved"; readonly reservation: RestoringReservation }
|
|
347
|
+
| { readonly status: "gone"; readonly reservation: RestoringReservation }
|
|
348
|
+
| InvalidStateInput;
|
|
349
|
+
export type RestoringReservationRenewResult = { readonly status: "renewed" } | LeaseStaleResult | InvalidStateInput;
|
|
350
|
+
export type RestoringReservationReleaseResult =
|
|
351
|
+
| { readonly status: "released" }
|
|
352
|
+
| { readonly status: "gone" }
|
|
353
|
+
| LeaseStaleResult
|
|
354
|
+
| InvalidStateInput;
|
|
355
|
+
export type RestoringReservationConsumeResult =
|
|
356
|
+
/** The reservations this registration ended, in insertion order; empty when none. */
|
|
357
|
+
| { readonly status: "consumed"; readonly reservationKeys: string[] }
|
|
358
|
+
| InvalidStateInput;
|
|
359
|
+
export type RestoringReservationReadResult =
|
|
360
|
+
| { readonly status: "active"; readonly reservation: RestoringReservation; readonly ageMs: number }
|
|
361
|
+
| { readonly status: "ended"; readonly end: RestoringReservationEnd }
|
|
362
|
+
| { readonly status: "missing" }
|
|
363
|
+
| InvalidStateInput;
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* S3: #3760 restoring-recipient reservations shared by every broker, so a sender on any host sees a child being
|
|
367
|
+
* restored and holds its send. Targets and groups are matched exactly; callers pass normalized values.
|
|
368
|
+
*/
|
|
369
|
+
export interface RestoringReservationStore {
|
|
370
|
+
/**
|
|
371
|
+
* Reserve the key, or add targets to it. The same owner keeps its token; another owner takes the reservation
|
|
372
|
+
* over with a greater token, because holding the capability is the authority. A new or ended key supersedes
|
|
373
|
+
* other active reservations of the same `childTarget` made by the same owner, or dormant in the same group,
|
|
374
|
+
* merging their targets.
|
|
375
|
+
*/
|
|
376
|
+
reserve(request: RestoringReservationRequest): Promise<RestoringReservationReserveResult>;
|
|
377
|
+
/**
|
|
378
|
+
* The first live reservation (`ageMs <= staleAfterMs`), in insertion order, with the target and owned by the
|
|
379
|
+
* sender or in one of the sender's groups. A gone marker is reported as `gone`.
|
|
380
|
+
*/
|
|
381
|
+
lookup(
|
|
382
|
+
target: string,
|
|
383
|
+
sender: RestoringReservationSender,
|
|
384
|
+
staleAfterMs: number,
|
|
385
|
+
): Promise<RestoringReservationLookupResult>;
|
|
386
|
+
/** Reset the age, also of a gone marker. A superseded token or an ended reservation is `stale`. */
|
|
387
|
+
renew(lease: RestoringReservationLease): Promise<RestoringReservationRenewResult>;
|
|
388
|
+
/** Owner only: mark the reservation gone, or end it as `released`. */
|
|
389
|
+
release(
|
|
390
|
+
lease: RestoringReservationLease,
|
|
391
|
+
outcome?: RestoringReservationReleaseOutcome,
|
|
392
|
+
): Promise<RestoringReservationReleaseResult>;
|
|
393
|
+
/**
|
|
394
|
+
* The child registered with this key through any broker: end its reservation unless it is a gone marker, and
|
|
395
|
+
* with `registered`, every other dormant, non-gone reservation of that child in one of its groups. Age is ignored.
|
|
396
|
+
*/
|
|
397
|
+
consume(
|
|
398
|
+
reservationKey: string,
|
|
399
|
+
registered?: RestoringReservationRegistration,
|
|
400
|
+
): Promise<RestoringReservationConsumeResult>;
|
|
401
|
+
read(reservationKey: string): Promise<RestoringReservationReadResult>;
|
|
402
|
+
/** Delete ended records ended, and reservations last renewed, more than `olderThanMs` ago; returns the count. */
|
|
403
|
+
prune(olderThanMs: number): Promise<number>;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/** A delivered ask whose reply route stays open until the reply or either peer leaves. */
|
|
407
|
+
export interface PendingAsk {
|
|
408
|
+
readonly senderSessionId: string;
|
|
409
|
+
readonly targetSessionId: string;
|
|
410
|
+
readonly messageId: string;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/** A reply travels the reverse route: from the ask's target back to the ask's sender. */
|
|
414
|
+
export interface ReplyRoute {
|
|
415
|
+
readonly replySenderSessionId: string;
|
|
416
|
+
readonly replyTargetSessionId: string;
|
|
417
|
+
readonly replyTo: string;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
export type PendingAskRecordResult =
|
|
421
|
+
| { readonly status: "recorded" }
|
|
422
|
+
| { readonly status: "duplicate" }
|
|
423
|
+
| InvalidStateInput;
|
|
424
|
+
export type PendingAskMatchResult = { readonly status: "match" } | { readonly status: "no-match" } | InvalidStateInput;
|
|
425
|
+
export type PendingAskClearResult =
|
|
426
|
+
| { readonly status: "cleared" }
|
|
427
|
+
| { readonly status: "no-match" }
|
|
428
|
+
| InvalidStateInput;
|
|
429
|
+
export type PendingAskPruneResult = { readonly status: "pruned"; readonly removed: number } | InvalidStateInput;
|
|
430
|
+
export type PendingAskTakeResult = { readonly status: "taken"; readonly asks: PendingAsk[] } | InvalidStateInput;
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* S3: shared pending-ask routes, recorded by the recipient's broker when the recipient acknowledges the ask and
|
|
434
|
+
* before its outbox row is marked delivered. A broker that stops between the two leaves the row claimed, so the next
|
|
435
|
+
* claimant redelivers it and records again; `record` answers that repeat with `duplicate`.
|
|
436
|
+
*/
|
|
437
|
+
export interface PendingAskStore {
|
|
438
|
+
record(ask: PendingAsk): Promise<PendingAskRecordResult>;
|
|
439
|
+
/** Only the exact reverse route of a recorded ask matches. */
|
|
440
|
+
matchesReply(route: ReplyRoute): Promise<PendingAskMatchResult>;
|
|
441
|
+
clearReply(route: ReplyRoute): Promise<PendingAskClearResult>;
|
|
442
|
+
pruneSender(senderSessionId: string): Promise<PendingAskPruneResult>;
|
|
443
|
+
/** Remove and return the asks addressed to this target, in insertion order. */
|
|
444
|
+
takeForTarget(targetSessionId: string): Promise<PendingAskTakeResult>;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
export type SentMessageState = "reserved" | "accepted";
|
|
448
|
+
|
|
449
|
+
export interface SentQuestionRoute {
|
|
450
|
+
readonly targetSessionId: string;
|
|
451
|
+
readonly senderGroupIdentity: string;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export interface SentMessageReservation {
|
|
455
|
+
readonly messageId: string;
|
|
456
|
+
readonly digest: string;
|
|
457
|
+
readonly targetIdentity: string;
|
|
458
|
+
readonly questionRoute?: SentQuestionRoute;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export interface SentMessageKey {
|
|
462
|
+
readonly messageId: string;
|
|
463
|
+
readonly digest: string;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
export type SentMessageReserveResult =
|
|
467
|
+
| { readonly status: "reserved" }
|
|
468
|
+
| { readonly status: "match"; readonly state: SentMessageState }
|
|
469
|
+
| { readonly status: "conflict" }
|
|
470
|
+
| InvalidStateInput;
|
|
471
|
+
export type SentMessageAcceptResult =
|
|
472
|
+
| { readonly status: "accepted" }
|
|
473
|
+
| { readonly status: "conflict" }
|
|
474
|
+
| { readonly status: "missing" }
|
|
475
|
+
| InvalidStateInput;
|
|
476
|
+
export type SentMessageLookupResult =
|
|
477
|
+
| { readonly status: "miss" }
|
|
478
|
+
| {
|
|
479
|
+
readonly status: "match";
|
|
480
|
+
readonly state: SentMessageState;
|
|
481
|
+
readonly targetIdentity: string;
|
|
482
|
+
readonly questionRoute?: SentQuestionRoute;
|
|
483
|
+
}
|
|
484
|
+
| { readonly status: "conflict" }
|
|
485
|
+
| InvalidStateInput;
|
|
486
|
+
export type SentMessageForgetResult =
|
|
487
|
+
| { readonly status: "forgotten" }
|
|
488
|
+
| { readonly status: "missing" }
|
|
489
|
+
| { readonly status: "conflict" }
|
|
490
|
+
| InvalidStateInput;
|
|
491
|
+
|
|
492
|
+
/** S3: duplicate-send authority shared by every broker, so a same-id retry through any broker is recognized. */
|
|
493
|
+
export interface SentMessageAuthority {
|
|
494
|
+
reserve(reservation: SentMessageReservation): Promise<SentMessageReserveResult>;
|
|
495
|
+
accept(key: SentMessageKey): Promise<SentMessageAcceptResult>;
|
|
496
|
+
lookup(key: SentMessageKey): Promise<SentMessageLookupResult>;
|
|
497
|
+
/** Drop a reservation whose forwarding failed, so a retry is not refused as a duplicate. */
|
|
498
|
+
forget(key: SentMessageKey): Promise<SentMessageForgetResult>;
|
|
499
|
+
/** Delete records recorded more than `olderThanMs` ago; returns the count. */
|
|
500
|
+
prune(olderThanMs: number): Promise<number>;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* A session some broker hosted, recorded when it registers and when it leaves (spec §14, S5 amendment). A send
|
|
505
|
+
* to a known session with no live presence lease is queued for its return instead of failing.
|
|
506
|
+
*/
|
|
507
|
+
export interface KnownSession {
|
|
508
|
+
/** Stable recipient authority (`deliveryTargetIdentity`); one record per identity. */
|
|
509
|
+
readonly targetIdentity: string;
|
|
510
|
+
/** The Intercom session id it last registered with. */
|
|
511
|
+
readonly intercomSessionId: string;
|
|
512
|
+
/** Verbatim. */
|
|
513
|
+
readonly name?: string;
|
|
514
|
+
/** The host (Atomic) session id: the registration's return address. */
|
|
515
|
+
readonly atomicSessionId?: string;
|
|
516
|
+
/** The normalized registration group. */
|
|
517
|
+
readonly group: string;
|
|
518
|
+
/** The normalized memberships at its last record. */
|
|
519
|
+
readonly groups: readonly string[];
|
|
520
|
+
/** The host-declared connection purpose at its last record; only agents receive Intercom messages. */
|
|
521
|
+
readonly recipientPurpose?: SessionInfo["recipientPurpose"];
|
|
522
|
+
/** The reply capability at its last record: an ask to a terminal session is refused, offline as live. */
|
|
523
|
+
readonly replyCapability?: SessionInfo["replyCapability"];
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
export interface KnownSessionState extends KnownSession {
|
|
527
|
+
readonly groups: string[];
|
|
528
|
+
/** Store-clock milliseconds since the session was last recorded. */
|
|
529
|
+
readonly ageMs: number;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
export type KnownSessionRecordResult = { readonly status: "recorded" } | InvalidStateInput;
|
|
533
|
+
export type KnownSessionLookupResult =
|
|
534
|
+
| { readonly status: "found"; readonly sessions: KnownSessionState[] }
|
|
535
|
+
| InvalidStateInput;
|
|
536
|
+
|
|
537
|
+
/** S5 amendment: sessions brokers hosted, kept for `KNOWN_SESSION_RETENTION_MS` so sends to them are queued. */
|
|
538
|
+
export interface KnownSessionStore {
|
|
539
|
+
/** Insert or replace the identity's record and reset its age; the identity keeps its first-record order. */
|
|
540
|
+
record(session: KnownSession): Promise<KnownSessionRecordResult>;
|
|
541
|
+
/**
|
|
542
|
+
* Records last recorded within `retentionMs` whose Intercom id or Atomic session id equals `target`, or whose
|
|
543
|
+
* name equals it case-insensitively (`toLowerCase`), in first-record order.
|
|
544
|
+
*/
|
|
545
|
+
lookup(target: string, retentionMs: number): Promise<KnownSessionLookupResult>;
|
|
546
|
+
/** Delete records last recorded more than `olderThanMs` ago; returns the count. */
|
|
547
|
+
prune(olderThanMs: number): Promise<number>;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/** One broker's view of the shared Intercom state. */
|
|
551
|
+
export interface IntercomBrokerStateStore {
|
|
552
|
+
readonly leases: BrokerLeaseStore;
|
|
553
|
+
readonly outbox: OutboxStore;
|
|
554
|
+
readonly heldSends: HeldSendStore;
|
|
555
|
+
readonly reservations: RestoringReservationStore;
|
|
556
|
+
readonly pendingAsks: PendingAskStore;
|
|
557
|
+
readonly sentMessages: SentMessageAuthority;
|
|
558
|
+
readonly knownSessions: KnownSessionStore;
|
|
559
|
+
close(): Promise<void>;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
export interface IntercomBrokerStateOptions {
|
|
563
|
+
/** The database of the selected shared `storage` adapter; the store does not close it. */
|
|
564
|
+
readonly database: SqliteDatabase;
|
|
565
|
+
/** The adapter's validated table prefix. */
|
|
566
|
+
readonly prefix: string;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
export type OpenIntercomBrokerStateStore = (options: IntercomBrokerStateOptions) => Promise<IntercomBrokerStateStore>;
|