@gendigital/sage-openclaw 0.13.0 → 0.14.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/dist/index.cjs +183 -74
- package/dist/index.cjs.map +4 -4
- package/dist/mcp-server.cjs +385 -90
- package/dist/mcp-server.cjs.map +4 -4
- package/dist/model-download-worker.cjs +1 -1
- package/dist/model-download-worker.cjs.map +2 -2
- package/dist/skill-upload-worker.cjs +129 -42
- package/dist/skill-upload-worker.cjs.map +4 -4
- package/openclaw.plugin.json +1 -1
- package/package.json +3 -3
- package/resources/skills/security-awareness/SKILL.md +2 -2
package/dist/mcp-server.cjs
CHANGED
|
@@ -46,6 +46,39 @@ function getProcEnv() {
|
|
|
46
46
|
function getHomeDir() {
|
|
47
47
|
return getProcEnv().HOME || (0, import_node_os.homedir)();
|
|
48
48
|
}
|
|
49
|
+
async function renameWithRetry(from, to, attempts = 5) {
|
|
50
|
+
if (process.platform !== "win32") {
|
|
51
|
+
await fsPromises.rename(from, to);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
for (let i = 0; i < attempts; i++) {
|
|
55
|
+
try {
|
|
56
|
+
await fsPromises.rename(from, to);
|
|
57
|
+
return;
|
|
58
|
+
} catch (err) {
|
|
59
|
+
const code = err.code;
|
|
60
|
+
const transient = code === "EPERM" || code === "EACCES" || code === "EBUSY";
|
|
61
|
+
if (!transient || i === attempts - 1)
|
|
62
|
+
throw err;
|
|
63
|
+
await new Promise((resolve2) => setTimeout(resolve2, 10 * 2 ** i));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function atomicWriteJson(path2, data) {
|
|
68
|
+
await fsPromises.mkdir((0, import_node_path.dirname)(path2), { recursive: true });
|
|
69
|
+
const tmp = `${path2}.${(0, import_node_crypto.randomBytes)(6).toString("hex")}.tmp`;
|
|
70
|
+
try {
|
|
71
|
+
await fsPromises.writeFile(tmp, `${JSON.stringify(data, null, 2)}
|
|
72
|
+
`, { mode: 384 });
|
|
73
|
+
await renameWithRetry(tmp, path2);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
try {
|
|
76
|
+
await fsPromises.unlink(tmp);
|
|
77
|
+
} catch {
|
|
78
|
+
}
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
49
82
|
async function removeStaleFileLock(lockPath, staleAgeMs) {
|
|
50
83
|
try {
|
|
51
84
|
const s = await fsPromises.stat(lockPath);
|
|
@@ -86,13 +119,15 @@ async function withFileLock(filePath, callback) {
|
|
|
86
119
|
await release();
|
|
87
120
|
}
|
|
88
121
|
}
|
|
89
|
-
var fs, fsPromises, import_node_os, import_promises, name1, name2;
|
|
122
|
+
var import_node_crypto, fs, fsPromises, import_node_os, import_node_path, import_promises, name1, name2;
|
|
90
123
|
var init_file_utils = __esm({
|
|
91
124
|
"../core/dist/file-utils.js"() {
|
|
92
125
|
"use strict";
|
|
126
|
+
import_node_crypto = require("node:crypto");
|
|
93
127
|
fs = __toESM(require("node:fs"), 1);
|
|
94
128
|
fsPromises = __toESM(require("node:fs/promises"), 1);
|
|
95
129
|
import_node_os = require("node:os");
|
|
130
|
+
import_node_path = require("node:path");
|
|
96
131
|
import_promises = require("node:timers/promises");
|
|
97
132
|
name1 = "read";
|
|
98
133
|
name2 = "File";
|
|
@@ -4346,38 +4381,48 @@ var init_types2 = __esm({
|
|
|
4346
4381
|
});
|
|
4347
4382
|
|
|
4348
4383
|
// ../core/dist/config.js
|
|
4384
|
+
function skillCacheTtlMs(config2) {
|
|
4385
|
+
return Math.max(1, config2.skill_check.cache_ttl_days) * MS_PER_DAY;
|
|
4386
|
+
}
|
|
4387
|
+
function isFreshTimestamp(ts, ttlMs, now = Date.now()) {
|
|
4388
|
+
if (Number.isNaN(ts))
|
|
4389
|
+
return false;
|
|
4390
|
+
if (ts - now > CLOCK_SKEW_TOLERANCE_MS)
|
|
4391
|
+
return false;
|
|
4392
|
+
return now - ts < ttlMs;
|
|
4393
|
+
}
|
|
4349
4394
|
function resolvedSageDir() {
|
|
4350
4395
|
return resolvePath(SAGE_DIR);
|
|
4351
4396
|
}
|
|
4352
4397
|
function defaultConfigPath() {
|
|
4353
|
-
return (0,
|
|
4398
|
+
return (0, import_node_path2.join)(resolvedSageDir(), "config.json");
|
|
4354
4399
|
}
|
|
4355
4400
|
function defaultCachePath() {
|
|
4356
|
-
return (0,
|
|
4401
|
+
return (0, import_node_path2.join)(resolvedSageDir(), "cache.json");
|
|
4357
4402
|
}
|
|
4358
4403
|
function defaultExceptionsPath() {
|
|
4359
|
-
return (0,
|
|
4404
|
+
return (0, import_node_path2.join)(resolvedSageDir(), "exceptions.json");
|
|
4360
4405
|
}
|
|
4361
4406
|
function defaultAuditPath() {
|
|
4362
|
-
return (0,
|
|
4407
|
+
return (0, import_node_path2.join)(resolvedSageDir(), "audit.jsonl");
|
|
4363
4408
|
}
|
|
4364
4409
|
function defaultOperationalLogPath() {
|
|
4365
|
-
return (0,
|
|
4410
|
+
return (0, import_node_path2.join)(resolvedSageDir(), "operational.jsonl");
|
|
4366
4411
|
}
|
|
4367
4412
|
function resolvePath(pathStr) {
|
|
4368
4413
|
if (pathStr.startsWith("~/") || pathStr === "~") {
|
|
4369
4414
|
const home = getHomeDir();
|
|
4370
|
-
return (0,
|
|
4415
|
+
return (0, import_node_path2.join)(home, pathStr.slice(1));
|
|
4371
4416
|
}
|
|
4372
4417
|
return pathStr;
|
|
4373
4418
|
}
|
|
4374
4419
|
function isWithinDirectory(baseDir, targetPath) {
|
|
4375
|
-
const rel = (0,
|
|
4420
|
+
const rel = (0, import_node_path2.relative)(baseDir, targetPath);
|
|
4376
4421
|
if (rel === "")
|
|
4377
4422
|
return true;
|
|
4378
|
-
if ((0,
|
|
4423
|
+
if ((0, import_node_path2.isAbsolute)(rel))
|
|
4379
4424
|
return false;
|
|
4380
|
-
return rel !== ".." && !rel.startsWith(`..${
|
|
4425
|
+
return rel !== ".." && !rel.startsWith(`..${import_node_path2.sep}`);
|
|
4381
4426
|
}
|
|
4382
4427
|
function normalizeStateFilePath(configuredPath, fallbackPath, field, logger2) {
|
|
4383
4428
|
const sageDir = resolvedSageDir();
|
|
@@ -4390,7 +4435,7 @@ function normalizeStateFilePath(configuredPath, fallbackPath, field, logger2) {
|
|
|
4390
4435
|
return fallbackPath;
|
|
4391
4436
|
}
|
|
4392
4437
|
const expanded = resolvePath(trimmed);
|
|
4393
|
-
const resolved = (0,
|
|
4438
|
+
const resolved = (0, import_node_path2.isAbsolute)(expanded) ? (0, import_node_path2.resolve)(expanded) : (0, import_node_path2.resolve)(sageDir, expanded);
|
|
4394
4439
|
if (isWithinDirectory(sageDir, resolved)) {
|
|
4395
4440
|
if (resolved === sageDir) {
|
|
4396
4441
|
logger2.warn(`Config ${field}.path must point to a file; using default`, {
|
|
@@ -4489,11 +4534,11 @@ async function loadConfig(configPath, logger2 = nullLogger) {
|
|
|
4489
4534
|
return defaultConfig(logger2);
|
|
4490
4535
|
}
|
|
4491
4536
|
}
|
|
4492
|
-
var
|
|
4537
|
+
var import_node_path2, SAGE_DIR, MS_PER_DAY, CLOCK_SKEW_TOLERANCE_MS, BRAND_KEY_RE;
|
|
4493
4538
|
var init_config = __esm({
|
|
4494
4539
|
"../core/dist/config.js"() {
|
|
4495
4540
|
"use strict";
|
|
4496
|
-
|
|
4541
|
+
import_node_path2 = require("node:path");
|
|
4497
4542
|
init_file_utils();
|
|
4498
4543
|
init_types2();
|
|
4499
4544
|
SAGE_DIR = "~/.sage";
|
|
@@ -20548,12 +20593,53 @@ function safeTruncate(value, maxLen) {
|
|
|
20548
20593
|
return value.slice(0, cutIndex);
|
|
20549
20594
|
}
|
|
20550
20595
|
|
|
20596
|
+
// ../core/dist/detection-names.js
|
|
20597
|
+
var REPORTING_ENGINE_SHORTHANDS = {
|
|
20598
|
+
package: "sgpk",
|
|
20599
|
+
amsi: "sgam",
|
|
20600
|
+
heuristics: "sghe",
|
|
20601
|
+
ml: "sgml",
|
|
20602
|
+
skill: "skan"
|
|
20603
|
+
};
|
|
20604
|
+
var REPORTING_DETECTION_SUFFIX = "sage";
|
|
20605
|
+
function formatReportingDetectionName(canonicalName, engine, optionalData = "") {
|
|
20606
|
+
const engineMetadata = optionalData ? `${REPORTING_ENGINE_SHORTHANDS[engine]}:${optionalData}` : REPORTING_ENGINE_SHORTHANDS[engine];
|
|
20607
|
+
return `${canonicalName}|${engineMetadata}|${REPORTING_DETECTION_SUFFIX}`;
|
|
20608
|
+
}
|
|
20609
|
+
var SKILL_DETECTION_NAMES = {
|
|
20610
|
+
HIGH: "Other:SageSuspiciousSkill-A [Susp]",
|
|
20611
|
+
CRITICAL: "Other:SageMaliciousSkill-A [Trj]"
|
|
20612
|
+
};
|
|
20613
|
+
function skillDetectionName(verdict) {
|
|
20614
|
+
return SKILL_DETECTION_NAMES[verdict.toUpperCase()];
|
|
20615
|
+
}
|
|
20616
|
+
function isSkillId(value) {
|
|
20617
|
+
return /^[0-9a-f]{64}$/i.test(value);
|
|
20618
|
+
}
|
|
20619
|
+
function skillReportingDetectionName(verdict, skillId) {
|
|
20620
|
+
const canonicalName = skillDetectionName(verdict);
|
|
20621
|
+
return canonicalName && isSkillId(skillId) ? formatReportingDetectionName(canonicalName, "skill", skillId.toLowerCase()) : void 0;
|
|
20622
|
+
}
|
|
20623
|
+
function buildSkillCheckSignal(skillId, analyzerVerdict, jobId) {
|
|
20624
|
+
const normalizedSkillId = skillId.toLowerCase();
|
|
20625
|
+
const normalizedVerdict = analyzerVerdict.toUpperCase();
|
|
20626
|
+
const detectionName = skillReportingDetectionName(normalizedVerdict, normalizedSkillId);
|
|
20627
|
+
if (!detectionName)
|
|
20628
|
+
return void 0;
|
|
20629
|
+
return {
|
|
20630
|
+
detection_name: detectionName,
|
|
20631
|
+
skill_id: normalizedSkillId,
|
|
20632
|
+
...jobId ? { job_id: jobId } : {},
|
|
20633
|
+
analyzer_verdict: normalizedVerdict
|
|
20634
|
+
};
|
|
20635
|
+
}
|
|
20636
|
+
|
|
20551
20637
|
// ../core/dist/audit-log.js
|
|
20552
20638
|
init_file_utils();
|
|
20553
20639
|
|
|
20554
20640
|
// ../core/dist/jsonl-log-writer.js
|
|
20555
20641
|
var import_promises2 = require("node:fs/promises");
|
|
20556
|
-
var
|
|
20642
|
+
var import_node_path3 = require("node:path");
|
|
20557
20643
|
init_config();
|
|
20558
20644
|
init_file_utils();
|
|
20559
20645
|
var writeQueues = /* @__PURE__ */ new Map();
|
|
@@ -20590,7 +20676,7 @@ async function rotateIfNeeded(filePath, maxBytes, maxFiles) {
|
|
|
20590
20676
|
});
|
|
20591
20677
|
}
|
|
20592
20678
|
async function appendJsonlEntryNow(path2, config2, entry) {
|
|
20593
|
-
await (0, import_promises2.mkdir)((0,
|
|
20679
|
+
await (0, import_promises2.mkdir)((0, import_node_path3.dirname)(path2), { recursive: true });
|
|
20594
20680
|
await rotateIfNeeded(path2, config2.max_bytes, config2.max_files);
|
|
20595
20681
|
await (0, import_promises2.appendFile)(path2, `${JSON.stringify(entry)}
|
|
20596
20682
|
`);
|
|
@@ -20784,15 +20870,15 @@ init_types2();
|
|
|
20784
20870
|
init_types2();
|
|
20785
20871
|
|
|
20786
20872
|
// ../core/dist/version.js
|
|
20787
|
-
var
|
|
20873
|
+
var import_node_path4 = require("node:path");
|
|
20788
20874
|
var import_node_url = require("node:url");
|
|
20789
20875
|
init_file_utils();
|
|
20790
20876
|
var import_meta = {};
|
|
20791
20877
|
function resolveVersion() {
|
|
20792
20878
|
if (true)
|
|
20793
|
-
return "0.
|
|
20879
|
+
return "0.14.0";
|
|
20794
20880
|
try {
|
|
20795
|
-
const pkgPath = (0,
|
|
20881
|
+
const pkgPath = (0, import_node_path4.join)((0, import_node_path4.dirname)((0, import_node_url.fileURLToPath)(import_meta.url)), "..", "package.json");
|
|
20796
20882
|
const pkg = JSON.parse(getFileContentSync(pkgPath));
|
|
20797
20883
|
if (typeof pkg.version === "string")
|
|
20798
20884
|
return pkg.version;
|
|
@@ -20913,7 +20999,7 @@ init_types2();
|
|
|
20913
20999
|
// ../core/dist/extended-info.js
|
|
20914
21000
|
var import_promises3 = require("node:fs/promises");
|
|
20915
21001
|
var import_node_os2 = require("node:os");
|
|
20916
|
-
var
|
|
21002
|
+
var import_node_path5 = require("node:path");
|
|
20917
21003
|
init_file_utils();
|
|
20918
21004
|
init_types2();
|
|
20919
21005
|
var EXTENDED_INFO_FILE_MAX_BYTES = 1024;
|
|
@@ -20979,8 +21065,8 @@ function sanitizeDocument(parsed, logger2) {
|
|
|
20979
21065
|
return out;
|
|
20980
21066
|
}
|
|
20981
21067
|
async function loadExtendedInfo(sageDirPath, logger2 = nullLogger) {
|
|
20982
|
-
const sageDir = sageDirPath ?? (0,
|
|
20983
|
-
const filePath = (0,
|
|
21068
|
+
const sageDir = sageDirPath ?? (0, import_node_path5.join)((0, import_node_os2.homedir)(), ".sage");
|
|
21069
|
+
const filePath = (0, import_node_path5.join)(sageDir, EXTENDED_INFO_FILENAME);
|
|
20984
21070
|
const cached2 = cache.get(filePath);
|
|
20985
21071
|
if (cached2)
|
|
20986
21072
|
return cached2.value;
|
|
@@ -21078,14 +21164,14 @@ function mergeExtendedInfo(envelope, extendedInfo) {
|
|
|
21078
21164
|
}
|
|
21079
21165
|
|
|
21080
21166
|
// ../core/dist/installation-id.js
|
|
21081
|
-
var
|
|
21167
|
+
var import_node_crypto2 = require("node:crypto");
|
|
21082
21168
|
var import_promises4 = require("node:fs/promises");
|
|
21083
|
-
var
|
|
21169
|
+
var import_node_path6 = require("node:path");
|
|
21084
21170
|
init_config();
|
|
21085
21171
|
init_file_utils();
|
|
21086
21172
|
async function getInstallationId(sageDirPath) {
|
|
21087
21173
|
const sageDir = sageDirPath ?? resolvePath("~/.sage");
|
|
21088
|
-
const idPath = (0,
|
|
21174
|
+
const idPath = (0, import_node_path6.join)(sageDir, "installation-id");
|
|
21089
21175
|
let fileExists = false;
|
|
21090
21176
|
try {
|
|
21091
21177
|
const existing = await getFileContent(idPath, "utf-8");
|
|
@@ -21096,7 +21182,7 @@ async function getInstallationId(sageDirPath) {
|
|
|
21096
21182
|
} catch {
|
|
21097
21183
|
}
|
|
21098
21184
|
try {
|
|
21099
|
-
const id = (0,
|
|
21185
|
+
const id = (0, import_node_crypto2.randomUUID)();
|
|
21100
21186
|
await (0, import_promises4.mkdir)(sageDir, { recursive: true, mode: 448 });
|
|
21101
21187
|
await (0, import_promises4.writeFile)(idPath, id, { encoding: "utf-8", mode: 384, flag: fileExists ? "w" : "wx" });
|
|
21102
21188
|
return id;
|
|
@@ -21311,11 +21397,11 @@ function createOperationalLogger(config2, runtime) {
|
|
|
21311
21397
|
}
|
|
21312
21398
|
|
|
21313
21399
|
// ../core/dist/plugin-scan-cache.js
|
|
21314
|
-
var
|
|
21400
|
+
var import_node_path7 = require("node:path");
|
|
21315
21401
|
init_config();
|
|
21316
21402
|
init_file_utils();
|
|
21317
21403
|
init_types2();
|
|
21318
|
-
var DEFAULT_CACHE_PATH = (0,
|
|
21404
|
+
var DEFAULT_CACHE_PATH = (0, import_node_path7.join)(getHomeDir(), ".sage", "plugin_scan_cache.json");
|
|
21319
21405
|
|
|
21320
21406
|
// ../core/dist/plugin-scanner.js
|
|
21321
21407
|
init_config();
|
|
@@ -21331,9 +21417,119 @@ init_types2();
|
|
|
21331
21417
|
var PENDING_TTL_MS = 60 * 60 * 1e3;
|
|
21332
21418
|
|
|
21333
21419
|
// ../core/dist/skill-verdict-cache.js
|
|
21420
|
+
var import_node_path8 = require("node:path");
|
|
21334
21421
|
init_config();
|
|
21335
21422
|
init_file_utils();
|
|
21336
21423
|
init_types2();
|
|
21424
|
+
var SCHEMA_VERSION = 1;
|
|
21425
|
+
var DEFAULT_VERDICT_TTL_MS = MS_PER_DAY;
|
|
21426
|
+
function parseSources(raw) {
|
|
21427
|
+
if (!Array.isArray(raw))
|
|
21428
|
+
return void 0;
|
|
21429
|
+
const sources = [];
|
|
21430
|
+
for (const item of raw) {
|
|
21431
|
+
if (!item || typeof item !== "object")
|
|
21432
|
+
continue;
|
|
21433
|
+
const entry = item;
|
|
21434
|
+
const source = {
|
|
21435
|
+
agentRuntime: typeof entry.agent_runtime === "string" ? entry.agent_runtime : void 0,
|
|
21436
|
+
containerKey: typeof entry.container_key === "string" ? entry.container_key : void 0
|
|
21437
|
+
};
|
|
21438
|
+
if (source.agentRuntime || source.containerKey)
|
|
21439
|
+
sources.push(source);
|
|
21440
|
+
}
|
|
21441
|
+
return sources.length > 0 ? sources : void 0;
|
|
21442
|
+
}
|
|
21443
|
+
function defaultCachePath2() {
|
|
21444
|
+
return (0, import_node_path8.join)(resolvePath(SAGE_DIR), "skill_verdict_cache.json");
|
|
21445
|
+
}
|
|
21446
|
+
async function loadSkillVerdictCacheInternal(cachePath, ttlMs, logger2, pruneOnLoad) {
|
|
21447
|
+
let raw;
|
|
21448
|
+
try {
|
|
21449
|
+
raw = await getFileContent(cachePath);
|
|
21450
|
+
} catch {
|
|
21451
|
+
return { entries: {} };
|
|
21452
|
+
}
|
|
21453
|
+
try {
|
|
21454
|
+
const data = JSON.parse(raw);
|
|
21455
|
+
const rawEntries = data.entries ?? {};
|
|
21456
|
+
const now = Date.now();
|
|
21457
|
+
const entries = {};
|
|
21458
|
+
let pruned = 0;
|
|
21459
|
+
for (const [skillId, entry] of Object.entries(rawEntries)) {
|
|
21460
|
+
const analyzedAt = entry.analyzed_at;
|
|
21461
|
+
if (!analyzedAt)
|
|
21462
|
+
continue;
|
|
21463
|
+
const ts = Date.parse(analyzedAt);
|
|
21464
|
+
if (!isFreshTimestamp(ts, ttlMs, now)) {
|
|
21465
|
+
pruned++;
|
|
21466
|
+
continue;
|
|
21467
|
+
}
|
|
21468
|
+
entries[skillId] = {
|
|
21469
|
+
entryId: typeof entry.entry_id === "string" ? entry.entry_id : void 0,
|
|
21470
|
+
jobId: typeof entry.job_id === "string" ? entry.job_id : void 0,
|
|
21471
|
+
verdict: typeof entry.verdict === "string" ? entry.verdict : void 0,
|
|
21472
|
+
summary: typeof entry.summary === "string" ? entry.summary : void 0,
|
|
21473
|
+
skillName: typeof entry.skill_name === "string" ? entry.skill_name : void 0,
|
|
21474
|
+
sources: parseSources(entry.sources),
|
|
21475
|
+
analyzedAt
|
|
21476
|
+
};
|
|
21477
|
+
}
|
|
21478
|
+
const cache2 = { entries };
|
|
21479
|
+
if (pruneOnLoad && pruned > 0) {
|
|
21480
|
+
logger2.debug(`Pruned ${pruned} stale verdict(s) from cache`, { cachePath });
|
|
21481
|
+
await saveSkillVerdictCache(cache2, cachePath, logger2);
|
|
21482
|
+
}
|
|
21483
|
+
return cache2;
|
|
21484
|
+
} catch (e) {
|
|
21485
|
+
logger2.warn(`Failed to load verdict cache from ${cachePath}`, { error: String(e) });
|
|
21486
|
+
return { entries: {} };
|
|
21487
|
+
}
|
|
21488
|
+
}
|
|
21489
|
+
async function loadSkillVerdictCacheReadOnly(cachePath = defaultCachePath2(), ttlMs = DEFAULT_VERDICT_TTL_MS, logger2 = nullLogger) {
|
|
21490
|
+
return loadSkillVerdictCacheInternal(cachePath, ttlMs, logger2, false);
|
|
21491
|
+
}
|
|
21492
|
+
async function saveSkillVerdictCache(cache2, cachePath = defaultCachePath2(), logger2 = nullLogger) {
|
|
21493
|
+
try {
|
|
21494
|
+
const data = {
|
|
21495
|
+
schema_version: SCHEMA_VERSION,
|
|
21496
|
+
entries: Object.fromEntries(Object.entries(cache2.entries).map(([skillId, v]) => [
|
|
21497
|
+
skillId,
|
|
21498
|
+
{
|
|
21499
|
+
entry_id: v.entryId,
|
|
21500
|
+
job_id: v.jobId,
|
|
21501
|
+
verdict: v.verdict,
|
|
21502
|
+
summary: v.summary,
|
|
21503
|
+
skill_name: v.skillName,
|
|
21504
|
+
sources: v.sources?.map((s) => ({
|
|
21505
|
+
agent_runtime: s.agentRuntime,
|
|
21506
|
+
container_key: s.containerKey
|
|
21507
|
+
})),
|
|
21508
|
+
analyzed_at: v.analyzedAt
|
|
21509
|
+
}
|
|
21510
|
+
]))
|
|
21511
|
+
};
|
|
21512
|
+
await atomicWriteJson(cachePath, data);
|
|
21513
|
+
return true;
|
|
21514
|
+
} catch (e) {
|
|
21515
|
+
logger2.warn(`Failed to save verdict cache to ${cachePath}`, { error: String(e) });
|
|
21516
|
+
return false;
|
|
21517
|
+
}
|
|
21518
|
+
}
|
|
21519
|
+
|
|
21520
|
+
// ../core/dist/skill-verdict-record.js
|
|
21521
|
+
var import_node_crypto3 = require("node:crypto");
|
|
21522
|
+
var SKILL_ENTRY_ID_NAMESPACE = Buffer.from("75bba79a376142a7bacbf95d791126d3", "hex");
|
|
21523
|
+
function legacySkillEntryId(skillId, analyzedAt) {
|
|
21524
|
+
const bytes = (0, import_node_crypto3.createHash)("sha1").update(SKILL_ENTRY_ID_NAMESPACE).update(`${skillId.toLowerCase()}:${analyzedAt}`).digest().subarray(0, 16);
|
|
21525
|
+
bytes[6] = (bytes[6] ?? 0) & 15 | 80;
|
|
21526
|
+
bytes[8] = (bytes[8] ?? 0) & 63 | 128;
|
|
21527
|
+
const hex = bytes.toString("hex");
|
|
21528
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
21529
|
+
}
|
|
21530
|
+
function resolveSkillVerdictEntryId(skillId, entry) {
|
|
21531
|
+
return entry.entryId ?? legacySkillEntryId(skillId, entry.analyzedAt);
|
|
21532
|
+
}
|
|
21337
21533
|
|
|
21338
21534
|
// ../core/dist/plugin-scanner.js
|
|
21339
21535
|
init_types2();
|
|
@@ -21341,12 +21537,12 @@ var MAX_FILE_SIZE = 512 * 1024;
|
|
|
21341
21537
|
|
|
21342
21538
|
// ../core/dist/product-version.js
|
|
21343
21539
|
var import_node_fs = require("node:fs");
|
|
21344
|
-
var
|
|
21540
|
+
var import_node_path9 = __toESM(require("node:path"), 1);
|
|
21345
21541
|
function readProductJsonVersion(appRoot) {
|
|
21346
21542
|
if (!appRoot)
|
|
21347
21543
|
return "unknown";
|
|
21348
21544
|
try {
|
|
21349
|
-
const productJsonPath =
|
|
21545
|
+
const productJsonPath = import_node_path9.default.join(appRoot, "product.json");
|
|
21350
21546
|
const raw = (0, import_node_fs.readFileSync)(productJsonPath, "utf8");
|
|
21351
21547
|
const parsed = JSON.parse(raw);
|
|
21352
21548
|
const version2 = parsed.version;
|
|
@@ -31409,7 +31605,7 @@ var StdioServerTransport = class {
|
|
|
31409
31605
|
};
|
|
31410
31606
|
|
|
31411
31607
|
// ../mcp/dist/tools/false-positive.js
|
|
31412
|
-
var
|
|
31608
|
+
var import_node_crypto4 = require("node:crypto");
|
|
31413
31609
|
init_zod();
|
|
31414
31610
|
|
|
31415
31611
|
// ../mcp/dist/tools/utils.js
|
|
@@ -31447,12 +31643,71 @@ function isRuntimeVerdictEntry(v) {
|
|
|
31447
31643
|
return false;
|
|
31448
31644
|
return v.type === "runtime_verdict";
|
|
31449
31645
|
}
|
|
31646
|
+
function isAnalyzedSkillVerdictEntry(v) {
|
|
31647
|
+
if (!v || typeof v !== "object" || Array.isArray(v))
|
|
31648
|
+
return false;
|
|
31649
|
+
const entry = v;
|
|
31650
|
+
return entry.type === "skill_verdict" && entry.status === "analyzed" && isSkillId(asString(entry.skill_id) ?? "");
|
|
31651
|
+
}
|
|
31652
|
+
function isReportableSkillVerdictEntry(v) {
|
|
31653
|
+
if (!isAnalyzedSkillVerdictEntry(v))
|
|
31654
|
+
return false;
|
|
31655
|
+
const entry = v;
|
|
31656
|
+
const verdict = asString(entry.verdict)?.toUpperCase();
|
|
31657
|
+
return (verdict === "HIGH" || verdict === "CRITICAL") && asString(entry.entry_id) !== void 0;
|
|
31658
|
+
}
|
|
31659
|
+
function latestReportableSkillVerdicts(entries, ttlMs, now = Date.now()) {
|
|
31660
|
+
const latestBySkillId = /* @__PURE__ */ new Map();
|
|
31661
|
+
for (const entry of entries) {
|
|
31662
|
+
if (!isAnalyzedSkillVerdictEntry(entry))
|
|
31663
|
+
continue;
|
|
31664
|
+
const timestamp = Date.parse(asString(entry.timestamp) ?? "");
|
|
31665
|
+
if (!isFreshTimestamp(timestamp, ttlMs, now))
|
|
31666
|
+
continue;
|
|
31667
|
+
const normalizedSkillId = entry.skill_id.toLowerCase();
|
|
31668
|
+
const current = latestBySkillId.get(normalizedSkillId);
|
|
31669
|
+
if (current && timestamp < current.timestamp)
|
|
31670
|
+
continue;
|
|
31671
|
+
latestBySkillId.delete(normalizedSkillId);
|
|
31672
|
+
latestBySkillId.set(normalizedSkillId, { entry, timestamp });
|
|
31673
|
+
}
|
|
31674
|
+
return [...latestBySkillId.values()].map(({ entry }) => entry).filter(isReportableSkillVerdictEntry);
|
|
31675
|
+
}
|
|
31676
|
+
async function cachedSkillVerdictEntries(config2, logger2) {
|
|
31677
|
+
const cache2 = await loadSkillVerdictCacheReadOnly(void 0, skillCacheTtlMs(config2), logger2);
|
|
31678
|
+
return Object.entries(cache2.entries).flatMap(([skillId, entry]) => {
|
|
31679
|
+
if (!isSkillId(skillId))
|
|
31680
|
+
return [];
|
|
31681
|
+
if (!entry.verdict)
|
|
31682
|
+
return [];
|
|
31683
|
+
const normalizedSkillId = skillId.toLowerCase();
|
|
31684
|
+
const analyzerVerdict = entry.verdict.toUpperCase();
|
|
31685
|
+
const signal = buildSkillCheckSignal(normalizedSkillId, analyzerVerdict, entry.jobId);
|
|
31686
|
+
return [
|
|
31687
|
+
{
|
|
31688
|
+
type: "skill_verdict",
|
|
31689
|
+
entry_id: resolveSkillVerdictEntryId(normalizedSkillId, entry),
|
|
31690
|
+
timestamp: entry.analyzedAt,
|
|
31691
|
+
agent_runtime: entry.sources?.[0]?.agentRuntime,
|
|
31692
|
+
skill_id: normalizedSkillId,
|
|
31693
|
+
job_id: entry.jobId,
|
|
31694
|
+
status: "analyzed",
|
|
31695
|
+
verdict: analyzerVerdict,
|
|
31696
|
+
signals: signal ? { skill_checks: [signal] } : void 0
|
|
31697
|
+
}
|
|
31698
|
+
];
|
|
31699
|
+
});
|
|
31700
|
+
}
|
|
31450
31701
|
function getConversationId(entry) {
|
|
31451
31702
|
return asString(entry.conversation_id) ?? asString(entry.session_id);
|
|
31452
31703
|
}
|
|
31453
31704
|
function getEntryId(entry) {
|
|
31454
31705
|
return asString(entry.entry_id);
|
|
31455
31706
|
}
|
|
31707
|
+
function getEntryTimestamp(entry) {
|
|
31708
|
+
const timestamp = Date.parse(asString(entry.timestamp) ?? "");
|
|
31709
|
+
return Number.isFinite(timestamp) ? timestamp : 0;
|
|
31710
|
+
}
|
|
31456
31711
|
function asHookType(value) {
|
|
31457
31712
|
const s = asString(value);
|
|
31458
31713
|
if (s === "PreToolUse" || s === "PostToolUse" || s === "SessionStart" || s === "GatewayStart" || s === "BeforeAgentStart" || s === "MessagesTransform") {
|
|
@@ -31473,9 +31728,10 @@ async function postMultipart(endpoint, payload, timeoutSeconds) {
|
|
|
31473
31728
|
const bodyText = await res.text().catch(() => "");
|
|
31474
31729
|
return { ok: res.ok, status: res.status, bodyText };
|
|
31475
31730
|
}
|
|
31731
|
+
var MAX_AUDIT_SCAN_ENTRIES = 5e3;
|
|
31476
31732
|
var ListInputSchema = external_exports.object({
|
|
31477
31733
|
conversation_id: external_exports.string().optional().describe("Conversation id to filter audit entries; if omitted, uses the most recent one."),
|
|
31478
|
-
limit: external_exports.number().int().min(1).max(
|
|
31734
|
+
limit: external_exports.number().int().min(1).max(MAX_AUDIT_SCAN_ENTRIES).optional().describe("Max merged entries to return.")
|
|
31479
31735
|
});
|
|
31480
31736
|
var MAX_IMPLICIT_ENTRIES = 3;
|
|
31481
31737
|
var MAX_EXPLICIT_ENTRIES = 10;
|
|
@@ -31484,10 +31740,10 @@ var ReportInputSchema = external_exports.object({
|
|
|
31484
31740
|
description: external_exports.string().min(1).describe("Short description of what is a false positive."),
|
|
31485
31741
|
reasoning: external_exports.string().min(1).describe("Reasoning for why this should be treated as a false positive."),
|
|
31486
31742
|
agent_runtime_version: external_exports.string().optional().describe("Optional agent runtime version override (Cursor/VS Code/Claude version). If omitted, the server resolves the host version from SAGE_APP_ROOT (the application root injected by the Sage extension at MCP-server install time, used to read product.json), then falls back to SAGE_AGENT_RUNTIME_VERSION env, then 'unknown'."),
|
|
31487
|
-
entry_ids: external_exports.array(external_exports.string()).optional().describe(`REQUIRED in normal use: list of specific audit entry_ids to report as false positives. ALWAYS call sage_list_audit_entries first to inspect the audit log and identify the exact deny/ask verdict the user considers a false positive, then pass its entry_id here. Up to ${MAX_EXPLICIT_ENTRIES} entry_ids may be provided per call. Omitting this parameter is a fallback that submits up to ${MAX_IMPLICIT_ENTRIES} most recent deny/ask entries for the conversation, which may include irrelevant verdicts and should only be used when entry_id discovery is impossible.`),
|
|
31743
|
+
entry_ids: external_exports.array(external_exports.string()).max(MAX_EXPLICIT_ENTRIES).optional().describe(`REQUIRED in normal use: list of specific audit entry_ids to report as false positives. ALWAYS call sage_list_audit_entries first to inspect the audit log and identify the exact deny/ask verdict the user considers a false positive, then pass its entry_id here. Up to ${MAX_EXPLICIT_ENTRIES} entry_ids may be provided per call. Omitting this parameter is a fallback that submits up to ${MAX_IMPLICIT_ENTRIES} most recent deny/ask entries for the conversation, which may include irrelevant verdicts and should only be used when entry_id discovery is impossible.`),
|
|
31488
31744
|
dry_run: external_exports.boolean().optional().describe("If true, do not POST; just show the payload.")
|
|
31489
31745
|
});
|
|
31490
|
-
function
|
|
31746
|
+
function parseRuntimeAuditSignals(raw, options = {}) {
|
|
31491
31747
|
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
31492
31748
|
return {};
|
|
31493
31749
|
const includePiContentSnippet = options.includePiContentSnippet ?? true;
|
|
@@ -31596,7 +31852,7 @@ function registerFalsePositiveTools(server, opts) {
|
|
|
31596
31852
|
const branding = opts.branding ?? defaultBranding;
|
|
31597
31853
|
server.registerTool("sage_list_audit_entries", {
|
|
31598
31854
|
title: `${branding.name}: List Audit Entries`,
|
|
31599
|
-
description: `List recent ${branding.name} audit
|
|
31855
|
+
description: `List recent ${branding.name} runtime and Skill Analyzer audit entries. Runtime entries are optionally scoped to a conversation id; skill entries are global because analysis can finish outside the originating conversation. Useful for selecting entry_ids to report as false positives.`,
|
|
31600
31856
|
inputSchema: ListInputSchema
|
|
31601
31857
|
}, async ({ conversation_id, limit }) => {
|
|
31602
31858
|
const complete = (result, data = {}) => {
|
|
@@ -31614,33 +31870,59 @@ function registerFalsePositiveTools(server, opts) {
|
|
|
31614
31870
|
});
|
|
31615
31871
|
try {
|
|
31616
31872
|
const config2 = await loadConfig(void 0, logger2);
|
|
31617
|
-
const
|
|
31618
|
-
const entries = await getRecentEntries(config2.logging,
|
|
31873
|
+
const outputLimit = limit ?? 500;
|
|
31874
|
+
const entries = await getRecentEntries(config2.logging, MAX_AUDIT_SCAN_ENTRIES);
|
|
31619
31875
|
const runtime = entries.filter(isRuntimeVerdictEntry);
|
|
31876
|
+
const cachedSkills = await cachedSkillVerdictEntries(config2, logger2);
|
|
31877
|
+
const skills = latestReportableSkillVerdicts([...entries, ...cachedSkills], skillCacheTtlMs(config2));
|
|
31620
31878
|
const inferredConversationId = conversation_id ?? getConversationId(runtime.slice().reverse().find((e) => !!getConversationId(e)) ?? {});
|
|
31621
|
-
if (!inferredConversationId) {
|
|
31622
|
-
complete("skipped", { skippedReason: "
|
|
31623
|
-
return textResult("No
|
|
31624
|
-
}
|
|
31625
|
-
const
|
|
31626
|
-
const
|
|
31627
|
-
|
|
31628
|
-
|
|
31629
|
-
|
|
31630
|
-
|
|
31631
|
-
|
|
31632
|
-
|
|
31633
|
-
|
|
31634
|
-
|
|
31635
|
-
|
|
31636
|
-
|
|
31637
|
-
|
|
31638
|
-
|
|
31639
|
-
|
|
31640
|
-
|
|
31641
|
-
|
|
31642
|
-
|
|
31643
|
-
|
|
31879
|
+
if (!inferredConversationId && skills.length === 0) {
|
|
31880
|
+
complete("skipped", { skippedReason: "no_reportable_entries", entriesReturned: 0 });
|
|
31881
|
+
return textResult("No reportable runtime or skill verdict entries found.", true);
|
|
31882
|
+
}
|
|
31883
|
+
const filteredRuntime = inferredConversationId ? runtime.filter((e) => getConversationId(e) === inferredConversationId) : [];
|
|
31884
|
+
const selected = [
|
|
31885
|
+
...filteredRuntime,
|
|
31886
|
+
...skills
|
|
31887
|
+
].sort((a, b) => getEntryTimestamp(a) - getEntryTimestamp(b)).slice(-outputLimit);
|
|
31888
|
+
const out = selected.map((entry) => {
|
|
31889
|
+
if (isReportableSkillVerdictEntry(entry)) {
|
|
31890
|
+
const skillId = entry.skill_id;
|
|
31891
|
+
const analyzerVerdict = asString(entry.verdict)?.toUpperCase() ?? "";
|
|
31892
|
+
const signal = buildSkillCheckSignal(skillId, analyzerVerdict, asString(entry.job_id));
|
|
31893
|
+
return {
|
|
31894
|
+
type: "skill_verdict",
|
|
31895
|
+
entry_id: getEntryId(entry),
|
|
31896
|
+
timestamp: asString(entry.timestamp),
|
|
31897
|
+
agent_runtime: asString(entry.agent_runtime),
|
|
31898
|
+
skill_id: skillId.toLowerCase(),
|
|
31899
|
+
job_id: asString(entry.job_id),
|
|
31900
|
+
status: asString(entry.status),
|
|
31901
|
+
analyzer_verdict: analyzerVerdict,
|
|
31902
|
+
signals: signal ? { skill_checks: [signal] } : {}
|
|
31903
|
+
};
|
|
31904
|
+
}
|
|
31905
|
+
const runtimeEntry = entry;
|
|
31906
|
+
return {
|
|
31907
|
+
type: "runtime_verdict",
|
|
31908
|
+
entry_id: getEntryId(runtimeEntry),
|
|
31909
|
+
timestamp: asString(runtimeEntry.timestamp),
|
|
31910
|
+
conversation_id: inferredConversationId,
|
|
31911
|
+
agent_runtime: asString(runtimeEntry.agent_runtime),
|
|
31912
|
+
tool_name: asString(runtimeEntry.tool_name),
|
|
31913
|
+
tool_input_summary: asString(runtimeEntry.tool_input_summary),
|
|
31914
|
+
verdict: asString(runtimeEntry.verdict),
|
|
31915
|
+
severity: asString(runtimeEntry.severity),
|
|
31916
|
+
source: asString(runtimeEntry.source),
|
|
31917
|
+
user_override: runtimeEntry.user_override,
|
|
31918
|
+
// PI snippets are untrusted fetched content. Exclude their raw
|
|
31919
|
+
// text from this model-visible response.
|
|
31920
|
+
signals: parseRuntimeAuditSignals(runtimeEntry.signals, {
|
|
31921
|
+
includePiContentSnippet: false
|
|
31922
|
+
}),
|
|
31923
|
+
content: readContent(runtimeEntry)
|
|
31924
|
+
};
|
|
31925
|
+
});
|
|
31644
31926
|
complete("completed", {
|
|
31645
31927
|
conversationId: inferredConversationId,
|
|
31646
31928
|
entriesReturned: out.length
|
|
@@ -31657,11 +31939,11 @@ function registerFalsePositiveTools(server, opts) {
|
|
|
31657
31939
|
});
|
|
31658
31940
|
server.registerTool("sage_report_false_positive", {
|
|
31659
31941
|
title: `${branding.name}: Report False Positive`,
|
|
31660
|
-
description: `Report ${branding.name} audit
|
|
31942
|
+
description: `Report ${branding.name} runtime or Skill Analyzer audit entries as false positives to the backend. Runtime entries are scoped to the current conversation; skill entries require explicit entry_ids.
|
|
31661
31943
|
|
|
31662
31944
|
USAGE \u2014 IMPORTANT:
|
|
31663
|
-
1. ALWAYS call \`sage_list_audit_entries\` FIRST to inspect the audit log and locate the specific
|
|
31664
|
-
2. ALWAYS pass the \`entry_ids\` parameter with just the relevant
|
|
31945
|
+
1. ALWAYS call \`sage_list_audit_entries\` FIRST to inspect the audit log and locate the specific runtime or skill verdict the user considers a false positive.
|
|
31946
|
+
2. ALWAYS pass the \`entry_ids\` parameter with just the relevant entry id(s); it is required for skill verdicts. Up to ${MAX_EXPLICIT_ENTRIES} entry_ids may be reported per call.
|
|
31665
31947
|
3. Only when entry_id discovery is impossible should \`entry_ids\` be omitted; in that fallback at most ${MAX_IMPLICIT_ENTRIES} most recent deny/ask entries for the conversation are submitted, which may include irrelevant verdicts and floods the backend with noise.`,
|
|
31666
31948
|
inputSchema: ReportInputSchema
|
|
31667
31949
|
}, async ({ conversation_id, description, reasoning, agent_runtime_version, entry_ids, dry_run }) => {
|
|
@@ -31685,31 +31967,35 @@ USAGE \u2014 IMPORTANT:
|
|
|
31685
31967
|
const config2 = await loadConfig(void 0, logger2);
|
|
31686
31968
|
const endpoint = resolveEndpoint("/v2/fp-report");
|
|
31687
31969
|
const timeoutSeconds = Number(process.env.SAGE_FALSE_POSITIVE_TIMEOUT_SECONDS) || 5;
|
|
31688
|
-
const
|
|
31689
|
-
const entries = await getRecentEntries(config2.logging, scanLimit);
|
|
31970
|
+
const entries = await getRecentEntries(config2.logging, MAX_AUDIT_SCAN_ENTRIES);
|
|
31690
31971
|
const runtime = entries.filter(isRuntimeVerdictEntry);
|
|
31972
|
+
const cachedSkills = await cachedSkillVerdictEntries(config2, logger2);
|
|
31973
|
+
const skills = latestReportableSkillVerdicts([...entries, ...cachedSkills], skillCacheTtlMs(config2));
|
|
31691
31974
|
const inferredConversationId = conversation_id ?? getConversationId(runtime.slice().reverse().find((e) => !!getConversationId(e)) ?? {});
|
|
31692
|
-
|
|
31693
|
-
complete("skipped", { skippedReason: "no_runtime_entries", reportsCount: 0 });
|
|
31694
|
-
return textResult("No runtime_verdict entries found in the audit log.", true);
|
|
31695
|
-
}
|
|
31696
|
-
let filtered = runtime.filter((e) => getConversationId(e) === inferredConversationId);
|
|
31975
|
+
let filtered;
|
|
31697
31976
|
if (entry_ids && entry_ids.length > 0) {
|
|
31698
|
-
|
|
31699
|
-
filtered = filtered.filter((e) => {
|
|
31700
|
-
const id = getEntryId(e);
|
|
31701
|
-
return id ? wanted.has(id) : false;
|
|
31702
|
-
});
|
|
31703
|
-
if (filtered.length > MAX_EXPLICIT_ENTRIES) {
|
|
31977
|
+
if (entry_ids.length > MAX_EXPLICIT_ENTRIES) {
|
|
31704
31978
|
complete("failed_validation", {
|
|
31705
31979
|
conversationId: inferredConversationId,
|
|
31706
|
-
|
|
31980
|
+
entryIdsCount: entry_ids.length,
|
|
31707
31981
|
maxEntries: MAX_EXPLICIT_ENTRIES
|
|
31708
31982
|
});
|
|
31709
|
-
return textResult(`Too many entry_ids (${
|
|
31983
|
+
return textResult(`Too many entry_ids (${entry_ids.length}). Provide at most ${MAX_EXPLICIT_ENTRIES} specific entry_ids per report call.`, true);
|
|
31710
31984
|
}
|
|
31985
|
+
const wanted = new Set(entry_ids);
|
|
31986
|
+
const scopedRuntime = inferredConversationId ? runtime.filter((e) => getConversationId(e) === inferredConversationId) : [];
|
|
31987
|
+
filtered = [...scopedRuntime, ...skills].filter((e) => {
|
|
31988
|
+
const id = getEntryId(e);
|
|
31989
|
+
return id !== void 0 && wanted.has(id);
|
|
31990
|
+
});
|
|
31711
31991
|
} else {
|
|
31712
|
-
|
|
31992
|
+
if (!inferredConversationId) {
|
|
31993
|
+
complete("skipped", { skippedReason: "no_runtime_entries", reportsCount: 0 });
|
|
31994
|
+
return textResult("No runtime_verdict entries found. Skill verdicts must be reported with explicit entry_ids.", true);
|
|
31995
|
+
}
|
|
31996
|
+
filtered = runtime.filter((e) => {
|
|
31997
|
+
if (getConversationId(e) !== inferredConversationId)
|
|
31998
|
+
return false;
|
|
31713
31999
|
const v = asString(e.verdict);
|
|
31714
32000
|
return v === "deny" || v === "ask";
|
|
31715
32001
|
});
|
|
@@ -31724,7 +32010,7 @@ USAGE \u2014 IMPORTANT:
|
|
|
31724
32010
|
skippedReason: "no_matching_entry_ids",
|
|
31725
32011
|
reportsCount: 0
|
|
31726
32012
|
});
|
|
31727
|
-
return textResult("No matching audit entries found for the provided entry_ids
|
|
32013
|
+
return textResult("No matching reportable audit entries found for the provided entry_ids. Run sage_list_audit_entries first.", true);
|
|
31728
32014
|
}
|
|
31729
32015
|
complete("skipped", {
|
|
31730
32016
|
conversationId: inferredConversationId,
|
|
@@ -31750,19 +32036,22 @@ USAGE \u2014 IMPORTANT:
|
|
|
31750
32036
|
${reasoning}`;
|
|
31751
32037
|
const extendedInfo = await loadExtendedInfo(void 0, logger2).catch(() => null);
|
|
31752
32038
|
const reports = filtered.map((e) => {
|
|
32039
|
+
const isSkill = isReportableSkillVerdictEntry(e);
|
|
32040
|
+
const runtimeEntry = isSkill ? void 0 : e;
|
|
31753
32041
|
const entry_id = getEntryId(e);
|
|
31754
32042
|
const timestamp = asString(e.timestamp) ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
31755
32043
|
const agent_runtime = asString(e.agent_runtime) ?? "unknown";
|
|
31756
|
-
const
|
|
31757
|
-
const
|
|
31758
|
-
const
|
|
32044
|
+
const analyzerVerdict = isSkill ? asString(e.verdict)?.toUpperCase() : void 0;
|
|
32045
|
+
const tool_type = isSkill ? "Skill" : asString(runtimeEntry?.tool_name) ?? "Unknown";
|
|
32046
|
+
const verdict = isSkill ? analyzerVerdict === "CRITICAL" ? "deny" : "ask" : asString(e.verdict) ?? "deny";
|
|
32047
|
+
const wasAllowed = !isSkill && (verdict === "allow" || runtimeEntry?.user_override === true);
|
|
31759
32048
|
const user_action = wasAllowed ? "allowed" : "blocked";
|
|
31760
|
-
const hook_type = asHookType(
|
|
31761
|
-
const signals =
|
|
32049
|
+
const hook_type = isSkill ? "SessionStart" : asHookType(runtimeEntry?.hook_type) ?? "PreToolUse";
|
|
32050
|
+
const signals = parseRuntimeAuditSignals(e.signals, {
|
|
31762
32051
|
includePiContentSnippet: !dry_run
|
|
31763
32052
|
});
|
|
31764
|
-
const content = readContent(
|
|
31765
|
-
const contentSnippet = readContentSnippet(
|
|
32053
|
+
const content = runtimeEntry ? readContent(runtimeEntry) : {};
|
|
32054
|
+
const contentSnippet = runtimeEntry ? readContentSnippet(runtimeEntry) : void 0;
|
|
31766
32055
|
const bestEffortSignals = {};
|
|
31767
32056
|
if (signals.heuristics)
|
|
31768
32057
|
bestEffortSignals.heuristics = signals.heuristics;
|
|
@@ -31776,6 +32065,11 @@ ${reasoning}`;
|
|
|
31776
32065
|
bestEffortSignals.pi_checks = signals.pi_checks;
|
|
31777
32066
|
if (signals.amsi_checks)
|
|
31778
32067
|
bestEffortSignals.amsi_checks = signals.amsi_checks;
|
|
32068
|
+
if (isSkill) {
|
|
32069
|
+
const signal = analyzerVerdict ? buildSkillCheckSignal(e.skill_id, analyzerVerdict, asString(e.job_id)) : void 0;
|
|
32070
|
+
if (signal)
|
|
32071
|
+
bestEffortSignals.skill_checks = [signal];
|
|
32072
|
+
}
|
|
31779
32073
|
const baseReportPayload = {
|
|
31780
32074
|
...buildSageProxyEnvelope({
|
|
31781
32075
|
iid,
|
|
@@ -31791,11 +32085,11 @@ ${reasoning}`;
|
|
|
31791
32085
|
user_action,
|
|
31792
32086
|
timestamp,
|
|
31793
32087
|
...Object.keys(bestEffortSignals).length > 0 ? { signals: bestEffortSignals } : {},
|
|
31794
|
-
content,
|
|
32088
|
+
...Object.keys(content).length > 0 ? { content } : {},
|
|
31795
32089
|
...contentSnippet && !dry_run ? { content_snippet: contentSnippet } : {}
|
|
31796
32090
|
},
|
|
31797
32091
|
comment,
|
|
31798
|
-
event_id: entry_id ?? (0,
|
|
32092
|
+
event_id: entry_id ?? (0, import_node_crypto4.randomUUID)()
|
|
31799
32093
|
};
|
|
31800
32094
|
const reportPayload = mergeExtendedInfo(baseReportPayload, extendedInfo);
|
|
31801
32095
|
return { entry_id, payload: reportPayload };
|
|
@@ -31849,7 +32143,8 @@ ${JSON.stringify(summary, null, 2)}`, true);
|
|
|
31849
32143
|
conversationId: inferredConversationId,
|
|
31850
32144
|
reported: filtered.length
|
|
31851
32145
|
});
|
|
31852
|
-
|
|
32146
|
+
const scope = inferredConversationId ? ` for conversation_id=${inferredConversationId}` : "";
|
|
32147
|
+
return textResult(`Reported ${filtered.length} audit entr${filtered.length === 1 ? "y" : "ies"}${scope}.`);
|
|
31853
32148
|
} catch (e) {
|
|
31854
32149
|
logger2.warn("MCP tool failed", {
|
|
31855
32150
|
toolName: "sage_report_false_positive",
|
|
@@ -31897,7 +32192,7 @@ async function main() {
|
|
|
31897
32192
|
);
|
|
31898
32193
|
const branding = resolveBranding(config2.brand_key, logger);
|
|
31899
32194
|
await runSageMcpServerStdio({
|
|
31900
|
-
version: "0.
|
|
32195
|
+
version: "0.14.0",
|
|
31901
32196
|
logger,
|
|
31902
32197
|
branding
|
|
31903
32198
|
});
|