session-collab-mcp 2.2.0 → 2.3.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/README.md +55 -10
- package/dist/{chunk-SOUW3JSS.js → chunk-UDJMG3TR.js} +580 -61
- package/dist/chunk-UDJMG3TR.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/http/cli.js +7 -1
- package/dist/http/cli.js.map +1 -1
- package/dist/http/client-cli.js +80 -6
- package/dist/http/client-cli.js.map +1 -1
- package/package.json +5 -3
- package/dist/chunk-SOUW3JSS.js.map +0 -1
|
@@ -161,12 +161,13 @@ var SERVER_INSTRUCTIONS = `
|
|
|
161
161
|
|
|
162
162
|
Coordinate sessions and persist context across conversations.
|
|
163
163
|
|
|
164
|
-
##
|
|
164
|
+
## Core Tools
|
|
165
165
|
|
|
166
|
-
### Session
|
|
166
|
+
### Session
|
|
167
167
|
- \`collab_session_start\`: Start session with project_root
|
|
168
168
|
- \`collab_session_end\`: End session, release claims
|
|
169
|
-
- \`collab_session_list\`: List active sessions
|
|
169
|
+
- \`collab_session_list\`: List active sessions and their active claim summaries
|
|
170
|
+
- \`collab_session_update\`: Update heartbeat, current task, todos, and progress
|
|
170
171
|
- \`collab_config\`: Configure session behavior
|
|
171
172
|
- \`collab_status\`: Get session status and summary
|
|
172
173
|
|
|
@@ -187,10 +188,19 @@ Coordinate sessions and persist context across conversations.
|
|
|
187
188
|
|
|
188
189
|
1. **On start**: \`collab_session_start\` with project_root
|
|
189
190
|
2. **Before editing**: \`collab_claim\` action="check"
|
|
190
|
-
3. **For changes**: \`collab_claim\` action="create"
|
|
191
|
+
3. **For changes**: \`collab_claim\` action="create" (smart mode claims safe files and queues blocked files)
|
|
191
192
|
4. **Save context**: \`collab_memory_save\` for important findings
|
|
192
|
-
5. **
|
|
193
|
-
6. **
|
|
193
|
+
5. **While working**: \`collab_session_update\` with current_task/todos
|
|
194
|
+
6. **When done**: \`collab_claim\` action="release"
|
|
195
|
+
7. **On end**: \`collab_session_end\`
|
|
196
|
+
|
|
197
|
+
## Conflict Handling
|
|
198
|
+
|
|
199
|
+
- \`strict\`: conflicting claims are blocked; coordinate before editing.
|
|
200
|
+
- \`smart\` (default): same-file work can proceed when symbol claims do not overlap; mixed requests claim safe files and create coordination requests for blocked files.
|
|
201
|
+
- \`bypass\`: overlapping claims require explicit \`allow_conflicts=true\` and return a warning.
|
|
202
|
+
- If a file is blocked, narrow the claim to specific symbols before retrying. Do not overwrite, revert, or delete another active session's work.
|
|
203
|
+
- Check \`collab_status\` or \`collab_session_list\` for pending coordination requests.
|
|
194
204
|
|
|
195
205
|
## Memory Categories
|
|
196
206
|
|
|
@@ -4284,6 +4294,29 @@ function createToolResult(text, isError = false) {
|
|
|
4284
4294
|
};
|
|
4285
4295
|
}
|
|
4286
4296
|
|
|
4297
|
+
// src/db/types.ts
|
|
4298
|
+
function getPriorityLevel(priority) {
|
|
4299
|
+
if (priority >= 90) return { value: priority, level: "critical", label: "Critical (90-100)" };
|
|
4300
|
+
if (priority >= 70) return { value: priority, level: "high", label: "High (70-89)" };
|
|
4301
|
+
if (priority >= 40) return { value: priority, level: "normal", label: "Normal (40-69)" };
|
|
4302
|
+
return { value: priority, level: "low", label: "Low (0-39)" };
|
|
4303
|
+
}
|
|
4304
|
+
var DEFAULT_SESSION_CONFIG = {
|
|
4305
|
+
mode: "smart",
|
|
4306
|
+
allow_release_others: false,
|
|
4307
|
+
auto_release_stale: false,
|
|
4308
|
+
stale_threshold_hours: 2,
|
|
4309
|
+
auto_release_immediate: false,
|
|
4310
|
+
auto_release_delay_minutes: 5
|
|
4311
|
+
};
|
|
4312
|
+
var SCOPE_WAIT_MINUTES = {
|
|
4313
|
+
small: 30,
|
|
4314
|
+
medium: 120,
|
|
4315
|
+
// 2 hours
|
|
4316
|
+
large: 480
|
|
4317
|
+
// 8 hours
|
|
4318
|
+
};
|
|
4319
|
+
|
|
4287
4320
|
// src/utils/crypto.ts
|
|
4288
4321
|
function generateId() {
|
|
4289
4322
|
return crypto.randomUUID();
|
|
@@ -4334,6 +4367,39 @@ async function listSessions(db, params = {}) {
|
|
|
4334
4367
|
const result = await db.prepare(query).bind(...bindings).all();
|
|
4335
4368
|
return result.results;
|
|
4336
4369
|
}
|
|
4370
|
+
async function updateSessionHeartbeat(db, id, statusUpdate) {
|
|
4371
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4372
|
+
let progress = null;
|
|
4373
|
+
let todosJson = null;
|
|
4374
|
+
if (statusUpdate?.todos) {
|
|
4375
|
+
const total = statusUpdate.todos.length;
|
|
4376
|
+
const completed = statusUpdate.todos.filter((t) => t.status === "completed").length;
|
|
4377
|
+
progress = {
|
|
4378
|
+
completed,
|
|
4379
|
+
total,
|
|
4380
|
+
percentage: total > 0 ? Math.round(completed / total * 100) : 0
|
|
4381
|
+
};
|
|
4382
|
+
todosJson = JSON.stringify(statusUpdate.todos);
|
|
4383
|
+
}
|
|
4384
|
+
let query = "UPDATE sessions SET last_heartbeat = ?";
|
|
4385
|
+
const bindings = [now];
|
|
4386
|
+
if (statusUpdate?.current_task !== void 0) {
|
|
4387
|
+
query += ", current_task = ?";
|
|
4388
|
+
bindings.push(statusUpdate.current_task);
|
|
4389
|
+
}
|
|
4390
|
+
if (progress) {
|
|
4391
|
+
query += ", progress = ?";
|
|
4392
|
+
bindings.push(JSON.stringify(progress));
|
|
4393
|
+
}
|
|
4394
|
+
if (todosJson) {
|
|
4395
|
+
query += ", todos = ?";
|
|
4396
|
+
bindings.push(todosJson);
|
|
4397
|
+
}
|
|
4398
|
+
query += " WHERE id = ? AND status = 'active'";
|
|
4399
|
+
bindings.push(id);
|
|
4400
|
+
const result = await db.prepare(query).bind(...bindings).run();
|
|
4401
|
+
return result.meta.changes > 0;
|
|
4402
|
+
}
|
|
4337
4403
|
async function endSession(db, id, release_claims = "abandon") {
|
|
4338
4404
|
const claimStatus = release_claims === "complete" ? "completed" : "abandoned";
|
|
4339
4405
|
await db.prepare("UPDATE claims SET status = ?, updated_at = ? WHERE session_id = ? AND status = 'active'").bind(claimStatus, (/* @__PURE__ */ new Date()).toISOString(), id).run();
|
|
@@ -4646,6 +4712,144 @@ async function removeSessionFromAllQueues(db, sessionId) {
|
|
|
4646
4712
|
const result = await db.prepare("DELETE FROM claim_queue WHERE session_id = ?").bind(sessionId).run();
|
|
4647
4713
|
return result.meta.changes;
|
|
4648
4714
|
}
|
|
4715
|
+
async function getNextQueuePosition(db, claimId) {
|
|
4716
|
+
const result = await db.prepare("SELECT MAX(position) as max_pos FROM claim_queue WHERE claim_id = ?").bind(claimId).first();
|
|
4717
|
+
return (result?.max_pos ?? 0) + 1;
|
|
4718
|
+
}
|
|
4719
|
+
async function calculateEstimatedWait(db, claimId, position) {
|
|
4720
|
+
const result = await db.prepare(
|
|
4721
|
+
`SELECT scope FROM claim_queue
|
|
4722
|
+
WHERE claim_id = ? AND position < ?
|
|
4723
|
+
ORDER BY priority DESC, position ASC`
|
|
4724
|
+
).bind(claimId, position).all();
|
|
4725
|
+
let totalMinutes = 0;
|
|
4726
|
+
for (const entry of result.results) {
|
|
4727
|
+
totalMinutes += SCOPE_WAIT_MINUTES[entry.scope] ?? SCOPE_WAIT_MINUTES.medium;
|
|
4728
|
+
}
|
|
4729
|
+
const claim = await getClaim(db, claimId);
|
|
4730
|
+
if (claim) {
|
|
4731
|
+
totalMinutes += Math.round(SCOPE_WAIT_MINUTES[claim.scope] / 2);
|
|
4732
|
+
}
|
|
4733
|
+
return totalMinutes;
|
|
4734
|
+
}
|
|
4735
|
+
async function joinQueue(db, params) {
|
|
4736
|
+
const existing = await db.prepare("SELECT * FROM claim_queue WHERE claim_id = ? AND session_id = ?").bind(params.claim_id, params.session_id).first();
|
|
4737
|
+
if (existing) {
|
|
4738
|
+
return existing;
|
|
4739
|
+
}
|
|
4740
|
+
const id = generateId();
|
|
4741
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4742
|
+
const priority = params.priority ?? 50;
|
|
4743
|
+
const scope = params.scope ?? "medium";
|
|
4744
|
+
const position = await getNextQueuePosition(db, params.claim_id);
|
|
4745
|
+
const estimatedWait = await calculateEstimatedWait(db, params.claim_id, position);
|
|
4746
|
+
await db.prepare(
|
|
4747
|
+
`INSERT INTO claim_queue (id, claim_id, session_id, intent, position, priority, scope, estimated_wait_minutes, created_at)
|
|
4748
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
4749
|
+
).bind(id, params.claim_id, params.session_id, params.intent, position, priority, scope, estimatedWait, now).run();
|
|
4750
|
+
return {
|
|
4751
|
+
id,
|
|
4752
|
+
claim_id: params.claim_id,
|
|
4753
|
+
session_id: params.session_id,
|
|
4754
|
+
intent: params.intent,
|
|
4755
|
+
position,
|
|
4756
|
+
priority,
|
|
4757
|
+
scope,
|
|
4758
|
+
estimated_wait_minutes: estimatedWait,
|
|
4759
|
+
created_at: now
|
|
4760
|
+
};
|
|
4761
|
+
}
|
|
4762
|
+
async function listQueue(db, params = {}) {
|
|
4763
|
+
let query = `
|
|
4764
|
+
SELECT
|
|
4765
|
+
q.*,
|
|
4766
|
+
s.name as session_name,
|
|
4767
|
+
c.session_id as owner_session_id,
|
|
4768
|
+
c.intent as claim_intent,
|
|
4769
|
+
cs.name as claim_session_name,
|
|
4770
|
+
GROUP_CONCAT(cf.file_path, '|||') as claim_files_concat
|
|
4771
|
+
FROM claim_queue q
|
|
4772
|
+
JOIN sessions s ON q.session_id = s.id
|
|
4773
|
+
JOIN claims c ON q.claim_id = c.id
|
|
4774
|
+
JOIN sessions cs ON c.session_id = cs.id
|
|
4775
|
+
LEFT JOIN claim_files cf ON c.id = cf.claim_id
|
|
4776
|
+
WHERE c.status = 'active'
|
|
4777
|
+
AND s.status = 'active'
|
|
4778
|
+
AND cs.status = 'active'
|
|
4779
|
+
`;
|
|
4780
|
+
const bindings = [];
|
|
4781
|
+
if (params.claim_id) {
|
|
4782
|
+
query += " AND q.claim_id = ?";
|
|
4783
|
+
bindings.push(params.claim_id);
|
|
4784
|
+
}
|
|
4785
|
+
if (params.session_id) {
|
|
4786
|
+
query += " AND q.session_id = ?";
|
|
4787
|
+
bindings.push(params.session_id);
|
|
4788
|
+
}
|
|
4789
|
+
if (params.owner_session_id) {
|
|
4790
|
+
query += " AND c.session_id = ?";
|
|
4791
|
+
bindings.push(params.owner_session_id);
|
|
4792
|
+
}
|
|
4793
|
+
query += " GROUP BY q.id ORDER BY q.priority DESC, q.position ASC";
|
|
4794
|
+
const result = await db.prepare(query).bind(...bindings).all();
|
|
4795
|
+
return result.results.map((entry) => ({
|
|
4796
|
+
...entry,
|
|
4797
|
+
claim_files: entry.claim_files_concat ? entry.claim_files_concat.split("|||") : []
|
|
4798
|
+
}));
|
|
4799
|
+
}
|
|
4800
|
+
async function createNotification(db, params) {
|
|
4801
|
+
const id = generateId();
|
|
4802
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4803
|
+
const metadataJson = params.metadata ? JSON.stringify(params.metadata) : null;
|
|
4804
|
+
await db.prepare(
|
|
4805
|
+
`INSERT INTO notifications (id, session_id, type, title, message, reference_type, reference_id, metadata, created_at)
|
|
4806
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
4807
|
+
).bind(
|
|
4808
|
+
id,
|
|
4809
|
+
params.session_id,
|
|
4810
|
+
params.type,
|
|
4811
|
+
params.title,
|
|
4812
|
+
params.message,
|
|
4813
|
+
params.reference_type ?? null,
|
|
4814
|
+
params.reference_id ?? null,
|
|
4815
|
+
metadataJson,
|
|
4816
|
+
now
|
|
4817
|
+
).run();
|
|
4818
|
+
return {
|
|
4819
|
+
id,
|
|
4820
|
+
session_id: params.session_id,
|
|
4821
|
+
type: params.type,
|
|
4822
|
+
title: params.title,
|
|
4823
|
+
message: params.message,
|
|
4824
|
+
reference_type: params.reference_type ?? null,
|
|
4825
|
+
reference_id: params.reference_id ?? null,
|
|
4826
|
+
metadata: metadataJson,
|
|
4827
|
+
read_at: null,
|
|
4828
|
+
created_at: now
|
|
4829
|
+
};
|
|
4830
|
+
}
|
|
4831
|
+
async function notifyQueueOnClaimRelease(db, claimId, releasedBy, files) {
|
|
4832
|
+
const queuedSessions = await listQueue(db, { claim_id: claimId });
|
|
4833
|
+
let notified = 0;
|
|
4834
|
+
for (const entry of queuedSessions) {
|
|
4835
|
+
await createNotification(db, {
|
|
4836
|
+
session_id: entry.session_id,
|
|
4837
|
+
type: "queue_ready",
|
|
4838
|
+
title: "Claim released",
|
|
4839
|
+
message: `The claim for ${files.join(", ")} has been released. You can claim these files now.`,
|
|
4840
|
+
reference_type: "claim",
|
|
4841
|
+
reference_id: claimId,
|
|
4842
|
+
metadata: {
|
|
4843
|
+
claim_id: claimId,
|
|
4844
|
+
files,
|
|
4845
|
+
released_by: releasedBy,
|
|
4846
|
+
queue_position: entry.position
|
|
4847
|
+
}
|
|
4848
|
+
});
|
|
4849
|
+
notified++;
|
|
4850
|
+
}
|
|
4851
|
+
return notified;
|
|
4852
|
+
}
|
|
4649
4853
|
async function logAuditEvent(db, params) {
|
|
4650
4854
|
const id = generateId();
|
|
4651
4855
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -4918,22 +5122,6 @@ async function getProtectedFiles(db, sessionId) {
|
|
|
4918
5122
|
});
|
|
4919
5123
|
}
|
|
4920
5124
|
|
|
4921
|
-
// src/db/types.ts
|
|
4922
|
-
function getPriorityLevel(priority) {
|
|
4923
|
-
if (priority >= 90) return { value: priority, level: "critical", label: "Critical (90-100)" };
|
|
4924
|
-
if (priority >= 70) return { value: priority, level: "high", label: "High (70-89)" };
|
|
4925
|
-
if (priority >= 40) return { value: priority, level: "normal", label: "Normal (40-69)" };
|
|
4926
|
-
return { value: priority, level: "low", label: "Low (0-39)" };
|
|
4927
|
-
}
|
|
4928
|
-
var DEFAULT_SESSION_CONFIG = {
|
|
4929
|
-
mode: "smart",
|
|
4930
|
-
allow_release_others: false,
|
|
4931
|
-
auto_release_stale: false,
|
|
4932
|
-
stale_threshold_hours: 2,
|
|
4933
|
-
auto_release_immediate: false,
|
|
4934
|
-
auto_release_delay_minutes: 5
|
|
4935
|
-
};
|
|
4936
|
-
|
|
4937
5125
|
// src/mcp/schemas.ts
|
|
4938
5126
|
var sessionIdSchema = external_exports.string().min(1, "session_id is required");
|
|
4939
5127
|
var claimIdSchema = external_exports.string().min(1, "claim_id is required");
|
|
@@ -4998,7 +5186,8 @@ var claimCreateSchema = external_exports.object({
|
|
|
4998
5186
|
symbols: symbolClaimsArraySchema.optional(),
|
|
4999
5187
|
intent: external_exports.string().min(1, "intent is required"),
|
|
5000
5188
|
scope: claimScopeSchema.optional(),
|
|
5001
|
-
priority: external_exports.number().min(0).max(100).optional()
|
|
5189
|
+
priority: external_exports.number().min(0).max(100).optional(),
|
|
5190
|
+
allow_conflicts: external_exports.boolean().optional()
|
|
5002
5191
|
}).refine(
|
|
5003
5192
|
(data) => data.files && data.files.length > 0 || data.symbols && data.symbols.length > 0,
|
|
5004
5193
|
{ message: "Either files or symbols must be provided" }
|
|
@@ -5104,6 +5293,43 @@ function validationError(message) {
|
|
|
5104
5293
|
}
|
|
5105
5294
|
|
|
5106
5295
|
// src/mcp/tools/session.ts
|
|
5296
|
+
function parseJsonField(value) {
|
|
5297
|
+
if (!value) return null;
|
|
5298
|
+
try {
|
|
5299
|
+
return JSON.parse(value);
|
|
5300
|
+
} catch {
|
|
5301
|
+
return null;
|
|
5302
|
+
}
|
|
5303
|
+
}
|
|
5304
|
+
function formatIncomingCoordination(entry) {
|
|
5305
|
+
return {
|
|
5306
|
+
queue_id: entry.id,
|
|
5307
|
+
owner_claim_id: entry.claim_id,
|
|
5308
|
+
requested_by_session_id: entry.session_id,
|
|
5309
|
+
requested_by_session_name: entry.session_name,
|
|
5310
|
+
intent: entry.intent,
|
|
5311
|
+
files: entry.claim_files,
|
|
5312
|
+
priority: getPriorityLevel(entry.priority),
|
|
5313
|
+
position: entry.position,
|
|
5314
|
+
estimated_wait_minutes: entry.estimated_wait_minutes,
|
|
5315
|
+
created_at: entry.created_at
|
|
5316
|
+
};
|
|
5317
|
+
}
|
|
5318
|
+
function formatOutgoingCoordination(entry) {
|
|
5319
|
+
return {
|
|
5320
|
+
queue_id: entry.id,
|
|
5321
|
+
owner_claim_id: entry.claim_id,
|
|
5322
|
+
owner_session_id: entry.owner_session_id,
|
|
5323
|
+
owner_session_name: entry.claim_session_name,
|
|
5324
|
+
owner_intent: entry.claim_intent,
|
|
5325
|
+
intent: entry.intent,
|
|
5326
|
+
files: entry.claim_files,
|
|
5327
|
+
priority: getPriorityLevel(entry.priority),
|
|
5328
|
+
position: entry.position,
|
|
5329
|
+
estimated_wait_minutes: entry.estimated_wait_minutes,
|
|
5330
|
+
created_at: entry.created_at
|
|
5331
|
+
};
|
|
5332
|
+
}
|
|
5107
5333
|
var sessionTools = [
|
|
5108
5334
|
{
|
|
5109
5335
|
name: "collab_session_start",
|
|
@@ -5159,6 +5385,39 @@ var sessionTools = [
|
|
|
5159
5385
|
}
|
|
5160
5386
|
}
|
|
5161
5387
|
},
|
|
5388
|
+
{
|
|
5389
|
+
name: "collab_session_update",
|
|
5390
|
+
description: "Update session heartbeat, current task, todos, and progress.",
|
|
5391
|
+
inputSchema: {
|
|
5392
|
+
type: "object",
|
|
5393
|
+
properties: {
|
|
5394
|
+
session_id: {
|
|
5395
|
+
type: "string",
|
|
5396
|
+
description: "Session ID to update"
|
|
5397
|
+
},
|
|
5398
|
+
current_task: {
|
|
5399
|
+
type: "string",
|
|
5400
|
+
description: "Current work summary"
|
|
5401
|
+
},
|
|
5402
|
+
todos: {
|
|
5403
|
+
type: "array",
|
|
5404
|
+
items: {
|
|
5405
|
+
type: "object",
|
|
5406
|
+
properties: {
|
|
5407
|
+
content: { type: "string" },
|
|
5408
|
+
status: {
|
|
5409
|
+
type: "string",
|
|
5410
|
+
enum: ["pending", "in_progress", "completed"]
|
|
5411
|
+
}
|
|
5412
|
+
},
|
|
5413
|
+
required: ["content", "status"]
|
|
5414
|
+
},
|
|
5415
|
+
description: "Current todo state for progress reporting"
|
|
5416
|
+
}
|
|
5417
|
+
},
|
|
5418
|
+
required: ["session_id"]
|
|
5419
|
+
}
|
|
5420
|
+
},
|
|
5162
5421
|
{
|
|
5163
5422
|
name: "collab_config",
|
|
5164
5423
|
description: "Configure session behavior.",
|
|
@@ -5304,11 +5563,28 @@ Decisions:
|
|
|
5304
5563
|
const sessionsWithClaims = await Promise.all(
|
|
5305
5564
|
sessions.map(async (session) => {
|
|
5306
5565
|
const claims = await listClaims(db, { session_id: session.id, status: "active" });
|
|
5566
|
+
const incomingCoordination = await listQueue(db, { owner_session_id: session.id });
|
|
5567
|
+
const outgoingCoordination = await listQueue(db, { session_id: session.id });
|
|
5307
5568
|
return {
|
|
5308
5569
|
id: session.id,
|
|
5309
5570
|
name: session.name,
|
|
5310
5571
|
status: session.status,
|
|
5572
|
+
current_task: session.current_task,
|
|
5573
|
+
progress: parseJsonField(session.progress),
|
|
5574
|
+
todos: parseJsonField(session.todos),
|
|
5311
5575
|
active_claims: claims.length,
|
|
5576
|
+
claims: claims.map((c) => ({
|
|
5577
|
+
id: c.id,
|
|
5578
|
+
files: c.files,
|
|
5579
|
+
intent: c.intent,
|
|
5580
|
+
priority: getPriorityLevel(c.priority),
|
|
5581
|
+
created_at: c.created_at
|
|
5582
|
+
})),
|
|
5583
|
+
pending_coordination: {
|
|
5584
|
+
incoming: incomingCoordination.length,
|
|
5585
|
+
outgoing: outgoingCoordination.length
|
|
5586
|
+
},
|
|
5587
|
+
coordination_requests: incomingCoordination.map(formatIncomingCoordination),
|
|
5312
5588
|
last_heartbeat: session.last_heartbeat
|
|
5313
5589
|
};
|
|
5314
5590
|
})
|
|
@@ -5318,6 +5594,34 @@ Decisions:
|
|
|
5318
5594
|
total: sessionsWithClaims.length
|
|
5319
5595
|
});
|
|
5320
5596
|
}
|
|
5597
|
+
case "collab_session_update": {
|
|
5598
|
+
const validation = validateInput(statusUpdateSchema, args);
|
|
5599
|
+
if (!validation.success) {
|
|
5600
|
+
return validationError(validation.error);
|
|
5601
|
+
}
|
|
5602
|
+
const input = validation.data;
|
|
5603
|
+
const sessionResult = await validateActiveSession(db, input.session_id);
|
|
5604
|
+
if (!sessionResult.valid) {
|
|
5605
|
+
return sessionResult.error;
|
|
5606
|
+
}
|
|
5607
|
+
const updated = await updateSessionHeartbeat(db, input.session_id, {
|
|
5608
|
+
current_task: input.current_task,
|
|
5609
|
+
todos: input.todos
|
|
5610
|
+
});
|
|
5611
|
+
if (!updated) {
|
|
5612
|
+
return errorResponse(ERROR_CODES.SESSION_NOT_FOUND, "Session not found");
|
|
5613
|
+
}
|
|
5614
|
+
const session = await getSession(db, input.session_id);
|
|
5615
|
+
return successResponse({
|
|
5616
|
+
success: true,
|
|
5617
|
+
session_id: input.session_id,
|
|
5618
|
+
current_task: session?.current_task ?? null,
|
|
5619
|
+
progress: parseJsonField(session?.progress ?? null),
|
|
5620
|
+
todos: parseJsonField(session?.todos ?? null),
|
|
5621
|
+
last_heartbeat: session?.last_heartbeat ?? null,
|
|
5622
|
+
message: "Session updated."
|
|
5623
|
+
});
|
|
5624
|
+
}
|
|
5321
5625
|
case "collab_config": {
|
|
5322
5626
|
const validation = validateInput(configSchema, args);
|
|
5323
5627
|
if (!validation.success) {
|
|
@@ -5364,6 +5668,8 @@ Decisions:
|
|
|
5364
5668
|
const claims = await listClaims(db, { session_id: sessionId, status: "active" });
|
|
5365
5669
|
const memories = await getActiveMemories(db, sessionId, { priority_threshold: 70, max_items: 10 });
|
|
5366
5670
|
const allSessions = await listSessions(db, { project_root: session.project_root });
|
|
5671
|
+
const incomingCoordination = await listQueue(db, { owner_session_id: sessionId });
|
|
5672
|
+
const outgoingCoordination = await listQueue(db, { session_id: sessionId });
|
|
5367
5673
|
return successResponse({
|
|
5368
5674
|
session: {
|
|
5369
5675
|
id: session.id,
|
|
@@ -5377,6 +5683,14 @@ Decisions:
|
|
|
5377
5683
|
})),
|
|
5378
5684
|
active_memories: memories.length,
|
|
5379
5685
|
other_sessions: allSessions.filter((s) => s.id !== sessionId).length,
|
|
5686
|
+
pending_coordination: {
|
|
5687
|
+
incoming: incomingCoordination.length,
|
|
5688
|
+
outgoing: outgoingCoordination.length
|
|
5689
|
+
},
|
|
5690
|
+
coordination_requests: {
|
|
5691
|
+
incoming: incomingCoordination.map(formatIncomingCoordination),
|
|
5692
|
+
outgoing: outgoingCoordination.map(formatOutgoingCoordination)
|
|
5693
|
+
},
|
|
5380
5694
|
message: `Session active. ${claims.length} claim(s), ${memories.length} memories.`
|
|
5381
5695
|
});
|
|
5382
5696
|
}
|
|
@@ -5386,11 +5700,155 @@ Decisions:
|
|
|
5386
5700
|
}
|
|
5387
5701
|
|
|
5388
5702
|
// src/mcp/tools/claim.ts
|
|
5703
|
+
function parseSessionConfig(session) {
|
|
5704
|
+
if (!session.config) {
|
|
5705
|
+
return DEFAULT_SESSION_CONFIG;
|
|
5706
|
+
}
|
|
5707
|
+
try {
|
|
5708
|
+
return { ...DEFAULT_SESSION_CONFIG, ...JSON.parse(session.config) };
|
|
5709
|
+
} catch {
|
|
5710
|
+
return DEFAULT_SESSION_CONFIG;
|
|
5711
|
+
}
|
|
5712
|
+
}
|
|
5713
|
+
function mapConflict(conflict, ownerSession) {
|
|
5714
|
+
return {
|
|
5715
|
+
claim_id: conflict.claim_id,
|
|
5716
|
+
session_id: conflict.session_id,
|
|
5717
|
+
session_name: conflict.session_name,
|
|
5718
|
+
file: conflict.file_path,
|
|
5719
|
+
intent: conflict.intent,
|
|
5720
|
+
scope: conflict.scope,
|
|
5721
|
+
created_at: conflict.created_at,
|
|
5722
|
+
conflict_level: conflict.conflict_level,
|
|
5723
|
+
symbol_name: conflict.symbol_name ?? null,
|
|
5724
|
+
symbol_type: conflict.symbol_type ?? null,
|
|
5725
|
+
current_task: ownerSession?.current_task ?? null,
|
|
5726
|
+
last_heartbeat: ownerSession?.last_heartbeat ?? null
|
|
5727
|
+
};
|
|
5728
|
+
}
|
|
5729
|
+
async function formatConflicts(db, conflicts) {
|
|
5730
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
5731
|
+
for (const conflict of conflicts) {
|
|
5732
|
+
if (!sessions.has(conflict.session_id)) {
|
|
5733
|
+
sessions.set(conflict.session_id, await getSession(db, conflict.session_id));
|
|
5734
|
+
}
|
|
5735
|
+
}
|
|
5736
|
+
return conflicts.map((conflict) => mapConflict(conflict, sessions.get(conflict.session_id) ?? null));
|
|
5737
|
+
}
|
|
5738
|
+
function uniqueBlockedFiles(conflicts) {
|
|
5739
|
+
return Array.from(new Set(conflicts.map((conflict) => conflict.file_path)));
|
|
5740
|
+
}
|
|
5741
|
+
function filterSafeSymbols(symbols, safeFiles) {
|
|
5742
|
+
if (!symbols || symbols.length === 0) {
|
|
5743
|
+
return void 0;
|
|
5744
|
+
}
|
|
5745
|
+
const safeFileSet = new Set(safeFiles);
|
|
5746
|
+
const safeSymbols = symbols.filter((symbol) => safeFileSet.has(symbol.file));
|
|
5747
|
+
return safeSymbols.length > 0 ? safeSymbols : void 0;
|
|
5748
|
+
}
|
|
5749
|
+
function getCoordinationRecommendation(symbols, conflicts) {
|
|
5750
|
+
const hasFileLevelConflict = conflicts.some((conflict) => conflict.conflict_level === "file");
|
|
5751
|
+
if (symbols && symbols.length > 0 && hasFileLevelConflict) {
|
|
5752
|
+
return "provide_symbols_or_wait";
|
|
5753
|
+
}
|
|
5754
|
+
return "wait_for_release_or_coordinate";
|
|
5755
|
+
}
|
|
5756
|
+
async function createTrackedClaim(db, input) {
|
|
5757
|
+
const { claim } = await createClaim(db, {
|
|
5758
|
+
session_id: input.session_id,
|
|
5759
|
+
files: input.files,
|
|
5760
|
+
symbols: input.symbols,
|
|
5761
|
+
intent: input.intent,
|
|
5762
|
+
scope: input.scope,
|
|
5763
|
+
priority: input.priority
|
|
5764
|
+
});
|
|
5765
|
+
await logAuditEvent(db, {
|
|
5766
|
+
session_id: input.session_id,
|
|
5767
|
+
action: "claim_created",
|
|
5768
|
+
entity_type: "claim",
|
|
5769
|
+
entity_id: claim.id,
|
|
5770
|
+
metadata: { files: input.files, intent: input.intent }
|
|
5771
|
+
});
|
|
5772
|
+
await saveMemory(db, input.session_id, {
|
|
5773
|
+
category: "state",
|
|
5774
|
+
key: `claim_${claim.id}`,
|
|
5775
|
+
content: `Working on: ${input.intent}
|
|
5776
|
+
Files: ${input.files.join(", ")}`,
|
|
5777
|
+
priority: 60,
|
|
5778
|
+
related_claim_id: claim.id,
|
|
5779
|
+
metadata: { claim_id: claim.id, files: input.files }
|
|
5780
|
+
});
|
|
5781
|
+
return claim.id;
|
|
5782
|
+
}
|
|
5783
|
+
async function createCoordinationRequests(db, input, conflicts) {
|
|
5784
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
5785
|
+
for (const conflict of conflicts) {
|
|
5786
|
+
const existing = grouped.get(conflict.claim_id);
|
|
5787
|
+
if (existing) {
|
|
5788
|
+
existing.files.add(conflict.file_path);
|
|
5789
|
+
} else {
|
|
5790
|
+
grouped.set(conflict.claim_id, {
|
|
5791
|
+
conflict,
|
|
5792
|
+
files: /* @__PURE__ */ new Set([conflict.file_path])
|
|
5793
|
+
});
|
|
5794
|
+
}
|
|
5795
|
+
}
|
|
5796
|
+
const requests = [];
|
|
5797
|
+
for (const { conflict, files } of grouped.values()) {
|
|
5798
|
+
const fileList = Array.from(files);
|
|
5799
|
+
const queueEntry = await joinQueue(db, {
|
|
5800
|
+
claim_id: conflict.claim_id,
|
|
5801
|
+
session_id: input.session_id,
|
|
5802
|
+
intent: input.intent,
|
|
5803
|
+
priority: input.priority,
|
|
5804
|
+
scope: input.scope
|
|
5805
|
+
});
|
|
5806
|
+
await logAuditEvent(db, {
|
|
5807
|
+
session_id: input.session_id,
|
|
5808
|
+
action: "queue_joined",
|
|
5809
|
+
entity_type: "queue",
|
|
5810
|
+
entity_id: queueEntry.id,
|
|
5811
|
+
metadata: {
|
|
5812
|
+
claim_id: conflict.claim_id,
|
|
5813
|
+
files: fileList,
|
|
5814
|
+
conflicting_session_id: conflict.session_id,
|
|
5815
|
+
conflicting_session_name: conflict.session_name ?? void 0,
|
|
5816
|
+
priority: queueEntry.priority,
|
|
5817
|
+
scope: queueEntry.scope
|
|
5818
|
+
}
|
|
5819
|
+
});
|
|
5820
|
+
await createNotification(db, {
|
|
5821
|
+
session_id: conflict.session_id,
|
|
5822
|
+
type: "conflict_detected",
|
|
5823
|
+
title: "Coordination requested",
|
|
5824
|
+
message: `Another session is waiting to coordinate work on ${fileList.join(", ")}.`,
|
|
5825
|
+
reference_type: "claim",
|
|
5826
|
+
reference_id: conflict.claim_id,
|
|
5827
|
+
metadata: {
|
|
5828
|
+
claim_id: conflict.claim_id,
|
|
5829
|
+
files: fileList,
|
|
5830
|
+
conflicting_session_id: input.session_id
|
|
5831
|
+
}
|
|
5832
|
+
});
|
|
5833
|
+
requests.push({
|
|
5834
|
+
queue_id: queueEntry.id,
|
|
5835
|
+
owner_claim_id: conflict.claim_id,
|
|
5836
|
+
owner_session_id: conflict.session_id,
|
|
5837
|
+
owner_session_name: conflict.session_name,
|
|
5838
|
+
requested_by_session_id: input.session_id,
|
|
5839
|
+
requested_intent: input.intent,
|
|
5840
|
+
files: fileList,
|
|
5841
|
+
position: queueEntry.position,
|
|
5842
|
+
estimated_wait_minutes: queueEntry.estimated_wait_minutes
|
|
5843
|
+
});
|
|
5844
|
+
}
|
|
5845
|
+
return requests;
|
|
5846
|
+
}
|
|
5389
5847
|
var claimTools = [
|
|
5390
5848
|
{
|
|
5391
5849
|
name: "collab_claim",
|
|
5392
5850
|
description: `Unified tool for file/symbol claims. Use action parameter to:
|
|
5393
|
-
- "create": Declare files you're about to modify
|
|
5851
|
+
- "create": Declare files you're about to modify; smart mode queues blocked files for coordination
|
|
5394
5852
|
- "check": Check if files are being worked on (ALWAYS call before editing)
|
|
5395
5853
|
- "release": Release a claim when done
|
|
5396
5854
|
- "list": List all active claims`,
|
|
@@ -5457,6 +5915,10 @@ var claimTools = [
|
|
|
5457
5915
|
type: "boolean",
|
|
5458
5916
|
description: "Force release even if not owner (for release action)"
|
|
5459
5917
|
},
|
|
5918
|
+
allow_conflicts: {
|
|
5919
|
+
type: "boolean",
|
|
5920
|
+
description: "Explicitly create a claim even when conflicts are detected (for create action)"
|
|
5921
|
+
},
|
|
5460
5922
|
summary: {
|
|
5461
5923
|
type: "string",
|
|
5462
5924
|
description: "Release summary (for release action)"
|
|
@@ -5490,53 +5952,111 @@ async function handleClaimTool(db, name, args) {
|
|
|
5490
5952
|
if (!sessionResult.valid) {
|
|
5491
5953
|
return sessionResult.error;
|
|
5492
5954
|
}
|
|
5955
|
+
const config = parseSessionConfig(sessionResult.session);
|
|
5493
5956
|
const symbolFiles = (input.symbols ?? []).map((symbol) => symbol.file);
|
|
5494
5957
|
const files = Array.from(/* @__PURE__ */ new Set([...input.files ?? [], ...symbolFiles]));
|
|
5495
|
-
const { claim } = await createClaim(db, {
|
|
5496
|
-
session_id: input.session_id,
|
|
5497
|
-
files,
|
|
5498
|
-
symbols: input.symbols,
|
|
5499
|
-
intent: input.intent,
|
|
5500
|
-
scope: input.scope,
|
|
5501
|
-
priority: input.priority
|
|
5502
|
-
});
|
|
5503
|
-
await logAuditEvent(db, {
|
|
5504
|
-
session_id: input.session_id,
|
|
5505
|
-
action: "claim_created",
|
|
5506
|
-
entity_type: "claim",
|
|
5507
|
-
entity_id: claim.id,
|
|
5508
|
-
metadata: { files, intent: input.intent }
|
|
5509
|
-
});
|
|
5510
|
-
await saveMemory(db, input.session_id, {
|
|
5511
|
-
category: "state",
|
|
5512
|
-
key: `claim_${claim.id}`,
|
|
5513
|
-
content: `Working on: ${input.intent}
|
|
5514
|
-
Files: ${files.join(", ")}`,
|
|
5515
|
-
priority: 60,
|
|
5516
|
-
related_claim_id: claim.id,
|
|
5517
|
-
metadata: { claim_id: claim.id, files }
|
|
5518
|
-
});
|
|
5519
5958
|
const conflicts = await checkConflicts(db, files, input.session_id, input.symbols);
|
|
5959
|
+
const formattedConflicts = await formatConflicts(db, conflicts);
|
|
5960
|
+
const blockedFiles = uniqueBlockedFiles(conflicts);
|
|
5961
|
+
const safeFiles = files.filter((file) => !blockedFiles.includes(file));
|
|
5962
|
+
const safeSymbols = filterSafeSymbols(input.symbols, safeFiles);
|
|
5963
|
+
if (conflicts.length > 0 && config.mode === "strict") {
|
|
5964
|
+
return successResponse({
|
|
5965
|
+
success: false,
|
|
5966
|
+
status: "blocked_by_conflicts",
|
|
5967
|
+
files,
|
|
5968
|
+
symbols: input.symbols ?? [],
|
|
5969
|
+
claimed_files: [],
|
|
5970
|
+
safe_files: safeFiles,
|
|
5971
|
+
blocked_files: blockedFiles,
|
|
5972
|
+
conflicts: formattedConflicts,
|
|
5973
|
+
recommendation: "coordinate_before_editing",
|
|
5974
|
+
message: `Claim not created. ${conflicts.length} conflict(s) detected. Coordinate before proceeding.`
|
|
5975
|
+
});
|
|
5976
|
+
}
|
|
5520
5977
|
if (conflicts.length > 0) {
|
|
5978
|
+
if (!input.allow_conflicts) {
|
|
5979
|
+
const coordinationRequests = await createCoordinationRequests(db, {
|
|
5980
|
+
session_id: input.session_id,
|
|
5981
|
+
intent: input.intent,
|
|
5982
|
+
scope: input.scope,
|
|
5983
|
+
priority: input.priority
|
|
5984
|
+
}, conflicts);
|
|
5985
|
+
if (config.mode === "smart" && safeFiles.length > 0) {
|
|
5986
|
+
const claimId3 = await createTrackedClaim(db, {
|
|
5987
|
+
session_id: input.session_id,
|
|
5988
|
+
files: safeFiles,
|
|
5989
|
+
symbols: safeSymbols,
|
|
5990
|
+
intent: input.intent,
|
|
5991
|
+
scope: input.scope,
|
|
5992
|
+
priority: input.priority
|
|
5993
|
+
});
|
|
5994
|
+
return successResponse({
|
|
5995
|
+
success: true,
|
|
5996
|
+
claim_id: claimId3,
|
|
5997
|
+
status: "partial_claim_created",
|
|
5998
|
+
files,
|
|
5999
|
+
claimed_files: safeFiles,
|
|
6000
|
+
safe_files: safeFiles,
|
|
6001
|
+
blocked_files: blockedFiles,
|
|
6002
|
+
symbols: safeSymbols ?? [],
|
|
6003
|
+
conflicts: formattedConflicts,
|
|
6004
|
+
coordination_requests: coordinationRequests,
|
|
6005
|
+
recommendation: getCoordinationRecommendation(input.symbols, conflicts),
|
|
6006
|
+
message: `Claim created for safe files only. Coordinate before editing blocked files: [${blockedFiles.join(", ")}].`
|
|
6007
|
+
});
|
|
6008
|
+
}
|
|
6009
|
+
return successResponse({
|
|
6010
|
+
success: false,
|
|
6011
|
+
status: "waiting_for_coordination",
|
|
6012
|
+
files,
|
|
6013
|
+
claimed_files: [],
|
|
6014
|
+
safe_files: [],
|
|
6015
|
+
blocked_files: blockedFiles,
|
|
6016
|
+
symbols: input.symbols ?? [],
|
|
6017
|
+
conflicts: formattedConflicts,
|
|
6018
|
+
coordination_requests: coordinationRequests,
|
|
6019
|
+
recommendation: getCoordinationRecommendation(input.symbols, conflicts),
|
|
6020
|
+
message: `Claim not created. Waiting for coordination on blocked files: [${blockedFiles.join(", ")}].`
|
|
6021
|
+
});
|
|
6022
|
+
}
|
|
6023
|
+
const claimId2 = await createTrackedClaim(db, {
|
|
6024
|
+
session_id: input.session_id,
|
|
6025
|
+
files,
|
|
6026
|
+
symbols: input.symbols,
|
|
6027
|
+
intent: input.intent,
|
|
6028
|
+
scope: input.scope,
|
|
6029
|
+
priority: input.priority
|
|
6030
|
+
});
|
|
5521
6031
|
return successResponse({
|
|
5522
6032
|
success: true,
|
|
5523
|
-
claim_id:
|
|
6033
|
+
claim_id: claimId2,
|
|
5524
6034
|
status: "created_with_conflicts",
|
|
5525
6035
|
files,
|
|
6036
|
+
claimed_files: files,
|
|
6037
|
+
safe_files: safeFiles,
|
|
6038
|
+
blocked_files: blockedFiles,
|
|
5526
6039
|
symbols: input.symbols ?? [],
|
|
5527
|
-
conflicts:
|
|
5528
|
-
session_name: c.session_name,
|
|
5529
|
-
file: c.file_path,
|
|
5530
|
-
intent: c.intent
|
|
5531
|
-
})),
|
|
6040
|
+
conflicts: formattedConflicts,
|
|
5532
6041
|
warning: `\u26A0\uFE0F ${conflicts.length} conflict(s) detected. Coordinate before proceeding.`
|
|
5533
6042
|
});
|
|
5534
6043
|
}
|
|
6044
|
+
const claimId = await createTrackedClaim(db, {
|
|
6045
|
+
session_id: input.session_id,
|
|
6046
|
+
files,
|
|
6047
|
+
symbols: input.symbols,
|
|
6048
|
+
intent: input.intent,
|
|
6049
|
+
scope: input.scope,
|
|
6050
|
+
priority: input.priority
|
|
6051
|
+
});
|
|
5535
6052
|
return successResponse({
|
|
5536
6053
|
success: true,
|
|
5537
|
-
claim_id:
|
|
6054
|
+
claim_id: claimId,
|
|
5538
6055
|
status: "created",
|
|
5539
6056
|
files,
|
|
6057
|
+
claimed_files: files,
|
|
6058
|
+
safe_files: files,
|
|
6059
|
+
blocked_files: [],
|
|
5540
6060
|
symbols: input.symbols ?? [],
|
|
5541
6061
|
intent: input.intent,
|
|
5542
6062
|
message: "Claim created successfully."
|
|
@@ -5554,6 +6074,7 @@ Files: ${files.join(", ")}`,
|
|
|
5554
6074
|
const excludeSelf = input.exclude_self ?? true;
|
|
5555
6075
|
const excludeSessionId = excludeSelf ? input.session_id : void 0;
|
|
5556
6076
|
const conflicts = await checkConflicts(db, input.files, excludeSessionId, input.symbols);
|
|
6077
|
+
const formattedConflicts = await formatConflicts(db, conflicts);
|
|
5557
6078
|
if (conflicts.length === 0) {
|
|
5558
6079
|
return successResponse({
|
|
5559
6080
|
safe: true,
|
|
@@ -5574,11 +6095,7 @@ Files: ${files.join(", ")}`,
|
|
|
5574
6095
|
recommendation: safeFiles.length > 0 ? "proceed_safe_only" : "abort",
|
|
5575
6096
|
safe_files: safeFiles,
|
|
5576
6097
|
blocked_files: Array.from(blockedFiles),
|
|
5577
|
-
conflicts:
|
|
5578
|
-
session_name: c.session_name,
|
|
5579
|
-
file: c.file_path,
|
|
5580
|
-
intent: c.intent
|
|
5581
|
-
})),
|
|
6098
|
+
conflicts: formattedConflicts,
|
|
5582
6099
|
message: safeFiles.length > 0 ? `Edit ONLY safe files: [${safeFiles.join(", ")}]. Skip blocked files.` : "All files blocked. Coordinate with other session(s)."
|
|
5583
6100
|
});
|
|
5584
6101
|
}
|
|
@@ -5613,6 +6130,7 @@ Files: ${files.join(", ")}`,
|
|
|
5613
6130
|
if (claim.status !== "active") {
|
|
5614
6131
|
return errorResponse(ERROR_CODES.CLAIM_ALREADY_RELEASED, `Claim already ${claim.status}`);
|
|
5615
6132
|
}
|
|
6133
|
+
const notifications_sent = await notifyQueueOnClaimRelease(db, input.claim_id, input.session_id, claim.files);
|
|
5616
6134
|
await releaseClaim(db, input.claim_id, {
|
|
5617
6135
|
status,
|
|
5618
6136
|
summary: input.summary
|
|
@@ -5629,6 +6147,7 @@ Files: ${files.join(", ")}`,
|
|
|
5629
6147
|
success: true,
|
|
5630
6148
|
claim_id: input.claim_id,
|
|
5631
6149
|
files: claim.files,
|
|
6150
|
+
notifications_sent,
|
|
5632
6151
|
message: `Claim ${status} released. Files now available.`
|
|
5633
6152
|
});
|
|
5634
6153
|
}
|
|
@@ -6101,4 +6620,4 @@ export {
|
|
|
6101
6620
|
getMcpTools,
|
|
6102
6621
|
handleMcpRequest
|
|
6103
6622
|
};
|
|
6104
|
-
//# sourceMappingURL=chunk-
|
|
6623
|
+
//# sourceMappingURL=chunk-UDJMG3TR.js.map
|