clay-server 2.34.0-beta.2 → 2.34.0-beta.4
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/ask-user-mcp-server.js +120 -0
- package/lib/daemon.js +97 -38
- package/lib/mate-datastore.js +27 -5
- package/lib/mates.js +2 -2
- package/lib/project-connection.js +15 -0
- package/lib/project-mate-datastore.js +16 -2
- package/lib/project-sessions.js +73 -4
- package/lib/project.js +75 -8
- package/lib/public/css/mates.css +94 -52
- package/lib/public/index.html +17 -47
- package/lib/public/modules/app-dm.js +0 -2
- package/lib/public/modules/app-messages.js +3 -5
- package/lib/public/modules/app-rendering.js +0 -2
- package/lib/public/modules/mate-datastore-ui.js +108 -98
- package/lib/public/modules/mate-sidebar.js +0 -9
- package/lib/public/modules/mate-wizard.js +15 -15
- package/lib/public/modules/tools.js +21 -3
- package/lib/sdk-bridge.js +20 -19
- package/lib/sdk-message-processor.js +14 -3
- package/lib/server.js +28 -72
- package/lib/sessions.js +67 -20
- package/lib/yoke/adapters/codex.js +318 -54
- package/lib/yoke/index.js +73 -35
- package/lib/yoke/instructions.js +0 -1
- package/lib/yoke/mcp-bridge-server.js +14 -6
- package/package.json +1 -2
- package/lib/yoke/adapters/gemini.js +0 -709
|
@@ -7,29 +7,85 @@ var panelEl = null;
|
|
|
7
7
|
var tableListEl = null;
|
|
8
8
|
var tableNameEl = null;
|
|
9
9
|
var tableSchemaEl = null;
|
|
10
|
-
var queryInputEl = null;
|
|
11
|
-
var paramsInputEl = null;
|
|
12
10
|
var resultEl = null;
|
|
13
11
|
var statusEl = null;
|
|
14
12
|
var dataBtnEl = null;
|
|
15
|
-
var
|
|
16
|
-
var memoryEl = null;
|
|
17
|
-
var knowledgeEl = null;
|
|
13
|
+
var mainColumnEl = null;
|
|
18
14
|
var currentTables = [];
|
|
19
15
|
var currentTable = null;
|
|
20
16
|
var panelOpen = false;
|
|
17
|
+
var routingToScheduler = false;
|
|
21
18
|
|
|
22
19
|
function sendWs(msg) {
|
|
23
20
|
var ws = wsGetter ? wsGetter() : getWs();
|
|
24
21
|
if (ws && ws.readyState === 1) ws.send(JSON.stringify(msg));
|
|
25
22
|
}
|
|
26
23
|
|
|
24
|
+
function quoteIdentifier(name) {
|
|
25
|
+
return '"' + String(name || "").replace(/"/g, '""') + '"';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function ensurePanel() {
|
|
29
|
+
if (panelEl) return;
|
|
30
|
+
mainColumnEl = document.getElementById("main-column");
|
|
31
|
+
if (!mainColumnEl) return;
|
|
32
|
+
|
|
33
|
+
panelEl = document.createElement("div");
|
|
34
|
+
panelEl.id = "mate-datastore-panel";
|
|
35
|
+
panelEl.className = "hidden";
|
|
36
|
+
|
|
37
|
+
panelEl.innerHTML =
|
|
38
|
+
'<div class="scheduler-top-bar mate-datastore-top-bar">' +
|
|
39
|
+
'<span class="scheduler-top-title mate-datastore-top-title"><i data-lucide="database"></i>Data</span>' +
|
|
40
|
+
'<div class="mate-datastore-top-actions">' +
|
|
41
|
+
'<button id="mate-db-refresh-btn" class="scheduler-close-btn" type="button" title="Refresh tables"><i data-lucide="refresh-cw"></i></button>' +
|
|
42
|
+
'<button id="mate-db-back-btn" class="scheduler-close-btn" type="button" title="Close"><i data-lucide="x"></i></button>' +
|
|
43
|
+
'</div>' +
|
|
44
|
+
'</div>' +
|
|
45
|
+
'<div class="mate-db-status" id="mate-db-status"></div>' +
|
|
46
|
+
'<div class="mate-db-layout">' +
|
|
47
|
+
'<div class="mate-db-table-column">' +
|
|
48
|
+
'<div class="mate-db-section-title">Objects</div>' +
|
|
49
|
+
'<div id="mate-db-table-list" class="mate-db-table-list"></div>' +
|
|
50
|
+
'</div>' +
|
|
51
|
+
'<div class="mate-db-detail">' +
|
|
52
|
+
'<div class="mate-db-section-title" id="mate-db-table-name">No table selected</div>' +
|
|
53
|
+
'<pre id="mate-db-table-schema" class="mate-db-table-schema"></pre>' +
|
|
54
|
+
'<div class="mate-db-section-title">Rows</div>' +
|
|
55
|
+
'<pre id="mate-db-result" class="mate-db-result"></pre>' +
|
|
56
|
+
'</div>' +
|
|
57
|
+
'</div>';
|
|
58
|
+
|
|
59
|
+
mainColumnEl.appendChild(panelEl);
|
|
60
|
+
tableListEl = document.getElementById("mate-db-table-list");
|
|
61
|
+
tableNameEl = document.getElementById("mate-db-table-name");
|
|
62
|
+
tableSchemaEl = document.getElementById("mate-db-table-schema");
|
|
63
|
+
resultEl = document.getElementById("mate-db-result");
|
|
64
|
+
statusEl = document.getElementById("mate-db-status");
|
|
65
|
+
|
|
66
|
+
var refreshBtn = document.getElementById("mate-db-refresh-btn");
|
|
67
|
+
var backBtn = document.getElementById("mate-db-back-btn");
|
|
68
|
+
|
|
69
|
+
if (refreshBtn) {
|
|
70
|
+
refreshBtn.addEventListener("click", function () {
|
|
71
|
+
requestTables();
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (backBtn) {
|
|
76
|
+
backBtn.addEventListener("click", function () {
|
|
77
|
+
setSectionVisibility(false);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
refreshIcons();
|
|
82
|
+
}
|
|
83
|
+
|
|
27
84
|
function setSectionVisibility(open) {
|
|
85
|
+
ensurePanel();
|
|
28
86
|
panelOpen = open;
|
|
29
87
|
if (panelEl) panelEl.classList.toggle("hidden", !open);
|
|
30
|
-
if (
|
|
31
|
-
if (memoryEl) memoryEl.classList.toggle("hidden", true);
|
|
32
|
-
if (knowledgeEl) knowledgeEl.classList.toggle("hidden", true);
|
|
88
|
+
if (mainColumnEl) mainColumnEl.classList.toggle("mate-datastore-open", open);
|
|
33
89
|
if (dataBtnEl) dataBtnEl.classList.toggle("active", open);
|
|
34
90
|
refreshIcons();
|
|
35
91
|
}
|
|
@@ -49,6 +105,21 @@ function renderResult(payload) {
|
|
|
49
105
|
resultEl.textContent = JSON.stringify(payload, null, 2);
|
|
50
106
|
}
|
|
51
107
|
|
|
108
|
+
function formatColumns(columns) {
|
|
109
|
+
var list = Array.isArray(columns) ? columns : [];
|
|
110
|
+
if (!list.length) return "No column information available.";
|
|
111
|
+
var lines = [];
|
|
112
|
+
for (var i = 0; i < list.length; i++) {
|
|
113
|
+
var col = list[i] || {};
|
|
114
|
+
var line = (col.name || "?") + " " + (col.type || "");
|
|
115
|
+
if (col.pk) line += " PRIMARY KEY";
|
|
116
|
+
if (col.notnull) line += " NOT NULL";
|
|
117
|
+
if (typeof col.dflt_value !== "undefined" && col.dflt_value !== null) line += " DEFAULT " + col.dflt_value;
|
|
118
|
+
lines.push(line.trim());
|
|
119
|
+
}
|
|
120
|
+
return lines.join("\n");
|
|
121
|
+
}
|
|
122
|
+
|
|
52
123
|
function renderTableList(objects) {
|
|
53
124
|
currentTables = objects || [];
|
|
54
125
|
if (!tableListEl) return;
|
|
@@ -101,94 +172,43 @@ function selectTable(tableName) {
|
|
|
101
172
|
if (obj.type !== "table" && obj.type !== "view") {
|
|
102
173
|
renderStatus("Selected " + (obj.type || "object") + " " + tableName + ".", "ok");
|
|
103
174
|
if (tableNameEl) tableNameEl.textContent = tableName;
|
|
104
|
-
if (tableSchemaEl) tableSchemaEl.textContent = obj.
|
|
175
|
+
if (tableSchemaEl) tableSchemaEl.textContent = (obj.type || "object") + ": " + tableName;
|
|
105
176
|
renderResult(obj);
|
|
106
177
|
return;
|
|
107
178
|
}
|
|
108
179
|
renderStatus("Loading " + tableName + "...", "info");
|
|
109
180
|
sendWs({ type: "mate_db_describe", table: tableName });
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function parseParams() {
|
|
114
|
-
if (!paramsInputEl) return [];
|
|
115
|
-
var text = paramsInputEl.value.trim();
|
|
116
|
-
if (!text) return [];
|
|
117
|
-
try {
|
|
118
|
-
var parsed = JSON.parse(text);
|
|
119
|
-
if (Array.isArray(parsed)) return parsed;
|
|
120
|
-
renderStatus("Parameters must be a JSON array.", "error");
|
|
121
|
-
} catch (e) {
|
|
122
|
-
renderStatus("Parameters must be valid JSON.", "error");
|
|
123
|
-
}
|
|
124
|
-
return null;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function sendQueryMessage(type) {
|
|
128
|
-
var sql = queryInputEl ? queryInputEl.value : "";
|
|
129
|
-
var params = parseParams();
|
|
130
|
-
if (params === null) return;
|
|
131
|
-
if (!sql || !sql.trim()) {
|
|
132
|
-
renderStatus("Enter SQL before running it.", "error");
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
sendWs({ type: type, sql: sql, params: params });
|
|
181
|
+
sendWs({ type: "mate_db_query", sql: "SELECT * FROM " + quoteIdentifier(tableName) + " LIMIT 100", params: [] });
|
|
136
182
|
}
|
|
137
183
|
|
|
138
184
|
export function initMateDatastoreUI(getWsFn) {
|
|
139
185
|
wsGetter = getWsFn;
|
|
140
|
-
panelEl = document.getElementById("mate-sidebar-datastore");
|
|
141
|
-
tableListEl = document.getElementById("mate-db-table-list");
|
|
142
|
-
tableNameEl = document.getElementById("mate-db-table-name");
|
|
143
|
-
tableSchemaEl = document.getElementById("mate-db-table-schema");
|
|
144
|
-
queryInputEl = document.getElementById("mate-db-query");
|
|
145
|
-
paramsInputEl = document.getElementById("mate-db-params");
|
|
146
|
-
resultEl = document.getElementById("mate-db-result");
|
|
147
|
-
statusEl = document.getElementById("mate-db-status");
|
|
148
186
|
dataBtnEl = document.getElementById("mate-data-btn");
|
|
149
|
-
|
|
150
|
-
memoryEl = document.getElementById("mate-sidebar-memory");
|
|
151
|
-
knowledgeEl = document.getElementById("mate-sidebar-knowledge");
|
|
152
|
-
|
|
153
|
-
var refreshBtn = document.getElementById("mate-db-refresh-btn");
|
|
154
|
-
var queryBtn = document.getElementById("mate-db-query-btn");
|
|
155
|
-
var execBtn = document.getElementById("mate-db-exec-btn");
|
|
156
|
-
var backBtn = document.getElementById("mate-db-back-btn");
|
|
157
|
-
|
|
158
|
-
if (dataBtnEl) {
|
|
159
|
-
dataBtnEl.addEventListener("click", function () {
|
|
160
|
-
if (panelOpen) {
|
|
161
|
-
setSectionVisibility(false);
|
|
162
|
-
} else {
|
|
163
|
-
setSectionVisibility(true);
|
|
164
|
-
requestTables();
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
}
|
|
187
|
+
if (!dataBtnEl) return;
|
|
168
188
|
|
|
169
|
-
|
|
170
|
-
|
|
189
|
+
dataBtnEl.addEventListener("click", function () {
|
|
190
|
+
if (panelOpen) {
|
|
191
|
+
setSectionVisibility(false);
|
|
192
|
+
} else {
|
|
193
|
+
setSectionVisibility(true);
|
|
171
194
|
requestTables();
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (queryBtn) {
|
|
176
|
-
queryBtn.addEventListener("click", function () {
|
|
177
|
-
sendQueryMessage("mate_db_query");
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (execBtn) {
|
|
182
|
-
execBtn.addEventListener("click", function () {
|
|
183
|
-
sendQueryMessage("mate_db_exec");
|
|
184
|
-
});
|
|
185
|
-
}
|
|
195
|
+
}
|
|
196
|
+
});
|
|
186
197
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
198
|
+
document.addEventListener("click", function (e) {
|
|
199
|
+
var btn = e.target && e.target.closest ? e.target.closest("#scheduler-btn, #mate-scheduler-btn") : null;
|
|
200
|
+
if (!btn || !panelOpen || routingToScheduler) return;
|
|
201
|
+
e.preventDefault();
|
|
202
|
+
e.stopPropagation();
|
|
203
|
+
if (typeof e.stopImmediatePropagation === "function") e.stopImmediatePropagation();
|
|
204
|
+
routingToScheduler = true;
|
|
205
|
+
setSectionVisibility(false);
|
|
206
|
+
setTimeout(function () {
|
|
207
|
+
var schedulerBtn = document.getElementById("scheduler-btn");
|
|
208
|
+
if (schedulerBtn) schedulerBtn.click();
|
|
209
|
+
routingToScheduler = false;
|
|
210
|
+
}, 0);
|
|
211
|
+
}, true);
|
|
192
212
|
|
|
193
213
|
setSectionVisibility(false);
|
|
194
214
|
}
|
|
@@ -234,8 +254,7 @@ export function handleMateDatastoreDescribeResult(msg) {
|
|
|
234
254
|
}
|
|
235
255
|
renderStatus(msg.warning || ("Described " + (msg.table || "table") + "."), msg.warning ? "warn" : "ok");
|
|
236
256
|
if (tableNameEl) tableNameEl.textContent = msg.table || "Table";
|
|
237
|
-
if (tableSchemaEl) tableSchemaEl.textContent = msg.
|
|
238
|
-
renderResult(msg);
|
|
257
|
+
if (tableSchemaEl) tableSchemaEl.textContent = formatColumns(msg.columns);
|
|
239
258
|
}
|
|
240
259
|
|
|
241
260
|
export function handleMateDatastoreQueryResult(msg) {
|
|
@@ -244,19 +263,10 @@ export function handleMateDatastoreQueryResult(msg) {
|
|
|
244
263
|
renderResult(msg);
|
|
245
264
|
return;
|
|
246
265
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
export function handleMateDatastoreExecResult(msg) {
|
|
252
|
-
if (msg.ok === false) {
|
|
253
|
-
renderStatus(msg.message || "Execution failed.", "error");
|
|
254
|
-
renderResult(msg);
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
renderStatus(msg.warning || ("Applied " + (msg.changes || 0) + " change(s)."), msg.warning ? "warn" : "ok");
|
|
258
|
-
renderResult(msg);
|
|
259
|
-
requestTables();
|
|
266
|
+
var status = "Showing " + (msg.rows ? msg.rows.length : 0) + " row(s).";
|
|
267
|
+
if (msg.truncated) status = status.replace(".", " (truncated).");
|
|
268
|
+
renderStatus(msg.warning || status, msg.warning ? "warn" : "ok");
|
|
269
|
+
renderResult(msg.rows || []);
|
|
260
270
|
}
|
|
261
271
|
|
|
262
272
|
export function handleMateDatastoreError(msg) {
|
|
@@ -225,15 +225,6 @@ export function showMateSidebar(mateId, mateData) {
|
|
|
225
225
|
if (sd.communicationStyle && sd.communicationStyle.length > 0) {
|
|
226
226
|
seedTooltip.appendChild(makeSeedTags("Style", sd.communicationStyle.map(function (s) { return s.replace(/_/g, " "); })));
|
|
227
227
|
}
|
|
228
|
-
if (sd.autonomy) {
|
|
229
|
-
var autonomyLabels = {
|
|
230
|
-
always_ask: "Ask me everything",
|
|
231
|
-
minor_stuff_ok: "Small stuff is fine",
|
|
232
|
-
mostly_autonomous: "Mostly free",
|
|
233
|
-
fully_autonomous: "Full freedom",
|
|
234
|
-
};
|
|
235
|
-
seedTooltip.appendChild(makeSeedRow("Autonomy", autonomyLabels[sd.autonomy] || sd.autonomy.replace(/_/g, " ")));
|
|
236
|
-
}
|
|
237
228
|
}
|
|
238
229
|
|
|
239
230
|
// Clear session list
|
|
@@ -6,7 +6,7 @@ var mateWizardData = {
|
|
|
6
6
|
relationship: null,
|
|
7
7
|
activity: [],
|
|
8
8
|
communicationStyle: [],
|
|
9
|
-
|
|
9
|
+
vendor: "claude",
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
var _sendWs = null;
|
|
@@ -85,19 +85,19 @@ export function initMateWizard(sendWs, onMateCreated) {
|
|
|
85
85
|
})(styleCards[i]);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
// Step 4:
|
|
89
|
-
var
|
|
90
|
-
for (var i = 0; i <
|
|
88
|
+
// Step 4: Vendor button clicks
|
|
89
|
+
var vendorBtns = document.querySelectorAll("#mate-wizard .mate-vendor-option-btn");
|
|
90
|
+
for (var i = 0; i < vendorBtns.length; i++) {
|
|
91
91
|
(function (btn) {
|
|
92
92
|
btn.addEventListener("click", function () {
|
|
93
|
-
var allBtns = document.querySelectorAll("#mate-wizard .mate-
|
|
93
|
+
var allBtns = document.querySelectorAll("#mate-wizard .mate-vendor-option-btn");
|
|
94
94
|
for (var j = 0; j < allBtns.length; j++) {
|
|
95
95
|
allBtns[j].classList.remove("active");
|
|
96
96
|
}
|
|
97
97
|
btn.classList.add("active");
|
|
98
|
-
mateWizardData.
|
|
98
|
+
mateWizardData.vendor = btn.dataset.value;
|
|
99
99
|
});
|
|
100
|
-
})(
|
|
100
|
+
})(vendorBtns[i]);
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
|
|
@@ -107,7 +107,7 @@ export function openMateWizard() {
|
|
|
107
107
|
relationship: null,
|
|
108
108
|
activity: [],
|
|
109
109
|
communicationStyle: [],
|
|
110
|
-
|
|
110
|
+
vendor: "claude",
|
|
111
111
|
};
|
|
112
112
|
|
|
113
113
|
// Reset UI
|
|
@@ -136,12 +136,12 @@ export function openMateWizard() {
|
|
|
136
136
|
styleCards[i].classList.remove("selected");
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
// Reset
|
|
140
|
-
var
|
|
141
|
-
for (var i = 0; i <
|
|
142
|
-
|
|
143
|
-
if (
|
|
144
|
-
|
|
139
|
+
// Reset vendor
|
|
140
|
+
var vendorBtns = el.querySelectorAll(".mate-vendor-option-btn");
|
|
141
|
+
for (var i = 0; i < vendorBtns.length; i++) {
|
|
142
|
+
vendorBtns[i].classList.remove("active");
|
|
143
|
+
if (vendorBtns[i].dataset.value === "claude") {
|
|
144
|
+
vendorBtns[i].classList.add("active");
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
147
|
|
|
@@ -220,7 +220,7 @@ function collectMateWizardData() {
|
|
|
220
220
|
mateWizardData.communicationStyle.push(selectedStyles[i].dataset.value);
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
//
|
|
223
|
+
// Vendor (already set via button clicks)
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
function mateWizardNext() {
|
|
@@ -799,7 +799,7 @@ function resolvePermissionIdentity(mateId, vendor) {
|
|
|
799
799
|
}
|
|
800
800
|
}
|
|
801
801
|
// Project chat: use vendor name and avatar
|
|
802
|
-
var vendorAvatars = { claude: "/claude-code-avatar.png", codex: "/codex-avatar.png"
|
|
802
|
+
var vendorAvatars = { claude: "/claude-code-avatar.png", codex: "/codex-avatar.png" };
|
|
803
803
|
var vendorName = (vendor && VENDOR_NAMES[vendor]) || VENDOR_NAMES.claude;
|
|
804
804
|
return {
|
|
805
805
|
name: vendorName,
|
|
@@ -1587,14 +1587,32 @@ export function stopThinking(duration) {
|
|
|
1587
1587
|
} else {
|
|
1588
1588
|
currentThinking.el.querySelector(".thinking-duration").textContent = " " + secs.toFixed(1) + "s";
|
|
1589
1589
|
}
|
|
1590
|
-
//
|
|
1590
|
+
// If no thinking text was streamed (e.g. Codex reasoning items arrive
|
|
1591
|
+
// with encrypted/hidden content, or Claude without extended-thinking),
|
|
1592
|
+
// the expand affordance is misleading because there's nothing inside.
|
|
1593
|
+
// Strip the chevron and the click handler so the header reads as a
|
|
1594
|
+
// plain label.
|
|
1595
|
+
var hasContent = !!(currentThinking.fullText && currentThinking.fullText.length > 0);
|
|
1596
|
+
if (!hasContent) {
|
|
1597
|
+
currentThinking.el.classList.add("empty");
|
|
1598
|
+
var chev = currentThinking.el.querySelector(".thinking-chevron");
|
|
1599
|
+
if (chev) chev.style.display = "none";
|
|
1600
|
+
var hdr = currentThinking.el.querySelector(".thinking-header");
|
|
1601
|
+
if (hdr) {
|
|
1602
|
+
hdr.style.cursor = "default";
|
|
1603
|
+
// Replace click listener by cloning the node (cheapest way to strip listeners).
|
|
1604
|
+
var clone = hdr.cloneNode(true);
|
|
1605
|
+
hdr.parentNode.replaceChild(clone, hdr);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
// In mate mode: hide sparkle activity, show compact thinking header.
|
|
1591
1609
|
if (currentThinking.el.classList.contains("mate-thinking")) {
|
|
1592
1610
|
var actRow = currentThinking.el.querySelector(".mate-thinking-activity");
|
|
1593
1611
|
if (actRow) actRow.style.display = "none";
|
|
1594
1612
|
var header = currentThinking.el.querySelector(".thinking-header");
|
|
1595
1613
|
if (header) {
|
|
1596
1614
|
header.style.display = "";
|
|
1597
|
-
header.style.cursor = "pointer";
|
|
1615
|
+
header.style.cursor = hasContent ? "pointer" : "default";
|
|
1598
1616
|
}
|
|
1599
1617
|
}
|
|
1600
1618
|
currentThinking = null;
|
package/lib/sdk-bridge.js
CHANGED
|
@@ -1183,11 +1183,13 @@ function createSDKBridge(opts) {
|
|
|
1183
1183
|
else if (session.acceptEditsAfterStart) effectiveDefault = "acceptEdits";
|
|
1184
1184
|
else effectiveDefault = globalMode;
|
|
1185
1185
|
var modeToApply = session._loopPermissionMode || effectiveDefault;
|
|
1186
|
-
if (session.acceptEditsAfterStart) delete session.acceptEditsAfterStart;
|
|
1187
1186
|
if (modeToApply && modeToApply !== "default") {
|
|
1188
1187
|
claudeOpts.permissionMode = modeToApply;
|
|
1189
1188
|
}
|
|
1190
1189
|
}
|
|
1190
|
+
// Clear one-shot acceptEditsAfterStart regardless of which branch ran above,
|
|
1191
|
+
// so the flag does not linger into subsequent turns.
|
|
1192
|
+
if (session.acceptEditsAfterStart) delete session.acceptEditsAfterStart;
|
|
1191
1193
|
if (session.cliSessionId && session.lastRewindUuid) {
|
|
1192
1194
|
claudeOpts.resumeSessionAt = session.lastRewindUuid;
|
|
1193
1195
|
delete session.lastRewindUuid;
|
|
@@ -1208,10 +1210,17 @@ function createSDKBridge(opts) {
|
|
|
1208
1210
|
}
|
|
1209
1211
|
}
|
|
1210
1212
|
|
|
1211
|
-
//
|
|
1213
|
+
// Pick a model that belongs to the session's vendor. sm.currentModel is
|
|
1214
|
+
// shared project-wide, so a Codex session that last set it to
|
|
1215
|
+
// "gpt-5.4-mini" would otherwise leak into a Claude session in the same
|
|
1216
|
+
// project (or in another session that switches vendor to claude) and
|
|
1217
|
+
// Claude would reject the unknown model. We validate against the
|
|
1218
|
+
// session vendor's model list regardless of which vendor happens to be
|
|
1219
|
+
// the project's default adapter.
|
|
1212
1220
|
var queryModel = ls.model || sm.currentModel || undefined;
|
|
1213
|
-
|
|
1214
|
-
|
|
1221
|
+
var sessionVendor = session.vendor || (adapter && adapter.vendor) || null;
|
|
1222
|
+
if (sessionVendor) {
|
|
1223
|
+
var vendorModels = (sm.modelsByVendor && sm.modelsByVendor[sessionVendor]) || [];
|
|
1215
1224
|
if (vendorModels.length > 0 && queryModel && vendorModels.indexOf(queryModel) === -1) {
|
|
1216
1225
|
queryModel = vendorModels[0];
|
|
1217
1226
|
}
|
|
@@ -1441,22 +1450,14 @@ function createSDKBridge(opts) {
|
|
|
1441
1450
|
}
|
|
1442
1451
|
}
|
|
1443
1452
|
|
|
1444
|
-
//
|
|
1453
|
+
// Non-default adapters are NOT eagerly initialized here. Doing so used
|
|
1454
|
+
// to spawn a CodexAppServer and an mcp-bridge child per project even
|
|
1455
|
+
// when the user never touched that vendor. Lazy paths cover the gap:
|
|
1456
|
+
// - get_vendor_models (project.js) inits a vendor when the user
|
|
1457
|
+
// opens its model picker.
|
|
1458
|
+
// - ensureVendorReady (this file) inits a vendor when a session
|
|
1459
|
+
// actually issues a query with it.
|
|
1445
1460
|
sm.modelsByVendor = sm.modelsByVendor || {};
|
|
1446
|
-
var otherVendors = Object.keys(adapters).filter(function(v) {
|
|
1447
|
-
return v !== defaultVendor && !sm.modelsByVendor[v];
|
|
1448
|
-
});
|
|
1449
|
-
for (var i = 0; i < otherVendors.length; i++) {
|
|
1450
|
-
(function(v) {
|
|
1451
|
-
adapters[v].init({ cwd: cwd, clayPort: clayPort, clayTls: clayTls, clayAuthToken: clayAuthToken, slug: slug }).then(function(r) {
|
|
1452
|
-
sm.modelsByVendor[v] = r.models || [];
|
|
1453
|
-
sm.capabilitiesByVendor[v] = r.capabilities || {};
|
|
1454
|
-
if (r.slashCommands) sm.setSlashCommandsForVendor(v, r.slashCommands);
|
|
1455
|
-
}).catch(function(e) {
|
|
1456
|
-
console.error("[sdk-bridge] warmup: " + v + " init failed:", e.message || e);
|
|
1457
|
-
});
|
|
1458
|
-
})(otherVendors[i]);
|
|
1459
|
-
}
|
|
1460
1461
|
|
|
1461
1462
|
// Detect installed vendors per-user (binary existence check)
|
|
1462
1463
|
sm.installedVendors = detectInstalledVendors(linuxUser);
|
|
@@ -370,12 +370,23 @@ function attachMessageProcessor(ctx) {
|
|
|
370
370
|
session.sentToolResults = {};
|
|
371
371
|
session.pendingPermissions = {};
|
|
372
372
|
session.pendingElicitations = {};
|
|
373
|
-
// Record ask_user_answered for any leftover pending questions so replay pairs correctly
|
|
373
|
+
// Record ask_user_answered for any leftover pending questions so replay pairs correctly.
|
|
374
|
+
// EXCEPTION: "mcp" mode entries are stateless — the tool returned immediately and the
|
|
375
|
+
// turn is expected to end while the card is still awaiting the user's answer. Those
|
|
376
|
+
// entries must survive across turns so the eventual ask_user_response can inject the
|
|
377
|
+
// answer as the next user message. Only blocking modes (Claude canUseTool) get closed.
|
|
374
378
|
var leftoverAskIds = Object.keys(session.pendingAskUser);
|
|
379
|
+
var keptAskUser = {};
|
|
375
380
|
for (var lai = 0; lai < leftoverAskIds.length; lai++) {
|
|
376
|
-
|
|
381
|
+
var lid = leftoverAskIds[lai];
|
|
382
|
+
var lentry = session.pendingAskUser[lid];
|
|
383
|
+
if (lentry && lentry.mode === "mcp") {
|
|
384
|
+
keptAskUser[lid] = lentry;
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
sendAndRecord(session, { type: "ask_user_answered", toolId: lid });
|
|
377
388
|
}
|
|
378
|
-
session.pendingAskUser =
|
|
389
|
+
session.pendingAskUser = keptAskUser;
|
|
379
390
|
session.activeTaskToolIds = {};
|
|
380
391
|
session.taskIdMap = {};
|
|
381
392
|
// Only clear rateLimitResetsAt on genuine success (non-zero cost).
|
package/lib/server.js
CHANGED
|
@@ -459,75 +459,6 @@ function createServer(opts) {
|
|
|
459
459
|
return;
|
|
460
460
|
}
|
|
461
461
|
|
|
462
|
-
// --- Global MCP bridge endpoint (localhost only) ---
|
|
463
|
-
// Used by Codex mcp-bridge-server.js which can't know the active project slug.
|
|
464
|
-
// Aggregates MCP tools from all project contexts.
|
|
465
|
-
if (req.method === "POST" && fullUrl === "/api/mcp-bridge") {
|
|
466
|
-
var mcpRemoteAddr = req.socket.remoteAddress || "";
|
|
467
|
-
var mcpIsLocal = mcpRemoteAddr === "127.0.0.1" || mcpRemoteAddr === "::1" || mcpRemoteAddr === "::ffff:127.0.0.1";
|
|
468
|
-
if (!mcpIsLocal) {
|
|
469
|
-
res.writeHead(403, { "Content-Type": "application/json" });
|
|
470
|
-
res.end('{"error":"Forbidden"}');
|
|
471
|
-
return;
|
|
472
|
-
}
|
|
473
|
-
var parseJsonBody = function(r) {
|
|
474
|
-
return new Promise(function(resolve, reject) {
|
|
475
|
-
var chunks = [];
|
|
476
|
-
r.on("data", function(c) { chunks.push(c); });
|
|
477
|
-
r.on("end", function() {
|
|
478
|
-
try { resolve(JSON.parse(Buffer.concat(chunks).toString("utf8"))); }
|
|
479
|
-
catch(e) { reject(e); }
|
|
480
|
-
});
|
|
481
|
-
});
|
|
482
|
-
};
|
|
483
|
-
parseJsonBody(req).then(function(body) {
|
|
484
|
-
// Find the first project context that has a MCP bridge handler
|
|
485
|
-
var handler = null;
|
|
486
|
-
projects.forEach(function(ctx) {
|
|
487
|
-
if (handler) return;
|
|
488
|
-
if (ctx.getMcpBridgeHandler) {
|
|
489
|
-
var h = ctx.getMcpBridgeHandler();
|
|
490
|
-
if (h) handler = h;
|
|
491
|
-
}
|
|
492
|
-
});
|
|
493
|
-
if (!handler) {
|
|
494
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
495
|
-
res.end('{"error":"No MCP bridge handler available"}');
|
|
496
|
-
return;
|
|
497
|
-
}
|
|
498
|
-
if (body.action === "list_tools") {
|
|
499
|
-
handler.listTools().then(function(tools) {
|
|
500
|
-
var serverCounts = {};
|
|
501
|
-
for (var ti = 0; ti < tools.length; ti++) {
|
|
502
|
-
serverCounts[tools[ti].server] = (serverCounts[tools[ti].server] || 0) + 1;
|
|
503
|
-
}
|
|
504
|
-
console.log("[mcp-bridge-http] global list_tools:", tools.length, "tools -", Object.keys(serverCounts).map(function(s) { return s + "(" + serverCounts[s] + ")"; }).join(", ") || "(none)");
|
|
505
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
506
|
-
res.end(JSON.stringify({ tools: tools }));
|
|
507
|
-
}).catch(function(err) {
|
|
508
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
509
|
-
res.end(JSON.stringify({ error: err.message }));
|
|
510
|
-
});
|
|
511
|
-
} else if (body.action === "call_tool") {
|
|
512
|
-
console.log("[mcp-bridge-http] global call_tool:", body.server + "/" + body.tool);
|
|
513
|
-
handler.callTool(body.server, body.tool, body.args || {}).then(function(result) {
|
|
514
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
515
|
-
res.end(JSON.stringify({ result: result }));
|
|
516
|
-
}).catch(function(err) {
|
|
517
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
518
|
-
res.end(JSON.stringify({ error: err.message }));
|
|
519
|
-
});
|
|
520
|
-
} else {
|
|
521
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
522
|
-
res.end('{"error":"Unknown action"}');
|
|
523
|
-
}
|
|
524
|
-
}).catch(function() {
|
|
525
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
526
|
-
res.end('{"error":"Invalid JSON"}');
|
|
527
|
-
});
|
|
528
|
-
return;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
462
|
// --- Skills routes (delegated to server-skills) ---
|
|
532
463
|
if (skills.handleRequest(req, res, fullUrl)) return;
|
|
533
464
|
|
|
@@ -1090,7 +1021,11 @@ function createServer(opts) {
|
|
|
1090
1021
|
getProject: function (s) { return projects.get(s) || null; },
|
|
1091
1022
|
});
|
|
1092
1023
|
projects.set(slug, ctx);
|
|
1093
|
-
ctx.warmup()
|
|
1024
|
+
// ctx.warmup() is now deferred to the first websocket connection into
|
|
1025
|
+
// this project (see project-connection.js handleConnection). Warming
|
|
1026
|
+
// every project at startup spawned a CodexAppServer and an mcp-bridge
|
|
1027
|
+
// child for each one, which cost 30+ processes on daemons with many
|
|
1028
|
+
// projects/mates even though the user typically only opens one.
|
|
1094
1029
|
// Schedule project registry refresh for all mates when a non-mate project is added
|
|
1095
1030
|
if (!extra.isMate) scheduleRegistryRefresh();
|
|
1096
1031
|
return true;
|
|
@@ -1126,8 +1061,13 @@ function createServer(opts) {
|
|
|
1126
1061
|
var ctx = projects.get(slug);
|
|
1127
1062
|
if (!ctx) return false;
|
|
1128
1063
|
var wasMate = ctx.getStatus().isMate;
|
|
1129
|
-
ctx.destroy();
|
|
1064
|
+
var shutdownResult = ctx.destroy();
|
|
1130
1065
|
projects.delete(slug);
|
|
1066
|
+
if (shutdownResult && typeof shutdownResult.catch === "function") {
|
|
1067
|
+
shutdownResult.catch(function(err) {
|
|
1068
|
+
console.error("[server] Project destroy failed for " + slug + ":", err && err.message ? err.message : err);
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1131
1071
|
if (!wasMate) scheduleRegistryRefresh();
|
|
1132
1072
|
return true;
|
|
1133
1073
|
}
|
|
@@ -1289,12 +1229,26 @@ function createServer(opts) {
|
|
|
1289
1229
|
});
|
|
1290
1230
|
}
|
|
1291
1231
|
|
|
1232
|
+
function forEachProject(fn) {
|
|
1233
|
+
projects.forEach(function (ctx, slug) {
|
|
1234
|
+
fn(ctx, slug);
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1292
1238
|
function destroyAll() {
|
|
1239
|
+
var shutdowns = [];
|
|
1293
1240
|
projects.forEach(function (ctx, slug) {
|
|
1294
1241
|
console.log("[server] Destroying project:", slug);
|
|
1295
|
-
ctx.destroy();
|
|
1242
|
+
var result = ctx.destroy();
|
|
1243
|
+
if (result && typeof result.then === "function") {
|
|
1244
|
+
shutdowns.push(result.catch(function(err) {
|
|
1245
|
+
console.error("[server] Project destroy failed for " + slug + ":", err && err.message ? err.message : err);
|
|
1246
|
+
return false;
|
|
1247
|
+
}));
|
|
1248
|
+
}
|
|
1296
1249
|
});
|
|
1297
1250
|
projects.clear();
|
|
1251
|
+
return Promise.all(shutdowns);
|
|
1298
1252
|
}
|
|
1299
1253
|
|
|
1300
1254
|
// --- Periodic cleanup of old chat images ---
|
|
@@ -1358,6 +1312,8 @@ function createServer(opts) {
|
|
|
1358
1312
|
setRecovery: auth.setRecovery,
|
|
1359
1313
|
clearRecovery: auth.clearRecovery,
|
|
1360
1314
|
broadcastAll: broadcastAll,
|
|
1315
|
+
forEachProject: forEachProject,
|
|
1316
|
+
destroyProject: removeProject,
|
|
1361
1317
|
destroyAll: destroyAll,
|
|
1362
1318
|
};
|
|
1363
1319
|
}
|