clay-server 5.3.0-beta.6 → 5.3.0-beta.7
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-admin-overview.js +22 -11
- package/lib/project-background-tasks.js +76 -0
- package/lib/project-connection.js +1 -1
- package/lib/project-sessions.js +12 -5
- package/lib/project-split-views.js +12 -3
- package/lib/project-view-store.js +7 -5
- package/lib/project-worker-conversation.js +5 -0
- package/lib/public/app.js +7 -0
- package/lib/public/css/contextual-clay.css +2 -1
- package/lib/public/css/mate-conversation.css +1 -1
- package/lib/public/css/pending-message-queue.css +24 -19
- package/lib/public/css/project-admin-overview.css +42 -13
- package/lib/public/css/shared-browser.css +31 -8
- package/lib/public/css/split-view-layout.css +92 -7
- package/lib/public/css/tool-pane.css +4 -7
- package/lib/public/index.html +5 -5
- package/lib/public/modules/agent-message-bubbles.js +96 -0
- package/lib/public/modules/app-header.js +6 -0
- package/lib/public/modules/app-messages.js +26 -9
- package/lib/public/modules/background-task-previews.js +8 -0
- package/lib/public/modules/background-task-viewer.js +97 -0
- package/lib/public/modules/background-tasks-ui.js +34 -125
- package/lib/public/modules/chat-bubble-renderer.js +11 -5
- package/lib/public/modules/contextual-clay-geometry.js +7 -3
- package/lib/public/modules/pane-activation.js +5 -3
- package/lib/public/modules/pane-header-tooltips.js +32 -0
- package/lib/public/modules/pending-message-queue-drag.js +69 -0
- package/lib/public/modules/pending-message-queue.js +3 -33
- package/lib/public/modules/permission-control.js +6 -5
- package/lib/public/modules/project-admin-overview.js +125 -10
- package/lib/public/modules/project-grouping.js +9 -0
- package/lib/public/modules/server-settings-controls.js +33 -4
- package/lib/public/modules/session-folder-toolbar.js +1 -1
- package/lib/public/modules/session-tool-item.js +86 -0
- package/lib/public/modules/settings-select.js +2 -1
- package/lib/public/modules/shared-browser-tabs.js +37 -21
- package/lib/public/modules/sidebar-projects.js +1 -10
- package/lib/public/modules/split-session-empty-pane.js +47 -0
- package/lib/public/modules/split-view-create-menu.js +74 -65
- package/lib/public/modules/split-view-layout-scope.js +4 -0
- package/lib/public/modules/split-view-picker-actions.js +71 -0
- package/lib/public/modules/split-view-picker-render.js +81 -0
- package/lib/public/modules/split-view-presentation-renderer.js +8 -8
- package/lib/public/modules/split-view-worker-activity.js +2 -2
- package/lib/public/modules/split-view-worker-transcript.js +70 -33
- package/lib/public/modules/split-view-workers.js +6 -4
- package/lib/public/modules/terminal-viewport.js +14 -0
- package/lib/public/modules/terminal.js +3 -4
- package/lib/public/modules/theme-palette.js +177 -0
- package/lib/public/modules/theme.js +1 -174
- package/lib/public/modules/tool-pane-chrome.js +7 -1
- package/lib/public/modules/tools.js +5 -89
- package/lib/public/modules/tooltip.js +32 -6
- package/lib/public/modules/wireframe-workbench.js +2 -2
- package/lib/public/modules/worker-message-previews.js +39 -0
- package/lib/public/pane-assets/app-5W6KJXK5.js +1 -0
- package/lib/public/pane-assets/chunk-2IJUTAB6.js +2 -0
- package/lib/public/pane-assets/{chunk-BMHTBRMO.js → chunk-7OVNC5HT.js} +1 -1
- package/lib/public/pane-assets/chunk-BQN7B62H.js +3 -0
- package/lib/public/pane-assets/{chunk-2AIWCI6Y.js → chunk-EMLN2Y7L.js} +1 -1
- package/lib/public/pane-assets/{chunk-VKYBAVWT.js → chunk-ENPAHXLB.js} +1 -1
- package/lib/public/pane-assets/chunk-EPBGGZRT.js +1 -0
- package/lib/public/pane-assets/chunk-EYTE56IA.js +1 -0
- package/lib/public/pane-assets/{chunk-6NYCNWVF.js → chunk-FQTNR4ZS.js} +1 -1
- package/lib/public/pane-assets/chunk-L6LSA5WI.js +1 -0
- package/lib/public/pane-assets/chunk-M57UIGZV.js +1 -0
- package/lib/public/pane-assets/chunk-MGTFTEJP.js +1 -0
- package/lib/public/pane-assets/chunk-MMG6MYCN.js +1 -0
- package/lib/public/pane-assets/chunk-MZLMEXMW.js +1 -0
- package/lib/public/pane-assets/chunk-OG3E5SVA.js +166 -0
- package/lib/public/pane-assets/chunk-RGAFZ3T2.js +1 -0
- package/lib/public/pane-assets/chunk-RNDRURMF.js +1 -0
- package/lib/public/pane-assets/{chunk-PV4PCH2V.js → chunk-SJOT74VW.js} +1 -1
- package/lib/public/pane-assets/{chunk-LDIZLKB2.js → chunk-SJTGHIN5.js} +1 -1
- package/lib/public/pane-assets/chunk-SLMAFKI2.js +1 -0
- package/lib/public/pane-assets/chunk-U3I5KY3B.js +1 -0
- package/lib/public/pane-assets/{chunk-VNAV23CM.js → chunk-UPXU2IPC.js} +1 -1
- package/lib/public/pane-assets/{chunk-TMGSDFJI.js → chunk-VEBYD7TL.js} +1 -1
- package/lib/public/pane-assets/chunk-XBHYNXXT.js +1 -0
- package/lib/public/pane-assets/manifest.json +426 -414
- package/lib/public/pane-assets/style-BSKKK54Q.css +1 -0
- package/lib/sdk-message-processor.js +5 -2
- package/lib/server-storage.js +27 -3
- package/lib/server.js +4 -2
- package/lib/sessions.js +1 -1
- package/lib/storage-usage.js +29 -7
- package/lib/terminal-manager.js +2 -2
- package/lib/worker-message-previews.js +50 -0
- package/lib/ws-schema.js +1 -1
- package/lib/yoke/adapters/claude-worker.js +8 -0
- package/lib/yoke/adapters/claude.js +17 -2
- package/lib/yoke/claude-task-reader.js +94 -0
- package/lib/yoke/interface.js +1 -0
- package/package.json +1 -1
- package/lib/public/pane-assets/app-7UZ727NA.js +0 -1
- package/lib/public/pane-assets/chunk-6MIHIQ2W.js +0 -1
- package/lib/public/pane-assets/chunk-6UJ2ALOB.js +0 -1
- package/lib/public/pane-assets/chunk-6XK2OATZ.js +0 -1
- package/lib/public/pane-assets/chunk-BKIDR2ME.js +0 -1
- package/lib/public/pane-assets/chunk-GEFCFZEN.js +0 -1
- package/lib/public/pane-assets/chunk-GN2NQEVC.js +0 -2
- package/lib/public/pane-assets/chunk-IMLMBPT4.js +0 -166
- package/lib/public/pane-assets/chunk-K5S6AILM.js +0 -1
- package/lib/public/pane-assets/chunk-OWNNLZ36.js +0 -1
- package/lib/public/pane-assets/chunk-P2C3LOWL.js +0 -3
- package/lib/public/pane-assets/chunk-R4LMPTKU.js +0 -1
- package/lib/public/pane-assets/chunk-VWTHIQCE.js +0 -1
- package/lib/public/pane-assets/chunk-WM3K5AFH.js +0 -1
- package/lib/public/pane-assets/chunk-XOB3O327.js +0 -1
- package/lib/public/pane-assets/chunk-XOOUCFYT.js +0 -1
- package/lib/public/pane-assets/style-ZM7OOU3M.css +0 -1
|
@@ -229,7 +229,8 @@ function attachMessageProcessor(ctx) {
|
|
|
229
229
|
// only clears ambient watchers must not rebroadcast the session list.
|
|
230
230
|
var previousBackgroundTaskCount = backgroundTaskTiming.countUserTasks(session.activeBackgroundTasks);
|
|
231
231
|
session.activeBackgroundTasks = [];
|
|
232
|
-
|
|
232
|
+
session.backgroundTaskCatalog = null;
|
|
233
|
+
sendToSession(session, { type: "active_background_tasks", tasks: [], catalog: [] });
|
|
233
234
|
if (previousBackgroundTaskCount !== 0) sm.broadcastSessionList();
|
|
234
235
|
var fsSkills = discoverSkillDirs(getSessionSkillOptions(session));
|
|
235
236
|
session.skillNames = Array.from(mergeSkills(parsed.skills, fsSkills));
|
|
@@ -463,6 +464,7 @@ function attachMessageProcessor(ctx) {
|
|
|
463
464
|
|
|
464
465
|
} else if (parsed.yokeType === "subagent_message") {
|
|
465
466
|
// Sub-agent messages: extract tool_use blocks for activity display
|
|
467
|
+
require("./project-background-tasks").observeMessage(session, parsed, sendToSession);
|
|
466
468
|
processSubagentMessage(session, parsed);
|
|
467
469
|
|
|
468
470
|
} else if (parsed.yokeType === "message") {
|
|
@@ -864,7 +866,8 @@ function attachMessageProcessor(ctx) {
|
|
|
864
866
|
Array.isArray(parsed.tasks) ? parsed.tasks : []
|
|
865
867
|
);
|
|
866
868
|
session.activeBackgroundTasks = activeBackgroundTasks;
|
|
867
|
-
|
|
869
|
+
var taskCatalog = require("./project-background-tasks").remember(session, activeBackgroundTasks);
|
|
870
|
+
sendToSession(session, { type: "active_background_tasks", tasks: activeBackgroundTasks, catalog: taskCatalog });
|
|
868
871
|
if (previousBackgroundTaskCount !== backgroundTaskTiming.countUserTasks(activeBackgroundTasks)) {
|
|
869
872
|
sm.broadcastSessionList();
|
|
870
873
|
}
|
package/lib/server-storage.js
CHANGED
|
@@ -6,6 +6,21 @@ function attachStorage(ctx) {
|
|
|
6
6
|
var progress = null;
|
|
7
7
|
var expires = 0;
|
|
8
8
|
var failed = false;
|
|
9
|
+
var scanIdentity = null;
|
|
10
|
+
function projectIdentity() {
|
|
11
|
+
return JSON.stringify((ctx.getProjects ? ctx.getProjects() : []).map(function (project) { return [project.slug, project.path, project.projectOwnerId, project.isMate]; }));
|
|
12
|
+
}
|
|
13
|
+
function targetProject(slug) {
|
|
14
|
+
return (ctx.getProjects ? ctx.getProjects() : []).find(function (project) { return project.slug === slug && !project.isMate && !/^mate-/.test(slug); });
|
|
15
|
+
}
|
|
16
|
+
function usageReply(req, res, result, slug) {
|
|
17
|
+
if (!authorized(req, res)) return;
|
|
18
|
+
if (!slug) { reply(res, result.scanning ? 202 : 200, result); return; }
|
|
19
|
+
if (!targetProject(slug)) { reply(res, 404, { error: 'Project is no longer registered.' }); return; }
|
|
20
|
+
if (scanIdentity !== projectIdentity()) { reply(res, 409, { error: 'Project registration changed. Refresh usage.' }); return; }
|
|
21
|
+
var usage = (result.projects || []).find(function (project) { return project.slug === slug; });
|
|
22
|
+
reply(res, result.scanning ? 202 : 200, { slug: slug, scanning: !!result.scanning, partial: !!result.partial, checkedAt: result.checkedAt || null, usage: usage || null });
|
|
23
|
+
}
|
|
9
24
|
function reply(res, status, data) {
|
|
10
25
|
res.writeHead(status, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
|
|
11
26
|
res.end(JSON.stringify(data));
|
|
@@ -19,14 +34,23 @@ function attachStorage(ctx) {
|
|
|
19
34
|
return true;
|
|
20
35
|
}
|
|
21
36
|
function handleRequest(req, res, url) {
|
|
22
|
-
|
|
37
|
+
var pathname = url.split('?')[0];
|
|
38
|
+
var match = /^\/api\/server\/storage\/project\/([^/]+)$/.exec(pathname);
|
|
39
|
+
if (pathname !== '/api/server/storage' && !match) return false;
|
|
40
|
+
var slug = null;
|
|
23
41
|
var refresh = new URL(url, 'http://localhost').searchParams.get('refresh') === '1';
|
|
24
42
|
if (!authorized(req, res)) return true;
|
|
25
43
|
if (req.method !== 'GET') { reply(res, 405, { error: 'Method not allowed.' }); return true; }
|
|
44
|
+
if (match) {
|
|
45
|
+
try { slug = decodeURIComponent(match[1]); } catch (error) { reply(res, 400, { error: 'Invalid project.' }); return true; }
|
|
46
|
+
if (!targetProject(slug)) { reply(res, 404, { error: 'Project not found.' }); return true; }
|
|
47
|
+
}
|
|
48
|
+
if (!pending && scanIdentity !== projectIdentity()) { cached = null; failed = false; }
|
|
26
49
|
if (failed && Date.now() < expires) { reply(res, 500, { error: 'Disk usage could not be measured. Try again in a few seconds.' }); return true; }
|
|
27
|
-
if (!pending && !refresh && cached && Date.now() < expires) {
|
|
50
|
+
if (!pending && !refresh && cached && Date.now() < expires) { usageReply(req, res, cached, slug); return true; }
|
|
28
51
|
if (!pending) {
|
|
29
52
|
progress = null; failed = false;
|
|
53
|
+
scanIdentity = projectIdentity();
|
|
30
54
|
var users = ctx.users.getAllUsers();
|
|
31
55
|
var projects = ctx.getProjects ? ctx.getProjects() : [];
|
|
32
56
|
if (!ctx.users.isMultiUser() && users.length <= 1) {
|
|
@@ -45,7 +69,7 @@ function attachStorage(ctx) {
|
|
|
45
69
|
timer = setTimeout(function () { resolve(progress || { scanning: true }); }, 750);
|
|
46
70
|
})]).then(function (result) {
|
|
47
71
|
clearTimeout(timer);
|
|
48
|
-
if (!res.destroyed
|
|
72
|
+
if (!res.destroyed) usageReply(req, res, result, slug);
|
|
49
73
|
}).catch(function () {
|
|
50
74
|
clearTimeout(timer);
|
|
51
75
|
if (!res.destroyed) reply(res, 500, { error: 'Disk usage could not be measured. Try refreshing.' });
|
package/lib/server.js
CHANGED
|
@@ -519,7 +519,7 @@ function createServer(opts) {
|
|
|
519
519
|
} : null;
|
|
520
520
|
var payload = JSON.stringify(seed).replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
521
521
|
var script = '<script>window.__clayAppearanceSeed=' + payload + ';</script>';
|
|
522
|
-
return html.replace(
|
|
522
|
+
return html.replace(/<script src="\/?appearance-bootstrap.js"><\/script>/, function (tag) { return script + tag; });
|
|
523
523
|
}
|
|
524
524
|
|
|
525
525
|
function sendAppShell(req, res, slug) {
|
|
@@ -1103,8 +1103,10 @@ function createServer(opts) {
|
|
|
1103
1103
|
if (!httpUser || !canUserAccessSlug(httpUser.id, slug)) {
|
|
1104
1104
|
var overview = httpUser && administrativeMetadata(httpUser.id, slug);
|
|
1105
1105
|
if (overview && req.method === "GET" && (projectUrlForAuth === "/" || projectUrlForAuth === "/index.html")) {
|
|
1106
|
+
var overviewAppearance = null;
|
|
1107
|
+
try { overviewAppearance = settings.getInitialAppearance(req); } catch (error) {}
|
|
1106
1108
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" });
|
|
1107
|
-
res.end(projectAdminOverview.page(overview, getVisibleProjects(httpUser.id)));
|
|
1109
|
+
res.end(injectAppearanceSeed(projectAdminOverview.page(overview, getVisibleProjects(httpUser.id)), overviewAppearance));
|
|
1108
1110
|
return;
|
|
1109
1111
|
}
|
|
1110
1112
|
res.writeHead(403, { "Content-Type": "application/json", "Cache-Control": "no-store" });
|
package/lib/sessions.js
CHANGED
|
@@ -886,7 +886,7 @@ function createSessionManager(opts) {
|
|
|
886
886
|
if (session.isProcessing) {
|
|
887
887
|
_send({ type: "status", status: "processing" });
|
|
888
888
|
}
|
|
889
|
-
_send({ type: "active_background_tasks", tasks: session.activeBackgroundTasks || [] });
|
|
889
|
+
_send({ type: "active_background_tasks", tasks: session.activeBackgroundTasks || [], catalog: session.backgroundTaskCatalog && session.backgroundTaskCatalog.tasks || [] });
|
|
890
890
|
|
|
891
891
|
// Re-send any pending permission requests
|
|
892
892
|
var pendingIds = Object.keys(session.pendingPermissions);
|
package/lib/storage-usage.js
CHANGED
|
@@ -39,10 +39,26 @@ async function scanStorage(options) {
|
|
|
39
39
|
if (identity && !owners.get(identity.uid)) rows[index].mapped = false;
|
|
40
40
|
});
|
|
41
41
|
var projectRoots = new Map();
|
|
42
|
+
var projectUsage = [];
|
|
43
|
+
var usageRoots = new Map();
|
|
42
44
|
var projectErrors = 0;
|
|
43
45
|
for (var project of projects) {
|
|
46
|
+
var usage = { slug: project.slug, available: false, bytes: 0, files: 0, capacityBytes: null, availableBytes: null };
|
|
47
|
+
if (!project.isMate && project.slug) projectUsage.push(usage);
|
|
44
48
|
try {
|
|
45
49
|
var projectPath = await fs.realpath(project.path);
|
|
50
|
+
usage.available = true;
|
|
51
|
+
if (usageRoots.has(projectPath)) {
|
|
52
|
+
var priorUsage = usageRoots.get(projectPath);
|
|
53
|
+
if (priorUsage) priorUsage.available = false;
|
|
54
|
+
usage.available = false;
|
|
55
|
+
usageRoots.set(projectPath, null);
|
|
56
|
+
} else usageRoots.set(projectPath, project.isMate ? null : usage);
|
|
57
|
+
try {
|
|
58
|
+
var volume = await fs.statfs(projectPath);
|
|
59
|
+
usage.capacityBytes = volume.blocks * volume.bsize;
|
|
60
|
+
usage.availableBytes = volume.bavail * volume.bsize;
|
|
61
|
+
} catch (error) { /* Volume capacity can be unavailable independently of allocation. */ }
|
|
46
62
|
var row = rows.find(function (item) { return item.id === project.projectOwnerId; }) || null;
|
|
47
63
|
if (row) row.projects++;
|
|
48
64
|
if (projectRoots.has(projectPath) && projectRoots.get(projectPath) !== row) row = null;
|
|
@@ -50,21 +66,20 @@ async function scanStorage(options) {
|
|
|
50
66
|
} catch (error) { projectErrors++; }
|
|
51
67
|
}
|
|
52
68
|
var orderedProjects = Array.from(projectRoots.keys()).sort(function (a, b) { return b.length - a.length; });
|
|
53
|
-
function
|
|
54
|
-
|
|
55
|
-
return root ? projectRoots.get(root) : null;
|
|
69
|
+
function projectRoot(filePath) {
|
|
70
|
+
return orderedProjects.find(function (candidate) { return filePath === candidate || filePath.indexOf(candidate + path.sep) === 0; });
|
|
56
71
|
}
|
|
57
72
|
var roots = orderedProjects.concat(options.projectPaths || []);
|
|
58
73
|
identities.forEach(function (identity) { if (identity) roots.push(identity.home); });
|
|
59
74
|
roots.push(options.configDir);
|
|
60
|
-
var report = { attribution: projectMode ? "project" : "filesystem", users: rows, totalBytes: 0, totalFiles: 0, unattributedBytes: 0, unattributedFiles: 0,
|
|
75
|
+
var report = { attribution: projectMode ? "project" : "filesystem", users: rows, projects: projectUsage, totalBytes: 0, totalFiles: 0, unattributedBytes: 0, unattributedFiles: 0,
|
|
61
76
|
partial: false, errors: projectErrors + users.filter(function (user, index) { return !projectMode && options.platform === 'linux' && user.linuxUser && !identities[index]; }).length, skippedLinks: 0, skippedMounts: 0, scannedEntries: 0, roots: 0, platform: options.platform };
|
|
62
77
|
var lastProgress = 0;
|
|
63
78
|
function progress() {
|
|
64
79
|
if (!options.onProgress || Date.now() - lastProgress < 500) return;
|
|
65
80
|
lastProgress = Date.now();
|
|
66
81
|
options.onProgress(Object.assign({}, report, { scanning: true, checkedAt: new Date().toISOString(),
|
|
67
|
-
users: rows.map(function (row) { return Object.assign({}, row); }) }));
|
|
82
|
+
users: rows.map(function (row) { return Object.assign({}, row); }), projects: projectUsage.map(function (row) { return Object.assign({}, row); }) }));
|
|
68
83
|
}
|
|
69
84
|
progress();
|
|
70
85
|
var seen = new Map();
|
|
@@ -118,9 +133,15 @@ async function scanStorage(options) {
|
|
|
118
133
|
if (stat.isSymbolicLink()) { report.skippedLinks++; return; }
|
|
119
134
|
if (stat.dev !== device) { report.skippedMounts++; return; }
|
|
120
135
|
var key = stat.dev + ':' + stat.ino;
|
|
121
|
-
var
|
|
136
|
+
var usageRoot = projectRoot(filePath);
|
|
137
|
+
var owner = projectMode ? projectRoots.get(usageRoot) : owners.get(stat.uid);
|
|
138
|
+
var usage = usageRoot && usageRoots.get(usageRoot);
|
|
122
139
|
if (seen.has(key)) {
|
|
123
140
|
var previous = seen.get(key);
|
|
141
|
+
if (stat.isFile() && previous.project && usage && previous.project !== usage) {
|
|
142
|
+
previous.project.bytes -= previous.bytes; previous.project.files--;
|
|
143
|
+
previous.project = null;
|
|
144
|
+
}
|
|
124
145
|
if (projectMode && stat.isFile() && previous.owner && previous.owner !== owner) {
|
|
125
146
|
previous.owner.bytes -= previous.bytes; previous.owner.files--;
|
|
126
147
|
report.unattributedBytes += previous.bytes; report.unattributedFiles++;
|
|
@@ -132,7 +153,8 @@ async function scanStorage(options) {
|
|
|
132
153
|
if (!stat.isDirectory() && !stat.isFile()) return;
|
|
133
154
|
var bytes = typeof stat.blocks === 'number' ? stat.blocks * 512 : stat.size;
|
|
134
155
|
// Only directories and multiply-linked files need identity deduplication.
|
|
135
|
-
if (stat.isDirectory() || stat.nlink > 1) seen.set(key, { owner: owner, bytes: bytes });
|
|
156
|
+
if (stat.isDirectory() || stat.nlink > 1) seen.set(key, { owner: owner, project: usage, bytes: bytes });
|
|
157
|
+
if (usage) { usage.bytes += bytes; if (stat.isFile()) usage.files++; }
|
|
136
158
|
report.totalBytes += bytes;
|
|
137
159
|
if (stat.isFile()) report.totalFiles++;
|
|
138
160
|
if (owner) { owner.bytes += bytes; if (stat.isFile()) owner.files++; }
|
package/lib/terminal-manager.js
CHANGED
|
@@ -136,9 +136,9 @@ function createTerminalManager(opts) {
|
|
|
136
136
|
sendTo(ws, { type: "term_output", id: id, data: replay });
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
//
|
|
139
|
+
// This snapshot may arrive after the attaching client has fitted a new viewport.
|
|
140
140
|
if (!alreadySubscribed && session.cols && session.rows) {
|
|
141
|
-
sendTo(ws, { type: "term_resized", id: id, cols: session.cols, rows: session.rows });
|
|
141
|
+
sendTo(ws, { type: "term_resized", id: id, cols: session.cols, rows: session.rows, initial: true });
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
// If already exited, notify
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Bounded, exact-owner message previews use the Worker reader's existing authority.
|
|
2
|
+
function latest(history) {
|
|
3
|
+
var found = null;
|
|
4
|
+
for (var i = history.length - 1, stop = Math.max(0, history.length - 1000); i >= stop; i--) {
|
|
5
|
+
var entry = history[i];
|
|
6
|
+
if (entry && (entry.type === 'delta' || entry.type === 'assistant_message') && typeof entry.text === 'string' && entry.text.trim()) {
|
|
7
|
+
var index = i, text = entry.text;
|
|
8
|
+
while (index > stop && history[index - 1].type === 'delta' && entry.type === 'delta' ) { index--; text = (history[index].text + text).slice(0, 1200); }
|
|
9
|
+
found = { id: String(index), text: text.slice(0, 1200), timestamp: history[index]._ts || null }; break;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return found;
|
|
13
|
+
}
|
|
14
|
+
function attach(ctx) {
|
|
15
|
+
var subscriptions = new WeakMap();
|
|
16
|
+
function release(ws) {
|
|
17
|
+
var old = subscriptions.get(ws); if (!old) return;
|
|
18
|
+
subscriptions.delete(ws); old.entries.forEach(function (entry) { clearTimeout(entry.timer); if (entry.release) entry.release(); });
|
|
19
|
+
if (ws.removeListener) ws.removeListener('close', old.close);
|
|
20
|
+
}
|
|
21
|
+
function handleMessage(ws, msg) {
|
|
22
|
+
if (msg.type !== 'split_worker_preview_request') return false;
|
|
23
|
+
release(ws);
|
|
24
|
+
if (typeof msg.requestId !== 'string' || msg.requestId.length > 160 || !Array.isArray(msg.targets) || msg.targets.length > 32) return true;
|
|
25
|
+
var state = { entries: [], close: function () { release(ws); } }; subscriptions.set(ws, state);
|
|
26
|
+
msg.targets.forEach(function (target) {
|
|
27
|
+
var pair = ctx.resolve(ws, target); if (!pair || state.entries.some(function (entry) { return entry.worker === pair.worker; })) return;
|
|
28
|
+
var entry = { worker: pair.worker, timer: null, last: null };
|
|
29
|
+
function send(live) {
|
|
30
|
+
var current = ctx.resolve(ws, target);
|
|
31
|
+
if (subscriptions.get(ws) !== state || ws.readyState !== 1 || !current || current.driver !== pair.driver || current.worker !== pair.worker) { if (entry.release) entry.release(); return; }
|
|
32
|
+
var preview = latest(pair.worker.history || []); if (!preview) return;
|
|
33
|
+
preview.vendor = pair.worker.vendor || null;
|
|
34
|
+
if (entry.last === JSON.stringify(preview)) return;
|
|
35
|
+
entry.last = JSON.stringify(preview);
|
|
36
|
+
ctx.send(ws, { type: 'split_worker_message_preview', requestId: msg.requestId, parentSessionOriginId: target.parentSessionOriginId,
|
|
37
|
+
workerSessionOriginId: target.workerSessionOriginId, generation: target.generation, live: live, preview: preview });
|
|
38
|
+
}
|
|
39
|
+
entry.release = ctx.sm.subscribeSession(pair.worker.localId, function (event) {
|
|
40
|
+
if (!event || ['delta', 'assistant_message'].indexOf(event.type) < 0 || entry.timer) return;
|
|
41
|
+
entry.timer = setTimeout(function () { entry.timer = null; send(true); }, 60);
|
|
42
|
+
});
|
|
43
|
+
state.entries.push(entry); send(false);
|
|
44
|
+
});
|
|
45
|
+
if (ws.once) ws.once('close', state.close);
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
return { handleMessage: handleMessage };
|
|
49
|
+
}
|
|
50
|
+
module.exports = { attach: attach, latest: latest };
|
package/lib/ws-schema.js
CHANGED
|
@@ -24,7 +24,7 @@ var schema = {
|
|
|
24
24
|
"switch_session": { direction: "c2s", handler: "lib/project-sessions.js", description: "Switch the active session by local ID" },
|
|
25
25
|
"resume_tui_session": { direction: "c2s", handler: "lib/project-sessions.js", description: "Spawn the claude --resume PTY for a TUI session shown read-only (lazy resume)" },
|
|
26
26
|
"suspend_tui_session": { direction: "c2s", handler: "lib/project-sessions.js", description: "Close a live TUI session's PTY now but keep it resumable (explicit Close)" },
|
|
27
|
-
"new_session": { direction: "c2s", handler: "lib/project-sessions.js", description: "Create a
|
|
27
|
+
"new_session": { direction: "c2s", handler: "lib/project-sessions.js", description: "Create a blank session; correlated ordinary-project requests may use activate:false to preserve the current View selection" },
|
|
28
28
|
"new_session_result": { direction: "s2c", handler: "lib/public/modules/scheduled-tasks.js", description: "Correlated result for a requested new session" },
|
|
29
29
|
"delete_session": { direction: "c2s", handler: "lib/project-sessions.js", description: "Delete a session by ID" },
|
|
30
30
|
"rename_session": { direction: "c2s", handler: "lib/project-sessions.js", description: "Rename a session" },
|
|
@@ -97,6 +97,8 @@ function applyWorkerClaudeBinary(options) {
|
|
|
97
97
|
// --- State ---
|
|
98
98
|
var sdkModule = null;
|
|
99
99
|
var queryInstance = null;
|
|
100
|
+
var taskReader = null;
|
|
101
|
+
var taskReaderModule = require("../claude-task-reader");
|
|
100
102
|
var messageQueue = null;
|
|
101
103
|
var abortController = null;
|
|
102
104
|
var pendingPermissions = {}; // requestId -> resolve
|
|
@@ -284,6 +286,9 @@ function handleMessage(msg) {
|
|
|
284
286
|
case "set_permission_mode":
|
|
285
287
|
handleSetPermissionMode(msg);
|
|
286
288
|
break;
|
|
289
|
+
case "read_background_task":
|
|
290
|
+
taskReaderModule.respond(taskReader, msg, sendToDaemon);
|
|
291
|
+
break;
|
|
287
292
|
case "stop_task":
|
|
288
293
|
handleStopTask(msg);
|
|
289
294
|
break;
|
|
@@ -598,6 +603,8 @@ async function handleQueryStart(msg) {
|
|
|
598
603
|
prompt: messageQueue,
|
|
599
604
|
options: options,
|
|
600
605
|
});
|
|
606
|
+
if (taskReader) taskReader.close();
|
|
607
|
+
taskReader = taskReaderModule.createReader(getSDK, function () { return queryInstance; }, options.cwd);
|
|
601
608
|
perf("query instance created");
|
|
602
609
|
} catch (e) {
|
|
603
610
|
sendToDaemon({ type: "query_error", error: "Failed to create query: " + (e.message || e), exitCode: null, stderr: null });
|
|
@@ -618,6 +625,7 @@ async function handleQueryStart(msg) {
|
|
|
618
625
|
var firstText = true;
|
|
619
626
|
var eventCounts = {};
|
|
620
627
|
for await (var event of queryInstance) {
|
|
628
|
+
taskReader.observe(event);
|
|
621
629
|
var etype = (event && event.type || "?");
|
|
622
630
|
var esubtype = (event && event.subtype || "");
|
|
623
631
|
eventCounts[etype] = (eventCounts[etype] || 0) + 1;
|
|
@@ -18,6 +18,7 @@ var claudeUserInput = require("../claude-user-input");
|
|
|
18
18
|
var claudeCorrelation = require("../claude-message-correlation");
|
|
19
19
|
var claudeContextUsage = require("../claude-context-usage");
|
|
20
20
|
var claudeModelSwitch = require("../claude-model-switch");
|
|
21
|
+
var taskReaderModule = require("../claude-task-reader");
|
|
21
22
|
|
|
22
23
|
// SDK-specific knowledge stays in the adapter (not the relay): the host dialog
|
|
23
24
|
// kinds this client can render, declared to the SDK when an onUserDialog
|
|
@@ -580,6 +581,8 @@ function normalizeBackgroundTasks(tasks) {
|
|
|
580
581
|
task_id: task && task.task_id,
|
|
581
582
|
task_type: taskType,
|
|
582
583
|
description: (task && task.description) || "",
|
|
584
|
+
run_id: task && task.run_id || null,
|
|
585
|
+
parent_task_id: task && task.parent_task_id || null,
|
|
583
586
|
// Housekeeping the CLI does not surface as user work (skip_transcript
|
|
584
587
|
// tasks, auto-started live-update watchers). Clay keeps these out of its
|
|
585
588
|
// activity indicators and lists them as a subdued secondary group.
|
|
@@ -597,7 +600,8 @@ function normalizeBackgroundTasks(tasks) {
|
|
|
597
600
|
// --- QueryHandle ---
|
|
598
601
|
// Wraps a raw SDK query object with the YOKE QueryHandle interface.
|
|
599
602
|
// Events are flattened via flattenEvent before yielding.
|
|
600
|
-
function createQueryHandle(rawQuery, messageQueue, abortController) {
|
|
603
|
+
function createQueryHandle(rawQuery, messageQueue, abortController, cwd) {
|
|
604
|
+
var taskReader = taskReaderModule.createReader(loadSDK, function () { return closed ? null : rawQuery; }, cwd);
|
|
601
605
|
var correlation = claudeCorrelation.createCorrelation();
|
|
602
606
|
var closed = false;
|
|
603
607
|
var removeAbort = function () {};
|
|
@@ -615,6 +619,7 @@ function createQueryHandle(rawQuery, messageQueue, abortController) {
|
|
|
615
619
|
return rawIter.next().then(function(result) {
|
|
616
620
|
if (result.done) { closed = true; removeAbort(); resolveStopped(); }
|
|
617
621
|
if (result.done) return result;
|
|
622
|
+
taskReader.observe(result.value);
|
|
618
623
|
return { value: correlation.result(result.value, flattenEvent(result.value)), done: false };
|
|
619
624
|
}, function(err) {
|
|
620
625
|
closed = true;
|
|
@@ -676,6 +681,8 @@ function createQueryHandle(rawQuery, messageQueue, abortController) {
|
|
|
676
681
|
return Promise.resolve();
|
|
677
682
|
},
|
|
678
683
|
|
|
684
|
+
readBackgroundTask: taskReader.read,
|
|
685
|
+
|
|
679
686
|
stopTask: function(taskId) {
|
|
680
687
|
if (rawQuery && typeof rawQuery.stopTask === "function") {
|
|
681
688
|
return rawQuery.stopTask(taskId);
|
|
@@ -1107,6 +1114,7 @@ function serializeWorkerValue(value, seen) {
|
|
|
1107
1114
|
function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool, onUserDialog, onUserInputRequest, onModelSwitch, preToolUsePolicy) {
|
|
1108
1115
|
var interactionAbort = new AbortController();
|
|
1109
1116
|
var contextRequests = claudeContextUsage.createRequests(worker);
|
|
1117
|
+
var taskRequests = taskReaderModule.createRequests(worker);
|
|
1110
1118
|
var correlation = claudeCorrelation.createCorrelation();
|
|
1111
1119
|
// Async iterable state
|
|
1112
1120
|
var iterQueue = [];
|
|
@@ -1153,6 +1161,7 @@ function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool,
|
|
|
1153
1161
|
function endIter() {
|
|
1154
1162
|
interactionAbort.abort();
|
|
1155
1163
|
contextRequests.close();
|
|
1164
|
+
taskRequests.close();
|
|
1156
1165
|
if (iterEnded) return;
|
|
1157
1166
|
iterEnded = true;
|
|
1158
1167
|
if (iterWaiting) {
|
|
@@ -1165,6 +1174,7 @@ function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool,
|
|
|
1165
1174
|
function errorIter(err) {
|
|
1166
1175
|
interactionAbort.abort();
|
|
1167
1176
|
contextRequests.close();
|
|
1177
|
+
taskRequests.close();
|
|
1168
1178
|
if (iterEnded) return;
|
|
1169
1179
|
iterEnded = true;
|
|
1170
1180
|
iterError = err;
|
|
@@ -1292,6 +1302,9 @@ function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool,
|
|
|
1292
1302
|
case "model_switched":
|
|
1293
1303
|
if (onModelSwitch) onModelSwitch(msg.change);
|
|
1294
1304
|
break;
|
|
1305
|
+
case "background_task_response":
|
|
1306
|
+
taskRequests.receive(msg);
|
|
1307
|
+
break;
|
|
1295
1308
|
case "context_usage_response":
|
|
1296
1309
|
contextRequests.receive(msg);
|
|
1297
1310
|
break;
|
|
@@ -1458,6 +1471,8 @@ function createWorkerQueryHandle(worker, canUseTool, onElicitation, callMcpTool,
|
|
|
1458
1471
|
});
|
|
1459
1472
|
},
|
|
1460
1473
|
|
|
1474
|
+
readBackgroundTask: function(input) { return iterEnded ? Promise.reject(new Error("Task connection closed.")) : taskRequests.request(input); },
|
|
1475
|
+
|
|
1461
1476
|
stopTask: function(taskId) {
|
|
1462
1477
|
worker.send({ type: "stop_task", taskId: taskId });
|
|
1463
1478
|
return Promise.resolve();
|
|
@@ -1821,7 +1836,7 @@ function createClaudeAdapter(opts) {
|
|
|
1821
1836
|
|
|
1822
1837
|
claudeModelSwitch.attach(sdkOptions, queryOpts.onModelSwitch);
|
|
1823
1838
|
var rawQuery = sdk.query({ prompt: mq, options: sdkOptions });
|
|
1824
|
-
return createQueryHandle(rawQuery, mq, ac);
|
|
1839
|
+
return createQueryHandle(rawQuery, mq, ac, sdkOptions.cwd || _cwd);
|
|
1825
1840
|
},
|
|
1826
1841
|
|
|
1827
1842
|
createOneShotQuery: async function(queryOpts) {
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// Native task inspection stays inside the process and OS identity owning the query.
|
|
2
|
+
var crypto = require('crypto');
|
|
3
|
+
function validId(value) { return typeof value === 'string' && /^[a-zA-Z0-9_-]{1,160}$/.test(value); }
|
|
4
|
+
function createReader(loadSdk, getQuery, dir) {
|
|
5
|
+
var sessionId = null, tasks = new Map(), closed = false;
|
|
6
|
+
function observe(event) {
|
|
7
|
+
if (!event) return;
|
|
8
|
+
if (event.session_id && !event.parent_tool_use_id) sessionId = event.session_id;
|
|
9
|
+
if (event.type !== 'system') return;
|
|
10
|
+
if (event.subtype === 'background_tasks_changed') {
|
|
11
|
+
tasks.forEach(function (task) { task.active = false; });
|
|
12
|
+
(event.tasks || []).slice(0, 128).forEach(function (task) {
|
|
13
|
+
if (!validId(task.task_id)) return;
|
|
14
|
+
var old = tasks.get(task.task_id);
|
|
15
|
+
if (old && String(old.run_id || '') === String(task.run_id || '') && old.task_type === task.task_type) Object.assign(old, task, { active: true });
|
|
16
|
+
else tasks.set(task.task_id, Object.assign({}, task, { active: true }));
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
if (event.subtype === 'task_notification') {
|
|
20
|
+
var old = tasks.get(event.task_id);
|
|
21
|
+
if (old && (!event.run_id || old.run_id === event.run_id)) old.active = false;
|
|
22
|
+
}
|
|
23
|
+
while (tasks.size > 128) tasks.delete(tasks.keys().next().value);
|
|
24
|
+
}
|
|
25
|
+
async function read(input) {
|
|
26
|
+
var task = tasks.get(input.taskId), query = getQuery(), parent = sessionId;
|
|
27
|
+
if (closed || !query || !task || !validId(input.taskId) || String(task.run_id || '') !== String(input.runId || '')) throw new Error('Task binding is unavailable.');
|
|
28
|
+
var offset = Number.isInteger(input.offset) && input.offset >= 0 && input.offset <= 10000 ? input.offset : 0;
|
|
29
|
+
function current() { if (closed || query !== getQuery() || parent !== sessionId || tasks.get(input.taskId) !== task) throw new Error('Task binding changed.'); }
|
|
30
|
+
var result;
|
|
31
|
+
if (task.task_type === 'local_agent') {
|
|
32
|
+
if (!parent || !dir) throw new Error('Task transcript is not ready.');
|
|
33
|
+
var sdk = await loadSdk(); current();
|
|
34
|
+
var agents = await sdk.listSubagents(parent, { dir: dir }); current();
|
|
35
|
+
// Never guess a sibling agent from its name, ordering or filesystem path.
|
|
36
|
+
if (agents.indexOf(task.task_id) < 0) throw new Error('Task transcript is not available yet.');
|
|
37
|
+
var messages = await sdk.getSubagentMessages(parent, task.task_id, { dir: dir, offset: offset, limit: 101 }); current();
|
|
38
|
+
result = { kind: 'agent', messages: projectMessages(messages.slice(0, 100), offset), offset: offset, hasMore: messages.length > 100, active: task.active };
|
|
39
|
+
} else {
|
|
40
|
+
if (typeof query.getTaskOutput !== 'function') throw new Error('This runtime does not expose task output.');
|
|
41
|
+
var output = await query.getTaskOutput(task.task_id); current();
|
|
42
|
+
result = { kind: 'shell', output: String(output.output || '').slice(-8192), truncated: !!output.truncated, active: task.active };
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
return { observe: observe, read: read, close: function () { closed = true; tasks.clear(); } };
|
|
47
|
+
}
|
|
48
|
+
function projectMessages(messages, offset) {
|
|
49
|
+
var rows = [], budget = 180000;
|
|
50
|
+
messages.forEach(function (message, index) {
|
|
51
|
+
var content = message.message && message.message.content;
|
|
52
|
+
if (typeof content === 'string') content = [{ type: 'text', text: content }];
|
|
53
|
+
if (!Array.isArray(content)) return;
|
|
54
|
+
content.slice(0, 100).forEach(function (block, blockIndex) {
|
|
55
|
+
if (budget <= 0) return;
|
|
56
|
+
var text, role = message.type === 'user' ? 'user' : 'assistant', type = block.type;
|
|
57
|
+
if (type === 'text') text = block.text;
|
|
58
|
+
else if (type === 'tool_use') { role = 'tool'; text = block.name + '\n' + JSON.stringify(block.input || {}); }
|
|
59
|
+
else if (type === 'tool_result') { role = 'tool'; text = typeof block.content === 'string' ? block.content : JSON.stringify(block.content || []); }
|
|
60
|
+
else return;
|
|
61
|
+
if (typeof text !== 'string') return;
|
|
62
|
+
text = text.slice(0, Math.min(16000, budget)); budget -= text.length;
|
|
63
|
+
rows.push({ role: role, text: text, recordIndex: ((offset || 0) + index) * 100 + blockIndex, recordType: type, toolName: block.name, toolId: block.id || block.tool_use_id, isError: !!block.is_error });
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
return rows;
|
|
67
|
+
}
|
|
68
|
+
function createRequests(worker) {
|
|
69
|
+
var pending = new Map();
|
|
70
|
+
function close() { pending.forEach(function (entry) { clearTimeout(entry.timer); entry.reject(new Error('Task connection closed.')); }); pending.clear(); }
|
|
71
|
+
return {
|
|
72
|
+
close: close,
|
|
73
|
+
request: function (input) {
|
|
74
|
+
if (pending.size >= 8) return Promise.reject(new Error('Task reader is busy.'));
|
|
75
|
+
return new Promise(function (resolve, reject) {
|
|
76
|
+
var id = crypto.randomUUID();
|
|
77
|
+
var timer = setTimeout(function () { pending.delete(id); reject(new Error('Task read timed out.')); }, 15000);
|
|
78
|
+
pending.set(id, { resolve: resolve, reject: reject, timer: timer });
|
|
79
|
+
try { if (worker.send({ type: 'read_background_task', requestId: id, input: input }) !== true) throw new Error('Task connection closed.'); }
|
|
80
|
+
catch (error) { pending.delete(id); clearTimeout(timer); reject(error); }
|
|
81
|
+
});
|
|
82
|
+
},
|
|
83
|
+
receive: function (message) {
|
|
84
|
+
var entry = pending.get(message.requestId); if (!entry) return;
|
|
85
|
+
pending.delete(message.requestId); clearTimeout(entry.timer);
|
|
86
|
+
if (message.error) entry.reject(new Error(message.error)); else entry.resolve(message.data);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
async function respond(reader, message, send) {
|
|
91
|
+
try { if (!reader) throw new Error('Task query is unavailable.'); var data = await reader.read(message.input || {}); send({ type: 'background_task_response', requestId: message.requestId, data: data }); }
|
|
92
|
+
catch (error) { send({ type: 'background_task_response', requestId: message.requestId, error: error.message }); }
|
|
93
|
+
}
|
|
94
|
+
module.exports = { createReader: createReader, createRequests: createRequests, respond: respond, projectMessages: projectMessages };
|
package/lib/yoke/interface.js
CHANGED
|
@@ -198,6 +198,7 @@ var INITIALIZE_TIMEOUT_MS = 90000;
|
|
|
198
198
|
* .setModel(model)
|
|
199
199
|
* .setEffort(effort)
|
|
200
200
|
* .setToolPolicy(policy) - "ask" | "allow-all"
|
|
201
|
+
* .readBackgroundTask({ taskId, runId, offset }) - optional bounded native transcript/output read
|
|
201
202
|
* .stopTask(taskId)
|
|
202
203
|
* .getContextUsage() - Promise<object|null>
|
|
203
204
|
* .endInput() - stop accepting turns after queued input drains
|
package/package.json
CHANGED