@ctrl-spc/cs 0.7.15 → 0.7.16
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/dist/agents.js +175 -1
- package/dist/codex-home.js +14 -11
- package/dist/companion-ui.js +24 -4
- package/dist/companion.js +33 -25
- package/dist/config.js +147 -16
- package/dist/daemon-lifecycle.js +39 -12
- package/dist/daemon-processes.js +123 -19
- package/dist/failure-reason.js +76 -16
- package/dist/index.js +6 -3
- package/dist/login.js +6 -8
- package/dist/mcp.js +55 -56
- package/dist/orchestrator.js +322 -197
- package/dist/panel3/coordinator.js +3 -1
- package/dist/panel3/presence.js +1 -1
- package/dist/panel3/run.js +220 -84
- package/dist/panel3/spawn.js +26 -12
- package/dist/panel3/tools.js +5 -0
- package/dist/presence-heartbeat.js +3 -0
- package/dist/presence.js +122 -145
- package/dist/supabase.js +141 -39
- package/package.json +1 -1
package/dist/mcp.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { deferMcpCleanup, pendingMcpCleanup, acknowledgeMcpCleanup } from './daemon-processes.js';
|
|
1
2
|
import { listStructureArtifactsHandler, getStructureArtifactHandler, searchAgentCardsHandler, createStructureArtifactHandler, updateStructureArtifactHandler } from './product-tools.js';
|
|
2
3
|
import { createEpicHandler, createSprintHandler, listStructureHandler, updateStructureHandler } from './product-tools.js';
|
|
3
4
|
export { createEpicHandler, createSprintHandler, listStructureHandler, updateStructureHandler } from './product-tools.js';
|
|
@@ -20,9 +21,10 @@ import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
|
|
|
20
21
|
import { z } from 'zod';
|
|
21
22
|
import { workflowToolPermission, validateWorkflowToolTarget, WORKFLOW_TOOL_TEACHING, WORKFLOW_AUTHORING_TEACHING, authorToolMentions } from './workflows.js';
|
|
22
23
|
import { workflowToolForName, validateApprovalAction } from './workflow-tool-mentions.js';
|
|
24
|
+
import { clientSessionCurrent } from './supabase.js';
|
|
23
25
|
import { TOOLS_SERVER_PORT, SESSION_TTL_MS } from './env.js';
|
|
24
26
|
import { agentPath } from './agents.js';
|
|
25
|
-
import { mcpToken, readMcpToken
|
|
27
|
+
import { mcpToken, readMcpToken } from './config.js';
|
|
26
28
|
import { FIREWALL_WRITING_RULE } from './firewall.js';
|
|
27
29
|
/* The path rule itself, moved out so `workflows.ts` can ask the same question
|
|
28
30
|
before it builds a workflow. `refuseAbsolutePaths` below is still v2's own
|
|
@@ -9237,6 +9239,7 @@ runTodoIdSource = null) {
|
|
|
9237
9239
|
await releaseSessionReservations([prevTodo.sessionId], client);
|
|
9238
9240
|
}
|
|
9239
9241
|
openSessions.set(connectionId, {
|
|
9242
|
+
accountId: userId,
|
|
9240
9243
|
sessionId: opened.id,
|
|
9241
9244
|
taskId: null,
|
|
9242
9245
|
todoId: args.todo_id,
|
|
@@ -9380,6 +9383,7 @@ runTodoIdSource = null) {
|
|
|
9380
9383
|
// Record the new session in the registry keyed by connectionId, with a
|
|
9381
9384
|
// fresh activity-TTL (extended by every subsequent tool call).
|
|
9382
9385
|
openSessions.set(connectionId, {
|
|
9386
|
+
accountId: userId,
|
|
9383
9387
|
sessionId: session.id,
|
|
9384
9388
|
taskId: session.task_id,
|
|
9385
9389
|
expiresAt: Date.now() + SESSION_TTL_MS,
|
|
@@ -11034,7 +11038,7 @@ export async function releaseSessionReservations(sessionIds,
|
|
|
11034
11038
|
/** The client to use. The lifecycle helpers hold `toolsClient`; the tool
|
|
11035
11039
|
* handlers hold their own per-connection client and pass it, matching what
|
|
11036
11040
|
* they use for the session-status write immediately above each call. */
|
|
11037
|
-
client = toolsClient) {
|
|
11041
|
+
client = toolsClient, strict = false) {
|
|
11038
11042
|
if (!client || sessionIds.length === 0)
|
|
11039
11043
|
return;
|
|
11040
11044
|
try {
|
|
@@ -11049,6 +11053,8 @@ client = toolsClient) {
|
|
|
11049
11053
|
throw new Error(error.message);
|
|
11050
11054
|
}
|
|
11051
11055
|
catch (err) {
|
|
11056
|
+
if (strict)
|
|
11057
|
+
throw err;
|
|
11052
11058
|
/* NOTHING ABOVE THIS, following `noteDroppedActivity` and the session
|
|
11053
11059
|
helpers. The session is already ended, which is the fact the product
|
|
11054
11060
|
reads; an unreleased lease degrades to exactly the pre-slice behaviour
|
|
@@ -11366,64 +11372,49 @@ export async function noteDroppedActivity(todoId) {
|
|
|
11366
11372
|
console.warn(`recording a dropped activity write failed: ${errorMessage(err)}`);
|
|
11367
11373
|
}
|
|
11368
11374
|
}
|
|
11369
|
-
/**
|
|
11370
|
-
export async function
|
|
11371
|
-
const
|
|
11372
|
-
|
|
11373
|
-
it is scoped to the CONNECTION rather than to the session. The connection
|
|
11374
|
-
is gone, so nothing can produce anything else under it. */
|
|
11375
|
-
lastSessionByConnection.delete(connectionId);
|
|
11376
|
-
if (!toolsClient || !s) {
|
|
11377
|
-
openSessions.delete(connectionId);
|
|
11375
|
+
/** Flush only the authenticated account's durable cleanup, using its captured client. */
|
|
11376
|
+
export async function reconcileMcpCleanup(client, accountId) {
|
|
11377
|
+
const sessionIds = pendingMcpCleanup(accountId);
|
|
11378
|
+
if (!sessionIds.length)
|
|
11378
11379
|
return;
|
|
11379
|
-
}
|
|
11380
|
+
const { error } = await client.from('cliv2_agent_sessions').update({ status: 'ended' }).in('id', sessionIds);
|
|
11381
|
+
if (error)
|
|
11382
|
+
throw error;
|
|
11383
|
+
await releaseSessionReservations(sessionIds, client, true);
|
|
11384
|
+
acknowledgeMcpCleanup(accountId, sessionIds);
|
|
11385
|
+
}
|
|
11386
|
+
/** Retire the local connection immediately; cloud cleanup survives authentication loss. */
|
|
11387
|
+
export async function endConnectionSession(connectionId) {
|
|
11388
|
+
const client = toolsClient;
|
|
11389
|
+
const session = openSessions.get(connectionId);
|
|
11390
|
+
if (session)
|
|
11391
|
+
deferMcpCleanup([session]);
|
|
11380
11392
|
openSessions.delete(connectionId);
|
|
11381
|
-
|
|
11382
|
-
|
|
11383
|
-
|
|
11384
|
-
|
|
11385
|
-
|
|
11386
|
-
|
|
11387
|
-
|
|
11388
|
-
|
|
11389
|
-
catch (err) {
|
|
11390
|
-
console.warn(`ending session on connection close failed: ${err.message}`);
|
|
11391
|
-
}
|
|
11392
|
-
/* 18c SLICE 9 (GAP 15), ROUTE 4a. THE CONNECTION IS GONE, so nothing can
|
|
11393
|
-
ever release these leases by asking. This is what covers every orchestrator
|
|
11394
|
-
finish path: they all end the agent PROCESS, and a dead process's MCP
|
|
11395
|
-
connection closes here. See `releaseSessionReservations` for why the fix
|
|
11396
|
-
hangs off the session rather than off those ten callers. */
|
|
11397
|
-
await releaseSessionReservations([s.sessionId]);
|
|
11398
|
-
}
|
|
11399
|
-
/** Mark ALL open sessions ended and clear the registry (server stop / logout). */
|
|
11400
|
-
export async function endAllOpenSessions() {
|
|
11401
|
-
if (openSessions.size === 0) {
|
|
11402
|
-
return;
|
|
11393
|
+
lastSessionByConnection.delete(connectionId);
|
|
11394
|
+
if (client && session) {
|
|
11395
|
+
try {
|
|
11396
|
+
await reconcileMcpCleanup(client, session.accountId);
|
|
11397
|
+
}
|
|
11398
|
+
catch {
|
|
11399
|
+
console.warn('Session cleanup is waiting for its original account to reconnect.');
|
|
11400
|
+
}
|
|
11403
11401
|
}
|
|
11404
|
-
|
|
11402
|
+
}
|
|
11403
|
+
export async function endAllOpenSessions(client = toolsClient) {
|
|
11404
|
+
const sessions = [...openSessions.values()];
|
|
11405
|
+
deferMcpCleanup(sessions);
|
|
11405
11406
|
openSessions.clear();
|
|
11406
|
-
/* 18c Slice 6: server stop or logout. Every connection is going away, so no
|
|
11407
|
-
later write can legitimately attribute to any of these. */
|
|
11408
11407
|
lastSessionByConnection.clear();
|
|
11409
|
-
if (!
|
|
11408
|
+
if (!client)
|
|
11410
11409
|
return;
|
|
11411
|
-
|
|
11412
|
-
|
|
11413
|
-
|
|
11414
|
-
|
|
11415
|
-
|
|
11416
|
-
|
|
11417
|
-
|
|
11418
|
-
}
|
|
11419
|
-
catch (err) {
|
|
11420
|
-
console.warn(`ending all open sessions failed: ${err.message}`);
|
|
11410
|
+
for (const accountId of new Set(sessions.map(session => session.accountId))) {
|
|
11411
|
+
try {
|
|
11412
|
+
await reconcileMcpCleanup(client, accountId);
|
|
11413
|
+
}
|
|
11414
|
+
catch {
|
|
11415
|
+
console.warn('Session cleanup is waiting for its original account to reconnect.');
|
|
11416
|
+
}
|
|
11421
11417
|
}
|
|
11422
|
-
/* 18c SLICE 9 (GAP 15), ROUTE 4b. Server stop or logout: every session is
|
|
11423
|
-
over at once, so every lease any of them holds goes with them. On logout in
|
|
11424
|
-
particular, leaving them active would have the next sign-in's first run
|
|
11425
|
-
collide with an account that is no longer even connected. */
|
|
11426
|
-
await releaseSessionReservations(sessionIds);
|
|
11427
11418
|
}
|
|
11428
11419
|
async function readJsonBody(req) {
|
|
11429
11420
|
const chunks = [];
|
|
@@ -11537,7 +11528,7 @@ export async function startToolsServer(deps) {
|
|
|
11537
11528
|
// clear session.json — it can't reach this server's in-memory session. So
|
|
11538
11529
|
// every request (including ones on an already-open MCP connection) re-checks
|
|
11539
11530
|
// the on-disk session; once it's gone, nothing is served for the old account.
|
|
11540
|
-
if (!
|
|
11531
|
+
if (!clientSessionCurrent(deps.client)) {
|
|
11541
11532
|
res.writeHead(401, { 'Content-Type': 'text/plain' }).end('Logged out');
|
|
11542
11533
|
return;
|
|
11543
11534
|
}
|
|
@@ -11563,6 +11554,12 @@ export async function startToolsServer(deps) {
|
|
|
11563
11554
|
await existing.handleRequest(req, res);
|
|
11564
11555
|
return;
|
|
11565
11556
|
}
|
|
11557
|
+
if (sessionId) {
|
|
11558
|
+
// MCP clients reinitialize after a lost session only when it returns 404.
|
|
11559
|
+
sessionTodoIds.delete(sessionId);
|
|
11560
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' }).end('Session not found');
|
|
11561
|
+
return;
|
|
11562
|
+
}
|
|
11566
11563
|
if (req.method !== 'POST') {
|
|
11567
11564
|
res.writeHead(400, { 'Content-Type': 'text/plain' }).end('Bad Request: no valid session ID provided');
|
|
11568
11565
|
return;
|
|
@@ -11668,9 +11665,11 @@ export async function stopToolsServer() {
|
|
|
11668
11665
|
// D2b: flip every open work session to ended (this needs toolsClient) BEFORE
|
|
11669
11666
|
// dropping the client reference, so the board's "working" chips clear on server
|
|
11670
11667
|
// stop / logout; then release the client alongside the reg-status resets.
|
|
11671
|
-
|
|
11672
|
-
toolsClient = null;
|
|
11668
|
+
const client = toolsClient;
|
|
11673
11669
|
await h.close();
|
|
11670
|
+
void endAllOpenSessions(client).catch(error => console.warn(`Session cleanup remains incomplete: ${String(error)}`));
|
|
11671
|
+
if (toolsClient === client)
|
|
11672
|
+
toolsClient = null;
|
|
11674
11673
|
}
|
|
11675
11674
|
/** Whether the tools server is currently listening, and its fixed port. */
|
|
11676
11675
|
export function toolsServerStatus() {
|