clay-server 2.47.0-beta.6 → 3.0.0-beta.2
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/daemon.js +6 -27
- package/lib/project-connection.js +21 -8
- package/lib/project-session-pair.js +312 -0
- package/lib/project-session-spawn.js +8 -4
- package/lib/project-sessions.js +108 -10
- package/lib/project-user-message.js +18 -8
- package/lib/project.js +33 -0
- package/lib/public/app.js +20 -0
- package/lib/public/css/menus.css +83 -1
- package/lib/public/css/messages.css +14 -0
- package/lib/public/css/pane.css +420 -0
- package/lib/public/css/sidebar.css +40 -0
- package/lib/public/css/sticky-notes.css +2 -0
- package/lib/public/index.html +4 -0
- package/lib/public/modules/app-connection.js +6 -0
- package/lib/public/modules/app-favicon.js +5 -0
- package/lib/public/modules/app-header.js +101 -6
- package/lib/public/modules/app-messages.js +59 -6
- package/lib/public/modules/app-panels.js +49 -27
- package/lib/public/modules/app-rendering.js +11 -4
- package/lib/public/modules/pane-bridge.js +41 -0
- package/lib/public/modules/pane-session.js +18 -0
- package/lib/public/modules/sidebar-sessions.js +175 -1
- package/lib/public/modules/sidebar.js +2 -0
- package/lib/public/modules/split-group-helpers.js +18 -0
- package/lib/public/modules/split-pair-ui.js +324 -0
- package/lib/public/modules/split-view.js +499 -0
- package/lib/public/modules/sticky-notes.js +32 -91
- package/lib/public/style.css +1 -0
- package/lib/sdk-bridge.js +111 -20
- package/lib/sdk-message-processor.js +2 -2
- package/lib/server.js +24 -4
- package/lib/session-hygiene.js +100 -0
- package/lib/session-pair-mcp-server.js +28 -0
- package/lib/session-split-groups.js +298 -0
- package/lib/sessions.js +163 -34
- package/lib/ws-request.js +14 -0
- package/lib/ws-schema.js +13 -0
- package/lib/yoke/adapters/claude.js +32 -0
- package/lib/yoke/adapters/codex.js +100 -77
- package/lib/yoke/adapters/kiro.js +34 -35
- package/lib/yoke/codex-app-server.js +56 -6
- package/lib/yoke/index.js +1 -0
- package/lib/yoke/skill-discovery.js +196 -0
- package/lib/yoke/vendor-registry.js +34 -0
- package/package.json +2 -2
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
var url = require("url");
|
|
2
|
+
|
|
3
|
+
function parseWsRequestUrl(requestUrl) {
|
|
4
|
+
var parsed = url.parse(requestUrl || "/", true);
|
|
5
|
+
var paneSession = Number(parsed.query.session);
|
|
6
|
+
if (!Number.isSafeInteger(paneSession) || paneSession < 1) paneSession = null;
|
|
7
|
+
return {
|
|
8
|
+
path: parsed.pathname || "/",
|
|
9
|
+
pane: parsed.query.pane === "1",
|
|
10
|
+
paneSession: paneSession,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = { parseWsRequestUrl: parseWsRequestUrl };
|
package/lib/ws-schema.js
CHANGED
|
@@ -32,6 +32,12 @@ var schema = {
|
|
|
32
32
|
"fork_session": { direction: "c2s", handler: "lib/project-sessions.js", description: "Fork a session from a given message UUID" },
|
|
33
33
|
"input_sync": { direction: "c2s", handler: "lib/project-sessions.js", description: "Sync the current input field text to other clients" },
|
|
34
34
|
"tui_transcript_request": { direction: "c2s", handler: "lib/project-sessions.js", description: "Ask for the assistant text index of a Claude TUI session (for hover-to-grab)" },
|
|
35
|
+
"split_group_create": { direction: "c2s", handler: "lib/session-split-groups.js", description: "Create a persistent two-session split group" },
|
|
36
|
+
"split_group_rename": { direction: "c2s", handler: "lib/session-split-groups.js", description: "Rename a split group" },
|
|
37
|
+
"split_group_dissolve": { direction: "c2s", handler: "lib/session-split-groups.js", description: "Separate a split group without deleting its sessions" },
|
|
38
|
+
"split_group_set_pair": { direction: "c2s", handler: "lib/session-split-groups.js", description: "Assign Driver/Worker roles on a split group (driverId null clears them)" },
|
|
39
|
+
"pair_session_options": { direction: "c2s", handler: "lib/project-session-pair.js", description: "Request vendors/models for the pair-session dialog (response reuses the same type)" },
|
|
40
|
+
"pair_session_create": { direction: "c2s", handler: "lib/project-session-pair.js", description: "Create a Driver/Worker session pair as a split group" },
|
|
35
41
|
|
|
36
42
|
"session_list": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Full list of sessions for the sidebar" },
|
|
37
43
|
"session_switched": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Confirmation that active session changed" },
|
|
@@ -44,6 +50,10 @@ var schema = {
|
|
|
44
50
|
"fork_complete": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Fork succeeded, includes new session ID" },
|
|
45
51
|
"input_sync_broadcast": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Broadcast input text from another client" },
|
|
46
52
|
"tui_transcript_state": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Assistant text index for a Claude TUI session (full replace; sent on request and after each new assistant message)" },
|
|
53
|
+
"split_groups": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Full persistent split-group list for the current user" },
|
|
54
|
+
"split_group_result": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Result of a split-group mutation" },
|
|
55
|
+
"pair_session_created": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Result of pair_session_create; carries the new group" },
|
|
56
|
+
"split_delegation": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "A pair delegation started or ended (drives the split-view flow indicator)" },
|
|
47
57
|
|
|
48
58
|
// -----------------------------------------------------------------------
|
|
49
59
|
// History replay
|
|
@@ -164,6 +174,9 @@ var schema = {
|
|
|
164
174
|
"set_server_default_model": { direction: "c2s", handler: "lib/project-sessions.js", description: "Set the server-wide default model" },
|
|
165
175
|
"set_project_default_model": { direction: "c2s", handler: "lib/project-sessions.js", description: "Set the project default model" },
|
|
166
176
|
"set_permission_mode": { direction: "c2s", handler: "lib/project-sessions.js", description: "Set permission mode for the current session" },
|
|
177
|
+
"set_session_full_access": { direction: "c2s", handler: "lib/project-sessions.js", description: "Toggle Clay-managed permission skipping for a GUI session" },
|
|
178
|
+
"session_full_access_changed": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "A session's full-access state changed" },
|
|
179
|
+
"session_full_access_result": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Result of toggling session full access" },
|
|
167
180
|
"reload_skills": { direction: "c2s", handler: "lib/project-sessions.js", description: "Hot-reload skills/MCP/agents on the active query without a restart" },
|
|
168
181
|
"set_mcp_permission_mode_override": { direction: "c2s", handler: "lib/project-sessions.js", description: "Override permission mode (default/auto/null) for one MCP server" },
|
|
169
182
|
"set_server_default_mode": { direction: "c2s", handler: "lib/project-sessions.js", description: "Set the server-wide default permission mode" },
|
|
@@ -11,6 +11,7 @@ var net = require("net");
|
|
|
11
11
|
var crypto = require("crypto");
|
|
12
12
|
var { spawn } = require("child_process");
|
|
13
13
|
var { resolveOsUserInfo } = require("../../os-users");
|
|
14
|
+
var skillDiscovery = require("../skill-discovery");
|
|
14
15
|
|
|
15
16
|
// SDK-specific knowledge stays in the adapter (not the relay): the host dialog
|
|
16
17
|
// kinds this client can render, declared to the SDK when an onUserDialog
|
|
@@ -18,6 +19,13 @@ var { resolveOsUserInfo } = require("../../os-users");
|
|
|
18
19
|
// emitted. Keep in sync with the client's generic user-dialog renderer.
|
|
19
20
|
var SUPPORTED_DIALOG_KINDS = ["refusal_fallback_prompt"];
|
|
20
21
|
|
|
22
|
+
function getSharedSkillPlugins(homeDir, existingPlugins) {
|
|
23
|
+
var plugins = Array.isArray(existingPlugins) ? existingPlugins.slice() : [];
|
|
24
|
+
var bridgePath = skillDiscovery.ensureClaudeCodexPlugin(homeDir ? { homeDir: homeDir } : null);
|
|
25
|
+
if (bridgePath) plugins.push({ type: "local", path: bridgePath, skipMcpDiscovery: true });
|
|
26
|
+
return plugins;
|
|
27
|
+
}
|
|
28
|
+
|
|
21
29
|
// --- SDK loading ---
|
|
22
30
|
// Async loader (ESM dynamic import, same pattern as current project.js getSDK)
|
|
23
31
|
var _sdkPromise = null;
|
|
@@ -1166,6 +1174,7 @@ function createClaudeAdapter(opts) {
|
|
|
1166
1174
|
abortController: ac,
|
|
1167
1175
|
settings: { disableAllHooks: true },
|
|
1168
1176
|
};
|
|
1177
|
+
warmupOptions.plugins = getSharedSkillPlugins(initOpts && initOpts.originalHome);
|
|
1169
1178
|
if (_claudeBinaryPath) warmupOptions.pathToClaudeCodeExecutable = _claudeBinaryPath;
|
|
1170
1179
|
|
|
1171
1180
|
if (initOpts && initOpts.dangerouslySkipPermissions) {
|
|
@@ -1181,6 +1190,7 @@ function createClaudeAdapter(opts) {
|
|
|
1181
1190
|
fastModeState: null,
|
|
1182
1191
|
capabilities: {
|
|
1183
1192
|
effort: true,
|
|
1193
|
+
midSessionModelSwitch: true,
|
|
1184
1194
|
fork: true,
|
|
1185
1195
|
rollback: false,
|
|
1186
1196
|
sessionListing: true,
|
|
@@ -1193,6 +1203,7 @@ function createClaudeAdapter(opts) {
|
|
|
1193
1203
|
elicitation: true,
|
|
1194
1204
|
fileCheckpointing: true,
|
|
1195
1205
|
contextCompacting: true,
|
|
1206
|
+
skillSharing: true,
|
|
1196
1207
|
toolPolicy: ["ask", "allow-all"],
|
|
1197
1208
|
},
|
|
1198
1209
|
};
|
|
@@ -1328,6 +1339,12 @@ function createClaudeAdapter(opts) {
|
|
|
1328
1339
|
|
|
1329
1340
|
// YOKE standard options -> SDK options
|
|
1330
1341
|
if (queryOpts.systemPrompt) sdkOptions.systemPrompt = queryOpts.systemPrompt;
|
|
1342
|
+
// Additive variant: keep the full Claude Code system prompt and append.
|
|
1343
|
+
// A plain-string systemPrompt REPLACES the whole prompt, so pair
|
|
1344
|
+
// presets and other add-ons must come through here instead.
|
|
1345
|
+
if (!queryOpts.systemPrompt && queryOpts.appendSystemPrompt) {
|
|
1346
|
+
sdkOptions.systemPrompt = { type: "preset", preset: "claude_code", append: queryOpts.appendSystemPrompt };
|
|
1347
|
+
}
|
|
1331
1348
|
if (queryOpts.model) sdkOptions.model = queryOpts.model;
|
|
1332
1349
|
if (queryOpts.effort) sdkOptions.effort = queryOpts.effort;
|
|
1333
1350
|
if (queryOpts.toolServers) sdkOptions.mcpServers = queryOpts.toolServers;
|
|
@@ -1358,6 +1375,7 @@ function createClaudeAdapter(opts) {
|
|
|
1358
1375
|
if (co.allowDangerouslySkipPermissions) sdkOptions.allowDangerouslySkipPermissions = true;
|
|
1359
1376
|
if (co.resumeSessionAt) sdkOptions.resumeSessionAt = co.resumeSessionAt;
|
|
1360
1377
|
if (co.settings) sdkOptions.settings = co.settings;
|
|
1378
|
+
sdkOptions.plugins = getSharedSkillPlugins(co.originalHome, co.plugins);
|
|
1361
1379
|
|
|
1362
1380
|
var rawQuery = sdk.query({ prompt: mq, options: sdkOptions });
|
|
1363
1381
|
return createQueryHandle(rawQuery, mq, ac);
|
|
@@ -1510,6 +1528,12 @@ function createClaudeAdapter(opts) {
|
|
|
1510
1528
|
if (claudeOpts.permissionMode) queryOptions.permissionMode = claudeOpts.permissionMode;
|
|
1511
1529
|
if (claudeOpts.allowDangerouslySkipPermissions) queryOptions.allowDangerouslySkipPermissions = true;
|
|
1512
1530
|
if (claudeOpts.settings) queryOptions.settings = claudeOpts.settings;
|
|
1531
|
+
// Same additive mapping as the in-process path: never pass a plain
|
|
1532
|
+
// string (it would replace the whole Claude Code system prompt).
|
|
1533
|
+
if (queryOpts.appendSystemPrompt) {
|
|
1534
|
+
queryOptions.systemPrompt = { type: "preset", preset: "claude_code", append: queryOpts.appendSystemPrompt };
|
|
1535
|
+
}
|
|
1536
|
+
queryOptions.plugins = getSharedSkillPlugins(claudeOpts.originalHome, claudeOpts.plugins);
|
|
1513
1537
|
|
|
1514
1538
|
if (queryOpts.toolServerDescriptors) queryOptions.mcpServerDescriptors = queryOpts.toolServerDescriptors;
|
|
1515
1539
|
if (queryOpts.onUserDialog) queryOptions.supportedDialogKinds = SUPPORTED_DIALOG_KINDS;
|
|
@@ -1562,6 +1586,7 @@ function createClaudeAdapter(opts) {
|
|
|
1562
1586
|
fastModeState: r.fastModeState || null,
|
|
1563
1587
|
capabilities: {
|
|
1564
1588
|
effort: true,
|
|
1589
|
+
midSessionModelSwitch: true,
|
|
1565
1590
|
fork: true,
|
|
1566
1591
|
rollback: false,
|
|
1567
1592
|
sessionListing: true,
|
|
@@ -1574,6 +1599,7 @@ function createClaudeAdapter(opts) {
|
|
|
1574
1599
|
elicitation: true,
|
|
1575
1600
|
fileCheckpointing: true,
|
|
1576
1601
|
contextCompacting: true,
|
|
1602
|
+
skillSharing: true,
|
|
1577
1603
|
toolPolicy: ["ask", "allow-all"],
|
|
1578
1604
|
},
|
|
1579
1605
|
});
|
|
@@ -1624,6 +1650,7 @@ function createClaudeAdapter(opts) {
|
|
|
1624
1650
|
abortController: ac,
|
|
1625
1651
|
settings: { disableAllHooks: true },
|
|
1626
1652
|
};
|
|
1653
|
+
warmupOptions.plugins = getSharedSkillPlugins(initOpts && initOpts.originalHome);
|
|
1627
1654
|
if (_claudeBinaryPath) warmupOptions.pathToClaudeCodeExecutable = _claudeBinaryPath;
|
|
1628
1655
|
|
|
1629
1656
|
if (initOpts && initOpts.dangerouslySkipPermissions) {
|
|
@@ -1639,6 +1666,7 @@ function createClaudeAdapter(opts) {
|
|
|
1639
1666
|
fastModeState: null,
|
|
1640
1667
|
capabilities: {
|
|
1641
1668
|
effort: true,
|
|
1669
|
+
midSessionModelSwitch: true,
|
|
1642
1670
|
fork: true,
|
|
1643
1671
|
rollback: false,
|
|
1644
1672
|
sessionListing: true,
|
|
@@ -1651,6 +1679,7 @@ function createClaudeAdapter(opts) {
|
|
|
1651
1679
|
elicitation: true,
|
|
1652
1680
|
fileCheckpointing: true,
|
|
1653
1681
|
contextCompacting: true,
|
|
1682
|
+
skillSharing: true,
|
|
1654
1683
|
toolPolicy: ["ask", "allow-all"],
|
|
1655
1684
|
},
|
|
1656
1685
|
};
|
|
@@ -1703,6 +1732,7 @@ function createClaudeAdapter(opts) {
|
|
|
1703
1732
|
}
|
|
1704
1733
|
|
|
1705
1734
|
var warmupOptions = { cwd: workerCwd, settingSources: ["user", "project", "local"], settings: { disableAllHooks: true } };
|
|
1735
|
+
warmupOptions.plugins = getSharedSkillPlugins(initOpts && initOpts.originalHome);
|
|
1706
1736
|
if (_claudeBinaryPath) warmupOptions.pathToClaudeCodeExecutable = _claudeBinaryPath;
|
|
1707
1737
|
if (initOpts && initOpts.dangerouslySkipPermissions) {
|
|
1708
1738
|
warmupOptions.permissionMode = "bypassPermissions";
|
|
@@ -1724,6 +1754,7 @@ function createClaudeAdapter(opts) {
|
|
|
1724
1754
|
fastModeState: r.fastModeState || null,
|
|
1725
1755
|
capabilities: {
|
|
1726
1756
|
effort: true,
|
|
1757
|
+
midSessionModelSwitch: true,
|
|
1727
1758
|
fork: true,
|
|
1728
1759
|
rollback: false,
|
|
1729
1760
|
sessionListing: true,
|
|
@@ -1736,6 +1767,7 @@ function createClaudeAdapter(opts) {
|
|
|
1736
1767
|
elicitation: true,
|
|
1737
1768
|
fileCheckpointing: true,
|
|
1738
1769
|
contextCompacting: true,
|
|
1770
|
+
skillSharing: true,
|
|
1739
1771
|
toolPolicy: ["ask", "allow-all"],
|
|
1740
1772
|
},
|
|
1741
1773
|
});
|
|
@@ -6,55 +6,7 @@
|
|
|
6
6
|
var path = require("path");
|
|
7
7
|
var fs = require("fs");
|
|
8
8
|
var { CodexAppServer } = require("../codex-app-server");
|
|
9
|
-
|
|
10
|
-
// --- Claude skill discovery ---
|
|
11
|
-
// Finds Claude skills in ~/.claude/skills/ and <cwd>/.claude/skills/
|
|
12
|
-
// so Codex can recognize $<skill-name> in user input.
|
|
13
|
-
function discoverClaudeSkills(cwd) {
|
|
14
|
-
var skills = {};
|
|
15
|
-
var REAL_HOME;
|
|
16
|
-
try { REAL_HOME = require("../../config").REAL_HOME; } catch (e) { REAL_HOME = require("os").homedir(); }
|
|
17
|
-
var dirs = [
|
|
18
|
-
path.join(REAL_HOME, ".claude", "skills"),
|
|
19
|
-
path.join(cwd || "", ".claude", "skills"),
|
|
20
|
-
];
|
|
21
|
-
for (var d = 0; d < dirs.length; d++) {
|
|
22
|
-
var base = dirs[d];
|
|
23
|
-
if (!base) continue;
|
|
24
|
-
var entries;
|
|
25
|
-
try { entries = fs.readdirSync(base, { withFileTypes: true }); } catch (e) { continue; }
|
|
26
|
-
for (var i = 0; i < entries.length; i++) {
|
|
27
|
-
var entry = entries[i];
|
|
28
|
-
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
29
|
-
var skillMd = path.join(base, entry.name, "SKILL.md");
|
|
30
|
-
try {
|
|
31
|
-
fs.accessSync(skillMd, fs.constants.R_OK);
|
|
32
|
-
// project skills override global skills
|
|
33
|
-
skills[entry.name] = skillMd;
|
|
34
|
-
} catch (e) {}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return skills;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Parse user text for $<skill-name> references.
|
|
41
|
-
// Returns { text, skills: [{ name, path }] }
|
|
42
|
-
function parseSkillRefs(text, availableSkills) {
|
|
43
|
-
if (typeof text !== "string") return { text: text, skills: [] };
|
|
44
|
-
var skills = [];
|
|
45
|
-
var seen = {};
|
|
46
|
-
var re = /\$([a-zA-Z0-9_-]+)/g;
|
|
47
|
-
var match;
|
|
48
|
-
while ((match = re.exec(text)) !== null) {
|
|
49
|
-
var name = match[1];
|
|
50
|
-
if (seen[name]) continue;
|
|
51
|
-
if (availableSkills[name]) {
|
|
52
|
-
seen[name] = true;
|
|
53
|
-
skills.push({ name: name, path: availableSkills[name] });
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return { text: text, skills: skills };
|
|
57
|
-
}
|
|
9
|
+
var skillDiscovery = require("../skill-discovery");
|
|
58
10
|
|
|
59
11
|
// --- Event flattening ---
|
|
60
12
|
// Converts app-server JSON-RPC notifications into flat objects with a yokeType field.
|
|
@@ -193,11 +145,14 @@ function flattenEvent(notification, state) {
|
|
|
193
145
|
events.push({ yokeType: "interrupted" });
|
|
194
146
|
}
|
|
195
147
|
var inputTokens = state.lastInputTokens || (usage ? (usage.input_tokens || 0) + (usage.cached_input_tokens || 0) : 0);
|
|
196
|
-
var outputTokens = usage ? (usage.output_tokens || 0) : 0;
|
|
197
|
-
var cachedTokens = usage ? (usage.cached_input_tokens || 0) : 0;
|
|
148
|
+
var outputTokens = (usage ? (usage.output_tokens || 0) : 0) || state.lastOutputTokens || 0;
|
|
149
|
+
var cachedTokens = (usage ? (usage.cached_input_tokens || 0) : 0) || state.lastCachedTokens || 0;
|
|
198
150
|
var hasTokenData = inputTokens > 0 || outputTokens > 0;
|
|
199
151
|
var resultModelUsage = {};
|
|
200
|
-
|
|
152
|
+
// Codex reports the real window in thread/tokenUsage/updated. Dropping it
|
|
153
|
+
// made the client fall back to its model-name table, which has no entry
|
|
154
|
+
// for codex model ids and defaulted to Claude's 200k denominator.
|
|
155
|
+
resultModelUsage[state.model] = { contextWindow: state.modelContextWindow || null };
|
|
201
156
|
var assistantUuid = generateUuid();
|
|
202
157
|
events.push({
|
|
203
158
|
yokeType: "result",
|
|
@@ -216,6 +171,8 @@ function flattenEvent(notification, state) {
|
|
|
216
171
|
lastStreamInputTokens: state.lastInputTokens || null,
|
|
217
172
|
});
|
|
218
173
|
state.lastInputTokens = null;
|
|
174
|
+
state.lastCachedTokens = null;
|
|
175
|
+
state.lastOutputTokens = null;
|
|
219
176
|
return events;
|
|
220
177
|
}
|
|
221
178
|
|
|
@@ -300,6 +257,41 @@ function flattenEvent(notification, state) {
|
|
|
300
257
|
return events;
|
|
301
258
|
}
|
|
302
259
|
|
|
260
|
+
// Reasoning deltas: streamed summarized thinking. The reasoning ITEM often
|
|
261
|
+
// carries only encrypted content with no readable text; the human-readable
|
|
262
|
+
// summary arrives through these delta notifications. Without handling them
|
|
263
|
+
// Codex sessions show no thinking at all.
|
|
264
|
+
if (method === "item/reasoning/summaryTextDelta" || method === "item/reasoning/textDelta") {
|
|
265
|
+
var rDeltaItemId = params.itemId || params.id;
|
|
266
|
+
if (rDeltaItemId && !state.thinkingBlocks[rDeltaItemId]) {
|
|
267
|
+
state.blockCounter++;
|
|
268
|
+
state.thinkingBlocks[rDeltaItemId] = "blk_" + state.blockCounter;
|
|
269
|
+
events.push({ yokeType: "thinking_start", blockId: state.thinkingBlocks[rDeltaItemId] });
|
|
270
|
+
}
|
|
271
|
+
if (params.delta) {
|
|
272
|
+
events.push({
|
|
273
|
+
yokeType: "thinking_delta",
|
|
274
|
+
blockId: rDeltaItemId ? state.thinkingBlocks[rDeltaItemId] : ("blk_" + state.blockCounter),
|
|
275
|
+
text: params.delta,
|
|
276
|
+
});
|
|
277
|
+
// Track streamed length so item/completed doesn't re-send the full text
|
|
278
|
+
if (rDeltaItemId) {
|
|
279
|
+
state.thinkingLengths[rDeltaItemId] = (state.thinkingLengths[rDeltaItemId] || 0) + params.delta.length;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return events;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Separator between reasoning summary sections
|
|
286
|
+
if (method === "item/reasoning/summaryPartAdded") {
|
|
287
|
+
var rPartItemId = params.itemId || params.id;
|
|
288
|
+
if (rPartItemId && state.thinkingBlocks[rPartItemId]) {
|
|
289
|
+
events.push({ yokeType: "thinking_delta", blockId: state.thinkingBlocks[rPartItemId], text: "\n\n" });
|
|
290
|
+
state.thinkingLengths[rPartItemId] = (state.thinkingLengths[rPartItemId] || 0) + 2;
|
|
291
|
+
}
|
|
292
|
+
return events;
|
|
293
|
+
}
|
|
294
|
+
|
|
303
295
|
if (method === "item/plan/delta") {
|
|
304
296
|
var planDeltaItemId = params.itemId || params.id;
|
|
305
297
|
var nextPlanText = (state.planTexts[planDeltaItemId] || "") + (params.delta || "");
|
|
@@ -553,11 +545,23 @@ function flattenEvent(notification, state) {
|
|
|
553
545
|
}
|
|
554
546
|
}
|
|
555
547
|
|
|
556
|
-
// Token usage update - track input tokens for context bar
|
|
548
|
+
// Token usage update - track input tokens for context bar.
|
|
549
|
+
//
|
|
550
|
+
// ThreadTokenUsage carries { lastTurn, total, modelContextWindow }. The
|
|
551
|
+
// context gauge must use the LAST turn, not the running total: each turn's
|
|
552
|
+
// input already contains the whole conversation, so summing turns
|
|
553
|
+
// overstates occupancy (a 5k + 8k thread reads as 13k of an 8k context).
|
|
554
|
+
// `total` is only a fallback for servers that omit lastTurn.
|
|
557
555
|
if (method === "thread/tokenUsage/updated") {
|
|
558
556
|
var tu = params.tokenUsage;
|
|
559
|
-
if (tu
|
|
560
|
-
|
|
557
|
+
if (tu) {
|
|
558
|
+
var turnUsage = tu.lastTurn || tu.total;
|
|
559
|
+
if (turnUsage) {
|
|
560
|
+
state.lastInputTokens = (turnUsage.inputTokens || 0) + (turnUsage.cachedInputTokens || 0);
|
|
561
|
+
state.lastCachedTokens = turnUsage.cachedInputTokens || 0;
|
|
562
|
+
state.lastOutputTokens = turnUsage.outputTokens || 0;
|
|
563
|
+
}
|
|
564
|
+
if (tu.modelContextWindow) state.modelContextWindow = tu.modelContextWindow;
|
|
561
565
|
}
|
|
562
566
|
return events;
|
|
563
567
|
}
|
|
@@ -604,6 +608,9 @@ function createEventState(model) {
|
|
|
604
608
|
turnStarted: false,
|
|
605
609
|
lastUsage: null,
|
|
606
610
|
lastInputTokens: null,
|
|
611
|
+
lastCachedTokens: null,
|
|
612
|
+
lastOutputTokens: null,
|
|
613
|
+
modelContextWindow: null,
|
|
607
614
|
done: false,
|
|
608
615
|
aborted: false,
|
|
609
616
|
loopStarted: false,
|
|
@@ -622,7 +629,7 @@ function createEventState(model) {
|
|
|
622
629
|
|
|
623
630
|
function createCodexQueryHandle(appServer, queryOpts) {
|
|
624
631
|
var abortController = queryOpts.abortController;
|
|
625
|
-
var systemPrompt = queryOpts.systemPrompt || "";
|
|
632
|
+
var systemPrompt = queryOpts.systemPrompt || queryOpts.appendSystemPrompt || "";
|
|
626
633
|
var canUseTool = queryOpts.canUseTool || null;
|
|
627
634
|
var onElicitation = queryOpts.onElicitation || null;
|
|
628
635
|
var onFinished = queryOpts.onFinished || null;
|
|
@@ -677,6 +684,7 @@ function createCodexQueryHandle(appServer, queryOpts) {
|
|
|
677
684
|
var messageQueue = [];
|
|
678
685
|
var messageWaiting = null;
|
|
679
686
|
var messageQueueEnded = false;
|
|
687
|
+
var handlerEntry = null;
|
|
680
688
|
|
|
681
689
|
function pushMessageToQueue(msg) {
|
|
682
690
|
if (messageQueueEnded) return;
|
|
@@ -863,8 +871,10 @@ function createCodexQueryHandle(appServer, queryOpts) {
|
|
|
863
871
|
}
|
|
864
872
|
|
|
865
873
|
try {
|
|
866
|
-
|
|
867
|
-
|
|
874
|
+
handlerEntry = appServer.addHandler(handleServerEvent);
|
|
875
|
+
if (queryOpts.resumeSessionId) {
|
|
876
|
+
handlerEntry.threadId = queryOpts.resumeSessionId;
|
|
877
|
+
}
|
|
868
878
|
|
|
869
879
|
// Start or resume thread
|
|
870
880
|
var threadParams = {
|
|
@@ -896,6 +906,7 @@ function createCodexQueryHandle(appServer, queryOpts) {
|
|
|
896
906
|
|
|
897
907
|
if (threadResult && threadResult.thread) {
|
|
898
908
|
state.threadId = threadResult.thread.id;
|
|
909
|
+
handlerEntry.threadId = state.threadId;
|
|
899
910
|
}
|
|
900
911
|
|
|
901
912
|
while (!isCancelled()) {
|
|
@@ -919,23 +930,24 @@ function createCodexQueryHandle(appServer, queryOpts) {
|
|
|
919
930
|
input = currentMessage;
|
|
920
931
|
}
|
|
921
932
|
|
|
922
|
-
//
|
|
923
|
-
|
|
933
|
+
// Explicit references use Codex's native skill input item regardless of
|
|
934
|
+
// which vendor owns the SKILL.md file.
|
|
935
|
+
var availableSkills = skillDiscovery.discoverSkills(queryOpts.cwd);
|
|
924
936
|
var skillItemsToInject = [];
|
|
925
937
|
var injected = {};
|
|
926
938
|
for (var ii = 0; ii < input.length; ii++) {
|
|
927
939
|
if (input[ii].type === "text" && input[ii].text) {
|
|
928
|
-
var
|
|
929
|
-
for (var si = 0; si <
|
|
930
|
-
if (!injected[
|
|
931
|
-
injected[
|
|
932
|
-
skillItemsToInject.push({ type: "skill", name:
|
|
940
|
+
var referencedSkills = skillDiscovery.findSkillReferences(input[ii].text, availableSkills);
|
|
941
|
+
for (var si = 0; si < referencedSkills.length; si++) {
|
|
942
|
+
if (!injected[referencedSkills[si].name]) {
|
|
943
|
+
injected[referencedSkills[si].name] = true;
|
|
944
|
+
skillItemsToInject.push({ type: "skill", name: referencedSkills[si].name, path: referencedSkills[si].path });
|
|
933
945
|
}
|
|
934
946
|
}
|
|
935
947
|
}
|
|
936
948
|
}
|
|
937
949
|
if (skillItemsToInject.length > 0) {
|
|
938
|
-
console.log("[yoke/codex] injecting
|
|
950
|
+
console.log("[yoke/codex] injecting shared skills:", skillItemsToInject.map(function(s) { return s.name; }).join(", "));
|
|
939
951
|
input = input.concat(skillItemsToInject);
|
|
940
952
|
}
|
|
941
953
|
|
|
@@ -964,6 +976,11 @@ function createCodexQueryHandle(appServer, queryOpts) {
|
|
|
964
976
|
? { yokeType: "auth_required", vendor: "codex" }
|
|
965
977
|
: { yokeType: "error", text: loopErrMsg });
|
|
966
978
|
}
|
|
979
|
+
} finally {
|
|
980
|
+
if (handlerEntry) {
|
|
981
|
+
appServer.removeHandler(handlerEntry);
|
|
982
|
+
handlerEntry = null;
|
|
983
|
+
}
|
|
967
984
|
}
|
|
968
985
|
|
|
969
986
|
state.done = true;
|
|
@@ -1143,6 +1160,7 @@ function createCodexAdapter(opts) {
|
|
|
1143
1160
|
fastModeState: null,
|
|
1144
1161
|
capabilities: {
|
|
1145
1162
|
effort: true,
|
|
1163
|
+
midSessionModelSwitch: false,
|
|
1146
1164
|
fork: true,
|
|
1147
1165
|
rollback: true,
|
|
1148
1166
|
sessionListing: false,
|
|
@@ -1155,6 +1173,7 @@ function createCodexAdapter(opts) {
|
|
|
1155
1173
|
elicitation: true,
|
|
1156
1174
|
fileCheckpointing: false,
|
|
1157
1175
|
contextCompacting: false,
|
|
1176
|
+
skillSharing: true,
|
|
1158
1177
|
toolPolicy: ["ask", "allow-all"],
|
|
1159
1178
|
},
|
|
1160
1179
|
};
|
|
@@ -1358,17 +1377,21 @@ function createCodexAdapter(opts) {
|
|
|
1358
1377
|
|
|
1359
1378
|
_cachedModels = CODEX_MODELS.slice();
|
|
1360
1379
|
|
|
1361
|
-
//
|
|
1380
|
+
// Register foreign vendor roots with Codex so automatic skill
|
|
1381
|
+
// triggering and skills/list use the same merged inventory.
|
|
1362
1382
|
var skillNames = [];
|
|
1363
1383
|
try {
|
|
1364
|
-
var
|
|
1365
|
-
|
|
1366
|
-
var
|
|
1367
|
-
|
|
1384
|
+
var roots = skillDiscovery.getSkillRoots(_cwd);
|
|
1385
|
+
var extraRootPaths = [];
|
|
1386
|
+
for (var ri = 0; ri < roots.length; ri++) {
|
|
1387
|
+
if (roots[ri].source !== "codex-user" && path.isAbsolute(roots[ri].path)) extraRootPaths.push(roots[ri].path);
|
|
1388
|
+
}
|
|
1389
|
+
await _appServer.send("skills/extraRoots/set", { extraRoots: extraRootPaths }, 10000).catch(function(e) {
|
|
1390
|
+
console.error("[codex] skills/extraRoots/set failed:", e.message);
|
|
1391
|
+
});
|
|
1368
1392
|
var skillsResult = await _appServer.send("skills/list", {
|
|
1369
1393
|
cwds: _cwd ? [_cwd] : [],
|
|
1370
1394
|
forceReload: true,
|
|
1371
|
-
perCwdExtraUserRoots: extraRoots,
|
|
1372
1395
|
}, 10000).catch(function(e) {
|
|
1373
1396
|
console.error("[codex] skills/list failed:", e.message);
|
|
1374
1397
|
return null;
|
|
@@ -1385,11 +1408,11 @@ function createCodexAdapter(opts) {
|
|
|
1385
1408
|
}
|
|
1386
1409
|
}
|
|
1387
1410
|
}
|
|
1388
|
-
//
|
|
1389
|
-
|
|
1390
|
-
var
|
|
1391
|
-
for (var csn = 0; csn <
|
|
1392
|
-
if (skillNames.indexOf(
|
|
1411
|
+
// Direct discovery remains the fallback for older Codex app-server
|
|
1412
|
+
// versions and keeps the GUI inventory vendor-neutral.
|
|
1413
|
+
var sharedSkills = skillDiscovery.discoverSkills(_cwd);
|
|
1414
|
+
for (var csn = 0; csn < sharedSkills.length; csn++) {
|
|
1415
|
+
if (skillNames.indexOf(sharedSkills[csn].name) === -1) skillNames.push(sharedSkills[csn].name);
|
|
1393
1416
|
}
|
|
1394
1417
|
console.log("[codex] Discovered skills:", skillNames.length, "(" + skillNames.slice(0, 5).join(", ") + (skillNames.length > 5 ? "..." : "") + ")");
|
|
1395
1418
|
} catch (e) {
|
|
@@ -12,40 +12,33 @@
|
|
|
12
12
|
// session/request_permission requests for tool approvals.
|
|
13
13
|
// session/cancel (notif) -> interrupt the active turn
|
|
14
14
|
|
|
15
|
-
var path = require("path");
|
|
16
|
-
var fs = require("fs");
|
|
17
15
|
var { execFile } = require("child_process");
|
|
18
16
|
var { KiroAcpServer, findKiroPath } = require("../kiro-acp-server");
|
|
17
|
+
var skillDiscovery = require("../skill-discovery");
|
|
19
18
|
var { KIRO_DEFAULTS } = require("../../kiro-defaults");
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
var
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
var entry = entries[i];
|
|
40
|
-
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
41
|
-
var skillMd = path.join(base, entry.name, "SKILL.md");
|
|
42
|
-
try {
|
|
43
|
-
fs.accessSync(skillMd, fs.constants.R_OK);
|
|
44
|
-
skills[entry.name] = skillMd;
|
|
45
|
-
} catch (e) {}
|
|
20
|
+
function injectSharedSkillContent(message, skills) {
|
|
21
|
+
var text = "";
|
|
22
|
+
if (typeof message === "string") text = message;
|
|
23
|
+
else if (Array.isArray(message)) {
|
|
24
|
+
for (var i = 0; i < message.length; i++) {
|
|
25
|
+
if (message[i] && message[i].type === "text") text += "\n" + (message[i].text || "");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
var content = skillDiscovery.readReferencedSkills(text, skills, 32768);
|
|
29
|
+
if (!content) return message;
|
|
30
|
+
var appendix = "\n\nUse the following shared skill instructions for this turn:\n" + content;
|
|
31
|
+
if (typeof message === "string") return message + appendix;
|
|
32
|
+
if (!Array.isArray(message)) return message;
|
|
33
|
+
var cloned = message.slice();
|
|
34
|
+
for (var ci = 0; ci < cloned.length; ci++) {
|
|
35
|
+
if (cloned[ci] && cloned[ci].type === "text") {
|
|
36
|
+
cloned[ci] = Object.assign({}, cloned[ci], { text: (cloned[ci].text || "") + appendix });
|
|
37
|
+
return cloned;
|
|
46
38
|
}
|
|
47
39
|
}
|
|
48
|
-
|
|
40
|
+
cloned.unshift({ type: "text", text: appendix.trim() });
|
|
41
|
+
return cloned;
|
|
49
42
|
}
|
|
50
43
|
|
|
51
44
|
var _uuidCounter = 0;
|
|
@@ -404,7 +397,7 @@ function createEventState(opts) {
|
|
|
404
397
|
|
|
405
398
|
function createKiroQueryHandle(acp, queryOpts) {
|
|
406
399
|
var abortController = queryOpts.abortController;
|
|
407
|
-
var systemPrompt = queryOpts.systemPrompt || "";
|
|
400
|
+
var systemPrompt = queryOpts.systemPrompt || queryOpts.appendSystemPrompt || "";
|
|
408
401
|
var canUseTool = queryOpts.canUseTool || null;
|
|
409
402
|
var onFinished = queryOpts.onFinished || null;
|
|
410
403
|
|
|
@@ -686,9 +679,10 @@ function createKiroQueryHandle(acp, queryOpts) {
|
|
|
686
679
|
while (!isCancelled()) {
|
|
687
680
|
resetTurnState();
|
|
688
681
|
|
|
689
|
-
var
|
|
690
|
-
|
|
691
|
-
:
|
|
682
|
+
var messageWithSkills = injectSharedSkillContent(currentMessage, queryOpts.skills || []);
|
|
683
|
+
var input = typeof messageWithSkills === "string"
|
|
684
|
+
? [{ type: "text", text: messageWithSkills }]
|
|
685
|
+
: messageWithSkills;
|
|
692
686
|
|
|
693
687
|
// Emit turn_start so the UI records a user turn boundary.
|
|
694
688
|
pushEvent({ yokeType: "turn_start", uuid: generateUuid(), messageType: "user" });
|
|
@@ -869,6 +863,7 @@ function createKiroAdapter(opts) {
|
|
|
869
863
|
fastModeState: null,
|
|
870
864
|
capabilities: {
|
|
871
865
|
effort: false,
|
|
866
|
+
midSessionModelSwitch: false,
|
|
872
867
|
fork: false,
|
|
873
868
|
rollback: false,
|
|
874
869
|
sessionListing: false,
|
|
@@ -881,6 +876,7 @@ function createKiroAdapter(opts) {
|
|
|
881
876
|
elicitation: false,
|
|
882
877
|
fileCheckpointing: false,
|
|
883
878
|
contextCompacting: true,
|
|
879
|
+
skillSharing: true,
|
|
884
880
|
toolPolicy: ["ask"],
|
|
885
881
|
},
|
|
886
882
|
};
|
|
@@ -1011,8 +1007,7 @@ function createKiroAdapter(opts) {
|
|
|
1011
1007
|
_defaultModel = "auto";
|
|
1012
1008
|
}
|
|
1013
1009
|
|
|
1014
|
-
|
|
1015
|
-
var skillNames = Object.keys(discoverClaudeSkills(_cwd));
|
|
1010
|
+
var skillNames = skillDiscovery.discoverSkills(_cwd).map(function(skill) { return skill.name; });
|
|
1016
1011
|
console.log("[yoke/kiro] ACP initialized, models: " + _cachedModels.length + ", skills: " + skillNames.length);
|
|
1017
1012
|
|
|
1018
1013
|
updateLastActiveAt();
|
|
@@ -1065,6 +1060,8 @@ function createKiroAdapter(opts) {
|
|
|
1065
1060
|
var model = queryOpts.model || _defaultModel || "auto";
|
|
1066
1061
|
var ac = queryOpts.abortController || new AbortController();
|
|
1067
1062
|
var kiroOpts = (queryOpts.adapterOptions && queryOpts.adapterOptions.KIRO) || {};
|
|
1063
|
+
var sharedSkills = skillDiscovery.discoverSkills(queryOpts.cwd || _cwd);
|
|
1064
|
+
var skillIndex = skillDiscovery.buildSkillIndex(sharedSkills);
|
|
1068
1065
|
|
|
1069
1066
|
var activeEntry = { abort: function() { try { ac.abort(); } catch (e) {} } };
|
|
1070
1067
|
|
|
@@ -1074,7 +1071,8 @@ function createKiroAdapter(opts) {
|
|
|
1074
1071
|
contextWindow: _modelContextWindows[model] || null,
|
|
1075
1072
|
mode: kiroOpts.mode || null,
|
|
1076
1073
|
cwd: queryOpts.cwd || _cwd,
|
|
1077
|
-
systemPrompt: queryOpts.systemPrompt || "",
|
|
1074
|
+
systemPrompt: (queryOpts.systemPrompt || "") + (skillIndex ? "\n\n" + skillIndex : ""),
|
|
1075
|
+
skills: sharedSkills,
|
|
1078
1076
|
abortController: ac,
|
|
1079
1077
|
canUseTool: queryOpts.canUseTool || null,
|
|
1080
1078
|
resumeSessionId: queryOpts.resumeSessionId || null,
|
|
@@ -1165,6 +1163,7 @@ module.exports = {
|
|
|
1165
1163
|
contractTestKit: {
|
|
1166
1164
|
createEventState: createEventState,
|
|
1167
1165
|
createQueryHandle: createKiroQueryHandle,
|
|
1166
|
+
injectSharedSkillContent: injectSharedSkillContent,
|
|
1168
1167
|
normalizeEvent: flattenUpdate,
|
|
1169
1168
|
},
|
|
1170
1169
|
};
|