@suveren/gateway 0.21.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/content/integrations/crm.json +212 -21
- package/content/integrations/erp.json +14 -2
- package/dist/control-plane/index.mjs +180 -47
- package/dist/mcp-server/http.mjs +485 -174
- package/dist/ui/assets/{index-D5hgQ_8Z.css → index-C604QQYJ.css} +1 -1
- package/dist/ui/assets/index-C98JiZLb.js +119 -0
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
- package/profiles/customers/0.10.profile.json +186 -0
- package/profiles/index.json +2 -1
- package/dist/ui/assets/index-BBjT8ue7.js +0 -116
package/dist/mcp-server/http.mjs
CHANGED
|
@@ -1931,6 +1931,7 @@ import { createHash as createHash2 } from "crypto";
|
|
|
1931
1931
|
var DEFAULT_DIR5 = process.env.SUVEREN_DATA_DIR ?? join8(homedir5(), ".suveren");
|
|
1932
1932
|
var FILE_NAME = "execution-journal.json";
|
|
1933
1933
|
var MAX_AGE_SECONDS2 = 90 * 24 * 60 * 60;
|
|
1934
|
+
var DETAIL_CAP = 500;
|
|
1934
1935
|
function hashToolArgs(args) {
|
|
1935
1936
|
return "sha256:" + createHash2("sha256").update(stableStringify(args)).digest("hex");
|
|
1936
1937
|
}
|
|
@@ -1969,13 +1970,20 @@ var ExecutionJournal = class {
|
|
|
1969
1970
|
this.persist(file);
|
|
1970
1971
|
return { ok: true };
|
|
1971
1972
|
}
|
|
1972
|
-
/**
|
|
1973
|
-
|
|
1973
|
+
/**
|
|
1974
|
+
* Close an `intent` row. Missing row is a programming error, not a silent
|
|
1975
|
+
* no-op. `detail` (capped to DETAIL_CAP) is stored only alongside outcome
|
|
1976
|
+
* 'refused' — see JournalEntry's doc comment.
|
|
1977
|
+
*/
|
|
1978
|
+
complete(ticketId, state2, outcome, detail) {
|
|
1974
1979
|
const file = this.load();
|
|
1975
1980
|
const row = file.entries.find((e) => e.ticketId === ticketId);
|
|
1976
1981
|
if (!row) throw new Error(`execution journal: no row for ticket ${ticketId} to complete`);
|
|
1977
1982
|
row.state = state2;
|
|
1978
1983
|
if (outcome) row.outcome = outcome;
|
|
1984
|
+
if (detail !== void 0) {
|
|
1985
|
+
row.detail = detail.length > DETAIL_CAP ? detail.slice(0, DETAIL_CAP) : detail;
|
|
1986
|
+
}
|
|
1979
1987
|
row.finishedAt = nowSec();
|
|
1980
1988
|
this.persist(file);
|
|
1981
1989
|
}
|
|
@@ -2045,7 +2053,8 @@ var ProposalSubmissionStore = class {
|
|
|
2045
2053
|
executionContextHash: hashToolArgs(entry.executionContext),
|
|
2046
2054
|
authorizationId: entry.authorizationId,
|
|
2047
2055
|
profileId: entry.profileId,
|
|
2048
|
-
submittedAt: nowSec2()
|
|
2056
|
+
submittedAt: nowSec2(),
|
|
2057
|
+
...entry.previewHash ? { previewHash: entry.previewHash } : {}
|
|
2049
2058
|
});
|
|
2050
2059
|
this.persist(file);
|
|
2051
2060
|
}
|
|
@@ -2073,6 +2082,56 @@ var ProposalSubmissionStore = class {
|
|
|
2073
2082
|
}
|
|
2074
2083
|
};
|
|
2075
2084
|
|
|
2085
|
+
// src/lib/preview-log.ts
|
|
2086
|
+
import { mkdirSync as mkdirSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync8, existsSync as existsSync10, renameSync as renameSync3 } from "fs";
|
|
2087
|
+
import { homedir as homedir7 } from "os";
|
|
2088
|
+
import { dirname as dirname8, join as join10 } from "path";
|
|
2089
|
+
var DEFAULT_DIR7 = process.env.SUVEREN_DATA_DIR ?? join10(homedir7(), ".suveren");
|
|
2090
|
+
var FILE_NAME3 = "preview-log.json";
|
|
2091
|
+
var MAX_RECORDS2 = 500;
|
|
2092
|
+
var MAX_AGE_MS2 = 30 * 24 * 60 * 60 * 1e3;
|
|
2093
|
+
var PreviewLog = class {
|
|
2094
|
+
filePath;
|
|
2095
|
+
constructor(filePath) {
|
|
2096
|
+
const baseDir = filePath && filePath.endsWith(".json") ? dirname8(filePath) : filePath ?? DEFAULT_DIR7;
|
|
2097
|
+
this.filePath = join10(baseDir, FILE_NAME3);
|
|
2098
|
+
}
|
|
2099
|
+
/** Record one preview read. Never throws — recording must not break the read. */
|
|
2100
|
+
record(entry) {
|
|
2101
|
+
try {
|
|
2102
|
+
const file = this.load();
|
|
2103
|
+
file.entries.push(entry);
|
|
2104
|
+
const cutoff = entry.ts - MAX_AGE_MS2;
|
|
2105
|
+
file.entries = file.entries.filter((e) => e.ts >= cutoff);
|
|
2106
|
+
if (file.entries.length > MAX_RECORDS2) {
|
|
2107
|
+
file.entries = file.entries.slice(file.entries.length - MAX_RECORDS2);
|
|
2108
|
+
}
|
|
2109
|
+
this.persist(file);
|
|
2110
|
+
} catch (err) {
|
|
2111
|
+
console.error("[PreviewLog] failed to record preview read:", err);
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
/** Diagnostic only. */
|
|
2115
|
+
getAll() {
|
|
2116
|
+
return [...this.load().entries];
|
|
2117
|
+
}
|
|
2118
|
+
load() {
|
|
2119
|
+
if (!existsSync10(this.filePath)) return { version: 1, entries: [] };
|
|
2120
|
+
try {
|
|
2121
|
+
const parsed = JSON.parse(readFileSync11(this.filePath, "utf-8"));
|
|
2122
|
+
return { version: 1, entries: parsed.entries ?? [] };
|
|
2123
|
+
} catch {
|
|
2124
|
+
return { version: 1, entries: [] };
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
persist(file) {
|
|
2128
|
+
mkdirSync8(dirname8(this.filePath), { recursive: true, mode: 448 });
|
|
2129
|
+
const tmp = `${this.filePath}.tmp`;
|
|
2130
|
+
writeFileSync8(tmp, JSON.stringify(file, null, 2), { encoding: "utf-8", mode: 384 });
|
|
2131
|
+
renameSync3(tmp, this.filePath);
|
|
2132
|
+
}
|
|
2133
|
+
};
|
|
2134
|
+
|
|
2076
2135
|
// src/lib/gatekeeper.ts
|
|
2077
2136
|
import { verify } from "@hap/core";
|
|
2078
2137
|
var MCPGatekeeper = class {
|
|
@@ -2165,9 +2224,9 @@ var MCPGatekeeper = class {
|
|
|
2165
2224
|
};
|
|
2166
2225
|
|
|
2167
2226
|
// src/lib/report/report-store.ts
|
|
2168
|
-
import { mkdirSync as
|
|
2169
|
-
import { homedir as
|
|
2170
|
-
import { dirname as
|
|
2227
|
+
import { mkdirSync as mkdirSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync9, existsSync as existsSync11, unlinkSync as unlinkSync5 } from "fs";
|
|
2228
|
+
import { homedir as homedir8 } from "os";
|
|
2229
|
+
import { dirname as dirname9, join as join11 } from "path";
|
|
2171
2230
|
import { createCipheriv as createCipheriv5, createDecipheriv as createDecipheriv5, randomBytes as randomBytes5 } from "crypto";
|
|
2172
2231
|
|
|
2173
2232
|
// src/lib/report/record-resolvers.ts
|
|
@@ -2476,27 +2535,27 @@ async function buildCoverage(caseElements, getExport, archive, referenced, windo
|
|
|
2476
2535
|
}
|
|
2477
2536
|
|
|
2478
2537
|
// src/lib/report/report-store.ts
|
|
2479
|
-
var
|
|
2538
|
+
var DEFAULT_DIR8 = process.env.SUVEREN_DATA_DIR ?? join11(homedir8(), ".suveren");
|
|
2480
2539
|
var ReportStore = class {
|
|
2481
2540
|
report = null;
|
|
2482
2541
|
baseDir;
|
|
2483
2542
|
vaultKey = null;
|
|
2484
2543
|
constructor(filePath) {
|
|
2485
2544
|
if (filePath && filePath.endsWith(".json")) {
|
|
2486
|
-
this.baseDir =
|
|
2545
|
+
this.baseDir = dirname9(filePath);
|
|
2487
2546
|
} else {
|
|
2488
|
-
this.baseDir = filePath ??
|
|
2547
|
+
this.baseDir = filePath ?? DEFAULT_DIR8;
|
|
2489
2548
|
}
|
|
2490
2549
|
this.loadPlaintext();
|
|
2491
2550
|
}
|
|
2492
2551
|
/** Set the vault key for encryption. Triggers migration from plaintext if needed. */
|
|
2493
2552
|
setVaultKey(key) {
|
|
2494
2553
|
this.vaultKey = key;
|
|
2495
|
-
if (
|
|
2554
|
+
if (existsSync11(this.encryptedFilePath)) {
|
|
2496
2555
|
this.loadEncrypted();
|
|
2497
2556
|
} else if (this.report !== null) {
|
|
2498
2557
|
this.persistEncrypted();
|
|
2499
|
-
if (
|
|
2558
|
+
if (existsSync11(this.plaintextFilePath)) {
|
|
2500
2559
|
try {
|
|
2501
2560
|
unlinkSync5(this.plaintextFilePath);
|
|
2502
2561
|
} catch {
|
|
@@ -2511,7 +2570,7 @@ var ReportStore = class {
|
|
|
2511
2570
|
* "the report exists but is locked".
|
|
2512
2571
|
*/
|
|
2513
2572
|
isLocked() {
|
|
2514
|
-
return this.vaultKey === null &&
|
|
2573
|
+
return this.vaultKey === null && existsSync11(this.encryptedFilePath);
|
|
2515
2574
|
}
|
|
2516
2575
|
/** The stored, already-verified report — or null if none has been saved
|
|
2517
2576
|
* (and the store is not locked; check `isLocked()` first). */
|
|
@@ -2560,34 +2619,34 @@ var ReportStore = class {
|
|
|
2560
2619
|
}
|
|
2561
2620
|
// ─── File paths ─────────────────────────────────────────────────────────
|
|
2562
2621
|
get plaintextFilePath() {
|
|
2563
|
-
return
|
|
2622
|
+
return join11(this.baseDir, "report.json");
|
|
2564
2623
|
}
|
|
2565
2624
|
get encryptedFilePath() {
|
|
2566
|
-
return
|
|
2625
|
+
return join11(this.baseDir, "report.enc.json");
|
|
2567
2626
|
}
|
|
2568
2627
|
// ─── Load / Persist ─────────────────────────────────────────────────────
|
|
2569
2628
|
loadPlaintext() {
|
|
2570
|
-
if (!
|
|
2571
|
-
|
|
2629
|
+
if (!existsSync11(this.plaintextFilePath)) {
|
|
2630
|
+
mkdirSync9(this.baseDir, { recursive: true });
|
|
2572
2631
|
return;
|
|
2573
2632
|
}
|
|
2574
2633
|
try {
|
|
2575
|
-
const raw =
|
|
2634
|
+
const raw = readFileSync12(this.plaintextFilePath, "utf-8");
|
|
2576
2635
|
const parsed = JSON.parse(raw);
|
|
2577
2636
|
this.report = parsed.report ?? null;
|
|
2578
2637
|
} catch {
|
|
2579
2638
|
console.error(`[ReportStore] Could not parse ${this.plaintextFilePath} \u2014 preserving as .corrupt`);
|
|
2580
2639
|
try {
|
|
2581
|
-
|
|
2640
|
+
writeFileSync9(`${this.plaintextFilePath}.corrupt-${Date.now()}`, readFileSync12(this.plaintextFilePath));
|
|
2582
2641
|
} catch {
|
|
2583
2642
|
}
|
|
2584
2643
|
this.report = null;
|
|
2585
2644
|
}
|
|
2586
2645
|
}
|
|
2587
2646
|
loadEncrypted() {
|
|
2588
|
-
if (!
|
|
2647
|
+
if (!existsSync11(this.encryptedFilePath)) return;
|
|
2589
2648
|
try {
|
|
2590
|
-
const raw =
|
|
2649
|
+
const raw = readFileSync12(this.encryptedFilePath, "utf-8");
|
|
2591
2650
|
const data = JSON.parse(raw);
|
|
2592
2651
|
const decrypted = this.decrypt(data.blob);
|
|
2593
2652
|
const parsed = JSON.parse(decrypted);
|
|
@@ -2595,7 +2654,7 @@ var ReportStore = class {
|
|
|
2595
2654
|
} catch (err) {
|
|
2596
2655
|
console.error(`[ReportStore] Could not decrypt ${this.encryptedFilePath} \u2014 preserving as .corrupt:`, err);
|
|
2597
2656
|
try {
|
|
2598
|
-
|
|
2657
|
+
writeFileSync9(`${this.encryptedFilePath}.corrupt-${Date.now()}`, readFileSync12(this.encryptedFilePath));
|
|
2599
2658
|
} catch {
|
|
2600
2659
|
}
|
|
2601
2660
|
this.report = null;
|
|
@@ -2610,16 +2669,16 @@ var ReportStore = class {
|
|
|
2610
2669
|
}
|
|
2611
2670
|
persistPlaintext() {
|
|
2612
2671
|
const data = { version: 1, report: this.report };
|
|
2613
|
-
|
|
2614
|
-
|
|
2672
|
+
mkdirSync9(this.baseDir, { recursive: true, mode: 448 });
|
|
2673
|
+
writeFileSync9(this.plaintextFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
2615
2674
|
}
|
|
2616
2675
|
persistEncrypted() {
|
|
2617
2676
|
const data = {
|
|
2618
2677
|
version: 1,
|
|
2619
2678
|
blob: this.encrypt(JSON.stringify({ version: 1, report: this.report }))
|
|
2620
2679
|
};
|
|
2621
|
-
|
|
2622
|
-
|
|
2680
|
+
mkdirSync9(this.baseDir, { recursive: true, mode: 448 });
|
|
2681
|
+
writeFileSync9(this.encryptedFilePath, JSON.stringify(data, null, 2), { encoding: "utf-8", mode: 384 });
|
|
2623
2682
|
}
|
|
2624
2683
|
};
|
|
2625
2684
|
|
|
@@ -2640,6 +2699,8 @@ var SharedState = class {
|
|
|
2640
2699
|
/** What THIS gateway submitted for each review-mode proposal it created —
|
|
2641
2700
|
* see ticket-verify.ts / commitments.ts. */
|
|
2642
2701
|
proposalSubmissions;
|
|
2702
|
+
/** AU3 — local, content-free record of preview reads. See preview-log.ts. */
|
|
2703
|
+
previewLog;
|
|
2643
2704
|
gatekeeper;
|
|
2644
2705
|
/**
|
|
2645
2706
|
* V7 — set by {@link checkAsCompat} (called once at process start, and
|
|
@@ -2673,6 +2734,7 @@ var SharedState = class {
|
|
|
2673
2734
|
this.reportStore = new ReportStore(gateStorePath);
|
|
2674
2735
|
this.executionJournal = new ExecutionJournal(gateStorePath);
|
|
2675
2736
|
this.proposalSubmissions = new ProposalSubmissionStore(gateStorePath);
|
|
2737
|
+
this.previewLog = new PreviewLog(gateStorePath);
|
|
2676
2738
|
this.gatekeeper = new MCPGatekeeper(this.cache);
|
|
2677
2739
|
}
|
|
2678
2740
|
/**
|
|
@@ -2861,29 +2923,29 @@ function formatConsumptionFull(entries) {
|
|
|
2861
2923
|
}
|
|
2862
2924
|
|
|
2863
2925
|
// src/lib/context-loader.ts
|
|
2864
|
-
import { readFileSync as
|
|
2865
|
-
import { homedir as
|
|
2866
|
-
import { join as
|
|
2867
|
-
var
|
|
2926
|
+
import { readFileSync as readFileSync13, existsSync as existsSync12, mkdirSync as mkdirSync10, writeFileSync as writeFileSync10, renameSync as renameSync4 } from "fs";
|
|
2927
|
+
import { homedir as homedir9 } from "os";
|
|
2928
|
+
import { join as join12, dirname as dirname10 } from "path";
|
|
2929
|
+
var DEFAULT_DIR9 = process.env.SUVEREN_DATA_DIR ?? join12(homedir9(), ".suveren");
|
|
2868
2930
|
var CONTEXT_MAX_BYTES = 16 * 1024;
|
|
2869
2931
|
function contextFilePath(dataDir2) {
|
|
2870
|
-
return
|
|
2932
|
+
return join12(dataDir2 ?? DEFAULT_DIR9, "context.md");
|
|
2871
2933
|
}
|
|
2872
2934
|
function writeContextFile(content, dataDir2) {
|
|
2873
2935
|
const bytes = Buffer.byteLength(content, "utf-8");
|
|
2874
2936
|
if (bytes > CONTEXT_MAX_BYTES) throw new Error(`the brief is ${bytes} bytes; the limit is ${CONTEXT_MAX_BYTES}.`);
|
|
2875
2937
|
const filePath = contextFilePath(dataDir2);
|
|
2876
|
-
|
|
2938
|
+
mkdirSync10(dirname10(filePath), { recursive: true });
|
|
2877
2939
|
const tmpPath = `${filePath}.tmp`;
|
|
2878
|
-
|
|
2879
|
-
|
|
2940
|
+
writeFileSync10(tmpPath, content, "utf-8");
|
|
2941
|
+
renameSync4(tmpPath, filePath);
|
|
2880
2942
|
}
|
|
2881
2943
|
var BRIEF_MAX_CHARS = 16 * 1024;
|
|
2882
2944
|
function readContextFile(dataDir2) {
|
|
2883
2945
|
const filePath = contextFilePath(dataDir2);
|
|
2884
|
-
if (!
|
|
2946
|
+
if (!existsSync12(filePath)) return null;
|
|
2885
2947
|
try {
|
|
2886
|
-
const content =
|
|
2948
|
+
const content = readFileSync13(filePath, "utf-8").trim();
|
|
2887
2949
|
return content || null;
|
|
2888
2950
|
} catch {
|
|
2889
2951
|
return null;
|
|
@@ -3069,6 +3131,257 @@ function attachTicketId(tool, args, ticketId) {
|
|
|
3069
3131
|
return { ...args, ticket_id: ticketId };
|
|
3070
3132
|
}
|
|
3071
3133
|
|
|
3134
|
+
// src/lib/preview.ts
|
|
3135
|
+
import { computeContentHash as computeContentHash2 } from "@hap/core";
|
|
3136
|
+
|
|
3137
|
+
// src/lib/manifest-loader.ts
|
|
3138
|
+
import { readFileSync as readFileSync14, existsSync as existsSync13 } from "fs";
|
|
3139
|
+
import { join as join13, resolve } from "path";
|
|
3140
|
+
var EXACT_SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
3141
|
+
function isExactSemver(value) {
|
|
3142
|
+
return typeof value === "string" && EXACT_SEMVER_RE.test(value);
|
|
3143
|
+
}
|
|
3144
|
+
function invalidNpmPinReason(manifest) {
|
|
3145
|
+
if (!manifest.npmPackage) return null;
|
|
3146
|
+
if (manifest.npmVersion === void 0) {
|
|
3147
|
+
return `npmPackage "${manifest.npmPackage}" is set but npmVersion is missing \u2014 every connector manifest must pin an exact tested version`;
|
|
3148
|
+
}
|
|
3149
|
+
if (!isExactSemver(manifest.npmVersion)) {
|
|
3150
|
+
return `npmVersion ${JSON.stringify(manifest.npmVersion)} for "${manifest.npmPackage}" is not an exact semver (e.g. "1.2.3") \u2014 ranges, dist-tags like "latest", git specs, URLs, and file paths are not allowed`;
|
|
3151
|
+
}
|
|
3152
|
+
return null;
|
|
3153
|
+
}
|
|
3154
|
+
function invalidPreviewReason(toolName, entry) {
|
|
3155
|
+
if (entry === null || typeof entry !== "object") return null;
|
|
3156
|
+
const preview = entry.preview;
|
|
3157
|
+
if (preview === void 0) return null;
|
|
3158
|
+
if (preview === null || typeof preview !== "object" || Array.isArray(preview)) {
|
|
3159
|
+
return `override "${toolName}".preview must be an object`;
|
|
3160
|
+
}
|
|
3161
|
+
const p = preview;
|
|
3162
|
+
if (typeof p.tool !== "string" || p.tool.length === 0) {
|
|
3163
|
+
return `override "${toolName}".preview.tool must be a non-empty string`;
|
|
3164
|
+
}
|
|
3165
|
+
if (p.args === void 0 || p.args === null || typeof p.args !== "object" || Array.isArray(p.args)) {
|
|
3166
|
+
return `override "${toolName}".preview.args must be an object mapping preview-tool arg names to action arg names`;
|
|
3167
|
+
}
|
|
3168
|
+
for (const [argName, source] of Object.entries(p.args)) {
|
|
3169
|
+
if (typeof source !== "string" || source.length === 0) {
|
|
3170
|
+
return `override "${toolName}".preview.args.${argName} must name a non-empty source argument (string)`;
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
3173
|
+
if (p.version !== void 0) {
|
|
3174
|
+
if (p.version === null || typeof p.version !== "object" || Array.isArray(p.version)) {
|
|
3175
|
+
return `override "${toolName}".preview.version must be an object`;
|
|
3176
|
+
}
|
|
3177
|
+
const v = p.version;
|
|
3178
|
+
if (typeof v.arg !== "string" || v.arg.length === 0) {
|
|
3179
|
+
return `override "${toolName}".preview.version.arg must be a non-empty string`;
|
|
3180
|
+
}
|
|
3181
|
+
if (typeof v.field !== "string" || v.field.length === 0) {
|
|
3182
|
+
return `override "${toolName}".preview.version.field must be a non-empty string`;
|
|
3183
|
+
}
|
|
3184
|
+
}
|
|
3185
|
+
if (p.fields !== void 0) {
|
|
3186
|
+
if (!Array.isArray(p.fields)) {
|
|
3187
|
+
return `override "${toolName}".preview.fields must be an array of field names`;
|
|
3188
|
+
}
|
|
3189
|
+
for (const [i, f] of p.fields.entries()) {
|
|
3190
|
+
if (typeof f !== "string" || f.length === 0) {
|
|
3191
|
+
return `override "${toolName}".preview.fields[${i}] must be a non-empty string`;
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3194
|
+
}
|
|
3195
|
+
return null;
|
|
3196
|
+
}
|
|
3197
|
+
function invalidManifestPreviewReason(manifest) {
|
|
3198
|
+
const overrides = manifest.toolGating?.overrides ?? {};
|
|
3199
|
+
for (const [toolName, entry] of Object.entries(overrides)) {
|
|
3200
|
+
const reason = invalidPreviewReason(toolName, entry);
|
|
3201
|
+
if (reason) return reason;
|
|
3202
|
+
}
|
|
3203
|
+
return null;
|
|
3204
|
+
}
|
|
3205
|
+
var manifests = /* @__PURE__ */ new Map();
|
|
3206
|
+
function loadManifests(integrationsDir) {
|
|
3207
|
+
const dir = resolve(
|
|
3208
|
+
integrationsDir ?? process.env.SUVEREN_MANIFESTS_DIR ?? join13(import.meta.dirname ?? __dirname, "..", "..", "..", "..", "content", "integrations")
|
|
3209
|
+
);
|
|
3210
|
+
const indexPath = join13(dir, "index.json");
|
|
3211
|
+
if (!existsSync13(indexPath)) {
|
|
3212
|
+
console.error(`[ManifestLoader] No index.json found at ${indexPath}, skipping manifest loading`);
|
|
3213
|
+
return 0;
|
|
3214
|
+
}
|
|
3215
|
+
let index;
|
|
3216
|
+
try {
|
|
3217
|
+
index = JSON.parse(readFileSync14(indexPath, "utf-8"));
|
|
3218
|
+
} catch (err) {
|
|
3219
|
+
console.error(`[ManifestLoader] Failed to parse ${indexPath}:`, err);
|
|
3220
|
+
return 0;
|
|
3221
|
+
}
|
|
3222
|
+
let loaded = 0;
|
|
3223
|
+
for (const [id, relativePath] of Object.entries(index.integrations)) {
|
|
3224
|
+
const manifestPath = join13(dir, relativePath);
|
|
3225
|
+
try {
|
|
3226
|
+
const manifest = JSON.parse(readFileSync14(manifestPath, "utf-8"));
|
|
3227
|
+
const pinReason = invalidNpmPinReason(manifest);
|
|
3228
|
+
if (pinReason) {
|
|
3229
|
+
console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${pinReason}`);
|
|
3230
|
+
continue;
|
|
3231
|
+
}
|
|
3232
|
+
const previewReason = invalidManifestPreviewReason(manifest);
|
|
3233
|
+
if (previewReason) {
|
|
3234
|
+
console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${previewReason}`);
|
|
3235
|
+
continue;
|
|
3236
|
+
}
|
|
3237
|
+
manifests.set(id, manifest);
|
|
3238
|
+
loaded++;
|
|
3239
|
+
} catch (err) {
|
|
3240
|
+
console.error(`[ManifestLoader] Failed to load manifest ${id} from ${manifestPath}:`, err);
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
3243
|
+
console.error(`[ManifestLoader] Loaded ${loaded} integration manifest(s) from ${dir}`);
|
|
3244
|
+
return loaded;
|
|
3245
|
+
}
|
|
3246
|
+
function getManifest(id) {
|
|
3247
|
+
return manifests.get(id);
|
|
3248
|
+
}
|
|
3249
|
+
function getAllManifests() {
|
|
3250
|
+
return Array.from(manifests.values());
|
|
3251
|
+
}
|
|
3252
|
+
|
|
3253
|
+
// src/lib/preview.ts
|
|
3254
|
+
var PREVIEW_READ_TIMEOUT_MS = 5e3;
|
|
3255
|
+
var PREVIEW_MESSAGE_CAP = 500;
|
|
3256
|
+
function capText(text, max = PREVIEW_MESSAGE_CAP) {
|
|
3257
|
+
if (text === void 0) return void 0;
|
|
3258
|
+
return text.length > max ? text.slice(0, max) : text;
|
|
3259
|
+
}
|
|
3260
|
+
function getToolPreviewConfig(integrationId, toolName) {
|
|
3261
|
+
const manifest = getManifest(integrationId);
|
|
3262
|
+
const overrides = manifest?.toolGating?.overrides ?? {};
|
|
3263
|
+
const entry = overrides[toolName];
|
|
3264
|
+
return entry?.preview;
|
|
3265
|
+
}
|
|
3266
|
+
function mapPreviewArgs(preview, actionArgs) {
|
|
3267
|
+
const mapped = {};
|
|
3268
|
+
for (const [targetArg, sourceArg] of Object.entries(preview.args)) {
|
|
3269
|
+
if (sourceArg in actionArgs) mapped[targetArg] = actionArgs[sourceArg];
|
|
3270
|
+
}
|
|
3271
|
+
return mapped;
|
|
3272
|
+
}
|
|
3273
|
+
function previewHasNoTarget(preview, mapped) {
|
|
3274
|
+
return Object.keys(preview.args).length > 0 && Object.keys(mapped).length === 0;
|
|
3275
|
+
}
|
|
3276
|
+
var PreviewTimeoutError = class extends Error {
|
|
3277
|
+
};
|
|
3278
|
+
function withTimeout(p, ms) {
|
|
3279
|
+
let timer;
|
|
3280
|
+
const timeout = new Promise((_, reject) => {
|
|
3281
|
+
timer = setTimeout(() => reject(new PreviewTimeoutError("preview read timed out")), ms);
|
|
3282
|
+
});
|
|
3283
|
+
return Promise.race([p, timeout]).finally(() => clearTimeout(timer));
|
|
3284
|
+
}
|
|
3285
|
+
async function readPreview(integrationManager2, integrationId, previewTool, args) {
|
|
3286
|
+
const discovered = integrationManager2.getAllTools().find((t) => t.integrationId === integrationId && t.originalName === previewTool);
|
|
3287
|
+
if (!discovered) {
|
|
3288
|
+
return { status: "unavailable", reason: "no_connector" };
|
|
3289
|
+
}
|
|
3290
|
+
try {
|
|
3291
|
+
const result = await withTimeout(
|
|
3292
|
+
integrationManager2.callTool(integrationId, previewTool, args),
|
|
3293
|
+
PREVIEW_READ_TIMEOUT_MS
|
|
3294
|
+
);
|
|
3295
|
+
const text = result.content?.[0]?.text;
|
|
3296
|
+
if (result.isError) {
|
|
3297
|
+
return { status: "not_found", message: capText(text) };
|
|
3298
|
+
}
|
|
3299
|
+
return {
|
|
3300
|
+
status: "ok",
|
|
3301
|
+
body: {
|
|
3302
|
+
...result.structuredContent !== void 0 ? { structured: result.structuredContent } : {},
|
|
3303
|
+
...discovered.outputSchema ? { outputSchema: discovered.outputSchema } : {},
|
|
3304
|
+
...text !== void 0 ? { text } : {}
|
|
3305
|
+
}
|
|
3306
|
+
};
|
|
3307
|
+
} catch (err) {
|
|
3308
|
+
if (err instanceof PreviewTimeoutError) {
|
|
3309
|
+
return { status: "unavailable", reason: "timeout" };
|
|
3310
|
+
}
|
|
3311
|
+
return {
|
|
3312
|
+
status: "unavailable",
|
|
3313
|
+
reason: "connector_error",
|
|
3314
|
+
message: capText(err instanceof Error ? err.message : String(err))
|
|
3315
|
+
};
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
function hashPreviewBody(body) {
|
|
3319
|
+
if (body.structured !== void 0) {
|
|
3320
|
+
return computeContentHash2({ kind: "jcs", version: "1" }, body.structured);
|
|
3321
|
+
}
|
|
3322
|
+
return computeContentHash2({ kind: "text", version: "1" }, body.text ?? "");
|
|
3323
|
+
}
|
|
3324
|
+
async function computeSubmissionPreviewHash(integrationManager2, integrationId, toolName, actionArgs) {
|
|
3325
|
+
const preview = getToolPreviewConfig(integrationId, toolName);
|
|
3326
|
+
if (!preview || preview.version) return void 0;
|
|
3327
|
+
const mapped = mapPreviewArgs(preview, actionArgs);
|
|
3328
|
+
if (previewHasNoTarget(preview, mapped)) return void 0;
|
|
3329
|
+
const read = await readPreview(integrationManager2, integrationId, preview.tool, mapped);
|
|
3330
|
+
if (read.status !== "ok") return void 0;
|
|
3331
|
+
return hashPreviewBody(read.body);
|
|
3332
|
+
}
|
|
3333
|
+
async function previewChangedSinceSubmission(integrationManager2, integrationId, toolName, actionArgs, previousHash) {
|
|
3334
|
+
const preview = getToolPreviewConfig(integrationId, toolName);
|
|
3335
|
+
if (!preview) return true;
|
|
3336
|
+
const mapped = mapPreviewArgs(preview, actionArgs);
|
|
3337
|
+
const read = await readPreview(integrationManager2, integrationId, preview.tool, mapped);
|
|
3338
|
+
if (read.status !== "ok") return true;
|
|
3339
|
+
return hashPreviewBody(read.body) !== previousHash;
|
|
3340
|
+
}
|
|
3341
|
+
async function buildInternalPreview(integrationManager2, integrationId, toolName, actionArgs) {
|
|
3342
|
+
const preview = getToolPreviewConfig(integrationId, toolName);
|
|
3343
|
+
if (!preview) return { status: "none" };
|
|
3344
|
+
const mappedArgs = mapPreviewArgs(preview, actionArgs);
|
|
3345
|
+
if (previewHasNoTarget(preview, mappedArgs)) return { status: "none", reason: "no_target" };
|
|
3346
|
+
const read1 = await readPreview(integrationManager2, integrationId, preview.tool, mappedArgs);
|
|
3347
|
+
if (read1.status !== "ok") return read1;
|
|
3348
|
+
const ok = (extra = {}) => ({
|
|
3349
|
+
status: "ok",
|
|
3350
|
+
integration: integrationId,
|
|
3351
|
+
tool: preview.tool,
|
|
3352
|
+
body: read1.body,
|
|
3353
|
+
...preview.fields ? { fields: preview.fields } : {},
|
|
3354
|
+
...extra
|
|
3355
|
+
});
|
|
3356
|
+
if (!preview.version) {
|
|
3357
|
+
return ok();
|
|
3358
|
+
}
|
|
3359
|
+
const approved = actionArgs[preview.version.arg];
|
|
3360
|
+
if (typeof approved !== "string" && typeof approved !== "number") {
|
|
3361
|
+
return ok();
|
|
3362
|
+
}
|
|
3363
|
+
const { [preview.version.arg]: _omit, ...withoutVersion } = actionArgs;
|
|
3364
|
+
const mappedCurrentArgs = mapPreviewArgs(preview, withoutVersion);
|
|
3365
|
+
const read2 = await readPreview(integrationManager2, integrationId, preview.tool, mappedCurrentArgs);
|
|
3366
|
+
if (read2.status !== "ok" || read2.body.structured === void 0) {
|
|
3367
|
+
return ok();
|
|
3368
|
+
}
|
|
3369
|
+
const current = read2.body.structured[preview.version.field];
|
|
3370
|
+
if (typeof current !== "string" && typeof current !== "number") {
|
|
3371
|
+
return ok();
|
|
3372
|
+
}
|
|
3373
|
+
const stale = String(current) !== String(approved);
|
|
3374
|
+
return ok({
|
|
3375
|
+
version: {
|
|
3376
|
+
field: preview.version.field,
|
|
3377
|
+
approved,
|
|
3378
|
+
current,
|
|
3379
|
+
stale,
|
|
3380
|
+
...stale ? { currentBody: read2.body } : {}
|
|
3381
|
+
}
|
|
3382
|
+
});
|
|
3383
|
+
}
|
|
3384
|
+
|
|
3072
3385
|
// src/lib/ticket-verify.ts
|
|
3073
3386
|
import { verifyTicketSignature } from "@hap/core";
|
|
3074
3387
|
var TicketBindingMismatchError = class extends Error {
|
|
@@ -4042,13 +4355,20 @@ function createGatedToolHandlerInner(tool, integrationManager2, state2) {
|
|
|
4042
4355
|
toolArgs: enrichedArgs,
|
|
4043
4356
|
executionContext: wireExecCtx
|
|
4044
4357
|
});
|
|
4358
|
+
const previewHash = await computeSubmissionPreviewHash(
|
|
4359
|
+
integrationManager2,
|
|
4360
|
+
tool.integrationId,
|
|
4361
|
+
tool.originalName,
|
|
4362
|
+
enrichedArgs
|
|
4363
|
+
);
|
|
4045
4364
|
state2.proposalSubmissions.record({
|
|
4046
4365
|
proposalId: proposal.id,
|
|
4047
4366
|
tool: tool.namespacedName,
|
|
4048
4367
|
toolArgs: enrichedArgs,
|
|
4049
4368
|
executionContext: wireExecCtx,
|
|
4050
4369
|
authorizationId: authzId,
|
|
4051
|
-
profileId: auth.profileId
|
|
4370
|
+
profileId: auth.profileId,
|
|
4371
|
+
previewHash
|
|
4052
4372
|
});
|
|
4053
4373
|
return {
|
|
4054
4374
|
content: [{
|
|
@@ -4214,13 +4534,20 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
|
|
|
4214
4534
|
executionContext: wireExecCtx,
|
|
4215
4535
|
pendingApprovers: uniqueApprovers
|
|
4216
4536
|
});
|
|
4537
|
+
const previewHash = await computeSubmissionPreviewHash(
|
|
4538
|
+
integrationManager2,
|
|
4539
|
+
tool.integrationId,
|
|
4540
|
+
tool.originalName,
|
|
4541
|
+
enrichedArgs
|
|
4542
|
+
);
|
|
4217
4543
|
state2.proposalSubmissions.record({
|
|
4218
4544
|
proposalId: proposal.id,
|
|
4219
4545
|
tool: tool.namespacedName,
|
|
4220
4546
|
toolArgs: enrichedArgs,
|
|
4221
4547
|
executionContext: wireExecCtx,
|
|
4222
4548
|
authorizationId: authzId,
|
|
4223
|
-
profileId: auth.profileId
|
|
4549
|
+
profileId: auth.profileId,
|
|
4550
|
+
previewHash
|
|
4224
4551
|
});
|
|
4225
4552
|
return {
|
|
4226
4553
|
content: [{
|
|
@@ -4955,6 +5282,22 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
4955
5282
|
`[Suveren MCP] Proposal ${proposal.id}: ticket ${receiptId} was replayed by the AS with no local execution record \u2014 executing once (lost-response recovery).`
|
|
4956
5283
|
);
|
|
4957
5284
|
}
|
|
5285
|
+
if (submitted.previewHash) {
|
|
5286
|
+
const changed = await previewChangedSinceSubmission(
|
|
5287
|
+
integrationManager2,
|
|
5288
|
+
integrationId,
|
|
5289
|
+
toolName,
|
|
5290
|
+
proposal.toolArgs,
|
|
5291
|
+
submitted.previewHash
|
|
5292
|
+
);
|
|
5293
|
+
if (changed) {
|
|
5294
|
+
state2.executionJournal.complete(receiptId, "failed", "changed");
|
|
5295
|
+
return {
|
|
5296
|
+
text: `Proposal ${proposal.id} was approved, but the record changed in ${integrationId} since it was requested \u2014 nothing was done. A new attempt needs a new request and approval.`,
|
|
5297
|
+
isError: true
|
|
5298
|
+
};
|
|
5299
|
+
}
|
|
5300
|
+
}
|
|
4958
5301
|
try {
|
|
4959
5302
|
let outgoingArgs = proposal.toolArgs;
|
|
4960
5303
|
if (discovered && receiptId) {
|
|
@@ -4973,7 +5316,7 @@ async function executeCommitted(proposal, state2, integrationManager2) {
|
|
|
4973
5316
|
}
|
|
4974
5317
|
const resultText = result.content?.[0]?.text ?? JSON.stringify(result);
|
|
4975
5318
|
if (result.isError) {
|
|
4976
|
-
state2.executionJournal.complete(receiptId, "failed", "refused");
|
|
5319
|
+
state2.executionJournal.complete(receiptId, "failed", "refused", resultText);
|
|
4977
5320
|
return {
|
|
4978
5321
|
text: `Proposal ${proposal.id} was approved, but ${integrationId} refused to run it \u2014 nothing was done.
|
|
4979
5322
|
Reason: ${resultText}`,
|
|
@@ -5003,7 +5346,7 @@ function localOutcomeOfExecuted(proposal, state2) {
|
|
|
5003
5346
|
const integration = proposal.tool.split("__")[0];
|
|
5004
5347
|
if (entry.state === "failed") {
|
|
5005
5348
|
return {
|
|
5006
|
-
text: 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.` : `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.`,
|
|
5349
|
+
text: 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.` : 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.` : `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.`,
|
|
5007
5350
|
isError: true
|
|
5008
5351
|
};
|
|
5009
5352
|
}
|
|
@@ -5308,21 +5651,21 @@ function createMcpServer(state2, integrationManager2) {
|
|
|
5308
5651
|
}
|
|
5309
5652
|
|
|
5310
5653
|
// src/lib/agent-contact.ts
|
|
5311
|
-
import { existsSync as
|
|
5312
|
-
import { dirname as
|
|
5654
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync11, readFileSync as readFileSync15, renameSync as renameSync5, writeFileSync as writeFileSync11 } from "fs";
|
|
5655
|
+
import { dirname as dirname11, join as join14 } from "path";
|
|
5313
5656
|
var FILE = "agent-contact.json";
|
|
5314
5657
|
var REWRITE_AFTER_MS = 6e4;
|
|
5315
5658
|
var AgentContactStore = class {
|
|
5316
5659
|
path;
|
|
5317
5660
|
current;
|
|
5318
5661
|
constructor(dataDir2) {
|
|
5319
|
-
this.path =
|
|
5662
|
+
this.path = join14(dataDir2, FILE);
|
|
5320
5663
|
this.current = this.load();
|
|
5321
5664
|
}
|
|
5322
5665
|
load() {
|
|
5323
|
-
if (!
|
|
5666
|
+
if (!existsSync14(this.path)) return null;
|
|
5324
5667
|
try {
|
|
5325
|
-
const v = JSON.parse(
|
|
5668
|
+
const v = JSON.parse(readFileSync15(this.path, "utf8"));
|
|
5326
5669
|
if (typeof v.clientName !== "string" || typeof v.firstSeenAt !== "string" || typeof v.lastSeenAt !== "string") return null;
|
|
5327
5670
|
return { clientName: v.clientName, clientVersion: v.clientVersion, firstSeenAt: v.firstSeenAt, lastSeenAt: v.lastSeenAt };
|
|
5328
5671
|
} catch {
|
|
@@ -5345,10 +5688,10 @@ var AgentContactStore = class {
|
|
|
5345
5688
|
};
|
|
5346
5689
|
if (!due) return;
|
|
5347
5690
|
try {
|
|
5348
|
-
|
|
5691
|
+
mkdirSync11(dirname11(this.path), { recursive: true });
|
|
5349
5692
|
const tmp = `${this.path}.tmp`;
|
|
5350
|
-
|
|
5351
|
-
|
|
5693
|
+
writeFileSync11(tmp, JSON.stringify(this.current, null, 2));
|
|
5694
|
+
renameSync5(tmp, this.path);
|
|
5352
5695
|
} catch (err) {
|
|
5353
5696
|
console.error("[Suveren MCP] Could not save agent contact:", err);
|
|
5354
5697
|
}
|
|
@@ -5382,16 +5725,16 @@ function verifyGateContentHashes(content, auth) {
|
|
|
5382
5725
|
}
|
|
5383
5726
|
|
|
5384
5727
|
// src/lib/integration-registry.ts
|
|
5385
|
-
import { mkdirSync as
|
|
5386
|
-
import { homedir as
|
|
5387
|
-
import { dirname as
|
|
5388
|
-
var DEFAULT_DATA_DIR = process.env.SUVEREN_DATA_DIR ??
|
|
5728
|
+
import { mkdirSync as mkdirSync12, readFileSync as readFileSync16, writeFileSync as writeFileSync12, existsSync as existsSync15 } from "fs";
|
|
5729
|
+
import { homedir as homedir10 } from "os";
|
|
5730
|
+
import { dirname as dirname12, join as join15 } from "path";
|
|
5731
|
+
var DEFAULT_DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join15(homedir10(), ".suveren");
|
|
5389
5732
|
var IntegrationRegistry = class {
|
|
5390
5733
|
integrations = /* @__PURE__ */ new Map();
|
|
5391
5734
|
filePath;
|
|
5392
5735
|
constructor(dataDir2) {
|
|
5393
5736
|
const dir = dataDir2 ?? DEFAULT_DATA_DIR;
|
|
5394
|
-
this.filePath =
|
|
5737
|
+
this.filePath = join15(dir, "integrations.json");
|
|
5395
5738
|
this.load();
|
|
5396
5739
|
}
|
|
5397
5740
|
// ─── CRUD ───────────────────────────────────────────────────────────────
|
|
@@ -5422,12 +5765,12 @@ var IntegrationRegistry = class {
|
|
|
5422
5765
|
}
|
|
5423
5766
|
// ─── Load / Save ────────────────────────────────────────────────────────
|
|
5424
5767
|
load() {
|
|
5425
|
-
if (!
|
|
5426
|
-
|
|
5768
|
+
if (!existsSync15(this.filePath)) {
|
|
5769
|
+
mkdirSync12(dirname12(this.filePath), { recursive: true });
|
|
5427
5770
|
return;
|
|
5428
5771
|
}
|
|
5429
5772
|
try {
|
|
5430
|
-
const raw =
|
|
5773
|
+
const raw = readFileSync16(this.filePath, "utf-8");
|
|
5431
5774
|
const data = JSON.parse(raw);
|
|
5432
5775
|
const migrated = data.integrations.map((i) => {
|
|
5433
5776
|
if ("toolGating" in i && !("profile" in i)) {
|
|
@@ -5456,79 +5799,19 @@ var IntegrationRegistry = class {
|
|
|
5456
5799
|
version: 1,
|
|
5457
5800
|
integrations: Array.from(this.integrations.values())
|
|
5458
5801
|
};
|
|
5459
|
-
|
|
5460
|
-
|
|
5802
|
+
mkdirSync12(dirname12(this.filePath), { recursive: true });
|
|
5803
|
+
writeFileSync12(this.filePath, JSON.stringify(data, null, 2), { encoding: "utf-8" });
|
|
5461
5804
|
}
|
|
5462
5805
|
};
|
|
5463
5806
|
|
|
5464
5807
|
// src/lib/integration-manager.ts
|
|
5465
|
-
import { homedir as
|
|
5466
|
-
import { join as
|
|
5467
|
-
import { existsSync as
|
|
5808
|
+
import { homedir as homedir11 } from "os";
|
|
5809
|
+
import { join as join16, delimiter } from "path";
|
|
5810
|
+
import { existsSync as existsSync16, writeFileSync as writeFileSync13, mkdirSync as mkdirSync13, readFileSync as readFileSync17, rmSync } from "fs";
|
|
5468
5811
|
import { execFile } from "child_process";
|
|
5469
5812
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
5470
5813
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
5471
5814
|
|
|
5472
|
-
// src/lib/manifest-loader.ts
|
|
5473
|
-
import { readFileSync as readFileSync15, existsSync as existsSync14 } from "fs";
|
|
5474
|
-
import { join as join14, resolve } from "path";
|
|
5475
|
-
var EXACT_SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
5476
|
-
function isExactSemver(value) {
|
|
5477
|
-
return typeof value === "string" && EXACT_SEMVER_RE.test(value);
|
|
5478
|
-
}
|
|
5479
|
-
function invalidNpmPinReason(manifest) {
|
|
5480
|
-
if (!manifest.npmPackage) return null;
|
|
5481
|
-
if (manifest.npmVersion === void 0) {
|
|
5482
|
-
return `npmPackage "${manifest.npmPackage}" is set but npmVersion is missing \u2014 every connector manifest must pin an exact tested version`;
|
|
5483
|
-
}
|
|
5484
|
-
if (!isExactSemver(manifest.npmVersion)) {
|
|
5485
|
-
return `npmVersion ${JSON.stringify(manifest.npmVersion)} for "${manifest.npmPackage}" is not an exact semver (e.g. "1.2.3") \u2014 ranges, dist-tags like "latest", git specs, URLs, and file paths are not allowed`;
|
|
5486
|
-
}
|
|
5487
|
-
return null;
|
|
5488
|
-
}
|
|
5489
|
-
var manifests = /* @__PURE__ */ new Map();
|
|
5490
|
-
function loadManifests(integrationsDir) {
|
|
5491
|
-
const dir = resolve(
|
|
5492
|
-
integrationsDir ?? process.env.SUVEREN_MANIFESTS_DIR ?? join14(import.meta.dirname ?? __dirname, "..", "..", "..", "..", "content", "integrations")
|
|
5493
|
-
);
|
|
5494
|
-
const indexPath = join14(dir, "index.json");
|
|
5495
|
-
if (!existsSync14(indexPath)) {
|
|
5496
|
-
console.error(`[ManifestLoader] No index.json found at ${indexPath}, skipping manifest loading`);
|
|
5497
|
-
return 0;
|
|
5498
|
-
}
|
|
5499
|
-
let index;
|
|
5500
|
-
try {
|
|
5501
|
-
index = JSON.parse(readFileSync15(indexPath, "utf-8"));
|
|
5502
|
-
} catch (err) {
|
|
5503
|
-
console.error(`[ManifestLoader] Failed to parse ${indexPath}:`, err);
|
|
5504
|
-
return 0;
|
|
5505
|
-
}
|
|
5506
|
-
let loaded = 0;
|
|
5507
|
-
for (const [id, relativePath] of Object.entries(index.integrations)) {
|
|
5508
|
-
const manifestPath = join14(dir, relativePath);
|
|
5509
|
-
try {
|
|
5510
|
-
const manifest = JSON.parse(readFileSync15(manifestPath, "utf-8"));
|
|
5511
|
-
const pinReason = invalidNpmPinReason(manifest);
|
|
5512
|
-
if (pinReason) {
|
|
5513
|
-
console.error(`[ManifestLoader] Refusing manifest ${id} (${manifestPath}): ${pinReason}`);
|
|
5514
|
-
continue;
|
|
5515
|
-
}
|
|
5516
|
-
manifests.set(id, manifest);
|
|
5517
|
-
loaded++;
|
|
5518
|
-
} catch (err) {
|
|
5519
|
-
console.error(`[ManifestLoader] Failed to load manifest ${id} from ${manifestPath}:`, err);
|
|
5520
|
-
}
|
|
5521
|
-
}
|
|
5522
|
-
console.error(`[ManifestLoader] Loaded ${loaded} integration manifest(s) from ${dir}`);
|
|
5523
|
-
return loaded;
|
|
5524
|
-
}
|
|
5525
|
-
function getManifest(id) {
|
|
5526
|
-
return manifests.get(id);
|
|
5527
|
-
}
|
|
5528
|
-
function getAllManifests() {
|
|
5529
|
-
return Array.from(manifests.values());
|
|
5530
|
-
}
|
|
5531
|
-
|
|
5532
5815
|
// src/lib/remote-auth-preflight.ts
|
|
5533
5816
|
var AUTH_HEADER_RE = /^authorization:\s*(.+)$/i;
|
|
5534
5817
|
function remotePreflightTarget(command, args) {
|
|
@@ -5654,8 +5937,8 @@ function inheritedConnectorEnv(parent, passEnv = []) {
|
|
|
5654
5937
|
}
|
|
5655
5938
|
|
|
5656
5939
|
// src/lib/integration-manager.ts
|
|
5657
|
-
var DEFAULT_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ??
|
|
5658
|
-
var INTEGRATIONS_DIR = process.env.SUVEREN_INTEGRATIONS_DIR ??
|
|
5940
|
+
var DEFAULT_DATA_DIR2 = process.env.SUVEREN_DATA_DIR ?? join16(homedir11(), ".suveren");
|
|
5941
|
+
var INTEGRATIONS_DIR = process.env.SUVEREN_INTEGRATIONS_DIR ?? join16(DEFAULT_DATA_DIR2, "integrations");
|
|
5659
5942
|
var OFFLINE_MODE = process.env.SUVEREN_OFFLINE === "1";
|
|
5660
5943
|
var installLock = Promise.resolve();
|
|
5661
5944
|
function withInstallLock(task) {
|
|
@@ -5663,7 +5946,7 @@ function withInstallLock(task) {
|
|
|
5663
5946
|
installLock = run.then(() => void 0, () => void 0);
|
|
5664
5947
|
return run;
|
|
5665
5948
|
}
|
|
5666
|
-
var INTEGRATIONS_BIN =
|
|
5949
|
+
var INTEGRATIONS_BIN = join16(INTEGRATIONS_DIR, "node_modules", ".bin");
|
|
5667
5950
|
function getIntegrationsBinDir() {
|
|
5668
5951
|
return INTEGRATIONS_BIN;
|
|
5669
5952
|
}
|
|
@@ -5672,12 +5955,12 @@ function buildPath() {
|
|
|
5672
5955
|
return [INTEGRATIONS_BIN, base].join(delimiter);
|
|
5673
5956
|
}
|
|
5674
5957
|
function ensureIntegrationsDir() {
|
|
5675
|
-
if (!
|
|
5676
|
-
|
|
5958
|
+
if (!existsSync16(INTEGRATIONS_DIR)) {
|
|
5959
|
+
mkdirSync13(INTEGRATIONS_DIR, { recursive: true });
|
|
5677
5960
|
}
|
|
5678
|
-
const pkgPath =
|
|
5679
|
-
if (!
|
|
5680
|
-
|
|
5961
|
+
const pkgPath = join16(INTEGRATIONS_DIR, "package.json");
|
|
5962
|
+
if (!existsSync16(pkgPath)) {
|
|
5963
|
+
writeFileSync13(pkgPath, JSON.stringify({ name: "suveren-integrations", version: "1.0.0", private: true }, null, 2));
|
|
5681
5964
|
}
|
|
5682
5965
|
}
|
|
5683
5966
|
function readAgeOf(config) {
|
|
@@ -5766,19 +6049,19 @@ var IntegrationManager = class {
|
|
|
5766
6049
|
* exist, and treat anything else as not-installed (and reinstallable).
|
|
5767
6050
|
*/
|
|
5768
6051
|
isUsableInstall(npmPackage) {
|
|
5769
|
-
const pkgDir =
|
|
5770
|
-
const pkgJson =
|
|
5771
|
-
if (!
|
|
6052
|
+
const pkgDir = join16(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"));
|
|
6053
|
+
const pkgJson = join16(pkgDir, "package.json");
|
|
6054
|
+
if (!existsSync16(pkgDir) || !existsSync16(pkgJson)) return false;
|
|
5772
6055
|
try {
|
|
5773
|
-
const pkg = JSON.parse(
|
|
6056
|
+
const pkg = JSON.parse(readFileSync17(pkgJson, "utf8"));
|
|
5774
6057
|
const defaultBinName = (pkg.name ?? npmPackage).split("/").pop() ?? "";
|
|
5775
6058
|
const binMap = typeof pkg.bin === "string" ? { [defaultBinName]: pkg.bin } : pkg.bin ?? {};
|
|
5776
6059
|
const binEntries = Object.entries(binMap);
|
|
5777
6060
|
for (const [binName, target] of binEntries) {
|
|
5778
|
-
if (!
|
|
6061
|
+
if (!existsSync16(join16(pkgDir, target))) return false;
|
|
5779
6062
|
if (!this.binShimExists(binName)) return false;
|
|
5780
6063
|
}
|
|
5781
|
-
if (binEntries.length === 0 && pkg.main && !
|
|
6064
|
+
if (binEntries.length === 0 && pkg.main && !existsSync16(join16(pkgDir, pkg.main))) return false;
|
|
5782
6065
|
return true;
|
|
5783
6066
|
} catch {
|
|
5784
6067
|
return false;
|
|
@@ -5793,9 +6076,9 @@ var IntegrationManager = class {
|
|
|
5793
6076
|
*/
|
|
5794
6077
|
readInstalledVersion(npmPackage) {
|
|
5795
6078
|
if (!this.isUsableInstall(npmPackage)) return null;
|
|
5796
|
-
const pkgJson =
|
|
6079
|
+
const pkgJson = join16(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"), "package.json");
|
|
5797
6080
|
try {
|
|
5798
|
-
const pkg = JSON.parse(
|
|
6081
|
+
const pkg = JSON.parse(readFileSync17(pkgJson, "utf8"));
|
|
5799
6082
|
return typeof pkg.version === "string" ? pkg.version : null;
|
|
5800
6083
|
} catch {
|
|
5801
6084
|
return null;
|
|
@@ -5809,7 +6092,7 @@ var IntegrationManager = class {
|
|
|
5809
6092
|
binShimExists(binName) {
|
|
5810
6093
|
if (!binName) return false;
|
|
5811
6094
|
const candidates = process.platform === "win32" ? [binName, `${binName}.cmd`, `${binName}.CMD`, `${binName}.ps1`] : [binName];
|
|
5812
|
-
return candidates.some((c) =>
|
|
6095
|
+
return candidates.some((c) => existsSync16(join16(INTEGRATIONS_BIN, c)));
|
|
5813
6096
|
}
|
|
5814
6097
|
/**
|
|
5815
6098
|
* The exact version THIS gateway's CURRENTLY LOADED manifest pins
|
|
@@ -5899,8 +6182,8 @@ var IntegrationManager = class {
|
|
|
5899
6182
|
* nothing at all.
|
|
5900
6183
|
*/
|
|
5901
6184
|
async installNow(npmPackage, exactVersion, cleanFirst = true) {
|
|
5902
|
-
const pkgDir =
|
|
5903
|
-
if (cleanFirst &&
|
|
6185
|
+
const pkgDir = join16(INTEGRATIONS_DIR, "node_modules", ...npmPackage.split("/"));
|
|
6186
|
+
if (cleanFirst && existsSync16(pkgDir)) {
|
|
5904
6187
|
console.error(`[IntegrationManager] Removing ${exactVersion ? "outdated" : "unusable"} install of ${npmPackage}`);
|
|
5905
6188
|
try {
|
|
5906
6189
|
rmSync(pkgDir, { recursive: true, force: true });
|
|
@@ -6093,6 +6376,7 @@ var IntegrationManager = class {
|
|
|
6093
6376
|
tool.inputSchema ?? {},
|
|
6094
6377
|
gating?.blockedArgs
|
|
6095
6378
|
),
|
|
6379
|
+
outputSchema: tool.outputSchema,
|
|
6096
6380
|
gating
|
|
6097
6381
|
};
|
|
6098
6382
|
});
|
|
@@ -6257,7 +6541,11 @@ var IntegrationManager = class {
|
|
|
6257
6541
|
type: item.type,
|
|
6258
6542
|
text: item.text ?? JSON.stringify(item)
|
|
6259
6543
|
}));
|
|
6260
|
-
return {
|
|
6544
|
+
return {
|
|
6545
|
+
content,
|
|
6546
|
+
isError: result.isError,
|
|
6547
|
+
structuredContent: result.structuredContent
|
|
6548
|
+
};
|
|
6261
6549
|
} catch (err) {
|
|
6262
6550
|
const message = err instanceof Error ? err.message : String(err);
|
|
6263
6551
|
return {
|
|
@@ -6806,28 +7094,28 @@ function agentAccess(token, opensSession) {
|
|
|
6806
7094
|
}
|
|
6807
7095
|
|
|
6808
7096
|
// src/lib/profile-loader.ts
|
|
6809
|
-
import { readFileSync as
|
|
6810
|
-
import { join as
|
|
7097
|
+
import { readFileSync as readFileSync18, existsSync as existsSync17 } from "fs";
|
|
7098
|
+
import { join as join17, resolve as resolve2 } from "path";
|
|
6811
7099
|
import { registerProfile } from "@hap/core";
|
|
6812
7100
|
function loadProfiles(profilesDir) {
|
|
6813
|
-
const dir = resolve2(profilesDir ?? process.env.SUVEREN_PROFILES_DIR ??
|
|
6814
|
-
const indexPath =
|
|
6815
|
-
if (!
|
|
7101
|
+
const dir = resolve2(profilesDir ?? process.env.SUVEREN_PROFILES_DIR ?? join17(import.meta.dirname ?? __dirname, "..", "..", "..", "..", "..", "hap-profiles"));
|
|
7102
|
+
const indexPath = join17(dir, "index.json");
|
|
7103
|
+
if (!existsSync17(indexPath)) {
|
|
6816
7104
|
warnNoProfiles(`No index.json at ${indexPath}`);
|
|
6817
7105
|
return 0;
|
|
6818
7106
|
}
|
|
6819
7107
|
let index;
|
|
6820
7108
|
try {
|
|
6821
|
-
index = JSON.parse(
|
|
7109
|
+
index = JSON.parse(readFileSync18(indexPath, "utf-8"));
|
|
6822
7110
|
} catch (err) {
|
|
6823
7111
|
console.error(`[ProfileLoader] Failed to parse ${indexPath}:`, err);
|
|
6824
7112
|
return 0;
|
|
6825
7113
|
}
|
|
6826
7114
|
let loaded = 0;
|
|
6827
7115
|
for (const [profileId, relativePath] of Object.entries(index.profiles)) {
|
|
6828
|
-
const profilePath =
|
|
7116
|
+
const profilePath = join17(dir, relativePath);
|
|
6829
7117
|
try {
|
|
6830
|
-
const profile = JSON.parse(
|
|
7118
|
+
const profile = JSON.parse(readFileSync18(profilePath, "utf-8"));
|
|
6831
7119
|
registerProfile(profileId, profile);
|
|
6832
7120
|
const shortName = profileId.split("/").pop()?.replace(/@.*$/, "");
|
|
6833
7121
|
if (shortName && shortName !== profileId) {
|
|
@@ -7165,20 +7453,20 @@ async function controlPlaneMandate(dryRun, request) {
|
|
|
7165
7453
|
}
|
|
7166
7454
|
|
|
7167
7455
|
// src/lib/guides.ts
|
|
7168
|
-
import { existsSync as
|
|
7169
|
-
import { dirname as
|
|
7456
|
+
import { existsSync as existsSync18, readdirSync, readFileSync as readFileSync19 } from "fs";
|
|
7457
|
+
import { dirname as dirname13, join as join18 } from "path";
|
|
7170
7458
|
var FILE_RE = /^(?:(\d+)-)?([a-z0-9][a-z0-9-]*)\.md$/;
|
|
7171
7459
|
function builtinGuidesDir() {
|
|
7172
7460
|
if (process.env.SUVEREN_BUILTIN_GUIDES_DIR) return process.env.SUVEREN_BUILTIN_GUIDES_DIR;
|
|
7173
7461
|
let dir = import.meta.dirname ?? __dirname;
|
|
7174
7462
|
for (let i = 0; i < 8; i++) {
|
|
7175
|
-
const candidate =
|
|
7176
|
-
if (
|
|
7177
|
-
const parent =
|
|
7463
|
+
const candidate = join18(dir, "content", "guides");
|
|
7464
|
+
if (existsSync18(candidate)) return candidate;
|
|
7465
|
+
const parent = dirname13(dir);
|
|
7178
7466
|
if (parent === dir) break;
|
|
7179
7467
|
dir = parent;
|
|
7180
7468
|
}
|
|
7181
|
-
return
|
|
7469
|
+
return join18(import.meta.dirname ?? __dirname, "content", "guides");
|
|
7182
7470
|
}
|
|
7183
7471
|
function parse(body) {
|
|
7184
7472
|
const lines = body.split(/\r?\n/);
|
|
@@ -7189,14 +7477,14 @@ function parse(body) {
|
|
|
7189
7477
|
return { title, summary };
|
|
7190
7478
|
}
|
|
7191
7479
|
function readDir(dir, source) {
|
|
7192
|
-
if (!dir || !
|
|
7480
|
+
if (!dir || !existsSync18(dir)) return [];
|
|
7193
7481
|
const out = [];
|
|
7194
7482
|
for (const file of readdirSync(dir)) {
|
|
7195
7483
|
const m = FILE_RE.exec(file);
|
|
7196
7484
|
if (!m) continue;
|
|
7197
7485
|
let body;
|
|
7198
7486
|
try {
|
|
7199
|
-
body =
|
|
7487
|
+
body = readFileSync19(join18(dir, file), "utf8");
|
|
7200
7488
|
} catch {
|
|
7201
7489
|
continue;
|
|
7202
7490
|
}
|
|
@@ -7506,8 +7794,8 @@ function registerBuiltins(deps, factories = BUILTIN_FACTORIES) {
|
|
|
7506
7794
|
import { decodeMandateBlob as decodeMandateBlob3 } from "@hap/core";
|
|
7507
7795
|
|
|
7508
7796
|
// src/lib/committed-executor.ts
|
|
7509
|
-
import { existsSync as
|
|
7510
|
-
import { dirname as
|
|
7797
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync14, readFileSync as readFileSync20, unlinkSync as unlinkSync6, writeFileSync as writeFileSync14 } from "fs";
|
|
7798
|
+
import { dirname as dirname14, join as join19 } from "path";
|
|
7511
7799
|
var CommittedExecutor = class {
|
|
7512
7800
|
constructor(run) {
|
|
7513
7801
|
this.run = run;
|
|
@@ -7535,7 +7823,7 @@ var ExecutorLock = class {
|
|
|
7535
7823
|
path;
|
|
7536
7824
|
held = false;
|
|
7537
7825
|
constructor(dataDir2) {
|
|
7538
|
-
this.path =
|
|
7826
|
+
this.path = join19(dataDir2, "executor.lock");
|
|
7539
7827
|
}
|
|
7540
7828
|
/** Try to take the lock. Returns the holder's pid when someone else has it. */
|
|
7541
7829
|
acquire() {
|
|
@@ -7543,9 +7831,9 @@ var ExecutorLock = class {
|
|
|
7543
7831
|
if (current && current.pid !== process.pid && isAlive(current.pid)) {
|
|
7544
7832
|
return { held: false, holderPid: current.pid };
|
|
7545
7833
|
}
|
|
7546
|
-
|
|
7834
|
+
mkdirSync14(dirname14(this.path), { recursive: true, mode: 448 });
|
|
7547
7835
|
const lock = { pid: process.pid, startedAt: Math.floor(Date.now() / 1e3) };
|
|
7548
|
-
|
|
7836
|
+
writeFileSync14(this.path, JSON.stringify(lock), { mode: 384 });
|
|
7549
7837
|
this.held = true;
|
|
7550
7838
|
return { held: true };
|
|
7551
7839
|
}
|
|
@@ -7567,9 +7855,9 @@ var ExecutorLock = class {
|
|
|
7567
7855
|
}
|
|
7568
7856
|
}
|
|
7569
7857
|
read() {
|
|
7570
|
-
if (!
|
|
7858
|
+
if (!existsSync19(this.path)) return null;
|
|
7571
7859
|
try {
|
|
7572
|
-
const parsed = JSON.parse(
|
|
7860
|
+
const parsed = JSON.parse(readFileSync20(this.path, "utf8"));
|
|
7573
7861
|
return typeof parsed?.pid === "number" ? parsed : null;
|
|
7574
7862
|
} catch {
|
|
7575
7863
|
return null;
|
|
@@ -7586,20 +7874,20 @@ function isAlive(pid) {
|
|
|
7586
7874
|
}
|
|
7587
7875
|
|
|
7588
7876
|
// bin/http.ts
|
|
7589
|
-
import { homedir as
|
|
7590
|
-
import { join as
|
|
7877
|
+
import { homedir as homedir12 } from "os";
|
|
7878
|
+
import { join as join21 } from "path";
|
|
7591
7879
|
|
|
7592
7880
|
// src/lib/mcp-as-tracker.ts
|
|
7593
|
-
import { existsSync as
|
|
7594
|
-
import { dirname as
|
|
7881
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync15, readFileSync as readFileSync21, writeFileSync as writeFileSync15 } from "fs";
|
|
7882
|
+
import { dirname as dirname15, join as join20 } from "path";
|
|
7595
7883
|
function trackerPath(dataDir2) {
|
|
7596
|
-
return
|
|
7884
|
+
return join20(dataDir2, "mcp-as-pairing.json");
|
|
7597
7885
|
}
|
|
7598
7886
|
function readMcpPairedAsUrl(dataDir2) {
|
|
7599
7887
|
const path = trackerPath(dataDir2);
|
|
7600
|
-
if (!
|
|
7888
|
+
if (!existsSync20(path)) return null;
|
|
7601
7889
|
try {
|
|
7602
|
-
const data = JSON.parse(
|
|
7890
|
+
const data = JSON.parse(readFileSync21(path, "utf-8"));
|
|
7603
7891
|
return typeof data.asUrl === "string" ? data.asUrl : null;
|
|
7604
7892
|
} catch {
|
|
7605
7893
|
return null;
|
|
@@ -7607,12 +7895,12 @@ function readMcpPairedAsUrl(dataDir2) {
|
|
|
7607
7895
|
}
|
|
7608
7896
|
function writeMcpPairedAsUrl(dataDir2, asUrl) {
|
|
7609
7897
|
const path = trackerPath(dataDir2);
|
|
7610
|
-
|
|
7611
|
-
|
|
7898
|
+
mkdirSync15(dirname15(path), { recursive: true, mode: 448 });
|
|
7899
|
+
writeFileSync15(path, JSON.stringify({ asUrl }, null, 2), { encoding: "utf-8", mode: 384 });
|
|
7612
7900
|
}
|
|
7613
7901
|
|
|
7614
7902
|
// bin/http.ts
|
|
7615
|
-
var dataDir = process.env.SUVEREN_DATA_DIR ??
|
|
7903
|
+
var dataDir = process.env.SUVEREN_DATA_DIR ?? join21(homedir12(), ".suveren");
|
|
7616
7904
|
var spUrl = resolveAsUrl(dataDir);
|
|
7617
7905
|
validatePinTlsForUrl(resolvePinTls(dataDir), spUrl);
|
|
7618
7906
|
var port = parseInt(process.env.SUVEREN_MCP_PORT ?? "3430", 10);
|
|
@@ -7822,6 +8110,29 @@ app.post("/internal/resync-gates", internalOnly, async (_req, res) => {
|
|
|
7822
8110
|
}
|
|
7823
8111
|
res.json({ ok: true, synced, orphaned });
|
|
7824
8112
|
});
|
|
8113
|
+
app.post("/internal/preview", internalOnly, async (req, res) => {
|
|
8114
|
+
const { proposalId, tool, toolArgs } = req.body;
|
|
8115
|
+
if (!proposalId || !tool || typeof toolArgs !== "object" || toolArgs === null) {
|
|
8116
|
+
res.status(400).json({ error: "Missing proposalId, tool, or toolArgs" });
|
|
8117
|
+
return;
|
|
8118
|
+
}
|
|
8119
|
+
const sep = tool.indexOf("__");
|
|
8120
|
+
if (sep < 0) {
|
|
8121
|
+
res.status(400).json({ error: `Invalid tool name: ${tool}` });
|
|
8122
|
+
return;
|
|
8123
|
+
}
|
|
8124
|
+
const integrationId = tool.slice(0, sep);
|
|
8125
|
+
const toolName = tool.slice(sep + 2);
|
|
8126
|
+
const result = await buildInternalPreview(integrationManager, integrationId, toolName, toolArgs);
|
|
8127
|
+
state.previewLog.record({
|
|
8128
|
+
ts: Date.now(),
|
|
8129
|
+
proposalId,
|
|
8130
|
+
integrationId,
|
|
8131
|
+
tool: toolName,
|
|
8132
|
+
status: result.status
|
|
8133
|
+
});
|
|
8134
|
+
res.json(result);
|
|
8135
|
+
});
|
|
7825
8136
|
app.post("/internal/add-integration", internalOnly, async (req, res) => {
|
|
7826
8137
|
try {
|
|
7827
8138
|
const config = req.body;
|
|
@@ -8315,7 +8626,7 @@ app.listen(port, bindHost, () => {
|
|
|
8315
8626
|
(proposal) => executeCommitted(proposal, state, integrationManager)
|
|
8316
8627
|
);
|
|
8317
8628
|
installCommittedExecutor(committedExecutor);
|
|
8318
|
-
const dataDir2 = process.env.SUVEREN_DATA_DIR ??
|
|
8629
|
+
const dataDir2 = process.env.SUVEREN_DATA_DIR ?? join21(homedir12(), ".suveren");
|
|
8319
8630
|
const executorLock = new ExecutorLock(dataDir2);
|
|
8320
8631
|
const lock = executorLock.acquire();
|
|
8321
8632
|
if (!lock.held) {
|