ultracontext 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,12 @@
4
4
  </a>
5
5
  </p>
6
6
 
7
- <h3 align="center">Context infrastructure for AI agents.</h3>
7
+ <h3 align="center">Same context. Everywhere.</h3>
8
+
9
+ <p align="center">
10
+ Start on Claude Code. Continue on Codex.<br/>
11
+ Open source, realtime and invisible context infrastructure for the ones shipping at inference speed.
12
+ </p>
8
13
 
9
14
  <p align="center">
10
15
  <a href="https://ultracontext.ai/docs">Documentation</a> ·
@@ -38,50 +43,54 @@
38
43
 
39
44
  ---
40
45
 
41
- <h2 align="center">All agents. One context.</h2>
46
+ ![ultracontext-gif](https://github.com/user-attachments/assets/be73afe5-161d-4fa3-8f4d-c4987fe63cb4)
42
47
 
43
- Auto-capture and share your agents' context everywhere. Realtime. Open source.
48
+ What Claude Code knows, Codex doesn't. What your teammate is shipping right now? Your agent has no idea.
44
49
 
45
- ![ultracontext-gif](https://github.com/user-attachments/assets/be73afe5-161d-4fa3-8f4d-c4987fe63cb4)
50
+ UltraContext captures every agent's context in realtime and makes it available to all of them. It's like having a personal context engineer everywhere. Continue a session in a different agent, or just ask what's happeming.
46
51
 
47
- Everyone is shipping with agents. Few are shipping with agents together.
52
+ For example:
48
53
 
49
- Multiple people, multiple agents, multiple machines. Our contexts are spread everywhere. There's no standard for context engineering. No infrastructure to build on. No fundamental building blocks to agree on. So we decided to make it.
54
+ - *"Codex, grab the last plan Claude Code made and implement it."*
55
+ - *"What's the team building today?"*
56
+ - *"What is Alex working on in Codex right now?"*
50
57
 
51
- UltraContext is the context infrastructure. The API gives you git-like primitives for context engineering. The Hub lets you auto-capture, share, and collaborate across agents in realtime.
58
+ Open source. Framework-agnostic. Customizable via the git-like Context API.
52
59
 
53
- ## Install
60
+ ## Features
54
61
 
55
- Requires Node >= 22.
62
+ | CLI | Auto-ingest Claude Code, Codex, and OpenClaw sessions with a terminal dashboard. |
63
+ | --- | --- |
64
+ | MCP Server | Share context everywhere. Built into the API, or run standalone via stdio. |
65
+ | Context API | Git-like context engineering API. Store, version, and retrieve agent context with zero complexity. |
56
66
 
57
- ```bash
58
- npm install -g ultracontext
59
- ```
67
+ ---
60
68
 
61
- ## The Hub
69
+ ## How it works
62
70
 
63
- **All agents. One context.**
71
+ 1. **Start the daemon.** It captures all your agents' context in realtime.
64
72
 
65
- The Hub lets you auto-capture, share, and collaborate across agents in realtime.
73
+ 2. **Add the MCP server.** Any agent gets full awareness of every other agent.
66
74
 
67
- ### Features
75
+ 3. **That's it.** Ask questions, continue sessions, fork — your context is everywhere.
68
76
 
69
- - **Auto-capture** — Ingests your agents' context in realtime. Zero config.
70
- - **Switch between agents** — Pick up where one agent left off with another.
71
- - **Collaborate** — Share contexts across your team. See what everyone sees. Realtime.
72
- - **Fork & clone** — Continue contexts while preserving the full history.
73
- - **Own your data** — Open source. Your contexts. Your rules.
77
+ ## Install
74
78
 
75
- ### How it works
79
+ Requires Node >= 22.
76
80
 
77
- 1. A daemon runs in the background, watching your agents.
78
- 2. Contexts are ingested in realtime.
79
- 3. Your dashboard gets updated.
81
+ ```bash
82
+ npm install -g ultracontext
83
+ ```
80
84
 
81
- ### Quick Start
85
+ ## Quick Start
82
86
 
83
87
  ```bash
84
88
  ultracontext # start daemon + open dashboard
89
+ ```
90
+
91
+ That's it. The daemon watches your agents, ingests context in realtime, and the dashboard shows everything.
92
+
93
+ ```bash
85
94
  ultracontext config # run setup wizard
86
95
  ultracontext start # start daemon only
87
96
  ultracontext stop # stop daemon
@@ -89,36 +98,22 @@ ultracontext status # check if daemon is running
89
98
  ultracontext tui # open dashboard only
90
99
  ```
91
100
 
92
- The default `ultracontext` command does everything: checks the daemon, starts it if needed, and opens the dashboard.
93
-
94
- When you open an existing session, it forks the context — the original is always preserved and automatically versioned. A local caching layer prevents duplicate context creations and appends.
95
-
96
- Add your own agents and extend behavior with the Context API. ([Docs here](https://ultracontext.ai/docs/))
101
+ ## Context API
97
102
 
98
- ## The API
99
-
100
- **Context engineering built like Git.**
101
-
102
- The API gives you git-like primitives for context engineering, without the complexity.
103
-
104
- ### Features
103
+ For builders who want to go deeper. Git-like primitives for context engineering.
105
104
 
106
105
  - **Five methods** — Create, get, append, update, delete. That's it.
107
106
  - **Automatic versioning** — Every change creates a new version. Full history out of the box.
108
107
  - **Time-travel** — Jump to any point in your context history.
109
108
  - **Framework-agnostic** — Works with any LLM framework. No vendor lock-in.
110
109
 
111
- The simplest way to control what your agents see. Replace messages, compact long context, replay decisions and roll back mistakes — all with a single API call.
112
-
113
- Use the API standalone to build your own agents, or to extend existing ones in UltraContext.
114
-
110
+ Use the API standalone to build your own agents, or extend existing ones in UltraContext.
115
111
 
116
112
  | SDK | Install | Source |
117
113
  | --------------------- | -------------------------- | ------------------------------------ |
118
114
  | JavaScript/TypeScript | `npm install ultracontext` | [apps/js-sdk](./apps/js-sdk) |
119
115
  | Python | `pip install ultracontext` | [apps/python-sdk](./apps/python-sdk) |
120
116
 
121
-
122
117
  ### JavaScript/TypeScript
123
118
 
124
119
  ```bash
@@ -170,9 +165,8 @@ response = generate_text(model=model, messages=uc.get(ctx["id"])["data"])
170
165
 
171
166
  [![Star History Chart](https://api.star-history.com/svg?repos=ultracontext/ultracontext-node&type=date&legend=top-left)](https://www.star-history.com/#ultracontext/ultracontext-node&type=date&legend=top-left)
172
167
 
173
-
174
168
  ## Documentation
175
169
 
176
- - [Quickstart](https://ultracontext.ai/docs/quickstart/nodejs) — Get running in 2 minutes
170
+ - [Quickstart](https://ultracontext.ai/docs/quickstart) — Get running in 2 minutes
177
171
  - [Guides](https://ultracontext.ai/docs/guides/store-retrieve-contexts) — Practical patterns for common use cases
178
- - [API Reference](https://ultracontext.ai/docs/api-reference/introduction) — Full endpoint documentation
172
+ - [API Reference](https://ultracontext.ai/docs/api-reference/introduction) — Full endpoint documentation
@@ -313,18 +313,18 @@ async function checkForUpdate() {
313
313
  if (latest && isNewer(latest, current)) printUpdateNotice(current, latest);
314
314
  }
315
315
  async function launchDaemonSDK() {
316
- const { launchDaemon } = await import("../launcher-VTbHuuaQ.mjs");
316
+ const { launchDaemon } = await import("../launcher-DXUM0K-z.mjs");
317
317
  await launchDaemon({
318
318
  entryPath: fileURLToPath(new URL("./sdk-daemon.mjs", import.meta.url)),
319
319
  diagnosticsHint: "DAEMON_VERBOSE=1 ultracontext start"
320
320
  });
321
321
  }
322
322
  async function runCtlSDK() {
323
- const { runCtl } = await import("../ctl-DaIi3tUU.mjs");
323
+ const { runCtl } = await import("../ctl-9dwvaRrC.mjs");
324
324
  await runCtl();
325
325
  }
326
326
  async function launchTuiSDK() {
327
- const { tuiBoot } = await import("../tui-BwpUi10R.mjs");
327
+ const { tuiBoot } = await import("../tui-DNqvslCq.mjs");
328
328
  await tuiBoot({
329
329
  assetsRoot: path.resolve(__dirname, "..", ".."),
330
330
  offlineNotice: "Daemon offline. Run: ultracontext start"
@@ -1,5 +1,5 @@
1
- import { a as extractSessionIdFromPath, c as toInt, i as expandHome, l as truncateString, n as resolveLockPath, o as safeJsonParse, r as boolFromEnv, s as sha256, t as acquireFileLock } from "../lock-H7LKrRSb.mjs";
2
- import { a as parseDaemonWsMessage, c as resolveDaemonWsInfoFile, i as normalizeBootstrapMode, l as resolveDaemonWsPort, n as buildDaemonWsMessage, o as parseProtocolJson, r as createBootstrapStateKey, s as resolveDaemonWsHost, t as DAEMON_WS_MESSAGE_TYPES } from "../src-NyDFgJXL.mjs";
1
+ import { a as extractProjectPathFromFile, c as sha256, i as expandHome, l as toInt, n as resolveLockPath, o as extractSessionIdFromPath, r as boolFromEnv, s as safeJsonParse, t as acquireFileLock, u as truncateString } from "../lock-Q6z0l6Mr.mjs";
2
+ import { a as parseDaemonWsMessage, c as resolveDaemonWsInfoFile, i as normalizeBootstrapMode, l as resolveDaemonWsPort, n as buildDaemonWsMessage, o as parseProtocolJson, r as createBootstrapStateKey, s as resolveDaemonWsHost, t as DAEMON_WS_MESSAGE_TYPES } from "../src-Xh68VkBy.mjs";
3
3
  import process$1 from "node:process";
4
4
  import path from "node:path";
5
5
  import fs from "node:fs";
@@ -255,32 +255,69 @@ function normalizeKind(kind, fallback = "system") {
255
255
  ].includes(lowered)) return "assistant";
256
256
  return fallback;
257
257
  }
258
- function toMessage(raw) {
258
+ function toMessage(raw, maxLen = 12e3) {
259
259
  if (!raw) return "";
260
- if (typeof raw === "string") return truncateString(raw, 3e3);
261
- if (typeof raw === "object") return truncateString(JSON.stringify(raw), 3e3);
262
- return truncateString(String(raw), 3e3);
260
+ if (typeof raw === "string") return truncateString(raw, maxLen);
261
+ if (typeof raw === "object") return truncateString(JSON.stringify(raw), maxLen);
262
+ return truncateString(String(raw), maxLen);
263
263
  }
264
264
  function normalizeWhitespace(value) {
265
265
  return String(value ?? "").replace(/\s+/g, " ").trim();
266
266
  }
267
+ function preserveText(value) {
268
+ return String(value ?? "").split("\n").map((l) => l.trimEnd()).join("\n").replace(/\n{3,}/g, "\n\n").trim();
269
+ }
270
+ function formatToolUse(item) {
271
+ const name = (item.name ?? "unknown").toLowerCase();
272
+ const input = item.input ?? {};
273
+ const filePath = input.file_path ?? input.path ?? "";
274
+ if (name === "write") return `[Write] ${filePath}\n${preserveText(input.content ?? input.file_text ?? "")}`;
275
+ if (name === "edit") {
276
+ const parts = [`[Edit] ${filePath}`];
277
+ if (input.old_string) parts.push(`- ${preserveText(input.old_string)}`);
278
+ if (input.new_string) parts.push(`+ ${preserveText(input.new_string)}`);
279
+ return parts.join("\n");
280
+ }
281
+ if (name === "read") return `[Read] ${filePath}`;
282
+ if (name === "bash") return `[Bash] ${preserveText(input.command ?? "")}`;
283
+ if (name === "grep" || name === "glob") {
284
+ const loc = filePath ? ` in ${filePath}` : "";
285
+ return `[${item.name}] ${input.pattern ?? ""}${loc}`;
286
+ }
287
+ const compact = JSON.stringify(input);
288
+ return `[${item.name ?? name}] ${compact.length > 500 ? compact.slice(0, 500) + "..." : compact}`;
289
+ }
290
+ function formatToolResult(item) {
291
+ const content = item.content ?? "";
292
+ if (typeof content === "string") {
293
+ const text = preserveText(content);
294
+ return text ? `[result] ${truncateString(text, 1e3)}` : "[result] ok";
295
+ }
296
+ if (Array.isArray(content)) {
297
+ const text = content.filter((c) => c?.type === "text" && typeof c.text === "string").map((c) => preserveText(c.text)).filter(Boolean).join("\n");
298
+ return text ? `[result] ${truncateString(text, 1e3)}` : "[result] ok";
299
+ }
300
+ return "[result] ok";
301
+ }
267
302
  function extractClaudeTextContent(content) {
268
303
  if (!content) return "";
269
- if (typeof content === "string") return normalizeWhitespace(content);
304
+ if (typeof content === "string") return preserveText(content);
270
305
  if (Array.isArray(content)) {
271
- const textParts = [];
306
+ const parts = [];
272
307
  for (const item of content) {
273
308
  if (!item || typeof item !== "object") continue;
274
309
  if (item.type === "text" && typeof item.text === "string") {
275
- const chunk = normalizeWhitespace(item.text);
276
- if (chunk) textParts.push(chunk);
310
+ const chunk = preserveText(item.text);
311
+ if (chunk) parts.push(chunk);
277
312
  }
313
+ if (item.type === "tool_use") parts.push(formatToolUse(item));
314
+ if (item.type === "tool_result") parts.push(formatToolResult(item));
278
315
  }
279
- return textParts.join("\n");
316
+ return parts.join("\n\n");
280
317
  }
281
318
  if (typeof content === "object") {
282
- if (typeof content.text === "string") return normalizeWhitespace(content.text);
283
- if (typeof content.content === "string") return normalizeWhitespace(content.content);
319
+ if (typeof content.text === "string") return preserveText(content.text);
320
+ if (typeof content.content === "string") return preserveText(content.content);
284
321
  }
285
322
  return "";
286
323
  }
@@ -765,7 +802,7 @@ async function daemonBoot({ createStore, resolveDbPath }) {
765
802
  const cfg = {
766
803
  apiKey: normalizeApiKey(process$1.env.ULTRACONTEXT_API_KEY),
767
804
  baseUrl: (process$1.env.ULTRACONTEXT_BASE_URL ?? "https://api.ultracontext.ai").trim(),
768
- engineerId: process$1.env.DAEMON_ENGINEER_ID ?? process$1.env.USER ?? "unknown-engineer",
805
+ userId: process$1.env.DAEMON_USER_ID ?? process$1.env.USER ?? "unknown-user",
769
806
  host: (process$1.env.DAEMON_HOST || os.hostname() || "unknown-host").trim(),
770
807
  pollMs: toInt(process$1.env.DAEMON_POLL_MS, 1500),
771
808
  logLevel: process$1.env.DAEMON_LOG_LEVEL ?? "info",
@@ -781,7 +818,6 @@ async function daemonBoot({ createStore, resolveDbPath }) {
781
818
  wsInfoFile: resolveDaemonWsInfoFile(process$1.env),
782
819
  dedupeTtlSec: toInt(process$1.env.DAEMON_DEDUPE_TTL_SEC, 3600 * 24 * 30),
783
820
  maxReadBytes: toInt(process$1.env.DAEMON_MAX_READ_BYTES, 512 * 1024),
784
- enableDailyContext: boolFromEnv(process$1.env.DAEMON_ENABLE_DAILY_CONTEXT, false),
785
821
  bootstrapMode: normalizeBootstrapModeWithPrompt(process$1.env.DAEMON_BOOTSTRAP_MODE ?? "prompt") || "prompt",
786
822
  bootstrapReset: boolFromEnv(process$1.env.DAEMON_BOOTSTRAP_RESET, false),
787
823
  claudeIncludeSubagents: boolFromEnv(process$1.env.CLAUDE_INCLUDE_SUBAGENTS, false),
@@ -892,8 +928,7 @@ async function daemonBoot({ createStore, resolveDbPath }) {
892
928
  }
893
929
  function pushRecentLog(level, message, data) {
894
930
  let line = String(message ?? "");
895
- if (line.startsWith("Appended event to session context")) line = "context append (session)";
896
- if (line.startsWith("Appended event to daily context")) line = "context append (daily)";
931
+ if (line.startsWith("Appended event to session context")) line = "context append";
897
932
  if (line.startsWith("Context created")) line = "Context created";
898
933
  if (line.startsWith("Context created without metadata fallback")) line = "Context created (fallback)";
899
934
  if (line.startsWith("UltraContext daemon started")) line = "Daemon started";
@@ -969,23 +1004,6 @@ async function daemonBoot({ createStore, resolveDbPath }) {
969
1004
  if ("bodyText" in error) details.bodyText = error.bodyText;
970
1005
  return details;
971
1006
  }
972
- function parseBool(value, fallback = false) {
973
- if (value === null || value === void 0) return fallback;
974
- const normalized = String(value).trim().toLowerCase();
975
- if ([
976
- "1",
977
- "true",
978
- "yes",
979
- "on"
980
- ].includes(normalized)) return true;
981
- if ([
982
- "0",
983
- "false",
984
- "no",
985
- "off"
986
- ].includes(normalized)) return false;
987
- return fallback;
988
- }
989
1007
  function serializeConfigPrefs() {
990
1008
  return {
991
1009
  bootstrapMode: normalizeBootstrapModeWithPrompt(cfg.bootstrapMode) || "prompt",
@@ -1139,7 +1157,7 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1139
1157
  running: Boolean(runtime.daemonRunning),
1140
1158
  pid: process$1.pid,
1141
1159
  host: cfg.host,
1142
- engineerId: cfg.engineerId,
1160
+ userId: cfg.userId,
1143
1161
  stats: { ...stats },
1144
1162
  sourceStats: state.sourceOrder.map((name) => ({
1145
1163
  name,
@@ -1189,7 +1207,7 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1189
1207
  function bootstrapStateStoreKey(sources) {
1190
1208
  return createBootstrapStateKey({
1191
1209
  host: cfg.host,
1192
- engineerId: cfg.engineerId,
1210
+ userId: cfg.userId,
1193
1211
  sourceNames: sources.map((s) => s.name)
1194
1212
  });
1195
1213
  }
@@ -1209,10 +1227,7 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1209
1227
  return `seen:${sourceName}:${eventId}`;
1210
1228
  }
1211
1229
  function sessionContextStoreKey(sourceName, sessionId) {
1212
- return `ctx:session:${sourceName}:${cfg.host}:${cfg.engineerId}:${sessionId}`;
1213
- }
1214
- function dailyContextStoreKey(sourceName, dayKey) {
1215
- return `ctx:daily:${sourceName}:${cfg.host}:${cfg.engineerId}:${dayKey}`;
1230
+ return `ctx:session:${sourceName}:${cfg.host}:${cfg.userId}:${sessionId}`;
1216
1231
  }
1217
1232
  async function primeOffsetsToEof(store, source, shouldStop = () => false) {
1218
1233
  if (shouldStop()) return;
@@ -1329,15 +1344,12 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1329
1344
  action: "created",
1330
1345
  source: sourceName,
1331
1346
  sessionId: String(metadata?.session_id ?? ""),
1332
- contextKind: String(metadata?.context_kind ?? "session"),
1333
1347
  contextId: created.id
1334
1348
  });
1335
1349
  if (cfg.logAppends) log("info", "Context created", {
1336
1350
  source: sourceName,
1337
1351
  context_id: created.id,
1338
- kind: metadata?.context_kind ?? "session",
1339
- session_id: metadata?.session_id ?? "",
1340
- day: metadata?.day ?? ""
1352
+ session_id: metadata?.session_id ?? ""
1341
1353
  });
1342
1354
  return created.id;
1343
1355
  } catch (error) {
@@ -1354,40 +1366,28 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1354
1366
  action: "created",
1355
1367
  source: sourceName,
1356
1368
  sessionId: String(metadata?.session_id ?? ""),
1357
- contextKind: String(metadata?.context_kind ?? "session"),
1358
1369
  contextId: created.id
1359
1370
  });
1360
1371
  if (cfg.logAppends) log("warn", "Context created without metadata fallback", {
1361
1372
  source: sourceName,
1362
- context_id: created.id,
1363
- kind: metadata?.context_kind ?? "session"
1373
+ context_id: created.id
1364
1374
  });
1365
1375
  return created.id;
1366
1376
  }
1367
1377
  throw error;
1368
1378
  }
1369
1379
  }
1370
- function toDayKey(timestamp) {
1371
- if (timestamp === void 0 || timestamp === null) return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1372
- if (typeof timestamp === "number" || /^\d+$/.test(String(timestamp))) {
1373
- const asNum = Number(timestamp);
1374
- const ms = asNum > 0xe8d4a51000 ? asNum : asNum * 1e3;
1375
- const d = new Date(ms);
1376
- if (!Number.isNaN(d.getTime())) return d.toISOString().slice(0, 10);
1377
- }
1378
- const d = new Date(String(timestamp));
1379
- if (!Number.isNaN(d.getTime())) return d.toISOString().slice(0, 10);
1380
- return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1381
- }
1382
1380
  async function appendToUltraContext({ store, uc, sourceName, normalized, eventId, filePath, lineOffset }) {
1383
- const dayKey = toDayKey(normalized.timestamp);
1384
- const sessionContextId = await getOrCreateContext(store, uc, sessionContextStoreKey(sourceName, normalized.sessionId), {
1381
+ const contextMeta = {
1385
1382
  source: sourceName,
1386
1383
  host: cfg.host,
1387
- engineer_id: cfg.engineerId,
1384
+ user_id: cfg.userId,
1388
1385
  session_id: normalized.sessionId,
1389
- context_kind: "session"
1390
- }, sourceName);
1386
+ started_at: normalized.timestamp
1387
+ };
1388
+ const projectPath = extractProjectPathFromFile(filePath);
1389
+ if (projectPath) contextMeta.project_path = projectPath;
1390
+ const sessionContextId = await getOrCreateContext(store, uc, sessionContextStoreKey(sourceName, normalized.sessionId), contextMeta, sourceName);
1391
1391
  const safeRaw = redact(normalized.raw);
1392
1392
  const payload = {
1393
1393
  role: normalized.kind,
@@ -1400,12 +1400,11 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1400
1400
  metadata: {
1401
1401
  source: sourceName,
1402
1402
  host: cfg.host,
1403
- engineer_id: cfg.engineerId,
1403
+ user_id: cfg.userId,
1404
1404
  session_id: normalized.sessionId,
1405
1405
  event_id: eventId,
1406
1406
  file_path: filePath,
1407
- file_offset: lineOffset,
1408
- day: dayKey
1407
+ file_offset: lineOffset
1409
1408
  }
1410
1409
  };
1411
1410
  await uc.append(sessionContextId, payload);
@@ -1415,7 +1414,6 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1415
1414
  action: "appended",
1416
1415
  source: sourceName,
1417
1416
  sessionId: normalized.sessionId,
1418
- contextKind: "session",
1419
1417
  contextId: sessionContextId
1420
1418
  });
1421
1419
  noteSourceActivity(sourceName, {
@@ -1431,25 +1429,6 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1431
1429
  role: normalized.kind,
1432
1430
  event_id: eventId
1433
1431
  });
1434
- if (!cfg.enableDailyContext) return;
1435
- const dailyContextId = await getOrCreateContext(store, uc, dailyContextStoreKey(sourceName, dayKey), {
1436
- source: sourceName,
1437
- host: cfg.host,
1438
- engineer_id: cfg.engineerId,
1439
- day: dayKey,
1440
- context_kind: "daily"
1441
- }, sourceName);
1442
- await uc.append(dailyContextId, payload);
1443
- bumpStat("appended");
1444
- bumpSourceStat(sourceName, "appended");
1445
- if (cfg.logAppends) log("info", "Appended event to daily context", {
1446
- source: sourceName,
1447
- day: dayKey,
1448
- context_id: dailyContextId,
1449
- event_type: normalized.eventType,
1450
- session_id: normalized.sessionId,
1451
- event_id: eventId
1452
- });
1453
1432
  }
1454
1433
  async function readNewLines(filePath, offset) {
1455
1434
  const handle = await fs$1.open(filePath, "r");
@@ -1526,7 +1505,7 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1526
1505
  lastSessionId: normalized.sessionId,
1527
1506
  lastAt: Date.now()
1528
1507
  });
1529
- const eventId = sha256(`${source.name}|${cfg.host}|${cfg.engineerId}|${normalized.sessionId}|${fileId}|${lineOffset}|${sha256(line)}`);
1508
+ const eventId = sha256(`${source.name}|${cfg.host}|${cfg.userId}|${normalized.sessionId}|${fileId}|${lineOffset}|${sha256(line)}`);
1530
1509
  if (!markEventSeen(store, source.name, eventId)) {
1531
1510
  bumpStat("deduped");
1532
1511
  bumpSourceStat(source.name, "deduped");
@@ -1593,7 +1572,7 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1593
1572
  const value = message?.data?.value;
1594
1573
  if (!key) throw new Error("config key is required");
1595
1574
  if (key === "claudeIncludeSubagents") {
1596
- cfg.claudeIncludeSubagents = parseBool(value, cfg.claudeIncludeSubagents);
1575
+ cfg.claudeIncludeSubagents = boolFromEnv(value, cfg.claudeIncludeSubagents);
1597
1576
  applyRuntimeSources(buildSources());
1598
1577
  await persistDaemonConfigEverywhere();
1599
1578
  log("info", "Config updated via TUI", {
@@ -1615,7 +1594,7 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1615
1594
  return { config: serializeConfigPrefs() };
1616
1595
  }
1617
1596
  if (key === "bootstrapReset") {
1618
- cfg.bootstrapReset = parseBool(value, false);
1597
+ cfg.bootstrapReset = boolFromEnv(value, false);
1619
1598
  if (cfg.bootstrapReset) await resetBootstrapState();
1620
1599
  await persistDaemonConfigEverywhere();
1621
1600
  log("info", "Config updated via TUI", {
@@ -1721,7 +1700,7 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1721
1700
  applyRuntimeSources(sources);
1722
1701
  runtime.lockHandle = await acquireFileLock({
1723
1702
  lockPath: cfg.lockFile,
1724
- engineerId: cfg.engineerId,
1703
+ userId: cfg.userId,
1725
1704
  host: cfg.host
1726
1705
  });
1727
1706
  const uc = new UltraContext({
@@ -1748,7 +1727,7 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1748
1727
  runtime.wsServer = wsServer;
1749
1728
  const wsInfo = await wsServer.start();
1750
1729
  log("info", "UltraContext daemon started", {
1751
- engineer_id: cfg.engineerId,
1730
+ user_id: cfg.userId,
1752
1731
  host: cfg.host,
1753
1732
  poll_ms: cfg.pollMs,
1754
1733
  mode: "headless",