@remnic/core 9.10.0 → 9.12.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/access-cli.js +1 -1
- package/dist/{chunk-7PY55HMI.js → chunk-3VBXP5HS.js} +626 -38
- package/dist/chunk-3VBXP5HS.js.map +1 -0
- package/dist/index.d.ts +154 -2
- package/dist/index.js +31 -1
- package/dist/orchestrator.js +1 -1
- package/package.json +2 -2
- package/src/activity/digest.test.ts +180 -0
- package/src/activity/digest.ts +429 -0
- package/src/activity/index.ts +9 -0
- package/src/activity/store.test.ts +226 -0
- package/src/activity/store.ts +352 -0
- package/src/activity/types.ts +86 -0
- package/src/admin/admin-surfaces.test.ts +4 -5
- package/src/chat/chat-cli.test.ts +4 -7
- package/src/chat/chat-engine.test.ts +4 -8
- package/src/chat/chat-http.test.ts +4 -7
- package/src/chat/chat-security.test.ts +4 -6
- package/src/console/trace.test.ts +4 -9
- package/src/contradiction/contradiction.test.ts +4 -3
- package/src/correction/correction-executor.test.ts +4 -10
- package/src/correction/correction-planner.test.ts +5 -10
- package/src/extraction-redaction-rules.test.ts +5 -10
- package/src/index.ts +1 -1
- package/src/maintenance/graph-edge-decay.test.ts +5 -10
- package/src/peers/migrate-from-identity-anchor.test.ts +3 -4
- package/src/peers/peers.test.ts +3 -4
- package/src/session-summaries/session-summaries.test.ts +4 -8
- package/src/testing/tmp-dir.ts +68 -0
- package/src/transfer/capsule-encrypt.test.ts +4 -5
- package/src/write-quarantine.test.ts +4 -8
- package/dist/chunk-7PY55HMI.js.map +0 -1
|
@@ -452,6 +452,9 @@ import {
|
|
|
452
452
|
import {
|
|
453
453
|
assertPathInsideRoot
|
|
454
454
|
} from "./chunk-GCASGYIO.js";
|
|
455
|
+
import {
|
|
456
|
+
openBetterSqlite3
|
|
457
|
+
} from "./chunk-RL6NEVXT.js";
|
|
455
458
|
import {
|
|
456
459
|
inferMemoryStatus,
|
|
457
460
|
isActiveMemoryStatus
|
|
@@ -2075,7 +2078,7 @@ var RecallRerankCoordinator = class _RecallRerankCoordinator {
|
|
|
2075
2078
|
namespaces,
|
|
2076
2079
|
{
|
|
2077
2080
|
readNamespaceMemories: async (ns) => (await this.getStorage(ns)).readAllMemories(),
|
|
2078
|
-
readMemoryFrontmatter: async (
|
|
2081
|
+
readMemoryFrontmatter: async (path33, preferredNamespace) => {
|
|
2079
2082
|
if (!fallbackReader) {
|
|
2080
2083
|
for (const ns of namespaces) {
|
|
2081
2084
|
try {
|
|
@@ -2087,7 +2090,7 @@ var RecallRerankCoordinator = class _RecallRerankCoordinator {
|
|
|
2087
2090
|
}
|
|
2088
2091
|
if (!fallbackReader) return null;
|
|
2089
2092
|
const memory = await this.readQmdResultMemory(
|
|
2090
|
-
|
|
2093
|
+
path33,
|
|
2091
2094
|
fallbackReader,
|
|
2092
2095
|
namespaces,
|
|
2093
2096
|
preferredNamespace
|
|
@@ -19165,9 +19168,9 @@ function readStructuralSymbol(entry) {
|
|
|
19165
19168
|
const rec = entry;
|
|
19166
19169
|
const symbol = typeof rec.symbol === "string" ? rec.symbol.trim() : "";
|
|
19167
19170
|
if (!symbol) return null;
|
|
19168
|
-
const
|
|
19171
|
+
const path33 = typeof rec.path === "string" && rec.path.length > 0 ? rec.path : void 0;
|
|
19169
19172
|
const kind = typeof rec.kind === "string" && rec.kind.length > 0 ? rec.kind : void 0;
|
|
19170
|
-
const item =
|
|
19173
|
+
const item = path33 !== void 0 && kind !== void 0 ? { symbol, path: path33, kind } : path33 !== void 0 ? { symbol, path: path33 } : kind !== void 0 ? { symbol, kind } : { symbol };
|
|
19171
19174
|
return item;
|
|
19172
19175
|
}
|
|
19173
19176
|
function classifySpawnError(err) {
|
|
@@ -22912,10 +22915,580 @@ function publisherForConnector(connectorId) {
|
|
|
22912
22915
|
return publisherFor(hostIdForConnector(connectorId));
|
|
22913
22916
|
}
|
|
22914
22917
|
|
|
22915
|
-
// src/
|
|
22916
|
-
import {
|
|
22917
|
-
import {
|
|
22918
|
+
// src/activity/store.ts
|
|
22919
|
+
import { mkdirSync } from "fs";
|
|
22920
|
+
import { mkdir as mkdir7 } from "fs/promises";
|
|
22918
22921
|
import path29 from "path";
|
|
22922
|
+
var ACTIVITY_SCHEMA_VERSION = 1;
|
|
22923
|
+
var MAX_SEARCH_RESULTS = 100;
|
|
22924
|
+
function activityDatabasePath(memoryDir) {
|
|
22925
|
+
return path29.join(memoryDir, "state", "activity.sqlite");
|
|
22926
|
+
}
|
|
22927
|
+
async function ensureActivityStateDir(memoryDir) {
|
|
22928
|
+
await mkdir7(path29.join(memoryDir, "state"), { recursive: true });
|
|
22929
|
+
}
|
|
22930
|
+
function openActivityDatabase(memoryDir) {
|
|
22931
|
+
mkdirSync(path29.join(memoryDir, "state"), { recursive: true });
|
|
22932
|
+
const db = openBetterSqlite3(activityDatabasePath(memoryDir));
|
|
22933
|
+
db.pragma("journal_mode = WAL");
|
|
22934
|
+
db.pragma("busy_timeout = 5000");
|
|
22935
|
+
db.pragma("synchronous = NORMAL");
|
|
22936
|
+
applySchema(db);
|
|
22937
|
+
return db;
|
|
22938
|
+
}
|
|
22939
|
+
function applyActivitySchema(db) {
|
|
22940
|
+
applySchema(db);
|
|
22941
|
+
}
|
|
22942
|
+
function applySchema(db) {
|
|
22943
|
+
db.exec(`
|
|
22944
|
+
CREATE TABLE IF NOT EXISTS activity_meta (
|
|
22945
|
+
key TEXT PRIMARY KEY,
|
|
22946
|
+
value TEXT NOT NULL
|
|
22947
|
+
);
|
|
22948
|
+
|
|
22949
|
+
CREATE TABLE IF NOT EXISTS activity_snapshots (
|
|
22950
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
22951
|
+
machine TEXT NOT NULL,
|
|
22952
|
+
captured_at_utc TEXT NOT NULL,
|
|
22953
|
+
app_name TEXT NOT NULL,
|
|
22954
|
+
window_title TEXT NOT NULL,
|
|
22955
|
+
browser_url TEXT,
|
|
22956
|
+
text TEXT NOT NULL,
|
|
22957
|
+
text_source TEXT NOT NULL,
|
|
22958
|
+
content_hash TEXT NOT NULL,
|
|
22959
|
+
simhash TEXT
|
|
22960
|
+
);
|
|
22961
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_activity_snapshots_dedup
|
|
22962
|
+
ON activity_snapshots(machine, captured_at_utc, content_hash);
|
|
22963
|
+
CREATE INDEX IF NOT EXISTS idx_activity_snapshots_time
|
|
22964
|
+
ON activity_snapshots(captured_at_utc, id);
|
|
22965
|
+
|
|
22966
|
+
CREATE TABLE IF NOT EXISTS activity_sync_state (
|
|
22967
|
+
machine TEXT PRIMARY KEY,
|
|
22968
|
+
cursor TEXT,
|
|
22969
|
+
updated_at_utc TEXT NOT NULL
|
|
22970
|
+
);
|
|
22971
|
+
`);
|
|
22972
|
+
const hasFts = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='activity_snapshots_fts'").get();
|
|
22973
|
+
if (!hasFts) {
|
|
22974
|
+
db.exec(`
|
|
22975
|
+
CREATE VIRTUAL TABLE activity_snapshots_fts USING fts5(
|
|
22976
|
+
text, app_name, window_title, browser_url
|
|
22977
|
+
);
|
|
22978
|
+
`);
|
|
22979
|
+
}
|
|
22980
|
+
db.prepare("INSERT OR REPLACE INTO activity_meta (key, value) VALUES ('schema_version', ?)").run(
|
|
22981
|
+
String(ACTIVITY_SCHEMA_VERSION)
|
|
22982
|
+
);
|
|
22983
|
+
}
|
|
22984
|
+
function canonicalizeUtc(iso) {
|
|
22985
|
+
const parsed = Date.parse(iso);
|
|
22986
|
+
return Number.isFinite(parsed) ? new Date(parsed).toISOString() : iso;
|
|
22987
|
+
}
|
|
22988
|
+
function assertRequiredSnapshotFields(s) {
|
|
22989
|
+
const required = [
|
|
22990
|
+
["machine", s.machine],
|
|
22991
|
+
["app", s.app],
|
|
22992
|
+
["windowTitle", s.windowTitle],
|
|
22993
|
+
["text", s.text],
|
|
22994
|
+
["textSource", s.textSource],
|
|
22995
|
+
["contentHash", s.contentHash]
|
|
22996
|
+
];
|
|
22997
|
+
for (const [field, value] of required) {
|
|
22998
|
+
if (typeof value !== "string") {
|
|
22999
|
+
throw new RangeError(`activity: snapshot field "${field}" is required (got ${value === null ? "null" : typeof value}).`);
|
|
23000
|
+
}
|
|
23001
|
+
}
|
|
23002
|
+
if (s.machine.length === 0 || s.contentHash.length === 0) {
|
|
23003
|
+
throw new RangeError('activity: snapshot "machine" and "contentHash" must be non-empty.');
|
|
23004
|
+
}
|
|
23005
|
+
if (s.textSource !== "ax" && s.textSource !== "ocr") {
|
|
23006
|
+
throw new RangeError(`activity: snapshot "textSource" must be "ax" or "ocr" (got "${s.textSource}").`);
|
|
23007
|
+
}
|
|
23008
|
+
}
|
|
23009
|
+
function assertValidUtcInstant(iso, what) {
|
|
23010
|
+
const m = iso.match(
|
|
23011
|
+
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|[+-](?:0\d:[0-5]\d|1[0-3]:[0-5]\d|14:00))$/
|
|
23012
|
+
);
|
|
23013
|
+
if (m === null || !Number.isFinite(Date.parse(iso))) {
|
|
23014
|
+
throw new RangeError(`activity: invalid ${what} "${iso}".`);
|
|
23015
|
+
}
|
|
23016
|
+
const [year, month, day, hour, minute, second] = m.slice(1).map(Number);
|
|
23017
|
+
const daysInMonth = month >= 1 && month <= 12 ? new Date(Date.UTC(year, month, 0)).getUTCDate() : 0;
|
|
23018
|
+
if (day < 1 || day > daysInMonth || hour > 23 || minute > 59 || second > 59) {
|
|
23019
|
+
throw new RangeError(`activity: ${what} "${iso}" is not a real calendar instant.`);
|
|
23020
|
+
}
|
|
23021
|
+
}
|
|
23022
|
+
function isRecord(value) {
|
|
23023
|
+
return typeof value === "object" && value !== null;
|
|
23024
|
+
}
|
|
23025
|
+
function str(value) {
|
|
23026
|
+
return typeof value === "string" ? value : "";
|
|
23027
|
+
}
|
|
23028
|
+
function optStr(value) {
|
|
23029
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
23030
|
+
}
|
|
23031
|
+
function ftsMatchFor(query) {
|
|
23032
|
+
if (typeof query !== "string") return null;
|
|
23033
|
+
const tokens = query.match(/[\p{L}\p{N}_]+/gu);
|
|
23034
|
+
if (!tokens || tokens.length === 0) return null;
|
|
23035
|
+
return tokens.map((token) => `"${token}"`).join(" ");
|
|
23036
|
+
}
|
|
23037
|
+
function rowToSnapshot(row) {
|
|
23038
|
+
if (!isRecord(row)) {
|
|
23039
|
+
throw new Error("activity store: unexpected non-object row");
|
|
23040
|
+
}
|
|
23041
|
+
const textSource = row.text_source === "ocr" ? "ocr" : "ax";
|
|
23042
|
+
return {
|
|
23043
|
+
id: typeof row.id === "number" ? row.id : void 0,
|
|
23044
|
+
machine: str(row.machine),
|
|
23045
|
+
capturedAtUtc: str(row.captured_at_utc),
|
|
23046
|
+
app: str(row.app_name),
|
|
23047
|
+
windowTitle: str(row.window_title),
|
|
23048
|
+
...optStr(row.browser_url) !== void 0 ? { browserUrl: optStr(row.browser_url) } : {},
|
|
23049
|
+
text: str(row.text),
|
|
23050
|
+
textSource,
|
|
23051
|
+
contentHash: str(row.content_hash),
|
|
23052
|
+
...optStr(row.simhash) !== void 0 ? { simhash: optStr(row.simhash) } : {}
|
|
23053
|
+
};
|
|
23054
|
+
}
|
|
23055
|
+
var ActivityStore = class _ActivityStore {
|
|
23056
|
+
db;
|
|
23057
|
+
constructor(db) {
|
|
23058
|
+
this.db = db;
|
|
23059
|
+
}
|
|
23060
|
+
static open(memoryDir) {
|
|
23061
|
+
return new _ActivityStore(openActivityDatabase(memoryDir));
|
|
23062
|
+
}
|
|
23063
|
+
/**
|
|
23064
|
+
* Insert a snapshot, idempotent on (machine, captured_at_utc, content_hash):
|
|
23065
|
+
* the same screen content recurring at a *different* time is kept; only an
|
|
23066
|
+
* exact re-ingestion of the same capture dedups. Returns `inserted: false`
|
|
23067
|
+
* for a duplicate. The FTS row is written only on a real insert (atomically),
|
|
23068
|
+
* so it never drifts from the base table.
|
|
23069
|
+
*/
|
|
23070
|
+
insertSnapshot(snapshot) {
|
|
23071
|
+
const runInsert = this.db.transaction((s) => {
|
|
23072
|
+
assertValidUtcInstant(s.capturedAtUtc, "capture timestamp");
|
|
23073
|
+
assertRequiredSnapshotFields(s);
|
|
23074
|
+
const capturedAtUtc = canonicalizeUtc(s.capturedAtUtc);
|
|
23075
|
+
const info = this.db.prepare(
|
|
23076
|
+
`INSERT OR IGNORE INTO activity_snapshots
|
|
23077
|
+
(machine, captured_at_utc, app_name, window_title, browser_url, text, text_source, content_hash, simhash)
|
|
23078
|
+
VALUES (@machine, @captured_at_utc, @app_name, @window_title, @browser_url, @text, @text_source, @content_hash, @simhash)`
|
|
23079
|
+
).run({
|
|
23080
|
+
machine: s.machine,
|
|
23081
|
+
captured_at_utc: capturedAtUtc,
|
|
23082
|
+
app_name: s.app,
|
|
23083
|
+
window_title: s.windowTitle,
|
|
23084
|
+
browser_url: s.browserUrl ?? null,
|
|
23085
|
+
text: s.text,
|
|
23086
|
+
text_source: s.textSource,
|
|
23087
|
+
content_hash: s.contentHash,
|
|
23088
|
+
simhash: s.simhash ?? null
|
|
23089
|
+
});
|
|
23090
|
+
if (info.changes === 0) {
|
|
23091
|
+
const existing = this.db.prepare("SELECT id FROM activity_snapshots WHERE machine = ? AND captured_at_utc = ? AND content_hash = ?").get(s.machine, capturedAtUtc, s.contentHash);
|
|
23092
|
+
const id2 = isRecord(existing) && typeof existing.id === "number" ? existing.id : -1;
|
|
23093
|
+
return { inserted: false, id: id2 };
|
|
23094
|
+
}
|
|
23095
|
+
const id = Number(info.lastInsertRowid);
|
|
23096
|
+
this.db.prepare(
|
|
23097
|
+
`INSERT INTO activity_snapshots_fts (rowid, text, app_name, window_title, browser_url)
|
|
23098
|
+
VALUES (?, ?, ?, ?, ?)`
|
|
23099
|
+
).run(id, s.text, s.app, s.windowTitle, s.browserUrl ?? "");
|
|
23100
|
+
return { inserted: true, id };
|
|
23101
|
+
});
|
|
23102
|
+
return runInsert(snapshot);
|
|
23103
|
+
}
|
|
23104
|
+
/** Snapshots whose capture instant is in the half-open [start, end) window. */
|
|
23105
|
+
listSnapshotsForDay(machine, startUtcInclusive, endUtcExclusive) {
|
|
23106
|
+
assertValidUtcInstant(startUtcInclusive, "range start");
|
|
23107
|
+
assertValidUtcInstant(endUtcExclusive, "range end");
|
|
23108
|
+
const start = canonicalizeUtc(startUtcInclusive);
|
|
23109
|
+
const end = canonicalizeUtc(endUtcExclusive);
|
|
23110
|
+
const rows = machine === null ? this.db.prepare(
|
|
23111
|
+
`SELECT * FROM activity_snapshots
|
|
23112
|
+
WHERE captured_at_utc >= ? AND captured_at_utc < ?
|
|
23113
|
+
ORDER BY captured_at_utc ASC, id ASC`
|
|
23114
|
+
).all(start, end) : this.db.prepare(
|
|
23115
|
+
`SELECT * FROM activity_snapshots
|
|
23116
|
+
WHERE machine = ? AND captured_at_utc >= ? AND captured_at_utc < ?
|
|
23117
|
+
ORDER BY captured_at_utc ASC, id ASC`
|
|
23118
|
+
).all(machine, start, end);
|
|
23119
|
+
return rows.map(rowToSnapshot);
|
|
23120
|
+
}
|
|
23121
|
+
getCursor(machine) {
|
|
23122
|
+
const row = this.db.prepare("SELECT cursor FROM activity_sync_state WHERE machine = ?").get(machine);
|
|
23123
|
+
return isRecord(row) && typeof row.cursor === "string" ? row.cursor : null;
|
|
23124
|
+
}
|
|
23125
|
+
setCursor(machine, cursor, updatedAtUtc = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
23126
|
+
this.db.prepare(
|
|
23127
|
+
`INSERT INTO activity_sync_state (machine, cursor, updated_at_utc)
|
|
23128
|
+
VALUES (?, ?, ?)
|
|
23129
|
+
ON CONFLICT(machine) DO UPDATE SET cursor = excluded.cursor, updated_at_utc = excluded.updated_at_utc`
|
|
23130
|
+
).run(machine, cursor, updatedAtUtc);
|
|
23131
|
+
}
|
|
23132
|
+
/** Full-text search over snapshot text/app/window/url; newest first. */
|
|
23133
|
+
searchSnapshots(query, limit) {
|
|
23134
|
+
const capped = Number.isInteger(limit) && limit > 0 ? Math.min(limit, MAX_SEARCH_RESULTS) : 20;
|
|
23135
|
+
const match = ftsMatchFor(query);
|
|
23136
|
+
if (match === null) return [];
|
|
23137
|
+
const rows = this.db.prepare(
|
|
23138
|
+
`SELECT s.* FROM activity_snapshots_fts f
|
|
23139
|
+
JOIN activity_snapshots s ON s.id = f.rowid
|
|
23140
|
+
WHERE activity_snapshots_fts MATCH ?
|
|
23141
|
+
ORDER BY s.captured_at_utc DESC, s.id DESC
|
|
23142
|
+
LIMIT ?`
|
|
23143
|
+
).all(match, capped);
|
|
23144
|
+
return rows.map(rowToSnapshot);
|
|
23145
|
+
}
|
|
23146
|
+
/** Retention: drop snapshots captured strictly before `cutoffUtc`. */
|
|
23147
|
+
pruneOlderThan(cutoffUtc) {
|
|
23148
|
+
assertValidUtcInstant(cutoffUtc, "prune cutoff");
|
|
23149
|
+
const ids = this.db.prepare("SELECT id FROM activity_snapshots WHERE captured_at_utc < ?").all(canonicalizeUtc(cutoffUtc)).map((row) => isRecord(row) && typeof row.id === "number" ? row.id : -1).filter((id) => id >= 0);
|
|
23150
|
+
const deleteFts = this.db.prepare("DELETE FROM activity_snapshots_fts WHERE rowid = ?");
|
|
23151
|
+
const deleteRow = this.db.prepare("DELETE FROM activity_snapshots WHERE id = ?");
|
|
23152
|
+
const tx = this.db.transaction((rowIds) => {
|
|
23153
|
+
for (const id of rowIds) {
|
|
23154
|
+
deleteFts.run(id);
|
|
23155
|
+
deleteRow.run(id);
|
|
23156
|
+
}
|
|
23157
|
+
});
|
|
23158
|
+
tx(ids);
|
|
23159
|
+
return ids.length;
|
|
23160
|
+
}
|
|
23161
|
+
close() {
|
|
23162
|
+
this.db.close();
|
|
23163
|
+
}
|
|
23164
|
+
};
|
|
23165
|
+
|
|
23166
|
+
// src/activity/digest.ts
|
|
23167
|
+
import { createHash as createHash9 } from "crypto";
|
|
23168
|
+
import path30 from "path";
|
|
23169
|
+
var ACTIVITY_DIGEST_FORMAT_VERSION = 1;
|
|
23170
|
+
var ACTIVITY_DIR_NAME = "activity";
|
|
23171
|
+
var MAX_DWELL_MS = 15 * 6e4;
|
|
23172
|
+
var NOTABLE_MAX_WINDOWS = 10;
|
|
23173
|
+
var NOTABLE_EXCERPT_CHARS = 280;
|
|
23174
|
+
var DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
|
23175
|
+
function isValidActivityDate(date) {
|
|
23176
|
+
if (typeof date !== "string" || !DATE_PATTERN.test(date)) return false;
|
|
23177
|
+
const parsed = /* @__PURE__ */ new Date(`${date}T00:00:00Z`);
|
|
23178
|
+
return Number.isFinite(parsed.getTime()) && parsed.toISOString().slice(0, 10) === date;
|
|
23179
|
+
}
|
|
23180
|
+
function activityDigestPath(memoryDir, date) {
|
|
23181
|
+
if (!isValidActivityDate(date)) {
|
|
23182
|
+
throw new RangeError(`Invalid activity date "${date}"; expected YYYY-MM-DD.`);
|
|
23183
|
+
}
|
|
23184
|
+
return path30.join(memoryDir, ACTIVITY_DIR_NAME, `${date}.md`);
|
|
23185
|
+
}
|
|
23186
|
+
function timezoneOffsetIso(instant, timezone) {
|
|
23187
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
23188
|
+
timeZone: timezone,
|
|
23189
|
+
timeZoneName: "longOffset"
|
|
23190
|
+
}).formatToParts(instant);
|
|
23191
|
+
const name = parts.find((part) => part.type === "timeZoneName")?.value ?? "GMT";
|
|
23192
|
+
const match = name.match(/GMT([+-]\d{2}:\d{2})?/);
|
|
23193
|
+
return match?.[1] ?? "+00:00";
|
|
23194
|
+
}
|
|
23195
|
+
function assertValidTimezone(timezone) {
|
|
23196
|
+
try {
|
|
23197
|
+
new Intl.DateTimeFormat("en-US", { timeZone: timezone });
|
|
23198
|
+
} catch {
|
|
23199
|
+
throw new RangeError(`Invalid IANA timezone "${timezone}" for the activity digest.`);
|
|
23200
|
+
}
|
|
23201
|
+
}
|
|
23202
|
+
function zonedDayStartIso(date, timezone) {
|
|
23203
|
+
const prevDate = shiftIsoDate(date, -1);
|
|
23204
|
+
const probeOffsets = new Set(
|
|
23205
|
+
[
|
|
23206
|
+
`${prevDate}T12:00:00Z`,
|
|
23207
|
+
`${prevDate}T23:00:00Z`,
|
|
23208
|
+
`${date}T00:00:00Z`,
|
|
23209
|
+
`${date}T12:00:00Z`,
|
|
23210
|
+
`${date}T23:00:00Z`
|
|
23211
|
+
].map((iso) => timezoneOffsetIso(new Date(iso), timezone))
|
|
23212
|
+
);
|
|
23213
|
+
let best = null;
|
|
23214
|
+
for (const offset of probeOffsets) {
|
|
23215
|
+
const candidate = Date.parse(`${date}T00:00:00${offset}`);
|
|
23216
|
+
if (!Number.isFinite(candidate)) continue;
|
|
23217
|
+
if (timezoneOffsetIso(new Date(candidate), timezone) !== offset) continue;
|
|
23218
|
+
if (best === null || candidate < best) best = candidate;
|
|
23219
|
+
}
|
|
23220
|
+
if (best === null) {
|
|
23221
|
+
for (let minute = 1; minute <= 180 && best === null; minute++) {
|
|
23222
|
+
const hh = String(Math.floor(minute / 60)).padStart(2, "0");
|
|
23223
|
+
const mm = String(minute % 60).padStart(2, "0");
|
|
23224
|
+
for (const offset of probeOffsets) {
|
|
23225
|
+
const candidate = Date.parse(`${date}T${hh}:${mm}:00${offset}`);
|
|
23226
|
+
if (!Number.isFinite(candidate)) continue;
|
|
23227
|
+
if (timezoneOffsetIso(new Date(candidate), timezone) !== offset) continue;
|
|
23228
|
+
if (best === null || candidate < best) best = candidate;
|
|
23229
|
+
}
|
|
23230
|
+
}
|
|
23231
|
+
}
|
|
23232
|
+
if (best === null) {
|
|
23233
|
+
const noon = timezoneOffsetIso(/* @__PURE__ */ new Date(`${date}T12:00:00Z`), timezone);
|
|
23234
|
+
best = Date.parse(`${date}T00:00:00${noon}`);
|
|
23235
|
+
}
|
|
23236
|
+
if (best === null || !Number.isFinite(best)) {
|
|
23237
|
+
throw new RangeError(`activity: could not resolve a local day start for "${date}" in "${timezone}".`);
|
|
23238
|
+
}
|
|
23239
|
+
return new Date(best).toISOString();
|
|
23240
|
+
}
|
|
23241
|
+
function shiftIsoDate(date, days) {
|
|
23242
|
+
const parsed = /* @__PURE__ */ new Date(`${date}T00:00:00Z`);
|
|
23243
|
+
parsed.setUTCDate(parsed.getUTCDate() + days);
|
|
23244
|
+
return parsed.toISOString().slice(0, 10);
|
|
23245
|
+
}
|
|
23246
|
+
function nextIsoDate(date) {
|
|
23247
|
+
return shiftIsoDate(date, 1);
|
|
23248
|
+
}
|
|
23249
|
+
function activityDayWindow(date, timezone) {
|
|
23250
|
+
if (!isValidActivityDate(date)) {
|
|
23251
|
+
throw new RangeError(`Invalid activity date "${date}"; expected a real YYYY-MM-DD day.`);
|
|
23252
|
+
}
|
|
23253
|
+
assertValidTimezone(timezone);
|
|
23254
|
+
return {
|
|
23255
|
+
startUtc: new Date(zonedDayStartIso(date, timezone)).toISOString(),
|
|
23256
|
+
endUtc: new Date(zonedDayStartIso(nextIsoDate(date), timezone)).toISOString()
|
|
23257
|
+
};
|
|
23258
|
+
}
|
|
23259
|
+
function sortedByTime(snapshots) {
|
|
23260
|
+
return [...snapshots].sort((a, b) => {
|
|
23261
|
+
const at2 = Date.parse(a.capturedAtUtc);
|
|
23262
|
+
const bt = Date.parse(b.capturedAtUtc);
|
|
23263
|
+
if (at2 !== bt && Number.isFinite(at2) && Number.isFinite(bt)) return at2 < bt ? -1 : 1;
|
|
23264
|
+
const aid = a.id ?? 0;
|
|
23265
|
+
const bid = b.id ?? 0;
|
|
23266
|
+
if (aid < bid) return -1;
|
|
23267
|
+
if (aid > bid) return 1;
|
|
23268
|
+
if (a.contentHash !== b.contentHash) return a.contentHash < b.contentHash ? -1 : 1;
|
|
23269
|
+
if (a.app !== b.app) return a.app < b.app ? -1 : 1;
|
|
23270
|
+
if (a.windowTitle !== b.windowTitle) return a.windowTitle < b.windowTitle ? -1 : 1;
|
|
23271
|
+
return 0;
|
|
23272
|
+
});
|
|
23273
|
+
}
|
|
23274
|
+
function computeDwell(snapshots) {
|
|
23275
|
+
const byMachine = /* @__PURE__ */ new Map();
|
|
23276
|
+
for (const snapshot of snapshots) {
|
|
23277
|
+
const list = byMachine.get(snapshot.machine);
|
|
23278
|
+
if (list === void 0) byMachine.set(snapshot.machine, [snapshot]);
|
|
23279
|
+
else list.push(snapshot);
|
|
23280
|
+
}
|
|
23281
|
+
const dwell = /* @__PURE__ */ new Map();
|
|
23282
|
+
for (const list of byMachine.values()) {
|
|
23283
|
+
const ordered = sortedByTime(list);
|
|
23284
|
+
for (let index = 0; index < ordered.length; index++) {
|
|
23285
|
+
const current = ordered[index];
|
|
23286
|
+
if (current === void 0) continue;
|
|
23287
|
+
const next = ordered[index + 1];
|
|
23288
|
+
let value = 0;
|
|
23289
|
+
if (next !== void 0) {
|
|
23290
|
+
const delta = Date.parse(next.capturedAtUtc) - Date.parse(current.capturedAtUtc);
|
|
23291
|
+
if (Number.isFinite(delta) && delta > 0) value = Math.min(delta, MAX_DWELL_MS);
|
|
23292
|
+
}
|
|
23293
|
+
dwell.set(current, value);
|
|
23294
|
+
}
|
|
23295
|
+
}
|
|
23296
|
+
return dwell;
|
|
23297
|
+
}
|
|
23298
|
+
function formatDurationMinutes(ms) {
|
|
23299
|
+
return `${Math.round(ms / 6e4)}m`;
|
|
23300
|
+
}
|
|
23301
|
+
function clockHhMm(iso, timezone) {
|
|
23302
|
+
const ms = Date.parse(iso);
|
|
23303
|
+
if (!Number.isFinite(ms)) return "??:??";
|
|
23304
|
+
const parts = new Intl.DateTimeFormat("en-GB", {
|
|
23305
|
+
timeZone: timezone,
|
|
23306
|
+
hour: "2-digit",
|
|
23307
|
+
minute: "2-digit",
|
|
23308
|
+
hour12: false
|
|
23309
|
+
}).formatToParts(new Date(ms));
|
|
23310
|
+
const hour = parts.find((p) => p.type === "hour")?.value ?? "00";
|
|
23311
|
+
const minute = parts.find((p) => p.type === "minute")?.value ?? "00";
|
|
23312
|
+
return `${hour}:${minute}`;
|
|
23313
|
+
}
|
|
23314
|
+
function collapseWhitespace(text) {
|
|
23315
|
+
return text.replace(/\s+/g, " ").trim();
|
|
23316
|
+
}
|
|
23317
|
+
function perAppSection(ordered, dwell) {
|
|
23318
|
+
const totals = /* @__PURE__ */ new Map();
|
|
23319
|
+
for (const snapshot of ordered) {
|
|
23320
|
+
totals.set(snapshot.app, (totals.get(snapshot.app) ?? 0) + (dwell.get(snapshot) ?? 0));
|
|
23321
|
+
}
|
|
23322
|
+
const rows = [...totals.entries()].sort((a, b) => {
|
|
23323
|
+
if (b[1] !== a[1]) return b[1] - a[1];
|
|
23324
|
+
if (a[0] < b[0]) return -1;
|
|
23325
|
+
if (a[0] > b[0]) return 1;
|
|
23326
|
+
return 0;
|
|
23327
|
+
});
|
|
23328
|
+
const lines = ["## Per-app time", ""];
|
|
23329
|
+
if (rows.length === 0) {
|
|
23330
|
+
lines.push("_No activity recorded._");
|
|
23331
|
+
} else {
|
|
23332
|
+
for (const [app, ms] of rows) {
|
|
23333
|
+
lines.push(`- ${app}: ${formatDurationMinutes(ms)}`);
|
|
23334
|
+
}
|
|
23335
|
+
}
|
|
23336
|
+
return lines.join("\n");
|
|
23337
|
+
}
|
|
23338
|
+
function timelineSpans(ordered) {
|
|
23339
|
+
const spans = [];
|
|
23340
|
+
for (const snapshot of ordered) {
|
|
23341
|
+
const last = spans[spans.length - 1];
|
|
23342
|
+
if (last !== void 0 && last.machine === snapshot.machine && last.app === snapshot.app && last.windowTitle === snapshot.windowTitle && last.browserUrl === snapshot.browserUrl) {
|
|
23343
|
+
continue;
|
|
23344
|
+
}
|
|
23345
|
+
spans.push({
|
|
23346
|
+
startIso: snapshot.capturedAtUtc,
|
|
23347
|
+
machine: snapshot.machine,
|
|
23348
|
+
app: snapshot.app,
|
|
23349
|
+
windowTitle: snapshot.windowTitle,
|
|
23350
|
+
...snapshot.browserUrl !== void 0 ? { browserUrl: snapshot.browserUrl } : {}
|
|
23351
|
+
});
|
|
23352
|
+
}
|
|
23353
|
+
return spans;
|
|
23354
|
+
}
|
|
23355
|
+
function timelineSection(ordered, timezone) {
|
|
23356
|
+
const lines = ["## Timeline", ""];
|
|
23357
|
+
const spans = timelineSpans(ordered);
|
|
23358
|
+
if (spans.length === 0) {
|
|
23359
|
+
lines.push("_No activity recorded._");
|
|
23360
|
+
return lines.join("\n");
|
|
23361
|
+
}
|
|
23362
|
+
for (const span of spans) {
|
|
23363
|
+
const clock = clockHhMm(span.startIso, timezone);
|
|
23364
|
+
const window = collapseWhitespace(span.windowTitle);
|
|
23365
|
+
const url = span.browserUrl !== void 0 ? ` (${collapseWhitespace(span.browserUrl)})` : "";
|
|
23366
|
+
lines.push(`- [${clock}] ${span.app}${window.length > 0 ? ` \u2014 ${window}` : ""}${url}`);
|
|
23367
|
+
}
|
|
23368
|
+
return lines.join("\n");
|
|
23369
|
+
}
|
|
23370
|
+
function notableSection(ordered, dwell) {
|
|
23371
|
+
const withDwell = ordered.map((snapshot) => ({ snapshot, dwell: dwell.get(snapshot) ?? 0 }));
|
|
23372
|
+
const ranked = withDwell.filter((entry) => collapseWhitespace(entry.snapshot.text).length > 0).sort((a, b) => {
|
|
23373
|
+
if (b.dwell !== a.dwell) return b.dwell - a.dwell;
|
|
23374
|
+
const at2 = Date.parse(a.snapshot.capturedAtUtc);
|
|
23375
|
+
const bt = Date.parse(b.snapshot.capturedAtUtc);
|
|
23376
|
+
if (at2 !== bt && Number.isFinite(at2) && Number.isFinite(bt)) return at2 < bt ? -1 : 1;
|
|
23377
|
+
const idDelta = (a.snapshot.id ?? 0) - (b.snapshot.id ?? 0);
|
|
23378
|
+
if (idDelta !== 0) return idDelta;
|
|
23379
|
+
if (a.snapshot.contentHash !== b.snapshot.contentHash) {
|
|
23380
|
+
return a.snapshot.contentHash < b.snapshot.contentHash ? -1 : 1;
|
|
23381
|
+
}
|
|
23382
|
+
return 0;
|
|
23383
|
+
}).slice(0, NOTABLE_MAX_WINDOWS);
|
|
23384
|
+
const lines = ["## Notable", ""];
|
|
23385
|
+
if (ranked.length === 0) {
|
|
23386
|
+
lines.push("_No notable text captured._");
|
|
23387
|
+
return lines.join("\n");
|
|
23388
|
+
}
|
|
23389
|
+
for (const { snapshot } of ranked) {
|
|
23390
|
+
const excerpt = collapseWhitespace(snapshot.text).slice(0, NOTABLE_EXCERPT_CHARS);
|
|
23391
|
+
lines.push(`- **${snapshot.app}** \u2014 ${excerpt}`);
|
|
23392
|
+
}
|
|
23393
|
+
return lines.join("\n");
|
|
23394
|
+
}
|
|
23395
|
+
function composeActivityDigestBody(date, timezone, snapshots) {
|
|
23396
|
+
assertValidTimezone(timezone);
|
|
23397
|
+
const ordered = sortedByTime(snapshots);
|
|
23398
|
+
const dwell = computeDwell(ordered);
|
|
23399
|
+
return [
|
|
23400
|
+
`# Activity \u2014 ${date}`,
|
|
23401
|
+
"",
|
|
23402
|
+
perAppSection(ordered, dwell),
|
|
23403
|
+
"",
|
|
23404
|
+
timelineSection(ordered, timezone),
|
|
23405
|
+
"",
|
|
23406
|
+
notableSection(ordered, dwell),
|
|
23407
|
+
""
|
|
23408
|
+
].join("\n");
|
|
23409
|
+
}
|
|
23410
|
+
function hashActivityBody(body) {
|
|
23411
|
+
return createHash9("sha256").update(body, "utf8").digest("hex");
|
|
23412
|
+
}
|
|
23413
|
+
function composeActivityDigestMeta(date, machines, snapshots, body) {
|
|
23414
|
+
const uniqueMachines = [...new Set(machines)].sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
23415
|
+
return {
|
|
23416
|
+
kind: "activity-digest",
|
|
23417
|
+
date,
|
|
23418
|
+
machines: uniqueMachines,
|
|
23419
|
+
snapshotCount: snapshots.length,
|
|
23420
|
+
contentHash: hashActivityBody(body),
|
|
23421
|
+
formatVersion: ACTIVITY_DIGEST_FORMAT_VERSION
|
|
23422
|
+
};
|
|
23423
|
+
}
|
|
23424
|
+
function serializeActivityDigest(meta, body) {
|
|
23425
|
+
const frontmatter = [
|
|
23426
|
+
"---",
|
|
23427
|
+
`kind: ${meta.kind}`,
|
|
23428
|
+
`date: ${meta.date}`,
|
|
23429
|
+
`machines: ${JSON.stringify(meta.machines)}`,
|
|
23430
|
+
`snapshotCount: ${meta.snapshotCount}`,
|
|
23431
|
+
`contentHash: ${meta.contentHash}`,
|
|
23432
|
+
`formatVersion: ${meta.formatVersion}`,
|
|
23433
|
+
"---",
|
|
23434
|
+
""
|
|
23435
|
+
].join("\n");
|
|
23436
|
+
return `${frontmatter}${body}`;
|
|
23437
|
+
}
|
|
23438
|
+
function parseActivityDigest(raw) {
|
|
23439
|
+
if (typeof raw !== "string" || !raw.startsWith("---\n")) return null;
|
|
23440
|
+
const end = raw.indexOf("\n---\n", 4);
|
|
23441
|
+
if (end === -1) return null;
|
|
23442
|
+
const frontmatter = raw.slice(4, end);
|
|
23443
|
+
const body = raw.slice(end + 5).replace(/^\n/, "");
|
|
23444
|
+
const fields = /* @__PURE__ */ new Map();
|
|
23445
|
+
for (const line of frontmatter.split("\n")) {
|
|
23446
|
+
const idx = line.indexOf(":");
|
|
23447
|
+
if (idx === -1) continue;
|
|
23448
|
+
fields.set(line.slice(0, idx).trim(), line.slice(idx + 1).trim());
|
|
23449
|
+
}
|
|
23450
|
+
const date = fields.get("date");
|
|
23451
|
+
const contentHash = fields.get("contentHash");
|
|
23452
|
+
if (date === void 0 || !isValidActivityDate(date) || contentHash === void 0) return null;
|
|
23453
|
+
if (fields.get("kind") !== "activity-digest") return null;
|
|
23454
|
+
const machines = parseMachinesList(fields.get("machines") ?? "[]");
|
|
23455
|
+
const snapshotCount = parseNonNegativeInt(fields.get("snapshotCount"));
|
|
23456
|
+
const formatVersion = parseNonNegativeInt(fields.get("formatVersion"));
|
|
23457
|
+
if (snapshotCount === null || formatVersion === null) return null;
|
|
23458
|
+
return {
|
|
23459
|
+
meta: {
|
|
23460
|
+
kind: "activity-digest",
|
|
23461
|
+
date,
|
|
23462
|
+
machines,
|
|
23463
|
+
snapshotCount,
|
|
23464
|
+
contentHash,
|
|
23465
|
+
formatVersion
|
|
23466
|
+
},
|
|
23467
|
+
body
|
|
23468
|
+
};
|
|
23469
|
+
}
|
|
23470
|
+
function parseNonNegativeInt(value) {
|
|
23471
|
+
if (value === void 0) return null;
|
|
23472
|
+
const trimmed = value.trim();
|
|
23473
|
+
if (!/^\d+$/.test(trimmed)) return null;
|
|
23474
|
+
const parsed = Number(trimmed);
|
|
23475
|
+
return Number.isSafeInteger(parsed) ? parsed : null;
|
|
23476
|
+
}
|
|
23477
|
+
function parseMachinesList(raw) {
|
|
23478
|
+
try {
|
|
23479
|
+
const parsed = JSON.parse(raw);
|
|
23480
|
+
if (Array.isArray(parsed)) {
|
|
23481
|
+
return parsed.filter((entry) => typeof entry === "string");
|
|
23482
|
+
}
|
|
23483
|
+
} catch {
|
|
23484
|
+
}
|
|
23485
|
+
return raw.replace(/^\[/, "").replace(/\]$/, "").split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
23486
|
+
}
|
|
23487
|
+
|
|
23488
|
+
// src/session-summaries/index.ts
|
|
23489
|
+
import { createHash as createHash10 } from "crypto";
|
|
23490
|
+
import { lstat as lstat2, mkdir as mkdir8, readFile as readFile6, readdir as readdir4, rename, rm, stat as stat4, writeFile as writeFile7 } from "fs/promises";
|
|
23491
|
+
import path31 from "path";
|
|
22919
23492
|
|
|
22920
23493
|
// src/session-summaries/adapters.ts
|
|
22921
23494
|
var VALID_ROLES = /* @__PURE__ */ new Set(["user", "assistant", "tool", "system", "other"]);
|
|
@@ -23415,12 +23988,12 @@ var DEFAULT_MAX_FILES = 5e3;
|
|
|
23415
23988
|
var DEFAULT_MAX_SESSIONS = 500;
|
|
23416
23989
|
var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([".json", ".jsonl"]);
|
|
23417
23990
|
function shortHash(value, length = 16) {
|
|
23418
|
-
return
|
|
23991
|
+
return createHash10("sha256").update(value).digest("hex").slice(0, length);
|
|
23419
23992
|
}
|
|
23420
23993
|
async function listTranscriptFiles(root, maxFiles) {
|
|
23421
23994
|
const out = [];
|
|
23422
23995
|
let truncated = false;
|
|
23423
|
-
const entrySortKey = (entryName, isDirectory) => isDirectory ? `${entryName}${
|
|
23996
|
+
const entrySortKey = (entryName, isDirectory) => isDirectory ? `${entryName}${path31.sep}` : entryName;
|
|
23424
23997
|
async function visit(dir) {
|
|
23425
23998
|
if (truncated) return;
|
|
23426
23999
|
const entries = (await readdir4(dir, { withFileTypes: true })).sort(
|
|
@@ -23430,13 +24003,13 @@ async function listTranscriptFiles(root, maxFiles) {
|
|
|
23430
24003
|
if (truncated) return;
|
|
23431
24004
|
if (entry.name.startsWith(".")) continue;
|
|
23432
24005
|
if (entry.isSymbolicLink()) continue;
|
|
23433
|
-
const fullPath =
|
|
24006
|
+
const fullPath = path31.join(dir, entry.name);
|
|
23434
24007
|
if (entry.isDirectory()) {
|
|
23435
24008
|
await visit(fullPath);
|
|
23436
24009
|
continue;
|
|
23437
24010
|
}
|
|
23438
24011
|
if (!entry.isFile()) continue;
|
|
23439
|
-
const ext =
|
|
24012
|
+
const ext = path31.extname(entry.name).toLowerCase();
|
|
23440
24013
|
if (!SUPPORTED_EXTENSIONS.has(ext)) continue;
|
|
23441
24014
|
out.push(fullPath);
|
|
23442
24015
|
if (out.length > maxFiles) {
|
|
@@ -23463,7 +24036,7 @@ function normalizeInputDir(inputDir) {
|
|
|
23463
24036
|
if (typeof inputDir !== "string" || inputDir.trim().length === 0) {
|
|
23464
24037
|
throw new Error("inputDir must be a non-empty string");
|
|
23465
24038
|
}
|
|
23466
|
-
return
|
|
24039
|
+
return path31.resolve(expandTildePath(inputDir.trim()));
|
|
23467
24040
|
}
|
|
23468
24041
|
function initRoleCounts() {
|
|
23469
24042
|
return {
|
|
@@ -23620,11 +24193,11 @@ async function collectLocalSessionSummaries(options) {
|
|
|
23620
24193
|
continue;
|
|
23621
24194
|
}
|
|
23622
24195
|
seenFileHashes.add(fileHash);
|
|
23623
|
-
const fileExtension =
|
|
24196
|
+
const fileExtension = path31.extname(filePath).toLowerCase();
|
|
23624
24197
|
const parsed = await adapter.parseFile(
|
|
23625
24198
|
{
|
|
23626
24199
|
content,
|
|
23627
|
-
fileName:
|
|
24200
|
+
fileName: path31.basename(filePath),
|
|
23628
24201
|
fileExtension,
|
|
23629
24202
|
fileRef: fileHash
|
|
23630
24203
|
},
|
|
@@ -23680,7 +24253,7 @@ async function collectLocalSessionSummaries(options) {
|
|
|
23680
24253
|
}
|
|
23681
24254
|
async function readRedactionConfig(pathLike) {
|
|
23682
24255
|
if (!pathLike) return void 0;
|
|
23683
|
-
const raw = await readFile6(
|
|
24256
|
+
const raw = await readFile6(path31.resolve(expandTildePath(pathLike)), "utf-8");
|
|
23684
24257
|
const parsed = JSON.parse(raw);
|
|
23685
24258
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
23686
24259
|
throw new Error("redaction config must be a JSON object");
|
|
@@ -23693,15 +24266,15 @@ function toJsonl(drafts) {
|
|
|
23693
24266
|
}
|
|
23694
24267
|
function defaultDraftOutputPath(memoryDir, generatedAt) {
|
|
23695
24268
|
const stamp = generatedAt.replace(/[:.]/g, "-");
|
|
23696
|
-
return
|
|
23697
|
-
|
|
24269
|
+
return path31.join(
|
|
24270
|
+
path31.resolve(expandTildePath(memoryDir)),
|
|
23698
24271
|
"state",
|
|
23699
24272
|
"session-summary-drafts",
|
|
23700
24273
|
`session-summaries-${stamp}.jsonl`
|
|
23701
24274
|
);
|
|
23702
24275
|
}
|
|
23703
24276
|
async function writeDrafts(filePath, drafts) {
|
|
23704
|
-
await
|
|
24277
|
+
await mkdir8(path31.dirname(filePath), { recursive: true });
|
|
23705
24278
|
const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
23706
24279
|
try {
|
|
23707
24280
|
await writeFile7(tempPath, toJsonl(drafts), "utf-8");
|
|
@@ -23719,7 +24292,7 @@ async function runLocalSessionSummaryCliCommand(options) {
|
|
|
23719
24292
|
});
|
|
23720
24293
|
const wroteFiles = [];
|
|
23721
24294
|
if (options.output) {
|
|
23722
|
-
const outputPath =
|
|
24295
|
+
const outputPath = path31.resolve(expandTildePath(options.output));
|
|
23723
24296
|
await writeDrafts(outputPath, report.drafts);
|
|
23724
24297
|
wroteFiles.push(outputPath);
|
|
23725
24298
|
}
|
|
@@ -23753,20 +24326,20 @@ async function runLocalSessionSummaryCliCommand(options) {
|
|
|
23753
24326
|
}
|
|
23754
24327
|
|
|
23755
24328
|
// src/transfer/capsule-fork.ts
|
|
23756
|
-
import { lstat as lstat3, mkdir as
|
|
23757
|
-
import
|
|
24329
|
+
import { lstat as lstat3, mkdir as mkdir9, readFile as readFile7, realpath as realpath2, writeFile as writeFile8 } from "fs/promises";
|
|
24330
|
+
import path32 from "path";
|
|
23758
24331
|
async function forkCapsule(opts) {
|
|
23759
24332
|
validateForkId(opts.forkId);
|
|
23760
|
-
const rootAbs =
|
|
24333
|
+
const rootAbs = path32.resolve(opts.targetRoot);
|
|
23761
24334
|
await assertIsDirectoryNotSymlink(rootAbs, "forkCapsule", "targetRoot");
|
|
23762
|
-
const forkDirAbs =
|
|
24335
|
+
const forkDirAbs = path32.join(rootAbs, "forks", opts.forkId);
|
|
23763
24336
|
const forkEntryExists = await pathEntryExists(forkDirAbs);
|
|
23764
24337
|
if (forkEntryExists) {
|
|
23765
24338
|
throw new Error(
|
|
23766
24339
|
`forkCapsule: fork path already exists \u2014 forkId "${opts.forkId}" is already in use at: ${forkDirAbs}`
|
|
23767
24340
|
);
|
|
23768
24341
|
}
|
|
23769
|
-
const archiveAbs =
|
|
24342
|
+
const archiveAbs = path32.resolve(opts.sourceArchive);
|
|
23770
24343
|
const importResult = await importCapsule({
|
|
23771
24344
|
archivePath: archiveAbs,
|
|
23772
24345
|
root: rootAbs,
|
|
@@ -23788,10 +24361,10 @@ async function forkCapsule(opts) {
|
|
|
23788
24361
|
importedRecords: importResult.imported.length,
|
|
23789
24362
|
skippedRecords: importResult.skipped.length
|
|
23790
24363
|
};
|
|
23791
|
-
const lineagePath =
|
|
24364
|
+
const lineagePath = path32.join(forkDirAbs, "lineage.json");
|
|
23792
24365
|
const rootReal = await realpath2(rootAbs);
|
|
23793
24366
|
await assertRealpathInsideRoot(rootReal, lineagePath, `forks/${opts.forkId}/lineage.json`, "forkCapsule");
|
|
23794
|
-
await
|
|
24367
|
+
await mkdir9(path32.dirname(lineagePath), { recursive: true });
|
|
23795
24368
|
await writeFile8(lineagePath, JSON.stringify(lineage, null, 2) + "\n", "utf-8");
|
|
23796
24369
|
return {
|
|
23797
24370
|
archivePath: archiveAbs,
|
|
@@ -23805,11 +24378,11 @@ async function readForkLineage(targetRoot, forkId) {
|
|
|
23805
24378
|
if (typeof forkId !== "string" || forkId.length === 0 || forkId.length > 64 || !CAPSULE_ID_PATTERN.test(forkId)) {
|
|
23806
24379
|
return null;
|
|
23807
24380
|
}
|
|
23808
|
-
const rootAbs =
|
|
24381
|
+
const rootAbs = path32.resolve(targetRoot);
|
|
23809
24382
|
const rootReal = await realpath2(rootAbs).catch(() => rootAbs);
|
|
23810
|
-
const lineagePath =
|
|
23811
|
-
const rel =
|
|
23812
|
-
if (rel.startsWith("..") ||
|
|
24383
|
+
const lineagePath = path32.join(rootReal, "forks", forkId, "lineage.json");
|
|
24384
|
+
const rel = path32.relative(rootReal, lineagePath);
|
|
24385
|
+
if (rel.startsWith("..") || path32.isAbsolute(rel)) {
|
|
23813
24386
|
return null;
|
|
23814
24387
|
}
|
|
23815
24388
|
if (!await isLineagePathContained(rootReal, lineagePath)) {
|
|
@@ -23850,19 +24423,19 @@ async function pathEntryExists(absPath) {
|
|
|
23850
24423
|
async function isLineagePathContained(rootReal, lineagePath) {
|
|
23851
24424
|
let existing = lineagePath;
|
|
23852
24425
|
const suffix = [];
|
|
23853
|
-
while (existing !==
|
|
24426
|
+
while (existing !== path32.dirname(existing)) {
|
|
23854
24427
|
const st = await lstat3(existing).catch(() => null);
|
|
23855
24428
|
if (st !== null) break;
|
|
23856
|
-
suffix.unshift(
|
|
23857
|
-
existing =
|
|
24429
|
+
suffix.unshift(path32.basename(existing));
|
|
24430
|
+
existing = path32.dirname(existing);
|
|
23858
24431
|
}
|
|
23859
24432
|
const existingReal = await realpath2(existing).catch(() => existing);
|
|
23860
|
-
const targetReal = suffix.length > 0 ?
|
|
23861
|
-
const rel =
|
|
24433
|
+
const targetReal = suffix.length > 0 ? path32.join(existingReal, ...suffix) : existingReal;
|
|
24434
|
+
const rel = path32.relative(rootReal, targetReal);
|
|
23862
24435
|
if (rel === "") return true;
|
|
23863
24436
|
if (rel === "..") return false;
|
|
23864
|
-
if (rel.startsWith(`..${
|
|
23865
|
-
if (
|
|
24437
|
+
if (rel.startsWith(`..${path32.sep}`)) return false;
|
|
24438
|
+
if (path32.isAbsolute(rel)) return false;
|
|
23866
24439
|
return true;
|
|
23867
24440
|
}
|
|
23868
24441
|
|
|
@@ -23989,6 +24562,21 @@ export {
|
|
|
23989
24562
|
ensureBuiltInWearableConnectors,
|
|
23990
24563
|
WearablesService,
|
|
23991
24564
|
locateTranscriptPath,
|
|
24565
|
+
activityDatabasePath,
|
|
24566
|
+
ensureActivityStateDir,
|
|
24567
|
+
openActivityDatabase,
|
|
24568
|
+
applyActivitySchema,
|
|
24569
|
+
ActivityStore,
|
|
24570
|
+
ACTIVITY_DIGEST_FORMAT_VERSION,
|
|
24571
|
+
ACTIVITY_DIR_NAME,
|
|
24572
|
+
isValidActivityDate,
|
|
24573
|
+
activityDigestPath,
|
|
24574
|
+
activityDayWindow,
|
|
24575
|
+
composeActivityDigestBody,
|
|
24576
|
+
hashActivityBody,
|
|
24577
|
+
composeActivityDigestMeta,
|
|
24578
|
+
serializeActivityDigest,
|
|
24579
|
+
parseActivityDigest,
|
|
23992
24580
|
registerLocalSessionSourceAdapter,
|
|
23993
24581
|
getLocalSessionSourceAdapter,
|
|
23994
24582
|
listLocalSessionSourceAdapters,
|
|
@@ -24004,4 +24592,4 @@ export {
|
|
|
24004
24592
|
blendGraphExpandedRecallScore,
|
|
24005
24593
|
Orchestrator
|
|
24006
24594
|
};
|
|
24007
|
-
//# sourceMappingURL=chunk-
|
|
24595
|
+
//# sourceMappingURL=chunk-3VBXP5HS.js.map
|