zam-core 0.3.13 → 0.4.1

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;
@@ -2130,6 +2170,415 @@ function getMonitorLogStats(sessionId) {
2130
2170
  return { exists: true, sizeBytes: stat.size, lineCount };
2131
2171
  }
2132
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
+
2133
2582
  // src/kernel/observation/session-synthesis.ts
2134
2583
  import { ulid as ulid7 } from "ulid";
2135
2584
 
@@ -2400,146 +2849,6 @@ async function unblockReady(db, userId) {
2400
2849
  return { unblocked };
2401
2850
  }
2402
2851
 
2403
- // src/kernel/observation/ui-observer-io.ts
2404
- import { appendFileSync as appendFileSync2, existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5 } from "fs";
2405
- import { homedir as homedir4 } from "os";
2406
- import { join as join5 } from "path";
2407
-
2408
- // src/kernel/observation/ui-observer.ts
2409
- var UI_OBSERVATION_PROTOCOL_VERSION = 1;
2410
- var OBSERVATION_KINDS = /* @__PURE__ */ new Set([
2411
- "progress",
2412
- "step-completed",
2413
- "error",
2414
- "help-seeking",
2415
- "uncertain",
2416
- "privacy-pause",
2417
- "heartbeat"
2418
- ]);
2419
- var ACTION_TYPES = /* @__PURE__ */ new Set([
2420
- "click",
2421
- "shortcut",
2422
- "typing",
2423
- "scroll",
2424
- "window-change"
2425
- ]);
2426
- var EVIDENCE_TYPES = /* @__PURE__ */ new Set([
2427
- "uia",
2428
- "keyframe",
2429
- "clip",
2430
- "window"
2431
- ]);
2432
- function isUiObservationReport(value) {
2433
- if (!isRecord(value)) return false;
2434
- if (value.version !== UI_OBSERVATION_PROTOCOL_VERSION) return false;
2435
- if (!isNonEmptyString(value.sessionId)) return false;
2436
- if (!isNonNegativeInteger(value.sequence)) return false;
2437
- if (!isNonEmptyString(value.observedFrom)) return false;
2438
- if (!isNonEmptyString(value.observedTo)) return false;
2439
- if (typeof value.kind !== "string" || !OBSERVATION_KINDS.has(value.kind)) {
2440
- return false;
2441
- }
2442
- if (!isApplication(value.application)) return false;
2443
- if (!isNonEmptyString(value.summary)) return false;
2444
- if (!Array.isArray(value.actions) || !value.actions.every(isObservedAction)) {
2445
- return false;
2446
- }
2447
- if (!Array.isArray(value.evidence) || !value.evidence.every(isEvidenceRef)) {
2448
- return false;
2449
- }
2450
- if (!Array.isArray(value.candidateTokens) || !value.candidateTokens.every(isCandidateToken)) {
2451
- return false;
2452
- }
2453
- return isConfidence(value.confidence);
2454
- }
2455
- function parseUiObservationLog(jsonl) {
2456
- const reports = [];
2457
- for (const line of jsonl.split("\n")) {
2458
- const trimmed = line.trim();
2459
- if (!trimmed) continue;
2460
- try {
2461
- const value = JSON.parse(trimmed);
2462
- if (isUiObservationReport(value)) {
2463
- reports.push(value);
2464
- }
2465
- } catch {
2466
- }
2467
- }
2468
- return reports.sort((left, right) => left.sequence - right.sequence);
2469
- }
2470
- function isApplication(value) {
2471
- if (!isRecord(value) || !isNonEmptyString(value.processName)) return false;
2472
- if (value.processId !== void 0 && !isNonNegativeInteger(value.processId)) {
2473
- return false;
2474
- }
2475
- return value.windowTitle === void 0 || typeof value.windowTitle === "string";
2476
- }
2477
- function isObservedAction(value) {
2478
- if (!isRecord(value) || typeof value.type !== "string") return false;
2479
- if (!ACTION_TYPES.has(value.type)) return false;
2480
- if (value.target !== void 0 && typeof value.target !== "string") {
2481
- return false;
2482
- }
2483
- return value.result === void 0 || typeof value.result === "string";
2484
- }
2485
- function isEvidenceRef(value) {
2486
- if (!isRecord(value) || typeof value.type !== "string") return false;
2487
- return EVIDENCE_TYPES.has(value.type) && isNonEmptyString(value.ref) && typeof value.redacted === "boolean";
2488
- }
2489
- function isCandidateToken(value) {
2490
- return isRecord(value) && isNonEmptyString(value.slug) && isConfidence(value.confidence) && isNonEmptyString(value.rationale);
2491
- }
2492
- function isRecord(value) {
2493
- return typeof value === "object" && value !== null && !Array.isArray(value);
2494
- }
2495
- function isNonEmptyString(value) {
2496
- return typeof value === "string" && value.trim().length > 0;
2497
- }
2498
- function isNonNegativeInteger(value) {
2499
- return Number.isInteger(value) && Number(value) >= 0;
2500
- }
2501
- function isConfidence(value) {
2502
- return typeof value === "number" && value >= 0 && value <= 1;
2503
- }
2504
-
2505
- // src/kernel/observation/ui-observer-io.ts
2506
- var DEFAULT_UI_OBSERVER_DIR = join5(homedir4(), ".zam", "observer");
2507
- function getUiObserverDir() {
2508
- return process.env.ZAM_OBSERVER_DIR || DEFAULT_UI_OBSERVER_DIR;
2509
- }
2510
- function getUiObservationPath(sessionId) {
2511
- if (!/^[A-Za-z0-9_-]+$/.test(sessionId)) {
2512
- throw new Error(`Invalid observer session ID: ${sessionId}`);
2513
- }
2514
- return join5(getUiObserverDir(), `${sessionId}.reports.jsonl`);
2515
- }
2516
- function ensureUiObserverDir() {
2517
- const dir = getUiObserverDir();
2518
- if (!existsSync5(dir)) {
2519
- mkdirSync4(dir, { recursive: true, mode: 448 });
2520
- }
2521
- }
2522
- function uiObservationLogExists(sessionId) {
2523
- return existsSync5(getUiObservationPath(sessionId));
2524
- }
2525
- function readUiObservationLog(sessionId) {
2526
- const path = getUiObservationPath(sessionId);
2527
- if (!existsSync5(path)) return [];
2528
- return parseUiObservationLog(readFileSync5(path, "utf8"));
2529
- }
2530
- function appendUiObservationReport(report) {
2531
- if (!isUiObservationReport(report)) {
2532
- throw new Error("Invalid UI observation report");
2533
- }
2534
- ensureUiObserverDir();
2535
- appendFileSync2(
2536
- getUiObservationPath(report.sessionId),
2537
- `${JSON.stringify(report)}
2538
- `,
2539
- { encoding: "utf8", mode: 384 }
2540
- );
2541
- }
2542
-
2543
2852
  // src/kernel/observation/ui-observer-synthesis.ts
