ultracontext 1.4.13 → 1.6.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.
Files changed (41) hide show
  1. package/dist/cli/entry.mjs +9 -3
  2. package/dist/cli/entry.mjs.map +1 -1
  3. package/dist/cli/onboarding.mjs +268 -111
  4. package/dist/cli/onboarding.mjs.map +1 -1
  5. package/dist/cli/sdk-sync.mjs +199 -939
  6. package/dist/cli/sdk-sync.mjs.map +1 -1
  7. package/dist/cli/switch.mjs +168 -0
  8. package/dist/cli/switch.mjs.map +1 -0
  9. package/dist/{ctl-CXfNEPN8.mjs → ctl-DTQZxn3N.mjs} +2 -2
  10. package/dist/{ctl-CXfNEPN8.mjs.map → ctl-DTQZxn3N.mjs.map} +1 -1
  11. package/dist/hero-art-C03HmDXN.mjs +46 -0
  12. package/dist/hero-art-C03HmDXN.mjs.map +1 -0
  13. package/dist/index.d.mts +21 -1
  14. package/dist/index.d.mts.map +1 -1
  15. package/dist/index.mjs +25 -3
  16. package/dist/index.mjs.map +1 -1
  17. package/dist/{launcher-BMMjzr5k.mjs → launcher-ZylswrpR.mjs} +3 -3
  18. package/dist/{launcher-BMMjzr5k.mjs.map → launcher-ZylswrpR.mjs.map} +1 -1
  19. package/dist/{lock-5aJnda81.mjs → lock-BhZX2aF3.mjs} +2 -2
  20. package/dist/{lock-5aJnda81.mjs.map → lock-BhZX2aF3.mjs.map} +1 -1
  21. package/dist/onboarding-preferences-Alhblobi.mjs +76 -0
  22. package/dist/onboarding-preferences-Alhblobi.mjs.map +1 -0
  23. package/dist/src-Bovo1ukU.mjs +1200 -0
  24. package/dist/src-Bovo1ukU.mjs.map +1 -0
  25. package/dist/{tui-DZ1SDOH2.mjs → tui-DLEjew3K.mjs} +334 -115
  26. package/dist/tui-DLEjew3K.mjs.map +1 -0
  27. package/dist/utils-BTfShW0g.mjs +36 -0
  28. package/dist/utils-BTfShW0g.mjs.map +1 -0
  29. package/dist/{utils-CmuIYHtm.mjs → utils-D9CKnbke.mjs} +26 -34
  30. package/dist/utils-D9CKnbke.mjs.map +1 -0
  31. package/lib/register-skills.mjs +96 -0
  32. package/package.json +8 -3
  33. package/plugin/.claude-plugin/plugin.json +6 -0
  34. package/plugin/README.md +112 -0
  35. package/plugin/marketplace.json +17 -0
  36. package/plugin/skills/switch/SKILL.md +27 -0
  37. package/postinstall.mjs +35 -2
  38. package/dist/Spinner-CwBjkXHv.mjs +0 -153
  39. package/dist/Spinner-CwBjkXHv.mjs.map +0 -1
  40. package/dist/tui-DZ1SDOH2.mjs.map +0 -1
  41. package/dist/utils-CmuIYHtm.mjs.map +0 -1
@@ -1,14 +1,17 @@
1
- import { a as asIso, c as extractSessionIdFromPath, d as normalizeWhitespace, f as preserveText, h as truncateString, i as toInt, l as firstMessageTimestamp, m as toMessage, n as extractProjectPathFromFile, o as coerceMessageText, p as safeJsonParse, r as sha256, s as expandHome, t as boolFromEnv, u as normalizeRole } from "../utils-CmuIYHtm.mjs";
1
+ import { c as parseCursorLine, d as parseClaudeCodeLine, l as parseOpenClawLine, o as parseGstackLine, s as parseGeminiFile, u as parseCodexLine } from "../src-Bovo1ukU.mjs";
2
2
  import { n as normalizeBootstrapMode, t as createBootstrapStateKey } from "../protocol-BI9ficcl.mjs";
3
- import { n as resolveLockPath, t as acquireFileLock } from "../lock-5aJnda81.mjs";
3
+ import { a as isPrimaryAgentSourceEnabled, c as normalizeProjectPaths, o as matchesConfiguredProjectPath, s as normalizeCaptureAgents } from "../onboarding-preferences-Alhblobi.mjs";
4
+ import { i as expandHome, m as truncateString } from "../utils-D9CKnbke.mjs";
5
+ import { i as toInt, n as extractProjectPathFromFile, r as sha256, t as boolFromEnv } from "../utils-BTfShW0g.mjs";
6
+ import { n as resolveLockPath, t as acquireFileLock } from "../lock-BhZX2aF3.mjs";
4
7
  import process$1 from "node:process";
5
8
  import path from "node:path";
6
9
  import fs from "node:fs";
7
10
  import os from "node:os";
8
11
  import { spawnSync } from "node:child_process";
9
12
  import { DatabaseSync } from "node:sqlite";
10
- import { randomUUID } from "node:crypto";
11
13
  import fs$1 from "node:fs/promises";
14
+ import readline from "node:readline";
12
15
  import fg from "fast-glob";
13
16
  import { UltraContext } from "ultracontext";
14
17
 
@@ -179,920 +182,6 @@ function redact(value, currentKey = "") {
179
182
  return REDACTED;
180
183
  }
181
184
 
