clay-server 3.2.2-beta.3 → 3.3.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/git-cli.js +370 -0
- package/lib/git-session-attribution.js +219 -0
- package/lib/project-connection.js +1 -15
- package/lib/project-email.js +0 -61
- package/lib/project-http.js +104 -1
- package/lib/project-session-pair.js +129 -43
- package/lib/project-sessions.js +1 -11
- package/lib/project-user-message.js +2 -0
- package/lib/project-worker-proposal.js +317 -0
- package/lib/project.js +23 -8
- package/lib/public/app.js +14 -2
- package/lib/public/css/filebrowser.css +320 -0
- package/lib/public/css/git-panel.css +424 -0
- package/lib/public/css/input.css +0 -3
- package/lib/public/css/worker-proposal.css +127 -0
- package/lib/public/index.html +12 -15
- package/lib/public/modules/app-messages.js +11 -6
- package/lib/public/modules/context-sources.js +0 -90
- package/lib/public/modules/filebrowser.js +135 -21
- package/lib/public/modules/git-panel.js +456 -0
- package/lib/public/modules/input.js +8 -0
- package/lib/public/modules/markdown-slides.js +292 -0
- package/lib/public/modules/mate-sidebar.js +0 -7
- package/lib/public/modules/sidebar.js +42 -0
- package/lib/public/modules/tool-palette.js +1 -2
- package/lib/public/modules/worker-proposal.js +239 -0
- package/lib/public/style.css +2 -0
- package/lib/sdk-bridge.js +47 -6
- package/lib/sdk-message-processor.js +26 -2
- package/lib/session-pair-mcp-server.js +2 -2
- package/lib/sessions.js +1 -1
- package/lib/ws-schema.js +3 -0
- package/lib/yoke/adapters/claude.js +43 -17
- package/lib/yoke/index.js +19 -0
- package/package.json +1 -1
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
var crypto = require("crypto");
|
|
2
|
+
var yoke = require("./yoke");
|
|
3
|
+
var buildShape = require("./session-spawn-mcp-server").buildShape;
|
|
4
|
+
|
|
5
|
+
var MAX_SUMMARY_CHARS = 600;
|
|
6
|
+
var MAX_PLAN_CHARS = 6000;
|
|
7
|
+
var MAX_TASK_CHARS = 30000;
|
|
8
|
+
|
|
9
|
+
function modelValue(entry) {
|
|
10
|
+
if (typeof entry === "string") return entry;
|
|
11
|
+
return entry && (entry.value || entry.id) || "";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function modelLabel(entry) {
|
|
15
|
+
if (typeof entry === "string") return entry;
|
|
16
|
+
return entry && (entry.displayName || entry.name || entry.value || entry.id) || "";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isFableSession(session, modelsByVendor, fallbackModel) {
|
|
20
|
+
if (!session || (session.vendor || "claude") !== "claude") return false;
|
|
21
|
+
var effectiveModel = session.model || fallbackModel || "";
|
|
22
|
+
var selected = String(effectiveModel).toLowerCase();
|
|
23
|
+
if (selected.indexOf("fable") !== -1) return true;
|
|
24
|
+
var models = (modelsByVendor && modelsByVendor.claude) || [];
|
|
25
|
+
for (var i = 0; i < models.length; i++) {
|
|
26
|
+
if (modelValue(models[i]) !== effectiveModel) continue;
|
|
27
|
+
if (modelLabel(models[i]).toLowerCase().indexOf("fable") !== -1) return true;
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function toolResult(value) {
|
|
33
|
+
return Promise.resolve({ content: [{ type: "text", text: JSON.stringify(value) }] });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function attachWorkerProposal(ctx) {
|
|
37
|
+
var sm = ctx.sm;
|
|
38
|
+
var store = ctx.splitStore;
|
|
39
|
+
|
|
40
|
+
function isEligible(session) {
|
|
41
|
+
if (ctx.isMate || !session || session.mode === "tui") return false;
|
|
42
|
+
if (store.groupForMember(session.localId)) return false;
|
|
43
|
+
return isFableSession(session, sm.modelsByVendor, sm.currentModel);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function safeModelsByVendor(installed) {
|
|
47
|
+
var result = {};
|
|
48
|
+
for (var i = 0; i < installed.length; i++) {
|
|
49
|
+
var vendor = installed[i];
|
|
50
|
+
var source = (sm.modelsByVendor && sm.modelsByVendor[vendor]) || [];
|
|
51
|
+
result[vendor] = source.map(function (entry) {
|
|
52
|
+
if (typeof entry === "string") return entry;
|
|
53
|
+
return {
|
|
54
|
+
value: modelValue(entry),
|
|
55
|
+
displayName: modelLabel(entry),
|
|
56
|
+
supportedEffortLevels: entry.supportedEffortLevels || null,
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function proposalOptions() {
|
|
64
|
+
var installed = (sm.installedVendors || []).slice();
|
|
65
|
+
var capabilities = {};
|
|
66
|
+
for (var i = 0; i < installed.length; i++) {
|
|
67
|
+
var source = (sm.capabilitiesByVendor && sm.capabilitiesByVendor[installed[i]]) || {};
|
|
68
|
+
var vendorInfo = yoke.getVendorInfo(installed[i]) || {};
|
|
69
|
+
var supportsEffort = Array.isArray(vendorInfo.effortLevels) && vendorInfo.effortLevels.length > 0;
|
|
70
|
+
capabilities[installed[i]] = { effort: source.effort === undefined ? supportsEffort : source.effort !== false };
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
installedVendors: installed,
|
|
74
|
+
modelsByVendor: safeModelsByVendor(installed),
|
|
75
|
+
capabilitiesByVendor: capabilities,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function ensureModelCatalogs() {
|
|
80
|
+
var installed = sm.installedVendors || [];
|
|
81
|
+
sm.modelsByVendor = sm.modelsByVendor || {};
|
|
82
|
+
for (var i = 0; i < installed.length; i++) {
|
|
83
|
+
var vendor = installed[i];
|
|
84
|
+
if (sm.modelsByVendor[vendor] && sm.modelsByVendor[vendor].length > 0) continue;
|
|
85
|
+
var adapter = ctx.adapters && ctx.adapters[vendor];
|
|
86
|
+
if (!adapter || typeof adapter.supportedModels !== "function") continue;
|
|
87
|
+
try { sm.modelsByVendor[vendor] = await adapter.supportedModels(); }
|
|
88
|
+
catch (err) { console.warn("[worker-proposal] Could not load " + vendor + " models:", err.message || err); }
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function modelIsAvailable(options, vendor, model) {
|
|
93
|
+
if (!model) return true;
|
|
94
|
+
var models = options.modelsByVendor[vendor] || [];
|
|
95
|
+
for (var i = 0; i < models.length; i++) {
|
|
96
|
+
if (modelValue(models[i]) === model) return true;
|
|
97
|
+
}
|
|
98
|
+
return models.length === 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function chooseRecommendation(args, session, options) {
|
|
102
|
+
var installed = options.installedVendors;
|
|
103
|
+
var vendor = args.recommendedVendor;
|
|
104
|
+
if (installed.indexOf(vendor) === -1) {
|
|
105
|
+
if (installed.indexOf("codex") !== -1) vendor = "codex";
|
|
106
|
+
else {
|
|
107
|
+
vendor = installed[0] || session.vendor || "claude";
|
|
108
|
+
for (var i = 0; i < installed.length; i++) {
|
|
109
|
+
if (installed[i] !== session.vendor) { vendor = installed[i]; break; }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
var models = options.modelsByVendor[vendor] || [];
|
|
114
|
+
var model = modelIsAvailable(options, vendor, args.recommendedModel) ? (args.recommendedModel || "") : "";
|
|
115
|
+
var currentModel = session.model || sm.currentModel || "";
|
|
116
|
+
if (vendor === session.vendor && model === currentModel) model = "";
|
|
117
|
+
if (!model && models.length > 0) {
|
|
118
|
+
for (var j = 0; j < models.length; j++) {
|
|
119
|
+
if (vendor === session.vendor && modelValue(models[j]) === currentModel) continue;
|
|
120
|
+
if (modelLabel(models[j]).toLowerCase().indexOf("fable") !== -1) continue;
|
|
121
|
+
model = modelValue(models[j]);
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (!model && models.length > 0 && vendor !== session.vendor) model = modelValue(models[0]);
|
|
126
|
+
var effort = yoke.clampEffort(vendor, args.recommendedEffort || "medium") || "";
|
|
127
|
+
return { vendor: vendor, model: model, effort: effort };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function findProposal(session, proposalId) {
|
|
131
|
+
var history = (session && session.history) || [];
|
|
132
|
+
for (var i = history.length - 1; i >= 0; i--) {
|
|
133
|
+
if (history[i] && history[i].type === "worker_proposal" && history[i].proposalId === proposalId) return history[i];
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function hasPendingProposal(session) {
|
|
139
|
+
var history = (session && session.history) || [];
|
|
140
|
+
for (var i = history.length - 1; i >= 0; i--) {
|
|
141
|
+
var item = history[i];
|
|
142
|
+
if (!item || item.type !== "worker_proposal") continue;
|
|
143
|
+
return item.status === "pending" || item.status === "starting" || item.status === "running";
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function updateProposal(session, proposal, patch) {
|
|
149
|
+
Object.assign(proposal, patch, { updatedAt: Date.now() });
|
|
150
|
+
sm.saveSessionFile(session);
|
|
151
|
+
sm.sendToSession(session, Object.assign({
|
|
152
|
+
type: "worker_proposal_update",
|
|
153
|
+
proposalId: proposal.proposalId,
|
|
154
|
+
}, patch));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function propose(args, session) {
|
|
158
|
+
if (!isEligible(session)) return toolResult({ error: "Worker suggestions are only available in an unpaired Fable session." });
|
|
159
|
+
if (hasPendingProposal(session)) return toolResult({ error: "A Worker suggestion is already awaiting a decision." });
|
|
160
|
+
var summary = typeof args.summary === "string" ? args.summary.trim() : "";
|
|
161
|
+
var plan = typeof args.plan === "string" ? args.plan.trim() : "";
|
|
162
|
+
var task = typeof args.message === "string" ? args.message.trim() : "";
|
|
163
|
+
if (!summary || !plan || !task) return toolResult({ error: "summary, plan, and message are required." });
|
|
164
|
+
if (task.length > MAX_TASK_CHARS) return toolResult({ error: "The Worker task is too long." });
|
|
165
|
+
await ensureModelCatalogs();
|
|
166
|
+
var options = proposalOptions();
|
|
167
|
+
if (options.installedVendors.length === 0) return toolResult({ error: "No coding agent is installed for a Worker session." });
|
|
168
|
+
var recommendation = chooseRecommendation(args, session, options);
|
|
169
|
+
var proposal = {
|
|
170
|
+
type: "worker_proposal",
|
|
171
|
+
proposalId: "worker_" + crypto.randomUUID(),
|
|
172
|
+
summary: summary.slice(0, MAX_SUMMARY_CHARS),
|
|
173
|
+
plan: plan.slice(0, MAX_PLAN_CHARS),
|
|
174
|
+
message: task,
|
|
175
|
+
status: "pending",
|
|
176
|
+
recommendedVendor: recommendation.vendor,
|
|
177
|
+
recommendedModel: recommendation.model,
|
|
178
|
+
recommendedEffort: recommendation.effort,
|
|
179
|
+
options: options,
|
|
180
|
+
};
|
|
181
|
+
sm.sendAndRecord(session, proposal);
|
|
182
|
+
return toolResult({
|
|
183
|
+
status: "posted",
|
|
184
|
+
proposalId: proposal.proposalId,
|
|
185
|
+
instruction: "The Worker suggestion is visible in the chat. End this turn now and wait for the user's decision.",
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function resumeDriver(session, text) {
|
|
190
|
+
var sdk = ctx.getSdk();
|
|
191
|
+
if (!sdk) return Promise.reject(new Error("SDK bridge is not ready"));
|
|
192
|
+
var message = { type: "user_message", text: text, _internal: true };
|
|
193
|
+
sm.sendAndRecord(session, message);
|
|
194
|
+
session.sentToolResults = {};
|
|
195
|
+
if (!session.isProcessing) {
|
|
196
|
+
session.isProcessing = true;
|
|
197
|
+
ctx.onProcessingChanged();
|
|
198
|
+
sm.sendToSession(session, { type: "status", status: "processing" });
|
|
199
|
+
}
|
|
200
|
+
if (sdk.pushMessage(session, text)) return Promise.resolve();
|
|
201
|
+
return Promise.resolve(sdk.startQuery(session, text, undefined, ctx.getLinuxUserForSession(session)));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function parsePartnerResult(result) {
|
|
205
|
+
if (!result || result.isError || !result.content || !result.content[0]) {
|
|
206
|
+
return { status: "error", error: "Worker execution failed." };
|
|
207
|
+
}
|
|
208
|
+
try { return JSON.parse(result.content[0].text); }
|
|
209
|
+
catch (e) { return { status: "error", error: result.content[0].text || "Worker execution failed." }; }
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function runWorker(session, proposal) {
|
|
213
|
+
var result = parsePartnerResult(await ctx.sendToPartner({ message: proposal.message, wait: true, timeoutSeconds: 900 }, session));
|
|
214
|
+
var status = result.status === "complete" ? "completed" : result.status;
|
|
215
|
+
updateProposal(session, proposal, {
|
|
216
|
+
status: status || "error",
|
|
217
|
+
resultPreview: result.response ? result.response.slice(0, 1200) : "",
|
|
218
|
+
error: result.error || null,
|
|
219
|
+
});
|
|
220
|
+
var followup;
|
|
221
|
+
if (result.status === "complete") {
|
|
222
|
+
followup = "[Worker execution completed]\nReview the Worker's result, verify the implementation, and integrate any remaining changes.\n\n" + (result.response || "The Worker completed without a text summary.");
|
|
223
|
+
} else if (result.status === "running") {
|
|
224
|
+
followup = "[Worker execution is still running]\nUse read_partner to inspect progress before completing the task.";
|
|
225
|
+
} else {
|
|
226
|
+
followup = "[Worker execution failed]\nInspect the failure and decide whether to fix it here or delegate a narrower follow-up.\n\n" + (result.error || result.response || "Unknown Worker error.");
|
|
227
|
+
}
|
|
228
|
+
await resumeDriver(session, followup);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function sessionForResponse(ws) {
|
|
232
|
+
var session = sm.sessions.get(ws._clayActiveSession);
|
|
233
|
+
if (!session) throw new Error("active Driver session not found");
|
|
234
|
+
if (ctx.usersModule.isMultiUser()) {
|
|
235
|
+
var userId = ws._clayUser && ws._clayUser.id;
|
|
236
|
+
if (!userId || session.ownerId !== userId) throw new Error("Driver session access denied");
|
|
237
|
+
}
|
|
238
|
+
return session;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function respondToProposal(ws, msg) {
|
|
242
|
+
var session = sessionForResponse(ws);
|
|
243
|
+
var proposal = findProposal(session, msg.proposalId);
|
|
244
|
+
if (!proposal) throw new Error("Worker suggestion not found");
|
|
245
|
+
if (proposal.status !== "pending") throw new Error("Worker suggestion has already been resolved");
|
|
246
|
+
if (!msg.accepted) {
|
|
247
|
+
updateProposal(session, proposal, { status: "declined" });
|
|
248
|
+
await resumeDriver(session, "[Worker suggestion declined]\nContinue this task in the current session using the plan you already prepared.");
|
|
249
|
+
return { ok: true, status: "declined" };
|
|
250
|
+
}
|
|
251
|
+
var options = proposal.options || proposalOptions();
|
|
252
|
+
var vendor = msg.vendor || proposal.recommendedVendor;
|
|
253
|
+
var model = msg.model || "";
|
|
254
|
+
if (options.installedVendors.indexOf(vendor) === -1) throw new Error("Selected Worker vendor is not installed");
|
|
255
|
+
if (!modelIsAvailable(options, vendor, model)) throw new Error("Selected Worker model is unavailable");
|
|
256
|
+
var effort = yoke.clampEffort(vendor, msg.effort || proposal.recommendedEffort || "medium") || "";
|
|
257
|
+
updateProposal(session, proposal, { status: "starting", selectedVendor: vendor, selectedModel: model, selectedEffort: effort });
|
|
258
|
+
try {
|
|
259
|
+
var created = ctx.createPairRecord(ws, {
|
|
260
|
+
driver: { sessionId: session.localId },
|
|
261
|
+
worker: { vendor: vendor, model: model, effort: effort },
|
|
262
|
+
});
|
|
263
|
+
updateProposal(session, proposal, { status: "running", groupId: created.group.id, workerId: created.worker.localId });
|
|
264
|
+
ctx.sendTo(ws, { type: "pair_session_created", ok: true, group: created.group });
|
|
265
|
+
runWorker(session, proposal).catch(function (err) {
|
|
266
|
+
updateProposal(session, proposal, { status: "error", error: err.message || String(err) });
|
|
267
|
+
resumeDriver(session, "[Worker execution failed]\n" + (err.message || String(err))).catch(function () {});
|
|
268
|
+
});
|
|
269
|
+
return { ok: true, status: "running", group: created.group };
|
|
270
|
+
} catch (err) {
|
|
271
|
+
updateProposal(session, proposal, { status: "pending", error: err.message || String(err) });
|
|
272
|
+
return { ok: false, status: "pending", error: err.message || String(err) };
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function handleMessage(ws, msg) {
|
|
277
|
+
if (msg.type !== "worker_proposal_response") return false;
|
|
278
|
+
respondToProposal(ws, msg).catch(function (err) {
|
|
279
|
+
ctx.sendTo(ws, { type: "worker_proposal_update", proposalId: msg.proposalId, status: "pending", error: err.message || String(err) });
|
|
280
|
+
});
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function getToolDefs(session) {
|
|
285
|
+
if (!isEligible(session)) return [];
|
|
286
|
+
return [{
|
|
287
|
+
name: "propose_worker",
|
|
288
|
+
description: "After making a concise plan for an implementation-heavy task, suggest that the user run the execution with another model in a visible Worker session. Use this only before substantial execution begins, never for small edits, explanations, or tasks the user asked you to keep in this session.",
|
|
289
|
+
inputSchema: buildShape({
|
|
290
|
+
summary: { type: "string", description: "One short sentence explaining why a Worker is useful for this task." },
|
|
291
|
+
plan: { type: "string", description: "A concise numbered implementation plan to show in the approval card." },
|
|
292
|
+
message: { type: "string", description: "Complete execution instructions for the Worker, including scope, constraints, validation, and expected result." },
|
|
293
|
+
recommendedVendor: { type: "string", description: "Optional installed vendor id for the Worker." },
|
|
294
|
+
recommendedModel: { type: "string", description: "Optional exact Worker model id. Omit when uncertain." },
|
|
295
|
+
recommendedEffort: { type: "string", description: "Optional reasoning effort: minimal, low, medium, high, xhigh, or max." },
|
|
296
|
+
}, ["summary", "plan", "message"]),
|
|
297
|
+
handler: function (args) { return propose(args || {}, session); },
|
|
298
|
+
}];
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function getSystemPrompt(session) {
|
|
302
|
+
if (!isEligible(session)) return "";
|
|
303
|
+
return "You can introduce Clay's Driver/Worker workflow with propose_worker. For an implementation-heavy task that is likely to consume substantial execution time, first reason about the work and form a concise plan. Before editing files or running substantial execution, call propose_worker once with that plan and a complete Worker instruction, recommending another suitable execution model. Use it conservatively: do not suggest a Worker for questions, investigations, tiny fixes, or when the user explicitly wants you to execute here. After posting a proposal, end the turn and wait. If accepted, the Worker runs visibly and its result returns to you for review.";
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
getToolDefs: getToolDefs,
|
|
308
|
+
getSystemPrompt: getSystemPrompt,
|
|
309
|
+
handleMessage: handleMessage,
|
|
310
|
+
respondToProposal: respondToProposal,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
module.exports = {
|
|
315
|
+
attachWorkerProposal: attachWorkerProposal,
|
|
316
|
+
isFableSession: isFableSession,
|
|
317
|
+
};
|
package/lib/project.js
CHANGED
|
@@ -3,6 +3,7 @@ var path = require("path");
|
|
|
3
3
|
var os = require("os");
|
|
4
4
|
var crypto = require("crypto");
|
|
5
5
|
var { createSessionManager } = require("./sessions");
|
|
6
|
+
var { attachGitSessionAttribution } = require("./git-session-attribution");
|
|
6
7
|
var { createSDKBridge, createMessageQueue } = require("./sdk-bridge");
|
|
7
8
|
var { createTerminalManager } = require("./terminal-manager");
|
|
8
9
|
var { createNotesManager } = require("./notes");
|
|
@@ -172,7 +173,9 @@ function createProjectContext(opts) {
|
|
|
172
173
|
// --- YOKE adapters (multi-vendor, lazy init) ---
|
|
173
174
|
var _yokeState = yoke.createAdapters({ cwd: cwd, slug: slug, osUsers: osUsers });
|
|
174
175
|
var adapters = _yokeState.adapters;
|
|
175
|
-
|
|
176
|
+
// A new project has no remembered vendor. Select the first installed
|
|
177
|
+
// provider by the shared Claude -> Codex -> Kiro preference order.
|
|
178
|
+
var defaultVendor = yoke.resolveDefaultVendor(adapters);
|
|
176
179
|
var adapter = adapters[defaultVendor] || null;
|
|
177
180
|
|
|
178
181
|
// Browser MCP server runs in-process via createSdkMcpServer (no child process spawn).
|
|
@@ -399,6 +402,7 @@ function createProjectContext(opts) {
|
|
|
399
402
|
var stopAllDirWatches = _fileWatch.stopAllDirWatches;
|
|
400
403
|
|
|
401
404
|
// --- Session manager ---
|
|
405
|
+
var _gitAttribution = null;
|
|
402
406
|
var sm = createSessionManager({
|
|
403
407
|
cwd: cwd,
|
|
404
408
|
send: send,
|
|
@@ -417,7 +421,17 @@ function createProjectContext(opts) {
|
|
|
417
421
|
fn(ws, filterFn);
|
|
418
422
|
}
|
|
419
423
|
},
|
|
420
|
-
onSessionDone:
|
|
424
|
+
onSessionDone: function (session) {
|
|
425
|
+
if (_gitAttribution) _gitAttribution.finishTurn(session);
|
|
426
|
+
onSessionDone();
|
|
427
|
+
},
|
|
428
|
+
});
|
|
429
|
+
_gitAttribution = attachGitSessionAttribution({
|
|
430
|
+
cwd: cwd,
|
|
431
|
+
getOsUserInfoForSession: function (session) {
|
|
432
|
+
var linuxUser = getLinuxUserForSession(session);
|
|
433
|
+
return linuxUser ? resolveOsUserInfo(linuxUser) : null;
|
|
434
|
+
},
|
|
421
435
|
});
|
|
422
436
|
sm.availableVendors = Object.keys(adapters);
|
|
423
437
|
sm.defaultVendor = defaultVendor;
|
|
@@ -541,6 +555,7 @@ function createProjectContext(opts) {
|
|
|
541
555
|
usersModule: usersModule,
|
|
542
556
|
getLinuxUserForSession: getLinuxUserForSession,
|
|
543
557
|
onProcessingChanged: onProcessingChanged,
|
|
558
|
+
adapters: adapters,
|
|
544
559
|
});
|
|
545
560
|
var _sessionSpawn = attachSessionSpawn({
|
|
546
561
|
cwd: cwd,
|
|
@@ -817,9 +832,10 @@ function createProjectContext(opts) {
|
|
|
817
832
|
clayTls: serverTls,
|
|
818
833
|
clayAuthToken: serverAuthToken,
|
|
819
834
|
onProcessingChanged: onProcessingChanged,
|
|
820
|
-
onTurnDone:
|
|
821
|
-
digestDmTurn(session, preview);
|
|
822
|
-
|
|
835
|
+
onTurnDone: function (session, preview) {
|
|
836
|
+
if (isMate) digestDmTurn(session, preview);
|
|
837
|
+
else _sessionPair.handleTurnDone(session);
|
|
838
|
+
},
|
|
823
839
|
scheduleMessage: function (session, text, resetsAt) {
|
|
824
840
|
scheduleMessage(session, text, resetsAt);
|
|
825
841
|
},
|
|
@@ -1150,9 +1166,6 @@ function createProjectContext(opts) {
|
|
|
1150
1166
|
return;
|
|
1151
1167
|
}
|
|
1152
1168
|
|
|
1153
|
-
// --- Email defaults (project-level) ---
|
|
1154
|
-
if (_email.handleEmailMessage(ws, msg)) return;
|
|
1155
|
-
|
|
1156
1169
|
// --- MCP bridge (remote MCP servers via extension) ---
|
|
1157
1170
|
if (_mcp.handleMcpMessage(ws, msg)) return;
|
|
1158
1171
|
|
|
@@ -1406,6 +1419,7 @@ function createProjectContext(opts) {
|
|
|
1406
1419
|
saveImageFile: saveImageFile,
|
|
1407
1420
|
imagesDir: imagesDir,
|
|
1408
1421
|
onProcessingChanged: onProcessingChanged,
|
|
1422
|
+
gitAttribution: _gitAttribution,
|
|
1409
1423
|
_loop: _loop,
|
|
1410
1424
|
browserState: browserState,
|
|
1411
1425
|
sendExtensionCommandAny: sendExtensionCommandAny,
|
|
@@ -1617,6 +1631,7 @@ function createProjectContext(opts) {
|
|
|
1617
1631
|
slug: slug,
|
|
1618
1632
|
project: title || project,
|
|
1619
1633
|
sm: sm,
|
|
1634
|
+
gitAttribution: _gitAttribution,
|
|
1620
1635
|
send: send,
|
|
1621
1636
|
imagesDir: imagesDir,
|
|
1622
1637
|
osUsers: osUsers,
|
package/lib/public/app.js
CHANGED
|
@@ -25,8 +25,9 @@ import { initNotifications, showDoneNotification, playDoneSound, isNotifAlertEna
|
|
|
25
25
|
import { initInput, clearPendingImages, handleInputSync, autoResize, builtinCommands, sendMessage, hasSendableContent, setScheduleBtnDisabled, setScheduleDelayMs, clearScheduleDelay } from './modules/input.js';
|
|
26
26
|
import { initQrCode, triggerShare } from './modules/qrcode.js';
|
|
27
27
|
import { initFileBrowser, loadRootDirectory, refreshTree, handleFsList, handleFsRead, handleDirChanged, refreshIfOpen, handleFileChanged, handleFileHistory, handleGitDiff, handleFileAt, getPendingNavigate, closeFileViewer, resetFileBrowser } from './modules/filebrowser.js';
|
|
28
|
+
import { initGitPanel } from './modules/git-panel.js';
|
|
28
29
|
import { initTerminal, openTerminal, closeTerminal, resetTerminals, handleTermList, handleTermCreated, handleTermOutput, handleTermResized, handleTermExited, handleTermClosed, sendTerminalCommand } from './modules/terminal.js';
|
|
29
|
-
import { initContextSources,
|
|
30
|
+
import { initContextSources, updateTerminalList, updateBrowserTabList, handleContextSourcesState, getActiveSources, hasActiveSources } from './modules/context-sources.js';
|
|
30
31
|
import { initStickyNotes, handleNotesList, handleNoteCreated, handleNoteUpdated, handleNoteDeleted, openArchive, closeArchive, isArchiveOpen, hideNotes, showNotes, isNotesVisible, createNote } from './modules/sticky-notes.js';
|
|
31
32
|
import { initTheme, getThemeColor, getComputedVar, onThemeChange, getCurrentTheme, getChatLayout } from './modules/theme.js';
|
|
32
33
|
import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUserQuestion, markAskUserAnswered, renderPermissionRequest, markPermissionResolved, markPermissionCancelled, renderElicitationRequest, markElicitationResolved, renderPlanBanner, renderPlanCard, handleTodoWrite, handleTaskCreate, handleTaskUpdate, startThinking, appendThinking, stopThinking, resetThinkingGroup, createToolItem, updateToolExecuting, updateToolResult, markAllToolsDone, addTurnMeta, resetTurnMetaCost, enableMainInput, getTools, getPlanContent, setPlanContent, isPlanFilePath, getTodoTools, updateSubagentActivity, addSubagentToolEntry, markSubagentDone, updateSubagentProgress, initSubagentStop, closeToolGroup, removeToolFromGroup } from './modules/tools.js';
|
|
@@ -324,6 +325,13 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
324
325
|
currentFullText: "",
|
|
325
326
|
matePreThinkingEl: null,
|
|
326
327
|
// panels
|
|
328
|
+
fileViewerFullscreen: false,
|
|
329
|
+
markdownSlidesActive: false,
|
|
330
|
+
markdownSlideIndex: 0,
|
|
331
|
+
markdownSlideCount: 0,
|
|
332
|
+
markdownSlideLevel: 1,
|
|
333
|
+
markdownSlidePreferredLevel: 1,
|
|
334
|
+
markdownSlideLevelExplicit: false,
|
|
327
335
|
currentModel: "",
|
|
328
336
|
currentModels: [],
|
|
329
337
|
// Project's last-used vendor; seeds the sidebar's "New session" button.
|
|
@@ -875,6 +883,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
875
883
|
if (!_perms.fileBrowser) {
|
|
876
884
|
var fbBtn = document.getElementById("file-browser-btn");
|
|
877
885
|
if (fbBtn) fbBtn.style.display = "none";
|
|
886
|
+
var gitBtn = document.getElementById("git-sidebar-btn");
|
|
887
|
+
if (gitBtn) gitBtn.style.display = "none";
|
|
878
888
|
}
|
|
879
889
|
if (!_perms.skills) {
|
|
880
890
|
var sBtn = document.getElementById("skills-btn");
|
|
@@ -963,6 +973,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
963
973
|
fileTreeEl: $("file-tree"),
|
|
964
974
|
fileViewerEl: $("file-viewer"),
|
|
965
975
|
});
|
|
976
|
+
initGitPanel();
|
|
966
977
|
|
|
967
978
|
// --- Terminal ---
|
|
968
979
|
initTerminal({
|
|
@@ -1047,8 +1058,10 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
1047
1058
|
|
|
1048
1059
|
// Close archive / scheduler panel when switching to other sidebar panels
|
|
1049
1060
|
var fileBrowserBtn = $("file-browser-btn");
|
|
1061
|
+
var gitSidebarBtn = $("git-sidebar-btn");
|
|
1050
1062
|
var terminalSidebarBtn = $("terminal-sidebar-btn");
|
|
1051
1063
|
if (fileBrowserBtn) fileBrowserBtn.addEventListener("click", function () { if (isArchiveOpen()) closeArchive(); if (isSchedulerOpen()) closeScheduler(); });
|
|
1064
|
+
if (gitSidebarBtn) gitSidebarBtn.addEventListener("click", function () { if (isArchiveOpen()) closeArchive(); if (isSchedulerOpen()) closeScheduler(); });
|
|
1052
1065
|
if (terminalSidebarBtn) terminalSidebarBtn.addEventListener("click", function () { if (isArchiveOpen()) closeArchive(); if (isSchedulerOpen()) closeScheduler(); });
|
|
1053
1066
|
|
|
1054
1067
|
// --- Ralph Loop UI (delegated to app-loop-ui.js + app-loop-wizard.js) ---
|
|
@@ -1114,7 +1127,6 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
1114
1127
|
|
|
1115
1128
|
// --- MCP Servers ---
|
|
1116
1129
|
initMcp();
|
|
1117
|
-
initEmailDefaultsModal();
|
|
1118
1130
|
|
|
1119
1131
|
// --- Skills ---
|
|
1120
1132
|
initSkills();
|