@remnic/core 9.3.641 → 9.3.643
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/access-http.js +3 -3
- package/dist/access-mcp.js +2 -2
- package/dist/{chunk-ACNU2UBF.js → chunk-2GCNTHRA.js} +3 -3
- package/dist/{chunk-5IDVOWBE.js → chunk-GJSNDJHR.js} +14 -2
- package/dist/chunk-GJSNDJHR.js.map +1 -0
- package/dist/{chunk-6DDBW2V4.js → chunk-S2SEPLLA.js} +2 -2
- package/dist/{chunk-6L46YAEZ.js → chunk-T4WDJPEZ.js} +45 -14
- package/dist/chunk-T4WDJPEZ.js.map +1 -0
- package/dist/cli.js +4 -4
- package/dist/index.d.ts +128 -1
- package/dist/index.js +916 -68
- package/dist/index.js.map +1 -1
- package/dist/mcp-memory-inspector-app.d.ts +1 -0
- package/dist/mcp-memory-inspector-app.js +1 -1
- package/dist/schemas.d.ts +22 -22
- package/dist/transfer/types.d.ts +12 -12
- package/package.json +1 -1
- package/src/access-mcp.ts +16 -0
- package/src/index.ts +7 -4
- package/src/mcp-memory-inspector-app.ts +79 -17
- package/src/session-summaries/adapters.ts +438 -0
- package/src/session-summaries/index.ts +401 -0
- package/src/session-summaries/redaction.ts +160 -0
- package/src/session-summaries/session-summaries.test.ts +1748 -0
- package/src/session-summaries/types.ts +130 -0
- package/dist/chunk-5IDVOWBE.js.map +0 -1
- package/dist/chunk-6L46YAEZ.js.map +0 -1
- /package/dist/{chunk-ACNU2UBF.js.map → chunk-2GCNTHRA.js.map} +0 -0
- /package/dist/{chunk-6DDBW2V4.js.map → chunk-S2SEPLLA.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -94,7 +94,7 @@ import {
|
|
|
94
94
|
registerTrainingExportAdapter,
|
|
95
95
|
runBulkImportCliCommand,
|
|
96
96
|
runWearablesCliCommand
|
|
97
|
-
} from "./chunk-
|
|
97
|
+
} from "./chunk-2GCNTHRA.js";
|
|
98
98
|
import "./chunk-MC4FJXPA.js";
|
|
99
99
|
import "./chunk-LQHDIS7L.js";
|
|
100
100
|
import "./chunk-7F7Z6MOS.js";
|
|
@@ -492,7 +492,7 @@ import "./chunk-HQ6NIBL6.js";
|
|
|
492
492
|
import "./chunk-OADWQ5CR.js";
|
|
493
493
|
import {
|
|
494
494
|
EngramAccessHttpServer
|
|
495
|
-
} from "./chunk-
|
|
495
|
+
} from "./chunk-S2SEPLLA.js";
|
|
496
496
|
import "./chunk-SEDEKFYQ.js";
|
|
497
497
|
import "./chunk-2QSZNTDO.js";
|
|
498
498
|
import "./chunk-RSUYKGGZ.js";
|
|
@@ -506,7 +506,7 @@ import {
|
|
|
506
506
|
} from "./chunk-7WV3F5DQ.js";
|
|
507
507
|
import {
|
|
508
508
|
EngramMcpServer
|
|
509
|
-
} from "./chunk-
|
|
509
|
+
} from "./chunk-GJSNDJHR.js";
|
|
510
510
|
import {
|
|
511
511
|
REMNIC_CHATGPT_MEMORY_INSPECTOR_CANONICAL_TOOL,
|
|
512
512
|
REMNIC_CHATGPT_MEMORY_INSPECTOR_MIME_TYPE,
|
|
@@ -515,7 +515,7 @@ import {
|
|
|
515
515
|
REMNIC_CHATGPT_MEMORY_INSPECTOR_WIDGET_URI,
|
|
516
516
|
buildChatGptMemoryInspectorActionRequest,
|
|
517
517
|
buildChatGptMemoryInspectorResult
|
|
518
|
-
} from "./chunk-
|
|
518
|
+
} from "./chunk-T4WDJPEZ.js";
|
|
519
519
|
import {
|
|
520
520
|
buildCitationGuidance,
|
|
521
521
|
formatOaiMemCitation,
|
|
@@ -1073,11 +1073,11 @@ var FilesystemBackend = class {
|
|
|
1073
1073
|
}
|
|
1074
1074
|
async realBasePathIfExists() {
|
|
1075
1075
|
try {
|
|
1076
|
-
const
|
|
1077
|
-
if (
|
|
1076
|
+
const stat2 = await fsp.lstat(this.basePath);
|
|
1077
|
+
if (stat2.isSymbolicLink()) {
|
|
1078
1078
|
throw new Error(`FilesystemBackend basePath must not be a symlink: ${this.basePath}`);
|
|
1079
1079
|
}
|
|
1080
|
-
if (!
|
|
1080
|
+
if (!stat2.isDirectory()) {
|
|
1081
1081
|
throw new Error(`FilesystemBackend basePath must be a directory: ${this.basePath}`);
|
|
1082
1082
|
}
|
|
1083
1083
|
return await fsp.realpath(this.basePath);
|
|
@@ -1106,11 +1106,11 @@ var FilesystemBackend = class {
|
|
|
1106
1106
|
if (segment === "." || segment === "") continue;
|
|
1107
1107
|
current = path.join(current, segment);
|
|
1108
1108
|
try {
|
|
1109
|
-
const
|
|
1110
|
-
if (
|
|
1109
|
+
const stat2 = await fsp.lstat(current);
|
|
1110
|
+
if (stat2.isSymbolicLink()) {
|
|
1111
1111
|
throw new Error(`FilesystemBackend remotePath traverses symlink: ${current}`);
|
|
1112
1112
|
}
|
|
1113
|
-
if (!
|
|
1113
|
+
if (!stat2.isDirectory()) {
|
|
1114
1114
|
throw new Error(`FilesystemBackend remotePath parent is not a directory: ${current}`);
|
|
1115
1115
|
}
|
|
1116
1116
|
} catch (err) {
|
|
@@ -1139,19 +1139,19 @@ var FilesystemBackend = class {
|
|
|
1139
1139
|
for (const segment of segments) {
|
|
1140
1140
|
if (segment === "." || segment === "") continue;
|
|
1141
1141
|
current = path.join(current, segment);
|
|
1142
|
-
let
|
|
1142
|
+
let stat2;
|
|
1143
1143
|
try {
|
|
1144
|
-
|
|
1144
|
+
stat2 = await fsp.lstat(current);
|
|
1145
1145
|
} catch (err) {
|
|
1146
1146
|
if (err.code === "ENOENT") {
|
|
1147
1147
|
return null;
|
|
1148
1148
|
}
|
|
1149
1149
|
throw err;
|
|
1150
1150
|
}
|
|
1151
|
-
if (
|
|
1151
|
+
if (stat2.isSymbolicLink()) {
|
|
1152
1152
|
throw new Error(`FilesystemBackend remotePath traverses symlink: ${current}`);
|
|
1153
1153
|
}
|
|
1154
|
-
if (!
|
|
1154
|
+
if (!stat2.isDirectory()) {
|
|
1155
1155
|
return null;
|
|
1156
1156
|
}
|
|
1157
1157
|
}
|
|
@@ -1166,11 +1166,11 @@ var FilesystemBackend = class {
|
|
|
1166
1166
|
throw new Error(`FilesystemBackend remotePath parent escapes basePath: ${JSON.stringify(remotePath)}`);
|
|
1167
1167
|
}
|
|
1168
1168
|
try {
|
|
1169
|
-
const
|
|
1170
|
-
if (
|
|
1169
|
+
const stat2 = await fsp.lstat(dest);
|
|
1170
|
+
if (stat2.isSymbolicLink()) {
|
|
1171
1171
|
throw new Error(`FilesystemBackend remotePath points to symlink: ${dest}`);
|
|
1172
1172
|
}
|
|
1173
|
-
if (!
|
|
1173
|
+
if (!stat2.isFile()) {
|
|
1174
1174
|
return null;
|
|
1175
1175
|
}
|
|
1176
1176
|
} catch (err) {
|
|
@@ -1192,8 +1192,8 @@ var FilesystemBackend = class {
|
|
|
1192
1192
|
const dest = this.resolveRemotePath(remotePath);
|
|
1193
1193
|
const realBase = await this.ensureSafeParentDirectory(dest);
|
|
1194
1194
|
try {
|
|
1195
|
-
const
|
|
1196
|
-
if (
|
|
1195
|
+
const stat2 = await fsp.lstat(dest);
|
|
1196
|
+
if (stat2.isSymbolicLink()) {
|
|
1197
1197
|
throw new Error(`FilesystemBackend remotePath points to symlink: ${dest}`);
|
|
1198
1198
|
}
|
|
1199
1199
|
} catch (err) {
|
|
@@ -1294,12 +1294,12 @@ async function scanForBinaries(memoryDir, config, manifest) {
|
|
|
1294
1294
|
if (!entry.isFile()) continue;
|
|
1295
1295
|
if (!matchesPatterns(entry.name, config.scanPatterns)) continue;
|
|
1296
1296
|
try {
|
|
1297
|
-
const
|
|
1298
|
-
if (
|
|
1299
|
-
if (
|
|
1297
|
+
const stat2 = await fsp2.stat(fullPath);
|
|
1298
|
+
if (stat2.size > config.maxBinarySizeBytes) continue;
|
|
1299
|
+
if (stat2.size === 0) continue;
|
|
1300
1300
|
const existing = tracked.get(relativePath);
|
|
1301
1301
|
if (existing) {
|
|
1302
|
-
if (existing.sizeBytes !==
|
|
1302
|
+
if (existing.sizeBytes !== stat2.size) {
|
|
1303
1303
|
results.push(relativePath);
|
|
1304
1304
|
continue;
|
|
1305
1305
|
}
|
|
@@ -1440,7 +1440,7 @@ async function stageMirror(memoryDir, newPaths, backend, assets, log2, dryRun) {
|
|
|
1440
1440
|
for (const relPath of newPaths) {
|
|
1441
1441
|
const fullPath = path4.join(memoryDir, relPath);
|
|
1442
1442
|
try {
|
|
1443
|
-
const
|
|
1443
|
+
const stat2 = await fsp4.stat(fullPath);
|
|
1444
1444
|
const contentHash = await hashFile2(fullPath);
|
|
1445
1445
|
const ext = path4.extname(relPath);
|
|
1446
1446
|
const mimeType = guessMimeType(ext);
|
|
@@ -1455,7 +1455,7 @@ async function stageMirror(memoryDir, newPaths, backend, assets, log2, dryRun) {
|
|
|
1455
1455
|
mirroredPath: backendLocation,
|
|
1456
1456
|
...redirectPath ? { redirectPath } : {},
|
|
1457
1457
|
contentHash,
|
|
1458
|
-
sizeBytes:
|
|
1458
|
+
sizeBytes: stat2.size,
|
|
1459
1459
|
mimeType,
|
|
1460
1460
|
mirroredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1461
1461
|
status: "mirrored"
|
|
@@ -1468,7 +1468,7 @@ async function stageMirror(memoryDir, newPaths, backend, assets, log2, dryRun) {
|
|
|
1468
1468
|
assets.push(record);
|
|
1469
1469
|
}
|
|
1470
1470
|
mirrored++;
|
|
1471
|
-
log2.info(`[binary-lifecycle] mirrored: ${relPath} (${
|
|
1471
|
+
log2.info(`[binary-lifecycle] mirrored: ${relPath} (${stat2.size} bytes)${dryRun ? " [dry-run]" : ""}`);
|
|
1472
1472
|
} catch (err) {
|
|
1473
1473
|
const msg = `mirror failed for ${relPath}: ${err instanceof Error ? err.message : String(err)}`;
|
|
1474
1474
|
log2.error(`[binary-lifecycle] ${msg}`);
|
|
@@ -1960,21 +1960,21 @@ function assertNotSymlink(targetPath, label) {
|
|
|
1960
1960
|
}
|
|
1961
1961
|
}
|
|
1962
1962
|
function assertSafeMemoryRoot(targetPath) {
|
|
1963
|
-
const
|
|
1964
|
-
if (
|
|
1963
|
+
const stat2 = fs.lstatSync(targetPath);
|
|
1964
|
+
if (stat2.isSymbolicLink()) {
|
|
1965
1965
|
throw new Error(`memoryDir must not be a symlink: ${targetPath}`);
|
|
1966
1966
|
}
|
|
1967
|
-
if (!
|
|
1967
|
+
if (!stat2.isDirectory()) {
|
|
1968
1968
|
throw new Error(`memoryDir must be a directory: ${targetPath}`);
|
|
1969
1969
|
}
|
|
1970
1970
|
return fs.realpathSync(targetPath);
|
|
1971
1971
|
}
|
|
1972
1972
|
function assertSafeInputRoot(realMemoryDir, targetPath, label) {
|
|
1973
|
-
const
|
|
1974
|
-
if (
|
|
1973
|
+
const stat2 = fs.lstatSync(targetPath);
|
|
1974
|
+
if (stat2.isSymbolicLink()) {
|
|
1975
1975
|
throw new Error(`${label} must not be a symlink: ${targetPath}`);
|
|
1976
1976
|
}
|
|
1977
|
-
if (!
|
|
1977
|
+
if (!stat2.isDirectory()) {
|
|
1978
1978
|
throw new Error(`${label} must be a directory: ${targetPath}`);
|
|
1979
1979
|
}
|
|
1980
1980
|
const realTarget = fs.realpathSync(targetPath);
|
|
@@ -1991,8 +1991,8 @@ function assertSafeOutputTarget(realOutputDir, outputPath) {
|
|
|
1991
1991
|
for (const segment of relative.split(path5.sep).filter(Boolean)) {
|
|
1992
1992
|
current = path5.join(current, segment);
|
|
1993
1993
|
try {
|
|
1994
|
-
const
|
|
1995
|
-
if (
|
|
1994
|
+
const stat2 = fs.lstatSync(current);
|
|
1995
|
+
if (stat2.isSymbolicLink()) {
|
|
1996
1996
|
throw new Error(`context tree output path contains symlink: ${current}`);
|
|
1997
1997
|
}
|
|
1998
1998
|
} catch (err) {
|
|
@@ -2661,8 +2661,8 @@ async function curate(options) {
|
|
|
2661
2661
|
};
|
|
2662
2662
|
}
|
|
2663
2663
|
function resolveTargets(targetPath) {
|
|
2664
|
-
const
|
|
2665
|
-
if (
|
|
2664
|
+
const stat2 = fs3.statSync(targetPath);
|
|
2665
|
+
if (stat2.isFile()) return [targetPath];
|
|
2666
2666
|
const results = [];
|
|
2667
2667
|
const extensions = /* @__PURE__ */ new Set([".md", ".txt", ".mdx", ".rst"]);
|
|
2668
2668
|
function walk(dir) {
|
|
@@ -2682,8 +2682,8 @@ function resolveTargets(targetPath) {
|
|
|
2682
2682
|
}
|
|
2683
2683
|
function resolveProvenanceRoot(targetPath) {
|
|
2684
2684
|
const resolvedTarget = path7.resolve(targetPath);
|
|
2685
|
-
const
|
|
2686
|
-
return
|
|
2685
|
+
const stat2 = fs3.statSync(resolvedTarget);
|
|
2686
|
+
return stat2.isFile() ? path7.dirname(resolvedTarget) : resolvedTarget;
|
|
2687
2687
|
}
|
|
2688
2688
|
function extractStatements(content, filePath, projectRoot, source, sourceFileHash, categoryOverride, confidence, entityRef, tags) {
|
|
2689
2689
|
const relativePath = path7.relative(projectRoot, path7.resolve(filePath)) || path7.basename(filePath);
|
|
@@ -3116,8 +3116,8 @@ import path9 from "path";
|
|
|
3116
3116
|
var DEFAULT_CONFIDENCE_THRESHOLD = 0.7;
|
|
3117
3117
|
function realMemoryRoot(memoryDir) {
|
|
3118
3118
|
try {
|
|
3119
|
-
const
|
|
3120
|
-
if (!
|
|
3119
|
+
const stat2 = fs5.lstatSync(memoryDir);
|
|
3120
|
+
if (!stat2.isDirectory() || stat2.isSymbolicLink()) return null;
|
|
3121
3121
|
return fs5.realpathSync(memoryDir);
|
|
3122
3122
|
} catch {
|
|
3123
3123
|
return null;
|
|
@@ -3129,8 +3129,8 @@ function isPathInside2(rootReal, candidateReal) {
|
|
|
3129
3129
|
}
|
|
3130
3130
|
function isSafeDirectory(rootReal, dir) {
|
|
3131
3131
|
try {
|
|
3132
|
-
const
|
|
3133
|
-
if (!
|
|
3132
|
+
const stat2 = fs5.lstatSync(dir);
|
|
3133
|
+
if (!stat2.isDirectory() || stat2.isSymbolicLink()) return false;
|
|
3134
3134
|
return isPathInside2(rootReal, fs5.realpathSync(dir));
|
|
3135
3135
|
} catch {
|
|
3136
3136
|
return false;
|
|
@@ -3909,9 +3909,9 @@ function removeStaleManifestReclaimLock(reclaimDir) {
|
|
|
3909
3909
|
fs7.rmSync(tombstoneDir, { recursive: true, force: true });
|
|
3910
3910
|
}
|
|
3911
3911
|
function readManifestLockSnapshot(lockDir) {
|
|
3912
|
-
let
|
|
3912
|
+
let stat2;
|
|
3913
3913
|
try {
|
|
3914
|
-
|
|
3914
|
+
stat2 = fs7.statSync(lockDir);
|
|
3915
3915
|
} catch (error) {
|
|
3916
3916
|
if (error.code === "ENOENT") {
|
|
3917
3917
|
return void 0;
|
|
@@ -3920,10 +3920,10 @@ function readManifestLockSnapshot(lockDir) {
|
|
|
3920
3920
|
}
|
|
3921
3921
|
try {
|
|
3922
3922
|
const owner = fs7.readFileSync(path11.join(lockDir, "owner"), "utf8").trim();
|
|
3923
|
-
return { mtimeMs:
|
|
3923
|
+
return { mtimeMs: stat2.mtimeMs, owner };
|
|
3924
3924
|
} catch (error) {
|
|
3925
3925
|
if (error.code === "ENOENT") {
|
|
3926
|
-
return { mtimeMs:
|
|
3926
|
+
return { mtimeMs: stat2.mtimeMs };
|
|
3927
3927
|
}
|
|
3928
3928
|
throw error;
|
|
3929
3929
|
}
|
|
@@ -4691,21 +4691,861 @@ function publisherForConnector(connectorId) {
|
|
|
4691
4691
|
return publisherFor(hostIdForConnector(connectorId));
|
|
4692
4692
|
}
|
|
4693
4693
|
|
|
4694
|
-
// src/
|
|
4695
|
-
import {
|
|
4694
|
+
// src/session-summaries/index.ts
|
|
4695
|
+
import { createHash } from "crypto";
|
|
4696
|
+
import { lstat, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "fs/promises";
|
|
4696
4697
|
import path13 from "path";
|
|
4698
|
+
|
|
4699
|
+
// src/session-summaries/adapters.ts
|
|
4700
|
+
var VALID_ROLES = /* @__PURE__ */ new Set(["user", "assistant", "tool", "system", "other"]);
|
|
4701
|
+
function normalizeRole(value) {
|
|
4702
|
+
if (typeof value !== "string") return "other";
|
|
4703
|
+
const role = value.trim().toLowerCase();
|
|
4704
|
+
if (VALID_ROLES.has(role)) {
|
|
4705
|
+
return role;
|
|
4706
|
+
}
|
|
4707
|
+
if (role === "human") return "user";
|
|
4708
|
+
if (role === "ai" || role === "model" || role === "bot") return "assistant";
|
|
4709
|
+
if (role === "function") return "tool";
|
|
4710
|
+
return "other";
|
|
4711
|
+
}
|
|
4712
|
+
function normalizeTimestamp(value) {
|
|
4713
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
4714
|
+
const millis = value > 1e12 ? value : value * 1e3;
|
|
4715
|
+
const date2 = new Date(millis);
|
|
4716
|
+
return Number.isFinite(date2.getTime()) ? date2.toISOString() : void 0;
|
|
4717
|
+
}
|
|
4718
|
+
if (typeof value !== "string" || value.trim().length === 0) return void 0;
|
|
4719
|
+
const date = new Date(value);
|
|
4720
|
+
return Number.isFinite(date.getTime()) ? date.toISOString() : void 0;
|
|
4721
|
+
}
|
|
4722
|
+
function normalizeContent(value) {
|
|
4723
|
+
if (typeof value === "string") {
|
|
4724
|
+
const trimmed = value.trim();
|
|
4725
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
4726
|
+
}
|
|
4727
|
+
if (Array.isArray(value)) {
|
|
4728
|
+
const parts = value.map((part) => normalizeContent(part)).filter((part) => typeof part === "string").join("\n").trim();
|
|
4729
|
+
return parts.length > 0 ? parts : void 0;
|
|
4730
|
+
}
|
|
4731
|
+
if (!value || typeof value !== "object") return void 0;
|
|
4732
|
+
const obj = value;
|
|
4733
|
+
if (firstString(obj.type) === "tool_result") return void 0;
|
|
4734
|
+
return normalizeContent(obj.text ?? obj.content ?? obj.message ?? obj.parts ?? obj.value ?? obj.input);
|
|
4735
|
+
}
|
|
4736
|
+
function firstString(...values) {
|
|
4737
|
+
for (const value of values) {
|
|
4738
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
4739
|
+
return value.trim();
|
|
4740
|
+
}
|
|
4741
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
4742
|
+
return String(value);
|
|
4743
|
+
}
|
|
4744
|
+
}
|
|
4745
|
+
return void 0;
|
|
4746
|
+
}
|
|
4747
|
+
function firstContent(...values) {
|
|
4748
|
+
for (const value of values) {
|
|
4749
|
+
const content = normalizeContent(value);
|
|
4750
|
+
if (content) return content;
|
|
4751
|
+
}
|
|
4752
|
+
return void 0;
|
|
4753
|
+
}
|
|
4754
|
+
function objectField(value) {
|
|
4755
|
+
return value && typeof value === "object" ? value : void 0;
|
|
4756
|
+
}
|
|
4757
|
+
var SESSION_KEY_FIELDS = [
|
|
4758
|
+
"sessionKey",
|
|
4759
|
+
"session_key",
|
|
4760
|
+
"sessionId",
|
|
4761
|
+
"session_id",
|
|
4762
|
+
"conversationId",
|
|
4763
|
+
"conversation_id",
|
|
4764
|
+
"threadId",
|
|
4765
|
+
"thread_id",
|
|
4766
|
+
"transcriptId",
|
|
4767
|
+
"transcript_id"
|
|
4768
|
+
];
|
|
4769
|
+
function hasUsableSessionKeyField(value) {
|
|
4770
|
+
return typeof value === "string" && value.trim().length > 0 || typeof value === "number" && Number.isFinite(value);
|
|
4771
|
+
}
|
|
4772
|
+
function inheritEnvelopeSessionFields(row, envelope) {
|
|
4773
|
+
if (!row || typeof row !== "object" || Array.isArray(row)) return row;
|
|
4774
|
+
const child = row;
|
|
4775
|
+
const inherited = {};
|
|
4776
|
+
for (const key of SESSION_KEY_FIELDS) {
|
|
4777
|
+
if (!hasUsableSessionKeyField(child[key]) && hasUsableSessionKeyField(envelope[key])) {
|
|
4778
|
+
inherited[key] = envelope[key];
|
|
4779
|
+
}
|
|
4780
|
+
}
|
|
4781
|
+
return Object.keys(inherited).length > 0 ? { ...child, ...inherited } : row;
|
|
4782
|
+
}
|
|
4783
|
+
function codexRoleFromTypes(rowType, payloadType) {
|
|
4784
|
+
if (payloadType === "agent_message") return "assistant";
|
|
4785
|
+
if (payloadType === "user_message") return "user";
|
|
4786
|
+
if (rowType === "event_msg") return "user";
|
|
4787
|
+
if (rowType === "response_item") return "assistant";
|
|
4788
|
+
return void 0;
|
|
4789
|
+
}
|
|
4790
|
+
function sessionKeyFromRow(row) {
|
|
4791
|
+
if (!row || typeof row !== "object") return void 0;
|
|
4792
|
+
const obj = row;
|
|
4793
|
+
const nestedMessage = objectField(obj.message);
|
|
4794
|
+
const payload = objectField(obj.payload);
|
|
4795
|
+
const payloadMessage = objectField(payload?.message);
|
|
4796
|
+
const rowType = firstString(obj.type, obj.kind, obj.event)?.toLowerCase();
|
|
4797
|
+
return firstString(
|
|
4798
|
+
obj.sessionKey,
|
|
4799
|
+
obj.session_key,
|
|
4800
|
+
obj.sessionId,
|
|
4801
|
+
obj.session_id,
|
|
4802
|
+
obj.conversationId,
|
|
4803
|
+
obj.conversation_id,
|
|
4804
|
+
obj.threadId,
|
|
4805
|
+
obj.thread_id,
|
|
4806
|
+
obj.transcriptId,
|
|
4807
|
+
obj.transcript_id,
|
|
4808
|
+
nestedMessage?.sessionKey,
|
|
4809
|
+
nestedMessage?.session_key,
|
|
4810
|
+
nestedMessage?.sessionId,
|
|
4811
|
+
nestedMessage?.session_id,
|
|
4812
|
+
nestedMessage?.conversationId,
|
|
4813
|
+
nestedMessage?.conversation_id,
|
|
4814
|
+
nestedMessage?.threadId,
|
|
4815
|
+
nestedMessage?.thread_id,
|
|
4816
|
+
nestedMessage?.transcriptId,
|
|
4817
|
+
nestedMessage?.transcript_id,
|
|
4818
|
+
rowType === "session_meta" ? payload?.id : void 0,
|
|
4819
|
+
payload?.sessionKey,
|
|
4820
|
+
payload?.session_key,
|
|
4821
|
+
payload?.sessionId,
|
|
4822
|
+
payload?.session_id,
|
|
4823
|
+
payload?.conversationId,
|
|
4824
|
+
payload?.conversation_id,
|
|
4825
|
+
payload?.threadId,
|
|
4826
|
+
payload?.thread_id,
|
|
4827
|
+
payload?.transcriptId,
|
|
4828
|
+
payload?.transcript_id,
|
|
4829
|
+
payloadMessage?.sessionKey,
|
|
4830
|
+
payloadMessage?.session_key,
|
|
4831
|
+
payloadMessage?.sessionId,
|
|
4832
|
+
payloadMessage?.session_id,
|
|
4833
|
+
payloadMessage?.conversationId,
|
|
4834
|
+
payloadMessage?.conversation_id,
|
|
4835
|
+
payloadMessage?.threadId,
|
|
4836
|
+
payloadMessage?.thread_id,
|
|
4837
|
+
payloadMessage?.transcriptId,
|
|
4838
|
+
payloadMessage?.transcript_id
|
|
4839
|
+
);
|
|
4840
|
+
}
|
|
4841
|
+
function extractRowsFromJson(value) {
|
|
4842
|
+
if (Array.isArray(value)) return value;
|
|
4843
|
+
if (!value || typeof value !== "object") return [];
|
|
4844
|
+
const obj = value;
|
|
4845
|
+
for (const key of ["turns", "messages", "events", "entries", "items"]) {
|
|
4846
|
+
const rows = obj[key];
|
|
4847
|
+
if (Array.isArray(rows) && rows.length > 0) {
|
|
4848
|
+
return rows.map((row) => inheritEnvelopeSessionFields(row, obj));
|
|
4849
|
+
}
|
|
4850
|
+
}
|
|
4851
|
+
return [obj];
|
|
4852
|
+
}
|
|
4853
|
+
function parseJsonlRows(content, adapterId, strict, fileRef) {
|
|
4854
|
+
const rows = [];
|
|
4855
|
+
const warnings = [];
|
|
4856
|
+
for (const [index, line] of content.split("\n").entries()) {
|
|
4857
|
+
const trimmed = line.trim();
|
|
4858
|
+
if (trimmed.length === 0) continue;
|
|
4859
|
+
try {
|
|
4860
|
+
rows.push(JSON.parse(trimmed));
|
|
4861
|
+
} catch {
|
|
4862
|
+
const warning = {
|
|
4863
|
+
code: `${adapterId}.jsonl.invalid_line`,
|
|
4864
|
+
message: `Skipping invalid JSONL line ${index + 1} in fileRef ${fileRef}.`,
|
|
4865
|
+
fileRef,
|
|
4866
|
+
line: index + 1
|
|
4867
|
+
};
|
|
4868
|
+
if (strict) throw new Error(warning.message);
|
|
4869
|
+
warnings.push(warning);
|
|
4870
|
+
}
|
|
4871
|
+
}
|
|
4872
|
+
return { rows, warnings };
|
|
4873
|
+
}
|
|
4874
|
+
function parseRows(content, extension, adapterId, strict, fileRef) {
|
|
4875
|
+
if (extension === ".jsonl") {
|
|
4876
|
+
return parseJsonlRows(content, adapterId, strict, fileRef);
|
|
4877
|
+
}
|
|
4878
|
+
try {
|
|
4879
|
+
return { rows: extractRowsFromJson(JSON.parse(content)), warnings: [] };
|
|
4880
|
+
} catch {
|
|
4881
|
+
const message = `Invalid ${adapterId} JSON in fileRef ${fileRef}; skipping file.`;
|
|
4882
|
+
if (strict) throw new Error(message);
|
|
4883
|
+
return {
|
|
4884
|
+
rows: [],
|
|
4885
|
+
warnings: [{ code: `${adapterId}.json.invalid`, message, fileRef }]
|
|
4886
|
+
};
|
|
4887
|
+
}
|
|
4888
|
+
}
|
|
4889
|
+
function turnFromRow(row, fallbackSessionKey) {
|
|
4890
|
+
if (!row || typeof row !== "object") return null;
|
|
4891
|
+
const obj = row;
|
|
4892
|
+
const nestedMessage = objectField(obj.message);
|
|
4893
|
+
const payload = objectField(obj.payload);
|
|
4894
|
+
const payloadItem = objectField(payload?.item);
|
|
4895
|
+
const payloadMessage = objectField(payload?.message);
|
|
4896
|
+
const author = objectField(obj.author);
|
|
4897
|
+
const rowType = firstString(obj.type, obj.kind, obj.event);
|
|
4898
|
+
const payloadType = firstString(payload?.type, payloadItem?.type);
|
|
4899
|
+
const codexRole = codexRoleFromTypes(rowType, payloadType);
|
|
4900
|
+
const content = firstContent(
|
|
4901
|
+
obj.content,
|
|
4902
|
+
obj.text,
|
|
4903
|
+
obj.message,
|
|
4904
|
+
obj.body,
|
|
4905
|
+
obj.parts,
|
|
4906
|
+
obj.input,
|
|
4907
|
+
payload?.message,
|
|
4908
|
+
payload?.content,
|
|
4909
|
+
payload?.text,
|
|
4910
|
+
payload?.body,
|
|
4911
|
+
payload?.parts,
|
|
4912
|
+
payload?.input,
|
|
4913
|
+
payloadMessage?.content,
|
|
4914
|
+
payloadMessage?.text,
|
|
4915
|
+
payloadMessage?.parts,
|
|
4916
|
+
payloadMessage?.input,
|
|
4917
|
+
payloadItem?.content,
|
|
4918
|
+
payloadItem?.text,
|
|
4919
|
+
payloadItem?.message,
|
|
4920
|
+
payloadItem?.parts,
|
|
4921
|
+
payloadItem?.input,
|
|
4922
|
+
nestedMessage?.content,
|
|
4923
|
+
nestedMessage?.text,
|
|
4924
|
+
nestedMessage?.parts,
|
|
4925
|
+
nestedMessage?.input
|
|
4926
|
+
);
|
|
4927
|
+
if (!content) return null;
|
|
4928
|
+
const role = normalizeRole(
|
|
4929
|
+
obj.role ?? obj.sender ?? obj.actor ?? payload?.role ?? payload?.sender ?? payloadMessage?.role ?? payloadMessage?.sender ?? payloadItem?.role ?? nestedMessage?.role ?? nestedMessage?.sender ?? author?.role ?? codexRole ?? obj.type
|
|
4930
|
+
);
|
|
4931
|
+
return {
|
|
4932
|
+
role,
|
|
4933
|
+
content,
|
|
4934
|
+
timestamp: normalizeTimestamp(
|
|
4935
|
+
obj.timestamp ?? obj.createdAt ?? obj.created_at ?? obj.time ?? obj.date ?? payload?.timestamp ?? payload?.createdAt ?? payload?.created_at ?? payloadMessage?.timestamp ?? payloadMessage?.createdAt ?? payloadMessage?.created_at ?? payloadItem?.timestamp ?? payloadItem?.created_at ?? nestedMessage?.timestamp ?? nestedMessage?.created_at
|
|
4936
|
+
),
|
|
4937
|
+
sessionKey: sessionKeyFromRow(row) ?? fallbackSessionKey,
|
|
4938
|
+
sourceId: firstString(obj.id, obj.uuid, obj.turnId, obj.turn_id, payload?.id, payloadItem?.id)
|
|
4939
|
+
};
|
|
4940
|
+
}
|
|
4941
|
+
function shouldSkipRowForAdapter(adapterId, row) {
|
|
4942
|
+
if (adapterId !== "codex-jsonl" || !row || typeof row !== "object") return false;
|
|
4943
|
+
const obj = row;
|
|
4944
|
+
const payload = objectField(obj.payload);
|
|
4945
|
+
const rowType = firstString(obj.type, obj.kind, obj.event)?.toLowerCase();
|
|
4946
|
+
const payloadType = firstString(payload?.type)?.toLowerCase();
|
|
4947
|
+
return rowType === "compacted" || payloadType === "compacted";
|
|
4948
|
+
}
|
|
4949
|
+
var CODEX_MIRROR_SURFACE_PRIORITY = {
|
|
4950
|
+
"event-agent-message": 1,
|
|
4951
|
+
"response-item": 2
|
|
4952
|
+
};
|
|
4953
|
+
function codexMirrorSurface(row) {
|
|
4954
|
+
if (!row || typeof row !== "object") return void 0;
|
|
4955
|
+
const obj = row;
|
|
4956
|
+
const payload = objectField(obj.payload);
|
|
4957
|
+
const payloadItem = objectField(payload?.item);
|
|
4958
|
+
const rowType = firstString(obj.type, obj.kind, obj.event)?.toLowerCase();
|
|
4959
|
+
const payloadType = firstString(payload?.type, payloadItem?.type)?.toLowerCase();
|
|
4960
|
+
if (rowType === "event_msg" && payloadType === "agent_message") return "event-agent-message";
|
|
4961
|
+
if (rowType === "response_item") return "response-item";
|
|
4962
|
+
return void 0;
|
|
4963
|
+
}
|
|
4964
|
+
function codexMirrorKey(adapterId, row, turn) {
|
|
4965
|
+
if (adapterId !== "codex-jsonl" || turn.role !== "assistant") return void 0;
|
|
4966
|
+
const surface = codexMirrorSurface(row);
|
|
4967
|
+
if (!surface) return void 0;
|
|
4968
|
+
const sessionKey = turn.sessionKey?.trim();
|
|
4969
|
+
const content = turn.content.replace(/\s+/g, " ").trim();
|
|
4970
|
+
if (!sessionKey || content.length === 0) return void 0;
|
|
4971
|
+
return `${sessionKey}\0${turn.role}\0${content}`;
|
|
4972
|
+
}
|
|
4973
|
+
function pushTurnWithCodexMirrorDedupe(adapterId, row, turn, turns, codexMirrorTurns) {
|
|
4974
|
+
const key = codexMirrorKey(adapterId, row, turn);
|
|
4975
|
+
const surface = key ? codexMirrorSurface(row) : void 0;
|
|
4976
|
+
if (!key || !surface) {
|
|
4977
|
+
turns.push(turn);
|
|
4978
|
+
return;
|
|
4979
|
+
}
|
|
4980
|
+
const candidates = codexMirrorTurns.get(key) ?? [];
|
|
4981
|
+
const rowSignal = turn.sourceId ?? turn.timestamp;
|
|
4982
|
+
const matchingIndex = candidates.findIndex((candidate) => {
|
|
4983
|
+
if (candidate.surface === surface) {
|
|
4984
|
+
return Boolean(candidate.rowSignal && rowSignal && candidate.rowSignal === rowSignal);
|
|
4985
|
+
}
|
|
4986
|
+
return Boolean(candidate.rowSignal && rowSignal && candidate.rowSignal === rowSignal);
|
|
4987
|
+
});
|
|
4988
|
+
const existing = matchingIndex >= 0 ? candidates[matchingIndex] : void 0;
|
|
4989
|
+
if (existing) {
|
|
4990
|
+
if (CODEX_MIRROR_SURFACE_PRIORITY[surface] > CODEX_MIRROR_SURFACE_PRIORITY[existing.surface]) {
|
|
4991
|
+
turns[existing.index] = turn;
|
|
4992
|
+
candidates[matchingIndex] = { index: existing.index, surface, ...rowSignal ? { rowSignal } : {} };
|
|
4993
|
+
codexMirrorTurns.set(key, candidates);
|
|
4994
|
+
}
|
|
4995
|
+
return;
|
|
4996
|
+
}
|
|
4997
|
+
candidates.push({ index: turns.length, surface, ...rowSignal ? { rowSignal } : {} });
|
|
4998
|
+
codexMirrorTurns.set(key, candidates);
|
|
4999
|
+
turns.push(turn);
|
|
5000
|
+
}
|
|
5001
|
+
function makeJsonTranscriptAdapter(id) {
|
|
5002
|
+
return {
|
|
5003
|
+
id,
|
|
5004
|
+
parseFile(input, options = {}) {
|
|
5005
|
+
const parsed = parseRows(input.content, input.fileExtension, id, options.strict, input.fileRef);
|
|
5006
|
+
const fallbackSessionKey = `${id}:${input.fileRef}`;
|
|
5007
|
+
let currentSessionKey = fallbackSessionKey;
|
|
5008
|
+
const turns = [];
|
|
5009
|
+
const codexMirrorTurns = /* @__PURE__ */ new Map();
|
|
5010
|
+
for (const row of parsed.rows) {
|
|
5011
|
+
if (shouldSkipRowForAdapter(id, row)) continue;
|
|
5012
|
+
currentSessionKey = sessionKeyFromRow(row) ?? currentSessionKey;
|
|
5013
|
+
const turn = turnFromRow(row, currentSessionKey);
|
|
5014
|
+
if (turn) {
|
|
5015
|
+
currentSessionKey = turn.sessionKey ?? currentSessionKey;
|
|
5016
|
+
pushTurnWithCodexMirrorDedupe(id, row, turn, turns, codexMirrorTurns);
|
|
5017
|
+
}
|
|
5018
|
+
}
|
|
5019
|
+
return { turns, warnings: parsed.warnings };
|
|
5020
|
+
}
|
|
5021
|
+
};
|
|
5022
|
+
}
|
|
5023
|
+
var BUILT_IN_ADAPTERS = /* @__PURE__ */ new Map();
|
|
5024
|
+
function registerLocalSessionSourceAdapter(adapter) {
|
|
5025
|
+
if (!adapter || typeof adapter !== "object") {
|
|
5026
|
+
throw new Error("local session source adapter must be an object");
|
|
5027
|
+
}
|
|
5028
|
+
if (typeof adapter.id !== "string" || adapter.id.trim().length === 0) {
|
|
5029
|
+
throw new Error("local session source adapter id must be a non-empty string");
|
|
5030
|
+
}
|
|
5031
|
+
if (typeof adapter.parseFile !== "function") {
|
|
5032
|
+
throw new Error(`local session adapter '${adapter.id}' must define parseFile`);
|
|
5033
|
+
}
|
|
5034
|
+
const id = adapter.id.trim();
|
|
5035
|
+
if (BUILT_IN_ADAPTERS.has(id)) {
|
|
5036
|
+
throw new Error(`local session adapter '${id}' is already registered`);
|
|
5037
|
+
}
|
|
5038
|
+
BUILT_IN_ADAPTERS.set(id, adapter.id === id ? adapter : { ...adapter, id });
|
|
5039
|
+
}
|
|
5040
|
+
function getLocalSessionSourceAdapter(id) {
|
|
5041
|
+
const key = typeof id === "string" && id.trim().length > 0 ? id.trim() : "generic-jsonl";
|
|
5042
|
+
return BUILT_IN_ADAPTERS.get(key);
|
|
5043
|
+
}
|
|
5044
|
+
function listLocalSessionSourceAdapters() {
|
|
5045
|
+
return [...BUILT_IN_ADAPTERS.keys()].sort();
|
|
5046
|
+
}
|
|
5047
|
+
for (const adapterId of ["generic-jsonl", "codex-jsonl", "claude-jsonl"]) {
|
|
5048
|
+
registerLocalSessionSourceAdapter(makeJsonTranscriptAdapter(adapterId));
|
|
5049
|
+
}
|
|
5050
|
+
|
|
5051
|
+
// src/session-summaries/redaction.ts
|
|
5052
|
+
var DEFAULT_REDACTION_RULES = [
|
|
5053
|
+
{
|
|
5054
|
+
name: "secret-token",
|
|
5055
|
+
pattern: /(?<![A-Za-z0-9_])(?:sk-[A-Za-z0-9_-]{12,}|sk-proj-[A-Za-z0-9_-]{12,}|(?:"authorization"|'authorization'|authorization)\s*[:=]\s*(?:"[A-Za-z][A-Za-z0-9._-]*\s+[^"\r\n]*"|'[A-Za-z][A-Za-z0-9._-]*\s+[^'\r\n]*'|[A-Za-z][A-Za-z0-9._-]*\s+(?:"[^"\r\n]*"|'[^'\r\n]*'|[^\s"',;]+))|(?:"(?:[A-Za-z0-9]+[_-]+)*(?:api[_-]?key|access[_-]?key|private[_-]?key|token|secret|password)"|'(?:[A-Za-z0-9]+[_-]+)*(?:api[_-]?key|access[_-]?key|private[_-]?key|token|secret|password)'|(?:[A-Za-z0-9]+[_-]+)*(?:api[_-]?key|access[_-]?key|private[_-]?key|token|secret|password))\s*[:=]\s*(?:"[^"\r\n]*"|'[^'\r\n]*'|[^\s"',;]+))/gi,
|
|
5056
|
+
replacement: "[REDACTED_SECRET]"
|
|
5057
|
+
},
|
|
5058
|
+
{
|
|
5059
|
+
name: "email",
|
|
5060
|
+
pattern: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi,
|
|
5061
|
+
replacement: "[REDACTED_EMAIL]"
|
|
5062
|
+
},
|
|
5063
|
+
{
|
|
5064
|
+
name: "private-ip",
|
|
5065
|
+
pattern: /\b(?:(?:10|127)\.(?:\d{1,3}\.){2}\d{1,3}|192\.168\.\d{1,3}\.\d{1,3}|172\.(?:1[6-9]|2\d|3[0-1])\.\d{1,3}\.\d{1,3})\b/g,
|
|
5066
|
+
replacement: "[REDACTED_PRIVATE_IP]"
|
|
5067
|
+
},
|
|
5068
|
+
{
|
|
5069
|
+
name: "url",
|
|
5070
|
+
pattern: /\bhttps?:\/\/[^\s"'<>)]*/gi,
|
|
5071
|
+
replacement: "[REDACTED_URL]"
|
|
5072
|
+
},
|
|
5073
|
+
{
|
|
5074
|
+
name: "home-relative-posix-spaced-path",
|
|
5075
|
+
pattern: /~\/(?:[^/\s"',;<>]+(?: [^/\s"',;<>]+)*\/)*[^/\s"',;<>]+(?: (?!(?:before|after|then|and|or)\b)[^/\s"',;<>]+)+\.[^/\s"',;<>]+?(?=$|[\s"',;<>)]|[.,](?:\s|$))/g,
|
|
5076
|
+
replacement: "[REDACTED_PATH]"
|
|
5077
|
+
},
|
|
5078
|
+
{
|
|
5079
|
+
name: "home-relative-posix-spaced-extensionless-path",
|
|
5080
|
+
pattern: /~\/(?:[^/\s"',;<>]+(?: [^/\s"',;<>]+)*\/)*[^/\s"',;<>]+(?: (?!(?:before|after|then|and(?=\s+[a-z])|or(?=\s+[a-z]))\b)[^/\s"',;<>]+)+?(?=$|["'<>)](?!\/)|[,;.]|\s+(?:before|after|then|and(?=\s+(?:[a-z]|[~/"']|$))|or(?=\s+(?:[a-z]|[~/"']|$)))\b)/g,
|
|
5081
|
+
replacement: "[REDACTED_PATH]"
|
|
5082
|
+
},
|
|
5083
|
+
{
|
|
5084
|
+
name: "home-relative-posix-path",
|
|
5085
|
+
pattern: /~\/(?:[^/\s"',;<>]+(?: [^/\s"',;<>]+)*\/)*[^/\s"',;<>]+?(?=$|[\s"',;<>)]|[.,](?:\s|$))/g,
|
|
5086
|
+
replacement: "[REDACTED_PATH]"
|
|
5087
|
+
},
|
|
5088
|
+
{
|
|
5089
|
+
name: "absolute-posix-spaced-path",
|
|
5090
|
+
pattern: /\/(?:[^/\s"',;<>]+(?: [^/\s"',;<>]+)*\/)*[^/\s"',;<>]+(?: (?!(?:before|after|then|and|or)\b)[^/\s"',;<>]+)+\.[^/\s"',;<>]+?(?=$|[\s"',;<>)]|[.,](?:\s|$))/g,
|
|
5091
|
+
replacement: "[REDACTED_PATH]"
|
|
5092
|
+
},
|
|
5093
|
+
{
|
|
5094
|
+
name: "absolute-posix-spaced-extensionless-path",
|
|
5095
|
+
pattern: /\/(?:[^/\s"',;<>]+(?: [^/\s"',;<>]+)*\/)*[^/\s"',;<>]+(?: (?!(?:before|after|then|and(?=\s+[a-z])|or(?=\s+[a-z]))\b)[^/\s"',;<>]+)+?(?=$|["'<>)](?!\/)|[,;.]|\s+(?:before|after|then|and(?=\s+(?:[a-z]|[~/"']|$))|or(?=\s+(?:[a-z]|[~/"']|$)))\b)/g,
|
|
5096
|
+
replacement: "[REDACTED_PATH]"
|
|
5097
|
+
},
|
|
5098
|
+
{
|
|
5099
|
+
name: "absolute-posix-path",
|
|
5100
|
+
pattern: /\/(?:[^/\s"',;<>]+(?: [^/\s"',;<>]+)*\/)*[^/\s"',;<>]+?(?=$|[\s"',;<>)]|[.,](?:\s|$))/g,
|
|
5101
|
+
replacement: "[REDACTED_PATH]"
|
|
5102
|
+
},
|
|
5103
|
+
{
|
|
5104
|
+
name: "absolute-windows-spaced-path",
|
|
5105
|
+
pattern: /\b[A-Za-z]:\\(?:[^\\/:*?"<>|,;\r\n]+\\)*[^\\/:*?"<>|,;\s\r\n]+(?: [^\\/:*?"<>|,;\s\r\n]+)+\.[A-Za-z0-9_-]+/g,
|
|
5106
|
+
replacement: "[REDACTED_PATH]"
|
|
5107
|
+
},
|
|
5108
|
+
{
|
|
5109
|
+
name: "absolute-windows-spaced-extensionless-path",
|
|
5110
|
+
pattern: /\b[A-Za-z]:\\(?:[^\\/:*?"<>|,;\r\n]+\\)*[^\\/:*?"<>|,;\s\r\n]+(?: [^\\/:*?"<>|,;\s\r\n]+)+(?=$|["'<>)](?!\\)|[,;.]|\s+(?:before|after|then|and|or)\b)/gi,
|
|
5111
|
+
replacement: "[REDACTED_PATH]"
|
|
5112
|
+
},
|
|
5113
|
+
{
|
|
5114
|
+
name: "absolute-windows-path",
|
|
5115
|
+
pattern: /\b[A-Za-z]:\\(?:[^\\/:*?"<>|,;\r\n]+\\)*[^\\/:*?"<>|,;\s\r\n]+/g,
|
|
5116
|
+
replacement: "[REDACTED_PATH]"
|
|
5117
|
+
},
|
|
5118
|
+
{
|
|
5119
|
+
name: "unc-windows-spaced-path",
|
|
5120
|
+
pattern: /\\\\[^\\/:*?"<>|,;\s\r\n]+\\[^\\/:*?"<>|,;\r\n]+\\(?:[^\\/:*?"<>|,;\r\n]+\\)*[^\\/:*?"<>|,;\s\r\n]+(?: [^\\/:*?"<>|,;\s\r\n]+)+\.[A-Za-z0-9_-]+/g,
|
|
5121
|
+
replacement: "[REDACTED_PATH]"
|
|
5122
|
+
},
|
|
5123
|
+
{
|
|
5124
|
+
name: "unc-windows-spaced-extensionless-path",
|
|
5125
|
+
pattern: /\\\\[^\\/:*?"<>|,;\s\r\n]+\\[^\\/:*?"<>|,;\r\n]+\\(?:[^\\/:*?"<>|,;\r\n]+\\)*[^\\/:*?"<>|,;\s\r\n]+(?: [^\\/:*?"<>|,;\s\r\n]+)+(?=$|["'<>)](?!\\)|[,;.]|\s+(?:before|after|then|and|or)\b)/gi,
|
|
5126
|
+
replacement: "[REDACTED_PATH]"
|
|
5127
|
+
},
|
|
5128
|
+
{
|
|
5129
|
+
name: "unc-windows-share-root",
|
|
5130
|
+
pattern: /\\\\[^\\/:*?"<>|,;\s\r\n]+\\[^\\/:*?"<>|,;\s\r\n]+(?=$|[\s"',;<>)]|[.,](?:\s|$))/g,
|
|
5131
|
+
replacement: "[REDACTED_PATH]"
|
|
5132
|
+
},
|
|
5133
|
+
{
|
|
5134
|
+
name: "unc-windows-path",
|
|
5135
|
+
pattern: /\\\\[^\\/:*?"<>|,;\s\r\n]+\\[^\\/:*?"<>|,;\r\n]+\\(?:[^\\/:*?"<>|,;\r\n]+\\)*[^\\/:*?"<>|,;\s\r\n]+/g,
|
|
5136
|
+
replacement: "[REDACTED_PATH]"
|
|
5137
|
+
}
|
|
5138
|
+
];
|
|
5139
|
+
function cloneRedactionRule(rule) {
|
|
5140
|
+
return {
|
|
5141
|
+
name: rule.name,
|
|
5142
|
+
pattern: new RegExp(rule.pattern.source, rule.pattern.flags),
|
|
5143
|
+
replacement: rule.replacement
|
|
5144
|
+
};
|
|
5145
|
+
}
|
|
5146
|
+
function compileRedactionRules(config = {}) {
|
|
5147
|
+
const rules = config.disableDefaults === true ? [] : DEFAULT_REDACTION_RULES.map(cloneRedactionRule);
|
|
5148
|
+
const customRules = config.rules ?? [];
|
|
5149
|
+
if (!Array.isArray(customRules)) {
|
|
5150
|
+
throw new Error("redaction rules must be an array");
|
|
5151
|
+
}
|
|
5152
|
+
for (const rule of customRules) {
|
|
5153
|
+
if (!rule || typeof rule !== "object") {
|
|
5154
|
+
throw new Error("redaction rule must be an object");
|
|
5155
|
+
}
|
|
5156
|
+
if (typeof rule.name !== "string" || rule.name.trim().length === 0) {
|
|
5157
|
+
throw new Error("redaction rule name must be a non-empty string");
|
|
5158
|
+
}
|
|
5159
|
+
if (typeof rule.pattern !== "string" || rule.pattern.length === 0) {
|
|
5160
|
+
throw new Error(`redaction rule '${rule.name}' pattern must be non-empty`);
|
|
5161
|
+
}
|
|
5162
|
+
const flags = rule.flags ?? "g";
|
|
5163
|
+
rules.push({
|
|
5164
|
+
name: rule.name.trim(),
|
|
5165
|
+
pattern: new RegExp(rule.pattern, flags.includes("g") ? flags : `${flags}g`),
|
|
5166
|
+
replacement: rule.replacement ?? `[REDACTED_${rule.name.trim().toUpperCase()}]`
|
|
5167
|
+
});
|
|
5168
|
+
}
|
|
5169
|
+
return rules;
|
|
5170
|
+
}
|
|
5171
|
+
function redactSessionSummaryText(text, rules) {
|
|
5172
|
+
let redacted = text;
|
|
5173
|
+
let applied = 0;
|
|
5174
|
+
const ruleNames = /* @__PURE__ */ new Set();
|
|
5175
|
+
for (const rule of rules) {
|
|
5176
|
+
rule.pattern.lastIndex = 0;
|
|
5177
|
+
const matches = redacted.match(rule.pattern);
|
|
5178
|
+
if (!matches || matches.length === 0) continue;
|
|
5179
|
+
applied += matches.length;
|
|
5180
|
+
ruleNames.add(rule.name);
|
|
5181
|
+
redacted = redacted.replace(rule.pattern, rule.replacement);
|
|
5182
|
+
}
|
|
5183
|
+
return {
|
|
5184
|
+
text: redacted,
|
|
5185
|
+
report: {
|
|
5186
|
+
applied,
|
|
5187
|
+
ruleNames: [...ruleNames].sort()
|
|
5188
|
+
}
|
|
5189
|
+
};
|
|
5190
|
+
}
|
|
5191
|
+
|
|
5192
|
+
// src/session-summaries/index.ts
|
|
5193
|
+
var DEFAULT_MAX_FILES = 5e3;
|
|
5194
|
+
var DEFAULT_MAX_SESSIONS = 500;
|
|
5195
|
+
var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([".json", ".jsonl"]);
|
|
5196
|
+
function shortHash(value, length = 16) {
|
|
5197
|
+
return createHash("sha256").update(value).digest("hex").slice(0, length);
|
|
5198
|
+
}
|
|
5199
|
+
async function listTranscriptFiles(root, maxFiles) {
|
|
5200
|
+
const out = [];
|
|
5201
|
+
let truncated = false;
|
|
5202
|
+
const entrySortKey = (entryName, isDirectory) => isDirectory ? `${entryName}${path13.sep}` : entryName;
|
|
5203
|
+
async function visit(dir) {
|
|
5204
|
+
if (truncated) return;
|
|
5205
|
+
const entries = (await readdir(dir, { withFileTypes: true })).sort(
|
|
5206
|
+
(a, b) => entrySortKey(a.name, a.isDirectory()).localeCompare(entrySortKey(b.name, b.isDirectory()))
|
|
5207
|
+
);
|
|
5208
|
+
for (const entry of entries) {
|
|
5209
|
+
if (truncated) return;
|
|
5210
|
+
if (entry.name.startsWith(".")) continue;
|
|
5211
|
+
if (entry.isSymbolicLink()) continue;
|
|
5212
|
+
const fullPath = path13.join(dir, entry.name);
|
|
5213
|
+
if (entry.isDirectory()) {
|
|
5214
|
+
await visit(fullPath);
|
|
5215
|
+
continue;
|
|
5216
|
+
}
|
|
5217
|
+
if (!entry.isFile()) continue;
|
|
5218
|
+
const ext = path13.extname(entry.name).toLowerCase();
|
|
5219
|
+
if (!SUPPORTED_EXTENSIONS.has(ext)) continue;
|
|
5220
|
+
out.push(fullPath);
|
|
5221
|
+
if (out.length > maxFiles) {
|
|
5222
|
+
truncated = true;
|
|
5223
|
+
return;
|
|
5224
|
+
}
|
|
5225
|
+
}
|
|
5226
|
+
}
|
|
5227
|
+
await visit(root);
|
|
5228
|
+
const sorted = out.sort();
|
|
5229
|
+
return {
|
|
5230
|
+
files: sorted.slice(0, maxFiles),
|
|
5231
|
+
truncated
|
|
5232
|
+
};
|
|
5233
|
+
}
|
|
5234
|
+
function validatePositiveInt(value, name, defaultValue) {
|
|
5235
|
+
if (value === void 0) return defaultValue;
|
|
5236
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
5237
|
+
throw new Error(`${name} must be a positive integer`);
|
|
5238
|
+
}
|
|
5239
|
+
return value;
|
|
5240
|
+
}
|
|
5241
|
+
function normalizeInputDir(inputDir) {
|
|
5242
|
+
if (typeof inputDir !== "string" || inputDir.trim().length === 0) {
|
|
5243
|
+
throw new Error("inputDir must be a non-empty string");
|
|
5244
|
+
}
|
|
5245
|
+
return path13.resolve(expandTildePath(inputDir.trim()));
|
|
5246
|
+
}
|
|
5247
|
+
function initRoleCounts() {
|
|
5248
|
+
return {
|
|
5249
|
+
user: 0,
|
|
5250
|
+
assistant: 0,
|
|
5251
|
+
tool: 0,
|
|
5252
|
+
system: 0,
|
|
5253
|
+
other: 0
|
|
5254
|
+
};
|
|
5255
|
+
}
|
|
5256
|
+
function safeTimestampSortValue(turn) {
|
|
5257
|
+
if (!turn.timestamp) return Number.MAX_SAFE_INTEGER;
|
|
5258
|
+
const millis = Date.parse(turn.timestamp);
|
|
5259
|
+
return Number.isFinite(millis) ? millis : Number.MAX_SAFE_INTEGER;
|
|
5260
|
+
}
|
|
5261
|
+
function earliestTurnSortValue(turns) {
|
|
5262
|
+
let earliest = Number.MAX_SAFE_INTEGER;
|
|
5263
|
+
for (const turn of turns) {
|
|
5264
|
+
const value = safeTimestampSortValue(turn);
|
|
5265
|
+
if (value < earliest) earliest = value;
|
|
5266
|
+
}
|
|
5267
|
+
return earliest;
|
|
5268
|
+
}
|
|
5269
|
+
function truncateExcerpt(text) {
|
|
5270
|
+
const compact = text.replace(/\s+/g, " ").trim();
|
|
5271
|
+
return compact.length > 240 ? `${compact.slice(0, 237)}...` : compact;
|
|
5272
|
+
}
|
|
5273
|
+
function mergeRuleNames(target, names) {
|
|
5274
|
+
for (const name of names) target.add(name);
|
|
5275
|
+
}
|
|
5276
|
+
function buildDraft(options) {
|
|
5277
|
+
const sortedTurns = [...options.turns].sort((a, b) => safeTimestampSortValue(a) - safeTimestampSortValue(b));
|
|
5278
|
+
const roles = initRoleCounts();
|
|
5279
|
+
const fileRefs = /* @__PURE__ */ new Map();
|
|
5280
|
+
let redactionApplied = 0;
|
|
5281
|
+
const redactionRuleNames = /* @__PURE__ */ new Set();
|
|
5282
|
+
const excerpts = [];
|
|
5283
|
+
for (const turn of sortedTurns) {
|
|
5284
|
+
roles[turn.role] += 1;
|
|
5285
|
+
fileRefs.set(turn.fileHash, turn.fileExtension);
|
|
5286
|
+
const redacted = redactSessionSummaryText(turn.content, options.redactionRules);
|
|
5287
|
+
redactionApplied += redacted.report.applied;
|
|
5288
|
+
mergeRuleNames(redactionRuleNames, redacted.report.ruleNames);
|
|
5289
|
+
if (options.includeRedactedExcerpts && excerpts.length < 5) {
|
|
5290
|
+
excerpts.push({
|
|
5291
|
+
role: turn.role,
|
|
5292
|
+
text: truncateExcerpt(redacted.text),
|
|
5293
|
+
...turn.timestamp ? { timestamp: turn.timestamp } : {}
|
|
5294
|
+
});
|
|
5295
|
+
}
|
|
5296
|
+
}
|
|
5297
|
+
const firstTimestamp = sortedTurns.find((turn) => turn.timestamp)?.timestamp;
|
|
5298
|
+
const lastTimestamp = [...sortedTurns].reverse().find((turn) => turn.timestamp)?.timestamp;
|
|
5299
|
+
const sourceSessionRef = shortHash(options.sessionKey);
|
|
5300
|
+
const sourceFileRefs = [...fileRefs.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([hash, extension]) => ({ hash, extension }));
|
|
5301
|
+
const dateText = firstTimestamp && lastTimestamp ? ` from ${firstTimestamp} to ${lastTimestamp}` : "";
|
|
5302
|
+
const summary = `Local AI session summary: ${sortedTurns.length} turn(s) (${roles.user} user, ${roles.assistant} assistant, ${roles.tool} tool, ${roles.system} system, ${roles.other} other)${dateText}. Raw transcript text, source session keys, and local file paths are not stored.`;
|
|
5303
|
+
return {
|
|
5304
|
+
schemaVersion: 1,
|
|
5305
|
+
draftId: shortHash(
|
|
5306
|
+
JSON.stringify({
|
|
5307
|
+
source: options.source,
|
|
5308
|
+
sourceSessionRef,
|
|
5309
|
+
firstTimestamp,
|
|
5310
|
+
lastTimestamp,
|
|
5311
|
+
turnCount: sortedTurns.length
|
|
5312
|
+
}),
|
|
5313
|
+
24
|
|
5314
|
+
),
|
|
5315
|
+
generatedAt: options.generatedAt,
|
|
5316
|
+
sourceAdapter: options.source,
|
|
5317
|
+
sourceSessionRef,
|
|
5318
|
+
sourceFileCount: sourceFileRefs.length,
|
|
5319
|
+
sourceFileRefs,
|
|
5320
|
+
...firstTimestamp ? { firstTimestamp } : {},
|
|
5321
|
+
...lastTimestamp ? { lastTimestamp } : {},
|
|
5322
|
+
turnCount: sortedTurns.length,
|
|
5323
|
+
roles,
|
|
5324
|
+
summary,
|
|
5325
|
+
redaction: {
|
|
5326
|
+
enabled: options.redactionRules.length > 0,
|
|
5327
|
+
applied: redactionApplied,
|
|
5328
|
+
ruleNames: [...redactionRuleNames].sort()
|
|
5329
|
+
},
|
|
5330
|
+
...options.includeRedactedExcerpts && excerpts.length > 0 ? { excerpts } : {},
|
|
5331
|
+
metadata: {
|
|
5332
|
+
storesRawTranscript: false,
|
|
5333
|
+
storesLocalPaths: false,
|
|
5334
|
+
storesSourceSessionKey: false
|
|
5335
|
+
}
|
|
5336
|
+
};
|
|
5337
|
+
}
|
|
5338
|
+
async function collectLocalSessionSummaries(options) {
|
|
5339
|
+
const inputDir = normalizeInputDir(options.inputDir);
|
|
5340
|
+
const inputLstat = await lstat(inputDir);
|
|
5341
|
+
if (inputLstat.isSymbolicLink()) {
|
|
5342
|
+
throw new Error(`inputDir must not be a symbolic link: ${options.inputDir}`);
|
|
5343
|
+
}
|
|
5344
|
+
const inputStat = await stat(inputDir);
|
|
5345
|
+
if (!inputStat.isDirectory()) {
|
|
5346
|
+
throw new Error(`inputDir must be a directory: ${options.inputDir}`);
|
|
5347
|
+
}
|
|
5348
|
+
const source = options.source?.trim() || "generic-jsonl";
|
|
5349
|
+
const adapter = getLocalSessionSourceAdapter(source);
|
|
5350
|
+
if (!adapter) {
|
|
5351
|
+
throw new Error(
|
|
5352
|
+
`Unknown local session source '${source}'. Valid sources: ${listLocalSessionSourceAdapters().join(", ")}`
|
|
5353
|
+
);
|
|
5354
|
+
}
|
|
5355
|
+
const maxFiles = validatePositiveInt(options.maxFiles, "maxFiles", DEFAULT_MAX_FILES);
|
|
5356
|
+
const maxSessions = validatePositiveInt(options.maxSessions, "maxSessions", DEFAULT_MAX_SESSIONS);
|
|
5357
|
+
const redactionConfig = options.redactionConfig ?? {};
|
|
5358
|
+
const excerptsRequested = options.includeRedactedExcerpts === true;
|
|
5359
|
+
const redactionRules = compileRedactionRules({
|
|
5360
|
+
...redactionConfig,
|
|
5361
|
+
...excerptsRequested ? { disableDefaults: false } : {}
|
|
5362
|
+
});
|
|
5363
|
+
const includeRedactedExcerpts = options.includeRedactedExcerpts === true && redactionRules.length > 0;
|
|
5364
|
+
const listed = await listTranscriptFiles(inputDir, maxFiles);
|
|
5365
|
+
const files = listed.files;
|
|
5366
|
+
const generatedAt = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
|
|
5367
|
+
const warnings = [];
|
|
5368
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
5369
|
+
const seenFileHashes = /* @__PURE__ */ new Set();
|
|
5370
|
+
let turnsParsed = 0;
|
|
5371
|
+
let filesParsed = 0;
|
|
5372
|
+
if (listed.truncated) {
|
|
5373
|
+
warnings.push({
|
|
5374
|
+
code: "session-summaries.max_files_truncated",
|
|
5375
|
+
message: `Scanned the first ${maxFiles} transcript file(s); increase maxFiles to include all matching files.`
|
|
5376
|
+
});
|
|
5377
|
+
}
|
|
5378
|
+
if (excerptsRequested && redactionConfig.disableDefaults === true) {
|
|
5379
|
+
warnings.push({
|
|
5380
|
+
code: "session-summaries.default_redaction_forced_for_excerpts",
|
|
5381
|
+
message: "Default redaction rules were enabled because redacted excerpts were requested."
|
|
5382
|
+
});
|
|
5383
|
+
}
|
|
5384
|
+
if (excerptsRequested && redactionRules.length === 0) {
|
|
5385
|
+
warnings.push({
|
|
5386
|
+
code: "session-summaries.excerpts_suppressed_no_redaction",
|
|
5387
|
+
message: "Redacted excerpts were requested, but no redaction rules are enabled; excerpts were omitted."
|
|
5388
|
+
});
|
|
5389
|
+
}
|
|
5390
|
+
for (const filePath of files) {
|
|
5391
|
+
const content = await readFile(filePath, "utf-8");
|
|
5392
|
+
const fileHash = shortHash(content, 24);
|
|
5393
|
+
if (seenFileHashes.has(fileHash)) {
|
|
5394
|
+
warnings.push({
|
|
5395
|
+
code: "session-summaries.duplicate_file_skipped",
|
|
5396
|
+
message: "Skipped a duplicate transcript file with identical content.",
|
|
5397
|
+
fileRef: fileHash
|
|
5398
|
+
});
|
|
5399
|
+
continue;
|
|
5400
|
+
}
|
|
5401
|
+
seenFileHashes.add(fileHash);
|
|
5402
|
+
const fileExtension = path13.extname(filePath).toLowerCase();
|
|
5403
|
+
const parsed = await adapter.parseFile(
|
|
5404
|
+
{
|
|
5405
|
+
content,
|
|
5406
|
+
fileName: path13.basename(filePath),
|
|
5407
|
+
fileExtension,
|
|
5408
|
+
fileRef: fileHash
|
|
5409
|
+
},
|
|
5410
|
+
{ strict: options.strict }
|
|
5411
|
+
);
|
|
5412
|
+
warnings.push(...parsed.warnings);
|
|
5413
|
+
if (parsed.turns.length === 0) continue;
|
|
5414
|
+
filesParsed += 1;
|
|
5415
|
+
for (const turn of parsed.turns) {
|
|
5416
|
+
turnsParsed += 1;
|
|
5417
|
+
const sessionKey = turn.sessionKey?.trim() || `${adapter.id}:${fileHash}`;
|
|
5418
|
+
const bucket = sessions.get(sessionKey) ?? [];
|
|
5419
|
+
bucket.push({ ...turn, fileHash, fileExtension });
|
|
5420
|
+
sessions.set(sessionKey, bucket);
|
|
5421
|
+
}
|
|
5422
|
+
}
|
|
5423
|
+
const drafts = [];
|
|
5424
|
+
const sessionEntries = [...sessions.entries()].sort(([aKey, a], [bKey, b]) => {
|
|
5425
|
+
const aFirst = earliestTurnSortValue(a);
|
|
5426
|
+
const bFirst = earliestTurnSortValue(b);
|
|
5427
|
+
return aFirst - bFirst || a[0]?.fileHash.localeCompare(b[0]?.fileHash ?? "") || aKey.localeCompare(bKey);
|
|
5428
|
+
});
|
|
5429
|
+
if (sessionEntries.length > maxSessions) {
|
|
5430
|
+
warnings.push({
|
|
5431
|
+
code: "session-summaries.max_sessions_truncated",
|
|
5432
|
+
message: `Summarized ${maxSessions} of ${sessionEntries.length} session(s); increase maxSessions to include all parsed sessions.`
|
|
5433
|
+
});
|
|
5434
|
+
}
|
|
5435
|
+
for (const [sessionKey, turns] of sessionEntries.slice(0, maxSessions)) {
|
|
5436
|
+
drafts.push(
|
|
5437
|
+
buildDraft({
|
|
5438
|
+
source: adapter.id,
|
|
5439
|
+
sessionKey,
|
|
5440
|
+
turns,
|
|
5441
|
+
generatedAt,
|
|
5442
|
+
redactionRules,
|
|
5443
|
+
includeRedactedExcerpts
|
|
5444
|
+
})
|
|
5445
|
+
);
|
|
5446
|
+
}
|
|
5447
|
+
drafts.sort((a, b) => (a.firstTimestamp ?? "").localeCompare(b.firstTimestamp ?? ""));
|
|
5448
|
+
return {
|
|
5449
|
+
generatedAt,
|
|
5450
|
+
source: adapter.id,
|
|
5451
|
+
filesScanned: files.length,
|
|
5452
|
+
filesParsed,
|
|
5453
|
+
turnsParsed,
|
|
5454
|
+
sessionsSummarized: drafts.length,
|
|
5455
|
+
warnings,
|
|
5456
|
+
drafts,
|
|
5457
|
+
wroteFiles: []
|
|
5458
|
+
};
|
|
5459
|
+
}
|
|
5460
|
+
async function readRedactionConfig(pathLike) {
|
|
5461
|
+
if (!pathLike) return void 0;
|
|
5462
|
+
const raw = await readFile(path13.resolve(expandTildePath(pathLike)), "utf-8");
|
|
5463
|
+
const parsed = JSON.parse(raw);
|
|
5464
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
5465
|
+
throw new Error("redaction config must be a JSON object");
|
|
5466
|
+
}
|
|
5467
|
+
return parsed;
|
|
5468
|
+
}
|
|
5469
|
+
function toJsonl(drafts) {
|
|
5470
|
+
return `${drafts.map((draft) => JSON.stringify(draft)).join("\n")}
|
|
5471
|
+
`;
|
|
5472
|
+
}
|
|
5473
|
+
function defaultDraftOutputPath(memoryDir, generatedAt) {
|
|
5474
|
+
const stamp = generatedAt.replace(/[:.]/g, "-");
|
|
5475
|
+
return path13.join(
|
|
5476
|
+
path13.resolve(expandTildePath(memoryDir)),
|
|
5477
|
+
"state",
|
|
5478
|
+
"session-summary-drafts",
|
|
5479
|
+
`session-summaries-${stamp}.jsonl`
|
|
5480
|
+
);
|
|
5481
|
+
}
|
|
5482
|
+
async function writeDrafts(filePath, drafts) {
|
|
5483
|
+
await mkdir(path13.dirname(filePath), { recursive: true });
|
|
5484
|
+
const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
5485
|
+
try {
|
|
5486
|
+
await writeFile(tempPath, toJsonl(drafts), "utf-8");
|
|
5487
|
+
await rename(tempPath, filePath);
|
|
5488
|
+
} catch (error) {
|
|
5489
|
+
await rm(tempPath, { force: true });
|
|
5490
|
+
throw error;
|
|
5491
|
+
}
|
|
5492
|
+
}
|
|
5493
|
+
async function runLocalSessionSummaryCliCommand(options) {
|
|
5494
|
+
const redactionConfig = options.redactionConfig ?? await readRedactionConfig(options.redactionConfigPath);
|
|
5495
|
+
const report = await collectLocalSessionSummaries({
|
|
5496
|
+
...options,
|
|
5497
|
+
...redactionConfig ? { redactionConfig } : {}
|
|
5498
|
+
});
|
|
5499
|
+
const wroteFiles = [];
|
|
5500
|
+
if (options.output) {
|
|
5501
|
+
const outputPath = path13.resolve(expandTildePath(options.output));
|
|
5502
|
+
await writeDrafts(outputPath, report.drafts);
|
|
5503
|
+
wroteFiles.push(outputPath);
|
|
5504
|
+
}
|
|
5505
|
+
if (options.write === true) {
|
|
5506
|
+
if (!options.memoryDir || options.memoryDir.trim().length === 0) {
|
|
5507
|
+
throw new Error("memoryDir is required when write is true");
|
|
5508
|
+
}
|
|
5509
|
+
const outputPath = defaultDraftOutputPath(options.memoryDir, report.generatedAt);
|
|
5510
|
+
await writeDrafts(outputPath, report.drafts);
|
|
5511
|
+
wroteFiles.push(outputPath);
|
|
5512
|
+
}
|
|
5513
|
+
const stdout = options.stdout;
|
|
5514
|
+
if (stdout) {
|
|
5515
|
+
stdout.write(`Local session summaries complete (source: ${report.source})
|
|
5516
|
+
`);
|
|
5517
|
+
stdout.write(` Files scanned: ${report.filesScanned}
|
|
5518
|
+
`);
|
|
5519
|
+
stdout.write(` Files parsed: ${report.filesParsed}
|
|
5520
|
+
`);
|
|
5521
|
+
stdout.write(` Turns parsed: ${report.turnsParsed}
|
|
5522
|
+
`);
|
|
5523
|
+
stdout.write(` Sessions summarized: ${report.sessionsSummarized}
|
|
5524
|
+
`);
|
|
5525
|
+
stdout.write(` Draft files written: ${wroteFiles.length}
|
|
5526
|
+
`);
|
|
5527
|
+
if (wroteFiles.length === 0) {
|
|
5528
|
+
stdout.write(" (dry run - no Remnic draft files were written)\n");
|
|
5529
|
+
}
|
|
5530
|
+
}
|
|
5531
|
+
return { ...report, wroteFiles };
|
|
5532
|
+
}
|
|
5533
|
+
|
|
5534
|
+
// src/transfer/capsule-fork.ts
|
|
5535
|
+
import { lstat as lstat2, mkdir as mkdir2, readFile as readFile2, realpath, writeFile as writeFile2 } from "fs/promises";
|
|
5536
|
+
import path14 from "path";
|
|
4697
5537
|
async function forkCapsule(opts) {
|
|
4698
5538
|
validateForkId(opts.forkId);
|
|
4699
|
-
const rootAbs =
|
|
5539
|
+
const rootAbs = path14.resolve(opts.targetRoot);
|
|
4700
5540
|
await assertIsDirectoryNotSymlink(rootAbs, "forkCapsule", "targetRoot");
|
|
4701
|
-
const forkDirAbs =
|
|
5541
|
+
const forkDirAbs = path14.join(rootAbs, "forks", opts.forkId);
|
|
4702
5542
|
const forkEntryExists = await pathEntryExists(forkDirAbs);
|
|
4703
5543
|
if (forkEntryExists) {
|
|
4704
5544
|
throw new Error(
|
|
4705
5545
|
`forkCapsule: fork path already exists \u2014 forkId "${opts.forkId}" is already in use at: ${forkDirAbs}`
|
|
4706
5546
|
);
|
|
4707
5547
|
}
|
|
4708
|
-
const archiveAbs =
|
|
5548
|
+
const archiveAbs = path14.resolve(opts.sourceArchive);
|
|
4709
5549
|
const importResult = await importCapsule({
|
|
4710
5550
|
archivePath: archiveAbs,
|
|
4711
5551
|
root: rootAbs,
|
|
@@ -4727,11 +5567,11 @@ async function forkCapsule(opts) {
|
|
|
4727
5567
|
importedRecords: importResult.imported.length,
|
|
4728
5568
|
skippedRecords: importResult.skipped.length
|
|
4729
5569
|
};
|
|
4730
|
-
const lineagePath =
|
|
5570
|
+
const lineagePath = path14.join(forkDirAbs, "lineage.json");
|
|
4731
5571
|
const rootReal = await realpath(rootAbs);
|
|
4732
5572
|
await assertRealpathInsideRoot(rootReal, lineagePath, `forks/${opts.forkId}/lineage.json`, "forkCapsule");
|
|
4733
|
-
await
|
|
4734
|
-
await
|
|
5573
|
+
await mkdir2(path14.dirname(lineagePath), { recursive: true });
|
|
5574
|
+
await writeFile2(lineagePath, JSON.stringify(lineage, null, 2) + "\n", "utf-8");
|
|
4735
5575
|
return {
|
|
4736
5576
|
archivePath: archiveAbs,
|
|
4737
5577
|
manifest,
|
|
@@ -4744,17 +5584,17 @@ async function readForkLineage(targetRoot, forkId) {
|
|
|
4744
5584
|
if (typeof forkId !== "string" || forkId.length === 0 || forkId.length > 64 || !CAPSULE_ID_PATTERN.test(forkId)) {
|
|
4745
5585
|
return null;
|
|
4746
5586
|
}
|
|
4747
|
-
const rootAbs =
|
|
5587
|
+
const rootAbs = path14.resolve(targetRoot);
|
|
4748
5588
|
const rootReal = await realpath(rootAbs).catch(() => rootAbs);
|
|
4749
|
-
const lineagePath =
|
|
4750
|
-
const rel =
|
|
4751
|
-
if (rel.startsWith("..") ||
|
|
5589
|
+
const lineagePath = path14.join(rootReal, "forks", forkId, "lineage.json");
|
|
5590
|
+
const rel = path14.relative(rootReal, lineagePath);
|
|
5591
|
+
if (rel.startsWith("..") || path14.isAbsolute(rel)) {
|
|
4752
5592
|
return null;
|
|
4753
5593
|
}
|
|
4754
5594
|
if (!await isLineagePathContained(rootReal, lineagePath)) {
|
|
4755
5595
|
return null;
|
|
4756
5596
|
}
|
|
4757
|
-
const raw = await
|
|
5597
|
+
const raw = await readFile2(lineagePath, "utf-8").catch(() => null);
|
|
4758
5598
|
if (raw === null) return null;
|
|
4759
5599
|
try {
|
|
4760
5600
|
const parsed = JSON.parse(raw);
|
|
@@ -4783,25 +5623,25 @@ function validateForkId(forkId) {
|
|
|
4783
5623
|
}
|
|
4784
5624
|
}
|
|
4785
5625
|
async function pathEntryExists(absPath) {
|
|
4786
|
-
const st = await
|
|
5626
|
+
const st = await lstat2(absPath).catch(() => null);
|
|
4787
5627
|
return st !== null;
|
|
4788
5628
|
}
|
|
4789
5629
|
async function isLineagePathContained(rootReal, lineagePath) {
|
|
4790
5630
|
let existing = lineagePath;
|
|
4791
5631
|
const suffix = [];
|
|
4792
|
-
while (existing !==
|
|
4793
|
-
const st = await
|
|
5632
|
+
while (existing !== path14.dirname(existing)) {
|
|
5633
|
+
const st = await lstat2(existing).catch(() => null);
|
|
4794
5634
|
if (st !== null) break;
|
|
4795
|
-
suffix.unshift(
|
|
4796
|
-
existing =
|
|
5635
|
+
suffix.unshift(path14.basename(existing));
|
|
5636
|
+
existing = path14.dirname(existing);
|
|
4797
5637
|
}
|
|
4798
5638
|
const existingReal = await realpath(existing).catch(() => existing);
|
|
4799
|
-
const targetReal = suffix.length > 0 ?
|
|
4800
|
-
const rel =
|
|
5639
|
+
const targetReal = suffix.length > 0 ? path14.join(existingReal, ...suffix) : existingReal;
|
|
5640
|
+
const rel = path14.relative(rootReal, targetReal);
|
|
4801
5641
|
if (rel === "") return true;
|
|
4802
5642
|
if (rel === "..") return false;
|
|
4803
|
-
if (rel.startsWith(`..${
|
|
4804
|
-
if (
|
|
5643
|
+
if (rel.startsWith(`..${path14.sep}`)) return false;
|
|
5644
|
+
if (path14.isAbsolute(rel)) return false;
|
|
4805
5645
|
return true;
|
|
4806
5646
|
}
|
|
4807
5647
|
export {
|
|
@@ -4831,6 +5671,7 @@ export {
|
|
|
4831
5671
|
DEFAULT_PPR_TOLERANCE,
|
|
4832
5672
|
DEFAULT_REASONING_TRACE_BOOST,
|
|
4833
5673
|
DEFAULT_RECALL_DISCLOSURE,
|
|
5674
|
+
DEFAULT_REDACTION_RULES,
|
|
4834
5675
|
DEFAULT_SCAN_PATTERNS,
|
|
4835
5676
|
DEFAULT_SELF_NAME,
|
|
4836
5677
|
DEFAULT_SURPRISE_K,
|
|
@@ -4956,12 +5797,14 @@ export {
|
|
|
4956
5797
|
collectContentLexicalCues,
|
|
4957
5798
|
collectExplicitTurnReferences,
|
|
4958
5799
|
collectLexicalCues,
|
|
5800
|
+
collectLocalSessionSummaries,
|
|
4959
5801
|
collectQuestionSlotCues,
|
|
4960
5802
|
collectStructuredPlanCues,
|
|
4961
5803
|
collectTemporalLexicalCues,
|
|
4962
5804
|
compileCorrectionRule,
|
|
4963
5805
|
compileCorrectionRules,
|
|
4964
5806
|
compileRedactionPatterns,
|
|
5807
|
+
compileRedactionRules,
|
|
4965
5808
|
composeDayTranscriptBody,
|
|
4966
5809
|
composeDayTranscriptMeta,
|
|
4967
5810
|
computeMemoryWorth,
|
|
@@ -5034,6 +5877,7 @@ export {
|
|
|
5034
5877
|
getBulkImportSource,
|
|
5035
5878
|
getConnectorToken,
|
|
5036
5879
|
getHostEmbeddingProvider,
|
|
5880
|
+
getLocalSessionSourceAdapter,
|
|
5037
5881
|
getManifestPath,
|
|
5038
5882
|
getMemoryForActiveMemory,
|
|
5039
5883
|
getSpacesDir,
|
|
@@ -5083,6 +5927,7 @@ export {
|
|
|
5083
5927
|
listBulkImportSources,
|
|
5084
5928
|
listConnectorStates,
|
|
5085
5929
|
listConnectors,
|
|
5930
|
+
listLocalSessionSourceAdapters,
|
|
5086
5931
|
listPairs,
|
|
5087
5932
|
listPeers,
|
|
5088
5933
|
listReviewItems,
|
|
@@ -5183,9 +6028,11 @@ export {
|
|
|
5183
6028
|
recallRequestSchema,
|
|
5184
6029
|
recordMemoryOutcome,
|
|
5185
6030
|
redactConnectorConfigSecrets,
|
|
6031
|
+
redactSessionSummaryText,
|
|
5186
6032
|
redactText,
|
|
5187
6033
|
registerBulkImportSource,
|
|
5188
6034
|
registerHostEmbeddingProvider,
|
|
6035
|
+
registerLocalSessionSourceAdapter,
|
|
5189
6036
|
registerPublisher,
|
|
5190
6037
|
registerTrainingExportAdapter,
|
|
5191
6038
|
registerWearableConnector,
|
|
@@ -5228,6 +6075,7 @@ export {
|
|
|
5228
6075
|
runGraphRecall,
|
|
5229
6076
|
runImporter,
|
|
5230
6077
|
runLiveConnectorsOnce,
|
|
6078
|
+
runLocalSessionSummaryCliCommand,
|
|
5231
6079
|
runPostConsolidationMaterialize,
|
|
5232
6080
|
runWearablesCliCommand,
|
|
5233
6081
|
sanitizeNoteForCitation,
|