salt-agent-sdk 0.1.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +941 -30
  3. package/dist/actions.d.ts +21 -5
  4. package/dist/actions.d.ts.map +1 -1
  5. package/dist/actions.js +673 -54
  6. package/dist/actions.js.map +1 -1
  7. package/dist/ask.d.ts +83 -0
  8. package/dist/ask.d.ts.map +1 -0
  9. package/dist/ask.js +215 -0
  10. package/dist/ask.js.map +1 -0
  11. package/dist/chatPrivacy.d.ts +34 -0
  12. package/dist/chatPrivacy.d.ts.map +1 -0
  13. package/dist/chatPrivacy.js +60 -0
  14. package/dist/chatPrivacy.js.map +1 -0
  15. package/dist/client.d.ts +3123 -35
  16. package/dist/client.d.ts.map +1 -1
  17. package/dist/client.js +1267 -143
  18. package/dist/client.js.map +1 -1
  19. package/dist/config.d.ts +16 -6
  20. package/dist/config.d.ts.map +1 -1
  21. package/dist/config.js +16 -5
  22. package/dist/config.js.map +1 -1
  23. package/dist/crypto.d.ts +62 -0
  24. package/dist/crypto.d.ts.map +1 -1
  25. package/dist/crypto.js +121 -0
  26. package/dist/crypto.js.map +1 -1
  27. package/dist/delegations.d.ts +25 -8
  28. package/dist/delegations.d.ts.map +1 -1
  29. package/dist/delegations.js +67 -11
  30. package/dist/delegations.js.map +1 -1
  31. package/dist/device/client.d.ts +161 -0
  32. package/dist/device/client.d.ts.map +1 -0
  33. package/dist/device/client.js +601 -0
  34. package/dist/device/client.js.map +1 -0
  35. package/dist/device/host.d.ts +222 -0
  36. package/dist/device/host.d.ts.map +1 -0
  37. package/dist/device/host.js +891 -0
  38. package/dist/device/host.js.map +1 -0
  39. package/dist/device/http.d.ts +12 -0
  40. package/dist/device/http.d.ts.map +1 -0
  41. package/dist/device/http.js +124 -0
  42. package/dist/device/http.js.map +1 -0
  43. package/dist/device/index.d.ts +5 -0
  44. package/dist/device/index.d.ts.map +1 -0
  45. package/dist/device/index.js +27 -0
  46. package/dist/device/index.js.map +1 -0
  47. package/dist/device/protocol.d.ts +312 -0
  48. package/dist/device/protocol.d.ts.map +1 -0
  49. package/dist/device/protocol.js +435 -0
  50. package/dist/device/protocol.js.map +1 -0
  51. package/dist/identities.d.ts +9 -2
  52. package/dist/identities.d.ts.map +1 -1
  53. package/dist/identities.js +33 -3
  54. package/dist/identities.js.map +1 -1
  55. package/dist/identity.d.ts +155 -0
  56. package/dist/identity.d.ts.map +1 -0
  57. package/dist/identity.js +196 -0
  58. package/dist/identity.js.map +1 -0
  59. package/dist/identityShare.d.ts +129 -0
  60. package/dist/identityShare.d.ts.map +1 -0
  61. package/dist/identityShare.js +308 -0
  62. package/dist/identityShare.js.map +1 -0
  63. package/dist/ids.d.ts +11 -0
  64. package/dist/ids.d.ts.map +1 -0
  65. package/dist/ids.js +17 -0
  66. package/dist/ids.js.map +1 -0
  67. package/dist/index.d.ts +13 -0
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +13 -0
  70. package/dist/index.js.map +1 -1
  71. package/dist/reconcile.d.ts +25 -0
  72. package/dist/reconcile.d.ts.map +1 -0
  73. package/dist/reconcile.js +60 -0
  74. package/dist/reconcile.js.map +1 -0
  75. package/dist/recovery.d.ts +51 -0
  76. package/dist/recovery.d.ts.map +1 -0
  77. package/dist/recovery.js +59 -0
  78. package/dist/recovery.js.map +1 -0
  79. package/dist/register.d.ts +32 -0
  80. package/dist/register.d.ts.map +1 -0
  81. package/dist/register.js +65 -0
  82. package/dist/register.js.map +1 -0
  83. package/dist/scriptJob.d.ts +92 -0
  84. package/dist/scriptJob.d.ts.map +1 -0
  85. package/dist/scriptJob.js +270 -0
  86. package/dist/scriptJob.js.map +1 -0
  87. package/dist/sessions.d.ts +131 -0
  88. package/dist/sessions.d.ts.map +1 -0
  89. package/dist/sessions.js +240 -0
  90. package/dist/sessions.js.map +1 -0
  91. package/dist/socket.d.ts +115 -0
  92. package/dist/socket.d.ts.map +1 -0
  93. package/dist/socket.js +1038 -0
  94. package/dist/socket.js.map +1 -0
  95. package/dist/webhook.d.ts +627 -28
  96. package/dist/webhook.d.ts.map +1 -1
  97. package/dist/webhook.js +1388 -84
  98. package/dist/webhook.js.map +1 -1
  99. package/dist/work.d.ts +57 -0
  100. package/dist/work.d.ts.map +1 -0
  101. package/dist/work.js +213 -0
  102. package/dist/work.js.map +1 -0
  103. package/package.json +26 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recovery.js","sourceRoot":"","sources":["../src/recovery.ts"],"names":[],"mappings":";;AA4EA,sDAEC;AAQD,0CAWC;AAjGD,4EAA4E;AAC5E,4EAA4E;AAC5E,2DAA2D;AAC3D,EAAE;AACF,yEAAyE;AACzE,6DAA6D;AAC7D,iEAAiE;AACjE,4EAA4E;AAC5E,uEAAuE;AACvE,qDAAqD;AACrD,2EAA2E;AAC3E,6CAA6C;AAC7C,uEAAuE;AACvE,EAAE;AACF,mDAAmD;AACnD,6EAA6E;AAC7E,6CAA6C;AAC7C,EAAE;AACF,2EAA2E;AAC3E,+EAA+E;AAC/E,yEAAyE;AACzE,EAAE;AACF,sEAAsE;AACtE,2EAA2E;AAC3E,2EAA2E;AAC3E,6EAA6E;AAC7E,0EAA0E;AAC1E,gFAAgF;AAChF,EAAE;AACF,6EAA6E;AAC7E,8EAA8E;AAC9E,0EAA0E;AAC1E,6EAA6E;AAC7E,iDAAiD;AACjD,2CAAkD;AAyClD,2GAA2G;AACpG,KAAK,UAAU,qBAAqB,CAAC,aAAqB,EAAE,kBAA0B;IAC3F,OAAO,IAAA,sBAAU,EAAC,aAAa,EAAE,CAAC,kBAAkB,CAAC,CAAC,CAAC;AACzD,CAAC;AAED;;;;;GAKG;AACI,KAAK,UAAU,eAAe,CAAC,MAKrC;IACC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC;IACjG,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IAC/D,MAAM,KAAK,GAAG,MAAM,IAAA,mBAAO,EAAC,IAAI,CAAC,eAAe,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;IACxF,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;IAC3E,OAAO,qBAAqB,CAAC,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC;AAC3E,CAAC"}
