@codemem/server 0.20.0-alpha.6 → 0.20.0-alpha.7

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
@@ -1509,6 +1509,7 @@ function syncRoutes(getStore) {
1509
1509
  const store = getStore();
1510
1510
  {
1511
1511
  const showDiag = queryBool(c.req.query("includeDiagnostics"));
1512
+ const includeJoinRequests = queryBool(c.req.query("includeJoinRequests"));
1512
1513
  const project = c.req.query("project") || null;
1513
1514
  const config = readCoordinatorSyncConfig();
1514
1515
  const d = drizzle(store.db, { schema });
@@ -1583,7 +1584,7 @@ function syncRoutes(getStore) {
1583
1584
  const sharingReview = store.sharingReviewSummary(project);
1584
1585
  const coordinator = await coordinatorStatusSnapshot(store, config);
1585
1586
  let joinRequests = [];
1586
- try {
1587
+ if (includeJoinRequests && config.syncCoordinatorAdminSecret) try {
1587
1588
  joinRequests = await listCoordinatorJoinRequests(config);
1588
1589
  } catch {
1589
1590
  joinRequests = [];
@@ -1602,16 +1603,17 @@ function syncRoutes(getStore) {
1602
1603
  statusPayload.daemon_state = daemonStateValue;
1603
1604
  statusBlock.daemon_state = daemonStateValue;
1604
1605
  }
1605
- return c.json({
1606
+ const responsePayload = {
1606
1607
  ...statusPayload,
1607
1608
  status: statusBlock,
1608
1609
  peers: peersItems,
1609
1610
  attempts: attemptsItems.slice(0, 5),
1610
1611
  legacy_devices: legacyDevices,
1611
1612
  sharing_review: sharingReview,
1612
- coordinator,
1613
- join_requests: joinRequests
1614
- });
1613
+ coordinator
1614
+ };
1615
+ if (includeJoinRequests) responsePayload.join_requests = joinRequests;
1616
+ return c.json(responsePayload);
1615
1617
  }
1616
1618
  });
1617
1619
  app.get("/api/sync/peers", (c) => {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/middleware.ts","../src/routes/config.ts","../src/helpers.ts","../src/routes/memory.ts","../src/routes/observer-status.ts","../src/routes/raw-events.ts","../src/routes/stats.ts","../src/routes/sync.ts","../src/index.ts"],"sourcesContent":["/**\n * CORS and cross-origin protection middleware.\n *\n * Ports Python's reject_cross_origin() logic from codemem/viewer_http.py.\n * GETs are allowed from any origin (viewer is local-only).\n * Mutations (POST/DELETE/PATCH/PUT) require an Origin header matching a\n * loopback address, or are rejected with 403.\n */\n\nimport type { Context, Next } from \"hono\";\nimport { createMiddleware } from \"hono/factory\";\n\nconst LOOPBACK_HOSTS = new Set([\"127.0.0.1\", \"localhost\", \"::1\"]);\n\n/**\n * Check whether an Origin header value is a valid loopback URL.\n * Mirrors Python's _is_allowed_loopback_origin_url().\n */\nfunction isLoopbackOrigin(origin: string): boolean {\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(origin);\n\t} catch {\n\t\treturn false;\n\t}\n\tif (url.protocol !== \"http:\" && url.protocol !== \"https:\") return false;\n\tif (url.username || url.password) return false;\n\treturn LOOPBACK_HOSTS.has(url.hostname);\n}\n\n/** HTTP methods that mutate state and require origin validation. */\nconst UNSAFE_METHODS = new Set([\"POST\", \"DELETE\", \"PATCH\", \"PUT\"]);\n\n/**\n * Check whether a missing-Origin request looks like a cross-site browser\n * request. Matches Python's `_is_unsafe_missing_origin()`:\n *\n * - Sec-Fetch-Site present and NOT same-origin/same-site/none → unsafe\n * - Referer present and NOT loopback → unsafe\n * - Otherwise → safe (CLI / programmatic caller, no browser context)\n */\nfunction isUnsafeMissingOrigin(c: Context): boolean {\n\tconst secFetchSite = (c.req.header(\"Sec-Fetch-Site\") ?? \"\").trim().toLowerCase();\n\tif (secFetchSite && ![\"same-origin\", \"same-site\", \"none\"].includes(secFetchSite)) {\n\t\treturn true;\n\t}\n\tconst referer = c.req.header(\"Referer\");\n\tif (!referer) return false;\n\treturn !isLoopbackOrigin(referer);\n}\n\n/**\n * Cross-origin protection middleware.\n *\n * Ports Python's `reject_cross_origin(missing_origin_policy=\"reject_if_unsafe\")`:\n *\n * - GET/HEAD/OPTIONS: allowed from any origin (viewer is local-only).\n * - POST/DELETE/PATCH/PUT:\n * - Origin present + loopback → allowed (browser on localhost)\n * - Origin present + non-loopback → rejected 403\n * - No Origin + no suspicious browser signals → allowed (CLI callers)\n * - No Origin + suspicious Sec-Fetch-Site/Referer → rejected 403\n *\n * For same-origin requests (no Origin header) on safe methods, no\n * Access-Control-Allow-Origin is set — the browser doesn't need it.\n * For valid loopback origins, ACAO is echoed back.\n */\nexport function originGuard() {\n\treturn createMiddleware(async (c: Context, next: Next) => {\n\t\tconst origin = c.req.header(\"Origin\");\n\t\tconst method = c.req.method;\n\n\t\tif (UNSAFE_METHODS.has(method)) {\n\t\t\tif (origin) {\n\t\t\t\t// Origin present — must be loopback\n\t\t\t\tif (!isLoopbackOrigin(origin)) {\n\t\t\t\t\treturn c.json({ error: \"forbidden\" }, 403);\n\t\t\t\t}\n\t\t\t\t// Valid loopback origin — echo it for CORS\n\t\t\t\tc.header(\"Access-Control-Allow-Origin\", origin);\n\t\t\t\tc.header(\"Access-Control-Allow-Methods\", \"GET, POST, DELETE, OPTIONS\");\n\t\t\t\tc.header(\"Access-Control-Allow-Headers\", \"Content-Type\");\n\t\t\t} else {\n\t\t\t\t// No Origin — reject only if browser signals indicate cross-site\n\t\t\t\t// (matches Python's reject_if_unsafe policy for API endpoints)\n\t\t\t\tif (isUnsafeMissingOrigin(c)) {\n\t\t\t\t\treturn c.json({ error: \"forbidden\" }, 403);\n\t\t\t\t}\n\t\t\t\t// CLI / programmatic caller — no CORS headers needed\n\t\t\t}\n\t\t} else if (origin && isLoopbackOrigin(origin)) {\n\t\t\t// Safe method with valid origin — echo for preflight\n\t\t\tc.header(\"Access-Control-Allow-Origin\", origin);\n\t\t\tc.header(\"Access-Control-Allow-Methods\", \"GET, POST, DELETE, OPTIONS\");\n\t\t\tc.header(\"Access-Control-Allow-Headers\", \"Content-Type\");\n\t\t}\n\t\t// No origin or non-loopback on safe method: no ACAO header set.\n\t\t// Browser enforces same-origin; we don't set permissive headers.\n\n\t\tawait next();\n\t});\n}\n\n/**\n * Handle OPTIONS preflight requests.\n * Returns 204 with appropriate CORS headers for loopback origins.\n */\nexport function preflightHandler() {\n\treturn createMiddleware(async (c: Context, next: Next) => {\n\t\tif (c.req.method !== \"OPTIONS\") {\n\t\t\tawait next();\n\t\t\treturn;\n\t\t}\n\t\tconst origin = c.req.header(\"Origin\");\n\t\tif (origin && isLoopbackOrigin(origin)) {\n\t\t\tc.header(\"Access-Control-Allow-Origin\", origin);\n\t\t\tc.header(\"Access-Control-Allow-Methods\", \"GET, POST, DELETE, OPTIONS\");\n\t\t\tc.header(\"Access-Control-Allow-Headers\", \"Content-Type\");\n\t\t\tc.header(\"Access-Control-Max-Age\", \"86400\");\n\t\t\treturn c.body(null, 204);\n\t\t}\n\t\treturn c.body(null, 204);\n\t});\n}\n","/**\n * Config routes — GET /api/config, POST /api/config.\n *\n * Ports the user-facing config read/write path from Python's\n * codemem/viewer_routes/config.py, scoped to the TS runtime's current needs.\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport {\n\tCODEMEM_CONFIG_ENV_OVERRIDES,\n\tgetCodememConfigPath,\n\tgetCodememEnvOverrides,\n\tlistObserverProviderOptions,\n\ttype RawEventSweeper,\n\treadCodememConfigFile,\n\tstripJsonComments,\n\tstripTrailingCommas,\n\twriteCodememConfigFile,\n} from \"@codemem/core\";\nimport { Hono } from \"hono\";\n\ntype ConfigData = Record<string, unknown>;\n\nconst RUNTIMES = new Set([\"api_http\", \"claude_sidecar\"]);\nconst AUTH_SOURCES = new Set([\"auto\", \"env\", \"file\", \"command\", \"none\"]);\nconst HOT_RELOAD_KEYS = new Set([\"raw_events_sweeper_interval_s\"]);\nconst ALLOWED_KEYS = [\n\t\"claude_command\",\n\t\"observer_base_url\",\n\t\"observer_provider\",\n\t\"observer_model\",\n\t\"observer_runtime\",\n\t\"observer_auth_source\",\n\t\"observer_auth_file\",\n\t\"observer_auth_command\",\n\t\"observer_auth_timeout_ms\",\n\t\"observer_auth_cache_ttl_s\",\n\t\"observer_headers\",\n\t\"observer_max_chars\",\n\t\"pack_observation_limit\",\n\t\"pack_session_limit\",\n\t\"sync_enabled\",\n\t\"sync_host\",\n\t\"sync_port\",\n\t\"sync_interval_s\",\n\t\"sync_mdns\",\n\t\"sync_coordinator_url\",\n\t\"sync_coordinator_group\",\n\t\"sync_coordinator_timeout_s\",\n\t\"sync_coordinator_presence_ttl_s\",\n\t\"raw_events_sweeper_interval_s\",\n] as const;\n\nconst DEFAULTS: ConfigData = {\n\tclaude_command: [\"claude\"],\n\tobserver_runtime: \"api_http\",\n\tobserver_auth_source: \"auto\",\n\tobserver_auth_command: [],\n\tobserver_auth_timeout_ms: 1500,\n\tobserver_auth_cache_ttl_s: 300,\n\tobserver_headers: {},\n\tobserver_max_chars: 12000,\n\tpack_observation_limit: 50,\n\tpack_session_limit: 10,\n\tsync_enabled: false,\n\tsync_host: \"0.0.0.0\",\n\tsync_port: 7337,\n\tsync_interval_s: 120,\n\tsync_mdns: true,\n\tsync_coordinator_timeout_s: 3,\n\tsync_coordinator_presence_ttl_s: 180,\n\traw_events_sweeper_interval_s: 30,\n};\n\nexport interface ConfigRouteOptions {\n\tgetSweeper?: () => RawEventSweeper | null;\n}\n\nfunction loadProviderOptions(): string[] {\n\treturn listObserverProviderOptions();\n}\n\nfunction getConfigPath(): string {\n\tconst envPath = process.env.CODEMEM_CONFIG;\n\tif (envPath) return envPath.replace(/^~/, homedir());\n\tconst configDir = join(homedir(), \".config\", \"codemem\");\n\tconst candidates = [join(configDir, \"config.json\"), join(configDir, \"config.jsonc\")];\n\treturn candidates.find((p) => existsSync(p)) ?? join(configDir, \"config.json\");\n}\n\nfunction readConfigFile(configPath: string): ConfigData {\n\tif (!existsSync(configPath)) return {};\n\ttry {\n\t\tlet text = readFileSync(configPath, \"utf-8\").trim();\n\t\tif (!text) return {};\n\t\ttry {\n\t\t\treturn JSON.parse(text) as ConfigData;\n\t\t} catch {\n\t\t\ttext = stripTrailingCommas(stripJsonComments(text));\n\t\t\treturn JSON.parse(text) as ConfigData;\n\t\t}\n\t} catch {\n\t\treturn {};\n\t}\n}\n\nfunction getEffectiveConfig(configData: ConfigData): ConfigData {\n\tconst effective: ConfigData = { ...DEFAULTS, ...configData };\n\tfor (const [key, envVar] of Object.entries(CODEMEM_CONFIG_ENV_OVERRIDES) as Array<\n\t\t[string, string]\n\t>) {\n\t\tconst val = process.env[envVar];\n\t\tif (val != null && val !== \"\") effective[key] = val;\n\t}\n\treturn effective;\n}\n\nfunction parsePositiveInt(value: unknown, allowZero = false): number | null {\n\tif (typeof value === \"boolean\") return null;\n\tconst parsed =\n\t\ttypeof value === \"number\"\n\t\t\t? value\n\t\t\t: typeof value === \"string\" && /^-?\\d+$/.test(value.trim())\n\t\t\t\t? Number(value.trim())\n\t\t\t\t: Number.NaN;\n\tif (!Number.isFinite(parsed) || !Number.isInteger(parsed)) return null;\n\tif (allowZero) return parsed >= 0 ? parsed : null;\n\treturn parsed > 0 ? parsed : null;\n}\n\nfunction asStringMap(value: unknown): Record<string, string> | null {\n\tif (value == null || typeof value !== \"object\" || Array.isArray(value)) return null;\n\tconst parsed: Record<string, string> = {};\n\tfor (const [key, item] of Object.entries(value)) {\n\t\tif (typeof item !== \"string\") return null;\n\t\tconst stripped = key.trim();\n\t\tif (!stripped) return null;\n\t\tparsed[stripped] = item;\n\t}\n\treturn parsed;\n}\n\nfunction asExecutableArgv(value: unknown): string[] | null {\n\tif (!Array.isArray(value)) return null;\n\tconst argv: string[] = [];\n\tfor (const item of value) {\n\t\tif (typeof item !== \"string\") return null;\n\t\tconst token = item.trim();\n\t\tif (!token) return null;\n\t\targv.push(token);\n\t}\n\treturn argv;\n}\n\nfunction validateAndApplyUpdate(\n\tconfigData: ConfigData,\n\tkey: (typeof ALLOWED_KEYS)[number],\n\tvalue: unknown,\n\tproviders: Set<string>,\n): string | null {\n\tif (value == null || value === \"\") {\n\t\tdelete configData[key];\n\t\treturn null;\n\t}\n\tif (key === \"observer_provider\") {\n\t\tif (typeof value !== \"string\") return \"observer_provider must be string\";\n\t\tconst provider = value.trim().toLowerCase();\n\t\tconst savedBaseUrl = configData.observer_base_url;\n\t\tconst hasSavedBaseUrl = typeof savedBaseUrl === \"string\" && savedBaseUrl.trim().length > 0;\n\t\tif (!providers.has(provider) && !hasSavedBaseUrl) {\n\t\t\treturn \"observer_provider must match a configured provider\";\n\t\t}\n\t\tconfigData[key] = provider;\n\t\treturn null;\n\t}\n\tif (key === \"observer_runtime\") {\n\t\tif (typeof value !== \"string\") return \"observer_runtime must be string\";\n\t\tconst runtime = value.trim().toLowerCase();\n\t\tif (!RUNTIMES.has(runtime)) {\n\t\t\treturn \"observer_runtime must be one of: api_http, claude_sidecar\";\n\t\t}\n\t\tconfigData[key] = runtime;\n\t\treturn null;\n\t}\n\tif (key === \"observer_auth_source\") {\n\t\tif (typeof value !== \"string\") return \"observer_auth_source must be string\";\n\t\tconst source = value.trim().toLowerCase();\n\t\tif (!AUTH_SOURCES.has(source)) {\n\t\t\treturn \"observer_auth_source must be one of: auto, env, file, command, none\";\n\t\t}\n\t\tconfigData[key] = source;\n\t\treturn null;\n\t}\n\tif (key === \"claude_command\" || key === \"observer_auth_command\") {\n\t\tconst argv = asExecutableArgv(value);\n\t\tif (argv == null) return `${key} must be string array`;\n\t\tif (argv.length > 0) configData[key] = argv;\n\t\telse delete configData[key];\n\t\treturn null;\n\t}\n\tif (key === \"observer_headers\") {\n\t\tconst headers = asStringMap(value);\n\t\tif (headers == null) return \"observer_headers must be object of string values\";\n\t\tif (Object.keys(headers).length > 0) configData[key] = headers;\n\t\telse delete configData[key];\n\t\treturn null;\n\t}\n\tif (key === \"sync_enabled\" || key === \"sync_mdns\") {\n\t\tif (typeof value !== \"boolean\") return `${key} must be boolean`;\n\t\tconfigData[key] = value;\n\t\treturn null;\n\t}\n\tif (\n\t\tkey === \"observer_base_url\" ||\n\t\tkey === \"observer_model\" ||\n\t\tkey === \"observer_auth_file\" ||\n\t\tkey === \"sync_host\" ||\n\t\tkey === \"sync_coordinator_url\" ||\n\t\tkey === \"sync_coordinator_group\"\n\t) {\n\t\tif (typeof value !== \"string\") return `${key} must be string`;\n\t\tconst trimmed = value.trim();\n\t\tif (!trimmed) delete configData[key];\n\t\telse configData[key] = trimmed;\n\t\treturn null;\n\t}\n\tconst allowZero = key === \"observer_auth_cache_ttl_s\";\n\tconst parsed = parsePositiveInt(value, allowZero);\n\tif (parsed == null) return `${key} must be ${allowZero ? \"non-negative int\" : \"int\"}`;\n\tconfigData[key] = parsed;\n\treturn null;\n}\n\nfunction applyRuntimeEffects(changedKeys: string[], opts: ConfigRouteOptions): string[] {\n\tconst applied: string[] = [];\n\tif (changedKeys.includes(\"raw_events_sweeper_interval_s\")) {\n\t\tconst configValue = readCodememConfigFile().raw_events_sweeper_interval_s;\n\t\tconst seconds =\n\t\t\ttypeof configValue === \"number\"\n\t\t\t\t? configValue\n\t\t\t\t: Number.parseInt(String(configValue ?? \"\"), 10);\n\t\tif (Number.isFinite(seconds) && seconds > 0) {\n\t\t\tprocess.env.CODEMEM_RAW_EVENTS_SWEEPER_INTERVAL_MS = String(seconds * 1000);\n\t\t} else {\n\t\t\tdelete process.env.CODEMEM_RAW_EVENTS_SWEEPER_INTERVAL_MS;\n\t\t}\n\t\topts.getSweeper?.()?.notifyConfigChanged();\n\t\tapplied.push(\"raw_events_sweeper_interval_s\");\n\t}\n\treturn applied;\n}\n\nexport function configRoutes(opts: ConfigRouteOptions = {}) {\n\tconst app = new Hono();\n\n\tapp.get(\"/api/config\", (c) => {\n\t\tconst configPath = getConfigPath();\n\t\tconst configData = readConfigFile(configPath);\n\t\treturn c.json({\n\t\t\tpath: configPath,\n\t\t\tconfig: configData,\n\t\t\tdefaults: DEFAULTS,\n\t\t\teffective: getEffectiveConfig(configData),\n\t\t\tenv_overrides: getCodememEnvOverrides(),\n\t\t\tproviders: loadProviderOptions(),\n\t\t});\n\t});\n\n\tapp.post(\"/api/config\", async (c) => {\n\t\tlet payload: unknown;\n\t\ttry {\n\t\t\tpayload = (await c.req.json()) as unknown;\n\t\t} catch {\n\t\t\treturn c.json({ error: \"invalid json\" }, 400);\n\t\t}\n\t\tif (payload == null || typeof payload !== \"object\" || Array.isArray(payload)) {\n\t\t\treturn c.json({ error: \"payload must be an object\" }, 400);\n\t\t}\n\t\tif (\n\t\t\t\"config\" in payload &&\n\t\t\t(payload as ConfigData).config != null &&\n\t\t\t(typeof (payload as ConfigData).config !== \"object\" ||\n\t\t\t\tArray.isArray((payload as ConfigData).config))\n\t\t) {\n\t\t\treturn c.json({ error: \"config must be an object\" }, 400);\n\t\t}\n\t\tconst updates =\n\t\t\t\"config\" in payload &&\n\t\t\t(payload as ConfigData).config != null &&\n\t\t\ttypeof (payload as ConfigData).config === \"object\" &&\n\t\t\t!Array.isArray((payload as ConfigData).config)\n\t\t\t\t? ((payload as ConfigData).config as ConfigData)\n\t\t\t\t: (payload as ConfigData);\n\n\t\tconst configPath = getCodememConfigPath();\n\t\tconst beforeConfig = readCodememConfigFile();\n\t\tconst beforeEffective = getEffectiveConfig(beforeConfig);\n\t\tconst nextConfig: ConfigData = { ...beforeConfig };\n\t\tconst providers = new Set(loadProviderOptions());\n\n\t\tconst touchedKeys = ALLOWED_KEYS.filter((key) => key in updates);\n\t\tfor (const key of ALLOWED_KEYS) {\n\t\t\tif (!(key in updates)) continue;\n\t\t\tconst error = validateAndApplyUpdate(nextConfig, key, updates[key], providers);\n\t\t\tif (error) return c.json({ error }, 400);\n\t\t}\n\n\t\tlet savedPath: string;\n\t\ttry {\n\t\t\tsavedPath = writeCodememConfigFile(nextConfig, configPath);\n\t\t} catch {\n\t\t\treturn c.json({ error: \"failed to write config\" }, 500);\n\t\t}\n\n\t\tconst afterEffective = getEffectiveConfig(nextConfig);\n\t\tconst savedChangedKeys = ALLOWED_KEYS.filter((key) => beforeConfig[key] !== nextConfig[key]);\n\t\tconst effectiveChangedKeys = ALLOWED_KEYS.filter(\n\t\t\t(key) => beforeEffective[key] !== afterEffective[key],\n\t\t);\n\t\tconst envOverrides = getCodememEnvOverrides();\n\t\tconst ignoredByEnvKeys = savedChangedKeys.filter(\n\t\t\t(key) => !effectiveChangedKeys.includes(key) && key in envOverrides,\n\t\t);\n\t\tconst runtimeChangedKeys = [\n\t\t\t...new Set([...touchedKeys, ...savedChangedKeys, ...effectiveChangedKeys]),\n\t\t];\n\t\tconst hotReloadedKeys = applyRuntimeEffects(runtimeChangedKeys, opts);\n\t\tconst restartRequiredKeys = effectiveChangedKeys.filter(\n\t\t\t(key) => !HOT_RELOAD_KEYS.has(key) && !(key in envOverrides),\n\t\t);\n\n\t\treturn c.json({\n\t\t\tpath: savedPath,\n\t\t\tconfig: nextConfig,\n\t\t\teffective: afterEffective,\n\t\t\teffects: {\n\t\t\t\tsaved_keys: savedChangedKeys,\n\t\t\t\teffective_keys: effectiveChangedKeys,\n\t\t\t\thot_reloaded_keys: hotReloadedKeys,\n\t\t\t\trestart_required_keys: restartRequiredKeys,\n\t\t\t\tignored_by_env_keys: ignoredByEnvKeys,\n\t\t\t\twarnings: ignoredByEnvKeys.map(\n\t\t\t\t\t(key) =>\n\t\t\t\t\t\t`${key} is currently controlled by ${envOverrides[key]}; saved config will not take effect until that override is removed.`,\n\t\t\t\t),\n\t\t\t},\n\t\t});\n\t});\n\n\treturn app;\n}\n","import { parseStrictInteger } from \"@codemem/core\";\n\n/**\n * Shared helpers for viewer-server routes.\n */\n\n/**\n * Parse a JSON string that should be an array of strings.\n * Returns an empty array on null, invalid JSON, or non-array values.\n * Mirrors Python's store._safe_json_list().\n */\nexport function safeJsonList(raw: string | null | undefined): string[] {\n\tif (raw == null) return [];\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(raw);\n\t\tif (!Array.isArray(parsed)) return [];\n\t\treturn parsed.filter((item): item is string => typeof item === \"string\");\n\t} catch {\n\t\treturn [];\n\t}\n}\n\n/**\n * Parse a query parameter as an integer, returning the default on failure.\n */\nexport function queryInt(value: string | undefined, defaultValue: number): number {\n\tif (value == null) return defaultValue;\n\tconst parsed = parseStrictInteger(value);\n\treturn parsed == null ? defaultValue : parsed;\n}\n\n/**\n * Parse a query parameter as a boolean flag.\n * Recognizes \"1\", \"true\", \"yes\" as truthy.\n */\nexport function queryBool(value: string | undefined): boolean {\n\tif (value == null) return false;\n\treturn value === \"1\" || value === \"true\" || value === \"yes\";\n}\n","/**\n * Memory routes — observations, summaries, sessions, projects, pack, artifacts.\n */\n\nimport type { MemoryStore } from \"@codemem/core\";\nimport { buildFilterClausesWithContext, fromJson, parseStrictInteger, schema } from \"@codemem/core\";\nimport { desc, eq, inArray, isNotNull } from \"drizzle-orm\";\nimport { drizzle } from \"drizzle-orm/better-sqlite3\";\nimport { Hono } from \"hono\";\nimport { queryInt } from \"../helpers.js\";\n\ntype StoreFactory = () => MemoryStore;\n\n/**\n * Attach session project/cwd fields to memory items.\n */\nfunction attachSessionFields(store: MemoryStore, items: Record<string, unknown>[]): void {\n\tconst sessionIds: number[] = [];\n\tconst seen = new Set<number>();\n\tfor (const item of items) {\n\t\tconst value = item.session_id;\n\t\tif (value == null) continue;\n\t\tconst sid = Number(value);\n\t\tif (Number.isNaN(sid) || seen.has(sid)) continue;\n\t\tseen.add(sid);\n\t\tsessionIds.push(sid);\n\t}\n\tif (sessionIds.length === 0) return;\n\n\tconst d = drizzle(store.db, { schema });\n\tconst rows = d\n\t\t.select({\n\t\t\tid: schema.sessions.id,\n\t\t\tproject: schema.sessions.project,\n\t\t\tcwd: schema.sessions.cwd,\n\t\t})\n\t\t.from(schema.sessions)\n\t\t.where(inArray(schema.sessions.id, sessionIds))\n\t\t.all();\n\n\tconst bySession = new Map<number, { project: string; cwd: string }>();\n\tfor (const row of rows) {\n\t\tconst projectRaw = String(row.project ?? \"\").trim();\n\t\tconst project = projectRaw ? projectBasename(projectRaw) : \"\";\n\t\tconst cwd = String(row.cwd ?? \"\");\n\t\tbySession.set(row.id, { project, cwd });\n\t}\n\n\tfor (const item of items) {\n\t\tconst sid = Number(item.session_id);\n\t\tif (Number.isNaN(sid)) continue;\n\t\tconst fields = bySession.get(sid);\n\t\tif (!fields) continue;\n\t\titem.project ??= fields.project;\n\t\titem.cwd ??= fields.cwd;\n\t}\n}\n\n/**\n * Extract the basename of a project path.\n * Strips \"fatal:\" prefixed values.\n */\nfunction projectBasename(raw: string): string {\n\tif (raw.toLowerCase().startsWith(\"fatal:\")) return \"\";\n\tconst parts = raw.replace(/\\\\/g, \"/\").split(\"/\");\n\treturn parts[parts.length - 1] ?? raw;\n}\n\nfunction normalizeScope(raw: string | undefined): \"mine\" | \"theirs\" | undefined {\n\tconst value = String(raw ?? \"\")\n\t\t.trim()\n\t\t.toLowerCase();\n\tif (value === \"mine\" || value === \"theirs\") return value;\n\treturn undefined;\n}\n\nfunction queryMemoryPage(\n\tstore: MemoryStore,\n\toptions: {\n\t\tlimit: number;\n\t\toffset: number;\n\t\tproject?: string;\n\t\tscope?: \"mine\" | \"theirs\";\n\t},\n): Record<string, unknown>[] {\n\tconst filters: Record<string, unknown> = {};\n\tif (options.project) filters.project = options.project;\n\tif (options.scope) filters.ownership_scope = options.scope;\n\n\tconst filterResult = buildFilterClausesWithContext(filters, {\n\t\tactorId: store.actorId,\n\t\tdeviceId: store.deviceId,\n\t});\n\tconst clauses = [\"memory_items.active = 1\", ...filterResult.clauses];\n\tconst where = clauses.join(\" AND \");\n\tconst from = filterResult.joinSessions\n\t\t? \"memory_items JOIN sessions ON sessions.id = memory_items.session_id\"\n\t\t: \"memory_items\";\n\n\tconst rows = store.db\n\t\t.prepare(\n\t\t\t`SELECT memory_items.* FROM ${from}\n\t\t\t WHERE ${where}\n\t\t\t ORDER BY memory_items.created_at DESC\n\t\t\t LIMIT ? OFFSET ?`,\n\t\t)\n\t\t.all(...filterResult.params, options.limit + 1, options.offset) as Record<string, unknown>[];\n\n\treturn rows.map((row) => ({\n\t\t...row,\n\t\tmetadata_json: fromJson((row.metadata_json as string) ?? null),\n\t}));\n}\n\nfunction isSummaryLikeMemory(item: Record<string, unknown>): boolean {\n\tif (String(item.kind ?? \"\").toLowerCase() === \"session_summary\") return true;\n\tconst metadata = (item.metadata_json ?? {}) as Record<string, unknown>;\n\tif (metadata.is_summary === true) return true;\n\treturn (\n\t\tString(metadata.source ?? \"\")\n\t\t\t.trim()\n\t\t\t.toLowerCase() === \"observer_summary\"\n\t);\n}\n\nfunction selectMemoryPage(\n\tstore: MemoryStore,\n\toptions: {\n\t\tlimit: number;\n\t\toffset: number;\n\t\tproject?: string;\n\t\tscope?: \"mine\" | \"theirs\";\n\t\tmatcher: (item: Record<string, unknown>) => boolean;\n\t},\n): Record<string, unknown>[] {\n\tconst pageSize = Math.max(options.limit + options.offset + 10, 50);\n\tlet rawOffset = 0;\n\tconst matched: Record<string, unknown>[] = [];\n\n\twhile (matched.length < options.offset + options.limit + 1) {\n\t\tconst page = queryMemoryPage(store, {\n\t\t\tlimit: pageSize,\n\t\t\toffset: rawOffset,\n\t\t\tproject: options.project,\n\t\t\tscope: options.scope,\n\t\t});\n\t\tif (page.length === 0) break;\n\t\tmatched.push(...page.filter(options.matcher));\n\t\tif (page.length < pageSize) break;\n\t\trawOffset += page.length;\n\t}\n\n\treturn matched.slice(options.offset, options.offset + options.limit + 1);\n}\n\nexport function memoryRoutes(getStore: StoreFactory) {\n\tconst app = new Hono();\n\n\t// GET /api/sessions\n\tapp.get(\"/api/sessions\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst limit = queryInt(c.req.query(\"limit\"), 20);\n\t\t\tconst d = drizzle(store.db, { schema });\n\t\t\tconst rows = d\n\t\t\t\t.select()\n\t\t\t\t.from(schema.sessions)\n\t\t\t\t.orderBy(desc(schema.sessions.started_at))\n\t\t\t\t.limit(limit)\n\t\t\t\t.all();\n\t\t\tconst items = rows.map((row) => ({\n\t\t\t\t...row,\n\t\t\t\tmetadata_json: fromJson(row.metadata_json),\n\t\t\t}));\n\t\t\treturn c.json({ items });\n\t\t}\n\t});\n\n\t// GET /api/projects\n\tapp.get(\"/api/projects\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst d = drizzle(store.db, { schema });\n\t\t\tconst rows = d\n\t\t\t\t.selectDistinct({ project: schema.sessions.project })\n\t\t\t\t.from(schema.sessions)\n\t\t\t\t.where(isNotNull(schema.sessions.project))\n\t\t\t\t.all();\n\t\t\tconst projects = [\n\t\t\t\t...new Set(\n\t\t\t\t\trows\n\t\t\t\t\t\t.map((r) => String(r.project ?? \"\").trim())\n\t\t\t\t\t\t.filter((p) => p && !p.toLowerCase().startsWith(\"fatal:\"))\n\t\t\t\t\t\t.map((p) => projectBasename(p))\n\t\t\t\t\t\t.filter(Boolean),\n\t\t\t\t),\n\t\t\t].sort();\n\t\t\treturn c.json({ projects });\n\t\t}\n\t});\n\n\t// GET /api/observations (aliased from /api/memories)\n\tapp.get(\"/api/memories\", (c) => {\n\t\tconst search = new URL(c.req.url).search;\n\t\treturn c.redirect(`/api/observations${search}`, 301);\n\t});\n\n\tapp.get(\"/api/observations\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst limit = Math.max(1, queryInt(c.req.query(\"limit\"), 20));\n\t\t\tconst offset = Math.max(0, queryInt(c.req.query(\"offset\"), 0));\n\t\t\tconst project = c.req.query(\"project\") || undefined;\n\t\t\tconst scope = normalizeScope(c.req.query(\"scope\"));\n\t\t\tconst items = selectMemoryPage(store, {\n\t\t\t\tlimit,\n\t\t\t\toffset,\n\t\t\t\tproject,\n\t\t\t\tscope,\n\t\t\t\tmatcher: (item) => !isSummaryLikeMemory(item),\n\t\t\t});\n\t\t\tconst hasMore = items.length > limit;\n\t\t\tconst result = hasMore ? items.slice(0, limit) : items;\n\t\t\tconst asRecords = result as unknown as Record<string, unknown>[];\n\t\t\tattachSessionFields(store, asRecords);\n\t\t\treturn c.json({\n\t\t\t\titems: asRecords,\n\t\t\t\tpagination: {\n\t\t\t\t\tlimit,\n\t\t\t\t\toffset,\n\t\t\t\t\tnext_offset: hasMore ? offset + result.length : null,\n\t\t\t\t\thas_more: hasMore,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\t});\n\n\t// GET /api/summaries\n\tapp.get(\"/api/summaries\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst limit = Math.max(1, queryInt(c.req.query(\"limit\"), 50));\n\t\t\tconst offset = Math.max(0, queryInt(c.req.query(\"offset\"), 0));\n\t\t\tconst project = c.req.query(\"project\") || undefined;\n\t\t\tconst scope = normalizeScope(c.req.query(\"scope\"));\n\t\t\tconst items = selectMemoryPage(store, {\n\t\t\t\tlimit,\n\t\t\t\toffset,\n\t\t\t\tproject,\n\t\t\t\tscope,\n\t\t\t\tmatcher: (item) => isSummaryLikeMemory(item),\n\t\t\t});\n\t\t\tconst hasMore = items.length > limit;\n\t\t\tconst result = hasMore ? items.slice(0, limit) : items;\n\t\t\tconst asRecords = result as unknown as Record<string, unknown>[];\n\t\t\tattachSessionFields(store, asRecords);\n\t\t\treturn c.json({\n\t\t\t\titems: asRecords,\n\t\t\t\tpagination: {\n\t\t\t\t\tlimit,\n\t\t\t\t\toffset,\n\t\t\t\t\tnext_offset: hasMore ? offset + result.length : null,\n\t\t\t\t\thas_more: hasMore,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\t});\n\n\t// GET /api/session (aggregate counts)\n\tapp.get(\"/api/session\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst project = c.req.query(\"project\") || null;\n\t\t\tconst count = (sql: string, ...params: unknown[]): number => {\n\t\t\t\tconst row = store.db.prepare(sql).get(...params) as Record<string, unknown> | undefined;\n\t\t\t\treturn Number(row?.total ?? 0);\n\t\t\t};\n\n\t\t\tlet prompts: number;\n\t\t\tlet artifacts: number;\n\t\t\tlet memories: number;\n\t\t\tlet observations: number;\n\t\t\tconst countObservations = (scopeProject?: string) => {\n\t\t\t\tlet offset = 0;\n\t\t\t\tlet total = 0;\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst page = queryMemoryPage(store, {\n\t\t\t\t\t\tlimit: 200,\n\t\t\t\t\t\toffset,\n\t\t\t\t\t\tproject: scopeProject,\n\t\t\t\t\t});\n\t\t\t\t\tif (page.length === 0) break;\n\t\t\t\t\ttotal += page.filter((item) => !isSummaryLikeMemory(item)).length;\n\t\t\t\t\tif (page.length < 200) break;\n\t\t\t\t\toffset += page.length;\n\t\t\t\t}\n\t\t\t\treturn total;\n\t\t\t};\n\t\t\tif (project) {\n\t\t\t\tprompts = count(\"SELECT COUNT(*) AS total FROM user_prompts WHERE project = ?\", project);\n\t\t\t\tartifacts = count(\n\t\t\t\t\t`SELECT COUNT(*) AS total FROM artifacts\n\t\t\t\t\t JOIN sessions ON sessions.id = artifacts.session_id\n\t\t\t\t\t WHERE sessions.project = ?`,\n\t\t\t\t\tproject,\n\t\t\t\t);\n\t\t\t\tmemories = count(\n\t\t\t\t\t`SELECT COUNT(*) AS total FROM memory_items\n\t\t\t\t\t JOIN sessions ON sessions.id = memory_items.session_id\n\t\t\t\t\t WHERE sessions.project = ?`,\n\t\t\t\t\tproject,\n\t\t\t\t);\n\t\t\t\tobservations = countObservations(project);\n\t\t\t} else {\n\t\t\t\tprompts = count(\"SELECT COUNT(*) AS total FROM user_prompts\");\n\t\t\t\tartifacts = count(\"SELECT COUNT(*) AS total FROM artifacts\");\n\t\t\t\tmemories = count(\"SELECT COUNT(*) AS total FROM memory_items\");\n\t\t\t\tobservations = countObservations();\n\t\t\t}\n\t\t\tconst total = prompts + artifacts + memories;\n\t\t\treturn c.json({ total, memories, artifacts, prompts, observations });\n\t\t}\n\t});\n\n\t// GET /api/pack\n\tapp.get(\"/api/pack\", async (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst context = c.req.query(\"context\") || \"\";\n\t\t\tif (!context) {\n\t\t\t\treturn c.json({ error: \"context required\" }, 400);\n\t\t\t}\n\t\t\tconst limit = queryInt(c.req.query(\"limit\"), 10);\n\t\t\tconst tokenBudgetStr = c.req.query(\"token_budget\");\n\t\t\tlet tokenBudget: number | undefined;\n\t\t\tif (tokenBudgetStr) {\n\t\t\t\ttokenBudget = parseStrictInteger(tokenBudgetStr) ?? undefined;\n\t\t\t\tif (tokenBudget === undefined) {\n\t\t\t\t\treturn c.json({ error: \"token_budget must be int\" }, 400);\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst project = c.req.query(\"project\") || undefined;\n\t\t\tconst filters: { project?: string } = {};\n\t\t\tif (project) filters.project = project;\n\t\t\tconst pack = await store.buildMemoryPackAsync(context, limit, tokenBudget ?? null, filters);\n\t\t\treturn c.json(pack);\n\t\t}\n\t});\n\n\t// GET /api/memory\n\tapp.get(\"/api/memory\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst limit = queryInt(c.req.query(\"limit\"), 20);\n\t\t\tconst kind = c.req.query(\"kind\") || undefined;\n\t\t\tconst project = c.req.query(\"project\") || undefined;\n\t\t\tconst filters: Record<string, unknown> = {};\n\t\t\tif (kind) filters.kind = kind;\n\t\t\tif (project) filters.project = project;\n\t\t\tconst items = store.recent(limit, filters);\n\t\t\tconst asRecords = items as unknown as Record<string, unknown>[];\n\t\t\tattachSessionFields(store, asRecords);\n\t\t\treturn c.json({ items: asRecords });\n\t\t}\n\t});\n\n\t// GET /api/artifacts\n\tapp.get(\"/api/artifacts\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst sessionIdStr = c.req.query(\"session_id\");\n\t\t\tif (!sessionIdStr) {\n\t\t\t\treturn c.json({ error: \"session_id required\" }, 400);\n\t\t\t}\n\t\t\tconst sessionId = parseStrictInteger(sessionIdStr);\n\t\t\tif (sessionId == null) {\n\t\t\t\treturn c.json({ error: \"session_id must be int\" }, 400);\n\t\t\t}\n\t\t\tconst d = drizzle(store.db, { schema });\n\t\t\tconst rows = d\n\t\t\t\t.select()\n\t\t\t\t.from(schema.artifacts)\n\t\t\t\t.where(eq(schema.artifacts.session_id, sessionId))\n\t\t\t\t.all();\n\t\t\treturn c.json({ items: rows });\n\t\t}\n\t});\n\n\t// POST /api/memories/visibility\n\tapp.post(\"/api/memories/visibility\", async (c) => {\n\t\tconst store = getStore();\n\t\tlet body: Record<string, unknown>;\n\t\ttry {\n\t\t\tbody = await c.req.json<Record<string, unknown>>();\n\t\t} catch {\n\t\t\treturn c.json({ error: \"invalid JSON\" }, 400);\n\t\t}\n\t\tconst memoryId = parseStrictInteger(\n\t\t\ttypeof body.memory_id === \"string\" ? body.memory_id : String(body.memory_id ?? \"\"),\n\t\t);\n\t\tif (memoryId == null || memoryId <= 0) {\n\t\t\treturn c.json({ error: \"memory_id must be int\" }, 400);\n\t\t}\n\t\tconst visibility = String(body.visibility ?? \"\").trim();\n\t\tif (visibility !== \"private\" && visibility !== \"shared\") {\n\t\t\treturn c.json({ error: \"visibility must be private or shared\" }, 400);\n\t\t}\n\t\ttry {\n\t\t\tconst item = store.updateMemoryVisibility(memoryId, visibility);\n\t\t\treturn c.json({ item });\n\t\t} catch (err) {\n\t\t\tconst msg = err instanceof Error ? err.message : String(err);\n\t\t\tif (msg.includes(\"not found\")) return c.json({ error: msg }, 404);\n\t\t\tif (msg.includes(\"not owned\")) return c.json({ error: msg }, 403);\n\t\t\treturn c.json({ error: msg }, 400);\n\t\t}\n\t});\n\n\treturn app;\n}\n","/**\n * Observer status route — GET /api/observer-status.\n *\n * Ports Python's viewer_routes/observer_status.py.\n * Returns observer runtime info, credential availability, and queue status.\n */\n\nimport type { ObserverClient } from \"@codemem/core\";\nimport { type MemoryStore, probeAvailableCredentials, type RawEventSweeper } from \"@codemem/core\";\nimport { Hono } from \"hono\";\n\ntype StoreFactory = () => MemoryStore;\n\nexport interface ObserverStatusDeps {\n\tgetStore: StoreFactory;\n\tgetSweeper: () => RawEventSweeper | null;\n\tgetObserver?: () => ObserverClient | null;\n}\n\nfunction normalizeActiveObserver(active: ReturnType<ObserverClient[\"getStatus\"]> | null) {\n\tif (!active) return null;\n\treturn {\n\t\t...active,\n\t\tauth: {\n\t\t\t...active.auth,\n\t\t\tmethod: active.auth.type,\n\t\t\ttoken_present: active.auth.hasToken,\n\t\t},\n\t};\n}\n\nfunction buildFailureImpact(\n\tlatestFailure: Record<string, unknown> | null,\n\tqueueTotals: { pending: number; sessions: number },\n\tauthBackoff: { active: boolean; remainingS: number },\n): string | null {\n\tif (!latestFailure) return null;\n\tif (authBackoff.active) {\n\t\treturn `Queue retries paused for ~${authBackoff.remainingS}s after an observer auth failure.`;\n\t}\n\tif (queueTotals.pending > 0) {\n\t\treturn `${queueTotals.pending} queued raw events across ${queueTotals.sessions} session(s) are waiting on a successful flush.`;\n\t}\n\treturn \"Failed flush batches are pending retry.\";\n}\n\nexport function observerStatusRoutes(deps?: ObserverStatusDeps) {\n\tconst app = new Hono();\n\n\tapp.get(\"/api/observer-status\", (c) => {\n\t\tconst store = deps?.getStore();\n\t\tconst sweeper = deps?.getSweeper();\n\t\tconst observer = deps?.getObserver?.() ?? null;\n\n\t\t// Stub fallback when store doesn't have the required methods (e.g. tests with mock store)\n\t\tif (!store || typeof store.rawEventBacklogTotals !== \"function\") {\n\t\t\treturn c.json({\n\t\t\t\tactive: null,\n\t\t\t\tavailable_credentials: {},\n\t\t\t\tlatest_failure: null,\n\t\t\t\tqueue: {\n\t\t\t\t\tpending: 0,\n\t\t\t\t\tsessions: 0,\n\t\t\t\t\tauth_backoff_active: false,\n\t\t\t\t\tauth_backoff_remaining_s: 0,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\tconst queueTotals = store.rawEventBacklogTotals();\n\t\tconst authBackoff = sweeper?.authBackoffStatus() ?? { active: false, remainingS: 0 };\n\t\tconst latestFailure = store.latestRawEventFlushFailure();\n\t\tconst active = normalizeActiveObserver(observer?.getStatus() ?? null);\n\t\tconst availableCredentials = probeAvailableCredentials();\n\t\tconst shouldShowFailure =\n\t\t\tlatestFailure != null && (authBackoff.active || queueTotals.pending > 0);\n\n\t\tconst failureWithImpact =\n\t\t\tshouldShowFailure && latestFailure\n\t\t\t\t? { ...latestFailure, impact: buildFailureImpact(latestFailure, queueTotals, authBackoff) }\n\t\t\t\t: null;\n\n\t\treturn c.json({\n\t\t\tactive,\n\t\t\tavailable_credentials: availableCredentials,\n\t\t\tlatest_failure: failureWithImpact,\n\t\t\tqueue: {\n\t\t\t\t...queueTotals,\n\t\t\t\tauth_backoff_active: authBackoff.active,\n\t\t\t\tauth_backoff_remaining_s: authBackoff.remainingS,\n\t\t\t},\n\t\t});\n\t});\n\n\treturn app;\n}\n","/**\n * Raw events routes — GET & POST /api/raw-events, GET /api/raw-events/status,\n * POST /api/claude-hooks.\n */\n\nimport { createHash } from \"node:crypto\";\nimport type { MemoryStore, RawEventSweeper } from \"@codemem/core\";\nimport { buildRawEventEnvelopeFromHook, schema, stripPrivateObj } from \"@codemem/core\";\nimport { desc } from \"drizzle-orm\";\nimport { drizzle } from \"drizzle-orm/better-sqlite3\";\nimport { Hono } from \"hono\";\nimport { queryInt } from \"../helpers.js\";\n\ntype StoreFactory = () => MemoryStore;\n\nconst MAX_RAW_EVENTS_BODY_BYTES =\n\tNumber.parseInt(process.env.CODEMEM_RAW_EVENTS_MAX_BODY_BYTES ?? \"\", 10) || 1048576;\n\n/** Keys to check (in priority order) when resolving a session stream id. */\nconst SESSION_ID_KEYS = [\n\t\"session_stream_id\",\n\t\"session_id\",\n\t\"stream_id\",\n\t\"opencode_session_id\",\n] as const;\n\n/**\n * Resolve a session stream id from a payload object.\n * Checks multiple field aliases. Throws on conflicting values.\n */\nfunction resolveSessionStreamId(payload: Record<string, unknown>): string | null {\n\tconst values = new Map<string, string>();\n\tfor (const key of SESSION_ID_KEYS) {\n\t\tconst value = payload[key];\n\t\tif (value == null) continue;\n\t\tif (typeof value !== \"string\") throw new Error(`${key} must be string`);\n\t\tconst text = value.trim();\n\t\tif (text) values.set(key, text);\n\t}\n\tif (values.size === 0) return null;\n\tconst unique = new Set(values.values());\n\tif (unique.size > 1) throw new Error(\"conflicting session id fields\");\n\t// Return the first matching key's value (preserves priority order)\n\tfor (const key of SESSION_ID_KEYS) {\n\t\tconst v = values.get(key);\n\t\tif (v) return v;\n\t}\n\treturn null;\n}\n\n/**\n * Parse and validate a JSON object body, enforcing size limits.\n * Returns the parsed payload or a Hono Response on error.\n */\nasync function parseJsonObjectBody(\n\tc: {\n\t\treq: { header: (name: string) => string | undefined; text: () => Promise<string> };\n\t\tjson: (data: unknown, status?: number) => Response;\n\t},\n\tmaxBytes: number,\n): Promise<Record<string, unknown> | Response> {\n\tconst contentLength = Number.parseInt(c.req.header(\"content-length\") ?? \"0\", 10);\n\tif (Number.isNaN(contentLength) || contentLength < 0) {\n\t\treturn c.json({ error: \"invalid content-length\" }, 400);\n\t}\n\tif (contentLength > maxBytes) {\n\t\treturn c.json({ error: \"payload too large\", max_bytes: maxBytes }, 413);\n\t}\n\tlet raw: string;\n\ttry {\n\t\traw = await c.req.text();\n\t} catch {\n\t\treturn c.json({ error: \"invalid json\" }, 400);\n\t}\n\tif (Buffer.byteLength(raw, \"utf-8\") > maxBytes) {\n\t\treturn c.json({ error: \"payload too large\", max_bytes: maxBytes }, 413);\n\t}\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = raw ? JSON.parse(raw) : {};\n\t} catch {\n\t\treturn c.json({ error: \"invalid json\" }, 400);\n\t}\n\tif (parsed == null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n\t\treturn c.json({ error: \"payload must be an object\" }, 400);\n\t}\n\treturn parsed as Record<string, unknown>;\n}\n\n/** Nudge the sweeper safely — never crashes the caller. */\nfunction nudgeSweeper(\n\tsweeper: RawEventSweeper | null | undefined,\n\tsessionIds: Iterable<string>,\n\tsource = \"opencode\",\n): void {\n\ttry {\n\t\tfor (const sessionId of sessionIds) {\n\t\t\tsweeper?.nudge(sessionId, source);\n\t\t}\n\t} catch {\n\t\t// never crash the request if sweeper nudge fails\n\t}\n}\n\nexport function rawEventsRoutes(getStore: StoreFactory, sweeper?: RawEventSweeper | null) {\n\tconst app = new Hono();\n\n\t// GET /api/raw-events (compat endpoint for stats panel)\n\tapp.get(\"/api/raw-events\", (c) => {\n\t\tconst store = getStore();\n\t\tconst totals = store.rawEventBacklogTotals();\n\t\treturn c.json(totals);\n\t});\n\n\t// GET /api/raw-events/status\n\tapp.get(\"/api/raw-events/status\", (c) => {\n\t\tconst store = getStore();\n\t\tconst limit = queryInt(c.req.query(\"limit\"), 25);\n\t\tconst d = drizzle(store.db, { schema });\n\t\tconst rows = d\n\t\t\t.select({\n\t\t\t\tsource: schema.rawEventSessions.source,\n\t\t\t\tstream_id: schema.rawEventSessions.stream_id,\n\t\t\t\topencode_session_id: schema.rawEventSessions.opencode_session_id,\n\t\t\t\tcwd: schema.rawEventSessions.cwd,\n\t\t\t\tproject: schema.rawEventSessions.project,\n\t\t\t\tstarted_at: schema.rawEventSessions.started_at,\n\t\t\t\tlast_seen_ts_wall_ms: schema.rawEventSessions.last_seen_ts_wall_ms,\n\t\t\t\tlast_received_event_seq: schema.rawEventSessions.last_received_event_seq,\n\t\t\t\tlast_flushed_event_seq: schema.rawEventSessions.last_flushed_event_seq,\n\t\t\t\tupdated_at: schema.rawEventSessions.updated_at,\n\t\t\t})\n\t\t\t.from(schema.rawEventSessions)\n\t\t\t.orderBy(desc(schema.rawEventSessions.updated_at))\n\t\t\t.limit(limit)\n\t\t\t.all();\n\t\tconst items = rows.map((row) => {\n\t\t\tconst streamId = String(row.stream_id ?? row.opencode_session_id ?? \"\");\n\t\t\treturn {\n\t\t\t\t...row,\n\t\t\t\tsession_stream_id: streamId,\n\t\t\t\tsession_id: streamId,\n\t\t\t};\n\t\t});\n\t\tconst totals = store.rawEventBacklogTotals();\n\t\treturn c.json({\n\t\t\titems,\n\t\t\ttotals,\n\t\t\tingest: {\n\t\t\t\tavailable: true,\n\t\t\t\tmode: \"stream_queue\",\n\t\t\t\tmax_body_bytes: MAX_RAW_EVENTS_BODY_BYTES,\n\t\t\t},\n\t\t});\n\t});\n\n\t// POST /api/raw-events — ingest raw events from plugin\n\tapp.post(\"/api/raw-events\", async (c) => {\n\t\tconst result = await parseJsonObjectBody(c, MAX_RAW_EVENTS_BODY_BYTES);\n\t\tif (result instanceof Response) return result;\n\t\tconst payload = result;\n\n\t\tconst store = getStore();\n\t\ttry {\n\t\t\t// Validate top-level string fields\n\t\t\tconst cwd = payload.cwd;\n\t\t\tif (cwd != null && typeof cwd !== \"string\") {\n\t\t\t\treturn c.json({ error: \"cwd must be string\" }, 400);\n\t\t\t}\n\t\t\tconst project = payload.project;\n\t\t\tif (project != null && typeof project !== \"string\") {\n\t\t\t\treturn c.json({ error: \"project must be string\" }, 400);\n\t\t\t}\n\t\t\tconst startedAt = payload.started_at;\n\t\t\tif (startedAt != null && typeof startedAt !== \"string\") {\n\t\t\t\treturn c.json({ error: \"started_at must be string\" }, 400);\n\t\t\t}\n\n\t\t\t// Determine event list: batch (events array) or single-event payload\n\t\t\tlet items = payload.events;\n\t\t\tif (items == null) {\n\t\t\t\titems = [payload];\n\t\t\t}\n\t\t\tif (!Array.isArray(items)) {\n\t\t\t\treturn c.json({ error: \"events must be a list\" }, 400);\n\t\t\t}\n\n\t\t\t// Resolve default session id from top-level payload\n\t\t\tlet defaultSessionId: string;\n\t\t\ttry {\n\t\t\t\tdefaultSessionId = resolveSessionStreamId(payload) ?? \"\";\n\t\t\t} catch (err) {\n\t\t\t\treturn c.json({ error: (err as Error).message }, 400);\n\t\t\t}\n\t\t\tif (defaultSessionId.startsWith(\"msg_\")) {\n\t\t\t\treturn c.json({ error: \"invalid session id\" }, 400);\n\t\t\t}\n\n\t\t\tlet inserted = 0;\n\t\t\tconst lastSeenBySession = new Map<string, number>();\n\t\t\tconst metaBySession = new Map<string, Record<string, string>>();\n\t\t\tconst sessionIds = new Set<string>();\n\t\t\tconst batchBySession = new Map<string, Record<string, unknown>[]>();\n\n\t\t\tfor (const item of items) {\n\t\t\t\tif (item == null || typeof item !== \"object\" || Array.isArray(item)) {\n\t\t\t\t\treturn c.json({ error: \"event must be an object\" }, 400);\n\t\t\t\t}\n\t\t\t\tconst itemObj = item as Record<string, unknown>;\n\n\t\t\t\tlet itemSessionId: string | null;\n\t\t\t\ttry {\n\t\t\t\t\titemSessionId = resolveSessionStreamId(itemObj);\n\t\t\t\t} catch (err) {\n\t\t\t\t\treturn c.json({ error: (err as Error).message }, 400);\n\t\t\t\t}\n\t\t\t\tconst opencodeSessionId = String(itemSessionId ?? defaultSessionId ?? \"\");\n\t\t\t\tif (!opencodeSessionId) {\n\t\t\t\t\treturn c.json({ error: \"session id required\" }, 400);\n\t\t\t\t}\n\t\t\t\tif (opencodeSessionId.startsWith(\"msg_\")) {\n\t\t\t\t\treturn c.json({ error: \"invalid session id\" }, 400);\n\t\t\t\t}\n\n\t\t\t\tlet eventId = String(itemObj.event_id ?? \"\");\n\t\t\t\tconst eventType = String(itemObj.event_type ?? \"\");\n\t\t\t\tif (!eventType) {\n\t\t\t\t\treturn c.json({ error: \"event_type required\" }, 400);\n\t\t\t\t}\n\n\t\t\t\tconst eventSeqValue = itemObj.event_seq;\n\t\t\t\tif (eventSeqValue != null) {\n\t\t\t\t\tconst parsed = Number(eventSeqValue);\n\t\t\t\t\tif (!Number.isFinite(parsed) || parsed !== Math.floor(parsed)) {\n\t\t\t\t\t\treturn c.json({ error: \"event_seq must be int\" }, 400);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet tsWallMs = itemObj.ts_wall_ms;\n\t\t\t\tif (tsWallMs != null) {\n\t\t\t\t\tconst parsed = Number(tsWallMs);\n\t\t\t\t\tif (!Number.isFinite(parsed)) {\n\t\t\t\t\t\treturn c.json({ error: \"ts_wall_ms must be int\" }, 400);\n\t\t\t\t\t}\n\t\t\t\t\ttsWallMs = Math.floor(parsed);\n\t\t\t\t\tconst prev = lastSeenBySession.get(opencodeSessionId) ?? (tsWallMs as number);\n\t\t\t\t\tlastSeenBySession.set(opencodeSessionId, Math.max(prev, tsWallMs as number));\n\t\t\t\t}\n\n\t\t\t\tlet tsMonoMs = itemObj.ts_mono_ms;\n\t\t\t\tif (tsMonoMs != null) {\n\t\t\t\t\tconst parsed = Number(tsMonoMs);\n\t\t\t\t\tif (!Number.isFinite(parsed)) {\n\t\t\t\t\t\treturn c.json({ error: \"ts_mono_ms must be number\" }, 400);\n\t\t\t\t\t}\n\t\t\t\t\ttsMonoMs = parsed;\n\t\t\t\t}\n\n\t\t\t\tlet eventPayload = itemObj.payload;\n\t\t\t\tif (eventPayload == null) eventPayload = {};\n\t\t\t\tif (typeof eventPayload !== \"object\" || Array.isArray(eventPayload)) {\n\t\t\t\t\treturn c.json({ error: \"payload must be an object\" }, 400);\n\t\t\t\t}\n\n\t\t\t\t// Per-item meta fields\n\t\t\t\tconst itemCwd = itemObj.cwd;\n\t\t\t\tif (itemCwd != null && typeof itemCwd !== \"string\") {\n\t\t\t\t\treturn c.json({ error: \"cwd must be string\" }, 400);\n\t\t\t\t}\n\t\t\t\tconst itemProject = itemObj.project;\n\t\t\t\tif (itemProject != null && typeof itemProject !== \"string\") {\n\t\t\t\t\treturn c.json({ error: \"project must be string\" }, 400);\n\t\t\t\t}\n\t\t\t\tconst itemStartedAt = itemObj.started_at;\n\t\t\t\tif (itemStartedAt != null && typeof itemStartedAt !== \"string\") {\n\t\t\t\t\treturn c.json({ error: \"started_at must be string\" }, 400);\n\t\t\t\t}\n\n\t\t\t\t// Sanitize payload\n\t\t\t\teventPayload = stripPrivateObj(eventPayload) as Record<string, unknown>;\n\n\t\t\t\t// Generate stable event_id for legacy senders.\n\t\t\t\t// Python uses json.dumps(sort_keys=True) which recursively sorts all keys.\n\t\t\t\t// We replicate with a recursive key-sorting replacer.\n\t\t\t\tif (!eventId) {\n\t\t\t\t\tconst sortedStringify = (obj: unknown): string =>\n\t\t\t\t\t\tJSON.stringify(obj, (_key, value) => {\n\t\t\t\t\t\t\tif (value != null && typeof value === \"object\" && !Array.isArray(value)) {\n\t\t\t\t\t\t\t\tconst sorted: Record<string, unknown> = {};\n\t\t\t\t\t\t\t\tfor (const k of Object.keys(value as Record<string, unknown>).sort()) {\n\t\t\t\t\t\t\t\t\tsorted[k] = (value as Record<string, unknown>)[k];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn sorted;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn value;\n\t\t\t\t\t\t});\n\t\t\t\t\tif (eventSeqValue != null) {\n\t\t\t\t\t\tconst rawId = sortedStringify({\n\t\t\t\t\t\t\ts: eventSeqValue,\n\t\t\t\t\t\t\tt: eventType,\n\t\t\t\t\t\t\tp: eventPayload,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst hash = createHash(\"sha256\").update(rawId, \"utf-8\").digest(\"hex\").slice(0, 16);\n\t\t\t\t\t\teventId = `legacy-seq-${eventSeqValue}-${hash}`;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst rawId = sortedStringify({\n\t\t\t\t\t\t\tm: tsMonoMs ?? null,\n\t\t\t\t\t\t\tp: eventPayload,\n\t\t\t\t\t\t\tt: eventType,\n\t\t\t\t\t\t\tw: tsWallMs ?? null,\n\t\t\t\t\t\t});\n\t\t\t\t\t\teventId = `legacy-${createHash(\"sha256\").update(rawId, \"utf-8\").digest(\"hex\").slice(0, 16)}`;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst eventEntry: Record<string, unknown> = {\n\t\t\t\t\tevent_id: eventId,\n\t\t\t\t\tevent_type: eventType,\n\t\t\t\t\tpayload: eventPayload,\n\t\t\t\t\tts_wall_ms: tsWallMs ?? null,\n\t\t\t\t\tts_mono_ms: tsMonoMs ?? null,\n\t\t\t\t};\n\n\t\t\t\tsessionIds.add(opencodeSessionId);\n\t\t\t\tconst list = batchBySession.get(opencodeSessionId) ?? [];\n\t\t\t\tlist.push({ ...eventEntry });\n\t\t\t\tbatchBySession.set(opencodeSessionId, list);\n\n\t\t\t\tif (itemCwd || itemProject || itemStartedAt) {\n\t\t\t\t\tconst perSession = metaBySession.get(opencodeSessionId) ?? {};\n\t\t\t\t\tif (itemCwd) perSession.cwd = itemCwd as string;\n\t\t\t\t\tif (itemProject) perSession.project = itemProject as string;\n\t\t\t\t\tif (itemStartedAt) perSession.started_at = itemStartedAt as string;\n\t\t\t\t\tmetaBySession.set(opencodeSessionId, perSession);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Insert events\n\t\t\tif (sessionIds.size === 1) {\n\t\t\t\tconst singleSessionId = sessionIds.values().next().value as string;\n\t\t\t\tconst batch = batchBySession.get(singleSessionId) ?? [];\n\t\t\t\tconst result = store.recordRawEventsBatch(singleSessionId, batch);\n\t\t\t\tinserted = result.inserted;\n\t\t\t} else {\n\t\t\t\tfor (const [sid, sidEvents] of batchBySession) {\n\t\t\t\t\tconst result = store.recordRawEventsBatch(sid, sidEvents);\n\t\t\t\t\tinserted += result.inserted;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Update session metadata\n\t\t\tfor (const metaSessionId of sessionIds) {\n\t\t\t\tconst sessionMeta = metaBySession.get(metaSessionId) ?? {};\n\t\t\t\tconst applyRequestMeta = sessionIds.size === 1 || metaSessionId === defaultSessionId;\n\t\t\t\tstore.updateRawEventSessionMeta({\n\t\t\t\t\topencodeSessionId: metaSessionId,\n\t\t\t\t\tcwd:\n\t\t\t\t\t\tsessionMeta.cwd ?? (applyRequestMeta ? (cwd as string | undefined) : undefined) ?? null,\n\t\t\t\t\tproject:\n\t\t\t\t\t\tsessionMeta.project ??\n\t\t\t\t\t\t(applyRequestMeta ? (project as string | undefined) : undefined) ??\n\t\t\t\t\t\tnull,\n\t\t\t\t\tstartedAt:\n\t\t\t\t\t\tsessionMeta.started_at ??\n\t\t\t\t\t\t(applyRequestMeta ? (startedAt as string | undefined) : undefined) ??\n\t\t\t\t\t\tnull,\n\t\t\t\t\tlastSeenTsWallMs: lastSeenBySession.get(metaSessionId) ?? null,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Note per-session activity for optional debounced auto-flush.\n\t\t\tnudgeSweeper(sweeper, sessionIds);\n\n\t\t\treturn c.json({ inserted, received: (items as unknown[]).length });\n\t\t} catch (err) {\n\t\t\tconst response: Record<string, unknown> = { error: \"internal server error\" };\n\t\t\tif (process.env.CODEMEM_VIEWER_DEBUG === \"1\") {\n\t\t\t\tresponse.detail = (err as Error).message;\n\t\t\t}\n\t\t\treturn c.json(response, 500);\n\t\t}\n\t});\n\n\t// POST /api/claude-hooks — ingest Claude Code hook events\n\tapp.post(\"/api/claude-hooks\", async (c) => {\n\t\tconst result = await parseJsonObjectBody(c, MAX_RAW_EVENTS_BODY_BYTES);\n\t\tif (result instanceof Response) return result;\n\t\tconst payload = result;\n\n\t\t// Map hook payload → raw event envelope\n\t\tconst envelope = buildRawEventEnvelopeFromHook(payload);\n\t\tif (envelope === null) {\n\t\t\t// Unsupported event type or missing required fields — skip gracefully\n\t\t\treturn c.json({ inserted: 0, skipped: 1 });\n\t\t}\n\n\t\tconst store = getStore();\n\t\ttry {\n\t\t\tconst opencodeSessionId = envelope.opencode_session_id;\n\t\t\tconst source = envelope.source;\n\t\t\tconst strippedPayload = stripPrivateObj(envelope.payload) as Record<string, unknown>;\n\n\t\t\tconst inserted = store.recordRawEvent({\n\t\t\t\topencodeSessionId,\n\t\t\t\tsource,\n\t\t\t\teventId: envelope.event_id,\n\t\t\t\teventType: \"claude.hook\",\n\t\t\t\tpayload: strippedPayload,\n\t\t\t\ttsWallMs: envelope.ts_wall_ms,\n\t\t\t});\n\n\t\t\tstore.updateRawEventSessionMeta({\n\t\t\t\topencodeSessionId,\n\t\t\t\tsource,\n\t\t\t\tcwd: envelope.cwd,\n\t\t\t\tproject: envelope.project,\n\t\t\t\tstartedAt: envelope.started_at,\n\t\t\t\tlastSeenTsWallMs: envelope.ts_wall_ms,\n\t\t\t});\n\n\t\t\t// Note activity for optional debounced auto-flush.\n\t\t\tnudgeSweeper(sweeper, [opencodeSessionId], source);\n\n\t\t\treturn c.json({ inserted: inserted ? 1 : 0, skipped: 0 });\n\t\t} catch (err) {\n\t\t\tconst response: Record<string, unknown> = { error: \"internal server error\" };\n\t\t\tif (process.env.CODEMEM_VIEWER_DEBUG === \"1\") {\n\t\t\t\tresponse.detail = (err as Error).message;\n\t\t\t}\n\t\t\treturn c.json(response, 500);\n\t\t}\n\t});\n\n\treturn app;\n}\n","/**\n * Stats routes — GET /api/stats, GET /api/usage.\n *\n * Ports Python's viewer_routes/stats.py.\n */\n\nimport type { MemoryStore } from \"@codemem/core\";\nimport { Hono } from \"hono\";\n\n/**\n * Create stats routes. The store factory is called per-request to get a\n * fresh connection (matching the Python viewer pattern).\n */\nexport function statsRoutes(getStore: () => MemoryStore) {\n\tconst app = new Hono();\n\n\tapp.get(\"/api/stats\", (c) => {\n\t\tconst store = getStore();\n\t\treturn c.json(store.stats());\n\t});\n\n\tapp.get(\"/api/usage\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst projectFilter = c.req.query(\"project\") || null;\n\t\t\tconst eventsGlobal = store.db\n\t\t\t\t.prepare(\n\t\t\t\t\t`SELECT event,\n\t\t\t\t\t\tSUM(tokens_read) AS total_tokens_read,\n\t\t\t\t\t\tSUM(tokens_written) AS total_tokens_written,\n\t\t\t\t\t\tSUM(tokens_saved) AS total_tokens_saved,\n\t\t\t\t\t\tCOUNT(*) AS count\n\t\t\t\t\t FROM usage_events GROUP BY event ORDER BY event`,\n\t\t\t\t)\n\t\t\t\t.all() as Record<string, unknown>[];\n\t\t\tconst totalsGlobal = store.db\n\t\t\t\t.prepare(\n\t\t\t\t\t`SELECT COALESCE(SUM(tokens_read),0) AS tokens_read,\n\t\t\t\t\t\tCOALESCE(SUM(tokens_written),0) AS tokens_written,\n\t\t\t\t\t\tCOALESCE(SUM(tokens_saved),0) AS tokens_saved,\n\t\t\t\t\t\tCOUNT(*) AS count\n\t\t\t\t\t FROM usage_events`,\n\t\t\t\t)\n\t\t\t\t.get() as Record<string, unknown>;\n\t\t\tlet eventsFiltered: Record<string, unknown>[] | null = null;\n\t\t\tlet totalsFiltered: Record<string, unknown> | null = null;\n\t\t\tif (projectFilter) {\n\t\t\t\teventsFiltered = store.db\n\t\t\t\t\t.prepare(\n\t\t\t\t\t\t`SELECT event,\n\t\t\t\t\t\t\tSUM(tokens_read) AS total_tokens_read,\n\t\t\t\t\t\t\tSUM(tokens_written) AS total_tokens_written,\n\t\t\t\t\t\t\tSUM(tokens_saved) AS total_tokens_saved,\n\t\t\t\t\t\t\tCOUNT(*) AS count\n\t\t\t\t\t\t FROM usage_events\n\t\t\t\t\t\t JOIN sessions ON sessions.id = usage_events.session_id\n\t\t\t\t\t\t WHERE sessions.project = ?\n\t\t\t\t\t\t GROUP BY event ORDER BY event`,\n\t\t\t\t\t)\n\t\t\t\t\t.all(projectFilter) as Record<string, unknown>[];\n\t\t\t\ttotalsFiltered = store.db\n\t\t\t\t\t.prepare(\n\t\t\t\t\t\t`SELECT COALESCE(SUM(tokens_read),0) AS tokens_read,\n\t\t\t\t\t\t\tCOALESCE(SUM(tokens_written),0) AS tokens_written,\n\t\t\t\t\t\t\tCOALESCE(SUM(tokens_saved),0) AS tokens_saved,\n\t\t\t\t\t\t\tCOUNT(*) AS count\n\t\t\t\t\t\t FROM usage_events\n\t\t\t\t\t\t JOIN sessions ON sessions.id = usage_events.session_id\n\t\t\t\t\t\t WHERE sessions.project = ?`,\n\t\t\t\t\t)\n\t\t\t\t\t.get(projectFilter) as Record<string, unknown>;\n\t\t\t}\n\t\t\treturn c.json({\n\t\t\t\tproject: projectFilter,\n\t\t\t\tevents: projectFilter ? eventsFiltered : eventsGlobal,\n\t\t\t\ttotals: projectFilter ? totalsFiltered : totalsGlobal,\n\t\t\t\tevents_global: eventsGlobal,\n\t\t\t\ttotals_global: totalsGlobal,\n\t\t\t\tevents_filtered: eventsFiltered,\n\t\t\t\ttotals_filtered: totalsFiltered,\n\t\t\t\trecent_packs: [],\n\t\t\t});\n\t\t}\n\t});\n\n\treturn app;\n}\n","/**\n * Sync routes — status, peers, actors, attempts, pairing, mutations.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport net from \"node:net\";\nimport { dirname, join } from \"node:path\";\nimport type { MemoryStore, ReplicationOp } from \"@codemem/core\";\nimport {\n\tapplyReplicationOps,\n\tcleanupNonces,\n\tcoordinatorCreateInviteAction,\n\tcoordinatorImportInviteAction,\n\tcoordinatorReviewJoinRequestAction,\n\tcoordinatorStatusSnapshot,\n\tDEFAULT_TIME_WINDOW_S,\n\tensureDeviceIdentity,\n\textractReplicationOps,\n\tfingerprintPublicKey,\n\tlistCoordinatorJoinRequests,\n\tloadReplicationOpsSince,\n\treadCoordinatorSyncConfig,\n\trecordNonce,\n\tschema,\n\tverifySignature,\n} from \"@codemem/core\";\nimport { count, desc, eq, max, ne } from \"drizzle-orm\";\nimport { drizzle } from \"drizzle-orm/better-sqlite3\";\nimport { Hono } from \"hono\";\nimport { queryBool, queryInt, safeJsonList } from \"../helpers.js\";\n\ntype StoreFactory = () => MemoryStore;\n\nconst SYNC_STALE_AFTER_SECONDS = 10 * 60;\nconst SYNC_PROTOCOL_VERSION = \"1\";\n\nfunction intEnvOr(name: string, fallback: number): number {\n\tconst value = Number.parseInt(process.env[name] ?? \"\", 10);\n\treturn Number.isFinite(value) ? value : fallback;\n}\n\nconst MAX_SYNC_BODY_BYTES = intEnvOr(\"CODEMEM_SYNC_MAX_BODY_BYTES\", 1_048_576);\nconst MAX_SYNC_OPS = intEnvOr(\"CODEMEM_SYNC_MAX_OPS\", 2000);\n\nconst PAIRING_FILTER_HINT =\n\t\"Run this on another device with codemem sync pair --accept '<payload>'. \" +\n\t\"On the accepting device, --include/--exclude control both what it sends and what it accepts from that peer.\";\n\nfunction pathWithQuery(url: string): string {\n\tconst parsed = new URL(url);\n\treturn parsed.search ? `${parsed.pathname}${parsed.search}` : parsed.pathname;\n}\n\nfunction unauthorizedPayload(reason: string): Record<string, string> {\n\tif (process.env.CODEMEM_SYNC_AUTH_DIAGNOSTICS === \"1\") {\n\t\treturn { error: \"unauthorized\", reason };\n\t}\n\treturn { error: \"unauthorized\" };\n}\n\nfunction authorizeSyncRequest(\n\tstore: MemoryStore,\n\trequest: { method: string; url: string; header(name: string): string | undefined },\n\tbody: Buffer,\n): { ok: boolean; reason: string; deviceId: string } {\n\tconst deviceId = (request.header(\"X-Opencode-Device\") ?? \"\").trim();\n\tconst signature = request.header(\"X-Opencode-Signature\") ?? \"\";\n\tconst timestamp = request.header(\"X-Opencode-Timestamp\") ?? \"\";\n\tconst nonce = request.header(\"X-Opencode-Nonce\") ?? \"\";\n\tif (!deviceId || !signature || !timestamp || !nonce) {\n\t\treturn { ok: false, reason: \"missing_headers\", deviceId };\n\t}\n\n\tconst peerRow = store.db\n\t\t.prepare(\n\t\t\t\"SELECT pinned_fingerprint, public_key FROM sync_peers WHERE peer_device_id = ? LIMIT 1\",\n\t\t)\n\t\t.get(deviceId) as { pinned_fingerprint: string | null; public_key: string | null } | undefined;\n\tif (!peerRow) {\n\t\treturn { ok: false, reason: \"unknown_peer\", deviceId };\n\t}\n\n\tconst pinnedFingerprint = String(peerRow.pinned_fingerprint ?? \"\").trim();\n\tconst publicKey = String(peerRow.public_key ?? \"\").trim();\n\tif (!pinnedFingerprint || !publicKey) {\n\t\treturn { ok: false, reason: \"peer_record_incomplete\", deviceId };\n\t}\n\tif (fingerprintPublicKey(publicKey) !== pinnedFingerprint) {\n\t\treturn { ok: false, reason: \"fingerprint_mismatch\", deviceId };\n\t}\n\n\tlet valid = false;\n\ttry {\n\t\tvalid = verifySignature({\n\t\t\tmethod: request.method,\n\t\t\tpathWithQuery: pathWithQuery(request.url),\n\t\t\tbodyBytes: body,\n\t\t\ttimestamp,\n\t\t\tnonce,\n\t\t\tsignature,\n\t\t\tpublicKey,\n\t\t\tdeviceId,\n\t\t});\n\t} catch {\n\t\treturn { ok: false, reason: \"signature_verification_error\", deviceId };\n\t}\n\n\tif (!valid) {\n\t\treturn { ok: false, reason: \"invalid_signature\", deviceId };\n\t}\n\n\tconst createdAt = new Date().toISOString();\n\tif (!recordNonce(store.db, deviceId, nonce, createdAt)) {\n\t\treturn { ok: false, reason: \"nonce_replay\", deviceId };\n\t}\n\n\tconst cutoff = new Date(Date.now() - DEFAULT_TIME_WINDOW_S * 2 * 1000).toISOString();\n\tcleanupNonces(store.db, cutoff);\n\treturn { ok: true, reason: \"ok\", deviceId };\n}\n\nfunction projectBasename(value: string | null | undefined): string {\n\tconst project = String(value ?? \"\")\n\t\t.trim()\n\t\t.replaceAll(\"\\\\\", \"/\");\n\tif (!project) return \"\";\n\tconst parts = project.split(\"/\").filter(Boolean);\n\treturn parts.length > 0 ? (parts[parts.length - 1] ?? \"\") : \"\";\n}\n\nfunction parseJsonList(value: unknown): string[] {\n\tif (value == null) return [];\n\tif (typeof value === \"string\") {\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(value) as unknown;\n\t\t\tif (!Array.isArray(parsed)) return [];\n\t\t\treturn parsed.map((entry) => String(entry ?? \"\").trim()).filter(Boolean);\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t}\n\tif (!Array.isArray(value)) return [];\n\treturn value.map((entry) => String(entry ?? \"\").trim()).filter(Boolean);\n}\n\nfunction readPeerProjectFilters(\n\tstore: MemoryStore,\n\tpeerDeviceId: string,\n): { include: string[]; exclude: string[] } {\n\tconst globalConfig = readCoordinatorSyncConfig();\n\tconst row = store.db\n\t\t.prepare(\n\t\t\t\"SELECT projects_include_json, projects_exclude_json FROM sync_peers WHERE peer_device_id = ? LIMIT 1\",\n\t\t)\n\t\t.get(peerDeviceId) as\n\t\t| { projects_include_json: string | null; projects_exclude_json: string | null }\n\t\t| undefined;\n\tif (!row) {\n\t\treturn {\n\t\t\tinclude: globalConfig.syncProjectsInclude,\n\t\t\texclude: globalConfig.syncProjectsExclude,\n\t\t};\n\t}\n\tconst hasOverride = row.projects_include_json != null || row.projects_exclude_json != null;\n\tif (!hasOverride) {\n\t\treturn {\n\t\t\tinclude: globalConfig.syncProjectsInclude,\n\t\t\texclude: globalConfig.syncProjectsExclude,\n\t\t};\n\t}\n\treturn {\n\t\tinclude: parseJsonList(row.projects_include_json),\n\t\texclude: parseJsonList(row.projects_exclude_json),\n\t};\n}\n\nfunction peerClaimedLocalActor(store: MemoryStore, peerDeviceId: string): boolean {\n\tconst row = store.db\n\t\t.prepare(\"SELECT claimed_local_actor FROM sync_peers WHERE peer_device_id = ? LIMIT 1\")\n\t\t.get(peerDeviceId) as { claimed_local_actor: number | null } | undefined;\n\treturn Boolean(row?.claimed_local_actor);\n}\n\nfunction parseOpPayload(op: { payload_json: string | null }): Record<string, unknown> | null {\n\tif (!op.payload_json || !String(op.payload_json).trim()) return null;\n\ttry {\n\t\tconst parsed = JSON.parse(op.payload_json) as unknown;\n\t\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) return null;\n\t\treturn parsed as Record<string, unknown>;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction isSharedVisibility(payload: Record<string, unknown> | null): boolean {\n\tif (!payload) return false;\n\tlet visibility = String(payload.visibility ?? \"\")\n\t\t.trim()\n\t\t.toLowerCase();\n\tconst metadata =\n\t\tpayload.metadata_json &&\n\t\ttypeof payload.metadata_json === \"object\" &&\n\t\t!Array.isArray(payload.metadata_json)\n\t\t\t? (payload.metadata_json as Record<string, unknown>)\n\t\t\t: {};\n\tconst metadataVisibility = String(metadata.visibility ?? \"\")\n\t\t.trim()\n\t\t.toLowerCase();\n\tif (!visibility && metadataVisibility) visibility = metadataVisibility;\n\tif (!visibility) {\n\t\tlet workspaceKind = String(payload.workspace_kind ?? \"\")\n\t\t\t.trim()\n\t\t\t.toLowerCase();\n\t\tlet workspaceId = String(payload.workspace_id ?? \"\")\n\t\t\t.trim()\n\t\t\t.toLowerCase();\n\t\tif (!workspaceKind)\n\t\t\tworkspaceKind = String(metadata.workspace_kind ?? \"\")\n\t\t\t\t.trim()\n\t\t\t\t.toLowerCase();\n\t\tif (!workspaceId)\n\t\t\tworkspaceId = String(metadata.workspace_id ?? \"\")\n\t\t\t\t.trim()\n\t\t\t\t.toLowerCase();\n\t\tif (workspaceKind === \"shared\" || workspaceId.startsWith(\"shared:\")) {\n\t\t\tvisibility = \"shared\";\n\t\t} else {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn visibility === \"shared\";\n}\n\nfunction projectAllowed(\n\tprojectValue: string | null,\n\tfilters: { include: string[]; exclude: string[] },\n): boolean {\n\tconst value = String(projectValue ?? \"\").trim();\n\tconst valueBase = projectBasename(value);\n\tfor (const blocked of filters.exclude) {\n\t\tif (blocked === value || blocked === valueBase) return false;\n\t}\n\tif (filters.include.length === 0) return true;\n\tfor (const allowed of filters.include) {\n\t\tif (allowed === value || allowed === valueBase) return true;\n\t}\n\treturn false;\n}\n\nfunction filterOpsForPeer(\n\tstore: MemoryStore,\n\tpeerDeviceId: string,\n\tops: ReplicationOp[],\n): { allowed: ReplicationOp[]; skipped: number } {\n\tconst filters = readPeerProjectFilters(store, peerDeviceId);\n\tconst allowPrivate = peerClaimedLocalActor(store, peerDeviceId);\n\tconst allowed: ReplicationOp[] = [];\n\tlet skipped = 0;\n\tfor (const op of ops) {\n\t\tif (op.entity_type !== \"memory_item\") {\n\t\t\tallowed.push(op);\n\t\t\tcontinue;\n\t\t}\n\t\tconst payload = parseOpPayload(op);\n\t\tif (!allowPrivate && !isSharedVisibility(payload)) {\n\t\t\tskipped++;\n\t\t\tcontinue;\n\t\t}\n\t\tconst project = payload && typeof payload.project === \"string\" ? payload.project : null;\n\t\tif (!projectAllowed(project, filters)) {\n\t\t\tskipped++;\n\t\t\tcontinue;\n\t\t}\n\t\tallowed.push(op);\n\t}\n\treturn { allowed, skipped };\n}\n\n// ---------------------------------------------------------------------------\n// Peer row mapping — deduplicated helper (fix #4)\n// ---------------------------------------------------------------------------\n\n/**\n * Map a raw sync_peers DB row to the API response shape.\n * When showDiag is false, sensitive fields (fingerprint, last_error, addresses)\n * are redacted.\n */\nfunction mapPeerRow(row: Record<string, unknown>, showDiag: boolean): Record<string, unknown> {\n\treturn {\n\t\tpeer_device_id: row.peer_device_id,\n\t\tname: row.name,\n\t\tfingerprint: showDiag ? row.pinned_fingerprint : null,\n\t\tpinned: Boolean(row.pinned_fingerprint),\n\t\taddresses: showDiag ? safeJsonList(row.addresses_json as string | null) : [],\n\t\tlast_seen_at: row.last_seen_at,\n\t\tlast_sync_at: row.last_sync_at,\n\t\tlast_error: showDiag ? row.last_error : null,\n\t\thas_error: Boolean(row.last_error),\n\t\tclaimed_local_actor: Boolean(row.claimed_local_actor),\n\t\tactor_id: row.actor_id ?? null,\n\t\tactor_display_name: row.actor_display_name ?? null,\n\t\tproject_scope: {\n\t\t\tinclude: safeJsonList(row.projects_include_json as string | null),\n\t\t\texclude: safeJsonList(row.projects_exclude_json as string | null),\n\t\t\teffective_include: safeJsonList(row.projects_include_json as string | null),\n\t\t\teffective_exclude: safeJsonList(row.projects_exclude_json as string | null),\n\t\t\tinherits_global: row.projects_include_json == null && row.projects_exclude_json == null,\n\t\t},\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// Peer status helpers\n// ---------------------------------------------------------------------------\n\nfunction isRecentIso(value: unknown, windowS = SYNC_STALE_AFTER_SECONDS): boolean {\n\tconst raw = String(value ?? \"\").trim();\n\tif (!raw) return false;\n\tconst normalized = raw.replace(\"Z\", \"+00:00\");\n\tconst hasOffset = /(?:Z|[+-]\\d{2}:?\\d{2})$/i.test(raw);\n\tconst ts = new Date(hasOffset ? normalized : `${normalized}+00:00`);\n\tif (Number.isNaN(ts.getTime())) return false;\n\tconst ageS = (Date.now() - ts.getTime()) / 1000;\n\treturn ageS >= 0 && ageS <= windowS;\n}\n\nfunction peerStatus(peer: Record<string, unknown>): Record<string, unknown> {\n\tconst lastSyncAt = peer.last_sync_at;\n\tconst lastPingAt = peer.last_seen_at;\n\tconst hasError = Boolean(peer.has_error);\n\n\tconst syncFresh = isRecentIso(lastSyncAt);\n\tconst pingFresh = isRecentIso(lastPingAt);\n\n\tlet peerState: string;\n\tif (hasError && !(syncFresh || pingFresh)) peerState = \"offline\";\n\telse if (hasError) peerState = \"degraded\";\n\telse if (syncFresh || pingFresh) peerState = \"online\";\n\telse if (lastSyncAt || lastPingAt) peerState = \"stale\";\n\telse peerState = \"unknown\";\n\n\tconst syncStatus = hasError ? \"error\" : syncFresh ? \"ok\" : lastSyncAt ? \"stale\" : \"unknown\";\n\tconst pingStatus = pingFresh ? \"ok\" : lastPingAt ? \"stale\" : \"unknown\";\n\n\treturn {\n\t\tsync_status: syncStatus,\n\t\tping_status: pingStatus,\n\t\tpeer_state: peerState,\n\t\tfresh: syncFresh || pingFresh,\n\t\tlast_sync_at: lastSyncAt,\n\t\tlast_ping_at: lastPingAt,\n\t};\n}\n\nfunction attemptStatus(attempt: Record<string, unknown>): string {\n\tif (attempt.ok) return \"ok\";\n\tif (attempt.error) return \"error\";\n\treturn \"unknown\";\n}\n\nfunction readViewerBinding(dbPath: string): { host: string; port: number } | null {\n\ttry {\n\t\tconst raw = readFileSync(join(dirname(dbPath), \"viewer.pid\"), \"utf8\");\n\t\tconst parsed = JSON.parse(raw) as Partial<{ host: string; port: number }>;\n\t\tif (typeof parsed.host === \"string\" && typeof parsed.port === \"number\") {\n\t\t\treturn { host: parsed.host, port: parsed.port };\n\t\t}\n\t} catch {\n\t\t// ignore missing/malformed pidfile\n\t}\n\treturn null;\n}\n\nasync function portOpen(host: string, port: number): Promise<boolean> {\n\treturn new Promise((resolve) => {\n\t\tconst socket = net.createConnection({ host, port });\n\t\tconst done = (ok: boolean) => {\n\t\t\tsocket.removeAllListeners();\n\t\t\tsocket.destroy();\n\t\t\tresolve(ok);\n\t\t};\n\t\tsocket.setTimeout(300);\n\t\tsocket.once(\"connect\", () => done(true));\n\t\tsocket.once(\"timeout\", () => done(false));\n\t\tsocket.once(\"error\", () => done(false));\n\t});\n}\n\nconst PEERS_QUERY = `\n\tSELECT p.peer_device_id, p.name, p.pinned_fingerprint, p.addresses_json,\n\t p.last_seen_at, p.last_sync_at, p.last_error,\n\t p.projects_include_json, p.projects_exclude_json, p.claimed_local_actor,\n\t p.actor_id, a.display_name AS actor_display_name\n\tFROM sync_peers AS p\n\tLEFT JOIN actors AS a ON a.actor_id = p.actor_id\n\tORDER BY name, peer_device_id\n`;\n\n// ---------------------------------------------------------------------------\n// Route factory\n// ---------------------------------------------------------------------------\n\nexport function syncRoutes(getStore: StoreFactory) {\n\tconst app = new Hono();\n\n\t// GET /v1/status (peer sync protocol)\n\tapp.get(\"/v1/status\", (c) => {\n\t\tconst store = getStore();\n\t\tconst auth = authorizeSyncRequest(store, c.req, Buffer.alloc(0));\n\t\tif (!auth.ok) return c.json(unauthorizedPayload(auth.reason), 401);\n\n\t\ttry {\n\t\t\tlet device = store.db\n\t\t\t\t.prepare(\"SELECT device_id, fingerprint FROM sync_device LIMIT 1\")\n\t\t\t\t.get() as { device_id: string; fingerprint: string } | undefined;\n\t\t\tif (!device) {\n\t\t\t\tconst [deviceId, fingerprint] = ensureDeviceIdentity(store.db);\n\t\t\t\tdevice = { device_id: deviceId, fingerprint };\n\t\t\t}\n\t\t\treturn c.json({\n\t\t\t\tdevice_id: device.device_id,\n\t\t\t\tprotocol_version: SYNC_PROTOCOL_VERSION,\n\t\t\t\tfingerprint: device.fingerprint,\n\t\t\t});\n\t\t} catch {\n\t\t\treturn c.json({ error: \"internal_error\" }, 500);\n\t\t}\n\t});\n\n\t// GET /v1/ops (peer sync protocol)\n\tapp.get(\"/v1/ops\", (c) => {\n\t\tconst store = getStore();\n\t\tconst auth = authorizeSyncRequest(store, c.req, Buffer.alloc(0));\n\t\tif (!auth.ok) return c.json(unauthorizedPayload(auth.reason), 401);\n\t\tconst peerDeviceId = auth.deviceId;\n\n\t\ttry {\n\t\t\tconst since = c.req.query(\"since\") ?? null;\n\t\t\tconst rawLimit = Number.parseInt(c.req.query(\"limit\") ?? \"200\", 10);\n\t\t\tconst limit = Number.isFinite(rawLimit) ? Math.max(1, Math.min(rawLimit, 1000)) : 200;\n\t\t\tlet localDeviceId = store.db.prepare(\"SELECT device_id FROM sync_device LIMIT 1\").get() as\n\t\t\t\t| { device_id: string }\n\t\t\t\t| undefined;\n\t\t\tif (!localDeviceId) {\n\t\t\t\tconst [deviceId] = ensureDeviceIdentity(store.db);\n\t\t\t\tlocalDeviceId = { device_id: deviceId };\n\t\t\t}\n\t\t\tconst [ops, nextCursor] = loadReplicationOpsSince(\n\t\t\t\tstore.db,\n\t\t\t\tsince,\n\t\t\t\tlimit,\n\t\t\t\tlocalDeviceId.device_id,\n\t\t\t);\n\t\t\tconst filtered = filterOpsForPeer(store, peerDeviceId, ops);\n\t\t\treturn c.json({\n\t\t\t\tops: filtered.allowed,\n\t\t\t\tnext_cursor: nextCursor,\n\t\t\t\tskipped: filtered.skipped,\n\t\t\t});\n\t\t} catch {\n\t\t\treturn c.json({ error: \"internal_error\" }, 500);\n\t\t}\n\t});\n\n\t// POST /v1/ops (peer sync protocol)\n\tapp.post(\"/v1/ops\", async (c) => {\n\t\tconst store = getStore();\n\t\tconst raw = Buffer.from(await c.req.arrayBuffer());\n\t\tif (raw.length > MAX_SYNC_BODY_BYTES) {\n\t\t\treturn c.json({ error: \"payload_too_large\" }, 413);\n\t\t}\n\n\t\tconst auth = authorizeSyncRequest(store, c.req, raw);\n\t\tif (!auth.ok) return c.json(unauthorizedPayload(auth.reason), 401);\n\t\tconst peerDeviceId = auth.deviceId;\n\n\t\tlet body: Record<string, unknown>;\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(raw.toString(\"utf-8\")) as unknown;\n\t\t\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n\t\t\t\treturn c.json({ error: \"invalid_json\" }, 400);\n\t\t\t}\n\t\t\tbody = parsed as Record<string, unknown>;\n\t\t} catch {\n\t\t\treturn c.json({ error: \"invalid_json\" }, 400);\n\t\t}\n\n\t\tif (!Array.isArray(body.ops)) {\n\t\t\treturn c.json({ error: \"invalid_ops\" }, 400);\n\t\t}\n\t\tif (body.ops.length > MAX_SYNC_OPS) {\n\t\t\treturn c.json({ error: \"too_many_ops\" }, 413);\n\t\t}\n\n\t\tconst normalizedOps = extractReplicationOps(body);\n\t\tfor (const op of normalizedOps) {\n\t\t\tif (op.device_id !== peerDeviceId || op.clock_device_id !== peerDeviceId) {\n\t\t\t\treturn c.json(\n\t\t\t\t\t{\n\t\t\t\t\t\terror: \"invalid_op_device\",\n\t\t\t\t\t\treason: \"device_id_mismatch\",\n\t\t\t\t\t\top_id: op.op_id,\n\t\t\t\t\t},\n\t\t\t\t\t400,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tlet localDeviceId = store.db.prepare(\"SELECT device_id FROM sync_device LIMIT 1\").get() as\n\t\t\t| { device_id: string }\n\t\t\t| undefined;\n\t\tif (!localDeviceId) {\n\t\t\tconst [deviceId] = ensureDeviceIdentity(store.db);\n\t\t\tlocalDeviceId = { device_id: deviceId };\n\t\t}\n\n\t\tconst filteredInbound = filterOpsForPeer(store, peerDeviceId, normalizedOps);\n\t\tconst result = applyReplicationOps(store.db, filteredInbound.allowed, localDeviceId.device_id);\n\t\treturn c.json({\n\t\t\t...result,\n\t\t\tskipped: result.skipped + filteredInbound.skipped,\n\t\t});\n\t});\n\n\t// GET /api/sync/status\n\tapp.get(\"/api/sync/status\", async (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst showDiag = queryBool(c.req.query(\"includeDiagnostics\"));\n\t\t\tconst project = c.req.query(\"project\") || null;\n\t\t\tconst config = readCoordinatorSyncConfig();\n\n\t\t\tconst d = drizzle(store.db, { schema });\n\n\t\t\tconst deviceRow = d\n\t\t\t\t.select({\n\t\t\t\t\tdevice_id: schema.syncDevice.device_id,\n\t\t\t\t\tfingerprint: schema.syncDevice.fingerprint,\n\t\t\t\t})\n\t\t\t\t.from(schema.syncDevice)\n\t\t\t\t.limit(1)\n\t\t\t\t.get();\n\n\t\t\tconst daemonState = d\n\t\t\t\t.select()\n\t\t\t\t.from(schema.syncDaemonState)\n\t\t\t\t.where(eq(schema.syncDaemonState.id, 1))\n\t\t\t\t.get();\n\n\t\t\tconst peerCountRow = d.select({ total: count() }).from(schema.syncPeers).get();\n\n\t\t\tconst lastSyncRow = d\n\t\t\t\t.select({ last_sync_at: max(schema.syncPeers.last_sync_at) })\n\t\t\t\t.from(schema.syncPeers)\n\t\t\t\t.get();\n\n\t\t\tconst lastError = daemonState?.last_error as string | null;\n\t\t\tconst lastErrorAt = daemonState?.last_error_at as string | null;\n\t\t\tconst lastOkAt = daemonState?.last_ok_at as string | null;\n\t\t\tconst viewerBinding = readViewerBinding(store.dbPath);\n\t\t\tconst daemonRunning = viewerBinding\n\t\t\t\t? await portOpen(viewerBinding.host, viewerBinding.port)\n\t\t\t\t: false;\n\t\t\tconst daemonDetail = viewerBinding\n\t\t\t\t? daemonRunning\n\t\t\t\t\t? `viewer pidfile at ${viewerBinding.host}:${viewerBinding.port}`\n\t\t\t\t\t: `pidfile present but ${viewerBinding.host}:${viewerBinding.port} is unreachable`\n\t\t\t\t: null;\n\n\t\t\tlet daemonStateValue = \"ok\";\n\t\t\tif (!config.syncEnabled) {\n\t\t\t\tdaemonStateValue = \"disabled\";\n\t\t\t} else if (lastError && (!lastOkAt || String(lastOkAt) < String(lastErrorAt ?? \"\"))) {\n\t\t\t\tdaemonStateValue = \"error\";\n\t\t\t} else if (!daemonRunning) {\n\t\t\t\tdaemonStateValue = \"stopped\";\n\t\t\t}\n\n\t\t\tconst statusPayload: Record<string, unknown> = {\n\t\t\t\tenabled: config.syncEnabled,\n\t\t\t\tinterval_s: config.syncIntervalS,\n\t\t\t\tpeer_count: Number(peerCountRow?.total ?? 0),\n\t\t\t\tlast_sync_at: lastSyncRow?.last_sync_at ?? null,\n\t\t\t\tdaemon_state: daemonStateValue,\n\t\t\t\tdaemon_running: daemonRunning,\n\t\t\t\tdaemon_detail: daemonDetail,\n\t\t\t\tproject_filter_active:\n\t\t\t\t\tconfig.syncProjectsInclude.length > 0 || config.syncProjectsExclude.length > 0,\n\t\t\t\tproject_filter: {\n\t\t\t\t\tinclude: config.syncProjectsInclude,\n\t\t\t\t\texclude: config.syncProjectsExclude,\n\t\t\t\t},\n\t\t\t\tredacted: !showDiag,\n\t\t\t};\n\n\t\t\tif (showDiag) {\n\t\t\t\tstatusPayload.device_id = deviceRow?.device_id ?? null;\n\t\t\t\tstatusPayload.fingerprint = deviceRow?.fingerprint ?? null;\n\t\t\t\tstatusPayload.bind = `${config.syncHost}:${config.syncPort}`;\n\t\t\t\tstatusPayload.daemon_last_error = lastError;\n\t\t\t\tstatusPayload.daemon_last_error_at = lastErrorAt;\n\t\t\t\tstatusPayload.daemon_last_ok_at = lastOkAt;\n\t\t\t}\n\n\t\t\t// Build peers list using deduplicated mapPeerRow\n\t\t\tconst peerRows = store.db.prepare(PEERS_QUERY).all() as Record<string, unknown>[];\n\t\t\tconst peersItems = peerRows.map((row) => {\n\t\t\t\tconst peer = mapPeerRow(row, showDiag);\n\t\t\t\tpeer.status = peerStatus(peer);\n\t\t\t\treturn peer;\n\t\t\t});\n\n\t\t\tconst peersMap: Record<string, unknown> = {};\n\t\t\tfor (const peer of peersItems) {\n\t\t\t\tpeersMap[String(peer.peer_device_id)] = peer.status;\n\t\t\t}\n\n\t\t\t// Attempts\n\t\t\tconst attemptRows = d\n\t\t\t\t.select({\n\t\t\t\t\tpeer_device_id: schema.syncAttempts.peer_device_id,\n\t\t\t\t\tok: schema.syncAttempts.ok,\n\t\t\t\t\terror: schema.syncAttempts.error,\n\t\t\t\t\tstarted_at: schema.syncAttempts.started_at,\n\t\t\t\t\tfinished_at: schema.syncAttempts.finished_at,\n\t\t\t\t\tops_in: schema.syncAttempts.ops_in,\n\t\t\t\t\tops_out: schema.syncAttempts.ops_out,\n\t\t\t\t})\n\t\t\t\t.from(schema.syncAttempts)\n\t\t\t\t.orderBy(desc(schema.syncAttempts.finished_at))\n\t\t\t\t.limit(25)\n\t\t\t\t.all();\n\t\t\tconst attemptsItems = attemptRows.map((row) => ({\n\t\t\t\t...row,\n\t\t\t\tstatus: attemptStatus(row),\n\t\t\t\taddress: null,\n\t\t\t}));\n\n\t\t\tconst statusBlock: Record<string, unknown> = {\n\t\t\t\t...statusPayload,\n\t\t\t\tpeers: peersMap,\n\t\t\t\tpending: 0,\n\t\t\t\tsync: {},\n\t\t\t\tping: {},\n\t\t\t};\n\t\t\tconst legacyDevices = store.claimableLegacyDeviceIds();\n\t\t\tconst sharingReview = store.sharingReviewSummary(project);\n\t\t\tconst coordinator = await coordinatorStatusSnapshot(store, config);\n\t\t\tlet joinRequests: Record<string, unknown>[] = [];\n\t\t\ttry {\n\t\t\t\tjoinRequests = await listCoordinatorJoinRequests(config);\n\t\t\t} catch {\n\t\t\t\tjoinRequests = [];\n\t\t\t}\n\n\t\t\tif (daemonStateValue === \"ok\") {\n\t\t\t\tconst peerStates = new Set(\n\t\t\t\t\tpeersItems.map((peer) =>\n\t\t\t\t\t\tString((peer.status as Record<string, unknown> | undefined)?.peer_state ?? \"\"),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tconst latestFailedRecently = Boolean(\n\t\t\t\t\tattemptsItems[0] &&\n\t\t\t\t\t\tattemptsItems[0].status === \"error\" &&\n\t\t\t\t\t\tisRecentIso(attemptsItems[0].finished_at),\n\t\t\t\t);\n\t\t\t\tconst allOffline =\n\t\t\t\t\tpeersItems.length > 0 &&\n\t\t\t\t\tpeersItems.every(\n\t\t\t\t\t\t(peer) =>\n\t\t\t\t\t\t\tString((peer.status as Record<string, unknown>)?.peer_state ?? \"\") === \"offline\",\n\t\t\t\t\t);\n\t\t\t\tif (latestFailedRecently) {\n\t\t\t\t\tconst hasLivePeer = peerStates.has(\"online\") || peerStates.has(\"degraded\");\n\t\t\t\t\tif (hasLivePeer) daemonStateValue = \"degraded\";\n\t\t\t\t\telse if (allOffline) daemonStateValue = \"offline-peers\";\n\t\t\t\t\telse if (peersItems.length > 0) daemonStateValue = \"stale\";\n\t\t\t\t} else if (peerStates.has(\"degraded\")) {\n\t\t\t\t\tdaemonStateValue = \"degraded\";\n\t\t\t\t} else if (allOffline) {\n\t\t\t\t\tdaemonStateValue = \"offline-peers\";\n\t\t\t\t} else if (peersItems.length > 0 && !peerStates.has(\"online\")) {\n\t\t\t\t\tdaemonStateValue = \"stale\";\n\t\t\t\t}\n\t\t\t\tstatusPayload.daemon_state = daemonStateValue;\n\t\t\t\tstatusBlock.daemon_state = daemonStateValue;\n\t\t\t}\n\n\t\t\treturn c.json({\n\t\t\t\t...statusPayload,\n\t\t\t\tstatus: statusBlock,\n\t\t\t\tpeers: peersItems,\n\t\t\t\tattempts: attemptsItems.slice(0, 5),\n\t\t\t\tlegacy_devices: legacyDevices,\n\t\t\t\tsharing_review: sharingReview,\n\t\t\t\tcoordinator,\n\t\t\t\tjoin_requests: joinRequests,\n\t\t\t});\n\t\t}\n\t});\n\n\t// GET /api/sync/peers\n\tapp.get(\"/api/sync/peers\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst showDiag = queryBool(c.req.query(\"includeDiagnostics\"));\n\t\t\tconst rows = store.db.prepare(PEERS_QUERY).all() as Record<string, unknown>[];\n\t\t\t// Use deduplicated mapPeerRow helper (fix #4)\n\t\t\tconst peers = rows.map((row) => mapPeerRow(row, showDiag));\n\t\t\treturn c.json({ items: peers, redacted: !showDiag });\n\t\t}\n\t});\n\n\t// GET /api/sync/actors\n\tapp.get(\"/api/sync/actors\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst d = drizzle(store.db, { schema });\n\t\t\tconst includeMerged = queryBool(c.req.query(\"includeMerged\"));\n\t\t\tconst query = d.select().from(schema.actors);\n\t\t\tconst rows = includeMerged\n\t\t\t\t? query.orderBy(schema.actors.display_name).all()\n\t\t\t\t: query.where(ne(schema.actors.status, \"merged\")).orderBy(schema.actors.display_name).all();\n\t\t\treturn c.json({ items: rows });\n\t\t}\n\t});\n\n\t// GET /api/sync/attempts\n\tapp.get(\"/api/sync/attempts\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst d = drizzle(store.db, { schema });\n\t\t\tlet limit = queryInt(c.req.query(\"limit\"), 25);\n\t\t\tif (limit <= 0) return c.json({ error: \"invalid_limit\" }, 400);\n\t\t\tlimit = Math.min(limit, 500);\n\t\t\tconst rows = d\n\t\t\t\t.select({\n\t\t\t\t\tpeer_device_id: schema.syncAttempts.peer_device_id,\n\t\t\t\t\tok: schema.syncAttempts.ok,\n\t\t\t\t\terror: schema.syncAttempts.error,\n\t\t\t\t\tstarted_at: schema.syncAttempts.started_at,\n\t\t\t\t\tfinished_at: schema.syncAttempts.finished_at,\n\t\t\t\t\tops_in: schema.syncAttempts.ops_in,\n\t\t\t\t\tops_out: schema.syncAttempts.ops_out,\n\t\t\t\t})\n\t\t\t\t.from(schema.syncAttempts)\n\t\t\t\t.orderBy(desc(schema.syncAttempts.finished_at))\n\t\t\t\t.limit(limit)\n\t\t\t\t.all();\n\t\t\treturn c.json({ items: rows });\n\t\t}\n\t});\n\n\t// GET /api/sync/pairing — uses ensureDeviceIdentity from core (fix #5 context)\n\tapp.get(\"/api/sync/pairing\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst showDiag = queryBool(c.req.query(\"includeDiagnostics\"));\n\t\t\tif (!showDiag) {\n\t\t\t\treturn c.json({\n\t\t\t\t\tredacted: true,\n\t\t\t\t\tpairing_filter_hint: PAIRING_FILTER_HINT,\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst d = drizzle(store.db, { schema });\n\t\t\tconst deviceRow = d\n\t\t\t\t.select({\n\t\t\t\t\tdevice_id: schema.syncDevice.device_id,\n\t\t\t\t\tpublic_key: schema.syncDevice.public_key,\n\t\t\t\t\tfingerprint: schema.syncDevice.fingerprint,\n\t\t\t\t})\n\t\t\t\t.from(schema.syncDevice)\n\t\t\t\t.limit(1)\n\t\t\t\t.get();\n\n\t\t\tlet deviceId: string | undefined;\n\t\t\tlet publicKey: string | undefined;\n\t\t\tlet fingerprint: string | undefined;\n\n\t\t\tif (deviceRow) {\n\t\t\t\tdeviceId = String(deviceRow.device_id);\n\t\t\t\tpublicKey = String(deviceRow.public_key);\n\t\t\t\tfingerprint = String(deviceRow.fingerprint);\n\t\t\t} else {\n\t\t\t\t// Fall back to ensureDeviceIdentity if no row exists\n\t\t\t\ttry {\n\t\t\t\t\tconst [id, fp] = ensureDeviceIdentity(store.db);\n\t\t\t\t\tdeviceId = id;\n\t\t\t\t\tfingerprint = fp;\n\t\t\t\t\tconst newRow = d\n\t\t\t\t\t\t.select({ public_key: schema.syncDevice.public_key })\n\t\t\t\t\t\t.from(schema.syncDevice)\n\t\t\t\t\t\t.where(eq(schema.syncDevice.device_id, id))\n\t\t\t\t\t\t.get();\n\t\t\t\t\tpublicKey = newRow?.public_key ?? \"\";\n\t\t\t\t} catch {\n\t\t\t\t\treturn c.json({ error: \"device identity unavailable\" }, 500);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!deviceId || !fingerprint) {\n\t\t\t\treturn c.json({ error: \"public key missing\" }, 500);\n\t\t\t}\n\n\t\t\treturn c.json({\n\t\t\t\tdevice_id: deviceId,\n\t\t\t\tfingerprint,\n\t\t\t\tpublic_key: publicKey ?? null,\n\t\t\t\tpairing_filter_hint: PAIRING_FILTER_HINT,\n\t\t\t\taddresses: [],\n\t\t\t});\n\t\t}\n\t});\n\n\t// ------------------------------------------------------------------\n\t// POST mutations\n\t// ------------------------------------------------------------------\n\n\t// POST /api/sync/peers/rename\n\tapp.post(\"/api/sync/peers/rename\", async (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst d = drizzle(store.db, { schema });\n\t\t\tconst body = await c.req.json<Record<string, unknown>>();\n\t\t\tconst peerDeviceId = String(body.peer_device_id ?? \"\").trim();\n\t\t\tconst name = String(body.name ?? \"\").trim();\n\t\t\tif (!peerDeviceId) return c.json({ error: \"peer_device_id required\" }, 400);\n\t\t\tif (!name) return c.json({ error: \"name required\" }, 400);\n\t\t\tconst exists = d\n\t\t\t\t.select({ peer_device_id: schema.syncPeers.peer_device_id })\n\t\t\t\t.from(schema.syncPeers)\n\t\t\t\t.where(eq(schema.syncPeers.peer_device_id, peerDeviceId))\n\t\t\t\t.get();\n\t\t\tif (!exists) return c.json({ error: \"peer not found\" }, 404);\n\t\t\td.update(schema.syncPeers)\n\t\t\t\t.set({ name })\n\t\t\t\t.where(eq(schema.syncPeers.peer_device_id, peerDeviceId))\n\t\t\t\t.run();\n\t\t\treturn c.json({ ok: true });\n\t\t}\n\t});\n\n\tapp.post(\"/api/sync/invites/create\", async (c) => {\n\t\tlet body: Record<string, unknown>;\n\t\ttry {\n\t\t\tbody = await c.req.json<Record<string, unknown>>();\n\t\t} catch {\n\t\t\treturn c.json({ error: \"invalid json\" }, 400);\n\t\t}\n\t\tconst groupId = String(body.group_id ?? \"\").trim();\n\t\tconst coordinatorUrl = body.coordinator_url == null ? null : String(body.coordinator_url ?? \"\");\n\t\tconst policy = String(body.policy ?? \"auto_admit\").trim();\n\t\tconst ttlHours = Number.parseInt(String(body.ttl_hours ?? 24), 10);\n\t\tif (!groupId) return c.json({ error: \"group_id required\" }, 400);\n\t\tif (body.coordinator_url != null && typeof body.coordinator_url !== \"string\") {\n\t\t\treturn c.json({ error: \"coordinator_url must be string\" }, 400);\n\t\t}\n\t\tif (![\"auto_admit\", \"approval_required\"].includes(policy)) {\n\t\t\treturn c.json({ error: \"policy must be auto_admit or approval_required\" }, 400);\n\t\t}\n\t\tif (!Number.isFinite(ttlHours)) return c.json({ error: \"ttl_hours must be int\" }, 400);\n\t\ttry {\n\t\t\tconst config = readCoordinatorSyncConfig();\n\t\t\tconst result = await coordinatorCreateInviteAction({\n\t\t\t\tgroupId,\n\t\t\t\tcoordinatorUrl,\n\t\t\t\tpolicy,\n\t\t\t\tttlHours,\n\t\t\t\tcreatedBy: null,\n\t\t\t\tremoteUrl: config.syncCoordinatorUrl || null,\n\t\t\t\tadminSecret: config.syncCoordinatorAdminSecret || null,\n\t\t\t});\n\t\t\treturn c.json(result);\n\t\t} catch (error) {\n\t\t\treturn c.json({ error: error instanceof Error ? error.message : String(error) }, 400);\n\t\t}\n\t});\n\n\tapp.post(\"/api/sync/invites/import\", async (c) => {\n\t\tconst store = getStore();\n\t\tlet body: Record<string, unknown>;\n\t\ttry {\n\t\t\tbody = await c.req.json<Record<string, unknown>>();\n\t\t} catch {\n\t\t\treturn c.json({ error: \"invalid json\" }, 400);\n\t\t}\n\t\tconst inviteValue = String(body.invite ?? \"\").trim();\n\t\tif (!inviteValue) return c.json({ error: \"invite required\" }, 400);\n\t\ttry {\n\t\t\tconst result = await coordinatorImportInviteAction({ inviteValue, dbPath: store.dbPath });\n\t\t\treturn c.json(result);\n\t\t} catch (error) {\n\t\t\treturn c.json({ error: error instanceof Error ? error.message : String(error) }, 400);\n\t\t}\n\t});\n\n\tapp.post(\"/api/sync/join-requests/review\", async (c) => {\n\t\tlet body: Record<string, unknown>;\n\t\ttry {\n\t\t\tbody = await c.req.json<Record<string, unknown>>();\n\t\t} catch {\n\t\t\treturn c.json({ error: \"invalid json\" }, 400);\n\t\t}\n\t\tconst requestId = String(body.request_id ?? \"\").trim();\n\t\tconst action = String(body.action ?? \"\").trim();\n\t\tif (!requestId) return c.json({ error: \"request_id required\" }, 400);\n\t\tif (![\"approve\", \"deny\"].includes(action)) {\n\t\t\treturn c.json({ error: \"action must be approve or deny\" }, 400);\n\t\t}\n\t\ttry {\n\t\t\tconst config = readCoordinatorSyncConfig();\n\t\t\tconst result = await coordinatorReviewJoinRequestAction({\n\t\t\t\trequestId,\n\t\t\t\tapprove: action === \"approve\",\n\t\t\t\treviewedBy: null,\n\t\t\t\tremoteUrl: config.syncCoordinatorUrl || null,\n\t\t\t\tadminSecret: config.syncCoordinatorAdminSecret || null,\n\t\t\t});\n\t\t\tif (!result) return c.json({ error: \"join request not found\" }, 404);\n\t\t\treturn c.json({ ok: true, request: result });\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\treturn c.json(\n\t\t\t\t{ error: message },\n\t\t\t\tmessage.includes(\"request_not_found\") || message.includes(\"not found\") ? 404 : 400,\n\t\t\t);\n\t\t}\n\t});\n\n\t// DELETE /api/sync/peers/:peer_device_id\n\tapp.delete(\"/api/sync/peers/:peer_device_id\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst d = drizzle(store.db, { schema });\n\t\t\tconst peerDeviceId = c.req.param(\"peer_device_id\")?.trim();\n\t\t\tif (!peerDeviceId) return c.json({ error: \"peer_device_id required\" }, 400);\n\t\t\tconst exists = d\n\t\t\t\t.select({ peer_device_id: schema.syncPeers.peer_device_id })\n\t\t\t\t.from(schema.syncPeers)\n\t\t\t\t.where(eq(schema.syncPeers.peer_device_id, peerDeviceId))\n\t\t\t\t.get();\n\t\t\tif (!exists) return c.json({ error: \"peer not found\" }, 404);\n\t\t\td.delete(schema.syncPeers).where(eq(schema.syncPeers.peer_device_id, peerDeviceId)).run();\n\t\t\treturn c.json({ ok: true });\n\t\t}\n\t});\n\n\treturn app;\n}\n","/**\n * @codemem/server — HTTP server (viewer, sync, API).\n *\n * Single HTTP server handling viewer routes and sync daemon.\n * Shares one better-sqlite3 connection between viewer and sync.\n * Embedding inference runs in a worker_thread (lazy-started).\n *\n * Entry: `codemem serve`\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { ObserverClient } from \"@codemem/core\";\nimport { MemoryStore, type RawEventSweeper, resolveDbPath, VERSION } from \"@codemem/core\";\nimport { serveStatic } from \"@hono/node-server/serve-static\";\nimport { Hono } from \"hono\";\nimport { originGuard, preflightHandler } from \"./middleware.js\";\nimport { configRoutes } from \"./routes/config.js\";\nimport { memoryRoutes } from \"./routes/memory.js\";\nimport { observerStatusRoutes } from \"./routes/observer-status.js\";\nimport { rawEventsRoutes } from \"./routes/raw-events.js\";\nimport { statsRoutes } from \"./routes/stats.js\";\nimport { syncRoutes } from \"./routes/sync.js\";\n\nexport { VERSION };\n\n/** Shared store instance — SQLite WAL mode handles concurrent reads safely. */\nlet sharedStore: MemoryStore | null = null;\n\n/** Get (or create) the shared store instance. Exported so the sweeper can share it. */\nexport function getStore(): MemoryStore {\n\tif (!sharedStore) {\n\t\tsharedStore = new MemoryStore(resolveDbPath());\n\t}\n\treturn sharedStore;\n}\n\n/** Close the shared store (called on shutdown). */\nexport function closeStore(): void {\n\tsharedStore?.close();\n\tsharedStore = null;\n}\n\n/**\n * Create the Hono app with all viewer routes.\n * Exported for testing — pass a custom store factory to inject test DBs.\n */\nexport interface AppOptions {\n\tstoreFactory?: () => MemoryStore;\n\tsweeper?: RawEventSweeper | null;\n\tobserver?: ObserverClient | null;\n}\n\nexport function createApp(opts?: AppOptions) {\n\tconst storeFactory = opts?.storeFactory ?? getStore;\n\tconst sweeper = opts?.sweeper ?? null;\n\tconst observer = opts?.observer ?? null;\n\tconst app = new Hono();\n\n\t// CORS / origin guard\n\tapp.use(\"*\", preflightHandler());\n\tapp.use(\"*\", originGuard());\n\n\t// API routes\n\tapp.route(\"/\", statsRoutes(storeFactory));\n\tapp.route(\"/\", memoryRoutes(storeFactory));\n\tapp.route(\n\t\t\"/\",\n\t\tobserverStatusRoutes({\n\t\t\tgetStore: storeFactory,\n\t\t\tgetSweeper: () => sweeper,\n\t\t\tgetObserver: () => observer,\n\t\t}),\n\t);\n\tapp.route(\"/\", configRoutes({ getSweeper: () => sweeper }));\n\tapp.route(\"/\", rawEventsRoutes(storeFactory, sweeper));\n\tapp.route(\"/\", syncRoutes(storeFactory));\n\n\t// Static assets — serve under /assets/*\n\t// Resolves to packages/viewer-server/static/ both in dev and when installed from npm.\n\tconst staticRoot =\n\t\tprocess.env.CODEMEM_VIEWER_STATIC_DIR ?? join(import.meta.dirname ?? \".\", \"../static\");\n\n\tapp.use(\n\t\t\"/assets/*\",\n\t\tserveStatic({\n\t\t\troot: staticRoot,\n\t\t\trewriteRequestPath: (path) => path.replace(/^\\/assets/, \"\"),\n\t\t}),\n\t);\n\n\t// SPA — serve index.html for root and all client-side routes\n\tconst indexHtml = readFileSync(join(staticRoot, \"index.html\"), \"utf-8\");\n\tapp.get(\"*\", (c) => {\n\t\tif (c.req.path.startsWith(\"/api/\")) {\n\t\t\treturn c.json({ error: \"not found\" }, 404);\n\t\t}\n\t\treturn c.html(indexHtml);\n\t});\n\n\treturn app;\n}\n\n// No auto-start — the CLI's `serve` command owns server startup.\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/middleware.ts","../src/routes/config.ts","../src/helpers.ts","../src/routes/memory.ts","../src/routes/observer-status.ts","../src/routes/raw-events.ts","../src/routes/stats.ts","../src/routes/sync.ts","../src/index.ts"],"sourcesContent":["/**\n * CORS and cross-origin protection middleware.\n *\n * Ports Python's reject_cross_origin() logic from codemem/viewer_http.py.\n * GETs are allowed from any origin (viewer is local-only).\n * Mutations (POST/DELETE/PATCH/PUT) require an Origin header matching a\n * loopback address, or are rejected with 403.\n */\n\nimport type { Context, Next } from \"hono\";\nimport { createMiddleware } from \"hono/factory\";\n\nconst LOOPBACK_HOSTS = new Set([\"127.0.0.1\", \"localhost\", \"::1\"]);\n\n/**\n * Check whether an Origin header value is a valid loopback URL.\n * Mirrors Python's _is_allowed_loopback_origin_url().\n */\nfunction isLoopbackOrigin(origin: string): boolean {\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(origin);\n\t} catch {\n\t\treturn false;\n\t}\n\tif (url.protocol !== \"http:\" && url.protocol !== \"https:\") return false;\n\tif (url.username || url.password) return false;\n\treturn LOOPBACK_HOSTS.has(url.hostname);\n}\n\n/** HTTP methods that mutate state and require origin validation. */\nconst UNSAFE_METHODS = new Set([\"POST\", \"DELETE\", \"PATCH\", \"PUT\"]);\n\n/**\n * Check whether a missing-Origin request looks like a cross-site browser\n * request. Matches Python's `_is_unsafe_missing_origin()`:\n *\n * - Sec-Fetch-Site present and NOT same-origin/same-site/none → unsafe\n * - Referer present and NOT loopback → unsafe\n * - Otherwise → safe (CLI / programmatic caller, no browser context)\n */\nfunction isUnsafeMissingOrigin(c: Context): boolean {\n\tconst secFetchSite = (c.req.header(\"Sec-Fetch-Site\") ?? \"\").trim().toLowerCase();\n\tif (secFetchSite && ![\"same-origin\", \"same-site\", \"none\"].includes(secFetchSite)) {\n\t\treturn true;\n\t}\n\tconst referer = c.req.header(\"Referer\");\n\tif (!referer) return false;\n\treturn !isLoopbackOrigin(referer);\n}\n\n/**\n * Cross-origin protection middleware.\n *\n * Ports Python's `reject_cross_origin(missing_origin_policy=\"reject_if_unsafe\")`:\n *\n * - GET/HEAD/OPTIONS: allowed from any origin (viewer is local-only).\n * - POST/DELETE/PATCH/PUT:\n * - Origin present + loopback → allowed (browser on localhost)\n * - Origin present + non-loopback → rejected 403\n * - No Origin + no suspicious browser signals → allowed (CLI callers)\n * - No Origin + suspicious Sec-Fetch-Site/Referer → rejected 403\n *\n * For same-origin requests (no Origin header) on safe methods, no\n * Access-Control-Allow-Origin is set — the browser doesn't need it.\n * For valid loopback origins, ACAO is echoed back.\n */\nexport function originGuard() {\n\treturn createMiddleware(async (c: Context, next: Next) => {\n\t\tconst origin = c.req.header(\"Origin\");\n\t\tconst method = c.req.method;\n\n\t\tif (UNSAFE_METHODS.has(method)) {\n\t\t\tif (origin) {\n\t\t\t\t// Origin present — must be loopback\n\t\t\t\tif (!isLoopbackOrigin(origin)) {\n\t\t\t\t\treturn c.json({ error: \"forbidden\" }, 403);\n\t\t\t\t}\n\t\t\t\t// Valid loopback origin — echo it for CORS\n\t\t\t\tc.header(\"Access-Control-Allow-Origin\", origin);\n\t\t\t\tc.header(\"Access-Control-Allow-Methods\", \"GET, POST, DELETE, OPTIONS\");\n\t\t\t\tc.header(\"Access-Control-Allow-Headers\", \"Content-Type\");\n\t\t\t} else {\n\t\t\t\t// No Origin — reject only if browser signals indicate cross-site\n\t\t\t\t// (matches Python's reject_if_unsafe policy for API endpoints)\n\t\t\t\tif (isUnsafeMissingOrigin(c)) {\n\t\t\t\t\treturn c.json({ error: \"forbidden\" }, 403);\n\t\t\t\t}\n\t\t\t\t// CLI / programmatic caller — no CORS headers needed\n\t\t\t}\n\t\t} else if (origin && isLoopbackOrigin(origin)) {\n\t\t\t// Safe method with valid origin — echo for preflight\n\t\t\tc.header(\"Access-Control-Allow-Origin\", origin);\n\t\t\tc.header(\"Access-Control-Allow-Methods\", \"GET, POST, DELETE, OPTIONS\");\n\t\t\tc.header(\"Access-Control-Allow-Headers\", \"Content-Type\");\n\t\t}\n\t\t// No origin or non-loopback on safe method: no ACAO header set.\n\t\t// Browser enforces same-origin; we don't set permissive headers.\n\n\t\tawait next();\n\t});\n}\n\n/**\n * Handle OPTIONS preflight requests.\n * Returns 204 with appropriate CORS headers for loopback origins.\n */\nexport function preflightHandler() {\n\treturn createMiddleware(async (c: Context, next: Next) => {\n\t\tif (c.req.method !== \"OPTIONS\") {\n\t\t\tawait next();\n\t\t\treturn;\n\t\t}\n\t\tconst origin = c.req.header(\"Origin\");\n\t\tif (origin && isLoopbackOrigin(origin)) {\n\t\t\tc.header(\"Access-Control-Allow-Origin\", origin);\n\t\t\tc.header(\"Access-Control-Allow-Methods\", \"GET, POST, DELETE, OPTIONS\");\n\t\t\tc.header(\"Access-Control-Allow-Headers\", \"Content-Type\");\n\t\t\tc.header(\"Access-Control-Max-Age\", \"86400\");\n\t\t\treturn c.body(null, 204);\n\t\t}\n\t\treturn c.body(null, 204);\n\t});\n}\n","/**\n * Config routes — GET /api/config, POST /api/config.\n *\n * Ports the user-facing config read/write path from Python's\n * codemem/viewer_routes/config.py, scoped to the TS runtime's current needs.\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport {\n\tCODEMEM_CONFIG_ENV_OVERRIDES,\n\tgetCodememConfigPath,\n\tgetCodememEnvOverrides,\n\tlistObserverProviderOptions,\n\ttype RawEventSweeper,\n\treadCodememConfigFile,\n\tstripJsonComments,\n\tstripTrailingCommas,\n\twriteCodememConfigFile,\n} from \"@codemem/core\";\nimport { Hono } from \"hono\";\n\ntype ConfigData = Record<string, unknown>;\n\nconst RUNTIMES = new Set([\"api_http\", \"claude_sidecar\"]);\nconst AUTH_SOURCES = new Set([\"auto\", \"env\", \"file\", \"command\", \"none\"]);\nconst HOT_RELOAD_KEYS = new Set([\"raw_events_sweeper_interval_s\"]);\nconst ALLOWED_KEYS = [\n\t\"claude_command\",\n\t\"observer_base_url\",\n\t\"observer_provider\",\n\t\"observer_model\",\n\t\"observer_runtime\",\n\t\"observer_auth_source\",\n\t\"observer_auth_file\",\n\t\"observer_auth_command\",\n\t\"observer_auth_timeout_ms\",\n\t\"observer_auth_cache_ttl_s\",\n\t\"observer_headers\",\n\t\"observer_max_chars\",\n\t\"pack_observation_limit\",\n\t\"pack_session_limit\",\n\t\"sync_enabled\",\n\t\"sync_host\",\n\t\"sync_port\",\n\t\"sync_interval_s\",\n\t\"sync_mdns\",\n\t\"sync_coordinator_url\",\n\t\"sync_coordinator_group\",\n\t\"sync_coordinator_timeout_s\",\n\t\"sync_coordinator_presence_ttl_s\",\n\t\"raw_events_sweeper_interval_s\",\n] as const;\n\nconst DEFAULTS: ConfigData = {\n\tclaude_command: [\"claude\"],\n\tobserver_runtime: \"api_http\",\n\tobserver_auth_source: \"auto\",\n\tobserver_auth_command: [],\n\tobserver_auth_timeout_ms: 1500,\n\tobserver_auth_cache_ttl_s: 300,\n\tobserver_headers: {},\n\tobserver_max_chars: 12000,\n\tpack_observation_limit: 50,\n\tpack_session_limit: 10,\n\tsync_enabled: false,\n\tsync_host: \"0.0.0.0\",\n\tsync_port: 7337,\n\tsync_interval_s: 120,\n\tsync_mdns: true,\n\tsync_coordinator_timeout_s: 3,\n\tsync_coordinator_presence_ttl_s: 180,\n\traw_events_sweeper_interval_s: 30,\n};\n\nexport interface ConfigRouteOptions {\n\tgetSweeper?: () => RawEventSweeper | null;\n}\n\nfunction loadProviderOptions(): string[] {\n\treturn listObserverProviderOptions();\n}\n\nfunction getConfigPath(): string {\n\tconst envPath = process.env.CODEMEM_CONFIG;\n\tif (envPath) return envPath.replace(/^~/, homedir());\n\tconst configDir = join(homedir(), \".config\", \"codemem\");\n\tconst candidates = [join(configDir, \"config.json\"), join(configDir, \"config.jsonc\")];\n\treturn candidates.find((p) => existsSync(p)) ?? join(configDir, \"config.json\");\n}\n\nfunction readConfigFile(configPath: string): ConfigData {\n\tif (!existsSync(configPath)) return {};\n\ttry {\n\t\tlet text = readFileSync(configPath, \"utf-8\").trim();\n\t\tif (!text) return {};\n\t\ttry {\n\t\t\treturn JSON.parse(text) as ConfigData;\n\t\t} catch {\n\t\t\ttext = stripTrailingCommas(stripJsonComments(text));\n\t\t\treturn JSON.parse(text) as ConfigData;\n\t\t}\n\t} catch {\n\t\treturn {};\n\t}\n}\n\nfunction getEffectiveConfig(configData: ConfigData): ConfigData {\n\tconst effective: ConfigData = { ...DEFAULTS, ...configData };\n\tfor (const [key, envVar] of Object.entries(CODEMEM_CONFIG_ENV_OVERRIDES) as Array<\n\t\t[string, string]\n\t>) {\n\t\tconst val = process.env[envVar];\n\t\tif (val != null && val !== \"\") effective[key] = val;\n\t}\n\treturn effective;\n}\n\nfunction parsePositiveInt(value: unknown, allowZero = false): number | null {\n\tif (typeof value === \"boolean\") return null;\n\tconst parsed =\n\t\ttypeof value === \"number\"\n\t\t\t? value\n\t\t\t: typeof value === \"string\" && /^-?\\d+$/.test(value.trim())\n\t\t\t\t? Number(value.trim())\n\t\t\t\t: Number.NaN;\n\tif (!Number.isFinite(parsed) || !Number.isInteger(parsed)) return null;\n\tif (allowZero) return parsed >= 0 ? parsed : null;\n\treturn parsed > 0 ? parsed : null;\n}\n\nfunction asStringMap(value: unknown): Record<string, string> | null {\n\tif (value == null || typeof value !== \"object\" || Array.isArray(value)) return null;\n\tconst parsed: Record<string, string> = {};\n\tfor (const [key, item] of Object.entries(value)) {\n\t\tif (typeof item !== \"string\") return null;\n\t\tconst stripped = key.trim();\n\t\tif (!stripped) return null;\n\t\tparsed[stripped] = item;\n\t}\n\treturn parsed;\n}\n\nfunction asExecutableArgv(value: unknown): string[] | null {\n\tif (!Array.isArray(value)) return null;\n\tconst argv: string[] = [];\n\tfor (const item of value) {\n\t\tif (typeof item !== \"string\") return null;\n\t\tconst token = item.trim();\n\t\tif (!token) return null;\n\t\targv.push(token);\n\t}\n\treturn argv;\n}\n\nfunction validateAndApplyUpdate(\n\tconfigData: ConfigData,\n\tkey: (typeof ALLOWED_KEYS)[number],\n\tvalue: unknown,\n\tproviders: Set<string>,\n): string | null {\n\tif (value == null || value === \"\") {\n\t\tdelete configData[key];\n\t\treturn null;\n\t}\n\tif (key === \"observer_provider\") {\n\t\tif (typeof value !== \"string\") return \"observer_provider must be string\";\n\t\tconst provider = value.trim().toLowerCase();\n\t\tconst savedBaseUrl = configData.observer_base_url;\n\t\tconst hasSavedBaseUrl = typeof savedBaseUrl === \"string\" && savedBaseUrl.trim().length > 0;\n\t\tif (!providers.has(provider) && !hasSavedBaseUrl) {\n\t\t\treturn \"observer_provider must match a configured provider\";\n\t\t}\n\t\tconfigData[key] = provider;\n\t\treturn null;\n\t}\n\tif (key === \"observer_runtime\") {\n\t\tif (typeof value !== \"string\") return \"observer_runtime must be string\";\n\t\tconst runtime = value.trim().toLowerCase();\n\t\tif (!RUNTIMES.has(runtime)) {\n\t\t\treturn \"observer_runtime must be one of: api_http, claude_sidecar\";\n\t\t}\n\t\tconfigData[key] = runtime;\n\t\treturn null;\n\t}\n\tif (key === \"observer_auth_source\") {\n\t\tif (typeof value !== \"string\") return \"observer_auth_source must be string\";\n\t\tconst source = value.trim().toLowerCase();\n\t\tif (!AUTH_SOURCES.has(source)) {\n\t\t\treturn \"observer_auth_source must be one of: auto, env, file, command, none\";\n\t\t}\n\t\tconfigData[key] = source;\n\t\treturn null;\n\t}\n\tif (key === \"claude_command\" || key === \"observer_auth_command\") {\n\t\tconst argv = asExecutableArgv(value);\n\t\tif (argv == null) return `${key} must be string array`;\n\t\tif (argv.length > 0) configData[key] = argv;\n\t\telse delete configData[key];\n\t\treturn null;\n\t}\n\tif (key === \"observer_headers\") {\n\t\tconst headers = asStringMap(value);\n\t\tif (headers == null) return \"observer_headers must be object of string values\";\n\t\tif (Object.keys(headers).length > 0) configData[key] = headers;\n\t\telse delete configData[key];\n\t\treturn null;\n\t}\n\tif (key === \"sync_enabled\" || key === \"sync_mdns\") {\n\t\tif (typeof value !== \"boolean\") return `${key} must be boolean`;\n\t\tconfigData[key] = value;\n\t\treturn null;\n\t}\n\tif (\n\t\tkey === \"observer_base_url\" ||\n\t\tkey === \"observer_model\" ||\n\t\tkey === \"observer_auth_file\" ||\n\t\tkey === \"sync_host\" ||\n\t\tkey === \"sync_coordinator_url\" ||\n\t\tkey === \"sync_coordinator_group\"\n\t) {\n\t\tif (typeof value !== \"string\") return `${key} must be string`;\n\t\tconst trimmed = value.trim();\n\t\tif (!trimmed) delete configData[key];\n\t\telse configData[key] = trimmed;\n\t\treturn null;\n\t}\n\tconst allowZero = key === \"observer_auth_cache_ttl_s\";\n\tconst parsed = parsePositiveInt(value, allowZero);\n\tif (parsed == null) return `${key} must be ${allowZero ? \"non-negative int\" : \"int\"}`;\n\tconfigData[key] = parsed;\n\treturn null;\n}\n\nfunction applyRuntimeEffects(changedKeys: string[], opts: ConfigRouteOptions): string[] {\n\tconst applied: string[] = [];\n\tif (changedKeys.includes(\"raw_events_sweeper_interval_s\")) {\n\t\tconst configValue = readCodememConfigFile().raw_events_sweeper_interval_s;\n\t\tconst seconds =\n\t\t\ttypeof configValue === \"number\"\n\t\t\t\t? configValue\n\t\t\t\t: Number.parseInt(String(configValue ?? \"\"), 10);\n\t\tif (Number.isFinite(seconds) && seconds > 0) {\n\t\t\tprocess.env.CODEMEM_RAW_EVENTS_SWEEPER_INTERVAL_MS = String(seconds * 1000);\n\t\t} else {\n\t\t\tdelete process.env.CODEMEM_RAW_EVENTS_SWEEPER_INTERVAL_MS;\n\t\t}\n\t\topts.getSweeper?.()?.notifyConfigChanged();\n\t\tapplied.push(\"raw_events_sweeper_interval_s\");\n\t}\n\treturn applied;\n}\n\nexport function configRoutes(opts: ConfigRouteOptions = {}) {\n\tconst app = new Hono();\n\n\tapp.get(\"/api/config\", (c) => {\n\t\tconst configPath = getConfigPath();\n\t\tconst configData = readConfigFile(configPath);\n\t\treturn c.json({\n\t\t\tpath: configPath,\n\t\t\tconfig: configData,\n\t\t\tdefaults: DEFAULTS,\n\t\t\teffective: getEffectiveConfig(configData),\n\t\t\tenv_overrides: getCodememEnvOverrides(),\n\t\t\tproviders: loadProviderOptions(),\n\t\t});\n\t});\n\n\tapp.post(\"/api/config\", async (c) => {\n\t\tlet payload: unknown;\n\t\ttry {\n\t\t\tpayload = (await c.req.json()) as unknown;\n\t\t} catch {\n\t\t\treturn c.json({ error: \"invalid json\" }, 400);\n\t\t}\n\t\tif (payload == null || typeof payload !== \"object\" || Array.isArray(payload)) {\n\t\t\treturn c.json({ error: \"payload must be an object\" }, 400);\n\t\t}\n\t\tif (\n\t\t\t\"config\" in payload &&\n\t\t\t(payload as ConfigData).config != null &&\n\t\t\t(typeof (payload as ConfigData).config !== \"object\" ||\n\t\t\t\tArray.isArray((payload as ConfigData).config))\n\t\t) {\n\t\t\treturn c.json({ error: \"config must be an object\" }, 400);\n\t\t}\n\t\tconst updates =\n\t\t\t\"config\" in payload &&\n\t\t\t(payload as ConfigData).config != null &&\n\t\t\ttypeof (payload as ConfigData).config === \"object\" &&\n\t\t\t!Array.isArray((payload as ConfigData).config)\n\t\t\t\t? ((payload as ConfigData).config as ConfigData)\n\t\t\t\t: (payload as ConfigData);\n\n\t\tconst configPath = getCodememConfigPath();\n\t\tconst beforeConfig = readCodememConfigFile();\n\t\tconst beforeEffective = getEffectiveConfig(beforeConfig);\n\t\tconst nextConfig: ConfigData = { ...beforeConfig };\n\t\tconst providers = new Set(loadProviderOptions());\n\n\t\tconst touchedKeys = ALLOWED_KEYS.filter((key) => key in updates);\n\t\tfor (const key of ALLOWED_KEYS) {\n\t\t\tif (!(key in updates)) continue;\n\t\t\tconst error = validateAndApplyUpdate(nextConfig, key, updates[key], providers);\n\t\t\tif (error) return c.json({ error }, 400);\n\t\t}\n\n\t\tlet savedPath: string;\n\t\ttry {\n\t\t\tsavedPath = writeCodememConfigFile(nextConfig, configPath);\n\t\t} catch {\n\t\t\treturn c.json({ error: \"failed to write config\" }, 500);\n\t\t}\n\n\t\tconst afterEffective = getEffectiveConfig(nextConfig);\n\t\tconst savedChangedKeys = ALLOWED_KEYS.filter((key) => beforeConfig[key] !== nextConfig[key]);\n\t\tconst effectiveChangedKeys = ALLOWED_KEYS.filter(\n\t\t\t(key) => beforeEffective[key] !== afterEffective[key],\n\t\t);\n\t\tconst envOverrides = getCodememEnvOverrides();\n\t\tconst ignoredByEnvKeys = savedChangedKeys.filter(\n\t\t\t(key) => !effectiveChangedKeys.includes(key) && key in envOverrides,\n\t\t);\n\t\tconst runtimeChangedKeys = [\n\t\t\t...new Set([...touchedKeys, ...savedChangedKeys, ...effectiveChangedKeys]),\n\t\t];\n\t\tconst hotReloadedKeys = applyRuntimeEffects(runtimeChangedKeys, opts);\n\t\tconst restartRequiredKeys = effectiveChangedKeys.filter(\n\t\t\t(key) => !HOT_RELOAD_KEYS.has(key) && !(key in envOverrides),\n\t\t);\n\n\t\treturn c.json({\n\t\t\tpath: savedPath,\n\t\t\tconfig: nextConfig,\n\t\t\teffective: afterEffective,\n\t\t\teffects: {\n\t\t\t\tsaved_keys: savedChangedKeys,\n\t\t\t\teffective_keys: effectiveChangedKeys,\n\t\t\t\thot_reloaded_keys: hotReloadedKeys,\n\t\t\t\trestart_required_keys: restartRequiredKeys,\n\t\t\t\tignored_by_env_keys: ignoredByEnvKeys,\n\t\t\t\twarnings: ignoredByEnvKeys.map(\n\t\t\t\t\t(key) =>\n\t\t\t\t\t\t`${key} is currently controlled by ${envOverrides[key]}; saved config will not take effect until that override is removed.`,\n\t\t\t\t),\n\t\t\t},\n\t\t});\n\t});\n\n\treturn app;\n}\n","import { parseStrictInteger } from \"@codemem/core\";\n\n/**\n * Shared helpers for viewer-server routes.\n */\n\n/**\n * Parse a JSON string that should be an array of strings.\n * Returns an empty array on null, invalid JSON, or non-array values.\n * Mirrors Python's store._safe_json_list().\n */\nexport function safeJsonList(raw: string | null | undefined): string[] {\n\tif (raw == null) return [];\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(raw);\n\t\tif (!Array.isArray(parsed)) return [];\n\t\treturn parsed.filter((item): item is string => typeof item === \"string\");\n\t} catch {\n\t\treturn [];\n\t}\n}\n\n/**\n * Parse a query parameter as an integer, returning the default on failure.\n */\nexport function queryInt(value: string | undefined, defaultValue: number): number {\n\tif (value == null) return defaultValue;\n\tconst parsed = parseStrictInteger(value);\n\treturn parsed == null ? defaultValue : parsed;\n}\n\n/**\n * Parse a query parameter as a boolean flag.\n * Recognizes \"1\", \"true\", \"yes\" as truthy.\n */\nexport function queryBool(value: string | undefined): boolean {\n\tif (value == null) return false;\n\treturn value === \"1\" || value === \"true\" || value === \"yes\";\n}\n","/**\n * Memory routes — observations, summaries, sessions, projects, pack, artifacts.\n */\n\nimport type { MemoryStore } from \"@codemem/core\";\nimport { buildFilterClausesWithContext, fromJson, parseStrictInteger, schema } from \"@codemem/core\";\nimport { desc, eq, inArray, isNotNull } from \"drizzle-orm\";\nimport { drizzle } from \"drizzle-orm/better-sqlite3\";\nimport { Hono } from \"hono\";\nimport { queryInt } from \"../helpers.js\";\n\ntype StoreFactory = () => MemoryStore;\n\n/**\n * Attach session project/cwd fields to memory items.\n */\nfunction attachSessionFields(store: MemoryStore, items: Record<string, unknown>[]): void {\n\tconst sessionIds: number[] = [];\n\tconst seen = new Set<number>();\n\tfor (const item of items) {\n\t\tconst value = item.session_id;\n\t\tif (value == null) continue;\n\t\tconst sid = Number(value);\n\t\tif (Number.isNaN(sid) || seen.has(sid)) continue;\n\t\tseen.add(sid);\n\t\tsessionIds.push(sid);\n\t}\n\tif (sessionIds.length === 0) return;\n\n\tconst d = drizzle(store.db, { schema });\n\tconst rows = d\n\t\t.select({\n\t\t\tid: schema.sessions.id,\n\t\t\tproject: schema.sessions.project,\n\t\t\tcwd: schema.sessions.cwd,\n\t\t})\n\t\t.from(schema.sessions)\n\t\t.where(inArray(schema.sessions.id, sessionIds))\n\t\t.all();\n\n\tconst bySession = new Map<number, { project: string; cwd: string }>();\n\tfor (const row of rows) {\n\t\tconst projectRaw = String(row.project ?? \"\").trim();\n\t\tconst project = projectRaw ? projectBasename(projectRaw) : \"\";\n\t\tconst cwd = String(row.cwd ?? \"\");\n\t\tbySession.set(row.id, { project, cwd });\n\t}\n\n\tfor (const item of items) {\n\t\tconst sid = Number(item.session_id);\n\t\tif (Number.isNaN(sid)) continue;\n\t\tconst fields = bySession.get(sid);\n\t\tif (!fields) continue;\n\t\titem.project ??= fields.project;\n\t\titem.cwd ??= fields.cwd;\n\t}\n}\n\n/**\n * Extract the basename of a project path.\n * Strips \"fatal:\" prefixed values.\n */\nfunction projectBasename(raw: string): string {\n\tif (raw.toLowerCase().startsWith(\"fatal:\")) return \"\";\n\tconst parts = raw.replace(/\\\\/g, \"/\").split(\"/\");\n\treturn parts[parts.length - 1] ?? raw;\n}\n\nfunction normalizeScope(raw: string | undefined): \"mine\" | \"theirs\" | undefined {\n\tconst value = String(raw ?? \"\")\n\t\t.trim()\n\t\t.toLowerCase();\n\tif (value === \"mine\" || value === \"theirs\") return value;\n\treturn undefined;\n}\n\nfunction queryMemoryPage(\n\tstore: MemoryStore,\n\toptions: {\n\t\tlimit: number;\n\t\toffset: number;\n\t\tproject?: string;\n\t\tscope?: \"mine\" | \"theirs\";\n\t},\n): Record<string, unknown>[] {\n\tconst filters: Record<string, unknown> = {};\n\tif (options.project) filters.project = options.project;\n\tif (options.scope) filters.ownership_scope = options.scope;\n\n\tconst filterResult = buildFilterClausesWithContext(filters, {\n\t\tactorId: store.actorId,\n\t\tdeviceId: store.deviceId,\n\t});\n\tconst clauses = [\"memory_items.active = 1\", ...filterResult.clauses];\n\tconst where = clauses.join(\" AND \");\n\tconst from = filterResult.joinSessions\n\t\t? \"memory_items JOIN sessions ON sessions.id = memory_items.session_id\"\n\t\t: \"memory_items\";\n\n\tconst rows = store.db\n\t\t.prepare(\n\t\t\t`SELECT memory_items.* FROM ${from}\n\t\t\t WHERE ${where}\n\t\t\t ORDER BY memory_items.created_at DESC\n\t\t\t LIMIT ? OFFSET ?`,\n\t\t)\n\t\t.all(...filterResult.params, options.limit + 1, options.offset) as Record<string, unknown>[];\n\n\treturn rows.map((row) => ({\n\t\t...row,\n\t\tmetadata_json: fromJson((row.metadata_json as string) ?? null),\n\t}));\n}\n\nfunction isSummaryLikeMemory(item: Record<string, unknown>): boolean {\n\tif (String(item.kind ?? \"\").toLowerCase() === \"session_summary\") return true;\n\tconst metadata = (item.metadata_json ?? {}) as Record<string, unknown>;\n\tif (metadata.is_summary === true) return true;\n\treturn (\n\t\tString(metadata.source ?? \"\")\n\t\t\t.trim()\n\t\t\t.toLowerCase() === \"observer_summary\"\n\t);\n}\n\nfunction selectMemoryPage(\n\tstore: MemoryStore,\n\toptions: {\n\t\tlimit: number;\n\t\toffset: number;\n\t\tproject?: string;\n\t\tscope?: \"mine\" | \"theirs\";\n\t\tmatcher: (item: Record<string, unknown>) => boolean;\n\t},\n): Record<string, unknown>[] {\n\tconst pageSize = Math.max(options.limit + options.offset + 10, 50);\n\tlet rawOffset = 0;\n\tconst matched: Record<string, unknown>[] = [];\n\n\twhile (matched.length < options.offset + options.limit + 1) {\n\t\tconst page = queryMemoryPage(store, {\n\t\t\tlimit: pageSize,\n\t\t\toffset: rawOffset,\n\t\t\tproject: options.project,\n\t\t\tscope: options.scope,\n\t\t});\n\t\tif (page.length === 0) break;\n\t\tmatched.push(...page.filter(options.matcher));\n\t\tif (page.length < pageSize) break;\n\t\trawOffset += page.length;\n\t}\n\n\treturn matched.slice(options.offset, options.offset + options.limit + 1);\n}\n\nexport function memoryRoutes(getStore: StoreFactory) {\n\tconst app = new Hono();\n\n\t// GET /api/sessions\n\tapp.get(\"/api/sessions\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst limit = queryInt(c.req.query(\"limit\"), 20);\n\t\t\tconst d = drizzle(store.db, { schema });\n\t\t\tconst rows = d\n\t\t\t\t.select()\n\t\t\t\t.from(schema.sessions)\n\t\t\t\t.orderBy(desc(schema.sessions.started_at))\n\t\t\t\t.limit(limit)\n\t\t\t\t.all();\n\t\t\tconst items = rows.map((row) => ({\n\t\t\t\t...row,\n\t\t\t\tmetadata_json: fromJson(row.metadata_json),\n\t\t\t}));\n\t\t\treturn c.json({ items });\n\t\t}\n\t});\n\n\t// GET /api/projects\n\tapp.get(\"/api/projects\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst d = drizzle(store.db, { schema });\n\t\t\tconst rows = d\n\t\t\t\t.selectDistinct({ project: schema.sessions.project })\n\t\t\t\t.from(schema.sessions)\n\t\t\t\t.where(isNotNull(schema.sessions.project))\n\t\t\t\t.all();\n\t\t\tconst projects = [\n\t\t\t\t...new Set(\n\t\t\t\t\trows\n\t\t\t\t\t\t.map((r) => String(r.project ?? \"\").trim())\n\t\t\t\t\t\t.filter((p) => p && !p.toLowerCase().startsWith(\"fatal:\"))\n\t\t\t\t\t\t.map((p) => projectBasename(p))\n\t\t\t\t\t\t.filter(Boolean),\n\t\t\t\t),\n\t\t\t].sort();\n\t\t\treturn c.json({ projects });\n\t\t}\n\t});\n\n\t// GET /api/observations (aliased from /api/memories)\n\tapp.get(\"/api/memories\", (c) => {\n\t\tconst search = new URL(c.req.url).search;\n\t\treturn c.redirect(`/api/observations${search}`, 301);\n\t});\n\n\tapp.get(\"/api/observations\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst limit = Math.max(1, queryInt(c.req.query(\"limit\"), 20));\n\t\t\tconst offset = Math.max(0, queryInt(c.req.query(\"offset\"), 0));\n\t\t\tconst project = c.req.query(\"project\") || undefined;\n\t\t\tconst scope = normalizeScope(c.req.query(\"scope\"));\n\t\t\tconst items = selectMemoryPage(store, {\n\t\t\t\tlimit,\n\t\t\t\toffset,\n\t\t\t\tproject,\n\t\t\t\tscope,\n\t\t\t\tmatcher: (item) => !isSummaryLikeMemory(item),\n\t\t\t});\n\t\t\tconst hasMore = items.length > limit;\n\t\t\tconst result = hasMore ? items.slice(0, limit) : items;\n\t\t\tconst asRecords = result as unknown as Record<string, unknown>[];\n\t\t\tattachSessionFields(store, asRecords);\n\t\t\treturn c.json({\n\t\t\t\titems: asRecords,\n\t\t\t\tpagination: {\n\t\t\t\t\tlimit,\n\t\t\t\t\toffset,\n\t\t\t\t\tnext_offset: hasMore ? offset + result.length : null,\n\t\t\t\t\thas_more: hasMore,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\t});\n\n\t// GET /api/summaries\n\tapp.get(\"/api/summaries\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst limit = Math.max(1, queryInt(c.req.query(\"limit\"), 50));\n\t\t\tconst offset = Math.max(0, queryInt(c.req.query(\"offset\"), 0));\n\t\t\tconst project = c.req.query(\"project\") || undefined;\n\t\t\tconst scope = normalizeScope(c.req.query(\"scope\"));\n\t\t\tconst items = selectMemoryPage(store, {\n\t\t\t\tlimit,\n\t\t\t\toffset,\n\t\t\t\tproject,\n\t\t\t\tscope,\n\t\t\t\tmatcher: (item) => isSummaryLikeMemory(item),\n\t\t\t});\n\t\t\tconst hasMore = items.length > limit;\n\t\t\tconst result = hasMore ? items.slice(0, limit) : items;\n\t\t\tconst asRecords = result as unknown as Record<string, unknown>[];\n\t\t\tattachSessionFields(store, asRecords);\n\t\t\treturn c.json({\n\t\t\t\titems: asRecords,\n\t\t\t\tpagination: {\n\t\t\t\t\tlimit,\n\t\t\t\t\toffset,\n\t\t\t\t\tnext_offset: hasMore ? offset + result.length : null,\n\t\t\t\t\thas_more: hasMore,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\t});\n\n\t// GET /api/session (aggregate counts)\n\tapp.get(\"/api/session\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst project = c.req.query(\"project\") || null;\n\t\t\tconst count = (sql: string, ...params: unknown[]): number => {\n\t\t\t\tconst row = store.db.prepare(sql).get(...params) as Record<string, unknown> | undefined;\n\t\t\t\treturn Number(row?.total ?? 0);\n\t\t\t};\n\n\t\t\tlet prompts: number;\n\t\t\tlet artifacts: number;\n\t\t\tlet memories: number;\n\t\t\tlet observations: number;\n\t\t\tconst countObservations = (scopeProject?: string) => {\n\t\t\t\tlet offset = 0;\n\t\t\t\tlet total = 0;\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst page = queryMemoryPage(store, {\n\t\t\t\t\t\tlimit: 200,\n\t\t\t\t\t\toffset,\n\t\t\t\t\t\tproject: scopeProject,\n\t\t\t\t\t});\n\t\t\t\t\tif (page.length === 0) break;\n\t\t\t\t\ttotal += page.filter((item) => !isSummaryLikeMemory(item)).length;\n\t\t\t\t\tif (page.length < 200) break;\n\t\t\t\t\toffset += page.length;\n\t\t\t\t}\n\t\t\t\treturn total;\n\t\t\t};\n\t\t\tif (project) {\n\t\t\t\tprompts = count(\"SELECT COUNT(*) AS total FROM user_prompts WHERE project = ?\", project);\n\t\t\t\tartifacts = count(\n\t\t\t\t\t`SELECT COUNT(*) AS total FROM artifacts\n\t\t\t\t\t JOIN sessions ON sessions.id = artifacts.session_id\n\t\t\t\t\t WHERE sessions.project = ?`,\n\t\t\t\t\tproject,\n\t\t\t\t);\n\t\t\t\tmemories = count(\n\t\t\t\t\t`SELECT COUNT(*) AS total FROM memory_items\n\t\t\t\t\t JOIN sessions ON sessions.id = memory_items.session_id\n\t\t\t\t\t WHERE sessions.project = ?`,\n\t\t\t\t\tproject,\n\t\t\t\t);\n\t\t\t\tobservations = countObservations(project);\n\t\t\t} else {\n\t\t\t\tprompts = count(\"SELECT COUNT(*) AS total FROM user_prompts\");\n\t\t\t\tartifacts = count(\"SELECT COUNT(*) AS total FROM artifacts\");\n\t\t\t\tmemories = count(\"SELECT COUNT(*) AS total FROM memory_items\");\n\t\t\t\tobservations = countObservations();\n\t\t\t}\n\t\t\tconst total = prompts + artifacts + memories;\n\t\t\treturn c.json({ total, memories, artifacts, prompts, observations });\n\t\t}\n\t});\n\n\t// GET /api/pack\n\tapp.get(\"/api/pack\", async (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst context = c.req.query(\"context\") || \"\";\n\t\t\tif (!context) {\n\t\t\t\treturn c.json({ error: \"context required\" }, 400);\n\t\t\t}\n\t\t\tconst limit = queryInt(c.req.query(\"limit\"), 10);\n\t\t\tconst tokenBudgetStr = c.req.query(\"token_budget\");\n\t\t\tlet tokenBudget: number | undefined;\n\t\t\tif (tokenBudgetStr) {\n\t\t\t\ttokenBudget = parseStrictInteger(tokenBudgetStr) ?? undefined;\n\t\t\t\tif (tokenBudget === undefined) {\n\t\t\t\t\treturn c.json({ error: \"token_budget must be int\" }, 400);\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst project = c.req.query(\"project\") || undefined;\n\t\t\tconst filters: { project?: string } = {};\n\t\t\tif (project) filters.project = project;\n\t\t\tconst pack = await store.buildMemoryPackAsync(context, limit, tokenBudget ?? null, filters);\n\t\t\treturn c.json(pack);\n\t\t}\n\t});\n\n\t// GET /api/memory\n\tapp.get(\"/api/memory\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst limit = queryInt(c.req.query(\"limit\"), 20);\n\t\t\tconst kind = c.req.query(\"kind\") || undefined;\n\t\t\tconst project = c.req.query(\"project\") || undefined;\n\t\t\tconst filters: Record<string, unknown> = {};\n\t\t\tif (kind) filters.kind = kind;\n\t\t\tif (project) filters.project = project;\n\t\t\tconst items = store.recent(limit, filters);\n\t\t\tconst asRecords = items as unknown as Record<string, unknown>[];\n\t\t\tattachSessionFields(store, asRecords);\n\t\t\treturn c.json({ items: asRecords });\n\t\t}\n\t});\n\n\t// GET /api/artifacts\n\tapp.get(\"/api/artifacts\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst sessionIdStr = c.req.query(\"session_id\");\n\t\t\tif (!sessionIdStr) {\n\t\t\t\treturn c.json({ error: \"session_id required\" }, 400);\n\t\t\t}\n\t\t\tconst sessionId = parseStrictInteger(sessionIdStr);\n\t\t\tif (sessionId == null) {\n\t\t\t\treturn c.json({ error: \"session_id must be int\" }, 400);\n\t\t\t}\n\t\t\tconst d = drizzle(store.db, { schema });\n\t\t\tconst rows = d\n\t\t\t\t.select()\n\t\t\t\t.from(schema.artifacts)\n\t\t\t\t.where(eq(schema.artifacts.session_id, sessionId))\n\t\t\t\t.all();\n\t\t\treturn c.json({ items: rows });\n\t\t}\n\t});\n\n\t// POST /api/memories/visibility\n\tapp.post(\"/api/memories/visibility\", async (c) => {\n\t\tconst store = getStore();\n\t\tlet body: Record<string, unknown>;\n\t\ttry {\n\t\t\tbody = await c.req.json<Record<string, unknown>>();\n\t\t} catch {\n\t\t\treturn c.json({ error: \"invalid JSON\" }, 400);\n\t\t}\n\t\tconst memoryId = parseStrictInteger(\n\t\t\ttypeof body.memory_id === \"string\" ? body.memory_id : String(body.memory_id ?? \"\"),\n\t\t);\n\t\tif (memoryId == null || memoryId <= 0) {\n\t\t\treturn c.json({ error: \"memory_id must be int\" }, 400);\n\t\t}\n\t\tconst visibility = String(body.visibility ?? \"\").trim();\n\t\tif (visibility !== \"private\" && visibility !== \"shared\") {\n\t\t\treturn c.json({ error: \"visibility must be private or shared\" }, 400);\n\t\t}\n\t\ttry {\n\t\t\tconst item = store.updateMemoryVisibility(memoryId, visibility);\n\t\t\treturn c.json({ item });\n\t\t} catch (err) {\n\t\t\tconst msg = err instanceof Error ? err.message : String(err);\n\t\t\tif (msg.includes(\"not found\")) return c.json({ error: msg }, 404);\n\t\t\tif (msg.includes(\"not owned\")) return c.json({ error: msg }, 403);\n\t\t\treturn c.json({ error: msg }, 400);\n\t\t}\n\t});\n\n\treturn app;\n}\n","/**\n * Observer status route — GET /api/observer-status.\n *\n * Ports Python's viewer_routes/observer_status.py.\n * Returns observer runtime info, credential availability, and queue status.\n */\n\nimport type { ObserverClient } from \"@codemem/core\";\nimport { type MemoryStore, probeAvailableCredentials, type RawEventSweeper } from \"@codemem/core\";\nimport { Hono } from \"hono\";\n\ntype StoreFactory = () => MemoryStore;\n\nexport interface ObserverStatusDeps {\n\tgetStore: StoreFactory;\n\tgetSweeper: () => RawEventSweeper | null;\n\tgetObserver?: () => ObserverClient | null;\n}\n\nfunction normalizeActiveObserver(active: ReturnType<ObserverClient[\"getStatus\"]> | null) {\n\tif (!active) return null;\n\treturn {\n\t\t...active,\n\t\tauth: {\n\t\t\t...active.auth,\n\t\t\tmethod: active.auth.type,\n\t\t\ttoken_present: active.auth.hasToken,\n\t\t},\n\t};\n}\n\nfunction buildFailureImpact(\n\tlatestFailure: Record<string, unknown> | null,\n\tqueueTotals: { pending: number; sessions: number },\n\tauthBackoff: { active: boolean; remainingS: number },\n): string | null {\n\tif (!latestFailure) return null;\n\tif (authBackoff.active) {\n\t\treturn `Queue retries paused for ~${authBackoff.remainingS}s after an observer auth failure.`;\n\t}\n\tif (queueTotals.pending > 0) {\n\t\treturn `${queueTotals.pending} queued raw events across ${queueTotals.sessions} session(s) are waiting on a successful flush.`;\n\t}\n\treturn \"Failed flush batches are pending retry.\";\n}\n\nexport function observerStatusRoutes(deps?: ObserverStatusDeps) {\n\tconst app = new Hono();\n\n\tapp.get(\"/api/observer-status\", (c) => {\n\t\tconst store = deps?.getStore();\n\t\tconst sweeper = deps?.getSweeper();\n\t\tconst observer = deps?.getObserver?.() ?? null;\n\n\t\t// Stub fallback when store doesn't have the required methods (e.g. tests with mock store)\n\t\tif (!store || typeof store.rawEventBacklogTotals !== \"function\") {\n\t\t\treturn c.json({\n\t\t\t\tactive: null,\n\t\t\t\tavailable_credentials: {},\n\t\t\t\tlatest_failure: null,\n\t\t\t\tqueue: {\n\t\t\t\t\tpending: 0,\n\t\t\t\t\tsessions: 0,\n\t\t\t\t\tauth_backoff_active: false,\n\t\t\t\t\tauth_backoff_remaining_s: 0,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\tconst queueTotals = store.rawEventBacklogTotals();\n\t\tconst authBackoff = sweeper?.authBackoffStatus() ?? { active: false, remainingS: 0 };\n\t\tconst latestFailure = store.latestRawEventFlushFailure();\n\t\tconst active = normalizeActiveObserver(observer?.getStatus() ?? null);\n\t\tconst availableCredentials = probeAvailableCredentials();\n\t\tconst shouldShowFailure =\n\t\t\tlatestFailure != null && (authBackoff.active || queueTotals.pending > 0);\n\n\t\tconst failureWithImpact =\n\t\t\tshouldShowFailure && latestFailure\n\t\t\t\t? { ...latestFailure, impact: buildFailureImpact(latestFailure, queueTotals, authBackoff) }\n\t\t\t\t: null;\n\n\t\treturn c.json({\n\t\t\tactive,\n\t\t\tavailable_credentials: availableCredentials,\n\t\t\tlatest_failure: failureWithImpact,\n\t\t\tqueue: {\n\t\t\t\t...queueTotals,\n\t\t\t\tauth_backoff_active: authBackoff.active,\n\t\t\t\tauth_backoff_remaining_s: authBackoff.remainingS,\n\t\t\t},\n\t\t});\n\t});\n\n\treturn app;\n}\n","/**\n * Raw events routes — GET & POST /api/raw-events, GET /api/raw-events/status,\n * POST /api/claude-hooks.\n */\n\nimport { createHash } from \"node:crypto\";\nimport type { MemoryStore, RawEventSweeper } from \"@codemem/core\";\nimport { buildRawEventEnvelopeFromHook, schema, stripPrivateObj } from \"@codemem/core\";\nimport { desc } from \"drizzle-orm\";\nimport { drizzle } from \"drizzle-orm/better-sqlite3\";\nimport { Hono } from \"hono\";\nimport { queryInt } from \"../helpers.js\";\n\ntype StoreFactory = () => MemoryStore;\n\nconst MAX_RAW_EVENTS_BODY_BYTES =\n\tNumber.parseInt(process.env.CODEMEM_RAW_EVENTS_MAX_BODY_BYTES ?? \"\", 10) || 1048576;\n\n/** Keys to check (in priority order) when resolving a session stream id. */\nconst SESSION_ID_KEYS = [\n\t\"session_stream_id\",\n\t\"session_id\",\n\t\"stream_id\",\n\t\"opencode_session_id\",\n] as const;\n\n/**\n * Resolve a session stream id from a payload object.\n * Checks multiple field aliases. Throws on conflicting values.\n */\nfunction resolveSessionStreamId(payload: Record<string, unknown>): string | null {\n\tconst values = new Map<string, string>();\n\tfor (const key of SESSION_ID_KEYS) {\n\t\tconst value = payload[key];\n\t\tif (value == null) continue;\n\t\tif (typeof value !== \"string\") throw new Error(`${key} must be string`);\n\t\tconst text = value.trim();\n\t\tif (text) values.set(key, text);\n\t}\n\tif (values.size === 0) return null;\n\tconst unique = new Set(values.values());\n\tif (unique.size > 1) throw new Error(\"conflicting session id fields\");\n\t// Return the first matching key's value (preserves priority order)\n\tfor (const key of SESSION_ID_KEYS) {\n\t\tconst v = values.get(key);\n\t\tif (v) return v;\n\t}\n\treturn null;\n}\n\n/**\n * Parse and validate a JSON object body, enforcing size limits.\n * Returns the parsed payload or a Hono Response on error.\n */\nasync function parseJsonObjectBody(\n\tc: {\n\t\treq: { header: (name: string) => string | undefined; text: () => Promise<string> };\n\t\tjson: (data: unknown, status?: number) => Response;\n\t},\n\tmaxBytes: number,\n): Promise<Record<string, unknown> | Response> {\n\tconst contentLength = Number.parseInt(c.req.header(\"content-length\") ?? \"0\", 10);\n\tif (Number.isNaN(contentLength) || contentLength < 0) {\n\t\treturn c.json({ error: \"invalid content-length\" }, 400);\n\t}\n\tif (contentLength > maxBytes) {\n\t\treturn c.json({ error: \"payload too large\", max_bytes: maxBytes }, 413);\n\t}\n\tlet raw: string;\n\ttry {\n\t\traw = await c.req.text();\n\t} catch {\n\t\treturn c.json({ error: \"invalid json\" }, 400);\n\t}\n\tif (Buffer.byteLength(raw, \"utf-8\") > maxBytes) {\n\t\treturn c.json({ error: \"payload too large\", max_bytes: maxBytes }, 413);\n\t}\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = raw ? JSON.parse(raw) : {};\n\t} catch {\n\t\treturn c.json({ error: \"invalid json\" }, 400);\n\t}\n\tif (parsed == null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n\t\treturn c.json({ error: \"payload must be an object\" }, 400);\n\t}\n\treturn parsed as Record<string, unknown>;\n}\n\n/** Nudge the sweeper safely — never crashes the caller. */\nfunction nudgeSweeper(\n\tsweeper: RawEventSweeper | null | undefined,\n\tsessionIds: Iterable<string>,\n\tsource = \"opencode\",\n): void {\n\ttry {\n\t\tfor (const sessionId of sessionIds) {\n\t\t\tsweeper?.nudge(sessionId, source);\n\t\t}\n\t} catch {\n\t\t// never crash the request if sweeper nudge fails\n\t}\n}\n\nexport function rawEventsRoutes(getStore: StoreFactory, sweeper?: RawEventSweeper | null) {\n\tconst app = new Hono();\n\n\t// GET /api/raw-events (compat endpoint for stats panel)\n\tapp.get(\"/api/raw-events\", (c) => {\n\t\tconst store = getStore();\n\t\tconst totals = store.rawEventBacklogTotals();\n\t\treturn c.json(totals);\n\t});\n\n\t// GET /api/raw-events/status\n\tapp.get(\"/api/raw-events/status\", (c) => {\n\t\tconst store = getStore();\n\t\tconst limit = queryInt(c.req.query(\"limit\"), 25);\n\t\tconst d = drizzle(store.db, { schema });\n\t\tconst rows = d\n\t\t\t.select({\n\t\t\t\tsource: schema.rawEventSessions.source,\n\t\t\t\tstream_id: schema.rawEventSessions.stream_id,\n\t\t\t\topencode_session_id: schema.rawEventSessions.opencode_session_id,\n\t\t\t\tcwd: schema.rawEventSessions.cwd,\n\t\t\t\tproject: schema.rawEventSessions.project,\n\t\t\t\tstarted_at: schema.rawEventSessions.started_at,\n\t\t\t\tlast_seen_ts_wall_ms: schema.rawEventSessions.last_seen_ts_wall_ms,\n\t\t\t\tlast_received_event_seq: schema.rawEventSessions.last_received_event_seq,\n\t\t\t\tlast_flushed_event_seq: schema.rawEventSessions.last_flushed_event_seq,\n\t\t\t\tupdated_at: schema.rawEventSessions.updated_at,\n\t\t\t})\n\t\t\t.from(schema.rawEventSessions)\n\t\t\t.orderBy(desc(schema.rawEventSessions.updated_at))\n\t\t\t.limit(limit)\n\t\t\t.all();\n\t\tconst items = rows.map((row) => {\n\t\t\tconst streamId = String(row.stream_id ?? row.opencode_session_id ?? \"\");\n\t\t\treturn {\n\t\t\t\t...row,\n\t\t\t\tsession_stream_id: streamId,\n\t\t\t\tsession_id: streamId,\n\t\t\t};\n\t\t});\n\t\tconst totals = store.rawEventBacklogTotals();\n\t\treturn c.json({\n\t\t\titems,\n\t\t\ttotals,\n\t\t\tingest: {\n\t\t\t\tavailable: true,\n\t\t\t\tmode: \"stream_queue\",\n\t\t\t\tmax_body_bytes: MAX_RAW_EVENTS_BODY_BYTES,\n\t\t\t},\n\t\t});\n\t});\n\n\t// POST /api/raw-events — ingest raw events from plugin\n\tapp.post(\"/api/raw-events\", async (c) => {\n\t\tconst result = await parseJsonObjectBody(c, MAX_RAW_EVENTS_BODY_BYTES);\n\t\tif (result instanceof Response) return result;\n\t\tconst payload = result;\n\n\t\tconst store = getStore();\n\t\ttry {\n\t\t\t// Validate top-level string fields\n\t\t\tconst cwd = payload.cwd;\n\t\t\tif (cwd != null && typeof cwd !== \"string\") {\n\t\t\t\treturn c.json({ error: \"cwd must be string\" }, 400);\n\t\t\t}\n\t\t\tconst project = payload.project;\n\t\t\tif (project != null && typeof project !== \"string\") {\n\t\t\t\treturn c.json({ error: \"project must be string\" }, 400);\n\t\t\t}\n\t\t\tconst startedAt = payload.started_at;\n\t\t\tif (startedAt != null && typeof startedAt !== \"string\") {\n\t\t\t\treturn c.json({ error: \"started_at must be string\" }, 400);\n\t\t\t}\n\n\t\t\t// Determine event list: batch (events array) or single-event payload\n\t\t\tlet items = payload.events;\n\t\t\tif (items == null) {\n\t\t\t\titems = [payload];\n\t\t\t}\n\t\t\tif (!Array.isArray(items)) {\n\t\t\t\treturn c.json({ error: \"events must be a list\" }, 400);\n\t\t\t}\n\n\t\t\t// Resolve default session id from top-level payload\n\t\t\tlet defaultSessionId: string;\n\t\t\ttry {\n\t\t\t\tdefaultSessionId = resolveSessionStreamId(payload) ?? \"\";\n\t\t\t} catch (err) {\n\t\t\t\treturn c.json({ error: (err as Error).message }, 400);\n\t\t\t}\n\t\t\tif (defaultSessionId.startsWith(\"msg_\")) {\n\t\t\t\treturn c.json({ error: \"invalid session id\" }, 400);\n\t\t\t}\n\n\t\t\tlet inserted = 0;\n\t\t\tconst lastSeenBySession = new Map<string, number>();\n\t\t\tconst metaBySession = new Map<string, Record<string, string>>();\n\t\t\tconst sessionIds = new Set<string>();\n\t\t\tconst batchBySession = new Map<string, Record<string, unknown>[]>();\n\n\t\t\tfor (const item of items) {\n\t\t\t\tif (item == null || typeof item !== \"object\" || Array.isArray(item)) {\n\t\t\t\t\treturn c.json({ error: \"event must be an object\" }, 400);\n\t\t\t\t}\n\t\t\t\tconst itemObj = item as Record<string, unknown>;\n\n\t\t\t\tlet itemSessionId: string | null;\n\t\t\t\ttry {\n\t\t\t\t\titemSessionId = resolveSessionStreamId(itemObj);\n\t\t\t\t} catch (err) {\n\t\t\t\t\treturn c.json({ error: (err as Error).message }, 400);\n\t\t\t\t}\n\t\t\t\tconst opencodeSessionId = String(itemSessionId ?? defaultSessionId ?? \"\");\n\t\t\t\tif (!opencodeSessionId) {\n\t\t\t\t\treturn c.json({ error: \"session id required\" }, 400);\n\t\t\t\t}\n\t\t\t\tif (opencodeSessionId.startsWith(\"msg_\")) {\n\t\t\t\t\treturn c.json({ error: \"invalid session id\" }, 400);\n\t\t\t\t}\n\n\t\t\t\tlet eventId = String(itemObj.event_id ?? \"\");\n\t\t\t\tconst eventType = String(itemObj.event_type ?? \"\");\n\t\t\t\tif (!eventType) {\n\t\t\t\t\treturn c.json({ error: \"event_type required\" }, 400);\n\t\t\t\t}\n\n\t\t\t\tconst eventSeqValue = itemObj.event_seq;\n\t\t\t\tif (eventSeqValue != null) {\n\t\t\t\t\tconst parsed = Number(eventSeqValue);\n\t\t\t\t\tif (!Number.isFinite(parsed) || parsed !== Math.floor(parsed)) {\n\t\t\t\t\t\treturn c.json({ error: \"event_seq must be int\" }, 400);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tlet tsWallMs = itemObj.ts_wall_ms;\n\t\t\t\tif (tsWallMs != null) {\n\t\t\t\t\tconst parsed = Number(tsWallMs);\n\t\t\t\t\tif (!Number.isFinite(parsed)) {\n\t\t\t\t\t\treturn c.json({ error: \"ts_wall_ms must be int\" }, 400);\n\t\t\t\t\t}\n\t\t\t\t\ttsWallMs = Math.floor(parsed);\n\t\t\t\t\tconst prev = lastSeenBySession.get(opencodeSessionId) ?? (tsWallMs as number);\n\t\t\t\t\tlastSeenBySession.set(opencodeSessionId, Math.max(prev, tsWallMs as number));\n\t\t\t\t}\n\n\t\t\t\tlet tsMonoMs = itemObj.ts_mono_ms;\n\t\t\t\tif (tsMonoMs != null) {\n\t\t\t\t\tconst parsed = Number(tsMonoMs);\n\t\t\t\t\tif (!Number.isFinite(parsed)) {\n\t\t\t\t\t\treturn c.json({ error: \"ts_mono_ms must be number\" }, 400);\n\t\t\t\t\t}\n\t\t\t\t\ttsMonoMs = parsed;\n\t\t\t\t}\n\n\t\t\t\tlet eventPayload = itemObj.payload;\n\t\t\t\tif (eventPayload == null) eventPayload = {};\n\t\t\t\tif (typeof eventPayload !== \"object\" || Array.isArray(eventPayload)) {\n\t\t\t\t\treturn c.json({ error: \"payload must be an object\" }, 400);\n\t\t\t\t}\n\n\t\t\t\t// Per-item meta fields\n\t\t\t\tconst itemCwd = itemObj.cwd;\n\t\t\t\tif (itemCwd != null && typeof itemCwd !== \"string\") {\n\t\t\t\t\treturn c.json({ error: \"cwd must be string\" }, 400);\n\t\t\t\t}\n\t\t\t\tconst itemProject = itemObj.project;\n\t\t\t\tif (itemProject != null && typeof itemProject !== \"string\") {\n\t\t\t\t\treturn c.json({ error: \"project must be string\" }, 400);\n\t\t\t\t}\n\t\t\t\tconst itemStartedAt = itemObj.started_at;\n\t\t\t\tif (itemStartedAt != null && typeof itemStartedAt !== \"string\") {\n\t\t\t\t\treturn c.json({ error: \"started_at must be string\" }, 400);\n\t\t\t\t}\n\n\t\t\t\t// Sanitize payload\n\t\t\t\teventPayload = stripPrivateObj(eventPayload) as Record<string, unknown>;\n\n\t\t\t\t// Generate stable event_id for legacy senders.\n\t\t\t\t// Python uses json.dumps(sort_keys=True) which recursively sorts all keys.\n\t\t\t\t// We replicate with a recursive key-sorting replacer.\n\t\t\t\tif (!eventId) {\n\t\t\t\t\tconst sortedStringify = (obj: unknown): string =>\n\t\t\t\t\t\tJSON.stringify(obj, (_key, value) => {\n\t\t\t\t\t\t\tif (value != null && typeof value === \"object\" && !Array.isArray(value)) {\n\t\t\t\t\t\t\t\tconst sorted: Record<string, unknown> = {};\n\t\t\t\t\t\t\t\tfor (const k of Object.keys(value as Record<string, unknown>).sort()) {\n\t\t\t\t\t\t\t\t\tsorted[k] = (value as Record<string, unknown>)[k];\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn sorted;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn value;\n\t\t\t\t\t\t});\n\t\t\t\t\tif (eventSeqValue != null) {\n\t\t\t\t\t\tconst rawId = sortedStringify({\n\t\t\t\t\t\t\ts: eventSeqValue,\n\t\t\t\t\t\t\tt: eventType,\n\t\t\t\t\t\t\tp: eventPayload,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst hash = createHash(\"sha256\").update(rawId, \"utf-8\").digest(\"hex\").slice(0, 16);\n\t\t\t\t\t\teventId = `legacy-seq-${eventSeqValue}-${hash}`;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst rawId = sortedStringify({\n\t\t\t\t\t\t\tm: tsMonoMs ?? null,\n\t\t\t\t\t\t\tp: eventPayload,\n\t\t\t\t\t\t\tt: eventType,\n\t\t\t\t\t\t\tw: tsWallMs ?? null,\n\t\t\t\t\t\t});\n\t\t\t\t\t\teventId = `legacy-${createHash(\"sha256\").update(rawId, \"utf-8\").digest(\"hex\").slice(0, 16)}`;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst eventEntry: Record<string, unknown> = {\n\t\t\t\t\tevent_id: eventId,\n\t\t\t\t\tevent_type: eventType,\n\t\t\t\t\tpayload: eventPayload,\n\t\t\t\t\tts_wall_ms: tsWallMs ?? null,\n\t\t\t\t\tts_mono_ms: tsMonoMs ?? null,\n\t\t\t\t};\n\n\t\t\t\tsessionIds.add(opencodeSessionId);\n\t\t\t\tconst list = batchBySession.get(opencodeSessionId) ?? [];\n\t\t\t\tlist.push({ ...eventEntry });\n\t\t\t\tbatchBySession.set(opencodeSessionId, list);\n\n\t\t\t\tif (itemCwd || itemProject || itemStartedAt) {\n\t\t\t\t\tconst perSession = metaBySession.get(opencodeSessionId) ?? {};\n\t\t\t\t\tif (itemCwd) perSession.cwd = itemCwd as string;\n\t\t\t\t\tif (itemProject) perSession.project = itemProject as string;\n\t\t\t\t\tif (itemStartedAt) perSession.started_at = itemStartedAt as string;\n\t\t\t\t\tmetaBySession.set(opencodeSessionId, perSession);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Insert events\n\t\t\tif (sessionIds.size === 1) {\n\t\t\t\tconst singleSessionId = sessionIds.values().next().value as string;\n\t\t\t\tconst batch = batchBySession.get(singleSessionId) ?? [];\n\t\t\t\tconst result = store.recordRawEventsBatch(singleSessionId, batch);\n\t\t\t\tinserted = result.inserted;\n\t\t\t} else {\n\t\t\t\tfor (const [sid, sidEvents] of batchBySession) {\n\t\t\t\t\tconst result = store.recordRawEventsBatch(sid, sidEvents);\n\t\t\t\t\tinserted += result.inserted;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Update session metadata\n\t\t\tfor (const metaSessionId of sessionIds) {\n\t\t\t\tconst sessionMeta = metaBySession.get(metaSessionId) ?? {};\n\t\t\t\tconst applyRequestMeta = sessionIds.size === 1 || metaSessionId === defaultSessionId;\n\t\t\t\tstore.updateRawEventSessionMeta({\n\t\t\t\t\topencodeSessionId: metaSessionId,\n\t\t\t\t\tcwd:\n\t\t\t\t\t\tsessionMeta.cwd ?? (applyRequestMeta ? (cwd as string | undefined) : undefined) ?? null,\n\t\t\t\t\tproject:\n\t\t\t\t\t\tsessionMeta.project ??\n\t\t\t\t\t\t(applyRequestMeta ? (project as string | undefined) : undefined) ??\n\t\t\t\t\t\tnull,\n\t\t\t\t\tstartedAt:\n\t\t\t\t\t\tsessionMeta.started_at ??\n\t\t\t\t\t\t(applyRequestMeta ? (startedAt as string | undefined) : undefined) ??\n\t\t\t\t\t\tnull,\n\t\t\t\t\tlastSeenTsWallMs: lastSeenBySession.get(metaSessionId) ?? null,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Note per-session activity for optional debounced auto-flush.\n\t\t\tnudgeSweeper(sweeper, sessionIds);\n\n\t\t\treturn c.json({ inserted, received: (items as unknown[]).length });\n\t\t} catch (err) {\n\t\t\tconst response: Record<string, unknown> = { error: \"internal server error\" };\n\t\t\tif (process.env.CODEMEM_VIEWER_DEBUG === \"1\") {\n\t\t\t\tresponse.detail = (err as Error).message;\n\t\t\t}\n\t\t\treturn c.json(response, 500);\n\t\t}\n\t});\n\n\t// POST /api/claude-hooks — ingest Claude Code hook events\n\tapp.post(\"/api/claude-hooks\", async (c) => {\n\t\tconst result = await parseJsonObjectBody(c, MAX_RAW_EVENTS_BODY_BYTES);\n\t\tif (result instanceof Response) return result;\n\t\tconst payload = result;\n\n\t\t// Map hook payload → raw event envelope\n\t\tconst envelope = buildRawEventEnvelopeFromHook(payload);\n\t\tif (envelope === null) {\n\t\t\t// Unsupported event type or missing required fields — skip gracefully\n\t\t\treturn c.json({ inserted: 0, skipped: 1 });\n\t\t}\n\n\t\tconst store = getStore();\n\t\ttry {\n\t\t\tconst opencodeSessionId = envelope.opencode_session_id;\n\t\t\tconst source = envelope.source;\n\t\t\tconst strippedPayload = stripPrivateObj(envelope.payload) as Record<string, unknown>;\n\n\t\t\tconst inserted = store.recordRawEvent({\n\t\t\t\topencodeSessionId,\n\t\t\t\tsource,\n\t\t\t\teventId: envelope.event_id,\n\t\t\t\teventType: \"claude.hook\",\n\t\t\t\tpayload: strippedPayload,\n\t\t\t\ttsWallMs: envelope.ts_wall_ms,\n\t\t\t});\n\n\t\t\tstore.updateRawEventSessionMeta({\n\t\t\t\topencodeSessionId,\n\t\t\t\tsource,\n\t\t\t\tcwd: envelope.cwd,\n\t\t\t\tproject: envelope.project,\n\t\t\t\tstartedAt: envelope.started_at,\n\t\t\t\tlastSeenTsWallMs: envelope.ts_wall_ms,\n\t\t\t});\n\n\t\t\t// Note activity for optional debounced auto-flush.\n\t\t\tnudgeSweeper(sweeper, [opencodeSessionId], source);\n\n\t\t\treturn c.json({ inserted: inserted ? 1 : 0, skipped: 0 });\n\t\t} catch (err) {\n\t\t\tconst response: Record<string, unknown> = { error: \"internal server error\" };\n\t\t\tif (process.env.CODEMEM_VIEWER_DEBUG === \"1\") {\n\t\t\t\tresponse.detail = (err as Error).message;\n\t\t\t}\n\t\t\treturn c.json(response, 500);\n\t\t}\n\t});\n\n\treturn app;\n}\n","/**\n * Stats routes — GET /api/stats, GET /api/usage.\n *\n * Ports Python's viewer_routes/stats.py.\n */\n\nimport type { MemoryStore } from \"@codemem/core\";\nimport { Hono } from \"hono\";\n\n/**\n * Create stats routes. The store factory is called per-request to get a\n * fresh connection (matching the Python viewer pattern).\n */\nexport function statsRoutes(getStore: () => MemoryStore) {\n\tconst app = new Hono();\n\n\tapp.get(\"/api/stats\", (c) => {\n\t\tconst store = getStore();\n\t\treturn c.json(store.stats());\n\t});\n\n\tapp.get(\"/api/usage\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst projectFilter = c.req.query(\"project\") || null;\n\t\t\tconst eventsGlobal = store.db\n\t\t\t\t.prepare(\n\t\t\t\t\t`SELECT event,\n\t\t\t\t\t\tSUM(tokens_read) AS total_tokens_read,\n\t\t\t\t\t\tSUM(tokens_written) AS total_tokens_written,\n\t\t\t\t\t\tSUM(tokens_saved) AS total_tokens_saved,\n\t\t\t\t\t\tCOUNT(*) AS count\n\t\t\t\t\t FROM usage_events GROUP BY event ORDER BY event`,\n\t\t\t\t)\n\t\t\t\t.all() as Record<string, unknown>[];\n\t\t\tconst totalsGlobal = store.db\n\t\t\t\t.prepare(\n\t\t\t\t\t`SELECT COALESCE(SUM(tokens_read),0) AS tokens_read,\n\t\t\t\t\t\tCOALESCE(SUM(tokens_written),0) AS tokens_written,\n\t\t\t\t\t\tCOALESCE(SUM(tokens_saved),0) AS tokens_saved,\n\t\t\t\t\t\tCOUNT(*) AS count\n\t\t\t\t\t FROM usage_events`,\n\t\t\t\t)\n\t\t\t\t.get() as Record<string, unknown>;\n\t\t\tlet eventsFiltered: Record<string, unknown>[] | null = null;\n\t\t\tlet totalsFiltered: Record<string, unknown> | null = null;\n\t\t\tif (projectFilter) {\n\t\t\t\teventsFiltered = store.db\n\t\t\t\t\t.prepare(\n\t\t\t\t\t\t`SELECT event,\n\t\t\t\t\t\t\tSUM(tokens_read) AS total_tokens_read,\n\t\t\t\t\t\t\tSUM(tokens_written) AS total_tokens_written,\n\t\t\t\t\t\t\tSUM(tokens_saved) AS total_tokens_saved,\n\t\t\t\t\t\t\tCOUNT(*) AS count\n\t\t\t\t\t\t FROM usage_events\n\t\t\t\t\t\t JOIN sessions ON sessions.id = usage_events.session_id\n\t\t\t\t\t\t WHERE sessions.project = ?\n\t\t\t\t\t\t GROUP BY event ORDER BY event`,\n\t\t\t\t\t)\n\t\t\t\t\t.all(projectFilter) as Record<string, unknown>[];\n\t\t\t\ttotalsFiltered = store.db\n\t\t\t\t\t.prepare(\n\t\t\t\t\t\t`SELECT COALESCE(SUM(tokens_read),0) AS tokens_read,\n\t\t\t\t\t\t\tCOALESCE(SUM(tokens_written),0) AS tokens_written,\n\t\t\t\t\t\t\tCOALESCE(SUM(tokens_saved),0) AS tokens_saved,\n\t\t\t\t\t\t\tCOUNT(*) AS count\n\t\t\t\t\t\t FROM usage_events\n\t\t\t\t\t\t JOIN sessions ON sessions.id = usage_events.session_id\n\t\t\t\t\t\t WHERE sessions.project = ?`,\n\t\t\t\t\t)\n\t\t\t\t\t.get(projectFilter) as Record<string, unknown>;\n\t\t\t}\n\t\t\treturn c.json({\n\t\t\t\tproject: projectFilter,\n\t\t\t\tevents: projectFilter ? eventsFiltered : eventsGlobal,\n\t\t\t\ttotals: projectFilter ? totalsFiltered : totalsGlobal,\n\t\t\t\tevents_global: eventsGlobal,\n\t\t\t\ttotals_global: totalsGlobal,\n\t\t\t\tevents_filtered: eventsFiltered,\n\t\t\t\ttotals_filtered: totalsFiltered,\n\t\t\t\trecent_packs: [],\n\t\t\t});\n\t\t}\n\t});\n\n\treturn app;\n}\n","/**\n * Sync routes — status, peers, actors, attempts, pairing, mutations.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport net from \"node:net\";\nimport { dirname, join } from \"node:path\";\nimport type { MemoryStore, ReplicationOp } from \"@codemem/core\";\nimport {\n\tapplyReplicationOps,\n\tcleanupNonces,\n\tcoordinatorCreateInviteAction,\n\tcoordinatorImportInviteAction,\n\tcoordinatorReviewJoinRequestAction,\n\tcoordinatorStatusSnapshot,\n\tDEFAULT_TIME_WINDOW_S,\n\tensureDeviceIdentity,\n\textractReplicationOps,\n\tfingerprintPublicKey,\n\tlistCoordinatorJoinRequests,\n\tloadReplicationOpsSince,\n\treadCoordinatorSyncConfig,\n\trecordNonce,\n\tschema,\n\tverifySignature,\n} from \"@codemem/core\";\nimport { count, desc, eq, max, ne } from \"drizzle-orm\";\nimport { drizzle } from \"drizzle-orm/better-sqlite3\";\nimport { Hono } from \"hono\";\nimport { queryBool, queryInt, safeJsonList } from \"../helpers.js\";\n\ntype StoreFactory = () => MemoryStore;\n\nconst SYNC_STALE_AFTER_SECONDS = 10 * 60;\nconst SYNC_PROTOCOL_VERSION = \"1\";\n\nfunction intEnvOr(name: string, fallback: number): number {\n\tconst value = Number.parseInt(process.env[name] ?? \"\", 10);\n\treturn Number.isFinite(value) ? value : fallback;\n}\n\nconst MAX_SYNC_BODY_BYTES = intEnvOr(\"CODEMEM_SYNC_MAX_BODY_BYTES\", 1_048_576);\nconst MAX_SYNC_OPS = intEnvOr(\"CODEMEM_SYNC_MAX_OPS\", 2000);\n\nconst PAIRING_FILTER_HINT =\n\t\"Run this on another device with codemem sync pair --accept '<payload>'. \" +\n\t\"On the accepting device, --include/--exclude control both what it sends and what it accepts from that peer.\";\n\nfunction pathWithQuery(url: string): string {\n\tconst parsed = new URL(url);\n\treturn parsed.search ? `${parsed.pathname}${parsed.search}` : parsed.pathname;\n}\n\nfunction unauthorizedPayload(reason: string): Record<string, string> {\n\tif (process.env.CODEMEM_SYNC_AUTH_DIAGNOSTICS === \"1\") {\n\t\treturn { error: \"unauthorized\", reason };\n\t}\n\treturn { error: \"unauthorized\" };\n}\n\nfunction authorizeSyncRequest(\n\tstore: MemoryStore,\n\trequest: { method: string; url: string; header(name: string): string | undefined },\n\tbody: Buffer,\n): { ok: boolean; reason: string; deviceId: string } {\n\tconst deviceId = (request.header(\"X-Opencode-Device\") ?? \"\").trim();\n\tconst signature = request.header(\"X-Opencode-Signature\") ?? \"\";\n\tconst timestamp = request.header(\"X-Opencode-Timestamp\") ?? \"\";\n\tconst nonce = request.header(\"X-Opencode-Nonce\") ?? \"\";\n\tif (!deviceId || !signature || !timestamp || !nonce) {\n\t\treturn { ok: false, reason: \"missing_headers\", deviceId };\n\t}\n\n\tconst peerRow = store.db\n\t\t.prepare(\n\t\t\t\"SELECT pinned_fingerprint, public_key FROM sync_peers WHERE peer_device_id = ? LIMIT 1\",\n\t\t)\n\t\t.get(deviceId) as { pinned_fingerprint: string | null; public_key: string | null } | undefined;\n\tif (!peerRow) {\n\t\treturn { ok: false, reason: \"unknown_peer\", deviceId };\n\t}\n\n\tconst pinnedFingerprint = String(peerRow.pinned_fingerprint ?? \"\").trim();\n\tconst publicKey = String(peerRow.public_key ?? \"\").trim();\n\tif (!pinnedFingerprint || !publicKey) {\n\t\treturn { ok: false, reason: \"peer_record_incomplete\", deviceId };\n\t}\n\tif (fingerprintPublicKey(publicKey) !== pinnedFingerprint) {\n\t\treturn { ok: false, reason: \"fingerprint_mismatch\", deviceId };\n\t}\n\n\tlet valid = false;\n\ttry {\n\t\tvalid = verifySignature({\n\t\t\tmethod: request.method,\n\t\t\tpathWithQuery: pathWithQuery(request.url),\n\t\t\tbodyBytes: body,\n\t\t\ttimestamp,\n\t\t\tnonce,\n\t\t\tsignature,\n\t\t\tpublicKey,\n\t\t\tdeviceId,\n\t\t});\n\t} catch {\n\t\treturn { ok: false, reason: \"signature_verification_error\", deviceId };\n\t}\n\n\tif (!valid) {\n\t\treturn { ok: false, reason: \"invalid_signature\", deviceId };\n\t}\n\n\tconst createdAt = new Date().toISOString();\n\tif (!recordNonce(store.db, deviceId, nonce, createdAt)) {\n\t\treturn { ok: false, reason: \"nonce_replay\", deviceId };\n\t}\n\n\tconst cutoff = new Date(Date.now() - DEFAULT_TIME_WINDOW_S * 2 * 1000).toISOString();\n\tcleanupNonces(store.db, cutoff);\n\treturn { ok: true, reason: \"ok\", deviceId };\n}\n\nfunction projectBasename(value: string | null | undefined): string {\n\tconst project = String(value ?? \"\")\n\t\t.trim()\n\t\t.replaceAll(\"\\\\\", \"/\");\n\tif (!project) return \"\";\n\tconst parts = project.split(\"/\").filter(Boolean);\n\treturn parts.length > 0 ? (parts[parts.length - 1] ?? \"\") : \"\";\n}\n\nfunction parseJsonList(value: unknown): string[] {\n\tif (value == null) return [];\n\tif (typeof value === \"string\") {\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(value) as unknown;\n\t\t\tif (!Array.isArray(parsed)) return [];\n\t\t\treturn parsed.map((entry) => String(entry ?? \"\").trim()).filter(Boolean);\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t}\n\tif (!Array.isArray(value)) return [];\n\treturn value.map((entry) => String(entry ?? \"\").trim()).filter(Boolean);\n}\n\nfunction readPeerProjectFilters(\n\tstore: MemoryStore,\n\tpeerDeviceId: string,\n): { include: string[]; exclude: string[] } {\n\tconst globalConfig = readCoordinatorSyncConfig();\n\tconst row = store.db\n\t\t.prepare(\n\t\t\t\"SELECT projects_include_json, projects_exclude_json FROM sync_peers WHERE peer_device_id = ? LIMIT 1\",\n\t\t)\n\t\t.get(peerDeviceId) as\n\t\t| { projects_include_json: string | null; projects_exclude_json: string | null }\n\t\t| undefined;\n\tif (!row) {\n\t\treturn {\n\t\t\tinclude: globalConfig.syncProjectsInclude,\n\t\t\texclude: globalConfig.syncProjectsExclude,\n\t\t};\n\t}\n\tconst hasOverride = row.projects_include_json != null || row.projects_exclude_json != null;\n\tif (!hasOverride) {\n\t\treturn {\n\t\t\tinclude: globalConfig.syncProjectsInclude,\n\t\t\texclude: globalConfig.syncProjectsExclude,\n\t\t};\n\t}\n\treturn {\n\t\tinclude: parseJsonList(row.projects_include_json),\n\t\texclude: parseJsonList(row.projects_exclude_json),\n\t};\n}\n\nfunction peerClaimedLocalActor(store: MemoryStore, peerDeviceId: string): boolean {\n\tconst row = store.db\n\t\t.prepare(\"SELECT claimed_local_actor FROM sync_peers WHERE peer_device_id = ? LIMIT 1\")\n\t\t.get(peerDeviceId) as { claimed_local_actor: number | null } | undefined;\n\treturn Boolean(row?.claimed_local_actor);\n}\n\nfunction parseOpPayload(op: { payload_json: string | null }): Record<string, unknown> | null {\n\tif (!op.payload_json || !String(op.payload_json).trim()) return null;\n\ttry {\n\t\tconst parsed = JSON.parse(op.payload_json) as unknown;\n\t\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) return null;\n\t\treturn parsed as Record<string, unknown>;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction isSharedVisibility(payload: Record<string, unknown> | null): boolean {\n\tif (!payload) return false;\n\tlet visibility = String(payload.visibility ?? \"\")\n\t\t.trim()\n\t\t.toLowerCase();\n\tconst metadata =\n\t\tpayload.metadata_json &&\n\t\ttypeof payload.metadata_json === \"object\" &&\n\t\t!Array.isArray(payload.metadata_json)\n\t\t\t? (payload.metadata_json as Record<string, unknown>)\n\t\t\t: {};\n\tconst metadataVisibility = String(metadata.visibility ?? \"\")\n\t\t.trim()\n\t\t.toLowerCase();\n\tif (!visibility && metadataVisibility) visibility = metadataVisibility;\n\tif (!visibility) {\n\t\tlet workspaceKind = String(payload.workspace_kind ?? \"\")\n\t\t\t.trim()\n\t\t\t.toLowerCase();\n\t\tlet workspaceId = String(payload.workspace_id ?? \"\")\n\t\t\t.trim()\n\t\t\t.toLowerCase();\n\t\tif (!workspaceKind)\n\t\t\tworkspaceKind = String(metadata.workspace_kind ?? \"\")\n\t\t\t\t.trim()\n\t\t\t\t.toLowerCase();\n\t\tif (!workspaceId)\n\t\t\tworkspaceId = String(metadata.workspace_id ?? \"\")\n\t\t\t\t.trim()\n\t\t\t\t.toLowerCase();\n\t\tif (workspaceKind === \"shared\" || workspaceId.startsWith(\"shared:\")) {\n\t\t\tvisibility = \"shared\";\n\t\t} else {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn visibility === \"shared\";\n}\n\nfunction projectAllowed(\n\tprojectValue: string | null,\n\tfilters: { include: string[]; exclude: string[] },\n): boolean {\n\tconst value = String(projectValue ?? \"\").trim();\n\tconst valueBase = projectBasename(value);\n\tfor (const blocked of filters.exclude) {\n\t\tif (blocked === value || blocked === valueBase) return false;\n\t}\n\tif (filters.include.length === 0) return true;\n\tfor (const allowed of filters.include) {\n\t\tif (allowed === value || allowed === valueBase) return true;\n\t}\n\treturn false;\n}\n\nfunction filterOpsForPeer(\n\tstore: MemoryStore,\n\tpeerDeviceId: string,\n\tops: ReplicationOp[],\n): { allowed: ReplicationOp[]; skipped: number } {\n\tconst filters = readPeerProjectFilters(store, peerDeviceId);\n\tconst allowPrivate = peerClaimedLocalActor(store, peerDeviceId);\n\tconst allowed: ReplicationOp[] = [];\n\tlet skipped = 0;\n\tfor (const op of ops) {\n\t\tif (op.entity_type !== \"memory_item\") {\n\t\t\tallowed.push(op);\n\t\t\tcontinue;\n\t\t}\n\t\tconst payload = parseOpPayload(op);\n\t\tif (!allowPrivate && !isSharedVisibility(payload)) {\n\t\t\tskipped++;\n\t\t\tcontinue;\n\t\t}\n\t\tconst project = payload && typeof payload.project === \"string\" ? payload.project : null;\n\t\tif (!projectAllowed(project, filters)) {\n\t\t\tskipped++;\n\t\t\tcontinue;\n\t\t}\n\t\tallowed.push(op);\n\t}\n\treturn { allowed, skipped };\n}\n\n// ---------------------------------------------------------------------------\n// Peer row mapping — deduplicated helper (fix #4)\n// ---------------------------------------------------------------------------\n\n/**\n * Map a raw sync_peers DB row to the API response shape.\n * When showDiag is false, sensitive fields (fingerprint, last_error, addresses)\n * are redacted.\n */\nfunction mapPeerRow(row: Record<string, unknown>, showDiag: boolean): Record<string, unknown> {\n\treturn {\n\t\tpeer_device_id: row.peer_device_id,\n\t\tname: row.name,\n\t\tfingerprint: showDiag ? row.pinned_fingerprint : null,\n\t\tpinned: Boolean(row.pinned_fingerprint),\n\t\taddresses: showDiag ? safeJsonList(row.addresses_json as string | null) : [],\n\t\tlast_seen_at: row.last_seen_at,\n\t\tlast_sync_at: row.last_sync_at,\n\t\tlast_error: showDiag ? row.last_error : null,\n\t\thas_error: Boolean(row.last_error),\n\t\tclaimed_local_actor: Boolean(row.claimed_local_actor),\n\t\tactor_id: row.actor_id ?? null,\n\t\tactor_display_name: row.actor_display_name ?? null,\n\t\tproject_scope: {\n\t\t\tinclude: safeJsonList(row.projects_include_json as string | null),\n\t\t\texclude: safeJsonList(row.projects_exclude_json as string | null),\n\t\t\teffective_include: safeJsonList(row.projects_include_json as string | null),\n\t\t\teffective_exclude: safeJsonList(row.projects_exclude_json as string | null),\n\t\t\tinherits_global: row.projects_include_json == null && row.projects_exclude_json == null,\n\t\t},\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// Peer status helpers\n// ---------------------------------------------------------------------------\n\nfunction isRecentIso(value: unknown, windowS = SYNC_STALE_AFTER_SECONDS): boolean {\n\tconst raw = String(value ?? \"\").trim();\n\tif (!raw) return false;\n\tconst normalized = raw.replace(\"Z\", \"+00:00\");\n\tconst hasOffset = /(?:Z|[+-]\\d{2}:?\\d{2})$/i.test(raw);\n\tconst ts = new Date(hasOffset ? normalized : `${normalized}+00:00`);\n\tif (Number.isNaN(ts.getTime())) return false;\n\tconst ageS = (Date.now() - ts.getTime()) / 1000;\n\treturn ageS >= 0 && ageS <= windowS;\n}\n\nfunction peerStatus(peer: Record<string, unknown>): Record<string, unknown> {\n\tconst lastSyncAt = peer.last_sync_at;\n\tconst lastPingAt = peer.last_seen_at;\n\tconst hasError = Boolean(peer.has_error);\n\n\tconst syncFresh = isRecentIso(lastSyncAt);\n\tconst pingFresh = isRecentIso(lastPingAt);\n\n\tlet peerState: string;\n\tif (hasError && !(syncFresh || pingFresh)) peerState = \"offline\";\n\telse if (hasError) peerState = \"degraded\";\n\telse if (syncFresh || pingFresh) peerState = \"online\";\n\telse if (lastSyncAt || lastPingAt) peerState = \"stale\";\n\telse peerState = \"unknown\";\n\n\tconst syncStatus = hasError ? \"error\" : syncFresh ? \"ok\" : lastSyncAt ? \"stale\" : \"unknown\";\n\tconst pingStatus = pingFresh ? \"ok\" : lastPingAt ? \"stale\" : \"unknown\";\n\n\treturn {\n\t\tsync_status: syncStatus,\n\t\tping_status: pingStatus,\n\t\tpeer_state: peerState,\n\t\tfresh: syncFresh || pingFresh,\n\t\tlast_sync_at: lastSyncAt,\n\t\tlast_ping_at: lastPingAt,\n\t};\n}\n\nfunction attemptStatus(attempt: Record<string, unknown>): string {\n\tif (attempt.ok) return \"ok\";\n\tif (attempt.error) return \"error\";\n\treturn \"unknown\";\n}\n\nfunction readViewerBinding(dbPath: string): { host: string; port: number } | null {\n\ttry {\n\t\tconst raw = readFileSync(join(dirname(dbPath), \"viewer.pid\"), \"utf8\");\n\t\tconst parsed = JSON.parse(raw) as Partial<{ host: string; port: number }>;\n\t\tif (typeof parsed.host === \"string\" && typeof parsed.port === \"number\") {\n\t\t\treturn { host: parsed.host, port: parsed.port };\n\t\t}\n\t} catch {\n\t\t// ignore missing/malformed pidfile\n\t}\n\treturn null;\n}\n\nasync function portOpen(host: string, port: number): Promise<boolean> {\n\treturn new Promise((resolve) => {\n\t\tconst socket = net.createConnection({ host, port });\n\t\tconst done = (ok: boolean) => {\n\t\t\tsocket.removeAllListeners();\n\t\t\tsocket.destroy();\n\t\t\tresolve(ok);\n\t\t};\n\t\tsocket.setTimeout(300);\n\t\tsocket.once(\"connect\", () => done(true));\n\t\tsocket.once(\"timeout\", () => done(false));\n\t\tsocket.once(\"error\", () => done(false));\n\t});\n}\n\nconst PEERS_QUERY = `\n\tSELECT p.peer_device_id, p.name, p.pinned_fingerprint, p.addresses_json,\n\t p.last_seen_at, p.last_sync_at, p.last_error,\n\t p.projects_include_json, p.projects_exclude_json, p.claimed_local_actor,\n\t p.actor_id, a.display_name AS actor_display_name\n\tFROM sync_peers AS p\n\tLEFT JOIN actors AS a ON a.actor_id = p.actor_id\n\tORDER BY name, peer_device_id\n`;\n\n// ---------------------------------------------------------------------------\n// Route factory\n// ---------------------------------------------------------------------------\n\nexport function syncRoutes(getStore: StoreFactory) {\n\tconst app = new Hono();\n\n\t// GET /v1/status (peer sync protocol)\n\tapp.get(\"/v1/status\", (c) => {\n\t\tconst store = getStore();\n\t\tconst auth = authorizeSyncRequest(store, c.req, Buffer.alloc(0));\n\t\tif (!auth.ok) return c.json(unauthorizedPayload(auth.reason), 401);\n\n\t\ttry {\n\t\t\tlet device = store.db\n\t\t\t\t.prepare(\"SELECT device_id, fingerprint FROM sync_device LIMIT 1\")\n\t\t\t\t.get() as { device_id: string; fingerprint: string } | undefined;\n\t\t\tif (!device) {\n\t\t\t\tconst [deviceId, fingerprint] = ensureDeviceIdentity(store.db);\n\t\t\t\tdevice = { device_id: deviceId, fingerprint };\n\t\t\t}\n\t\t\treturn c.json({\n\t\t\t\tdevice_id: device.device_id,\n\t\t\t\tprotocol_version: SYNC_PROTOCOL_VERSION,\n\t\t\t\tfingerprint: device.fingerprint,\n\t\t\t});\n\t\t} catch {\n\t\t\treturn c.json({ error: \"internal_error\" }, 500);\n\t\t}\n\t});\n\n\t// GET /v1/ops (peer sync protocol)\n\tapp.get(\"/v1/ops\", (c) => {\n\t\tconst store = getStore();\n\t\tconst auth = authorizeSyncRequest(store, c.req, Buffer.alloc(0));\n\t\tif (!auth.ok) return c.json(unauthorizedPayload(auth.reason), 401);\n\t\tconst peerDeviceId = auth.deviceId;\n\n\t\ttry {\n\t\t\tconst since = c.req.query(\"since\") ?? null;\n\t\t\tconst rawLimit = Number.parseInt(c.req.query(\"limit\") ?? \"200\", 10);\n\t\t\tconst limit = Number.isFinite(rawLimit) ? Math.max(1, Math.min(rawLimit, 1000)) : 200;\n\t\t\tlet localDeviceId = store.db.prepare(\"SELECT device_id FROM sync_device LIMIT 1\").get() as\n\t\t\t\t| { device_id: string }\n\t\t\t\t| undefined;\n\t\t\tif (!localDeviceId) {\n\t\t\t\tconst [deviceId] = ensureDeviceIdentity(store.db);\n\t\t\t\tlocalDeviceId = { device_id: deviceId };\n\t\t\t}\n\t\t\tconst [ops, nextCursor] = loadReplicationOpsSince(\n\t\t\t\tstore.db,\n\t\t\t\tsince,\n\t\t\t\tlimit,\n\t\t\t\tlocalDeviceId.device_id,\n\t\t\t);\n\t\t\tconst filtered = filterOpsForPeer(store, peerDeviceId, ops);\n\t\t\treturn c.json({\n\t\t\t\tops: filtered.allowed,\n\t\t\t\tnext_cursor: nextCursor,\n\t\t\t\tskipped: filtered.skipped,\n\t\t\t});\n\t\t} catch {\n\t\t\treturn c.json({ error: \"internal_error\" }, 500);\n\t\t}\n\t});\n\n\t// POST /v1/ops (peer sync protocol)\n\tapp.post(\"/v1/ops\", async (c) => {\n\t\tconst store = getStore();\n\t\tconst raw = Buffer.from(await c.req.arrayBuffer());\n\t\tif (raw.length > MAX_SYNC_BODY_BYTES) {\n\t\t\treturn c.json({ error: \"payload_too_large\" }, 413);\n\t\t}\n\n\t\tconst auth = authorizeSyncRequest(store, c.req, raw);\n\t\tif (!auth.ok) return c.json(unauthorizedPayload(auth.reason), 401);\n\t\tconst peerDeviceId = auth.deviceId;\n\n\t\tlet body: Record<string, unknown>;\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(raw.toString(\"utf-8\")) as unknown;\n\t\t\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n\t\t\t\treturn c.json({ error: \"invalid_json\" }, 400);\n\t\t\t}\n\t\t\tbody = parsed as Record<string, unknown>;\n\t\t} catch {\n\t\t\treturn c.json({ error: \"invalid_json\" }, 400);\n\t\t}\n\n\t\tif (!Array.isArray(body.ops)) {\n\t\t\treturn c.json({ error: \"invalid_ops\" }, 400);\n\t\t}\n\t\tif (body.ops.length > MAX_SYNC_OPS) {\n\t\t\treturn c.json({ error: \"too_many_ops\" }, 413);\n\t\t}\n\n\t\tconst normalizedOps = extractReplicationOps(body);\n\t\tfor (const op of normalizedOps) {\n\t\t\tif (op.device_id !== peerDeviceId || op.clock_device_id !== peerDeviceId) {\n\t\t\t\treturn c.json(\n\t\t\t\t\t{\n\t\t\t\t\t\terror: \"invalid_op_device\",\n\t\t\t\t\t\treason: \"device_id_mismatch\",\n\t\t\t\t\t\top_id: op.op_id,\n\t\t\t\t\t},\n\t\t\t\t\t400,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tlet localDeviceId = store.db.prepare(\"SELECT device_id FROM sync_device LIMIT 1\").get() as\n\t\t\t| { device_id: string }\n\t\t\t| undefined;\n\t\tif (!localDeviceId) {\n\t\t\tconst [deviceId] = ensureDeviceIdentity(store.db);\n\t\t\tlocalDeviceId = { device_id: deviceId };\n\t\t}\n\n\t\tconst filteredInbound = filterOpsForPeer(store, peerDeviceId, normalizedOps);\n\t\tconst result = applyReplicationOps(store.db, filteredInbound.allowed, localDeviceId.device_id);\n\t\treturn c.json({\n\t\t\t...result,\n\t\t\tskipped: result.skipped + filteredInbound.skipped,\n\t\t});\n\t});\n\n\t// GET /api/sync/status\n\tapp.get(\"/api/sync/status\", async (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst showDiag = queryBool(c.req.query(\"includeDiagnostics\"));\n\t\t\tconst includeJoinRequests = queryBool(c.req.query(\"includeJoinRequests\"));\n\t\t\tconst project = c.req.query(\"project\") || null;\n\t\t\tconst config = readCoordinatorSyncConfig();\n\n\t\t\tconst d = drizzle(store.db, { schema });\n\n\t\t\tconst deviceRow = d\n\t\t\t\t.select({\n\t\t\t\t\tdevice_id: schema.syncDevice.device_id,\n\t\t\t\t\tfingerprint: schema.syncDevice.fingerprint,\n\t\t\t\t})\n\t\t\t\t.from(schema.syncDevice)\n\t\t\t\t.limit(1)\n\t\t\t\t.get();\n\n\t\t\tconst daemonState = d\n\t\t\t\t.select()\n\t\t\t\t.from(schema.syncDaemonState)\n\t\t\t\t.where(eq(schema.syncDaemonState.id, 1))\n\t\t\t\t.get();\n\n\t\t\tconst peerCountRow = d.select({ total: count() }).from(schema.syncPeers).get();\n\n\t\t\tconst lastSyncRow = d\n\t\t\t\t.select({ last_sync_at: max(schema.syncPeers.last_sync_at) })\n\t\t\t\t.from(schema.syncPeers)\n\t\t\t\t.get();\n\n\t\t\tconst lastError = daemonState?.last_error as string | null;\n\t\t\tconst lastErrorAt = daemonState?.last_error_at as string | null;\n\t\t\tconst lastOkAt = daemonState?.last_ok_at as string | null;\n\t\t\tconst viewerBinding = readViewerBinding(store.dbPath);\n\t\t\tconst daemonRunning = viewerBinding\n\t\t\t\t? await portOpen(viewerBinding.host, viewerBinding.port)\n\t\t\t\t: false;\n\t\t\tconst daemonDetail = viewerBinding\n\t\t\t\t? daemonRunning\n\t\t\t\t\t? `viewer pidfile at ${viewerBinding.host}:${viewerBinding.port}`\n\t\t\t\t\t: `pidfile present but ${viewerBinding.host}:${viewerBinding.port} is unreachable`\n\t\t\t\t: null;\n\n\t\t\tlet daemonStateValue = \"ok\";\n\t\t\tif (!config.syncEnabled) {\n\t\t\t\tdaemonStateValue = \"disabled\";\n\t\t\t} else if (lastError && (!lastOkAt || String(lastOkAt) < String(lastErrorAt ?? \"\"))) {\n\t\t\t\tdaemonStateValue = \"error\";\n\t\t\t} else if (!daemonRunning) {\n\t\t\t\tdaemonStateValue = \"stopped\";\n\t\t\t}\n\n\t\t\tconst statusPayload: Record<string, unknown> = {\n\t\t\t\tenabled: config.syncEnabled,\n\t\t\t\tinterval_s: config.syncIntervalS,\n\t\t\t\tpeer_count: Number(peerCountRow?.total ?? 0),\n\t\t\t\tlast_sync_at: lastSyncRow?.last_sync_at ?? null,\n\t\t\t\tdaemon_state: daemonStateValue,\n\t\t\t\tdaemon_running: daemonRunning,\n\t\t\t\tdaemon_detail: daemonDetail,\n\t\t\t\tproject_filter_active:\n\t\t\t\t\tconfig.syncProjectsInclude.length > 0 || config.syncProjectsExclude.length > 0,\n\t\t\t\tproject_filter: {\n\t\t\t\t\tinclude: config.syncProjectsInclude,\n\t\t\t\t\texclude: config.syncProjectsExclude,\n\t\t\t\t},\n\t\t\t\tredacted: !showDiag,\n\t\t\t};\n\n\t\t\tif (showDiag) {\n\t\t\t\tstatusPayload.device_id = deviceRow?.device_id ?? null;\n\t\t\t\tstatusPayload.fingerprint = deviceRow?.fingerprint ?? null;\n\t\t\t\tstatusPayload.bind = `${config.syncHost}:${config.syncPort}`;\n\t\t\t\tstatusPayload.daemon_last_error = lastError;\n\t\t\t\tstatusPayload.daemon_last_error_at = lastErrorAt;\n\t\t\t\tstatusPayload.daemon_last_ok_at = lastOkAt;\n\t\t\t}\n\n\t\t\t// Build peers list using deduplicated mapPeerRow\n\t\t\tconst peerRows = store.db.prepare(PEERS_QUERY).all() as Record<string, unknown>[];\n\t\t\tconst peersItems = peerRows.map((row) => {\n\t\t\t\tconst peer = mapPeerRow(row, showDiag);\n\t\t\t\tpeer.status = peerStatus(peer);\n\t\t\t\treturn peer;\n\t\t\t});\n\n\t\t\tconst peersMap: Record<string, unknown> = {};\n\t\t\tfor (const peer of peersItems) {\n\t\t\t\tpeersMap[String(peer.peer_device_id)] = peer.status;\n\t\t\t}\n\n\t\t\t// Attempts\n\t\t\tconst attemptRows = d\n\t\t\t\t.select({\n\t\t\t\t\tpeer_device_id: schema.syncAttempts.peer_device_id,\n\t\t\t\t\tok: schema.syncAttempts.ok,\n\t\t\t\t\terror: schema.syncAttempts.error,\n\t\t\t\t\tstarted_at: schema.syncAttempts.started_at,\n\t\t\t\t\tfinished_at: schema.syncAttempts.finished_at,\n\t\t\t\t\tops_in: schema.syncAttempts.ops_in,\n\t\t\t\t\tops_out: schema.syncAttempts.ops_out,\n\t\t\t\t})\n\t\t\t\t.from(schema.syncAttempts)\n\t\t\t\t.orderBy(desc(schema.syncAttempts.finished_at))\n\t\t\t\t.limit(25)\n\t\t\t\t.all();\n\t\t\tconst attemptsItems = attemptRows.map((row) => ({\n\t\t\t\t...row,\n\t\t\t\tstatus: attemptStatus(row),\n\t\t\t\taddress: null,\n\t\t\t}));\n\n\t\t\tconst statusBlock: Record<string, unknown> = {\n\t\t\t\t...statusPayload,\n\t\t\t\tpeers: peersMap,\n\t\t\t\tpending: 0,\n\t\t\t\tsync: {},\n\t\t\t\tping: {},\n\t\t\t};\n\t\t\tconst legacyDevices = store.claimableLegacyDeviceIds();\n\t\t\tconst sharingReview = store.sharingReviewSummary(project);\n\t\t\tconst coordinator = await coordinatorStatusSnapshot(store, config);\n\t\t\tlet joinRequests: Record<string, unknown>[] = [];\n\t\t\tif (includeJoinRequests && config.syncCoordinatorAdminSecret) {\n\t\t\t\ttry {\n\t\t\t\t\tjoinRequests = await listCoordinatorJoinRequests(config);\n\t\t\t\t} catch {\n\t\t\t\t\tjoinRequests = [];\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (daemonStateValue === \"ok\") {\n\t\t\t\tconst peerStates = new Set(\n\t\t\t\t\tpeersItems.map((peer) =>\n\t\t\t\t\t\tString((peer.status as Record<string, unknown> | undefined)?.peer_state ?? \"\"),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tconst latestFailedRecently = Boolean(\n\t\t\t\t\tattemptsItems[0] &&\n\t\t\t\t\t\tattemptsItems[0].status === \"error\" &&\n\t\t\t\t\t\tisRecentIso(attemptsItems[0].finished_at),\n\t\t\t\t);\n\t\t\t\tconst allOffline =\n\t\t\t\t\tpeersItems.length > 0 &&\n\t\t\t\t\tpeersItems.every(\n\t\t\t\t\t\t(peer) =>\n\t\t\t\t\t\t\tString((peer.status as Record<string, unknown>)?.peer_state ?? \"\") === \"offline\",\n\t\t\t\t\t);\n\t\t\t\tif (latestFailedRecently) {\n\t\t\t\t\tconst hasLivePeer = peerStates.has(\"online\") || peerStates.has(\"degraded\");\n\t\t\t\t\tif (hasLivePeer) daemonStateValue = \"degraded\";\n\t\t\t\t\telse if (allOffline) daemonStateValue = \"offline-peers\";\n\t\t\t\t\telse if (peersItems.length > 0) daemonStateValue = \"stale\";\n\t\t\t\t} else if (peerStates.has(\"degraded\")) {\n\t\t\t\t\tdaemonStateValue = \"degraded\";\n\t\t\t\t} else if (allOffline) {\n\t\t\t\t\tdaemonStateValue = \"offline-peers\";\n\t\t\t\t} else if (peersItems.length > 0 && !peerStates.has(\"online\")) {\n\t\t\t\t\tdaemonStateValue = \"stale\";\n\t\t\t\t}\n\t\t\t\tstatusPayload.daemon_state = daemonStateValue;\n\t\t\t\tstatusBlock.daemon_state = daemonStateValue;\n\t\t\t}\n\n\t\t\tconst responsePayload: Record<string, unknown> = {\n\t\t\t\t...statusPayload,\n\t\t\t\tstatus: statusBlock,\n\t\t\t\tpeers: peersItems,\n\t\t\t\tattempts: attemptsItems.slice(0, 5),\n\t\t\t\tlegacy_devices: legacyDevices,\n\t\t\t\tsharing_review: sharingReview,\n\t\t\t\tcoordinator,\n\t\t\t};\n\t\t\tif (includeJoinRequests) {\n\t\t\t\tresponsePayload.join_requests = joinRequests;\n\t\t\t}\n\t\t\treturn c.json(responsePayload);\n\t\t}\n\t});\n\n\t// GET /api/sync/peers\n\tapp.get(\"/api/sync/peers\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst showDiag = queryBool(c.req.query(\"includeDiagnostics\"));\n\t\t\tconst rows = store.db.prepare(PEERS_QUERY).all() as Record<string, unknown>[];\n\t\t\t// Use deduplicated mapPeerRow helper (fix #4)\n\t\t\tconst peers = rows.map((row) => mapPeerRow(row, showDiag));\n\t\t\treturn c.json({ items: peers, redacted: !showDiag });\n\t\t}\n\t});\n\n\t// GET /api/sync/actors\n\tapp.get(\"/api/sync/actors\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst d = drizzle(store.db, { schema });\n\t\t\tconst includeMerged = queryBool(c.req.query(\"includeMerged\"));\n\t\t\tconst query = d.select().from(schema.actors);\n\t\t\tconst rows = includeMerged\n\t\t\t\t? query.orderBy(schema.actors.display_name).all()\n\t\t\t\t: query.where(ne(schema.actors.status, \"merged\")).orderBy(schema.actors.display_name).all();\n\t\t\treturn c.json({ items: rows });\n\t\t}\n\t});\n\n\t// GET /api/sync/attempts\n\tapp.get(\"/api/sync/attempts\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst d = drizzle(store.db, { schema });\n\t\t\tlet limit = queryInt(c.req.query(\"limit\"), 25);\n\t\t\tif (limit <= 0) return c.json({ error: \"invalid_limit\" }, 400);\n\t\t\tlimit = Math.min(limit, 500);\n\t\t\tconst rows = d\n\t\t\t\t.select({\n\t\t\t\t\tpeer_device_id: schema.syncAttempts.peer_device_id,\n\t\t\t\t\tok: schema.syncAttempts.ok,\n\t\t\t\t\terror: schema.syncAttempts.error,\n\t\t\t\t\tstarted_at: schema.syncAttempts.started_at,\n\t\t\t\t\tfinished_at: schema.syncAttempts.finished_at,\n\t\t\t\t\tops_in: schema.syncAttempts.ops_in,\n\t\t\t\t\tops_out: schema.syncAttempts.ops_out,\n\t\t\t\t})\n\t\t\t\t.from(schema.syncAttempts)\n\t\t\t\t.orderBy(desc(schema.syncAttempts.finished_at))\n\t\t\t\t.limit(limit)\n\t\t\t\t.all();\n\t\t\treturn c.json({ items: rows });\n\t\t}\n\t});\n\n\t// GET /api/sync/pairing — uses ensureDeviceIdentity from core (fix #5 context)\n\tapp.get(\"/api/sync/pairing\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst showDiag = queryBool(c.req.query(\"includeDiagnostics\"));\n\t\t\tif (!showDiag) {\n\t\t\t\treturn c.json({\n\t\t\t\t\tredacted: true,\n\t\t\t\t\tpairing_filter_hint: PAIRING_FILTER_HINT,\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst d = drizzle(store.db, { schema });\n\t\t\tconst deviceRow = d\n\t\t\t\t.select({\n\t\t\t\t\tdevice_id: schema.syncDevice.device_id,\n\t\t\t\t\tpublic_key: schema.syncDevice.public_key,\n\t\t\t\t\tfingerprint: schema.syncDevice.fingerprint,\n\t\t\t\t})\n\t\t\t\t.from(schema.syncDevice)\n\t\t\t\t.limit(1)\n\t\t\t\t.get();\n\n\t\t\tlet deviceId: string | undefined;\n\t\t\tlet publicKey: string | undefined;\n\t\t\tlet fingerprint: string | undefined;\n\n\t\t\tif (deviceRow) {\n\t\t\t\tdeviceId = String(deviceRow.device_id);\n\t\t\t\tpublicKey = String(deviceRow.public_key);\n\t\t\t\tfingerprint = String(deviceRow.fingerprint);\n\t\t\t} else {\n\t\t\t\t// Fall back to ensureDeviceIdentity if no row exists\n\t\t\t\ttry {\n\t\t\t\t\tconst [id, fp] = ensureDeviceIdentity(store.db);\n\t\t\t\t\tdeviceId = id;\n\t\t\t\t\tfingerprint = fp;\n\t\t\t\t\tconst newRow = d\n\t\t\t\t\t\t.select({ public_key: schema.syncDevice.public_key })\n\t\t\t\t\t\t.from(schema.syncDevice)\n\t\t\t\t\t\t.where(eq(schema.syncDevice.device_id, id))\n\t\t\t\t\t\t.get();\n\t\t\t\t\tpublicKey = newRow?.public_key ?? \"\";\n\t\t\t\t} catch {\n\t\t\t\t\treturn c.json({ error: \"device identity unavailable\" }, 500);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!deviceId || !fingerprint) {\n\t\t\t\treturn c.json({ error: \"public key missing\" }, 500);\n\t\t\t}\n\n\t\t\treturn c.json({\n\t\t\t\tdevice_id: deviceId,\n\t\t\t\tfingerprint,\n\t\t\t\tpublic_key: publicKey ?? null,\n\t\t\t\tpairing_filter_hint: PAIRING_FILTER_HINT,\n\t\t\t\taddresses: [],\n\t\t\t});\n\t\t}\n\t});\n\n\t// ------------------------------------------------------------------\n\t// POST mutations\n\t// ------------------------------------------------------------------\n\n\t// POST /api/sync/peers/rename\n\tapp.post(\"/api/sync/peers/rename\", async (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst d = drizzle(store.db, { schema });\n\t\t\tconst body = await c.req.json<Record<string, unknown>>();\n\t\t\tconst peerDeviceId = String(body.peer_device_id ?? \"\").trim();\n\t\t\tconst name = String(body.name ?? \"\").trim();\n\t\t\tif (!peerDeviceId) return c.json({ error: \"peer_device_id required\" }, 400);\n\t\t\tif (!name) return c.json({ error: \"name required\" }, 400);\n\t\t\tconst exists = d\n\t\t\t\t.select({ peer_device_id: schema.syncPeers.peer_device_id })\n\t\t\t\t.from(schema.syncPeers)\n\t\t\t\t.where(eq(schema.syncPeers.peer_device_id, peerDeviceId))\n\t\t\t\t.get();\n\t\t\tif (!exists) return c.json({ error: \"peer not found\" }, 404);\n\t\t\td.update(schema.syncPeers)\n\t\t\t\t.set({ name })\n\t\t\t\t.where(eq(schema.syncPeers.peer_device_id, peerDeviceId))\n\t\t\t\t.run();\n\t\t\treturn c.json({ ok: true });\n\t\t}\n\t});\n\n\tapp.post(\"/api/sync/invites/create\", async (c) => {\n\t\tlet body: Record<string, unknown>;\n\t\ttry {\n\t\t\tbody = await c.req.json<Record<string, unknown>>();\n\t\t} catch {\n\t\t\treturn c.json({ error: \"invalid json\" }, 400);\n\t\t}\n\t\tconst groupId = String(body.group_id ?? \"\").trim();\n\t\tconst coordinatorUrl = body.coordinator_url == null ? null : String(body.coordinator_url ?? \"\");\n\t\tconst policy = String(body.policy ?? \"auto_admit\").trim();\n\t\tconst ttlHours = Number.parseInt(String(body.ttl_hours ?? 24), 10);\n\t\tif (!groupId) return c.json({ error: \"group_id required\" }, 400);\n\t\tif (body.coordinator_url != null && typeof body.coordinator_url !== \"string\") {\n\t\t\treturn c.json({ error: \"coordinator_url must be string\" }, 400);\n\t\t}\n\t\tif (![\"auto_admit\", \"approval_required\"].includes(policy)) {\n\t\t\treturn c.json({ error: \"policy must be auto_admit or approval_required\" }, 400);\n\t\t}\n\t\tif (!Number.isFinite(ttlHours)) return c.json({ error: \"ttl_hours must be int\" }, 400);\n\t\ttry {\n\t\t\tconst config = readCoordinatorSyncConfig();\n\t\t\tconst result = await coordinatorCreateInviteAction({\n\t\t\t\tgroupId,\n\t\t\t\tcoordinatorUrl,\n\t\t\t\tpolicy,\n\t\t\t\tttlHours,\n\t\t\t\tcreatedBy: null,\n\t\t\t\tremoteUrl: config.syncCoordinatorUrl || null,\n\t\t\t\tadminSecret: config.syncCoordinatorAdminSecret || null,\n\t\t\t});\n\t\t\treturn c.json(result);\n\t\t} catch (error) {\n\t\t\treturn c.json({ error: error instanceof Error ? error.message : String(error) }, 400);\n\t\t}\n\t});\n\n\tapp.post(\"/api/sync/invites/import\", async (c) => {\n\t\tconst store = getStore();\n\t\tlet body: Record<string, unknown>;\n\t\ttry {\n\t\t\tbody = await c.req.json<Record<string, unknown>>();\n\t\t} catch {\n\t\t\treturn c.json({ error: \"invalid json\" }, 400);\n\t\t}\n\t\tconst inviteValue = String(body.invite ?? \"\").trim();\n\t\tif (!inviteValue) return c.json({ error: \"invite required\" }, 400);\n\t\ttry {\n\t\t\tconst result = await coordinatorImportInviteAction({ inviteValue, dbPath: store.dbPath });\n\t\t\treturn c.json(result);\n\t\t} catch (error) {\n\t\t\treturn c.json({ error: error instanceof Error ? error.message : String(error) }, 400);\n\t\t}\n\t});\n\n\tapp.post(\"/api/sync/join-requests/review\", async (c) => {\n\t\tlet body: Record<string, unknown>;\n\t\ttry {\n\t\t\tbody = await c.req.json<Record<string, unknown>>();\n\t\t} catch {\n\t\t\treturn c.json({ error: \"invalid json\" }, 400);\n\t\t}\n\t\tconst requestId = String(body.request_id ?? \"\").trim();\n\t\tconst action = String(body.action ?? \"\").trim();\n\t\tif (!requestId) return c.json({ error: \"request_id required\" }, 400);\n\t\tif (![\"approve\", \"deny\"].includes(action)) {\n\t\t\treturn c.json({ error: \"action must be approve or deny\" }, 400);\n\t\t}\n\t\ttry {\n\t\t\tconst config = readCoordinatorSyncConfig();\n\t\t\tconst result = await coordinatorReviewJoinRequestAction({\n\t\t\t\trequestId,\n\t\t\t\tapprove: action === \"approve\",\n\t\t\t\treviewedBy: null,\n\t\t\t\tremoteUrl: config.syncCoordinatorUrl || null,\n\t\t\t\tadminSecret: config.syncCoordinatorAdminSecret || null,\n\t\t\t});\n\t\t\tif (!result) return c.json({ error: \"join request not found\" }, 404);\n\t\t\treturn c.json({ ok: true, request: result });\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\treturn c.json(\n\t\t\t\t{ error: message },\n\t\t\t\tmessage.includes(\"request_not_found\") || message.includes(\"not found\") ? 404 : 400,\n\t\t\t);\n\t\t}\n\t});\n\n\t// DELETE /api/sync/peers/:peer_device_id\n\tapp.delete(\"/api/sync/peers/:peer_device_id\", (c) => {\n\t\tconst store = getStore();\n\t\t{\n\t\t\tconst d = drizzle(store.db, { schema });\n\t\t\tconst peerDeviceId = c.req.param(\"peer_device_id\")?.trim();\n\t\t\tif (!peerDeviceId) return c.json({ error: \"peer_device_id required\" }, 400);\n\t\t\tconst exists = d\n\t\t\t\t.select({ peer_device_id: schema.syncPeers.peer_device_id })\n\t\t\t\t.from(schema.syncPeers)\n\t\t\t\t.where(eq(schema.syncPeers.peer_device_id, peerDeviceId))\n\t\t\t\t.get();\n\t\t\tif (!exists) return c.json({ error: \"peer not found\" }, 404);\n\t\t\td.delete(schema.syncPeers).where(eq(schema.syncPeers.peer_device_id, peerDeviceId)).run();\n\t\t\treturn c.json({ ok: true });\n\t\t}\n\t});\n\n\treturn app;\n}\n","/**\n * @codemem/server — HTTP server (viewer, sync, API).\n *\n * Single HTTP server handling viewer routes and sync daemon.\n * Shares one better-sqlite3 connection between viewer and sync.\n * Embedding inference runs in a worker_thread (lazy-started).\n *\n * Entry: `codemem serve`\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { ObserverClient } from \"@codemem/core\";\nimport { MemoryStore, type RawEventSweeper, resolveDbPath, VERSION } from \"@codemem/core\";\nimport { serveStatic } from \"@hono/node-server/serve-static\";\nimport { Hono } from \"hono\";\nimport { originGuard, preflightHandler } from \"./middleware.js\";\nimport { configRoutes } from \"./routes/config.js\";\nimport { memoryRoutes } from \"./routes/memory.js\";\nimport { observerStatusRoutes } from \"./routes/observer-status.js\";\nimport { rawEventsRoutes } from \"./routes/raw-events.js\";\nimport { statsRoutes } from \"./routes/stats.js\";\nimport { syncRoutes } from \"./routes/sync.js\";\n\nexport { VERSION };\n\n/** Shared store instance — SQLite WAL mode handles concurrent reads safely. */\nlet sharedStore: MemoryStore | null = null;\n\n/** Get (or create) the shared store instance. Exported so the sweeper can share it. */\nexport function getStore(): MemoryStore {\n\tif (!sharedStore) {\n\t\tsharedStore = new MemoryStore(resolveDbPath());\n\t}\n\treturn sharedStore;\n}\n\n/** Close the shared store (called on shutdown). */\nexport function closeStore(): void {\n\tsharedStore?.close();\n\tsharedStore = null;\n}\n\n/**\n * Create the Hono app with all viewer routes.\n * Exported for testing — pass a custom store factory to inject test DBs.\n */\nexport interface AppOptions {\n\tstoreFactory?: () => MemoryStore;\n\tsweeper?: RawEventSweeper | null;\n\tobserver?: ObserverClient | null;\n}\n\nexport function createApp(opts?: AppOptions) {\n\tconst storeFactory = opts?.storeFactory ?? getStore;\n\tconst sweeper = opts?.sweeper ?? null;\n\tconst observer = opts?.observer ?? null;\n\tconst app = new Hono();\n\n\t// CORS / origin guard\n\tapp.use(\"*\", preflightHandler());\n\tapp.use(\"*\", originGuard());\n\n\t// API routes\n\tapp.route(\"/\", statsRoutes(storeFactory));\n\tapp.route(\"/\", memoryRoutes(storeFactory));\n\tapp.route(\n\t\t\"/\",\n\t\tobserverStatusRoutes({\n\t\t\tgetStore: storeFactory,\n\t\t\tgetSweeper: () => sweeper,\n\t\t\tgetObserver: () => observer,\n\t\t}),\n\t);\n\tapp.route(\"/\", configRoutes({ getSweeper: () => sweeper }));\n\tapp.route(\"/\", rawEventsRoutes(storeFactory, sweeper));\n\tapp.route(\"/\", syncRoutes(storeFactory));\n\n\t// Static assets — serve under /assets/*\n\t// Resolves to packages/viewer-server/static/ both in dev and when installed from npm.\n\tconst staticRoot =\n\t\tprocess.env.CODEMEM_VIEWER_STATIC_DIR ?? join(import.meta.dirname ?? \".\", \"../static\");\n\n\tapp.use(\n\t\t\"/assets/*\",\n\t\tserveStatic({\n\t\t\troot: staticRoot,\n\t\t\trewriteRequestPath: (path) => path.replace(/^\\/assets/, \"\"),\n\t\t}),\n\t);\n\n\t// SPA — serve index.html for root and all client-side routes\n\tconst indexHtml = readFileSync(join(staticRoot, \"index.html\"), \"utf-8\");\n\tapp.get(\"*\", (c) => {\n\t\tif (c.req.path.startsWith(\"/api/\")) {\n\t\t\treturn c.json({ error: \"not found\" }, 404);\n\t\t}\n\t\treturn c.html(indexHtml);\n\t});\n\n\treturn app;\n}\n\n// No auto-start — the CLI's `serve` command owns server startup.\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/routes/sync.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,WAAW,EAAiB,MAAM,eAAe,CAAC;AAqBhE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,KAAK,YAAY,GAAG,MAAM,WAAW,CAAC;AAmXtC,wBAAgB,UAAU,CAAC,QAAQ,EAAE,YAAY,8EAiiBhD"}
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/routes/sync.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,WAAW,EAAiB,MAAM,eAAe,CAAC;AAqBhE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,KAAK,YAAY,GAAG,MAAM,WAAW,CAAC;AAmXtC,wBAAgB,UAAU,CAAC,QAAQ,EAAE,YAAY,8EAuiBhD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codemem/server",
3
- "version": "0.20.0-alpha.6",
3
+ "version": "0.20.0-alpha.7",
4
4
  "type": "module",
5
5
  "types": "./dist/index.d.ts",
6
6
  "exports": {
@@ -18,7 +18,7 @@
18
18
  "@hono/node-server": "^1.14.3",
19
19
  "drizzle-orm": "^0.45.1",
20
20
  "hono": "^4.7.10",
21
- "@codemem/core": "^0.20.0-alpha.6"
21
+ "@codemem/core": "^0.20.0-alpha.7"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@types/better-sqlite3": "^7.6.13",
package/static/app.js CHANGED
@@ -287,10 +287,11 @@
287
287
  }
288
288
  return parsed;
289
289
  }
290
- async function loadSyncStatus(includeDiagnostics, project = "") {
290
+ async function loadSyncStatus(includeDiagnostics, project = "", options) {
291
291
  const params = new URLSearchParams();
292
292
  if (includeDiagnostics) params.set("includeDiagnostics", "1");
293
293
  if (project) params.set("project", project);
294
+ if (options?.includeJoinRequests) params.set("includeJoinRequests", "1");
294
295
  return fetchJson(`/api/sync/status${params.size ? `?${params.toString()}` : ""}`);
295
296
  }
296
297
  async function createCoordinatorInvite(payload) {
@@ -2768,9 +2769,48 @@
2768
2769
  //#endregion
2769
2770
  //#region src/tabs/sync/index.ts
2770
2771
  var lastSyncHash = "";
2772
+ var cachedSyncStatus = null;
2773
+ var HEALTH_SYNC_STATUS_CACHE_TTL_MS = 15e3;
2774
+ function syncStatusCacheKey(project) {
2775
+ return `project:${project || ""}|includeJoinRequests:false`;
2776
+ }
2777
+ function readCachedSyncStatus(project) {
2778
+ const key = syncStatusCacheKey(project);
2779
+ if (!cachedSyncStatus) return null;
2780
+ if (cachedSyncStatus.key !== key) return null;
2781
+ if (Date.now() >= cachedSyncStatus.expiresAtMs) return null;
2782
+ return cachedSyncStatus.payload;
2783
+ }
2784
+ function writeCachedSyncStatus(project, payload) {
2785
+ cachedSyncStatus = {
2786
+ key: syncStatusCacheKey(project),
2787
+ expiresAtMs: Date.now() + HEALTH_SYNC_STATUS_CACHE_TTL_MS,
2788
+ payload
2789
+ };
2790
+ }
2791
+ function normalizeSyncStatusForCache(payload) {
2792
+ if (!payload || typeof payload !== "object") return payload;
2793
+ return {
2794
+ ...payload,
2795
+ join_requests: []
2796
+ };
2797
+ }
2771
2798
  async function loadSyncData() {
2772
2799
  try {
2773
- const payload = await loadSyncStatus(true, state.currentProject || "");
2800
+ const project = state.currentProject || "";
2801
+ const includeJoinRequests = state.activeTab === "sync";
2802
+ const useCache = state.activeTab === "health";
2803
+ let payload;
2804
+ if (useCache) {
2805
+ payload = readCachedSyncStatus(project);
2806
+ if (!payload) {
2807
+ payload = await loadSyncStatus(true, project, { includeJoinRequests: false });
2808
+ writeCachedSyncStatus(project, normalizeSyncStatusForCache(payload));
2809
+ }
2810
+ } else {
2811
+ payload = await loadSyncStatus(true, project, { includeJoinRequests });
2812
+ writeCachedSyncStatus(project, normalizeSyncStatusForCache(payload));
2813
+ }
2774
2814
  let actorsPayload = null;
2775
2815
  let actorLoadError = false;
2776
2816
  try {
@@ -2787,7 +2827,7 @@
2787
2827
  state.lastSyncPeers = payload.peers || [];
2788
2828
  state.lastSyncSharingReview = payload.sharing_review || [];
2789
2829
  state.lastSyncCoordinator = payload.coordinator || null;
2790
- state.lastSyncJoinRequests = payload.join_requests || [];
2830
+ if (Array.isArray(payload.join_requests)) state.lastSyncJoinRequests = payload.join_requests;
2791
2831
  state.lastSyncAttempts = payload.attempts || [];
2792
2832
  state.lastSyncLegacyDevices = payload.legacy_devices || [];
2793
2833
  renderSyncStatus();