2544
2853
  var MIN_UI_CONFIDENCE = 0.6;
2545
2854
  function kindToRating(kind) {
@@ -3344,7 +3653,7 @@ function generatePrompt(input) {
3344
3653
 
3345
3654
  // src/kernel/recall/reference-resolver.ts
3346
3655
  import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
3347
- import { dirname as dirname3, join as join6, resolve } from "path";
3656
+ import { dirname as dirname3, join as join7, resolve } from "path";
3348
3657
  var DEFAULT_REVIEW_CONTEXT_MAX_CHARS = 6e3;
3349
3658
  function htmlToText(html) {
3350
3659
  let content = html;
@@ -3405,8 +3714,8 @@ async function resolveReference(sourceLink) {
3405
3714
  const filePath = anchorIndex2 !== -1 ? fullPathWithAnchor.slice(0, anchorIndex2) : fullPathWithAnchor;
3406
3715
  const anchor2 = anchorIndex2 !== -1 ? fullPathWithAnchor.slice(anchorIndex2) : "";
3407
3716
  const parentDir = dirname3(process.cwd());
3408
- const localRepoPath = join6(parentDir, repo);
3409
- const localFilePath = join6(localRepoPath, filePath);
3717
+ const localRepoPath = join7(parentDir, repo);
3718
+ const localFilePath = join7(localRepoPath, filePath);
3410
3719
  if (existsSync6(localFilePath)) {
3411
3720
  try {
3412
3721
  let fileContent = readFileSync6(localFilePath, "utf-8");
@@ -3720,12 +4029,12 @@ import {
3720
4029
  appendFileSync as appendFileSync3,
3721
4030
  copyFileSync,
3722
4031
  existsSync as existsSync7,
3723
- mkdirSync as mkdirSync5,
4032
+ mkdirSync as mkdirSync6,
3724
4033
  readFileSync as readFileSync7,
3725
- writeFileSync as writeFileSync3
4034
+ writeFileSync as writeFileSync4
3726
4035
  } from "fs";
3727
4036
  import { homedir as homedir5 } from "os";
3728
- import { join as join7 } from "path";
4037
+ import { join as join8 } from "path";
3729
4038
  import { fileURLToPath } from "url";
3730
4039
  var HOME = homedir5();
3731
4040
  function getPackageSkillPath(agent = "default") {
@@ -3733,15 +4042,15 @@ function getPackageSkillPath(agent = "default") {
3733
4042
  fileURLToPath(new URL("../../..", import.meta.url)),
3734
4043
  fileURLToPath(new URL("../..", import.meta.url)),
3735
4044
  fileURLToPath(new URL("..", import.meta.url))
3736
- ].find((candidate) => existsSync7(join7(candidate, "package.json"))) ?? "";
4045
+ ].find((candidate) => existsSync7(join8(candidate, "package.json"))) ?? "";
3737
4046
  if (!packageRoot) return "";
3738
4047
  if (agent === "codex") {
3739
- const codexPath = join7(packageRoot, ".agents", "skills", "zam", "SKILL.md");
4048
+ const codexPath = join8(packageRoot, ".agents", "skills", "zam", "SKILL.md");
3740
4049
  if (existsSync7(codexPath)) return codexPath;
3741
4050
  return "";
3742
4051
  }
3743
4052
  if (agent === "claude") {
3744
- const claudePath = join7(
4053
+ const claudePath = join8(
3745
4054
  packageRoot,
3746
4055
  ".claude",
3747
4056
  "skills",
@@ -3751,9 +4060,9 @@ function getPackageSkillPath(agent = "default") {
3751
4060
  if (existsSync7(claudePath)) return claudePath;
3752
4061
  return "";
3753
4062
  }
3754
- let path = join7(packageRoot, ".agent", "skills", "zam", "SKILL.md");
4063
+ let path = join8(packageRoot, ".agent", "skills", "zam", "SKILL.md");
3755
4064
  if (existsSync7(path)) return path;
3756
- path = join7(packageRoot, ".claude", "skills", "zam", "SKILL.md");
4065
+ path = join8(packageRoot, ".claude", "skills", "zam", "SKILL.md");
3757
4066
  if (existsSync7(path)) return path;
3758
4067
  return "";
3759
4068
  }
@@ -3766,16 +4075,16 @@ function distributeGlobalSkills(home = HOME) {
3766
4075
  console.warn("Could not find ZAM source SKILL.md in the package folder.");
3767
4076
  return results;
3768
4077
  }
3769
- const claudeSkillsDir = join7(home, ".claude", "skills", "zam");
4078
+ const claudeSkillsDir = join8(home, ".claude", "skills", "zam");
3770
4079
  try {
3771
4080
  if (!claudeSourceSkill) {
3772
4081
  throw new Error("Claude skill source not found");
3773
4082
  }
3774
- mkdirSync5(claudeSkillsDir, { recursive: true });
3775
- copyFileSync(claudeSourceSkill, join7(claudeSkillsDir, "SKILL.md"));
4083
+ mkdirSync6(claudeSkillsDir, { recursive: true });
4084
+ copyFileSync(claudeSourceSkill, join8(claudeSkillsDir, "SKILL.md"));
3776
4085
  results.push({
3777
4086
  name: "Claude Code Global",
3778
- path: join7(claudeSkillsDir, "SKILL.md"),
4087
+ path: join8(claudeSkillsDir, "SKILL.md"),
3779
4088
  success: true
3780
4089
  });
3781
4090
  } catch (_err) {
@@ -3785,13 +4094,13 @@ function distributeGlobalSkills(home = HOME) {
3785
4094
  success: false
3786
4095
  });
3787
4096
  }
3788
- const geminiSkillsDir = join7(home, ".gemini", "skills", "zam");
4097
+ const geminiSkillsDir = join8(home, ".gemini", "skills", "zam");
3789
4098
  try {
3790
- mkdirSync5(geminiSkillsDir, { recursive: true });
3791
- copyFileSync(sourceSkill, join7(geminiSkillsDir, "SKILL.md"));
4099
+ mkdirSync6(geminiSkillsDir, { recursive: true });
4100
+ copyFileSync(sourceSkill, join8(geminiSkillsDir, "SKILL.md"));
3792
4101
  results.push({
3793
4102
  name: "Gemini CLI Global",
3794
- path: join7(geminiSkillsDir, "SKILL.md"),
4103
+ path: join8(geminiSkillsDir, "SKILL.md"),
3795
4104
  success: true
3796
4105
  });
3797
4106
  } catch (_err) {
@@ -3801,16 +4110,16 @@ function distributeGlobalSkills(home = HOME) {
3801
4110
  success: false
3802
4111
  });
3803
4112
  }
3804
- const codexSkillsDir = join7(home, ".agents", "skills", "zam");
4113
+ const codexSkillsDir = join8(home, ".agents", "skills", "zam");
3805
4114
  try {
3806
4115
  if (!codexSourceSkill) {
3807
4116
  throw new Error("Codex skill source not found");
3808
4117
  }
3809
- mkdirSync5(codexSkillsDir, { recursive: true });
3810
- copyFileSync(codexSourceSkill, join7(codexSkillsDir, "SKILL.md"));
4118
+ mkdirSync6(codexSkillsDir, { recursive: true });
4119
+ copyFileSync(codexSourceSkill, join8(codexSkillsDir, "SKILL.md"));
3811
4120
  results.push({
3812
4121
  name: "Codex Global",
3813
- path: join7(codexSkillsDir, "SKILL.md"),
4122
+ path: join8(codexSkillsDir, "SKILL.md"),
3814
4123
  success: true
3815
4124
  });
3816
4125
  } catch (_err) {
@@ -3820,13 +4129,13 @@ function distributeGlobalSkills(home = HOME) {
3820
4129
  success: false
3821
4130
  });
3822
4131
  }
3823
- const gooseSkillsDir = join7(home, ".goose", "skills", "zam");
4132
+ const gooseSkillsDir = join8(home, ".goose", "skills", "zam");
3824
4133
  try {
3825
- mkdirSync5(gooseSkillsDir, { recursive: true });
3826
- copyFileSync(sourceSkill, join7(gooseSkillsDir, "SKILL.md"));
4134
+ mkdirSync6(gooseSkillsDir, { recursive: true });
4135
+ copyFileSync(sourceSkill, join8(gooseSkillsDir, "SKILL.md"));
3827
4136
  results.push({
3828
4137
  name: "Goose Global",
3829
- path: join7(gooseSkillsDir, "SKILL.md"),
4138
+ path: join8(gooseSkillsDir, "SKILL.md"),
3830
4139
  success: true
3831
4140
  });
3832
4141
  } catch (_err) {
@@ -3875,7 +4184,7 @@ function installHook(file, hook, oldHook) {
3875
4184
  return { success: true, alreadyHooked: true };
3876
4185
  }
3877
4186
  if (content.includes(oldHook.trim())) {
3878
- writeFileSync3(file, content.replace(oldHook.trim(), hook.trim()), "utf8");
4187
+ writeFileSync4(file, content.replace(oldHook.trim(), hook.trim()), "utf8");
3879
4188
  } else {
3880
4189
  appendFileSync3(file, hook);
3881
4190
  }
@@ -3886,24 +4195,24 @@ function installHook(file, hook, oldHook) {
3886
4195
  }
3887
4196
  function injectShellHooks(home = HOME) {
3888
4197
  const results = [];
3889
- const zshrc = join7(home, ".zshrc");
4198
+ const zshrc = join8(home, ".zshrc");
3890
4199
  if (existsSync7(zshrc)) {
3891
4200
  const status = installHook(zshrc, posixHook("zsh"), POSIX_OLD_HOOK);
3892
4201
  results.push({ shell: "zsh", file: zshrc, ...status });
3893
4202
  }
3894
- const bashrc = join7(home, ".bashrc");
4203
+ const bashrc = join8(home, ".bashrc");
3895
4204
  if (existsSync7(bashrc)) {
3896
4205
  const status = installHook(bashrc, posixHook("bash"), POSIX_OLD_HOOK);
3897
4206
  results.push({ shell: "bash", file: bashrc, ...status });
3898
4207
  }
3899
4208
  const pwshDirs = [
3900
- join7(home, "Documents", "PowerShell"),
3901
- join7(home, "Documents", "WindowsPowerShell")
4209
+ join8(home, "Documents", "PowerShell"),
4210
+ join8(home, "Documents", "WindowsPowerShell")
3902
4211
  ];
3903
4212
  for (const dir of pwshDirs) {
3904
- const profileFile = join7(dir, "Microsoft.PowerShell_profile.ps1");
4213
+ const profileFile = join8(dir, "Microsoft.PowerShell_profile.ps1");
3905
4214
  try {
3906
- mkdirSync5(dir, { recursive: true });
4215
+ mkdirSync6(dir, { recursive: true });
3907
4216
  const status = installHook(
3908
4217
  profileFile,
3909
4218
  POWERSHELL_HOOK,
@@ -4135,10 +4444,10 @@ function t(locale, key, params = {}) {
4135
4444
  }
4136
4445
 
4137
4446
  // src/kernel/system/install-config.ts
4138
- 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";
4139
4448
  import { homedir as homedir6 } from "os";
4140
- import { dirname as dirname4, join as join8 } from "path";
4141
- 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");
4142
4451
  function loadInstallConfig(path = DEFAULT_CONFIG_PATH) {
4143
4452
  if (!existsSync8(path)) return {};
4144
4453
  try {
@@ -4149,8 +4458,8 @@ function loadInstallConfig(path = DEFAULT_CONFIG_PATH) {
4149
4458
  }
4150
4459
  function saveInstallConfig(config, path = DEFAULT_CONFIG_PATH) {
4151
4460
  const dir = dirname4(path);
4152
- if (!existsSync8(dir)) mkdirSync6(dir, { recursive: true });
4153
- writeFileSync4(path, `${JSON.stringify(config, null, 2)}
4461
+ if (!existsSync8(dir)) mkdirSync7(dir, { recursive: true });
4462
+ writeFileSync5(path, `${JSON.stringify(config, null, 2)}
4154
4463
  `, "utf-8");
4155
4464
  }
4156
4465
  function getInstallMode(path = DEFAULT_CONFIG_PATH) {
@@ -4188,7 +4497,7 @@ function detectSyncProvider(dir) {
4188
4497
  import { execFileSync, execSync } from "child_process";
4189
4498
  import { existsSync as existsSync9 } from "fs";
4190
4499
  import { homedir as homedir7 } from "os";
4191
- import { join as join9 } from "path";
4500
+ import { join as join10 } from "path";
4192
4501
  function hasCommand(cmd) {
4193
4502
  try {
4194
4503
  const checkCmd = process.platform === "win32" ? `where ${cmd}` : `which ${cmd}`;
@@ -4233,7 +4542,7 @@ function installOllama() {
4233
4542
  const isMac = process.platform === "darwin";
4234
4543
  const isWin = process.platform === "win32";
4235
4544
  const hasOllama = hasCommand("ollama") || isMac && existsSync9("/Applications/Ollama.app") || isWin && existsSync9(
4236
- join9(homedir7(), "AppData", "Local", "Programs", "Ollama", "ollama.exe")
4545
+ join10(homedir7(), "AppData", "Local", "Programs", "Ollama", "ollama.exe")
4237
4546
  );
4238
4547
  if (hasOllama) {
4239
4548
  return { success: true, message: "Ollama is already installed." };
@@ -4293,7 +4602,7 @@ function installOllama() {
4293
4602
  function resolveOllamaCommand() {
4294
4603
  if (hasCommand("ollama")) return "ollama";
4295
4604
  const candidates = process.platform === "win32" ? [
4296
- join9(
4605
+ join10(
4297
4606
  homedir7(),
4298
4607
  "AppData",
4299
4608
  "Local",
@@ -4625,9 +4934,45 @@ function decideUpdate(input) {
4625
4934
  };
4626
4935
  }
4627
4936
  }
4937
+ function planUpdate(decision) {
4938
+ if (!decision.updateAvailable) return [];
4939
+ switch (decision.channel) {
4940
+ case "developer":
4941
+ return [
4942
+ { kind: "git-pull", label: "Pull the latest source" },
4943
+ { kind: "npm-install", label: "Install dependencies" },
4944
+ { kind: "npm-build", label: "Rebuild the CLI" },
4945
+ {
4946
+ kind: "distribute-skills",
4947
+ label: "Refresh skill files (zam setup --force)"
4948
+ }
4949
+ ];
4950
+ case "winget":
4951
+ case "homebrew":
4952
+ return [
4953
+ {
4954
+ kind: "run-command",
4955
+ label: `Update via ${decision.channel}`,
4956
+ command: decision.command
4957
+ }
4958
+ ];
4959
+ case "direct":
4960
+ return [
4961
+ {
4962
+ kind: "self-update",
4963
+ label: "Apply the signed update via the desktop app"
4964
+ }
4965
+ ];
4966
+ }
4967
+ }
4628
4968
  export {
4969
+ BUILT_IN_SENSITIVE_MATCHERS,
4970
+ DEFAULT_OBSERVER_POLICY,
4629
4971
  DEFAULT_REVIEW_CONTEXT_MAX_CHARS,
4630
4972
  HOMEBREW_CASK,
4973
+ OBSERVER_POLICY_UNSET_HINT,
4974
+ OBSERVER_POLICY_VERSION,
4975
+ SIDECAR_POLICY_FILE,
4631
4976
  SNAPSHOT_VERSION,
4632
4977
  UI_OBSERVATION_PROTOCOL_VERSION,
4633
4978
  WINGET_PACKAGE_ID,
@@ -4645,6 +4990,8 @@ export {
4645
4990
  createFSRS,
4646
4991
  createGoal,
4647
4992
  createToken,
4993
+ decidePostCapture,
4994
+ decidePreCapture,
4648
4995
  decideUpdate,
4649
4996
  deleteCardForUser,
4650
4997
  deleteSetting,
@@ -4681,6 +5028,7 @@ export {
4681
5028
  getCard,
4682
5029
  getCardById,
4683
5030
  getCardDeletionImpact,
5031
+ getDatabaseTargetInfo,
4684
5032
  getDefaultDbPath,
4685
5033
  getDependents,
4686
5034
  getDomainCompetence,
@@ -4716,6 +5064,7 @@ export {
4716
5064
  installOllama,
4717
5065
  installOpenCode,
4718
5066
  interleave,
5067
+ isObserverPolicyConfigured,
4719
5068
  isUiObservationReport,
4720
5069
  listAgentSkills,
4721
5070
  listGoals,
@@ -4725,6 +5074,8 @@ export {
4725
5074
  loadInstallConfig,
4726
5075
  logReview,
4727
5076
  logStep,
5077
+ matchBuiltInSensitive,
5078
+ matchDenylist,
4728
5079
  matchesFilePath,
4729
5080
  monitorLogExists,
4730
5081
  normalizeLocale,
@@ -4735,15 +5086,19 @@ export {
4735
5086
  pairCommands,
4736
5087
  parseGoalFile,
4737
5088
  parseMonitorLog,
5089
+ parseObserverList,
5090
+ parseObserverPolicy,
4738
5091
  parseSnapshot,
4739
5092
  parseUiObservationLog,
4740
5093
  planOpenCodeInstall,
5094
+ planUpdate,
4741
5095
  prepareLocalModel,
4742
5096
  prepareSessionSynthesis,
4743
5097
  readMonitorLog,
4744
5098
  readUiObservationLog,
4745
5099
  resolveAllBeliefPaths,
4746
5100
  resolveAllGoalPaths,
5101
+ resolveObserverPolicy,
4747
5102
  resolveReference,
4748
5103
  resolveRepoPath,
4749
5104
  resolveReviewContext,
@@ -4756,7 +5111,9 @@ export {
4756
5111
  setSetting,
4757
5112
  setTursoCredentials,
4758
5113
  startSession,
5114
+ syncObserverSidecarPolicy,
4759
5115
  t,
5116
+ toSidecarPrivacyPolicy,
4760
5117
  uiObservationLogExists,
4761
5118
  uiObservationTimeSpan,
4762
5119
  unblockReady,