@@ -0,0 +1,32 @@
1
+ import { type SaltUser } from "./client.js";
2
+ import type { AgentIdentity, IdentityStore } from "./identities.js";
3
+ export interface RegisterAgentOptions {
4
+ /** Defaults to https://saltapp.ai. */
5
+ baseUrl?: string;
6
+ username: string;
7
+ /** Must not start with "salt". */
8
+ displayName: string;
9
+ /** List the agent in Salt's directory. Defaults to unlisted. */
10
+ listed?: boolean;
11
+ /** Omit for socket mode (no public URL needed). */
12
+ webhook?: string;
13
+ /** Protects the private key at rest. Defaults to a fresh random one, returned as `passphrase`. */
14
+ passphrase?: string;
15
+ /** When given, the new identity is registered (and persisted) there. */
16
+ identities?: IdentityStore;
17
+ /** Override fetch (tests). */
18
+ fetchImpl?: typeof fetch;
19
+ }
20
+ export interface RegisteredAgent {
21
+ agent: SaltUser;
22
+ /** Shown by Salt exactly once. */
23
+ apiKey: string;
24
+ privateKey: string;
25
+ publicKey: string;
26
+ /** The passphrase protecting `privateKey`. */
27
+ passphrase: string;
28
+ /** Ready for `identities.register` (already done when `identities` was passed). */
29
+ identity: AgentIdentity;
30
+ }
31
+ export declare function registerAgent(options: RegisterAgentOptions): Promise<RegisteredAgent>;
32
+ //# sourceMappingURL=register.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../src/register.ts"],"names":[],"mappings":"AAQA,OAAO,EAAgB,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEpE,MAAM,WAAW,oBAAoB;IACnC,sCAAsC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kGAAkG;IAClG,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wEAAwE;IACxE,UAAU,CAAC,EAAE,aAAa,CAAC;IAC3B,8BAA8B;IAC9B,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,QAAQ,CAAC;IAChB,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,UAAU,EAAE,MAAM,CAAC;IACnB,mFAAmF;IACnF,QAAQ,EAAE,aAAa,CAAC;CACzB;AAUD,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,eAAe,CAAC,CA4C3F"}
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ // registerAgent: a root agent with no human account (POST /auth,
3
+ // account_type "Agent"). Generates the OpenPGP pair locally, reads the
4
+ // current terms/privacy versions from GET /api/v1/config, registers, and
5
+ // hands back everything once. The private key is generated here and never
6
+ // sent anywhere; Salt receives only the public key.
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.registerAgent = registerAgent;
9
+ const node_crypto_1 = require("node:crypto");
10
+ const crypto_js_1 = require("./crypto.js");
11
+ const client_js_1 = require("./client.js");
12
+ async function readJson(res) {
13
+ try {
14
+ return await res.json();
15
+ }
16
+ catch {
17
+ return await res.text().catch(() => undefined);
18
+ }
19
+ }
20
+ async function registerAgent(options) {
21
+ const baseUrl = (options.baseUrl ?? "https://saltapp.ai").replace(/\/$/, "");
22
+ const doFetch = options.fetchImpl ?? fetch;
23
+ const configUrl = `${baseUrl}/api/v1/config`;
24
+ const configRes = await doFetch(configUrl, { method: "GET" });
25
+ if (!configRes.ok)
26
+ throw new client_js_1.SaltApiError("GET", configUrl, configRes.status, await readJson(configRes));
27
+ const config = (await configRes.json());
28
+ if (!config.terms_version || !config.privacy_version) {
29
+ throw new Error("Salt's config did not name the current terms and privacy versions.");
30
+ }
31
+ const passphrase = options.passphrase ?? (0, node_crypto_1.randomBytes)(24).toString("hex");
32
+ const keys = await (0, crypto_js_1.generateKeypair)(passphrase);
33
+ const body = {
34
+ account_type: "Agent",
35
+ username: options.username,
36
+ display_name: options.displayName,
37
+ public_key: keys.publicKey,
38
+ accepted_terms_version: config.terms_version,
39
+ accepted_privacy_version: config.privacy_version,
40
+ };
41
+ if (options.listed !== undefined)
42
+ body.listed = options.listed;
43
+ if (options.webhook)
44
+ body.webhook = options.webhook;
45
+ // The trailing slash matters on saltfor.com (a bare POST /auth is a CloudFront 403).
46
+ const url = `${baseUrl}/auth/`;
47
+ const res = await doFetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) });
48
+ if (!res.ok)
49
+ throw new client_js_1.SaltApiError("POST", url, res.status, await readJson(res));
50
+ const agent = (await res.json());
51
+ if (!agent.api_key)
52
+ throw new Error("Salt registered the agent but returned no api_key.");
53
+ const { api_key: apiKey, ...rest } = agent;
54
+ const identity = {
55
+ saltAppId: agent.id,
56
+ username: agent.username,
57
+ displayName: agent.display_name,
58
+ apiKey,
59
+ publicKey: keys.publicKey,
60
+ privateKey: keys.privateKey,
61
+ };
62
+ options.identities?.register(identity);
63
+ return { agent: rest, apiKey, privateKey: keys.privateKey, publicKey: keys.publicKey, passphrase, identity };
64
+ }
65
+ //# sourceMappingURL=register.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register.js","sourceRoot":"","sources":["../src/register.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,92 @@
1
+ export interface SeenMarkOptions {
2
+ /** How long these keys stay "seen", in ms. Overrides the store's default; `Infinity` never expires. */
3
+ ttlMs?: number;
4
+ }
5
+ /**
6
+ * What a script job has already sent. Keys are opaque strings (a URL, a post
7
+ * id); each is remembered until its own expiry, so a feed that republishes an
8
+ * old story a month later can be told again on purpose.
9
+ */
10
+ export interface SeenStore {
11
+ /** True when `key` was marked and has not expired. */
12
+ has(key: string): Promise<boolean>;
13
+ /** Marks every key in `keys` seen, each expiring `ttlMs` from now (the store's default when omitted). */
14
+ mark(keys: string[], opts?: SeenMarkOptions): Promise<void>;
15
+ /** Forgets `key`, so the next run treats it as new. A no-op when it was never marked. */
16
+ forget(key: string): Promise<void>;
17
+ }
18
+ export interface SeenStoreOptions {
19
+ /** Default lifetime of a mark. Defaults to DEFAULT_SEEN_TTL_MS. */
20
+ ttlMs?: number;
21
+ /** Clock, for tests. Defaults to Date.now. */
22
+ now?: () => number;
23
+ }
24
+ /** A mark lasts 30 days unless the store or the call says otherwise. */
25
+ export declare const DEFAULT_SEEN_TTL_MS: number;
26
+ /** In-memory SeenStore. Marks are lost on process restart, and it dedupes only while the SAME instance outlives the runs -- construct it once, not inside the job. */
27
+ export declare function MemorySeenStore(options?: SeenStoreOptions): SeenStore;
28
+ /**
29
+ * One plain JSON file, `{ "<key>": <expires-at epoch ms, or null for never> }`,
30
+ * written atomically (temp file + rename, the same convention as
31
+ * FileSessionStore) so a crash mid-write never leaves half a file. Expired
32
+ * keys are dropped whenever the file is rewritten. A missing file is an empty
33
+ * store; an unreadable one throws rather than silently re-sending everything.
34
+ *
35
+ * The keys are whatever you pass to `key(item)` -- usually URLs -- stored in
36
+ * PLAINTEXT (the file is created 0600, its new directories 0700). Run ONE job
37
+ * per file at a time: writes are queued per instance, in this process only,
38
+ * so two processes (or two instances) on one file can lose each other's keys.
39
+ */
40
+ export declare function FileSeenStore(file: string, options?: SeenStoreOptions): SeenStore;
41
+ /** Handed to every source: abort the fetch when it fires (the job's timeout). Sources that ignore it still get raced against the timeout. */
42
+ export interface ScriptSourceContext {
43
+ signal: AbortSignal;
44
+ }
45
+ /** A source is a plain async function: fetch something, normalise it into items, return them. No model. */
46
+ export type ScriptSource<T> = (ctx: ScriptSourceContext) => Promise<T[]> | T[];
47
+ /** What an item looks like when you don't write `compose`: `title` and `url` make the default list. */
48
+ export interface ScriptJobItem {
49
+ title?: string;
50
+ url?: string;
51
+ [key: string]: unknown;
52
+ }
53
+ export interface ScriptJobLogger {
54
+ error(msg: string): void;
55
+ }
56
+ export interface ScriptJobOptions<T> {
57
+ /** Named sources (`{ hackerNews, rss }`) or a plain array. The name is what `failedSources` reports; an array entry is named by its function name, else `source-<index>`. */
58
+ sources: Record<string, ScriptSource<T>> | Array<ScriptSource<T>>;
59
+ /** A stable id for an item -- its URL, a post id. Two items with the same key are the same story. */
60
+ key: (item: T) => string;
61
+ /** What was already sent. */
62
+ seen: SeenStore;
63
+ /** OPTIONAL, and the only place a model may be called. Gets only the NEW items, and only when there are some. Return the text to post; an empty string or null posts nothing and marks nothing. Omit it for a plain list. */
64
+ compose?: (fresh: T[]) => Promise<string | null | undefined> | string | null | undefined;
65
+ /** Sends the text to the chat. Throwing leaves every item unseen. */
66
+ post: (text: string, fresh: T[]) => Promise<unknown> | unknown;
67
+ /** Per-source timeout in ms. Default DEFAULT_SOURCE_TIMEOUT_MS. */
68
+ sourceTimeoutMs?: number;
69
+ /** How long posted items stay seen, in ms. Default: the store's own. */
70
+ seenTtlMs?: number;
71
+ logger?: ScriptJobLogger;
72
+ }
73
+ export interface ScriptJobResult {
74
+ /** Items the sources returned, before dropping any (items from a failed source are not counted). */
75
+ fetched: number;
76
+ /** Items not seen before and not duplicated within this run: what compose got. */
77
+ fresh: number;
78
+ /** True when `post` ran and succeeded. */
79
+ posted: boolean;
80
+ /** Names of the sources that threw or timed out. */
81
+ failedSources: string[];
82
+ }
83
+ export declare const DEFAULT_SOURCE_TIMEOUT_MS = 20000;
84
+ /** The deterministic text posted when there is no `compose`: one line per item, "- title" and its url beneath. */
85
+ export declare function defaultCompose<T>(fresh: T[], key: (item: T) => string): string;
86
+ /**
87
+ * Runs one pass of a script job and says what happened. See the header
88
+ * comment for the contract; the short version: fetch in code, drop what was
89
+ * sent, compose only if something is new, post, and only then mark seen.
90
+ */
91
+ export declare function runScriptJob<T>(options: ScriptJobOptions<T>): Promise<ScriptJobResult>;
92
+ //# sourceMappingURL=scriptJob.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scriptJob.d.ts","sourceRoot":"","sources":["../src/scriptJob.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,270 @@
1
+ "use strict";
2
+ // Deterministic-first scheduled work ("script jobs").
3
+ //
4
+ // A recurring job -- a news digest, a price watch, a "tell me when the build
5
+ // is green" -- is mostly plumbing: fetch some pages, drop what was already
6
+ // sent, write it up, post it. Only the "write it up" step ever needs a model,
7
+ // and often not even that. The owner's rule (2026-10-13): "use the right
8
+ // tools for the right jobs; don't use AI as the hammer for everything when
9
+ // deterministic scripts and tools can accommodate just as well."
10
+ //
11
+ // runScriptJob is that rule as a function. The host's scheduler (cron, a
12
+ // Grains wake_me, setInterval) calls it; this file never schedules anything.
13
+ //
14
+ // sources plain async functions that fetch and normalise items in code
15
+ // (no model). Each runs ONCE per call, with a timeout; one that
16
+ // throws or times out is listed in `failedSources` and the rest
17
+ // carry on.
18
+ // key a stable id for an item (its URL, a post id) -- what `seen`
19
+ // remembers.
20
+ // seen a SeenStore (required): drops items already sent. MemorySeenStore() lives
21
+ // until the process restarts; FileSeenStore(file) survives it.
22
+ // compose OPTIONAL, and the ONLY place a model may be called. It runs
23
+ // only when there is something new, so a quiet day costs no
24
+ // tokens. Use the cheapest model that can write the text.
25
+ // Leave it out and a plain list is posted (see defaultCompose).
26
+ // Return empty only for a transient failure: empty posts nothing
27
+ // and marks nothing, so the same items reach the model next run.
28
+ // post sends the text to the chat.
29
+ //
30
+ // Nothing new means no compose and no post. Items are marked seen only AFTER
31
+ // post succeeds: a failed post leaves them unseen so the next run retries,
32
+ // and a compose or post error is thrown to the scheduler (re-arm the schedule
33
+ // in a `finally` so a failed run never silently ends it).
34
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
35
+ if (k2 === undefined) k2 = k;
36
+ var desc = Object.getOwnPropertyDescriptor(m, k);
37
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
38
+ desc = { enumerable: true, get: function() { return m[k]; } };
39
+ }
40
+ Object.defineProperty(o, k2, desc);
41
+ }) : (function(o, m, k, k2) {
42
+ if (k2 === undefined) k2 = k;
43
+ o[k2] = m[k];
44
+ }));
45
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
46
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
47
+ }) : function(o, v) {
48
+ o["default"] = v;
49
+ });
50
+ var __importStar = (this && this.__importStar) || (function () {
51
+ var ownKeys = function(o) {
52
+ ownKeys = Object.getOwnPropertyNames || function (o) {
53
+ var ar = [];
54
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
55
+ return ar;
56
+ };
57
+ return ownKeys(o);
58
+ };
59
+ return function (mod) {
60
+ if (mod && mod.__esModule) return mod;
61
+ var result = {};
62
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
63
+ __setModuleDefault(result, mod);
64
+ return result;
65
+ };
66
+ })();
67
+ Object.defineProperty(exports, "__esModule", { value: true });
68
+ exports.DEFAULT_SOURCE_TIMEOUT_MS = exports.DEFAULT_SEEN_TTL_MS = void 0;
69
+ exports.MemorySeenStore = MemorySeenStore;
70
+ exports.FileSeenStore = FileSeenStore;
71
+ exports.defaultCompose = defaultCompose;
72
+ exports.runScriptJob = runScriptJob;
73
+ const node_crypto_1 = require("node:crypto");
74
+ const fs = __importStar(require("node:fs/promises"));
75
+ const path = __importStar(require("node:path"));
76
+ // Owner-only, like socket.ts's cursor files: the keys are usually URLs.
77
+ const DIR_MODE = 0o700;
78
+ const FILE_MODE = 0o600;
79
+ /** A mark lasts 30 days unless the store or the call says otherwise. */
80
+ exports.DEFAULT_SEEN_TTL_MS = 30 * 24 * 60 * 60 * 1000;
81
+ function expiryFor(now, ttlMs, fallback) {
82
+ const ttl = ttlMs ?? fallback;
83
+ return Number.isFinite(ttl) ? now + ttl : null;
84
+ }
85
+ /** In-memory SeenStore. Marks are lost on process restart, and it dedupes only while the SAME instance outlives the runs -- construct it once, not inside the job. */
86
+ function MemorySeenStore(options = {}) {
87
+ const now = options.now ?? Date.now;
88
+ const defaultTtl = options.ttlMs ?? exports.DEFAULT_SEEN_TTL_MS;
89
+ const store = new Map();
90
+ return {
91
+ async has(key) {
92
+ if (!store.has(key))
93
+ return false;
94
+ const expiresAt = store.get(key);
95
+ if (expiresAt != null && expiresAt <= now()) {
96
+ store.delete(key);
97
+ return false;
98
+ }
99
+ return true;
100
+ },
101
+ async mark(keys, opts) {
102
+ const at = now();
103
+ for (const key of keys)
104
+ store.set(key, expiryFor(at, opts?.ttlMs, defaultTtl));
105
+ },
106
+ async forget(key) {
107
+ store.delete(key);
108
+ },
109
+ };
110
+ }
111
+ /**
112
+ * One plain JSON file, `{ "<key>": <expires-at epoch ms, or null for never> }`,
113
+ * written atomically (temp file + rename, the same convention as
114
+ * FileSessionStore) so a crash mid-write never leaves half a file. Expired
115
+ * keys are dropped whenever the file is rewritten. A missing file is an empty
116
+ * store; an unreadable one throws rather than silently re-sending everything.
117
+ *
118
+ * The keys are whatever you pass to `key(item)` -- usually URLs -- stored in
119
+ * PLAINTEXT (the file is created 0600, its new directories 0700). Run ONE job
120
+ * per file at a time: writes are queued per instance, in this process only,
121
+ * so two processes (or two instances) on one file can lose each other's keys.
122
+ */
123
+ function FileSeenStore(file, options = {}) {
124
+ const now = options.now ?? Date.now;
125
+ const defaultTtl = options.ttlMs ?? exports.DEFAULT_SEEN_TTL_MS;
126
+ async function load() {
127
+ try {
128
+ const parsed = JSON.parse(await fs.readFile(file, "utf8"));
129
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
130
+ }
131
+ catch (err) {
132
+ if (err.code === "ENOENT")
133
+ return {};
134
+ throw err;
135
+ }
136
+ }
137
+ async function save(entries) {
138
+ await fs.mkdir(path.dirname(file), { recursive: true, mode: DIR_MODE });
139
+ const tmp = `${file}.${process.pid}.${(0, node_crypto_1.randomBytes)(4).toString("hex")}.tmp`;
140
+ await fs.writeFile(tmp, JSON.stringify(entries, null, 2), { mode: FILE_MODE });
141
+ await fs.rename(tmp, file);
142
+ }
143
+ // Writes are read-modify-write; run them one at a time so two overlapping
144
+ // marks in this process never lose each other's keys.
145
+ let chain = Promise.resolve();
146
+ function serial(fn) {
147
+ const run = chain.then(fn, fn);
148
+ chain = run.catch(() => undefined);
149
+ return run;
150
+ }
151
+ return {
152
+ async has(key) {
153
+ const entries = await load();
154
+ if (!Object.prototype.hasOwnProperty.call(entries, key))
155
+ return false;
156
+ const expiresAt = entries[key];
157
+ return expiresAt == null || expiresAt > now();
158
+ },
159
+ mark(keys, opts) {
160
+ return serial(async () => {
161
+ const at = now();
162
+ const entries = await load();
163
+ for (const [k, expiresAt] of Object.entries(entries)) {
164
+ if (expiresAt != null && expiresAt <= at)
165
+ delete entries[k];
166
+ }
167
+ for (const key of keys)
168
+ entries[key] = expiryFor(at, opts?.ttlMs, defaultTtl);
169
+ await save(entries);
170
+ });
171
+ },
172
+ forget(key) {
173
+ return serial(async () => {
174
+ const entries = await load();
175
+ if (!Object.prototype.hasOwnProperty.call(entries, key))
176
+ return;
177
+ delete entries[key];
178
+ await save(entries);
179
+ });
180
+ },
181
+ };
182
+ }
183
+ exports.DEFAULT_SOURCE_TIMEOUT_MS = 20_000;
184
+ /** The deterministic text posted when there is no `compose`: one line per item, "- title" and its url beneath. */
185
+ function defaultCompose(fresh, key) {
186
+ return fresh
187
+ .map((item) => {
188
+ const it = item;
189
+ const k = key(item);
190
+ const title = typeof it?.title === "string" && it.title ? it.title : k;
191
+ const url = typeof it?.url === "string" && it.url && it.url !== title ? it.url : null;
192
+ return url ? `- ${title}\n ${url}` : `- ${title}`;
193
+ })
194
+ .join("\n");
195
+ }
196
+ function withTimeout(run, ms, name) {
197
+ const controller = new AbortController();
198
+ let timer;
199
+ const timeout = new Promise((_, reject) => {
200
+ timer = setTimeout(() => {
201
+ controller.abort();
202
+ reject(new Error(`${name} timed out after ${ms}ms`));
203
+ }, ms);
204
+ });
205
+ return Promise.race([Promise.resolve().then(() => run(controller.signal)), timeout]).finally(() => clearTimeout(timer));
206
+ }
207
+ /**
208
+ * Runs one pass of a script job and says what happened. See the header
209
+ * comment for the contract; the short version: fetch in code, drop what was
210
+ * sent, compose only if something is new, post, and only then mark seen.
211
+ */
212
+ async function runScriptJob(options) {
213
+ const { key, seen, compose, post } = options;
214
+ const logger = options.logger ?? { error: (msg) => console.error(msg) };
215
+ const timeoutMs = options.sourceTimeoutMs ?? exports.DEFAULT_SOURCE_TIMEOUT_MS;
216
+ const named = Array.isArray(options.sources)
217
+ ? options.sources.map((fn, i) => [fn.name || `source-${i}`, fn])
218
+ : Object.entries(options.sources);
219
+ const settled = await Promise.all(named.map(async ([name, fn]) => {
220
+ try {
221
+ const items = await withTimeout((signal) => fn({ signal }), timeoutMs, name);
222
+ return { name, items: Array.isArray(items) ? items : [], ok: true };
223
+ }
224
+ catch (err) {
225
+ logger.error(`[script job] source ${name} failed: ${err.message}`);
226
+ return { name, items: [], ok: false };
227
+ }
228
+ }));
229
+ const failedSources = settled.filter((s) => !s.ok).map((s) => s.name);
230
+ const all = settled.flatMap((s) => s.items);
231
+ // Source order decides which copy of a duplicate wins.
232
+ const fresh = [];
233
+ const freshKeys = [];
234
+ const inRun = new Set();
235
+ for (const item of all) {
236
+ let k;
237
+ try {
238
+ k = String(key(item));
239
+ }
240
+ catch (err) {
241
+ logger.error(`[script job] key() threw, item skipped: ${err.message}`);
242
+ continue;
243
+ }
244
+ if (inRun.has(k))
245
+ continue;
246
+ inRun.add(k);
247
+ if (await seen.has(k))
248
+ continue;
249
+ fresh.push(item);
250
+ freshKeys.push(k);
251
+ }
252
+ const result = { fetched: all.length, fresh: fresh.length, posted: false, failedSources };
253
+ if (fresh.length === 0)
254
+ return result;
255
+ const text = compose ? await compose(fresh) : defaultCompose(fresh, key);
256
+ if (typeof text !== "string" || text.trim() === "")
257
+ return result;
258
+ await post(text, fresh);
259
+ result.posted = true;
260
+ try {
261
+ await seen.mark(freshKeys, options.seenTtlMs === undefined ? undefined : { ttlMs: options.seenTtlMs });
262
+ }
263
+ catch (err) {
264
+ // The post already went out; throwing now would tell the scheduler it
265
+ // did not. The cost is a possible repeat next run, so say so loudly.
266
+ logger.error(`[script job] posted, but marking ${freshKeys.length} item(s) seen failed (they may repeat): ${err.message}`);
267
+ }
268
+ return result;
269
+ }
270
+ //# sourceMappingURL=scriptJob.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scriptJob.js","sourceRoot":"","sources":["../src/scriptJob.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,131 @@
1
+ import type { SaltId } from "./ids.js";
2
+ import type { CardSection } from "./identity.js";
3
+ export interface SessionTurn {
4
+ role: "user" | "assistant";
5
+ content: string;
6
+ /** epoch ms */
7
+ at: number;
8
+ /** Who said it, for a "user" turn -- a username when known, else an id. Omitted for "assistant" (that's always this identity). */
9
+ from?: string;
10
+ /** Why this delivery reached the identity, on an open room only -- mirrors
11
+ * webhook.ts's MessageContext.deliveredBecause for a "user" turn rebuilt
12
+ * from the chat's own history (see rebuildTranscriptTail). Never set on
13
+ * an "assistant" turn (this identity's own message); omitted for an
14
+ * ordinary encrypted chat, where the question doesn't apply. */
15
+ deliveredBecause?: "mention" | "reply" | "keyword" | "all" | "team_lead";
16
+ }
17
+ export interface SessionNote {
18
+ /** What this session is trying to accomplish, in the agent's own words. */
19
+ goal?: string;
20
+ /** Set while blocked on something from the person/other party; cleared once they answer. */
21
+ waitingOn?: string;
22
+ /** Every delegate_to_agent / consult_agent call made in service of this session -- oldest first. */
23
+ consulted: Array<{
24
+ handle: string;
25
+ laneId: string;
26
+ }>;
27
+ /** The most recent work.ts report id this session posted, if any -- lets a resumed session keep updating the same report instead of starting a new one. */
28
+ lastReportId?: string;
29
+ }
30
+ /**
31
+ * One SALT-IDENTITY-SLICE this session received from a chat member (see
32
+ * identityShare.ts) -- kept so a later turn can use what someone shared
33
+ * without re-asking. `verified` mirrors identityShare's own signature
34
+ * check (never re-verified here). Nothing removes an entry except a
35
+ * matching SALT-IDENTITY-REVOKE naming the same `id` (see
36
+ * forgetReceivedIdentitySlice) or MAX_IDENTITY_SLICES_PER_SENDER eviction.
37
+ */
38
+ export interface ReceivedIdentitySlice {
39
+ id: string;
40
+ sections: CardSection[];
41
+ verified: boolean;
42
+ /** epoch ms this slice was received. */
43
+ receivedAt: number;
44
+ }
45
+ export interface Session {
46
+ /** The room this session ultimately serves -- itself for an ordinary chat, or the shared chat a lane (sidechain/coaching/consult) was opened from. */
47
+ roomId: SaltId;
48
+ /** The actual chat this session's messages arrive/post in -- may be a lane. */
49
+ chatId: SaltId;
50
+ /** This identity's relationship to `chatId`: the active participant ("active"), a lane off some room ("lane" -- Mediator coaching, a translator, a work.ts report lane), a consult lane specifically ("consult"), or present without being the one who speaks ("observer"). Descriptive only -- nothing in this SDK gates behavior on it. */
51
+ role: "active" | "observer" | "consult" | "lane";
52
+ /** Recent turns, oldest first, capped at MAX_TRANSCRIPT_TURNS. */
53
+ transcriptTail: SessionTurn[];
54
+ note: SessionNote;
55
+ /** Verified (and unverified) SALT-IDENTITY-SLICEs received in this chat, keyed by the lowercased sender id -- oldest first, capped at MAX_IDENTITY_SLICES_PER_SENDER per sender. Empty object when nothing has been shared here yet. */
56
+ identity: Record<string, ReceivedIdentitySlice[]>;
57
+ /** epoch ms of the last write. */
58
+ updatedAt: number;
59
+ }
60
+ export interface SessionStore {
61
+ get(identityId: SaltId, chatId: SaltId): Promise<Session | null>;
62
+ put(identityId: SaltId, chatId: SaltId, session: Session): Promise<void>;
63
+ forget(identityId: SaltId, chatId: SaltId): Promise<void>;
64
+ }
65
+ /** Turns kept per session -- old ones drop off the front, oldest first. */
66
+ export declare const MAX_TRANSCRIPT_TURNS = 40;
67
+ /** A note's serialized size budget, in characters. */
68
+ export declare const MAX_NOTE_CHARS = 1200;
69
+ /** Received identity slices kept per sender -- old ones drop off the front, oldest first, same convention as MAX_TRANSCRIPT_TURNS. */
70
+ export declare const MAX_IDENTITY_SLICES_PER_SENDER = 20;
71
+ /** A fresh, empty session for `chatId` (in room `roomId`). */
72
+ export declare function emptySession(chatId: SaltId, roomId: SaltId, role?: Session["role"]): Session;
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
+ export declare function recordReceivedIdentitySlice(session: Session, fromId: SaltId, slice: ReceivedIdentitySlice): void;
79
+ /**
80
+ * Drops every slice from `fromId` whose `id` matches `sliceId`, in place --
81
+ * called on an incoming SALT-IDENTITY-REVOKE naming that id. A no-op when
82
+ * nothing recorded under that (sender, id) pair exists (the revoke arrived
83
+ * before this process ever saw the slice, or from a different process
84
+ * hosting the same identity).
85
+ */
86
+ export declare function forgetReceivedIdentitySlice(session: Session, fromId: SaltId, sliceId: string): void;
87
+ /** Appends one turn in place, dropping the oldest turns past MAX_TRANSCRIPT_TURNS. */
88
+ export declare function appendTurn(session: Session, turn: SessionTurn): void;
89
+ /**
90
+ * Returns a copy of `note` whose serialized JSON fits MAX_NOTE_CHARS,
91
+ * dropping the OLDEST `consulted` entries first -- `goal`/`waitingOn`/
92
+ * `lastReportId` are the actual point of the note and are never what gets
93
+ * cut to make room. If it's still over budget with nothing left to drop,
94
+ * returns it as-is: that's a caller writing an oversized goal/waitingOn
95
+ * string, which no amount of trimming `consulted` will fix.
96
+ */
97
+ export declare function boundNote(note: SessionNote): SessionNote;
98
+ /**
99
+ * In-memory session store -- the default. Sessions are lost on process
100
+ * restart (a cold-start rebuild from chat history fills the gap -- see
101
+ * webhook.ts's loadOrRebuildSession).
102
+ */
103
+ export declare function MemorySessionStore(): SessionStore;
104
+ /**
105
+ * One plain JSON file per (identity, chat) under `dir`, written atomically
106
+ * (temp file + rename, so a crash mid-write never leaves a half-written
107
+ * session on disk -- the same convention identities.ts uses for its own
108
+ * store).
109
+ *
110
+ * TRUST BOUNDARY, stated plainly: sessions are stored in PLAINTEXT on this
111
+ * filesystem, right beside the identities.json this same process already
112
+ * keeps there (which holds every hosted identity's PRIVATE PGP KEY). This
113
+ * store adds no encryption of its own -- it is exactly as trusted as the
114
+ * disk it's on, which is the same trust level the rest of this process
115
+ * already assumes. Fine for a single-tenant agent host's own local/attached
116
+ * disk; do not point `dir` at shared or otherwise untrusted storage, and
117
+ * do not treat a session file as safe to hand to anyone Salt itself
118
+ * couldn't already show the conversation to.
119
+ */
120
+ export declare function FileSessionStore(dir: string): SessionStore;
121
+ export declare const SESSION_NOTE_MARKER = "[[SALT-SESSION-NOTE]]";
122
+ /**
123
+ * A session note's wire line, or undefined when there's nothing worth
124
+ * carrying forward (an empty note would just be noise on every hand-off).
125
+ */
126
+ export declare function formatSessionNoteLine(note: SessionNote | undefined | null): string | undefined;
127
+ /** The LAST [[SALT-SESSION-NOTE]] line's parsed payload in `text`, or undefined if there isn't one or it doesn't parse. */
128
+ export declare function extractSessionNote(text: string): SessionNote | undefined;
129
+ /** Removes every [[SALT-SESSION-NOTE]] line from `text` -- wire protocol, not conversational content. */
130
+ export declare function stripSessionNoteLines(text: string): string;
131
+ //# sourceMappingURL=sessions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../src/sessions.ts"],"names":[],"mappings":"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"}