clay-server 5.2.0-beta.5 → 5.2.0-beta.6

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 (151) hide show
  1. package/lib/claude-hook-installer.js +1 -267
  2. package/lib/claude-jsonl-watcher.js +1 -204
  3. package/lib/clay-skill-install.js +1 -33
  4. package/lib/cli-sessions.js +1 -300
  5. package/lib/codex-defaults.js +1 -28
  6. package/lib/context-usage.js +3 -2
  7. package/lib/daemon.js +5 -55
  8. package/lib/default-ai-runtime.js +4 -8
  9. package/lib/email-mcp-server.js +1 -1
  10. package/lib/kiro-defaults.js +1 -22
  11. package/lib/mcp-proxy.js +2 -32
  12. package/lib/mcp-query-bridge.js +1 -1
  13. package/lib/mcp-source-adapters.js +1 -91
  14. package/lib/mcp-source-context.js +1 -42
  15. package/lib/mcp-source-discovery.js +1 -169
  16. package/lib/mcp-source-plugins.js +1 -68
  17. package/lib/mcp-source-restrictions.js +1 -47
  18. package/lib/mcp-tool-policy.js +1 -26
  19. package/lib/os-user-diagnostics.js +2 -4
  20. package/lib/os-users.js +4 -65
  21. package/lib/project-connection.js +8 -7
  22. package/lib/project-http.js +4 -4
  23. package/lib/project-mcp.js +2 -2
  24. package/lib/project-pair-result-delivery.js +2 -2
  25. package/lib/project-pair-usage.js +12 -13
  26. package/lib/project-pending-message-queue.js +3 -1
  27. package/lib/project-session-history-actions.js +5 -3
  28. package/lib/project-session-pair.js +1 -1
  29. package/lib/project-session-runtime-actions.js +2 -1
  30. package/lib/project-session-spawn.js +2 -3
  31. package/lib/project-session-vendor.js +2 -1
  32. package/lib/project-sessions.js +18 -26
  33. package/lib/project-user-message.js +1 -0
  34. package/lib/project-vendor-login.js +6 -36
  35. package/lib/public/app.js +1 -0
  36. package/lib/public/css/messages.css +6 -2
  37. package/lib/public/modules/app-favicon.js +11 -2
  38. package/lib/public/modules/app-header.js +4 -2
  39. package/lib/public/modules/app-loop-ui.js +2 -0
  40. package/lib/public/modules/app-messages.js +36 -5
  41. package/lib/public/modules/app-notifications.js +1 -2
  42. package/lib/public/modules/app-panels.js +61 -123
  43. package/lib/public/modules/app-rate-limit.js +7 -27
  44. package/lib/public/modules/chat-render-runtime.js +3 -0
  45. package/lib/public/modules/mcp-connection-view.js +1 -1
  46. package/lib/public/modules/permission-control.js +5 -4
  47. package/lib/public/modules/scheduler.js +2 -0
  48. package/lib/public/modules/session-create-form.js +10 -10
  49. package/lib/public/modules/settings-defaults.js +27 -28
  50. package/lib/public/modules/tools.js +2 -1
  51. package/lib/public/modules/tui-grab.js +1 -2
  52. package/lib/public/modules/vendor-login-browser.js +5 -3
  53. package/lib/public/modules/vendor-login.js +2 -2
  54. package/lib/public/modules/vendor-priority.js +15 -1
  55. package/lib/public/muse-avatar.svg +4 -0
  56. package/lib/safe-bash-commands.js +3 -45
  57. package/lib/sdk-bridge.js +127 -395
  58. package/lib/sdk-foreground-admission.js +16 -0
  59. package/lib/sdk-mcp-descriptors.js +2 -37
  60. package/lib/sdk-message-processor.js +50 -17
  61. package/lib/sdk-quota-snapshot.js +1 -1
  62. package/lib/sdk-response-recovery.js +1 -155
  63. package/lib/sdk-worker.js +2 -677
  64. package/lib/server.js +1 -1
  65. package/lib/session-fork-history.js +21 -0
  66. package/lib/session-hygiene.js +3 -35
  67. package/lib/session-permission-mode.js +4 -3
  68. package/lib/session-skill-scope.js +11 -0
  69. package/lib/sessions.js +17 -89
  70. package/lib/tool-llm.js +1 -1
  71. package/lib/tui-transcript-index.js +1 -132
  72. package/lib/vendor-login-browser.js +1 -11
  73. package/lib/yoke/acp-agent-profiles.js +5 -3
  74. package/lib/yoke/adapters/antigravity.js +3 -14
  75. package/lib/yoke/adapters/claude-query-contract.js +9 -0
  76. package/lib/yoke/adapters/claude.js +7 -42
  77. package/lib/yoke/adapters/codex-query-contract.js +24 -0
  78. package/lib/yoke/adapters/codex.js +1 -0
  79. package/lib/yoke/adapters/kiro.js +3 -1
  80. package/lib/yoke/adapters/muse-query-contract.js +7 -0
  81. package/lib/yoke/adapters/muse.js +122 -0
  82. package/lib/yoke/auth-events.js +17 -0
  83. package/lib/yoke/claude-hook-installer.js +267 -0
  84. package/lib/yoke/claude-jsonl-watcher.js +204 -0
  85. package/lib/yoke/claude-maintenance.js +55 -0
  86. package/lib/yoke/claude-permission-patterns.js +53 -0
  87. package/lib/yoke/claude-response-recovery.js +155 -0
  88. package/lib/yoke/claude-transcript-hygiene.js +34 -0
  89. package/lib/yoke/clay-skill-install.js +33 -0
  90. package/lib/yoke/cli-sessions.js +300 -0
  91. package/lib/yoke/codex-app-server.js +7 -12
  92. package/lib/yoke/configuration.js +13 -0
  93. package/lib/yoke/discovery.js +70 -0
  94. package/lib/yoke/errors.js +8 -0
  95. package/lib/yoke/host-environment.js +7 -0
  96. package/lib/yoke/index.js +9 -51
  97. package/lib/yoke/interface.js +116 -0
  98. package/lib/yoke/kiro-acp-server.js +5 -5
  99. package/lib/yoke/legacy-sessions.js +89 -0
  100. package/lib/yoke/login-protocol.js +25 -0
  101. package/lib/yoke/maintenance.js +12 -0
  102. package/lib/yoke/mcp-proxy.js +32 -0
  103. package/lib/yoke/mcp-source-adapters.js +91 -0
  104. package/lib/yoke/mcp-source-context.js +42 -0
  105. package/lib/yoke/mcp-source-discovery.js +169 -0
  106. package/lib/yoke/mcp-source-plugins.js +68 -0
  107. package/lib/yoke/mcp-source-restrictions.js +47 -0
  108. package/lib/yoke/mcp-tool-policy-normalize.js +28 -0
  109. package/lib/yoke/model-preferences.js +6 -0
  110. package/lib/yoke/muse-artifacts.js +36 -0
  111. package/lib/yoke/muse-controls.js +50 -0
  112. package/lib/yoke/muse-input.js +64 -0
  113. package/lib/yoke/muse-items.js +157 -0
  114. package/lib/yoke/muse-policy.js +52 -0
  115. package/lib/yoke/muse-query.js +428 -0
  116. package/lib/yoke/muse-runtime.js +211 -0
  117. package/lib/yoke/muse-services.js +79 -0
  118. package/lib/yoke/muse-skills.js +79 -0
  119. package/lib/yoke/muse-turns.js +71 -0
  120. package/lib/yoke/options/acp.js +2 -0
  121. package/lib/yoke/options/antigravity.js +2 -0
  122. package/lib/yoke/options/claude.js +21 -0
  123. package/lib/yoke/options/codex-defaults.js +29 -0
  124. package/lib/yoke/options/codex-settings.js +21 -0
  125. package/lib/yoke/options/codex.js +6 -0
  126. package/lib/yoke/options/kiro-defaults.js +22 -0
  127. package/lib/yoke/options/kiro.js +5 -0
  128. package/lib/yoke/options/muse.js +5 -0
  129. package/lib/yoke/provider-contract.js +5 -0
  130. package/lib/yoke/providers/antigravity-executable.js +8 -0
  131. package/lib/yoke/providers/claude-executable.js +40 -0
  132. package/lib/yoke/providers/claude-model-descriptions.js +14 -0
  133. package/lib/yoke/providers/claude-process.js +97 -0
  134. package/lib/yoke/providers/claude-provision.js +75 -0
  135. package/lib/yoke/providers/codex-errors.js +8 -0
  136. package/lib/yoke/providers/muse-executable.js +17 -0
  137. package/lib/yoke/provisioning.js +6 -0
  138. package/lib/yoke/query-options.js +10 -0
  139. package/lib/yoke/response-recovery.js +41 -0
  140. package/lib/yoke/sdk-mcp-descriptors.js +37 -0
  141. package/lib/yoke/sdk-server-factory.js +7 -0
  142. package/lib/yoke/session-history.js +8 -0
  143. package/lib/yoke/session-tool-transport.js +31 -13
  144. package/lib/yoke/settings.js +23 -0
  145. package/lib/yoke/source-locations.js +4 -0
  146. package/lib/yoke/title-query.js +18 -7
  147. package/lib/yoke/tui-transcript-index.js +132 -0
  148. package/lib/yoke/usage.js +48 -0
  149. package/lib/yoke/user-input.js +13 -0
  150. package/lib/yoke/vendor-registry.js +81 -1
  151. package/package.json +3 -2
