salt-agent-sdk 0.1.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +941 -30
- package/dist/actions.d.ts +21 -5
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +673 -54
- package/dist/actions.js.map +1 -1
- package/dist/ask.d.ts +83 -0
- package/dist/ask.d.ts.map +1 -0
- package/dist/ask.js +215 -0
- package/dist/ask.js.map +1 -0
- package/dist/chatPrivacy.d.ts +34 -0
- package/dist/chatPrivacy.d.ts.map +1 -0
- package/dist/chatPrivacy.js +60 -0
- package/dist/chatPrivacy.js.map +1 -0
- package/dist/client.d.ts +2783 -35
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +1200 -143
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +16 -6
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +16 -5
- package/dist/config.js.map +1 -1
- package/dist/crypto.d.ts +62 -0
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +121 -0
- package/dist/crypto.js.map +1 -1
- package/dist/delegations.d.ts +25 -8
- package/dist/delegations.d.ts.map +1 -1
- package/dist/delegations.js +67 -11
- package/dist/delegations.js.map +1 -1
- package/dist/device/client.d.ts +161 -0
- package/dist/device/client.d.ts.map +1 -0
- package/dist/device/client.js +601 -0
- package/dist/device/client.js.map +1 -0
- package/dist/device/host.d.ts +222 -0
- package/dist/device/host.d.ts.map +1 -0
- package/dist/device/host.js +891 -0
- package/dist/device/host.js.map +1 -0
- package/dist/device/http.d.ts +12 -0
- package/dist/device/http.d.ts.map +1 -0
- package/dist/device/http.js +124 -0
- package/dist/device/http.js.map +1 -0
- package/dist/device/index.d.ts +5 -0
- package/dist/device/index.d.ts.map +1 -0
- package/dist/device/index.js +27 -0
- package/dist/device/index.js.map +1 -0
- package/dist/device/protocol.d.ts +312 -0
- package/dist/device/protocol.d.ts.map +1 -0
- package/dist/device/protocol.js +435 -0
- package/dist/device/protocol.js.map +1 -0
- package/dist/identities.d.ts +9 -2
- package/dist/identities.d.ts.map +1 -1
- package/dist/identities.js +33 -3
- package/dist/identities.js.map +1 -1
- package/dist/identity.d.ts +155 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +196 -0
- package/dist/identity.js.map +1 -0
- package/dist/identityShare.d.ts +129 -0
- package/dist/identityShare.d.ts.map +1 -0
- package/dist/identityShare.js +308 -0
- package/dist/identityShare.js.map +1 -0
- package/dist/ids.d.ts +11 -0
- package/dist/ids.d.ts.map +1 -0
- package/dist/ids.js +17 -0
- package/dist/ids.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -1
- package/dist/reconcile.d.ts +25 -0
- package/dist/reconcile.d.ts.map +1 -0
- package/dist/reconcile.js +60 -0
- package/dist/reconcile.js.map +1 -0
- package/dist/recovery.d.ts +51 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +59 -0
- package/dist/recovery.js.map +1 -0
- package/dist/register.d.ts +32 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +65 -0
- package/dist/register.js.map +1 -0
- package/dist/scriptJob.d.ts +92 -0
- package/dist/scriptJob.d.ts.map +1 -0
- package/dist/scriptJob.js +270 -0
- package/dist/scriptJob.js.map +1 -0
- package/dist/sessions.d.ts +131 -0
- package/dist/sessions.d.ts.map +1 -0
- package/dist/sessions.js +240 -0
- package/dist/sessions.js.map +1 -0
- package/dist/socket.d.ts +115 -0
- package/dist/socket.d.ts.map +1 -0
- package/dist/socket.js +1038 -0
- package/dist/socket.js.map +1 -0
- package/dist/webhook.d.ts +613 -24
- package/dist/webhook.d.ts.map +1 -1
- package/dist/webhook.js +1384 -83
- package/dist/webhook.js.map +1 -1
- package/dist/work.d.ts +57 -0
- package/dist/work.d.ts.map +1 -0
- package/dist/work.js +213 -0
- package/dist/work.js.map +1 -0
- package/package.json +26 -4
|
@@ -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"}
|
package/dist/register.js
ADDED
|
@@ -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";
|
|
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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"}
|