clay-server 4.1.0-beta.2 → 4.1.0-beta.3
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.
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
function canonicalize(value) {
|
|
2
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
3
|
+
if (!value || typeof value !== "object") return value;
|
|
4
|
+
var keys = Object.keys(value).sort();
|
|
5
|
+
var result = {};
|
|
6
|
+
for (var i = 0; i < keys.length; i++) result[keys[i]] = canonicalize(value[keys[i]]);
|
|
7
|
+
return result;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function sameAskUserRequest(event, id, input) {
|
|
11
|
+
return !!(event && event.type === "tool_executing" && event.name === "AskUserQuestion"
|
|
12
|
+
&& event.id === id && JSON.stringify(canonicalize(event.input || {})) === JSON.stringify(canonicalize(input || {})));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function hasRecordedAskUserRequest(history, id, input) {
|
|
16
|
+
if (!Array.isArray(history) || !id) return false;
|
|
17
|
+
for (var i = history.length - 1; i >= 0; i--) {
|
|
18
|
+
if (sameAskUserRequest(history[i], id, input)) return true;
|
|
19
|
+
}
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = { canonicalize: canonicalize, sameAskUserRequest: sameAskUserRequest, hasRecordedAskUserRequest: hasRecordedAskUserRequest };
|
package/lib/project-ask-user.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
var userInput = require("./yoke/user-input");
|
|
2
|
+
var askUserIdentity = require("./ask-user-request-identity");
|
|
2
3
|
|
|
3
4
|
function attachAskUser(ctx) {
|
|
4
5
|
function isFirstDebateQuestion(session) {
|
|
@@ -41,7 +42,9 @@ function attachAskUser(ctx) {
|
|
|
41
42
|
if (boundSession.pendingAskUser && boundSession.pendingAskUser[request.id] === pending) delete boundSession.pendingAskUser[request.id];
|
|
42
43
|
});
|
|
43
44
|
}
|
|
44
|
-
|
|
45
|
+
if (!askUserIdentity.hasRecordedAskUserRequest(boundSession.history, request.id, input)) {
|
|
46
|
+
ctx.record(boundSession, { type: "tool_executing", id: request.id, name: "AskUserQuestion", input: input });
|
|
47
|
+
}
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
function createHandler(boundSession) {
|
package/lib/public/app.js
CHANGED
|
@@ -287,6 +287,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
|
|
|
287
287
|
slashCommands: [],
|
|
288
288
|
processing: false,
|
|
289
289
|
activeSessionId: null,
|
|
290
|
+
askUserRequests: {},
|
|
290
291
|
activeBackgroundTasks: [],
|
|
291
292
|
sessionVendorBound: false,
|
|
292
293
|
cliSessionId: null,
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
|
|
3
|
+
function scopeKey() {
|
|
4
|
+
var sessionId = store.get('paneSessionId') || store.get('activeSessionId') || "unknown";
|
|
5
|
+
var project = store.get('currentSlug') || "unknown-project";
|
|
6
|
+
var pane = store.get('paneMode') ? "pane" : "main";
|
|
7
|
+
return project + "\u0000" + pane + "\u0000" + String(sessionId);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function requestKey(toolId) {
|
|
11
|
+
return scopeKey() + "\u0000" + String(toolId);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function requests() {
|
|
15
|
+
return store.get('askUserRequests') || {};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getAskUserRequestKey(toolId) {
|
|
19
|
+
return requestKey(toolId);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function rememberAskUserRequest(toolId) {
|
|
23
|
+
var key = requestKey(toolId);
|
|
24
|
+
var current = requests();
|
|
25
|
+
if (!current[key]) {
|
|
26
|
+
var next = Object.assign({}, current);
|
|
27
|
+
next[key] = { toolId: String(toolId), scope: scopeKey(), answered: false };
|
|
28
|
+
store.set({ askUserRequests: next });
|
|
29
|
+
}
|
|
30
|
+
return key;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function hasAskUserRequest(toolId) {
|
|
34
|
+
return !!requests()[requestKey(toolId)];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function markAskUserRequestAnswered(toolId) {
|
|
38
|
+
var key = requestKey(toolId);
|
|
39
|
+
var current = requests();
|
|
40
|
+
if (!current[key]) return;
|
|
41
|
+
var next = Object.assign({}, current);
|
|
42
|
+
next[key] = Object.assign({}, next[key], { answered: true });
|
|
43
|
+
store.set({ askUserRequests: next });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function findAskUserCard(root, toolId) {
|
|
47
|
+
if (!root || !root.querySelectorAll) return null;
|
|
48
|
+
var key = requestKey(toolId);
|
|
49
|
+
var cards = root.querySelectorAll(".ask-user-container");
|
|
50
|
+
for (var i = 0; i < cards.length; i++) {
|
|
51
|
+
if (cards[i].dataset && cards[i].dataset.askUserRequestKey === key) return cards[i];
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function saveAskUserState() {
|
|
57
|
+
return Object.assign({}, requests());
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function restoreAskUserState(saved) {
|
|
61
|
+
store.set({ askUserRequests: Object.assign({}, saved || {}) });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function resetAskUserState() {
|
|
65
|
+
store.set({ askUserRequests: {} });
|
|
66
|
+
}
|
|
@@ -8,6 +8,7 @@ import { getChatLayout } from './theme.js';
|
|
|
8
8
|
import { store } from './store.js';
|
|
9
9
|
import { VENDOR_NAMES } from './app-rendering.js';
|
|
10
10
|
import { startThinkingSegment, appendThinkingText, stopThinkingSegment, resetThinkingTurn, clearThinkingState, saveThinkingState, restoreThinkingState, updateThinkingTokenEstimate } from './thinking-lifecycle.js';
|
|
11
|
+
import { findAskUserCard, getAskUserRequestKey, hasAskUserRequest, rememberAskUserRequest, markAskUserRequestAnswered, saveAskUserState, restoreAskUserState, resetAskUserState } from './ask-user-state.js';
|
|
11
12
|
|
|
12
13
|
var ctx;
|
|
13
14
|
|
|
@@ -227,6 +228,10 @@ function shortPath(p) {
|
|
|
227
228
|
|
|
228
229
|
// --- AskUserQuestion ---
|
|
229
230
|
export function renderAskUserQuestion(toolId, input) {
|
|
231
|
+
var existingCard = findAskUserCard(ctx.messagesEl, toolId);
|
|
232
|
+
if (existingCard) return existingCard;
|
|
233
|
+
if (hasAskUserRequest(toolId)) return null;
|
|
234
|
+
|
|
230
235
|
ctx.finalizeAssistantBlock();
|
|
231
236
|
stopThinking();
|
|
232
237
|
closeToolGroup();
|
|
@@ -237,6 +242,9 @@ export function renderAskUserQuestion(toolId, input) {
|
|
|
237
242
|
var container = document.createElement("div");
|
|
238
243
|
container.className = "ask-user-container";
|
|
239
244
|
container.dataset.toolId = toolId;
|
|
245
|
+
var requestKey = getAskUserRequestKey(toolId);
|
|
246
|
+
container.dataset.askUserRequestKey = requestKey;
|
|
247
|
+
rememberAskUserRequest(toolId);
|
|
240
248
|
|
|
241
249
|
// Mate DM: wrap in avatar + content layout (same as msg-assistant)
|
|
242
250
|
var mateContentWrap = null;
|
|
@@ -372,6 +380,7 @@ export function renderAskUserQuestion(toolId, input) {
|
|
|
372
380
|
skipBtn.addEventListener("click", function () {
|
|
373
381
|
if (container.classList.contains("answered")) return;
|
|
374
382
|
container.classList.add("answered");
|
|
383
|
+
markAskUserRequestAnswered(toolId);
|
|
375
384
|
enableMainInput();
|
|
376
385
|
if (ctx.ws && ctx.connected) {
|
|
377
386
|
ctx.ws.send(JSON.stringify({ type: "stop" }));
|
|
@@ -415,7 +424,7 @@ export function enableMainInput() {
|
|
|
415
424
|
}
|
|
416
425
|
|
|
417
426
|
function submitAskUserAnswer(container, toolId, questions, answers, multiSelections) {
|
|
418
|
-
if (container.classList.contains("answered")) return;
|
|
427
|
+
if (container.classList.contains("answered") || container.dataset.askUserSubmitted === "true") return;
|
|
419
428
|
|
|
420
429
|
var result = {};
|
|
421
430
|
for (var i = 0; i < questions.length; i++) {
|
|
@@ -430,6 +439,7 @@ function submitAskUserAnswer(container, toolId, questions, answers, multiSelecti
|
|
|
430
439
|
if (Object.keys(result).length === 0) return;
|
|
431
440
|
|
|
432
441
|
container.classList.add("answered");
|
|
442
|
+
container.dataset.askUserSubmitted = "true";
|
|
433
443
|
enableMainInput();
|
|
434
444
|
if (ctx.stopUrgentBlink) ctx.stopUrgentBlink();
|
|
435
445
|
|
|
@@ -471,9 +481,10 @@ function showAnswerSummary(container, questions, answers) {
|
|
|
471
481
|
}
|
|
472
482
|
|
|
473
483
|
export function markAskUserAnswered(toolId, answers) {
|
|
474
|
-
var container =
|
|
484
|
+
var container = findAskUserCard(ctx.messagesEl, toolId);
|
|
475
485
|
if (container && !container.classList.contains("answered")) {
|
|
476
486
|
container.classList.add("answered");
|
|
487
|
+
markAskUserRequestAnswered(toolId);
|
|
477
488
|
enableMainInput();
|
|
478
489
|
// Restore answers from history replay
|
|
479
490
|
if (answers && Object.keys(answers).length > 0) {
|
|
@@ -2420,6 +2431,7 @@ export function saveToolState() {
|
|
|
2420
2431
|
toolGroupCounter: toolGroupCounter,
|
|
2421
2432
|
toolGroups: toolGroups,
|
|
2422
2433
|
lastCumulativeCost: _lastCumulativeCost,
|
|
2434
|
+
askUserState: saveAskUserState(),
|
|
2423
2435
|
};
|
|
2424
2436
|
}
|
|
2425
2437
|
|
|
@@ -2435,6 +2447,7 @@ export function restoreToolState(saved) {
|
|
|
2435
2447
|
toolGroupCounter = saved.toolGroupCounter;
|
|
2436
2448
|
toolGroups = saved.toolGroups;
|
|
2437
2449
|
_lastCumulativeCost = saved.lastCumulativeCost || 0;
|
|
2450
|
+
restoreAskUserState(saved.askUserState);
|
|
2438
2451
|
if (todoWidgetEl) {
|
|
2439
2452
|
setupTodoObserver();
|
|
2440
2453
|
}
|
|
@@ -2442,6 +2455,7 @@ export function restoreToolState(saved) {
|
|
|
2442
2455
|
|
|
2443
2456
|
export function resetToolState() {
|
|
2444
2457
|
tools = {};
|
|
2458
|
+
resetAskUserState();
|
|
2445
2459
|
clearThinkingState();
|
|
2446
2460
|
inPlanMode = false;
|
|
2447
2461
|
planContent = null;
|
|
@@ -2,6 +2,7 @@ var usersModule = require("./users");
|
|
|
2
2
|
var yoke = require("./yoke");
|
|
3
3
|
var backgroundTaskTiming = require("./background-task-timing");
|
|
4
4
|
var sessionTitlePolicy = require("./session-title-policy");
|
|
5
|
+
var askUserIdentity = require("./ask-user-request-identity");
|
|
5
6
|
|
|
6
7
|
var OVERLOAD_RETRY_LIMIT = 3;
|
|
7
8
|
|
|
@@ -379,7 +380,24 @@ function attachMessageProcessor(ctx) {
|
|
|
379
380
|
if (block && block.type === "tool_use") {
|
|
380
381
|
var input = {};
|
|
381
382
|
try { input = JSON.parse(block.inputJson); } catch (e) {}
|
|
382
|
-
|
|
383
|
+
// Native AskUserQuestion is recorded when canUseTool opens the
|
|
384
|
+
// request. The later block_stop is the SDK's completion notification
|
|
385
|
+
// for that same request, not a second question. Keeping both records
|
|
386
|
+
// made replay render two independently editable cards and allowed the
|
|
387
|
+
// client to submit the same request twice.
|
|
388
|
+
var alreadyRecordedAsk = false;
|
|
389
|
+
if (block.name === "AskUserQuestion" && block.id && Array.isArray(session.history)) {
|
|
390
|
+
for (var ahi = session.history.length - 1; ahi >= 0; ahi--) {
|
|
391
|
+
var priorAsk = session.history[ahi];
|
|
392
|
+
if (askUserIdentity.sameAskUserRequest(priorAsk, block.id, input)) {
|
|
393
|
+
alreadyRecordedAsk = true;
|
|
394
|
+
break;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (!alreadyRecordedAsk) {
|
|
399
|
+
sendAndRecord(session, { type: "tool_executing", id: block.id, name: block.name, input: input });
|
|
400
|
+
}
|
|
383
401
|
|
|
384
402
|
// Track active Task tools for sub-agent done detection
|
|
385
403
|
if (block.name === "Task") {
|
package/package.json
CHANGED