clay-server 3.6.0 → 3.7.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/project-http.js +51 -0
- package/lib/project-image.js +1 -1
- package/lib/project.js +3 -1
- package/lib/public/css/filebrowser.css +117 -7
- package/lib/public/css/generated-images.css +225 -0
- package/lib/public/css/input.css +2 -77
- package/lib/public/css/tui-attention.css +1 -2
- package/lib/public/index.html +3 -44
- package/lib/public/modules/app-messages.js +9 -33
- package/lib/public/modules/app-panels.js +0 -72
- package/lib/public/modules/filebrowser-context-menu.js +148 -0
- package/lib/public/modules/filebrowser.js +41 -4
- package/lib/public/modules/generated-images.js +132 -0
- package/lib/public/modules/input.js +2 -11
- package/lib/public/modules/session-tui-view.js +2 -3
- package/lib/public/modules/theme.js +4 -2
- package/lib/public/modules/tui-attention.js +5 -8
- package/lib/public/style.css +3 -2
- package/lib/sdk-bridge.js +2 -0
- package/lib/sdk-message-processor.js +57 -2
- package/lib/sessions.js +8 -6
- package/lib/yoke/adapters/codex.js +39 -0
- package/package.json +1 -1
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
import { getTerminalTheme } from './theme.js';
|
|
17
17
|
import { getTerminalFontFamily, getTerminalFontSize, onTerminalFontChange } from './terminal-prefs.js';
|
|
18
18
|
|
|
19
|
-
// TUI attention modal
|
|
20
|
-
// getTerminalTheme()
|
|
21
|
-
// which theme.js calls from applyTheme.
|
|
19
|
+
// The TUI attention modal always uses Clay Studio Dark via
|
|
20
|
+
// getTerminalTheme(). Theme switches reapply that fixed palette through
|
|
21
|
+
// setTuiAttentionTheme(), which theme.js calls from applyTheme().
|
|
22
22
|
|
|
23
23
|
var modalEl = null;
|
|
24
24
|
var modalXterm = null;
|
|
@@ -266,9 +266,8 @@ export function tuiModalHandleTermResized() { return false; }
|
|
|
266
266
|
export function tuiModalHandleTermExited() { return false; }
|
|
267
267
|
export function tuiModalHandleTermClosed() { return false; }
|
|
268
268
|
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
// so the frame doesn't stay black when a light theme is active.
|
|
269
|
+
// Theme update hook for the attention modal. The supplied palette remains
|
|
270
|
+
// Clay Studio Dark even when the surrounding workspace switches themes.
|
|
272
271
|
export function setTuiAttentionTheme(xtermTheme) {
|
|
273
272
|
if (modalXterm) {
|
|
274
273
|
try { modalXterm.options.theme = xtermTheme; } catch (e) {}
|
|
@@ -276,8 +275,6 @@ export function setTuiAttentionTheme(xtermTheme) {
|
|
|
276
275
|
if (modalEl && xtermTheme && xtermTheme.background) {
|
|
277
276
|
var bodyEl = modalEl.querySelector(".tui-modal-body");
|
|
278
277
|
if (bodyEl) bodyEl.style.background = xtermTheme.background;
|
|
279
|
-
var frameEl = modalEl.querySelector(".tui-modal");
|
|
280
|
-
if (frameEl) frameEl.style.background = xtermTheme.background;
|
|
281
278
|
}
|
|
282
279
|
}
|
|
283
280
|
|
package/lib/public/style.css
CHANGED
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
@import url("css/overlays.css?v=20260827-brand3");
|
|
9
9
|
@import url("css/menus.css");
|
|
10
10
|
@import url("css/messages.css?v=20260828-delegated-muted1");
|
|
11
|
+
@import url("css/generated-images.css?v=20260829c");
|
|
11
12
|
@import url("css/rewind.css");
|
|
12
13
|
@import url("css/input.css?v=20260828-composer-depth1");
|
|
13
|
-
@import url("css/filebrowser.css?v=
|
|
14
|
+
@import url("css/filebrowser.css?v=20260829-terminal-canvas1");
|
|
14
15
|
@import url("css/git-panel.css");
|
|
15
16
|
@import url("css/diff.css");
|
|
16
17
|
@import url("css/highlight.css");
|
|
@@ -35,5 +36,5 @@
|
|
|
35
36
|
@import url("css/mention.css");
|
|
36
37
|
@import url("css/debate.css");
|
|
37
38
|
@import url("css/notifications-center.css");
|
|
38
|
-
@import url("css/tui-attention.css");
|
|
39
|
+
@import url("css/tui-attention.css?v=20260829-terminal-canvas1");
|
|
39
40
|
@import url("css/pwa-mobile.css?v=20260824f");
|
package/lib/sdk-bridge.js
CHANGED
|
@@ -392,6 +392,8 @@ function createSDKBridge(opts) {
|
|
|
392
392
|
pushModule: pushModule,
|
|
393
393
|
getNotificationsModule: getNotificationsModule,
|
|
394
394
|
adapter: adapter,
|
|
395
|
+
saveImageFile: opts.saveImageFile,
|
|
396
|
+
getSessionLinuxUser: getSessionLinuxUser,
|
|
395
397
|
onProcessingChanged: onProcessingChanged,
|
|
396
398
|
onTurnDone: onTurnDone,
|
|
397
399
|
onAutoTitle: function (session) { autoGenerateTitle(session); },
|
|
@@ -18,6 +18,8 @@ function attachMessageProcessor(ctx) {
|
|
|
18
18
|
var opts = ctx.opts;
|
|
19
19
|
var discoverSkillDirs = ctx.discoverSkillDirs;
|
|
20
20
|
var mergeSkills = ctx.mergeSkills;
|
|
21
|
+
var saveImageFile = ctx.saveImageFile;
|
|
22
|
+
var getSessionLinuxUser = ctx.getSessionLinuxUser || function () { return null; };
|
|
21
23
|
|
|
22
24
|
var AUTO_TITLE_TURN_THRESHOLD = 2;
|
|
23
25
|
|
|
@@ -29,8 +31,19 @@ function attachMessageProcessor(ctx) {
|
|
|
29
31
|
return null;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
function sendAndRecord(session, obj) {
|
|
33
|
-
sm.sendAndRecord(session, obj);
|
|
34
|
+
function sendAndRecord(session, obj, clientObj) {
|
|
35
|
+
sm.sendAndRecord(session, obj, clientObj);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function parseGeneratedImage(parsed) {
|
|
39
|
+
var data = parsed.data || "";
|
|
40
|
+
var mediaType = parsed.mediaType || "image/png";
|
|
41
|
+
var dataUrlMatch = /^data:(image\/[a-z0-9.+-]+);base64,([\s\S]+)$/i.exec(data);
|
|
42
|
+
if (dataUrlMatch) {
|
|
43
|
+
mediaType = dataUrlMatch[1];
|
|
44
|
+
data = dataUrlMatch[2];
|
|
45
|
+
}
|
|
46
|
+
return { mediaType: mediaType, data: data.replace(/\s/g, "") };
|
|
34
47
|
}
|
|
35
48
|
|
|
36
49
|
function sendToSession(session, obj) {
|
|
@@ -276,6 +289,48 @@ function attachMessageProcessor(ctx) {
|
|
|
276
289
|
is_error: !!parsed.isError,
|
|
277
290
|
});
|
|
278
291
|
|
|
292
|
+
} else if (parsed.yokeType === "generated_image") {
|
|
293
|
+
var generated = parseGeneratedImage(parsed);
|
|
294
|
+
var fileName = null;
|
|
295
|
+
if (!parsed.isError && generated.data && typeof saveImageFile === "function") {
|
|
296
|
+
fileName = saveImageFile(generated.mediaType, generated.data, getSessionLinuxUser(session));
|
|
297
|
+
}
|
|
298
|
+
if (!fileName) {
|
|
299
|
+
sendAndRecord(session, {
|
|
300
|
+
type: "tool_result",
|
|
301
|
+
id: parsed.toolId,
|
|
302
|
+
content: parsed.isError ? "Image generation failed." : "The generated image could not be saved.",
|
|
303
|
+
is_error: true,
|
|
304
|
+
});
|
|
305
|
+
} else {
|
|
306
|
+
sendAndRecord(session, {
|
|
307
|
+
type: "tool_result",
|
|
308
|
+
id: parsed.toolId,
|
|
309
|
+
content: "Image generated",
|
|
310
|
+
is_error: false,
|
|
311
|
+
});
|
|
312
|
+
var imageRef = { mediaType: generated.mediaType, file: fileName };
|
|
313
|
+
var storedImage = {
|
|
314
|
+
type: "generated_image",
|
|
315
|
+
id: parsed.toolId,
|
|
316
|
+
prompt: parsed.prompt || "",
|
|
317
|
+
transparentBackground: parsed.transparentBackground,
|
|
318
|
+
imageRefs: [imageRef],
|
|
319
|
+
};
|
|
320
|
+
var liveImage = {
|
|
321
|
+
type: "generated_image",
|
|
322
|
+
id: parsed.toolId,
|
|
323
|
+
prompt: parsed.prompt || "",
|
|
324
|
+
transparentBackground: parsed.transparentBackground,
|
|
325
|
+
images: [{
|
|
326
|
+
mediaType: generated.mediaType,
|
|
327
|
+
url: "/p/" + slug + "/images/" + fileName,
|
|
328
|
+
fileName: fileName,
|
|
329
|
+
}],
|
|
330
|
+
};
|
|
331
|
+
sendAndRecord(session, storedImage, liveImage);
|
|
332
|
+
}
|
|
333
|
+
|
|
279
334
|
} else if (parsed.yokeType === "plan_updated") {
|
|
280
335
|
var todos = Array.isArray(parsed.plan) ? parsed.plan.map(function(step, idx) {
|
|
281
336
|
var todo = {
|
package/lib/sessions.js
CHANGED
|
@@ -874,9 +874,11 @@ function createSessionManager(opts) {
|
|
|
874
874
|
}
|
|
875
875
|
}
|
|
876
876
|
|
|
877
|
-
function doSendAndRecord(session, obj) {
|
|
877
|
+
function doSendAndRecord(session, obj, clientObj) {
|
|
878
878
|
// Stamp every recorded message so history replay preserves original times
|
|
879
879
|
if (!obj._ts) obj._ts = Date.now();
|
|
880
|
+
var outgoingObj = clientObj || obj;
|
|
881
|
+
if (!outgoingObj._ts) outgoingObj._ts = obj._ts;
|
|
880
882
|
session.history.push(obj);
|
|
881
883
|
appendToSessionFile(session, obj);
|
|
882
884
|
// Per-session out-of-band subscribers (used by home-chat to mirror
|
|
@@ -885,12 +887,12 @@ function createSessionManager(opts) {
|
|
|
885
887
|
// clients; they are responsible for any transform + dispatch.
|
|
886
888
|
if (session._subscribers && session._subscribers.size > 0) {
|
|
887
889
|
for (var sub of session._subscribers) {
|
|
888
|
-
try { sub(
|
|
890
|
+
try { sub(outgoingObj); } catch (e) { /* swallow — subscriber is optional */ }
|
|
889
891
|
}
|
|
890
892
|
}
|
|
891
893
|
if (sendEach) {
|
|
892
894
|
// Multi-user: send to clients whose active session matches this one
|
|
893
|
-
var data = JSON.stringify(
|
|
895
|
+
var data = JSON.stringify(outgoingObj);
|
|
894
896
|
var ioData = null;
|
|
895
897
|
sendEach(function (ws) {
|
|
896
898
|
if (ws._clayActiveSession === session.localId) {
|
|
@@ -901,7 +903,7 @@ function createSessionManager(opts) {
|
|
|
901
903
|
}
|
|
902
904
|
// Track unread: increment on "done" for clients not viewing this session
|
|
903
905
|
// Only count if session has no owner (my session) or owner matches this client
|
|
904
|
-
if (
|
|
906
|
+
if (outgoingObj.type === "done" && ws._clayActiveSession !== session.localId) {
|
|
905
907
|
var _isMySession = !session.ownerId || (ws._clayUser && ws._clayUser.id === session.ownerId);
|
|
906
908
|
if (_isMySession) {
|
|
907
909
|
if (!ws._clayUnread) ws._clayUnread = {};
|
|
@@ -917,10 +919,10 @@ function createSessionManager(opts) {
|
|
|
917
919
|
setTimeout(function () { session._ioThrottle = false; }, 80);
|
|
918
920
|
}
|
|
919
921
|
} else if (session.localId === activeSessionId) {
|
|
920
|
-
send(
|
|
922
|
+
send(outgoingObj);
|
|
921
923
|
} else {
|
|
922
924
|
// Track unread for single-user mode on "done"
|
|
923
|
-
if (
|
|
925
|
+
if (outgoingObj.type === "done") {
|
|
924
926
|
singleUserUnread[session.localId] = (singleUserUnread[session.localId] || 0) + 1;
|
|
925
927
|
send({ type: "session_unread", id: session.localId, count: singleUserUnread[session.localId] });
|
|
926
928
|
}
|
|
@@ -31,6 +31,7 @@ var backgroundTasks = require("../codex-background-tasks");
|
|
|
31
31
|
// agentMessage -> text response
|
|
32
32
|
// reasoning -> thinking
|
|
33
33
|
// commandExecution -> bash/shell
|
|
34
|
+
// imageGeneration -> ImageGen with a persisted inline image
|
|
34
35
|
// fileChange -> file edits
|
|
35
36
|
// mcpToolCall -> MCP tool
|
|
36
37
|
// webSearch -> web search
|
|
@@ -417,6 +418,44 @@ function flattenEvent(notification, state) {
|
|
|
417
418
|
return events;
|
|
418
419
|
}
|
|
419
420
|
|
|
421
|
+
// Image generation. Codex emits the generated PNG as base64 in `result`;
|
|
422
|
+
// the message processor persists it before anything reaches the browser.
|
|
423
|
+
if (item.type === "imageGeneration" || item.type === "image_generation") {
|
|
424
|
+
if (!state.toolBlocks[item.id]) {
|
|
425
|
+
state.blockCounter++;
|
|
426
|
+
state.toolBlocks[item.id] = "blk_" + state.blockCounter;
|
|
427
|
+
var imageBlockId = state.toolBlocks[item.id];
|
|
428
|
+
events.push({
|
|
429
|
+
yokeType: "tool_start",
|
|
430
|
+
blockId: imageBlockId,
|
|
431
|
+
toolId: item.id,
|
|
432
|
+
toolName: "ImageGen",
|
|
433
|
+
});
|
|
434
|
+
events.push({
|
|
435
|
+
yokeType: "tool_executing",
|
|
436
|
+
blockId: imageBlockId,
|
|
437
|
+
toolId: item.id,
|
|
438
|
+
toolName: "ImageGen",
|
|
439
|
+
input: { prompt: item.revisedPrompt || "" },
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
if (evtPhase === "completed") {
|
|
443
|
+
events.push({
|
|
444
|
+
yokeType: "generated_image",
|
|
445
|
+
toolId: item.id,
|
|
446
|
+
blockId: state.toolBlocks[item.id],
|
|
447
|
+
data: item.result || "",
|
|
448
|
+
mediaType: item.mediaType || "image/png",
|
|
449
|
+
prompt: item.revisedPrompt || "",
|
|
450
|
+
savedPath: item.savedPath || null,
|
|
451
|
+
transparentBackground: item.transparentBackground == null ? null : !!item.transparentBackground,
|
|
452
|
+
isError: item.status === "failed",
|
|
453
|
+
status: item.status || "completed",
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
return events;
|
|
457
|
+
}
|
|
458
|
+
|
|
420
459
|
// Command execution (bash/shell)
|
|
421
460
|
if (item.type === "commandExecution" || item.type === "command_execution") {
|
|
422
461
|
var commandText = item.command || state.commandInputs[item.id] || "";
|
package/package.json
CHANGED