clay-server 3.4.0-beta.2 → 3.4.0-beta.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/lib/daemon-projects.js +3 -3
  2. package/lib/daemon.js +59 -59
  3. package/lib/project-connection.js +35 -7
  4. package/lib/project-debate.js +4 -0
  5. package/lib/project-mate-interaction.js +21 -1
  6. package/lib/project-memory.js +3 -0
  7. package/lib/project-models.js +220 -0
  8. package/lib/project-session-handoff.js +162 -0
  9. package/lib/project-sessions.js +13 -45
  10. package/lib/project-worker-proposal.js +49 -14
  11. package/lib/project.js +38 -80
  12. package/lib/public/antigravity-avatar.png +0 -0
  13. package/lib/public/app.js +29 -0
  14. package/lib/public/copilot-avatar.svg +5 -0
  15. package/lib/public/css/command-palette.css +22 -2
  16. package/lib/public/css/icon-strip.css +29 -13
  17. package/lib/public/css/input.css +56 -0
  18. package/lib/public/css/mates.css +6 -0
  19. package/lib/public/css/menus.css +38 -25
  20. package/lib/public/css/messages.css +9 -0
  21. package/lib/public/css/pane.css +16 -3
  22. package/lib/public/css/pwa-mobile.css +17 -0
  23. package/lib/public/css/session-actions.css +98 -0
  24. package/lib/public/css/title-bar.css +19 -0
  25. package/lib/public/grok-avatar.svg +4 -0
  26. package/lib/public/index.html +29 -7
  27. package/lib/public/junie-avatar.svg +11 -0
  28. package/lib/public/kimi-avatar.svg +4 -0
  29. package/lib/public/modules/agent-config-selects.js +69 -0
  30. package/lib/public/modules/app-header.js +4 -22
  31. package/lib/public/modules/app-messages.js +54 -11
  32. package/lib/public/modules/app-panels.js +36 -83
  33. package/lib/public/modules/app-projects.js +3 -1
  34. package/lib/public/modules/app-rendering.js +19 -4
  35. package/lib/public/modules/background-tasks-ui.js +55 -0
  36. package/lib/public/modules/mate-sidebar.js +11 -3
  37. package/lib/public/modules/model-picker.js +263 -0
  38. package/lib/public/modules/notifications.js +7 -1
  39. package/lib/public/modules/pane-bridge.js +11 -0
  40. package/lib/public/modules/pane-links.js +23 -0
  41. package/lib/public/modules/project-switcher.js +7 -6
  42. package/lib/public/modules/session-actions.js +294 -0
  43. package/lib/public/modules/sidebar-mates.js +1 -1
  44. package/lib/public/modules/sidebar-mobile.js +2 -1
  45. package/lib/public/modules/sidebar-projects.js +34 -43
  46. package/lib/public/modules/sidebar-sessions.js +20 -6
  47. package/lib/public/modules/split-pair-ui.js +16 -77
  48. package/lib/public/modules/split-view.js +3 -0
  49. package/lib/public/modules/tools.js +6 -1
  50. package/lib/public/modules/vendor-priority.js +14 -0
  51. package/lib/public/modules/vendor-selection.js +20 -0
  52. package/lib/public/modules/worker-proposal.js +5 -1
  53. package/lib/public/modules/worktree-location.js +17 -0
  54. package/lib/public/qwen-avatar.svg +4 -0
  55. package/lib/public/style.css +4 -2
  56. package/lib/sdk-bridge.js +99 -56
  57. package/lib/sdk-message-processor.js +26 -4
  58. package/lib/session-handoff-context.js +110 -0
  59. package/lib/session-notes-mcp-server.js +8 -1
  60. package/lib/sessions.js +4 -0
  61. package/lib/worktree.js +9 -4
  62. package/lib/ws-schema.js +9 -1
  63. package/lib/yoke/acp-agent-profiles.js +64 -14
  64. package/lib/yoke/adapters/antigravity.js +417 -0
  65. package/lib/yoke/adapters/claude.js +34 -0
  66. package/lib/yoke/adapters/codex.js +101 -5
  67. package/lib/yoke/adapters/copilot.js +7 -0
  68. package/lib/yoke/adapters/grok.js +7 -0
  69. package/lib/yoke/adapters/junie.js +7 -0
  70. package/lib/yoke/adapters/kimi.js +7 -0
  71. package/lib/yoke/adapters/kiro.js +2 -2
  72. package/lib/yoke/adapters/qwen.js +7 -0
  73. package/lib/yoke/codex-app-server.js +25 -8
  74. package/lib/yoke/index.js +67 -28
  75. package/lib/yoke/instructions.js +3 -0
  76. package/lib/yoke/interface.js +12 -0
  77. package/lib/yoke/vendor-registry.js +61 -6
  78. package/package.json +1 -1
  79. package/lib/public/gemini-avatar.svg +0 -11
  80. package/lib/yoke/adapters/gemini.js +0 -7
