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/README.md +282 -0
- package/dist/actions.d.ts +72 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +706 -0
- package/dist/actions.js.map +1 -0
- package/dist/client.d.ts +141 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +188 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +40 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +61 -0
- package/dist/config.js.map +1 -0
- package/dist/crypto.d.ts +46 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +124 -0
- package/dist/crypto.js.map +1 -0
- package/dist/delegations.d.ts +58 -0
- package/dist/delegations.d.ts.map +1 -0
- package/dist/delegations.js +140 -0
- package/dist/delegations.js.map +1 -0
- package/dist/identities.d.ts +25 -0
- package/dist/identities.d.ts.map +1 -0
- package/dist/identities.js +107 -0
- package/dist/identities.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/webhook.d.ts +127 -0
- package/dist/webhook.d.ts.map +1 -0
- package/dist/webhook.js +470 -0
- package/dist/webhook.js.map +1 -0
- package/package.json +29 -0
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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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
|