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.
- package/LICENSE +21 -0
- package/README.md +941 -30
- package/dist/actions.d.ts +21 -5
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +673 -54
- package/dist/actions.js.map +1 -1
- package/dist/ask.d.ts +83 -0
- package/dist/ask.d.ts.map +1 -0
- package/dist/ask.js +215 -0
- package/dist/ask.js.map +1 -0
- package/dist/chatPrivacy.d.ts +34 -0
- package/dist/chatPrivacy.d.ts.map +1 -0
- package/dist/chatPrivacy.js +60 -0
- package/dist/chatPrivacy.js.map +1 -0
- package/dist/client.d.ts +3123 -35
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +1267 -143
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +16 -6
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +16 -5
- package/dist/config.js.map +1 -1
- package/dist/crypto.d.ts +62 -0
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +121 -0
- package/dist/crypto.js.map +1 -1
- package/dist/delegations.d.ts +25 -8
- package/dist/delegations.d.ts.map +1 -1
- package/dist/delegations.js +67 -11
- package/dist/delegations.js.map +1 -1
- package/dist/device/client.d.ts +161 -0
- package/dist/device/client.d.ts.map +1 -0
- package/dist/device/client.js +601 -0
- package/dist/device/client.js.map +1 -0
- package/dist/device/host.d.ts +222 -0
- package/dist/device/host.d.ts.map +1 -0
- package/dist/device/host.js +891 -0
- package/dist/device/host.js.map +1 -0
- package/dist/device/http.d.ts +12 -0
- package/dist/device/http.d.ts.map +1 -0
- package/dist/device/http.js +124 -0
- package/dist/device/http.js.map +1 -0
- package/dist/device/index.d.ts +5 -0
- package/dist/device/index.d.ts.map +1 -0
- package/dist/device/index.js +27 -0
- package/dist/device/index.js.map +1 -0
- package/dist/device/protocol.d.ts +312 -0
- package/dist/device/protocol.d.ts.map +1 -0
- package/dist/device/protocol.js +435 -0
- package/dist/device/protocol.js.map +1 -0
- package/dist/identities.d.ts +9 -2
- package/dist/identities.d.ts.map +1 -1
- package/dist/identities.js +33 -3
- package/dist/identities.js.map +1 -1
- package/dist/identity.d.ts +155 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +196 -0
- package/dist/identity.js.map +1 -0
- package/dist/identityShare.d.ts +129 -0
- package/dist/identityShare.d.ts.map +1 -0
- package/dist/identityShare.js +308 -0
- package/dist/identityShare.js.map +1 -0
- package/dist/ids.d.ts +11 -0
- package/dist/ids.d.ts.map +1 -0
- package/dist/ids.js +17 -0
- package/dist/ids.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -1
- package/dist/reconcile.d.ts +25 -0
- package/dist/reconcile.d.ts.map +1 -0
- package/dist/reconcile.js +60 -0
- package/dist/reconcile.js.map +1 -0
- package/dist/recovery.d.ts +51 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +59 -0
- package/dist/recovery.js.map +1 -0
- package/dist/register.d.ts +32 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +65 -0
- package/dist/register.js.map +1 -0
- package/dist/scriptJob.d.ts +92 -0
- package/dist/scriptJob.d.ts.map +1 -0
- package/dist/scriptJob.js +270 -0
- package/dist/scriptJob.js.map +1 -0
- package/dist/sessions.d.ts +131 -0
- package/dist/sessions.d.ts.map +1 -0
- package/dist/sessions.js +240 -0
- package/dist/sessions.js.map +1 -0
- package/dist/socket.d.ts +115 -0
- package/dist/socket.d.ts.map +1 -0
- package/dist/socket.js +1038 -0
- package/dist/socket.js.map +1 -0
- package/dist/webhook.d.ts +627 -28
- package/dist/webhook.d.ts.map +1 -1
- package/dist/webhook.js +1388 -84
- package/dist/webhook.js.map +1 -1
- package/dist/work.d.ts +57 -0
- package/dist/work.d.ts.map +1 -0
- package/dist/work.js +213 -0
- package/dist/work.js.map +1 -0
- package/package.json +26 -4
package/dist/sessions.js
ADDED
|
@@ -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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"}
|
package/dist/socket.d.ts
ADDED
|
@@ -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"}
|