salt-agent-sdk 0.1.0 → 0.19.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 (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +941 -30
  3. package/dist/actions.d.ts +21 -5
  4. package/dist/actions.d.ts.map +1 -1
  5. package/dist/actions.js +673 -54
  6. package/dist/actions.js.map +1 -1
  7. package/dist/ask.d.ts +83 -0
  8. package/dist/ask.d.ts.map +1 -0
  9. package/dist/ask.js +215 -0
  10. package/dist/ask.js.map +1 -0
  11. package/dist/chatPrivacy.d.ts +34 -0
  12. package/dist/chatPrivacy.d.ts.map +1 -0
  13. package/dist/chatPrivacy.js +60 -0
  14. package/dist/chatPrivacy.js.map +1 -0
  15. package/dist/client.d.ts +3123 -35
  16. package/dist/client.d.ts.map +1 -1
  17. package/dist/client.js +1267 -143
  18. package/dist/client.js.map +1 -1
  19. package/dist/config.d.ts +16 -6
  20. package/dist/config.d.ts.map +1 -1
  21. package/dist/config.js +16 -5
  22. package/dist/config.js.map +1 -1
  23. package/dist/crypto.d.ts +62 -0
  24. package/dist/crypto.d.ts.map +1 -1
  25. package/dist/crypto.js +121 -0
  26. package/dist/crypto.js.map +1 -1
  27. package/dist/delegations.d.ts +25 -8
  28. package/dist/delegations.d.ts.map +1 -1
  29. package/dist/delegations.js +67 -11
  30. package/dist/delegations.js.map +1 -1
  31. package/dist/device/client.d.ts +161 -0
  32. package/dist/device/client.d.ts.map +1 -0
  33. package/dist/device/client.js +601 -0
  34. package/dist/device/client.js.map +1 -0
  35. package/dist/device/host.d.ts +222 -0
  36. package/dist/device/host.d.ts.map +1 -0
  37. package/dist/device/host.js +891 -0
  38. package/dist/device/host.js.map +1 -0
  39. package/dist/device/http.d.ts +12 -0
  40. package/dist/device/http.d.ts.map +1 -0
  41. package/dist/device/http.js +124 -0
  42. package/dist/device/http.js.map +1 -0
  43. package/dist/device/index.d.ts +5 -0
  44. package/dist/device/index.d.ts.map +1 -0
  45. package/dist/device/index.js +27 -0
  46. package/dist/device/index.js.map +1 -0
  47. package/dist/device/protocol.d.ts +312 -0
  48. package/dist/device/protocol.d.ts.map +1 -0
  49. package/dist/device/protocol.js +435 -0
  50. package/dist/device/protocol.js.map +1 -0
  51. package/dist/identities.d.ts +9 -2
  52. package/dist/identities.d.ts.map +1 -1
  53. package/dist/identities.js +33 -3
  54. package/dist/identities.js.map +1 -1
  55. package/dist/identity.d.ts +155 -0
  56. package/dist/identity.d.ts.map +1 -0
  57. package/dist/identity.js +196 -0
  58. package/dist/identity.js.map +1 -0
  59. package/dist/identityShare.d.ts +129 -0
  60. package/dist/identityShare.d.ts.map +1 -0
  61. package/dist/identityShare.js +308 -0
  62. package/dist/identityShare.js.map +1 -0
  63. package/dist/ids.d.ts +11 -0
  64. package/dist/ids.d.ts.map +1 -0
  65. package/dist/ids.js +17 -0
  66. package/dist/ids.js.map +1 -0
  67. package/dist/index.d.ts +13 -0
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +13 -0
  70. package/dist/index.js.map +1 -1
  71. package/dist/reconcile.d.ts +25 -0
  72. package/dist/reconcile.d.ts.map +1 -0
  73. package/dist/reconcile.js +60 -0
  74. package/dist/reconcile.js.map +1 -0
  75. package/dist/recovery.d.ts +51 -0
  76. package/dist/recovery.d.ts.map +1 -0
  77. package/dist/recovery.js +59 -0
  78. package/dist/recovery.js.map +1 -0
  79. package/dist/register.d.ts +32 -0
  80. package/dist/register.d.ts.map +1 -0
  81. package/dist/register.js +65 -0
  82. package/dist/register.js.map +1 -0
  83. package/dist/scriptJob.d.ts +92 -0
  84. package/dist/scriptJob.d.ts.map +1 -0
  85. package/dist/scriptJob.js +270 -0
  86. package/dist/scriptJob.js.map +1 -0
  87. package/dist/sessions.d.ts +131 -0
  88. package/dist/sessions.d.ts.map +1 -0
  89. package/dist/sessions.js +240 -0
  90. package/dist/sessions.js.map +1 -0
  91. package/dist/socket.d.ts +115 -0
  92. package/dist/socket.d.ts.map +1 -0
  93. package/dist/socket.js +1038 -0
  94. package/dist/socket.js.map +1 -0
  95. package/dist/webhook.d.ts +627 -28
  96. package/dist/webhook.d.ts.map +1 -1
  97. package/dist/webhook.js +1388 -84
  98. package/dist/webhook.js.map +1 -1
  99. package/dist/work.d.ts +57 -0
  100. package/dist/work.d.ts.map +1 -0
  101. package/dist/work.js +213 -0
  102. package/dist/work.js.map +1 -0
  103. package/package.json +26 -4
@@ -0,0 +1,240 @@
1
+ "use strict";
2
+ // A hosted identity's memory of ONE chat: enough of the recent back-and-forth
3
+ // and a short structured note to pick a conversation back up -- across a
4
+ // process restart (FileSessionStore), and across a hand-off (the note rides
5
+ // the outgoing agent's briefing message as a final wire-protocol line and is
6
+ // parsed back into the new session on the other end -- see webhook.ts).
7
+ //
8
+ // This is bookkeeping, not intelligence: nothing here decides what an agent
9
+ // says. webhook.ts loads/rebuilds a session before onMessage runs, hands it
10
+ // to the consumer as ctx.session (a plain mutable object -- write to
11
+ // ctx.session.note directly, e.g. to record who you consulted), and persists
12
+ // it after the handler returns. A consumer that ignores ctx.session entirely
13
+ // sees no behavior change.
14
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ var desc = Object.getOwnPropertyDescriptor(m, k);
17
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
18
+ desc = { enumerable: true, get: function() { return m[k]; } };
19
+ }
20
+ Object.defineProperty(o, k2, desc);
21
+ }) : (function(o, m, k, k2) {
22
+ if (k2 === undefined) k2 = k;
23
+ o[k2] = m[k];
24
+ }));
25
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
26
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
27
+ }) : function(o, v) {
28
+ o["default"] = v;
29
+ });
30
+ var __importStar = (this && this.__importStar) || (function () {
31
+ var ownKeys = function(o) {
32
+ ownKeys = Object.getOwnPropertyNames || function (o) {
33
+ var ar = [];
34
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
35
+ return ar;
36
+ };
37
+ return ownKeys(o);
38
+ };
39
+ return function (mod) {
40
+ if (mod && mod.__esModule) return mod;
41
+ var result = {};
42
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
43
+ __setModuleDefault(result, mod);
44
+ return result;
45
+ };
46
+ })();
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.SESSION_NOTE_MARKER = exports.MAX_IDENTITY_SLICES_PER_SENDER = exports.MAX_NOTE_CHARS = exports.MAX_TRANSCRIPT_TURNS = void 0;
49
+ exports.emptySession = emptySession;
50
+ exports.recordReceivedIdentitySlice = recordReceivedIdentitySlice;
51
+ exports.forgetReceivedIdentitySlice = forgetReceivedIdentitySlice;
52
+ exports.appendTurn = appendTurn;
53
+ exports.boundNote = boundNote;
54
+ exports.MemorySessionStore = MemorySessionStore;
55
+ exports.FileSessionStore = FileSessionStore;
56
+ exports.formatSessionNoteLine = formatSessionNoteLine;
57
+ exports.extractSessionNote = extractSessionNote;
58
+ exports.stripSessionNoteLines = stripSessionNoteLines;
59
+ const node_crypto_1 = require("node:crypto");
60
+ const fs = __importStar(require("node:fs/promises"));
61
+ const path = __importStar(require("node:path"));
62
+ /** Turns kept per session -- old ones drop off the front, oldest first. */
63
+ exports.MAX_TRANSCRIPT_TURNS = 40;
64
+ /** A note's serialized size budget, in characters. */
65
+ exports.MAX_NOTE_CHARS = 1200;
66
+ /** Received identity slices kept per sender -- old ones drop off the front, oldest first, same convention as MAX_TRANSCRIPT_TURNS. */
67
+ exports.MAX_IDENTITY_SLICES_PER_SENDER = 20;
68
+ const mapKey = (id) => String(id).toLowerCase();
69
+ /** A fresh, empty session for `chatId` (in room `roomId`). */
70
+ function emptySession(chatId, roomId, role = "active") {
71
+ return { roomId, chatId, role, transcriptTail: [], note: { consulted: [] }, identity: {}, updatedAt: Date.now() };
72
+ }
73
+ /**
74
+ * Records a SALT-IDENTITY-SLICE this session just received from
75
+ * `fromId`, in place. Called by webhook.ts right after
76
+ * identityShare.parseIdentityMarker/crypto.verifyDetached resolve one.
77
+ */
78
+ function recordReceivedIdentitySlice(session, fromId, slice) {
79
+ if (!session.identity)
80
+ session.identity = {};
81
+ const key = mapKey(fromId);
82
+ const list = session.identity[key] || (session.identity[key] = []);
83
+ list.push(slice);
84
+ if (list.length > exports.MAX_IDENTITY_SLICES_PER_SENDER)
85
+ list.splice(0, list.length - exports.MAX_IDENTITY_SLICES_PER_SENDER);
86
+ }
87
+ /**
88
+ * Drops every slice from `fromId` whose `id` matches `sliceId`, in place --
89
+ * called on an incoming SALT-IDENTITY-REVOKE naming that id. A no-op when
90
+ * nothing recorded under that (sender, id) pair exists (the revoke arrived
91
+ * before this process ever saw the slice, or from a different process
92
+ * hosting the same identity).
93
+ */
94
+ function forgetReceivedIdentitySlice(session, fromId, sliceId) {
95
+ if (!session.identity)
96
+ return;
97
+ const key = mapKey(fromId);
98
+ const list = session.identity[key];
99
+ if (!list)
100
+ return;
101
+ session.identity[key] = list.filter((s) => s.id !== sliceId);
102
+ }
103
+ /** Appends one turn in place, dropping the oldest turns past MAX_TRANSCRIPT_TURNS. */
104
+ function appendTurn(session, turn) {
105
+ session.transcriptTail.push(turn);
106
+ if (session.transcriptTail.length > exports.MAX_TRANSCRIPT_TURNS) {
107
+ session.transcriptTail.splice(0, session.transcriptTail.length - exports.MAX_TRANSCRIPT_TURNS);
108
+ }
109
+ }
110
+ /**
111
+ * Returns a copy of `note` whose serialized JSON fits MAX_NOTE_CHARS,
112
+ * dropping the OLDEST `consulted` entries first -- `goal`/`waitingOn`/
113
+ * `lastReportId` are the actual point of the note and are never what gets
114
+ * cut to make room. If it's still over budget with nothing left to drop,
115
+ * returns it as-is: that's a caller writing an oversized goal/waitingOn
116
+ * string, which no amount of trimming `consulted` will fix.
117
+ */
118
+ function boundNote(note) {
119
+ const bounded = { ...note, consulted: [...(note.consulted || [])] };
120
+ while (JSON.stringify(bounded).length > exports.MAX_NOTE_CHARS && bounded.consulted.length > 0) {
121
+ bounded.consulted.shift();
122
+ }
123
+ return bounded;
124
+ }
125
+ /**
126
+ * In-memory session store -- the default. Sessions are lost on process
127
+ * restart (a cold-start rebuild from chat history fills the gap -- see
128
+ * webhook.ts's loadOrRebuildSession).
129
+ */
130
+ function MemorySessionStore() {
131
+ const store = new Map();
132
+ const key = (identityId, chatId) => `${mapKey(identityId)}:${mapKey(chatId)}`;
133
+ return {
134
+ async get(identityId, chatId) {
135
+ return store.get(key(identityId, chatId)) ?? null;
136
+ },
137
+ async put(identityId, chatId, session) {
138
+ store.set(key(identityId, chatId), session);
139
+ },
140
+ async forget(identityId, chatId) {
141
+ store.delete(key(identityId, chatId));
142
+ },
143
+ };
144
+ }
145
+ // --- FileSessionStore ----------------------------------------------------
146
+ function fileFor(dir, identityId, chatId) {
147
+ const safe = (id) => mapKey(id).replace(/[^a-z0-9_-]/g, "_");
148
+ return path.join(dir, `${safe(identityId)}__${safe(chatId)}.json`);
149
+ }
150
+ /**
151
+ * One plain JSON file per (identity, chat) under `dir`, written atomically
152
+ * (temp file + rename, so a crash mid-write never leaves a half-written
153
+ * session on disk -- the same convention identities.ts uses for its own
154
+ * store).
155
+ *
156
+ * TRUST BOUNDARY, stated plainly: sessions are stored in PLAINTEXT on this
157
+ * filesystem, right beside the identities.json this same process already
158
+ * keeps there (which holds every hosted identity's PRIVATE PGP KEY). This
159
+ * store adds no encryption of its own -- it is exactly as trusted as the
160
+ * disk it's on, which is the same trust level the rest of this process
161
+ * already assumes. Fine for a single-tenant agent host's own local/attached
162
+ * disk; do not point `dir` at shared or otherwise untrusted storage, and
163
+ * do not treat a session file as safe to hand to anyone Salt itself
164
+ * couldn't already show the conversation to.
165
+ */
166
+ function FileSessionStore(dir) {
167
+ return {
168
+ async get(identityId, chatId) {
169
+ try {
170
+ const raw = await fs.readFile(fileFor(dir, identityId, chatId), "utf8");
171
+ return JSON.parse(raw);
172
+ }
173
+ catch (err) {
174
+ if (err.code === "ENOENT")
175
+ return null;
176
+ throw err;
177
+ }
178
+ },
179
+ async put(identityId, chatId, session) {
180
+ await fs.mkdir(dir, { recursive: true });
181
+ const target = fileFor(dir, identityId, chatId);
182
+ const tmp = `${target}.${process.pid}.${(0, node_crypto_1.randomBytes)(4).toString("hex")}.tmp`;
183
+ await fs.writeFile(tmp, JSON.stringify(session, null, 2));
184
+ await fs.rename(tmp, target);
185
+ },
186
+ async forget(identityId, chatId) {
187
+ try {
188
+ await fs.unlink(fileFor(dir, identityId, chatId));
189
+ }
190
+ catch (err) {
191
+ if (err.code !== "ENOENT")
192
+ throw err;
193
+ }
194
+ },
195
+ };
196
+ }
197
+ // --- Hand-off session note wire format -------------------------------------
198
+ //
199
+ // A hand-off briefing's FINAL line, appended by webhook.ts's
200
+ // handleHandoffConfirmed to whatever text the consumer's onHandoffConfirmed
201
+ // sent -- transparent to that consumer. The incoming agent's
202
+ // handleHandoffReceived parses it back out of the polled transcript (see
203
+ // HANDOFF_BRIEFING_MARKER in webhook.ts) into the new session's note, then
204
+ // strips the line before handing the transcript to onHandoffReceived: it's
205
+ // wire protocol, never conversational content. salt-fe strips the same line
206
+ // from what a human reads.
207
+ exports.SESSION_NOTE_MARKER = "[[SALT-SESSION-NOTE]]";
208
+ const SESSION_NOTE_LINE_RE = /^\[\[SALT-SESSION-NOTE\]\] (.+)$/gm;
209
+ /**
210
+ * A session note's wire line, or undefined when there's nothing worth
211
+ * carrying forward (an empty note would just be noise on every hand-off).
212
+ */
213
+ function formatSessionNoteLine(note) {
214
+ if (!note)
215
+ return undefined;
216
+ const hasContent = !!(note.goal || note.waitingOn || note.lastReportId || (note.consulted && note.consulted.length > 0));
217
+ if (!hasContent)
218
+ return undefined;
219
+ return `${exports.SESSION_NOTE_MARKER} ${JSON.stringify(boundNote(note))}`;
220
+ }
221
+ /** The LAST [[SALT-SESSION-NOTE]] line's parsed payload in `text`, or undefined if there isn't one or it doesn't parse. */
222
+ function extractSessionNote(text) {
223
+ const matches = [...text.matchAll(SESSION_NOTE_LINE_RE)];
224
+ if (matches.length === 0)
225
+ return undefined;
226
+ try {
227
+ return JSON.parse(matches[matches.length - 1][1]);
228
+ }
229
+ catch {
230
+ return undefined;
231
+ }
232
+ }
233
+ /** Removes every [[SALT-SESSION-NOTE]] line from `text` -- wire protocol, not conversational content. */
234
+ function stripSessionNoteLines(text) {
235
+ return text
236
+ .replace(/^\[\[SALT-SESSION-NOTE\]\].*$/gm, "")
237
+ .replace(/\n{3,}/g, "\n\n")
238
+ .trim();
239
+ }
240
+ //# sourceMappingURL=sessions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions.js","sourceRoot":"","sources":["../src/sessions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,115 @@
1
+ import WebSocket from "ws";
2
+ import { type WebhookServerOptions } from "./webhook.js";
3
+ import type { SaltId } from "./ids.js";
4
+ export interface CursorStore {
5
+ /** The last-acked update id for this identity, or 0 for "never polled". */
6
+ get(agentId: SaltId): Promise<number>;
7
+ put(agentId: SaltId, cursor: number): Promise<void>;
8
+ }
9
+ /** In-memory cursor store. Lost on restart (a fresh process re-subscribes
10
+ * from cursor 0, which resumes from salt-api's own server-side ack --
11
+ * see this file's header comment). Opt in explicitly; the default is
12
+ * FileCursorStore -- see createSocketClient. */
13
+ export declare function MemoryCursorStore(): CursorStore;
14
+ /**
15
+ * `<agentId>.cursor.json` inside `dir` -- written atomically (temp file +
16
+ * rename), the same convention sessions.ts's FileSessionStore uses. `dir`
17
+ * defaults to one identity's own directory (see defaultStateDir), but N9
18
+ * (second security review, 2026-09-18) keys the FILENAME by agent id too:
19
+ * a caller pointing several identities at one SHARED directory (rather
20
+ * than createSocketClient's per-identity default) used to collide on the
21
+ * same fixed `cursor.json` -- one agent's cursor silently clobbering
22
+ * another's. Keying by id makes a shared directory safe the same way
23
+ * sessions.ts's one-file-per-key convention already is.
24
+ */
25
+ export declare function FileCursorStore(dir: string): CursorStore;
26
+ export interface DedupeStore {
27
+ has(agentId: SaltId, deliveryId: string): Promise<boolean>;
28
+ add(agentId: SaltId, deliveryId: string): Promise<void>;
29
+ }
30
+ export declare const DEFAULT_DEDUPE_MAX = 5000;
31
+ /** In-memory dedupe set, bounded to the last `max` delivery ids (oldest
32
+ * dropped first). Opt in explicitly; the default is FileDedupeStore. */
33
+ export declare function MemoryDedupeStore(max?: number): DedupeStore;
34
+ /**
35
+ * `<agentId>.seen.json` inside `dir` (an ordered array of the last `max`
36
+ * delivery ids, oldest first) -- same atomic-write convention as
37
+ * FileCursorStore. N9 (second security review, 2026-09-18): the filename
38
+ * is keyed by agent id, same reasoning as FileCursorStore above -- a
39
+ * shared `dir` across identities must not let one identity's dedupe set
40
+ * clobber another's (or worse, let identity B's real deliveries appear
41
+ * "already seen" because they share a delivery_id namespace collision
42
+ * with identity A's file).
43
+ */
44
+ export declare function FileDedupeStore(dir: string, max?: number): DedupeStore;
45
+ /** Exponential reconnect backoff bounds (jittered). Not a poll interval --
46
+ * these only govern the wait between one dropped/closed connection and
47
+ * the next connection attempt. */
48
+ export declare const RECONNECT_MIN_DELAY_MS = 1000;
49
+ export declare const RECONNECT_MAX_DELAY_MS = 60000;
50
+ /** No Action Cable ping for this long means the connection is presumed
51
+ * dead (a half-open TCP socket may never emit its own 'close'/'error'). */
52
+ export declare const PING_TIMEOUT_MS = 30000;
53
+ export interface SocketClientOptions extends WebhookServerOptions {
54
+ /** The salt-api deployment's origin, e.g. "https://saltapp.ai" (or
55
+ * "http://localhost:3000" in dev) -- rewritten to a ws(s):// `/cable`
56
+ * URL for the websocket, and used as-is for the backfill/ack HTTP
57
+ * calls. No trailing slash required either way. */
58
+ host: string;
59
+ /** This identity's OWN api-key, sent as the `api-key` header on the
60
+ * websocket handshake and on every backfill/ack HTTP call. Not the same
61
+ * thing as `identities`, which can hold several hosted identities for
62
+ * DECRYPT/ROUTE purposes -- a process draining more than one identity's
63
+ * outbox runs one createSocketClient per identity. */
64
+ apiKey: string;
65
+ /** That identity's Salt id -- the cursor/dedupe stores' default directory
66
+ * key, and used in log lines. */
67
+ agentId: SaltId;
68
+ /** Where the resume cursor is persisted across restarts/reconnects.
69
+ * Defaults to `FileCursorStore(~/.salt/agents/<agentId>)` -- pass
70
+ * `MemoryCursorStore()` explicitly to opt OUT of persistence instead. */
71
+ cursorStore?: CursorStore;
72
+ /** Persistent per-agent delivery_id dedupe (M5/F3) -- replay protection
73
+ * that doesn't rely on the envelope's own timestamp. Defaults to
74
+ * `FileDedupeStore(~/.salt/agents/<agentId>)`; pass `MemoryDedupeStore()`
75
+ * explicitly to opt out of persistence. */
76
+ dedupeStore?: DedupeStore;
77
+ /** Max rows per backfill page (only fetched when replay_done.more is
78
+ * true -- see this file's header comment). Defaults to 100. */
79
+ limit?: number;
80
+ /** Reconnect backoff bounds (ms), exponential + jittered. Also used for
81
+ * a failed backfill/ack HTTP call's own retry wait. Defaults to
82
+ * RECONNECT_MIN_DELAY_MS..RECONNECT_MAX_DELAY_MS (1s..60s). */
83
+ minBackoffMs?: number;
84
+ maxBackoffMs?: number;
85
+ /** Override fetch (backfill/ack HTTP calls; tests). Defaults to global fetch. */
86
+ fetchImpl?: typeof fetch;
87
+ /** Override the WebSocket implementation (tests). Defaults to `ws`'s WebSocket. */
88
+ webSocketImpl?: typeof WebSocket;
89
+ /** How long without an Action Cable ping before this connection is
90
+ * presumed dead and torn down for a reconnect. Defaults to
91
+ * PING_TIMEOUT_MS (30s) -- exposed mainly so tests don't have to wait
92
+ * out the real default. */
93
+ pingTimeoutMs?: number;
94
+ }
95
+ export interface SocketClient {
96
+ /** Starts the connection loop in the background. A no-op if already running. */
97
+ start(): void;
98
+ /** Stops the loop: terminates any open/connecting websocket and aborts
99
+ * any in-flight backfill/ack request immediately, and resolves once
100
+ * everything has actually settled (never mid-reconnect, never with a
101
+ * background ack still in flight). */
102
+ stop(): Promise<void>;
103
+ }
104
+ /**
105
+ * Drains one identity's K2 socket-mode outbox by staying connected to
106
+ * salt-api's `AgentUpdatesChannel` over Action Cable, verifying each
107
+ * envelope with the exact same check createWebhookServer applies to a
108
+ * webhook POST (createDispatcher's verifyEnvelope), and dispatching it to
109
+ * the SAME onMessage/onCardInteraction/etc. handlers. See this file's
110
+ * header comment for the full wire protocol, the one-line switch from
111
+ * createWebhookServer, and the reasoning behind cursor persistence, the
112
+ * ack call, backfill, and reconnect.
113
+ */
114
+ export declare function createSocketClient(options: SocketClientOptions): SocketClient;
115
+ //# sourceMappingURL=socket.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"socket.d.ts","sourceRoot":"","sources":["../src/socket.ts"],"names":[],"mappings":"AAiJA,OAAO,SAAS,MAAM,IAAI,CAAC;AAC3B,OAAO,EAAiC,KAAK,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAExF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAkGvC,MAAM,WAAW,WAAW;IAC1B,2EAA2E;IAC3E,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACtC,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrD;AAED;;;iDAGiD;AACjD,wBAAgB,iBAAiB,IAAI,WAAW,CAW/C;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAiBxD;AAKD,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3D,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzD;AAED,eAAO,MAAM,kBAAkB,OAAO,CAAC;AAEvC;yEACyE;AACzE,wBAAgB,iBAAiB,CAAC,GAAG,GAAE,MAA2B,GAAG,WAAW,CAwB/E;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAA2B,GAAG,WAAW,CAmC1F;AA2BD;;mCAEmC;AACnC,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAC3C,eAAO,MAAM,sBAAsB,QAAS,CAAC;AAE7C;4EAC4E;AAC5E,eAAO,MAAM,eAAe,QAAS,CAAC;AAEtC,MAAM,WAAW,mBAAoB,SAAQ,oBAAoB;IAC/D;;;wDAGoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb;;;;2DAIuD;IACvD,MAAM,EAAE,MAAM,CAAC;IACf;sCACkC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB;;8EAE0E;IAC1E,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;gDAG4C;IAC5C,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;oEACgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;oEAEgE;IAChE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,mFAAmF;IACnF,aAAa,CAAC,EAAE,OAAO,SAAS,CAAC;IACjC;;;gCAG4B;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,gFAAgF;IAChF,KAAK,IAAI,IAAI,CAAC;IACd;;;2CAGuC;IACvC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAiDD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,YAAY,CAyiB7E"}