clay-server 4.2.0 → 4.3.0-beta.10
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/driver-continuation-access.js +54 -0
- package/lib/driver-continuation-lease.js +52 -0
- package/lib/driver-continuation-pair-transfer.js +86 -0
- package/lib/driver-continuation-pair.js +86 -0
- package/lib/driver-continuation-record.js +40 -0
- package/lib/driver-continuation-startup.js +68 -0
- package/lib/driver-continuation-transaction.js +61 -0
- package/lib/driver-continuation-trigger.js +110 -0
- package/lib/multi-worker-feature.js +11 -0
- package/lib/project-connection.js +3 -1
- package/lib/project-driver-continuation.js +499 -0
- package/lib/project-message-delivery.js +20 -0
- package/lib/project-pair-autonomous-stop.js +14 -8
- package/lib/project-pair-close-transaction.js +59 -0
- package/lib/project-pair-global-stop.js +18 -0
- package/lib/project-pair-lifecycle-status.js +34 -0
- package/lib/project-pair-lifecycle.js +117 -135
- package/lib/project-pair-message.js +1 -1
- package/lib/project-pair-owned-stop.js +7 -4
- package/lib/project-pair-replacement-state.js +5 -1
- package/lib/project-pair-result-capture.js +116 -0
- package/lib/project-pair-result-delivery.js +140 -0
- package/lib/project-pair-result-outbox.js +400 -0
- package/lib/project-pair-result-pipeline.js +16 -0
- package/lib/project-pair-result-recovery.js +229 -0
- package/lib/project-pair-structural-close.js +20 -0
- package/lib/project-pair-task-control.js +45 -18
- package/lib/project-pair-worker-close.js +68 -0
- package/lib/project-session-handoff.js +48 -55
- package/lib/project-session-pair-prompt.js +26 -0
- package/lib/project-session-pair.js +87 -106
- package/lib/project-sessions.js +4 -0
- package/lib/project-user-message.js +21 -0
- package/lib/project-worker-creation-proposal.js +176 -0
- package/lib/project-worker-permission.js +7 -8
- package/lib/project-worker-proposal-runner.js +45 -0
- package/lib/project-worker-proposal.js +79 -115
- package/lib/project.js +87 -3
- package/lib/public/app.js +1 -0
- package/lib/public/css/driver-continuation.css +56 -0
- package/lib/public/css/messages.css +12 -0
- package/lib/public/css/mobile-nav.css +29 -14
- package/lib/public/css/overlays.css +54 -25
- package/lib/public/css/pane.css +66 -12
- package/lib/public/css/sidebar.css +62 -7
- package/lib/public/index.html +3 -3
- package/lib/public/modules/app-connection.js +165 -74
- package/lib/public/modules/app-messages.js +31 -3
- package/lib/public/modules/driver-continuation-state.js +76 -0
- package/lib/public/modules/driver-continuation.js +260 -0
- package/lib/public/modules/message-delivery-ui.js +63 -0
- package/lib/public/modules/message-delivery.js +163 -6
- package/lib/public/modules/pair-result-status.js +87 -0
- package/lib/public/modules/permission-control.js +10 -5
- package/lib/public/modules/project-activation.js +10 -7
- package/lib/public/modules/session-creation-cta.js +46 -0
- package/lib/public/modules/sidebar-mobile.js +42 -30
- package/lib/public/modules/sidebar-sessions.js +77 -52
- package/lib/public/modules/split-group-helpers.js +74 -2
- package/lib/public/modules/split-pair-ui.js +28 -6
- package/lib/public/modules/split-pane-reconciler.js +28 -0
- package/lib/public/modules/split-pane-renderer.js +107 -0
- package/lib/public/modules/split-view.js +58 -188
- package/lib/public/modules/websocket-lifecycle.js +178 -0
- package/lib/public/modules/websocket-watchdog.js +126 -0
- package/lib/public/modules/worker-pane-lock.js +13 -3
- package/lib/public/style.css +1 -0
- package/lib/sdk-bridge.js +77 -0
- package/lib/server.js +1 -0
- package/lib/session-handoff-discovery.js +443 -0
- package/lib/session-handoff-mcp-server.js +32 -4
- package/lib/session-pair-factory.js +66 -0
- package/lib/session-pair-mcp-server.js +10 -5
- package/lib/session-pair-prompts.js +46 -25
- package/lib/session-pair-target.js +42 -0
- package/lib/session-pair-turn-control.js +30 -9
- package/lib/session-split-group-anchors.js +126 -0
- package/lib/session-split-group-persistence.js +136 -0
- package/lib/session-split-group-roles.js +164 -0
- package/lib/session-split-group-v2-store.js +146 -0
- package/lib/session-split-groups.js +293 -133
- package/lib/sessions.js +89 -22
- package/lib/ws-schema.js +8 -0
- package/package.json +1 -1
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// websocket-lifecycle.js - bounded connection attempt and retry policy
|
|
2
|
+
|
|
3
|
+
var DEFAULTS = {
|
|
4
|
+
handshakeTimeoutMs: 12000,
|
|
5
|
+
stableConnectionMs: 15000,
|
|
6
|
+
retryMinMs: 1000,
|
|
7
|
+
retryMaxMs: 15000,
|
|
8
|
+
jitterRatio: 0.2,
|
|
9
|
+
authTimeoutMs: 4000
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function createWebSocketLifecycle(options) {
|
|
13
|
+
var config = Object.assign({}, DEFAULTS, options || {});
|
|
14
|
+
var epoch = 0;
|
|
15
|
+
var retryAttempt = 0;
|
|
16
|
+
var retryTimer = null;
|
|
17
|
+
var handshakeTimer = null;
|
|
18
|
+
var stableTimer = null;
|
|
19
|
+
var authTimer = null;
|
|
20
|
+
var authInFlight = false;
|
|
21
|
+
var authCancel = null;
|
|
22
|
+
var authGeneration = 0;
|
|
23
|
+
var stableEpoch = 0;
|
|
24
|
+
var cancelled = false;
|
|
25
|
+
var offline = false;
|
|
26
|
+
|
|
27
|
+
function clearTimer(timer) {
|
|
28
|
+
if (timer) clearTimeout(timer);
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function clearTimers() {
|
|
33
|
+
retryTimer = clearTimer(retryTimer);
|
|
34
|
+
handshakeTimer = clearTimer(handshakeTimer);
|
|
35
|
+
stableTimer = clearTimer(stableTimer);
|
|
36
|
+
authTimer = clearTimer(authTimer);
|
|
37
|
+
stableEpoch = 0;
|
|
38
|
+
if (authCancel) authCancel();
|
|
39
|
+
authCancel = null;
|
|
40
|
+
authInFlight = false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function current(value) {
|
|
44
|
+
return value === epoch && !cancelled;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function canProceed(value) {
|
|
48
|
+
return current(value) && !offline;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function beginAttempt() {
|
|
52
|
+
clearTimers();
|
|
53
|
+
cancelled = false;
|
|
54
|
+
epoch += 1;
|
|
55
|
+
var attemptEpoch = epoch;
|
|
56
|
+
handshakeTimer = setTimeout(function () {
|
|
57
|
+
handshakeTimer = null;
|
|
58
|
+
if (current(attemptEpoch) && config.onHandshakeTimeout) config.onHandshakeTimeout(attemptEpoch);
|
|
59
|
+
}, config.handshakeTimeoutMs);
|
|
60
|
+
return attemptEpoch;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function delayForAttempt() {
|
|
64
|
+
var base = Math.min(config.retryMaxMs, config.retryMinMs * Math.pow(1.5, retryAttempt));
|
|
65
|
+
retryAttempt += 1;
|
|
66
|
+
var jitter = base * config.jitterRatio;
|
|
67
|
+
var random = config.random || Math.random;
|
|
68
|
+
return Math.min(config.retryMaxMs, Math.max(0, Math.round(base - jitter + random() * jitter * 2)));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function schedule(reason, callback) {
|
|
72
|
+
if (cancelled || offline || retryTimer) return null;
|
|
73
|
+
var scheduledEpoch = epoch;
|
|
74
|
+
var delay = delayForAttempt();
|
|
75
|
+
retryTimer = setTimeout(function () {
|
|
76
|
+
retryTimer = null;
|
|
77
|
+
if (!canProceed(scheduledEpoch)) return;
|
|
78
|
+
callback(scheduledEpoch, reason, delay);
|
|
79
|
+
}, delay);
|
|
80
|
+
return { epoch: scheduledEpoch, delay: delay };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function clearRetry() {
|
|
84
|
+
retryTimer = clearTimer(retryTimer);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function beginAuth(authEpoch, onTimeout, onCancel) {
|
|
88
|
+
if (!canProceed(authEpoch) || authInFlight) return null;
|
|
89
|
+
authInFlight = true;
|
|
90
|
+
authGeneration += 1;
|
|
91
|
+
var authToken = { epoch: authEpoch, generation: authGeneration };
|
|
92
|
+
authCancel = onCancel || null;
|
|
93
|
+
authTimer = setTimeout(function () {
|
|
94
|
+
authTimer = null;
|
|
95
|
+
authInFlight = false;
|
|
96
|
+
authCancel = null;
|
|
97
|
+
if (canProceed(authEpoch) && onTimeout) onTimeout(authEpoch);
|
|
98
|
+
}, config.authTimeoutMs);
|
|
99
|
+
return authToken;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function finishAuth(authEpoch, authToken) {
|
|
103
|
+
if (!authInFlight || !current(authEpoch) || !authToken || authToken.epoch !== authEpoch || authToken.generation !== authGeneration) return false;
|
|
104
|
+
authInFlight = false;
|
|
105
|
+
authCancel = null;
|
|
106
|
+
authTimer = clearTimer(authTimer);
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function markOpen(attemptEpoch) {
|
|
111
|
+
if (!current(attemptEpoch)) return false;
|
|
112
|
+
handshakeTimer = clearTimer(handshakeTimer);
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function markLive(attemptEpoch) {
|
|
117
|
+
if (!current(attemptEpoch)) return false;
|
|
118
|
+
handshakeTimer = clearTimer(handshakeTimer);
|
|
119
|
+
if (stableEpoch === attemptEpoch) return true;
|
|
120
|
+
stableEpoch = attemptEpoch;
|
|
121
|
+
stableTimer = clearTimer(stableTimer);
|
|
122
|
+
stableTimer = setTimeout(function () {
|
|
123
|
+
stableTimer = null;
|
|
124
|
+
if (current(attemptEpoch)) retryAttempt = 0;
|
|
125
|
+
}, config.stableConnectionMs);
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function invalidate(attemptEpoch) {
|
|
130
|
+
if (attemptEpoch !== epoch) return false;
|
|
131
|
+
epoch += 1;
|
|
132
|
+
clearTimers();
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function cancel() {
|
|
137
|
+
cancelled = true;
|
|
138
|
+
epoch += 1;
|
|
139
|
+
clearTimers();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function setOffline(value) {
|
|
143
|
+
var nextOffline = value === true;
|
|
144
|
+
if (nextOffline === offline) return offline;
|
|
145
|
+
offline = nextOffline;
|
|
146
|
+
if (offline) {
|
|
147
|
+
epoch += 1;
|
|
148
|
+
clearTimers();
|
|
149
|
+
}
|
|
150
|
+
return offline;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function wake(callback) {
|
|
154
|
+
offline = false;
|
|
155
|
+
if (!cancelled && callback) callback();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
beginAttempt: beginAttempt,
|
|
160
|
+
beginAuth: beginAuth,
|
|
161
|
+
cancel: cancel,
|
|
162
|
+
canProceed: canProceed,
|
|
163
|
+
clearRetry: clearRetry,
|
|
164
|
+
current: current,
|
|
165
|
+
finishAuth: finishAuth,
|
|
166
|
+
invalidate: invalidate,
|
|
167
|
+
markOpen: markOpen,
|
|
168
|
+
markLive: markLive,
|
|
169
|
+
schedule: schedule,
|
|
170
|
+
setOffline: setOffline,
|
|
171
|
+
wake: wake,
|
|
172
|
+
getRetryAttempt: function () { return retryAttempt; },
|
|
173
|
+
getEpoch: function () { return epoch; },
|
|
174
|
+
isOffline: function () { return offline; }
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export { createWebSocketLifecycle };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// websocket-watchdog.js - suspendable liveness timers for the browser socket
|
|
2
|
+
|
|
3
|
+
function createWebSocketWatchdog(options) {
|
|
4
|
+
var suspended = false;
|
|
5
|
+
var heartbeatTimer = null;
|
|
6
|
+
var heartbeatDeadlineTimer = null;
|
|
7
|
+
var heartbeatSocket = null;
|
|
8
|
+
var heartbeatEpoch = 0;
|
|
9
|
+
var heartbeatGeneration = 0;
|
|
10
|
+
var deadlineGeneration = 0;
|
|
11
|
+
var probeTimer = null;
|
|
12
|
+
var probeSocket = null;
|
|
13
|
+
var probeEpoch = 0;
|
|
14
|
+
var probeStartedAt = 0;
|
|
15
|
+
var probeGeneration = 0;
|
|
16
|
+
|
|
17
|
+
function clearHeartbeatDeadline() {
|
|
18
|
+
deadlineGeneration += 1;
|
|
19
|
+
if (heartbeatDeadlineTimer) clearTimeout(heartbeatDeadlineTimer);
|
|
20
|
+
heartbeatDeadlineTimer = null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function stopHeartbeat() {
|
|
24
|
+
heartbeatGeneration += 1;
|
|
25
|
+
clearHeartbeatDeadline();
|
|
26
|
+
if (heartbeatTimer) clearInterval(heartbeatTimer);
|
|
27
|
+
heartbeatTimer = null;
|
|
28
|
+
heartbeatSocket = null;
|
|
29
|
+
heartbeatEpoch = 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function clearProbe() {
|
|
33
|
+
probeGeneration += 1;
|
|
34
|
+
if (probeTimer) clearTimeout(probeTimer);
|
|
35
|
+
probeTimer = null;
|
|
36
|
+
probeSocket = null;
|
|
37
|
+
probeEpoch = 0;
|
|
38
|
+
probeStartedAt = 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function suspend() {
|
|
42
|
+
suspended = true;
|
|
43
|
+
stopHeartbeat();
|
|
44
|
+
clearProbe();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function resume() {
|
|
48
|
+
var changed = suspended;
|
|
49
|
+
suspended = false;
|
|
50
|
+
return changed;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function startHeartbeat(socket, epoch) {
|
|
54
|
+
stopHeartbeat();
|
|
55
|
+
if (suspended || !socket) return false;
|
|
56
|
+
heartbeatSocket = socket;
|
|
57
|
+
heartbeatEpoch = epoch;
|
|
58
|
+
var generation = heartbeatGeneration;
|
|
59
|
+
heartbeatTimer = setInterval(function () {
|
|
60
|
+
if (suspended || generation !== heartbeatGeneration) return;
|
|
61
|
+
if (!options.isCurrent(socket, epoch)) {
|
|
62
|
+
stopHeartbeat();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
clearHeartbeatDeadline();
|
|
66
|
+
var currentDeadlineGeneration = deadlineGeneration;
|
|
67
|
+
heartbeatDeadlineTimer = setTimeout(function () {
|
|
68
|
+
if (suspended || currentDeadlineGeneration !== deadlineGeneration) return;
|
|
69
|
+
heartbeatDeadlineTimer = null;
|
|
70
|
+
if (heartbeatSocket === socket && heartbeatEpoch === epoch && options.isCurrent(socket, epoch)) {
|
|
71
|
+
options.onFailure("heartbeat_timeout", epoch, null);
|
|
72
|
+
}
|
|
73
|
+
}, options.heartbeatDeadlineMs);
|
|
74
|
+
try {
|
|
75
|
+
options.sendPing(socket);
|
|
76
|
+
} catch (e) {
|
|
77
|
+
options.onFailure("heartbeat_error", epoch, null);
|
|
78
|
+
}
|
|
79
|
+
}, options.heartbeatIntervalMs);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function probe(socket, epoch) {
|
|
84
|
+
if (suspended) return "suspended";
|
|
85
|
+
if (probeTimer) return "pending";
|
|
86
|
+
probeSocket = socket;
|
|
87
|
+
probeEpoch = epoch;
|
|
88
|
+
probeStartedAt = options.now();
|
|
89
|
+
probeGeneration += 1;
|
|
90
|
+
var generation = probeGeneration;
|
|
91
|
+
probeTimer = setTimeout(function () {
|
|
92
|
+
if (suspended || generation !== probeGeneration) return;
|
|
93
|
+
probeTimer = null;
|
|
94
|
+
if (probeSocket === socket && probeEpoch === epoch && options.isCurrent(socket, epoch)) {
|
|
95
|
+
options.onFailure("probe_timeout", epoch, options.now() - probeStartedAt);
|
|
96
|
+
}
|
|
97
|
+
}, options.probeTimeoutMs);
|
|
98
|
+
try {
|
|
99
|
+
options.sendPing(socket);
|
|
100
|
+
} catch (e) {
|
|
101
|
+
if (generation !== probeGeneration) return "cleared";
|
|
102
|
+
var latency = options.now() - probeStartedAt;
|
|
103
|
+
clearProbe();
|
|
104
|
+
options.onFailure("probe_error", epoch, latency);
|
|
105
|
+
return "failed";
|
|
106
|
+
}
|
|
107
|
+
return "started";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function acceptPong(socket, epoch) {
|
|
111
|
+
if (heartbeatSocket === socket && heartbeatEpoch === epoch) clearHeartbeatDeadline();
|
|
112
|
+
if (probeSocket === socket && probeEpoch === epoch) clearProbe();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
acceptPong: acceptPong,
|
|
117
|
+
clearProbe: clearProbe,
|
|
118
|
+
probe: probe,
|
|
119
|
+
resume: resume,
|
|
120
|
+
startHeartbeat: startHeartbeat,
|
|
121
|
+
stopHeartbeat: stopHeartbeat,
|
|
122
|
+
suspend: suspend
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export { createWebSocketWatchdog };
|
|
@@ -27,6 +27,18 @@ import { store } from './store.js';
|
|
|
27
27
|
var STATUS_ID = "worker-pane-status";
|
|
28
28
|
var LOCK_CLASS = "worker-pane-locked";
|
|
29
29
|
|
|
30
|
+
function configuredWorkerIds(group) {
|
|
31
|
+
if (!group || !group.pair || !Array.isArray(group.members)) return [];
|
|
32
|
+
var pair = group.pair;
|
|
33
|
+
var ids = Array.isArray(pair.workerIds) ? pair.workerIds :
|
|
34
|
+
(pair.workerId !== undefined ? [pair.workerId] : []);
|
|
35
|
+
if (pair.driverId === undefined || ids.length < 1 || ids.length > 2 || group.members.indexOf(pair.driverId) === -1) return [];
|
|
36
|
+
for (var i = 0; i < ids.length; i++) {
|
|
37
|
+
if (ids[i] === pair.driverId || group.members.indexOf(ids[i]) === -1) return [];
|
|
38
|
+
}
|
|
39
|
+
return ids;
|
|
40
|
+
}
|
|
41
|
+
|
|
30
42
|
// Every interactive composer affordance. Queried lazily: a pane iframe and the
|
|
31
43
|
// main window share this markup, and some ids are mobile-only.
|
|
32
44
|
var LOCKED_CONTROL_IDS = [
|
|
@@ -64,9 +76,7 @@ export function isDriverOperatedView(state) {
|
|
|
64
76
|
if (!sessionId) return false;
|
|
65
77
|
var groups = snapshot.splitGroups || [];
|
|
66
78
|
for (var i = 0; i < groups.length; i++) {
|
|
67
|
-
|
|
68
|
-
if (!pair) continue;
|
|
69
|
-
if (pair.workerId === sessionId) return true;
|
|
79
|
+
if (configuredWorkerIds(groups[i]).indexOf(sessionId) !== -1) return true;
|
|
70
80
|
}
|
|
71
81
|
return false;
|
|
72
82
|
}
|
package/lib/public/style.css
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
@import url("css/loop.css");
|
|
27
27
|
@import url("css/scheduler.css");
|
|
28
28
|
@import url("css/project-logs.css");
|
|
29
|
+
@import url("css/driver-continuation.css");
|
|
29
30
|
@import url("css/scheduled-tasks.css");
|
|
30
31
|
@import url("css/scheduler-modal.css");
|
|
31
32
|
@import url("css/home-hub.css");
|
package/lib/sdk-bridge.js
CHANGED
|
@@ -603,6 +603,10 @@ function createSDKBridge(opts) {
|
|
|
603
603
|
return { behavior: "allow", updatedInput: input };
|
|
604
604
|
}
|
|
605
605
|
|
|
606
|
+
if (toolName === "propose_driver_continuation" || toolName === "mcp__clay-continuation__propose_driver_continuation") {
|
|
607
|
+
return { behavior: "allow", updatedInput: input };
|
|
608
|
+
}
|
|
609
|
+
|
|
606
610
|
var safeCapsuleTools = {
|
|
607
611
|
clay_tool_list: true,
|
|
608
612
|
clay_tool_snapshot: true,
|
|
@@ -1004,6 +1008,26 @@ function createSDKBridge(opts) {
|
|
|
1004
1008
|
mode: session.permissionMode || sm.currentPermissionMode || "default", effectivePermissionMode: message.permissionMode,
|
|
1005
1009
|
permissionCapabilities: session.permissionCapabilities || { auto: false, mcpOverride: false },
|
|
1006
1010
|
mcpPermissionModeOverrides: session.mcpPermissionModeOverrides || {} });
|
|
1011
|
+
if (typeof sm.broadcastSessionList === "function") sm.broadcastSessionList();
|
|
1012
|
+
return true;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
function settleContinuationStartup(session, queryInstance, queryGeneration, accepted, reason) {
|
|
1016
|
+
var probe = session._continuationStartupProbe;
|
|
1017
|
+
if (!probe || probe.queryInstance !== queryInstance || probe.queryGeneration !== queryGeneration) return false;
|
|
1018
|
+
if (!ownsQueryRuntime(session, queryInstance, queryGeneration)) return false;
|
|
1019
|
+
delete session._continuationStartupProbe;
|
|
1020
|
+
clearTimeout(probe.timer);
|
|
1021
|
+
if (probe.timedOut) {
|
|
1022
|
+
if (typeof probe.onLateStartup === "function") probe.onLateStartup({
|
|
1023
|
+
accepted: accepted === true,
|
|
1024
|
+
queryAlive: session.queryInstance === queryInstance,
|
|
1025
|
+
queryGeneration: queryGeneration,
|
|
1026
|
+
reason: reason || null,
|
|
1027
|
+
});
|
|
1028
|
+
return true;
|
|
1029
|
+
}
|
|
1030
|
+
probe.resolve({ accepted: accepted === true, reason: reason || null });
|
|
1007
1031
|
return true;
|
|
1008
1032
|
}
|
|
1009
1033
|
|
|
@@ -1054,7 +1078,16 @@ function createSDKBridge(opts) {
|
|
|
1054
1078
|
}
|
|
1055
1079
|
continue;
|
|
1056
1080
|
}
|
|
1081
|
+
var continuationIdentity = msg && (msg.yokeType === "init" || msg.yokeType === "session_started");
|
|
1082
|
+
if (continuationIdentity && !ownsQueryRuntime(session, myQueryInstance, myQueryGeneration)) {
|
|
1083
|
+
continue;
|
|
1084
|
+
}
|
|
1057
1085
|
processSDKMessage(session, msg);
|
|
1086
|
+
if (continuationIdentity) {
|
|
1087
|
+
settleContinuationStartup(session, myQueryInstance, myQueryGeneration, true, null);
|
|
1088
|
+
} else if (msg && msg.yokeType === "error") {
|
|
1089
|
+
settleContinuationStartup(session, myQueryInstance, myQueryGeneration, false, "The successor reported an error before initialization.");
|
|
1090
|
+
}
|
|
1058
1091
|
// SDK init/status events are the only authority for runtime-effective
|
|
1059
1092
|
// permission state. Setter acknowledgement deliberately does not
|
|
1060
1093
|
// change this field.
|
|
@@ -1075,6 +1108,7 @@ function createSDKBridge(opts) {
|
|
|
1075
1108
|
// so the session is still marked as processing. Send interrupted feedback.
|
|
1076
1109
|
console.log("[sdk-bridge] processQueryStream ended: isProcessing=" + session.isProcessing + " taskStopRequested=" + session.taskStopRequested);
|
|
1077
1110
|
var stillOwnsRuntime = session.queryInstance === myQueryInstance;
|
|
1111
|
+
settleContinuationStartup(session, myQueryInstance, myQueryGeneration, false, "The successor query ended before initialization.");
|
|
1078
1112
|
if (session.isProcessing && session.taskStopRequested && stillOwnsRuntime) {
|
|
1079
1113
|
session._lastTurnInterrupted = true;
|
|
1080
1114
|
session.isProcessing = false;
|
|
@@ -1108,6 +1142,7 @@ function createSDKBridge(opts) {
|
|
|
1108
1142
|
sm.broadcastSessionList();
|
|
1109
1143
|
}
|
|
1110
1144
|
} catch (err) {
|
|
1145
|
+
settleContinuationStartup(session, myQueryInstance, myQueryGeneration, false, err.message || "The successor query failed before initialization.");
|
|
1111
1146
|
if (session.isProcessing && session.queryInstance === myQueryInstance) {
|
|
1112
1147
|
session.isProcessing = false;
|
|
1113
1148
|
session._awaitingTurnResult = false;
|
|
@@ -1202,6 +1237,7 @@ function createSDKBridge(opts) {
|
|
|
1202
1237
|
sm.broadcastSessionList();
|
|
1203
1238
|
}
|
|
1204
1239
|
} finally {
|
|
1240
|
+
settleContinuationStartup(session, myQueryInstance, myQueryGeneration, false, "The successor query closed before initialization.");
|
|
1205
1241
|
// Close the SDK query to terminate the underlying claude child process.
|
|
1206
1242
|
// Without this, the process stays alive indefinitely (single-user mode).
|
|
1207
1243
|
// Only clean up if the session still references OUR resources.
|
|
@@ -1398,6 +1434,46 @@ function createSDKBridge(opts) {
|
|
|
1398
1434
|
}
|
|
1399
1435
|
}
|
|
1400
1436
|
|
|
1437
|
+
// Continuation startup needs stronger evidence than a resolved startQuery
|
|
1438
|
+
// promise: createQuery and initial delivery failures are intentionally
|
|
1439
|
+
// contained inside startQuery. Return success only when the initial message
|
|
1440
|
+
// was accepted and that exact query emitted its provider initialization event.
|
|
1441
|
+
async function startQueryWithAcceptance(session, text, images, linuxUser, beforePush, onLateStartup) {
|
|
1442
|
+
var acceptedHandle = null;
|
|
1443
|
+
var acceptedGeneration = null;
|
|
1444
|
+
var resolveStartup;
|
|
1445
|
+
var startup = new Promise(function (resolve) { resolveStartup = resolve; });
|
|
1446
|
+
var result = await startQuery(session, text, images, linuxUser, beforePush, function () {
|
|
1447
|
+
acceptedHandle = session.queryInstance || null;
|
|
1448
|
+
acceptedGeneration = Number(session._sdkQueryGeneration || 0);
|
|
1449
|
+
session._continuationStartupProbe = {
|
|
1450
|
+
queryInstance: acceptedHandle,
|
|
1451
|
+
queryGeneration: acceptedGeneration,
|
|
1452
|
+
resolve: resolveStartup,
|
|
1453
|
+
onLateStartup: onLateStartup,
|
|
1454
|
+
timedOut: false,
|
|
1455
|
+
};
|
|
1456
|
+
session._continuationStartupProbe.timer = setTimeout(function () {
|
|
1457
|
+
var probe = session._continuationStartupProbe;
|
|
1458
|
+
if (!probe || probe.queryInstance !== acceptedHandle || probe.queryGeneration !== acceptedGeneration) return;
|
|
1459
|
+
probe.timedOut = true;
|
|
1460
|
+
probe.resolve({ accepted: false, timedOut: true, reason: "The successor did not initialize in time." });
|
|
1461
|
+
}, Number(opts.continuationStartupWaitMs || 15000));
|
|
1462
|
+
});
|
|
1463
|
+
var initialAccepted = result === true && !!acceptedHandle;
|
|
1464
|
+
if (!initialAccepted) resolveStartup({ accepted: false, reason: "The successor did not accept its initial query." });
|
|
1465
|
+
var startupResult = await startup;
|
|
1466
|
+
var accepted = initialAccepted && startupResult.accepted === true;
|
|
1467
|
+
return {
|
|
1468
|
+
accepted: accepted,
|
|
1469
|
+
initialAccepted: initialAccepted,
|
|
1470
|
+
queryAlive: session.queryInstance === acceptedHandle,
|
|
1471
|
+
queryGeneration: acceptedGeneration,
|
|
1472
|
+
timedOut: startupResult.timedOut === true,
|
|
1473
|
+
reason: accepted ? null : startupResult.reason || "The successor did not initialize its accepted query.",
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1401
1477
|
async function startQueryInner(session, text, images, linuxUser, beforePush, onAccepted) {
|
|
1402
1478
|
var sessionRuntimeEnv = getRuntimeEnv(session);
|
|
1403
1479
|
var resumedAtQueryStart = !!session.cliSessionId;
|
|
@@ -2647,6 +2723,7 @@ function createSDKBridge(opts) {
|
|
|
2647
2723
|
rollbackConversation: rollbackConversation,
|
|
2648
2724
|
forkSession: forkSessionUnified,
|
|
2649
2725
|
startQuery: startQuery,
|
|
2726
|
+
startQueryWithAcceptance: startQueryWithAcceptance,
|
|
2650
2727
|
pushMessage: pushMessage,
|
|
2651
2728
|
refreshSessionRuntime: refreshSessionRuntime,
|
|
2652
2729
|
refreshEnvironmentRuntime: refreshEnvironmentRuntime,
|
package/lib/server.js
CHANGED
|
@@ -1336,6 +1336,7 @@ function createServer(opts) {
|
|
|
1336
1336
|
worktreeMeta: worktreeMeta || null,
|
|
1337
1337
|
mcpBridgeToken: extra.mcpBridgeToken || null,
|
|
1338
1338
|
isMate: extra.isMate || false,
|
|
1339
|
+
multiWorkerRuntimeEnabled: !extra.isMate,
|
|
1339
1340
|
mateId: extra.mateId || (extra.isMate ? path.basename(cwd) : null),
|
|
1340
1341
|
mateDisplayName: extra.mateDisplayName || "",
|
|
1341
1342
|
isHostAgent: !!extra.isHostAgent,
|