clay-server 4.0.0 → 4.1.0-beta.1
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 +71 -61
- 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 +73 -0
- package/lib/project-session-pair.js +115 -116
- package/lib/project-sessions.js +40 -6
- package/lib/project-worker-proposal.js +54 -57
- package/lib/project.js +27 -4
- package/lib/public/app.js +1 -0
- package/lib/public/css/command-palette.css +26 -0
- package/lib/public/css/icon-strip.css +40 -0
- package/lib/public/index.html +2 -2
- package/lib/public/modules/app-message-router.js +1 -1
- 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/search-clay-chat.js +47 -6
- package/lib/public/modules/search-clay-controls.js +86 -0
- package/lib/public/modules/sidebar-projects.js +14 -171
- package/lib/public/modules/worker-proposal.js +4 -0
- package/lib/public/style.css +1 -1
- package/lib/sdk-bridge.js +21 -1
- package/lib/server-admin.js +3 -3
- package/lib/server-global-ws.js +10 -0
- package/lib/server-home-chat-events.js +3 -0
- package/lib/server-home-chat.js +35 -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 +12 -1
- 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/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 +7 -0
- package/lib/yoke/adapters/codex.js +6 -2
- package/package.json +1 -1
|
@@ -42,6 +42,9 @@ function attachPairFactory(ctx) {
|
|
|
42
42
|
function validateVendor(vendor) {
|
|
43
43
|
var installed = sm.installedVendors || [];
|
|
44
44
|
if (!vendor || installed.indexOf(vendor) === -1) throw new Error("vendor is not installed: " + (vendor || "unknown"));
|
|
45
|
+
if (Array.isArray(sm.availableVendors) && sm.availableVendors.indexOf(vendor) === -1) {
|
|
46
|
+
throw new Error("vendor runtime is temporarily unavailable: " + vendor);
|
|
47
|
+
}
|
|
45
48
|
return vendor;
|
|
46
49
|
}
|
|
47
50
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
var MAX_RESPONSE_CHARS = 30000;
|
|
2
|
+
|
|
3
|
+
function responseText(history, fromIndex) {
|
|
4
|
+
var text = "";
|
|
5
|
+
for (var i = Math.max(0, fromIndex || 0); i < history.length; i++) {
|
|
6
|
+
if (history[i] && history[i].type === "delta" && history[i].text) text += history[i].text;
|
|
7
|
+
}
|
|
8
|
+
return text.length > MAX_RESPONSE_CHARS ? text.slice(-MAX_RESPONSE_CHARS) : text;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function errorSince(history, fromIndex) {
|
|
12
|
+
for (var i = history.length - 1; i >= Math.max(0, fromIndex || 0); i--) {
|
|
13
|
+
if (history[i] && history[i].type === "error") return history[i].text || "partner turn failed";
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function recentTurns(session, count) {
|
|
19
|
+
var history = session.history || [];
|
|
20
|
+
var starts = [];
|
|
21
|
+
for (var i = 0; i < history.length; i++) if (history[i] && history[i].type === "user_message") starts.push(i);
|
|
22
|
+
var from = starts.length > 0 ? starts[Math.max(0, starts.length - count)] : 0;
|
|
23
|
+
var turns = [];
|
|
24
|
+
var current = null;
|
|
25
|
+
for (var j = from; j < history.length; j++) {
|
|
26
|
+
var item = history[j];
|
|
27
|
+
if (!item) continue;
|
|
28
|
+
if (item.type === "user_message") {
|
|
29
|
+
current = { user: item.text || "", delegated: !!item.delegated, response: "" };
|
|
30
|
+
turns.push(current);
|
|
31
|
+
} else if (item.type === "delta" && item.text) {
|
|
32
|
+
if (!current) { current = { user: "", delegated: false, response: "" }; turns.push(current); }
|
|
33
|
+
current.response += item.text;
|
|
34
|
+
if (current.response.length > MAX_RESPONSE_CHARS) current.response = current.response.slice(-MAX_RESPONSE_CHARS);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return turns;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = { errorSince: errorSince, recentTurns: recentTurns, responseText: responseText };
|
|
@@ -15,6 +15,7 @@ function getToolDefs(handlers, options) {
|
|
|
15
15
|
wait: { type: "boolean", description: "Wait for the partner's turn to finish. Defaults to true." },
|
|
16
16
|
timeoutSeconds: { type: "number", description: "Maximum wait in seconds, from 1 to 900. Defaults to 300." },
|
|
17
17
|
operationId: { type: "string", description: "Optional stable id for this delegation. Reusing it within the same human turn returns the original operation instead of sending twice." },
|
|
18
|
+
taskId: { type: "string", description: "Optional stable task id for generation and completion correlation." },
|
|
18
19
|
}, ["message"]),
|
|
19
20
|
handler: function (args) { return handlers.send(args || {}); },
|
|
20
21
|
},
|
|
@@ -29,7 +30,7 @@ function getToolDefs(handlers, options) {
|
|
|
29
30
|
{
|
|
30
31
|
name: "interrupt_partner",
|
|
31
32
|
description: "Interrupt the Split Worker's current task. Use this when the task is going in the wrong direction, needs to be reprioritized, or must stop before the next instruction. The Split Worker returns any partial result to you for review.",
|
|
32
|
-
inputSchema: buildShape({}),
|
|
33
|
+
inputSchema: buildShape({ reason: { type: "string", description: "Why the Driver is interrupting this task." } }),
|
|
33
34
|
handler: function (args) { return handlers.interrupt(args || {}); },
|
|
34
35
|
},
|
|
35
36
|
{
|
|
@@ -41,6 +42,15 @@ function getToolDefs(handlers, options) {
|
|
|
41
42
|
];
|
|
42
43
|
if (!lifecycle) return defs;
|
|
43
44
|
return defs.concat([
|
|
45
|
+
{
|
|
46
|
+
name: "message_partner",
|
|
47
|
+
description: "Queue a non-interrupting message on the active Split Worker's runtime input without creating a delegated follow-up. Returns queued or rejected with an exact request id; queued does not claim model execution.",
|
|
48
|
+
inputSchema: buildShape({
|
|
49
|
+
message: { type: "string", description: "Bounded correction or context for the current active Worker turn." },
|
|
50
|
+
requestId: { type: "string", description: "Optional stable request id. Reuse it to deduplicate a retried delivery within this human turn." },
|
|
51
|
+
}, ["message"]),
|
|
52
|
+
handler: function (args) { return handlers.message(args || {}); },
|
|
53
|
+
},
|
|
44
54
|
{
|
|
45
55
|
name: "partner_status",
|
|
46
56
|
description: "Bounded capacity and continuity report for your Split Worker, for deciding reuse against replacement: context tokens used and the ratio of its window, current task and activity, vendor/model/effort, history size and idle time, whether it is safe to replace right now, and your own recorded results for earlier Worker generations. Returns no transcript. Prefer this over reading turns when you only need to decide.",
|
|
@@ -62,6 +72,7 @@ function getToolDefs(handlers, options) {
|
|
|
62
72
|
description: "Optional bounded assessment of the Worker being replaced, recorded against that exact generation.",
|
|
63
73
|
},
|
|
64
74
|
operationId: { type: "string", description: "Optional stable id for this replacement. Reusing it within the same human turn returns the original operation instead of replacing twice." },
|
|
75
|
+
supersedeProposalId: { type: "string", description: "Exact pending replacement proposal id to supersede with this new configuration." },
|
|
65
76
|
}, ["message", "recommendationRationale"]),
|
|
66
77
|
handler: function (args) { return handlers.replace(args || {}); },
|
|
67
78
|
},
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
function fingerprint(args, fields) {
|
|
2
|
+
var source = args || {};
|
|
3
|
+
var value = {};
|
|
4
|
+
for (var i = 0; i < fields.length; i++) {
|
|
5
|
+
var name = fields[i];
|
|
6
|
+
value[name] = Object.prototype.hasOwnProperty.call(source, name) ? source[name] : null;
|
|
7
|
+
}
|
|
8
|
+
return JSON.stringify(value);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
module.exports = { fingerprint: fingerprint };
|
|
@@ -3,6 +3,19 @@
|
|
|
3
3
|
// Pure data, extracted from project-session-pair.js so that module stays under
|
|
4
4
|
// the size limit and so the guidance can be reviewed as prose.
|
|
5
5
|
|
|
6
|
+
var WORKER_RUNTIME_SELECTION = [
|
|
7
|
+
"Use a high-low model mix: keep decomposition, judgment, and review with the Driver, and prefer a lighter",
|
|
8
|
+
"Worker model capable of the bounded execution task. Do not mirror the Driver's model or default to the",
|
|
9
|
+
"strongest available model merely because it is available. Explicitly fill the vendor, model, and thinking",
|
|
10
|
+
"effort recommendation fields for both creation and replacement; never leave model selection to card defaults.",
|
|
11
|
+
"Select model capability and thinking effort separately. Start with the lowest effort adequate for the task,",
|
|
12
|
+
"not high effort by habit. Choose from the installed, offered catalog; do not invent model availability or prices.",
|
|
13
|
+
"Explain why the lighter choice can handle the task. Escalate only for a concrete requirement beyond its",
|
|
14
|
+
"capabilities or observed failures, and state that reason. Use recorded Worker outcomes when available,",
|
|
15
|
+
"and reconsider a lighter model for the next bounded task after an escalation. Honor the user's explicit",
|
|
16
|
+
"runtime choice; this is a recommendation policy, not a restriction on which model the user may select.",
|
|
17
|
+
].join(" ").replace(/ {2,}/g, " ");
|
|
18
|
+
|
|
6
19
|
var DRIVER_DELEGATION = [
|
|
7
20
|
"Your management objective: protect your own context, keep the Split Worker compact, and put execution where",
|
|
8
21
|
"it runs best. Delegate implementation-heavy work rather than doing it here. Treat work spanning multiple modules,",
|
|
@@ -13,9 +26,10 @@ var DRIVER_DELEGATION = [
|
|
|
13
26
|
].join(" ").replace(/ {2,}/g, " ");
|
|
14
27
|
|
|
15
28
|
var DRIVER_CORE = [
|
|
29
|
+
WORKER_RUNTIME_SELECTION,
|
|
16
30
|
"You are the Driver of a visible Driver/Split Worker pair, and you manage that Split Worker yourself.",
|
|
17
|
-
"The tools
|
|
18
|
-
"and
|
|
31
|
+
"The pair tools distinguish a live message, a queued follow-up, an interrupt-and-replace task, resumption,",
|
|
32
|
+
"and replacement of the Worker runtime. Inspect exact task and proposal ids before changing queued work.",
|
|
19
33
|
"",
|
|
20
34
|
"Reuse the existing Split Worker",
|
|
21
35
|
"only when its accumulated context genuinely helps the next task; when it is context-bloated, stale, or",
|
|
@@ -34,6 +48,8 @@ var DRIVER_CORE = [
|
|
|
34
48
|
"interrupt true, which stops it first only after acceptance. A human Stop",
|
|
35
49
|
"is authoritative: do not retry, send more work, or replace the Worker in the same turn. Clay blocks those",
|
|
36
50
|
"actions until the human sends a new Driver message. Use close_partner when they ask to close the pane.",
|
|
51
|
+
"A Worker outcome envelope is Worker-reported evidence, not Driver verification. Preserve partial results and",
|
|
52
|
+
"correlate completions to their task and generation; never attach a late old-generation result to a new Worker.",
|
|
37
53
|
"",
|
|
38
54
|
"Recommend a Worker vendor, model, and effort from what is actually installed and offered, and include a concise",
|
|
39
55
|
"rationale explaining why all three fit the task. The card remains visible as an audit trail even when full access",
|
|
@@ -56,7 +72,12 @@ var DRIVER_CORE = [
|
|
|
56
72
|
|
|
57
73
|
var DRIVER = DRIVER_CORE + " " + DRIVER_DELEGATION;
|
|
58
74
|
|
|
75
|
+
function worker(taskId) {
|
|
76
|
+
return "You are the configured Split Worker. For task " + (taskId || "unknown") + ", call report_partner_outcome before finishing when the tool is available. Report only what you observed: changed files, verification commands/results, unverified work, running processes, and next action. Never claim Driver verification.";
|
|
77
|
+
}
|
|
78
|
+
|
|
59
79
|
var UNPAIRED = [
|
|
80
|
+
WORKER_RUNTIME_SELECTION,
|
|
60
81
|
"Infer the user's intended target from conversational and UI context. References to a user-visible paired or",
|
|
61
82
|
"split pane, its session, its activity or status, or a collaborator the user wants opened resolve to Clay's",
|
|
62
83
|
"Split Worker and its partner tools. Internal Sub-agents are a distinct execution mechanism, not a lexical",
|
|
@@ -76,4 +97,5 @@ module.exports = {
|
|
|
76
97
|
DRIVER_CORE: DRIVER_CORE,
|
|
77
98
|
DRIVER_DELEGATION: DRIVER_DELEGATION,
|
|
78
99
|
UNPAIRED: UNPAIRED,
|
|
100
|
+
worker: worker,
|
|
79
101
|
};
|
|
@@ -111,14 +111,19 @@ function attachPairTurnControl(ctx) {
|
|
|
111
111
|
return clean;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
function runOperation(driver, kind, rawId, fn) {
|
|
114
|
+
function runOperation(driver, kind, rawId, fn, fingerprint) {
|
|
115
115
|
var id = operationId(rawId);
|
|
116
116
|
if (!id) return Promise.resolve().then(fn);
|
|
117
117
|
var state = stateFor(driver);
|
|
118
118
|
var key = kind + ":" + id;
|
|
119
|
-
if (state.operations[key])
|
|
119
|
+
if (state.operations[key]) {
|
|
120
|
+
if (fingerprint !== undefined && state.operations[key].fingerprint !== fingerprint) {
|
|
121
|
+
return Promise.reject(new Error("operation id was already used with different input"));
|
|
122
|
+
}
|
|
123
|
+
return state.operations[key].promise;
|
|
124
|
+
}
|
|
120
125
|
var promise = Promise.resolve().then(fn);
|
|
121
|
-
state.operations[key] = promise;
|
|
126
|
+
state.operations[key] = { fingerprint: fingerprint, promise: promise };
|
|
122
127
|
return promise;
|
|
123
128
|
}
|
|
124
129
|
|
|
@@ -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
|
|
@@ -643,6 +644,8 @@ var schema = {
|
|
|
643
644
|
"home_mate_sessions_list": { direction: "c2s", handler: "lib/server-home-chat.js", description: "List the current user's conversations with the selected mate" },
|
|
644
645
|
"home_mate_session_open": { direction: "c2s", handler: "lib/server-home-chat.js", description: "Open one explicit conversation with the selected mate" },
|
|
645
646
|
"home_mate_send": { direction: "c2s", handler: "lib/server-home-chat.js", description: "Send a message to the selected mate from the home hub" },
|
|
647
|
+
"home_mate_stop": { direction: "c2s", handler: "lib/server-home-chat.js", description: "Stop the exact query-bound global Ask Clay turn" },
|
|
648
|
+
"home_mate_permission_response": { direction: "c2s", handler: "lib/server-home-chat.js", description: "Answer an exact pending global Ask Clay permission request" },
|
|
646
649
|
"home_mate_new_session": { direction: "c2s", handler: "lib/server-home-chat.js", description: "Start a fresh embedded session with the selected mate, optionally with a typed creation intent" },
|
|
647
650
|
"home_mate_debate_plan": { direction: "c2s", handler: "lib/server-home-chat.js", description: "Start a fresh server-seeded debate planning conversation with Clay using an optional prefilled topic" },
|
|
648
651
|
"home_mate_creation_plan": { direction: "c2s", handler: "lib/server-home-chat.js", description: "Start a fresh server-seeded Mate creation interview with Clay" },
|
|
@@ -659,6 +662,10 @@ var schema = {
|
|
|
659
662
|
"home_mate_model_set": { direction: "c2s", handler: "lib/server-home-chat.js", description: "Validate and persist the Mate vendor/model, optionally applying it to an exact owned pristine Home draft" },
|
|
660
663
|
"home_mate_history": { direction: "s2c", handler: "lib/public/modules/home-mate-chat.js", description: "Selected mate's initial or refreshed chat history" },
|
|
661
664
|
"home_clay_activity": { direction: "s2c", handler: "lib/public/modules/search-clay-chat.js", description: "Sanitized progress stage for a query-bound global-search Clay conversation" },
|
|
665
|
+
"home_mate_permission_request": { direction: "s2c", handler: "lib/public/modules/search-clay-chat.js", description: "Exact pending permission card for a global Ask Clay conversation" },
|
|
666
|
+
"home_mate_permission_resolved": { direction: "s2c", handler: "lib/public/modules/search-clay-chat.js", description: "Server-confirmed Ask Clay permission decision" },
|
|
667
|
+
"home_mate_permission_cancelled": { direction: "s2c", handler: "lib/public/modules/search-clay-chat.js", description: "Server-confirmed Ask Clay permission cancellation" },
|
|
668
|
+
"home_mate_stopping": { direction: "s2c", handler: "lib/public/modules/search-clay-chat.js", description: "Server accepted Stop for the exact Ask Clay conversation" },
|
|
662
669
|
"home_mate_session_identity": { direction: "s2c", handler: "lib/public/modules/app-message-router.js", description: "Promote an active Home session from its local reference to its durable runtime identity" },
|
|
663
670
|
"home_mate_sessions_state": { direction: "s2c", handler: "lib/public/modules/home-mate-chat.js", description: "Owned visible Mate conversations with sanitized runtime identity, model, status, and timestamps" },
|
|
664
671
|
"home_mate_delta": { direction: "s2c", handler: "lib/public/modules/home-mate-chat.js", description: "Selected mate's streaming assistant text delta" },
|
|
@@ -1269,7 +1269,11 @@ function createCodexQueryHandle(appServer, queryOpts) {
|
|
|
1269
1269
|
},
|
|
1270
1270
|
|
|
1271
1271
|
getContextUsage: function() {
|
|
1272
|
-
return Promise.resolve(
|
|
1272
|
+
if (state.lastInputTokens == null && state.modelContextWindow == null) return Promise.resolve(null);
|
|
1273
|
+
return Promise.resolve({
|
|
1274
|
+
input_tokens: state.lastInputTokens == null ? null : state.lastInputTokens,
|
|
1275
|
+
contextWindow: state.modelContextWindow || null,
|
|
1276
|
+
});
|
|
1273
1277
|
},
|
|
1274
1278
|
|
|
1275
1279
|
abort: function() {
|
|
@@ -1342,8 +1346,8 @@ function createCodexAdapter(opts) {
|
|
|
1342
1346
|
// listing them would only surface a runtime failure in the model picker.
|
|
1343
1347
|
var CODEX_MODELS = [
|
|
1344
1348
|
"gpt-6-astra",
|
|
1345
|
-
"gpt-5.6-terra",
|
|
1346
1349
|
"gpt-5.6-sol",
|
|
1350
|
+
"gpt-5.6-terra",
|
|
1347
1351
|
"gpt-5.6-luna",
|
|
1348
1352
|
"gpt-5.5",
|
|
1349
1353
|
"gpt-5.2",
|
package/package.json
CHANGED