@@ -175,6 +175,10 @@ function attachMessageProcessor(ctx) {
175
175
 
176
176
  // Cache slash_commands and model from CLI init message
177
177
  if (parsed.yokeType === "init") {
178
+ var previousBackgroundTaskCount = (session.activeBackgroundTasks || []).length;
179
+ session.activeBackgroundTasks = [];
180
+ sendToSession(session, { type: "active_background_tasks", tasks: [] });
181
+ if (previousBackgroundTaskCount !== 0) sm.broadcastSessionList();
178
182
  var fsSkills = discoverSkillDirs();
179
183
  sm.skillNames = mergeSkills(parsed.skills, fsSkills);
180
184
  if (parsed.slashCommands) {
@@ -192,13 +196,20 @@ function attachMessageProcessor(ctx) {
192
196
  send({ type: "slash_commands", commands: sm.slashCommands });
193
197
  }
194
198
  if (parsed.model) {
195
- sm.currentModel = sm.currentModel || sm._savedDefaultModel || parsed.model;
196
199
  var initVendor = session.vendor || (adapter && adapter.vendor) || "claude";
197
- send({
200
+ sm.defaultModelByVendor = sm.defaultModelByVendor || {};
201
+ sm.defaultModelByVendor[initVendor] = sm.defaultModelByVendor[initVendor] || parsed.model;
202
+ var initModels = getModelsForVendor(initVendor);
203
+ var initModel = session.model || sm.defaultModelByVendor[initVendor] || "";
204
+ if (initModels.length > 0 && !initModels.some(function(entry) {
205
+ return entry === initModel || (entry && (entry.value === initModel || entry.id === initModel || entry.resolvedModel === initModel));
206
+ })) initModel = typeof initModels[0] === "string" ? initModels[0] : (initModels[0].value || initModels[0].id || "");
207
+ sendToSession(session, {
198
208
  type: "model_info",
199
- model: sm.currentModel,
200
- models: getModelsForVendor(initVendor),
209
+ model: initModel,
210
+ models: initModels,
201
211
  vendor: initVendor,
212
+ sessionId: session.localId,
202
213
  availableVendors: sm.availableVendors || [],
203
214
  installedVendors: sm.installedVendors || [],
204
215
  });
@@ -612,6 +623,15 @@ function attachMessageProcessor(ctx) {
612
623
  });
613
624
  }
614
625
 
626
+ } else if (parsed.yokeType === "background_tasks_changed") {
627
+ var previousBackgroundTaskCount = (session.activeBackgroundTasks || []).length;
628
+ var activeBackgroundTasks = Array.isArray(parsed.tasks) ? parsed.tasks : [];
629
+ session.activeBackgroundTasks = activeBackgroundTasks;
630
+ sendToSession(session, { type: "active_background_tasks", tasks: activeBackgroundTasks });
631
+ if (previousBackgroundTaskCount !== activeBackgroundTasks.length) {
632
+ sm.broadcastSessionList();
633
+ }
634
+
615
635
  } else if (parsed.yokeType === "tool_progress") {
616
636
  // Sub-agent tool_progress: forward as activity update
617
637
  var parentId = parsed.parentToolId;
@@ -783,6 +803,8 @@ function attachMessageProcessor(ctx) {
783
803
  // app-server returned an unauthorized/token-revoked error). Trigger the
784
804
  // same login flow as the Claude login-prompt path.
785
805
  session.isProcessing = false;
806
+ session._awaitingTurnResult = false;
807
+ session._queuedTurnCount = 0;
786
808
  onProcessingChanged();
787
809
  emitAuthRequired(session);
788
810
 
@@ -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
+ };
@@ -2,7 +2,14 @@
2
2
 
3
3
  var buildShape = require("./session-spawn-mcp-server").buildShape;
4
4
 
5
- var MEMORY_CONTRACT = "The sticky-note board persists across sessions and is a user-facing artifact shared with people and Clay agents, not private agent scratch space. 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. Good notes capture an unresolved commitment, durable product decision, user preference, constraint, or handoff that will materially change future work. 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. 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.";
5
+ var MEMORY_CONTRACT =
6
+ "The sticky-note board persists across sessions and is a user-facing artifact shared with people and Clay agents, not private agent scratch space. " +
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
+ "Good notes capture an unresolved commitment, durable product decision, user preference, constraint, or handoff that will materially change future work. " +
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. " +
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. " +
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.";
6
13
 
7
14
  function getToolDefs(handlers) {
8
15
  return [
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/worktree.js CHANGED
@@ -43,9 +43,14 @@ function isWorktree(projectPath) {
43
43
  }
44
44
  }
45
45
 
46
+ function isPathInside(parentPath, candidatePath) {
47
+ var relative = path.relative(path.resolve(parentPath), path.resolve(candidatePath));
48
+ return relative !== "" && relative !== ".." && relative.indexOf(".." + path.sep) !== 0 && !path.isAbsolute(relative);
49
+ }
50
+
46
51
  // Scan worktrees for a given project path
47
- // Returns array of { path, branch, bare, detached, accessible }
48
- // accessible = true if worktree path is inside parentPath
52
+ // Returns array of { path, branch, bare, detached, external }
53
+ // external = true when Git registered the worktree outside the main project folder
49
54
  function scanWorktrees(projectPath) {
50
55
  var resolvedParent = path.resolve(projectPath);
51
56
  try {
@@ -63,7 +68,7 @@ function scanWorktrees(projectPath) {
63
68
  if (wt.bare) continue;
64
69
  var resolvedWt = path.resolve(wt.path);
65
70
  if (resolvedWt === resolvedParent) continue;
66
- wt.accessible = resolvedWt.indexOf(resolvedParent + path.sep) === 0;
71
+ wt.external = !isPathInside(resolvedParent, resolvedWt);
67
72
  wt.dirName = path.basename(wt.path);
68
73
  results.push(wt);
69
74
  }
@@ -131,4 +136,4 @@ function removeWorktree(projectPath, worktreeDirName) {
131
136
  }
132
137
  }
133
138
 
134
- module.exports = { scanWorktrees: scanWorktrees, createWorktree: createWorktree, removeWorktree: removeWorktree, isWorktree: isWorktree };
139
+ module.exports = { scanWorktrees: scanWorktrees, createWorktree: createWorktree, removeWorktree: removeWorktree, isWorktree: isWorktree, isPathInside: isPathInside };
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-sessions.js", description: "Set the model for the current session" },
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,21 +96,16 @@ 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
- gemini: {
101
- vendor: "gemini",
102
- displayName: "Gemini CLI",
103
- binaryName: "gemini",
104
- overrideName: "GEMINI_CLI_PATH",
105
- args: ["--acp", "--approval-mode=default"],
106
- defaultModels: ["auto"],
107
- defaultModel: "auto",
108
- // Gemini advertises loadSession, but releases through 0.46.0 could load
109
- // the transcript without restoring the model's conversation memory.
110
- // Keep resume gated until Clay's live contract test proves it reliable.
111
- sessionResume: false,
112
- permissionModeGuaranteed: true,
113
- },
114
109
  opencode: {
115
110
  vendor: "opencode",
116
111
  displayName: "OpenCode",
@@ -161,6 +156,61 @@ var ACP_AGENT_PROFILES = {
161
156
  return Promise.resolve();
162
157
  },
163
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
+ },
164
214
  };
165
215
 
166
216
  function getAcpAgentProfile(vendor) {