@@ -5,7 +5,7 @@
5
5
  var sessionProvenance = require("./session-provenance");
6
6
 
7
7
  var BLANK_SESSION_MAX_AGE_MS = 24 * 60 * 60 * 1000;
8
- var CLAUDE_INTERRUPTED_TEXT = "[Request interrupted by user]";
8
+ var transcriptHygiene = require("./yoke/claude-transcript-hygiene");
9
9
 
10
10
  // A session is "blank" when nothing has ever happened in it and nothing is
11
11
  // attached to it. Adopted external sessions are never blank (their transcript
@@ -61,43 +61,11 @@ function collectStaleBlankSessions(sessions, activeSessionId, now) {
61
61
  return stale;
62
62
  }
63
63
 
64
- function extractClaudeUserText(event) {
65
- var content = event && event.message && event.message.content;
66
- if (typeof content === "string") return content;
67
- if (!Array.isArray(content)) return "";
68
- var parts = [];
69
- for (var i = 0; i < content.length; i++) {
70
- if (content[i] && content[i].type === "text" && typeof content[i].text === "string") {
71
- parts.push(content[i].text);
72
- }
73
- }
74
- return parts.join("");
75
- }
76
-
77
- // SDK capability probes create a real Claude transcript with a single "hi"
78
- // prompt. Newer Claude Code versions append an interruption record when Clay
79
- // aborts the probe, so that bookkeeping record must not make the transcript
80
- // look like a real two-turn conversation.
81
- function isClaudeWarmupTranscript(events) {
82
- var promptCount = 0;
83
- for (var i = 0; i < events.length; i++) {
84
- var event = events[i];
85
- if (!event || typeof event !== "object") continue;
86
- if (event.type === "assistant") return false;
87
- if (event.type !== "user" || !event.message || event.message.role !== "user") continue;
88
- var text = extractClaudeUserText(event);
89
- if (text === CLAUDE_INTERRUPTED_TEXT) continue;
90
- if (text !== "hi") return false;
91
- promptCount++;
92
- }
93
- return promptCount === 1;
94
- }
95
-
96
64
  module.exports = {
97
65
  isBlankSession: isBlankSession,
98
66
  findReusableBlankSession: findReusableBlankSession,
99
67
  collectStaleBlankSessions: collectStaleBlankSessions,
100
- extractClaudeUserText: extractClaudeUserText,
101
- isClaudeWarmupTranscript: isClaudeWarmupTranscript,
68
+ extractClaudeUserText: transcriptHygiene.extractClaudeUserText,
69
+ isClaudeWarmupTranscript: transcriptHygiene.isClaudeWarmupTranscript,
102
70
  BLANK_SESSION_MAX_AGE_MS: BLANK_SESSION_MAX_AGE_MS,
103
71
  };
