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 +40 -46
- package/dist/cli/entry.mjs +3 -3
- package/dist/cli/sdk-daemon.mjs +71 -92
- package/dist/cli/sdk-daemon.mjs.map +1 -1
- package/dist/{ctl-DaIi3tUU.mjs → ctl-9dwvaRrC.mjs} +3 -3
- package/dist/{ctl-DaIi3tUU.mjs.map → ctl-9dwvaRrC.mjs.map} +1 -1
- package/dist/index.d.mts +12 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +10 -1
- package/dist/index.mjs.map +1 -1
- package/dist/{launcher-VTbHuuaQ.mjs → launcher-DXUM0K-z.mjs} +4 -4
- package/dist/launcher-DXUM0K-z.mjs.map +1 -0
- package/dist/{lock-H7LKrRSb.mjs → lock-Q6z0l6Mr.mjs} +9 -4
- package/dist/lock-Q6z0l6Mr.mjs.map +1 -0
- package/dist/{src-NyDFgJXL.mjs → src-Xh68VkBy.mjs} +3 -3
- package/dist/src-Xh68VkBy.mjs.map +1 -0
- package/dist/{tui-BwpUi10R.mjs → tui-DNqvslCq.mjs} +12 -14
- package/dist/tui-DNqvslCq.mjs.map +1 -0
- package/package.json +1 -1
- package/dist/launcher-VTbHuuaQ.mjs.map +0 -1
- package/dist/lock-H7LKrRSb.mjs.map +0 -1
- package/dist/src-NyDFgJXL.mjs.map +0 -1
- package/dist/tui-BwpUi10R.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -4,7 +4,12 @@
|
|
|
4
4
|
</a>
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
|
-
<h3 align="center">
|
|
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
|
-
|
|
46
|
+

|
|
42
47
|
|
|
43
|
-
|
|
48
|
+
What Claude Code knows, Codex doesn't. What your teammate is shipping right now? Your agent has no idea.
|
|
44
49
|
|
|
45
|
-
|
|
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
|
-
|
|
52
|
+
For example:
|
|
48
53
|
|
|
49
|
-
|
|
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
|
-
|
|
58
|
+
Open source. Framework-agnostic. Customizable via the git-like Context API.
|
|
52
59
|
|
|
53
|
-
##
|
|
60
|
+
## Features
|
|
54
61
|
|
|
55
|
-
|
|
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
|
-
|
|
58
|
-
npm install -g ultracontext
|
|
59
|
-
```
|
|
67
|
+
---
|
|
60
68
|
|
|
61
|
-
##
|
|
69
|
+
## How it works
|
|
62
70
|
|
|
63
|
-
**
|
|
71
|
+
1. **Start the daemon.** It captures all your agents' context in realtime.
|
|
64
72
|
|
|
65
|
-
|
|
73
|
+
2. **Add the MCP server.** Any agent gets full awareness of every other agent.
|
|
66
74
|
|
|
67
|
-
|
|
75
|
+
3. **That's it.** Ask questions, continue sessions, fork — your context is everywhere.
|
|
68
76
|
|
|
69
|
-
|
|
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
|
-
|
|
79
|
+
Requires Node >= 22.
|
|
76
80
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
81
|
+
```bash
|
|
82
|
+
npm install -g ultracontext
|
|
83
|
+
```
|
|
80
84
|
|
|
81
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
[](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
|
|
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
|
package/dist/cli/entry.mjs
CHANGED
|
@@ -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-
|
|
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-
|
|
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-
|
|
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"
|
package/dist/cli/sdk-daemon.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as
|
|
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-
|
|
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,
|
|
261
|
-
if (typeof raw === "object") return truncateString(JSON.stringify(raw),
|
|
262
|
-
return truncateString(String(raw),
|
|
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
|
|
304
|
+
if (typeof content === "string") return preserveText(content);
|
|
270
305
|
if (Array.isArray(content)) {
|
|
271
|
-
const
|
|
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 =
|
|
276
|
-
if (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
|
|
316
|
+
return parts.join("\n\n");
|
|
280
317
|
}
|
|
281
318
|
if (typeof content === "object") {
|
|
282
|
-
if (typeof content.text === "string") return
|
|
283
|
-
if (typeof content.content === "string") return
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
1384
|
-
const sessionContextId = await getOrCreateContext(store, uc, sessionContextStoreKey(sourceName, normalized.sessionId), {
|
|
1381
|
+
const contextMeta = {
|
|
1385
1382
|
source: sourceName,
|
|
1386
1383
|
host: cfg.host,
|
|
1387
|
-
|
|
1384
|
+
user_id: cfg.userId,
|
|
1388
1385
|
session_id: normalized.sessionId,
|
|
1389
|
-
|
|
1390
|
-
}
|
|
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
|
-
|
|
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.
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
1730
|
+
user_id: cfg.userId,
|
|
1752
1731
|
host: cfg.host,
|
|
1753
1732
|
poll_ms: cfg.pollMs,
|
|
1754
1733
|
mode: "headless",
|