clay-server 4.0.0-beta.13 → 4.0.0-beta.15
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/capsule-display-floor.js +51 -0
- package/lib/capsule-frame-server.js +229 -0
- package/lib/capsule-pig-logic.js +268 -0
- package/lib/capsule-server-runtimes.js +41 -0
- package/lib/capsule-tictactoe-logic.js +260 -0
- package/lib/capsules/pig/display.js +190 -0
- package/lib/capsules/pig/manifest.json +9 -0
- package/lib/capsules/pig/ui.json +46 -0
- package/lib/capsules/tictactoe/manifest.json +9 -0
- package/lib/capsules/tictactoe/ui.json +203 -0
- package/lib/project-capsule-catalog.js +9 -1
- package/lib/project-connection.js +1 -1
- package/lib/project-log-feedback-delivery.js +76 -0
- package/lib/project-logs.js +4 -0
- package/lib/project-pair-lifecycle.js +7 -12
- package/lib/project-session-pair.js +29 -39
- package/lib/project-worker-proposal.js +203 -72
- package/lib/project.js +13 -0
- package/lib/public/css/capsule-ui.css +18 -0
- package/lib/public/css/home-session-actions.css +66 -0
- package/lib/public/css/home-sidebar.css +66 -0
- package/lib/public/css/mobile-nav.css +80 -0
- package/lib/public/css/sidebar.css +92 -0
- package/lib/public/css/worker-proposal.css +34 -1
- package/lib/public/modules/app-messages.js +14 -1
- package/lib/public/modules/home-conversations-sheet.js +102 -48
- package/lib/public/modules/home-session-actions.js +6 -0
- package/lib/public/modules/home-sidebar-chat-list.js +101 -41
- package/lib/public/modules/home-tool-frame.js +165 -0
- package/lib/public/modules/home-tools.js +129 -1
- package/lib/public/modules/session-hierarchy.js +54 -0
- package/lib/public/modules/sidebar-mobile.js +35 -6
- package/lib/public/modules/sidebar-session-hierarchy.js +206 -0
- package/lib/public/modules/sidebar-sessions.js +49 -7
- package/lib/public/modules/worker-proposal-state.js +16 -0
- package/lib/public/modules/worker-proposal.js +49 -12
- package/lib/sdk-bridge.js +16 -8
- package/lib/sdk-message-processor.js +34 -4
- package/lib/server-home-chat.js +12 -1
- package/lib/server-tools.js +97 -9
- package/lib/server.js +3 -0
- package/lib/session-driver-eligibility.js +20 -165
- package/lib/session-pair-factory.js +31 -28
- package/lib/session-pair-mcp-server.js +5 -9
- package/lib/session-pair-prompts.js +15 -15
- package/lib/session-provenance.js +119 -0
- package/lib/session-spawn-mcp-server.js +1 -1
- package/lib/sessions.js +40 -2
- package/lib/tools-registry.js +44 -10
- package/lib/ws-schema.js +8 -3
- package/lib/yoke/adapters/claude.js +12 -0
- package/package.json +1 -1
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "stack",
|
|
3
|
+
"props": {
|
|
4
|
+
"gap": "md"
|
|
5
|
+
},
|
|
6
|
+
"children": [
|
|
7
|
+
{
|
|
8
|
+
"type": "text",
|
|
9
|
+
"props": {
|
|
10
|
+
"text": {
|
|
11
|
+
"$bind": "$state.statusText",
|
|
12
|
+
"fallback": "You (X) to play."
|
|
13
|
+
},
|
|
14
|
+
"role": "output"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"type": "row",
|
|
19
|
+
"props": {
|
|
20
|
+
"gap": "sm"
|
|
21
|
+
},
|
|
22
|
+
"children": [
|
|
23
|
+
{
|
|
24
|
+
"type": "button",
|
|
25
|
+
"action": "mark",
|
|
26
|
+
"props": {
|
|
27
|
+
"label": {
|
|
28
|
+
"$bind": "$state.cell0",
|
|
29
|
+
"fallback": " "
|
|
30
|
+
},
|
|
31
|
+
"accessibleLabel": "Mark cell 0",
|
|
32
|
+
"variant": "secondary",
|
|
33
|
+
"disabled": "$state.cell0Disabled",
|
|
34
|
+
"args": {
|
|
35
|
+
"cell": 0
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"type": "button",
|
|
41
|
+
"action": "mark",
|
|
42
|
+
"props": {
|
|
43
|
+
"label": {
|
|
44
|
+
"$bind": "$state.cell1",
|
|
45
|
+
"fallback": " "
|
|
46
|
+
},
|
|
47
|
+
"accessibleLabel": "Mark cell 1",
|
|
48
|
+
"variant": "secondary",
|
|
49
|
+
"disabled": "$state.cell1Disabled",
|
|
50
|
+
"args": {
|
|
51
|
+
"cell": 1
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"type": "button",
|
|
57
|
+
"action": "mark",
|
|
58
|
+
"props": {
|
|
59
|
+
"label": {
|
|
60
|
+
"$bind": "$state.cell2",
|
|
61
|
+
"fallback": " "
|
|
62
|
+
},
|
|
63
|
+
"accessibleLabel": "Mark cell 2",
|
|
64
|
+
"variant": "secondary",
|
|
65
|
+
"disabled": "$state.cell2Disabled",
|
|
66
|
+
"args": {
|
|
67
|
+
"cell": 2
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"type": "row",
|
|
75
|
+
"props": {
|
|
76
|
+
"gap": "sm"
|
|
77
|
+
},
|
|
78
|
+
"children": [
|
|
79
|
+
{
|
|
80
|
+
"type": "button",
|
|
81
|
+
"action": "mark",
|
|
82
|
+
"props": {
|
|
83
|
+
"label": {
|
|
84
|
+
"$bind": "$state.cell3",
|
|
85
|
+
"fallback": " "
|
|
86
|
+
},
|
|
87
|
+
"accessibleLabel": "Mark cell 3",
|
|
88
|
+
"variant": "secondary",
|
|
89
|
+
"disabled": "$state.cell3Disabled",
|
|
90
|
+
"args": {
|
|
91
|
+
"cell": 3
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"type": "button",
|
|
97
|
+
"action": "mark",
|
|
98
|
+
"props": {
|
|
99
|
+
"label": {
|
|
100
|
+
"$bind": "$state.cell4",
|
|
101
|
+
"fallback": " "
|
|
102
|
+
},
|
|
103
|
+
"accessibleLabel": "Mark cell 4",
|
|
104
|
+
"variant": "secondary",
|
|
105
|
+
"disabled": "$state.cell4Disabled",
|
|
106
|
+
"args": {
|
|
107
|
+
"cell": 4
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"type": "button",
|
|
113
|
+
"action": "mark",
|
|
114
|
+
"props": {
|
|
115
|
+
"label": {
|
|
116
|
+
"$bind": "$state.cell5",
|
|
117
|
+
"fallback": " "
|
|
118
|
+
},
|
|
119
|
+
"accessibleLabel": "Mark cell 5",
|
|
120
|
+
"variant": "secondary",
|
|
121
|
+
"disabled": "$state.cell5Disabled",
|
|
122
|
+
"args": {
|
|
123
|
+
"cell": 5
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"type": "row",
|
|
131
|
+
"props": {
|
|
132
|
+
"gap": "sm"
|
|
133
|
+
},
|
|
134
|
+
"children": [
|
|
135
|
+
{
|
|
136
|
+
"type": "button",
|
|
137
|
+
"action": "mark",
|
|
138
|
+
"props": {
|
|
139
|
+
"label": {
|
|
140
|
+
"$bind": "$state.cell6",
|
|
141
|
+
"fallback": " "
|
|
142
|
+
},
|
|
143
|
+
"accessibleLabel": "Mark cell 6",
|
|
144
|
+
"variant": "secondary",
|
|
145
|
+
"disabled": "$state.cell6Disabled",
|
|
146
|
+
"args": {
|
|
147
|
+
"cell": 6
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"type": "button",
|
|
153
|
+
"action": "mark",
|
|
154
|
+
"props": {
|
|
155
|
+
"label": {
|
|
156
|
+
"$bind": "$state.cell7",
|
|
157
|
+
"fallback": " "
|
|
158
|
+
},
|
|
159
|
+
"accessibleLabel": "Mark cell 7",
|
|
160
|
+
"variant": "secondary",
|
|
161
|
+
"disabled": "$state.cell7Disabled",
|
|
162
|
+
"args": {
|
|
163
|
+
"cell": 7
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"type": "button",
|
|
169
|
+
"action": "mark",
|
|
170
|
+
"props": {
|
|
171
|
+
"label": {
|
|
172
|
+
"$bind": "$state.cell8",
|
|
173
|
+
"fallback": " "
|
|
174
|
+
},
|
|
175
|
+
"accessibleLabel": "Mark cell 8",
|
|
176
|
+
"variant": "secondary",
|
|
177
|
+
"disabled": "$state.cell8Disabled",
|
|
178
|
+
"args": {
|
|
179
|
+
"cell": 8
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
]
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"type": "row",
|
|
187
|
+
"props": {
|
|
188
|
+
"gap": "sm"
|
|
189
|
+
},
|
|
190
|
+
"children": [
|
|
191
|
+
{
|
|
192
|
+
"type": "button",
|
|
193
|
+
"action": "reset",
|
|
194
|
+
"props": {
|
|
195
|
+
"label": "Reset",
|
|
196
|
+
"accessibleLabel": "Reset the game",
|
|
197
|
+
"variant": "ghost"
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
]
|
|
201
|
+
}
|
|
202
|
+
]
|
|
203
|
+
}
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
// Bounded progressive-disclosure catalog for Capsules visible to a Mate.
|
|
2
|
+
//
|
|
3
|
+
// Skills are gated by Display: a Capsule the human cannot operate by hand is a
|
|
4
|
+
// Capsule no Mate may reach either, so an entry appears here only while its
|
|
5
|
+
// declarative Display floor validates. The gate reads the Capsule's own tree,
|
|
6
|
+
// carried alongside each manifest, and fails closed on a scan error, a missing
|
|
7
|
+
// ID, or a missing or invalid Display.
|
|
8
|
+
|
|
9
|
+
var capsuleFloor = require("./capsule-display-floor");
|
|
2
10
|
|
|
3
11
|
var MAX_NAME_LENGTH = 80;
|
|
4
12
|
var MAX_DESCRIPTION_LENGTH = 240;
|
|
@@ -35,7 +43,7 @@ function discoveryEntry(manifest) {
|
|
|
35
43
|
function buildCapsuleCatalogPrompt(manifests) {
|
|
36
44
|
if (!Array.isArray(manifests) || manifests.length === 0) return "";
|
|
37
45
|
var sorted = manifests.filter(function (manifest) {
|
|
38
|
-
return
|
|
46
|
+
return capsuleFloor.hasUsableFloor(manifest);
|
|
39
47
|
}).slice().sort(function (a, b) {
|
|
40
48
|
var first = String(a.id);
|
|
41
49
|
var second = String(b.id);
|
|
@@ -233,7 +233,7 @@ function attachConnection(ctx) {
|
|
|
233
233
|
// so vendor badges showed Claude until the next broadcast).
|
|
234
234
|
sendTo(ws, {
|
|
235
235
|
type: "session_list",
|
|
236
|
-
sessions:
|
|
236
|
+
sessions: sm.mapSessionsForClient(allSessions),
|
|
237
237
|
});
|
|
238
238
|
if (_splitGroups) _splitGroups.sendConnectionState(ws);
|
|
239
239
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Deliver new Project Log comments to the exact live session responsible for
|
|
2
|
+
// the canonical entry. Comments remain durable in the ledger when that session
|
|
3
|
+
// is gone; this module never guesses another session or creates one.
|
|
4
|
+
|
|
5
|
+
function attachProjectLogFeedbackDelivery(ctx) {
|
|
6
|
+
var sm = ctx.sm;
|
|
7
|
+
var getSdk = ctx.getSdk;
|
|
8
|
+
var onProcessingChanged = ctx.onProcessingChanged || function () {};
|
|
9
|
+
var getLinuxUserForSession = ctx.getLinuxUserForSession || function () { return null; };
|
|
10
|
+
|
|
11
|
+
function findAuthorSession(entry) {
|
|
12
|
+
var author = entry && entry.updatedBy;
|
|
13
|
+
if (!author || author.type !== "session" || !author.sessionKey) return null;
|
|
14
|
+
var match = null;
|
|
15
|
+
sm.sessions.forEach(function (session) {
|
|
16
|
+
if (match || !session || session.destroying || session.hidden) return;
|
|
17
|
+
if (session.cliSessionId !== author.sessionKey && "local:" + session.localId !== author.sessionKey) return;
|
|
18
|
+
if ((session.ownerId || null) !== (author.userId || null)) return;
|
|
19
|
+
if (sm.sessions.get(session.localId) !== session) return;
|
|
20
|
+
match = session;
|
|
21
|
+
});
|
|
22
|
+
return match;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function feedbackPrompt(entry) {
|
|
26
|
+
return "[Project Log feedback]\n" +
|
|
27
|
+
"A user added a comment to " + entry.ref + ". Review the pending feedback now with list_log_feedback, " +
|
|
28
|
+
"then resolve this specific comment through review_log_comment. Apply supported corrections to the canonical " +
|
|
29
|
+
"entry; otherwise clarify or decline with a concise reason. Do not treat this internal notification as user " +
|
|
30
|
+
"chat or answer it only in the conversation.\n" +
|
|
31
|
+
"[End Project Log feedback]";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function deliver(entry) {
|
|
35
|
+
if (!entry || !entry.ref || !entry.pendingFeedbackCount) return false;
|
|
36
|
+
var session = findAuthorSession(entry);
|
|
37
|
+
var sdk = getSdk();
|
|
38
|
+
if (!session || !sdk) return false;
|
|
39
|
+
var text = feedbackPrompt(entry);
|
|
40
|
+
sm.sendAndRecord(session, {
|
|
41
|
+
type: "user_message",
|
|
42
|
+
text: text,
|
|
43
|
+
_internal: true,
|
|
44
|
+
projectLogFeedback: true,
|
|
45
|
+
projectLogRef: entry.ref,
|
|
46
|
+
});
|
|
47
|
+
session.lastActivity = Date.now();
|
|
48
|
+
if (!session.isProcessing) {
|
|
49
|
+
session.isProcessing = true;
|
|
50
|
+
session.sentToolResults = {};
|
|
51
|
+
onProcessingChanged();
|
|
52
|
+
sm.sendToSession(session, { type: "status", status: "processing" });
|
|
53
|
+
}
|
|
54
|
+
if (!sdk.pushMessage(session, text)) {
|
|
55
|
+
session._queryStartTs = Date.now();
|
|
56
|
+
Promise.resolve(sdk.startQuery(session, text, null, getLinuxUserForSession(session))).catch(function (error) {
|
|
57
|
+
session.isProcessing = false;
|
|
58
|
+
sm.sendAndRecord(session, {
|
|
59
|
+
type: "error",
|
|
60
|
+
text: "Could not deliver Project Log feedback: " + (error.message || String(error)),
|
|
61
|
+
_internal: true,
|
|
62
|
+
});
|
|
63
|
+
onProcessingChanged();
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
sm.broadcastSessionList();
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
deliver: deliver,
|
|
72
|
+
findAuthorSession: findAuthorSession,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = { attachProjectLogFeedbackDelivery: attachProjectLogFeedbackDelivery };
|
package/lib/project-logs.js
CHANGED
|
@@ -88,6 +88,7 @@ function attachProjectLogs(ctx) {
|
|
|
88
88
|
var sendTo = ctx.sendTo;
|
|
89
89
|
var getClients = ctx.getClients || function () { return []; };
|
|
90
90
|
var getProjectOwnerId = ctx.getProjectOwnerId || function () { return null; };
|
|
91
|
+
var onFeedback = ctx.onFeedback || function () {};
|
|
91
92
|
|
|
92
93
|
// Every socket in this project's client set passed the project access check
|
|
93
94
|
// at WebSocket upgrade, so a broadcast here is already scoped to authorized
|
|
@@ -204,6 +205,9 @@ function attachProjectLogs(ctx) {
|
|
|
204
205
|
if (msg.type === "project_log_comment") {
|
|
205
206
|
var commented = bound.commentLog({ ref: msg.ref, body: msg.body });
|
|
206
207
|
sendTo(ws, { type: "project_log_commented", requestId: requestId, entry: commented });
|
|
208
|
+
try { onFeedback(commented); } catch (deliveryError) {
|
|
209
|
+
console.error("[project-logs] Failed to deliver comment:", deliveryError.message || deliveryError);
|
|
210
|
+
}
|
|
207
211
|
return true;
|
|
208
212
|
}
|
|
209
213
|
} catch (e) {
|
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
//
|
|
2
|
-
// the per-generation evaluation ledger.
|
|
3
|
-
//
|
|
4
|
-
// A qualified Driver manages its visible Worker without asking the user: it
|
|
5
|
-
// decides reuse against replacement from exact server-derived accounting, and
|
|
6
|
-
// it replaces in one operation rather than a close/create dance. Nothing here
|
|
7
|
-
// posts a proposal or waits for an approval card.
|
|
1
|
+
// Split Worker lifecycle internals: bounded status, transactional replacement,
|
|
2
|
+
// and the per-generation evaluation ledger. The Driver-facing tool posts a
|
|
3
|
+
// user-controlled runtime card before accepted replacement reaches this module.
|
|
8
4
|
//
|
|
9
5
|
// Everything is bound to the exact live pair. The Driver is re-resolved from
|
|
10
|
-
// the split store on every call, its
|
|
6
|
+
// the split store on every call, its structural capability is re-checked every time, and
|
|
11
7
|
// ownership must match on both sessions. No active-tab or global-session
|
|
12
8
|
// fallback exists in this module.
|
|
13
9
|
//
|
|
@@ -187,7 +183,7 @@ function attachPairLifecycle(ctx) {
|
|
|
187
183
|
return record;
|
|
188
184
|
}
|
|
189
185
|
|
|
190
|
-
// The exact pair, with the Driver's
|
|
186
|
+
// The exact pair, with the Driver's structural capability re-checked on every call.
|
|
191
187
|
function resolveDriverPair(caller) {
|
|
192
188
|
if (!caller) throw new Error("pair lifecycle tools require a session-bound tool server");
|
|
193
189
|
// Exact live object identity, before anything is read off the caller. A
|
|
@@ -206,7 +202,7 @@ function attachPairLifecycle(ctx) {
|
|
|
206
202
|
var worker = sm.sessions.get(group.pair.workerId);
|
|
207
203
|
if (!worker) throw new Error("split partner session was not found");
|
|
208
204
|
if ((caller.ownerId || null) !== (worker.ownerId || null)) throw new Error("split partner access denied");
|
|
209
|
-
return { group: group, worker: worker
|
|
205
|
+
return { group: group, worker: worker };
|
|
210
206
|
}
|
|
211
207
|
|
|
212
208
|
function replaceBlockedReason(worker) {
|
|
@@ -246,12 +242,11 @@ function attachPairLifecycle(ctx) {
|
|
|
246
242
|
orchestration: turnControl.status(caller),
|
|
247
243
|
replaceSafe: !blocked,
|
|
248
244
|
replaceBlockedReason: blocked,
|
|
249
|
-
driverTier: resolved.tier ? resolved.tier.name : null,
|
|
250
245
|
generations: ledger,
|
|
251
246
|
};
|
|
252
247
|
}
|
|
253
248
|
|
|
254
|
-
//
|
|
249
|
+
// Transactional replacement. Rejects an active Worker unless interrupt is
|
|
255
250
|
// explicitly true, cancels anything the old Worker was waiting on, dissolves
|
|
256
251
|
// the exact pair without deleting history, and creates a fresh Worker.
|
|
257
252
|
// Idempotent in the sense that it either completes or leaves the existing
|
|
@@ -7,17 +7,13 @@ var driverEligibility = require("./session-driver-eligibility");
|
|
|
7
7
|
var { attachWorkerPermission } = require("./project-worker-permission");
|
|
8
8
|
var { attachWorkerProposal } = require("./project-worker-proposal");
|
|
9
9
|
var MAX_RESPONSE_CHARS = 30000;
|
|
10
|
-
function toolResult(value) {
|
|
11
|
-
return Promise.resolve({ content: [{ type: "text", text: JSON.stringify(value) }] });
|
|
12
|
-
}
|
|
13
|
-
|
|
10
|
+
function toolResult(value) { return Promise.resolve({ content: [{ type: "text", text: JSON.stringify(value) }] }); }
|
|
14
11
|
function toolError(err) {
|
|
15
12
|
return Promise.resolve({
|
|
16
13
|
content: [{ type: "text", text: "Error: " + (err.message || err) }],
|
|
17
14
|
isError: true,
|
|
18
15
|
});
|
|
19
16
|
}
|
|
20
|
-
|
|
21
17
|
function responseText(history, fromIndex) {
|
|
22
18
|
var text = "";
|
|
23
19
|
for (var i = Math.max(0, fromIndex || 0); i < history.length; i++) {
|
|
@@ -65,14 +61,14 @@ function attachSessionPair(ctx) {
|
|
|
65
61
|
var workerPermission;
|
|
66
62
|
var lifecycle;
|
|
67
63
|
var turnControl = attachPairTurnControl({ sm: sm, splitStore: store });
|
|
68
|
-
function groupAndPartner(caller) {
|
|
64
|
+
function groupAndPartner(caller, missingPairMessage) {
|
|
69
65
|
if (!caller) throw new Error("partner tools require a session-bound tool server");
|
|
70
66
|
// Captured handlers must remain bound to the exact live Session object.
|
|
71
67
|
if (sm.sessions.get(caller.localId) !== caller) {
|
|
72
68
|
throw new Error("this session is no longer live; the partner tools are bound to an exact session");
|
|
73
69
|
}
|
|
74
70
|
var group = store.groupForMember(caller.localId);
|
|
75
|
-
if (!group) throw new Error("this session is not in a split group");
|
|
71
|
+
if (!group) throw new Error(missingPairMessage || "this session is not in a split group");
|
|
76
72
|
if (group.pair && group.pair.driverId !== caller.localId) {
|
|
77
73
|
throw new Error("only the configured Driver can direct this pair");
|
|
78
74
|
}
|
|
@@ -102,7 +98,6 @@ function attachSessionPair(ctx) {
|
|
|
102
98
|
broadcastDelegation(group, caller, partner, false);
|
|
103
99
|
}
|
|
104
100
|
|
|
105
|
-
// Shared resume path for delegated results and Worker permission routing.
|
|
106
101
|
function resumeDriverWithMessage(caller, text, meta) {
|
|
107
102
|
if (!caller || caller.destroying) return false;
|
|
108
103
|
if (turnControl.blockedReason(caller)) return false;
|
|
@@ -160,7 +155,6 @@ function attachSessionPair(ctx) {
|
|
|
160
155
|
});
|
|
161
156
|
}
|
|
162
157
|
|
|
163
|
-
// Give a waiting Driver its turn back so it can answer Worker permissions.
|
|
164
158
|
function requestDetach(worker) {
|
|
165
159
|
var token = worker && worker._pairDelegation;
|
|
166
160
|
if (!token || token.detached || token.detachRequested) return false;
|
|
@@ -234,17 +228,11 @@ function attachSessionPair(ctx) {
|
|
|
234
228
|
async function sendToPartner(args, caller) {
|
|
235
229
|
try {
|
|
236
230
|
if (caller && caller._delegatedBy) throw new Error("delegated turns cannot delegate to another session");
|
|
231
|
+
var resolved = groupAndPartner(caller,
|
|
232
|
+
"send_to_partner requires an exact existing Driver/Split Worker pair; call propose_worker while unpaired");
|
|
237
233
|
var message = typeof args.message === "string" ? args.message.trim() : "";
|
|
238
234
|
if (!message) throw new Error("message is required");
|
|
239
235
|
turnControl.assertWorkerAction(caller);
|
|
240
|
-
var created = null;
|
|
241
|
-
if (caller && !store.groupForMember(caller.localId)) {
|
|
242
|
-
var creationTicket = turnControl.reserveCreation(caller, "create");
|
|
243
|
-
try { created = createWorkerForDriver(caller, args); }
|
|
244
|
-
catch (createError) { turnControl.releaseCreation(creationTicket); throw createError; }
|
|
245
|
-
}
|
|
246
|
-
var resolved = groupAndPartner(caller);
|
|
247
|
-
if (created && created.worker) lifecycle.recordGenerationStart(caller, created.worker);
|
|
248
236
|
var wait = args.wait !== false;
|
|
249
237
|
var timeout = Number.isFinite(args.timeoutSeconds) ? Math.floor(args.timeoutSeconds) : 300;
|
|
250
238
|
timeout = Math.max(1, Math.min(900, timeout));
|
|
@@ -282,13 +270,18 @@ function attachSessionPair(ctx) {
|
|
|
282
270
|
if (!wait) {
|
|
283
271
|
token.detached = true;
|
|
284
272
|
monitorPartner(resolved.group, caller, partner, token);
|
|
285
|
-
return toolResult({ status: "running", partnerId: partner.localId,
|
|
273
|
+
return toolResult({ status: "running", partnerId: partner.localId, hint: "The completed result will be pushed back automatically. Use read_partner only for an interim status check." });
|
|
286
274
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
275
|
+
if (!partner.isProcessing && !partner._queryStarting) {
|
|
276
|
+
finishDelegation(resolved.group, caller, partner, token);
|
|
277
|
+
var immediateFailure = errorSince(partner.history || [], token.startIndex);
|
|
278
|
+
return toolResult({
|
|
279
|
+
status: immediateFailure ? "error" : "complete",
|
|
280
|
+
response: responseText(partner.history || [], token.startIndex),
|
|
281
|
+
error: immediateFailure || undefined,
|
|
282
|
+
});
|
|
291
283
|
}
|
|
284
|
+
var completed = await waitForPartner(resolved.group, caller, partner, token, timeout);
|
|
292
285
|
return toolResult(completed);
|
|
293
286
|
} catch (e) {
|
|
294
287
|
if (caller) {
|
|
@@ -316,7 +309,6 @@ function attachSessionPair(ctx) {
|
|
|
316
309
|
title: resolved.partner.title || "New Session",
|
|
317
310
|
turns: count > 0 ? recentTurns(resolved.partner, count) : [],
|
|
318
311
|
};
|
|
319
|
-
// partner_status's bounded report, so reading turns needs no second call.
|
|
320
312
|
payload.capacity = lifecycle.optionalStatus(caller);
|
|
321
313
|
return toolResult(payload);
|
|
322
314
|
} catch (e) {
|
|
@@ -349,8 +341,6 @@ function attachSessionPair(ctx) {
|
|
|
349
341
|
if (partner.abortController) partner.abortController.abort();
|
|
350
342
|
}
|
|
351
343
|
if (partner._pairDelegation) finishDelegation(resolved.group, caller, partner, partner._pairDelegation);
|
|
352
|
-
// Anything the Worker was waiting on the Driver to decide dies with the
|
|
353
|
-
// pair, immediately rather than on the next liveness sweep.
|
|
354
344
|
workerPermission.cancelForSession(partner, "The Driver closed the Split Worker pair.");
|
|
355
345
|
var ws = { _clayUser: caller.ownerId ? { id: caller.ownerId } : null };
|
|
356
346
|
var result = store.dissolve(ws, { id: resolved.group.id });
|
|
@@ -378,12 +368,14 @@ function attachSessionPair(ctx) {
|
|
|
378
368
|
replace: function () { return toolError(new Error("replace_partner requires a session-bound tool server")); },
|
|
379
369
|
evaluate: function () { return toolError(new Error("record_partner_evaluation requires a session-bound tool server")); },
|
|
380
370
|
});
|
|
381
|
-
// Mount for the query lifetime; handlers re-resolve live pair state.
|
|
382
371
|
var group = store.groupForMember(boundSession.localId);
|
|
383
372
|
if (group && group.pair && group.pair.driverId !== boundSession.localId) return [];
|
|
384
|
-
// Plain splits keep base tools; autonomous Drivers must pass the tier gate.
|
|
385
373
|
var adHocSplit = !!(group && !group.pair);
|
|
386
|
-
if (!
|
|
374
|
+
if (!driverEligibility.isEligibleDriverSession(boundSession, sm)) return [];
|
|
375
|
+
if (!group) {
|
|
376
|
+
return workerProposal.getToolDefs(boundSession)
|
|
377
|
+
.concat(workerPermission.getToolDefs(boundSession, { dormantDriver: true }));
|
|
378
|
+
}
|
|
387
379
|
var lifecycleHandlers = lifecycle.toolHandlers(boundSession);
|
|
388
380
|
var tools = pairMcp.getToolDefs({
|
|
389
381
|
send: function (args) {
|
|
@@ -397,17 +389,15 @@ function attachSessionPair(ctx) {
|
|
|
397
389
|
status: lifecycleHandlers.status,
|
|
398
390
|
replace: function (args) {
|
|
399
391
|
return turnControl.runOperation(boundSession, "replace", args && args.operationId, function () {
|
|
400
|
-
return
|
|
392
|
+
return workerProposal.proposeReplacement(args || {}, boundSession);
|
|
401
393
|
});
|
|
402
394
|
},
|
|
403
395
|
evaluate: lifecycleHandlers.evaluate,
|
|
404
396
|
}, { lifecycle: !adHocSplit });
|
|
405
397
|
return tools
|
|
406
|
-
.concat(workerPermission.getToolDefs(boundSession, { dormantDriver:
|
|
407
|
-
.concat(workerProposal.getToolDefs(boundSession));
|
|
398
|
+
.concat(workerPermission.getToolDefs(boundSession, { dormantDriver: false }));
|
|
408
399
|
}
|
|
409
400
|
|
|
410
|
-
// Passed through as-is; re-wrapping drops fields. See session-pair-factory.js.
|
|
411
401
|
var factory = attachPairFactory(ctx);
|
|
412
402
|
var createPairRecord = factory.createPairRecord;
|
|
413
403
|
var createWorkerForDriver = factory.createWorkerForDriver;
|
|
@@ -445,8 +435,11 @@ function attachSessionPair(ctx) {
|
|
|
445
435
|
getLinuxUserForSession: ctx.getLinuxUserForSession,
|
|
446
436
|
onProcessingChanged: ctx.onProcessingChanged,
|
|
447
437
|
adapters: ctx.adapters,
|
|
438
|
+
dangerouslySkipPermissions: ctx.dangerouslySkipPermissions,
|
|
448
439
|
createPairRecord: createPairRecord,
|
|
440
|
+
recordGenerationStart: function (driver, worker) { return lifecycle.recordGenerationStart(driver, worker); },
|
|
449
441
|
sendToPartner: sendToPartner,
|
|
442
|
+
replacePartner: function (args, session) { return lifecycle.replacePartner(args, session); },
|
|
450
443
|
});
|
|
451
444
|
|
|
452
445
|
function handleMessage(ws, msg) {
|
|
@@ -474,8 +467,8 @@ function attachSessionPair(ctx) {
|
|
|
474
467
|
var pairPrompt = "";
|
|
475
468
|
if (group && group.pair && group.pair.driverId === session.localId) {
|
|
476
469
|
pairPrompt = pairPrompts.DRIVER;
|
|
477
|
-
} else if (!group && !ctx.isMate) {
|
|
478
|
-
pairPrompt = pairPrompts.UNPAIRED;
|
|
470
|
+
} else if (!group && !ctx.isMate && driverEligibility.isEligibleDriverSession(session, sm)) {
|
|
471
|
+
pairPrompt = pairPrompts.UNPAIRED + " " + workerProposal.getSystemPrompt(session);
|
|
479
472
|
}
|
|
480
473
|
return pairPrompt;
|
|
481
474
|
}
|
|
@@ -487,12 +480,9 @@ function attachSessionPair(ctx) {
|
|
|
487
480
|
handleMessage: handleMessage,
|
|
488
481
|
handleTurnDone: handleTurnDone,
|
|
489
482
|
getSystemPrompt: getSystemPrompt,
|
|
483
|
+
respondToWorkerProposal: workerProposal.respondToProposal,
|
|
490
484
|
workerPermission: workerPermission,
|
|
491
485
|
};
|
|
492
486
|
}
|
|
493
487
|
|
|
494
|
-
module.exports = {
|
|
495
|
-
attachSessionPair: attachSessionPair,
|
|
496
|
-
responseText: responseText,
|
|
497
|
-
recentTurns: recentTurns,
|
|
498
|
-
};
|
|
488
|
+
module.exports = { attachSessionPair: attachSessionPair, responseText: responseText, recentTurns: recentTurns };
|