clay-server 3.4.0-beta.9 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/daemon.js +58 -58
- package/lib/project-connection.js +19 -6
- package/lib/project-models.js +220 -0
- package/lib/project-session-handoff.js +162 -0
- package/lib/project-session-pair.js +14 -7
- package/lib/project-session-spawn.js +1 -1
- package/lib/project-sessions.js +13 -45
- package/lib/project-worker-proposal.js +51 -14
- package/lib/project.js +33 -77
- package/lib/public/app.js +11 -0
- package/lib/public/copilot-avatar.svg +5 -0
- package/lib/public/css/filebrowser.css +6 -1
- package/lib/public/css/input.css +56 -0
- package/lib/public/css/mates.css +6 -0
- package/lib/public/css/menus.css +35 -56
- package/lib/public/css/pane.css +18 -3
- package/lib/public/css/session-actions.css +98 -0
- package/lib/public/grok-avatar.svg +4 -0
- package/lib/public/index.html +26 -9
- package/lib/public/junie-avatar.svg +11 -0
- package/lib/public/kimi-avatar.svg +4 -0
- package/lib/public/modules/agent-config-selects.js +69 -0
- package/lib/public/modules/app-connection.js +6 -0
- package/lib/public/modules/app-header.js +0 -22
- package/lib/public/modules/app-messages.js +48 -9
- package/lib/public/modules/app-panels.js +33 -82
- package/lib/public/modules/app-projects.js +2 -0
- package/lib/public/modules/app-rendering.js +16 -1
- package/lib/public/modules/background-tasks-ui.js +55 -0
- package/lib/public/modules/filebrowser-tabs.js +65 -0
- package/lib/public/modules/filebrowser.js +23 -3
- package/lib/public/modules/mate-sidebar.js +10 -2
- package/lib/public/modules/model-picker.js +263 -0
- package/lib/public/modules/pane-bridge.js +6 -0
- package/lib/public/modules/session-actions.js +294 -0
- package/lib/public/modules/sidebar-mobile.js +2 -1
- package/lib/public/modules/sidebar-sessions.js +17 -1
- package/lib/public/modules/split-pair-ui.js +16 -77
- package/lib/public/modules/split-view.js +11 -1
- package/lib/public/modules/tools.js +5 -0
- package/lib/public/modules/vendor-priority.js +6 -1
- package/lib/public/modules/worker-proposal.js +6 -1
- package/lib/public/qwen-avatar.svg +4 -0
- package/lib/public/style.css +1 -0
- package/lib/sdk-bridge.js +57 -34
- package/lib/sdk-message-processor.js +26 -4
- package/lib/session-handoff-context.js +110 -0
- package/lib/session-notes-mcp-server.js +1 -0
- package/lib/sessions.js +4 -0
- package/lib/ws-schema.js +9 -1
- package/lib/yoke/acp-agent-profiles.js +64 -0
- package/lib/yoke/adapters/acp.js +2 -1
- package/lib/yoke/adapters/claude.js +34 -0
- package/lib/yoke/adapters/codex.js +38 -3
- package/lib/yoke/adapters/copilot.js +7 -0
- package/lib/yoke/adapters/grok.js +7 -0
- package/lib/yoke/adapters/junie.js +7 -0
- package/lib/yoke/adapters/kimi.js +7 -0
- package/lib/yoke/adapters/kiro.js +4 -3
- package/lib/yoke/adapters/qwen.js +7 -0
- package/lib/yoke/codex-background-tasks.js +84 -0
- package/lib/yoke/index.js +43 -14
- package/lib/yoke/instructions.js +3 -0
- package/lib/yoke/interface.js +21 -0
- package/lib/yoke/vendor-registry.js +55 -0
- package/package.json +1 -1
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
var spawnSync = require("child_process").spawnSync;
|
|
2
|
+
var yoke = require("./yoke");
|
|
3
|
+
|
|
4
|
+
var MAX_CONTEXT_CHARS = 36000;
|
|
5
|
+
var MAX_TURNS = 12;
|
|
6
|
+
var MAX_USER_CHARS = 5000;
|
|
7
|
+
var MAX_ASSISTANT_CHARS = 9000;
|
|
8
|
+
var MAX_GIT_CHARS = 5000;
|
|
9
|
+
var MAX_TRANSCRIPT_CHARS = 24000;
|
|
10
|
+
|
|
11
|
+
function trimText(value, limit) {
|
|
12
|
+
var text = typeof value === "string" ? value.trim() : "";
|
|
13
|
+
if (text.length <= limit) return text;
|
|
14
|
+
return text.slice(0, limit) + "\n[truncated]";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function recentTurns(history) {
|
|
18
|
+
var turns = [];
|
|
19
|
+
var current = null;
|
|
20
|
+
history = Array.isArray(history) ? history : [];
|
|
21
|
+
for (var i = 0; i < history.length; i++) {
|
|
22
|
+
var entry = history[i];
|
|
23
|
+
if (!entry) continue;
|
|
24
|
+
if (entry.type === "user_message" || (entry.type === "handoff_context" && entry.request)) {
|
|
25
|
+
current = { user: trimText(entry.text || entry.request, MAX_USER_CHARS), assistant: "" };
|
|
26
|
+
turns.push(current);
|
|
27
|
+
} else if (entry.type === "delta" && entry.text) {
|
|
28
|
+
if (!current) {
|
|
29
|
+
current = { user: "", assistant: "" };
|
|
30
|
+
turns.push(current);
|
|
31
|
+
}
|
|
32
|
+
current.assistant += entry.text;
|
|
33
|
+
if (current.assistant.length > MAX_ASSISTANT_CHARS) {
|
|
34
|
+
current.assistant = current.assistant.slice(-MAX_ASSISTANT_CHARS);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return turns.slice(-MAX_TURNS);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function latestUserRequest(history) {
|
|
42
|
+
var turns = recentTurns(history);
|
|
43
|
+
for (var i = turns.length - 1; i >= 0; i--) {
|
|
44
|
+
if (turns[i].user) return turns[i].user;
|
|
45
|
+
}
|
|
46
|
+
return "";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function gitCommand(cwd, args) {
|
|
50
|
+
var result = spawnSync("git", args, {
|
|
51
|
+
cwd: cwd,
|
|
52
|
+
encoding: "utf8",
|
|
53
|
+
timeout: 5000,
|
|
54
|
+
maxBuffer: 1024 * 1024,
|
|
55
|
+
});
|
|
56
|
+
if (result.error || result.status !== 0) return "";
|
|
57
|
+
return trimText(result.stdout, MAX_GIT_CHARS);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function repositoryState(cwd) {
|
|
61
|
+
var status = gitCommand(cwd, ["status", "--short", "--branch"]);
|
|
62
|
+
var lines = [];
|
|
63
|
+
lines.push("Working tree:\n" + (status || "clean"));
|
|
64
|
+
return lines.join("\n\n");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function transcriptText(turns) {
|
|
68
|
+
var sections = [];
|
|
69
|
+
for (var i = 0; i < turns.length; i++) {
|
|
70
|
+
var turn = turns[i];
|
|
71
|
+
var parts = ["Turn " + (i + 1)];
|
|
72
|
+
if (turn.user) parts.push("USER:\n" + turn.user);
|
|
73
|
+
if (turn.assistant) parts.push("ASSISTANT:\n" + trimText(turn.assistant, MAX_ASSISTANT_CHARS));
|
|
74
|
+
sections.push(parts.join("\n\n"));
|
|
75
|
+
}
|
|
76
|
+
while (sections.length > 1 && sections.join("\n\n---\n\n").length > MAX_TRANSCRIPT_CHARS) {
|
|
77
|
+
sections.shift();
|
|
78
|
+
}
|
|
79
|
+
return sections.join("\n\n---\n\n");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function buildHandoffContext(options) {
|
|
83
|
+
var source = options.source;
|
|
84
|
+
var targetVendor = options.targetVendor;
|
|
85
|
+
var sourceVendor = source.vendor || "claude";
|
|
86
|
+
var sourceName = (yoke.getVendorInfo(sourceVendor) || {}).displayName || sourceVendor;
|
|
87
|
+
var targetName = (yoke.getVendorInfo(targetVendor) || {}).displayName || targetVendor;
|
|
88
|
+
var turns = recentTurns(source.history);
|
|
89
|
+
var latestUser = latestUserRequest(source.history);
|
|
90
|
+
var parts = [
|
|
91
|
+
"[Clay session handoff]",
|
|
92
|
+
"You are continuing work from another Clay coding-agent session. This is a snapshot, not a native conversation resume. Verify the current filesystem state before acting.",
|
|
93
|
+
"Source agent: " + sourceName,
|
|
94
|
+
"Target agent: " + targetName,
|
|
95
|
+
"Source session: " + (source.title || "Untitled session") + " (#" + source.localId + ")",
|
|
96
|
+
];
|
|
97
|
+
if (latestUser) parts.push("Current user request, verbatim:\n" + latestUser);
|
|
98
|
+
parts.push("Repository state at handoff:\n" + repositoryState(options.cwd));
|
|
99
|
+
parts.push("Recent conversation:\n" + transcriptText(turns));
|
|
100
|
+
parts.push("Continue from the unresolved work above. Preserve the user's decisions and constraints, inspect the actual files before making assumptions, and proceed without asking the user to repeat context.");
|
|
101
|
+
return trimText(parts.join("\n\n"), MAX_CONTEXT_CHARS);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = {
|
|
105
|
+
MAX_CONTEXT_CHARS: MAX_CONTEXT_CHARS,
|
|
106
|
+
buildHandoffContext: buildHandoffContext,
|
|
107
|
+
latestUserRequest: latestUserRequest,
|
|
108
|
+
recentTurns: recentTurns,
|
|
109
|
+
repositoryState: repositoryState,
|
|
110
|
+
};
|
|
@@ -7,6 +7,7 @@ var MEMORY_CONTRACT =
|
|
|
7
7
|
"Default to not writing. Create a note proactively only when the user explicitly asks to remember or track something, or when all of these are true: it will remain useful after the current task and session, it is not already adequately recorded in the repository or another note, and the user would likely be glad to find it on the board a week later. " +
|
|
8
8
|
"Good notes capture an unresolved commitment, durable product decision, user preference, constraint, or handoff that will materially change future work. " +
|
|
9
9
|
"Important exception for deferred defects: while doing code or technical work, actively create a sticky note when you discover a concrete defect, regression risk, security issue, or data-loss risk that is outside the current session goal and will remain unfixed when the turn ends. Do not wait for the user to ask. Include the observable evidence, affected component, likely impact, and a clear next action. Check active notes first when a duplicate is plausible. Do not create defect notes for speculation, general cleanup ideas, or problems you fixed in the current session. " +
|
|
10
|
+
"Important exception for deferred proposals: when you propose work (a fix, follow-up, improvement, or next step) and the user defers it rather than declining it (\"let's do that later\", \"next time\", \"after this\"), actively create a sticky note before the topic moves on. Do not wait to be asked. Capture what was proposed, why it mattered, and the agreed timing if any. Deferred agreements scroll out of chat history quickly and are hard to track; the board is where they survive. If the user declines the idea outright, do not write a note. " +
|
|
10
11
|
"Never create a note merely because work is important, lengthy, spans agents or restarts, or might help another agent. Do not record completed work, implementation details, test results, investigation logs, transient blockers, conversation summaries, or announcements of your own activity. When uncertain, do not write. " +
|
|
11
12
|
"Updates are visible too: update only when durable state materially changes, and remove a note created by your session when it stops being useful instead of turning it into a completion log. Put a concise plain-text title on the first line, stay focused on one topic, and include only the context needed for future action.";
|
|
12
13
|
|
package/lib/sessions.js
CHANGED
|
@@ -169,6 +169,7 @@ function createSessionManager(opts) {
|
|
|
169
169
|
if (session.lastRewindUuid) metaObj.lastRewindUuid = session.lastRewindUuid;
|
|
170
170
|
if (session.loop) metaObj.loop = session.loop;
|
|
171
171
|
if (session.spawn) metaObj.spawn = session.spawn;
|
|
172
|
+
if (session.handoff) metaObj.handoff = session.handoff;
|
|
172
173
|
if (session.debateState) metaObj.debateState = session.debateState;
|
|
173
174
|
if (session.debateSetupMode) metaObj.debateSetupMode = true;
|
|
174
175
|
var meta = JSON.stringify(metaObj);
|
|
@@ -281,6 +282,7 @@ function createSessionManager(opts) {
|
|
|
281
282
|
session.effort = m.effort || null;
|
|
282
283
|
if (m.loop) session.loop = m.loop;
|
|
283
284
|
if (m.spawn) session.spawn = m.spawn;
|
|
285
|
+
if (m.handoff) session.handoff = m.handoff;
|
|
284
286
|
if (m.debateState) session.debateState = m.debateState;
|
|
285
287
|
if (m.debateSetupMode) session.debateSetupMode = true;
|
|
286
288
|
if (m.ownerId) session.ownerId = m.ownerId;
|
|
@@ -493,6 +495,7 @@ function createSessionManager(opts) {
|
|
|
493
495
|
title: s.title || "New Session",
|
|
494
496
|
active: isActive,
|
|
495
497
|
isProcessing: s.isProcessing,
|
|
498
|
+
backgroundTaskCount: (s.activeBackgroundTasks || []).length,
|
|
496
499
|
lastActivity: s.lastActivity || s.createdAt || 0,
|
|
497
500
|
loop: loop,
|
|
498
501
|
spawn: s.spawn || null,
|
|
@@ -715,6 +718,7 @@ function createSessionManager(opts) {
|
|
|
715
718
|
if (session.isProcessing) {
|
|
716
719
|
_send({ type: "status", status: "processing" });
|
|
717
720
|
}
|
|
721
|
+
_send({ type: "active_background_tasks", tasks: session.activeBackgroundTasks || [] });
|
|
718
722
|
|
|
719
723
|
// Re-send any pending permission requests
|
|
720
724
|
var pendingIds = Object.keys(session.pendingPermissions);
|
package/lib/ws-schema.js
CHANGED
|
@@ -30,6 +30,8 @@ var schema = {
|
|
|
30
30
|
"search_session_content": { direction: "c2s", handler: "lib/project-sessions.js", description: "Full-text search within a session" },
|
|
31
31
|
"load_more_history": { direction: "c2s", handler: "lib/project-sessions.js", description: "Request older history entries for the current session" },
|
|
32
32
|
"fork_session": { direction: "c2s", handler: "lib/project-sessions.js", description: "Fork a session from a given message UUID" },
|
|
33
|
+
"handoff_session": { direction: "c2s", handler: "lib/project-session-handoff.js", description: "Continue the active session in a newly created agent session" },
|
|
34
|
+
"handoff_session_options": { direction: "c2s", handler: "lib/project-session-handoff.js", description: "Request vendors, models, and effort capabilities for the handoff dialog (response reuses the same type)" },
|
|
33
35
|
"input_sync": { direction: "c2s", handler: "lib/project-sessions.js", description: "Sync the current input field text to other clients" },
|
|
34
36
|
"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
37
|
"split_group_create": { direction: "c2s", handler: "lib/session-split-groups.js", description: "Create a persistent two-session split group" },
|
|
@@ -49,6 +51,9 @@ var schema = {
|
|
|
49
51
|
"search_results": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Session title search results" },
|
|
50
52
|
"search_content_results": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Full-text content search results" },
|
|
51
53
|
"fork_complete": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Fork succeeded, includes new session ID" },
|
|
54
|
+
"session_handoff_result": { direction: "s2c", handler: "lib/public/modules/session-actions.js", description: "Session handoff result and new target session ID" },
|
|
55
|
+
"handoff_context": { direction: "s2c", handler: "lib/public/modules/session-actions.js", description: "Timeline marker identifying the source of a handoff session" },
|
|
56
|
+
"handoff_created": { direction: "s2c", handler: "lib/public/modules/session-actions.js", description: "Timeline marker linking a source session to its handoff target" },
|
|
52
57
|
"input_sync_broadcast": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Broadcast input text from another client" },
|
|
53
58
|
"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)" },
|
|
54
59
|
"split_groups": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Full persistent split-group list for the current user" },
|
|
@@ -109,6 +114,7 @@ var schema = {
|
|
|
109
114
|
"subagent_done": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Subagent task completed" },
|
|
110
115
|
"task_started": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Background task started" },
|
|
111
116
|
"task_progress": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Background task progress update" },
|
|
117
|
+
"active_background_tasks": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Current background tasks for the active session (full replacement)" },
|
|
112
118
|
"markdown_edit_present": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Open a user-requested Markdown edit with a before snapshot" },
|
|
113
119
|
|
|
114
120
|
// -----------------------------------------------------------------------
|
|
@@ -175,7 +181,8 @@ var schema = {
|
|
|
175
181
|
// -----------------------------------------------------------------------
|
|
176
182
|
// Model / config
|
|
177
183
|
// -----------------------------------------------------------------------
|
|
178
|
-
"set_model": { direction: "c2s", handler: "lib/project-
|
|
184
|
+
"set_model": { direction: "c2s", handler: "lib/project-models.js", description: "Set the model for the current session" },
|
|
185
|
+
"get_vendor_models": { direction: "c2s", handler: "lib/project-models.js", description: "Load a vendor model catalog" },
|
|
179
186
|
"set_server_default_model": { direction: "c2s", handler: "lib/project-sessions.js", description: "Set the server-wide default model" },
|
|
180
187
|
"set_project_default_model": { direction: "c2s", handler: "lib/project-sessions.js", description: "Set the project default model" },
|
|
181
188
|
"set_permission_mode": { direction: "c2s", handler: "lib/project-sessions.js", description: "Set permission mode for the current session" },
|
|
@@ -192,6 +199,7 @@ var schema = {
|
|
|
192
199
|
"set_betas": { direction: "c2s", handler: "lib/project-sessions.js", description: "Set beta feature flags" },
|
|
193
200
|
"set_thinking": { direction: "c2s", handler: "lib/project-sessions.js", description: "Set thinking mode and budget" },
|
|
194
201
|
"model_info": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Current model and available models list" },
|
|
202
|
+
"model_selection_result": { direction: "s2c", handler: "lib/public/modules/model-picker.js", description: "Acknowledgement or error for a model selection" },
|
|
195
203
|
"config_state": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Full config state: model, mode, effort, betas, thinking" },
|
|
196
204
|
"slash_commands": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Available slash commands list" },
|
|
197
205
|
"fast_mode_state": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Fast mode toggle state" },
|
|
@@ -96,6 +96,15 @@ function validateOpenCodeConfig(config) {
|
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
function trustDefaultWhenModeIsNotExposed(ctx, next) {
|
|
100
|
+
var options = ctx.state && ctx.state.configOptions;
|
|
101
|
+
for (var i = 0; i < (options || []).length; i++) {
|
|
102
|
+
if (options[i].category === "mode" || options[i].id === "mode") return next();
|
|
103
|
+
}
|
|
104
|
+
if (ctx.state && ctx.state.modes) return next();
|
|
105
|
+
return Promise.resolve();
|
|
106
|
+
}
|
|
107
|
+
|
|
99
108
|
var ACP_AGENT_PROFILES = {
|
|
100
109
|
opencode: {
|
|
101
110
|
vendor: "opencode",
|
|
@@ -147,6 +156,61 @@ var ACP_AGENT_PROFILES = {
|
|
|
147
156
|
return Promise.resolve();
|
|
148
157
|
},
|
|
149
158
|
},
|
|
159
|
+
kimi: {
|
|
160
|
+
vendor: "kimi",
|
|
161
|
+
displayName: "Kimi Code",
|
|
162
|
+
binaryName: "kimi",
|
|
163
|
+
overrideName: "KIMI_CLI_PATH",
|
|
164
|
+
args: ["acp"],
|
|
165
|
+
defaultModels: ["auto"],
|
|
166
|
+
defaultModel: "auto",
|
|
167
|
+
sessionResume: null,
|
|
168
|
+
ensureSafePermissionMode: trustDefaultWhenModeIsNotExposed,
|
|
169
|
+
},
|
|
170
|
+
grok: {
|
|
171
|
+
vendor: "grok",
|
|
172
|
+
displayName: "Grok Build",
|
|
173
|
+
binaryName: "grok",
|
|
174
|
+
overrideName: "GROK_CLI_PATH",
|
|
175
|
+
args: ["--no-auto-update", "--permission-mode", "ask", "agent", "stdio"],
|
|
176
|
+
defaultModels: ["auto"],
|
|
177
|
+
defaultModel: "auto",
|
|
178
|
+
sessionResume: null,
|
|
179
|
+
permissionModeGuaranteed: true,
|
|
180
|
+
},
|
|
181
|
+
copilot: {
|
|
182
|
+
vendor: "copilot",
|
|
183
|
+
displayName: "GitHub Copilot CLI",
|
|
184
|
+
binaryName: "copilot",
|
|
185
|
+
overrideName: "COPILOT_CLI_PATH",
|
|
186
|
+
args: ["--acp"],
|
|
187
|
+
defaultModels: ["auto"],
|
|
188
|
+
defaultModel: "auto",
|
|
189
|
+
sessionResume: null,
|
|
190
|
+
ensureSafePermissionMode: trustDefaultWhenModeIsNotExposed,
|
|
191
|
+
},
|
|
192
|
+
qwen: {
|
|
193
|
+
vendor: "qwen",
|
|
194
|
+
displayName: "Qwen Code",
|
|
195
|
+
binaryName: "qwen",
|
|
196
|
+
overrideName: "QWEN_CLI_PATH",
|
|
197
|
+
args: ["--acp", "--approval-mode", "default"],
|
|
198
|
+
defaultModels: ["auto"],
|
|
199
|
+
defaultModel: "auto",
|
|
200
|
+
sessionResume: null,
|
|
201
|
+
permissionModeGuaranteed: true,
|
|
202
|
+
},
|
|
203
|
+
junie: {
|
|
204
|
+
vendor: "junie",
|
|
205
|
+
displayName: "Junie CLI",
|
|
206
|
+
binaryName: "junie",
|
|
207
|
+
overrideName: "JUNIE_CLI_PATH",
|
|
208
|
+
args: ["--acp", "true"],
|
|
209
|
+
defaultModels: ["auto"],
|
|
210
|
+
defaultModel: "auto",
|
|
211
|
+
sessionResume: null,
|
|
212
|
+
ensureSafePermissionMode: trustDefaultWhenModeIsNotExposed,
|
|
213
|
+
},
|
|
150
214
|
};
|
|
151
215
|
|
|
152
216
|
function getAcpAgentProfile(vendor) {
|
package/lib/yoke/adapters/acp.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// Implements the YOKE Adapter contract once for standard ACP agents.
|
|
4
4
|
|
|
5
5
|
var AcpProcessManager = require("../acp-process-manager").AcpProcessManager;
|
|
6
|
+
var INITIALIZE_TIMEOUT_MS = require("../interface").INITIALIZE_TIMEOUT_MS;
|
|
6
7
|
var createAcpQueryHandle = require("../acp-query-handle").createAcpQueryHandle;
|
|
7
8
|
var profiles = require("../acp-agent-profiles");
|
|
8
9
|
var driverRuntime = require("../acp-driver-runtime");
|
|
@@ -178,7 +179,7 @@ function createAcpAdapter(vendor, opts) {
|
|
|
178
179
|
session: { configOptions: { boolean: {} } },
|
|
179
180
|
},
|
|
180
181
|
});
|
|
181
|
-
initResult = await acp.send("initialize", initializeParams,
|
|
182
|
+
initResult = await acp.send("initialize", initializeParams, INITIALIZE_TIMEOUT_MS);
|
|
182
183
|
await driverRuntime.callAsync(driver, "onInitialize", context({ acp: acp, initResult: initResult }), function() {});
|
|
183
184
|
if (shuttingDown) throw new Error(driver.displayName + " adapter is shutting down");
|
|
184
185
|
} catch (e) {
|
|
@@ -197,6 +197,26 @@ function flattenEvent(raw) {
|
|
|
197
197
|
base.summary = raw.summary || null;
|
|
198
198
|
return base;
|
|
199
199
|
}
|
|
200
|
+
if (raw.subtype === "background_tasks_changed") {
|
|
201
|
+
base.yokeType = "background_tasks_changed";
|
|
202
|
+
base.tasks = normalizeBackgroundTasks(raw.tasks);
|
|
203
|
+
return base;
|
|
204
|
+
}
|
|
205
|
+
if (raw.subtype === "task_updated") {
|
|
206
|
+
base.yokeType = "task_updated";
|
|
207
|
+
base.task_id = raw.task_id;
|
|
208
|
+
base.patch = raw.patch || {};
|
|
209
|
+
return base;
|
|
210
|
+
}
|
|
211
|
+
if (raw.subtype === "task_notification") {
|
|
212
|
+
base.yokeType = "task_notification";
|
|
213
|
+
base.parentToolId = raw.parent_tool_use_id;
|
|
214
|
+
base.taskId = raw.task_id;
|
|
215
|
+
base.status = raw.status || "completed";
|
|
216
|
+
base.summary = raw.summary || "";
|
|
217
|
+
base.usage = raw.usage || null;
|
|
218
|
+
return base;
|
|
219
|
+
}
|
|
200
220
|
// Model refusal: the model declined and the CLI either fell back to
|
|
201
221
|
// another model or ended the turn. Translate to a vendor-neutral
|
|
202
222
|
// yokeType so the relay never sees SDK subtype strings.
|
|
@@ -343,6 +363,20 @@ function flattenEvent(raw) {
|
|
|
343
363
|
return base;
|
|
344
364
|
}
|
|
345
365
|
|
|
366
|
+
function normalizeBackgroundTasks(tasks) {
|
|
367
|
+
if (!Array.isArray(tasks)) return [];
|
|
368
|
+
return tasks.map(function(task) {
|
|
369
|
+
var nativeType = task && task.task_type;
|
|
370
|
+
var taskType = nativeType === "local_bash" ? "shell"
|
|
371
|
+
: nativeType === "local_agent" ? "agent" : "other";
|
|
372
|
+
return {
|
|
373
|
+
task_id: task && task.task_id,
|
|
374
|
+
task_type: taskType,
|
|
375
|
+
description: (task && task.description) || "",
|
|
376
|
+
};
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
346
380
|
// --- QueryHandle ---
|
|
347
381
|
// Wraps a raw SDK query object with the YOKE QueryHandle interface.
|
|
348
382
|
// Events are flattened via flattenEvent before yielding.
|
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
var path = require("path");
|
|
7
7
|
var fs = require("fs");
|
|
8
8
|
var { CodexAppServer } = require("../codex-app-server");
|
|
9
|
+
var INITIALIZE_TIMEOUT_MS = require("../interface").INITIALIZE_TIMEOUT_MS;
|
|
9
10
|
var skillDiscovery = require("../skill-discovery");
|
|
10
11
|
var { resolveOsUserInfo } = require("../../os-users");
|
|
12
|
+
var backgroundTasks = require("../codex-background-tasks");
|
|
11
13
|
|
|
12
14
|
// --- Event flattening ---
|
|
13
15
|
// Converts app-server JSON-RPC notifications into flat objects with a yokeType field.
|
|
@@ -672,6 +674,7 @@ function createEventState(model) {
|
|
|
672
674
|
toolBlocks: {},
|
|
673
675
|
commandInputs: {},
|
|
674
676
|
planTexts: {},
|
|
677
|
+
backgroundTasks: backgroundTasks.createState(),
|
|
675
678
|
};
|
|
676
679
|
}
|
|
677
680
|
|
|
@@ -698,6 +701,7 @@ function createCodexQueryHandle(appServer, queryOpts) {
|
|
|
698
701
|
var eventWaiting = null;
|
|
699
702
|
var iteratorDone = false;
|
|
700
703
|
var finishedNotified = false;
|
|
704
|
+
var removeBackgroundTaskReset = null;
|
|
701
705
|
|
|
702
706
|
function notifyFinished() {
|
|
703
707
|
if (finishedNotified) return;
|
|
@@ -722,6 +726,12 @@ function createCodexQueryHandle(appServer, queryOpts) {
|
|
|
722
726
|
}
|
|
723
727
|
}
|
|
724
728
|
|
|
729
|
+
if (typeof queryOpts.registerBackgroundTaskReset === "function") {
|
|
730
|
+
removeBackgroundTaskReset = queryOpts.registerBackgroundTaskReset(function() {
|
|
731
|
+
backgroundTasks.emitReset(state.backgroundTasks, pushEvent);
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
|
|
725
735
|
function endIterator() {
|
|
726
736
|
iteratorDone = true;
|
|
727
737
|
if (eventWaiting) {
|
|
@@ -729,6 +739,10 @@ function createCodexQueryHandle(appServer, queryOpts) {
|
|
|
729
739
|
eventWaiting = null;
|
|
730
740
|
resolve({ value: undefined, done: true });
|
|
731
741
|
}
|
|
742
|
+
if (removeBackgroundTaskReset) {
|
|
743
|
+
removeBackgroundTaskReset();
|
|
744
|
+
removeBackgroundTaskReset = null;
|
|
745
|
+
}
|
|
732
746
|
notifyFinished();
|
|
733
747
|
}
|
|
734
748
|
|
|
@@ -934,6 +948,10 @@ function createCodexQueryHandle(appServer, queryOpts) {
|
|
|
934
948
|
pushEvent(yokeEvents[i]);
|
|
935
949
|
}
|
|
936
950
|
|
|
951
|
+
if (method === "turn/completed" || method === "turn/failed") {
|
|
952
|
+
backgroundTasks.poll(appServer, state.threadId, state.backgroundTasks, pushEvent).catch(function() {});
|
|
953
|
+
}
|
|
954
|
+
|
|
937
955
|
// Resolve turn promise when turn ends
|
|
938
956
|
if (method === "turn/completed" || method === "turn/failed") {
|
|
939
957
|
if (turnResolve) {
|
|
@@ -1072,6 +1090,7 @@ function createCodexQueryHandle(appServer, queryOpts) {
|
|
|
1072
1090
|
await appServer.send("turn/start", {
|
|
1073
1091
|
threadId: state.threadId,
|
|
1074
1092
|
input: input,
|
|
1093
|
+
model: state.model,
|
|
1075
1094
|
}, 60000);
|
|
1076
1095
|
|
|
1077
1096
|
// Wait for turn to complete
|
|
@@ -1144,7 +1163,7 @@ function createCodexQueryHandle(appServer, queryOpts) {
|
|
|
1144
1163
|
},
|
|
1145
1164
|
|
|
1146
1165
|
setModel: function(model) {
|
|
1147
|
-
|
|
1166
|
+
state.model = model || "gpt-5.6-terra";
|
|
1148
1167
|
return Promise.resolve();
|
|
1149
1168
|
},
|
|
1150
1169
|
|
|
@@ -1245,6 +1264,20 @@ function createCodexAdapter(opts) {
|
|
|
1245
1264
|
];
|
|
1246
1265
|
var _cachedModels = CODEX_MODELS.slice();
|
|
1247
1266
|
var _appServer = null;
|
|
1267
|
+
var _backgroundTaskResetListeners = [];
|
|
1268
|
+
|
|
1269
|
+
function registerBackgroundTaskReset(listener) {
|
|
1270
|
+
_backgroundTaskResetListeners.push(listener);
|
|
1271
|
+
return function() {
|
|
1272
|
+
var index = _backgroundTaskResetListeners.indexOf(listener);
|
|
1273
|
+
if (index !== -1) _backgroundTaskResetListeners.splice(index, 1);
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
function emitBackgroundTaskReset() {
|
|
1278
|
+
var listeners = _backgroundTaskResetListeners.slice();
|
|
1279
|
+
for (var i = 0; i < listeners.length; i++) listeners[i]();
|
|
1280
|
+
}
|
|
1248
1281
|
var _initPromise = null;
|
|
1249
1282
|
var _shutdownPromise = null;
|
|
1250
1283
|
var _refCount = 0;
|
|
@@ -1326,7 +1359,7 @@ function createCodexAdapter(opts) {
|
|
|
1326
1359
|
fastModeState: null,
|
|
1327
1360
|
capabilities: {
|
|
1328
1361
|
effort: true,
|
|
1329
|
-
midSessionModelSwitch:
|
|
1362
|
+
midSessionModelSwitch: true,
|
|
1330
1363
|
fork: true,
|
|
1331
1364
|
rollback: true,
|
|
1332
1365
|
sessionListing: false,
|
|
@@ -1540,11 +1573,12 @@ function createCodexAdapter(opts) {
|
|
|
1540
1573
|
// Spawn and initialize app-server
|
|
1541
1574
|
_appServer = _createAppServer(serverOpts);
|
|
1542
1575
|
await _appServer.start();
|
|
1576
|
+
emitBackgroundTaskReset();
|
|
1543
1577
|
|
|
1544
1578
|
await _appServer.send("initialize", {
|
|
1545
1579
|
clientInfo: { name: "clay", title: "Clay", version: "1.0.0" },
|
|
1546
1580
|
capabilities: { experimentalApi: true },
|
|
1547
|
-
});
|
|
1581
|
+
}, INITIALIZE_TIMEOUT_MS);
|
|
1548
1582
|
_appServer.notify("initialized", {});
|
|
1549
1583
|
|
|
1550
1584
|
if (_shuttingDown) {
|
|
@@ -1675,6 +1709,7 @@ function createCodexAdapter(opts) {
|
|
|
1675
1709
|
canUseTool: queryOpts.canUseTool || null,
|
|
1676
1710
|
onElicitation: queryOpts.onElicitation || null,
|
|
1677
1711
|
resumeSessionId: queryOpts.resumeSessionId || null,
|
|
1712
|
+
registerBackgroundTaskReset: registerBackgroundTaskReset,
|
|
1678
1713
|
};
|
|
1679
1714
|
|
|
1680
1715
|
// Reasoning effort
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
var { execFile } = require("child_process");
|
|
16
16
|
var { KiroAcpServer, findKiroPath } = require("../kiro-acp-server");
|
|
17
|
+
var INITIALIZE_TIMEOUT_MS = require("../interface").INITIALIZE_TIMEOUT_MS;
|
|
17
18
|
var skillDiscovery = require("../skill-discovery");
|
|
18
19
|
var { KIRO_DEFAULTS } = require("../../kiro-defaults");
|
|
19
20
|
|
|
@@ -762,7 +763,7 @@ function createKiroQueryHandle(acp, queryOpts) {
|
|
|
762
763
|
|
|
763
764
|
setModel: function(model) {
|
|
764
765
|
state.model = model;
|
|
765
|
-
return setSessionModel(model)
|
|
766
|
+
return setSessionModel(model);
|
|
766
767
|
},
|
|
767
768
|
|
|
768
769
|
setEffort: function() { return Promise.resolve(); },
|
|
@@ -867,7 +868,7 @@ function createKiroAdapter(opts) {
|
|
|
867
868
|
fastModeState: null,
|
|
868
869
|
capabilities: {
|
|
869
870
|
effort: false,
|
|
870
|
-
midSessionModelSwitch:
|
|
871
|
+
midSessionModelSwitch: true,
|
|
871
872
|
fork: false,
|
|
872
873
|
rollback: false,
|
|
873
874
|
sessionListing: false,
|
|
@@ -1000,7 +1001,7 @@ function createKiroAdapter(opts) {
|
|
|
1000
1001
|
// calls, so implementing them later means bypassing canUseTool and
|
|
1001
1002
|
// needs explicit cwd confinement first.
|
|
1002
1003
|
clientCapabilities: { fs: { readTextFile: false, writeTextFile: false } },
|
|
1003
|
-
},
|
|
1004
|
+
}, INITIALIZE_TIMEOUT_MS);
|
|
1004
1005
|
_initialized = true;
|
|
1005
1006
|
|
|
1006
1007
|
if (_shuttingDown) throw createShutdownError();
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
function terminalList(result) {
|
|
2
|
+
if (Array.isArray(result)) return result;
|
|
3
|
+
if (!result || typeof result !== "object") return [];
|
|
4
|
+
if (Array.isArray(result.terminals)) return result.terminals;
|
|
5
|
+
if (Array.isArray(result.backgroundTerminals)) return result.backgroundTerminals;
|
|
6
|
+
if (Array.isArray(result.items)) return result.items;
|
|
7
|
+
return [];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function isLiveTerminal(terminal) {
|
|
11
|
+
var status = String((terminal && (terminal.status || terminal.state)) || "").toLowerCase();
|
|
12
|
+
return status !== "exited" && status !== "terminated" && status !== "completed" && status !== "failed";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function mapTerminals(result) {
|
|
16
|
+
var terminals = terminalList(result);
|
|
17
|
+
var tasks = [];
|
|
18
|
+
for (var i = 0; i < terminals.length; i++) {
|
|
19
|
+
var terminal = terminals[i] || {};
|
|
20
|
+
var taskId = terminal.id || terminal.terminalId || terminal.taskId || "";
|
|
21
|
+
if (!taskId || !isLiveTerminal(terminal)) continue;
|
|
22
|
+
tasks.push({
|
|
23
|
+
task_id: String(taskId),
|
|
24
|
+
task_type: "shell",
|
|
25
|
+
description: terminal.commandLine || terminal.command || terminal.name || "",
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return tasks;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function taskIds(tasks) {
|
|
32
|
+
var ids = {};
|
|
33
|
+
for (var i = 0; i < tasks.length; i++) ids[tasks[i].task_id] = true;
|
|
34
|
+
return ids;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function sameMembership(first, second) {
|
|
38
|
+
var firstKeys = Object.keys(first || {});
|
|
39
|
+
var secondKeys = Object.keys(second || {});
|
|
40
|
+
if (firstKeys.length !== secondKeys.length) return false;
|
|
41
|
+
for (var i = 0; i < firstKeys.length; i++) {
|
|
42
|
+
if (!second[firstKeys[i]]) return false;
|
|
43
|
+
}
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function createState() {
|
|
48
|
+
return { taskIds: null };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function emitIfChanged(state, tasks, pushEvent) {
|
|
52
|
+
var ids = taskIds(tasks);
|
|
53
|
+
if (state.taskIds === null && tasks.length === 0) {
|
|
54
|
+
state.taskIds = ids;
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
if (state.taskIds && sameMembership(state.taskIds, ids)) return false;
|
|
58
|
+
state.taskIds = ids;
|
|
59
|
+
pushEvent({ yokeType: "background_tasks_changed", tasks: tasks });
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function poll(appServer, threadId, state, pushEvent) {
|
|
64
|
+
if (!appServer || !threadId || appServer._clayBackgroundTasksPollingDisabled) return Promise.resolve(false);
|
|
65
|
+
return appServer.send("thread/backgroundTerminals/list", { threadId: threadId }).then(function(result) {
|
|
66
|
+
return emitIfChanged(state, mapTerminals(result), pushEvent);
|
|
67
|
+
}).catch(function() {
|
|
68
|
+
appServer._clayBackgroundTasksPollingDisabled = true;
|
|
69
|
+
return false;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function emitReset(state, pushEvent) {
|
|
74
|
+
state.taskIds = {};
|
|
75
|
+
pushEvent({ yokeType: "background_tasks_changed", tasks: [] });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = {
|
|
79
|
+
createState: createState,
|
|
80
|
+
mapTerminals: mapTerminals,
|
|
81
|
+
emitIfChanged: emitIfChanged,
|
|
82
|
+
poll: poll,
|
|
83
|
+
emitReset: emitReset,
|
|
84
|
+
};
|