salt-agent-sdk 0.1.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/dist/crypto.js ADDED
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ // PGP message crypto, wallet-payload crypto, and attachment decryption.
3
+ // Ported from salt-claude-agent/src/{pgp,walletCrypto,attachments}.js --
4
+ // zero LLM/agent-loop dependency in the original, moved verbatim with types.
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
18
+ }) : function(o, v) {
19
+ o["default"] = v;
20
+ });
21
+ var __importStar = (this && this.__importStar) || (function () {
22
+ var ownKeys = function(o) {
23
+ ownKeys = Object.getOwnPropertyNames || function (o) {
24
+ var ar = [];
25
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
+ return ar;
27
+ };
28
+ return ownKeys(o);
29
+ };
30
+ return function (mod) {
31
+ if (mod && mod.__esModule) return mod;
32
+ var result = {};
33
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
+ __setModuleDefault(result, mod);
35
+ return result;
36
+ };
37
+ })();
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.decrypt = decrypt;
40
+ exports.encryptFor = encryptFor;
41
+ exports.generateKeypair = generateKeypair;
42
+ exports.encryptWalletPayload = encryptWalletPayload;
43
+ exports.decryptAttachment = decryptAttachment;
44
+ const openpgp = __importStar(require("openpgp"));
45
+ const nodeCrypto = __importStar(require("node:crypto"));
46
+ /**
47
+ * Decrypt an armored PGP message with the agent's private key. Salt
48
+ * encrypts each message to every chat member's public key, so the agent's
49
+ * own key is among the recipients and can read the ciphertext directly.
50
+ */
51
+ async function decrypt(armoredMessage, armoredPrivateKey, passphrase) {
52
+ const privateKey = await openpgp.decryptKey({
53
+ privateKey: await openpgp.readPrivateKey({ armoredKey: armoredPrivateKey }),
54
+ passphrase,
55
+ });
56
+ const { data } = await openpgp.decrypt({
57
+ message: await openpgp.readMessage({ armoredMessage }),
58
+ decryptionKeys: [privateKey],
59
+ });
60
+ return data;
61
+ }
62
+ /**
63
+ * Encrypt plaintext to one or more armored public keys. Passing every
64
+ * non-agent member's key produces a single ciphertext all of them can read
65
+ * -- what Salt stores in a message's `message` field.
66
+ */
67
+ async function encryptFor(plaintext, armoredPublicKeys) {
68
+ const keys = await Promise.all(armoredPublicKeys.map((k) => openpgp.readKey({ armoredKey: k })));
69
+ const result = await openpgp.encrypt({
70
+ message: await openpgp.createMessage({ text: plaintext }),
71
+ encryptionKeys: keys,
72
+ });
73
+ return result;
74
+ }
75
+ /**
76
+ * Generate a fresh agent keypair -- same curve/format the Salt frontend
77
+ * uses (salt-fe's src/utilities/encrypt.jsx generateKeys()), so a key
78
+ * minted here is indistinguishable from one a human created through the UI.
79
+ */
80
+ async function generateKeypair(passphrase) {
81
+ const { privateKey, publicKey, revocationCertificate } = await openpgp.generateKey({
82
+ type: "ecc",
83
+ curve: "curve25519",
84
+ userIDs: [{}],
85
+ passphrase,
86
+ format: "armored",
87
+ });
88
+ const fingerprint = await (await openpgp.readKey({ armoredKey: publicKey })).getFingerprint();
89
+ return { publicKey, privateKey, revocationCertificate, fingerprint };
90
+ }
91
+ /**
92
+ * Encrypt a wallet's secrets for upload via client.createWallet. Byte
93
+ * compatible with salt-fe's walletVault.js wire format: base64 of a
94
+ * 12-byte random IV || AES-256-GCM ciphertext || 16-byte auth tag. AAD
95
+ * binds the ciphertext to the wallet's public_address (same as the browser
96
+ * vault), so a payload can't be replayed onto a different wallet row.
97
+ * There is no human-memorable recovery phrase here -- the master key
98
+ * (WALLET_MASTER_KEY) is the only key, held by this process alone.
99
+ */
100
+ function encryptWalletPayload(masterKeyHex, secrets, publicAddress) {
101
+ const key = Buffer.from(masterKeyHex, "hex");
102
+ const iv = nodeCrypto.randomBytes(12);
103
+ const cipher = nodeCrypto.createCipheriv("aes-256-gcm", key, iv, { authTagLength: 16 });
104
+ cipher.setAAD(Buffer.from(publicAddress, "utf8"));
105
+ const plaintext = Buffer.from(JSON.stringify({ private_key: secrets.private_key, mnemonic: secrets.mnemonic || null }), "utf8");
106
+ const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
107
+ return Buffer.concat([iv, ciphertext, cipher.getAuthTag()]).toString("base64");
108
+ }
109
+ /**
110
+ * Decrypt a message attachment's ciphertext bytes (see client.getAttachment).
111
+ * Mirrors salt-fe's src/utilities/attachments.js: Web Crypto AES-256-GCM
112
+ * output is ciphertext with the 16-byte auth tag appended, so Node's crypto
113
+ * module needs that tag split off and set explicitly via setAuthTag.
114
+ */
115
+ function decryptAttachment(ciphertextBuffer, keyB64, ivB64) {
116
+ const key = Buffer.from(keyB64, "base64");
117
+ const iv = Buffer.from(ivB64, "base64");
118
+ const tag = ciphertextBuffer.subarray(ciphertextBuffer.length - 16);
119
+ const encrypted = ciphertextBuffer.subarray(0, ciphertextBuffer.length - 16);
120
+ const decipher = nodeCrypto.createDecipheriv("aes-256-gcm", key, iv);
121
+ decipher.setAuthTag(tag);
122
+ return Buffer.concat([decipher.update(encrypted), decipher.final()]);
123
+ }
124
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ /** depth counts hops already taken; a call at depth >= this is refused, so a
2
+ * chain reaches at most MAX_DELEGATION_DEPTH+1 agents before the last one
3
+ * is forced to answer directly instead of delegating further. */
4
+ export declare const MAX_DELEGATION_DEPTH = 3;
5
+ /** observed single search-heavy replies take up to ~30s; a delegate's own
6
+ * reply can itself involve tool rounds (including one more delegation
7
+ * hop), so this gives ~3x headroom per hop. */
8
+ export declare const DELEGATION_TIMEOUT_MS = 90000;
9
+ /**
10
+ * Prefixes an outbound delegation message with its hop depth. Stripped by
11
+ * parseIncoming on the receiving end before the task text ever reaches that
12
+ * identity's agent logic -- the marker is wire protocol, not conversational
13
+ * content.
14
+ */
15
+ export declare function wrap(depth: number, text: string): string;
16
+ /**
17
+ * Splits an incoming plaintext into { depth, text }. Ordinary,
18
+ * non-delegated messages (the overwhelming majority -- anything from a
19
+ * human, or an agent message with no marker) have no prefix and come back
20
+ * as { depth: 0, text: plaintext } unchanged, so this is a no-op on the
21
+ * existing message path.
22
+ */
23
+ export declare function parseIncoming(plaintext: string): {
24
+ depth: number;
25
+ text: string;
26
+ };
27
+ export declare function hasPending(chatId: number): boolean;
28
+ /**
29
+ * Registers a wait for the next reply in `chatId` from `targetId`, and
30
+ * returns a Promise that resolves with the reply's plaintext, or rejects if
31
+ * `timeoutMs` elapses first. Throws synchronously (before any network call)
32
+ * if this chat already has a pending wait.
33
+ */
34
+ export declare function register(chatId: number, targetId: number, timeoutMs: number): Promise<string>;
35
+ /**
36
+ * Clears a pending wait without settling its promise -- used when sending
37
+ * the delegation message itself fails after register() already ran, so the
38
+ * registry entry doesn't leak (the caller throws its own error instead).
39
+ */
40
+ export declare function cancel(chatId: number): void;
41
+ /**
42
+ * Call right after decrypting an inbound message, before deciding whether
43
+ * to run a fresh reply cycle. If `chatId` has a pending wait AND `senderId`
44
+ * is the identity it's waiting on, resolves that wait with `plaintext` and
45
+ * returns true (caller should stop processing this message as a new
46
+ * prompt). Returns false for everything else -- including a delegation
47
+ * TARGET's very first inbound request, which by definition has no pending
48
+ * entry on its own server.
49
+ */
50
+ export declare function resolveIfPending(chatId: number, senderId: number, plaintext: string): boolean;
51
+ export interface DelegationTrailEntry {
52
+ agent_id: number;
53
+ chat_id: number;
54
+ username: string;
55
+ }
56
+ export declare function recordTrail(identityId: number, mainChatId: number, entry: DelegationTrailEntry): void;
57
+ export declare function drainTrail(identityId: number, mainChatId: number): DelegationTrailEntry[];
58
+ //# sourceMappingURL=delegations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delegations.d.ts","sourceRoot":"","sources":["../src/delegations.ts"],"names":[],"mappings":"AAmBA;;kEAEkE;AAClE,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC;;gDAEgD;AAChD,eAAO,MAAM,qBAAqB,QAAS,CAAC;AAI5C;;;;;GAKG;AACH,wBAAgB,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAIhF;AAeD,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAElD;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAW7F;AAED;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAK3C;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAO7F;AAID,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAeD,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,oBAAoB,GAAG,IAAI,CAIrG;AAED,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,oBAAoB,EAAE,CAKzF"}
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ // Lets one hosted identity ask another Salt agent for help and wait for its
3
+ // reply, entirely over real Salt chat messages. Ported from
4
+ // salt-claude-agent/src/delegations.js. Two problems only this module solves:
5
+ //
6
+ // 1. The target's reply comes back through the exact same webhook path as
7
+ // any ordinary inbound message. In a 1:1 chat, Salt webhooks EVERY member
8
+ // for EVERY message (no @mention gating -- that's group-chat only), so
9
+ // without this registry the delegator would treat the target's reply as a
10
+ // brand-new prompt and answer it back into the same chat, which the
11
+ // target would then also treat as new, forever. `register`/`resolveIfPending`
12
+ // let a webhook handler recognize "this inbound message is the reply I'm
13
+ // waiting on" and resolve it before the agent's own reply logic ever
14
+ // sees it.
15
+ // 2. Nothing else bounds how many hops a delegation chain can take (an agent
16
+ // asked could itself try to delegate further) or how long one hop can
17
+ // take. `wrap`/`parseIncoming` carry a hop counter across the wire (the
18
+ // only channel available between two separately-hosted agents is the
19
+ // message text itself), and `register`'s timeout bounds each hop.
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.DELEGATION_TIMEOUT_MS = exports.MAX_DELEGATION_DEPTH = void 0;
22
+ exports.wrap = wrap;
23
+ exports.parseIncoming = parseIncoming;
24
+ exports.hasPending = hasPending;
25
+ exports.register = register;
26
+ exports.cancel = cancel;
27
+ exports.resolveIfPending = resolveIfPending;
28
+ exports.recordTrail = recordTrail;
29
+ exports.drainTrail = drainTrail;
30
+ /** depth counts hops already taken; a call at depth >= this is refused, so a
31
+ * chain reaches at most MAX_DELEGATION_DEPTH+1 agents before the last one
32
+ * is forced to answer directly instead of delegating further. */
33
+ exports.MAX_DELEGATION_DEPTH = 3;
34
+ /** observed single search-heavy replies take up to ~30s; a delegate's own
35
+ * reply can itself involve tool rounds (including one more delegation
36
+ * hop), so this gives ~3x headroom per hop. */
37
+ exports.DELEGATION_TIMEOUT_MS = 90_000;
38
+ const MARKER_RE = /^\[\[SALT-DELEGATION depth=(\d+)\]\]\n/;
39
+ /**
40
+ * Prefixes an outbound delegation message with its hop depth. Stripped by
41
+ * parseIncoming on the receiving end before the task text ever reaches that
42
+ * identity's agent logic -- the marker is wire protocol, not conversational
43
+ * content.
44
+ */
45
+ function wrap(depth, text) {
46
+ return `[[SALT-DELEGATION depth=${depth}]]\n${text}`;
47
+ }
48
+ /**
49
+ * Splits an incoming plaintext into { depth, text }. Ordinary,
50
+ * non-delegated messages (the overwhelming majority -- anything from a
51
+ * human, or an agent message with no marker) have no prefix and come back
52
+ * as { depth: 0, text: plaintext } unchanged, so this is a no-op on the
53
+ * existing message path.
54
+ */
55
+ function parseIncoming(plaintext) {
56
+ const match = MARKER_RE.exec(plaintext);
57
+ if (!match)
58
+ return { depth: 0, text: plaintext };
59
+ return { depth: parseInt(match[1], 10), text: plaintext.slice(match[0].length) };
60
+ }
61
+ // chatId -> pending wait. One pending wait per chat at a time -- a second
62
+ // concurrent delegation call into the same chat is a clean error (see
63
+ // register below) rather than an ambiguous race over which reply belongs
64
+ // to which caller.
65
+ const pending = new Map();
66
+ function hasPending(chatId) {
67
+ return pending.has(chatId);
68
+ }
69
+ /**
70
+ * Registers a wait for the next reply in `chatId` from `targetId`, and
71
+ * returns a Promise that resolves with the reply's plaintext, or rejects if
72
+ * `timeoutMs` elapses first. Throws synchronously (before any network call)
73
+ * if this chat already has a pending wait.
74
+ */
75
+ function register(chatId, targetId, timeoutMs) {
76
+ if (pending.has(chatId)) {
77
+ throw new Error("Already waiting on a reply from this agent in this chat -- wait for that to finish first.");
78
+ }
79
+ return new Promise((resolve, reject) => {
80
+ const timer = setTimeout(() => {
81
+ pending.delete(chatId);
82
+ reject(new Error(`No reply within ${Math.round(timeoutMs / 1000)}s -- the agent may be offline or slow.`));
83
+ }, timeoutMs);
84
+ pending.set(chatId, { targetId, resolve, reject, timer });
85
+ });
86
+ }
87
+ /**
88
+ * Clears a pending wait without settling its promise -- used when sending
89
+ * the delegation message itself fails after register() already ran, so the
90
+ * registry entry doesn't leak (the caller throws its own error instead).
91
+ */
92
+ function cancel(chatId) {
93
+ const entry = pending.get(chatId);
94
+ if (!entry)
95
+ return;
96
+ clearTimeout(entry.timer);
97
+ pending.delete(chatId);
98
+ }
99
+ /**
100
+ * Call right after decrypting an inbound message, before deciding whether
101
+ * to run a fresh reply cycle. If `chatId` has a pending wait AND `senderId`
102
+ * is the identity it's waiting on, resolves that wait with `plaintext` and
103
+ * returns true (caller should stop processing this message as a new
104
+ * prompt). Returns false for everything else -- including a delegation
105
+ * TARGET's very first inbound request, which by definition has no pending
106
+ * entry on its own server.
107
+ */
108
+ function resolveIfPending(chatId, senderId, plaintext) {
109
+ const entry = pending.get(chatId);
110
+ if (!entry || entry.targetId !== senderId)
111
+ return false;
112
+ clearTimeout(entry.timer);
113
+ pending.delete(chatId);
114
+ entry.resolve(plaintext);
115
+ return true;
116
+ }
117
+ // While an identity is composing ONE reply, each successful delegation
118
+ // records who was consulted here. When the reply is posted back to the
119
+ // originating chat, the caller drains this and attaches it as the
120
+ // message's `delegations` metadata -- powering the "consulted @agent"
121
+ // expander a human sees on that reply. Keyed by identity + originating
122
+ // chat so concurrent replies (different chats, or different hosted
123
+ // identities) never mix their trails.
124
+ const trails = new Map();
125
+ function trailKey(identityId, mainChatId) {
126
+ return `${identityId}:${mainChatId}`;
127
+ }
128
+ function recordTrail(identityId, mainChatId, entry) {
129
+ const key = trailKey(identityId, mainChatId);
130
+ if (!trails.has(key))
131
+ trails.set(key, []);
132
+ trails.get(key).push(entry);
133
+ }
134
+ function drainTrail(identityId, mainChatId) {
135
+ const key = trailKey(identityId, mainChatId);
136
+ const entries = trails.get(key) || [];
137
+ trails.delete(key);
138
+ return entries;
139
+ }
140
+ //# sourceMappingURL=delegations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delegations.js","sourceRoot":"","sources":["../src/delegations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ export interface AgentIdentity {
2
+ saltAppId: number;
3
+ username: string;
4
+ displayName?: string;
5
+ apiKey: string;
6
+ publicKey: string;
7
+ privateKey: string;
8
+ /** Free-form per-identity state a consumer's own agent loop may want to stash (e.g. a system prompt). */
9
+ [key: string]: unknown;
10
+ }
11
+ export interface IdentityStore {
12
+ register(identity: AgentIdentity): void;
13
+ get(saltAppId: number): AgentIdentity | undefined;
14
+ all(): AgentIdentity[];
15
+ }
16
+ /**
17
+ * Creates a JSON-file-backed identity store. Loads synchronously at
18
+ * creation time so a spawned agent's keys survive a process restart --
19
+ * without this, restarting to pick up a code change would silently orphan
20
+ * every agent the process had ever spawned (their identity would only have
21
+ * lived in memory, so they'd stop being able to decrypt anything sent to
22
+ * them).
23
+ */
24
+ export declare function createIdentityStore(storePath?: string): IdentityStore;
25
+ //# sourceMappingURL=identities.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identities.d.ts","sourceRoot":"","sources":["../src/identities.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,yGAAyG;IACzG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI,CAAC;IACxC,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAAC;IAClD,GAAG,IAAI,aAAa,EAAE,CAAC;CACxB;AAID;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,GAAE,MAA2B,GAAG,aAAa,CA6CzF"}
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ // Registry of every Salt agent identity a process can act as: a primary
3
+ // one from the consumer's own config, plus any it spawns at runtime (e.g.
4
+ // via an actions.createSaltAgent call). Spawned agents share the same
5
+ // webhook server as their parent, so incoming requests get routed to the
6
+ // right identity by trying each one's private key against the ciphertext
7
+ // (see webhook.ts's resolveIdentity).
8
+ //
9
+ // Ported from salt-claude-agent/src/identities.js, restructured from a
10
+ // module-level singleton into a factory: the original hardcoded its store
11
+ // path relative to its own __dirname, which only works when the file lives
12
+ // in the top-level app -- once this is a node_modules dependency,
13
+ // __dirname points inside node_modules/salt-agent-sdk/dist instead of the
14
+ // consumer's own project, silently breaking persistence. Callers now pass
15
+ // an explicit path (or accept the process.cwd()-relative default).
16
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ var desc = Object.getOwnPropertyDescriptor(m, k);
19
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
20
+ desc = { enumerable: true, get: function() { return m[k]; } };
21
+ }
22
+ Object.defineProperty(o, k2, desc);
23
+ }) : (function(o, m, k, k2) {
24
+ if (k2 === undefined) k2 = k;
25
+ o[k2] = m[k];
26
+ }));
27
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
28
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
29
+ }) : function(o, v) {
30
+ o["default"] = v;
31
+ });
32
+ var __importStar = (this && this.__importStar) || (function () {
33
+ var ownKeys = function(o) {
34
+ ownKeys = Object.getOwnPropertyNames || function (o) {
35
+ var ar = [];
36
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
37
+ return ar;
38
+ };
39
+ return ownKeys(o);
40
+ };
41
+ return function (mod) {
42
+ if (mod && mod.__esModule) return mod;
43
+ var result = {};
44
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
45
+ __setModuleDefault(result, mod);
46
+ return result;
47
+ };
48
+ })();
49
+ Object.defineProperty(exports, "__esModule", { value: true });
50
+ exports.createIdentityStore = createIdentityStore;
51
+ const fs = __importStar(require("node:fs"));
52
+ const path = __importStar(require("node:path"));
53
+ const DEFAULT_STORE_PATH = path.join(process.cwd(), "data", "identities.json");
54
+ /**
55
+ * Creates a JSON-file-backed identity store. Loads synchronously at
56
+ * creation time so a spawned agent's keys survive a process restart --
57
+ * without this, restarting to pick up a code change would silently orphan
58
+ * every agent the process had ever spawned (their identity would only have
59
+ * lived in memory, so they'd stop being able to decrypt anything sent to
60
+ * them).
61
+ */
62
+ function createIdentityStore(storePath = DEFAULT_STORE_PATH) {
63
+ const identities = new Map();
64
+ function load() {
65
+ let raw;
66
+ try {
67
+ raw = fs.readFileSync(storePath, "utf8");
68
+ }
69
+ catch (err) {
70
+ if (err.code !== "ENOENT") {
71
+ console.error("[identities] failed to read store:", err.message);
72
+ }
73
+ return;
74
+ }
75
+ try {
76
+ for (const identity of JSON.parse(raw)) {
77
+ identities.set(identity.saltAppId, identity);
78
+ }
79
+ }
80
+ catch (err) {
81
+ console.error("[identities] failed to parse store:", err.message);
82
+ }
83
+ }
84
+ function persist() {
85
+ try {
86
+ fs.mkdirSync(path.dirname(storePath), { recursive: true });
87
+ fs.writeFileSync(storePath, JSON.stringify(Array.from(identities.values()), null, 2));
88
+ }
89
+ catch (err) {
90
+ console.error("[identities] failed to persist store:", err.message);
91
+ }
92
+ }
93
+ load();
94
+ return {
95
+ register(identity) {
96
+ identities.set(identity.saltAppId, identity);
97
+ persist();
98
+ },
99
+ get(saltAppId) {
100
+ return identities.get(saltAppId);
101
+ },
102
+ all() {
103
+ return Array.from(identities.values());
104
+ },
105
+ };
106
+ }
107
+ //# sourceMappingURL=identities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identities.js","sourceRoot":"","sources":["../src/identities.ts"],"names":[],"mappings":";AAAA,wEAAwE;AACxE,0EAA0E;AAC1E,sEAAsE;AACtE,yEAAyE;AACzE,yEAAyE;AACzE,sCAAsC;AACtC,EAAE;AACF,uEAAuE;AACvE,0EAA0E;AAC1E,2EAA2E;AAC3E,kEAAkE;AAClE,0EAA0E;AAC1E,0EAA0E;AAC1E,mEAAmE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCnE,kDA6CC;AA3ED,4CAA8B;AAC9B,gDAAkC;AAmBlC,MAAM,kBAAkB,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,iBAAiB,CAAC,CAAC;AAE/E;;;;;;;GAOG;AACH,SAAgB,mBAAmB,CAAC,YAAoB,kBAAkB;IACxE,MAAM,UAAU,GAAG,IAAI,GAAG,EAAyB,CAAC;IAEpD,SAAS,IAAI;QACX,IAAI,GAAW,CAAC;QAChB,IAAI,CAAC;YACH,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QAC3C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACrD,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAG,GAAa,CAAC,OAAO,CAAC,CAAC;YAC9E,CAAC;YACD,OAAO;QACT,CAAC;QACD,IAAI,CAAC;YACH,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAoB,EAAE,CAAC;gBAC1D,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;YAC/C,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,qCAAqC,EAAG,GAAa,CAAC,OAAO,CAAC,CAAC;QAC/E,CAAC;IACH,CAAC;IAED,SAAS,OAAO;QACd,IAAI,CAAC;YACH,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAC3D,EAAE,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QACxF,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,uCAAuC,EAAG,GAAa,CAAC,OAAO,CAAC,CAAC;QACjF,CAAC;IACH,CAAC;IAED,IAAI,EAAE,CAAC;IAEP,OAAO;QACL,QAAQ,CAAC,QAAuB;YAC9B,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;YAC7C,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,GAAG,CAAC,SAAiB;YACnB,OAAO,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACnC,CAAC;QACD,GAAG;YACD,OAAO,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;QACzC,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,8 @@
1
+ export * from "./client";
2
+ export * from "./crypto";
3
+ export * from "./identities";
4
+ export * from "./delegations";
5
+ export * from "./webhook";
6
+ export * from "./actions";
7
+ export * from "./config";
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./client"), exports);
18
+ __exportStar(require("./crypto"), exports);
19
+ __exportStar(require("./identities"), exports);
20
+ __exportStar(require("./delegations"), exports);
21
+ __exportStar(require("./webhook"), exports);
22
+ __exportStar(require("./actions"), exports);
23
+ __exportStar(require("./config"), exports);
24
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,2CAAyB;AACzB,+CAA6B;AAC7B,gDAA8B;AAC9B,4CAA0B;AAC1B,4CAA0B;AAC1B,2CAAyB"}
@@ -0,0 +1,127 @@
1
+ import { type Express } from "express";
2
+ import type { SaltClient } from "./client";
3
+ import type { AgentIdentity, IdentityStore } from "./identities";
4
+ /** The exact marker a hand-off briefing must start with, so the incoming
5
+ * agent's polling loop (below) can detect it arrived. Consumers building
6
+ * the outgoing briefing prompt must instruct their model to emit this
7
+ * exact line first. */
8
+ export declare const HANDOFF_BRIEFING_MARKER = "[[SALT-HANDOFF-BRIEFING]]";
9
+ export interface RawChatMeta {
10
+ active_agent_id?: number | string;
11
+ mediator_agent_id?: number | string;
12
+ coaching_for_chat_id?: number | string;
13
+ mode?: "auto" | "manual";
14
+ [key: string]: unknown;
15
+ }
16
+ export interface RawSender {
17
+ id: number | string;
18
+ display_name?: string;
19
+ account_type?: "User" | "Agent";
20
+ [key: string]: unknown;
21
+ }
22
+ export interface DecryptedAttachment {
23
+ filename: string;
24
+ contentType: string;
25
+ size: number;
26
+ /** Present only when contentType starts with "image/" -- the SDK doesn't
27
+ * know how to represent other file types beyond a note in `unsupportedNote`. */
28
+ data?: Buffer;
29
+ unsupportedNote?: string;
30
+ }
31
+ export interface MessageContext {
32
+ identity: AgentIdentity;
33
+ chatId: number;
34
+ senderId: number;
35
+ sender: RawSender;
36
+ /** Delegation-marker-stripped plaintext (see delegations.parseIncoming). */
37
+ text: string;
38
+ /** Delegation hop depth this message arrived at; pass through to any further delegate call. */
39
+ delegationDepth: number;
40
+ chatMeta?: RawChatMeta;
41
+ /** Set when this identity is the configured Mediator privately coaching
42
+ * someone about a shared chat it also observes -- the generic decrypted
43
+ * transcript of that shared chat (not yet wrapped in any prompt framing). */
44
+ mediatorSharedContext?: string;
45
+ /** Set when the inbound message was an Attachment with decryptable metadata. */
46
+ attachment?: DecryptedAttachment;
47
+ /** Encrypts `text` for every current chat member (+ this identity's own
48
+ * copy), posts it, drains this reply's delegation trail onto it, and
49
+ * emits an agent_reply_sent metric. Handles a typing-indicator heartbeat
50
+ * for the duration of the call automatically. */
51
+ reply(text: string): Promise<void>;
52
+ }
53
+ export interface CardInteractionContext {
54
+ identity: AgentIdentity;
55
+ chatId: number;
56
+ cardId: number | string;
57
+ actionId: string;
58
+ user: RawSender;
59
+ blocks: unknown;
60
+ }
61
+ export interface InvoicePaidContext {
62
+ identity: AgentIdentity;
63
+ chatId: number;
64
+ buyer: RawSender;
65
+ lineItems: Array<{
66
+ name: string;
67
+ qty: number;
68
+ [key: string]: unknown;
69
+ }>;
70
+ amount: string | number;
71
+ /** True for a "Credits top-up" invoice -- no delivery owed, the credit already landed server-side. */
72
+ isTopUp: boolean;
73
+ transferRequestId: number | string;
74
+ reply(text: string): Promise<void>;
75
+ }
76
+ export interface HandoffConfirmedContext {
77
+ identity: AgentIdentity;
78
+ chatId: number;
79
+ reason?: string;
80
+ reply(text: string): Promise<void>;
81
+ }
82
+ export interface HandoffReceivedContext {
83
+ identity: AgentIdentity;
84
+ chatId: number;
85
+ reason?: string;
86
+ /** The shared chat's decrypted transcript, polled for up to ~20s until
87
+ * the outgoing agent's HANDOFF_BRIEFING_MARKER shows up (or timeout). */
88
+ context: string;
89
+ reply(text: string): Promise<void>;
90
+ }
91
+ export interface Logger {
92
+ info(msg: string): void;
93
+ error(msg: string): void;
94
+ }
95
+ export interface WebhookServerOptions {
96
+ client: SaltClient;
97
+ identities: IdentityStore;
98
+ pgpPassphrase: string;
99
+ /** Required in production: must match the X-Salt-Webhook-Secret header
100
+ * salt-api's jobs send. Unset (local dev) skips the check entirely --
101
+ * encrypted-message webhooks fail safe regardless (forged ciphertext
102
+ * never decrypts), but card_interaction/invoice_paid payloads are
103
+ * plaintext and would otherwise be actable by anyone who can reach this server. */
104
+ webhookSharedSecret?: string;
105
+ /** This process's configured Mediator identity, if any -- enables the
106
+ * "observe the shared chat silently, only speak in private coaching
107
+ * chats" gate. */
108
+ mediatorAgentId?: number;
109
+ logger?: Logger;
110
+ onMessage?: (ctx: MessageContext) => Promise<void> | void;
111
+ onCardInteraction?: (ctx: CardInteractionContext) => Promise<void> | void;
112
+ onInvoicePaid?: (ctx: InvoicePaidContext) => Promise<void> | void;
113
+ onHandoffConfirmed?: (ctx: HandoffConfirmedContext) => Promise<void> | void;
114
+ onHandoffReceived?: (ctx: HandoffReceivedContext) => Promise<void> | void;
115
+ /** Extra fields to merge into the /health JSON response (e.g. which model is configured). */
116
+ healthExtra?: () => Record<string, unknown>;
117
+ }
118
+ /**
119
+ * Creates the Express app for a Salt agent's webhook endpoint. Mount it
120
+ * yourself (e.g. `createWebhookServer(opts).listen(port)`, or grab `.app`
121
+ * to compose it into a bigger server).
122
+ */
123
+ export declare function createWebhookServer(options: WebhookServerOptions): {
124
+ app: Express;
125
+ listen: (port: number) => void;
126
+ };
127
+ //# sourceMappingURL=webhook.d.ts.map