@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.
- package/dist/access-cli.js +1 -1
- package/dist/{chunk-7PY55HMI.js → chunk-5RZHHANR.js} +811 -42
- package/dist/chunk-5RZHHANR.js.map +1 -0
- package/dist/index.d.ts +260 -4
- package/dist/index.js +39 -1
- package/dist/orchestrator.js +1 -1
- package/dist/schemas.d.ts +22 -22
- package/dist/transfer/types.d.ts +12 -12
- 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/index.ts +2 -2
- package/src/meetings/detect.test.ts +366 -0
- package/src/meetings/detect.ts +287 -0
- package/src/meetings/index.ts +8 -0
- package/src/meetings/types.ts +76 -0
- 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
|
|
@@ -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
|
|
2836
|
-
const candidate = new Date(
|
|
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
|
|
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) {
|
|
@@ -22203,8 +22206,8 @@ function isProcessAlive(pid) {
|
|
|
22203
22206
|
return true;
|
|
22204
22207
|
}
|
|
22205
22208
|
}
|
|
22206
|
-
function sleepSync(
|
|
22207
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0,
|
|
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/
|
|
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(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
|
|
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}${
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
24373
|
+
const fileExtension = path31.extname(filePath).toLowerCase();
|
|
23624
24374
|
const parsed = await adapter.parseFile(
|
|
23625
24375
|
{
|
|
23626
24376
|
content,
|
|
23627
|
-
fileName:
|
|
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(
|
|
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
|
|
23697
|
-
|
|
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
|
|
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 =
|
|
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
|
|
23757
|
-
import
|
|
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 =
|
|
24510
|
+
const rootAbs = path32.resolve(opts.targetRoot);
|
|
23761
24511
|
await assertIsDirectoryNotSymlink(rootAbs, "forkCapsule", "targetRoot");
|
|
23762
|
-
const forkDirAbs =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
24558
|
+
const rootAbs = path32.resolve(targetRoot);
|
|
23809
24559
|
const rootReal = await realpath2(rootAbs).catch(() => rootAbs);
|
|
23810
|
-
const lineagePath =
|
|
23811
|
-
const rel =
|
|
23812
|
-
if (rel.startsWith("..") ||
|
|
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 !==
|
|
24603
|
+
while (existing !== path32.dirname(existing)) {
|
|
23854
24604
|
const st = await lstat3(existing).catch(() => null);
|
|
23855
24605
|
if (st !== null) break;
|
|
23856
|
-
suffix.unshift(
|
|
23857
|
-
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 ?
|
|
23861
|
-
const rel =
|
|
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(`..${
|
|
23865
|
-
if (
|
|
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-
|
|
24776
|
+
//# sourceMappingURL=chunk-5RZHHANR.js.map
|