clay-server 4.7.0-beta.1 → 4.7.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.
- package/lib/project-connection.js +0 -14
- package/lib/project-sessions.js +0 -18
- package/lib/public/app.js +16 -14
- package/lib/public/css/input.css +27 -0
- package/lib/public/css/pane.css +0 -1
- package/lib/public/css/projects-hub.css +75 -0
- package/lib/public/css/tui-attention.css +0 -407
- package/lib/public/index.html +6 -14
- package/lib/public/modules/app-connection.js +23 -2
- package/lib/public/modules/app-favicon.js +1 -0
- package/lib/public/modules/app-home-hub.js +8 -3
- package/lib/public/modules/app-messages.js +2 -16
- package/lib/public/modules/app-projects.js +16 -11
- package/lib/public/modules/app-rendering.js +16 -10
- package/lib/public/modules/generation-stop.js +24 -0
- package/lib/public/modules/home-surface-boot.js +10 -3
- package/lib/public/modules/home-surface.js +13 -1
- package/lib/public/modules/projects-hub-view.js +62 -0
- package/lib/public/modules/projects-hub.js +105 -0
- package/lib/public/style.css +2 -0
- package/lib/sdk-bridge.js +17 -3
- package/lib/server.js +1 -1
- package/lib/sessions.js +2 -0
- package/lib/users-home-surface-preferences.js +4 -2
- package/lib/users-preferences.js +0 -36
- package/lib/users.js +0 -4
- package/lib/ws-schema.js +0 -3
- package/lib/yoke/acp-process-manager.js +25 -15
- package/lib/yoke/acp-query-handle.js +52 -40
- package/lib/yoke/adapters/antigravity.js +25 -1
- package/lib/yoke/adapters/claude.js +25 -7
- package/lib/yoke/adapters/codex.js +55 -24
- package/lib/yoke/adapters/kiro.js +30 -19
- package/lib/yoke/codex-turn-events.js +32 -0
- package/lib/yoke/codex-turn-stop.js +59 -0
- package/lib/yoke/query-abort.js +40 -0
- package/lib/yoke/user-input-guidance.js +11 -0
- package/lib/yoke/user-input.js +1 -1
- package/package.json +1 -1
- package/lib/public/modules/whats-new-article.js +0 -70
- package/lib/public/modules/whats-new.js +0 -264
- package/lib/whats-new-content.js +0 -84
- package/lib/whats-new.js +0 -57
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { getWs } from './ws-ref.js';
|
|
3
|
+
import { hideHomeHub, showHomeHub } from './app-home-hub.js';
|
|
4
|
+
import { openAddProjectModal, switchProject } from './app-projects.js';
|
|
5
|
+
import { openUserSettings } from './user-settings.js';
|
|
6
|
+
import { rememberHomePrimarySurface, updateHomeSurfacePreference } from './home-surface.js';
|
|
7
|
+
import { projectsHubContent } from './projects-hub-view.js';
|
|
8
|
+
import { refreshIcons } from './icons.js';
|
|
9
|
+
|
|
10
|
+
function root() { return document.getElementById('projects-hub'); }
|
|
11
|
+
|
|
12
|
+
function render() {
|
|
13
|
+
var element = root();
|
|
14
|
+
if (!element || !store.get('projectsHubVisible')) return;
|
|
15
|
+
var html = projectsHubContent(store.snap());
|
|
16
|
+
if (store.get('projectsHubMarkup') === html) return;
|
|
17
|
+
store.set({ projectsHubMarkup: html });
|
|
18
|
+
var active = element.contains(document.activeElement) ? document.activeElement : null;
|
|
19
|
+
var action = active && (active.dataset.projectsAction || active.dataset.projectsMode || active.dataset.projectSlug);
|
|
20
|
+
element.innerHTML = html;
|
|
21
|
+
refreshIcons();
|
|
22
|
+
if (action) {
|
|
23
|
+
var buttons = element.querySelectorAll('button, a');
|
|
24
|
+
for (var i = 0; i < buttons.length; i++) {
|
|
25
|
+
if ((buttons[i].dataset.projectsAction || buttons[i].dataset.projectsMode || buttons[i].dataset.projectSlug) === action && !buttons[i].disabled) {
|
|
26
|
+
buttons[i].focus({ preventScroll: true });
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function focusHeading() {
|
|
34
|
+
if (root()) root().scrollTop = 0;
|
|
35
|
+
var title = document.getElementById('projects-title');
|
|
36
|
+
if (title) title.focus({ preventScroll: true });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function showProjectsHub(fromHistory) {
|
|
40
|
+
hideHomeHub();
|
|
41
|
+
store.set({ projectsHubVisible: true, pendingHomeProjectSlug: null });
|
|
42
|
+
document.body.classList.add('projects-active');
|
|
43
|
+
root().classList.remove('hidden');
|
|
44
|
+
rememberHomePrimarySurface('projects');
|
|
45
|
+
if (!fromHistory && location.pathname !== '/projects') {
|
|
46
|
+
var method = document.documentElement.classList.contains('pwa-standalone') ? 'replaceState' : 'pushState';
|
|
47
|
+
history[method](null, '', '/projects');
|
|
48
|
+
}
|
|
49
|
+
render();
|
|
50
|
+
focusHeading();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function hideProjectsHub() {
|
|
54
|
+
if (!store.get('projectsHubVisible')) return;
|
|
55
|
+
store.set({ projectsHubVisible: false, projectsShowGuide: false });
|
|
56
|
+
document.body.classList.remove('projects-active');
|
|
57
|
+
if (root()) root().classList.add('hidden');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function completeProjectsOnboarding() {
|
|
61
|
+
if (store.get('onboardingStep') !== 'project') return;
|
|
62
|
+
updateHomeSurfacePreference({ onboardingStep: 'complete' });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function handleClick(event) {
|
|
66
|
+
var button = event.target.closest('[data-projects-action], [data-projects-mode], [data-project-slug]');
|
|
67
|
+
if (!button || button.disabled) return;
|
|
68
|
+
event.preventDefault();
|
|
69
|
+
var action = button.dataset.projectsAction;
|
|
70
|
+
if (action === 'home') { showHomeHub(); return; }
|
|
71
|
+
if (action === 'settings') { openUserSettings(); return; }
|
|
72
|
+
if (action === 'guide') { store.set({ projectsShowGuide: true }); render(); focusHeading(); return; }
|
|
73
|
+
if (action === 'start' || action === 'skip') {
|
|
74
|
+
store.set({ projectsShowGuide: false });
|
|
75
|
+
updateHomeSurfacePreference({ onboardingStep: 'project' });
|
|
76
|
+
if (action === 'skip') showHomeHub();
|
|
77
|
+
else { render(); focusHeading(); }
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
var ws = getWs();
|
|
81
|
+
if (!ws || ws.readyState !== 1) return;
|
|
82
|
+
if (button.dataset.projectSlug) { switchProject(button.dataset.projectSlug); return; }
|
|
83
|
+
if (button.dataset.projectsMode) {
|
|
84
|
+
var permissions = store.get('permissions');
|
|
85
|
+
if (!store.get('projectsAccessLoaded') || (permissions && permissions.createProject !== true)) return;
|
|
86
|
+
openAddProjectModal(button.dataset.projectsMode);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function initProjectsHub() {
|
|
91
|
+
root().addEventListener('click', handleClick);
|
|
92
|
+
store.subscribe(function (state, previous) {
|
|
93
|
+
if (state.projectsHubRestoreRequested && !previous.projectsHubRestoreRequested) {
|
|
94
|
+
showProjectsHub(true);
|
|
95
|
+
if (location.pathname === '/') history.replaceState(null, '', '/projects');
|
|
96
|
+
store.set({ projectsHubRestoreRequested: false });
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (!state.projectsHubVisible) return;
|
|
100
|
+
if (state.projectsHubList !== previous.projectsHubList || state.projectListLoaded !== previous.projectListLoaded ||
|
|
101
|
+
state.homeSurfaceLoaded !== previous.homeSurfaceLoaded || state.onboardingStep !== previous.onboardingStep ||
|
|
102
|
+
state.projectsAccessLoaded !== previous.projectsAccessLoaded || state.permissions !== previous.permissions ||
|
|
103
|
+
state.connected !== previous.connected || state.pendingHomeProjectSlug !== previous.pendingHomeProjectSlug) render();
|
|
104
|
+
});
|
|
105
|
+
}
|
package/lib/public/style.css
CHANGED
package/lib/sdk-bridge.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const crypto = require("crypto");
|
|
2
|
+
var codexQuestionGuidance = require("./yoke/user-input-guidance").codexQuestionGuidance;
|
|
2
3
|
var fs = require("fs");
|
|
3
4
|
var path = require("path");
|
|
4
5
|
var execSync = require("child_process").execSync;
|
|
@@ -1054,6 +1055,10 @@ function createSDKBridge(opts) {
|
|
|
1054
1055
|
console.log("[sdk-bridge] processQueryStream: starting for-await loop, vendor=" + (session.vendor || adapter.vendor));
|
|
1055
1056
|
try {
|
|
1056
1057
|
for await (var msg of myQueryInstance) {
|
|
1058
|
+
// An old iterator may drain after Stop or after a replacement query starts.
|
|
1059
|
+
if (!ownsQueryRuntime(session, myQueryInstance, myQueryGeneration)) continue;
|
|
1060
|
+
if ((session.taskStopRequested || (myAbortController && myAbortController.signal.aborted))
|
|
1061
|
+
&& msg && msg.yokeType !== "error" && msg.yokeType !== "interrupted") continue;
|
|
1057
1062
|
if (msg && msg.yokeType !== "text_delta" && msg.yokeType !== "thinking_delta" && msg.yokeType !== "tool_input_delta") {
|
|
1058
1063
|
console.log("[sdk-bridge] processQueryStream: received event yokeType=" + msg.yokeType);
|
|
1059
1064
|
}
|
|
@@ -1111,6 +1116,7 @@ function createSDKBridge(opts) {
|
|
|
1111
1116
|
&& session.cliSessionId === msg.sessionId
|
|
1112
1117
|
&& (!sm.sessions || typeof sm.sessions.get !== "function" || sm.sessions.get(session.localId) === session)) {
|
|
1113
1118
|
if (codexCatalogCandidate.version) session.codexDynamicToolCatalogVersion = codexCatalogCandidate.version;
|
|
1119
|
+
if (codexCatalogCandidate.userInputVersion) session.codexUserInputToolCatalogVersion = codexCatalogCandidate.userInputVersion;
|
|
1114
1120
|
if (codexCatalogCandidate.issuesVersion) session.codexIssuesToolCatalogVersion = codexCatalogCandidate.issuesVersion;
|
|
1115
1121
|
sm.saveSessionFile(session);
|
|
1116
1122
|
codexCatalogCandidate = null;
|
|
@@ -1804,8 +1810,10 @@ function createSDKBridge(opts) {
|
|
|
1804
1810
|
var activeLoopInterview = !isDriverOperatedSession(session) && !session.loopInterviewHandoff && !activeRun && !!loopInterview.activeInterviewId(session);
|
|
1805
1811
|
var structuredInputSession = hasScheduleInterviewTool || activeLoopInterview || (!isDriverOperatedSession(session) && session.ralphCraftingMode === true && session.loop && session.loop.role === "crafting");
|
|
1806
1812
|
var requestedUserInputMode = session.debateSetupMode || session.mateCreationMode ? "fallback" : "auto";
|
|
1807
|
-
if (structuredInputSession) requestedUserInputMode = "fallback";
|
|
1813
|
+
if (structuredInputSession || sessionAdapter.vendor === "codex") requestedUserInputMode = "fallback";
|
|
1808
1814
|
var selectedUserInputMode = yoke.userInput.selectMode(sessionAdapter, requestedUserInputMode);
|
|
1815
|
+
var recoverCodexQuestionTools = sessionAdapter.vendor === "codex" && !!session.cliSessionId
|
|
1816
|
+
&& Number(session.codexUserInputToolCatalogVersion || 0) < 1 && queryContextAvailable();
|
|
1809
1817
|
function structuredInputContextAvailable() {
|
|
1810
1818
|
if (!queryContextAvailable()) return false;
|
|
1811
1819
|
// A schedule-triggered fallback shares the schedule feature's live
|
|
@@ -1928,7 +1936,10 @@ function createSDKBridge(opts) {
|
|
|
1928
1936
|
// would strip the driver session down to the two-sentence pair preset.
|
|
1929
1937
|
// Adapters map this to their additive mechanism (claude: preset+append,
|
|
1930
1938
|
// codex/kiro: prepend to the first message).
|
|
1931
|
-
appendSystemPrompt:
|
|
1939
|
+
appendSystemPrompt: [
|
|
1940
|
+
typeof opts.getSessionSystemPrompt === "function" ? opts.getSessionSystemPrompt(session) : "",
|
|
1941
|
+
sessionAdapter.vendor === "codex" && queryContextAvailable() ? codexQuestionGuidance() : "",
|
|
1942
|
+
].filter(Boolean).join("\n\n") || undefined,
|
|
1932
1943
|
model: queryModel,
|
|
1933
1944
|
// Effort is remembered per vendor and clamped to the session vendor's
|
|
1934
1945
|
// supported levels: a global value would leak codex "minimal" into
|
|
@@ -1946,7 +1957,7 @@ function createSDKBridge(opts) {
|
|
|
1946
1957
|
dynamicTools: sessionToolDefs,
|
|
1947
1958
|
// Codex persists dynamic tools with a thread. A stale authorized Driver
|
|
1948
1959
|
// gets this exact-query bridge; its handlers still reauthorize live.
|
|
1949
|
-
sessionMcpServer: recoverCodexIssueTools ? getCodexSessionMcpServer(session, queryGeneration) : undefined,
|
|
1960
|
+
sessionMcpServer: (recoverCodexIssueTools || recoverCodexQuestionTools) ? getCodexSessionMcpServer(session, queryGeneration) : undefined,
|
|
1950
1961
|
callDynamicTool: function (toolName, args) {
|
|
1951
1962
|
var toolDef = findSessionToolDef(toolName);
|
|
1952
1963
|
if (toolDef && typeof toolDef.handler === "function") {
|
|
@@ -2159,6 +2170,9 @@ function createSDKBridge(opts) {
|
|
|
2159
2170
|
if (completeInterviewCatalog || requiredCatalogTools.report_scheduled_task_outcome) {
|
|
2160
2171
|
codexCatalogCandidate = { generation: queryGeneration, version: yoke.sessionTools.CODEX_CATALOG_VERSION };
|
|
2161
2172
|
}
|
|
2173
|
+
if (requiredCatalogTools.ask_user_questions) {
|
|
2174
|
+
codexCatalogCandidate = Object.assign({}, codexCatalogCandidate || {}, { generation: queryGeneration, userInputVersion: 1 });
|
|
2175
|
+
}
|
|
2162
2176
|
if (hasIssueCatalog) {
|
|
2163
2177
|
codexCatalogCandidate = Object.assign({}, codexCatalogCandidate || {}, {
|
|
2164
2178
|
generation: queryGeneration,
|
package/lib/server.js
CHANGED
|
@@ -814,7 +814,7 @@ function createServer(opts) {
|
|
|
814
814
|
|
|
815
815
|
// Root path — render the default project workspace while keeping Home
|
|
816
816
|
// available as an explicit client-side surface.
|
|
817
|
-
if (fullUrl === "/" && req.method === "GET") {
|
|
817
|
+
if ((fullUrl === "/" || fullUrl === "/projects" || fullUrl === "/projects/") && req.method === "GET") {
|
|
818
818
|
if (!isRequestAuthed(req)) {
|
|
819
819
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
820
820
|
res.end(auth.getAuthPage());
|
package/lib/sessions.js
CHANGED
|
@@ -180,6 +180,7 @@ function createSessionManager(opts) {
|
|
|
180
180
|
if (session.ownerId) metaObj.ownerId = session.ownerId;
|
|
181
181
|
if (session.vendor) metaObj.vendor = session.vendor;
|
|
182
182
|
if (session.codexDynamicToolCatalogVersion) metaObj.codexDynamicToolCatalogVersion = session.codexDynamicToolCatalogVersion;
|
|
183
|
+
if (session.codexUserInputToolCatalogVersion) metaObj.codexUserInputToolCatalogVersion = session.codexUserInputToolCatalogVersion;
|
|
183
184
|
if (session.codexIssuesToolCatalogVersion) metaObj.codexIssuesToolCatalogVersion = session.codexIssuesToolCatalogVersion;
|
|
184
185
|
if (session.model) metaObj.model = session.model;
|
|
185
186
|
if (session.effort) metaObj.effort = session.effort;
|
|
@@ -354,6 +355,7 @@ function createSessionManager(opts) {
|
|
|
354
355
|
};
|
|
355
356
|
if (m.vendor) session.vendor = m.vendor;
|
|
356
357
|
session.codexDynamicToolCatalogVersion = Number(m.codexDynamicToolCatalogVersion || 0);
|
|
358
|
+
session.codexUserInputToolCatalogVersion = Number(m.codexUserInputToolCatalogVersion || 0);
|
|
357
359
|
session.codexIssuesToolCatalogVersion = Number(m.codexIssuesToolCatalogVersion || 0);
|
|
358
360
|
session.model = m.model || deriveSessionModel(loaded[i].history);
|
|
359
361
|
session.effort = m.effort || null;
|
|
@@ -31,8 +31,8 @@ function attachHomeSurfacePreferences(deps) {
|
|
|
31
31
|
if (!validId(mateIds[i]) || !validSessionReference(sessions[mateIds[i]])) continue;
|
|
32
32
|
normalizedSessions[mateIds[i]] = sessions[mateIds[i]];
|
|
33
33
|
}
|
|
34
|
-
|
|
35
|
-
surface: source.surface === "home" || source.surface === "project" ? source.surface : null,
|
|
34
|
+
var result = {
|
|
35
|
+
surface: source.surface === "home" || source.surface === "project" || source.surface === "projects" ? source.surface : null,
|
|
36
36
|
projectSlug: validProjectSlug(source.projectSlug) ? source.projectSlug : null,
|
|
37
37
|
activeMateId: validId(source.activeMateId) ? source.activeMateId : null,
|
|
38
38
|
activeSessionByMate: normalizedSessions,
|
|
@@ -41,6 +41,8 @@ function attachHomeSurfacePreferences(deps) {
|
|
|
41
41
|
chatScope: source.chatScope === "current" ? "current" : "all",
|
|
42
42
|
subSurface: source.subSurface === "debates" ? "debates" : "chat",
|
|
43
43
|
};
|
|
44
|
+
if (["welcome", "project", "complete"].indexOf(source.onboardingStep) !== -1) result.onboardingStep = source.onboardingStep;
|
|
45
|
+
return result;
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
function loadSingleUserPreference() {
|
package/lib/users-preferences.js
CHANGED
|
@@ -484,40 +484,6 @@ function attachPreferences(deps) {
|
|
|
484
484
|
return { error: "User not found" };
|
|
485
485
|
}
|
|
486
486
|
|
|
487
|
-
// --- What's New seen ids ---
|
|
488
|
-
//
|
|
489
|
-
// Per-user list of dismissed "What's New" entry ids. The whats-new
|
|
490
|
-
// service uses this to filter unseen entries on connect. Ids are stable
|
|
491
|
-
// strings authored in lib/whats-new-content.js.
|
|
492
|
-
|
|
493
|
-
function getWhatsNewSeenIds(userId) {
|
|
494
|
-
var data = loadUsers();
|
|
495
|
-
for (var i = 0; i < data.users.length; i++) {
|
|
496
|
-
if (data.users[i].id === userId) {
|
|
497
|
-
var ids = data.users[i].whatsNewSeenIds;
|
|
498
|
-
return Array.isArray(ids) ? ids.slice() : [];
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
return [];
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
function markWhatsNewSeen(userId, entryId) {
|
|
505
|
-
if (!entryId || typeof entryId !== "string") return { error: "bad_id" };
|
|
506
|
-
var data = loadUsers();
|
|
507
|
-
for (var i = 0; i < data.users.length; i++) {
|
|
508
|
-
if (data.users[i].id === userId) {
|
|
509
|
-
var ids = Array.isArray(data.users[i].whatsNewSeenIds) ? data.users[i].whatsNewSeenIds.slice() : [];
|
|
510
|
-
if (ids.indexOf(entryId) === -1) {
|
|
511
|
-
ids.push(entryId);
|
|
512
|
-
data.users[i].whatsNewSeenIds = ids;
|
|
513
|
-
saveUsers(data);
|
|
514
|
-
}
|
|
515
|
-
return { ok: true, seenIds: ids };
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
return { error: "User not found" };
|
|
519
|
-
}
|
|
520
|
-
|
|
521
487
|
// --- Mate onboarding ---
|
|
522
488
|
|
|
523
489
|
function setMateOnboarded(userId) {
|
|
@@ -556,8 +522,6 @@ function attachPreferences(deps) {
|
|
|
556
522
|
setMatesEnabled: setMatesEnabled,
|
|
557
523
|
getToolPalettes: getToolPalettes,
|
|
558
524
|
setToolPalette: setToolPalette,
|
|
559
|
-
getWhatsNewSeenIds: getWhatsNewSeenIds,
|
|
560
|
-
markWhatsNewSeen: markWhatsNewSeen,
|
|
561
525
|
getTerminalFont: getTerminalFont,
|
|
562
526
|
setTerminalFont: setTerminalFont,
|
|
563
527
|
setMateOnboarded: setMateOnboarded,
|
package/lib/users.js
CHANGED
|
@@ -453,8 +453,6 @@ var getMatesEnabled = preferences.getMatesEnabled;
|
|
|
453
453
|
var setMatesEnabled = preferences.setMatesEnabled;
|
|
454
454
|
var getToolPalettes = preferences.getToolPalettes;
|
|
455
455
|
var setToolPalette = preferences.setToolPalette;
|
|
456
|
-
var getWhatsNewSeenIds = preferences.getWhatsNewSeenIds;
|
|
457
|
-
var markWhatsNewSeen = preferences.markWhatsNewSeen;
|
|
458
456
|
var getTerminalFont = preferences.getTerminalFont;
|
|
459
457
|
var setTerminalFont = preferences.setTerminalFont;
|
|
460
458
|
var setMateOnboarded = preferences.setMateOnboarded;
|
|
@@ -541,8 +539,6 @@ module.exports = {
|
|
|
541
539
|
setCapsulesEnabled: setCapsulesEnabled,
|
|
542
540
|
getToolPalettes: getToolPalettes,
|
|
543
541
|
setToolPalette: setToolPalette,
|
|
544
|
-
getWhatsNewSeenIds: getWhatsNewSeenIds,
|
|
545
|
-
markWhatsNewSeen: markWhatsNewSeen,
|
|
546
542
|
getTerminalFont: getTerminalFont,
|
|
547
543
|
setTerminalFont: setTerminalFont,
|
|
548
544
|
getHomeDockPreference: getHomeDockPreference,
|
package/lib/ws-schema.js
CHANGED
|
@@ -332,9 +332,6 @@ var schema = {
|
|
|
332
332
|
// -----------------------------------------------------------------------
|
|
333
333
|
// What's New
|
|
334
334
|
// -----------------------------------------------------------------------
|
|
335
|
-
"whats_new_state": { direction: "s2c", handler: "lib/public/modules/whats-new.js", description: "Unseen What's New entries for the connecting user" },
|
|
336
|
-
"whats_new_seen": { direction: "c2s", handler: "lib/project-sessions.js", description: "Mark a What's New entry as dismissed by the current user" },
|
|
337
|
-
"whats_new_seen_result": { direction: "s2c", handler: "lib/public/modules/whats-new.js", description: "Result of marking a What's New entry seen" },
|
|
338
335
|
"home_dock_get": { direction: "c2s", handler: "lib/server-home-preferences.js", description: "Request the current user's home dock preference" },
|
|
339
336
|
"home_dock_set": { direction: "c2s", handler: "lib/server-home-preferences.js", description: "Persist the current user's home dock preference" },
|
|
340
337
|
"home_dock_state": { direction: "s2c", handler: "lib/public/modules/home-dock.js", description: "Current user home dock preference" },
|
|
@@ -188,33 +188,41 @@ AcpProcessManager.prototype.addRequestHandler = function(method, fn) {
|
|
|
188
188
|
this.requestHandlers[method] = fn;
|
|
189
189
|
};
|
|
190
190
|
|
|
191
|
-
AcpProcessManager.prototype.send = function(method, params, timeoutMs) {
|
|
191
|
+
AcpProcessManager.prototype.send = function(method, params, timeoutMs, signal) {
|
|
192
192
|
var self = this;
|
|
193
193
|
var id = this.nextId++;
|
|
194
194
|
timeoutMs = timeoutMs || 30000;
|
|
195
|
-
|
|
196
195
|
return new Promise(function(resolve, reject) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
var timer = setTimeout(function() {
|
|
196
|
+
var timer;
|
|
197
|
+
function cleanup() {
|
|
198
|
+
if (timer) clearTimeout(timer);
|
|
202
199
|
delete self.pendingRequests[id];
|
|
203
|
-
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
|
|
200
|
+
if (signal) signal.removeEventListener("abort", cancel);
|
|
201
|
+
}
|
|
202
|
+
function fail(error) { cleanup(); reject(error); }
|
|
203
|
+
function cancel() {
|
|
204
|
+
var error = new Error("ACP request cancelled: " + method);
|
|
205
|
+
error.name = "AbortError";
|
|
206
|
+
fail(error);
|
|
207
|
+
}
|
|
208
|
+
if (signal && signal.aborted) { cancel(); return; }
|
|
209
|
+
if (!self.proc || !self.started) { fail(new Error("ACP server not started")); return; }
|
|
210
|
+
timer = setTimeout(function() { fail(new Error("Request timeout: " + method + " (id=" + id + ")")); }, timeoutMs);
|
|
211
|
+
self.pendingRequests[id] = {
|
|
212
|
+
resolve: function (value) { cleanup(); resolve(value); }, reject: fail, timer: timer,
|
|
213
|
+
};
|
|
214
|
+
if (signal) signal.addEventListener("abort", cancel, { once: true });
|
|
207
215
|
var msg = { jsonrpc: "2.0", id: id, method: method };
|
|
208
216
|
if (params !== undefined) msg.params = params;
|
|
209
|
-
self._write(msg);
|
|
217
|
+
if (self._write(msg) === false) fail(new Error("ACP transport is not writable"));
|
|
210
218
|
});
|
|
211
219
|
};
|
|
212
220
|
|
|
213
221
|
AcpProcessManager.prototype.notify = function(method, params) {
|
|
214
|
-
if (!this.proc || !this.started) return;
|
|
222
|
+
if (!this.proc || !this.started) return false;
|
|
215
223
|
var msg = { jsonrpc: "2.0", method: method };
|
|
216
224
|
if (params !== undefined) msg.params = params;
|
|
217
|
-
this._write(msg);
|
|
225
|
+
return this._write(msg);
|
|
218
226
|
};
|
|
219
227
|
|
|
220
228
|
AcpProcessManager.prototype.respond = function(id, result) {
|
|
@@ -228,11 +236,13 @@ AcpProcessManager.prototype.respondError = function(id, code, message) {
|
|
|
228
236
|
};
|
|
229
237
|
|
|
230
238
|
AcpProcessManager.prototype._write = function(msg) {
|
|
231
|
-
if (!this.proc || !this.proc.stdin || this.proc.stdin.destroyed) return;
|
|
239
|
+
if (!this.proc || !this.proc.stdin || this.proc.stdin.destroyed) return false;
|
|
232
240
|
try {
|
|
233
241
|
this.proc.stdin.write(JSON.stringify(msg) + "\n");
|
|
242
|
+
return true;
|
|
234
243
|
} catch (e) {
|
|
235
244
|
this._error("Write error:", e.message);
|
|
245
|
+
return false;
|
|
236
246
|
}
|
|
237
247
|
};
|
|
238
248
|
|
|
@@ -1,29 +1,33 @@
|
|
|
1
1
|
// Shared ACP Query Handle
|
|
2
2
|
// -----------------------
|
|
3
3
|
// Implements the YOKE QueryHandle contract with standard ACP session methods.
|
|
4
|
-
|
|
5
4
|
var normalizer = require("./acp-event-normalizer");
|
|
6
5
|
var driverRuntime = require("./acp-driver-runtime");
|
|
7
|
-
|
|
6
|
+
var queryAbort = require("./query-abort");
|
|
8
7
|
var uuidCounter = 0;
|
|
9
8
|
function generateUuid(vendor) {
|
|
10
9
|
uuidCounter++;
|
|
11
10
|
return (vendor || "acp") + "-" + Date.now().toString(36) + "-" + uuidCounter.toString(36);
|
|
12
11
|
}
|
|
13
|
-
|
|
14
12
|
function isAuthError(value) {
|
|
15
13
|
return /not logged in|sign in|log ?in|authenticate|authentication|credentials|unauthorized|forbidden|\b401\b/i.test(String(value || ""));
|
|
16
14
|
}
|
|
17
|
-
|
|
18
15
|
function isApproved(decision) {
|
|
19
16
|
return decision === true || (decision && decision.behavior === "allow");
|
|
20
17
|
}
|
|
21
|
-
|
|
22
18
|
function createAcpQueryHandle(acp, queryOpts) {
|
|
23
19
|
queryOpts = queryOpts || {};
|
|
24
20
|
var vendor = queryOpts.vendor || "acp";
|
|
25
21
|
var driver = queryOpts.driver || null;
|
|
26
22
|
var abortController = queryOpts.abortController;
|
|
23
|
+
var cancellation = queryAbort.createAbortScope();
|
|
24
|
+
var removeAbort = function () {};
|
|
25
|
+
var pendingPermissions = new Set();
|
|
26
|
+
var stopDelivery = Promise.resolve();
|
|
27
|
+
function send(method, params, timeout) {
|
|
28
|
+
cancellation.check();
|
|
29
|
+
return cancellation.wait(acp.send(method, params, timeout, cancellation.signal));
|
|
30
|
+
}
|
|
27
31
|
var canUseTool = queryOpts.canUseTool || null;
|
|
28
32
|
var state = normalizer.createEventState({ vendor: vendor, contextWindow: queryOpts.contextWindow });
|
|
29
33
|
state.sessionId = queryOpts.resumeSessionId || null;
|
|
@@ -76,6 +80,7 @@ function createAcpQueryHandle(acp, queryOpts) {
|
|
|
76
80
|
function endIterator() {
|
|
77
81
|
if (iteratorDone) return;
|
|
78
82
|
iteratorDone = true;
|
|
83
|
+
removeAbort();
|
|
79
84
|
if (eventWaiting) {
|
|
80
85
|
var resolve = eventWaiting;
|
|
81
86
|
eventWaiting = null;
|
|
@@ -128,7 +133,19 @@ function createAcpQueryHandle(acp, queryOpts) {
|
|
|
128
133
|
return;
|
|
129
134
|
}
|
|
130
135
|
|
|
136
|
+
var answered = false;
|
|
137
|
+
function cancelPermission() {
|
|
138
|
+
if (answered) return;
|
|
139
|
+
answered = true;
|
|
140
|
+
pendingPermissions.delete(cancelPermission);
|
|
141
|
+
acp.respond(msg.id, { outcome: { outcome: "cancelled" } });
|
|
142
|
+
}
|
|
143
|
+
pendingPermissions.add(cancelPermission);
|
|
131
144
|
function respond(allowed) {
|
|
145
|
+
if (answered) return;
|
|
146
|
+
if (isCancelled()) { cancelPermission(); return; }
|
|
147
|
+
answered = true;
|
|
148
|
+
pendingPermissions.delete(cancelPermission);
|
|
132
149
|
var optionId = allowed ? allowId : rejectId;
|
|
133
150
|
if (!optionId) {
|
|
134
151
|
acp.respond(msg.id, { outcome: { outcome: "cancelled" } });
|
|
@@ -160,7 +177,7 @@ function createAcpQueryHandle(acp, queryOpts) {
|
|
|
160
177
|
input: input,
|
|
161
178
|
options: options,
|
|
162
179
|
}), function() { return { toolName: toolName, input: input }; }) || { toolName: toolName, input: input };
|
|
163
|
-
Promise.resolve(canUseTool(permission.toolName || toolName, permission.input || input, {})).then(function(decision) {
|
|
180
|
+
Promise.resolve(canUseTool(permission.toolName || toolName, permission.input || input, { signal: cancellation.signal })).then(function(decision) {
|
|
164
181
|
respond(isApproved(decision));
|
|
165
182
|
}).catch(function(err) {
|
|
166
183
|
console.error("[yoke/" + vendor + "] canUseTool error:", err.message || err);
|
|
@@ -193,7 +210,7 @@ function createAcpQueryHandle(acp, queryOpts) {
|
|
|
193
210
|
if (!state.sessionId || !acp.started || value === undefined || value === null) return Promise.resolve();
|
|
194
211
|
var option = configOptionFor(category, fallbackId);
|
|
195
212
|
if (option) {
|
|
196
|
-
return
|
|
213
|
+
return send("session/set_config_option", {
|
|
197
214
|
sessionId: state.sessionId,
|
|
198
215
|
configId: option.id,
|
|
199
216
|
value: value,
|
|
@@ -202,7 +219,7 @@ function createAcpQueryHandle(acp, queryOpts) {
|
|
|
202
219
|
});
|
|
203
220
|
}
|
|
204
221
|
if (category === "mode") {
|
|
205
|
-
return
|
|
222
|
+
return send("session/set_mode", { sessionId: state.sessionId, modeId: value }, 15000);
|
|
206
223
|
}
|
|
207
224
|
return Promise.reject(new Error("ACP session does not expose configuration option: " + fallbackId));
|
|
208
225
|
}
|
|
@@ -230,7 +247,7 @@ function createAcpQueryHandle(acp, queryOpts) {
|
|
|
230
247
|
if (safeModeValue([modes.currentModeId])) return Promise.resolve();
|
|
231
248
|
var safeMode = safeModeValue(modes.availableModes || []);
|
|
232
249
|
if (!safeMode) return Promise.reject(new Error("ACP agent started in an unsafe permission mode and exposed no supervised mode"));
|
|
233
|
-
return
|
|
250
|
+
return send("session/set_mode", { sessionId: state.sessionId, modeId: safeMode }, 15000);
|
|
234
251
|
}
|
|
235
252
|
if (driver && driver.permissionModeGuaranteed === true) return Promise.resolve();
|
|
236
253
|
return Promise.reject(new Error("ACP agent did not expose a verifiable supervised permission mode"));
|
|
@@ -302,6 +319,7 @@ function createAcpQueryHandle(acp, queryOpts) {
|
|
|
302
319
|
}
|
|
303
320
|
|
|
304
321
|
function applySessionResult(result) {
|
|
322
|
+
cancellation.check();
|
|
305
323
|
result = result || {};
|
|
306
324
|
if (result.sessionId) state.sessionId = result.sessionId;
|
|
307
325
|
if (Array.isArray(result.configOptions)) state.configOptions = result.configOptions;
|
|
@@ -313,7 +331,7 @@ function createAcpQueryHandle(acp, queryOpts) {
|
|
|
313
331
|
async function openDefaultSession(handlerEntry) {
|
|
314
332
|
if (state.sessionId && queryOpts.canResumeSession) {
|
|
315
333
|
handlerEntry.sessionId = state.sessionId;
|
|
316
|
-
var resumed = await
|
|
334
|
+
var resumed = await send("session/resume", buildSessionParams("resume", {
|
|
317
335
|
sessionId: state.sessionId,
|
|
318
336
|
cwd: queryOpts.cwd,
|
|
319
337
|
mcpServers: queryOpts.mcpServers || [],
|
|
@@ -324,7 +342,7 @@ function createAcpQueryHandle(acp, queryOpts) {
|
|
|
324
342
|
handlerEntry.sessionId = state.sessionId;
|
|
325
343
|
state.loadingSession = true;
|
|
326
344
|
try {
|
|
327
|
-
var loaded = await
|
|
345
|
+
var loaded = await send("session/load", buildSessionParams("load", {
|
|
328
346
|
sessionId: state.sessionId,
|
|
329
347
|
cwd: queryOpts.cwd,
|
|
330
348
|
mcpServers: queryOpts.mcpServers || [],
|
|
@@ -336,7 +354,7 @@ function createAcpQueryHandle(acp, queryOpts) {
|
|
|
336
354
|
}
|
|
337
355
|
|
|
338
356
|
state.sessionId = null;
|
|
339
|
-
var result = await
|
|
357
|
+
var result = await send("session/new", buildSessionParams("new", {
|
|
340
358
|
cwd: queryOpts.cwd,
|
|
341
359
|
mcpServers: queryOpts.mcpServers || [],
|
|
342
360
|
}), 60000);
|
|
@@ -355,7 +373,7 @@ function createAcpQueryHandle(acp, queryOpts) {
|
|
|
355
373
|
sessionId: state.sessionId,
|
|
356
374
|
prompt: asPrompt(message),
|
|
357
375
|
});
|
|
358
|
-
return
|
|
376
|
+
return send("session/prompt", params, 30 * 60 * 1000);
|
|
359
377
|
});
|
|
360
378
|
}
|
|
361
379
|
|
|
@@ -363,17 +381,17 @@ function createAcpQueryHandle(acp, queryOpts) {
|
|
|
363
381
|
var currentMessage = prependSystemPrompt(initialMessage);
|
|
364
382
|
var handlerEntry = acp.addHandler(handleServerEvent);
|
|
365
383
|
try {
|
|
366
|
-
var sessionResult = await openSession(handlerEntry);
|
|
384
|
+
var sessionResult = await cancellation.wait(openSession(handlerEntry));
|
|
367
385
|
handlerEntry.sessionId = state.sessionId;
|
|
368
|
-
await driverRuntime.callAsync(driver, "afterSessionOpen", driverContext({ sessionResult: sessionResult }), function() {});
|
|
369
|
-
await ensureSafePermissionMode();
|
|
386
|
+
await cancellation.wait(driverRuntime.callAsync(driver, "afterSessionOpen", driverContext({ sessionResult: sessionResult }), function() {}));
|
|
387
|
+
await cancellation.wait(ensureSafePermissionMode());
|
|
370
388
|
if (state.model && state.model !== "auto") await setModel(state.model);
|
|
371
389
|
if (queryOpts.mode) await setConfig("mode", "mode", queryOpts.mode);
|
|
372
390
|
|
|
373
391
|
while (!isCancelled()) {
|
|
374
392
|
normalizer.resetTurnState(state);
|
|
375
393
|
pushEvent({ yokeType: "turn_start", uuid: generateUuid(vendor), messageType: "user" });
|
|
376
|
-
var result = await prompt(currentMessage);
|
|
394
|
+
var result = await cancellation.wait(prompt(currentMessage));
|
|
377
395
|
var closing = normalizer.closeOpenBlocks(state);
|
|
378
396
|
for (var i = 0; i < closing.length; i++) pushEvent(closing[i]);
|
|
379
397
|
if (isCancelled() || (result && result.stopReason === "cancelled")) {
|
|
@@ -395,6 +413,7 @@ function createAcpQueryHandle(acp, queryOpts) {
|
|
|
395
413
|
: { yokeType: "error", text: message });
|
|
396
414
|
}
|
|
397
415
|
} finally {
|
|
416
|
+
await stopDelivery;
|
|
398
417
|
acp.removeHandler(handlerEntry);
|
|
399
418
|
state.done = true;
|
|
400
419
|
endIterator();
|
|
@@ -448,36 +467,29 @@ function createAcpQueryHandle(acp, queryOpts) {
|
|
|
448
467
|
}; });
|
|
449
468
|
},
|
|
450
469
|
abort: function() {
|
|
470
|
+
if (state.aborted || iteratorDone) return;
|
|
451
471
|
state.aborted = true;
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
endIterator();
|
|
472
|
+
cancellation.stop();
|
|
473
|
+
handle.endInput();
|
|
474
|
+
messageQueue = [];
|
|
475
|
+
pendingPermissions.forEach(function (cancel) { try { cancel(); } catch (e) {} });
|
|
476
|
+
stopDelivery = driverRuntime.callAsync(driver, "cancel", driverContext(), function() {
|
|
477
|
+
if (state.sessionId && (!acp.started || acp.notify("session/cancel", { sessionId: state.sessionId }) === false)) {
|
|
478
|
+
throw new Error("ACP transport is disconnected");
|
|
479
|
+
}
|
|
480
|
+
}).catch(function (error) {
|
|
481
|
+
pushEvent({ yokeType: "error", text: "Cancellation could not be delivered: " + error.message });
|
|
482
|
+
}).finally(endIterator);
|
|
483
|
+
return stopDelivery;
|
|
465
484
|
},
|
|
485
|
+
close: function() { handle.endInput(); endIterator(); },
|
|
466
486
|
endInput: function() {
|
|
467
487
|
messageQueueEnded = true;
|
|
468
|
-
if (messageWaiting) {
|
|
469
|
-
var resolve = messageWaiting;
|
|
470
|
-
messageWaiting = null;
|
|
471
|
-
resolve(null);
|
|
472
|
-
}
|
|
488
|
+
if (messageWaiting) { var resolve = messageWaiting; messageWaiting = null; resolve(null); }
|
|
473
489
|
},
|
|
474
490
|
};
|
|
475
491
|
|
|
476
|
-
|
|
477
|
-
abortController.signal.addEventListener("abort", function() {
|
|
478
|
-
if (!state.aborted) handle.abort();
|
|
479
|
-
}, { once: true });
|
|
480
|
-
}
|
|
492
|
+
removeAbort = queryAbort.bindAbort(abortController, function () { handle.abort(); });
|
|
481
493
|
return handle;
|
|
482
494
|
}
|
|
483
495
|
|