@suveren/gateway 0.28.1 → 0.29.1
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/content/integrations/crm.json +33 -1
- package/content/integrations/erp.json +16 -0
- package/content/integrations/gmail.json +27 -2
- package/content/integrations/mail.json +28 -3
- package/content/integrations/mollie.json +10 -0
- package/dist/control-plane/index.mjs +387 -201
- package/dist/mcp-server/http.mjs +1358 -385
- package/dist/ui/assets/{index-RX5juZkm.css → index-DUIlMVlP.css} +1 -1
- package/dist/ui/assets/index-wtX8LuiU.js +123 -0
- package/dist/ui/index.html +2 -2
- package/node_modules/sanitize-html/index.js +47 -10
- package/node_modules/sanitize-html/package.json +2 -3
- package/package.json +1 -1
- package/dist/ui/assets/index-I9WCdLMY.js +0 -120
- package/node_modules/deepmerge/.editorconfig +0 -7
- package/node_modules/deepmerge/.eslintcache +0 -1
- package/node_modules/deepmerge/changelog.md +0 -167
- package/node_modules/deepmerge/dist/cjs.js +0 -133
- package/node_modules/deepmerge/dist/umd.js +0 -139
- package/node_modules/deepmerge/index.d.ts +0 -20
- package/node_modules/deepmerge/index.js +0 -106
- package/node_modules/deepmerge/license.txt +0 -21
- package/node_modules/deepmerge/package.json +0 -42
- package/node_modules/deepmerge/readme.md +0 -264
- package/node_modules/deepmerge/rollup.config.js +0 -22
package/dist/mcp-server/http.mjs
CHANGED
|
@@ -2190,21 +2190,168 @@ function nowSec() {
|
|
|
2190
2190
|
return Math.floor(Date.now() / 1e3);
|
|
2191
2191
|
}
|
|
2192
2192
|
|
|
2193
|
-
// src/lib/
|
|
2194
|
-
import { mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync7, existsSync as existsSync9,
|
|
2193
|
+
// src/lib/execution-result-store.ts
|
|
2194
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync7, existsSync as existsSync9, unlinkSync as unlinkSync5 } from "fs";
|
|
2195
2195
|
import { homedir as homedir6 } from "os";
|
|
2196
2196
|
import { dirname as dirname7, join as join9 } from "path";
|
|
2197
|
+
import { createCipheriv as createCipheriv5, createDecipheriv as createDecipheriv5, randomBytes as randomBytes5 } from "crypto";
|
|
2197
2198
|
var DEFAULT_DIR6 = process.env.SUVEREN_DATA_DIR ?? join9(homedir6(), ".suveren");
|
|
2198
|
-
var FILE_NAME2 = "proposal-submissions.json";
|
|
2199
2199
|
var MAX_AGE_SECONDS3 = 90 * 24 * 60 * 60;
|
|
2200
|
+
var MAX_BYTES = 16 * 1024;
|
|
2201
|
+
function capResult(text, structuredContent) {
|
|
2202
|
+
const structJson = structuredContent ? JSON.stringify(structuredContent) : void 0;
|
|
2203
|
+
const textBytes = Buffer.byteLength(text, "utf8");
|
|
2204
|
+
const structBytes = structJson ? Buffer.byteLength(structJson, "utf8") : 0;
|
|
2205
|
+
if (textBytes + structBytes <= MAX_BYTES) {
|
|
2206
|
+
return { text, ...structuredContent ? { structuredContent } : {}, truncated: false };
|
|
2207
|
+
}
|
|
2208
|
+
if (textBytes <= MAX_BYTES) {
|
|
2209
|
+
return { text, truncated: true };
|
|
2210
|
+
}
|
|
2211
|
+
const cappedText = Buffer.from(text, "utf8").subarray(0, MAX_BYTES).toString("utf8");
|
|
2212
|
+
return { text: cappedText, truncated: true };
|
|
2213
|
+
}
|
|
2214
|
+
var ExecutionResultStore = class {
|
|
2215
|
+
entries = [];
|
|
2216
|
+
baseDir;
|
|
2217
|
+
vaultKey = null;
|
|
2218
|
+
constructor(filePath) {
|
|
2219
|
+
this.baseDir = filePath && filePath.endsWith(".json") ? dirname7(filePath) : filePath ?? DEFAULT_DIR6;
|
|
2220
|
+
this.loadPlaintext();
|
|
2221
|
+
}
|
|
2222
|
+
/** Mirrors OutcomeOutbox.setVaultKey — migrates plaintext -> encrypted once, in place. */
|
|
2223
|
+
setVaultKey(key) {
|
|
2224
|
+
this.vaultKey = key;
|
|
2225
|
+
if (existsSync9(this.encryptedFilePath)) {
|
|
2226
|
+
this.loadEncrypted();
|
|
2227
|
+
} else if (this.entries.length > 0) {
|
|
2228
|
+
this.persistEncrypted();
|
|
2229
|
+
if (existsSync9(this.plaintextFilePath)) {
|
|
2230
|
+
try {
|
|
2231
|
+
unlinkSync5(this.plaintextFilePath);
|
|
2232
|
+
} catch {
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
/**
|
|
2238
|
+
* True when encrypted evidence exists on disk that this process cannot
|
|
2239
|
+
* read yet (no vault key). Callers should not report "nothing retained" in
|
|
2240
|
+
* this state — that would claim a hollow absence over unreadable content.
|
|
2241
|
+
*/
|
|
2242
|
+
isLocked() {
|
|
2243
|
+
return this.vaultKey === null && existsSync9(this.encryptedFilePath);
|
|
2244
|
+
}
|
|
2245
|
+
/** Record the outcome of one execution. Replaces any prior entry for the same proposal. */
|
|
2246
|
+
record(input, now = nowSec2()) {
|
|
2247
|
+
const capped = capResult(input.text, input.structuredContent);
|
|
2248
|
+
this.entries = this.entries.filter((e) => e.proposalId !== input.proposalId);
|
|
2249
|
+
this.entries.push({
|
|
2250
|
+
proposalId: input.proposalId,
|
|
2251
|
+
ticketId: input.ticketId,
|
|
2252
|
+
ok: input.ok,
|
|
2253
|
+
text: capped.text,
|
|
2254
|
+
...capped.structuredContent ? { structuredContent: capped.structuredContent } : {},
|
|
2255
|
+
...capped.truncated ? { truncated: true } : {},
|
|
2256
|
+
recordedAt: now
|
|
2257
|
+
});
|
|
2258
|
+
this.prune(now);
|
|
2259
|
+
this.persist();
|
|
2260
|
+
}
|
|
2261
|
+
/** The retained outcome for a proposal, or undefined if never recorded (or pruned). */
|
|
2262
|
+
get(proposalId) {
|
|
2263
|
+
return this.entries.find((e) => e.proposalId === proposalId);
|
|
2264
|
+
}
|
|
2265
|
+
/** Diagnostic / tests. */
|
|
2266
|
+
get size() {
|
|
2267
|
+
return this.entries.length;
|
|
2268
|
+
}
|
|
2269
|
+
// ─── Retention ────────────────────────────────────────────────────────────
|
|
2270
|
+
prune(now) {
|
|
2271
|
+
const cutoff = now - MAX_AGE_SECONDS3;
|
|
2272
|
+
this.entries = this.entries.filter((e) => e.recordedAt >= cutoff);
|
|
2273
|
+
}
|
|
2274
|
+
// ─── Encryption (mirrors OutcomeOutbox / DenialLog) ────────────────────────
|
|
2275
|
+
encrypt(plaintext) {
|
|
2276
|
+
if (!this.vaultKey) throw new Error("No vault key");
|
|
2277
|
+
const iv = randomBytes5(12);
|
|
2278
|
+
const cipher = createCipheriv5("aes-256-gcm", this.vaultKey, iv);
|
|
2279
|
+
const enc = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
2280
|
+
return { iv: iv.toString("hex"), ciphertext: enc.toString("hex"), tag: cipher.getAuthTag().toString("hex") };
|
|
2281
|
+
}
|
|
2282
|
+
decrypt(blob) {
|
|
2283
|
+
if (!this.vaultKey) throw new Error("No vault key");
|
|
2284
|
+
const decipher = createDecipheriv5("aes-256-gcm", this.vaultKey, Buffer.from(blob.iv, "hex"));
|
|
2285
|
+
decipher.setAuthTag(Buffer.from(blob.tag, "hex"));
|
|
2286
|
+
return Buffer.concat([decipher.update(Buffer.from(blob.ciphertext, "hex")), decipher.final()]).toString("utf8");
|
|
2287
|
+
}
|
|
2288
|
+
get plaintextFilePath() {
|
|
2289
|
+
return join9(this.baseDir, "execution-results.json");
|
|
2290
|
+
}
|
|
2291
|
+
get encryptedFilePath() {
|
|
2292
|
+
return join9(this.baseDir, "execution-results.enc.json");
|
|
2293
|
+
}
|
|
2294
|
+
loadPlaintext() {
|
|
2295
|
+
if (!existsSync9(this.plaintextFilePath)) {
|
|
2296
|
+
mkdirSync7(this.baseDir, { recursive: true });
|
|
2297
|
+
return;
|
|
2298
|
+
}
|
|
2299
|
+
try {
|
|
2300
|
+
const data = JSON.parse(readFileSync10(this.plaintextFilePath, "utf-8"));
|
|
2301
|
+
this.entries = data.entries ?? [];
|
|
2302
|
+
this.prune(nowSec2());
|
|
2303
|
+
} catch {
|
|
2304
|
+
console.error(`[ExecutionResultStore] Could not parse ${this.plaintextFilePath}, starting fresh`);
|
|
2305
|
+
this.entries = [];
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
loadEncrypted() {
|
|
2309
|
+
if (!existsSync9(this.encryptedFilePath)) return;
|
|
2310
|
+
try {
|
|
2311
|
+
const data = JSON.parse(readFileSync10(this.encryptedFilePath, "utf-8"));
|
|
2312
|
+
const storeFile = JSON.parse(this.decrypt(data.blob));
|
|
2313
|
+
this.entries = storeFile.entries ?? [];
|
|
2314
|
+
this.prune(nowSec2());
|
|
2315
|
+
} catch (err) {
|
|
2316
|
+
console.error(`[ExecutionResultStore] Could not decrypt ${this.encryptedFilePath}:`, err);
|
|
2317
|
+
this.entries = [];
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
persist() {
|
|
2321
|
+
if (this.vaultKey) this.persistEncrypted();
|
|
2322
|
+
else this.persistPlaintext();
|
|
2323
|
+
}
|
|
2324
|
+
persistPlaintext() {
|
|
2325
|
+
const data = { version: 1, entries: this.entries };
|
|
2326
|
+
mkdirSync7(this.baseDir, { recursive: true, mode: 448 });
|
|
2327
|
+
writeFileSync7(this.plaintextFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
2328
|
+
}
|
|
2329
|
+
persistEncrypted() {
|
|
2330
|
+
const storeFile = { version: 1, entries: this.entries };
|
|
2331
|
+
const data = { version: 1, blob: this.encrypt(JSON.stringify(storeFile)) };
|
|
2332
|
+
mkdirSync7(this.baseDir, { recursive: true, mode: 448 });
|
|
2333
|
+
writeFileSync7(this.encryptedFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
2334
|
+
}
|
|
2335
|
+
};
|
|
2200
2336
|
function nowSec2() {
|
|
2201
2337
|
return Math.floor(Date.now() / 1e3);
|
|
2202
2338
|
}
|
|
2339
|
+
|
|
2340
|
+
// src/lib/proposal-submission-store.ts
|
|
2341
|
+
import { mkdirSync as mkdirSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync8, existsSync as existsSync10, renameSync as renameSync2 } from "fs";
|
|
2342
|
+
import { homedir as homedir7 } from "os";
|
|
2343
|
+
import { dirname as dirname8, join as join10 } from "path";
|
|
2344
|
+
var DEFAULT_DIR7 = process.env.SUVEREN_DATA_DIR ?? join10(homedir7(), ".suveren");
|
|
2345
|
+
var FILE_NAME2 = "proposal-submissions.json";
|
|
2346
|
+
var MAX_AGE_SECONDS4 = 90 * 24 * 60 * 60;
|
|
2347
|
+
function nowSec3() {
|
|
2348
|
+
return Math.floor(Date.now() / 1e3);
|
|
2349
|
+
}
|
|
2203
2350
|
var ProposalSubmissionStore = class {
|
|
2204
2351
|
filePath;
|
|
2205
2352
|
constructor(filePath) {
|
|
2206
|
-
const baseDir = filePath && filePath.endsWith(".json") ?
|
|
2207
|
-
this.filePath =
|
|
2353
|
+
const baseDir = filePath && filePath.endsWith(".json") ? dirname8(filePath) : filePath ?? DEFAULT_DIR7;
|
|
2354
|
+
this.filePath = join10(baseDir, FILE_NAME2);
|
|
2208
2355
|
}
|
|
2209
2356
|
/** Record what was submitted, right after a successful submitProposal(). */
|
|
2210
2357
|
record(entry) {
|
|
@@ -2217,7 +2364,7 @@ var ProposalSubmissionStore = class {
|
|
|
2217
2364
|
executionContextHash: hashToolArgs(entry.executionContext),
|
|
2218
2365
|
authorizationId: entry.authorizationId,
|
|
2219
2366
|
profileId: entry.profileId,
|
|
2220
|
-
submittedAt:
|
|
2367
|
+
submittedAt: nowSec3(),
|
|
2221
2368
|
...entry.previewHash ? { previewHash: entry.previewHash } : {}
|
|
2222
2369
|
});
|
|
2223
2370
|
this.persist(file);
|
|
@@ -2228,10 +2375,10 @@ var ProposalSubmissionStore = class {
|
|
|
2228
2375
|
return this.load().entries.find((e) => e.proposalId === proposalId);
|
|
2229
2376
|
}
|
|
2230
2377
|
load() {
|
|
2231
|
-
if (!
|
|
2378
|
+
if (!existsSync10(this.filePath)) return { version: 1, entries: [] };
|
|
2232
2379
|
try {
|
|
2233
|
-
const parsed = JSON.parse(
|
|
2234
|
-
const cutoff =
|
|
2380
|
+
const parsed = JSON.parse(readFileSync11(this.filePath, "utf-8"));
|
|
2381
|
+
const cutoff = nowSec3() - MAX_AGE_SECONDS4;
|
|
2235
2382
|
parsed.entries = (parsed.entries ?? []).filter((e) => e.submittedAt >= cutoff);
|
|
2236
2383
|
return parsed;
|
|
2237
2384
|
} catch {
|
|
@@ -2239,26 +2386,26 @@ var ProposalSubmissionStore = class {
|
|
|
2239
2386
|
}
|
|
2240
2387
|
}
|
|
2241
2388
|
persist(file) {
|
|
2242
|
-
|
|
2389
|
+
mkdirSync8(dirname8(this.filePath), { recursive: true, mode: 448 });
|
|
2243
2390
|
const tmpPath = `${this.filePath}.tmp`;
|
|
2244
|
-
|
|
2391
|
+
writeFileSync8(tmpPath, JSON.stringify(file, null, 2), { encoding: "utf-8", mode: 384 });
|
|
2245
2392
|
renameSync2(tmpPath, this.filePath);
|
|
2246
2393
|
}
|
|
2247
2394
|
};
|
|
2248
2395
|
|
|
2249
2396
|
// src/lib/preview-log.ts
|
|
2250
|
-
import { mkdirSync as
|
|
2251
|
-
import { homedir as
|
|
2252
|
-
import { dirname as
|
|
2253
|
-
var
|
|
2397
|
+
import { mkdirSync as mkdirSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync9, existsSync as existsSync11, renameSync as renameSync3 } from "fs";
|
|
2398
|
+
import { homedir as homedir8 } from "os";
|
|
2399
|
+
import { dirname as dirname9, join as join11 } from "path";
|
|
2400
|
+
var DEFAULT_DIR8 = process.env.SUVEREN_DATA_DIR ?? join11(homedir8(), ".suveren");
|
|
2254
2401
|
var FILE_NAME3 = "preview-log.json";
|
|
2255
2402
|
var MAX_RECORDS2 = 500;
|
|
2256
2403
|
var MAX_AGE_MS2 = 30 * 24 * 60 * 60 * 1e3;
|
|
2257
2404
|
var PreviewLog = class {
|
|
2258
2405
|
filePath;
|
|
2259
2406
|
constructor(filePath) {
|
|
2260
|
-
const baseDir = filePath && filePath.endsWith(".json") ?
|
|
2261
|
-
this.filePath =
|
|
2407
|
+
const baseDir = filePath && filePath.endsWith(".json") ? dirname9(filePath) : filePath ?? DEFAULT_DIR8;
|
|
2408
|
+
this.filePath = join11(baseDir, FILE_NAME3);
|
|
2262
2409
|
}
|
|
2263
2410
|
/** Record one preview read. Never throws — recording must not break the read. */
|
|
2264
2411
|
record(entry) {
|
|
@@ -2280,28 +2427,28 @@ var PreviewLog = class {
|
|
|
2280
2427
|
return [...this.load().entries];
|
|
2281
2428
|
}
|
|
2282
2429
|
load() {
|
|
2283
|
-
if (!
|
|
2430
|
+
if (!existsSync11(this.filePath)) return { version: 1, entries: [] };
|
|
2284
2431
|
try {
|
|
2285
|
-
const parsed = JSON.parse(
|
|
2432
|
+
const parsed = JSON.parse(readFileSync12(this.filePath, "utf-8"));
|
|
2286
2433
|
return { version: 1, entries: parsed.entries ?? [] };
|
|
2287
2434
|
} catch {
|
|
2288
2435
|
return { version: 1, entries: [] };
|
|
2289
2436
|
}
|
|
2290
2437
|
}
|
|
2291
2438
|
persist(file) {
|
|
2292
|
-
|
|
2439
|
+
mkdirSync9(dirname9(this.filePath), { recursive: true, mode: 448 });
|
|
2293
2440
|
const tmp = `${this.filePath}.tmp`;
|
|
2294
|
-
|
|
2441
|
+
writeFileSync9(tmp, JSON.stringify(file, null, 2), { encoding: "utf-8", mode: 384 });
|
|
2295
2442
|
renameSync3(tmp, this.filePath);
|
|
2296
2443
|
}
|
|
2297
2444
|
};
|
|
2298
2445
|
|
|
2299
2446
|
// src/lib/outcome-outbox.ts
|
|
2300
|
-
import { mkdirSync as
|
|
2301
|
-
import { homedir as
|
|
2302
|
-
import { dirname as
|
|
2303
|
-
import { createCipheriv as
|
|
2304
|
-
var
|
|
2447
|
+
import { mkdirSync as mkdirSync10, readFileSync as readFileSync13, writeFileSync as writeFileSync10, existsSync as existsSync12, unlinkSync as unlinkSync6 } from "fs";
|
|
2448
|
+
import { homedir as homedir9 } from "os";
|
|
2449
|
+
import { dirname as dirname10, join as join12 } from "path";
|
|
2450
|
+
import { createCipheriv as createCipheriv6, createDecipheriv as createDecipheriv6, randomBytes as randomBytes6 } from "crypto";
|
|
2451
|
+
var DEFAULT_DIR9 = process.env.SUVEREN_DATA_DIR ?? join12(homedir9(), ".suveren");
|
|
2305
2452
|
var MAX_AGE_MS3 = 7 * 24 * 60 * 60 * 1e3;
|
|
2306
2453
|
function outcomeEntryFor(proposal, submittedHere, who, nowMs) {
|
|
2307
2454
|
if (!submittedHere) return null;
|
|
@@ -2316,19 +2463,19 @@ var OutcomeOutbox = class {
|
|
|
2316
2463
|
baseDir;
|
|
2317
2464
|
vaultKey = null;
|
|
2318
2465
|
constructor(filePath) {
|
|
2319
|
-
this.baseDir = filePath && filePath.endsWith(".json") ?
|
|
2466
|
+
this.baseDir = filePath && filePath.endsWith(".json") ? dirname10(filePath) : filePath ?? DEFAULT_DIR9;
|
|
2320
2467
|
this.loadPlaintext();
|
|
2321
2468
|
}
|
|
2322
2469
|
/** Mirrors DenialLog.setVaultKey — migrates plaintext -> encrypted once, in place. */
|
|
2323
2470
|
setVaultKey(key) {
|
|
2324
2471
|
this.vaultKey = key;
|
|
2325
|
-
if (
|
|
2472
|
+
if (existsSync12(this.encryptedFilePath)) {
|
|
2326
2473
|
this.loadEncrypted();
|
|
2327
2474
|
} else if (this.entries.length > 0) {
|
|
2328
2475
|
this.persistEncrypted();
|
|
2329
|
-
if (
|
|
2476
|
+
if (existsSync12(this.plaintextFilePath)) {
|
|
2330
2477
|
try {
|
|
2331
|
-
|
|
2478
|
+
unlinkSync6(this.plaintextFilePath);
|
|
2332
2479
|
} catch {
|
|
2333
2480
|
}
|
|
2334
2481
|
}
|
|
@@ -2377,30 +2524,30 @@ var OutcomeOutbox = class {
|
|
|
2377
2524
|
// ─── Encryption (mirrors DenialLog / ExecutionLog) ─────────────────────────
|
|
2378
2525
|
encrypt(plaintext) {
|
|
2379
2526
|
if (!this.vaultKey) throw new Error("No vault key");
|
|
2380
|
-
const iv =
|
|
2381
|
-
const cipher =
|
|
2527
|
+
const iv = randomBytes6(12);
|
|
2528
|
+
const cipher = createCipheriv6("aes-256-gcm", this.vaultKey, iv);
|
|
2382
2529
|
const enc = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
2383
2530
|
return { iv: iv.toString("hex"), ciphertext: enc.toString("hex"), tag: cipher.getAuthTag().toString("hex") };
|
|
2384
2531
|
}
|
|
2385
2532
|
decrypt(blob) {
|
|
2386
2533
|
if (!this.vaultKey) throw new Error("No vault key");
|
|
2387
|
-
const decipher =
|
|
2534
|
+
const decipher = createDecipheriv6("aes-256-gcm", this.vaultKey, Buffer.from(blob.iv, "hex"));
|
|
2388
2535
|
decipher.setAuthTag(Buffer.from(blob.tag, "hex"));
|
|
2389
2536
|
return Buffer.concat([decipher.update(Buffer.from(blob.ciphertext, "hex")), decipher.final()]).toString("utf8");
|
|
2390
2537
|
}
|
|
2391
2538
|
get plaintextFilePath() {
|
|
2392
|
-
return
|
|
2539
|
+
return join12(this.baseDir, "outcome-outbox.json");
|
|
2393
2540
|
}
|
|
2394
2541
|
get encryptedFilePath() {
|
|
2395
|
-
return
|
|
2542
|
+
return join12(this.baseDir, "outcome-outbox.enc.json");
|
|
2396
2543
|
}
|
|
2397
2544
|
loadPlaintext() {
|
|
2398
|
-
if (!
|
|
2399
|
-
|
|
2545
|
+
if (!existsSync12(this.plaintextFilePath)) {
|
|
2546
|
+
mkdirSync10(this.baseDir, { recursive: true });
|
|
2400
2547
|
return;
|
|
2401
2548
|
}
|
|
2402
2549
|
try {
|
|
2403
|
-
const data = JSON.parse(
|
|
2550
|
+
const data = JSON.parse(readFileSync13(this.plaintextFilePath, "utf-8"));
|
|
2404
2551
|
this.entries = data.entries ?? [];
|
|
2405
2552
|
this.prune(Date.now());
|
|
2406
2553
|
} catch {
|
|
@@ -2409,9 +2556,9 @@ var OutcomeOutbox = class {
|
|
|
2409
2556
|
}
|
|
2410
2557
|
}
|
|
2411
2558
|
loadEncrypted() {
|
|
2412
|
-
if (!
|
|
2559
|
+
if (!existsSync12(this.encryptedFilePath)) return;
|
|
2413
2560
|
try {
|
|
2414
|
-
const data = JSON.parse(
|
|
2561
|
+
const data = JSON.parse(readFileSync13(this.encryptedFilePath, "utf-8"));
|
|
2415
2562
|
const storeFile = JSON.parse(this.decrypt(data.blob));
|
|
2416
2563
|
this.entries = storeFile.entries ?? [];
|
|
2417
2564
|
this.prune(Date.now());
|
|
@@ -2426,14 +2573,14 @@ var OutcomeOutbox = class {
|
|
|
2426
2573
|
}
|
|
2427
2574
|
persistPlaintext() {
|
|
2428
2575
|
const data = { version: 1, entries: this.entries };
|
|
2429
|
-
|
|
2430
|
-
|
|
2576
|
+
mkdirSync10(this.baseDir, { recursive: true, mode: 448 });
|
|
2577
|
+
writeFileSync10(this.plaintextFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
2431
2578
|
}
|
|
2432
2579
|
persistEncrypted() {
|
|
2433
2580
|
const storeFile = { version: 1, entries: this.entries };
|
|
2434
2581
|
const data = { version: 1, blob: this.encrypt(JSON.stringify(storeFile)) };
|
|
2435
|
-
|
|
2436
|
-
|
|
2582
|
+
mkdirSync10(this.baseDir, { recursive: true, mode: 448 });
|
|
2583
|
+
writeFileSync10(this.encryptedFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
2437
2584
|
}
|
|
2438
2585
|
};
|
|
2439
2586
|
function formatOutcomeLine(e) {
|
|
@@ -2545,10 +2692,10 @@ var MCPGatekeeper = class {
|
|
|
2545
2692
|
};
|
|
2546
2693
|
|
|
2547
2694
|
// src/lib/report/report-store.ts
|
|
2548
|
-
import { mkdirSync as
|
|
2549
|
-
import { homedir as
|
|
2550
|
-
import { dirname as
|
|
2551
|
-
import { createCipheriv as
|
|
2695
|
+
import { mkdirSync as mkdirSync11, readFileSync as readFileSync14, writeFileSync as writeFileSync11, existsSync as existsSync13, unlinkSync as unlinkSync7 } from "fs";
|
|
2696
|
+
import { homedir as homedir10 } from "os";
|
|
2697
|
+
import { dirname as dirname11, join as join13 } from "path";
|
|
2698
|
+
import { createCipheriv as createCipheriv7, createDecipheriv as createDecipheriv7, randomBytes as randomBytes7 } from "crypto";
|
|
2552
2699
|
|
|
2553
2700
|
// src/lib/report/record-resolvers.ts
|
|
2554
2701
|
function findById(rows, id) {
|
|
@@ -2856,29 +3003,29 @@ async function buildCoverage(caseElements, getExport, archive, referenced, windo
|
|
|
2856
3003
|
}
|
|
2857
3004
|
|
|
2858
3005
|
// src/lib/report/report-store.ts
|
|
2859
|
-
var
|
|
3006
|
+
var DEFAULT_DIR10 = process.env.SUVEREN_DATA_DIR ?? join13(homedir10(), ".suveren");
|
|
2860
3007
|
var ReportStore = class {
|
|
2861
3008
|
report = null;
|
|
2862
3009
|
baseDir;
|
|
2863
3010
|
vaultKey = null;
|
|
2864
3011
|
constructor(filePath) {
|
|
2865
3012
|
if (filePath && filePath.endsWith(".json")) {
|
|
2866
|
-
this.baseDir =
|
|
3013
|
+
this.baseDir = dirname11(filePath);
|
|
2867
3014
|
} else {
|
|
2868
|
-
this.baseDir = filePath ??
|
|
3015
|
+
this.baseDir = filePath ?? DEFAULT_DIR10;
|
|
2869
3016
|
}
|
|
2870
3017
|
this.loadPlaintext();
|
|
2871
3018
|
}
|
|
2872
3019
|
/** Set the vault key for encryption. Triggers migration from plaintext if needed. */
|
|
2873
3020
|
setVaultKey(key) {
|
|
2874
3021
|
this.vaultKey = key;
|
|
2875
|
-
if (
|
|
3022
|
+
if (existsSync13(this.encryptedFilePath)) {
|
|
2876
3023
|
this.loadEncrypted();
|
|
2877
3024
|
} else if (this.report !== null) {
|
|
2878
3025
|
this.persistEncrypted();
|
|
2879
|
-
if (
|
|
3026
|
+
if (existsSync13(this.plaintextFilePath)) {
|
|
2880
3027
|
try {
|
|
2881
|
-
|
|
3028
|
+
unlinkSync7(this.plaintextFilePath);
|
|
2882
3029
|
} catch {
|
|
2883
3030
|
}
|
|
2884
3031
|
}
|
|
@@ -2891,7 +3038,7 @@ var ReportStore = class {
|
|
|
2891
3038
|
* "the report exists but is locked".
|
|
2892
3039
|
*/
|
|
2893
3040
|
isLocked() {
|
|
2894
|
-
return this.vaultKey === null &&
|
|
3041
|
+
return this.vaultKey === null && existsSync13(this.encryptedFilePath);
|
|
2895
3042
|
}
|
|
2896
3043
|
/** The stored, already-verified report — or null if none has been saved
|
|
2897
3044
|
* (and the store is not locked; check `isLocked()` first). */
|
|
@@ -2925,49 +3072,49 @@ var ReportStore = class {
|
|
|
2925
3072
|
// ─── Encryption helpers ─────────────────────────────────────────────────
|
|
2926
3073
|
encrypt(plaintext) {
|
|
2927
3074
|
if (!this.vaultKey) throw new Error("No vault key");
|
|
2928
|
-
const iv =
|
|
2929
|
-
const cipher =
|
|
3075
|
+
const iv = randomBytes7(12);
|
|
3076
|
+
const cipher = createCipheriv7("aes-256-gcm", this.vaultKey, iv);
|
|
2930
3077
|
const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
2931
3078
|
const tag = cipher.getAuthTag();
|
|
2932
3079
|
return { iv: iv.toString("hex"), ciphertext: encrypted.toString("hex"), tag: tag.toString("hex") };
|
|
2933
3080
|
}
|
|
2934
3081
|
decrypt(blob) {
|
|
2935
3082
|
if (!this.vaultKey) throw new Error("No vault key");
|
|
2936
|
-
const decipher =
|
|
3083
|
+
const decipher = createDecipheriv7("aes-256-gcm", this.vaultKey, Buffer.from(blob.iv, "hex"));
|
|
2937
3084
|
decipher.setAuthTag(Buffer.from(blob.tag, "hex"));
|
|
2938
3085
|
const decrypted = Buffer.concat([decipher.update(Buffer.from(blob.ciphertext, "hex")), decipher.final()]);
|
|
2939
3086
|
return decrypted.toString("utf8");
|
|
2940
3087
|
}
|
|
2941
3088
|
// ─── File paths ─────────────────────────────────────────────────────────
|
|
2942
3089
|
get plaintextFilePath() {
|
|
2943
|
-
return
|
|
3090
|
+
return join13(this.baseDir, "report.json");
|
|
2944
3091
|
}
|
|
2945
3092
|
get encryptedFilePath() {
|
|
2946
|
-
return
|
|
3093
|
+
return join13(this.baseDir, "report.enc.json");
|
|
2947
3094
|
}
|
|
2948
3095
|
// ─── Load / Persist ─────────────────────────────────────────────────────
|
|
2949
3096
|
loadPlaintext() {
|
|
2950
|
-
if (!
|
|
2951
|
-
|
|
3097
|
+
if (!existsSync13(this.plaintextFilePath)) {
|
|
3098
|
+
mkdirSync11(this.baseDir, { recursive: true });
|
|
2952
3099
|
return;
|
|
2953
3100
|
}
|
|
2954
3101
|
try {
|
|
2955
|
-
const raw =
|
|
3102
|
+
const raw = readFileSync14(this.plaintextFilePath, "utf-8");
|
|
2956
3103
|
const parsed = JSON.parse(raw);
|
|
2957
3104
|
this.report = parsed.report ?? null;
|
|
2958
3105
|
} catch {
|
|
2959
3106
|
console.error(`[ReportStore] Could not parse ${this.plaintextFilePath} \u2014 preserving as .corrupt`);
|
|
2960
3107
|
try {
|
|
2961
|
-
|
|
3108
|
+
writeFileSync11(`${this.plaintextFilePath}.corrupt-${Date.now()}`, readFileSync14(this.plaintextFilePath));
|
|
2962
3109
|
} catch {
|
|
2963
3110
|
}
|
|
2964
3111
|
this.report = null;
|
|
2965
3112
|
}
|
|
2966
3113
|
}
|
|
2967
3114
|
loadEncrypted() {
|
|
2968
|
-
if (!
|
|
3115
|
+
if (!existsSync13(this.encryptedFilePath)) return;
|
|
2969
3116
|
try {
|
|
2970
|
-
const raw =
|
|
3117
|
+
const raw = readFileSync14(this.encryptedFilePath, "utf-8");
|
|
2971
3118
|
const data = JSON.parse(raw);
|
|
2972
3119
|
const decrypted = this.decrypt(data.blob);
|
|
2973
3120
|
const parsed = JSON.parse(decrypted);
|
|
@@ -2975,7 +3122,7 @@ var ReportStore = class {
|
|
|
2975
3122
|
} catch (err) {
|
|
2976
3123
|
console.error(`[ReportStore] Could not decrypt ${this.encryptedFilePath} \u2014 preserving as .corrupt:`, err);
|
|
2977
3124
|
try {
|
|
2978
|
-
|
|
3125
|
+
writeFileSync11(`${this.encryptedFilePath}.corrupt-${Date.now()}`, readFileSync14(this.encryptedFilePath));
|
|
2979
3126
|
} catch {
|
|
2980
3127
|
}
|
|
2981
3128
|
this.report = null;
|
|
@@ -2990,16 +3137,16 @@ var ReportStore = class {
|
|
|
2990
3137
|
}
|
|
2991
3138
|
persistPlaintext() {
|
|
2992
3139
|
const data = { version: 1, report: this.report };
|
|
2993
|
-
|
|
2994
|
-
|
|
3140
|
+
mkdirSync11(this.baseDir, { recursive: true, mode: 448 });
|
|
3141
|
+
writeFileSync11(this.plaintextFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
2995
3142
|
}
|
|
2996
3143
|
persistEncrypted() {
|
|
2997
3144
|
const data = {
|
|
2998
3145
|
version: 1,
|
|
2999
3146
|
blob: this.encrypt(JSON.stringify({ version: 1, report: this.report }))
|
|
3000
3147
|
};
|
|
3001
|
-
|
|
3002
|
-
|
|
3148
|
+
mkdirSync11(this.baseDir, { recursive: true, mode: 448 });
|
|
3149
|
+
writeFileSync11(this.encryptedFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
3003
3150
|
}
|
|
3004
3151
|
};
|
|
3005
3152
|
|
|
@@ -3017,6 +3164,8 @@ var SharedState = class {
|
|
|
3017
3164
|
reportStore;
|
|
3018
3165
|
/** Which tickets this Gatekeeper has executed — the "one execution per ticket" half of exactly-once. */
|
|
3019
3166
|
executionJournal;
|
|
3167
|
+
/** The content half of what executionJournal records — see execution-result-store.ts. */
|
|
3168
|
+
executionResults;
|
|
3020
3169
|
/** What THIS gateway submitted for each review-mode proposal it created —
|
|
3021
3170
|
* see ticket-verify.ts / commitments.ts. */
|
|
3022
3171
|
proposalSubmissions;
|
|
@@ -3057,6 +3206,7 @@ var SharedState = class {
|
|
|
3057
3206
|
this.receiptArchive = new ReceiptArchive(gateStorePath);
|
|
3058
3207
|
this.reportStore = new ReportStore(gateStorePath);
|
|
3059
3208
|
this.executionJournal = new ExecutionJournal(gateStorePath);
|
|
3209
|
+
this.executionResults = new ExecutionResultStore(gateStorePath);
|
|
3060
3210
|
this.proposalSubmissions = new ProposalSubmissionStore(gateStorePath);
|
|
3061
3211
|
this.previewLog = new PreviewLog(gateStorePath);
|
|
3062
3212
|
this.outcomeOutbox = new OutcomeOutbox(gateStorePath);
|
|
@@ -3180,7 +3330,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
3180
3330
|
import { z as z2 } from "zod";
|
|
3181
3331
|
|
|
3182
3332
|
// src/lib/mandate-brief.ts
|
|
3183
|
-
import { getProfile as
|
|
3333
|
+
import { getProfile as getProfile5 } from "@hap/core";
|
|
3184
3334
|
|
|
3185
3335
|
// src/lib/consumption.ts
|
|
3186
3336
|
import { boundActionTypes } from "@hap/core";
|
|
@@ -3248,29 +3398,29 @@ function formatConsumptionFull(entries) {
|
|
|
3248
3398
|
}
|
|
3249
3399
|
|
|
3250
3400
|
// src/lib/context-loader.ts
|
|
3251
|
-
import { readFileSync as
|
|
3252
|
-
import { homedir as
|
|
3253
|
-
import { join as
|
|
3254
|
-
var
|
|
3401
|
+
import { readFileSync as readFileSync15, existsSync as existsSync14, mkdirSync as mkdirSync12, writeFileSync as writeFileSync12, renameSync as renameSync4 } from "fs";
|
|
3402
|
+
import { homedir as homedir11 } from "os";
|
|
3403
|
+
import { join as join14, dirname as dirname12 } from "path";
|
|
3404
|
+
var DEFAULT_DIR11 = process.env.SUVEREN_DATA_DIR ?? join14(homedir11(), ".suveren");
|
|
3255
3405
|
var CONTEXT_MAX_BYTES = 16 * 1024;
|
|
3256
3406
|
function contextFilePath(dataDir2) {
|
|
3257
|
-
return
|
|
3407
|
+
return join14(dataDir2 ?? DEFAULT_DIR11, "context.md");
|
|
3258
3408
|
}
|
|
3259
3409
|
function writeContextFile(content, dataDir2) {
|
|
3260
3410
|
const bytes = Buffer.byteLength(content, "utf-8");
|
|
3261
3411
|
if (bytes > CONTEXT_MAX_BYTES) throw new Error(`the brief is ${bytes} bytes; the limit is ${CONTEXT_MAX_BYTES}.`);
|
|
3262
3412
|
const filePath = contextFilePath(dataDir2);
|
|
3263
|
-
|
|
3413
|
+
mkdirSync12(dirname12(filePath), { recursive: true });
|
|
3264
3414
|
const tmpPath = `${filePath}.tmp`;
|
|
3265
|
-
|
|
3415
|
+
writeFileSync12(tmpPath, content, "utf-8");
|
|
3266
3416
|
renameSync4(tmpPath, filePath);
|
|
3267
3417
|
}
|
|
3268
3418
|
var BRIEF_MAX_CHARS = 16 * 1024;
|
|
3269
3419
|
function readContextFile(dataDir2) {
|
|
3270
3420
|
const filePath = contextFilePath(dataDir2);
|
|
3271
|
-
if (!
|
|
3421
|
+
if (!existsSync14(filePath)) return null;
|
|
3272
3422
|
try {
|
|
3273
|
-
const content =
|
|
3423
|
+
const content = readFileSync15(filePath, "utf-8").trim();
|
|
3274
3424
|
return content || null;
|
|
3275
3425
|
} catch {
|
|
3276
3426
|
return null;
|
|
@@ -3467,12 +3617,234 @@ function attachTicketId(tool, args, ticketId) {
|
|
|
3467
3617
|
import { computeContentHash as computeContentHash2 } from "@hap/core";
|
|
3468
3618
|
|
|
3469
3619
|
// src/lib/manifest-loader.ts
|
|
3470
|
-
import { readFileSync as
|
|
3471
|
-
import { join as
|
|
3620
|
+
import { readFileSync as readFileSync16, existsSync as existsSync15 } from "fs";
|
|
3621
|
+
import { join as join15, resolve } from "path";
|
|
3622
|
+
import { getProfile as getProfile2 } from "@hap/core";
|
|
3472
3623
|
|
|
3473
3624
|
// src/lib/tool-gating-types.ts
|
|
3474
3625
|
var MAX_PREVIEW_READS = 10;
|
|
3475
3626
|
var MAX_FOREACH_ELEMENTS = 50;
|
|
3627
|
+
var MAX_RECORD_KEY_STEPS = 2;
|
|
3628
|
+
|
|
3629
|
+
// src/lib/scope-specificity.ts
|
|
3630
|
+
function tokenSet(value) {
|
|
3631
|
+
if (value === void 0 || value === null) return /* @__PURE__ */ new Set();
|
|
3632
|
+
return new Set(
|
|
3633
|
+
String(value).split(",").map((s) => s.trim().toLowerCase()).filter(Boolean)
|
|
3634
|
+
);
|
|
3635
|
+
}
|
|
3636
|
+
function subsetOrEqual(a, b) {
|
|
3637
|
+
if (b.size === 0) return true;
|
|
3638
|
+
if (a.size === 0) return false;
|
|
3639
|
+
for (const x of a) if (!b.has(x)) return false;
|
|
3640
|
+
return true;
|
|
3641
|
+
}
|
|
3642
|
+
function atLeastAsSpecific(keys, a, b) {
|
|
3643
|
+
for (const k of keys) {
|
|
3644
|
+
if (!subsetOrEqual(tokenSet(a[k]), tokenSet(b[k]))) return false;
|
|
3645
|
+
}
|
|
3646
|
+
return true;
|
|
3647
|
+
}
|
|
3648
|
+
function strictlyMoreSpecific(keys, a, b) {
|
|
3649
|
+
return atLeastAsSpecific(keys, a, b) && !atLeastAsSpecific(keys, b, a);
|
|
3650
|
+
}
|
|
3651
|
+
function selectAuthorization(contextKeys, passers) {
|
|
3652
|
+
if (passers.length === 1) {
|
|
3653
|
+
return { chosen: passers[0], superseded: [], reason: "sole" };
|
|
3654
|
+
}
|
|
3655
|
+
const dominators = passers.filter(
|
|
3656
|
+
(a) => passers.every((b) => b === a || strictlyMoreSpecific(contextKeys, a.context, b.context))
|
|
3657
|
+
);
|
|
3658
|
+
if (dominators.length === 1) {
|
|
3659
|
+
const chosen2 = dominators[0];
|
|
3660
|
+
return { chosen: chosen2, superseded: passers.filter((p) => p !== chosen2), reason: "most-specific" };
|
|
3661
|
+
}
|
|
3662
|
+
const approvers = passers.filter((p) => p.requiresApproval);
|
|
3663
|
+
const pool = approvers.length > 0 ? approvers : passers;
|
|
3664
|
+
const chosen = pool.slice().sort((a, b) => a.id.localeCompare(b.id))[0];
|
|
3665
|
+
return {
|
|
3666
|
+
chosen,
|
|
3667
|
+
superseded: passers.filter((p) => p !== chosen),
|
|
3668
|
+
reason: approvers.length > 0 ? "fail-safe-approval" : "fail-safe-tiebreak"
|
|
3669
|
+
};
|
|
3670
|
+
}
|
|
3671
|
+
|
|
3672
|
+
// src/lib/read-gate.ts
|
|
3673
|
+
function boundsSatisfyReadGate(bounds, gate) {
|
|
3674
|
+
if (!gate.boundField) return true;
|
|
3675
|
+
const actual = bounds?.[gate.boundField];
|
|
3676
|
+
return actual !== void 0 && String(actual) === gate.requiredValue;
|
|
3677
|
+
}
|
|
3678
|
+
function readToolIsGoverned(g) {
|
|
3679
|
+
if (!g) return false;
|
|
3680
|
+
return Boolean(g.boundField) || g.read != null || g.readGovernance === "none";
|
|
3681
|
+
}
|
|
3682
|
+
function allowedResources(contexts, boundField) {
|
|
3683
|
+
const out = /* @__PURE__ */ new Set();
|
|
3684
|
+
for (const ctx of contexts) {
|
|
3685
|
+
for (const v of tokenSet(ctx?.[boundField])) out.add(v);
|
|
3686
|
+
}
|
|
3687
|
+
return out;
|
|
3688
|
+
}
|
|
3689
|
+
function deniedResources(requested, allowed) {
|
|
3690
|
+
return requested.filter((r) => !allowed.has(r));
|
|
3691
|
+
}
|
|
3692
|
+
function filterItemsByResource(items, idPath, allowed) {
|
|
3693
|
+
return items.filter((it) => {
|
|
3694
|
+
const id = getByDottedPath(it, idPath);
|
|
3695
|
+
return typeof id === "string" && allowed.has(id);
|
|
3696
|
+
});
|
|
3697
|
+
}
|
|
3698
|
+
function hasBlockedValue(item, valuesPath, blocked) {
|
|
3699
|
+
const v = getByDottedPath(item, valuesPath);
|
|
3700
|
+
const arr = Array.isArray(v) ? v : v != null ? [v] : [];
|
|
3701
|
+
return arr.some((x) => typeof x === "string" && blocked.has(x));
|
|
3702
|
+
}
|
|
3703
|
+
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
3704
|
+
function parseMessageTimestamp(value) {
|
|
3705
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
3706
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
3707
|
+
const n = Number(value);
|
|
3708
|
+
if (Number.isFinite(n)) return n;
|
|
3709
|
+
const iso = Date.parse(value);
|
|
3710
|
+
if (Number.isFinite(iso)) return iso;
|
|
3711
|
+
}
|
|
3712
|
+
return null;
|
|
3713
|
+
}
|
|
3714
|
+
function ageInDays(timestampMs, nowMs) {
|
|
3715
|
+
return Math.floor((nowMs - timestampMs) / MS_PER_DAY);
|
|
3716
|
+
}
|
|
3717
|
+
function maxReadAgeDays(boundsList, field) {
|
|
3718
|
+
let max = null;
|
|
3719
|
+
for (const b of boundsList) {
|
|
3720
|
+
const raw = b?.[field];
|
|
3721
|
+
const n = typeof raw === "number" ? raw : raw !== void 0 ? Number(raw) : NaN;
|
|
3722
|
+
if (Number.isFinite(n)) max = max === null ? n : Math.max(max, n);
|
|
3723
|
+
}
|
|
3724
|
+
return max;
|
|
3725
|
+
}
|
|
3726
|
+
function isOlderThanMaxAge(timestampMs, maxAgeDays, nowMs) {
|
|
3727
|
+
if (timestampMs === null) return true;
|
|
3728
|
+
return ageInDays(timestampMs, nowMs) > maxAgeDays;
|
|
3729
|
+
}
|
|
3730
|
+
var TRAILING_OPERATOR = /(?:^|\s)(?:OR|AND|NOT)$|[-+|&:]$/i;
|
|
3731
|
+
function parensBalanced(s) {
|
|
3732
|
+
let depth = 0;
|
|
3733
|
+
let inQuote = false;
|
|
3734
|
+
for (const c of s) {
|
|
3735
|
+
if (c === '"') {
|
|
3736
|
+
inQuote = !inQuote;
|
|
3737
|
+
continue;
|
|
3738
|
+
}
|
|
3739
|
+
if (inQuote) continue;
|
|
3740
|
+
if (c === "(") depth++;
|
|
3741
|
+
else if (c === ")" && --depth < 0) return false;
|
|
3742
|
+
}
|
|
3743
|
+
return !inQuote && depth === 0;
|
|
3744
|
+
}
|
|
3745
|
+
function composeReadQuery(agentQuery, clauses) {
|
|
3746
|
+
const injected = clauses.filter((c) => typeof c === "string" && c.trim() !== "").map((c) => c.trim());
|
|
3747
|
+
const base = (agentQuery ?? "").trim();
|
|
3748
|
+
if (base === "") return { ok: true, query: injected.join(" ") };
|
|
3749
|
+
if (!parensBalanced(base)) {
|
|
3750
|
+
return { ok: false, reason: "the requested search query has unbalanced parentheses" };
|
|
3751
|
+
}
|
|
3752
|
+
if (TRAILING_OPERATOR.test(base)) {
|
|
3753
|
+
return { ok: false, reason: "the requested search query ends in a dangling operator" };
|
|
3754
|
+
}
|
|
3755
|
+
if (injected.length === 0) return { ok: true, query: base };
|
|
3756
|
+
return { ok: true, query: `(${base}) ${injected.join(" ")}` };
|
|
3757
|
+
}
|
|
3758
|
+
function detectAgeConflict(agentQuery, conflictPattern, maxAgeDays) {
|
|
3759
|
+
if (!agentQuery || !conflictPattern || maxAgeDays === null) return null;
|
|
3760
|
+
let re;
|
|
3761
|
+
try {
|
|
3762
|
+
re = new RegExp(conflictPattern, "gi");
|
|
3763
|
+
} catch {
|
|
3764
|
+
return null;
|
|
3765
|
+
}
|
|
3766
|
+
let worst = null;
|
|
3767
|
+
for (const match of agentQuery.matchAll(re)) {
|
|
3768
|
+
const days = Number(match[1]);
|
|
3769
|
+
if (!Number.isFinite(days)) continue;
|
|
3770
|
+
if (days >= maxAgeDays && (worst === null || days > worst)) worst = days;
|
|
3771
|
+
}
|
|
3772
|
+
return worst;
|
|
3773
|
+
}
|
|
3774
|
+
function resolveAgeBoundField(boundsSchema, ageExecField) {
|
|
3775
|
+
const fields = boundsSchema?.fields;
|
|
3776
|
+
if (!fields) return null;
|
|
3777
|
+
for (const [name, def] of Object.entries(fields)) {
|
|
3778
|
+
if (def?.boundType?.kind === "per_transaction" && def.boundType.of === ageExecField) return name;
|
|
3779
|
+
}
|
|
3780
|
+
return null;
|
|
3781
|
+
}
|
|
3782
|
+
function classifyEnumSwitches(boundsSchema) {
|
|
3783
|
+
const readSide = [];
|
|
3784
|
+
const writeSide = [];
|
|
3785
|
+
for (const [name, def] of Object.entries(boundsSchema?.fields ?? {})) {
|
|
3786
|
+
if (def?.boundType?.kind !== "enum") continue;
|
|
3787
|
+
const values = new Set(def.boundType.values ?? []);
|
|
3788
|
+
if (!values.has("none")) continue;
|
|
3789
|
+
if (values.size === 2 && values.has("unlimited")) readSide.push(name);
|
|
3790
|
+
else writeSide.push(name);
|
|
3791
|
+
}
|
|
3792
|
+
return { readSide, writeSide };
|
|
3793
|
+
}
|
|
3794
|
+
function uncoveredReadSwitchReason(overrides, boundsSchema) {
|
|
3795
|
+
const { readSide, writeSide } = classifyEnumSwitches(boundsSchema);
|
|
3796
|
+
const anySwitch = [...readSide, ...writeSide];
|
|
3797
|
+
if (anySwitch.length === 0) return null;
|
|
3798
|
+
const valuesOf = (field) => boundsSchema?.fields?.[field]?.boundType?.values ?? [];
|
|
3799
|
+
for (const [toolName, entry] of Object.entries(overrides)) {
|
|
3800
|
+
if (entry !== null && typeof entry !== "object") continue;
|
|
3801
|
+
const e = entry ?? {};
|
|
3802
|
+
if (entry !== null && e.category !== "read") continue;
|
|
3803
|
+
if (e.readGovernance === "none") {
|
|
3804
|
+
if (!e.readGovernanceReason) {
|
|
3805
|
+
return `override "${toolName}" declares readGovernance:"none" with no readGovernanceReason \u2014 an exemption from this profile's switch(es) [${anySwitch.join(", ")}] needs a written reason`;
|
|
3806
|
+
}
|
|
3807
|
+
continue;
|
|
3808
|
+
}
|
|
3809
|
+
if (!e.boundField || !anySwitch.includes(e.boundField)) {
|
|
3810
|
+
return `override "${toolName}" is category:"read" under a profile that declares enum switch(es) [${anySwitch.join(", ")}] (read-side: [${readSide.join(", ")}], write-side: [${writeSide.join(", ")}]), but does not reference one via boundField (and carries no readGovernance exemption) \u2014 it would silently ignore e.g. "${anySwitch[0]}: none"`;
|
|
3811
|
+
}
|
|
3812
|
+
const positiveValues = valuesOf(e.boundField).filter((v) => v !== "none");
|
|
3813
|
+
if (!e.requiredValue || !positiveValues.includes(e.requiredValue)) {
|
|
3814
|
+
return `override "${toolName}".requiredValue for boundField "${e.boundField}" must be ${positiveValues.map((v) => `"${v}"`).join(" or ")}, not ${JSON.stringify(e.requiredValue)} \u2014 requiring "none" (or an unlisted value) would invert the gate`;
|
|
3815
|
+
}
|
|
3816
|
+
}
|
|
3817
|
+
return null;
|
|
3818
|
+
}
|
|
3819
|
+
function getByDottedPath(obj, path) {
|
|
3820
|
+
return path.split(".").reduce(
|
|
3821
|
+
(o, k) => o == null ? void 0 : o[k],
|
|
3822
|
+
obj
|
|
3823
|
+
);
|
|
3824
|
+
}
|
|
3825
|
+
function firstParsableDate(obj, paths) {
|
|
3826
|
+
for (const path of Array.isArray(paths) ? paths : [paths]) {
|
|
3827
|
+
const parsed = parseMessageTimestamp(getByDottedPath(obj, path));
|
|
3828
|
+
if (parsed !== null) return parsed;
|
|
3829
|
+
}
|
|
3830
|
+
return null;
|
|
3831
|
+
}
|
|
3832
|
+
function renderAgeConstraint(template, maxAgeDays, nowMs) {
|
|
3833
|
+
const floorIso = new Date(nowMs - maxAgeDays * MS_PER_DAY).toISOString();
|
|
3834
|
+
return template.replace(/\{days\}/g, String(maxAgeDays)).replace(/\{date\}/g, floorIso.slice(0, 10));
|
|
3835
|
+
}
|
|
3836
|
+
function ageFloorValue(maxAgeDays, nowMs, format = "iso") {
|
|
3837
|
+
const floorMs = nowMs - maxAgeDays * MS_PER_DAY;
|
|
3838
|
+
if (format === "epoch_ms") return floorMs;
|
|
3839
|
+
if (format === "epoch_s") return String(Math.floor(floorMs / 1e3));
|
|
3840
|
+
return new Date(floorMs).toISOString();
|
|
3841
|
+
}
|
|
3842
|
+
function clampAgeFloor(requested, maxAgeDays, nowMs, format = "iso") {
|
|
3843
|
+
const floorMs = nowMs - maxAgeDays * MS_PER_DAY;
|
|
3844
|
+
const asked = parseMessageTimestamp(requested);
|
|
3845
|
+
if (asked !== null && asked >= floorMs) return requested;
|
|
3846
|
+
return ageFloorValue(maxAgeDays, nowMs, format);
|
|
3847
|
+
}
|
|
3476
3848
|
|
|
3477
3849
|
// src/lib/manifest-loader.ts
|
|
3478
3850
|
var EXACT_SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
@@ -3597,28 +3969,259 @@ function invalidManifestPreviewReason(manifest) {
|
|
|
3597
3969
|
}
|
|
3598
3970
|
return null;
|
|
3599
3971
|
}
|
|
3972
|
+
function isNonEmptyString(v) {
|
|
3973
|
+
return typeof v === "string" && v.length > 0;
|
|
3974
|
+
}
|
|
3975
|
+
var VALID_FIELD_PATH_PARSE = ["address.name", "address.email", "epoch-ms"];
|
|
3976
|
+
function invalidFieldPathReason(label, value) {
|
|
3977
|
+
if (isNonEmptyString(value)) return null;
|
|
3978
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
3979
|
+
return `${label} must be a non-empty string or a field-path object`;
|
|
3980
|
+
}
|
|
3981
|
+
const f = value;
|
|
3982
|
+
if (!isNonEmptyString(f.path)) return `${label}.path must be a non-empty string`;
|
|
3983
|
+
if (f.where !== void 0) {
|
|
3984
|
+
if (f.where === null || typeof f.where !== "object" || Array.isArray(f.where)) {
|
|
3985
|
+
return `${label}.where must be an object`;
|
|
3986
|
+
}
|
|
3987
|
+
for (const [k, v] of Object.entries(f.where)) {
|
|
3988
|
+
if (typeof v !== "string" && typeof v !== "number" && typeof v !== "boolean") {
|
|
3989
|
+
return `${label}.where.${k} must be a string, number, or boolean`;
|
|
3990
|
+
}
|
|
3991
|
+
}
|
|
3992
|
+
}
|
|
3993
|
+
if (f.pick !== void 0 && !isNonEmptyString(f.pick)) {
|
|
3994
|
+
return `${label}.pick must be a non-empty string`;
|
|
3995
|
+
}
|
|
3996
|
+
if (f.parse !== void 0 && !VALID_FIELD_PATH_PARSE.includes(f.parse)) {
|
|
3997
|
+
return `${label}.parse must be one of ${VALID_FIELD_PATH_PARSE.join(", ")}`;
|
|
3998
|
+
}
|
|
3999
|
+
return null;
|
|
4000
|
+
}
|
|
4001
|
+
function invalidContextReadReason(label, entry) {
|
|
4002
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
|
|
4003
|
+
return `${label} must be an object`;
|
|
4004
|
+
}
|
|
4005
|
+
const r = entry;
|
|
4006
|
+
if (!isNonEmptyString(r.tool)) return `${label}.tool must be a non-empty string`;
|
|
4007
|
+
if (r.args === void 0 || r.args === null || typeof r.args !== "object" || Array.isArray(r.args)) {
|
|
4008
|
+
return `${label}.args must be an object`;
|
|
4009
|
+
}
|
|
4010
|
+
for (const [k, v] of Object.entries(r.args)) {
|
|
4011
|
+
if (typeof v !== "string" || v.length === 0) {
|
|
4012
|
+
return `${label}.args.${k} must be a non-empty string`;
|
|
4013
|
+
}
|
|
4014
|
+
}
|
|
4015
|
+
return null;
|
|
4016
|
+
}
|
|
4017
|
+
function invalidContextSourceReason(toolName, entry) {
|
|
4018
|
+
const label = `context.sources.${toolName}`;
|
|
4019
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
|
|
4020
|
+
return `${label} must be an object`;
|
|
4021
|
+
}
|
|
4022
|
+
const s = entry;
|
|
4023
|
+
const readReason = invalidContextReadReason(`${label}.read`, s.read);
|
|
4024
|
+
if (readReason) return readReason;
|
|
4025
|
+
for (const field of ["title", "author", "at", "body"]) {
|
|
4026
|
+
if (s[field] !== void 0) {
|
|
4027
|
+
const reason = invalidFieldPathReason(`${label}.${field}`, s[field]);
|
|
4028
|
+
if (reason) return reason;
|
|
4029
|
+
}
|
|
4030
|
+
}
|
|
4031
|
+
if (s.keys !== void 0) {
|
|
4032
|
+
if (s.keys === null || typeof s.keys !== "object" || Array.isArray(s.keys)) {
|
|
4033
|
+
return `${label}.keys must be an object`;
|
|
4034
|
+
}
|
|
4035
|
+
for (const [k, v] of Object.entries(s.keys)) {
|
|
4036
|
+
if (k !== "email") return `${label}.keys.${k}: only "email" is a recognized identity key type`;
|
|
4037
|
+
const reason = invalidFieldPathReason(`${label}.keys.${k}`, v);
|
|
4038
|
+
if (reason) return reason;
|
|
4039
|
+
}
|
|
4040
|
+
}
|
|
4041
|
+
if (s.chain !== void 0) {
|
|
4042
|
+
if (s.chain === null || typeof s.chain !== "object" || Array.isArray(s.chain)) {
|
|
4043
|
+
return `${label}.chain must be an object`;
|
|
4044
|
+
}
|
|
4045
|
+
const c = s.chain;
|
|
4046
|
+
if (!isNonEmptyString(c.field)) return `${label}.chain.field must be a non-empty string`;
|
|
4047
|
+
if (typeof c.max !== "number" || c.max <= 0) return `${label}.chain.max must be a positive number`;
|
|
4048
|
+
}
|
|
4049
|
+
return null;
|
|
4050
|
+
}
|
|
4051
|
+
function invalidContextHistoryReason(history) {
|
|
4052
|
+
const label = "context.history";
|
|
4053
|
+
if (history === null || typeof history !== "object" || Array.isArray(history)) {
|
|
4054
|
+
return `${label} must be an object`;
|
|
4055
|
+
}
|
|
4056
|
+
const h = history;
|
|
4057
|
+
const isKeyType = (v) => v === "email" || v === "email_domain";
|
|
4058
|
+
if (Array.isArray(h.byKey)) {
|
|
4059
|
+
if (h.byKey.length === 0) return `${label}.byKey must not be an empty array`;
|
|
4060
|
+
for (const [i, v] of h.byKey.entries()) {
|
|
4061
|
+
if (!isKeyType(v)) return `${label}.byKey[${i}] must be "email" or "email_domain"`;
|
|
4062
|
+
}
|
|
4063
|
+
} else if (!isKeyType(h.byKey)) {
|
|
4064
|
+
return `${label}.byKey must be "email" or "email_domain" (or an array of those, in preference order)`;
|
|
4065
|
+
}
|
|
4066
|
+
const readReason = invalidContextReadReason(`${label}.read`, h.read);
|
|
4067
|
+
if (readReason) return readReason;
|
|
4068
|
+
if (typeof h.limit !== "number" || h.limit <= 0) return `${label}.limit must be a positive number`;
|
|
4069
|
+
if (typeof h.maxAgeDays !== "number" || h.maxAgeDays <= 0) return `${label}.maxAgeDays must be a positive number`;
|
|
4070
|
+
if (h.item === null || typeof h.item !== "object" || Array.isArray(h.item)) {
|
|
4071
|
+
return `${label}.item must be an object`;
|
|
4072
|
+
}
|
|
4073
|
+
for (const [k, v] of Object.entries(h.item)) {
|
|
4074
|
+
if (k === "kind") {
|
|
4075
|
+
if (!isNonEmptyString(v)) return `${label}.item.kind must be a non-empty string`;
|
|
4076
|
+
continue;
|
|
4077
|
+
}
|
|
4078
|
+
const reason = invalidFieldPathReason(`${label}.item.${k}`, v);
|
|
4079
|
+
if (reason) return reason;
|
|
4080
|
+
}
|
|
4081
|
+
return null;
|
|
4082
|
+
}
|
|
4083
|
+
function invalidContextRecordKeyReason(name, entry) {
|
|
4084
|
+
const label = `context.recordKeys.${name}`;
|
|
4085
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
|
|
4086
|
+
return `${label} must be an object`;
|
|
4087
|
+
}
|
|
4088
|
+
const rk = entry;
|
|
4089
|
+
const readReason = invalidContextReadReason(`${label}.read`, rk.read);
|
|
4090
|
+
if (readReason) return readReason;
|
|
4091
|
+
if (rk.keys !== void 0) {
|
|
4092
|
+
if (rk.keys === null || typeof rk.keys !== "object" || Array.isArray(rk.keys)) {
|
|
4093
|
+
return `${label}.keys must be an object`;
|
|
4094
|
+
}
|
|
4095
|
+
for (const [k, v] of Object.entries(rk.keys)) {
|
|
4096
|
+
if (k !== "email") return `${label}.keys.${k}: only "email" is a recognized identity key type`;
|
|
4097
|
+
const reason = invalidFieldPathReason(`${label}.keys.${k}`, v);
|
|
4098
|
+
if (reason) return reason;
|
|
4099
|
+
}
|
|
4100
|
+
}
|
|
4101
|
+
if (rk.next !== void 0) {
|
|
4102
|
+
if (rk.next === null || typeof rk.next !== "object" || Array.isArray(rk.next)) {
|
|
4103
|
+
return `${label}.next must be an object`;
|
|
4104
|
+
}
|
|
4105
|
+
const next = rk.next;
|
|
4106
|
+
if (!isNonEmptyString(next.recordKey)) return `${label}.next.recordKey must be a non-empty string`;
|
|
4107
|
+
if (!isNonEmptyString(next.field)) return `${label}.next.field must be a non-empty string`;
|
|
4108
|
+
}
|
|
4109
|
+
if (rk.keys === void 0 && rk.next === void 0) {
|
|
4110
|
+
return `${label} must declare "keys", "next", or both \u2014 an entry with neither learns nothing from the record it reads`;
|
|
4111
|
+
}
|
|
4112
|
+
return null;
|
|
4113
|
+
}
|
|
4114
|
+
function recordKeyChainLength(name, recordKeys, seen) {
|
|
4115
|
+
if (seen.has(name)) return "cycle";
|
|
4116
|
+
const entry = recordKeys[name];
|
|
4117
|
+
if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return "missing";
|
|
4118
|
+
const next = entry.next;
|
|
4119
|
+
if (!next || typeof next !== "object") return 1;
|
|
4120
|
+
const nextName = next.recordKey;
|
|
4121
|
+
if (typeof nextName !== "string" || !(nextName in recordKeys)) return "missing";
|
|
4122
|
+
const rest = recordKeyChainLength(nextName, recordKeys, /* @__PURE__ */ new Set([...seen, name]));
|
|
4123
|
+
if (rest === "cycle" || rest === "missing") return rest;
|
|
4124
|
+
return 1 + rest;
|
|
4125
|
+
}
|
|
4126
|
+
function invalidManifestContextReason(manifest) {
|
|
4127
|
+
const ctx = manifest.context;
|
|
4128
|
+
const recordKeyNames = new Set(Object.keys(ctx?.recordKeys ?? {}));
|
|
4129
|
+
if (ctx) {
|
|
4130
|
+
if (ctx.sources !== void 0) {
|
|
4131
|
+
if (ctx.sources === null || typeof ctx.sources !== "object" || Array.isArray(ctx.sources)) {
|
|
4132
|
+
return "context.sources must be an object";
|
|
4133
|
+
}
|
|
4134
|
+
for (const [kind, entry] of Object.entries(ctx.sources)) {
|
|
4135
|
+
const reason = invalidContextSourceReason(kind, entry);
|
|
4136
|
+
if (reason) return reason;
|
|
4137
|
+
}
|
|
4138
|
+
}
|
|
4139
|
+
if (ctx.history !== void 0) {
|
|
4140
|
+
const reason = invalidContextHistoryReason(ctx.history);
|
|
4141
|
+
if (reason) return reason;
|
|
4142
|
+
}
|
|
4143
|
+
if (ctx.recordKeys !== void 0) {
|
|
4144
|
+
if (ctx.recordKeys === null || typeof ctx.recordKeys !== "object" || Array.isArray(ctx.recordKeys)) {
|
|
4145
|
+
return "context.recordKeys must be an object";
|
|
4146
|
+
}
|
|
4147
|
+
for (const [name, entry] of Object.entries(ctx.recordKeys)) {
|
|
4148
|
+
const reason = invalidContextRecordKeyReason(name, entry);
|
|
4149
|
+
if (reason) return reason;
|
|
4150
|
+
}
|
|
4151
|
+
for (const name of Object.keys(ctx.recordKeys)) {
|
|
4152
|
+
const length = recordKeyChainLength(name, ctx.recordKeys, /* @__PURE__ */ new Set());
|
|
4153
|
+
if (length === "cycle") {
|
|
4154
|
+
return `context.recordKeys.${name}.next forms a cycle`;
|
|
4155
|
+
}
|
|
4156
|
+
if (length === "missing") {
|
|
4157
|
+
return `context.recordKeys.${name}.next.recordKey names an entry this manifest does not declare`;
|
|
4158
|
+
}
|
|
4159
|
+
if (length > MAX_RECORD_KEY_STEPS) {
|
|
4160
|
+
return `context.recordKeys.${name}.next chain is ${length} steps long \u2014 max ${MAX_RECORD_KEY_STEPS}`;
|
|
4161
|
+
}
|
|
4162
|
+
}
|
|
4163
|
+
}
|
|
4164
|
+
}
|
|
4165
|
+
const sourceKinds = new Set(Object.keys(ctx?.sources ?? {}));
|
|
4166
|
+
const overrides = manifest.toolGating?.overrides ?? {};
|
|
4167
|
+
for (const [toolName, entry] of Object.entries(overrides)) {
|
|
4168
|
+
if (entry === null || typeof entry !== "object") continue;
|
|
4169
|
+
const e = entry;
|
|
4170
|
+
if (e.contextKeys !== void 0) {
|
|
4171
|
+
if (e.contextKeys === null || typeof e.contextKeys !== "object" || Array.isArray(e.contextKeys)) {
|
|
4172
|
+
return `override "${toolName}".contextKeys must be an object`;
|
|
4173
|
+
}
|
|
4174
|
+
for (const [name, argName] of Object.entries(e.contextKeys)) {
|
|
4175
|
+
if (!isNonEmptyString(argName)) {
|
|
4176
|
+
return `override "${toolName}".contextKeys.${name} must be a non-empty string naming an action argument`;
|
|
4177
|
+
}
|
|
4178
|
+
if (name !== "email" && !recordKeyNames.has(name)) {
|
|
4179
|
+
return `override "${toolName}".contextKeys.${name}: "${name}" is neither "email" nor a context.recordKeys entry on this manifest`;
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
4182
|
+
}
|
|
4183
|
+
if (e.sourceFromArg !== void 0) {
|
|
4184
|
+
if (e.sourceFromArg === null || typeof e.sourceFromArg !== "object" || Array.isArray(e.sourceFromArg)) {
|
|
4185
|
+
return `override "${toolName}".sourceFromArg must be an object`;
|
|
4186
|
+
}
|
|
4187
|
+
const sfa = e.sourceFromArg;
|
|
4188
|
+
if (!isNonEmptyString(sfa.kind)) return `override "${toolName}".sourceFromArg.kind must be a non-empty string`;
|
|
4189
|
+
if (!isNonEmptyString(sfa.arg)) return `override "${toolName}".sourceFromArg.arg must be a non-empty string`;
|
|
4190
|
+
if (!sourceKinds.has(sfa.kind)) {
|
|
4191
|
+
return `override "${toolName}".sourceFromArg.kind "${String(sfa.kind)}" is not declared in this manifest's context.sources`;
|
|
4192
|
+
}
|
|
4193
|
+
}
|
|
4194
|
+
}
|
|
4195
|
+
return null;
|
|
4196
|
+
}
|
|
4197
|
+
function invalidManifestReadGateReason(manifest) {
|
|
4198
|
+
const profileDef = getProfile2(manifest.profile);
|
|
4199
|
+
if (!profileDef) return null;
|
|
4200
|
+
const overrides = manifest.toolGating?.overrides ?? {};
|
|
4201
|
+
return uncoveredReadSwitchReason(overrides, profileDef.boundsSchema);
|
|
4202
|
+
}
|
|
3600
4203
|
var manifests = /* @__PURE__ */ new Map();
|
|
3601
4204
|
function loadManifests(integrationsDir) {
|
|
3602
4205
|
const dir = resolve(
|
|
3603
|
-
integrationsDir ?? process.env.SUVEREN_MANIFESTS_DIR ??
|
|
4206
|
+
integrationsDir ?? process.env.SUVEREN_MANIFESTS_DIR ?? join15(import.meta.dirname ?? __dirname, "..", "..", "..", "..", "content", "integrations")
|
|
3604
4207
|
);
|
|
3605
|
-
const indexPath =
|
|
3606
|
-
if (!
|
|
4208
|
+
const indexPath = join15(dir, "index.json");
|
|
4209
|
+
if (!existsSync15(indexPath)) {
|
|
3607
4210
|
console.error(`[ManifestLoader] No index.json found at ${indexPath}, skipping manifest loading`);
|
|
3608
4211
|
return 0;
|
|
3609
4212
|
}
|
|
3610
4213
|
let index;
|
|
3611
4214
|
try {
|
|
3612
|
-
index = JSON.parse(
|
|
4215
|
+
index = JSON.parse(readFileSync16(indexPath, "utf-8"));
|
|
3613
4216
|
} catch (err) {
|
|
3614
4217
|
console.error(`[ManifestLoader] Failed to parse ${indexPath}:`, err);
|
|
3615
4218
|
return 0;
|
|
3616
4219
|
}
|
|
3617
4220
|
let loaded = 0;
|
|
3618
4221
|
for (const [id, relativePath] of Object.entries(index.integrations)) {
|
|
3619
|
-
const manifestPath =
|
|
4222
|
+
const manifestPath = join15(dir, relativePath);
|
|
3620
4223
|
try {
|
|
3621
|
-
const manifest = JSON.parse(
|
|
4224
|
+
const manifest = JSON.parse(readFileSync16(manifestPath, "utf-8"));
|
|
3622
4225
|
const pinReason = invalidNpmPinReason(manifest);
|
|
3623
4226
|
if (pinReason) {
|
|
3624
4227
|
console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${pinReason}`);
|
|
@@ -3629,6 +4232,16 @@ function loadManifests(integrationsDir) {
|
|
|
3629
4232
|
console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${previewReason}`);
|
|
3630
4233
|
continue;
|
|
3631
4234
|
}
|
|
4235
|
+
const contextReason = invalidManifestContextReason(manifest);
|
|
4236
|
+
if (contextReason) {
|
|
4237
|
+
console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${contextReason}`);
|
|
4238
|
+
continue;
|
|
4239
|
+
}
|
|
4240
|
+
const readGateReason = invalidManifestReadGateReason(manifest);
|
|
4241
|
+
if (readGateReason) {
|
|
4242
|
+
console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${readGateReason}`);
|
|
4243
|
+
continue;
|
|
4244
|
+
}
|
|
3632
4245
|
manifests.set(id, manifest);
|
|
3633
4246
|
loaded++;
|
|
3634
4247
|
} catch (err) {
|
|
@@ -3897,20 +4510,436 @@ async function buildInternalPreview(integrationManager2, integrationId, toolName
|
|
|
3897
4510
|
if (read2.status !== "ok" || read2.body.structured === void 0) {
|
|
3898
4511
|
return ok();
|
|
3899
4512
|
}
|
|
3900
|
-
const current = read2.body.structured[preview.version.field];
|
|
3901
|
-
if (typeof current !== "string" && typeof current !== "number") {
|
|
3902
|
-
return ok();
|
|
4513
|
+
const current = read2.body.structured[preview.version.field];
|
|
4514
|
+
if (typeof current !== "string" && typeof current !== "number") {
|
|
4515
|
+
return ok();
|
|
4516
|
+
}
|
|
4517
|
+
const stale = String(current) !== String(approved);
|
|
4518
|
+
return ok({
|
|
4519
|
+
version: {
|
|
4520
|
+
field: preview.version.field,
|
|
4521
|
+
approved,
|
|
4522
|
+
current,
|
|
4523
|
+
stale,
|
|
4524
|
+
...stale ? { currentBody: read2.body } : {}
|
|
4525
|
+
}
|
|
4526
|
+
});
|
|
4527
|
+
}
|
|
4528
|
+
|
|
4529
|
+
// src/lib/field-path.ts
|
|
4530
|
+
function matchesWhere(element, where) {
|
|
4531
|
+
if (element === null || typeof element !== "object" || Array.isArray(element)) return false;
|
|
4532
|
+
const row = element;
|
|
4533
|
+
return Object.entries(where).every(([key, expected]) => {
|
|
4534
|
+
const actual = row[key];
|
|
4535
|
+
if (typeof expected === "string" && typeof actual === "string") {
|
|
4536
|
+
return expected.toLowerCase() === actual.toLowerCase();
|
|
4537
|
+
}
|
|
4538
|
+
return actual === expected;
|
|
4539
|
+
});
|
|
4540
|
+
}
|
|
4541
|
+
function parseAddress(value, which) {
|
|
4542
|
+
const m = value.match(/^(.*?)<([^<>]+)>\s*$/);
|
|
4543
|
+
if (m) {
|
|
4544
|
+
const email = m[2].trim();
|
|
4545
|
+
if (which === "address.email") return email || void 0;
|
|
4546
|
+
const name = m[1].trim().replace(/^"(.*)"$/, "$1").trim();
|
|
4547
|
+
return name || void 0;
|
|
4548
|
+
}
|
|
4549
|
+
if (which === "address.email") return value.trim() || void 0;
|
|
4550
|
+
return void 0;
|
|
4551
|
+
}
|
|
4552
|
+
function parseEpochMs(value) {
|
|
4553
|
+
const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
|
|
4554
|
+
if (!Number.isFinite(n)) return void 0;
|
|
4555
|
+
return new Date(n).toISOString();
|
|
4556
|
+
}
|
|
4557
|
+
function applyParse(value, parse2) {
|
|
4558
|
+
if (value === void 0 || value === null) return void 0;
|
|
4559
|
+
if (parse2 === "epoch-ms") return parseEpochMs(value);
|
|
4560
|
+
if (typeof value !== "string") return void 0;
|
|
4561
|
+
return parseAddress(value, parse2);
|
|
4562
|
+
}
|
|
4563
|
+
function resolveFieldPath(record, fieldPath) {
|
|
4564
|
+
if (fieldPath === void 0) return void 0;
|
|
4565
|
+
if (typeof fieldPath === "string") return getByDottedPath(record, fieldPath);
|
|
4566
|
+
let value = getByDottedPath(record, fieldPath.path);
|
|
4567
|
+
if (fieldPath.where) {
|
|
4568
|
+
if (!Array.isArray(value)) return void 0;
|
|
4569
|
+
value = value.find((el) => matchesWhere(el, fieldPath.where));
|
|
4570
|
+
if (value === void 0) return void 0;
|
|
4571
|
+
}
|
|
4572
|
+
if (fieldPath.pick !== void 0) {
|
|
4573
|
+
value = getByDottedPath(value, fieldPath.pick);
|
|
4574
|
+
}
|
|
4575
|
+
if (fieldPath.parse) {
|
|
4576
|
+
value = applyParse(value, fieldPath.parse);
|
|
4577
|
+
}
|
|
4578
|
+
return value;
|
|
4579
|
+
}
|
|
4580
|
+
|
|
4581
|
+
// src/lib/request-context.ts
|
|
4582
|
+
var SOURCE_LINK_ARG = "_source";
|
|
4583
|
+
function withSourceLink(schema) {
|
|
4584
|
+
const props = schema.properties ?? {};
|
|
4585
|
+
if ("_source" in props) return schema;
|
|
4586
|
+
return {
|
|
4587
|
+
...schema,
|
|
4588
|
+
properties: {
|
|
4589
|
+
...props,
|
|
4590
|
+
_source: {
|
|
4591
|
+
type: "object",
|
|
4592
|
+
description: 'Optional. Link the request you are answering \u2014 the ONE record (e.g. an inbound email) this action responds to, so the human approver can see why it was proposed. Only a reference: {"connector": "<integration id>", "kind": "<declared kind>", "id": "<record id>"}. Example: {"connector":"mail","kind":"message","id":"m-17"}. The gateway reads the record itself \u2014 never write a summary or quote here.',
|
|
4593
|
+
properties: {
|
|
4594
|
+
connector: { type: "string" },
|
|
4595
|
+
kind: { type: "string" },
|
|
4596
|
+
id: { type: "string" }
|
|
4597
|
+
}
|
|
4598
|
+
}
|
|
4599
|
+
}
|
|
4600
|
+
};
|
|
4601
|
+
}
|
|
4602
|
+
function stripSourceLink(args) {
|
|
4603
|
+
if (!(SOURCE_LINK_ARG in args)) return args;
|
|
4604
|
+
const { [SOURCE_LINK_ARG]: _omit, ...rest } = args;
|
|
4605
|
+
return rest;
|
|
4606
|
+
}
|
|
4607
|
+
var FREE_MAIL_DOMAINS = /* @__PURE__ */ new Set([
|
|
4608
|
+
"gmail.com",
|
|
4609
|
+
"googlemail.com",
|
|
4610
|
+
"outlook.com",
|
|
4611
|
+
"hotmail.com",
|
|
4612
|
+
"hotmail.co.uk",
|
|
4613
|
+
"hotmail.fr",
|
|
4614
|
+
"hotmail.de",
|
|
4615
|
+
"live.com",
|
|
4616
|
+
"msn.com",
|
|
4617
|
+
"yahoo.com",
|
|
4618
|
+
"yahoo.co.uk",
|
|
4619
|
+
"yahoo.de",
|
|
4620
|
+
"yahoo.fr",
|
|
4621
|
+
"ymail.com",
|
|
4622
|
+
"rocketmail.com",
|
|
4623
|
+
"gmx.de",
|
|
4624
|
+
"gmx.net",
|
|
4625
|
+
"gmx.at",
|
|
4626
|
+
"gmx.ch",
|
|
4627
|
+
"gmx.com",
|
|
4628
|
+
"web.de",
|
|
4629
|
+
"t-online.de",
|
|
4630
|
+
"icloud.com",
|
|
4631
|
+
"me.com",
|
|
4632
|
+
"mac.com",
|
|
4633
|
+
"aol.com",
|
|
4634
|
+
"protonmail.com",
|
|
4635
|
+
"proton.me",
|
|
4636
|
+
"mail.com",
|
|
4637
|
+
"zoho.com",
|
|
4638
|
+
"yandex.com"
|
|
4639
|
+
]);
|
|
4640
|
+
function isFreeMailDomain(domain) {
|
|
4641
|
+
return FREE_MAIL_DOMAINS.has(domain.toLowerCase());
|
|
4642
|
+
}
|
|
4643
|
+
function deriveIdentityKeysFromEmail(email) {
|
|
4644
|
+
const lower = email.toLowerCase().trim();
|
|
4645
|
+
const at = lower.lastIndexOf("@");
|
|
4646
|
+
if (at <= 0 || at === lower.length - 1) return [];
|
|
4647
|
+
const domain = lower.slice(at + 1);
|
|
4648
|
+
const keys = [{ type: "email", value: lower }];
|
|
4649
|
+
if (!isFreeMailDomain(domain)) keys.push({ type: "email_domain", value: domain });
|
|
4650
|
+
return keys;
|
|
4651
|
+
}
|
|
4652
|
+
function deriveIdentityKeysFromValue(value) {
|
|
4653
|
+
if (typeof value === "string") return deriveIdentityKeysFromEmail(value);
|
|
4654
|
+
if (Array.isArray(value)) {
|
|
4655
|
+
return value.flatMap((v) => typeof v === "string" ? deriveIdentityKeysFromEmail(v) : []);
|
|
4656
|
+
}
|
|
4657
|
+
return [];
|
|
4658
|
+
}
|
|
4659
|
+
function dedupeKeys(keys) {
|
|
4660
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4661
|
+
const out = [];
|
|
4662
|
+
for (const k of keys) {
|
|
4663
|
+
const sig = `${k.type}:${k.value}`;
|
|
4664
|
+
if (seen.has(sig)) continue;
|
|
4665
|
+
seen.add(sig);
|
|
4666
|
+
out.push(k);
|
|
4667
|
+
}
|
|
4668
|
+
return out;
|
|
4669
|
+
}
|
|
4670
|
+
function substitute(template, values) {
|
|
4671
|
+
const out = {};
|
|
4672
|
+
for (const [arg, token] of Object.entries(template)) {
|
|
4673
|
+
if (token.startsWith("$")) {
|
|
4674
|
+
const key = token.slice(1);
|
|
4675
|
+
if (key in values) out[arg] = values[key];
|
|
4676
|
+
} else {
|
|
4677
|
+
out[arg] = token;
|
|
4678
|
+
}
|
|
4679
|
+
}
|
|
4680
|
+
return out;
|
|
4681
|
+
}
|
|
4682
|
+
function parseContextBody(body) {
|
|
4683
|
+
if (body.structured !== void 0) return body.structured;
|
|
4684
|
+
if (body.text !== void 0) {
|
|
4685
|
+
try {
|
|
4686
|
+
return JSON.parse(body.text);
|
|
4687
|
+
} catch {
|
|
4688
|
+
return void 0;
|
|
4689
|
+
}
|
|
4690
|
+
}
|
|
4691
|
+
return void 0;
|
|
4692
|
+
}
|
|
4693
|
+
async function readContextRecord(integrationManager2, connectorId, tool, args) {
|
|
4694
|
+
const read = await readPreview(integrationManager2, connectorId, tool, args);
|
|
4695
|
+
if (read.status === "ok") {
|
|
4696
|
+
const record = parseContextBody(read.body);
|
|
4697
|
+
if (record === void 0) return { status: "unavailable", reason: "connector_error" };
|
|
4698
|
+
return { status: "ok", record };
|
|
4699
|
+
}
|
|
4700
|
+
if (read.status === "not_found") return { status: "not_found" };
|
|
4701
|
+
return { status: "unavailable", reason: read.reason };
|
|
4702
|
+
}
|
|
4703
|
+
function parseExplicitSourceLink(raw) {
|
|
4704
|
+
if (raw === void 0 || raw === null) return void 0;
|
|
4705
|
+
if (typeof raw !== "object" || Array.isArray(raw)) return "invalid";
|
|
4706
|
+
const o = raw;
|
|
4707
|
+
if (typeof o.connector !== "string" || !o.connector) return "invalid";
|
|
4708
|
+
if (typeof o.kind !== "string" || !o.kind) return "invalid";
|
|
4709
|
+
if (typeof o.id !== "string" || !o.id) return "invalid";
|
|
4710
|
+
return { connector: o.connector, kind: o.kind, id: o.id };
|
|
4711
|
+
}
|
|
4712
|
+
function resolveSourceLink(toolArgs, connectorId, sourceFromArg) {
|
|
4713
|
+
const explicit = parseExplicitSourceLink(toolArgs._source);
|
|
4714
|
+
if (explicit !== void 0) return explicit;
|
|
4715
|
+
if (sourceFromArg) {
|
|
4716
|
+
const argVal = toolArgs[sourceFromArg.arg];
|
|
4717
|
+
if (typeof argVal === "string" && argVal) {
|
|
4718
|
+
return { connector: connectorId, kind: sourceFromArg.kind, id: argVal };
|
|
4719
|
+
}
|
|
4720
|
+
}
|
|
4721
|
+
return void 0;
|
|
4722
|
+
}
|
|
4723
|
+
async function readSourceOnce(integrationManager2, link, sourceConfig) {
|
|
4724
|
+
const args = substitute(sourceConfig.read.args, { id: link.id });
|
|
4725
|
+
const read = await readContextRecord(integrationManager2, link.connector, sourceConfig.read.tool, args);
|
|
4726
|
+
if (read.status === "not_found") return { status: "not_found" };
|
|
4727
|
+
if (read.status === "unavailable") return { status: "unreadable", reason: read.reason };
|
|
4728
|
+
const keys = [];
|
|
4729
|
+
if (sourceConfig.keys?.email) {
|
|
4730
|
+
const v = resolveFieldPath(read.record, sourceConfig.keys.email);
|
|
4731
|
+
if (typeof v === "string" && v) keys.push(...deriveIdentityKeysFromEmail(v));
|
|
4732
|
+
}
|
|
4733
|
+
return { record: read.record, keys };
|
|
4734
|
+
}
|
|
4735
|
+
async function resolveRequestContext(integrationManager2, toolArgs, actionConnectorId, sourceFromArg, configuredConnectorIds) {
|
|
4736
|
+
const link = resolveSourceLink(toolArgs, actionConnectorId, sourceFromArg);
|
|
4737
|
+
if (link === void 0) return { result: { status: "none" }, keys: [] };
|
|
4738
|
+
if (link === "invalid") {
|
|
4739
|
+
return { result: { status: "unreadable", reason: "malformed link" }, keys: [] };
|
|
4740
|
+
}
|
|
4741
|
+
if (!configuredConnectorIds.has(link.connector)) {
|
|
4742
|
+
return { result: { status: "unreadable", reason: "connector not installed" }, keys: [] };
|
|
4743
|
+
}
|
|
4744
|
+
const connectorManifest = getManifest(link.connector);
|
|
4745
|
+
if (!connectorManifest) {
|
|
4746
|
+
return { result: { status: "unreadable", reason: "connector not installed" }, keys: [] };
|
|
4747
|
+
}
|
|
4748
|
+
const sourceConfig = connectorManifest.context?.sources?.[link.kind];
|
|
4749
|
+
if (!sourceConfig) {
|
|
4750
|
+
return { result: { status: "unreadable", reason: `connector "${link.connector}" declares no "${link.kind}" source` }, keys: [] };
|
|
4751
|
+
}
|
|
4752
|
+
const primary = await readSourceOnce(integrationManager2, link, sourceConfig);
|
|
4753
|
+
if ("status" in primary) {
|
|
4754
|
+
if (primary.status === "not_found") return { result: { status: "not_found" }, keys: [] };
|
|
4755
|
+
return { result: { status: "unreadable", reason: primary.reason ?? "connector unreachable" }, keys: [] };
|
|
4756
|
+
}
|
|
4757
|
+
const implicit = toolArgs._source === void 0;
|
|
4758
|
+
const base = {
|
|
4759
|
+
connector: link.connector,
|
|
4760
|
+
kind: link.kind,
|
|
4761
|
+
implicit,
|
|
4762
|
+
...sourceConfig.title ? { title: asString(resolveFieldPath(primary.record, sourceConfig.title)) } : {},
|
|
4763
|
+
...sourceConfig.author ? { author: asString(resolveFieldPath(primary.record, sourceConfig.author)) } : {},
|
|
4764
|
+
...sourceConfig.at ? { at: asString(resolveFieldPath(primary.record, sourceConfig.at)) } : {},
|
|
4765
|
+
...sourceConfig.body ? { body: asBody(resolveFieldPath(primary.record, sourceConfig.body)) } : {}
|
|
4766
|
+
};
|
|
4767
|
+
const chain = [];
|
|
4768
|
+
if (sourceConfig.chain) {
|
|
4769
|
+
let currentId = asString(getByDottedPath(primary.record, sourceConfig.chain.field));
|
|
4770
|
+
let hops = 0;
|
|
4771
|
+
const seen = /* @__PURE__ */ new Set([link.id]);
|
|
4772
|
+
while (currentId && hops < sourceConfig.chain.max && !seen.has(currentId)) {
|
|
4773
|
+
seen.add(currentId);
|
|
4774
|
+
const hopLink = { connector: link.connector, kind: link.kind, id: currentId };
|
|
4775
|
+
const hop = await readSourceOnce(integrationManager2, hopLink, sourceConfig);
|
|
4776
|
+
hops++;
|
|
4777
|
+
if ("status" in hop) break;
|
|
4778
|
+
chain.push({
|
|
4779
|
+
...sourceConfig.author ? { author: asString(resolveFieldPath(hop.record, sourceConfig.author)) } : {},
|
|
4780
|
+
...sourceConfig.at ? { at: asString(resolveFieldPath(hop.record, sourceConfig.at)) } : {},
|
|
4781
|
+
...sourceConfig.title ? { title: asString(resolveFieldPath(hop.record, sourceConfig.title)) } : {},
|
|
4782
|
+
...sourceConfig.body ? { body: asBody(resolveFieldPath(hop.record, sourceConfig.body)) } : {}
|
|
4783
|
+
});
|
|
4784
|
+
currentId = sourceConfig.chain ? asString(getByDottedPath(hop.record, sourceConfig.chain.field)) : void 0;
|
|
4785
|
+
}
|
|
4786
|
+
}
|
|
4787
|
+
return { result: { status: "ok", ...base, chain }, keys: primary.keys };
|
|
4788
|
+
}
|
|
4789
|
+
function asString(v) {
|
|
4790
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
4791
|
+
}
|
|
4792
|
+
var REQUEST_BODY_CAP = 2e4;
|
|
4793
|
+
function asBody(v) {
|
|
4794
|
+
const s = asString(v);
|
|
4795
|
+
return s === void 0 ? void 0 : s.length > REQUEST_BODY_CAP ? s.slice(0, REQUEST_BODY_CAP) : s;
|
|
4796
|
+
}
|
|
4797
|
+
async function resolveRecordKeyChain(integrationManager2, connectorId, manifest, recordKeyName, idValue, stepsLeft) {
|
|
4798
|
+
if (stepsLeft <= 0) return [];
|
|
4799
|
+
const recordKey = manifest.context?.recordKeys?.[recordKeyName];
|
|
4800
|
+
if (!recordKey) return [];
|
|
4801
|
+
const args = substitute(recordKey.read.args, { id: idValue });
|
|
4802
|
+
const read = await readContextRecord(integrationManager2, connectorId, recordKey.read.tool, args);
|
|
4803
|
+
if (read.status !== "ok") return [];
|
|
4804
|
+
const out = [];
|
|
4805
|
+
const emailField = recordKey.keys?.email;
|
|
4806
|
+
if (emailField) {
|
|
4807
|
+
const v = resolveFieldPath(read.record, emailField);
|
|
4808
|
+
if (typeof v === "string" && v) out.push(...deriveIdentityKeysFromEmail(v));
|
|
4809
|
+
}
|
|
4810
|
+
if (recordKey.next) {
|
|
4811
|
+
const nextId = asString(getByDottedPath(read.record, recordKey.next.field));
|
|
4812
|
+
if (nextId) {
|
|
4813
|
+
out.push(
|
|
4814
|
+
...await resolveRecordKeyChain(
|
|
4815
|
+
integrationManager2,
|
|
4816
|
+
connectorId,
|
|
4817
|
+
manifest,
|
|
4818
|
+
recordKey.next.recordKey,
|
|
4819
|
+
nextId,
|
|
4820
|
+
stepsLeft - 1
|
|
4821
|
+
)
|
|
4822
|
+
);
|
|
4823
|
+
}
|
|
4824
|
+
}
|
|
4825
|
+
return out;
|
|
4826
|
+
}
|
|
4827
|
+
async function gatherContextKeysFromAction(integrationManager2, connectorId, contextKeys, toolArgs) {
|
|
4828
|
+
if (!contextKeys) return [];
|
|
4829
|
+
const manifest = getManifest(connectorId);
|
|
4830
|
+
const out = [];
|
|
4831
|
+
for (const [name, argName] of Object.entries(contextKeys)) {
|
|
4832
|
+
const raw = toolArgs[argName];
|
|
4833
|
+
if (raw === void 0 || raw === null) continue;
|
|
4834
|
+
if (name === "email") {
|
|
4835
|
+
out.push(...deriveIdentityKeysFromValue(raw));
|
|
4836
|
+
continue;
|
|
4837
|
+
}
|
|
4838
|
+
if (!manifest?.context?.recordKeys?.[name] || typeof raw !== "string" || !raw) continue;
|
|
4839
|
+
out.push(...await resolveRecordKeyChain(integrationManager2, connectorId, manifest, name, raw, MAX_RECORD_KEY_STEPS));
|
|
4840
|
+
}
|
|
4841
|
+
return out;
|
|
4842
|
+
}
|
|
4843
|
+
function mapHistoryItem(raw, mapping) {
|
|
4844
|
+
return {
|
|
4845
|
+
// `kind` is a literal label, copied as-is — never a path (doc §8.1).
|
|
4846
|
+
...mapping.kind ? { kind: mapping.kind } : {},
|
|
4847
|
+
...mapping.title ? { title: asString(resolveFieldPath(raw, mapping.title)) } : {},
|
|
4848
|
+
...mapping.body ? { body: asBody(resolveFieldPath(raw, mapping.body)) } : {},
|
|
4849
|
+
...mapping.at ? { at: asString(resolveFieldPath(raw, mapping.at)) } : {},
|
|
4850
|
+
...mapping.author ? { author: asString(resolveFieldPath(raw, mapping.author)) } : {},
|
|
4851
|
+
...mapping.type ? { type: asString(resolveFieldPath(raw, mapping.type)) } : {}
|
|
4852
|
+
};
|
|
4853
|
+
}
|
|
4854
|
+
function withinWindow(at, cutoffMs) {
|
|
4855
|
+
if (!at) return true;
|
|
4856
|
+
const ms = Date.parse(at);
|
|
4857
|
+
if (Number.isNaN(ms)) return true;
|
|
4858
|
+
return ms >= cutoffMs;
|
|
4859
|
+
}
|
|
4860
|
+
function chooseHistoryKey(byKey, keys) {
|
|
4861
|
+
const order = Array.isArray(byKey) ? byKey : [byKey];
|
|
4862
|
+
for (const type of order) {
|
|
4863
|
+
const found = keys.find((k) => k.type === type);
|
|
4864
|
+
if (found) return found;
|
|
4865
|
+
}
|
|
4866
|
+
return void 0;
|
|
4867
|
+
}
|
|
4868
|
+
function resolveHistoryArgsTemplate(template, keyType) {
|
|
4869
|
+
const out = {};
|
|
4870
|
+
for (const [argName, token] of Object.entries(template)) {
|
|
4871
|
+
out[argName === "$keyType" ? keyType : argName] = token;
|
|
3903
4872
|
}
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
4873
|
+
return out;
|
|
4874
|
+
}
|
|
4875
|
+
async function readConnectorHistory(integrationManager2, connectorId, history, keys, executionJournal) {
|
|
4876
|
+
const key = chooseHistoryKey(history.byKey, keys);
|
|
4877
|
+
if (!key) return { status: "no_match" };
|
|
4878
|
+
const sinceMs = Date.now() - history.maxAgeDays * 24 * 60 * 60 * 1e3;
|
|
4879
|
+
const args = substitute(resolveHistoryArgsTemplate(history.read.args, key.type), {
|
|
4880
|
+
key: key.value,
|
|
4881
|
+
since: new Date(sinceMs).toISOString(),
|
|
4882
|
+
limit: history.limit
|
|
3913
4883
|
});
|
|
4884
|
+
const read = await readContextRecord(integrationManager2, connectorId, history.read.tool, args);
|
|
4885
|
+
if (read.status !== "ok") return { status: "unreachable" };
|
|
4886
|
+
const rows = Array.isArray(read.record) ? read.record : [];
|
|
4887
|
+
const items = [];
|
|
4888
|
+
for (const row of rows.slice(0, history.limit)) {
|
|
4889
|
+
const item = mapHistoryItem(row, history.item);
|
|
4890
|
+
if (!withinWindow(item.at, sinceMs)) continue;
|
|
4891
|
+
if (history.item.ticket) {
|
|
4892
|
+
const ticketId = asString(resolveFieldPath(row, history.item.ticket));
|
|
4893
|
+
if (ticketId) {
|
|
4894
|
+
const entry = executionJournal.get(ticketId);
|
|
4895
|
+
if (entry?.state === "done") {
|
|
4896
|
+
item.aiWritten = { at: entry.finishedAt ? entry.finishedAt * 1e3 : void 0 };
|
|
4897
|
+
}
|
|
4898
|
+
}
|
|
4899
|
+
}
|
|
4900
|
+
items.push(item);
|
|
4901
|
+
}
|
|
4902
|
+
return { status: "ok", match: { connectorId, matchedKeyType: key.type, matchedKeyValue: key.value, items } };
|
|
4903
|
+
}
|
|
4904
|
+
async function buildHistory(integrationManager2, keys, executionJournal, configuredConnectorIds) {
|
|
4905
|
+
if (keys.length === 0) return { status: "no_customer_link" };
|
|
4906
|
+
const connectors = getAllManifests().filter((m) => m.context?.history && configuredConnectorIds.has(m.id));
|
|
4907
|
+
if (connectors.length === 0) return { status: "no_match" };
|
|
4908
|
+
const matches = [];
|
|
4909
|
+
const unreachable = [];
|
|
4910
|
+
let anyMatchedType = false;
|
|
4911
|
+
for (const manifest of connectors) {
|
|
4912
|
+
const history = manifest.context.history;
|
|
4913
|
+
const outcome = await readConnectorHistory(integrationManager2, manifest.id, history, keys, executionJournal);
|
|
4914
|
+
if (outcome.status === "no_match") continue;
|
|
4915
|
+
anyMatchedType = true;
|
|
4916
|
+
if (outcome.status === "unreachable") {
|
|
4917
|
+
unreachable.push(manifest.id);
|
|
4918
|
+
continue;
|
|
4919
|
+
}
|
|
4920
|
+
matches.push(outcome.match);
|
|
4921
|
+
}
|
|
4922
|
+
if (!anyMatchedType) return { status: "no_match" };
|
|
4923
|
+
if (matches.length === 0 && unreachable.length > 0) return { status: "unreachable", connectorIds: unreachable };
|
|
4924
|
+
return { status: "ok", matches, unreachableConnectorIds: unreachable };
|
|
4925
|
+
}
|
|
4926
|
+
async function buildRequestContext(integrationManager2, executionJournal, actionConnectorId, toolArgs, actionGating, configuredConnectorIds) {
|
|
4927
|
+
const { result: request, keys: sourceKeys } = await resolveRequestContext(
|
|
4928
|
+
integrationManager2,
|
|
4929
|
+
toolArgs,
|
|
4930
|
+
actionConnectorId,
|
|
4931
|
+
actionGating?.sourceFromArg,
|
|
4932
|
+
configuredConnectorIds
|
|
4933
|
+
);
|
|
4934
|
+
const actionKeys = await gatherContextKeysFromAction(
|
|
4935
|
+
integrationManager2,
|
|
4936
|
+
actionConnectorId,
|
|
4937
|
+
actionGating?.contextKeys,
|
|
4938
|
+
toolArgs
|
|
4939
|
+
);
|
|
4940
|
+
const keys = dedupeKeys([...actionKeys, ...sourceKeys]);
|
|
4941
|
+
const history = await buildHistory(integrationManager2, keys, executionJournal, configuredConnectorIds);
|
|
4942
|
+
return { request, history };
|
|
3914
4943
|
}
|
|
3915
4944
|
|
|
3916
4945
|
// src/lib/ticket-verify.ts
|
|
@@ -3981,8 +5010,8 @@ async function verifyTicket(cache, receipt, expected) {
|
|
|
3981
5010
|
if (typeof timestamp !== "number" || !Number.isFinite(timestamp)) {
|
|
3982
5011
|
throw new TicketBindingMismatchError("Ticket carries no valid timestamp \u2014 refusing to execute.");
|
|
3983
5012
|
}
|
|
3984
|
-
const
|
|
3985
|
-
const ageSec =
|
|
5013
|
+
const nowSec4 = Math.floor(Date.now() / 1e3);
|
|
5014
|
+
const ageSec = nowSec4 - timestamp;
|
|
3986
5015
|
if (ageSec > TICKET_MAX_AGE_SECONDS) {
|
|
3987
5016
|
throw new TicketBindingMismatchError(
|
|
3988
5017
|
`Ticket is ${ageSec}s old, older than the ${TICKET_MAX_AGE_SECONDS}s freshness window \u2014 refusing to execute.`
|
|
@@ -4116,191 +5145,34 @@ function normalizeIncomingArgs(tool, args) {
|
|
|
4116
5145
|
return out ?? args;
|
|
4117
5146
|
}
|
|
4118
5147
|
|
|
4119
|
-
// src/lib/
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
return
|
|
4123
|
-
|
|
4124
|
-
);
|
|
4125
|
-
}
|
|
4126
|
-
function subsetOrEqual(a, b) {
|
|
4127
|
-
if (b.size === 0) return true;
|
|
4128
|
-
if (a.size === 0) return false;
|
|
4129
|
-
for (const x of a) if (!b.has(x)) return false;
|
|
4130
|
-
return true;
|
|
4131
|
-
}
|
|
4132
|
-
function atLeastAsSpecific(keys, a, b) {
|
|
4133
|
-
for (const k of keys) {
|
|
4134
|
-
if (!subsetOrEqual(tokenSet(a[k]), tokenSet(b[k]))) return false;
|
|
4135
|
-
}
|
|
4136
|
-
return true;
|
|
4137
|
-
}
|
|
4138
|
-
function strictlyMoreSpecific(keys, a, b) {
|
|
4139
|
-
return atLeastAsSpecific(keys, a, b) && !atLeastAsSpecific(keys, b, a);
|
|
4140
|
-
}
|
|
4141
|
-
function selectAuthorization(contextKeys, passers) {
|
|
4142
|
-
if (passers.length === 1) {
|
|
4143
|
-
return { chosen: passers[0], superseded: [], reason: "sole" };
|
|
4144
|
-
}
|
|
4145
|
-
const dominators = passers.filter(
|
|
4146
|
-
(a) => passers.every((b) => b === a || strictlyMoreSpecific(contextKeys, a.context, b.context))
|
|
4147
|
-
);
|
|
4148
|
-
if (dominators.length === 1) {
|
|
4149
|
-
const chosen2 = dominators[0];
|
|
4150
|
-
return { chosen: chosen2, superseded: passers.filter((p) => p !== chosen2), reason: "most-specific" };
|
|
4151
|
-
}
|
|
4152
|
-
const approvers = passers.filter((p) => p.requiresApproval);
|
|
4153
|
-
const pool = approvers.length > 0 ? approvers : passers;
|
|
4154
|
-
const chosen = pool.slice().sort((a, b) => a.id.localeCompare(b.id))[0];
|
|
4155
|
-
return {
|
|
4156
|
-
chosen,
|
|
4157
|
-
superseded: passers.filter((p) => p !== chosen),
|
|
4158
|
-
reason: approvers.length > 0 ? "fail-safe-approval" : "fail-safe-tiebreak"
|
|
4159
|
-
};
|
|
4160
|
-
}
|
|
4161
|
-
|
|
4162
|
-
// src/lib/read-gate.ts
|
|
4163
|
-
function boundsSatisfyReadGate(bounds, gate) {
|
|
4164
|
-
if (!gate.boundField) return true;
|
|
4165
|
-
const actual = bounds?.[gate.boundField];
|
|
4166
|
-
return actual !== void 0 && String(actual) === gate.requiredValue;
|
|
4167
|
-
}
|
|
4168
|
-
function readToolIsGoverned(g) {
|
|
4169
|
-
if (!g) return false;
|
|
4170
|
-
return Boolean(g.boundField) || g.read != null || g.readGovernance === "none";
|
|
5148
|
+
// src/lib/numeric-execution.ts
|
|
5149
|
+
import { getProfile as getProfile3 } from "@hap/core";
|
|
5150
|
+
function parseNumericString(value) {
|
|
5151
|
+
if (!/^-?\d+(\.\d+)?$/.test(value)) return void 0;
|
|
5152
|
+
const parsed = Number(value);
|
|
5153
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
4171
5154
|
}
|
|
4172
|
-
function
|
|
5155
|
+
function declaredNumericFields(profile) {
|
|
4173
5156
|
const out = /* @__PURE__ */ new Set();
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
}
|
|
4177
|
-
return out;
|
|
4178
|
-
}
|
|
4179
|
-
function deniedResources(requested, allowed) {
|
|
4180
|
-
return requested.filter((r) => !allowed.has(r));
|
|
4181
|
-
}
|
|
4182
|
-
function filterItemsByResource(items, idPath, allowed) {
|
|
4183
|
-
return items.filter((it) => {
|
|
4184
|
-
const id = getByDottedPath(it, idPath);
|
|
4185
|
-
return typeof id === "string" && allowed.has(id);
|
|
4186
|
-
});
|
|
4187
|
-
}
|
|
4188
|
-
function hasBlockedValue(item, valuesPath, blocked) {
|
|
4189
|
-
const v = getByDottedPath(item, valuesPath);
|
|
4190
|
-
const arr = Array.isArray(v) ? v : v != null ? [v] : [];
|
|
4191
|
-
return arr.some((x) => typeof x === "string" && blocked.has(x));
|
|
4192
|
-
}
|
|
4193
|
-
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
4194
|
-
function parseMessageTimestamp(value) {
|
|
4195
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
4196
|
-
if (typeof value === "string" && value.trim() !== "") {
|
|
4197
|
-
const n = Number(value);
|
|
4198
|
-
if (Number.isFinite(n)) return n;
|
|
4199
|
-
const iso = Date.parse(value);
|
|
4200
|
-
if (Number.isFinite(iso)) return iso;
|
|
4201
|
-
}
|
|
4202
|
-
return null;
|
|
4203
|
-
}
|
|
4204
|
-
function ageInDays(timestampMs, nowMs) {
|
|
4205
|
-
return Math.floor((nowMs - timestampMs) / MS_PER_DAY);
|
|
4206
|
-
}
|
|
4207
|
-
function maxReadAgeDays(boundsList, field) {
|
|
4208
|
-
let max = null;
|
|
4209
|
-
for (const b of boundsList) {
|
|
4210
|
-
const raw = b?.[field];
|
|
4211
|
-
const n = typeof raw === "number" ? raw : raw !== void 0 ? Number(raw) : NaN;
|
|
4212
|
-
if (Number.isFinite(n)) max = max === null ? n : Math.max(max, n);
|
|
4213
|
-
}
|
|
4214
|
-
return max;
|
|
4215
|
-
}
|
|
4216
|
-
function isOlderThanMaxAge(timestampMs, maxAgeDays, nowMs) {
|
|
4217
|
-
if (timestampMs === null) return true;
|
|
4218
|
-
return ageInDays(timestampMs, nowMs) > maxAgeDays;
|
|
4219
|
-
}
|
|
4220
|
-
var TRAILING_OPERATOR = /(?:^|\s)(?:OR|AND|NOT)$|[-+|&:]$/i;
|
|
4221
|
-
function parensBalanced(s) {
|
|
4222
|
-
let depth = 0;
|
|
4223
|
-
let inQuote = false;
|
|
4224
|
-
for (const c of s) {
|
|
4225
|
-
if (c === '"') {
|
|
4226
|
-
inQuote = !inQuote;
|
|
4227
|
-
continue;
|
|
4228
|
-
}
|
|
4229
|
-
if (inQuote) continue;
|
|
4230
|
-
if (c === "(") depth++;
|
|
4231
|
-
else if (c === ")" && --depth < 0) return false;
|
|
4232
|
-
}
|
|
4233
|
-
return !inQuote && depth === 0;
|
|
4234
|
-
}
|
|
4235
|
-
function composeReadQuery(agentQuery, clauses) {
|
|
4236
|
-
const injected = clauses.filter((c) => typeof c === "string" && c.trim() !== "").map((c) => c.trim());
|
|
4237
|
-
const base = (agentQuery ?? "").trim();
|
|
4238
|
-
if (base === "") return { ok: true, query: injected.join(" ") };
|
|
4239
|
-
if (!parensBalanced(base)) {
|
|
4240
|
-
return { ok: false, reason: "the requested search query has unbalanced parentheses" };
|
|
4241
|
-
}
|
|
4242
|
-
if (TRAILING_OPERATOR.test(base)) {
|
|
4243
|
-
return { ok: false, reason: "the requested search query ends in a dangling operator" };
|
|
4244
|
-
}
|
|
4245
|
-
if (injected.length === 0) return { ok: true, query: base };
|
|
4246
|
-
return { ok: true, query: `(${base}) ${injected.join(" ")}` };
|
|
4247
|
-
}
|
|
4248
|
-
function detectAgeConflict(agentQuery, conflictPattern, maxAgeDays) {
|
|
4249
|
-
if (!agentQuery || !conflictPattern || maxAgeDays === null) return null;
|
|
4250
|
-
let re;
|
|
4251
|
-
try {
|
|
4252
|
-
re = new RegExp(conflictPattern, "gi");
|
|
4253
|
-
} catch {
|
|
4254
|
-
return null;
|
|
4255
|
-
}
|
|
4256
|
-
let worst = null;
|
|
4257
|
-
for (const match of agentQuery.matchAll(re)) {
|
|
4258
|
-
const days = Number(match[1]);
|
|
4259
|
-
if (!Number.isFinite(days)) continue;
|
|
4260
|
-
if (days >= maxAgeDays && (worst === null || days > worst)) worst = days;
|
|
4261
|
-
}
|
|
4262
|
-
return worst;
|
|
4263
|
-
}
|
|
4264
|
-
function resolveAgeBoundField(boundsSchema, ageExecField) {
|
|
4265
|
-
const fields = boundsSchema?.fields;
|
|
4266
|
-
if (!fields) return null;
|
|
5157
|
+
const fields = profile?.executionContextSchema?.fields;
|
|
5158
|
+
if (!fields) return out;
|
|
4267
5159
|
for (const [name, def] of Object.entries(fields)) {
|
|
4268
|
-
if (def
|
|
5160
|
+
if (def.source === "declared" && def.constraint?.type === "number") out.add(name);
|
|
4269
5161
|
}
|
|
4270
|
-
return
|
|
4271
|
-
}
|
|
4272
|
-
function getByDottedPath(obj, path) {
|
|
4273
|
-
return path.split(".").reduce(
|
|
4274
|
-
(o, k) => o == null ? void 0 : o[k],
|
|
4275
|
-
obj
|
|
4276
|
-
);
|
|
5162
|
+
return out;
|
|
4277
5163
|
}
|
|
4278
|
-
function
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
5164
|
+
function coerceDeclaredNumericFields(profileId, execution) {
|
|
5165
|
+
const fields = declaredNumericFields(getProfile3(profileId));
|
|
5166
|
+
for (const field of fields) {
|
|
5167
|
+
const value = execution[field];
|
|
5168
|
+
if (typeof value !== "string") continue;
|
|
5169
|
+
const parsed = parseNumericString(value);
|
|
5170
|
+
if (parsed !== void 0) execution[field] = parsed;
|
|
4282
5171
|
}
|
|
4283
|
-
return null;
|
|
4284
|
-
}
|
|
4285
|
-
function renderAgeConstraint(template, maxAgeDays, nowMs) {
|
|
4286
|
-
const floorIso = new Date(nowMs - maxAgeDays * MS_PER_DAY).toISOString();
|
|
4287
|
-
return template.replace(/\{days\}/g, String(maxAgeDays)).replace(/\{date\}/g, floorIso.slice(0, 10));
|
|
4288
|
-
}
|
|
4289
|
-
function ageFloorValue(maxAgeDays, nowMs, format = "iso") {
|
|
4290
|
-
const floorMs = nowMs - maxAgeDays * MS_PER_DAY;
|
|
4291
|
-
if (format === "epoch_ms") return floorMs;
|
|
4292
|
-
if (format === "epoch_s") return String(Math.floor(floorMs / 1e3));
|
|
4293
|
-
return new Date(floorMs).toISOString();
|
|
4294
|
-
}
|
|
4295
|
-
function clampAgeFloor(requested, maxAgeDays, nowMs, format = "iso") {
|
|
4296
|
-
const floorMs = nowMs - maxAgeDays * MS_PER_DAY;
|
|
4297
|
-
const asked = parseMessageTimestamp(requested);
|
|
4298
|
-
if (asked !== null && asked >= floorMs) return requested;
|
|
4299
|
-
return ageFloorValue(maxAgeDays, nowMs, format);
|
|
4300
5172
|
}
|
|
4301
5173
|
|
|
4302
5174
|
// src/lib/tool-proxy.ts
|
|
4303
|
-
import { getProfile as
|
|
5175
|
+
import { getProfile as getProfile4, ContentBindingError, boundActionTypes as boundActionTypes2 } from "@hap/core";
|
|
4304
5176
|
import { readFile } from "fs/promises";
|
|
4305
5177
|
import { extname } from "path";
|
|
4306
5178
|
import { randomUUID } from "crypto";
|
|
@@ -4372,7 +5244,7 @@ function profileMatches(profileId, shortName2) {
|
|
|
4372
5244
|
return profileId === shortName2 || profileId.includes("/" + shortName2 + "@") || profileId.endsWith("/" + shortName2);
|
|
4373
5245
|
}
|
|
4374
5246
|
function wireExecutionContext(profileId, execution) {
|
|
4375
|
-
const profile =
|
|
5247
|
+
const profile = getProfile4(profileId);
|
|
4376
5248
|
const allowed = /* @__PURE__ */ new Set(["action_type"]);
|
|
4377
5249
|
const fields = profile?.boundsSchema?.fields;
|
|
4378
5250
|
if (fields) {
|
|
@@ -4390,7 +5262,7 @@ function wireExecutionContext(profileId, execution) {
|
|
|
4390
5262
|
return out;
|
|
4391
5263
|
}
|
|
4392
5264
|
function countBoundsFor(profileId, actionType) {
|
|
4393
|
-
const fields =
|
|
5265
|
+
const fields = getProfile4(profileId)?.boundsSchema?.fields;
|
|
4394
5266
|
if (!fields) return null;
|
|
4395
5267
|
return Object.entries(fields).filter(([, def]) => def.boundType?.kind === "cumulative_count").filter(([name, def]) => {
|
|
4396
5268
|
const applies = boundActionTypes2(name, def);
|
|
@@ -4465,7 +5337,7 @@ function recordBlocked(state2, tool, rec) {
|
|
|
4465
5337
|
function recordGatekeeperRefusal(state2, tool, refusals) {
|
|
4466
5338
|
if (refusals.length === 0) return;
|
|
4467
5339
|
const pick = refusals.find((r) => r.bound !== void 0 && r.actual !== void 0) ?? refusals[0];
|
|
4468
|
-
const scoped = pick.field ? Boolean(
|
|
5340
|
+
const scoped = pick.field ? Boolean(getProfile4(pick.profileId)?.scopeSchema?.fields?.[pick.field]) : false;
|
|
4469
5341
|
const asValueOrString = (v) => typeof v === "number" || typeof v === "string" ? v : void 0;
|
|
4470
5342
|
recordBlocked(state2, tool, {
|
|
4471
5343
|
kind: scoped ? "scope" : "bound",
|
|
@@ -4565,7 +5437,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
|
|
|
4565
5437
|
};
|
|
4566
5438
|
};
|
|
4567
5439
|
}
|
|
4568
|
-
const { profile, executionMapping, staticExecution, category } = tool.gating;
|
|
5440
|
+
const { profile, executionMapping, staticExecution, category, boundField: toolBoundField, requiredValue: toolRequiredValue } = tool.gating;
|
|
4569
5441
|
const eligibleMandates = () => {
|
|
4570
5442
|
const refusals = [];
|
|
4571
5443
|
const matching = state2.getEnrichedAuthorizations().filter((a) => a.complete && profileMatches(a.profileId, profile)).filter((a) => {
|
|
@@ -4644,7 +5516,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
|
|
|
4644
5516
|
);
|
|
4645
5517
|
}
|
|
4646
5518
|
}
|
|
4647
|
-
const readProfile = readAdapter ?
|
|
5519
|
+
const readProfile = readAdapter ? getProfile4(matchingAuths[0].profileId) : void 0;
|
|
4648
5520
|
const boundsSchema = readProfile?.boundsSchema;
|
|
4649
5521
|
const ageBoundField = readAdapter?.ageField ? resolveAgeBoundField(boundsSchema, readAdapter.ageField) : null;
|
|
4650
5522
|
const hasAgeDimension = Boolean(readAdapter?.ageField);
|
|
@@ -4789,6 +5661,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
|
|
|
4789
5661
|
}
|
|
4790
5662
|
}
|
|
4791
5663
|
}
|
|
5664
|
+
coerceDeclaredNumericFields(profile, execution);
|
|
4792
5665
|
const declaredActionType = typeof execution.action_type === "string" && execution.action_type.length > 0 ? execution.action_type : void 0;
|
|
4793
5666
|
if (!declaredActionType) {
|
|
4794
5667
|
recordBlocked(state2, tool, {
|
|
@@ -4807,7 +5680,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
|
|
|
4807
5680
|
};
|
|
4808
5681
|
}
|
|
4809
5682
|
{
|
|
4810
|
-
const profileDef =
|
|
5683
|
+
const profileDef = getProfile4(profile);
|
|
4811
5684
|
const registry = profileDef?.boundsSchema?.actionTypes;
|
|
4812
5685
|
if (Array.isArray(registry) && registry.length > 0 && !registry.includes(declaredActionType)) {
|
|
4813
5686
|
recordBlocked(state2, tool, {
|
|
@@ -4845,6 +5718,25 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
|
|
|
4845
5718
|
const passers = [];
|
|
4846
5719
|
const structuredRefusals = [];
|
|
4847
5720
|
for (const candidate of matchingAuths) {
|
|
5721
|
+
if (toolBoundField) {
|
|
5722
|
+
const gateOk = boundsSatisfyReadGate(
|
|
5723
|
+
candidate.bounds ?? candidate.frame,
|
|
5724
|
+
{ boundField: toolBoundField, requiredValue: toolRequiredValue }
|
|
5725
|
+
);
|
|
5726
|
+
if (!gateOk) {
|
|
5727
|
+
errors.push(
|
|
5728
|
+
`${candidate.path}: requires "${toolBoundField}: ${toolRequiredValue}", but this authorization does not grant it`
|
|
5729
|
+
);
|
|
5730
|
+
structuredRefusals.push({
|
|
5731
|
+
authorizationId: candidate.authorizationId,
|
|
5732
|
+
profileId: candidate.profileId,
|
|
5733
|
+
code: "BOUND_EXCEEDED",
|
|
5734
|
+
field: toolBoundField,
|
|
5735
|
+
bound: toolRequiredValue
|
|
5736
|
+
});
|
|
5737
|
+
continue;
|
|
5738
|
+
}
|
|
5739
|
+
}
|
|
4848
5740
|
const { result } = await state2.gatekeeper.verifyExecution(candidate.authorizationId, execution, {
|
|
4849
5741
|
bounds: candidate.bounds,
|
|
4850
5742
|
context: candidate.context
|
|
@@ -4892,7 +5784,7 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
|
|
|
4892
5784
|
isError: true
|
|
4893
5785
|
};
|
|
4894
5786
|
}
|
|
4895
|
-
const contextKeys =
|
|
5787
|
+
const contextKeys = getProfile4(passers[0].profileId)?.scopeSchema?.keyOrder ?? [];
|
|
4896
5788
|
return attemptWithCandidates(passers);
|
|
4897
5789
|
async function attemptWithCandidates(candidates) {
|
|
4898
5790
|
const selection = selectAuthorization(
|
|
@@ -5194,6 +6086,7 @@ Proposal ID: ${proposal.id}. Use check-pending-commitments to track status.` + a
|
|
|
5194
6086
|
let outgoingArgs = shouldAttachFooter() && receiptId ? appendVerificationFooter(tool, args, receiptId, auth.subjects?.[0]) : args;
|
|
5195
6087
|
if (receiptId) outgoingArgs = attachTicketId(tool, outgoingArgs, receiptId);
|
|
5196
6088
|
outgoingArgs = encodeOutgoingArgs(tool, outgoingArgs);
|
|
6089
|
+
outgoingArgs = stripSourceLink(outgoingArgs);
|
|
5197
6090
|
if (receiptId) {
|
|
5198
6091
|
const begun = state2.executionJournal.begin({
|
|
5199
6092
|
ticketId: receiptId,
|
|
@@ -5270,7 +6163,8 @@ function agentVisibleAuthorizations(authorizations, integrationManager2) {
|
|
|
5270
6163
|
// src/lib/agent-rules.ts
|
|
5271
6164
|
var AGENT_RULES = [
|
|
5272
6165
|
"Never open or operate the Suveren Gateway's web page \u2014 not in a browser you control, not with computer use. Never ask for, read or type the person's API key. Never approve a proposal: approvals are the person's alone.",
|
|
5273
|
-
"Use only the Suveren tools. No terminal, HTTP or browser calls to the gateway's ports (for example `curl localhost:3500/mcp`)."
|
|
6166
|
+
"Use only the Suveren tools. No terminal, HTTP or browser calls to the gateway's ports (for example `curl localhost:3500/mcp`).",
|
|
6167
|
+
"When a tool offers an optional `_source` argument, set it to link the ONE record (e.g. an inbound email) you are acting on \u2014 just the reference ({connector, kind, id}), never a summary or quote; the gateway reads the record itself for the approver."
|
|
5274
6168
|
];
|
|
5275
6169
|
|
|
5276
6170
|
// src/lib/mandate-brief.ts
|
|
@@ -5350,7 +6244,7 @@ function buildMandateBrief(opts) {
|
|
|
5350
6244
|
const parts = [];
|
|
5351
6245
|
if (boundsDesc) parts.push(boundsDesc);
|
|
5352
6246
|
if (executionLog) {
|
|
5353
|
-
const profile =
|
|
6247
|
+
const profile = getProfile5(auth.profileId) ?? getProfile5(shortName2);
|
|
5354
6248
|
const consumption = getConsumptionState(auth, executionLog, profile);
|
|
5355
6249
|
const compact = formatConsumptionCompact(consumption);
|
|
5356
6250
|
if (compact) parts.push(compact);
|
|
@@ -5388,7 +6282,7 @@ function buildMandateBrief(opts) {
|
|
|
5388
6282
|
}
|
|
5389
6283
|
|
|
5390
6284
|
// src/tools/authorizations.ts
|
|
5391
|
-
import { getProfile as
|
|
6285
|
+
import { getProfile as getProfile6 } from "@hap/core";
|
|
5392
6286
|
function shortProfileName2(profileId) {
|
|
5393
6287
|
const withoutVersion = profileId.replace(/@.*$/, "");
|
|
5394
6288
|
const parts = withoutVersion.split("/");
|
|
@@ -5506,7 +6400,7 @@ function listAuthorizationsHandler(state2, integrationManager2, contextDir) {
|
|
|
5506
6400
|
} catch {
|
|
5507
6401
|
}
|
|
5508
6402
|
const shortName2 = shortProfileName2(auth.profileId);
|
|
5509
|
-
const profile =
|
|
6403
|
+
const profile = getProfile6(auth.profileId) ?? getProfile6(shortName2);
|
|
5510
6404
|
if (profile) {
|
|
5511
6405
|
const consumption = getConsumptionState(auth, state2.executionLog, profile);
|
|
5512
6406
|
const consumptionText = formatConsumptionFull(consumption);
|
|
@@ -5566,7 +6460,7 @@ function listAuthorizationsHandler(state2, integrationManager2, contextDir) {
|
|
|
5566
6460
|
lines.push(" Mode: review (each action requires your approval)");
|
|
5567
6461
|
}
|
|
5568
6462
|
const shortName2 = shortProfileName2(auth.profileId);
|
|
5569
|
-
const profile =
|
|
6463
|
+
const profile = getProfile6(auth.profileId) ?? getProfile6(shortName2);
|
|
5570
6464
|
if (profile) {
|
|
5571
6465
|
const consumption = getConsumptionState(auth, state2.executionLog, profile);
|
|
5572
6466
|
const compact = formatConsumptionCompact(consumption);
|
|
@@ -5759,6 +6653,18 @@ async function decryptRejectionNote(by) {
|
|
|
5759
6653
|
}
|
|
5760
6654
|
|
|
5761
6655
|
// src/tools/commitments.ts
|
|
6656
|
+
function recordExecutionOutcome(state2, proposalId, ticketId, ok, text, structuredContent) {
|
|
6657
|
+
try {
|
|
6658
|
+
state2.executionResults?.record({ proposalId, ticketId, ok, text, structuredContent });
|
|
6659
|
+
} catch (err) {
|
|
6660
|
+
console.error(`[Suveren MCP] Could not record the execution result for proposal ${proposalId}:`, err);
|
|
6661
|
+
}
|
|
6662
|
+
}
|
|
6663
|
+
function logExecutionOutcome(proposalId, ok, shortMessage) {
|
|
6664
|
+
console.error(
|
|
6665
|
+
`[Suveren MCP] Proposal ${proposalId}: execution outcome=${ok ? "ok" : `error (${shortMessage ?? "unknown"})`}`
|
|
6666
|
+
);
|
|
6667
|
+
}
|
|
5762
6668
|
function buildSkippedProposalNote(proposalId) {
|
|
5763
6669
|
return `Proposal ${proposalId} was approved, but was submitted from another device/installation of this operator (or one whose local record is gone) \u2014 this gateway will not execute it. Re-run the same request here to execute it from this gateway.`;
|
|
5764
6670
|
}
|
|
@@ -6020,16 +6926,24 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
6020
6926
|
outgoingArgs = attachTicketId(discovered, outgoingArgs, receiptId);
|
|
6021
6927
|
outgoingArgs = encodeOutgoingArgs(discovered, outgoingArgs);
|
|
6022
6928
|
}
|
|
6929
|
+
outgoingArgs = stripSourceLink(outgoingArgs);
|
|
6023
6930
|
let result;
|
|
6024
6931
|
try {
|
|
6025
6932
|
result = await integrationManager2.callTool(integrationId, toolName, outgoingArgs);
|
|
6026
6933
|
} catch (err) {
|
|
6934
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6027
6935
|
state2.executionJournal.complete(receiptId, "failed");
|
|
6936
|
+
recordExecutionOutcome(state2, proposal.id, receiptId, false, `The tool call threw: ${msg}`);
|
|
6937
|
+
logExecutionOutcome(proposal.id, false, err instanceof Error ? err.name : "thrown error");
|
|
6938
|
+
void notifyControlPlane("execution-failed");
|
|
6028
6939
|
throw err;
|
|
6029
6940
|
}
|
|
6030
6941
|
const resultText = result.content?.[0]?.text ?? JSON.stringify(result);
|
|
6031
6942
|
if (result.isError) {
|
|
6032
6943
|
state2.executionJournal.complete(receiptId, "failed", "refused", resultText);
|
|
6944
|
+
recordExecutionOutcome(state2, proposal.id, receiptId, false, resultText);
|
|
6945
|
+
logExecutionOutcome(proposal.id, false, "the connector returned an error");
|
|
6946
|
+
void notifyControlPlane("execution-failed");
|
|
6033
6947
|
return {
|
|
6034
6948
|
text: `Proposal ${proposal.id} was approved, but ${integrationId} refused to run it \u2014 nothing was done.
|
|
6035
6949
|
Reason: ${resultText}`,
|
|
@@ -6037,6 +6951,8 @@ Reason: ${resultText}`,
|
|
|
6037
6951
|
};
|
|
6038
6952
|
}
|
|
6039
6953
|
state2.executionJournal.complete(receiptId, "done");
|
|
6954
|
+
recordExecutionOutcome(state2, proposal.id, receiptId, true, resultText, result.structuredContent);
|
|
6955
|
+
logExecutionOutcome(proposal.id, true);
|
|
6040
6956
|
state2.executionLog.record({
|
|
6041
6957
|
profileId: proposal.profileId,
|
|
6042
6958
|
path: proposal.path,
|
|
@@ -6047,6 +6963,12 @@ Reason: ${resultText}`,
|
|
|
6047
6963
|
Result: ${resultText}` };
|
|
6048
6964
|
} catch (err) {
|
|
6049
6965
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6966
|
+
if (state2.executionJournal.get(receiptId)?.state === "intent") {
|
|
6967
|
+
state2.executionJournal.complete(receiptId, "failed");
|
|
6968
|
+
recordExecutionOutcome(state2, proposal.id, receiptId, false, `Receipt issued but tool execution failed: ${msg}`);
|
|
6969
|
+
logExecutionOutcome(proposal.id, false, err instanceof Error ? err.name : "error");
|
|
6970
|
+
void notifyControlPlane("execution-failed");
|
|
6971
|
+
}
|
|
6050
6972
|
return {
|
|
6051
6973
|
text: `Proposal ${proposal.id} receipt issued but tool execution failed: ${msg}`,
|
|
6052
6974
|
isError: true
|
|
@@ -6055,11 +6977,22 @@ Result: ${resultText}` };
|
|
|
6055
6977
|
}
|
|
6056
6978
|
function localOutcomeOfExecuted(proposal, state2) {
|
|
6057
6979
|
const entry = state2.executionJournal?.findByProposal?.(proposal.id);
|
|
6058
|
-
if (!entry
|
|
6980
|
+
if (!entry) return null;
|
|
6059
6981
|
const integration = proposal.tool.split("__")[0];
|
|
6982
|
+
if (entry.state === "done") {
|
|
6983
|
+
const stored = state2.executionResults?.get(proposal.id);
|
|
6984
|
+
if (!stored || !stored.ok) return null;
|
|
6985
|
+
return {
|
|
6986
|
+
text: `Proposal ${proposal.id} was approved and executed.
|
|
6987
|
+
Result: ${stored.text}`,
|
|
6988
|
+
isError: false
|
|
6989
|
+
};
|
|
6990
|
+
}
|
|
6060
6991
|
if (entry.state === "failed") {
|
|
6061
|
-
const
|
|
6062
|
-
|
|
6992
|
+
const stored = state2.executionResults?.get(proposal.id);
|
|
6993
|
+
const detail = (stored && !stored.ok ? stored.text : void 0) ?? entry.detail;
|
|
6994
|
+
const reasonSuffix = detail ? `
|
|
6995
|
+
Reason: ${detail}` : "";
|
|
6063
6996
|
return {
|
|
6064
6997
|
text: entry.outcome === "as_refused" ? `Proposal ${proposal.id} was approved, but the Authority Server refused its ticket \u2014 nothing ran. Do not retry the same values; ask the person (e.g. for a higher limit).${reasonSuffix}` : entry.outcome === "refused" ? `Proposal ${proposal.id} was approved, but ${integration} refused to run it \u2014 nothing was done. Do not call the tool again with the same values; a new attempt needs a new request and approval.${reasonSuffix}` : entry.outcome === "changed" ? `Proposal ${proposal.id} was approved, but the record changed in ${integration} since it was requested \u2014 nothing was done. A new attempt needs a new request and approval.${reasonSuffix}` : `Proposal ${proposal.id} was approved, but running it failed \u2014 whether anything happened in ${integration} is unknown. Check ${integration} before trying again; a new attempt needs a new request and approval.${reasonSuffix}`,
|
|
6065
6998
|
isError: true
|
|
@@ -6406,21 +7339,21 @@ function createMcpServer(state2, integrationManager2) {
|
|
|
6406
7339
|
}
|
|
6407
7340
|
|
|
6408
7341
|
// src/lib/agent-contact.ts
|
|
6409
|
-
import { existsSync as
|
|
6410
|
-
import { dirname as
|
|
7342
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync13, readFileSync as readFileSync17, renameSync as renameSync5, writeFileSync as writeFileSync13 } from "fs";
|
|
7343
|
+
import { dirname as dirname13, join as join16 } from "path";
|
|
6411
7344
|
var FILE = "agent-contact.json";
|
|
6412
7345
|
var REWRITE_AFTER_MS = 6e4;
|
|
6413
7346
|
var AgentContactStore = class {
|
|
6414
7347
|
path;
|
|
6415
7348
|
current;
|
|
6416
7349
|
constructor(dataDir2) {
|
|
6417
|
-
this.path =
|
|
7350
|
+
this.path = join16(dataDir2, FILE);
|
|
6418
7351
|
this.current = this.load();
|
|
6419
7352
|
}
|
|
6420
7353
|
load() {
|
|
6421
|
-
if (!
|
|
7354
|
+
if (!existsSync16(this.path)) return null;
|
|
6422
7355
|
try {
|
|
6423
|
-
const v = JSON.parse(
|
|
7356
|
+
const v = JSON.parse(readFileSync17(this.path, "utf8"));
|
|
6424
7357
|
if (typeof v.clientName !== "string" || typeof v.firstSeenAt !== "string" || typeof v.lastSeenAt !== "string") return null;
|
|
6425
7358
|
return { clientName: v.clientName, clientVersion: v.clientVersion, firstSeenAt: v.firstSeenAt, lastSeenAt: v.lastSeenAt };
|
|
6426
7359
|
} catch {
|
|
@@ -6443,9 +7376,9 @@ var AgentContactStore = class {
|
|
|
6443
7376
|
};
|
|
6444
7377
|
if (!due) return;
|
|
6445
7378
|
try {
|
|
6446
|
-
|
|
7379
|
+
mkdirSync13(dirname13(this.path), { recursive: true });
|
|
6447
7380
|
const tmp = `${this.path}.tmp`;
|
|
6448
|
-
|
|
7381
|
+
writeFileSync13(tmp, JSON.stringify(this.current, null, 2));
|
|
6449
7382
|
renameSync5(tmp, this.path);
|
|
6450
7383
|
} catch (err) {
|
|
6451
7384
|
console.error("[Suveren MCP] Could not save agent contact:", err);
|
|
@@ -6480,16 +7413,16 @@ function verifyGateContentHashes(content, auth) {
|
|
|
6480
7413
|
}
|
|
6481
7414
|
|
|
6482
7415
|
// src/lib/integration-registry.ts
|
|
6483
|
-
import { mkdirSync as
|
|
6484
|
-
import { homedir as
|
|
6485
|
-
import { dirname as
|
|
6486
|
-
var DEFAULT_DATA_DIR = process.env.SUVEREN_DATA_DIR ??
|
|
7416
|
+
import { mkdirSync as mkdirSync14, readFileSync as readFileSync18, writeFileSync as writeFileSync14, existsSync as existsSync17 } from "fs";
|
|
7417
|
+
import { homedir as homedir12 } from "os";
|
|
7418
|
+
import { dirname as dirname14, join as join17 } from "path";
|
|
7419
|
+
var DEFAULT_DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join17(homedir12(), ".suveren");
|
|
6487
7420
|
var IntegrationRegistry = class {
|
|
6488
7421
|
integrations = /* @__PURE__ */ new Map();
|
|
6489
7422
|
filePath;
|
|
6490
7423
|
constructor(dataDir2) {
|
|
6491
7424
|
const dir = dataDir2 ?? DEFAULT_DATA_DIR;
|
|
6492
|
-
this.filePath =
|
|
7425
|
+
this.filePath = join17(dir, "integrations.json");
|
|
6493
7426
|
this.load();
|
|
6494
7427
|
}
|
|
6495
7428
|
// ─── CRUD ───────────────────────────────────────────────────────────────
|
|
@@ -6520,12 +7453,12 @@ var IntegrationRegistry = class {
|
|
|
6520
7453
|
}
|
|
6521
7454
|
// ─── Load / Save ────────────────────────────────────────────────────────
|
|
6522
7455
|
load() {
|
|
6523
|
-
if (!
|
|
6524
|
-
|
|
7456
|
+
if (!existsSync17(this.filePath)) {
|
|
7457
|
+
mkdirSync14(dirname14(this.filePath), { recursive: true });
|
|
6525
7458
|
return;
|
|
6526
7459
|
}
|
|
6527
7460
|
try {
|
|
6528
|
-
const raw =
|
|
7461
|
+
const raw = readFileSync18(this.filePath, "utf-8");
|
|
6529
7462
|
const data = JSON.parse(raw);
|
|
6530
7463
|
const migrated = data.integrations.map((i) => {
|
|
6531
7464
|
if ("toolGating" in i && !("profile" in i)) {
|
|
@@ -6554,15 +7487,15 @@ var IntegrationRegistry = class {
|
|
|
6554
7487
|
version: 1,
|
|
6555
7488
|
integrations: Array.from(this.integrations.values())
|
|
6556
7489
|
};
|
|
6557
|
-
|
|
6558
|
-
|
|
7490
|
+
mkdirSync14(dirname14(this.filePath), { recursive: true });
|
|
7491
|
+
writeFileSync14(this.filePath, JSON.stringify(data, null, 2), { encoding: "utf-8" });
|
|
6559
7492
|
}
|
|
6560
7493
|
};
|
|
6561
7494
|
|
|
6562
7495
|
// src/lib/integration-manager.ts
|
|
6563
|
-
import { homedir as
|
|
6564
|
-
import { join as
|
|
6565
|
-
import { existsSync as
|
|
7496
|
+
import { homedir as homedir13 } from "os";
|
|
7497
|
+
import { join as join18, delimiter } from "path";
|
|
7498
|
+
import { existsSync as existsSync18, writeFileSync as writeFileSync15, mkdirSync as mkdirSync15, readFileSync as readFileSync19, rmSync } from "fs";
|
|
6566
7499
|
import { execFile } from "child_process";
|
|
6567
7500
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
6568
7501
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
@@ -6692,8 +7625,8 @@ function inheritedConnectorEnv(parent, passEnv = []) {
|
|
|
6692
7625
|
}
|
|
6693
7626
|
|
|
6694
7627
|
// src/lib/integration-manager.ts
|
|
6695
|
-
var DEFAULT_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ??
|
|
6696
|
-
var INTEGRATIONS_DIR = process.env.SUVEREN_INTEGRATIONS_DIR ??
|
|
7628
|
+
var DEFAULT_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ?? join18(homedir13(), ".suveren");
|
|
7629
|
+
var INTEGRATIONS_DIR = process.env.SUVEREN_INTEGRATIONS_DIR ?? join18(DEFAULT_DATA_DIR2, "integrations");
|
|
6697
7630
|
var OFFLINE_MODE = process.env.SUVEREN_OFFLINE === "1";
|
|
6698
7631
|
var installLock = Promise.resolve();
|
|
6699
7632
|
function withInstallLock(task) {
|
|
@@ -6701,7 +7634,7 @@ function withInstallLock(task) {
|
|
|
6701
7634
|
installLock = run.then(() => void 0, () => void 0);
|
|
6702
7635
|
return run;
|
|
6703
7636
|
}
|
|
6704
|
-
var INTEGRATIONS_BIN =
|
|
7637
|
+
var INTEGRATIONS_BIN = join18(INTEGRATIONS_DIR, "node_modules", ".bin");
|
|
6705
7638
|
function getIntegrationsBinDir() {
|
|
6706
7639
|
return INTEGRATIONS_BIN;
|
|
6707
7640
|
}
|
|
@@ -6710,12 +7643,12 @@ function buildPath() {
|
|
|
6710
7643
|
return [INTEGRATIONS_BIN, base].join(delimiter);
|
|
6711
7644
|
}
|
|
6712
7645
|
function ensureIntegrationsDir() {
|
|
6713
|
-
if (!
|
|
6714
|
-
|
|
7646
|
+
if (!existsSync18(INTEGRATIONS_DIR)) {
|
|
7647
|
+
mkdirSync15(INTEGRATIONS_DIR, { recursive: true });
|
|
6715
7648
|
}
|
|
6716
|
-
const pkgPath =
|
|
6717
|
-
if (!
|
|
6718
|
-
|
|
7649
|
+
const pkgPath = join18(INTEGRATIONS_DIR, "package.json");
|
|
7650
|
+
if (!existsSync18(pkgPath)) {
|
|
7651
|
+
writeFileSync15(pkgPath, JSON.stringify({ name: "suveren-integrations", version: "1.0.0", private: true }, null, 2));
|
|
6719
7652
|
}
|
|
6720
7653
|
}
|
|
6721
7654
|
function readAgeOf(config) {
|
|
@@ -6804,19 +7737,19 @@ var IntegrationManager = class {
|
|
|
6804
7737
|
* exist, and treat anything else as not-installed (and reinstallable).
|
|
6805
7738
|
*/
|
|
6806
7739
|
isUsableInstall(npmPackage) {
|
|
6807
|
-
const pkgDir =
|
|
6808
|
-
const pkgJson =
|
|
6809
|
-
if (!
|
|
7740
|
+
const pkgDir = join18(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"));
|
|
7741
|
+
const pkgJson = join18(pkgDir, "package.json");
|
|
7742
|
+
if (!existsSync18(pkgDir) || !existsSync18(pkgJson)) return false;
|
|
6810
7743
|
try {
|
|
6811
|
-
const pkg = JSON.parse(
|
|
7744
|
+
const pkg = JSON.parse(readFileSync19(pkgJson, "utf8"));
|
|
6812
7745
|
const defaultBinName = (pkg.name ?? npmPackage).split("/").pop() ?? "";
|
|
6813
7746
|
const binMap = typeof pkg.bin === "string" ? { [defaultBinName]: pkg.bin } : pkg.bin ?? {};
|
|
6814
7747
|
const binEntries = Object.entries(binMap);
|
|
6815
7748
|
for (const [binName, target] of binEntries) {
|
|
6816
|
-
if (!
|
|
7749
|
+
if (!existsSync18(join18(pkgDir, target))) return false;
|
|
6817
7750
|
if (!this.binShimExists(binName)) return false;
|
|
6818
7751
|
}
|
|
6819
|
-
if (binEntries.length === 0 && pkg.main && !
|
|
7752
|
+
if (binEntries.length === 0 && pkg.main && !existsSync18(join18(pkgDir, pkg.main))) return false;
|
|
6820
7753
|
return true;
|
|
6821
7754
|
} catch {
|
|
6822
7755
|
return false;
|
|
@@ -6831,9 +7764,9 @@ var IntegrationManager = class {
|
|
|
6831
7764
|
*/
|
|
6832
7765
|
readInstalledVersion(npmPackage) {
|
|
6833
7766
|
if (!this.isUsableInstall(npmPackage)) return null;
|
|
6834
|
-
const pkgJson =
|
|
7767
|
+
const pkgJson = join18(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"), "package.json");
|
|
6835
7768
|
try {
|
|
6836
|
-
const pkg = JSON.parse(
|
|
7769
|
+
const pkg = JSON.parse(readFileSync19(pkgJson, "utf8"));
|
|
6837
7770
|
return typeof pkg.version === "string" ? pkg.version : null;
|
|
6838
7771
|
} catch {
|
|
6839
7772
|
return null;
|
|
@@ -6847,7 +7780,7 @@ var IntegrationManager = class {
|
|
|
6847
7780
|
binShimExists(binName) {
|
|
6848
7781
|
if (!binName) return false;
|
|
6849
7782
|
const candidates = process.platform === "win32" ? [binName, `${binName}.cmd`, `${binName}.CMD`, `${binName}.ps1`] : [binName];
|
|
6850
|
-
return candidates.some((c) =>
|
|
7783
|
+
return candidates.some((c) => existsSync18(join18(INTEGRATIONS_BIN, c)));
|
|
6851
7784
|
}
|
|
6852
7785
|
/**
|
|
6853
7786
|
* The exact version THIS gateway's CURRENTLY LOADED manifest pins
|
|
@@ -6937,8 +7870,8 @@ var IntegrationManager = class {
|
|
|
6937
7870
|
* nothing at all.
|
|
6938
7871
|
*/
|
|
6939
7872
|
async installNow(npmPackage, exactVersion, cleanFirst = true) {
|
|
6940
|
-
const pkgDir =
|
|
6941
|
-
if (cleanFirst &&
|
|
7873
|
+
const pkgDir = join18(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"));
|
|
7874
|
+
if (cleanFirst && existsSync18(pkgDir)) {
|
|
6942
7875
|
console.error(`[IntegrationManager] Removing ${exactVersion ? "outdated" : "unusable"} install of ${npmPackage}`);
|
|
6943
7876
|
try {
|
|
6944
7877
|
rmSync(pkgDir, { recursive: true, force: true });
|
|
@@ -7118,19 +8051,19 @@ var IntegrationManager = class {
|
|
|
7118
8051
|
const profileGating = config.toolGating ?? null;
|
|
7119
8052
|
const tools = (toolsResult.tools ?? []).map((tool) => {
|
|
7120
8053
|
const gating = this.resolveToolGating(config.profile, profileGating, tool.name);
|
|
8054
|
+
let inputSchema = withoutBlockedArgs(
|
|
8055
|
+
tool.inputSchema ?? {},
|
|
8056
|
+
gating?.blockedArgs
|
|
8057
|
+
);
|
|
8058
|
+
if (gating && gating.category !== "read" && gating.category !== "disabled") {
|
|
8059
|
+
inputSchema = withSourceLink(inputSchema);
|
|
8060
|
+
}
|
|
7121
8061
|
return {
|
|
7122
8062
|
originalName: tool.name,
|
|
7123
8063
|
namespacedName: `${config.id}__${tool.name}`,
|
|
7124
8064
|
integrationId: config.id,
|
|
7125
8065
|
description: tool.description ?? "",
|
|
7126
|
-
|
|
7127
|
-
// connector can offer a control-bypassing argument and we would pass it
|
|
7128
|
-
// straight on. Anything the manifest blocks is removed here, before the
|
|
7129
|
-
// agent ever sees it exists.
|
|
7130
|
-
inputSchema: withoutBlockedArgs(
|
|
7131
|
-
tool.inputSchema ?? {},
|
|
7132
|
-
gating?.blockedArgs
|
|
7133
|
-
),
|
|
8066
|
+
inputSchema,
|
|
7134
8067
|
outputSchema: tool.outputSchema,
|
|
7135
8068
|
gating
|
|
7136
8069
|
};
|
|
@@ -7525,7 +8458,20 @@ var IntegrationManager = class {
|
|
|
7525
8458
|
argNormalization: ext.argNormalization,
|
|
7526
8459
|
hideUnlessAuthorized: ext.hideUnlessAuthorized,
|
|
7527
8460
|
simulationOnly: ext.simulationOnly,
|
|
7528
|
-
approvalView: ext.approvalView
|
|
8461
|
+
approvalView: ext.approvalView,
|
|
8462
|
+
contextKeys: ext.contextKeys,
|
|
8463
|
+
sourceFromArg: ext.sourceFromArg,
|
|
8464
|
+
// A WRITE override may ALSO declare a capability-gate (enum boundType,
|
|
8465
|
+
// e.g. records.json's archive_access/delete_access) — the same
|
|
8466
|
+
// mechanism the read branch uses, now generalized: hap-core's
|
|
8467
|
+
// Gatekeeper deliberately never compares an `enum` bound against
|
|
8468
|
+
// execution (gatekeeper.ts's 'enum' case: "the gateway tool-proxy
|
|
8469
|
+
// gates tool calls ... before they reach the Gatekeeper"), so if this
|
|
8470
|
+
// branch dropped it, nothing anywhere would ever enforce it. Before
|
|
8471
|
+
// this, boundField/requiredValue on a non-"read" override was silently
|
|
8472
|
+
// discarded here — the config in the manifest existed but did nothing.
|
|
8473
|
+
boundField: ext.boundField,
|
|
8474
|
+
requiredValue: ext.requiredValue
|
|
7529
8475
|
};
|
|
7530
8476
|
}
|
|
7531
8477
|
return {
|
|
@@ -7656,7 +8602,7 @@ async function buildTicketDetails(archive, ticketIds) {
|
|
|
7656
8602
|
}
|
|
7657
8603
|
|
|
7658
8604
|
// src/lib/report/window.ts
|
|
7659
|
-
import { getProfile as
|
|
8605
|
+
import { getProfile as getProfile7 } from "@hap/core";
|
|
7660
8606
|
var REPORT_AGE_FIELD = "read_age_days";
|
|
7661
8607
|
var REPORTING_PROFILE = "reporting";
|
|
7662
8608
|
var END_SKEW_SECONDS = 120;
|
|
@@ -7670,7 +8616,7 @@ function windowLabel(start, days, loadedAt) {
|
|
|
7670
8616
|
if (loadedAt !== null && loadedAt === start) return `${since} (when the test data was loaded)`;
|
|
7671
8617
|
return `${since} (the last ${days} ${days === 1 ? "day" : "days"})`;
|
|
7672
8618
|
}
|
|
7673
|
-
var defaultLookup = (id) =>
|
|
8619
|
+
var defaultLookup = (id) => getProfile7(id);
|
|
7674
8620
|
function reportingMandateRefusal(m, lookup = defaultLookup) {
|
|
7675
8621
|
const schema = lookup(m.profileId)?.boundsSchema;
|
|
7676
8622
|
const field = resolveAgeBoundField(schema, REPORT_AGE_FIELD);
|
|
@@ -7880,28 +8826,28 @@ function agentAccess(token, opensSession) {
|
|
|
7880
8826
|
}
|
|
7881
8827
|
|
|
7882
8828
|
// src/lib/profile-loader.ts
|
|
7883
|
-
import { readFileSync as
|
|
7884
|
-
import { join as
|
|
8829
|
+
import { readFileSync as readFileSync20, existsSync as existsSync19 } from "fs";
|
|
8830
|
+
import { join as join19, resolve as resolve2 } from "path";
|
|
7885
8831
|
import { registerProfile } from "@hap/core";
|
|
7886
8832
|
function loadProfiles(profilesDir) {
|
|
7887
|
-
const dir = resolve2(profilesDir ?? process.env.SUVEREN_PROFILES_DIR ??
|
|
7888
|
-
const indexPath =
|
|
7889
|
-
if (!
|
|
8833
|
+
const dir = resolve2(profilesDir ?? process.env.SUVEREN_PROFILES_DIR ?? join19(import.meta.dirname ?? __dirname, "..", "..", "..", "..", "..", "hap-profiles"));
|
|
8834
|
+
const indexPath = join19(dir, "index.json");
|
|
8835
|
+
if (!existsSync19(indexPath)) {
|
|
7890
8836
|
warnNoProfiles(`No index.json at ${indexPath}`);
|
|
7891
8837
|
return 0;
|
|
7892
8838
|
}
|
|
7893
8839
|
let index;
|
|
7894
8840
|
try {
|
|
7895
|
-
index = JSON.parse(
|
|
8841
|
+
index = JSON.parse(readFileSync20(indexPath, "utf-8"));
|
|
7896
8842
|
} catch (err) {
|
|
7897
8843
|
console.error(`[ProfileLoader] Failed to parse ${indexPath}:`, err);
|
|
7898
8844
|
return 0;
|
|
7899
8845
|
}
|
|
7900
8846
|
let loaded = 0;
|
|
7901
8847
|
for (const [profileId, relativePath] of Object.entries(index.profiles)) {
|
|
7902
|
-
const profilePath =
|
|
8848
|
+
const profilePath = join19(dir, relativePath);
|
|
7903
8849
|
try {
|
|
7904
|
-
const profile = JSON.parse(
|
|
8850
|
+
const profile = JSON.parse(readFileSync20(profilePath, "utf-8"));
|
|
7905
8851
|
registerProfile(profileId, profile);
|
|
7906
8852
|
const shortName2 = profileId.split("/").pop()?.replace(/@.*$/, "");
|
|
7907
8853
|
if (shortName2 && shortName2 !== profileId) {
|
|
@@ -8242,20 +9188,20 @@ async function controlPlaneMandate(dryRun, request) {
|
|
|
8242
9188
|
}
|
|
8243
9189
|
|
|
8244
9190
|
// src/lib/guides.ts
|
|
8245
|
-
import { existsSync as
|
|
8246
|
-
import { dirname as
|
|
9191
|
+
import { existsSync as existsSync20, readdirSync, readFileSync as readFileSync21 } from "fs";
|
|
9192
|
+
import { dirname as dirname15, join as join20 } from "path";
|
|
8247
9193
|
var FILE_RE = /^(?:(\d+)-)?([a-z0-9][a-z0-9-]*)\.md$/;
|
|
8248
9194
|
function builtinGuidesDir() {
|
|
8249
9195
|
if (process.env.SUVEREN_BUILTIN_GUIDES_DIR) return process.env.SUVEREN_BUILTIN_GUIDES_DIR;
|
|
8250
9196
|
let dir = import.meta.dirname ?? __dirname;
|
|
8251
9197
|
for (let i = 0; i < 8; i++) {
|
|
8252
|
-
const candidate =
|
|
8253
|
-
if (
|
|
8254
|
-
const parent =
|
|
9198
|
+
const candidate = join20(dir, "content", "guides");
|
|
9199
|
+
if (existsSync20(candidate)) return candidate;
|
|
9200
|
+
const parent = dirname15(dir);
|
|
8255
9201
|
if (parent === dir) break;
|
|
8256
9202
|
dir = parent;
|
|
8257
9203
|
}
|
|
8258
|
-
return
|
|
9204
|
+
return join20(import.meta.dirname ?? __dirname, "content", "guides");
|
|
8259
9205
|
}
|
|
8260
9206
|
function parse(body) {
|
|
8261
9207
|
const lines = body.split(/\r?\n/);
|
|
@@ -8266,14 +9212,14 @@ function parse(body) {
|
|
|
8266
9212
|
return { title, summary };
|
|
8267
9213
|
}
|
|
8268
9214
|
function readDir(dir, source) {
|
|
8269
|
-
if (!dir || !
|
|
9215
|
+
if (!dir || !existsSync20(dir)) return [];
|
|
8270
9216
|
const out = [];
|
|
8271
9217
|
for (const file of readdirSync(dir)) {
|
|
8272
9218
|
const m = FILE_RE.exec(file);
|
|
8273
9219
|
if (!m) continue;
|
|
8274
9220
|
let body;
|
|
8275
9221
|
try {
|
|
8276
|
-
body =
|
|
9222
|
+
body = readFileSync21(join20(dir, file), "utf8");
|
|
8277
9223
|
} catch {
|
|
8278
9224
|
continue;
|
|
8279
9225
|
}
|
|
@@ -8408,7 +9354,7 @@ function unifiedLineDiff(before, after) {
|
|
|
8408
9354
|
}
|
|
8409
9355
|
|
|
8410
9356
|
// src/lib/builtins/setup.ts
|
|
8411
|
-
import { getProfile as
|
|
9357
|
+
import { getProfile as getProfile8 } from "@hap/core";
|
|
8412
9358
|
var DELEGATION_PROFILE = "delegation";
|
|
8413
9359
|
var DELEGATION_INTENT_HINT = [
|
|
8414
9360
|
"Why \u2014 With test data only, I want to find out which routine parts of my work an AI could take over, so people gain time \u2014 before anything touches real systems.",
|
|
@@ -8460,7 +9406,7 @@ function delegationItselfRefusal(args) {
|
|
|
8460
9406
|
}
|
|
8461
9407
|
function delegationMandateRefusal(auth) {
|
|
8462
9408
|
if (isSimulationMode()) return void 0;
|
|
8463
|
-
const profile =
|
|
9409
|
+
const profile = getProfile8(auth.profileId);
|
|
8464
9410
|
if (profile?.scopeSchema?.fields?.allowed_profiles) return void 0;
|
|
8465
9411
|
return `your Delegation mandate (${shortName(auth.profileId)} ${versionOf(auth.profileId)}) does not declare which profiles or modes you may propose \u2014 only a Delegation mandate on delegation 0.4 or later works outside simulation mode. Ask the person to give a current Delegation mandate.`;
|
|
8466
9412
|
}
|
|
@@ -8518,7 +9464,7 @@ function delegationChecks(deps, args, targetShortName, targetDisplayName) {
|
|
|
8518
9464
|
);
|
|
8519
9465
|
const first = governing[0];
|
|
8520
9466
|
if (first) {
|
|
8521
|
-
const delegationProfile =
|
|
9467
|
+
const delegationProfile = getProfile8(first.profileId);
|
|
8522
9468
|
const entry = getConsumptionState(first, deps.state.executionLog, delegationProfile).find((e) => e.field === "mandate_daily_max");
|
|
8523
9469
|
if (entry && entry.limit !== null) {
|
|
8524
9470
|
checks.push(`Proposals today: ${entry.current} of ${entry.limit}`);
|
|
@@ -8528,7 +9474,7 @@ function delegationChecks(deps, args, targetShortName, targetDisplayName) {
|
|
|
8528
9474
|
}
|
|
8529
9475
|
function resolveTargetProfile(input) {
|
|
8530
9476
|
if (typeof input !== "string" || !input.trim()) return void 0;
|
|
8531
|
-
const profile =
|
|
9477
|
+
const profile = getProfile8(input.trim());
|
|
8532
9478
|
if (!profile) return void 0;
|
|
8533
9479
|
return { profile, shortName: shortName(profile.id) };
|
|
8534
9480
|
}
|
|
@@ -8679,7 +9625,7 @@ Read each topic with get_guide(topic) before doing that step.`);
|
|
|
8679
9625
|
return { ...builtinText(`Unknown topic "${topic}". Topics: ${guides.map((g) => g.topic).join(", ") || "none installed"}.`), isError: true };
|
|
8680
9626
|
}
|
|
8681
9627
|
const body = fillGenerated(guide.body.trim(), {
|
|
8682
|
-
limits: () => limitsSection(systems.map((s) => ({ id: s.id, profile:
|
|
9628
|
+
limits: () => limitsSection(systems.map((s) => ({ id: s.id, profile: getProfile8(s.profile) }))),
|
|
8683
9629
|
package: () => packageSection(
|
|
8684
9630
|
deps.integrationManager.getAllTools().find((t) => !deps.integrationManager.isBuiltin(t.integrationId) && t.originalName === "load_simulation")
|
|
8685
9631
|
)
|
|
@@ -8821,8 +9767,8 @@ function registerBuiltins(deps, factories = BUILTIN_FACTORIES) {
|
|
|
8821
9767
|
import { decodeMandateBlob as decodeMandateBlob3 } from "@hap/core";
|
|
8822
9768
|
|
|
8823
9769
|
// src/lib/committed-executor.ts
|
|
8824
|
-
import { existsSync as
|
|
8825
|
-
import { dirname as
|
|
9770
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync16, readFileSync as readFileSync22, unlinkSync as unlinkSync8, writeFileSync as writeFileSync16 } from "fs";
|
|
9771
|
+
import { dirname as dirname16, join as join21 } from "path";
|
|
8826
9772
|
function describeAutoExecutionLog(proposalId, tool, result) {
|
|
8827
9773
|
if (result.skipped) return null;
|
|
8828
9774
|
return result.isError ? `[Suveren MCP] Auto-exec proposal ${proposalId}: ${result.text}` : `[Suveren MCP] Auto-executed proposal ${proposalId}: ${tool}`;
|
|
@@ -8854,7 +9800,7 @@ var ExecutorLock = class {
|
|
|
8854
9800
|
path;
|
|
8855
9801
|
held = false;
|
|
8856
9802
|
constructor(dataDir2) {
|
|
8857
|
-
this.path =
|
|
9803
|
+
this.path = join21(dataDir2, "executor.lock");
|
|
8858
9804
|
}
|
|
8859
9805
|
/** Try to take the lock. Returns the holder's pid when someone else has it. */
|
|
8860
9806
|
acquire() {
|
|
@@ -8862,9 +9808,9 @@ var ExecutorLock = class {
|
|
|
8862
9808
|
if (current && current.pid !== process.pid && isAlive(current.pid)) {
|
|
8863
9809
|
return { held: false, holderPid: current.pid };
|
|
8864
9810
|
}
|
|
8865
|
-
|
|
9811
|
+
mkdirSync16(dirname16(this.path), { recursive: true, mode: 448 });
|
|
8866
9812
|
const lock = { pid: process.pid, startedAt: Math.floor(Date.now() / 1e3) };
|
|
8867
|
-
|
|
9813
|
+
writeFileSync16(this.path, JSON.stringify(lock), { mode: 384 });
|
|
8868
9814
|
this.held = true;
|
|
8869
9815
|
return { held: true };
|
|
8870
9816
|
}
|
|
@@ -8880,15 +9826,15 @@ var ExecutorLock = class {
|
|
|
8880
9826
|
const current = this.read();
|
|
8881
9827
|
if (current?.pid === process.pid) {
|
|
8882
9828
|
try {
|
|
8883
|
-
|
|
9829
|
+
unlinkSync8(this.path);
|
|
8884
9830
|
} catch {
|
|
8885
9831
|
}
|
|
8886
9832
|
}
|
|
8887
9833
|
}
|
|
8888
9834
|
read() {
|
|
8889
|
-
if (!
|
|
9835
|
+
if (!existsSync21(this.path)) return null;
|
|
8890
9836
|
try {
|
|
8891
|
-
const parsed = JSON.parse(
|
|
9837
|
+
const parsed = JSON.parse(readFileSync22(this.path, "utf8"));
|
|
8892
9838
|
return typeof parsed?.pid === "number" ? parsed : null;
|
|
8893
9839
|
} catch {
|
|
8894
9840
|
return null;
|
|
@@ -8905,20 +9851,20 @@ function isAlive(pid) {
|
|
|
8905
9851
|
}
|
|
8906
9852
|
|
|
8907
9853
|
// bin/http.ts
|
|
8908
|
-
import { homedir as
|
|
8909
|
-
import { join as
|
|
9854
|
+
import { homedir as homedir14 } from "os";
|
|
9855
|
+
import { join as join23 } from "path";
|
|
8910
9856
|
|
|
8911
9857
|
// src/lib/mcp-as-tracker.ts
|
|
8912
|
-
import { existsSync as
|
|
8913
|
-
import { dirname as
|
|
9858
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync17, readFileSync as readFileSync23, writeFileSync as writeFileSync17 } from "fs";
|
|
9859
|
+
import { dirname as dirname17, join as join22 } from "path";
|
|
8914
9860
|
function trackerPath(dataDir2) {
|
|
8915
|
-
return
|
|
9861
|
+
return join22(dataDir2, "mcp-as-pairing.json");
|
|
8916
9862
|
}
|
|
8917
9863
|
function readMcpPairedAsUrl(dataDir2) {
|
|
8918
9864
|
const path = trackerPath(dataDir2);
|
|
8919
|
-
if (!
|
|
9865
|
+
if (!existsSync22(path)) return null;
|
|
8920
9866
|
try {
|
|
8921
|
-
const data = JSON.parse(
|
|
9867
|
+
const data = JSON.parse(readFileSync23(path, "utf-8"));
|
|
8922
9868
|
return typeof data.asUrl === "string" ? data.asUrl : null;
|
|
8923
9869
|
} catch {
|
|
8924
9870
|
return null;
|
|
@@ -8926,12 +9872,12 @@ function readMcpPairedAsUrl(dataDir2) {
|
|
|
8926
9872
|
}
|
|
8927
9873
|
function writeMcpPairedAsUrl(dataDir2, asUrl) {
|
|
8928
9874
|
const path = trackerPath(dataDir2);
|
|
8929
|
-
|
|
8930
|
-
|
|
9875
|
+
mkdirSync17(dirname17(path), { recursive: true, mode: 448 });
|
|
9876
|
+
writeFileSync17(path, JSON.stringify({ asUrl }, null, 2), { encoding: "utf-8", mode: 384 });
|
|
8931
9877
|
}
|
|
8932
9878
|
|
|
8933
9879
|
// bin/http.ts
|
|
8934
|
-
var dataDir = process.env.SUVEREN_DATA_DIR ??
|
|
9880
|
+
var dataDir = process.env.SUVEREN_DATA_DIR ?? join23(homedir14(), ".suveren");
|
|
8935
9881
|
var spUrl = resolveAsUrl(dataDir);
|
|
8936
9882
|
validatePinTlsForUrl(resolvePinTls(dataDir), spUrl);
|
|
8937
9883
|
var port = parseInt(process.env.SUVEREN_MCP_PORT ?? "3430", 10);
|
|
@@ -9017,7 +9963,8 @@ app.post("/internal/configure", internalOnly, (req, res) => {
|
|
|
9017
9963
|
state.receiptArchive.setVaultKey(key);
|
|
9018
9964
|
state.reportStore.setVaultKey(key);
|
|
9019
9965
|
state.outcomeOutbox.setVaultKey(key);
|
|
9020
|
-
|
|
9966
|
+
state.executionResults.setVaultKey(key);
|
|
9967
|
+
console.error("[Suveren MCP] Vault key configured \u2014 gate store + denial log + receipt archive + report store + outcome outbox + execution results encryption active");
|
|
9021
9968
|
}
|
|
9022
9969
|
if (apiKey) {
|
|
9023
9970
|
state.spClient.setApiKey(apiKey);
|
|
@@ -9166,6 +10113,32 @@ app.post("/internal/preview", internalOnly, async (req, res) => {
|
|
|
9166
10113
|
});
|
|
9167
10114
|
res.json(result);
|
|
9168
10115
|
});
|
|
10116
|
+
app.post("/internal/request-context", internalOnly, async (req, res) => {
|
|
10117
|
+
const { tool, toolArgs } = req.body;
|
|
10118
|
+
if (!tool || typeof toolArgs !== "object" || toolArgs === null) {
|
|
10119
|
+
res.status(400).json({ error: "Missing tool or toolArgs" });
|
|
10120
|
+
return;
|
|
10121
|
+
}
|
|
10122
|
+
const sep = tool.indexOf("__");
|
|
10123
|
+
if (sep < 0) {
|
|
10124
|
+
res.status(400).json({ error: `Invalid tool name: ${tool}` });
|
|
10125
|
+
return;
|
|
10126
|
+
}
|
|
10127
|
+
const integrationId = tool.slice(0, sep);
|
|
10128
|
+
const toolName = tool.slice(sep + 2);
|
|
10129
|
+
const manifest = getManifest(integrationId);
|
|
10130
|
+
const actionGating = manifest?.toolGating?.overrides?.[toolName];
|
|
10131
|
+
const configuredConnectorIds = new Set(integrationRegistry.getAll().map((c) => c.id));
|
|
10132
|
+
const result = await buildRequestContext(
|
|
10133
|
+
integrationManager,
|
|
10134
|
+
state.executionJournal,
|
|
10135
|
+
integrationId,
|
|
10136
|
+
toolArgs,
|
|
10137
|
+
actionGating ?? void 0,
|
|
10138
|
+
configuredConnectorIds
|
|
10139
|
+
);
|
|
10140
|
+
res.json(result);
|
|
10141
|
+
});
|
|
9169
10142
|
app.post("/internal/add-integration", internalOnly, async (req, res) => {
|
|
9170
10143
|
try {
|
|
9171
10144
|
const config = req.body;
|
|
@@ -9659,7 +10632,7 @@ app.listen(port, bindHost, () => {
|
|
|
9659
10632
|
(proposal) => executeCommitted(proposal, state, integrationManager)
|
|
9660
10633
|
);
|
|
9661
10634
|
installCommittedExecutor(committedExecutor);
|
|
9662
|
-
const dataDir2 = process.env.SUVEREN_DATA_DIR ??
|
|
10635
|
+
const dataDir2 = process.env.SUVEREN_DATA_DIR ?? join23(homedir14(), ".suveren");
|
|
9663
10636
|
const executorLock = new ExecutorLock(dataDir2);
|
|
9664
10637
|
const lock = executorLock.acquire();
|
|
9665
10638
|
if (!lock.held) {
|