@@ -7,11 +7,12 @@ function normalize(mode) {
7
7
  return null;
8
8
  }
9
9
 
10
- function capabilities(session, defaultVendor) {
10
+ function capabilities(session, defaultVendor, provider) {
11
11
  var vendor = session && session.vendor || defaultVendor || "claude";
12
+ var contract = require("./yoke/session-tool-transport").contract(provider || vendor);
12
13
  return {
13
- auto: vendor === "claude",
14
- mcpOverride: vendor === "claude" && !!(session && session.queryInstance &&
14
+ auto: Array.isArray(contract.permissionModes) && contract.permissionModes.indexOf("auto") !== -1,
15
+ mcpOverride: contract.mcpPermissionOverrides === true && !!(session && session.queryInstance &&
15
16
  typeof session.queryInstance.setMcpPermissionModeOverride === "function"),
16
17
  };
17
18
  }
@@ -0,0 +1,11 @@
1
+ // Bind personal instruction reads to the existing live session/query authority.
2
+ function bind(session, sessions, options, valid, multiUser) {
3
+ var owner = session.ownerId || null;
4
+ return Object.assign({}, options, { isAvailable: function() {
5
+ if (!sessions || typeof sessions.get !== "function" || sessions.get(session.localId) !== session || !valid() || (session.ownerId || null) !== owner || session.youCurator || session.sessionVisibility === "shared" || session._mcpForeignInput) return false;
6
+ if (owner) return require("./mcp-session-access").canUsePersonalConnections(session, sessions);
7
+ return !multiUser && !session.delegated && !session._delegatedBy && !session._pairDelegation
8
+ && !(session.history || []).some(function(entry) { return entry.type === "user_message" && entry.from && entry.from !== "default"; });
9
+ } });
10
+ }
11
+ module.exports = { bind: bind };
package/lib/sessions.js CHANGED
@@ -3,7 +3,7 @@ var path = require("path");
3
3
  var config = require("./config");
4
4
  var utils = require("./utils");
5
5
  var users = require("./users");
6
- var { CODEX_DEFAULTS } = require("./codex-defaults");
6
+ var providerDefaults = require("./yoke/settings").defaults();
7
7
  var hygiene = require("./session-hygiene");
8
8
  var backgroundTaskTiming = require("./background-task-timing");
9
9
  var sessionProvenance = require("./session-provenance");
@@ -39,16 +39,16 @@ function createSessionManager(opts) {
39
39
  var permissionRequestIndex = {}; // requestId -> sessionLocalId (O(1) lookup)
40
40
  var capabilitiesByVendor = null; // set by sdk-bridge after adapter init
41
41
  var defaultVendor = null; // set by sdk-bridge
42
- var codexApproval = CODEX_DEFAULTS.approval;
43
- var codexSandbox = CODEX_DEFAULTS.sandbox;
44
- var codexWebSearch = CODEX_DEFAULTS.webSearch;
42
+ var codexApproval = providerDefaults.codexApproval;
43
+ var codexSandbox = providerDefaults.codexSandbox;
44
+ var codexWebSearch = providerDefaults.codexWebSearch;
45
45
 
46
46
  // --- Session persistence (centralized in ~/.clay/sessions/{encoded-cwd}/) ---
47
47
  var sessionsBase = opts.sessionsBase || path.join(config.CONFIG_DIR, "sessions");
48
48
  var encodedCwd = utils.resolveEncodedDir(sessionsBase, cwd);
49
49
  var sessionsDir = path.join(sessionsBase, encodedCwd);
50
50
  fs.mkdirSync(sessionsDir, { recursive: true });
51
- var cliSessionsDir = opts.cliSessionsDir || path.join(config.REAL_HOME, ".claude", "projects", utils.encodeCwd(cwd));
51
+ var cliSessionsDir = opts.cliSessionsDir || require("./yoke/legacy-sessions").directory(config.REAL_HOME, cwd);
52
52
  var deletedCliSessionsPath = path.join(sessionsDir, "deleted-cli-sessions.json");
53
53
  var deletedCliSessionIds = new Set();
54
54
  try {
@@ -77,29 +77,6 @@ function createSessionManager(opts) {
77
77
  }
78
78
  }
79
79
 
80
- function readCliTranscriptHead(filePath) {
81
- var MAX_READ = 64 * 1024;
82
- try {
83
- var stat = fs.statSync(filePath);
84
- var fd = fs.openSync(filePath, "r");
85
- var bytesToRead = Math.min(stat.size, MAX_READ);
86
- var buf = Buffer.alloc(bytesToRead);
87
- fs.readSync(fd, buf, 0, bytesToRead, 0);
88
- fs.closeSync(fd);
89
- var lines = buf.toString("utf8").split("\n");
90
- var events = [];
91
- for (var i = 0; i < lines.length; i++) {
92
- if (!lines[i]) continue;
93
- try {
94
- var event = JSON.parse(lines[i]);
95
- if (event && typeof event === "object") events.push(event);
96
- } catch (e) {}
97
- }
98
- return events;
99
- } catch (e) {
100
- return null;
101
- }
102
- }
103
80
 
104
81
  // Auto-migrate sessions from legacy locations:
105
82
  // v1: {cwd}/.claude-relay/sessions/
@@ -470,9 +447,6 @@ function createSessionManager(opts) {
470
447
  // - Warmup shape: 1 real "hi" prompt, optional automatic interruption,
471
448
  // and 0 assistant messages (covered by daemon cleanup but defensive)
472
449
  function adoptOrphanedCliSessions() {
473
- var files;
474
- try { files = fs.readdirSync(cliSessionsDir); } catch (e) { return; }
475
-
476
450
  // Build set of cliSessionIds Clay already tracks
477
451
  var knownCliIds = new Set();
478
452
  sessions.forEach(function (s) {
@@ -481,56 +455,10 @@ function createSessionManager(opts) {
481
455
 
482
456
  var adopted = 0;
483
457
 
484
- for (var i = 0; i < files.length; i++) {
485
- if (!files[i].endsWith(".jsonl")) continue;
486
- var cliSid = files[i].slice(0, -".jsonl".length);
487
- if (knownCliIds.has(cliSid)) continue;
488
- if (deletedCliSessionIds.has(cliSid)) continue;
489
-
490
- var fp = path.join(cliSessionsDir, files[i]);
491
- var events = readCliTranscriptHead(fp);
492
- if (!events) continue;
493
- var userCount = 0;
494
- var firstUserText = null;
495
- var createdAtIso = null;
496
- for (var li = 0; li < events.length; li++) {
497
- var ev = events[li];
498
- if (ev.type === "user" && ev.message && ev.message.role === "user") {
499
- userCount++;
500
- if (firstUserText == null) {
501
- var c = ev.message.content;
502
- if (typeof c === "string") {
503
- firstUserText = c;
504
- } else if (Array.isArray(c)) {
505
- var parts = [];
506
- for (var ci = 0; ci < c.length; ci++) {
507
- if (c[ci] && c[ci].type === "text" && typeof c[ci].text === "string") parts.push(c[ci].text);
508
- }
509
- firstUserText = parts.join("");
510
- }
511
- if (ev.timestamp && !createdAtIso) createdAtIso = ev.timestamp;
512
- }
513
- }
514
- }
515
-
516
- if (userCount === 0) continue;
517
- // Defensive warmup skip (daemon also cleans these)
518
- if (hygiene.isClaudeWarmupTranscript(events)) continue;
519
-
520
- // Title: first user message trimmed/truncated. Fall back to a
521
- // neutral label so the sidebar entry is identifiable.
522
- var title = (firstUserText || "").trim().replace(/\s+/g, " ");
523
- if (title.length > 60) title = title.slice(0, 57) + "...";
524
- if (!title) title = "Imported CLI session";
525
-
526
- var createdAt = Date.now();
527
- if (createdAtIso) {
528
- var t = Date.parse(createdAtIso);
529
- if (!isNaN(t)) createdAt = t;
530
- }
531
- var lastActivity = createdAt;
532
- try { lastActivity = fs.statSync(fp).mtimeMs; } catch (e) {}
533
-
458
+ var candidates = require("./yoke/legacy-sessions").candidates(cliSessionsDir, knownCliIds, deletedCliSessionIds);
459
+ for (var i = 0; i < candidates.length; i++) {
460
+ var candidate = candidates[i], cliSid = candidate.id;
461
+ var title = candidate.title, createdAt = candidate.createdAt, lastActivity = candidate.lastActivity;
534
462
  var localId = nextLocalId++;
535
463
  var session = {
536
464
  localId: localId,
@@ -548,7 +476,7 @@ function createSessionManager(opts) {
548
476
  history: [],
549
477
  messageUUIDs: [],
550
478
  lastRewindUuid: null,
551
- vendor: "claude",
479
+ vendor: candidate.vendor,
552
480
  mode: "tui",
553
481
  // Adopted from an external `claude` run in this cwd, not started by
554
482
  // Clay. Notification emitters must stay silent for these until the
@@ -580,10 +508,7 @@ function createSessionManager(opts) {
580
508
  var stale = [];
581
509
  sessions.forEach(function (session) {
582
510
  if (!session.adopted || !session.cliSessionId) return;
583
- var transcriptPath = path.join(cliSessionsDir, session.cliSessionId + ".jsonl");
584
- var events = readCliTranscriptHead(transcriptPath);
585
- var missingEmptySource = !fs.existsSync(transcriptPath) && (!session.history || session.history.length === 0);
586
- if (missingEmptySource || (events && hygiene.isClaudeWarmupTranscript(events))) stale.push(session.localId);
511
+ if (require("./yoke/legacy-sessions").obsolete(cliSessionsDir, session)) stale.push(session.localId);
587
512
  });
588
513
  for (var i = 0; i < stale.length; i++) deleteSessionQuiet(stale[i]);
589
514
  if (stale.length > 0) {
@@ -877,7 +802,8 @@ function createSessionManager(opts) {
877
802
  if (_item && (_item.type === "mention_user" || _item.type === "mention_response")) {
878
803
  console.log("[DEBUG replayHistory] sending mention at index=" + i + " from=" + fromIndex + " total=" + total + " type=" + _item.type + " mate=" + (_item.mateName || ""));
879
804
  }
880
- var replayItem = transform ? transform(_item) : _item;
805
+ var normalizedItem = require("./yoke/usage").clientEvent(_item, session.vendor || "claude");
806
+ var replayItem = transform ? transform(normalizedItem) : normalizedItem;
881
807
  if (replayItem && typeof replayItem === "object") replayItem = Object.assign({}, replayItem, { _historyIndex: i });
882
808
  _send(replayItem);
883
809
  }
@@ -887,9 +813,11 @@ function createSessionManager(opts) {
887
813
  var lastModelUsage = null;
888
814
  var lastCost = null;
889
815
  var lastStreamInputTokens = null;
816
+ var lastContext = null;
890
817
  for (var j = total - 1; j >= 0; j--) {
891
818
  if (session.history[j].type === "result") {
892
- var r = session.history[j];
819
+ var r = require("./yoke/usage").clientEvent(session.history[j], session.vendor || "claude");
820
+ lastContext = r.context || null;
893
821
  lastUsage = r.usage || null;
894
822
  lastModelUsage = r.modelUsage || null;
895
823
  lastCost = r.cost != null ? r.cost : null;
@@ -898,7 +826,7 @@ function createSessionManager(opts) {
898
826
  }
899
827
  }
900
828
 
901
- _send({ type: "history_done", lastUsage: lastUsage, lastModelUsage: lastModelUsage, lastCost: lastCost, lastStreamInputTokens: lastStreamInputTokens, contextUsage: session.lastContextUsage || null });
829
+ _send({ type: "history_done", lastUsage: lastUsage, lastModelUsage: lastModelUsage, lastCost: lastCost, lastStreamInputTokens: lastStreamInputTokens, context: lastContext, contextUsage: require("./yoke/usage").context(session.lastContextUsage, session.vendor || "claude") });
902
830
  for (var vi = 0; vi < sessionViewedListeners.length; vi++) {
903
831
  try { sessionViewedListeners[vi](session, targetWs, _send); } catch (e) {}
904
832
  }
package/lib/tool-llm.js CHANGED
@@ -36,7 +36,7 @@ async function complete(opts) {
36
36
  skipProjectInstructions: true,
37
37
  skipSkills: true,
38
38
  canUseTool: function () { return Promise.resolve({ behavior: "deny", message: "Capsule LLM calls cannot use tools." }); },
39
- adapterOptions: { CLAUDE: { linuxUser: opts.linuxUser || undefined, settingSources: ["user"] } },
39
+ adapterOptions: require("./yoke/query-options").map(adapter, { instructionSources: ["user"], readOnly: true, isolation: { user: opts.linuxUser || undefined } }),
40
40
  },
41
41
  });
42
42
  return result.text;
@@ -1,132 +1 @@
1
- // tui-transcript-index.js
2
- //
3
- // Build a per-session index of assistant text messages from a Claude Code
4
- // TUI transcript (~/.claude/projects/{encoded-cwd}/{cliSessionId}.jsonl).
5
- //
6
- // The TUI message-grab feature uses this index on the client to map a
7
- // hovered xterm region back to its original markdown source. We only
8
- // extract assistant text blocks. Tool calls and tool results render in
9
- // the terminal in a shape that's quite different from their JSONL form,
10
- // so matching them would be noisy and not particularly useful.
11
- //
12
- // Codex sessions don't write transcripts at all; callers should skip
13
- // indexing for non-claude vendors.
14
-
15
- var fs = require("fs");
16
- var path = require("path");
17
- var utils = require("./utils");
18
- var { REAL_HOME } = require("./config");
19
-
20
- var encodeCwd = utils.encodeCwd;
21
-
22
- // Min length of a normalized assistant message to bother indexing.
23
- // Anything shorter ("ok", "sure", "yes") would generate too many false
24
- // positives when matched against a hovered terminal block.
25
- var MIN_MATCH_LEN = 20;
26
-
27
- // Claude Code writes its JSONL under the *running user's* home, which
28
- // is not the daemon's home in OS-isolation mode (where each Clay user
29
- // gets a real Linux account like /home/clay-name). Callers from the
30
- // project context resolve the session owner's home and pass it in;
31
- // when no home is provided we fall back to REAL_HOME for single-user
32
- // installs.
33
- function transcriptFilePath(home, cwd, cliSessionId) {
34
- if (!cwd || !cliSessionId) return null;
35
- var base = home || REAL_HOME;
36
- return path.join(base, ".claude", "projects", encodeCwd(cwd), cliSessionId + ".jsonl");
37
- }
38
-
39
- // matchKey is just whitespace-normalized raw markdown. We intentionally
40
- // do NOT strip markdown decoration here.
41
- //
42
- // Reasoning: TUI rendering drops markdown markers (**bold** -> bold,
43
- // `code` -> code, [text](url) -> text, etc.) and the visible text is
44
- // therefore always a substring of the JSONL source's character stream.
45
- // If we strip server-side we risk getting the rules wrong on corner
46
- // cases (double-backtick code spans, list-bullet rendering quirks,
47
- // nested formatting) and the resulting matchKey diverges from both
48
- // sides at once. Leaving the source alone and letting the client do a
49
- // multi-offset substring probe against block text is more robust and
50
- // much less code.
51
- function normalizeForMatch(s) {
52
- if (!s) return "";
53
- return s.replace(/\s+/g, " ").trim();
54
- }
55
-
56
- // Stable per-message id. Claude's JSONL usually carries one of these but
57
- // fall back to the parsed line index so the client always has a handle.
58
- function messageId(obj, index) {
59
- if (obj && obj.uuid) return String(obj.uuid);
60
- if (obj && obj.message && obj.message.id) return String(obj.message.id);
61
- return "msg-" + index;
62
- }
63
-
64
- // Parse a single JSONL line into one assistant-text record, or null
65
- // when the line isn't one we index (user prompts, tool_use blocks,
66
- // tool_result records, etc.). Text-only assistant blocks get joined
67
- // in order so a multi-block response surfaces as a single grabbable
68
- // message.
69
- function parseAssistantTextLine(line, index) {
70
- var obj;
71
- try { obj = JSON.parse(line); } catch (e) { return null; }
72
- if (!obj || !obj.message) return null;
73
- if (obj.message.role !== "assistant") return null;
74
- if (!Array.isArray(obj.message.content)) return null;
75
-
76
- var text = "";
77
- for (var i = 0; i < obj.message.content.length; i++) {
78
- var block = obj.message.content[i];
79
- if (block && block.type === "text" && typeof block.text === "string") {
80
- text += block.text;
81
- }
82
- }
83
- text = text.trim();
84
- if (!text) return null;
85
-
86
- var normalized = normalizeForMatch(text);
87
- if (normalized.length < MIN_MATCH_LEN) return null;
88
-
89
- return {
90
- id: messageId(obj, index),
91
- text: text,
92
- matchKey: normalized,
93
- };
94
- }
95
-
96
- // Read the whole transcript and return the assistant text index. The
97
- // transcripts are small enough (KB to a few MB) that a sync read on
98
- // session open or on a watcher-driven update is fine. We also return
99
- // the mtime/size so callers can short-circuit re-reads when nothing
100
- // changed.
101
- function readAssistantIndex(home, cwd, cliSessionId) {
102
- var file = transcriptFilePath(home, cwd, cliSessionId);
103
- if (!file) return { messages: [], mtimeMs: 0, byteLength: 0 };
104
- var raw;
105
- var stat;
106
- try {
107
- raw = fs.readFileSync(file, "utf8");
108
- stat = fs.statSync(file);
109
- } catch (e) {
110
- return { messages: [], mtimeMs: 0, byteLength: 0 };
111
- }
112
- var lines = raw.split("\n");
113
- var messages = [];
114
- for (var i = 0; i < lines.length; i++) {
115
- if (!lines[i]) continue;
116
- var entry = parseAssistantTextLine(lines[i], i);
117
- if (entry) messages.push(entry);
118
- }
119
- return {
120
- messages: messages,
121
- mtimeMs: stat.mtimeMs || 0,
122
- byteLength: stat.size || raw.length,
123
- };
124
- }
125
-
126
- module.exports = {
127
- MIN_MATCH_LEN: MIN_MATCH_LEN,
128
- transcriptFilePath: transcriptFilePath,
129
- readAssistantIndex: readAssistantIndex,
130
- parseAssistantTextLine: parseAssistantTextLine,
131
- normalizeForMatch: normalizeForMatch,
132
- };
1
+ module.exports = require("./yoke/tui-transcript-index");
@@ -7,17 +7,7 @@ var spawn = require("child_process").spawn;
7
7
  var buildUserEnv = require("./build-user-env").buildUserEnv;
8
8
  var wrapSpawnAsUser = require("./os-users").wrapSpawnAsUser;
9
9
 
10
- function loginUrl(value, vendor) {
11
- try {
12
- var url = new URL(value);
13
- var hosts = vendor === "codex" ? ["auth.openai.com"] : ["claude.ai", "claude.com", "console.anthropic.com", "platform.claude.com"];
14
- if (url.protocol !== "https:" || url.username || url.password || hosts.indexOf(url.hostname) < 0) return null;
15
- if (!url.searchParams.get("state") || !url.searchParams.get("code_challenge")) return null;
16
- var callback = new URL(url.searchParams.get("redirect_uri"));
17
- if (callback.protocol !== "http:" || ["localhost", "127.0.0.1", "[::1]"].indexOf(callback.hostname) < 0) return null;
18
- return url.href;
19
- } catch (e) { return null; }
20
- }
10
+ var loginUrl = require("./yoke/login-protocol").loginUrl;
21
11
 
22
12
  function createLoginBrowser(options) {
23
13
  var initial = null;
@@ -9,8 +9,9 @@ var execFile = require("child_process").execFile;
9
9
  var execFileSync = require("child_process").execFileSync;
10
10
 
11
11
  function findOnPath(binaryName, overrideName) {
12
- var overridePath = overrideName && process.env[overrideName];
13
- if (overridePath && fs.existsSync(overridePath)) return overridePath;
12
+ var discovery = require("./discovery");
13
+ var overridePath = overrideName && discovery.explicitEntry(process.env, overrideName, false);
14
+ if (overridePath !== undefined && overrideName) return overridePath;
14
15
  try {
15
16
  var command = process.platform === "win32" ? "where" : "which";
16
17
  var out = execFileSync(command, [binaryName], {
@@ -18,7 +19,8 @@ function findOnPath(binaryName, overrideName) {
18
19
  encoding: "utf8",
19
20
  stdio: ["pipe", "pipe", "pipe"],
20
21
  });
21
- return out.trim().split(/\r?\n/)[0] || null;
22
+ var resolved = out.trim().split(/\r?\n/)[0] || null;
23
+ return discovery.validEntry(resolved, false) ? resolved : null;
22
24
  } catch (e) {
23
25
  return null;
24
26
  }
@@ -3,19 +3,7 @@ var childProcess = require("child_process");
3
3
  var readline = require("readline");
4
4
  var skillDiscovery = require("../skill-discovery");
5
5
 
6
- function findBinary() {
7
- if (process.env.ANTIGRAVITY_CLI_PATH) return process.env.ANTIGRAVITY_CLI_PATH;
8
- try {
9
- var command = process.platform === "win32" ? "where" : "which";
10
- return childProcess.execFileSync(command, ["agy"], {
11
- encoding: "utf8",
12
- timeout: 3000,
13
- stdio: ["pipe", "pipe", "pipe"],
14
- }).trim().split(/\r?\n/)[0] || null;
15
- } catch (e) {
16
- return null;
17
- }
18
- }
6
+ var findBinary = require("../providers/antigravity-executable").resolve;
19
7
 
20
8
  function modelValue(model) {
21
9
  if (typeof model === "string") return model;
@@ -427,7 +415,8 @@ function createAntigravityAdapter(opts) {
427
415
  var adapter = {
428
416
  vendor: "antigravity",
429
417
  init: async function(initOpts) {
430
- if (!binaryPath) binaryPath = findBinary();
418
+ if (opts.linuxUser || initOpts && initOpts.linuxUser) throw new Error("Antigravity does not support mapped OS-user execution.");
419
+ if (!binaryPath) binaryPath = findBinary({ env: initOpts && initOpts.env || opts.env || process.env });
431
420
  if (!binaryPath) throw new Error("Antigravity CLI binary not found: agy");
432
421
  var fetched = opts._fetchModels ? await opts._fetchModels(binaryPath, cwd) : await fetchModels(binaryPath, cwd);
433
422
  if (fetched && fetched.length) cachedModels = fetched;
@@ -0,0 +1,9 @@
1
+ // Init-free provider transport obligations, shared by the registry and adapter.
2
+ module.exports = {
3
+ questionInput: function(name, input) { return name === "AskUserQuestion" ? input : null; },
4
+ toolTransport: "sdk-mcp",
5
+ catalogLifetime: "query",
6
+ permissionModes: ["auto"],
7
+ mcpPermissionOverrides: true,
8
+ interruptionMessage: "Interrupted · What should Claude do instead?",
9
+ };
@@ -528,7 +528,7 @@ function flattenEvent(raw) {
528
528
  // --- rate_limit_event ---
529
529
  if (raw.type === "rate_limit_event" && raw.rate_limit_info) {
530
530
  base.yokeType = "rate_limit";
531
- base.rateLimitInfo = raw.rate_limit_info;
531
+ base.rateLimitInfo = Object.assign({}, raw.rate_limit_info, { isUsingOverage: raw.rate_limit_info.isUsingOverage === true || raw.rate_limit_info.overageInUse === true });
532
532
  return base;
533
533
  }
534
534
 
@@ -1543,43 +1543,7 @@ function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool,
1543
1543
 
1544
1544
  // --- Adapter factory ---
1545
1545
 
1546
- function resolveClaudeBinaryPath() {
1547
- // 1. Explicit env var override
1548
- if (process.env.CLAUDE_CODE_PATH && fs.existsSync(process.env.CLAUDE_CODE_PATH)) {
1549
- return process.env.CLAUDE_CODE_PATH;
1550
- }
1551
-
1552
- // 2. `which claude` in the daemon's PATH
1553
- try {
1554
- var result = require("child_process").execSync("which claude", { encoding: "utf8", timeout: 5000 }).trim();
1555
- if (result && fs.existsSync(result)) return result;
1556
- } catch (e) {}
1557
-
1558
- // 3. Common per-user and system locations (best-effort fallback for the daemon user)
1559
- var home = process.env.HOME || "";
1560
- var candidates = [];
1561
- if (home) {
1562
- candidates.push(home + "/.npm-global/bin/claude");
1563
- candidates.push(home + "/.local/bin/claude");
1564
- candidates.push(home + "/.volta/bin/claude");
1565
- candidates.push(home + "/.bun/bin/claude");
1566
- candidates.push(home + "/bin/claude");
1567
- }
1568
- candidates.push("/usr/local/bin/claude");
1569
- candidates.push("/usr/bin/claude");
1570
- candidates.push("/opt/homebrew/bin/claude");
1571
- for (var i = 0; i < candidates.length; i++) {
1572
- try { if (fs.existsSync(candidates[i])) return candidates[i]; } catch (e) {}
1573
- }
1574
-
1575
- // 4. Bundled CLI entry from the SDK's peer
1576
- try {
1577
- var resolved = require.resolve("@anthropic-ai/claude-code/cli.js");
1578
- if (resolved && fs.existsSync(resolved)) return resolved;
1579
- } catch (e) {}
1580
-
1581
- return null;
1582
- }
1546
+ var resolveClaudeBinaryPath = require("../providers/claude-executable").resolve;
1583
1547
 
1584
1548
  function createClaudeAdapter(opts) {
1585
1549
  var _cwd = (opts && opts.cwd) || process.cwd();
@@ -1592,6 +1556,7 @@ function createClaudeAdapter(opts) {
1592
1556
 
1593
1557
  var adapter = {
1594
1558
  vendor: "claude",
1559
+ queryContract: require("./claude-query-contract"),
1595
1560
  userInputCapability: { mode: "native", native: true, transport: "AskUserQuestion/onElicitation" },
1596
1561
 
1597
1562
  // Path to worker script (sdk-bridge uses this to spawn worker processes)
@@ -1678,7 +1643,7 @@ function createClaudeAdapter(opts) {
1678
1643
 
1679
1644
  try {
1680
1645
  var models = await stream.supportedModels();
1681
- result.models = models || [];
1646
+ result.models = require("../providers/claude-model-descriptions").list(models);
1682
1647
  _cachedModels = result.models;
1683
1648
  } catch (e) {
1684
1649
  // supportedModels may fail, models list will be empty
@@ -1993,7 +1958,7 @@ function createClaudeAdapter(opts) {
1993
1958
  warmupDone = true;
1994
1959
  var r = msg.result || {};
1995
1960
  resolve({
1996
- models: r.models || [],
1961
+ models: require("../providers/claude-model-descriptions").list(r.models),
1997
1962
  defaultModel: r.model || "",
1998
1963
  skills: r.skills || [],
1999
1964
  slashCommands: r.slashCommands || [],
@@ -2110,7 +2075,7 @@ function createClaudeAdapter(opts) {
2110
2075
 
2111
2076
  try {
2112
2077
  var models = await stream.supportedModels();
2113
- result.models = models || [];
2078
+ result.models = require("../providers/claude-model-descriptions").list(models);
2114
2079
  _cachedModels = result.models;
2115
2080
  } catch (e) {
2116
2081
  // supportedModels may fail, models list will be empty
@@ -2161,7 +2126,7 @@ function createClaudeAdapter(opts) {
2161
2126
  warmupDone = true;
2162
2127
  var r = msg.result || {};
2163
2128
  resolve({
2164
- models: r.models || [],
2129
+ models: require("../providers/claude-model-descriptions").list(r.models),
2165
2130
  defaultModel: r.model || "",
2166
2131
  skills: r.skills || [],
2167
2132
  slashCommands: r.slashCommands || [],
@@ -0,0 +1,24 @@
1
+ // Codex restores dynamic definitions from the original thread catalog.
2
+ // Keep persisted legacy field names here so old conversations remain readable.
3
+ var fields = {
4
+ schedules: "codexDynamicToolCatalogVersion",
5
+ questions: "codexUserInputToolCatalogVersion",
6
+ browser: "codexSharedBrowserToolCatalogVersion",
7
+ issues: "codexIssuesToolCatalogVersion",
8
+ knowledge: "codexYouToolCatalogVersion",
9
+ linear: "codexLinearToolCatalogVersion",
10
+ };
11
+ module.exports = {
12
+ toolTransport: "dynamic",
13
+ catalogLifetime: "conversation",
14
+ externalToolTransport: "dynamic",
15
+ defaultUserInputMode: "fallback",
16
+ questionGuidance: require("../user-input-guidance").codexQuestionGuidance,
17
+ catalogVersion: function (session, family) { return Number(session[fields[family]] || 0); },
18
+ recordCatalog: function (session, versions) {
19
+ Object.keys(versions).forEach(function (family) {
20
+ if (fields[family]) session[fields[family]] = versions[family];
21
+ });
22
+ },
23
+ staleCatalogReason: "This Codex conversation predates Clay's scheduled-task tool catalog. Start a new conversation to use the assisted interview; manual task creation remains available here.",
24
+ };
@@ -1682,6 +1682,7 @@ function createCodexAdapter(opts) {
1682
1682
  var adapter = {
1683
1683
  vendor: "codex",
1684
1684
  queryBoundToolCapability: CODEX_QUERY_BOUND_TOOL_CAPABILITY,
1685
+ queryContract: require("./codex-query-contract"),
1685
1686
  userInputCapability: { mode: "native", native: true, transport: "item/tool/requestUserInput" },
1686
1687
 
1687
1688
  init: function(initOpts) {
@@ -279,7 +279,7 @@ function flattenUpdate(update, state) {
279
279
 
280
280
  // Token usage
281
281
  if (type === "usage_update") {
282
- if (typeof update.used === "number") state.lastInputTokens = update.used;
282
+ if (typeof update.used === "number") { state.lastInputTokens = update.used; state.contextMeasurement = "observed"; }
283
283
  if (typeof update.size === "number") state.contextWindow = update.size;
284
284
  return events;
285
285
  }
@@ -294,6 +294,7 @@ function flattenUpdate(update, state) {
294
294
  if (typeof percentage !== "number") percentage = kiroMeta.usagePercentage;
295
295
  if (typeof percentage === "number" && state.contextWindow) {
296
296
  state.lastInputTokens = Math.round(state.contextWindow * percentage / 100);
297
+ state.contextMeasurement = "estimated";
297
298
  }
298
299
  }
299
300
  return events;
@@ -782,6 +783,7 @@ function createKiroQueryHandle(acp, queryOpts) {
782
783
  getContextUsage: function() {
783
784
  return Promise.resolve(state.lastInputTokens != null ? {
784
785
  input_tokens: state.lastInputTokens,
786
+ measurement: state.contextMeasurement || "observed",
785
787
  contextWindow: state.contextWindow || null,
786
788
  } : null);
787
789
  },
@@ -0,0 +1,7 @@
1
+ // Muse's sessionMcp configuration is supplied on both start and resume.
2
+ module.exports = {
3
+ toolTransport: "stdio-mcp",
4
+ catalogLifetime: "query",
5
+ permissionModes: ["auto"],
6
+ interruptionMessage: "Interrupted · What should Muse do instead?",
7
+ };