clay-server 3.8.1 → 4.0.0-beta.1
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 +5 -119
- package/lib/builtin-mates.js +6 -6
- package/lib/claude-hook-installer.js +15 -0
- package/lib/clay-history-mcp-server.js +62 -297
- package/lib/daemon.js +1 -1
- package/lib/debate-model-selection.js +119 -0
- package/lib/home-debate-tool-policy.js +164 -0
- package/lib/home-response-notifications.js +26 -0
- package/lib/mate-creation-mcp-server.js +55 -0
- package/lib/mate-ready-creation.js +95 -0
- package/lib/mates.js +2 -0
- package/lib/notes.js +2 -1
- package/lib/project-ask-user.js +64 -0
- package/lib/project-capsule-catalog.js +101 -0
- package/lib/project-debate-proposal.js +133 -17
- package/lib/project-debate.js +214 -110
- package/lib/project-delegated-follow-up.js +84 -0
- package/lib/project-delegated-session.js +53 -0
- package/lib/project-filesystem.js +7 -9
- package/lib/project-knowledge.js +1 -0
- package/lib/project-mate-creation-proposal.js +120 -0
- package/lib/project-memory.js +14 -7
- package/lib/project-models.js +157 -32
- package/lib/project-session-notes.js +2 -2
- package/lib/project-session-pair.js +8 -8
- package/lib/project-sessions.js +66 -51
- package/lib/project-worker-proposal.js +25 -25
- package/lib/project-workspace-query.js +144 -0
- package/lib/project.js +329 -102
- package/lib/public/app.js +85 -42
- package/lib/public/css/avatar-imprints.css +0 -1
- package/lib/public/css/base.css +4 -0
- package/lib/public/css/capsule-ui.css +389 -0
- package/lib/public/css/command-palette.css +306 -6
- package/lib/public/css/home-capsule-library.css +243 -0
- package/lib/public/css/home-capsule-source.css +192 -0
- package/lib/public/css/home-debate-live.css +146 -0
- package/lib/public/css/home-debate-planning.css +125 -0
- package/lib/public/css/home-debates-archive.css +281 -0
- package/lib/public/css/home-hub.css +896 -405
- package/lib/public/css/home-mate-creation.css +34 -0
- package/lib/public/css/home-mate-model-picker.css +131 -0
- package/lib/public/css/home-mate-settings.css +344 -0
- package/lib/public/css/home-session-actions.css +265 -0
- package/lib/public/css/home-sidebar.css +667 -0
- package/lib/public/css/icon-strip.css +1 -2
- package/lib/public/css/mates.css +7 -34
- package/lib/public/css/pane.css +1 -1
- package/lib/public/css/playbook.css +0 -80
- package/lib/public/css/sticky-notes.css +53 -117
- package/lib/public/css/tui-attention.css +0 -44
- package/lib/public/css/workspace-assignment.css +45 -0
- package/lib/public/fonts/source-serif-4/LICENSE.md +93 -0
- package/lib/public/fonts/source-serif-4/SourceSerif4Caption-Semibold.ttf.woff2 +0 -0
- package/lib/public/index.html +93 -74
- package/lib/public/modules/app-connection.js +10 -1
- package/lib/public/modules/app-dm.js +43 -160
- package/lib/public/modules/app-home-hub.js +328 -585
- package/lib/public/modules/app-message-cards.js +242 -0
- package/lib/public/modules/app-message-router.js +84 -0
- package/lib/public/modules/app-messages.js +78 -20
- package/lib/public/modules/app-notifications.js +4 -2
- package/lib/public/modules/app-projects.js +9 -1
- package/lib/public/modules/app-rendering.js +21 -602
- package/lib/public/modules/avatar-imprint.js +11 -8
- package/lib/public/modules/chat-bubble-renderer.js +152 -0
- package/lib/public/modules/chat-render-runtime.js +198 -0
- package/lib/public/modules/command-palette.js +225 -436
- package/lib/public/modules/dm-render.js +82 -0
- package/lib/public/modules/home-capsule-creation-intent.js +31 -0
- package/lib/public/modules/home-capsule-library.js +146 -0
- package/lib/public/modules/home-capsule-source.js +383 -0
- package/lib/public/modules/home-chat-identity.js +35 -0
- package/lib/public/modules/home-chat-scroll.js +77 -0
- package/lib/public/modules/home-chat-stream-state.js +34 -0
- package/lib/public/modules/home-composer-focus.js +25 -0
- package/lib/public/modules/home-conversations-sheet.js +179 -0
- package/lib/public/modules/home-debate-activity.js +48 -0
- package/lib/public/modules/home-debate-controls.js +295 -0
- package/lib/public/modules/home-debate-launch.js +41 -0
- package/lib/public/modules/home-debate-live.js +284 -0
- package/lib/public/modules/home-debate-models.js +73 -0
- package/lib/public/modules/home-debate-planning.js +335 -0
- package/lib/public/modules/home-debates-archive.js +279 -0
- package/lib/public/modules/home-dock-resize.js +74 -0
- package/lib/public/modules/home-dock.js +446 -0
- package/lib/public/modules/home-mate-chat.js +496 -0
- package/lib/public/modules/home-mate-creation.js +109 -0
- package/lib/public/modules/home-mate-model-picker.js +281 -0
- package/lib/public/modules/home-mate-selection.js +38 -0
- package/lib/public/modules/home-mate-settings-menu.js +131 -0
- package/lib/public/modules/home-mate-settings.js +383 -0
- package/lib/public/modules/home-session-actions.js +373 -0
- package/lib/public/modules/home-session-model-confirmation.js +13 -0
- package/lib/public/modules/home-shell.js +9 -0
- package/lib/public/modules/home-sidebar-chat-list.js +63 -0
- package/lib/public/modules/home-sidebar.js +158 -0
- package/lib/public/modules/home-sub-surface.js +19 -0
- package/lib/public/modules/home-surface-boot.js +68 -0
- package/lib/public/modules/home-surface.js +141 -0
- package/lib/public/modules/home-tools.js +271 -0
- package/lib/public/modules/markdown.js +50 -2
- package/lib/public/modules/mate-management.js +86 -0
- package/lib/public/modules/mate-wizard.js +0 -7
- package/lib/public/modules/mention.js +17 -19
- package/lib/public/modules/profile.js +4 -5
- package/lib/public/modules/project-settings.js +34 -28
- package/lib/public/modules/project-switcher.js +11 -5
- package/lib/public/modules/project-workspace-assignment.js +23 -0
- package/lib/public/modules/search-clay-chat.js +402 -0
- package/lib/public/modules/server-settings.js +14 -12
- package/lib/public/modules/session-actions.js +1 -1
- package/lib/public/modules/sidebar-mates.js +56 -552
- package/lib/public/modules/sidebar-mobile.js +7 -64
- package/lib/public/modules/sidebar-presence.js +37 -0
- package/lib/public/modules/split-pair-ui.js +13 -13
- package/lib/public/modules/sticky-notes.js +7 -6
- package/lib/public/modules/tool-input-composition.js +48 -0
- package/lib/public/modules/tool-llm-status.js +167 -0
- package/lib/public/modules/tool-renderer-advanced.js +285 -0
- package/lib/public/modules/tool-renderer-chart.js +158 -0
- package/lib/public/modules/tool-renderer-semantics.js +35 -0
- package/lib/public/modules/tool-renderer.js +464 -0
- package/lib/public/modules/tool-runtime.js +309 -0
- package/lib/public/modules/tool-ui-evaluator.js +97 -0
- package/lib/public/modules/tool-ui-tree.js +21 -0
- package/lib/public/modules/user-settings.js +16 -23
- package/lib/public/modules/worker-proposal.js +2 -2
- package/lib/public/modules/workspace-assignment-card.js +149 -0
- package/lib/public/modules/workspace-assignment-routing.js +4 -0
- package/lib/public/style.css +13 -2
- package/lib/runtime-env.js +65 -0
- package/lib/sdk-bridge.js +120 -32
- package/lib/sdk-message-processor.js +10 -6
- package/lib/server-home-capsule-creation.js +36 -0
- package/lib/server-home-chat-events.js +287 -0
- package/lib/server-home-chat.js +518 -0
- package/lib/server-home-clay-entry.js +65 -0
- package/lib/server-home-clay-session-links.js +26 -0
- package/lib/server-home-debate-planning.js +214 -0
- package/lib/server-home-debates.js +137 -0
- package/lib/server-home-mate-creation.js +261 -0
- package/lib/server-home-models.js +241 -0
- package/lib/server-home-preferences.js +61 -0
- package/lib/server-mates.js +37 -0
- package/lib/server-tools.js +377 -0
- package/lib/server.js +111 -59
- package/lib/session-pair-mcp-server.js +6 -6
- package/lib/sessions.js +18 -3
- package/lib/tool-capsule-source.js +142 -0
- package/lib/tool-control-mcp-server.js +126 -0
- package/lib/tool-llm.js +48 -0
- package/lib/tool-storage.js +77 -0
- package/lib/tool-ui-spec-advanced.js +82 -0
- package/lib/tool-ui-spec.js +261 -0
- package/lib/tools-registry.js +276 -0
- package/lib/users-home-dock-preferences.js +77 -0
- package/lib/users-home-surface-preferences.js +99 -0
- package/lib/users-preferences.js +4 -7
- package/lib/users.js +12 -0
- package/lib/whats-new-content.js +3 -3
- package/lib/workspace-assignment-service.js +420 -0
- package/lib/workspace-query-mcp-server.js +154 -0
- package/lib/workspace-query-service.js +492 -0
- package/lib/ws-schema.js +86 -12
- package/lib/yoke/adapters/acp.js +2 -1
- package/lib/yoke/adapters/antigravity.js +2 -1
- package/lib/yoke/adapters/claude-worker.js +11 -4
- package/lib/yoke/adapters/claude.js +73 -43
- package/lib/yoke/adapters/codex.js +37 -6
- package/lib/yoke/adapters/kiro.js +2 -1
- package/lib/yoke/claude-user-input.js +38 -0
- package/lib/yoke/codex-user-input.js +64 -0
- package/lib/yoke/complete-once.js +164 -0
- package/lib/yoke/index.js +5 -1
- package/lib/yoke/interface.js +19 -0
- package/lib/yoke/mcp-bridge-server.js +2 -6
- package/lib/yoke/user-input.js +254 -0
- package/package.json +2 -1
- package/lib/public/css/home-chat.css +0 -380
- package/lib/public/modules/home-chat.js +0 -438
- package/lib/server-clay-home.js +0 -245
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
// tool-runtime.js - Web Worker host for user-installed tool logic.
|
|
2
|
+
|
|
3
|
+
import { getWs } from './ws-ref.js';
|
|
4
|
+
|
|
5
|
+
var ACTION_TIMEOUT_MS = 10000;
|
|
6
|
+
var LLM_ACTION_TIMEOUT_MS = 90000;
|
|
7
|
+
var LLM_TIMEOUT_MS = 60000;
|
|
8
|
+
var WS_WAIT_MS = 5000;
|
|
9
|
+
var WS_RETRY_MS = 250;
|
|
10
|
+
|
|
11
|
+
function workerProgram(logicSource, allowLlm) {
|
|
12
|
+
return [
|
|
13
|
+
"self.fetch = undefined; self.XMLHttpRequest = undefined; self.WebSocket = undefined; self.EventSource = undefined; self.importScripts = undefined;",
|
|
14
|
+
"var tool; var currentState = {}; var storageSeq = 0; var storagePending = Object.create(null); var llmSeq = 0; var llmPending = Object.create(null); var actionQueue = Promise.resolve();",
|
|
15
|
+
"function storageCall(op, args) {",
|
|
16
|
+
" return new Promise(function (resolve, reject) {",
|
|
17
|
+
" storageSeq++; storagePending[storageSeq] = { resolve: resolve, reject: reject };",
|
|
18
|
+
" self.postMessage({ type: 'storage', op: op, args: args || {}, seq: storageSeq });",
|
|
19
|
+
" });",
|
|
20
|
+
"}",
|
|
21
|
+
"function llmCall(args, callerId) {",
|
|
22
|
+
allowLlm ? " return new Promise(function (resolve, reject) { llmSeq++; llmPending[llmSeq] = { resolve: resolve, reject: reject }; self.postMessage({ type: 'llm', seq: llmSeq, args: args || {}, callerId: callerId || 'user' }); });" : " return Promise.reject(new Error('This capsule does not have the llm permission.'));",
|
|
23
|
+
"}",
|
|
24
|
+
"var api = { storage: {",
|
|
25
|
+
" list: function () { return storageCall('list', {}); },",
|
|
26
|
+
" get: function (id) { return storageCall('get', { id: id }); },",
|
|
27
|
+
" put: function (doc) { return storageCall('put', { doc: doc }); },",
|
|
28
|
+
" delete: function (id) { return storageCall('delete', { id: id }); },",
|
|
29
|
+
" query: function (query) { return storageCall('query', { query: query }); }",
|
|
30
|
+
"}, llm: { complete: function () { return Promise.reject(new Error('api.llm.complete is only available inside an action.')); } } };",
|
|
31
|
+
logicSource,
|
|
32
|
+
"if (!tool || !tool.actions || typeof tool.actions !== 'object') throw new Error('logic.js must define var tool with an actions object.');",
|
|
33
|
+
"self.onmessage = function (event) {",
|
|
34
|
+
" var msg = event.data || {};",
|
|
35
|
+
" if (msg.type === 'init') {",
|
|
36
|
+
" currentState = msg.state || tool.initialState || {};",
|
|
37
|
+
" self.postMessage({ type: 'state', newState: currentState }); return;",
|
|
38
|
+
" }",
|
|
39
|
+
" if (msg.type === 'storage_result') {",
|
|
40
|
+
" var pending = storagePending[msg.seq]; if (!pending) return; delete storagePending[msg.seq];",
|
|
41
|
+
" if (msg.error) pending.reject(new Error(msg.error)); else pending.resolve(msg.data); return;",
|
|
42
|
+
" }",
|
|
43
|
+
" if (msg.type === 'llm_result') {",
|
|
44
|
+
" var llmRequest = llmPending[msg.seq]; if (!llmRequest) return; delete llmPending[msg.seq];",
|
|
45
|
+
" if (msg.error) llmRequest.reject(new Error(msg.error)); else llmRequest.resolve(msg.data); return;",
|
|
46
|
+
" }",
|
|
47
|
+
" if (msg.type === 'action') {",
|
|
48
|
+
" var action = tool.actions[msg.name];",
|
|
49
|
+
" if (typeof action !== 'function') { self.postMessage({ type: 'error', message: 'Unknown action: ' + msg.name, actionSeq: msg.actionSeq }); return; }",
|
|
50
|
+
" var actionStartState;",
|
|
51
|
+
" actionQueue = actionQueue.then(function () {",
|
|
52
|
+
" actionStartState = currentState;",
|
|
53
|
+
" var actionApi = Object.create(api); actionApi.callerId = msg.callerId || 'user'; actionApi.llm = { complete: function (args) { return llmCall(args, actionApi.callerId); } };",
|
|
54
|
+
" actionApi.setState = function (nextState) { if (!nextState || typeof nextState !== 'object' || Array.isArray(nextState)) throw new Error('api.setState requires a state object.'); currentState = nextState; self.postMessage({ type: 'state', newState: currentState, actionSeq: msg.actionSeq, intermediate: true }); };",
|
|
55
|
+
" return action(currentState, msg.args || {}, actionApi);",
|
|
56
|
+
" }).then(function (nextState) {",
|
|
57
|
+
" if (nextState !== undefined) currentState = nextState;",
|
|
58
|
+
" self.postMessage({ type: 'state', newState: currentState, actionSeq: msg.actionSeq });",
|
|
59
|
+
" }).catch(function (error) { if (actionStartState !== undefined) { currentState = actionStartState; self.postMessage({ type: 'state', newState: currentState, actionSeq: msg.actionSeq, intermediate: true, rollback: true }); } self.postMessage({ type: 'error', message: error && error.message ? error.message : String(error), actionSeq: msg.actionSeq }); });",
|
|
60
|
+
" }",
|
|
61
|
+
"};",
|
|
62
|
+
].join("\n");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function createToolRuntime(config) {
|
|
66
|
+
var worker = null;
|
|
67
|
+
var state = config.state || null;
|
|
68
|
+
var actionSeq = 0;
|
|
69
|
+
var actionTimeouts = Object.create(null);
|
|
70
|
+
var stopped = false;
|
|
71
|
+
var restartCount = 0;
|
|
72
|
+
var initialActionSent = false;
|
|
73
|
+
var workerGeneration = 0;
|
|
74
|
+
var pendingStorage = Object.create(null);
|
|
75
|
+
var pendingLlm = Object.create(null);
|
|
76
|
+
var pendingActions = Object.create(null);
|
|
77
|
+
var readyWaiters = [];
|
|
78
|
+
var isReady = false;
|
|
79
|
+
|
|
80
|
+
function clearActionTimeout() {
|
|
81
|
+
var keys = Object.keys(actionTimeouts);
|
|
82
|
+
for (var i = 0; i < keys.length; i++) clearTimeout(actionTimeouts[keys[i]]);
|
|
83
|
+
actionTimeouts = Object.create(null);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function clearOneActionTimeout(seq) {
|
|
87
|
+
if (seq === undefined || !actionTimeouts[seq]) return;
|
|
88
|
+
clearTimeout(actionTimeouts[seq]);
|
|
89
|
+
delete actionTimeouts[seq];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function rejectActions(message) {
|
|
93
|
+
var keys = Object.keys(pendingActions);
|
|
94
|
+
for (var i = 0; i < keys.length; i++) pendingActions[keys[i]].reject(new Error(message));
|
|
95
|
+
pendingActions = Object.create(null);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function reportError(message) {
|
|
99
|
+
if (typeof config.onError === "function") config.onError(message);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function markReady() {
|
|
103
|
+
isReady = true;
|
|
104
|
+
if (readyWaiters.length === 0) return;
|
|
105
|
+
var waiters = readyWaiters;
|
|
106
|
+
readyWaiters = [];
|
|
107
|
+
for (var i = 0; i < waiters.length; i++) waiters[i].resolve(state);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function terminateWorker() {
|
|
111
|
+
clearActionTimeout();
|
|
112
|
+
if (worker) worker.terminate();
|
|
113
|
+
worker = null;
|
|
114
|
+
pendingStorage = Object.create(null);
|
|
115
|
+
var llmKeys = Object.keys(pendingLlm);
|
|
116
|
+
for (var li = 0; li < llmKeys.length; li++) clearTimeout(pendingLlm[llmKeys[li]].timer);
|
|
117
|
+
pendingLlm = Object.create(null);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function restartWorker(message) {
|
|
121
|
+
rejectActions(message);
|
|
122
|
+
terminateWorker();
|
|
123
|
+
reportError(message);
|
|
124
|
+
if (stopped) return;
|
|
125
|
+
restartCount++;
|
|
126
|
+
if (restartCount > 3) {
|
|
127
|
+
reportError("Tool worker stopped after repeated failures.");
|
|
128
|
+
var waiters = readyWaiters;
|
|
129
|
+
readyWaiters = [];
|
|
130
|
+
for (var i = 0; i < waiters.length; i++) waiters[i].reject(new Error(message));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
initialActionSent = false;
|
|
134
|
+
isReady = false;
|
|
135
|
+
setTimeout(function () { if (!stopped) spawnWorker(); }, 100);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function withReadySocket(startedAt, generation, onReady, onTimeout) {
|
|
139
|
+
if (stopped || generation !== workerGeneration || !worker) return;
|
|
140
|
+
var ws = getWs();
|
|
141
|
+
if (ws && ws.readyState === 1) {
|
|
142
|
+
try {
|
|
143
|
+
onReady(ws);
|
|
144
|
+
return;
|
|
145
|
+
} catch (error) {}
|
|
146
|
+
}
|
|
147
|
+
var waitMs = config.wsWaitMs === undefined ? WS_WAIT_MS : config.wsWaitMs;
|
|
148
|
+
if (Date.now() - startedAt >= waitMs) {
|
|
149
|
+
onTimeout();
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
var retryMs = config.wsRetryMs === undefined ? WS_RETRY_MS : config.wsRetryMs;
|
|
153
|
+
setTimeout(function () { withReadySocket(startedAt, generation, onReady, onTimeout); }, retryMs);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function sendStorageOperation(msg) {
|
|
157
|
+
var generation = workerGeneration;
|
|
158
|
+
withReadySocket(Date.now(), generation, function (ws) {
|
|
159
|
+
var externalSeq = config.toolId + ":" + generation + ":" + msg.seq;
|
|
160
|
+
pendingStorage[externalSeq] = msg.seq;
|
|
161
|
+
try {
|
|
162
|
+
ws.send(JSON.stringify({ type: "tool_storage_op", toolId: config.toolId, op: msg.op, args: msg.args, seq: externalSeq }));
|
|
163
|
+
} catch (error) {
|
|
164
|
+
delete pendingStorage[externalSeq];
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
167
|
+
}, function () {
|
|
168
|
+
if (worker && generation === workerGeneration) {
|
|
169
|
+
worker.postMessage({ type: "storage_result", seq: msg.seq, error: "Tool storage is unavailable after waiting 5 seconds for the connection." });
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function sendLlmOperation(msg) {
|
|
175
|
+
if (!config.allowLlm) {
|
|
176
|
+
worker.postMessage({ type: "llm_result", seq: msg.seq, error: "This capsule does not have the llm permission." });
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (typeof config.onLlmRequest === "function") config.onLlmRequest(msg.args && msg.args.model ? msg.args.model : "fast");
|
|
180
|
+
var generation = workerGeneration;
|
|
181
|
+
withReadySocket(Date.now(), generation, function (ws) {
|
|
182
|
+
var requestId = config.toolId + ":llm:" + generation + ":" + msg.seq;
|
|
183
|
+
pendingLlm[requestId] = {
|
|
184
|
+
seq: msg.seq,
|
|
185
|
+
timer: setTimeout(function () {
|
|
186
|
+
if (!pendingLlm[requestId] || !worker) return;
|
|
187
|
+
delete pendingLlm[requestId];
|
|
188
|
+
worker.postMessage({ type: "llm_result", seq: msg.seq, error: "LLM request timed out after 60 seconds." });
|
|
189
|
+
}, LLM_TIMEOUT_MS),
|
|
190
|
+
};
|
|
191
|
+
try {
|
|
192
|
+
ws.send(JSON.stringify({ type: "tool_llm_op", toolId: config.toolId, requestId: requestId, args: msg.args, callerId: msg.callerId || "user" }));
|
|
193
|
+
} catch (error) {
|
|
194
|
+
clearTimeout(pendingLlm[requestId].timer);
|
|
195
|
+
delete pendingLlm[requestId];
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
}, function () {
|
|
199
|
+
if (worker && generation === workerGeneration) {
|
|
200
|
+
worker.postMessage({ type: "llm_result", seq: msg.seq, error: "Tool LLM service is unavailable after waiting 5 seconds for the connection." });
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function handleWorkerMessage(event) {
|
|
206
|
+
var msg = event.data || {};
|
|
207
|
+
if (msg.type === "state") {
|
|
208
|
+
restartCount = 0;
|
|
209
|
+
state = msg.newState || {};
|
|
210
|
+
if (typeof config.onState === "function") config.onState(state);
|
|
211
|
+
if (msg.intermediate) return;
|
|
212
|
+
clearOneActionTimeout(msg.actionSeq);
|
|
213
|
+
if (msg.actionSeq !== undefined && pendingActions[msg.actionSeq]) {
|
|
214
|
+
pendingActions[msg.actionSeq].resolve(state);
|
|
215
|
+
delete pendingActions[msg.actionSeq];
|
|
216
|
+
}
|
|
217
|
+
var shouldInitialize = !initialActionSent && config.initialAction;
|
|
218
|
+
if (shouldInitialize) {
|
|
219
|
+
initialActionSent = true;
|
|
220
|
+
dispatchAction(config.initialAction, {}, "user").catch(function () { markReady(); });
|
|
221
|
+
} else {
|
|
222
|
+
markReady();
|
|
223
|
+
}
|
|
224
|
+
} else if (msg.type === "storage") {
|
|
225
|
+
sendStorageOperation(msg);
|
|
226
|
+
} else if (msg.type === "llm") {
|
|
227
|
+
sendLlmOperation(msg);
|
|
228
|
+
} else if (msg.type === "error") {
|
|
229
|
+
clearOneActionTimeout(msg.actionSeq);
|
|
230
|
+
if (msg.actionSeq !== undefined) {
|
|
231
|
+
if (pendingActions[msg.actionSeq]) {
|
|
232
|
+
pendingActions[msg.actionSeq].reject(new Error(msg.message || "Tool action failed."));
|
|
233
|
+
delete pendingActions[msg.actionSeq];
|
|
234
|
+
}
|
|
235
|
+
reportError(msg.message || "Tool action failed.");
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
restartWorker(msg.message || "Tool worker failed.");
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function spawnWorker() {
|
|
243
|
+
terminateWorker();
|
|
244
|
+
workerGeneration++;
|
|
245
|
+
var blob = new Blob([workerProgram(config.logicSource, !!config.allowLlm)], { type: "text/javascript" });
|
|
246
|
+
var url = URL.createObjectURL(blob);
|
|
247
|
+
worker = new Worker(url);
|
|
248
|
+
URL.revokeObjectURL(url);
|
|
249
|
+
worker.onmessage = handleWorkerMessage;
|
|
250
|
+
worker.onerror = function (event) {
|
|
251
|
+
if (event.preventDefault) event.preventDefault();
|
|
252
|
+
restartWorker(event.message || "Tool worker crashed.");
|
|
253
|
+
};
|
|
254
|
+
worker.postMessage({ type: "init", state: state });
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function dispatchAction(name, args, callerId) {
|
|
258
|
+
if (!worker || stopped) return Promise.reject(new Error("Tool worker is not running."));
|
|
259
|
+
actionSeq++;
|
|
260
|
+
var currentActionSeq = actionSeq;
|
|
261
|
+
return new Promise(function (resolve, reject) {
|
|
262
|
+
pendingActions[currentActionSeq] = { resolve: resolve, reject: reject };
|
|
263
|
+
var timeoutMs = config.allowLlm ? LLM_ACTION_TIMEOUT_MS : ACTION_TIMEOUT_MS;
|
|
264
|
+
actionTimeouts[currentActionSeq] = setTimeout(function () {
|
|
265
|
+
restartWorker("Action '" + name + "' timed out after " + Math.round(timeoutMs / 1000) + " seconds.");
|
|
266
|
+
}, timeoutMs);
|
|
267
|
+
worker.postMessage({ type: "action", name: name, args: args || {}, callerId: callerId || "user", actionSeq: currentActionSeq });
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function handleStorageResult(msg) {
|
|
272
|
+
if (!worker) return;
|
|
273
|
+
var localSeq = pendingStorage[msg.seq];
|
|
274
|
+
if (localSeq === undefined) return;
|
|
275
|
+
delete pendingStorage[msg.seq];
|
|
276
|
+
worker.postMessage({ type: "storage_result", seq: localSeq, data: msg.data, error: msg.error });
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function handleLlmResult(msg) {
|
|
280
|
+
if (!worker) return;
|
|
281
|
+
var pending = pendingLlm[msg.requestId];
|
|
282
|
+
if (!pending) return;
|
|
283
|
+
clearTimeout(pending.timer);
|
|
284
|
+
delete pendingLlm[msg.requestId];
|
|
285
|
+
worker.postMessage({ type: "llm_result", seq: pending.seq, data: msg.data, error: msg.error });
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function start() {
|
|
289
|
+
stopped = false;
|
|
290
|
+
isReady = false;
|
|
291
|
+
spawnWorker();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function stop() {
|
|
295
|
+
stopped = true;
|
|
296
|
+
rejectActions("Tool worker stopped.");
|
|
297
|
+
var waiters = readyWaiters;
|
|
298
|
+
readyWaiters = [];
|
|
299
|
+
for (var i = 0; i < waiters.length; i++) waiters[i].reject(new Error("Tool worker stopped."));
|
|
300
|
+
terminateWorker();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function ready() {
|
|
304
|
+
if (isReady) return Promise.resolve(state);
|
|
305
|
+
return new Promise(function (resolve, reject) { readyWaiters.push({ resolve: resolve, reject: reject }); });
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return { start: start, ready: ready, action: dispatchAction, handleStorageResult: handleStorageResult, handleLlmResult: handleLlmResult, stop: stop, getState: function () { return state; } };
|
|
309
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Pure bounded evaluation for safe declarative Capsule UI.
|
|
2
|
+
|
|
3
|
+
var MAX_COLLECTION_ITEMS = 500;
|
|
4
|
+
|
|
5
|
+
export function normalizeOptions(value) {
|
|
6
|
+
if (!Array.isArray(value)) return [];
|
|
7
|
+
return value.slice(0, 100).reduce(function (result, option) {
|
|
8
|
+
if (typeof option === "string" || typeof option === "number") result.push({ value: option, label: String(option), disabled: false });
|
|
9
|
+
else if (option && typeof option === "object" && (typeof option.value === "string" || typeof option.value === "number") && typeof option.label === "string") result.push({ value: option.value, label: option.label.slice(0, 500), disabled: option.disabled === true });
|
|
10
|
+
return result;
|
|
11
|
+
}, []);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function normalizeColumns(value) {
|
|
15
|
+
if (!Array.isArray(value)) return [];
|
|
16
|
+
return value.slice(0, 20).filter(function (column) {
|
|
17
|
+
return column && typeof column === "object" && typeof column.key === "string" && /^(?:\$(?:state|item)\.)?[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*$/.test(column.key) && !/(?:__proto__|constructor|prototype)/.test(column.key);
|
|
18
|
+
}).map(function (column) { return { key: column.key, label: typeof column.label === "string" ? column.label.slice(0, 500) : column.key }; });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function valueAtPath(state, path, item) {
|
|
22
|
+
if (!path) return undefined;
|
|
23
|
+
var source = state;
|
|
24
|
+
var parts = path.split(".");
|
|
25
|
+
if (parts[0] === "$item") { source = item; parts.shift(); }
|
|
26
|
+
else if (parts[0] === "$state") parts.shift();
|
|
27
|
+
for (var i = 0; i < parts.length; i++) {
|
|
28
|
+
if (source === null || source === undefined) return undefined;
|
|
29
|
+
source = source[parts[i]];
|
|
30
|
+
}
|
|
31
|
+
return source;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function resolveValue(value, state, item) {
|
|
35
|
+
if (value && typeof value === "object" && !Array.isArray(value) && value.$bind) {
|
|
36
|
+
var bound = valueAtPath(state, value.$bind, item);
|
|
37
|
+
if (Array.isArray(value.$enum)) return value.$enum.indexOf(bound) !== -1 ? bound : value.fallback !== undefined ? value.fallback : value.$enum[0];
|
|
38
|
+
return bound === undefined ? value.fallback : bound;
|
|
39
|
+
}
|
|
40
|
+
if (typeof value === "string" && (value.indexOf("$item") === 0 || value.indexOf("$state") === 0)) return valueAtPath(state, value, item);
|
|
41
|
+
if (Array.isArray(value)) return value.map(function (entry) { return resolveValue(entry, state, item); });
|
|
42
|
+
if (value && typeof value === "object") {
|
|
43
|
+
var result = {};
|
|
44
|
+
Object.keys(value).forEach(function (key) { result[key] = resolveValue(value[key], state, item); });
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function evaluateCondition(condition, state, item) {
|
|
51
|
+
if (typeof condition === "string") return !!valueAtPath(state, condition, item);
|
|
52
|
+
if (!condition || typeof condition !== "object") return false;
|
|
53
|
+
if (condition.all) return condition.all.every(function (entry) { return evaluateCondition(entry, state, item); });
|
|
54
|
+
if (condition.any) return condition.any.some(function (entry) { return evaluateCondition(entry, state, item); });
|
|
55
|
+
if (condition.not) return !evaluateCondition(condition.not, state, item);
|
|
56
|
+
var operators = ["equals", "notEquals", "in", "gt", "gte", "lt", "lte"];
|
|
57
|
+
for (var i = 0; i < operators.length; i++) {
|
|
58
|
+
var operator = operators[i];
|
|
59
|
+
if (!condition[operator]) continue;
|
|
60
|
+
var rule = condition[operator];
|
|
61
|
+
var actual = valueAtPath(state, rule.path, item);
|
|
62
|
+
if (operator === "equals") return actual === rule.value;
|
|
63
|
+
if (operator === "notEquals") return actual !== rule.value;
|
|
64
|
+
if (operator === "in") return rule.values.indexOf(actual) !== -1;
|
|
65
|
+
if (typeof actual !== "number") return false;
|
|
66
|
+
if (operator === "gt") return actual > rule.value;
|
|
67
|
+
if (operator === "gte") return actual >= rule.value;
|
|
68
|
+
if (operator === "lt") return actual < rule.value;
|
|
69
|
+
if (operator === "lte") return actual <= rule.value;
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function resolveProps(props, state, item) {
|
|
75
|
+
var result = {};
|
|
76
|
+
Object.keys(props || {}).forEach(function (key) { result[key] = resolveValue(props[key], state, item); });
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function collectionView(value, props) {
|
|
81
|
+
var items = Array.isArray(value) ? value.slice(0, MAX_COLLECTION_ITEMS) : [];
|
|
82
|
+
var filter = String(props.filter || "").trim().toLocaleLowerCase();
|
|
83
|
+
if (filter) items = items.filter(function (item) {
|
|
84
|
+
var candidate = props.filterKey ? valueAtPath(item, props.filterKey, null) : item;
|
|
85
|
+
return String(candidate === undefined ? "" : candidate).toLocaleLowerCase().indexOf(filter) !== -1;
|
|
86
|
+
});
|
|
87
|
+
if (props.sortKey) items.sort(function (left, right) {
|
|
88
|
+
var a = valueAtPath(left, props.sortKey, null);
|
|
89
|
+
var b = valueAtPath(right, props.sortKey, null);
|
|
90
|
+
if (a === b) return 0;
|
|
91
|
+
var result = a === undefined || a === null ? 1 : b === undefined || b === null ? -1 : a < b ? -1 : 1;
|
|
92
|
+
return props.sortDirection === "desc" ? -result : result;
|
|
93
|
+
});
|
|
94
|
+
var pageSize = Math.max(1, Math.min(100, Number(props.pageSize) || 100));
|
|
95
|
+
var page = Math.max(1, Number(props.page) || 1);
|
|
96
|
+
return items.slice((page - 1) * pageSize, page * pageSize);
|
|
97
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Pure helpers for inspecting declarative Capsule UI trees.
|
|
2
|
+
|
|
3
|
+
export function hasToolUiNodeType(node, type) {
|
|
4
|
+
if (!node || typeof node !== "object") return false;
|
|
5
|
+
if (node.type === type) return true;
|
|
6
|
+
var children = Array.isArray(node.children) ? node.children : [];
|
|
7
|
+
for (var i = 0; i < children.length; i++) {
|
|
8
|
+
if (hasToolUiNodeType(children[i], type)) return true;
|
|
9
|
+
}
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function isToolModelAlias(value) {
|
|
14
|
+
return value === "fast" || value === "standard" || value === "deep";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function shouldInjectToolLlmStatus(definition) {
|
|
18
|
+
var permissions = definition && definition.manifest && Array.isArray(definition.manifest.permissions)
|
|
19
|
+
? definition.manifest.permissions : [];
|
|
20
|
+
return permissions.indexOf("llm") !== -1 && !hasToolUiNodeType(definition.uiTree, "model-select");
|
|
21
|
+
}
|
|
@@ -211,22 +211,20 @@ export function initUserSettings(appCtx) {
|
|
|
211
211
|
});
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
// via the body.mates-disabled CSS gate. Flipping back on restores the
|
|
217
|
-
// surface; existing Mate data is never deleted.
|
|
214
|
+
// This legacy preference now controls only project-local Mate DM entry
|
|
215
|
+
// points. Home, mentions, creation, debates, and Mate data stay available.
|
|
218
216
|
var matesEnabledToggle = document.getElementById('us-mates-enabled');
|
|
219
217
|
if (matesEnabledToggle) {
|
|
220
218
|
matesEnabledToggle.addEventListener('change', function () {
|
|
221
219
|
var want = !!this.checked;
|
|
222
|
-
|
|
223
|
-
store.set({
|
|
220
|
+
applyProjectMateDmsClass(want);
|
|
221
|
+
store.set({ projectMateDmsEnabled: want });
|
|
224
222
|
fetch('/api/user/mates-enabled', {
|
|
225
223
|
method: 'PUT',
|
|
226
224
|
headers: { 'Content-Type': 'application/json' },
|
|
227
225
|
body: JSON.stringify({ enabled: want }),
|
|
228
226
|
}).then(function (r) { return r.json(); }).then(function (data) {
|
|
229
|
-
if (data.ok) showToast(data.matesEnabled ? '
|
|
227
|
+
if (data.ok) showToast(data.matesEnabled ? 'Mate DMs shown in projects' : 'Mate DMs hidden in projects');
|
|
230
228
|
}).catch(function () {});
|
|
231
229
|
});
|
|
232
230
|
}
|
|
@@ -317,22 +315,17 @@ function stopProp(e) {
|
|
|
317
315
|
e.stopPropagation();
|
|
318
316
|
}
|
|
319
317
|
|
|
320
|
-
//
|
|
321
|
-
//
|
|
322
|
-
|
|
323
|
-
// underlying data. JS code paths that gate on `store.get('matesEnabled')`
|
|
324
|
-
// also short-circuit so we don't, for example, open the mate wizard if
|
|
325
|
-
// someone fires it programmatically.
|
|
326
|
-
function applyMatesEnabledClass(enabled) {
|
|
318
|
+
// Project-only Mate DM gate. The persisted server field retains its legacy
|
|
319
|
+
// name for migration compatibility, but it no longer disables Mate features.
|
|
320
|
+
function applyProjectMateDmsClass(enabled) {
|
|
327
321
|
if (!document.body) return;
|
|
328
|
-
document.body.classList.toggle('
|
|
322
|
+
document.body.classList.toggle('project-mate-dms-disabled', !enabled);
|
|
329
323
|
}
|
|
330
324
|
|
|
331
|
-
export function
|
|
332
|
-
// Default to true
|
|
333
|
-
//
|
|
334
|
-
|
|
335
|
-
return store.get('matesEnabled') !== false;
|
|
325
|
+
export function isProjectMateDmsEnabled() {
|
|
326
|
+
// Default to true until the profile arrives so boot does not briefly hide
|
|
327
|
+
// a project shortcut that the persisted preference enables.
|
|
328
|
+
return store.get('projectMateDmsEnabled') !== false;
|
|
336
329
|
}
|
|
337
330
|
|
|
338
331
|
function showPinForm() {
|
|
@@ -467,12 +460,12 @@ function populateAccount() {
|
|
|
467
460
|
if (data.mateOnboardingShown) {
|
|
468
461
|
try { localStorage.setItem("clay-mate-onboarding-shown", "1"); } catch (e) {}
|
|
469
462
|
}
|
|
470
|
-
//
|
|
463
|
+
// Project Mate DM shortcuts remain opt-in for legacy profiles.
|
|
471
464
|
var matesOn = data.matesEnabled === true;
|
|
472
465
|
var matesToggle = document.getElementById('us-mates-enabled');
|
|
473
466
|
if (matesToggle) matesToggle.checked = matesOn;
|
|
474
|
-
store.set({
|
|
475
|
-
|
|
467
|
+
store.set({ projectMateDmsEnabled: matesOn });
|
|
468
|
+
applyProjectMateDmsClass(matesOn);
|
|
476
469
|
if (data.chatLayout) {
|
|
477
470
|
setChatLayout(data.chatLayout); // update local cache + CSS
|
|
478
471
|
}
|
|
@@ -158,7 +158,7 @@ export function renderWorkerProposal(msg) {
|
|
|
158
158
|
|
|
159
159
|
var summary = document.createElement("p");
|
|
160
160
|
summary.className = "worker-proposal-summary";
|
|
161
|
-
summary.textContent = msg.summary || "This task can be planned here and executed in a visible Worker session.";
|
|
161
|
+
summary.textContent = msg.summary || "This task can be planned here and executed in a visible Split Worker session.";
|
|
162
162
|
card.appendChild(summary);
|
|
163
163
|
|
|
164
164
|
var steps = planSteps(msg.plan);
|
|
@@ -225,7 +225,7 @@ export function renderWorkerProposal(msg) {
|
|
|
225
225
|
var accept = document.createElement("button");
|
|
226
226
|
accept.type = "button";
|
|
227
227
|
accept.className = "worker-proposal-action primary";
|
|
228
|
-
accept.innerHTML = iconHtml("panel-right-open") + "<span>Run with Worker</span>";
|
|
228
|
+
accept.innerHTML = iconHtml("panel-right-open") + "<span>Run with Split Worker</span>";
|
|
229
229
|
accept.addEventListener("click", function () { sendDecision(card, true); });
|
|
230
230
|
actions.appendChild(decline);
|
|
231
231
|
actions.appendChild(accept);
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { getWs } from './ws-ref.js';
|
|
2
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
3
|
+
|
|
4
|
+
var pendingFocus = {};
|
|
5
|
+
|
|
6
|
+
function textElement(tag, className, text) {
|
|
7
|
+
var element = document.createElement(tag);
|
|
8
|
+
element.className = className;
|
|
9
|
+
element.textContent = text || "";
|
|
10
|
+
return element;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function statusLabel(status) {
|
|
14
|
+
if (status === "starting") return "Starting";
|
|
15
|
+
if (status === "running") return "In progress";
|
|
16
|
+
if (status === "completed") return "Completed";
|
|
17
|
+
if (status === "failed") return "Failed";
|
|
18
|
+
if (status === "cancelled") return "Cancelled";
|
|
19
|
+
if (status === "interrupted") return "Interrupted";
|
|
20
|
+
return "Approval required";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function sendDecision(assignment, action, surface, context, card, opener) {
|
|
24
|
+
var ws = getWs();
|
|
25
|
+
if (!ws || ws.readyState !== 1 || !assignment || assignment.status !== "proposed") return false;
|
|
26
|
+
pendingFocus[assignment.assignmentId] = opener || null;
|
|
27
|
+
card.setAttribute("aria-busy", "true");
|
|
28
|
+
var buttons = card.querySelectorAll("button");
|
|
29
|
+
for (var i = 0; i < buttons.length; i++) buttons[i].disabled = true;
|
|
30
|
+
ws.send(JSON.stringify({
|
|
31
|
+
type: "project_assignment_response",
|
|
32
|
+
assignmentId: assignment.assignmentId,
|
|
33
|
+
action: action,
|
|
34
|
+
surface: surface,
|
|
35
|
+
sourceProjectSlug: assignment.sourceProjectSlug,
|
|
36
|
+
sourceSessionRef: assignment.sourceSessionRef,
|
|
37
|
+
mateId: context && context.mateId || null,
|
|
38
|
+
sessionId: context && context.sessionId || null,
|
|
39
|
+
requestId: context && context.requestId || null,
|
|
40
|
+
}));
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function restoreWorkspaceAssignmentFocus(assignmentId, card) {
|
|
45
|
+
if (!assignmentId || !pendingFocus[assignmentId] || !card) return false;
|
|
46
|
+
delete pendingFocus[assignmentId];
|
|
47
|
+
var focusTarget = card.querySelector(".workspace-assignment-error") || card.querySelector('[role="status"]');
|
|
48
|
+
if (!focusTarget) return false;
|
|
49
|
+
focusTarget.tabIndex = -1;
|
|
50
|
+
requestAnimationFrame(function () { focusTarget.focus({ preventScroll: true }); });
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function createWorkspaceAssignmentCard(assignment, options) {
|
|
55
|
+
var config = options || {};
|
|
56
|
+
var followUp = assignment.delivery === "follow_up";
|
|
57
|
+
var card = document.createElement("section");
|
|
58
|
+
card.className = "workspace-assignment-card" + (config.home ? " home-workspace-assignment" : "");
|
|
59
|
+
card.dataset.assignmentId = assignment.assignmentId || "";
|
|
60
|
+
card.dataset.status = assignment.status || "proposed";
|
|
61
|
+
card.dataset.delivery = followUp ? "follow_up" : "new_session";
|
|
62
|
+
card.setAttribute("aria-label", (followUp ? "Project follow-up: " : "Project assignment: ") + (assignment.title || "Untitled assignment"));
|
|
63
|
+
|
|
64
|
+
var header = document.createElement("div");
|
|
65
|
+
header.className = "workspace-assignment-header";
|
|
66
|
+
var mark = document.createElement("span");
|
|
67
|
+
mark.className = "workspace-assignment-mark";
|
|
68
|
+
mark.innerHTML = iconHtml(followUp ? "message-square-plus" : "send");
|
|
69
|
+
mark.setAttribute("aria-hidden", "true");
|
|
70
|
+
header.appendChild(mark);
|
|
71
|
+
var heading = document.createElement("div");
|
|
72
|
+
heading.className = "workspace-assignment-heading";
|
|
73
|
+
heading.appendChild(textElement("span", "workspace-assignment-kicker", followUp ? "PROJECT FOLLOW-UP" : "PROJECT ASSIGNMENT"));
|
|
74
|
+
heading.appendChild(textElement("h3", "workspace-assignment-title", assignment.title || "Untitled assignment"));
|
|
75
|
+
header.appendChild(heading);
|
|
76
|
+
var status = textElement("span", "workspace-assignment-status", statusLabel(assignment.status));
|
|
77
|
+
status.setAttribute("role", "status");
|
|
78
|
+
header.appendChild(status);
|
|
79
|
+
card.appendChild(header);
|
|
80
|
+
|
|
81
|
+
var targetText = followUp
|
|
82
|
+
? "Continue “" + (assignment.targetSessionTitle || "Existing session") + "” in " + (assignment.projectTitle || assignment.projectSlug || "project")
|
|
83
|
+
: "New private session in " + (assignment.projectTitle || assignment.projectSlug || "project");
|
|
84
|
+
var target = textElement("p", "workspace-assignment-target", targetText);
|
|
85
|
+
card.appendChild(target);
|
|
86
|
+
card.appendChild(textElement("p", "workspace-assignment-task", assignment.task || ""));
|
|
87
|
+
|
|
88
|
+
if (assignment.error) {
|
|
89
|
+
var error = textElement("p", "workspace-assignment-error", assignment.error);
|
|
90
|
+
error.setAttribute("role", "alert");
|
|
91
|
+
card.appendChild(error);
|
|
92
|
+
}
|
|
93
|
+
if (assignment.resultSummary) card.appendChild(textElement("p", "workspace-assignment-result", assignment.resultSummary));
|
|
94
|
+
|
|
95
|
+
var actions = document.createElement("div");
|
|
96
|
+
actions.className = "workspace-assignment-actions";
|
|
97
|
+
if (assignment.status === "proposed") {
|
|
98
|
+
var cancel = textElement("button", "workspace-assignment-action secondary", "Cancel");
|
|
99
|
+
cancel.type = "button";
|
|
100
|
+
cancel.setAttribute("aria-label", "Cancel project assignment");
|
|
101
|
+
cancel.addEventListener("click", function () { sendDecision(assignment, "cancel", config.home ? "home" : "project", config.context, card, cancel); });
|
|
102
|
+
var approve = textElement("button", "workspace-assignment-action primary", "Approve");
|
|
103
|
+
approve.type = "button";
|
|
104
|
+
approve.setAttribute("aria-label", followUp ? "Approve follow-up in " + (assignment.targetSessionTitle || "the target session") : "Approve new session in " + (assignment.projectTitle || assignment.projectSlug || "the target project"));
|
|
105
|
+
approve.addEventListener("click", function () { sendDecision(assignment, "approve", config.home ? "home" : "project", config.context, card, approve); });
|
|
106
|
+
actions.appendChild(cancel);
|
|
107
|
+
actions.appendChild(approve);
|
|
108
|
+
} else {
|
|
109
|
+
var resolved = textElement("span", "workspace-assignment-resolved", statusLabel(assignment.status));
|
|
110
|
+
resolved.setAttribute("role", "status");
|
|
111
|
+
resolved.tabIndex = -1;
|
|
112
|
+
actions.appendChild(resolved);
|
|
113
|
+
}
|
|
114
|
+
card.appendChild(actions);
|
|
115
|
+
restoreWorkspaceAssignmentFocus(assignment.assignmentId, card);
|
|
116
|
+
refreshIcons();
|
|
117
|
+
return card;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function showWorkspaceAssignmentError(card, msg) {
|
|
121
|
+
if (!card) return false;
|
|
122
|
+
card.removeAttribute("aria-busy");
|
|
123
|
+
var buttons = card.querySelectorAll("button");
|
|
124
|
+
for (var i = 0; i < buttons.length; i++) buttons[i].disabled = false;
|
|
125
|
+
var error = card.querySelector(".workspace-assignment-error");
|
|
126
|
+
if (!error) {
|
|
127
|
+
error = textElement("p", "workspace-assignment-error", "");
|
|
128
|
+
error.setAttribute("role", "alert");
|
|
129
|
+
card.insertBefore(error, card.querySelector(".workspace-assignment-actions"));
|
|
130
|
+
}
|
|
131
|
+
error.textContent = msg.error || "The assignment could not be updated.";
|
|
132
|
+
error.tabIndex = -1;
|
|
133
|
+
error.focus({ preventScroll: true });
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function applyHomeWorkspaceAssignment(messages, msg) {
|
|
138
|
+
var assignment = msg && msg.assignment;
|
|
139
|
+
if (!assignment || !assignment.assignmentId) return messages;
|
|
140
|
+
var next = (messages || []).slice();
|
|
141
|
+
for (var i = 0; i < next.length; i++) {
|
|
142
|
+
if (next[i].role === "assignment" && next[i].assignment && next[i].assignment.assignmentId === assignment.assignmentId) {
|
|
143
|
+
next[i] = { role: "assignment", assignment: assignment };
|
|
144
|
+
return next;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
next.push({ role: "assignment", assignment: assignment });
|
|
148
|
+
return next;
|
|
149
|
+
}
|