@remnic/core 9.11.0 → 9.13.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.
@@ -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 (path31, preferredNamespace) => {
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
- path31,
2093
+ path33,
2091
2094
  fallbackReader,
2092
2095
  namespaces,
2093
2096
  preferredNamespace
@@ -2832,8 +2835,8 @@ function utcDateKeysForLocalDay(date, timeZone) {
2832
2835
  const hourMs = 36e5;
2833
2836
  const scanStart = date.getTime() - 48 * hourMs;
2834
2837
  const scanEnd = date.getTime() + 48 * hourMs;
2835
- for (let ms = scanStart; ms <= scanEnd; ms += hourMs) {
2836
- const candidate = new Date(ms);
2838
+ for (let ms2 = scanStart; ms2 <= scanEnd; ms2 += hourMs) {
2839
+ const candidate = new Date(ms2);
2837
2840
  if (formatDateInTimeZone(candidate, timeZone) === targetLocalDate) {
2838
2841
  keys.add(utcDateKey(candidate));
2839
2842
  }
@@ -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 path31 = typeof rec.path === "string" && rec.path.length > 0 ? rec.path : void 0;
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 = path31 !== void 0 && kind !== void 0 ? { symbol, path: path31, kind } : path31 !== void 0 ? { symbol, path: path31 } : kind !== void 0 ? { symbol, kind } : { symbol };
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) {
@@ -22203,8 +22206,8 @@ function isProcessAlive(pid) {
22203
22206
  return true;
22204
22207
  }
22205
22208
  }
22206
- function sleepSync(ms) {
22207
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
22209
+ function sleepSync(ms2) {
22210
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms2);
22208
22211
  }
22209
22212
  function createPersonalSpace(baseDir, memoryDirOverride) {
22210
22213
  const homeDir = baseDir ?? resolveHomeDir();
@@ -22912,10 +22915,757 @@ function publisherForConnector(connectorId) {
22912
22915
  return publisherFor(hostIdForConnector(connectorId));
22913
22916
  }
22914
22917
 
22915
- // src/session-summaries/index.ts
22916
- import { createHash as createHash9 } from "crypto";
22917
- import { lstat as lstat2, mkdir as mkdir7, readFile as readFile6, readdir as readdir4, rename, rm, stat as stat4, writeFile as writeFile7 } from "fs/promises";
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(ms2) {
23299
+ return `${Math.round(ms2 / 6e4)}m`;
23300
+ }
23301
+ function clockHhMm(iso, timezone) {
23302
+ const ms2 = Date.parse(iso);
23303
+ if (!Number.isFinite(ms2)) 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(ms2));
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, ms2] of rows) {
23333
+ lines.push(`- ${app}: ${formatDurationMinutes(ms2)}`);
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/meetings/detect.ts
23489
+ import { createHash as createHash10 } from "crypto";
23490
+ var DEFAULT_MEETING_APP_PATTERNS = [
23491
+ "zoom.us",
23492
+ "Zoom",
23493
+ "Microsoft Teams",
23494
+ "teams.microsoft.com",
23495
+ "meet.google.com",
23496
+ "Webex",
23497
+ "Slack",
23498
+ // huddle windows
23499
+ "FaceTime"
23500
+ ];
23501
+ var DEFAULT_MEETINGS_DETECTION_CONFIG = {
23502
+ appPatterns: [...DEFAULT_MEETING_APP_PATTERNS],
23503
+ minOverlapMinutes: 2,
23504
+ audioOnlyMinMinutes: 15,
23505
+ mergeGapMinutes: 2
23506
+ };
23507
+ function ms(iso) {
23508
+ if (typeof iso !== "string") return Number.NaN;
23509
+ if (!Number.isFinite(Date.parse(iso))) return Number.NaN;
23510
+ const m = iso.match(/^(\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))$/);
23511
+ if (m === null) return Number.NaN;
23512
+ const [year, month, day, hour, minute, second] = m.slice(1).map(Number);
23513
+ const daysInMonth = month >= 1 && month <= 12 ? new Date(Date.UTC(year, month, 0)).getUTCDate() : 0;
23514
+ if (day < 1 || day > daysInMonth || hour > 23 || minute > 59 || second > 59) return Number.NaN;
23515
+ return Date.parse(iso);
23516
+ }
23517
+ function overlapMs(aStart, aEnd, bStart, bEnd) {
23518
+ const start = Math.max(aStart, bStart);
23519
+ const end = Math.min(aEnd, bEnd);
23520
+ return end > start ? end - start : 0;
23521
+ }
23522
+ function meetingId(date, startUtc) {
23523
+ if (!isValidDay(date)) {
23524
+ throw new RangeError(`meetings: invalid day "${date}" for a meeting id; expected a real YYYY-MM-DD.`);
23525
+ }
23526
+ const startMs = ms(startUtc);
23527
+ if (Number.isNaN(startMs)) {
23528
+ throw new RangeError(`meetings: invalid meeting start "${startUtc}" for a meeting id.`);
23529
+ }
23530
+ const anchor = new Date(startMs).toISOString();
23531
+ const hash = createHash10("sha256").update(`${date}|${anchor}`, "utf8").digest("hex").slice(0, 8);
23532
+ return `mtg-${date}-${hash}`;
23533
+ }
23534
+ function isFinitePair(a, b) {
23535
+ return Number.isFinite(a) && Number.isFinite(b) && b > a;
23536
+ }
23537
+ function combineDetection(a, b) {
23538
+ if (a === "app+audio" || b === "app+audio") return "app+audio";
23539
+ if (a === "provider" || b === "provider") return "provider";
23540
+ return "audio";
23541
+ }
23542
+ function buildCandidates(audioWindows, appSpans, config) {
23543
+ const minOverlapMs = config.minOverlapMinutes * 6e4;
23544
+ const audioOnlyMs = config.audioOnlyMinMinutes * 6e4;
23545
+ const candidates = [];
23546
+ for (const window of audioWindows) {
23547
+ const startMs = ms(window.startUtc);
23548
+ const endMs = ms(window.endUtc);
23549
+ if (!isFinitePair(startMs, endMs)) continue;
23550
+ if (window.providerMeeting === true) {
23551
+ candidates.push({
23552
+ startMs,
23553
+ endMs,
23554
+ detectionSource: "provider",
23555
+ sources: [window.source],
23556
+ ...window.title !== void 0 ? { title: window.title } : {}
23557
+ });
23558
+ continue;
23559
+ }
23560
+ let bestApp;
23561
+ for (const span of appSpans) {
23562
+ const spanStart = ms(span.startUtc);
23563
+ const spanEnd = ms(span.endUtc);
23564
+ if (!isFinitePair(spanStart, spanEnd)) continue;
23565
+ const overlap = overlapMs(startMs, endMs, spanStart, spanEnd);
23566
+ if (overlap <= 0 || overlap < minOverlapMs) continue;
23567
+ if (bestApp === void 0 || overlap > bestApp.overlap) {
23568
+ bestApp = { span, overlap };
23569
+ } else if (overlap === bestApp.overlap) {
23570
+ const bestStart = ms(bestApp.span.startUtc);
23571
+ const better = spanStart < bestStart || spanStart === bestStart && (span.app < bestApp.span.app || span.app === bestApp.span.app && spanEnd < ms(bestApp.span.endUtc));
23572
+ if (better) bestApp = { span, overlap };
23573
+ }
23574
+ }
23575
+ if (bestApp !== void 0) {
23576
+ candidates.push({
23577
+ startMs,
23578
+ endMs,
23579
+ app: bestApp.span.app,
23580
+ detectionSource: "app+audio",
23581
+ sources: [window.source],
23582
+ ...window.title !== void 0 ? { title: window.title } : {}
23583
+ });
23584
+ continue;
23585
+ }
23586
+ if (endMs - startMs >= audioOnlyMs && Number.isInteger(window.distinctNonWearerSpeakers) && window.distinctNonWearerSpeakers >= 2) {
23587
+ candidates.push({
23588
+ startMs,
23589
+ endMs,
23590
+ detectionSource: "audio",
23591
+ sources: [window.source],
23592
+ ...window.title !== void 0 ? { title: window.title } : {}
23593
+ });
23594
+ }
23595
+ }
23596
+ return candidates;
23597
+ }
23598
+ var DETECTION_RANK = {
23599
+ "app+audio": 0,
23600
+ provider: 1,
23601
+ audio: 2
23602
+ };
23603
+ function candidateOrder(a, b) {
23604
+ return a.startMs - b.startMs || a.endMs - b.endMs || DETECTION_RANK[a.detectionSource] - DETECTION_RANK[b.detectionSource] || (a.app ?? "").localeCompare(b.app ?? "") || (a.title ?? "").localeCompare(b.title ?? "") || (a.sources[0] ?? "").localeCompare(b.sources[0] ?? "");
23605
+ }
23606
+ function mergeCandidates(candidates, mergeGapMs) {
23607
+ const sorted = [...candidates].sort(candidateOrder);
23608
+ const merged = [];
23609
+ for (const candidate of sorted) {
23610
+ const prev = merged[merged.length - 1];
23611
+ const overlaps = prev !== void 0 && candidate.startMs < prev.endMs;
23612
+ const sameAppAdjacent = prev !== void 0 && prev.app !== void 0 && prev.app === candidate.app && candidate.startMs - prev.endMs <= mergeGapMs;
23613
+ if (prev !== void 0 && (overlaps || sameAppAdjacent)) {
23614
+ prev.endMs = Math.max(prev.endMs, candidate.endMs);
23615
+ prev.app = prev.app ?? candidate.app;
23616
+ prev.detectionSource = combineDetection(prev.detectionSource, candidate.detectionSource);
23617
+ prev.sources = [.../* @__PURE__ */ new Set([...prev.sources, ...candidate.sources])];
23618
+ prev.title = prev.title ?? candidate.title;
23619
+ continue;
23620
+ }
23621
+ merged.push({ ...candidate, sources: [...candidate.sources] });
23622
+ }
23623
+ return merged;
23624
+ }
23625
+ function assertFiniteNonNegative(name, value) {
23626
+ if (!Number.isFinite(value) || value < 0) {
23627
+ throw new RangeError(`meetings config "${name}" must be a finite, non-negative number (got ${value}).`);
23628
+ }
23629
+ }
23630
+ function isValidDay(date) {
23631
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(date);
23632
+ if (m === null) return false;
23633
+ const [, year, month, day] = m.map(Number);
23634
+ const daysInMonth = month >= 1 && month <= 12 ? new Date(Date.UTC(year, month, 0)).getUTCDate() : 0;
23635
+ return day >= 1 && day <= daysInMonth;
23636
+ }
23637
+ function validateConfig(config) {
23638
+ assertFiniteNonNegative("minOverlapMinutes", config.minOverlapMinutes);
23639
+ assertFiniteNonNegative("audioOnlyMinMinutes", config.audioOnlyMinMinutes);
23640
+ assertFiniteNonNegative("mergeGapMinutes", config.mergeGapMinutes);
23641
+ }
23642
+ function detectMeetings(input, config = DEFAULT_MEETINGS_DETECTION_CONFIG) {
23643
+ validateConfig(config);
23644
+ if (!isValidDay(input.date)) {
23645
+ throw new RangeError(`meetings: invalid day "${input.date}"; expected a real YYYY-MM-DD.`);
23646
+ }
23647
+ const candidates = buildCandidates(input.audioWindows, input.appSpans, config);
23648
+ const merged = mergeCandidates(candidates, config.mergeGapMinutes * 6e4);
23649
+ return merged.map((candidate) => {
23650
+ const startUtc = new Date(candidate.startMs).toISOString();
23651
+ const endUtc = new Date(candidate.endMs).toISOString();
23652
+ return {
23653
+ id: meetingId(input.date, startUtc),
23654
+ date: input.date,
23655
+ startUtc,
23656
+ endUtc,
23657
+ ...candidate.app !== void 0 ? { app: candidate.app } : {},
23658
+ detectionSource: candidate.detectionSource,
23659
+ sources: [...candidate.sources].sort((a, b) => a < b ? -1 : a > b ? 1 : 0),
23660
+ ...candidate.title !== void 0 ? { title: candidate.title } : {}
23661
+ };
23662
+ });
23663
+ }
23664
+
23665
+ // src/session-summaries/index.ts
23666
+ import { createHash as createHash11 } from "crypto";
23667
+ import { lstat as lstat2, mkdir as mkdir8, readFile as readFile6, readdir as readdir4, rename, rm, stat as stat4, writeFile as writeFile7 } from "fs/promises";
23668
+ import path31 from "path";
22919
23669
 
22920
23670
  // src/session-summaries/adapters.ts
22921
23671
  var VALID_ROLES = /* @__PURE__ */ new Set(["user", "assistant", "tool", "system", "other"]);
@@ -23415,12 +24165,12 @@ var DEFAULT_MAX_FILES = 5e3;
23415
24165
  var DEFAULT_MAX_SESSIONS = 500;
23416
24166
  var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([".json", ".jsonl"]);
23417
24167
  function shortHash(value, length = 16) {
23418
- return createHash9("sha256").update(value).digest("hex").slice(0, length);
24168
+ return createHash11("sha256").update(value).digest("hex").slice(0, length);
23419
24169
  }
23420
24170
  async function listTranscriptFiles(root, maxFiles) {
23421
24171
  const out = [];
23422
24172
  let truncated = false;
23423
- const entrySortKey = (entryName, isDirectory) => isDirectory ? `${entryName}${path29.sep}` : entryName;
24173
+ const entrySortKey = (entryName, isDirectory) => isDirectory ? `${entryName}${path31.sep}` : entryName;
23424
24174
  async function visit(dir) {
23425
24175
  if (truncated) return;
23426
24176
  const entries = (await readdir4(dir, { withFileTypes: true })).sort(
@@ -23430,13 +24180,13 @@ async function listTranscriptFiles(root, maxFiles) {
23430
24180
  if (truncated) return;
23431
24181
  if (entry.name.startsWith(".")) continue;
23432
24182
  if (entry.isSymbolicLink()) continue;
23433
- const fullPath = path29.join(dir, entry.name);
24183
+ const fullPath = path31.join(dir, entry.name);
23434
24184
  if (entry.isDirectory()) {
23435
24185
  await visit(fullPath);
23436
24186
  continue;
23437
24187
  }
23438
24188
  if (!entry.isFile()) continue;
23439
- const ext = path29.extname(entry.name).toLowerCase();
24189
+ const ext = path31.extname(entry.name).toLowerCase();
23440
24190
  if (!SUPPORTED_EXTENSIONS.has(ext)) continue;
23441
24191
  out.push(fullPath);
23442
24192
  if (out.length > maxFiles) {
@@ -23463,7 +24213,7 @@ function normalizeInputDir(inputDir) {
23463
24213
  if (typeof inputDir !== "string" || inputDir.trim().length === 0) {
23464
24214
  throw new Error("inputDir must be a non-empty string");
23465
24215
  }
23466
- return path29.resolve(expandTildePath(inputDir.trim()));
24216
+ return path31.resolve(expandTildePath(inputDir.trim()));
23467
24217
  }
23468
24218
  function initRoleCounts() {
23469
24219
  return {
@@ -23620,11 +24370,11 @@ async function collectLocalSessionSummaries(options) {
23620
24370
  continue;
23621
24371
  }
23622
24372
  seenFileHashes.add(fileHash);
23623
- const fileExtension = path29.extname(filePath).toLowerCase();
24373
+ const fileExtension = path31.extname(filePath).toLowerCase();
23624
24374
  const parsed = await adapter.parseFile(
23625
24375
  {
23626
24376
  content,
23627
- fileName: path29.basename(filePath),
24377
+ fileName: path31.basename(filePath),
23628
24378
  fileExtension,
23629
24379
  fileRef: fileHash
23630
24380
  },
@@ -23680,7 +24430,7 @@ async function collectLocalSessionSummaries(options) {
23680
24430
  }
23681
24431
  async function readRedactionConfig(pathLike) {
23682
24432
  if (!pathLike) return void 0;
23683
- const raw = await readFile6(path29.resolve(expandTildePath(pathLike)), "utf-8");
24433
+ const raw = await readFile6(path31.resolve(expandTildePath(pathLike)), "utf-8");
23684
24434
  const parsed = JSON.parse(raw);
23685
24435
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
23686
24436
  throw new Error("redaction config must be a JSON object");
@@ -23693,15 +24443,15 @@ function toJsonl(drafts) {
23693
24443
  }
23694
24444
  function defaultDraftOutputPath(memoryDir, generatedAt) {
23695
24445
  const stamp = generatedAt.replace(/[:.]/g, "-");
23696
- return path29.join(
23697
- path29.resolve(expandTildePath(memoryDir)),
24446
+ return path31.join(
24447
+ path31.resolve(expandTildePath(memoryDir)),
23698
24448
  "state",
23699
24449
  "session-summary-drafts",
23700
24450
  `session-summaries-${stamp}.jsonl`
23701
24451
  );
23702
24452
  }
23703
24453
  async function writeDrafts(filePath, drafts) {
23704
- await mkdir7(path29.dirname(filePath), { recursive: true });
24454
+ await mkdir8(path31.dirname(filePath), { recursive: true });
23705
24455
  const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
23706
24456
  try {
23707
24457
  await writeFile7(tempPath, toJsonl(drafts), "utf-8");
@@ -23719,7 +24469,7 @@ async function runLocalSessionSummaryCliCommand(options) {
23719
24469
  });
23720
24470
  const wroteFiles = [];
23721
24471
  if (options.output) {
23722
- const outputPath = path29.resolve(expandTildePath(options.output));
24472
+ const outputPath = path31.resolve(expandTildePath(options.output));
23723
24473
  await writeDrafts(outputPath, report.drafts);
23724
24474
  wroteFiles.push(outputPath);
23725
24475
  }
@@ -23753,20 +24503,20 @@ async function runLocalSessionSummaryCliCommand(options) {
23753
24503
  }
23754
24504
 
23755
24505
  // src/transfer/capsule-fork.ts
23756
- import { lstat as lstat3, mkdir as mkdir8, readFile as readFile7, realpath as realpath2, writeFile as writeFile8 } from "fs/promises";
23757
- import path30 from "path";
24506
+ import { lstat as lstat3, mkdir as mkdir9, readFile as readFile7, realpath as realpath2, writeFile as writeFile8 } from "fs/promises";
24507
+ import path32 from "path";
23758
24508
  async function forkCapsule(opts) {
23759
24509
  validateForkId(opts.forkId);
23760
- const rootAbs = path30.resolve(opts.targetRoot);
24510
+ const rootAbs = path32.resolve(opts.targetRoot);
23761
24511
  await assertIsDirectoryNotSymlink(rootAbs, "forkCapsule", "targetRoot");
23762
- const forkDirAbs = path30.join(rootAbs, "forks", opts.forkId);
24512
+ const forkDirAbs = path32.join(rootAbs, "forks", opts.forkId);
23763
24513
  const forkEntryExists = await pathEntryExists(forkDirAbs);
23764
24514
  if (forkEntryExists) {
23765
24515
  throw new Error(
23766
24516
  `forkCapsule: fork path already exists \u2014 forkId "${opts.forkId}" is already in use at: ${forkDirAbs}`
23767
24517
  );
23768
24518
  }
23769
- const archiveAbs = path30.resolve(opts.sourceArchive);
24519
+ const archiveAbs = path32.resolve(opts.sourceArchive);
23770
24520
  const importResult = await importCapsule({
23771
24521
  archivePath: archiveAbs,
23772
24522
  root: rootAbs,
@@ -23788,10 +24538,10 @@ async function forkCapsule(opts) {
23788
24538
  importedRecords: importResult.imported.length,
23789
24539
  skippedRecords: importResult.skipped.length
23790
24540
  };
23791
- const lineagePath = path30.join(forkDirAbs, "lineage.json");
24541
+ const lineagePath = path32.join(forkDirAbs, "lineage.json");
23792
24542
  const rootReal = await realpath2(rootAbs);
23793
24543
  await assertRealpathInsideRoot(rootReal, lineagePath, `forks/${opts.forkId}/lineage.json`, "forkCapsule");
23794
- await mkdir8(path30.dirname(lineagePath), { recursive: true });
24544
+ await mkdir9(path32.dirname(lineagePath), { recursive: true });
23795
24545
  await writeFile8(lineagePath, JSON.stringify(lineage, null, 2) + "\n", "utf-8");
23796
24546
  return {
23797
24547
  archivePath: archiveAbs,
@@ -23805,11 +24555,11 @@ async function readForkLineage(targetRoot, forkId) {
23805
24555
  if (typeof forkId !== "string" || forkId.length === 0 || forkId.length > 64 || !CAPSULE_ID_PATTERN.test(forkId)) {
23806
24556
  return null;
23807
24557
  }
23808
- const rootAbs = path30.resolve(targetRoot);
24558
+ const rootAbs = path32.resolve(targetRoot);
23809
24559
  const rootReal = await realpath2(rootAbs).catch(() => rootAbs);
23810
- const lineagePath = path30.join(rootReal, "forks", forkId, "lineage.json");
23811
- const rel = path30.relative(rootReal, lineagePath);
23812
- if (rel.startsWith("..") || path30.isAbsolute(rel)) {
24560
+ const lineagePath = path32.join(rootReal, "forks", forkId, "lineage.json");
24561
+ const rel = path32.relative(rootReal, lineagePath);
24562
+ if (rel.startsWith("..") || path32.isAbsolute(rel)) {
23813
24563
  return null;
23814
24564
  }
23815
24565
  if (!await isLineagePathContained(rootReal, lineagePath)) {
@@ -23850,19 +24600,19 @@ async function pathEntryExists(absPath) {
23850
24600
  async function isLineagePathContained(rootReal, lineagePath) {
23851
24601
  let existing = lineagePath;
23852
24602
  const suffix = [];
23853
- while (existing !== path30.dirname(existing)) {
24603
+ while (existing !== path32.dirname(existing)) {
23854
24604
  const st = await lstat3(existing).catch(() => null);
23855
24605
  if (st !== null) break;
23856
- suffix.unshift(path30.basename(existing));
23857
- existing = path30.dirname(existing);
24606
+ suffix.unshift(path32.basename(existing));
24607
+ existing = path32.dirname(existing);
23858
24608
  }
23859
24609
  const existingReal = await realpath2(existing).catch(() => existing);
23860
- const targetReal = suffix.length > 0 ? path30.join(existingReal, ...suffix) : existingReal;
23861
- const rel = path30.relative(rootReal, targetReal);
24610
+ const targetReal = suffix.length > 0 ? path32.join(existingReal, ...suffix) : existingReal;
24611
+ const rel = path32.relative(rootReal, targetReal);
23862
24612
  if (rel === "") return true;
23863
24613
  if (rel === "..") return false;
23864
- if (rel.startsWith(`..${path30.sep}`)) return false;
23865
- if (path30.isAbsolute(rel)) return false;
24614
+ if (rel.startsWith(`..${path32.sep}`)) return false;
24615
+ if (path32.isAbsolute(rel)) return false;
23866
24616
  return true;
23867
24617
  }
23868
24618
 
@@ -23989,6 +24739,25 @@ export {
23989
24739
  ensureBuiltInWearableConnectors,
23990
24740
  WearablesService,
23991
24741
  locateTranscriptPath,
24742
+ activityDatabasePath,
24743
+ ensureActivityStateDir,
24744
+ openActivityDatabase,
24745
+ applyActivitySchema,
24746
+ ActivityStore,
24747
+ ACTIVITY_DIGEST_FORMAT_VERSION,
24748
+ ACTIVITY_DIR_NAME,
24749
+ isValidActivityDate,
24750
+ activityDigestPath,
24751
+ activityDayWindow,
24752
+ composeActivityDigestBody,
24753
+ hashActivityBody,
24754
+ composeActivityDigestMeta,
24755
+ serializeActivityDigest,
24756
+ parseActivityDigest,
24757
+ DEFAULT_MEETING_APP_PATTERNS,
24758
+ DEFAULT_MEETINGS_DETECTION_CONFIG,
24759
+ meetingId,
24760
+ detectMeetings,
23992
24761
  registerLocalSessionSourceAdapter,
23993
24762
  getLocalSessionSourceAdapter,
23994
24763
  listLocalSessionSourceAdapters,
@@ -24004,4 +24773,4 @@ export {
24004
24773
  blendGraphExpandedRecallScore,
24005
24774
  Orchestrator
24006
24775
  };
24007
- //# sourceMappingURL=chunk-7PY55HMI.js.map
24776
+ //# sourceMappingURL=chunk-5RZHHANR.js.map