clay-server 4.0.1-beta.1 → 4.1.0-beta.2
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/daemon-project-access.js +118 -0
- package/lib/daemon-projects.js +19 -5
- package/lib/daemon.js +19 -18
- package/lib/project-logs-access-scope.js +94 -0
- package/lib/project-logs-query.js +3 -0
- package/lib/project-logs-service.js +86 -84
- package/lib/project-logs-store.js +5 -3
- package/lib/project-pair-lifecycle.js +140 -102
- package/lib/project-pair-message.js +50 -0
- package/lib/project-pair-replacement-state.js +72 -0
- package/lib/project-pair-task-control.js +264 -0
- package/lib/project-pair-usage.js +76 -0
- package/lib/project-session-pair.js +115 -116
- package/lib/project-sessions.js +25 -1
- package/lib/project-worker-proposal.js +69 -58
- package/lib/project.js +21 -4
- package/lib/public/css/icon-strip.css +40 -0
- package/lib/public/modules/app-messages.js +9 -0
- package/lib/public/modules/project-access.js +141 -0
- package/lib/public/modules/project-grouping.js +22 -0
- package/lib/public/modules/sidebar-projects.js +14 -171
- package/lib/public/modules/worker-proposal.js +4 -0
- package/lib/sdk-bridge.js +20 -0
- package/lib/server-admin.js +3 -3
- package/lib/server-global-ws.js +10 -0
- package/lib/server-mates.js +4 -3
- package/lib/server-palette.js +17 -14
- package/lib/server.js +184 -102
- package/lib/session-pair-factory.js +3 -0
- package/lib/session-pair-history.js +40 -0
- package/lib/session-pair-mcp-server.js +19 -2
- package/lib/session-pair-operation.js +11 -0
- package/lib/session-pair-prompts.js +24 -2
- package/lib/session-pair-turn-control.js +8 -3
- package/lib/session-provenance.js +1 -0
- package/lib/session-spawn-mcp-server.js +2 -1
- package/lib/sessions.js +2 -0
- package/lib/worker-proposal-control.js +69 -0
- package/lib/worker-proposal-decision.js +7 -0
- package/lib/worker-runtime-catalog.js +52 -0
- package/lib/workspace-query-access.js +3 -2
- package/lib/workspace-query-service.js +1 -1
- package/lib/worktree.js +8 -4
- package/lib/ws-schema.js +1 -0
- package/lib/yoke/adapters/codex.js +25 -7
- package/package.json +1 -1
package/lib/sessions.js
CHANGED
|
@@ -186,6 +186,7 @@ function createSessionManager(opts) {
|
|
|
186
186
|
var provenanceMeta = sessionProvenance.metadata(session);
|
|
187
187
|
metaObj.sessionOriginId = provenanceMeta.sessionOriginId;
|
|
188
188
|
if (provenanceMeta.sessionProvenance) metaObj.sessionProvenance = provenanceMeta.sessionProvenance;
|
|
189
|
+
if (Array.isArray(session._workerGenerations)) metaObj.workerGenerations = session._workerGenerations;
|
|
189
190
|
var meta = JSON.stringify(metaObj);
|
|
190
191
|
var lines = [meta];
|
|
191
192
|
for (var i = 0; i < session.history.length; i++) {
|
|
@@ -308,6 +309,7 @@ function createSessionManager(opts) {
|
|
|
308
309
|
if (m.ownerId) session.ownerId = m.ownerId;
|
|
309
310
|
session.sessionOriginId = m.sessionOriginId || null;
|
|
310
311
|
session._provenanceMigrationNeeded = sessionProvenance.restore(m, session);
|
|
312
|
+
if (Array.isArray(m.workerGenerations)) session._workerGenerations = m.workerGenerations;
|
|
311
313
|
// Born-TUI session: PTY is gone after restart, but the cliSessionId
|
|
312
314
|
// is still resumable via `claude --resume <id>`. We mark the mode
|
|
313
315
|
// here so it shows up in the sidebar with the right icon; the
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
var buildShape = require("./session-spawn-mcp-server").buildShape;
|
|
2
|
+
|
|
3
|
+
function findProposal(session, proposalId) {
|
|
4
|
+
var history = (session && session.history) || [];
|
|
5
|
+
for (var i = history.length - 1; i >= 0; i--) {
|
|
6
|
+
if (history[i] && history[i].type === "worker_proposal" && history[i].proposalId === proposalId) return history[i];
|
|
7
|
+
}
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function pendingProposal(session) {
|
|
12
|
+
var history = (session && session.history) || [];
|
|
13
|
+
for (var i = history.length - 1; i >= 0; i--) {
|
|
14
|
+
var item = history[i];
|
|
15
|
+
if (!item || item.type !== "worker_proposal") continue;
|
|
16
|
+
if (item.status === "pending" || item.status === "starting") return item;
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function projection(proposal) {
|
|
22
|
+
if (!proposal) return { status: "none", proposal: null };
|
|
23
|
+
return {
|
|
24
|
+
status: proposal.status,
|
|
25
|
+
proposal: {
|
|
26
|
+
proposalId: proposal.proposalId,
|
|
27
|
+
action: proposal.action === "replace" ? "replace" : "create",
|
|
28
|
+
summary: proposal.summary || "",
|
|
29
|
+
recommendedVendor: proposal.recommendedVendor || null,
|
|
30
|
+
recommendedModel: proposal.recommendedModel || null,
|
|
31
|
+
recommendedEffort: proposal.recommendedEffort || null,
|
|
32
|
+
status: proposal.status,
|
|
33
|
+
pending: proposal.status === "pending",
|
|
34
|
+
decisionRequired: proposal.status === "pending",
|
|
35
|
+
transactionId: proposal.transactionId || null,
|
|
36
|
+
sourceWorkerId: proposal.sourceWorkerId || null,
|
|
37
|
+
createdAt: proposal.createdAt || null,
|
|
38
|
+
updatedAt: proposal.updatedAt || null,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getToolDefs(handlers) {
|
|
44
|
+
return [{
|
|
45
|
+
name: "inspect_worker_proposal",
|
|
46
|
+
description: "Inspect the exact unresolved Split Worker configuration proposal for this Driver. Returns none when no user decision is pending.",
|
|
47
|
+
inputSchema: buildShape({}),
|
|
48
|
+
handler: function () { return handlers.inspect(); },
|
|
49
|
+
}, {
|
|
50
|
+
name: "cancel_worker_proposal",
|
|
51
|
+
description: "Cancel one exact pending Split Worker configuration proposal. A proposal that is already accepted, starting, superseded, declined, or otherwise resolved cannot be cancelled.",
|
|
52
|
+
inputSchema: buildShape({
|
|
53
|
+
proposalId: { type: "string", description: "Exact proposal id returned by propose_worker, replace_partner, or inspect_worker_proposal." },
|
|
54
|
+
}, ["proposalId"]),
|
|
55
|
+
handler: function (args) { return handlers.cancel(args || {}); },
|
|
56
|
+
}, {
|
|
57
|
+
name: "worker_runtime_catalog",
|
|
58
|
+
description: "List server-observed executable Split Worker vendor, model, and effort combinations, including approval mode and temporary unavailability reasons.",
|
|
59
|
+
inputSchema: buildShape({}),
|
|
60
|
+
handler: function () { return handlers.catalog(); },
|
|
61
|
+
}];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = {
|
|
65
|
+
findProposal: findProposal,
|
|
66
|
+
getToolDefs: getToolDefs,
|
|
67
|
+
pendingProposal: pendingProposal,
|
|
68
|
+
projection: projection,
|
|
69
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// An accepted execution record is not an unanswered configuration decision.
|
|
2
|
+
// Execution interruption and replacement safety belong to the pair lifecycle.
|
|
3
|
+
var pendingProposal = require("./worker-proposal-control").pendingProposal;
|
|
4
|
+
|
|
5
|
+
function hasPendingProposal(session) { return !!pendingProposal(session); }
|
|
6
|
+
|
|
7
|
+
module.exports = { hasPendingProposal: hasPendingProposal };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
var yoke = require("./yoke");
|
|
2
|
+
|
|
3
|
+
function modelValue(entry) { return typeof entry === "string" ? entry : entry && (entry.value || entry.id) || ""; }
|
|
4
|
+
|
|
5
|
+
function build(options, fullAccess, preflight) {
|
|
6
|
+
var vendors = [];
|
|
7
|
+
var installed = options.installedVendors || [];
|
|
8
|
+
for (var i = 0; i < installed.length; i++) {
|
|
9
|
+
var vendor = installed[i];
|
|
10
|
+
var models = options.modelsByVendor[vendor] || [];
|
|
11
|
+
var capability = options.capabilitiesByVendor[vendor] || {};
|
|
12
|
+
var vendorInfo = yoke.getVendorInfo(vendor) || {};
|
|
13
|
+
var combinations = [];
|
|
14
|
+
var readyObserved = Array.isArray(options.availableVendors);
|
|
15
|
+
var adapterReady = !readyObserved || options.availableVendors.indexOf(vendor) !== -1;
|
|
16
|
+
for (var j = 0; j < models.length; j++) {
|
|
17
|
+
var model = modelValue(models[j]);
|
|
18
|
+
if (!model) continue;
|
|
19
|
+
var levels = models[j] && models[j].supportedEffortLevels;
|
|
20
|
+
if (!Array.isArray(levels) || levels.length === 0) levels = vendorInfo.effortLevels || [];
|
|
21
|
+
if (capability.effort === false || levels.length === 0) levels = [null];
|
|
22
|
+
for (var k = 0; k < levels.length; k++) {
|
|
23
|
+
try {
|
|
24
|
+
if (preflight) preflight(vendor, model, levels[k]);
|
|
25
|
+
if (adapterReady) combinations.push({ model: model, effort: levels[k] });
|
|
26
|
+
} catch (err) {}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
vendors.push({
|
|
30
|
+
vendor: vendor,
|
|
31
|
+
status: combinations.length ? "available" : "temporarily_unavailable",
|
|
32
|
+
unavailableReason: combinations.length ? null : (!adapterReady ? "The runtime adapter is not currently available." : (options.unavailableReasons && options.unavailableReasons[vendor] || "No executable model and effort combination passed server preflight.")),
|
|
33
|
+
evidence: {
|
|
34
|
+
installation: "observed",
|
|
35
|
+
adapter: readyObserved ? (adapterReady ? "ready" : "unavailable") : "unknown",
|
|
36
|
+
authentication: "unknown",
|
|
37
|
+
authenticationReason: "Authentication is confirmed by the vendor runtime when a task starts; catalog discovery alone is not proof of credentials.",
|
|
38
|
+
},
|
|
39
|
+
combinations: combinations,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
status: "ok",
|
|
44
|
+
observedAt: Date.now(),
|
|
45
|
+
vendors: vendors,
|
|
46
|
+
approval: fullAccess
|
|
47
|
+
? { mode: "conditional_auto_accept", reason: "Only an exact server-validated Driver recommendation may auto-accept." }
|
|
48
|
+
: { mode: "user_required", reason: "Runtime creation and replacement require the visible configuration decision." },
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = { build: build };
|
|
@@ -77,10 +77,11 @@ function attachWorkspaceQueryAccess(deps) {
|
|
|
77
77
|
|
|
78
78
|
// `accessible` decides whether the container is visible at all; `owned`
|
|
79
79
|
// drives the separate rule that an accessible container holding no session of
|
|
80
|
-
// the caller's own is omitted.
|
|
80
|
+
// the caller's own is omitted. Exact worktree grants are resolved from their
|
|
81
|
+
// own authoritative access record rather than inferred from the parent.
|
|
81
82
|
function evaluate(principal, status) {
|
|
82
83
|
var denied = { owned: false, accessible: false };
|
|
83
|
-
if (!principal || !status
|
|
84
|
+
if (!principal || !status) return denied;
|
|
84
85
|
if (typeof isMultiUser !== "function" || !isMultiUser()) {
|
|
85
86
|
var owned = ownsSingleUser(principal, status);
|
|
86
87
|
return { owned: owned, accessible: owned };
|
|
@@ -181,7 +181,7 @@ function attachWorkspaceQueryService(ctx) {
|
|
|
181
181
|
var projects = [];
|
|
182
182
|
getProjects().forEach(function (project, slug) {
|
|
183
183
|
var status = project.getStatus();
|
|
184
|
-
if (!status
|
|
184
|
+
if (!status) return;
|
|
185
185
|
// Authorized here rather than at bind time, so an already-created tool
|
|
186
186
|
// handler loses a project the moment its access record changes.
|
|
187
187
|
var verdict = projectAccess.evaluate(principal, status);
|
package/lib/worktree.js
CHANGED
|
@@ -55,7 +55,7 @@ function worktreePath(projectPath, dirName) {
|
|
|
55
55
|
// Scan worktrees for a given project path
|
|
56
56
|
// Returns array of { path, branch, bare, detached, external }
|
|
57
57
|
// external = true when Git registered the worktree outside the main project folder
|
|
58
|
-
function
|
|
58
|
+
function scanWorktreesResult(projectPath, osUserInfo) {
|
|
59
59
|
var resolvedParent = path.resolve(projectPath);
|
|
60
60
|
try { resolvedParent = fs.realpathSync(resolvedParent); } catch (e) {}
|
|
61
61
|
try {
|
|
@@ -76,12 +76,16 @@ function scanWorktrees(projectPath, osUserInfo) {
|
|
|
76
76
|
wt.dirName = path.basename(wt.path);
|
|
77
77
|
results.push(wt);
|
|
78
78
|
}
|
|
79
|
-
return results;
|
|
79
|
+
return { ok: true, worktrees: results };
|
|
80
80
|
} catch (e) {
|
|
81
|
-
return [];
|
|
81
|
+
return { ok: false, worktrees: [], error: e.message || "Failed to scan worktrees" };
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
function scanWorktrees(projectPath, osUserInfo) {
|
|
86
|
+
return scanWorktreesResult(projectPath, osUserInfo).worktrees;
|
|
87
|
+
}
|
|
88
|
+
|
|
85
89
|
// Create a new worktree inside the parent project directory
|
|
86
90
|
// Returns { ok, path, error }
|
|
87
91
|
function createWorktree(projectPath, branchName, dirName, baseBranch, osUserInfo) {
|
|
@@ -127,4 +131,4 @@ function removeWorktree(projectPath, worktreeDirName, osUserInfo) {
|
|
|
127
131
|
}
|
|
128
132
|
}
|
|
129
133
|
|
|
130
|
-
module.exports = { scanWorktrees: scanWorktrees, createWorktree: createWorktree, removeWorktree: removeWorktree, isWorktree: isWorktree, isPathInside: isPathInside };
|
|
134
|
+
module.exports = { scanWorktrees: scanWorktrees, scanWorktreesResult: scanWorktreesResult, createWorktree: createWorktree, removeWorktree: removeWorktree, isWorktree: isWorktree, isPathInside: isPathInside };
|
package/lib/ws-schema.js
CHANGED
|
@@ -72,6 +72,7 @@ var schema = {
|
|
|
72
72
|
"home_project_assignment_proposal": { direction: "s2c", handler: "lib/public/modules/app-message-router.js", description: "Home-projected exact-session assignment approval card" },
|
|
73
73
|
"home_project_assignment_status": { direction: "s2c", handler: "lib/public/modules/app-message-router.js", description: "Home-projected exact-session assignment lifecycle" },
|
|
74
74
|
"split_delegation": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "A pair delegation started or ended (drives the split-view flow indicator)" },
|
|
75
|
+
"partner_task_completed": { direction: "s2c", handler: "lib/public/modules/app-messages.js", description: "Structured completion event for one correlated Split Worker task" },
|
|
75
76
|
|
|
76
77
|
// -----------------------------------------------------------------------
|
|
77
78
|
// History replay
|
|
@@ -193,7 +193,8 @@ function flattenEvent(notification, state) {
|
|
|
193
193
|
if (turnStatus === "interrupted" || state.aborted) {
|
|
194
194
|
events.push({ yokeType: "interrupted" });
|
|
195
195
|
}
|
|
196
|
-
var inputTokens = state.lastInputTokens
|
|
196
|
+
var inputTokens = state.lastInputTokens !== null && state.lastInputTokens !== undefined
|
|
197
|
+
? state.lastInputTokens : (usage ? (usage.input_tokens || 0) : 0);
|
|
197
198
|
var outputTokens = (usage ? (usage.output_tokens || 0) : 0) || state.lastOutputTokens || 0;
|
|
198
199
|
var cachedTokens = (usage ? (usage.cached_input_tokens || 0) : 0) || state.lastCachedTokens || 0;
|
|
199
200
|
var hasTokenData = inputTokens > 0 || outputTokens > 0;
|
|
@@ -217,7 +218,7 @@ function flattenEvent(notification, state) {
|
|
|
217
218
|
} : null,
|
|
218
219
|
modelUsage: resultModelUsage,
|
|
219
220
|
sessionId: state.threadId || null,
|
|
220
|
-
lastStreamInputTokens:
|
|
221
|
+
lastStreamInputTokens: null,
|
|
221
222
|
});
|
|
222
223
|
state.lastInputTokens = null;
|
|
223
224
|
state.lastCachedTokens = null;
|
|
@@ -675,15 +676,28 @@ function flattenEvent(notification, state) {
|
|
|
675
676
|
// context gauge must use the LAST turn, not the running total: each turn's
|
|
676
677
|
// input already contains the whole conversation, so summing turns
|
|
677
678
|
// overstates occupancy (a 5k + 8k thread reads as 13k of an 8k context).
|
|
678
|
-
//
|
|
679
|
+
// A missing lastTurn is not evidence of current context occupancy. The
|
|
680
|
+
// protocol's total is cumulative across the thread and must never be used
|
|
681
|
+
// as the context gauge.
|
|
679
682
|
if (method === "thread/tokenUsage/updated") {
|
|
680
683
|
var tu = params.tokenUsage;
|
|
681
684
|
if (tu) {
|
|
682
|
-
var turnUsage = tu.lastTurn
|
|
685
|
+
var turnUsage = tu.lastTurn;
|
|
683
686
|
if (turnUsage) {
|
|
684
|
-
|
|
687
|
+
var candidateInput = typeof turnUsage.inputTokens === "number" ? turnUsage.inputTokens : null;
|
|
688
|
+
var currentWindow = typeof tu.modelContextWindow === "number" ? tu.modelContextWindow : null;
|
|
689
|
+
state.lastInputTokens = candidateInput !== null && (currentWindow === null || candidateInput <= currentWindow) ? candidateInput : null;
|
|
685
690
|
state.lastCachedTokens = turnUsage.cachedInputTokens || 0;
|
|
686
691
|
state.lastOutputTokens = turnUsage.outputTokens || 0;
|
|
692
|
+
state.currentContextUsage = state.lastInputTokens !== null &&
|
|
693
|
+
(currentWindow === null || state.lastInputTokens <= currentWindow)
|
|
694
|
+
? { input_tokens: state.lastInputTokens, contextWindow: currentWindow }
|
|
695
|
+
: { input_tokens: null, contextWindow: currentWindow };
|
|
696
|
+
} else {
|
|
697
|
+
state.lastInputTokens = null;
|
|
698
|
+
state.lastCachedTokens = null;
|
|
699
|
+
state.lastOutputTokens = null;
|
|
700
|
+
state.currentContextUsage = null;
|
|
687
701
|
}
|
|
688
702
|
if (tu.modelContextWindow) state.modelContextWindow = tu.modelContextWindow;
|
|
689
703
|
}
|
|
@@ -734,6 +748,7 @@ function createEventState(model) {
|
|
|
734
748
|
lastInputTokens: null,
|
|
735
749
|
lastCachedTokens: null,
|
|
736
750
|
lastOutputTokens: null,
|
|
751
|
+
currentContextUsage: null,
|
|
737
752
|
modelContextWindow: null,
|
|
738
753
|
done: false,
|
|
739
754
|
aborted: false,
|
|
@@ -1107,6 +1122,9 @@ function createCodexQueryHandle(appServer, queryOpts) {
|
|
|
1107
1122
|
|
|
1108
1123
|
var threadResult;
|
|
1109
1124
|
if (queryOpts.resumeSessionId) {
|
|
1125
|
+
// ThreadResumeParams in the installed app-server schema has no
|
|
1126
|
+
// dynamicTools field. New tools therefore require a new Driver/Worker
|
|
1127
|
+
// thread; resumed threads can only refresh handlers for persisted tools.
|
|
1110
1128
|
threadResult = await appServer.send("thread/resume", {
|
|
1111
1129
|
threadId: queryOpts.resumeSessionId,
|
|
1112
1130
|
model: threadParams.model,
|
|
@@ -1269,7 +1287,7 @@ function createCodexQueryHandle(appServer, queryOpts) {
|
|
|
1269
1287
|
},
|
|
1270
1288
|
|
|
1271
1289
|
getContextUsage: function() {
|
|
1272
|
-
return Promise.resolve(state.
|
|
1290
|
+
return Promise.resolve(state.currentContextUsage);
|
|
1273
1291
|
},
|
|
1274
1292
|
|
|
1275
1293
|
abort: function() {
|
|
@@ -1342,8 +1360,8 @@ function createCodexAdapter(opts) {
|
|
|
1342
1360
|
// listing them would only surface a runtime failure in the model picker.
|
|
1343
1361
|
var CODEX_MODELS = [
|
|
1344
1362
|
"gpt-6-astra",
|
|
1345
|
-
"gpt-5.6-terra",
|
|
1346
1363
|
"gpt-5.6-sol",
|
|
1364
|
+
"gpt-5.6-terra",
|
|
1347
1365
|
"gpt-5.6-luna",
|
|
1348
1366
|
"gpt-5.5",
|
|
1349
1367
|
"gpt-5.2",
|
package/package.json
CHANGED