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.
- package/lib/claude-hook-installer.js +1 -267
- package/lib/claude-jsonl-watcher.js +1 -204
- package/lib/clay-skill-install.js +1 -33
- package/lib/cli-sessions.js +1 -300
- package/lib/codex-defaults.js +1 -28
- package/lib/context-usage.js +3 -2
- package/lib/daemon.js +5 -55
- package/lib/default-ai-runtime.js +4 -8
- package/lib/email-mcp-server.js +1 -1
- package/lib/kiro-defaults.js +1 -22
- package/lib/mcp-proxy.js +2 -32
- package/lib/mcp-query-bridge.js +1 -1
- package/lib/mcp-source-adapters.js +1 -91
- package/lib/mcp-source-context.js +1 -42
- package/lib/mcp-source-discovery.js +1 -169
- package/lib/mcp-source-plugins.js +1 -68
- package/lib/mcp-source-restrictions.js +1 -47
- package/lib/mcp-tool-policy.js +1 -26
- package/lib/os-user-diagnostics.js +2 -4
- package/lib/os-users.js +4 -65
- package/lib/project-connection.js +8 -7
- package/lib/project-http.js +4 -4
- package/lib/project-mcp.js +2 -2
- package/lib/project-pair-result-delivery.js +2 -2
- package/lib/project-pair-usage.js +12 -13
- package/lib/project-pending-message-queue.js +3 -1
- package/lib/project-session-history-actions.js +5 -3
- package/lib/project-session-pair.js +1 -1
- package/lib/project-session-runtime-actions.js +2 -1
- package/lib/project-session-spawn.js +2 -3
- package/lib/project-session-vendor.js +2 -1
- package/lib/project-sessions.js +18 -26
- package/lib/project-user-message.js +1 -0
- package/lib/project-vendor-login.js +6 -36
- package/lib/public/app.js +1 -0
- package/lib/public/css/messages.css +6 -2
- package/lib/public/modules/app-favicon.js +11 -2
- package/lib/public/modules/app-header.js +4 -2
- package/lib/public/modules/app-loop-ui.js +2 -0
- package/lib/public/modules/app-messages.js +36 -5
- package/lib/public/modules/app-notifications.js +1 -2
- package/lib/public/modules/app-panels.js +61 -123
- package/lib/public/modules/app-rate-limit.js +7 -27
- package/lib/public/modules/chat-render-runtime.js +3 -0
- package/lib/public/modules/mcp-connection-view.js +1 -1
- package/lib/public/modules/permission-control.js +5 -4
- package/lib/public/modules/scheduler.js +2 -0
- package/lib/public/modules/session-create-form.js +10 -10
- package/lib/public/modules/settings-defaults.js +27 -28
- package/lib/public/modules/tools.js +2 -1
- package/lib/public/modules/tui-grab.js +1 -2
- package/lib/public/modules/vendor-login-browser.js +5 -3
- package/lib/public/modules/vendor-login.js +2 -2
- package/lib/public/modules/vendor-priority.js +15 -1
- package/lib/public/muse-avatar.svg +4 -0
- package/lib/safe-bash-commands.js +3 -45
- package/lib/sdk-bridge.js +127 -395
- package/lib/sdk-foreground-admission.js +16 -0
- package/lib/sdk-mcp-descriptors.js +2 -37
- package/lib/sdk-message-processor.js +50 -17
- package/lib/sdk-quota-snapshot.js +1 -1
- package/lib/sdk-response-recovery.js +1 -155
- package/lib/sdk-worker.js +2 -677
- package/lib/server.js +1 -1
- package/lib/session-fork-history.js +21 -0
- package/lib/session-hygiene.js +3 -35
- package/lib/session-permission-mode.js +4 -3
- package/lib/session-skill-scope.js +11 -0
- package/lib/sessions.js +17 -89
- package/lib/tool-llm.js +1 -1
- package/lib/tui-transcript-index.js +1 -132
- package/lib/vendor-login-browser.js +1 -11
- package/lib/yoke/acp-agent-profiles.js +5 -3
- package/lib/yoke/adapters/antigravity.js +3 -14
- package/lib/yoke/adapters/claude-query-contract.js +9 -0
- package/lib/yoke/adapters/claude.js +7 -42
- package/lib/yoke/adapters/codex-query-contract.js +24 -0
- package/lib/yoke/adapters/codex.js +1 -0
- package/lib/yoke/adapters/kiro.js +3 -1
- package/lib/yoke/adapters/muse-query-contract.js +7 -0
- package/lib/yoke/adapters/muse.js +122 -0
- package/lib/yoke/auth-events.js +17 -0
- package/lib/yoke/claude-hook-installer.js +267 -0
- package/lib/yoke/claude-jsonl-watcher.js +204 -0
- package/lib/yoke/claude-maintenance.js +55 -0
- package/lib/yoke/claude-permission-patterns.js +53 -0
- package/lib/yoke/claude-response-recovery.js +155 -0
- package/lib/yoke/claude-transcript-hygiene.js +34 -0
- package/lib/yoke/clay-skill-install.js +33 -0
- package/lib/yoke/cli-sessions.js +300 -0
- package/lib/yoke/codex-app-server.js +7 -12
- package/lib/yoke/configuration.js +13 -0
- package/lib/yoke/discovery.js +70 -0
- package/lib/yoke/errors.js +8 -0
- package/lib/yoke/host-environment.js +7 -0
- package/lib/yoke/index.js +9 -51
- package/lib/yoke/interface.js +116 -0
- package/lib/yoke/kiro-acp-server.js +5 -5
- package/lib/yoke/legacy-sessions.js +89 -0
- package/lib/yoke/login-protocol.js +25 -0
- package/lib/yoke/maintenance.js +12 -0
- package/lib/yoke/mcp-proxy.js +32 -0
- package/lib/yoke/mcp-source-adapters.js +91 -0
- package/lib/yoke/mcp-source-context.js +42 -0
- package/lib/yoke/mcp-source-discovery.js +169 -0
- package/lib/yoke/mcp-source-plugins.js +68 -0
- package/lib/yoke/mcp-source-restrictions.js +47 -0
- package/lib/yoke/mcp-tool-policy-normalize.js +28 -0
- package/lib/yoke/model-preferences.js +6 -0
- package/lib/yoke/muse-artifacts.js +36 -0
- package/lib/yoke/muse-controls.js +50 -0
- package/lib/yoke/muse-input.js +64 -0
- package/lib/yoke/muse-items.js +157 -0
- package/lib/yoke/muse-policy.js +52 -0
- package/lib/yoke/muse-query.js +428 -0
- package/lib/yoke/muse-runtime.js +211 -0
- package/lib/yoke/muse-services.js +79 -0
- package/lib/yoke/muse-skills.js +79 -0
- package/lib/yoke/muse-turns.js +71 -0
- package/lib/yoke/options/acp.js +2 -0
- package/lib/yoke/options/antigravity.js +2 -0
- package/lib/yoke/options/claude.js +21 -0
- package/lib/yoke/options/codex-defaults.js +29 -0
- package/lib/yoke/options/codex-settings.js +21 -0
- package/lib/yoke/options/codex.js +6 -0
- package/lib/yoke/options/kiro-defaults.js +22 -0
- package/lib/yoke/options/kiro.js +5 -0
- package/lib/yoke/options/muse.js +5 -0
- package/lib/yoke/provider-contract.js +5 -0
- package/lib/yoke/providers/antigravity-executable.js +8 -0
- package/lib/yoke/providers/claude-executable.js +40 -0
- package/lib/yoke/providers/claude-model-descriptions.js +14 -0
- package/lib/yoke/providers/claude-process.js +97 -0
- package/lib/yoke/providers/claude-provision.js +75 -0
- package/lib/yoke/providers/codex-errors.js +8 -0
- package/lib/yoke/providers/muse-executable.js +17 -0
- package/lib/yoke/provisioning.js +6 -0
- package/lib/yoke/query-options.js +10 -0
- package/lib/yoke/response-recovery.js +41 -0
- package/lib/yoke/sdk-mcp-descriptors.js +37 -0
- package/lib/yoke/sdk-server-factory.js +7 -0
- package/lib/yoke/session-history.js +8 -0
- package/lib/yoke/session-tool-transport.js +31 -13
- package/lib/yoke/settings.js +23 -0
- package/lib/yoke/source-locations.js +4 -0
- package/lib/yoke/title-query.js +18 -7
- package/lib/yoke/tui-transcript-index.js +132 -0
- package/lib/yoke/usage.js +48 -0
- package/lib/yoke/user-input.js +13 -0
- package/lib/yoke/vendor-registry.js +81 -1
- package/package.json +3 -2
package/lib/session-hygiene.js
CHANGED
|
@@ -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
|
|
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:
|
|
14
|
-
mcpOverride:
|
|
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
|
|
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 =
|
|
43
|
-
var codexSandbox =
|
|
44
|
-
var codexWebSearch =
|
|
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 ||
|
|
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
|
-
|
|
485
|
-
|
|
486
|
-
var
|
|
487
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
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 ||
|
|
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: {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
},
|