zam-core 0.3.12 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -690,6 +690,66 @@ function isRemoteDatabasePath(dbPath) {
690
690
  function isDatabaseProvider(value) {
691
691
  return value === "local" || value === "native" || value === "remote";
692
692
  }
693
+ function cwdRequiresTursoCredentials() {
694
+ try {
695
+ const configPath = join2(process.cwd(), ".zam", "config.yaml");
696
+ if (existsSync2(configPath)) {
697
+ const configText = readFileSync2(configPath, "utf-8");
698
+ return /[\s\S]*turso:[\s\S]*url:/m.test(configText);
699
+ }
700
+ } catch (_e) {
701
+ }
702
+ return false;
703
+ }
704
+ function resolveDatabaseTarget(options = {}) {
705
+ const configuredCloud = options.useConfiguredCloud !== false && !options.dbPath && !options.syncUrl ? getTursoCredentials() : null;
706
+ if (cwdRequiresTursoCredentials() && !configuredCloud && options.useConfiguredCloud !== false && !options.dbPath && !options.syncUrl) {
707
+ throw new Error(
708
+ "Turso cloud database is configured in .zam/config.yaml but missing local credentials. Run: zam connector setup turso"
709
+ );
710
+ }
711
+ const dbPath = configuredCloud?.url ?? options.dbPath ?? DEFAULT_DB_PATH;
712
+ const isRemote = isRemoteDatabasePath(dbPath);
713
+ const isEmbeddedReplica = Boolean(options.syncUrl);
714
+ const provider = resolveProvider(options, configuredCloud?.mode, isRemote);
715
+ return {
716
+ dbPath,
717
+ provider,
718
+ isRemote,
719
+ isEmbeddedReplica,
720
+ configuredCloud
721
+ };
722
+ }
723
+ function getDatabaseTargetInfo(options = {}) {
724
+ const target = resolveDatabaseTarget(options);
725
+ if (target.isEmbeddedReplica) {
726
+ return {
727
+ kind: "turso-replica",
728
+ provider: target.provider,
729
+ location: target.dbPath,
730
+ syncUrl: options.syncUrl
731
+ };
732
+ }
733
+ if (target.provider === "remote" && target.isRemote) {
734
+ return {
735
+ kind: "turso-remote",
736
+ provider: target.provider,
737
+ location: target.dbPath
738
+ };
739
+ }
740
+ if (target.isRemote) {
741
+ return {
742
+ kind: "turso-native",
743
+ provider: target.provider,
744
+ location: target.dbPath
745
+ };
746
+ }
747
+ return {
748
+ kind: "local",
749
+ provider: target.provider,
750
+ location: target.dbPath
751
+ };
752
+ }
693
753
  function openLocalSqlite(dbPath) {
694
754
  const mod = require2("better-sqlite3");
695
755
  const BetterSqlite3 = "default" in mod ? mod.default : mod;
@@ -707,27 +767,7 @@ function loadLibsql() {
707
767
  }
708
768
  }
709
769
  async function openDatabase(options = {}) {
710
- const configuredCloud = options.useConfiguredCloud !== false && !options.dbPath && !options.syncUrl ? getTursoCredentials() : null;
711
- let requiresTurso = false;
712
- try {
713
- const configPath = join2(process.cwd(), ".zam", "config.yaml");
714
- if (existsSync2(configPath)) {
715
- const configText = readFileSync2(configPath, "utf-8");
716
- if (/[\s\S]*turso:[\s\S]*url:/m.test(configText)) {
717
- requiresTurso = true;
718
- }
719
- }
720
- } catch (_e) {
721
- }
722
- if (requiresTurso && !configuredCloud && options.useConfiguredCloud !== false && !options.dbPath && !options.syncUrl) {
723
- throw new Error(
724
- "Turso cloud database is configured in .zam/config.yaml but missing local credentials. Run: zam connector setup turso"
725
- );
726
- }
727
- const dbPath = configuredCloud?.url ?? options.dbPath ?? DEFAULT_DB_PATH;
728
- const isRemote = isRemoteDatabasePath(dbPath);
729
- const isEmbeddedReplica = Boolean(options.syncUrl);
730
- const provider = resolveProvider(options, configuredCloud?.mode, isRemote);
770
+ const { dbPath, provider, isRemote, isEmbeddedReplica, configuredCloud } = resolveDatabaseTarget(options);
731
771
  const shouldInitialize = options.initialize === true || !isRemote && !isEmbeddedReplica && !existsSync2(dbPath);
732
772
  if (provider === "remote") {
733
773
  const url = isRemote ? dbPath : options.syncUrl;
@@ -1347,8 +1387,17 @@ async function deleteCardForUser(db, tokenId, userId) {
1347
1387
  await db.prepare("DELETE FROM cards WHERE id = ?").run(card.id);
1348
1388
  return { card, impact };
1349
1389
  }
1350
- async function getDueCards(db, userId, now) {
1390
+ async function getDueCards(db, userId, now, domain) {
1351
1391
  const cutoff = now ?? (/* @__PURE__ */ new Date()).toISOString();
1392
+ if (domain) {
1393
+ return await db.prepare(
1394
+ `SELECT c.*, t.slug, t.concept, t.domain, t.bloom_level
1395
+ FROM cards c
1396
+ JOIN tokens t ON t.id = c.token_id
1397
+ WHERE c.user_id = ? AND c.blocked = 0 AND c.due_at <= ? AND t.domain = ?
1398
+ ORDER BY t.bloom_level ASC, c.due_at ASC`
1399
+ ).all(userId, cutoff, domain);
1400
+ }
1352
1401
  return await db.prepare(
1353
1402
  `SELECT c.*, t.slug, t.concept, t.domain, t.bloom_level
1354
1403
  FROM cards c
@@ -2121,6 +2170,415 @@ function getMonitorLogStats(sessionId) {
2121
2170
  return { exists: true, sizeBytes: stat.size, lineCount };
2122
2171
  }
2123
2172
 
2173
+ // src/kernel/observation/observer-sidecar-policy.ts
2174
+ import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync3 } from "fs";
2175
+ import { join as join6 } from "path";
2176
+
2177
+ // src/kernel/observation/policy.ts
2178
+ var OBSERVER_POLICY_VERSION = 1;
2179
+ var DEFAULT_OBSERVER_POLICY = {
2180
+ version: OBSERVER_POLICY_VERSION,
2181
+ scope: "window",
2182
+ allowlist: [],
2183
+ denylist: [],
2184
+ consent: "per-session",
2185
+ retention: "none",
2186
+ redactWindowTitles: true,
2187
+ audioOptIn: false
2188
+ };
2189
+ var BUILT_IN_SENSITIVE_MATCHERS = [
2190
+ // Password managers
2191
+ "1password",
2192
+ "bitwarden",
2193
+ "keepass",
2194
+ "lastpass",
2195
+ "dashlane",
2196
+ "nordpass",
2197
+ "enpass",
2198
+ "proton pass",
2199
+ // Authentication / credential / UAC surfaces
2200
+ "credentialuibroker",
2201
+ "consentux",
2202
+ "logonui",
2203
+ "windowssecurity",
2204
+ "authenticator",
2205
+ // Conservative banking/title hints
2206
+ "online banking",
2207
+ "onlinebanking"
2208
+ ];
2209
+ function parseList(raw) {
2210
+ if (!raw) return [];
2211
+ return raw.split(",").map((entry) => entry.trim().toLowerCase()).filter((entry) => entry.length > 0);
2212
+ }
2213
+ function parseObserverList(raw) {
2214
+ return parseList(raw);
2215
+ }
2216
+ function parseScope(raw, defaultScope) {
2217
+ if (raw === "off" || raw === "window" || raw === "fullscreen") {
2218
+ return raw;
2219
+ }
2220
+ return defaultScope ?? DEFAULT_OBSERVER_POLICY.scope;
2221
+ }
2222
+ function parseConsent(raw, defaultConsent) {
2223
+ if (raw === "per-capture" || raw === "per-session" || raw === "standing") {
2224
+ return raw;
2225
+ }
2226
+ return defaultConsent ?? DEFAULT_OBSERVER_POLICY.consent;
2227
+ }
2228
+ function parseRetention(raw) {
2229
+ return raw === "none" || raw === "session" || raw === "persist" ? raw : DEFAULT_OBSERVER_POLICY.retention;
2230
+ }
2231
+ function parseBool(raw, fallback) {
2232
+ if (raw === void 0) return fallback;
2233
+ return raw === "true";
2234
+ }
2235
+ function parseObserverPolicy(raw, defaults) {
2236
+ return {
2237
+ version: OBSERVER_POLICY_VERSION,
2238
+ scope: parseScope(raw["observer.scope"], defaults?.scope),
2239
+ allowlist: parseList(raw["observer.allowlist"]),
2240
+ denylist: parseList(raw["observer.denylist"]),
2241
+ consent: parseConsent(raw["observer.consent"], defaults?.consent),
2242
+ retention: parseRetention(raw["observer.retention"]),
2243
+ redactWindowTitles: parseBool(
2244
+ raw["observer.redact_titles"],
2245
+ DEFAULT_OBSERVER_POLICY.redactWindowTitles
2246
+ ),
2247
+ audioOptIn: parseBool(
2248
+ raw["observer.audio"],
2249
+ DEFAULT_OBSERVER_POLICY.audioOptIn
2250
+ )
2251
+ };
2252
+ }
2253
+ function getDefaultsForSymbiosisMode(mode) {
2254
+ if (mode === "autonomy") {
2255
+ return { scope: "fullscreen", consent: "standing" };
2256
+ }
2257
+ return { scope: "window", consent: "per-session" };
2258
+ }
2259
+ async function resolveActiveSymbiosisMode(db) {
2260
+ const userId = await getSetting(db, "user.id") || "default";
2261
+ const activeSession = await db.prepare(
2262
+ "SELECT id FROM sessions WHERE user_id = ? AND completed_at IS NULL ORDER BY started_at DESC LIMIT 1"
2263
+ ).get(userId);
2264
+ if (activeSession) {
2265
+ const stepToken = await db.prepare(
2266
+ `SELECT t.symbiosis_mode, t.domain
2267
+ FROM session_steps s
2268
+ JOIN tokens t ON t.id = s.token_id
2269
+ WHERE s.session_id = ?
2270
+ ORDER BY s.created_at DESC, s.id DESC
2271
+ LIMIT 1`
2272
+ ).get(activeSession.id);
2273
+ if (stepToken) {
2274
+ if (stepToken.symbiosis_mode) {
2275
+ return stepToken.symbiosis_mode;
2276
+ }
2277
+ if (stepToken.domain) {
2278
+ const competence = await getDomainCompetence(db, userId);
2279
+ const comp = competence.find((c) => c.domain === stepToken.domain);
2280
+ if (comp) {
2281
+ return comp.suggestedMode;
2282
+ }
2283
+ }
2284
+ }
2285
+ }
2286
+ const lastReviewToken = await db.prepare(
2287
+ `SELECT t.symbiosis_mode, t.domain
2288
+ FROM review_logs r
2289
+ JOIN tokens t ON t.id = r.token_id
2290
+ WHERE r.user_id = ?
2291
+ ORDER BY r.reviewed_at DESC, r.id DESC
2292
+ LIMIT 1`
2293
+ ).get(userId);
2294
+ if (lastReviewToken) {
2295
+ if (lastReviewToken.symbiosis_mode) {
2296
+ return lastReviewToken.symbiosis_mode;
2297
+ }
2298
+ if (lastReviewToken.domain) {
2299
+ const competence = await getDomainCompetence(db, userId);
2300
+ const comp = competence.find((c) => c.domain === lastReviewToken.domain);
2301
+ if (comp) {
2302
+ return comp.suggestedMode;
2303
+ }
2304
+ }
2305
+ }
2306
+ return "shadowing";
2307
+ }
2308
+ async function resolveObserverPolicy(db) {
2309
+ const [scope, allowlist, denylist, consent, retention, redactTitles, audio] = await Promise.all([
2310
+ getSetting(db, "observer.scope"),
2311
+ getSetting(db, "observer.allowlist"),
2312
+ getSetting(db, "observer.denylist"),
2313
+ getSetting(db, "observer.consent"),
2314
+ getSetting(db, "observer.retention"),
2315
+ getSetting(db, "observer.redact_titles"),
2316
+ getSetting(db, "observer.audio")
2317
+ ]);
2318
+ const mode = await resolveActiveSymbiosisMode(db);
2319
+ const defaults = getDefaultsForSymbiosisMode(mode);
2320
+ return parseObserverPolicy(
2321
+ {
2322
+ "observer.scope": scope,
2323
+ "observer.allowlist": allowlist,
2324
+ "observer.denylist": denylist,
2325
+ "observer.consent": consent,
2326
+ "observer.retention": retention,
2327
+ "observer.redact_titles": redactTitles,
2328
+ "observer.audio": audio
2329
+ },
2330
+ defaults
2331
+ );
2332
+ }
2333
+ function haystacks(...values) {
2334
+ return values.filter((v) => typeof v === "string" && v.length > 0).map((v) => v.toLowerCase());
2335
+ }
2336
+ function deny(reason, denialReason) {
2337
+ return { allowed: false, reason, denialReason };
2338
+ }
2339
+ function matchBuiltInSensitive(processName, windowTitle) {
2340
+ const fields = haystacks(processName, windowTitle);
2341
+ for (const matcher of BUILT_IN_SENSITIVE_MATCHERS) {
2342
+ if (fields.some((field) => field.includes(matcher))) return matcher;
2343
+ }
2344
+ return null;
2345
+ }
2346
+ function matchDenylist(policy, processName, windowTitle) {
2347
+ const fields = haystacks(processName, windowTitle);
2348
+ for (const matcher of policy.denylist) {
2349
+ if (fields.some((field) => field.includes(matcher))) return matcher;
2350
+ }
2351
+ return null;
2352
+ }
2353
+ function inAllowlist(policy, processName) {
2354
+ if (policy.allowlist.length === 0) return true;
2355
+ if (!processName) return false;
2356
+ const name = processName.toLowerCase();
2357
+ return policy.allowlist.some((entry) => name.includes(entry));
2358
+ }
2359
+ function decidePreCapture(policy, request) {
2360
+ if (policy.scope === "off") {
2361
+ return deny("observer is disabled (observer.scope=off)", "scope-off");
2362
+ }
2363
+ if (policy.scope === "window" && !request.hasExplicitTarget) {
2364
+ return deny(
2365
+ "window scope requires a target: pass --process-name or --hwnd, or set observer.scope=fullscreen",
2366
+ "scope-requires-target"
2367
+ );
2368
+ }
2369
+ const requested = request.requestedProcessName;
2370
+ if (requested) {
2371
+ const sensitive = matchBuiltInSensitive(requested, null);
2372
+ if (sensitive) {
2373
+ return deny(`refusing sensitive surface (${sensitive})`, "sensitive");
2374
+ }
2375
+ const denied = matchDenylist(policy, requested, null);
2376
+ if (denied) {
2377
+ return deny(`process is denylisted (${denied})`, "denylisted");
2378
+ }
2379
+ if (policy.scope === "window" && !inAllowlist(policy, requested)) {
2380
+ return deny(
2381
+ `process not in observer.allowlist (${requested.toLowerCase()})`,
2382
+ "not-allowlisted"
2383
+ );
2384
+ }
2385
+ }
2386
+ return { allowed: true };
2387
+ }
2388
+ function decidePostCapture(policy, target) {
2389
+ const isFullscreen = target.method.toLowerCase().includes("fullscreen");
2390
+ if (policy.scope === "window" && isFullscreen) {
2391
+ return deny(
2392
+ "window scope but capture fell back to fullscreen (target window not resolved)",
2393
+ "scope-requires-target"
2394
+ );
2395
+ }
2396
+ const sensitive = matchBuiltInSensitive(
2397
+ target.processName,
2398
+ target.windowTitle
2399
+ );
2400
+ if (sensitive) {
2401
+ return deny(`refusing sensitive surface (${sensitive})`, "sensitive");
2402
+ }
2403
+ const denied = matchDenylist(policy, target.processName, target.windowTitle);
2404
+ if (denied) {
2405
+ return deny(`captured window is denylisted (${denied})`, "denylisted");
2406
+ }
2407
+ if (policy.scope === "window" && !inAllowlist(policy, target.processName)) {
2408
+ return deny(
2409
+ `captured window not in observer.allowlist (${target.processName ?? "unknown"})`,
2410
+ "not-allowlisted"
2411
+ );
2412
+ }
2413
+ return { allowed: true };
2414
+ }
2415
+ async function isObserverPolicyConfigured(db) {
2416
+ const settings = await getAllSettings(db);
2417
+ return Object.keys(settings).some((key) => key.startsWith("observer."));
2418
+ }
2419
+ var OBSERVER_POLICY_UNSET_HINT = "Observer policy is at defaults (scope=window: only a targeted window is captured). Configure with `zam observer status|grant|revoke` or `zam settings set observer.scope <off|window|fullscreen>`.";
2420
+
2421
+ // src/kernel/observation/ui-observer-io.ts
2422
+ import { appendFileSync as appendFileSync2, existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5 } from "fs";
2423
+ import { homedir as homedir4 } from "os";
2424
+ import { join as join5 } from "path";
2425
+
2426
+ // src/kernel/observation/ui-observer.ts
2427
+ var UI_OBSERVATION_PROTOCOL_VERSION = 1;
2428
+ var OBSERVATION_KINDS = /* @__PURE__ */ new Set([
2429
+ "progress",
2430
+ "step-completed",
2431
+ "error",
2432
+ "help-seeking",
2433
+ "uncertain",
2434
+ "privacy-pause",
2435
+ "heartbeat"
2436
+ ]);
2437
+ var ACTION_TYPES = /* @__PURE__ */ new Set([
2438
+ "click",
2439
+ "shortcut",
2440
+ "typing",
2441
+ "scroll",
2442
+ "window-change"
2443
+ ]);
2444
+ var EVIDENCE_TYPES = /* @__PURE__ */ new Set([
2445
+ "uia",
2446
+ "keyframe",
2447
+ "clip",
2448
+ "window"
2449
+ ]);
2450
+ function isUiObservationReport(value) {
2451
+ if (!isRecord(value)) return false;
2452
+ if (value.version !== UI_OBSERVATION_PROTOCOL_VERSION) return false;
2453
+ if (!isNonEmptyString(value.sessionId)) return false;
2454
+ if (!isNonNegativeInteger(value.sequence)) return false;
2455
+ if (!isNonEmptyString(value.observedFrom)) return false;
2456
+ if (!isNonEmptyString(value.observedTo)) return false;
2457
+ if (typeof value.kind !== "string" || !OBSERVATION_KINDS.has(value.kind)) {
2458
+ return false;
2459
+ }
2460
+ if (!isApplication(value.application)) return false;
2461
+ if (!isNonEmptyString(value.summary)) return false;
2462
+ if (!Array.isArray(value.actions) || !value.actions.every(isObservedAction)) {
2463
+ return false;
2464
+ }
2465
+ if (!Array.isArray(value.evidence) || !value.evidence.every(isEvidenceRef)) {
2466
+ return false;
2467
+ }
2468
+ if (!Array.isArray(value.candidateTokens) || !value.candidateTokens.every(isCandidateToken)) {
2469
+ return false;
2470
+ }
2471
+ return isConfidence(value.confidence);
2472
+ }
2473
+ function parseUiObservationLog(jsonl) {
2474
+ const reports = [];
2475
+ for (const line of jsonl.split("\n")) {
2476
+ const trimmed = line.trim();
2477
+ if (!trimmed) continue;
2478
+ try {
2479
+ const value = JSON.parse(trimmed);
2480
+ if (isUiObservationReport(value)) {
2481
+ reports.push(value);
2482
+ }
2483
+ } catch {
2484
+ }
2485
+ }
2486
+ return reports.sort((left, right) => left.sequence - right.sequence);
2487
+ }
2488
+ function isApplication(value) {
2489
+ if (!isRecord(value) || !isNonEmptyString(value.processName)) return false;
2490
+ if (value.processId !== void 0 && !isNonNegativeInteger(value.processId)) {
2491
+ return false;
2492
+ }
2493
+ return value.windowTitle === void 0 || typeof value.windowTitle === "string";
2494
+ }
2495
+ function isObservedAction(value) {
2496
+ if (!isRecord(value) || typeof value.type !== "string") return false;
2497
+ if (!ACTION_TYPES.has(value.type)) return false;
2498
+ if (value.target !== void 0 && typeof value.target !== "string") {
2499
+ return false;
2500
+ }
2501
+ return value.result === void 0 || typeof value.result === "string";
2502
+ }
2503
+ function isEvidenceRef(value) {
2504
+ if (!isRecord(value) || typeof value.type !== "string") return false;
2505
+ return EVIDENCE_TYPES.has(value.type) && isNonEmptyString(value.ref) && typeof value.redacted === "boolean";
2506
+ }
2507
+ function isCandidateToken(value) {
2508
+ return isRecord(value) && isNonEmptyString(value.slug) && isConfidence(value.confidence) && isNonEmptyString(value.rationale);
2509
+ }
2510
+ function isRecord(value) {
2511
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2512
+ }
2513
+ function isNonEmptyString(value) {
2514
+ return typeof value === "string" && value.trim().length > 0;
2515
+ }
2516
+ function isNonNegativeInteger(value) {
2517
+ return Number.isInteger(value) && Number(value) >= 0;
2518
+ }
2519
+ function isConfidence(value) {
2520
+ return typeof value === "number" && value >= 0 && value <= 1;
2521
+ }
2522
+
2523
+ // src/kernel/observation/ui-observer-io.ts
2524
+ var DEFAULT_UI_OBSERVER_DIR = join5(homedir4(), ".zam", "observer");
2525
+ function getUiObserverDir() {
2526
+ return process.env.ZAM_OBSERVER_DIR || DEFAULT_UI_OBSERVER_DIR;
2527
+ }
2528
+ function getUiObservationPath(sessionId) {
2529
+ if (!/^[A-Za-z0-9_-]+$/.test(sessionId)) {
2530
+ throw new Error(`Invalid observer session ID: ${sessionId}`);
2531
+ }
2532
+ return join5(getUiObserverDir(), `${sessionId}.reports.jsonl`);
2533
+ }
2534
+ function ensureUiObserverDir() {
2535
+ const dir = getUiObserverDir();
2536
+ if (!existsSync5(dir)) {
2537
+ mkdirSync4(dir, { recursive: true, mode: 448 });
2538
+ }
2539
+ }
2540
+ function uiObservationLogExists(sessionId) {
2541
+ return existsSync5(getUiObservationPath(sessionId));
2542
+ }
2543
+ function readUiObservationLog(sessionId) {
2544
+ const path = getUiObservationPath(sessionId);
2545
+ if (!existsSync5(path)) return [];
2546
+ return parseUiObservationLog(readFileSync5(path, "utf8"));
2547
+ }
2548
+ function appendUiObservationReport(report) {
2549
+ if (!isUiObservationReport(report)) {
2550
+ throw new Error("Invalid UI observation report");
2551
+ }
2552
+ ensureUiObserverDir();
2553
+ appendFileSync2(
2554
+ getUiObservationPath(report.sessionId),
2555
+ `${JSON.stringify(report)}
2556
+ `,
2557
+ { encoding: "utf8", mode: 384 }
2558
+ );
2559
+ }
2560
+
2561
+ // src/kernel/observation/observer-sidecar-policy.ts
2562
+ var SIDECAR_POLICY_FILE = "policy.json";
2563
+ function toSidecarPrivacyPolicy(policy) {
2564
+ return {
2565
+ allowProcesses: [...policy.allowlist],
2566
+ denyProcesses: [...policy.denylist],
2567
+ denyTitleMarkers: [...policy.denylist]
2568
+ };
2569
+ }
2570
+ async function syncObserverSidecarPolicy(db, dir = getUiObserverDir()) {
2571
+ const policy = toSidecarPrivacyPolicy(await resolveObserverPolicy(db));
2572
+ mkdirSync5(dir, { recursive: true, mode: 448 });
2573
+ const path = join6(dir, SIDECAR_POLICY_FILE);
2574
+ writeFileSync3(path, `${JSON.stringify(policy, null, 2)}
2575
+ `, {
2576
+ encoding: "utf8",
2577
+ mode: 384
2578
+ });
2579
+ return { path, policy };
2580
+ }
2581
+
2124
2582
  // src/kernel/observation/session-synthesis.ts
2125
2583
  import { ulid as ulid7 } from "ulid";
2126
2584
 
@@ -2391,146 +2849,6 @@ async function unblockReady(db, userId) {
2391
2849
  return { unblocked };
2392
2850
  }
2393
2851
 
2394
- // src/kernel/observation/ui-observer-io.ts
2395
- import { appendFileSync as appendFileSync2, existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5 } from "fs";
2396
- import { homedir as homedir4 } from "os";
2397
- import { join as join5 } from "path";
2398
-
2399
- // src/kernel/observation/ui-observer.ts
2400
- var UI_OBSERVATION_PROTOCOL_VERSION = 1;
2401
- var OBSERVATION_KINDS = /* @__PURE__ */ new Set([
2402
- "progress",
2403
- "step-completed",
2404
- "error",
2405
- "help-seeking",
2406
- "uncertain",
2407
- "privacy-pause",
2408
- "heartbeat"
2409
- ]);
2410
- var ACTION_TYPES = /* @__PURE__ */ new Set([
2411
- "click",
2412
- "shortcut",
2413
- "typing",
2414
- "scroll",
2415
- "window-change"
2416
- ]);
2417
- var EVIDENCE_TYPES = /* @__PURE__ */ new Set([
2418
- "uia",
2419
- "keyframe",
2420
- "clip",
2421
- "window"
2422
- ]);
2423
- function isUiObservationReport(value) {
2424
- if (!isRecord(value)) return false;
2425
- if (value.version !== UI_OBSERVATION_PROTOCOL_VERSION) return false;
2426
- if (!isNonEmptyString(value.sessionId)) return false;
2427
- if (!isNonNegativeInteger(value.sequence)) return false;
2428
- if (!isNonEmptyString(value.observedFrom)) return false;
2429
- if (!isNonEmptyString(value.observedTo)) return false;
2430
- if (typeof value.kind !== "string" || !OBSERVATION_KINDS.has(value.kind)) {
2431
- return false;
2432
- }
2433
- if (!isApplication(value.application)) return false;
2434
- if (!isNonEmptyString(value.summary)) return false;
2435
- if (!Array.isArray(value.actions) || !value.actions.every(isObservedAction)) {
2436
- return false;
2437
- }
2438
- if (!Array.isArray(value.evidence) || !value.evidence.every(isEvidenceRef)) {
2439
- return false;
2440
- }
2441
- if (!Array.isArray(value.candidateTokens) || !value.candidateTokens.every(isCandidateToken)) {
2442
- return false;
2443
- }
2444
- return isConfidence(value.confidence);
2445
- }
2446
- function parseUiObservationLog(jsonl) {
2447
- const reports = [];
2448
- for (const line of jsonl.split("\n")) {
2449
- const trimmed = line.trim();
2450
- if (!trimmed) continue;
2451
- try {
2452
- const value = JSON.parse(trimmed);
2453
- if (isUiObservationReport(value)) {
2454
- reports.push(value);
2455
- }
2456
- } catch {
2457
- }
2458
- }
2459
- return reports.sort((left, right) => left.sequence - right.sequence);
2460
- }
2461
- function isApplication(value) {
2462
- if (!isRecord(value) || !isNonEmptyString(value.processName)) return false;
2463
- if (value.processId !== void 0 && !isNonNegativeInteger(value.processId)) {
2464
- return false;
2465
- }
2466
- return value.windowTitle === void 0 || typeof value.windowTitle === "string";
2467
- }
2468
- function isObservedAction(value) {
2469
- if (!isRecord(value) || typeof value.type !== "string") return false;
2470
- if (!ACTION_TYPES.has(value.type)) return false;
2471
- if (value.target !== void 0 && typeof value.target !== "string") {
2472
- return false;
2473
- }
2474
- return value.result === void 0 || typeof value.result === "string";
2475
- }
2476
- function isEvidenceRef(value) {
2477
- if (!isRecord(value) || typeof value.type !== "string") return false;
2478
- return EVIDENCE_TYPES.has(value.type) && isNonEmptyString(value.ref) && typeof value.redacted === "boolean";
2479
- }
2480
- function isCandidateToken(value) {
2481
- return isRecord(value) && isNonEmptyString(value.slug) && isConfidence(value.confidence) && isNonEmptyString(value.rationale);
2482
- }
2483
- function isRecord(value) {
2484
- return typeof value === "object" && value !== null && !Array.isArray(value);
2485
- }
2486
- function isNonEmptyString(value) {
2487
- return typeof value === "string" && value.trim().length > 0;
2488
- }
2489
- function isNonNegativeInteger(value) {
2490
- return Number.isInteger(value) && Number(value) >= 0;
2491
- }
2492
- function isConfidence(value) {
2493
- return typeof value === "number" && value >= 0 && value <= 1;
2494
- }
2495
-
2496
- // src/kernel/observation/ui-observer-io.ts
2497
- var DEFAULT_UI_OBSERVER_DIR = join5(homedir4(), ".zam", "observer");
2498
- function getUiObserverDir() {
2499
- return process.env.ZAM_OBSERVER_DIR || DEFAULT_UI_OBSERVER_DIR;
2500
- }
2501
- function getUiObservationPath(sessionId) {
2502
- if (!/^[A-Za-z0-9_-]+$/.test(sessionId)) {
2503
- throw new Error(`Invalid observer session ID: ${sessionId}`);
2504
- }
2505
- return join5(getUiObserverDir(), `${sessionId}.reports.jsonl`);
2506
- }
2507
- function ensureUiObserverDir() {
2508
- const dir = getUiObserverDir();
2509
- if (!existsSync5(dir)) {
2510
- mkdirSync4(dir, { recursive: true, mode: 448 });
2511
- }
2512
- }
2513
- function uiObservationLogExists(sessionId) {
2514
- return existsSync5(getUiObservationPath(sessionId));
2515
- }
2516
- function readUiObservationLog(sessionId) {
2517
- const path = getUiObservationPath(sessionId);
2518
- if (!existsSync5(path)) return [];
2519
- return parseUiObservationLog(readFileSync5(path, "utf8"));
2520
- }
2521
- function appendUiObservationReport(report) {
2522
- if (!isUiObservationReport(report)) {
2523
- throw new Error("Invalid UI observation report");
2524
- }
2525
- ensureUiObserverDir();
2526
- appendFileSync2(
2527
- getUiObservationPath(report.sessionId),
2528
- `${JSON.stringify(report)}
2529
- `,
2530
- { encoding: "utf8", mode: 384 }
2531
- );
2532
- }
2533
-
2534
2852
  // src/kernel/observation/ui-observer-synthesis.ts
2535
2853
  var MIN_UI_CONFIDENCE = 0.6;
2536
2854
  function kindToRating(kind) {
@@ -3335,7 +3653,7 @@ function generatePrompt(input) {
3335
3653
 
3336
3654
  // src/kernel/recall/reference-resolver.ts
3337
3655
  import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
3338
- import { dirname as dirname3, join as join6, resolve } from "path";
3656
+ import { dirname as dirname3, join as join7, resolve } from "path";
3339
3657
  var DEFAULT_REVIEW_CONTEXT_MAX_CHARS = 6e3;
3340
3658
  function htmlToText(html) {
3341
3659
  let content = html;
@@ -3396,8 +3714,8 @@ async function resolveReference(sourceLink) {
3396
3714
  const filePath = anchorIndex2 !== -1 ? fullPathWithAnchor.slice(0, anchorIndex2) : fullPathWithAnchor;
3397
3715
  const anchor2 = anchorIndex2 !== -1 ? fullPathWithAnchor.slice(anchorIndex2) : "";
3398
3716
  const parentDir = dirname3(process.cwd());
3399
- const localRepoPath = join6(parentDir, repo);
3400
- const localFilePath = join6(localRepoPath, filePath);
3717
+ const localRepoPath = join7(parentDir, repo);
3718
+ const localFilePath = join7(localRepoPath, filePath);
3401
3719
  if (existsSync6(localFilePath)) {
3402
3720
  try {
3403
3721
  let fileContent = readFileSync6(localFilePath, "utf-8");
@@ -3711,12 +4029,12 @@ import {
3711
4029
  appendFileSync as appendFileSync3,
3712
4030
  copyFileSync,
3713
4031
  existsSync as existsSync7,
3714
- mkdirSync as mkdirSync5,
4032
+ mkdirSync as mkdirSync6,
3715
4033
  readFileSync as readFileSync7,
3716
- writeFileSync as writeFileSync3
4034
+ writeFileSync as writeFileSync4
3717
4035
  } from "fs";
3718
4036
  import { homedir as homedir5 } from "os";
3719
- import { join as join7 } from "path";
4037
+ import { join as join8 } from "path";
3720
4038
  import { fileURLToPath } from "url";
3721
4039
  var HOME = homedir5();
3722
4040
  function getPackageSkillPath(agent = "default") {
@@ -3724,15 +4042,15 @@ function getPackageSkillPath(agent = "default") {
3724
4042
  fileURLToPath(new URL("../../..", import.meta.url)),
3725
4043
  fileURLToPath(new URL("../..", import.meta.url)),
3726
4044
  fileURLToPath(new URL("..", import.meta.url))
3727
- ].find((candidate) => existsSync7(join7(candidate, "package.json"))) ?? "";
4045
+ ].find((candidate) => existsSync7(join8(candidate, "package.json"))) ?? "";
3728
4046
  if (!packageRoot) return "";
3729
4047
  if (agent === "codex") {
3730
- const codexPath = join7(packageRoot, ".agents", "skills", "zam", "SKILL.md");
4048
+ const codexPath = join8(packageRoot, ".agents", "skills", "zam", "SKILL.md");
3731
4049
  if (existsSync7(codexPath)) return codexPath;
3732
4050
  return "";
3733
4051
  }
3734
4052
  if (agent === "claude") {
3735
- const claudePath = join7(
4053
+ const claudePath = join8(
3736
4054
  packageRoot,
3737
4055
  ".claude",
3738
4056
  "skills",
@@ -3742,9 +4060,9 @@ function getPackageSkillPath(agent = "default") {
3742
4060
  if (existsSync7(claudePath)) return claudePath;
3743
4061
  return "";
3744
4062
  }
3745
- let path = join7(packageRoot, ".agent", "skills", "zam", "SKILL.md");
4063
+ let path = join8(packageRoot, ".agent", "skills", "zam", "SKILL.md");
3746
4064
  if (existsSync7(path)) return path;
3747
- path = join7(packageRoot, ".claude", "skills", "zam", "SKILL.md");
4065
+ path = join8(packageRoot, ".claude", "skills", "zam", "SKILL.md");
3748
4066
  if (existsSync7(path)) return path;
3749
4067
  return "";
3750
4068
  }
@@ -3757,16 +4075,16 @@ function distributeGlobalSkills(home = HOME) {
3757
4075
  console.warn("Could not find ZAM source SKILL.md in the package folder.");
3758
4076
  return results;
3759
4077
  }
3760
- const claudeSkillsDir = join7(home, ".claude", "skills", "zam");
4078
+ const claudeSkillsDir = join8(home, ".claude", "skills", "zam");
3761
4079
  try {
3762
4080
  if (!claudeSourceSkill) {
3763
4081
  throw new Error("Claude skill source not found");
3764
4082
  }
3765
- mkdirSync5(claudeSkillsDir, { recursive: true });
3766
- copyFileSync(claudeSourceSkill, join7(claudeSkillsDir, "SKILL.md"));
4083
+ mkdirSync6(claudeSkillsDir, { recursive: true });
4084
+ copyFileSync(claudeSourceSkill, join8(claudeSkillsDir, "SKILL.md"));
3767
4085
  results.push({
3768
4086
  name: "Claude Code Global",
3769
- path: join7(claudeSkillsDir, "SKILL.md"),
4087
+ path: join8(claudeSkillsDir, "SKILL.md"),
3770
4088
  success: true
3771
4089
  });
3772
4090
  } catch (_err) {
@@ -3776,13 +4094,13 @@ function distributeGlobalSkills(home = HOME) {
3776
4094
  success: false
3777
4095
  });
3778
4096
  }
3779
- const geminiSkillsDir = join7(home, ".gemini", "skills", "zam");
4097
+ const geminiSkillsDir = join8(home, ".gemini", "skills", "zam");
3780
4098
  try {
3781
- mkdirSync5(geminiSkillsDir, { recursive: true });
3782
- copyFileSync(sourceSkill, join7(geminiSkillsDir, "SKILL.md"));
4099
+ mkdirSync6(geminiSkillsDir, { recursive: true });
4100
+ copyFileSync(sourceSkill, join8(geminiSkillsDir, "SKILL.md"));
3783
4101
  results.push({
3784
4102
  name: "Gemini CLI Global",
3785
- path: join7(geminiSkillsDir, "SKILL.md"),
4103
+ path: join8(geminiSkillsDir, "SKILL.md"),
3786
4104
  success: true
3787
4105
  });
3788
4106
  } catch (_err) {
@@ -3792,16 +4110,16 @@ function distributeGlobalSkills(home = HOME) {
3792
4110
  success: false
3793
4111
  });
3794
4112
  }
3795
- const codexSkillsDir = join7(home, ".agents", "skills", "zam");
4113
+ const codexSkillsDir = join8(home, ".agents", "skills", "zam");
3796
4114
  try {
3797
4115
  if (!codexSourceSkill) {
3798
4116
  throw new Error("Codex skill source not found");
3799
4117
  }
3800
- mkdirSync5(codexSkillsDir, { recursive: true });
3801
- copyFileSync(codexSourceSkill, join7(codexSkillsDir, "SKILL.md"));
4118
+ mkdirSync6(codexSkillsDir, { recursive: true });
4119
+ copyFileSync(codexSourceSkill, join8(codexSkillsDir, "SKILL.md"));
3802
4120
  results.push({
3803
4121
  name: "Codex Global",
3804
- path: join7(codexSkillsDir, "SKILL.md"),
4122
+ path: join8(codexSkillsDir, "SKILL.md"),
3805
4123
  success: true
3806
4124
  });
3807
4125
  } catch (_err) {
@@ -3811,13 +4129,13 @@ function distributeGlobalSkills(home = HOME) {
3811
4129
  success: false
3812
4130
  });
3813
4131
  }
3814
- const gooseSkillsDir = join7(home, ".goose", "skills", "zam");
4132
+ const gooseSkillsDir = join8(home, ".goose", "skills", "zam");
3815
4133
  try {
3816
- mkdirSync5(gooseSkillsDir, { recursive: true });
3817
- copyFileSync(sourceSkill, join7(gooseSkillsDir, "SKILL.md"));
4134
+ mkdirSync6(gooseSkillsDir, { recursive: true });
4135
+ copyFileSync(sourceSkill, join8(gooseSkillsDir, "SKILL.md"));
3818
4136
  results.push({
3819
4137
  name: "Goose Global",
3820
- path: join7(gooseSkillsDir, "SKILL.md"),
4138
+ path: join8(gooseSkillsDir, "SKILL.md"),
3821
4139
  success: true
3822
4140
  });
3823
4141
  } catch (_err) {
@@ -3866,7 +4184,7 @@ function installHook(file, hook, oldHook) {
3866
4184
  return { success: true, alreadyHooked: true };
3867
4185
  }
3868
4186
  if (content.includes(oldHook.trim())) {
3869
- writeFileSync3(file, content.replace(oldHook.trim(), hook.trim()), "utf8");
4187
+ writeFileSync4(file, content.replace(oldHook.trim(), hook.trim()), "utf8");
3870
4188
  } else {
3871
4189
  appendFileSync3(file, hook);
3872
4190
  }
@@ -3877,24 +4195,24 @@ function installHook(file, hook, oldHook) {
3877
4195
  }
3878
4196
  function injectShellHooks(home = HOME) {
3879
4197
  const results = [];
3880
- const zshrc = join7(home, ".zshrc");
4198
+ const zshrc = join8(home, ".zshrc");
3881
4199
  if (existsSync7(zshrc)) {
3882
4200
  const status = installHook(zshrc, posixHook("zsh"), POSIX_OLD_HOOK);
3883
4201
  results.push({ shell: "zsh", file: zshrc, ...status });
3884
4202
  }
3885
- const bashrc = join7(home, ".bashrc");
4203
+ const bashrc = join8(home, ".bashrc");
3886
4204
  if (existsSync7(bashrc)) {
3887
4205
  const status = installHook(bashrc, posixHook("bash"), POSIX_OLD_HOOK);
3888
4206
  results.push({ shell: "bash", file: bashrc, ...status });
3889
4207
  }
3890
4208
  const pwshDirs = [
3891
- join7(home, "Documents", "PowerShell"),
3892
- join7(home, "Documents", "WindowsPowerShell")
4209
+ join8(home, "Documents", "PowerShell"),
4210
+ join8(home, "Documents", "WindowsPowerShell")
3893
4211
  ];
3894
4212
  for (const dir of pwshDirs) {
3895
- const profileFile = join7(dir, "Microsoft.PowerShell_profile.ps1");
4213
+ const profileFile = join8(dir, "Microsoft.PowerShell_profile.ps1");
3896
4214
  try {
3897
- mkdirSync5(dir, { recursive: true });
4215
+ mkdirSync6(dir, { recursive: true });
3898
4216
  const status = installHook(
3899
4217
  profileFile,
3900
4218
  POWERSHELL_HOOK,
@@ -4126,10 +4444,10 @@ function t(locale, key, params = {}) {
4126
4444
  }
4127
4445
 
4128
4446
  // src/kernel/system/install-config.ts
4129
- import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
4447
+ import { existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
4130
4448
  import { homedir as homedir6 } from "os";
4131
- import { dirname as dirname4, join as join8 } from "path";
4132
- var DEFAULT_CONFIG_PATH = join8(homedir6(), ".zam", "config.json");
4449
+ import { dirname as dirname4, join as join9 } from "path";
4450
+ var DEFAULT_CONFIG_PATH = join9(homedir6(), ".zam", "config.json");
4133
4451
  function loadInstallConfig(path = DEFAULT_CONFIG_PATH) {
4134
4452
  if (!existsSync8(path)) return {};
4135
4453
  try {
@@ -4140,8 +4458,8 @@ function loadInstallConfig(path = DEFAULT_CONFIG_PATH) {
4140
4458
  }
4141
4459
  function saveInstallConfig(config, path = DEFAULT_CONFIG_PATH) {
4142
4460
  const dir = dirname4(path);
4143
- if (!existsSync8(dir)) mkdirSync6(dir, { recursive: true });
4144
- writeFileSync4(path, `${JSON.stringify(config, null, 2)}
4461
+ if (!existsSync8(dir)) mkdirSync7(dir, { recursive: true });
4462
+ writeFileSync5(path, `${JSON.stringify(config, null, 2)}
4145
4463
  `, "utf-8");
4146
4464
  }
4147
4465
  function getInstallMode(path = DEFAULT_CONFIG_PATH) {
@@ -4179,7 +4497,7 @@ function detectSyncProvider(dir) {
4179
4497
  import { execFileSync, execSync } from "child_process";
4180
4498
  import { existsSync as existsSync9 } from "fs";
4181
4499
  import { homedir as homedir7 } from "os";
4182
- import { join as join9 } from "path";
4500
+ import { join as join10 } from "path";
4183
4501
  function hasCommand(cmd) {
4184
4502
  try {
4185
4503
  const checkCmd = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
@@ -4224,7 +4542,7 @@ function installOllama() {
4224
4542
  const isMac = process.platform === "darwin";
4225
4543
  const isWin = process.platform === "win32";
4226
4544
  const hasOllama = hasCommand("ollama") || isMac && existsSync9("/Applications/Ollama.app") || isWin && existsSync9(
4227
- join9(homedir7(), "AppData", "Local", "Programs", "Ollama", "ollama.exe")
4545
+ join10(homedir7(), "AppData", "Local", "Programs", "Ollama", "ollama.exe")
4228
4546
  );
4229
4547
  if (hasOllama) {
4230
4548
  return { success: true, message: "Ollama is already installed." };
@@ -4284,7 +4602,7 @@ function installOllama() {
4284
4602
  function resolveOllamaCommand() {
4285
4603
  if (hasCommand("ollama")) return "ollama";
4286
4604
  const candidates = process.platform === "win32" ? [
4287
- join9(
4605
+ join10(
4288
4606
  homedir7(),
4289
4607
  "AppData",
4290
4608
  "Local",
@@ -4617,8 +4935,13 @@ function decideUpdate(input) {
4617
4935
  }
4618
4936
  }
4619
4937
  export {
4938
+ BUILT_IN_SENSITIVE_MATCHERS,
4939
+ DEFAULT_OBSERVER_POLICY,
4620
4940
  DEFAULT_REVIEW_CONTEXT_MAX_CHARS,
4621
4941
  HOMEBREW_CASK,
4942
+ OBSERVER_POLICY_UNSET_HINT,
4943
+ OBSERVER_POLICY_VERSION,
4944
+ SIDECAR_POLICY_FILE,
4622
4945
  SNAPSHOT_VERSION,
4623
4946
  UI_OBSERVATION_PROTOCOL_VERSION,
4624
4947
  WINGET_PACKAGE_ID,
@@ -4636,6 +4959,8 @@ export {
4636
4959
  createFSRS,
4637
4960
  createGoal,
4638
4961
  createToken,
4962
+ decidePostCapture,
4963
+ decidePreCapture,
4639
4964
  decideUpdate,
4640
4965
  deleteCardForUser,
4641
4966
  deleteSetting,
@@ -4672,6 +4997,7 @@ export {
4672
4997
  getCard,
4673
4998
  getCardById,
4674
4999
  getCardDeletionImpact,
5000
+ getDatabaseTargetInfo,
4675
5001
  getDefaultDbPath,
4676
5002
  getDependents,
4677
5003
  getDomainCompetence,
@@ -4707,6 +5033,7 @@ export {
4707
5033
  installOllama,
4708
5034
  installOpenCode,
4709
5035
  interleave,
5036
+ isObserverPolicyConfigured,
4710
5037
  isUiObservationReport,
4711
5038
  listAgentSkills,
4712
5039
  listGoals,
@@ -4716,6 +5043,8 @@ export {
4716
5043
  loadInstallConfig,
4717
5044
  logReview,
4718
5045
  logStep,
5046
+ matchBuiltInSensitive,
5047
+ matchDenylist,
4719
5048
  matchesFilePath,
4720
5049
  monitorLogExists,
4721
5050
  normalizeLocale,
@@ -4726,6 +5055,8 @@ export {
4726
5055
  pairCommands,
4727
5056
  parseGoalFile,
4728
5057
  parseMonitorLog,
5058
+ parseObserverList,
5059
+ parseObserverPolicy,
4729
5060
  parseSnapshot,
4730
5061
  parseUiObservationLog,
4731
5062
  planOpenCodeInstall,
@@ -4735,6 +5066,7 @@ export {
4735
5066
  readUiObservationLog,
4736
5067
  resolveAllBeliefPaths,
4737
5068
  resolveAllGoalPaths,
5069
+ resolveObserverPolicy,
4738
5070
  resolveReference,
4739
5071
  resolveRepoPath,
4740
5072
  resolveReviewContext,
@@ -4747,7 +5079,9 @@ export {
4747
5079
  setSetting,
4748
5080
  setTursoCredentials,
4749
5081
  startSession,
5082
+ syncObserverSidecarPolicy,
4750
5083
  t,
5084
+ toSidecarPrivacyPolicy,
4751
5085
  uiObservationLogExists,
4752
5086
  uiObservationTimeSpan,
4753
5087
  unblockReady,