182
- //#endregion
183
- //#region ../../packages/parsers/src/agents/claude.mjs
184
- function formatToolUse(item) {
185
- const name = (item.name ?? "unknown").toLowerCase();
186
- const input = item.input ?? {};
187
- const filePath = input.file_path ?? input.path ?? "";
188
- if (name === "write") return `[Write] ${filePath}\n${preserveText(input.content ?? input.file_text ?? "")}`;
189
- if (name === "edit") {
190
- const parts = [`[Edit] ${filePath}`];
191
- if (input.old_string) parts.push(`- ${preserveText(input.old_string)}`);
192
- if (input.new_string) parts.push(`+ ${preserveText(input.new_string)}`);
193
- return parts.join("\n");
194
- }
195
- if (name === "read") return `[Read] ${filePath}`;
196
- if (name === "bash") return `[Bash] ${preserveText(input.command ?? "")}`;
197
- if (name === "grep" || name === "glob") {
198
- const loc = filePath ? ` in ${filePath}` : "";
199
- return `[${item.name}] ${input.pattern ?? ""}${loc}`;
200
- }
201
- const compact = JSON.stringify(input);
202
- return `[${item.name ?? name}] ${compact.length > 500 ? compact.slice(0, 500) + "..." : compact}`;
203
- }
204
- function formatToolResult(item) {
205
- const content = item.content ?? "";
206
- if (typeof content === "string") {
207
- const text = preserveText(content);
208
- return text ? `[result] ${truncateString(text, 1e3)}` : "[result] ok";
209
- }
210
- if (Array.isArray(content)) {
211
- const text = content.filter((c) => c?.type === "text" && typeof c.text === "string").map((c) => preserveText(c.text)).filter(Boolean).join("\n");
212
- return text ? `[result] ${truncateString(text, 1e3)}` : "[result] ok";
213
- }
214
- return "[result] ok";
215
- }
216
- function extractClaudeTextContent(content) {
217
- if (!content) return "";
218
- if (typeof content === "string") return preserveText(content);
219
- if (Array.isArray(content)) {
220
- const parts = [];
221
- for (const item of content) {
222
- if (!item || typeof item !== "object") continue;
223
- if (item.type === "text" && typeof item.text === "string") {
224
- const chunk = preserveText(item.text);
225
- if (chunk) parts.push(chunk);
226
- }
227
- if (item.type === "thinking" && typeof item.thinking === "string") {
228
- const chunk = preserveText(item.thinking);
229
- if (chunk) parts.push(`[thinking] ${chunk}`);
230
- }
231
- if (item.type === "tool_use") parts.push(formatToolUse(item));
232
- if (item.type === "tool_result") parts.push(formatToolResult(item));
233
- }
234
- return parts.join("\n\n");
235
- }
236
- if (typeof content === "object") {
237
- if (typeof content.text === "string") return preserveText(content.text);
238
- if (typeof content.content === "string") return preserveText(content.content);
239
- }
240
- return "";
241
- }
242
- function parseClaudeCodeLine({ line, filePath }) {
243
- const parsed = safeJsonParse(line);
244
- if (!parsed || typeof parsed !== "object") return null;
245
- const type = String(parsed.type ?? "").toLowerCase();
246
- const sessionId = parsed.sessionId ?? parsed.session_id ?? parsed.payload?.sessionId ?? parsed.payload?.session_id ?? extractSessionIdFromPath(filePath);
247
- const timestamp = parsed.timestamp ?? parsed.ts ?? (/* @__PURE__ */ new Date()).toISOString();
248
- if (type === "summary") {
249
- const summary = normalizeWhitespace(parsed.summary);
250
- if (!summary) return null;
251
- return {
252
- sessionId,
253
- eventType: "claude.summary",
254
- kind: "system",
255
- timestamp,
256
- message: toMessage(summary),
257
- raw: parsed
258
- };
259
- }
260
- if (type === "file-history-snapshot") {
261
- const trackedFiles = Object.keys(parsed.snapshot?.trackedFileBackups ?? {}).length;
262
- return {
263
- sessionId,
264
- eventType: "claude.file_snapshot",
265
- kind: "system",
266
- timestamp,
267
- message: toMessage(`${parsed.isSnapshotUpdate ? "File snapshot update" : "File snapshot"}: ${trackedFiles} tracked files`),
268
- raw: parsed
269
- };
270
- }
271
- if (type === "system") {
272
- const subtype = parsed.subtype ?? "unknown";
273
- let message;
274
- if (subtype === "local_command") message = parsed.content ? preserveText(parsed.content) : "Local command executed";
275
- else if (subtype === "stop_hook_summary") message = `Hook summary: ${parsed.hookCount ?? 0} hooks`;
276
- else if (subtype === "turn_duration") message = `Turn completed in ${parsed.durationMs ?? 0}ms (${parsed.messageCount ?? 0} messages)`;
277
- else message = `System event: ${subtype}`;
278
- if (!message) return null;
279
- return {
280
- sessionId,
281
- eventType: `claude.system.${subtype}`,
282
- kind: "system",
283
- timestamp,
284
- message: toMessage(message),
285
- raw: parsed
286
- };
287
- }
288
- if (type === "attachment") {
289
- const attachmentType = parsed.attachment?.type ?? "unknown";
290
- const parts = [];
291
- const added = parsed.attachment?.addedNames;
292
- const removed = parsed.attachment?.removedNames;
293
- if (Array.isArray(added) && added.length) parts.push(`added: ${added.join(", ")}`);
294
- if (Array.isArray(removed) && removed.length) parts.push(`removed: ${removed.join(", ")}`);
295
- const detail = parts.length ? ` (${parts.join("; ")})` : "";
296
- return {
297
- sessionId,
298
- eventType: `claude.attachment.${attachmentType}`,
299
- kind: "system",
300
- timestamp,
301
- message: toMessage(`Attachment: ${attachmentType}${detail}`),
302
- raw: parsed
303
- };
304
- }
305
- if (type === "progress") {
306
- const data = parsed.data ?? {};
307
- const subtype = data.type ?? "unknown";
308
- const label = data.hookName ?? subtype;
309
- return {
310
- sessionId,
311
- eventType: `claude.progress.${subtype}`,
312
- kind: "system",
313
- timestamp,
314
- message: toMessage(`Progress: ${subtype} (${label})`),
315
- raw: parsed
316
- };
317
- }
318
- if (type === "queue-operation") return {
319
- sessionId,
320
- eventType: "claude.queue_operation",
321
- kind: "system",
322
- timestamp,
323
- message: toMessage(`Queue ${parsed.operation ?? "unknown"}${parsed.content ? `: ${parsed.content}` : ""}`),
324
- raw: parsed
325
- };
326
- if (type === "custom-title") return {
327
- sessionId,
328
- eventType: "claude.custom_title",
329
- kind: "system",
330
- timestamp,
331
- message: toMessage(`Custom title: ${parsed.customTitle ?? "unknown"}`),
332
- raw: parsed
333
- };
334
- if (type === "agent-name") return {
335
- sessionId,
336
- eventType: "claude.agent_name",
337
- kind: "system",
338
- timestamp,
339
- message: toMessage(`Agent name: ${parsed.agentName ?? "unknown"}`),
340
- raw: parsed
341
- };
342
- if (type === "pr-link") return {
343
- sessionId,
344
- eventType: "claude.pr_link",
345
- kind: "system",
346
- timestamp,
347
- message: toMessage(`PR #${parsed.prNumber ?? 0}: ${parsed.prUrl ?? ""}`),
348
- raw: parsed
349
- };
350
- if (type === "permission-mode") return {
351
- sessionId,
352
- eventType: "claude.permission_mode",
353
- kind: "system",
354
- timestamp,
355
- message: toMessage(`Permission mode: ${parsed.permissionMode ?? "unknown"}`),
356
- raw: parsed
357
- };
358
- if (type === "last-prompt") {
359
- const prompt = parsed.lastPrompt ?? "";
360
- if (!prompt) return null;
361
- return {
362
- sessionId,
363
- eventType: "claude.last_prompt",
364
- kind: "system",
365
- timestamp,
366
- message: toMessage(prompt),
367
- raw: parsed
368
- };
369
- }
370
- if (type === "ai-title") return {
371
- sessionId,
372
- eventType: "claude.ai_title",
373
- kind: "system",
374
- timestamp,
375
- message: toMessage(`AI title: ${parsed.aiTitle ?? "unknown"}`),
376
- raw: parsed
377
- };
378
- if (type === "task-summary") return {
379
- sessionId,
380
- eventType: "claude.task_summary",
381
- kind: "system",
382
- timestamp,
383
- message: toMessage(parsed.summary ?? "[task summary]"),
384
- raw: parsed
385
- };
386
- if (type === "tag") return {
387
- sessionId,
388
- eventType: "claude.tag",
389
- kind: "system",
390
- timestamp,
391
- message: toMessage(`Tag: ${parsed.tag ?? ""}`),
392
- raw: parsed
393
- };
394
- if (type === "agent-color") return {
395
- sessionId,
396
- eventType: "claude.agent_color",
397
- kind: "system",
398
- timestamp,
399
- message: toMessage(`Agent color: ${parsed.agentColor ?? "unknown"}`),
400
- raw: parsed
401
- };
402
- if (type === "agent-setting") return {
403
- sessionId,
404
- eventType: "claude.agent_setting",
405
- kind: "system",
406
- timestamp,
407
- message: toMessage(`Agent setting: ${parsed.agentSetting ?? "unknown"}`),
408
- raw: parsed
409
- };
410
- if (type === "mode") return {
411
- sessionId,
412
- eventType: "claude.mode",
413
- kind: "system",
414
- timestamp,
415
- message: toMessage(`Mode: ${parsed.mode ?? "unknown"}`),
416
- raw: parsed
417
- };
418
- if (type === "worktree-state") {
419
- const ws = parsed.worktreeSession;
420
- return {
421
- sessionId,
422
- eventType: "claude.worktree_state",
423
- kind: "system",
424
- timestamp,
425
- message: toMessage(`Worktree: ${ws ? `entered ${ws.worktreePath ?? ""}` : "exited"}`),
426
- raw: parsed
427
- };
428
- }
429
- if (type === "attribution-snapshot") {
430
- const fileCount = Object.keys(parsed.fileStates ?? {}).length;
431
- return {
432
- sessionId,
433
- eventType: "claude.attribution_snapshot",
434
- kind: "system",
435
- timestamp,
436
- message: toMessage(`Attribution snapshot: ${fileCount} files`),
437
- raw: parsed
438
- };
439
- }
440
- if (type === "content-replacement") return {
441
- sessionId,
442
- eventType: "claude.content_replacement",
443
- kind: "system",
444
- timestamp,
445
- message: toMessage(`Content replacement: ${parsed.replacements?.length ?? 0} blocks`),
446
- raw: parsed
447
- };
448
- if (type === "speculation-accept") return {
449
- sessionId,
450
- eventType: "claude.speculation_accept",
451
- kind: "system",
452
- timestamp,
453
- message: toMessage(`Speculation accepted: saved ${parsed.timeSavedMs ?? 0}ms`),
454
- raw: parsed
455
- };
456
- if (type === "marble-origami-commit") return {
457
- sessionId,
458
- eventType: "claude.context_collapse_commit",
459
- kind: "system",
460
- timestamp,
461
- message: toMessage(parsed.summary ?? `Context collapse: ${parsed.collapseId ?? ""}`),
462
- raw: parsed
463
- };
464
- if (type === "marble-origami-snapshot") return {
465
- sessionId,
466
- eventType: "claude.context_collapse_snapshot",
467
- kind: "system",
468
- timestamp,
469
- message: toMessage(`Context collapse snapshot: ${parsed.staged?.length ?? 0} staged`),
470
- raw: parsed
471
- };
472
- if (type !== "user" && type !== "assistant") return null;
473
- const rawContent = parsed.message?.content ?? parsed.message ?? parsed.content ?? parsed.payload?.content ?? "";
474
- let message = extractClaudeTextContent(rawContent);
475
- if (!message) {
476
- if (!(Array.isArray(rawContent) && rawContent.some((c) => c?.type === "thinking"))) return null;
477
- message = "[thinking]";
478
- }
479
- const roleHint = parsed.message?.role ?? type;
480
- return {
481
- sessionId,
482
- eventType: `claude.${type}`,
483
- kind: normalizeRole(roleHint, type === "user" ? "user" : "assistant"),
484
- timestamp,
485
- message: toMessage(message),
486
- raw: parsed
487
- };
488
- }
489
-
490
- //#endregion
491
- //#region ../../packages/parsers/src/agents/codex.mjs
492
- function parseCodexLine({ line, filePath }) {
493
- const parsed = safeJsonParse(line);
494
- if (!parsed || typeof parsed !== "object") return null;
495
- const payload = parsed.payload ?? {};
496
- const sessionId = payload.session_id ?? payload.id ?? parsed.session_id ?? extractSessionIdFromPath(filePath);
497
- if (parsed.type === "event_msg") {
498
- const eventType = payload.type ?? "unknown";
499
- if (![
500
- "user_message",
501
- "agent_message",
502
- "task_started",
503
- "task_complete",
504
- "token_count",
505
- "agent_reasoning",
506
- "turn_aborted",
507
- "context_compacted"
508
- ].includes(eventType)) return null;
509
- const kind = eventType === "user_message" ? "user" : eventType === "agent_message" ? "assistant" : "system";
510
- const message = eventType === "token_count" ? "Token count update" : eventType === "agent_reasoning" ? payload.text : eventType === "turn_aborted" ? payload.reason ? `Turn aborted: ${payload.reason}` : "Turn aborted" : eventType === "context_compacted" ? "Context compacted" : payload.message ?? payload.last_agent_message ?? `${eventType}${payload.turn_id ? ` (${payload.turn_id})` : ""}`;
511
- return {
512
- sessionId,
513
- eventType: `event_msg.${eventType}`,
514
- kind,
515
- timestamp: parsed.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
516
- message: toMessage(message),
517
- raw: parsed
518
- };
519
- }
520
- if (parsed.type === "session_meta") return {
521
- sessionId,
522
- eventType: "session_meta",
523
- kind: "system",
524
- timestamp: parsed.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
525
- message: `Session started in ${payload.cwd ?? "unknown cwd"}`,
526
- raw: parsed
527
- };
528
- if (parsed.type === "response_item") {
529
- const subtype = payload.type;
530
- const ts = parsed.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
531
- if (subtype === "message") {
532
- const text = (payload.content ?? []).map((c) => c.text ?? "").filter(Boolean).join("\n");
533
- return {
534
- sessionId,
535
- eventType: "response_item.message",
536
- kind: {
537
- developer: "system",
538
- assistant: "assistant",
539
- user: "user"
540
- }[payload.role] ?? normalizeRole(payload.role),
541
- timestamp: ts,
542
- message: toMessage(text || `[${payload.role ?? "unknown"} message]`),
543
- raw: parsed
544
- };
545
- }
546
- if (subtype === "reasoning") return {
547
- sessionId,
548
- eventType: "response_item.reasoning",
549
- kind: "system",
550
- timestamp: ts,
551
- message: toMessage((payload.summary ?? []).map((s) => s.text ?? "").filter(Boolean).join("\n") || "[reasoning]"),
552
- raw: parsed
553
- };
554
- if (subtype === "function_call") return {
555
- sessionId,
556
- eventType: "response_item.function_call",
557
- kind: "system",
558
- timestamp: ts,
559
- message: toMessage(`[${payload.name ?? "unknown"}] ${payload.arguments ?? ""}`),
560
- raw: parsed
561
- };
562
- if (subtype === "function_call_output") return {
563
- sessionId,
564
- eventType: "response_item.function_call_output",
565
- kind: "system",
566
- timestamp: ts,
567
- message: toMessage(payload.output ?? `[output ${payload.call_id ?? ""}]`),
568
- raw: parsed
569
- };
570
- if (subtype === "web_search_call") {
571
- const query = payload.action?.query;
572
- return {
573
- sessionId,
574
- eventType: "response_item.web_search_call",
575
- kind: "system",
576
- timestamp: ts,
577
- message: query ? `[web_search] ${query}` : "[web_search]",
578
- raw: parsed
579
- };
580
- }
581
- if (subtype === "custom_tool_call") return {
582
- sessionId,
583
- eventType: "response_item.custom_tool_call",
584
- kind: "system",
585
- timestamp: ts,
586
- message: toMessage(`[${payload.name ?? "unknown"}] ${payload.input ?? ""}`),
587
- raw: parsed
588
- };
589
- if (subtype === "custom_tool_call_output") return {
590
- sessionId,
591
- eventType: "response_item.custom_tool_call_output",
592
- kind: "system",
593
- timestamp: ts,
594
- message: toMessage(payload.output ?? `[output ${payload.call_id ?? ""}]`),
595
- raw: parsed
596
- };
597
- return null;
598
- }
599
- if (parsed.type === "compacted") return {
600
- sessionId,
601
- eventType: "compacted",
602
- kind: "system",
603
- timestamp: parsed.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
604
- message: "Session compacted",
605
- raw: parsed
606
- };
607
- if (parsed.type === "turn_context") return {
608
- sessionId,
609
- eventType: "turn_context",
610
- kind: "system",
611
- timestamp: parsed.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
612
- message: `Turn context: model=${payload.model}, policy=${payload.approval_policy}`,
613
- raw: parsed
614
- };
615
- return null;
616
- }
617
-
618
- //#endregion
619
- //#region ../../packages/parsers/src/agents/openclaw.mjs
620
- function extractOpenClawTextContent(content) {
621
- if (!content) return "";
622
- if (typeof content === "string") return normalizeWhitespace(content);
623
- if (Array.isArray(content)) {
624
- const textParts = [];
625
- for (const item of content) {
626
- if (!item || typeof item !== "object") continue;
627
- if (item.type === "text" && typeof item.text === "string") {
628
- const chunk = normalizeWhitespace(item.text);
629
- if (chunk) textParts.push(chunk);
630
- }
631
- }
632
- return textParts.join("\n");
633
- }
634
- if (typeof content === "object" && typeof content.text === "string") return normalizeWhitespace(content.text);
635
- return "";
636
- }
637
- function extractOpenClawToolCalls(content) {
638
- if (!Array.isArray(content)) return [];
639
- const names = [];
640
- for (const item of content) {
641
- if (!item || typeof item !== "object" || item.type !== "toolCall") continue;
642
- const name = normalizeWhitespace(item.name);
643
- if (name) names.push(name);
644
- }
645
- return names;
646
- }
647
- function buildOpenClawRaw(parsed) {
648
- const raw = {
649
- type: parsed.type,
650
- id: parsed.id,
651
- parentId: parsed.parentId,
652
- timestamp: parsed.timestamp
653
- };
654
- if (parsed.type === "session") {
655
- raw.session = {
656
- id: parsed.id,
657
- version: parsed.version,
658
- cwd: parsed.cwd,
659
- parentSession: parsed.parentSession
660
- };
661
- return raw;
662
- }
663
- if (parsed.type === "custom") {
664
- raw.customType = parsed.customType;
665
- if (parsed.customType === "model-snapshot" && parsed.data && typeof parsed.data === "object") raw.data = {
666
- provider: parsed.data.provider,
667
- modelApi: parsed.data.modelApi,
668
- modelId: parsed.data.modelId,
669
- timestamp: parsed.data.timestamp
670
- };
671
- return raw;
672
- }
673
- if (parsed.message && typeof parsed.message === "object") {
674
- const contentTypes = Array.isArray(parsed.message.content) ? parsed.message.content.filter((item) => item && typeof item === "object").map((item) => String(item.type ?? "unknown")).slice(0, 12) : [];
675
- raw.message = {
676
- role: parsed.message.role,
677
- stopReason: parsed.message.stopReason,
678
- toolName: parsed.message.toolName,
679
- toolCallId: parsed.message.toolCallId,
680
- isError: parsed.message.isError,
681
- contentTypes
682
- };
683
- }
684
- if (parsed.type === "compaction") raw.compaction = {
685
- firstKeptEntryId: parsed.firstKeptEntryId,
686
- tokensBefore: parsed.tokensBefore
687
- };
688
- else if (parsed.type === "branch_summary") raw.branchSummary = {
689
- firstKeptEntryId: parsed.firstKeptEntryId,
690
- summary: typeof parsed.summary === "string" ? truncateString(parsed.summary, 350) : ""
691
- };
692
- return raw;
693
- }
694
- function parseOpenClawLine({ line, filePath }) {
695
- const parsed = safeJsonParse(line);
696
- if (!parsed || typeof parsed !== "object") return null;
697
- const type = String(parsed.type ?? "").toLowerCase();
698
- const sessionId = parsed.session_id ?? parsed.sessionId ?? parsed.message?.session_id ?? parsed.message?.sessionId ?? extractSessionIdFromPath(filePath);
699
- const timestamp = parsed.timestamp ?? parsed.message?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
700
- if (type === "session") return {
701
- sessionId,
702
- eventType: "openclaw.session",
703
- kind: "system",
704
- timestamp,
705
- message: toMessage(`Session started in ${parsed.cwd ?? "unknown cwd"}`),
706
- raw: buildOpenClawRaw(parsed)
707
- };
708
- if (type === "custom") {
709
- const customType = normalizeWhitespace(parsed.customType || "custom");
710
- if (customType === "openclaw.cache-ttl") return null;
711
- let message = `Custom event: ${customType}`;
712
- if (customType === "model-snapshot" && parsed.data && typeof parsed.data === "object") {
713
- const provider = normalizeWhitespace(parsed.data.provider || "");
714
- const modelId = normalizeWhitespace(parsed.data.modelId || "");
715
- const modelApi = normalizeWhitespace(parsed.data.modelApi || "");
716
- const details = [provider, modelId].filter(Boolean).join("/");
717
- message = `Model snapshot${details ? `: ${details}` : ""}${modelApi ? ` (${modelApi})` : ""}`;
718
- }
719
- return {
720
- sessionId,
721
- eventType: `openclaw.custom.${customType || "custom"}`,
722
- kind: "system",
723
- timestamp,
724
- message: toMessage(message),
725
- raw: buildOpenClawRaw(parsed)
726
- };
727
- }
728
- if (type === "compaction") return {
729
- sessionId,
730
- eventType: "openclaw.compaction",
731
- kind: "system",
732
- timestamp,
733
- message: toMessage("Session compaction summary updated"),
734
- raw: buildOpenClawRaw(parsed)
735
- };
736
- if (type === "branch_summary") return {
737
- sessionId,
738
- eventType: "openclaw.branch_summary",
739
- kind: "system",
740
- timestamp,
741
- message: toMessage(normalizeWhitespace(parsed.summary || "") || "Branch summary updated"),
742
- raw: buildOpenClawRaw(parsed)
743
- };
744
- if (type !== "message" && type !== "custom_message") return null;
745
- const eventMessage = parsed.message ?? {};
746
- const role = String(eventMessage.role ?? "").toLowerCase();
747
- if (role === "user" || role === "assistant") {
748
- const text = extractOpenClawTextContent(eventMessage.content);
749
- if (text) return {
750
- sessionId,
751
- eventType: `openclaw.${role}`,
752
- kind: role === "user" ? "user" : "assistant",
753
- timestamp,
754
- message: toMessage(text),
755
- raw: buildOpenClawRaw(parsed)
756
- };
757
- if (role === "assistant") {
758
- const toolCalls = extractOpenClawToolCalls(eventMessage.content);
759
- if (toolCalls.length > 0) return {
760
- sessionId,
761
- eventType: "openclaw.assistant.tool_use",
762
- kind: "system",
763
- timestamp,
764
- message: toMessage(`Assistant requested tools: ${toolCalls.slice(0, 5).join(", ")}${toolCalls.length > 5 ? ` (+${toolCalls.length - 5})` : ""}`),
765
- raw: buildOpenClawRaw(parsed)
766
- };
767
- }
768
- return null;
769
- }
770
- if (role === "toolresult") {
771
- const toolName = normalizeWhitespace(eventMessage.toolName || "");
772
- const isError = Boolean(eventMessage.isError);
773
- let message = `Tool result${toolName ? `: ${toolName}` : ""} (${isError ? "error" : "ok"})`;
774
- const text = extractOpenClawTextContent(eventMessage.content);
775
- if (text) message = `${message} ${truncateString(text, 320)}`;
776
- return {
777
- sessionId,
778
- eventType: "openclaw.tool_result",
779
- kind: "system",
780
- timestamp,
781
- message: toMessage(message),
782
- raw: buildOpenClawRaw(parsed)
783
- };
784
- }
785
- return null;
786
- }
787
-
788
- //#endregion
789
- //#region ../../packages/parsers/src/gstack.mjs
790
- function extractGstackProjectSlug(filePath) {
791
- return filePath.match(/\.gstack\/projects\/([^/]+)\//)?.[1] ?? "unknown-project";
792
- }
793
- function gstackFileType(filePath) {
794
- const base = filePath.split("/").pop() ?? "";
795
- if (base === "learnings.jsonl") return "learning";
796
- if (base === "timeline.jsonl") return "timeline";
797
- if (base === "resources-shown.jsonl") return "resource";
798
- if (base.endsWith("-reviews.jsonl")) return "review";
799
- return "unknown";
800
- }
801
- function parseGstackLine({ line, filePath }) {
802
- const parsed = safeJsonParse(line);
803
- if (!parsed || typeof parsed !== "object") return null;
804
- const projectSlug = extractGstackProjectSlug(filePath);
805
- const fileType = gstackFileType(filePath);
806
- const timestamp = parsed.ts ?? parsed.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
807
- const sessionId = projectSlug;
808
- if (fileType === "learning") {
809
- const insight = normalizeWhitespace(parsed.insight);
810
- if (!insight) return null;
811
- const skill = parsed.skill ?? "unknown";
812
- const conf = typeof parsed.confidence === "number" ? ` [${parsed.confidence}/10]` : "";
813
- return {
814
- sessionId,
815
- eventType: `gstack.learning.${parsed.type ?? "insight"}`,
816
- kind: "system",
817
- timestamp,
818
- message: toMessage(`[${skill}]${conf} ${insight}`),
819
- raw: parsed
820
- };
821
- }
822
- if (fileType === "timeline") {
823
- const skill = parsed.skill ?? "unknown";
824
- const event = parsed.event ?? "unknown";
825
- const branch = parsed.branch ? ` (${parsed.branch})` : "";
826
- const outcome = parsed.outcome ? ` → ${parsed.outcome}` : "";
827
- const duration = parsed.duration_s ? ` ${parsed.duration_s}s` : "";
828
- return {
829
- sessionId,
830
- eventType: `gstack.timeline.${event}`,
831
- kind: "system",
832
- timestamp,
833
- message: toMessage(`${skill} ${event}${branch}${outcome}${duration}`),
834
- raw: parsed
835
- };
836
- }
837
- if (fileType === "review") {
838
- const skill = parsed.skill ?? "review";
839
- const score = parsed.overall_score != null ? ` score=${parsed.overall_score}` : "";
840
- return {
841
- sessionId,
842
- eventType: "gstack.review",
843
- kind: "system",
844
- timestamp,
845
- message: toMessage(`${skill}${parsed.status ? ` [${parsed.status}]` : ""}${score}`),
846
- raw: parsed
847
- };
848
- }
849
- if (fileType === "resource") {
850
- const title = normalizeWhitespace(parsed.title);
851
- const url = parsed.url ?? "";
852
- if (!title && !url) return null;
853
- return {
854
- sessionId,
855
- eventType: "gstack.resource",
856
- kind: "system",
857
- timestamp,
858
- message: toMessage(title ? `${title} — ${url}` : url),
859
- raw: parsed
860
- };
861
- }
862
- return null;
863
- }
864
-
865
- //#endregion
866
- //#region ../../packages/parsers/src/writers/claude.mjs
867
- function isUuid(value) {
868
- return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(String(value ?? "").trim());
869
- }
870
- function normalizeSessionUuid(raw) {
871
- const value = String(raw ?? "").trim();
872
- if (isUuid(value)) return value;
873
- return randomUUID();
874
- }
875
- function claudeProjectDirName(cwd) {
876
- return path.resolve(String(cwd || process.cwd())).replace(/[\\/]/g, "-").replace(/[^A-Za-z0-9._-]/g, "-");
877
- }
878
- function claudeSessionFilePath(sessionId, cwd, baseDir) {
879
- const root = baseDir || expandHome("~/.claude/projects");
880
- return path.join(root, claudeProjectDirName(cwd), `${sessionId}.jsonl`);
881
- }
882
- async function hasLocalClaudeSession(sessionId, cwd = "", baseDir) {
883
- const id = String(sessionId ?? "").trim();
884
- if (!id) return false;
885
- const preferredPath = claudeSessionFilePath(id, cwd || process.cwd(), baseDir);
886
- try {
887
- if ((await fs$1.stat(preferredPath)).isFile()) return true;
888
- } catch {}
889
- const fg = (await import("fast-glob")).default;
890
- const root = baseDir || expandHome("~/.claude/projects");
891
- return (await fg([path.join(root, `**/*${id}.jsonl`)], {
892
- onlyFiles: true,
893
- absolute: true,
894
- unique: true,
895
- suppressErrors: true,
896
- followSymbolicLinks: false
897
- })).some((filePath) => path.basename(filePath, ".jsonl") === id);
898
- }
899
- async function writeClaudeSession({ sessionId, cwd, messages, baseDir }) {
900
- const runCwd = String(cwd || process.cwd());
901
- const resolvedSessionId = normalizeSessionUuid(sessionId);
902
- const firstTs = asIso(firstMessageTimestamp(messages));
903
- const filePath = claudeSessionFilePath(resolvedSessionId, runCwd, baseDir);
904
- if (await hasLocalClaudeSession(resolvedSessionId, runCwd, baseDir)) return {
905
- written: false,
906
- reason: "already_exists",
907
- filePath,
908
- sessionId: resolvedSessionId
909
- };
910
- try {
911
- await fs$1.mkdir(path.dirname(filePath), { recursive: true });
912
- const lines = [];
913
- let parentUuid = null;
914
- for (let i = 0; i < (messages?.length ?? 0); i += 1) {
915
- const message = messages[i];
916
- const normalizedRole = normalizeRole(message?.role);
917
- const role = normalizedRole === "assistant" ? "assistant" : normalizedRole === "user" ? "user" : "assistant";
918
- const rawText = coerceMessageText(message).trim();
919
- if (!rawText) continue;
920
- const text = normalizedRole === "system" ? `[system] ${rawText}` : rawText;
921
- const ts = asIso(message?.content?.timestamp ?? message?.metadata?.timestamp ?? new Date(new Date(firstTs).getTime() + i * 1e3).toISOString());
922
- const entryUuid = randomUUID();
923
- lines.push(JSON.stringify({
924
- parentUuid,
925
- isSidechain: false,
926
- userType: "external",
927
- cwd: runCwd,
928
- sessionId: resolvedSessionId,
929
- version: "adapter",
930
- gitBranch: "",
931
- type: role,
932
- message: {
933
- role,
934
- content: text
935
- },
936
- timestamp: ts,
937
- uuid: entryUuid
938
- }));
939
- parentUuid = entryUuid;
940
- }
941
- if (lines.length === 0) {
942
- const entryUuid = randomUUID();
943
- lines.push(JSON.stringify({
944
- parentUuid: null,
945
- isSidechain: false,
946
- userType: "external",
947
- cwd: runCwd,
948
- sessionId: resolvedSessionId,
949
- version: "adapter",
950
- gitBranch: "",
951
- type: "assistant",
952
- message: {
953
- role: "assistant",
954
- content: "[system] Session restored from UltraContext with no user/assistant messages."
955
- },
956
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
957
- uuid: entryUuid
958
- }));
959
- }
960
- await fs$1.writeFile(filePath, `${lines.join("\n")}\n`, "utf8");
961
- return {
962
- written: true,
963
- reason: "created",
964
- filePath,
965
- sessionId: resolvedSessionId
966
- };
967
- } catch (error) {
968
- return {
969
- written: false,
970
- reason: "write_failed",
971
- filePath,
972
- sessionId: resolvedSessionId,
973
- error: error instanceof Error ? error.message : String(error)
974
- };
975
- }
976
- }
977
-
978
- //#endregion
979
- //#region ../../packages/parsers/src/writers/codex.mjs
980
- function buildEventMsgLine(message, fallbackTs) {
981
- const ts = asIso(message?.content?.timestamp ?? message?.metadata?.timestamp ?? fallbackTs);
982
- const role = normalizeRole(message?.role);
983
- const text = coerceMessageText(message).trim();
984
- if (!text) return null;
985
- if (role === "user") return {
986
- timestamp: ts,
987
- type: "event_msg",
988
- payload: {
989
- type: "user_message",
990
- message: text,
991
- images: [],
992
- local_images: [],
993
- text_elements: []
994
- }
995
- };
996
- if (role === "assistant") return {
997
- timestamp: ts,
998
- type: "event_msg",
999
- payload: {
1000
- type: "agent_message",
1001
- message: text
1002
- }
1003
- };
1004
- return {
1005
- timestamp: ts,
1006
- type: "event_msg",
1007
- payload: {
1008
- type: "agent_message",
1009
- message: `[system] ${text}`
1010
- }
1011
- };
1012
- }
1013
- function sessionFilePath(sessionId, firstTimestamp, baseDir) {
1014
- const iso = asIso(firstTimestamp);
1015
- const [year, month, day] = iso.slice(0, 10).split("-");
1016
- const stamp = iso.replace(/\.\d{3}Z$/, "").replace(/:/g, "-").replace("Z", "");
1017
- const root = baseDir || expandHome("~/.codex/sessions");
1018
- const fileName = `rollout-${stamp}-${sessionId}.jsonl`;
1019
- return path.join(root, year, month, day, fileName);
1020
- }
1021
- async function hasLocalCodexSession(sessionId, baseDir) {
1022
- const id = String(sessionId ?? "").trim();
1023
- if (!id) return false;
1024
- const fg = (await import("fast-glob")).default;
1025
- const root = baseDir || expandHome("~/.codex/sessions");
1026
- return (await fg([path.join(root, `**/*${id}*.jsonl`)], {
1027
- onlyFiles: true,
1028
- absolute: true,
1029
- unique: true,
1030
- suppressErrors: true,
1031
- followSymbolicLinks: false
1032
- })).some((filePath) => filePath.includes(id));
1033
- }
1034
- async function writeCodexSession({ sessionId, cwd, messages, baseDir }) {
1035
- const id = String(sessionId ?? "").trim();
1036
- if (!id) return {
1037
- written: false,
1038
- reason: "missing_session_id",
1039
- filePath: ""
1040
- };
1041
- if (await hasLocalCodexSession(id, baseDir)) return {
1042
- written: false,
1043
- reason: "already_exists",
1044
- filePath: ""
1045
- };
1046
- const firstTs = asIso(firstMessageTimestamp(messages));
1047
- const filePath = sessionFilePath(id, firstTs, baseDir);
1048
- try {
1049
- await fs$1.mkdir(path.dirname(filePath), { recursive: true });
1050
- const lines = [];
1051
- lines.push(JSON.stringify({
1052
- timestamp: firstTs,
1053
- type: "session_meta",
1054
- payload: {
1055
- id,
1056
- timestamp: firstTs,
1057
- cwd: cwd || process.cwd(),
1058
- originator: "ultracontext_daemon",
1059
- cli_version: "restored",
1060
- source: "cli",
1061
- model_provider: "openai"
1062
- }
1063
- }));
1064
- let emitted = 0;
1065
- for (let i = 0; i < (messages?.length ?? 0); i += 1) {
1066
- const fallbackTs = new Date(new Date(firstTs).getTime() + i * 1e3).toISOString();
1067
- const line = buildEventMsgLine(messages[i], fallbackTs);
1068
- if (!line) continue;
1069
- lines.push(JSON.stringify(line));
1070
- emitted += 1;
1071
- }
1072
- if (emitted === 0) lines.push(JSON.stringify({
1073
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1074
- type: "event_msg",
1075
- payload: {
1076
- type: "agent_message",
1077
- message: "[system] Session restored from UltraContext with no user/assistant messages."
1078
- }
1079
- }));
1080
- await fs$1.writeFile(filePath, `${lines.join("\n")}\n`, "utf8");
1081
- return {
1082
- written: true,
1083
- reason: "created",
1084
- filePath
1085
- };
1086
- } catch (error) {
1087
- return {
1088
- written: false,
1089
- reason: "write_failed",
1090
- filePath,
1091
- error: error instanceof Error ? error.message : String(error)
1092
- };
1093
- }
1094
- }
1095
-
1096
185
  //#endregion
1097
186
  //#region ../sync/src/daemon.mjs
1098
187
  const LOG_LEVELS = {
@@ -1173,7 +262,9 @@ async function writeStatusJson(cfg, stats, state, runtime) {
1173
262
  recentLogs: state.recentLogs.slice(-240),
1174
263
  config: {
1175
264
  bootstrapMode: cfg.bootstrapMode,
1176
- claudeIncludeSubagents: cfg.claudeIncludeSubagents
265
+ claudeIncludeSubagents: cfg.claudeIncludeSubagents,
266
+ captureAgents: cfg.captureAgents,
267
+ projectPaths: cfg.projectPaths
1177
268
  }
1178
269
  };
1179
270
  const tmp = STATUS_FILE + ".tmp";
@@ -1246,6 +337,8 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1246
337
  bootstrapMode: normalizeBootstrapModeWithPrompt(process$1.env.DAEMON_BOOTSTRAP_MODE ?? "prompt") || "prompt",
1247
338
  bootstrapReset: boolFromEnv(process$1.env.DAEMON_BOOTSTRAP_RESET, false),
1248
339
  claudeIncludeSubagents: boolFromEnv(process$1.env.CLAUDE_INCLUDE_SUBAGENTS, false),
340
+ captureAgents: normalizeCaptureAgents(),
341
+ projectPaths: normalizeProjectPaths(),
1249
342
  cleanupEveryCycles: Math.max(toInt(process$1.env.DAEMON_STORE_CLEANUP_CYCLES, 20), 1)
1250
343
  };
1251
344
  const stats = {
@@ -1272,7 +365,8 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1272
365
  sources: null,
1273
366
  ingestMode: "all",
1274
367
  daemonRunning: false,
1275
- lockHandle: null
368
+ lockHandle: null,
369
+ projectPathCache: /* @__PURE__ */ new Map()
1276
370
  };
1277
371
  function isBenignStdioError(error) {
1278
372
  const code = String(error?.code ?? "");
@@ -1350,7 +444,7 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1350
444
  return "";
1351
445
  }
1352
446
  function pushRecentLog(level, message, data) {
1353
- let line = String(message ?? "");
447
+ let line = String(message ?? "").replace(/[\r\n\t\v\f\x00-\x1f]+/g, " ").replace(/\s{2,}/g, " ");
1354
448
  if (line.startsWith("Appended event to session context")) line = "context append";
1355
449
  if (line.startsWith("Context created")) line = "Context created";
1356
450
  if (line.startsWith("Context created without metadata fallback")) line = "Context created (fallback)";
@@ -1428,7 +522,9 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1428
522
  function serializeConfigPrefs() {
1429
523
  return {
1430
524
  bootstrapMode: normalizeBootstrapModeWithPrompt(cfg.bootstrapMode) || "prompt",
1431
- claudeIncludeSubagents: Boolean(cfg.claudeIncludeSubagents)
525
+ claudeIncludeSubagents: Boolean(cfg.claudeIncludeSubagents),
526
+ captureAgents: normalizeCaptureAgents(cfg.captureAgents),
527
+ projectPaths: normalizeProjectPaths(cfg.projectPaths)
1432
528
  };
1433
529
  }
1434
530
  async function persistConfigPrefsToFile(targetFile = cfg.configFile) {
@@ -1503,12 +599,25 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1503
599
  }
1504
600
  function applyConfigPrefs(prefs) {
1505
601
  if (!prefs || typeof prefs !== "object") return;
1506
- for (const field of ["bootstrapMode", "claudeIncludeSubagents"]) {
602
+ for (const field of [
603
+ "bootstrapMode",
604
+ "claudeIncludeSubagents",
605
+ "captureAgents",
606
+ "projectPaths"
607
+ ]) {
1507
608
  if (!(field in prefs)) continue;
1508
609
  if (field === "bootstrapMode") {
1509
610
  cfg.bootstrapMode = normalizeBootstrapModeWithPrompt(prefs.bootstrapMode) || "prompt";
1510
611
  continue;
1511
612
  }
613
+ if (field === "captureAgents") {
614
+ cfg.captureAgents = normalizeCaptureAgents(prefs.captureAgents);
615
+ continue;
616
+ }
617
+ if (field === "projectPaths") {
618
+ cfg.projectPaths = normalizeProjectPaths(prefs.projectPaths);
619
+ continue;
620
+ }
1512
621
  cfg[field] = Boolean(prefs[field]);
1513
622
  }
1514
623
  }
@@ -1518,10 +627,12 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1518
627
  const before = serializeConfigPrefs();
1519
628
  applyConfigPrefs(data);
1520
629
  const after = serializeConfigPrefs();
1521
- if (before.claudeIncludeSubagents !== after.claudeIncludeSubagents) applyRuntimeSources(buildSources());
630
+ if (before.claudeIncludeSubagents !== after.claudeIncludeSubagents || JSON.stringify(before.captureAgents) !== JSON.stringify(after.captureAgents)) applyRuntimeSources(buildSources());
1522
631
  if (JSON.stringify(before) !== JSON.stringify(after)) log("info", "Reloaded config from config.json", {
1523
632
  claude_subagents: after.claudeIncludeSubagents ? "on" : "off",
1524
- bootstrap_mode: after.bootstrapMode
633
+ bootstrap_mode: after.bootstrapMode,
634
+ capture_agents: after.captureAgents.join(","),
635
+ project_paths: after.projectPaths.length
1525
636
  });
1526
637
  if (data.bootstrapReset) {
1527
638
  cfg.bootstrapReset = true;
@@ -1542,13 +653,13 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1542
653
  const claudeGlob = expandHome(process$1.env.CLAUDE_GLOB ?? "~/.claude/projects/**/*.jsonl");
1543
654
  const openclawGlob = expandHome(process$1.env.OPENCLAW_GLOB ?? "~/.openclaw/agents/*/sessions/**/*.jsonl");
1544
655
  const sources = [];
1545
- if (boolFromEnv(process$1.env.INGEST_CODEX, true)) sources.push({
656
+ if (boolFromEnv(process$1.env.INGEST_CODEX, true) && isPrimaryAgentSourceEnabled("codex", cfg.captureAgents)) sources.push({
1546
657
  name: "codex",
1547
658
  enabled: true,
1548
659
  globs: [codexGlob],
1549
660
  parseLine: parseCodexLine
1550
661
  });
1551
- if (boolFromEnv(process$1.env.INGEST_CLAUDE, true)) sources.push({
662
+ if (boolFromEnv(process$1.env.INGEST_CLAUDE, true) && isPrimaryAgentSourceEnabled("claude", cfg.captureAgents)) sources.push({
1552
663
  name: "claude",
1553
664
  enabled: true,
1554
665
  globs: [claudeGlob],
@@ -1561,6 +672,20 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1561
672
  globs: [openclawGlob],
1562
673
  parseLine: parseOpenClawLine
1563
674
  });
675
+ const cursorGlob = expandHome(process$1.env.CURSOR_GLOB ?? "~/.cursor/projects/**/*.jsonl");
676
+ if (boolFromEnv(process$1.env.INGEST_CURSOR, true) && isPrimaryAgentSourceEnabled("cursor", cfg.captureAgents)) sources.push({
677
+ name: "cursor",
678
+ enabled: true,
679
+ globs: [cursorGlob],
680
+ parseLine: parseCursorLine
681
+ });
682
+ const geminiGlob = expandHome(process$1.env.GEMINI_GLOB ?? "~/.gemini/tmp/*/chats/session-*.json");
683
+ if (boolFromEnv(process$1.env.INGEST_GEMINI, true)) sources.push({
684
+ name: "gemini",
685
+ enabled: true,
686
+ globs: [geminiGlob],
687
+ parseFile: parseGeminiFile
688
+ });
1564
689
  const gstackGlob = expandHome(process$1.env.GSTACK_GLOB ?? "~/.gstack/projects/**/*.jsonl");
1565
690
  if (boolFromEnv(process$1.env.INGEST_GSTACK, true)) sources.push({
1566
691
  name: "gstack",
@@ -1580,6 +705,64 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1580
705
  ignore: source.ignoreGlobs ?? []
1581
706
  });
1582
707
  }
708
+ function extractProjectPathFromNormalized(normalized) {
709
+ const candidates = [normalized?.raw?.payload?.cwd, normalized?.raw?.cwd];
710
+ for (const candidate of candidates) {
711
+ const value = String(candidate ?? "").trim();
712
+ if (value) return path.resolve(value);
713
+ }
714
+ return "";
715
+ }
716
+ async function discoverProjectPathFromFileHead(source, filePath) {
717
+ const fromFilePath = extractProjectPathFromFile(filePath);
718
+ if (fromFilePath) return path.resolve(fromFilePath);
719
+ if (!source.parseLine) return "";
720
+ const stream = fs.createReadStream(filePath, { encoding: "utf8" });
721
+ const rl = readline.createInterface({
722
+ input: stream,
723
+ crlfDelay: Infinity
724
+ });
725
+ let scanned = 0;
726
+ try {
727
+ for await (const line of rl) {
728
+ if (scanned++ >= 32) break;
729
+ if (!line.trim()) continue;
730
+ const projectPath = extractProjectPathFromNormalized(source.parseLine({
731
+ line,
732
+ filePath
733
+ }));
734
+ if (projectPath) return projectPath;
735
+ }
736
+ } catch {
737
+ return "";
738
+ } finally {
739
+ rl.close();
740
+ stream.destroy();
741
+ }
742
+ return "";
743
+ }
744
+ const PROJECT_PATH_CACHE_LIMIT = 5e3;
745
+ function rememberProjectPath(cacheKey, projectPath) {
746
+ const cache = runtime.projectPathCache;
747
+ if (cache.has(cacheKey)) cache.delete(cacheKey);
748
+ cache.set(cacheKey, projectPath);
749
+ if (cache.size > PROJECT_PATH_CACHE_LIMIT) {
750
+ const oldest = cache.keys().next().value;
751
+ cache.delete(oldest);
752
+ }
753
+ }
754
+ async function resolveSourceFileProjectPath({ source, filePath, fileId }) {
755
+ const cacheKey = `${source.name}:${fileId}`;
756
+ if (runtime.projectPathCache.has(cacheKey)) {
757
+ const cached = runtime.projectPathCache.get(cacheKey);
758
+ runtime.projectPathCache.delete(cacheKey);
759
+ runtime.projectPathCache.set(cacheKey, cached);
760
+ return cached || "";
761
+ }
762
+ const projectPath = await discoverProjectPathFromFileHead(source, filePath);
763
+ rememberProjectPath(cacheKey, projectPath || "");
764
+ return projectPath || "";
765
+ }
1583
766
  function bootstrapStateStoreKey(sources) {
1584
767
  return createBootstrapStateKey({
1585
768
  host: cfg.host,
@@ -1612,7 +795,11 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1612
795
  if (shouldStop()) break;
1613
796
  try {
1614
797
  const stat = await fs$1.stat(filePath);
1615
- store.setOffset(offsetStoreKey(source.name, `${stat.dev}:${stat.ino}`), stat.size);
798
+ const fileId = `${stat.dev}:${stat.ino}`;
799
+ if (source.parseFile) {
800
+ const contents = await fs$1.readFile(filePath, "utf8");
801
+ store.setOffset(offsetStoreKey(source.name, fileId), sha256(contents));
802
+ } else store.setOffset(offsetStoreKey(source.name, fileId), stat.size);
1616
803
  } catch {}
1617
804
  }
1618
805
  }
@@ -1764,7 +951,7 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1764
951
  await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker()));
1765
952
  return results;
1766
953
  }
1767
- async function appendBulkToUltraContext({ store, uc, sourceName, events, filePath }) {
954
+ async function appendBulkToUltraContext({ store, uc, sourceName, events, filePath, projectPath = "" }) {
1768
955
  const bySession = /* @__PURE__ */ new Map();
1769
956
  for (const ev of events) {
1770
957
  const key = ev.normalized.sessionId;
@@ -1772,7 +959,6 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1772
959
  bySession.get(key).push(ev);
1773
960
  }
1774
961
  const sessionEntries = [...bySession.entries()];
1775
- const projectPath = extractProjectPathFromFile(filePath);
1776
962
  const contextIds = /* @__PURE__ */ new Map();
1777
963
  for (const [sessionId, sessionEvents] of sessionEntries) {
1778
964
  const contextMeta = {
@@ -1782,7 +968,20 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1782
968
  session_id: sessionId,
1783
969
  started_at: sessionEvents[0].normalized.timestamp
1784
970
  };
1785
- if (projectPath) contextMeta.project_path = projectPath;
971
+ const sessionProjectPath = sessionEvents.find((event) => event.projectPath)?.projectPath || projectPath;
972
+ if (sessionProjectPath) contextMeta.project_path = sessionProjectPath;
973
+ const isRealUserEvent = (ev) => {
974
+ if (ev.normalized.kind !== "user") return false;
975
+ const et = ev.normalized.eventType ?? "";
976
+ const msg = ev.normalized.message ?? "";
977
+ if (et === "response_item.message") return false;
978
+ if (msg.startsWith("A new session was started")) return false;
979
+ if (msg.startsWith("[result]")) return false;
980
+ if (msg.startsWith("<")) return false;
981
+ return true;
982
+ };
983
+ const firstUserEvent = sessionEvents.find(isRealUserEvent) ?? sessionEvents.find((ev) => ev.normalized.kind === "user");
984
+ if (firstUserEvent?.normalized?.message) contextMeta.title = firstUserEvent.normalized.message.replace(/[\r\n\t\v\f\x00-\x1f]+/g, " ").replace(/\s{2,}/g, " ").trim().slice(0, 120);
1786
985
  const ctxId = await getOrCreateContext(store, uc, sessionContextStoreKey(sourceName, sessionId), contextMeta, sourceName);
1787
986
  contextIds.set(sessionId, ctxId);
1788
987
  }
@@ -1821,12 +1020,13 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1821
1020
  lastSessionId: sessionId,
1822
1021
  lastAt: Date.now()
1823
1022
  });
1824
- if (cfg.logAppends) log("info", "Bulk appended events to session context", {
1825
- source: sourceName,
1826
- session_id: sessionId,
1827
- context_id: sessionContextId,
1828
- count: sessionEvents.length
1829
- });
1023
+ if (cfg.logAppends) for (const { normalized } of sessionEvents) {
1024
+ const msg = (normalized.message ?? "").replace(/[\r\n\t\v\f\x00-\x1f]+/g, " ").replace(/\s{2,}/g, " ").trim().slice(0, 80);
1025
+ log("info", `[${normalized.eventType}] ${msg}`, {
1026
+ source: sourceName,
1027
+ session_id: sessionId
1028
+ });
1029
+ }
1830
1030
  });
1831
1031
  }
1832
1032
  async function readNewLines(filePath, offset) {
@@ -1881,6 +1081,64 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1881
1081
  bumpSourceStat(source.name, "filesScanned");
1882
1082
  const fileId = `${stat.dev}:${stat.ino}`;
1883
1083
  const offsetKey = offsetStoreKey(source.name, fileId);
1084
+ const fileProjectPath = await resolveSourceFileProjectPath({
1085
+ source,
1086
+ filePath,
1087
+ fileId
1088
+ });
1089
+ if (!matchesConfiguredProjectPath(cfg.projectPaths, fileProjectPath)) return;
1090
+ if (source.parseFile) {
1091
+ const fileContents = await fs$1.readFile(filePath, "utf8");
1092
+ const contentHash = sha256(fileContents);
1093
+ if (store.getOffset(offsetKey) === contentHash) return;
1094
+ noteSourceActivity(source.name, {
1095
+ lastFile: filePath,
1096
+ lastAt: Date.now()
1097
+ });
1098
+ const allEvents = source.parseFile({
1099
+ fileContents,
1100
+ filePath
1101
+ });
1102
+ if (!Array.isArray(allEvents) || allEvents.length === 0) return;
1103
+ bumpStat("linesRead", allEvents.length);
1104
+ bumpSourceStat(source.name, "linesRead", allEvents.length);
1105
+ const pendingEvents = [];
1106
+ for (let i = 0; i < allEvents.length; i++) {
1107
+ if (shouldStop()) break;
1108
+ const normalized = allEvents[i];
1109
+ if (!normalized || !normalized.sessionId) continue;
1110
+ if (ingestMode === "last_24h" && !isWithinLast24h(normalized.timestamp)) continue;
1111
+ bumpStat("parsedEvents");
1112
+ bumpSourceStat(source.name, "parsedEvents");
1113
+ noteSourceActivity(source.name, {
1114
+ lastEventType: normalized.eventType,
1115
+ lastSessionId: normalized.sessionId,
1116
+ lastAt: Date.now()
1117
+ });
1118
+ const eventId = sha256(`${source.name}|${cfg.host}|${cfg.userId}|${normalized.sessionId}|${fileId}|${i}`);
1119
+ if (!markEventSeen(store, source.name, eventId)) {
1120
+ bumpStat("deduped");
1121
+ bumpSourceStat(source.name, "deduped");
1122
+ continue;
1123
+ }
1124
+ pendingEvents.push({
1125
+ normalized,
1126
+ eventId,
1127
+ lineOffset: i,
1128
+ projectPath: fileProjectPath
1129
+ });
1130
+ }
1131
+ if (pendingEvents.length > 0) await appendBulkToUltraContext({
1132
+ store,
1133
+ uc,
1134
+ sourceName: source.name,
1135
+ events: pendingEvents,
1136
+ filePath,
1137
+ projectPath: fileProjectPath
1138
+ });
1139
+ store.setOffset(offsetKey, contentHash);
1140
+ return;
1141
+ }
1884
1142
  const { lines, nextOffset } = await readNewLines(filePath, toInt(store.getOffset(offsetKey), 0));
1885
1143
  bumpStat("linesRead", lines.length);
1886
1144
  bumpSourceStat(source.name, "linesRead", lines.length);
@@ -1914,7 +1172,8 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1914
1172
  pendingEvents.push({
1915
1173
  normalized,
1916
1174
  eventId,
1917
- lineOffset
1175
+ lineOffset,
1176
+ projectPath: fileProjectPath
1918
1177
  });
1919
1178
  }
1920
1179
  if (pendingEvents.length > 0) await appendBulkToUltraContext({
@@ -1922,7 +1181,8 @@ async function daemonBoot({ createStore, resolveDbPath }) {
1922
1181
  uc,
1923
1182
  sourceName: source.name,
1924
1183
  events: pendingEvents,
1925
- filePath
1184
+ filePath,
1185
+ projectPath: fileProjectPath
1926
1186
  });
1927
1187
  store.setOffset(offsetKey, nextOffset);
1928
1188
  } catch (error) {
@@ -2118,5 +1378,5 @@ if (process.argv.includes("--daemon")) daemonBoot({
2118
1378
  });
2119
1379
 
2120
1380
  //#endregion
2121
- export { writeClaudeSession as i, writeCodexSession as n, hasLocalClaudeSession as r, hasLocalCodexSession as t };
1381
+ export { };
2122
1382
  //# sourceMappingURL=sdk-sync.mjs.map