session-collab-mcp 0.8.8 → 1.0.0
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/cli.js +478 -3820
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4203,13 +4203,6 @@ var DEFAULT_SESSION_CONFIG = {
|
|
|
4203
4203
|
auto_release_immediate: false,
|
|
4204
4204
|
auto_release_delay_minutes: 5
|
|
4205
4205
|
};
|
|
4206
|
-
var SCOPE_WAIT_MINUTES = {
|
|
4207
|
-
small: 30,
|
|
4208
|
-
medium: 120,
|
|
4209
|
-
// 2 hours
|
|
4210
|
-
large: 480
|
|
4211
|
-
// 8 hours
|
|
4212
|
-
};
|
|
4213
4206
|
|
|
4214
4207
|
// src/db/queries.ts
|
|
4215
4208
|
async function createSession(db, params) {
|
|
@@ -4256,66 +4249,6 @@ async function listSessions(db, params = {}) {
|
|
|
4256
4249
|
const result = await db.prepare(query).bind(...bindings).all();
|
|
4257
4250
|
return result.results;
|
|
4258
4251
|
}
|
|
4259
|
-
async function updateSessionHeartbeat(db, id, statusUpdate) {
|
|
4260
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4261
|
-
let progress = null;
|
|
4262
|
-
let todosJson = null;
|
|
4263
|
-
if (statusUpdate?.todos) {
|
|
4264
|
-
const total = statusUpdate.todos.length;
|
|
4265
|
-
const completed = statusUpdate.todos.filter((t) => t.status === "completed").length;
|
|
4266
|
-
progress = {
|
|
4267
|
-
completed,
|
|
4268
|
-
total,
|
|
4269
|
-
percentage: total > 0 ? Math.round(completed / total * 100) : 0
|
|
4270
|
-
};
|
|
4271
|
-
todosJson = JSON.stringify(statusUpdate.todos);
|
|
4272
|
-
}
|
|
4273
|
-
let query = "UPDATE sessions SET last_heartbeat = ?";
|
|
4274
|
-
const bindings = [now];
|
|
4275
|
-
if (statusUpdate?.current_task !== void 0) {
|
|
4276
|
-
query += ", current_task = ?";
|
|
4277
|
-
bindings.push(statusUpdate.current_task);
|
|
4278
|
-
}
|
|
4279
|
-
if (progress) {
|
|
4280
|
-
query += ", progress = ?";
|
|
4281
|
-
bindings.push(JSON.stringify(progress));
|
|
4282
|
-
}
|
|
4283
|
-
if (todosJson) {
|
|
4284
|
-
query += ", todos = ?";
|
|
4285
|
-
bindings.push(todosJson);
|
|
4286
|
-
}
|
|
4287
|
-
query += " WHERE id = ? AND status = 'active'";
|
|
4288
|
-
bindings.push(id);
|
|
4289
|
-
const result = await db.prepare(query).bind(...bindings).run();
|
|
4290
|
-
return result.meta.changes > 0;
|
|
4291
|
-
}
|
|
4292
|
-
async function updateSessionStatus(db, id, params) {
|
|
4293
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4294
|
-
let progress = null;
|
|
4295
|
-
let todosJson = null;
|
|
4296
|
-
if (params.todos) {
|
|
4297
|
-
const total = params.todos.length;
|
|
4298
|
-
const completed = params.todos.filter((t) => t.status === "completed").length;
|
|
4299
|
-
progress = {
|
|
4300
|
-
completed,
|
|
4301
|
-
total,
|
|
4302
|
-
percentage: total > 0 ? Math.round(completed / total * 100) : 0
|
|
4303
|
-
};
|
|
4304
|
-
todosJson = JSON.stringify(params.todos);
|
|
4305
|
-
}
|
|
4306
|
-
const result = await db.prepare(
|
|
4307
|
-
`UPDATE sessions
|
|
4308
|
-
SET current_task = ?, progress = ?, todos = ?, last_heartbeat = ?
|
|
4309
|
-
WHERE id = ? AND status = 'active'`
|
|
4310
|
-
).bind(
|
|
4311
|
-
params.current_task ?? null,
|
|
4312
|
-
progress ? JSON.stringify(progress) : null,
|
|
4313
|
-
todosJson,
|
|
4314
|
-
now,
|
|
4315
|
-
id
|
|
4316
|
-
).run();
|
|
4317
|
-
return result.meta.changes > 0;
|
|
4318
|
-
}
|
|
4319
4252
|
async function endSession(db, id, release_claims = "abandon") {
|
|
4320
4253
|
const claimStatus = release_claims === "complete" ? "completed" : "abandoned";
|
|
4321
4254
|
await db.prepare("UPDATE claims SET status = ?, updated_at = ? WHERE session_id = ? AND status = 'active'").bind(claimStatus, (/* @__PURE__ */ new Date()).toISOString(), id).run();
|
|
@@ -4423,11 +4356,6 @@ async function createClaim(db, params) {
|
|
|
4423
4356
|
symbols: params.symbols
|
|
4424
4357
|
};
|
|
4425
4358
|
}
|
|
4426
|
-
async function updateClaimPriority(db, claimId, priority) {
|
|
4427
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4428
|
-
const result = await db.prepare("UPDATE claims SET priority = ?, updated_at = ? WHERE id = ? AND status = ?").bind(priority, now, claimId, "active").run();
|
|
4429
|
-
return result.meta.changes > 0;
|
|
4430
|
-
}
|
|
4431
4359
|
async function getClaim(db, id) {
|
|
4432
4360
|
const result = await db.prepare(
|
|
4433
4361
|
`SELECT
|
|
@@ -4617,209 +4545,6 @@ async function releaseClaim(db, id, params) {
|
|
|
4617
4545
|
const result = await db.prepare("UPDATE claims SET status = ?, updated_at = ?, completed_summary = ? WHERE id = ?").bind(params.status, now, params.summary ?? null, id).run();
|
|
4618
4546
|
return result.meta.changes > 0;
|
|
4619
4547
|
}
|
|
4620
|
-
async function getClaimInfoByFile(db, sessionId, filePath) {
|
|
4621
|
-
const claim = await db.prepare(
|
|
4622
|
-
`SELECT c.id, c.scope
|
|
4623
|
-
FROM claims c
|
|
4624
|
-
JOIN claim_files cf ON c.id = cf.claim_id
|
|
4625
|
-
WHERE c.session_id = ? AND c.status = 'active' AND cf.file_path = ?`
|
|
4626
|
-
).bind(sessionId, filePath).first();
|
|
4627
|
-
if (!claim) {
|
|
4628
|
-
return null;
|
|
4629
|
-
}
|
|
4630
|
-
const filesResult = await db.prepare("SELECT file_path FROM claim_files WHERE claim_id = ?").bind(claim.id).all();
|
|
4631
|
-
const files = (filesResult.results ?? []).map((f) => f.file_path);
|
|
4632
|
-
return {
|
|
4633
|
-
claim_id: claim.id,
|
|
4634
|
-
scope: claim.scope,
|
|
4635
|
-
file_count: files.length,
|
|
4636
|
-
files
|
|
4637
|
-
};
|
|
4638
|
-
}
|
|
4639
|
-
async function releaseClaimByFile(db, sessionId, filePath) {
|
|
4640
|
-
const claim = await db.prepare(
|
|
4641
|
-
`SELECT c.id, c.scope
|
|
4642
|
-
FROM claims c
|
|
4643
|
-
JOIN claim_files cf ON c.id = cf.claim_id
|
|
4644
|
-
WHERE c.session_id = ? AND c.status = 'active' AND cf.file_path = ?`
|
|
4645
|
-
).bind(sessionId, filePath).first();
|
|
4646
|
-
if (!claim) {
|
|
4647
|
-
return { released: false };
|
|
4648
|
-
}
|
|
4649
|
-
const fileCount = await db.prepare("SELECT COUNT(*) as count FROM claim_files WHERE claim_id = ?").bind(claim.id).first();
|
|
4650
|
-
const count = fileCount?.count ?? 0;
|
|
4651
|
-
if (count <= 1) {
|
|
4652
|
-
await releaseClaim(db, claim.id, { status: "completed" });
|
|
4653
|
-
return { released: true, claim_id: claim.id, scope: claim.scope, partial: false };
|
|
4654
|
-
} else {
|
|
4655
|
-
await db.prepare("DELETE FROM claim_files WHERE claim_id = ? AND file_path = ?").bind(claim.id, filePath).run();
|
|
4656
|
-
await db.prepare("DELETE FROM claim_symbols WHERE claim_id = ? AND file_path = ?").bind(claim.id, filePath).run();
|
|
4657
|
-
await db.prepare("UPDATE claims SET updated_at = ? WHERE id = ?").bind((/* @__PURE__ */ new Date()).toISOString(), claim.id).run();
|
|
4658
|
-
return {
|
|
4659
|
-
released: true,
|
|
4660
|
-
claim_id: claim.id,
|
|
4661
|
-
scope: claim.scope,
|
|
4662
|
-
partial: true,
|
|
4663
|
-
files_remaining: count - 1
|
|
4664
|
-
};
|
|
4665
|
-
}
|
|
4666
|
-
}
|
|
4667
|
-
async function sendMessage(db, params) {
|
|
4668
|
-
const id = generateId();
|
|
4669
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4670
|
-
await db.prepare(
|
|
4671
|
-
`INSERT INTO messages (id, from_session_id, to_session_id, content, created_at)
|
|
4672
|
-
VALUES (?, ?, ?, ?, ?)`
|
|
4673
|
-
).bind(id, params.from_session_id, params.to_session_id ?? null, params.content, now).run();
|
|
4674
|
-
return {
|
|
4675
|
-
id,
|
|
4676
|
-
from_session_id: params.from_session_id,
|
|
4677
|
-
to_session_id: params.to_session_id ?? null,
|
|
4678
|
-
content: params.content,
|
|
4679
|
-
read_at: null,
|
|
4680
|
-
created_at: now
|
|
4681
|
-
};
|
|
4682
|
-
}
|
|
4683
|
-
async function listMessages(db, params) {
|
|
4684
|
-
let query = `
|
|
4685
|
-
SELECT * FROM messages
|
|
4686
|
-
WHERE (to_session_id = ? OR to_session_id IS NULL)
|
|
4687
|
-
`;
|
|
4688
|
-
const bindings = [params.session_id];
|
|
4689
|
-
if (params.unread_only) {
|
|
4690
|
-
query += " AND read_at IS NULL";
|
|
4691
|
-
}
|
|
4692
|
-
query += " ORDER BY created_at DESC";
|
|
4693
|
-
const messages = await db.prepare(query).bind(...bindings).all();
|
|
4694
|
-
if (params.mark_as_read && messages.results.length > 0) {
|
|
4695
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4696
|
-
const ids = messages.results.map((m) => m.id);
|
|
4697
|
-
const placeholders = ids.map(() => "?").join(",");
|
|
4698
|
-
await db.prepare(`UPDATE messages SET read_at = ? WHERE id IN (${placeholders}) AND read_at IS NULL`).bind(now, ...ids).run();
|
|
4699
|
-
}
|
|
4700
|
-
return messages.results;
|
|
4701
|
-
}
|
|
4702
|
-
async function addDecision(db, params) {
|
|
4703
|
-
const id = generateId();
|
|
4704
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4705
|
-
await db.prepare(
|
|
4706
|
-
`INSERT INTO decisions (id, session_id, category, title, description, created_at)
|
|
4707
|
-
VALUES (?, ?, ?, ?, ?, ?)`
|
|
4708
|
-
).bind(id, params.session_id, params.category ?? null, params.title, params.description, now).run();
|
|
4709
|
-
return {
|
|
4710
|
-
id,
|
|
4711
|
-
session_id: params.session_id,
|
|
4712
|
-
category: params.category ?? null,
|
|
4713
|
-
title: params.title,
|
|
4714
|
-
description: params.description,
|
|
4715
|
-
created_at: now
|
|
4716
|
-
};
|
|
4717
|
-
}
|
|
4718
|
-
async function listDecisions(db, params = {}) {
|
|
4719
|
-
let query = "SELECT * FROM decisions WHERE 1=1";
|
|
4720
|
-
const bindings = [];
|
|
4721
|
-
if (params.category) {
|
|
4722
|
-
query += " AND category = ?";
|
|
4723
|
-
bindings.push(params.category);
|
|
4724
|
-
}
|
|
4725
|
-
query += " ORDER BY created_at DESC LIMIT ?";
|
|
4726
|
-
bindings.push(params.limit ?? 20);
|
|
4727
|
-
const result = await db.prepare(query).bind(...bindings).all();
|
|
4728
|
-
return result.results;
|
|
4729
|
-
}
|
|
4730
|
-
async function storeReferences(db, sessionId, references) {
|
|
4731
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4732
|
-
const statements = [];
|
|
4733
|
-
for (const ref of references) {
|
|
4734
|
-
for (const r of ref.references) {
|
|
4735
|
-
statements.push(
|
|
4736
|
-
db.prepare(
|
|
4737
|
-
`INSERT OR IGNORE INTO symbol_references
|
|
4738
|
-
(source_file, source_symbol, ref_file, ref_line, ref_context, session_id, created_at)
|
|
4739
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
4740
|
-
).bind(ref.source_file, ref.source_symbol, r.file, r.line, r.context ?? null, sessionId, now)
|
|
4741
|
-
);
|
|
4742
|
-
}
|
|
4743
|
-
}
|
|
4744
|
-
if (statements.length === 0) {
|
|
4745
|
-
return { stored: 0, skipped: 0 };
|
|
4746
|
-
}
|
|
4747
|
-
try {
|
|
4748
|
-
const results = await db.batch(statements);
|
|
4749
|
-
const stored = results.reduce((acc, r) => acc + r.meta.changes, 0);
|
|
4750
|
-
return { stored, skipped: statements.length - stored };
|
|
4751
|
-
} catch (err) {
|
|
4752
|
-
console.error("[storeReferences] Batch insert failed:", err);
|
|
4753
|
-
return { stored: 0, skipped: statements.length };
|
|
4754
|
-
}
|
|
4755
|
-
}
|
|
4756
|
-
async function getReferencesForSymbol(db, sourceFile, sourceSymbol) {
|
|
4757
|
-
const result = await db.prepare(
|
|
4758
|
-
`SELECT * FROM symbol_references
|
|
4759
|
-
WHERE source_file = ? AND source_symbol = ?
|
|
4760
|
-
ORDER BY ref_file, ref_line`
|
|
4761
|
-
).bind(sourceFile, sourceSymbol).all();
|
|
4762
|
-
return result.results;
|
|
4763
|
-
}
|
|
4764
|
-
async function analyzeClaimImpact(db, sourceFile, sourceSymbol, excludeSessionId) {
|
|
4765
|
-
const refs = await getReferencesForSymbol(db, sourceFile, sourceSymbol);
|
|
4766
|
-
const affectedFiles = [...new Set(refs.map((r) => r.ref_file))];
|
|
4767
|
-
const affectedClaims = [];
|
|
4768
|
-
if (affectedFiles.length > 0) {
|
|
4769
|
-
const placeholders = affectedFiles.map(() => "?").join(",");
|
|
4770
|
-
let query = `
|
|
4771
|
-
SELECT DISTINCT
|
|
4772
|
-
c.id as claim_id,
|
|
4773
|
-
s.name as session_name,
|
|
4774
|
-
c.intent,
|
|
4775
|
-
cf.file_path
|
|
4776
|
-
FROM claim_files cf
|
|
4777
|
-
JOIN claims c ON cf.claim_id = c.id
|
|
4778
|
-
JOIN sessions s ON c.session_id = s.id
|
|
4779
|
-
WHERE c.status = 'active'
|
|
4780
|
-
AND s.status = 'active'
|
|
4781
|
-
AND cf.file_path IN (${placeholders})
|
|
4782
|
-
`;
|
|
4783
|
-
const bindings = [...affectedFiles];
|
|
4784
|
-
if (excludeSessionId) {
|
|
4785
|
-
query += " AND c.session_id != ?";
|
|
4786
|
-
bindings.push(excludeSessionId);
|
|
4787
|
-
}
|
|
4788
|
-
const claimResults = await db.prepare(query).bind(...bindings).all();
|
|
4789
|
-
const claimMap = /* @__PURE__ */ new Map();
|
|
4790
|
-
for (const r of claimResults.results) {
|
|
4791
|
-
const existing = claimMap.get(r.claim_id);
|
|
4792
|
-
if (existing) {
|
|
4793
|
-
existing.files.push(r.file_path);
|
|
4794
|
-
} else {
|
|
4795
|
-
claimMap.set(r.claim_id, {
|
|
4796
|
-
session_name: r.session_name,
|
|
4797
|
-
intent: r.intent,
|
|
4798
|
-
files: [r.file_path]
|
|
4799
|
-
});
|
|
4800
|
-
}
|
|
4801
|
-
}
|
|
4802
|
-
for (const [claimId, data] of claimMap) {
|
|
4803
|
-
affectedClaims.push({
|
|
4804
|
-
claim_id: claimId,
|
|
4805
|
-
session_name: data.session_name,
|
|
4806
|
-
intent: data.intent,
|
|
4807
|
-
affected_symbols: data.files
|
|
4808
|
-
});
|
|
4809
|
-
}
|
|
4810
|
-
}
|
|
4811
|
-
return {
|
|
4812
|
-
symbol: sourceSymbol,
|
|
4813
|
-
file: sourceFile,
|
|
4814
|
-
affected_claims: affectedClaims,
|
|
4815
|
-
reference_count: refs.length,
|
|
4816
|
-
affected_files: affectedFiles
|
|
4817
|
-
};
|
|
4818
|
-
}
|
|
4819
|
-
async function clearSessionReferences(db, sessionId) {
|
|
4820
|
-
const result = await db.prepare("DELETE FROM symbol_references WHERE session_id = ?").bind(sessionId).run();
|
|
4821
|
-
return result.meta.changes;
|
|
4822
|
-
}
|
|
4823
4548
|
async function logAuditEvent(db, params) {
|
|
4824
4549
|
const id = generateId();
|
|
4825
4550
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -4838,226 +4563,10 @@ async function logAuditEvent(db, params) {
|
|
|
4838
4563
|
created_at: now
|
|
4839
4564
|
};
|
|
4840
4565
|
}
|
|
4841
|
-
async function listAuditHistory(db, params = {}) {
|
|
4842
|
-
let query = `
|
|
4843
|
-
SELECT h.*, s.name as session_name
|
|
4844
|
-
FROM audit_history h
|
|
4845
|
-
LEFT JOIN sessions s ON h.session_id = s.id
|
|
4846
|
-
WHERE 1=1
|
|
4847
|
-
`;
|
|
4848
|
-
const bindings = [];
|
|
4849
|
-
if (params.session_id) {
|
|
4850
|
-
query += " AND h.session_id = ?";
|
|
4851
|
-
bindings.push(params.session_id);
|
|
4852
|
-
}
|
|
4853
|
-
if (params.action) {
|
|
4854
|
-
query += " AND h.action = ?";
|
|
4855
|
-
bindings.push(params.action);
|
|
4856
|
-
}
|
|
4857
|
-
if (params.entity_type) {
|
|
4858
|
-
query += " AND h.entity_type = ?";
|
|
4859
|
-
bindings.push(params.entity_type);
|
|
4860
|
-
}
|
|
4861
|
-
if (params.entity_id) {
|
|
4862
|
-
query += " AND h.entity_id = ?";
|
|
4863
|
-
bindings.push(params.entity_id);
|
|
4864
|
-
}
|
|
4865
|
-
if (params.from_date) {
|
|
4866
|
-
query += " AND h.created_at >= ?";
|
|
4867
|
-
bindings.push(params.from_date);
|
|
4868
|
-
}
|
|
4869
|
-
if (params.to_date) {
|
|
4870
|
-
query += " AND h.created_at <= ?";
|
|
4871
|
-
bindings.push(params.to_date);
|
|
4872
|
-
}
|
|
4873
|
-
query += " ORDER BY h.created_at DESC LIMIT ?";
|
|
4874
|
-
bindings.push(params.limit ?? 50);
|
|
4875
|
-
const result = await db.prepare(query).bind(...bindings).all();
|
|
4876
|
-
return result.results;
|
|
4877
|
-
}
|
|
4878
|
-
async function cleanupOldAuditHistory(db, retentionDays = 7) {
|
|
4879
|
-
const cutoff = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1e3).toISOString();
|
|
4880
|
-
const result = await db.prepare("DELETE FROM audit_history WHERE created_at < ?").bind(cutoff).run();
|
|
4881
|
-
return result.meta.changes;
|
|
4882
|
-
}
|
|
4883
|
-
async function getNextQueuePosition(db, claimId) {
|
|
4884
|
-
const result = await db.prepare("SELECT MAX(position) as max_pos FROM claim_queue WHERE claim_id = ?").bind(claimId).first();
|
|
4885
|
-
return (result?.max_pos ?? 0) + 1;
|
|
4886
|
-
}
|
|
4887
|
-
async function calculateEstimatedWait(db, claimId, position) {
|
|
4888
|
-
const result = await db.prepare(
|
|
4889
|
-
`SELECT scope FROM claim_queue
|
|
4890
|
-
WHERE claim_id = ? AND position < ?
|
|
4891
|
-
ORDER BY priority DESC, position ASC`
|
|
4892
|
-
).bind(claimId, position).all();
|
|
4893
|
-
let totalMinutes = 0;
|
|
4894
|
-
for (const entry of result.results) {
|
|
4895
|
-
totalMinutes += SCOPE_WAIT_MINUTES[entry.scope] ?? SCOPE_WAIT_MINUTES.medium;
|
|
4896
|
-
}
|
|
4897
|
-
const claim = await getClaim(db, claimId);
|
|
4898
|
-
if (claim) {
|
|
4899
|
-
totalMinutes += Math.round(SCOPE_WAIT_MINUTES[claim.scope] / 2);
|
|
4900
|
-
}
|
|
4901
|
-
return totalMinutes;
|
|
4902
|
-
}
|
|
4903
|
-
async function joinQueue(db, params) {
|
|
4904
|
-
const id = generateId();
|
|
4905
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4906
|
-
const priority = params.priority ?? 50;
|
|
4907
|
-
const scope = params.scope ?? "medium";
|
|
4908
|
-
const position = await getNextQueuePosition(db, params.claim_id);
|
|
4909
|
-
const estimatedWait = await calculateEstimatedWait(db, params.claim_id, position);
|
|
4910
|
-
await db.prepare(
|
|
4911
|
-
`INSERT INTO claim_queue (id, claim_id, session_id, intent, position, priority, scope, estimated_wait_minutes, created_at)
|
|
4912
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
4913
|
-
).bind(id, params.claim_id, params.session_id, params.intent, position, priority, scope, estimatedWait, now).run();
|
|
4914
|
-
return {
|
|
4915
|
-
id,
|
|
4916
|
-
claim_id: params.claim_id,
|
|
4917
|
-
session_id: params.session_id,
|
|
4918
|
-
intent: params.intent,
|
|
4919
|
-
position,
|
|
4920
|
-
priority,
|
|
4921
|
-
scope,
|
|
4922
|
-
estimated_wait_minutes: estimatedWait,
|
|
4923
|
-
created_at: now
|
|
4924
|
-
};
|
|
4925
|
-
}
|
|
4926
|
-
async function leaveQueue(db, queueId) {
|
|
4927
|
-
const result = await db.prepare("DELETE FROM claim_queue WHERE id = ?").bind(queueId).run();
|
|
4928
|
-
return result.meta.changes > 0;
|
|
4929
|
-
}
|
|
4930
|
-
async function getQueueEntry(db, queueId) {
|
|
4931
|
-
const result = await db.prepare("SELECT * FROM claim_queue WHERE id = ?").bind(queueId).first();
|
|
4932
|
-
return result ?? null;
|
|
4933
|
-
}
|
|
4934
|
-
async function listQueue(db, params = {}) {
|
|
4935
|
-
let query = `
|
|
4936
|
-
SELECT
|
|
4937
|
-
q.*,
|
|
4938
|
-
s.name as session_name,
|
|
4939
|
-
c.intent as claim_intent,
|
|
4940
|
-
cs.name as claim_session_name,
|
|
4941
|
-
GROUP_CONCAT(cf.file_path, '|||') as claim_files_concat
|
|
4942
|
-
FROM claim_queue q
|
|
4943
|
-
JOIN sessions s ON q.session_id = s.id
|
|
4944
|
-
JOIN claims c ON q.claim_id = c.id
|
|
4945
|
-
JOIN sessions cs ON c.session_id = cs.id
|
|
4946
|
-
LEFT JOIN claim_files cf ON c.id = cf.claim_id
|
|
4947
|
-
WHERE 1=1
|
|
4948
|
-
`;
|
|
4949
|
-
const bindings = [];
|
|
4950
|
-
if (params.claim_id) {
|
|
4951
|
-
query += " AND q.claim_id = ?";
|
|
4952
|
-
bindings.push(params.claim_id);
|
|
4953
|
-
}
|
|
4954
|
-
if (params.session_id) {
|
|
4955
|
-
query += " AND q.session_id = ?";
|
|
4956
|
-
bindings.push(params.session_id);
|
|
4957
|
-
}
|
|
4958
|
-
query += " GROUP BY q.id ORDER BY q.priority DESC, q.position ASC";
|
|
4959
|
-
const result = await db.prepare(query).bind(...bindings).all();
|
|
4960
|
-
return result.results.map((r) => ({
|
|
4961
|
-
...r,
|
|
4962
|
-
claim_files: r.claim_files_concat ? r.claim_files_concat.split("|||") : []
|
|
4963
|
-
}));
|
|
4964
|
-
}
|
|
4965
4566
|
async function removeSessionFromAllQueues(db, sessionId) {
|
|
4966
4567
|
const result = await db.prepare("DELETE FROM claim_queue WHERE session_id = ?").bind(sessionId).run();
|
|
4967
4568
|
return result.meta.changes;
|
|
4968
4569
|
}
|
|
4969
|
-
async function getQueuedSessionsForClaim(db, claimId) {
|
|
4970
|
-
const result = await db.prepare(
|
|
4971
|
-
`SELECT q.session_id, s.name as session_name, q.position
|
|
4972
|
-
FROM claim_queue q
|
|
4973
|
-
JOIN sessions s ON q.session_id = s.id
|
|
4974
|
-
WHERE q.claim_id = ?
|
|
4975
|
-
ORDER BY q.priority DESC, q.position ASC`
|
|
4976
|
-
).bind(claimId).all();
|
|
4977
|
-
return result.results;
|
|
4978
|
-
}
|
|
4979
|
-
async function createNotification(db, params) {
|
|
4980
|
-
const id = generateId();
|
|
4981
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4982
|
-
const metadataJson = params.metadata ? JSON.stringify(params.metadata) : null;
|
|
4983
|
-
await db.prepare(
|
|
4984
|
-
`INSERT INTO notifications (id, session_id, type, title, message, reference_type, reference_id, metadata, created_at)
|
|
4985
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
4986
|
-
).bind(
|
|
4987
|
-
id,
|
|
4988
|
-
params.session_id,
|
|
4989
|
-
params.type,
|
|
4990
|
-
params.title,
|
|
4991
|
-
params.message,
|
|
4992
|
-
params.reference_type ?? null,
|
|
4993
|
-
params.reference_id ?? null,
|
|
4994
|
-
metadataJson,
|
|
4995
|
-
now
|
|
4996
|
-
).run();
|
|
4997
|
-
return {
|
|
4998
|
-
id,
|
|
4999
|
-
session_id: params.session_id,
|
|
5000
|
-
type: params.type,
|
|
5001
|
-
title: params.title,
|
|
5002
|
-
message: params.message,
|
|
5003
|
-
reference_type: params.reference_type ?? null,
|
|
5004
|
-
reference_id: params.reference_id ?? null,
|
|
5005
|
-
metadata: metadataJson,
|
|
5006
|
-
read_at: null,
|
|
5007
|
-
created_at: now
|
|
5008
|
-
};
|
|
5009
|
-
}
|
|
5010
|
-
async function listNotifications(db, params) {
|
|
5011
|
-
let query = "SELECT * FROM notifications WHERE session_id = ?";
|
|
5012
|
-
const bindings = [params.session_id];
|
|
5013
|
-
if (params.unread_only) {
|
|
5014
|
-
query += " AND read_at IS NULL";
|
|
5015
|
-
}
|
|
5016
|
-
if (params.type) {
|
|
5017
|
-
query += " AND type = ?";
|
|
5018
|
-
bindings.push(params.type);
|
|
5019
|
-
}
|
|
5020
|
-
query += " ORDER BY created_at DESC LIMIT ?";
|
|
5021
|
-
bindings.push(params.limit ?? 50);
|
|
5022
|
-
const result = await db.prepare(query).bind(...bindings).all();
|
|
5023
|
-
return result.results;
|
|
5024
|
-
}
|
|
5025
|
-
async function markNotificationsRead(db, notificationIds) {
|
|
5026
|
-
if (notificationIds.length === 0) return 0;
|
|
5027
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5028
|
-
const placeholders = notificationIds.map(() => "?").join(",");
|
|
5029
|
-
const result = await db.prepare(`UPDATE notifications SET read_at = ? WHERE id IN (${placeholders}) AND read_at IS NULL`).bind(now, ...notificationIds).run();
|
|
5030
|
-
return result.meta.changes;
|
|
5031
|
-
}
|
|
5032
|
-
async function getNotification(db, id) {
|
|
5033
|
-
const result = await db.prepare("SELECT * FROM notifications WHERE id = ?").bind(id).first();
|
|
5034
|
-
return result ?? null;
|
|
5035
|
-
}
|
|
5036
|
-
async function notifyQueueOnClaimRelease(db, claimId, releasedBy, files) {
|
|
5037
|
-
const queuedSessions = await getQueuedSessionsForClaim(db, claimId);
|
|
5038
|
-
if (queuedSessions.length === 0) return 0;
|
|
5039
|
-
let notified = 0;
|
|
5040
|
-
for (let i = 0; i < queuedSessions.length; i++) {
|
|
5041
|
-
const entry = queuedSessions[i];
|
|
5042
|
-
const isFirst = i === 0;
|
|
5043
|
-
await createNotification(db, {
|
|
5044
|
-
session_id: entry.session_id,
|
|
5045
|
-
type: isFirst ? "queue_ready" : "claim_released",
|
|
5046
|
-
title: isFirst ? "You are next in queue!" : "Claim released",
|
|
5047
|
-
message: isFirst ? `The claim for ${files.join(", ")} has been released. You can now claim these files.` : `A claim you were waiting for has been released. Position: ${i + 1}`,
|
|
5048
|
-
reference_type: "claim",
|
|
5049
|
-
reference_id: claimId,
|
|
5050
|
-
metadata: {
|
|
5051
|
-
claim_id: claimId,
|
|
5052
|
-
files,
|
|
5053
|
-
released_by: releasedBy,
|
|
5054
|
-
queue_position: i + 1
|
|
5055
|
-
}
|
|
5056
|
-
});
|
|
5057
|
-
notified++;
|
|
5058
|
-
}
|
|
5059
|
-
return notified;
|
|
5060
|
-
}
|
|
5061
4570
|
async function saveMemory(db, sessionId, input) {
|
|
5062
4571
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5063
4572
|
const metadataJson = input.metadata ? JSON.stringify(input.metadata) : null;
|
|
@@ -5117,34 +4626,6 @@ async function recallMemory(db, sessionId, params = {}) {
|
|
|
5117
4626
|
const result = await db.prepare(query).bind(...bindings).all();
|
|
5118
4627
|
return result.results;
|
|
5119
4628
|
}
|
|
5120
|
-
async function updateMemory(db, sessionId, key, updates) {
|
|
5121
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5122
|
-
const setParts = ["updated_at = ?"];
|
|
5123
|
-
const bindings = [now];
|
|
5124
|
-
if (updates.content !== void 0) {
|
|
5125
|
-
setParts.push("content = ?");
|
|
5126
|
-
bindings.push(updates.content);
|
|
5127
|
-
}
|
|
5128
|
-
if (updates.priority !== void 0) {
|
|
5129
|
-
setParts.push("priority = ?");
|
|
5130
|
-
bindings.push(updates.priority);
|
|
5131
|
-
}
|
|
5132
|
-
if (updates.pinned !== void 0) {
|
|
5133
|
-
setParts.push("pinned = ?");
|
|
5134
|
-
bindings.push(updates.pinned ? 1 : 0);
|
|
5135
|
-
}
|
|
5136
|
-
if (updates.expires_at !== void 0) {
|
|
5137
|
-
setParts.push("expires_at = ?");
|
|
5138
|
-
bindings.push(updates.expires_at);
|
|
5139
|
-
}
|
|
5140
|
-
if (updates.metadata !== void 0) {
|
|
5141
|
-
setParts.push("metadata = ?");
|
|
5142
|
-
bindings.push(JSON.stringify(updates.metadata));
|
|
5143
|
-
}
|
|
5144
|
-
bindings.push(sessionId, key);
|
|
5145
|
-
const result = await db.prepare(`UPDATE working_memory SET ${setParts.join(", ")} WHERE session_id = ? AND key = ?`).bind(...bindings).run();
|
|
5146
|
-
return result.meta.changes > 0;
|
|
5147
|
-
}
|
|
5148
4629
|
async function clearMemory(db, sessionId, params = {}) {
|
|
5149
4630
|
if (params.key) {
|
|
5150
4631
|
const result = await db.prepare("DELETE FROM working_memory WHERE session_id = ? AND key = ?").bind(sessionId, params.key).run();
|
|
@@ -5160,30 +4641,6 @@ async function clearMemory(db, sessionId, params = {}) {
|
|
|
5160
4641
|
}
|
|
5161
4642
|
return 0;
|
|
5162
4643
|
}
|
|
5163
|
-
async function pinMemory(db, sessionId, key, pinned) {
|
|
5164
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5165
|
-
const result = await db.prepare("UPDATE working_memory SET pinned = ?, updated_at = ? WHERE session_id = ? AND key = ?").bind(pinned ? 1 : 0, now, sessionId, key).run();
|
|
5166
|
-
return result.meta.changes > 0;
|
|
5167
|
-
}
|
|
5168
|
-
async function getMemoryStats(db, sessionId) {
|
|
5169
|
-
const totalResult = await db.prepare("SELECT COUNT(*) as count FROM working_memory WHERE session_id = ?").bind(sessionId).first();
|
|
5170
|
-
const categoryResult = await db.prepare(
|
|
5171
|
-
`SELECT category, COUNT(*) as count
|
|
5172
|
-
FROM working_memory
|
|
5173
|
-
WHERE session_id = ?
|
|
5174
|
-
GROUP BY category`
|
|
5175
|
-
).bind(sessionId).all();
|
|
5176
|
-
const pinnedResult = await db.prepare("SELECT COUNT(*) as count FROM working_memory WHERE session_id = ? AND pinned = 1").bind(sessionId).first();
|
|
5177
|
-
const byCategory = {};
|
|
5178
|
-
for (const row of categoryResult.results) {
|
|
5179
|
-
byCategory[row.category] = row.count;
|
|
5180
|
-
}
|
|
5181
|
-
return {
|
|
5182
|
-
total: totalResult?.count ?? 0,
|
|
5183
|
-
by_category: byCategory,
|
|
5184
|
-
pinned_count: pinnedResult?.count ?? 0
|
|
5185
|
-
};
|
|
5186
|
-
}
|
|
5187
4644
|
async function getActiveMemories(db, sessionId, params = {}) {
|
|
5188
4645
|
const threshold = params.priority_threshold ?? 70;
|
|
5189
4646
|
const limit = params.max_items ?? 20;
|
|
@@ -5219,40 +4676,6 @@ ${params.content_summary}`,
|
|
|
5219
4676
|
}
|
|
5220
4677
|
});
|
|
5221
4678
|
}
|
|
5222
|
-
async function updatePlanStatus(db, sessionId, filePath, newStatus, summary) {
|
|
5223
|
-
const key = `plan:${filePath}`;
|
|
5224
|
-
const memories = await recallMemory(db, sessionId, { key });
|
|
5225
|
-
if (memories.length === 0) return false;
|
|
5226
|
-
const current = memories[0];
|
|
5227
|
-
let metadata = {};
|
|
5228
|
-
try {
|
|
5229
|
-
metadata = current.metadata ? JSON.parse(current.metadata) : {};
|
|
5230
|
-
} catch {
|
|
5231
|
-
}
|
|
5232
|
-
metadata.status = newStatus;
|
|
5233
|
-
if (newStatus === "completed") {
|
|
5234
|
-
metadata.completed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
5235
|
-
}
|
|
5236
|
-
let priority = 95;
|
|
5237
|
-
let pinned = true;
|
|
5238
|
-
if (newStatus === "completed") {
|
|
5239
|
-
priority = 50;
|
|
5240
|
-
pinned = false;
|
|
5241
|
-
} else if (newStatus === "archived") {
|
|
5242
|
-
priority = 30;
|
|
5243
|
-
pinned = false;
|
|
5244
|
-
}
|
|
5245
|
-
const updatedContent = summary ? `[PLAN] ${metadata.title}
|
|
5246
|
-
Status: ${newStatus}
|
|
5247
|
-
|
|
5248
|
-
${summary}` : current.content.replace(/Status: \w+/, `Status: ${newStatus}`);
|
|
5249
|
-
return updateMemory(db, sessionId, key, {
|
|
5250
|
-
content: updatedContent,
|
|
5251
|
-
priority,
|
|
5252
|
-
pinned,
|
|
5253
|
-
metadata
|
|
5254
|
-
});
|
|
5255
|
-
}
|
|
5256
4679
|
async function getPlan(db, sessionId, filePath) {
|
|
5257
4680
|
const key = `plan:${filePath}`;
|
|
5258
4681
|
const memories = await recallMemory(db, sessionId, { key });
|
|
@@ -5401,7 +4824,10 @@ async function getProtectedFiles(db, sessionId) {
|
|
|
5401
4824
|
// src/mcp/schemas.ts
|
|
5402
4825
|
var sessionIdSchema = external_exports.string().min(1, "session_id is required");
|
|
5403
4826
|
var claimIdSchema = external_exports.string().min(1, "claim_id is required");
|
|
5404
|
-
var filePathSchema = external_exports.string().min(1)
|
|
4827
|
+
var filePathSchema = external_exports.string().min(1).refine(
|
|
4828
|
+
(path) => !path.includes("..") && !path.includes("\0"),
|
|
4829
|
+
{ message: "Path cannot contain path traversal sequences (..) or null bytes" }
|
|
4830
|
+
);
|
|
5405
4831
|
var filesArraySchema = external_exports.array(filePathSchema).min(1, "At least one file is required");
|
|
5406
4832
|
var symbolClaimSchema = external_exports.object({
|
|
5407
4833
|
file: external_exports.string().min(1),
|
|
@@ -5676,7 +5102,7 @@ function validationError(message) {
|
|
|
5676
5102
|
var sessionTools = [
|
|
5677
5103
|
{
|
|
5678
5104
|
name: "collab_session_start",
|
|
5679
|
-
description: "
|
|
5105
|
+
description: "Start a new session. Call this when starting work.",
|
|
5680
5106
|
inputSchema: {
|
|
5681
5107
|
type: "object",
|
|
5682
5108
|
properties: {
|
|
@@ -5687,10 +5113,6 @@ var sessionTools = [
|
|
|
5687
5113
|
project_root: {
|
|
5688
5114
|
type: "string",
|
|
5689
5115
|
description: "Project root directory path"
|
|
5690
|
-
},
|
|
5691
|
-
machine_id: {
|
|
5692
|
-
type: "string",
|
|
5693
|
-
description: "Optional machine identifier for multi-machine setups"
|
|
5694
5116
|
}
|
|
5695
5117
|
},
|
|
5696
5118
|
required: ["project_root"]
|
|
@@ -5698,7 +5120,7 @@ var sessionTools = [
|
|
|
5698
5120
|
},
|
|
5699
5121
|
{
|
|
5700
5122
|
name: "collab_session_end",
|
|
5701
|
-
description: "End
|
|
5123
|
+
description: "End session and release all claims.",
|
|
5702
5124
|
inputSchema: {
|
|
5703
5125
|
type: "object",
|
|
5704
5126
|
properties: {
|
|
@@ -5709,7 +5131,7 @@ var sessionTools = [
|
|
|
5709
5131
|
release_claims: {
|
|
5710
5132
|
type: "string",
|
|
5711
5133
|
enum: ["complete", "abandon"],
|
|
5712
|
-
description: "How to handle unreleased claims
|
|
5134
|
+
description: "How to handle unreleased claims"
|
|
5713
5135
|
}
|
|
5714
5136
|
},
|
|
5715
5137
|
required: ["session_id"]
|
|
@@ -5717,13 +5139,13 @@ var sessionTools = [
|
|
|
5717
5139
|
},
|
|
5718
5140
|
{
|
|
5719
5141
|
name: "collab_session_list",
|
|
5720
|
-
description: "List all active sessions.
|
|
5142
|
+
description: "List all active sessions.",
|
|
5721
5143
|
inputSchema: {
|
|
5722
5144
|
type: "object",
|
|
5723
5145
|
properties: {
|
|
5724
5146
|
include_inactive: {
|
|
5725
5147
|
type: "boolean",
|
|
5726
|
-
description: "Include inactive
|
|
5148
|
+
description: "Include inactive sessions"
|
|
5727
5149
|
},
|
|
5728
5150
|
project_root: {
|
|
5729
5151
|
type: "string",
|
|
@@ -5732,68 +5154,9 @@ var sessionTools = [
|
|
|
5732
5154
|
}
|
|
5733
5155
|
}
|
|
5734
5156
|
},
|
|
5735
|
-
{
|
|
5736
|
-
name: "collab_session_heartbeat",
|
|
5737
|
-
description: "Update session heartbeat to indicate the session is still active.",
|
|
5738
|
-
inputSchema: {
|
|
5739
|
-
type: "object",
|
|
5740
|
-
properties: {
|
|
5741
|
-
session_id: {
|
|
5742
|
-
type: "string",
|
|
5743
|
-
description: "Session ID to update"
|
|
5744
|
-
},
|
|
5745
|
-
current_task: {
|
|
5746
|
-
type: "string",
|
|
5747
|
-
description: "Optional: Current task being worked on"
|
|
5748
|
-
},
|
|
5749
|
-
todos: {
|
|
5750
|
-
type: "array",
|
|
5751
|
-
description: "Optional: Current todo list to sync",
|
|
5752
|
-
items: {
|
|
5753
|
-
type: "object",
|
|
5754
|
-
properties: {
|
|
5755
|
-
content: { type: "string" },
|
|
5756
|
-
status: { type: "string", enum: ["pending", "in_progress", "completed"] }
|
|
5757
|
-
}
|
|
5758
|
-
}
|
|
5759
|
-
}
|
|
5760
|
-
},
|
|
5761
|
-
required: ["session_id"]
|
|
5762
|
-
}
|
|
5763
|
-
},
|
|
5764
|
-
{
|
|
5765
|
-
name: "collab_status_update",
|
|
5766
|
-
description: "Update session work status. Use this to share what you are currently working on with other sessions.",
|
|
5767
|
-
inputSchema: {
|
|
5768
|
-
type: "object",
|
|
5769
|
-
properties: {
|
|
5770
|
-
session_id: {
|
|
5771
|
-
type: "string",
|
|
5772
|
-
description: "Your session ID"
|
|
5773
|
-
},
|
|
5774
|
-
current_task: {
|
|
5775
|
-
type: "string",
|
|
5776
|
-
description: 'Description of current task (e.g., "Refactoring auth module")'
|
|
5777
|
-
},
|
|
5778
|
-
todos: {
|
|
5779
|
-
type: "array",
|
|
5780
|
-
description: "Your current todo list",
|
|
5781
|
-
items: {
|
|
5782
|
-
type: "object",
|
|
5783
|
-
properties: {
|
|
5784
|
-
content: { type: "string", description: "Task description" },
|
|
5785
|
-
status: { type: "string", enum: ["pending", "in_progress", "completed"] }
|
|
5786
|
-
},
|
|
5787
|
-
required: ["content", "status"]
|
|
5788
|
-
}
|
|
5789
|
-
}
|
|
5790
|
-
},
|
|
5791
|
-
required: ["session_id"]
|
|
5792
|
-
}
|
|
5793
|
-
},
|
|
5794
5157
|
{
|
|
5795
5158
|
name: "collab_config",
|
|
5796
|
-
description: "Configure session behavior
|
|
5159
|
+
description: "Configure session behavior.",
|
|
5797
5160
|
inputSchema: {
|
|
5798
5161
|
type: "object",
|
|
5799
5162
|
properties: {
|
|
@@ -5804,19 +5167,11 @@ var sessionTools = [
|
|
|
5804
5167
|
mode: {
|
|
5805
5168
|
type: "string",
|
|
5806
5169
|
enum: ["strict", "smart", "bypass"],
|
|
5807
|
-
description: "Conflict handling mode
|
|
5170
|
+
description: "Conflict handling mode"
|
|
5808
5171
|
},
|
|
5809
5172
|
allow_release_others: {
|
|
5810
5173
|
type: "boolean",
|
|
5811
|
-
description: "Allow releasing
|
|
5812
|
-
},
|
|
5813
|
-
auto_release_stale: {
|
|
5814
|
-
type: "boolean",
|
|
5815
|
-
description: "Automatically release stale claims (default: false)"
|
|
5816
|
-
},
|
|
5817
|
-
stale_threshold_hours: {
|
|
5818
|
-
type: "number",
|
|
5819
|
-
description: "Hours before a claim is considered stale (default: 2)"
|
|
5174
|
+
description: "Allow releasing other sessions claims"
|
|
5820
5175
|
}
|
|
5821
5176
|
},
|
|
5822
5177
|
required: ["session_id"]
|
|
@@ -5855,54 +5210,19 @@ async function handleSessionTool(db, name, args, userId) {
|
|
|
5855
5210
|
});
|
|
5856
5211
|
for (const mem of memories) {
|
|
5857
5212
|
allMemories.push({
|
|
5858
|
-
session_name: otherSession.name,
|
|
5859
5213
|
category: mem.category,
|
|
5860
5214
|
key: mem.key,
|
|
5861
|
-
content: mem.content
|
|
5862
|
-
priority: mem.priority,
|
|
5863
|
-
pinned: mem.pinned === 1
|
|
5215
|
+
content: mem.content
|
|
5864
5216
|
});
|
|
5865
5217
|
}
|
|
5866
5218
|
}
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5219
|
+
return successResponse({
|
|
5220
|
+
session_id: session.id,
|
|
5221
|
+
name: session.name,
|
|
5222
|
+
active_sessions: activeSessions.length,
|
|
5223
|
+
restored_context: allMemories.length > 0 ? allMemories.slice(0, 15) : null,
|
|
5224
|
+
message: `Session started. ${activeSessions.length} active session(s).`
|
|
5870
5225
|
});
|
|
5871
|
-
const topMemories = allMemories.slice(0, 20);
|
|
5872
|
-
const memoriesByCategory = {};
|
|
5873
|
-
for (const mem of topMemories) {
|
|
5874
|
-
if (!memoriesByCategory[mem.category]) {
|
|
5875
|
-
memoriesByCategory[mem.category] = [];
|
|
5876
|
-
}
|
|
5877
|
-
memoriesByCategory[mem.category].push({
|
|
5878
|
-
key: mem.key,
|
|
5879
|
-
content: mem.content,
|
|
5880
|
-
session: mem.session_name
|
|
5881
|
-
});
|
|
5882
|
-
}
|
|
5883
|
-
return createToolResult(
|
|
5884
|
-
JSON.stringify(
|
|
5885
|
-
{
|
|
5886
|
-
session_id: session.id,
|
|
5887
|
-
name: session.name,
|
|
5888
|
-
message: `Session registered. ${activeSessions.length} active session(s) in this project.`,
|
|
5889
|
-
active_sessions: activeSessions.map((s) => ({
|
|
5890
|
-
id: s.id,
|
|
5891
|
-
name: s.name,
|
|
5892
|
-
last_heartbeat: s.last_heartbeat
|
|
5893
|
-
})),
|
|
5894
|
-
// AUTO-LOADED CONTEXT
|
|
5895
|
-
restored_context: topMemories.length > 0 ? {
|
|
5896
|
-
count: topMemories.length,
|
|
5897
|
-
by_category: memoriesByCategory,
|
|
5898
|
-
note: "These memories were automatically loaded from previous sessions. Use them to maintain context continuity."
|
|
5899
|
-
} : null,
|
|
5900
|
-
tip: "Use collab_memory_save to persist important context. Plans and files are auto-protected when registered."
|
|
5901
|
-
},
|
|
5902
|
-
null,
|
|
5903
|
-
2
|
|
5904
|
-
)
|
|
5905
|
-
);
|
|
5906
5226
|
}
|
|
5907
5227
|
case "collab_session_end": {
|
|
5908
5228
|
const validation = validateInput(sessionEndSchema, args);
|
|
@@ -5915,26 +5235,10 @@ async function handleSessionTool(db, name, args, userId) {
|
|
|
5915
5235
|
return sessionResult.error;
|
|
5916
5236
|
}
|
|
5917
5237
|
const activeClaims = await listClaims(db, { session_id: input.session_id, status: "active" });
|
|
5918
|
-
const claimsToRelease = activeClaims.map((claim) => ({
|
|
5919
|
-
id: claim.id,
|
|
5920
|
-
files: claim.files,
|
|
5921
|
-
intent: claim.intent,
|
|
5922
|
-
scope: claim.scope,
|
|
5923
|
-
created_at: claim.created_at
|
|
5924
|
-
}));
|
|
5925
5238
|
const memories = await recallMemory(db, input.session_id, {});
|
|
5926
|
-
let memorySummary = null;
|
|
5927
5239
|
if (memories.length > 0) {
|
|
5928
5240
|
const findings = memories.filter((m) => m.category === "finding").map((m) => m.content);
|
|
5929
5241
|
const decisions = memories.filter((m) => m.category === "decision").map((m) => m.content);
|
|
5930
|
-
const important = memories.filter((m) => m.category === "important" || m.pinned === 1).map((m) => m.content);
|
|
5931
|
-
memorySummary = {
|
|
5932
|
-
total: memories.length,
|
|
5933
|
-
findings: findings.slice(0, 5),
|
|
5934
|
-
// Top 5
|
|
5935
|
-
decisions: decisions.slice(0, 5),
|
|
5936
|
-
important: important.slice(0, 5)
|
|
5937
|
-
};
|
|
5938
5242
|
const summaryContent = [
|
|
5939
5243
|
`Session: ${sessionResult.session.name || input.session_id}`,
|
|
5940
5244
|
`Ended: ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
@@ -5943,43 +5247,29 @@ Findings:
|
|
|
5943
5247
|
- ${findings.slice(0, 5).join("\n- ")}` : "",
|
|
5944
5248
|
decisions.length > 0 ? `
|
|
5945
5249
|
Decisions:
|
|
5946
|
-
- ${decisions.slice(0, 5).join("\n- ")}` : ""
|
|
5947
|
-
important.length > 0 ? `
|
|
5948
|
-
Important:
|
|
5949
|
-
- ${important.slice(0, 5).join("\n- ")}` : ""
|
|
5250
|
+
- ${decisions.slice(0, 5).join("\n- ")}` : ""
|
|
5950
5251
|
].filter(Boolean).join("\n");
|
|
5951
5252
|
await saveMemory(db, input.session_id, {
|
|
5952
5253
|
category: "context",
|
|
5953
5254
|
key: "session_summary",
|
|
5954
5255
|
content: summaryContent,
|
|
5955
5256
|
priority: 80,
|
|
5956
|
-
pinned: true
|
|
5957
|
-
metadata: {
|
|
5958
|
-
session_name: sessionResult.session.name,
|
|
5959
|
-
ended_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5960
|
-
memory_count: memories.length
|
|
5961
|
-
}
|
|
5257
|
+
pinned: true
|
|
5962
5258
|
});
|
|
5963
5259
|
}
|
|
5964
|
-
|
|
5260
|
+
await removeSessionFromAllQueues(db, input.session_id);
|
|
5965
5261
|
await endSession(db, input.session_id, input.release_claims);
|
|
5966
|
-
const claimStatus = input.release_claims === "complete" ? "completed" : "abandoned";
|
|
5967
5262
|
await logAuditEvent(db, {
|
|
5968
5263
|
session_id: input.session_id,
|
|
5969
5264
|
action: "session_ended",
|
|
5970
5265
|
entity_type: "session",
|
|
5971
|
-
entity_id: input.session_id
|
|
5972
|
-
metadata: { status: claimStatus, memory_count: memories.length }
|
|
5266
|
+
entity_id: input.session_id
|
|
5973
5267
|
});
|
|
5974
5268
|
return successResponse({
|
|
5975
5269
|
success: true,
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
status: input.release_claims === "complete" ? "completed" : "abandoned",
|
|
5980
|
-
details: claimsToRelease
|
|
5981
|
-
} : null,
|
|
5982
|
-
memory_summary: memorySummary
|
|
5270
|
+
claims_released: activeClaims.length,
|
|
5271
|
+
memories_saved: memories.length,
|
|
5272
|
+
message: `Session ended. ${activeClaims.length} claim(s) released.`
|
|
5983
5273
|
});
|
|
5984
5274
|
}
|
|
5985
5275
|
case "collab_session_list": {
|
|
@@ -5992,102 +5282,32 @@ Important:
|
|
|
5992
5282
|
include_inactive: input.include_inactive,
|
|
5993
5283
|
project_root: input.project_root
|
|
5994
5284
|
});
|
|
5995
|
-
const
|
|
5285
|
+
const sessionsWithClaims = await Promise.all(
|
|
5996
5286
|
sessions.map(async (session) => {
|
|
5997
5287
|
const claims = await listClaims(db, { session_id: session.id, status: "active" });
|
|
5998
|
-
let progress = null;
|
|
5999
|
-
let todos = null;
|
|
6000
|
-
try {
|
|
6001
|
-
if (session.progress) progress = JSON.parse(session.progress);
|
|
6002
|
-
if (session.todos) todos = JSON.parse(session.todos);
|
|
6003
|
-
} catch {
|
|
6004
|
-
}
|
|
6005
5288
|
return {
|
|
6006
5289
|
id: session.id,
|
|
6007
5290
|
name: session.name,
|
|
6008
|
-
project_root: session.project_root,
|
|
6009
5291
|
status: session.status,
|
|
6010
5292
|
active_claims: claims.length,
|
|
6011
|
-
last_heartbeat: session.last_heartbeat
|
|
6012
|
-
current_task: session.current_task,
|
|
6013
|
-
progress,
|
|
6014
|
-
todos
|
|
5293
|
+
last_heartbeat: session.last_heartbeat
|
|
6015
5294
|
};
|
|
6016
5295
|
})
|
|
6017
5296
|
);
|
|
6018
|
-
return
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
total: sessionsWithDetails.length
|
|
6023
|
-
},
|
|
6024
|
-
null,
|
|
6025
|
-
2
|
|
6026
|
-
)
|
|
6027
|
-
);
|
|
5297
|
+
return successResponse({
|
|
5298
|
+
sessions: sessionsWithClaims,
|
|
5299
|
+
total: sessionsWithClaims.length
|
|
5300
|
+
});
|
|
6028
5301
|
}
|
|
6029
|
-
case "
|
|
6030
|
-
const validation = validateInput(
|
|
5302
|
+
case "collab_config": {
|
|
5303
|
+
const validation = validateInput(configSchema, args);
|
|
6031
5304
|
if (!validation.success) {
|
|
6032
5305
|
return validationError(validation.error);
|
|
6033
5306
|
}
|
|
6034
5307
|
const input = validation.data;
|
|
6035
|
-
const
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
});
|
|
6039
|
-
if (!updated) {
|
|
6040
|
-
return errorResponse(
|
|
6041
|
-
ERROR_CODES.SESSION_NOT_FOUND,
|
|
6042
|
-
"Session not found or inactive. Please start a new session."
|
|
6043
|
-
);
|
|
6044
|
-
}
|
|
6045
|
-
return successResponse({
|
|
6046
|
-
success: true,
|
|
6047
|
-
message: "Heartbeat updated",
|
|
6048
|
-
status_synced: !!(input.current_task || input.todos)
|
|
6049
|
-
});
|
|
6050
|
-
}
|
|
6051
|
-
case "collab_status_update": {
|
|
6052
|
-
const validation = validateInput(statusUpdateSchema, args);
|
|
6053
|
-
if (!validation.success) {
|
|
6054
|
-
return validationError(validation.error);
|
|
6055
|
-
}
|
|
6056
|
-
const input = validation.data;
|
|
6057
|
-
const todos = input.todos;
|
|
6058
|
-
const sessionResult = await validateActiveSession(db, input.session_id);
|
|
6059
|
-
if (!sessionResult.valid) {
|
|
6060
|
-
return sessionResult.error;
|
|
6061
|
-
}
|
|
6062
|
-
await updateSessionStatus(db, input.session_id, {
|
|
6063
|
-
current_task: input.current_task,
|
|
6064
|
-
todos
|
|
6065
|
-
});
|
|
6066
|
-
let progress = null;
|
|
6067
|
-
if (todos && todos.length > 0) {
|
|
6068
|
-
const completed = todos.filter((t) => t.status === "completed").length;
|
|
6069
|
-
progress = {
|
|
6070
|
-
completed,
|
|
6071
|
-
total: todos.length,
|
|
6072
|
-
percentage: Math.round(completed / todos.length * 100)
|
|
6073
|
-
};
|
|
6074
|
-
}
|
|
6075
|
-
return successResponse({
|
|
6076
|
-
success: true,
|
|
6077
|
-
message: "Status updated successfully.",
|
|
6078
|
-
current_task: input.current_task ?? null,
|
|
6079
|
-
progress
|
|
6080
|
-
});
|
|
6081
|
-
}
|
|
6082
|
-
case "collab_config": {
|
|
6083
|
-
const validation = validateInput(configSchema, args);
|
|
6084
|
-
if (!validation.success) {
|
|
6085
|
-
return validationError(validation.error);
|
|
6086
|
-
}
|
|
6087
|
-
const input = validation.data;
|
|
6088
|
-
const sessionResult = await validateActiveSession(db, input.session_id);
|
|
6089
|
-
if (!sessionResult.valid) {
|
|
6090
|
-
return sessionResult.error;
|
|
5308
|
+
const sessionResult = await validateActiveSession(db, input.session_id);
|
|
5309
|
+
if (!sessionResult.valid) {
|
|
5310
|
+
return sessionResult.error;
|
|
6091
5311
|
}
|
|
6092
5312
|
const { session } = sessionResult;
|
|
6093
5313
|
let currentConfig = DEFAULT_SESSION_CONFIG;
|
|
@@ -6099,17 +5319,45 @@ Important:
|
|
|
6099
5319
|
}
|
|
6100
5320
|
const newConfig = {
|
|
6101
5321
|
mode: input.mode ?? currentConfig.mode,
|
|
6102
|
-
allow_release_others: input.allow_release_others
|
|
6103
|
-
auto_release_stale: input.auto_release_stale
|
|
5322
|
+
allow_release_others: input.allow_release_others ?? currentConfig.allow_release_others,
|
|
5323
|
+
auto_release_stale: input.auto_release_stale ?? currentConfig.auto_release_stale,
|
|
6104
5324
|
stale_threshold_hours: input.stale_threshold_hours ?? currentConfig.stale_threshold_hours,
|
|
6105
|
-
auto_release_immediate: input.auto_release_immediate
|
|
5325
|
+
auto_release_immediate: input.auto_release_immediate ?? currentConfig.auto_release_immediate,
|
|
6106
5326
|
auto_release_delay_minutes: input.auto_release_delay_minutes ?? currentConfig.auto_release_delay_minutes
|
|
6107
5327
|
};
|
|
6108
5328
|
await updateSessionConfig(db, input.session_id, newConfig);
|
|
6109
5329
|
return successResponse({
|
|
6110
5330
|
success: true,
|
|
6111
|
-
|
|
6112
|
-
|
|
5331
|
+
config: newConfig,
|
|
5332
|
+
message: "Configuration updated."
|
|
5333
|
+
});
|
|
5334
|
+
}
|
|
5335
|
+
case "collab_status": {
|
|
5336
|
+
const sessionId = args.session_id;
|
|
5337
|
+
if (!sessionId) {
|
|
5338
|
+
return validationError("session_id is required");
|
|
5339
|
+
}
|
|
5340
|
+
const session = await getSession(db, sessionId);
|
|
5341
|
+
if (!session) {
|
|
5342
|
+
return errorResponse(ERROR_CODES.SESSION_NOT_FOUND, "Session not found");
|
|
5343
|
+
}
|
|
5344
|
+
const claims = await listClaims(db, { session_id: sessionId, status: "active" });
|
|
5345
|
+
const memories = await getActiveMemories(db, sessionId, { priority_threshold: 70, max_items: 10 });
|
|
5346
|
+
const allSessions = await listSessions(db, { project_root: session.project_root });
|
|
5347
|
+
return successResponse({
|
|
5348
|
+
session: {
|
|
5349
|
+
id: session.id,
|
|
5350
|
+
name: session.name,
|
|
5351
|
+
status: session.status
|
|
5352
|
+
},
|
|
5353
|
+
claims: claims.map((c) => ({
|
|
5354
|
+
id: c.id,
|
|
5355
|
+
files: c.files,
|
|
5356
|
+
intent: c.intent
|
|
5357
|
+
})),
|
|
5358
|
+
active_memories: memories.length,
|
|
5359
|
+
other_sessions: allSessions.filter((s) => s.id !== sessionId).length,
|
|
5360
|
+
message: `Session active. ${claims.length} claim(s), ${memories.length} memories.`
|
|
6113
5361
|
});
|
|
6114
5362
|
}
|
|
6115
5363
|
default:
|
|
@@ -6121,10 +5369,19 @@ Important:
|
|
|
6121
5369
|
var claimTools = [
|
|
6122
5370
|
{
|
|
6123
5371
|
name: "collab_claim",
|
|
6124
|
-
description:
|
|
5372
|
+
description: `Unified tool for file/symbol claims. Use action parameter to:
|
|
5373
|
+
- "create": Declare files you're about to modify
|
|
5374
|
+
- "check": Check if files are being worked on (ALWAYS call before editing)
|
|
5375
|
+
- "release": Release a claim when done
|
|
5376
|
+
- "list": List all active claims`,
|
|
6125
5377
|
inputSchema: {
|
|
6126
5378
|
type: "object",
|
|
6127
5379
|
properties: {
|
|
5380
|
+
action: {
|
|
5381
|
+
type: "string",
|
|
5382
|
+
enum: ["create", "check", "release", "list"],
|
|
5383
|
+
description: "Action to perform"
|
|
5384
|
+
},
|
|
6128
5385
|
session_id: {
|
|
6129
5386
|
type: "string",
|
|
6130
5387
|
description: "Your session ID"
|
|
@@ -6132,476 +5389,141 @@ var claimTools = [
|
|
|
6132
5389
|
files: {
|
|
6133
5390
|
type: "array",
|
|
6134
5391
|
items: { type: "string" },
|
|
6135
|
-
description: "File paths
|
|
6136
|
-
},
|
|
6137
|
-
symbols: {
|
|
6138
|
-
type: "array",
|
|
6139
|
-
items: {
|
|
6140
|
-
type: "object",
|
|
6141
|
-
properties: {
|
|
6142
|
-
file: { type: "string", description: "File path containing the symbols" },
|
|
6143
|
-
symbols: {
|
|
6144
|
-
type: "array",
|
|
6145
|
-
items: { type: "string" },
|
|
6146
|
-
description: "Symbol names (function, class, method names) to claim"
|
|
6147
|
-
},
|
|
6148
|
-
symbol_type: {
|
|
6149
|
-
type: "string",
|
|
6150
|
-
enum: ["function", "class", "method", "variable", "block", "other"],
|
|
6151
|
-
description: "Type of symbols being claimed (default: function)"
|
|
6152
|
-
}
|
|
6153
|
-
},
|
|
6154
|
-
required: ["file", "symbols"]
|
|
6155
|
-
},
|
|
6156
|
-
description: "Symbol-level claims for fine-grained conflict detection. Use this instead of files when you only need to modify specific functions/classes."
|
|
5392
|
+
description: "File paths (for create/check actions)"
|
|
6157
5393
|
},
|
|
6158
5394
|
intent: {
|
|
6159
5395
|
type: "string",
|
|
6160
|
-
description: "What you plan to do
|
|
6161
|
-
},
|
|
6162
|
-
scope: {
|
|
6163
|
-
type: "string",
|
|
6164
|
-
enum: ["small", "medium", "large"],
|
|
6165
|
-
description: "Estimated scope: small(<30min), medium(30min-2hr), large(>2hr)"
|
|
6166
|
-
},
|
|
6167
|
-
priority: {
|
|
6168
|
-
type: "number",
|
|
6169
|
-
description: "Priority (0-100). Levels: critical (90-100), high (70-89), normal (40-69, default: 50), low (0-39)"
|
|
6170
|
-
}
|
|
6171
|
-
},
|
|
6172
|
-
required: ["session_id", "intent"]
|
|
6173
|
-
}
|
|
6174
|
-
},
|
|
6175
|
-
{
|
|
6176
|
-
name: "collab_check",
|
|
6177
|
-
description: "Check if files or symbols are being worked on by other sessions. ALWAYS call this before modifying files. Supports symbol-level checking for fine-grained conflict detection.",
|
|
6178
|
-
inputSchema: {
|
|
6179
|
-
type: "object",
|
|
6180
|
-
properties: {
|
|
6181
|
-
files: {
|
|
6182
|
-
type: "array",
|
|
6183
|
-
items: { type: "string" },
|
|
6184
|
-
description: "File paths to check"
|
|
6185
|
-
},
|
|
6186
|
-
symbols: {
|
|
6187
|
-
type: "array",
|
|
6188
|
-
items: {
|
|
6189
|
-
type: "object",
|
|
6190
|
-
properties: {
|
|
6191
|
-
file: { type: "string", description: "File path containing the symbols" },
|
|
6192
|
-
symbols: {
|
|
6193
|
-
type: "array",
|
|
6194
|
-
items: { type: "string" },
|
|
6195
|
-
description: "Symbol names to check for conflicts"
|
|
6196
|
-
}
|
|
6197
|
-
},
|
|
6198
|
-
required: ["file", "symbols"]
|
|
6199
|
-
},
|
|
6200
|
-
description: "Symbol-level check. If provided, only checks for conflicts with these specific symbols."
|
|
6201
|
-
},
|
|
6202
|
-
session_id: {
|
|
6203
|
-
type: "string",
|
|
6204
|
-
description: "Your session ID (to exclude your own claims from results)"
|
|
6205
|
-
}
|
|
6206
|
-
},
|
|
6207
|
-
required: ["files"]
|
|
6208
|
-
}
|
|
6209
|
-
},
|
|
6210
|
-
{
|
|
6211
|
-
name: "collab_release",
|
|
6212
|
-
description: "Release a claim when done or abandoning work. By default you can only release your own claims. Use force=true with user confirmation to release stale claims from other sessions.",
|
|
6213
|
-
inputSchema: {
|
|
6214
|
-
type: "object",
|
|
6215
|
-
properties: {
|
|
6216
|
-
session_id: {
|
|
6217
|
-
type: "string",
|
|
6218
|
-
description: "Your session ID (required to verify ownership)"
|
|
5396
|
+
description: "What you plan to do (for create action)"
|
|
6219
5397
|
},
|
|
6220
5398
|
claim_id: {
|
|
6221
5399
|
type: "string",
|
|
6222
|
-
description: "Claim ID
|
|
5400
|
+
description: "Claim ID (for release action)"
|
|
6223
5401
|
},
|
|
6224
5402
|
status: {
|
|
6225
5403
|
type: "string",
|
|
6226
5404
|
enum: ["completed", "abandoned"],
|
|
6227
|
-
description: "
|
|
6228
|
-
},
|
|
6229
|
-
summary: {
|
|
6230
|
-
type: "string",
|
|
6231
|
-
description: "Optional summary of what was done (for completed claims)"
|
|
6232
|
-
},
|
|
6233
|
-
force: {
|
|
6234
|
-
type: "boolean",
|
|
6235
|
-
description: "Force release even if claim belongs to another session (requires user confirmation)"
|
|
6236
|
-
}
|
|
6237
|
-
},
|
|
6238
|
-
required: ["session_id", "claim_id", "status"]
|
|
6239
|
-
}
|
|
6240
|
-
},
|
|
6241
|
-
{
|
|
6242
|
-
name: "collab_claims_list",
|
|
6243
|
-
description: "List all WIP claims. Use to see what files are being worked on.",
|
|
6244
|
-
inputSchema: {
|
|
6245
|
-
type: "object",
|
|
6246
|
-
properties: {
|
|
6247
|
-
session_id: {
|
|
6248
|
-
type: "string",
|
|
6249
|
-
description: "Filter by session ID"
|
|
6250
|
-
},
|
|
6251
|
-
status: {
|
|
6252
|
-
type: "string",
|
|
6253
|
-
enum: ["active", "completed", "abandoned", "all"],
|
|
6254
|
-
description: "Filter by claim status"
|
|
6255
|
-
},
|
|
6256
|
-
project_root: {
|
|
6257
|
-
type: "string",
|
|
6258
|
-
description: "Filter by project root"
|
|
6259
|
-
}
|
|
6260
|
-
}
|
|
6261
|
-
}
|
|
6262
|
-
},
|
|
6263
|
-
{
|
|
6264
|
-
name: "collab_claim_update_priority",
|
|
6265
|
-
description: "Update the priority of an existing claim. Higher priority claims take precedence in queue ordering.",
|
|
6266
|
-
inputSchema: {
|
|
6267
|
-
type: "object",
|
|
6268
|
-
properties: {
|
|
6269
|
-
session_id: {
|
|
6270
|
-
type: "string",
|
|
6271
|
-
description: "Your session ID"
|
|
6272
|
-
},
|
|
6273
|
-
claim_id: {
|
|
6274
|
-
type: "string",
|
|
6275
|
-
description: "Claim ID to update"
|
|
6276
|
-
},
|
|
6277
|
-
priority: {
|
|
6278
|
-
type: "number",
|
|
6279
|
-
description: "New priority (0-100). Levels: critical (90-100), high (70-89), normal (40-69), low (0-39)"
|
|
6280
|
-
},
|
|
6281
|
-
reason: {
|
|
6282
|
-
type: "string",
|
|
6283
|
-
description: "Optional reason for priority change"
|
|
6284
|
-
}
|
|
6285
|
-
},
|
|
6286
|
-
required: ["session_id", "claim_id", "priority"]
|
|
6287
|
-
}
|
|
6288
|
-
},
|
|
6289
|
-
{
|
|
6290
|
-
name: "collab_auto_release",
|
|
6291
|
-
description: "Automatically release claims for a file after editing. Call this after Edit/Write operations to release the claim for the edited file. For small scope claims, releases immediately. For medium/large scope claims, requires force=true or auto_release_immediate config.",
|
|
6292
|
-
inputSchema: {
|
|
6293
|
-
type: "object",
|
|
6294
|
-
properties: {
|
|
6295
|
-
session_id: {
|
|
6296
|
-
type: "string",
|
|
6297
|
-
description: "Your session ID"
|
|
6298
|
-
},
|
|
6299
|
-
file_path: {
|
|
6300
|
-
type: "string",
|
|
6301
|
-
description: "The file that was just edited"
|
|
5405
|
+
description: "Release status (for release action)"
|
|
6302
5406
|
},
|
|
6303
5407
|
force: {
|
|
6304
5408
|
type: "boolean",
|
|
6305
|
-
description: "Force release even for
|
|
5409
|
+
description: "Force release even if not owner (for release action)"
|
|
6306
5410
|
}
|
|
6307
5411
|
},
|
|
6308
|
-
required: ["
|
|
5412
|
+
required: ["action", "session_id"]
|
|
6309
5413
|
}
|
|
6310
5414
|
}
|
|
6311
5415
|
];
|
|
6312
5416
|
async function handleClaimTool(db, name, args) {
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
5417
|
+
if (name !== "collab_claim") {
|
|
5418
|
+
return createToolResult(`Unknown tool: ${name}`, true);
|
|
5419
|
+
}
|
|
5420
|
+
const action = args.action;
|
|
5421
|
+
const sessionId = args.session_id;
|
|
5422
|
+
if (!action || !sessionId) {
|
|
5423
|
+
return validationError("action and session_id are required");
|
|
5424
|
+
}
|
|
5425
|
+
switch (action) {
|
|
5426
|
+
case "create": {
|
|
5427
|
+
const files = args.files;
|
|
5428
|
+
const intent = args.intent;
|
|
5429
|
+
if (!intent) {
|
|
5430
|
+
return validationError("intent is required for create action");
|
|
5431
|
+
}
|
|
5432
|
+
if (!files || files.length === 0) {
|
|
5433
|
+
return validationError("files array is required for create action");
|
|
6318
5434
|
}
|
|
6319
|
-
const { session_id: sessionId, files, symbols, intent, scope = "medium", priority = 50 } = validation.data;
|
|
6320
|
-
const hasSymbols = symbols && symbols.length > 0;
|
|
6321
|
-
const priorityInfo = getPriorityLevel(priority);
|
|
6322
5435
|
const sessionResult = await validateActiveSession(db, sessionId);
|
|
6323
5436
|
if (!sessionResult.valid) {
|
|
6324
5437
|
return sessionResult.error;
|
|
6325
5438
|
}
|
|
6326
|
-
const allFiles = new Set(files ?? []);
|
|
6327
|
-
if (hasSymbols) {
|
|
6328
|
-
for (const sc of symbols) {
|
|
6329
|
-
allFiles.add(sc.file);
|
|
6330
|
-
}
|
|
6331
|
-
}
|
|
6332
|
-
const fileList = Array.from(allFiles);
|
|
6333
5439
|
const { claim } = await createClaim(db, {
|
|
6334
5440
|
session_id: sessionId,
|
|
6335
|
-
files
|
|
5441
|
+
files,
|
|
6336
5442
|
intent,
|
|
6337
|
-
scope,
|
|
6338
|
-
priority
|
|
6339
|
-
symbols: hasSymbols ? symbols : void 0
|
|
5443
|
+
scope: "medium",
|
|
5444
|
+
priority: 50
|
|
6340
5445
|
});
|
|
6341
5446
|
await logAuditEvent(db, {
|
|
6342
5447
|
session_id: sessionId,
|
|
6343
5448
|
action: "claim_created",
|
|
6344
5449
|
entity_type: "claim",
|
|
6345
5450
|
entity_id: claim.id,
|
|
6346
|
-
metadata: { files
|
|
5451
|
+
metadata: { files, intent }
|
|
6347
5452
|
});
|
|
6348
5453
|
await saveMemory(db, sessionId, {
|
|
6349
5454
|
category: "state",
|
|
6350
5455
|
key: `claim_${claim.id}`,
|
|
6351
5456
|
content: `Working on: ${intent}
|
|
6352
|
-
Files: ${
|
|
6353
|
-
Symbols: ${symbols.map((s) => `${s.file}:[${s.symbols.join(",")}]`).join(", ")}` : ""}`,
|
|
5457
|
+
Files: ${files.join(", ")}`,
|
|
6354
5458
|
priority: 60,
|
|
6355
5459
|
related_claim_id: claim.id,
|
|
6356
|
-
metadata: {
|
|
6357
|
-
claim_id: claim.id,
|
|
6358
|
-
files: fileList,
|
|
6359
|
-
symbols: hasSymbols ? symbols : void 0,
|
|
6360
|
-
scope
|
|
6361
|
-
}
|
|
5460
|
+
metadata: { claim_id: claim.id, files }
|
|
6362
5461
|
});
|
|
6363
|
-
const
|
|
6364
|
-
for (const file of fileList) {
|
|
6365
|
-
const lowerFile = file.toLowerCase();
|
|
6366
|
-
const isPlanLike = (lowerFile.endsWith(".md") || lowerFile.endsWith(".txt")) && (lowerFile.includes("plan") || lowerFile.includes("design") || lowerFile.includes("spec") || lowerFile.includes("proposal") || lowerFile.includes("rfc") || lowerFile.includes("adr"));
|
|
6367
|
-
if (isPlanLike) {
|
|
6368
|
-
try {
|
|
6369
|
-
await registerPlan(db, sessionId, {
|
|
6370
|
-
file_path: file,
|
|
6371
|
-
title: `Plan: ${file.split("/").pop() || file}`,
|
|
6372
|
-
content_summary: intent,
|
|
6373
|
-
status: "in_progress"
|
|
6374
|
-
});
|
|
6375
|
-
autoRegistered.push(file);
|
|
6376
|
-
} catch {
|
|
6377
|
-
}
|
|
6378
|
-
}
|
|
6379
|
-
}
|
|
6380
|
-
const conflicts = await checkConflicts(db, fileList, sessionId, symbols);
|
|
5462
|
+
const conflicts = await checkConflicts(db, files, sessionId);
|
|
6381
5463
|
if (conflicts.length > 0) {
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
entity_id: claim.id,
|
|
6388
|
-
metadata: {
|
|
6389
|
-
conflicting_session_id: conflict.session_id,
|
|
6390
|
-
conflicting_session_name: conflict.session_name ?? void 0,
|
|
6391
|
-
files: [conflict.file_path]
|
|
6392
|
-
}
|
|
6393
|
-
});
|
|
6394
|
-
}
|
|
6395
|
-
const fileConflicts = conflicts.filter((c) => c.conflict_level === "file");
|
|
6396
|
-
const symbolConflicts = conflicts.filter((c) => c.conflict_level === "symbol");
|
|
6397
|
-
const conflictDetails = {
|
|
6398
|
-
file_level: fileConflicts.map((c) => ({
|
|
5464
|
+
return successResponse({
|
|
5465
|
+
claim_id: claim.id,
|
|
5466
|
+
status: "created_with_conflicts",
|
|
5467
|
+
files,
|
|
5468
|
+
conflicts: conflicts.map((c) => ({
|
|
6399
5469
|
session_name: c.session_name,
|
|
6400
5470
|
file: c.file_path,
|
|
6401
5471
|
intent: c.intent
|
|
6402
5472
|
})),
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
file: c.file_path,
|
|
6406
|
-
symbol: c.symbol_name,
|
|
6407
|
-
symbol_type: c.symbol_type,
|
|
6408
|
-
intent: c.intent
|
|
6409
|
-
}))
|
|
6410
|
-
};
|
|
6411
|
-
return createToolResult(
|
|
6412
|
-
JSON.stringify(
|
|
6413
|
-
{
|
|
6414
|
-
claim_id: claim.id,
|
|
6415
|
-
status: "created_with_conflicts",
|
|
6416
|
-
files: fileList,
|
|
6417
|
-
symbols: hasSymbols ? symbols : void 0,
|
|
6418
|
-
conflicts: conflictDetails,
|
|
6419
|
-
warning: `\u26A0\uFE0F Conflicts detected: ${fileConflicts.length} file-level, ${symbolConflicts.length} symbol-level. Coordinate before proceeding.`,
|
|
6420
|
-
auto_registered_plans: autoRegistered.length > 0 ? autoRegistered : void 0
|
|
6421
|
-
},
|
|
6422
|
-
null,
|
|
6423
|
-
2
|
|
6424
|
-
)
|
|
6425
|
-
);
|
|
5473
|
+
warning: `\u26A0\uFE0F ${conflicts.length} conflict(s) detected. Coordinate before proceeding.`
|
|
5474
|
+
});
|
|
6426
5475
|
}
|
|
6427
|
-
return
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
scope,
|
|
6435
|
-
priority: priorityInfo,
|
|
6436
|
-
message: hasSymbols ? "Symbol-level claim created. Other sessions can work on different symbols in the same file." : "Claim created successfully. Other sessions will be warned about these files.",
|
|
6437
|
-
auto_registered_plans: autoRegistered.length > 0 ? autoRegistered : void 0,
|
|
6438
|
-
auto_registered_note: autoRegistered.length > 0 ? `Auto-protected ${autoRegistered.length} plan file(s). Use collab_plan_update_status to manage lifecycle.` : void 0
|
|
6439
|
-
})
|
|
6440
|
-
);
|
|
5476
|
+
return successResponse({
|
|
5477
|
+
claim_id: claim.id,
|
|
5478
|
+
status: "created",
|
|
5479
|
+
files,
|
|
5480
|
+
intent,
|
|
5481
|
+
message: "Claim created successfully."
|
|
5482
|
+
});
|
|
6441
5483
|
}
|
|
6442
|
-
case "
|
|
6443
|
-
const
|
|
6444
|
-
if (!
|
|
6445
|
-
return validationError(
|
|
6446
|
-
}
|
|
6447
|
-
const { files, symbols, session_id: sessionId } = validation.data;
|
|
6448
|
-
let hasInProgressTodo = false;
|
|
6449
|
-
let todosStatus = null;
|
|
6450
|
-
if (sessionId) {
|
|
6451
|
-
const session = await getSession(db, sessionId);
|
|
6452
|
-
if (session?.todos) {
|
|
6453
|
-
try {
|
|
6454
|
-
const todos = JSON.parse(session.todos);
|
|
6455
|
-
const inProgress = todos.filter((t) => t.status === "in_progress").length;
|
|
6456
|
-
const completed = todos.filter((t) => t.status === "completed").length;
|
|
6457
|
-
const pending = todos.filter((t) => t.status === "pending").length;
|
|
6458
|
-
hasInProgressTodo = inProgress > 0;
|
|
6459
|
-
todosStatus = {
|
|
6460
|
-
total: todos.length,
|
|
6461
|
-
in_progress: inProgress,
|
|
6462
|
-
completed,
|
|
6463
|
-
pending
|
|
6464
|
-
};
|
|
6465
|
-
} catch {
|
|
6466
|
-
}
|
|
6467
|
-
}
|
|
6468
|
-
}
|
|
6469
|
-
const hasSymbols = symbols && Array.isArray(symbols) && symbols.length > 0;
|
|
6470
|
-
const conflicts = await checkConflicts(db, files, sessionId, hasSymbols ? symbols : void 0);
|
|
6471
|
-
const blockedFiles = /* @__PURE__ */ new Set();
|
|
6472
|
-
const blockedSymbols = /* @__PURE__ */ new Map();
|
|
6473
|
-
for (const c of conflicts) {
|
|
6474
|
-
if (c.conflict_level === "file") {
|
|
6475
|
-
blockedFiles.add(c.file_path);
|
|
6476
|
-
} else if (c.conflict_level === "symbol" && c.symbol_name) {
|
|
6477
|
-
const existing = blockedSymbols.get(c.file_path) ?? /* @__PURE__ */ new Set();
|
|
6478
|
-
existing.add(c.symbol_name);
|
|
6479
|
-
blockedSymbols.set(c.file_path, existing);
|
|
6480
|
-
}
|
|
6481
|
-
}
|
|
6482
|
-
let safeSymbols = [];
|
|
6483
|
-
let blockedSymbolsList = [];
|
|
6484
|
-
if (hasSymbols) {
|
|
6485
|
-
for (const sc of symbols) {
|
|
6486
|
-
const blocked = blockedSymbols.get(sc.file) ?? /* @__PURE__ */ new Set();
|
|
6487
|
-
const safe = sc.symbols.filter((s) => !blocked.has(s));
|
|
6488
|
-
const blockedList = sc.symbols.filter((s) => blocked.has(s));
|
|
6489
|
-
if (safe.length > 0) {
|
|
6490
|
-
safeSymbols.push({ file: sc.file, symbols: safe });
|
|
6491
|
-
}
|
|
6492
|
-
if (blockedList.length > 0) {
|
|
6493
|
-
blockedSymbolsList.push({ file: sc.file, symbols: blockedList });
|
|
6494
|
-
}
|
|
6495
|
-
}
|
|
6496
|
-
}
|
|
6497
|
-
const safeFiles = files.filter((f) => !blockedFiles.has(f) && !blockedSymbols.has(f));
|
|
6498
|
-
const blockedFilesList = files.filter((f) => blockedFiles.has(f));
|
|
6499
|
-
let recommendation;
|
|
6500
|
-
let canEdit;
|
|
6501
|
-
const hasSafeContent = safeFiles.length > 0 || safeSymbols.length > 0;
|
|
6502
|
-
if (conflicts.length === 0) {
|
|
6503
|
-
recommendation = "proceed_all";
|
|
6504
|
-
canEdit = true;
|
|
6505
|
-
} else if (hasSafeContent) {
|
|
6506
|
-
recommendation = "proceed_safe_only";
|
|
6507
|
-
canEdit = true;
|
|
6508
|
-
} else {
|
|
6509
|
-
recommendation = "abort";
|
|
6510
|
-
canEdit = false;
|
|
5484
|
+
case "check": {
|
|
5485
|
+
const files = args.files;
|
|
5486
|
+
if (!files || files.length === 0) {
|
|
5487
|
+
return validationError("files array is required for check action");
|
|
6511
5488
|
}
|
|
5489
|
+
const conflicts = await checkConflicts(db, files, sessionId);
|
|
6512
5490
|
if (conflicts.length === 0) {
|
|
6513
|
-
return
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
safe: files,
|
|
6521
|
-
blocked: []
|
|
6522
|
-
},
|
|
6523
|
-
symbol_status: hasSymbols ? { safe: symbols, blocked: [] } : void 0,
|
|
6524
|
-
message: hasSymbols ? "All symbols are safe to edit. Proceed." : "All files are safe to edit. Proceed.",
|
|
6525
|
-
has_in_progress_todo: hasInProgressTodo,
|
|
6526
|
-
todos_status: todosStatus
|
|
6527
|
-
})
|
|
6528
|
-
);
|
|
6529
|
-
}
|
|
6530
|
-
const bySession = /* @__PURE__ */ new Map();
|
|
6531
|
-
for (const c of conflicts) {
|
|
6532
|
-
const key = c.session_id;
|
|
6533
|
-
const existing = bySession.get(key) ?? [];
|
|
6534
|
-
existing.push(c);
|
|
6535
|
-
bySession.set(key, existing);
|
|
5491
|
+
return successResponse({
|
|
5492
|
+
has_conflicts: false,
|
|
5493
|
+
can_edit: true,
|
|
5494
|
+
recommendation: "proceed",
|
|
5495
|
+
safe_files: files,
|
|
5496
|
+
message: "All files are safe to edit. Proceed."
|
|
5497
|
+
});
|
|
6536
5498
|
}
|
|
6537
|
-
const
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
|
|
6548
|
-
|
|
6549
|
-
|
|
6550
|
-
|
|
6551
|
-
started_at: items[0].created_at
|
|
6552
|
-
};
|
|
5499
|
+
const blockedFiles = new Set(conflicts.map((c) => c.file_path));
|
|
5500
|
+
const safeFiles = files.filter((f) => !blockedFiles.has(f));
|
|
5501
|
+
return successResponse({
|
|
5502
|
+
has_conflicts: true,
|
|
5503
|
+
can_edit: safeFiles.length > 0,
|
|
5504
|
+
recommendation: safeFiles.length > 0 ? "proceed_safe_only" : "abort",
|
|
5505
|
+
safe_files: safeFiles,
|
|
5506
|
+
blocked_files: Array.from(blockedFiles),
|
|
5507
|
+
conflicts: conflicts.map((c) => ({
|
|
5508
|
+
session_name: c.session_name,
|
|
5509
|
+
file: c.file_path,
|
|
5510
|
+
intent: c.intent
|
|
5511
|
+
})),
|
|
5512
|
+
message: safeFiles.length > 0 ? `Edit ONLY safe files: [${safeFiles.join(", ")}]. Skip blocked files.` : "All files blocked. Coordinate with other session(s)."
|
|
6553
5513
|
});
|
|
6554
|
-
let message;
|
|
6555
|
-
if (recommendation === "proceed_safe_only") {
|
|
6556
|
-
if (hasSymbols && safeSymbols.length > 0) {
|
|
6557
|
-
const safeDesc = safeSymbols.map((s) => `${s.file}:[${s.symbols.join(",")}]`).join(", ");
|
|
6558
|
-
const blockedDesc = blockedSymbolsList.map((s) => `${s.file}:[${s.symbols.join(",")}]`).join(", ");
|
|
6559
|
-
message = `Edit ONLY these safe symbols: ${safeDesc}. Skip blocked: ${blockedDesc}.`;
|
|
6560
|
-
} else {
|
|
6561
|
-
message = `Edit ONLY these safe files: [${safeFiles.join(", ")}]. Skip blocked files: [${blockedFilesList.join(", ")}].`;
|
|
6562
|
-
}
|
|
6563
|
-
} else {
|
|
6564
|
-
message = hasSymbols ? `All requested symbols are blocked. Coordinate with other session(s) or wait.` : `All ${files.length} file(s) are blocked. Coordinate with other session(s) or wait.`;
|
|
6565
|
-
}
|
|
6566
|
-
return createToolResult(
|
|
6567
|
-
JSON.stringify(
|
|
6568
|
-
{
|
|
6569
|
-
has_conflicts: true,
|
|
6570
|
-
safe: false,
|
|
6571
|
-
can_edit: canEdit,
|
|
6572
|
-
recommendation,
|
|
6573
|
-
file_status: {
|
|
6574
|
-
safe: safeFiles,
|
|
6575
|
-
blocked: blockedFilesList
|
|
6576
|
-
},
|
|
6577
|
-
symbol_status: hasSymbols ? {
|
|
6578
|
-
safe: safeSymbols,
|
|
6579
|
-
blocked: blockedSymbolsList
|
|
6580
|
-
} : void 0,
|
|
6581
|
-
conflicts: conflictDetails,
|
|
6582
|
-
message,
|
|
6583
|
-
has_in_progress_todo: hasInProgressTodo,
|
|
6584
|
-
todos_status: todosStatus
|
|
6585
|
-
},
|
|
6586
|
-
null,
|
|
6587
|
-
2
|
|
6588
|
-
)
|
|
6589
|
-
);
|
|
6590
5514
|
}
|
|
6591
|
-
case "
|
|
6592
|
-
const
|
|
6593
|
-
|
|
6594
|
-
|
|
5515
|
+
case "release": {
|
|
5516
|
+
const claimId = args.claim_id;
|
|
5517
|
+
const status = args.status || "completed";
|
|
5518
|
+
const force = args.force;
|
|
5519
|
+
if (!claimId) {
|
|
5520
|
+
return validationError("claim_id is required for release action");
|
|
6595
5521
|
}
|
|
6596
|
-
const { session_id: sessionId, claim_id: claimId, status, summary, force } = validation.data;
|
|
6597
5522
|
const claim = await getClaim(db, claimId);
|
|
6598
5523
|
if (!claim) {
|
|
6599
|
-
return errorResponse(
|
|
6600
|
-
ERROR_CODES.CLAIM_NOT_FOUND,
|
|
6601
|
-
"Claim not found. It may have already been released."
|
|
6602
|
-
);
|
|
5524
|
+
return errorResponse(ERROR_CODES.CLAIM_NOT_FOUND, "Claim not found");
|
|
6603
5525
|
}
|
|
6604
|
-
if (claim.session_id !== sessionId) {
|
|
5526
|
+
if (claim.session_id !== sessionId && !force) {
|
|
6605
5527
|
const callerSession = await getSession(db, sessionId);
|
|
6606
5528
|
let config = DEFAULT_SESSION_CONFIG;
|
|
6607
5529
|
if (callerSession?.config) {
|
|
@@ -6610,36 +5532,17 @@ Symbols: ${symbols.map((s) => `${s.file}:[${s.symbols.join(",")}]`).join(", ")}`
|
|
|
6610
5532
|
} catch {
|
|
6611
5533
|
}
|
|
6612
5534
|
}
|
|
6613
|
-
|
|
6614
|
-
|
|
6615
|
-
|
|
6616
|
-
|
|
6617
|
-
const isStale = claimAge > staleHours * 60 * 60 * 1e3;
|
|
6618
|
-
return createToolResult(
|
|
6619
|
-
JSON.stringify({
|
|
6620
|
-
error: "NOT_OWNER",
|
|
6621
|
-
message: "You can only release your own claims. This claim belongs to another session.",
|
|
6622
|
-
claim_owner: claim.session_name,
|
|
6623
|
-
claim_age_hours: Math.round(claimAge / (60 * 60 * 1e3) * 10) / 10,
|
|
6624
|
-
is_stale: isStale,
|
|
6625
|
-
suggestions: [
|
|
6626
|
-
"Use collab_message_send to ask the owner to release it.",
|
|
6627
|
-
isStale ? "This claim is stale. Ask user for confirmation, then use force=true to release." : null,
|
|
6628
|
-
"Use collab_config to enable allow_release_others for future releases."
|
|
6629
|
-
].filter(Boolean)
|
|
6630
|
-
}),
|
|
6631
|
-
true
|
|
5535
|
+
if (!config.allow_release_others) {
|
|
5536
|
+
return errorResponse(
|
|
5537
|
+
ERROR_CODES.NOT_OWNER,
|
|
5538
|
+
`Not your claim. Owner: ${claim.session_name}. Use force=true to override.`
|
|
6632
5539
|
);
|
|
6633
5540
|
}
|
|
6634
5541
|
}
|
|
6635
5542
|
if (claim.status !== "active") {
|
|
6636
|
-
return errorResponse(
|
|
6637
|
-
ERROR_CODES.CLAIM_ALREADY_RELEASED,
|
|
6638
|
-
`Claim was already ${claim.status}.`
|
|
6639
|
-
);
|
|
5543
|
+
return errorResponse(ERROR_CODES.CLAIM_ALREADY_RELEASED, `Claim already ${claim.status}`);
|
|
6640
5544
|
}
|
|
6641
|
-
|
|
6642
|
-
await releaseClaim(db, claimId, { status, summary });
|
|
5545
|
+
await releaseClaim(db, claimId, { status });
|
|
6643
5546
|
await logAuditEvent(db, {
|
|
6644
5547
|
session_id: sessionId,
|
|
6645
5548
|
action: "claim_released",
|
|
@@ -6648,211 +5551,72 @@ Symbols: ${symbols.map((s) => `${s.file}:[${s.symbols.join(",")}]`).join(", ")}`
|
|
|
6648
5551
|
metadata: { status, files: claim.files }
|
|
6649
5552
|
});
|
|
6650
5553
|
await clearMemory(db, claim.session_id, { key: `claim_${claimId}` });
|
|
6651
|
-
if (status === "completed" && summary) {
|
|
6652
|
-
await saveMemory(db, sessionId, {
|
|
6653
|
-
category: "decision",
|
|
6654
|
-
key: `completed_${claimId}`,
|
|
6655
|
-
content: `Completed: ${claim.intent}
|
|
6656
|
-
Summary: ${summary}
|
|
6657
|
-
Files: ${claim.files.join(", ")}`,
|
|
6658
|
-
priority: 50,
|
|
6659
|
-
metadata: {
|
|
6660
|
-
claim_id: claimId,
|
|
6661
|
-
files: claim.files,
|
|
6662
|
-
completed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
6663
|
-
}
|
|
6664
|
-
});
|
|
6665
|
-
}
|
|
6666
|
-
const notifiedCount = await notifyQueueOnClaimRelease(db, claimId, sessionId, claim.files);
|
|
6667
|
-
return createToolResult(
|
|
6668
|
-
JSON.stringify({
|
|
6669
|
-
success: true,
|
|
6670
|
-
message: isOwnClaim ? `Claim ${status}. Files are now available for other sessions.` : `Claim from ${claim.session_name} forcefully ${status}.`,
|
|
6671
|
-
files: claim.files,
|
|
6672
|
-
summary: summary ?? null,
|
|
6673
|
-
was_forced: !isOwnClaim,
|
|
6674
|
-
notified_sessions: notifiedCount
|
|
6675
|
-
})
|
|
6676
|
-
);
|
|
6677
|
-
}
|
|
6678
|
-
case "collab_auto_release": {
|
|
6679
|
-
const validation = validateInput(autoReleaseSchema, args);
|
|
6680
|
-
if (!validation.success) {
|
|
6681
|
-
return validationError(validation.error);
|
|
6682
|
-
}
|
|
6683
|
-
const { session_id: sessionId, file_path: filePath, force } = validation.data;
|
|
6684
|
-
const sessionResult = await validateActiveSession(db, sessionId);
|
|
6685
|
-
if (!sessionResult.valid) {
|
|
6686
|
-
return sessionResult.error;
|
|
6687
|
-
}
|
|
6688
|
-
const session = await getSession(db, sessionId);
|
|
6689
|
-
let config = DEFAULT_SESSION_CONFIG;
|
|
6690
|
-
if (session?.config) {
|
|
6691
|
-
try {
|
|
6692
|
-
config = { ...DEFAULT_SESSION_CONFIG, ...JSON.parse(session.config) };
|
|
6693
|
-
} catch {
|
|
6694
|
-
}
|
|
6695
|
-
}
|
|
6696
|
-
const claimInfo = await getClaimInfoByFile(db, sessionId, filePath);
|
|
6697
|
-
if (!claimInfo) {
|
|
6698
|
-
return successResponse({
|
|
6699
|
-
released: false,
|
|
6700
|
-
message: "No active claim found for this file",
|
|
6701
|
-
file_path: filePath
|
|
6702
|
-
});
|
|
6703
|
-
}
|
|
6704
|
-
const isPartialRelease = claimInfo.file_count > 1;
|
|
6705
|
-
const shouldAutoRelease = isPartialRelease || claimInfo.scope === "small" || force === true || config.auto_release_immediate;
|
|
6706
|
-
if (!shouldAutoRelease) {
|
|
6707
|
-
return successResponse({
|
|
6708
|
-
released: false,
|
|
6709
|
-
claim_id: claimInfo.claim_id,
|
|
6710
|
-
scope: claimInfo.scope,
|
|
6711
|
-
file_path: filePath,
|
|
6712
|
-
file_count: claimInfo.file_count,
|
|
6713
|
-
message: `Claim has ${claimInfo.scope} scope. Use force=true or enable auto_release_immediate config to auto-release.`,
|
|
6714
|
-
suggestions: [
|
|
6715
|
-
"Use collab_release with claim_id to release the full claim",
|
|
6716
|
-
"Use collab_config to enable auto_release_immediate",
|
|
6717
|
-
"Use force=true to force auto-release"
|
|
6718
|
-
]
|
|
6719
|
-
});
|
|
6720
|
-
}
|
|
6721
|
-
const result = await releaseClaimByFile(db, sessionId, filePath);
|
|
6722
|
-
if (!result.released) {
|
|
6723
|
-
return errorResponse(
|
|
6724
|
-
ERROR_CODES.RELEASE_FAILED,
|
|
6725
|
-
"Failed to release claim. It may have been released by another operation."
|
|
6726
|
-
);
|
|
6727
|
-
}
|
|
6728
|
-
await logAuditEvent(db, {
|
|
6729
|
-
session_id: sessionId,
|
|
6730
|
-
action: "claim_released",
|
|
6731
|
-
entity_type: "claim",
|
|
6732
|
-
entity_id: result.claim_id,
|
|
6733
|
-
metadata: {
|
|
6734
|
-
status: result.partial ? "partial" : "completed",
|
|
6735
|
-
files: [filePath],
|
|
6736
|
-
auto_release: true,
|
|
6737
|
-
partial: result.partial,
|
|
6738
|
-
files_remaining: result.files_remaining
|
|
6739
|
-
}
|
|
6740
|
-
});
|
|
6741
|
-
let notifiedCount = 0;
|
|
6742
|
-
if (!result.partial) {
|
|
6743
|
-
notifiedCount = await notifyQueueOnClaimRelease(db, result.claim_id, sessionId, claimInfo.files);
|
|
6744
|
-
}
|
|
6745
5554
|
return successResponse({
|
|
6746
|
-
|
|
6747
|
-
claim_id:
|
|
6748
|
-
|
|
6749
|
-
|
|
6750
|
-
files_remaining: result.files_remaining,
|
|
6751
|
-
notified_sessions: notifiedCount,
|
|
6752
|
-
message: result.partial ? `File released from claim. ${result.files_remaining} file(s) remaining in claim.` : "Claim released successfully."
|
|
5555
|
+
success: true,
|
|
5556
|
+
claim_id: claimId,
|
|
5557
|
+
files: claim.files,
|
|
5558
|
+
message: `Claim ${status}. Files now available.`
|
|
6753
5559
|
});
|
|
6754
5560
|
}
|
|
6755
|
-
case "
|
|
6756
|
-
const
|
|
6757
|
-
if (!validation.success) {
|
|
6758
|
-
return validationError(validation.error);
|
|
6759
|
-
}
|
|
6760
|
-
const { session_id, status = "active", project_root } = validation.data;
|
|
6761
|
-
const claims = await listClaims(db, { session_id, status, project_root });
|
|
5561
|
+
case "list": {
|
|
5562
|
+
const claims = await listClaims(db, { session_id: sessionId, status: "active" });
|
|
6762
5563
|
return successResponse({
|
|
6763
5564
|
claims: claims.map((c) => ({
|
|
6764
5565
|
id: c.id,
|
|
6765
|
-
session_id: c.session_id,
|
|
6766
5566
|
session_name: c.session_name,
|
|
6767
5567
|
files: c.files,
|
|
6768
5568
|
intent: c.intent,
|
|
6769
|
-
scope: c.scope,
|
|
6770
5569
|
priority: getPriorityLevel(c.priority),
|
|
6771
|
-
|
|
6772
|
-
created_at: c.created_at,
|
|
6773
|
-
completed_summary: c.completed_summary
|
|
5570
|
+
created_at: c.created_at
|
|
6774
5571
|
})),
|
|
6775
5572
|
total: claims.length
|
|
6776
|
-
}, true);
|
|
6777
|
-
}
|
|
6778
|
-
case "collab_claim_update_priority": {
|
|
6779
|
-
const validation = validateInput(claimUpdatePrioritySchema, args);
|
|
6780
|
-
if (!validation.success) {
|
|
6781
|
-
return validationError(validation.error);
|
|
6782
|
-
}
|
|
6783
|
-
const { session_id: sessionId, claim_id: claimId, priority, reason } = validation.data;
|
|
6784
|
-
const sessionResult = await validateActiveSession(db, sessionId);
|
|
6785
|
-
if (!sessionResult.valid) {
|
|
6786
|
-
return sessionResult.error;
|
|
6787
|
-
}
|
|
6788
|
-
const claim = await getClaim(db, claimId);
|
|
6789
|
-
if (!claim) {
|
|
6790
|
-
return errorResponse(ERROR_CODES.CLAIM_NOT_FOUND, "Claim not found");
|
|
6791
|
-
}
|
|
6792
|
-
if (claim.session_id !== sessionId) {
|
|
6793
|
-
return errorResponse(ERROR_CODES.NOT_OWNER, "You can only update priority of your own claims");
|
|
6794
|
-
}
|
|
6795
|
-
if (claim.status !== "active") {
|
|
6796
|
-
return errorResponse(ERROR_CODES.CLAIM_ALREADY_RELEASED, `Claim is ${claim.status}, cannot update priority`);
|
|
6797
|
-
}
|
|
6798
|
-
const oldPriority = getPriorityLevel(claim.priority);
|
|
6799
|
-
const newPriority = getPriorityLevel(priority);
|
|
6800
|
-
const updated = await updateClaimPriority(db, claimId, priority);
|
|
6801
|
-
if (!updated) {
|
|
6802
|
-
return errorResponse(ERROR_CODES.CLAIM_NOT_FOUND, "Failed to update priority");
|
|
6803
|
-
}
|
|
6804
|
-
await logAuditEvent(db, {
|
|
6805
|
-
session_id: sessionId,
|
|
6806
|
-
action: "priority_changed",
|
|
6807
|
-
entity_type: "claim",
|
|
6808
|
-
entity_id: claimId,
|
|
6809
|
-
metadata: {
|
|
6810
|
-
old_value: claim.priority,
|
|
6811
|
-
new_value: priority,
|
|
6812
|
-
reason
|
|
6813
|
-
}
|
|
6814
|
-
});
|
|
6815
|
-
return successResponse({
|
|
6816
|
-
success: true,
|
|
6817
|
-
claim_id: claimId,
|
|
6818
|
-
old_priority: oldPriority,
|
|
6819
|
-
new_priority: newPriority,
|
|
6820
|
-
reason: reason ?? null,
|
|
6821
|
-
message: `Priority updated from ${oldPriority.level} (${oldPriority.value}) to ${newPriority.level} (${newPriority.value})`
|
|
6822
5573
|
});
|
|
6823
5574
|
}
|
|
6824
5575
|
default:
|
|
6825
|
-
return createToolResult(`Unknown
|
|
5576
|
+
return createToolResult(`Unknown action: ${action}`, true);
|
|
6826
5577
|
}
|
|
6827
5578
|
}
|
|
6828
5579
|
|
|
6829
|
-
// src/mcp/tools/
|
|
6830
|
-
var
|
|
5580
|
+
// src/mcp/tools/memory.ts
|
|
5581
|
+
var memoryTools = [
|
|
6831
5582
|
{
|
|
6832
|
-
name: "
|
|
6833
|
-
description:
|
|
5583
|
+
name: "collab_memory_save",
|
|
5584
|
+
description: `Save or update context to working memory (upsert). Use to persist findings, decisions, or state that should survive context compaction.`,
|
|
6834
5585
|
inputSchema: {
|
|
6835
5586
|
type: "object",
|
|
6836
5587
|
properties: {
|
|
6837
|
-
|
|
5588
|
+
session_id: {
|
|
6838
5589
|
type: "string",
|
|
6839
5590
|
description: "Your session ID"
|
|
6840
5591
|
},
|
|
6841
|
-
|
|
5592
|
+
category: {
|
|
5593
|
+
type: "string",
|
|
5594
|
+
enum: ["finding", "decision", "state", "todo", "important", "context"],
|
|
5595
|
+
description: "Category: finding, decision, state, todo, important, context"
|
|
5596
|
+
},
|
|
5597
|
+
key: {
|
|
6842
5598
|
type: "string",
|
|
6843
|
-
description:
|
|
5599
|
+
description: 'Unique identifier (e.g., "auth_bug_root_cause")'
|
|
6844
5600
|
},
|
|
6845
5601
|
content: {
|
|
6846
5602
|
type: "string",
|
|
6847
|
-
description: "
|
|
5603
|
+
description: "The content to remember"
|
|
5604
|
+
},
|
|
5605
|
+
priority: {
|
|
5606
|
+
type: "number",
|
|
5607
|
+
description: "Priority 0-100 (default 50). Higher = more important."
|
|
5608
|
+
},
|
|
5609
|
+
pinned: {
|
|
5610
|
+
type: "boolean",
|
|
5611
|
+
description: "If true, always loaded when recalling active memories."
|
|
6848
5612
|
}
|
|
6849
5613
|
},
|
|
6850
|
-
required: ["
|
|
5614
|
+
required: ["session_id", "category", "key", "content"]
|
|
6851
5615
|
}
|
|
6852
5616
|
},
|
|
6853
5617
|
{
|
|
6854
|
-
name: "
|
|
6855
|
-
description:
|
|
5618
|
+
name: "collab_memory_recall",
|
|
5619
|
+
description: `Recall memories. Use active=true to get pinned + high priority memories for context restoration.`,
|
|
6856
5620
|
inputSchema: {
|
|
6857
5621
|
type: "object",
|
|
6858
5622
|
properties: {
|
|
@@ -6860,2477 +5624,371 @@ var messageTools = [
|
|
|
6860
5624
|
type: "string",
|
|
6861
5625
|
description: "Your session ID"
|
|
6862
5626
|
},
|
|
6863
|
-
|
|
5627
|
+
active: {
|
|
6864
5628
|
type: "boolean",
|
|
6865
|
-
description: "
|
|
5629
|
+
description: "If true, return pinned + high priority memories only (default behavior for context restoration)"
|
|
5630
|
+
},
|
|
5631
|
+
category: {
|
|
5632
|
+
type: "string",
|
|
5633
|
+
enum: ["finding", "decision", "state", "todo", "important", "context"],
|
|
5634
|
+
description: "Filter by category"
|
|
5635
|
+
},
|
|
5636
|
+
key: {
|
|
5637
|
+
type: "string",
|
|
5638
|
+
description: "Get a specific memory by key"
|
|
5639
|
+
}
|
|
5640
|
+
},
|
|
5641
|
+
required: ["session_id"]
|
|
5642
|
+
}
|
|
5643
|
+
},
|
|
5644
|
+
{
|
|
5645
|
+
name: "collab_memory_clear",
|
|
5646
|
+
description: "Clear memories. Specify key, category, or clear_all.",
|
|
5647
|
+
inputSchema: {
|
|
5648
|
+
type: "object",
|
|
5649
|
+
properties: {
|
|
5650
|
+
session_id: {
|
|
5651
|
+
type: "string",
|
|
5652
|
+
description: "Your session ID"
|
|
6866
5653
|
},
|
|
6867
|
-
|
|
5654
|
+
key: {
|
|
5655
|
+
type: "string",
|
|
5656
|
+
description: "Clear specific memory by key"
|
|
5657
|
+
},
|
|
5658
|
+
category: {
|
|
5659
|
+
type: "string",
|
|
5660
|
+
enum: ["finding", "decision", "state", "todo", "important", "context"],
|
|
5661
|
+
description: "Clear all in category"
|
|
5662
|
+
},
|
|
5663
|
+
clear_all: {
|
|
6868
5664
|
type: "boolean",
|
|
6869
|
-
description: "
|
|
5665
|
+
description: "Clear ALL memories (use with caution)"
|
|
6870
5666
|
}
|
|
6871
5667
|
},
|
|
6872
5668
|
required: ["session_id"]
|
|
6873
5669
|
}
|
|
6874
5670
|
}
|
|
6875
5671
|
];
|
|
6876
|
-
async function
|
|
5672
|
+
async function handleMemoryTool(db, name, args) {
|
|
5673
|
+
const sessionId = args.session_id;
|
|
5674
|
+
if (!sessionId) {
|
|
5675
|
+
return validationError("session_id is required");
|
|
5676
|
+
}
|
|
5677
|
+
const sessionCheck = await validateActiveSession(db, sessionId);
|
|
5678
|
+
if (!sessionCheck.valid) {
|
|
5679
|
+
return sessionCheck.error;
|
|
5680
|
+
}
|
|
6877
5681
|
switch (name) {
|
|
6878
|
-
case "
|
|
6879
|
-
const
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
|
|
6900
|
-
content: input.content
|
|
5682
|
+
case "collab_memory_save": {
|
|
5683
|
+
const category = args.category;
|
|
5684
|
+
const key = args.key;
|
|
5685
|
+
const content = args.content;
|
|
5686
|
+
if (!category || !key || !content) {
|
|
5687
|
+
return validationError("category, key, and content are required");
|
|
5688
|
+
}
|
|
5689
|
+
const memory = await saveMemory(db, sessionId, {
|
|
5690
|
+
category,
|
|
5691
|
+
key,
|
|
5692
|
+
content,
|
|
5693
|
+
priority: args.priority ?? 50,
|
|
5694
|
+
pinned: args.pinned ?? false
|
|
5695
|
+
});
|
|
5696
|
+
return successResponse({
|
|
5697
|
+
saved: true,
|
|
5698
|
+
id: memory.id,
|
|
5699
|
+
category: memory.category,
|
|
5700
|
+
key: memory.key,
|
|
5701
|
+
priority: memory.priority,
|
|
5702
|
+
pinned: memory.pinned === 1,
|
|
5703
|
+
message: `Memory saved: ${category}/${key}`
|
|
6901
5704
|
});
|
|
6902
|
-
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
reference_id: message.id,
|
|
6912
|
-
metadata: { from_session_id: input.from_session_id, from_session_name: senderName }
|
|
5705
|
+
}
|
|
5706
|
+
case "collab_memory_recall": {
|
|
5707
|
+
const active = args.active;
|
|
5708
|
+
const category = args.category;
|
|
5709
|
+
const key = args.key;
|
|
5710
|
+
if (active) {
|
|
5711
|
+
const memories2 = await getActiveMemories(db, sessionId, {
|
|
5712
|
+
priority_threshold: 70,
|
|
5713
|
+
max_items: 20
|
|
6913
5714
|
});
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
metadata: { from_session_id: input.from_session_id, from_session_name: senderName, is_broadcast: true }
|
|
6926
|
-
});
|
|
5715
|
+
const formatted2 = memories2.map((m) => ({
|
|
5716
|
+
category: m.category,
|
|
5717
|
+
key: m.key,
|
|
5718
|
+
content: m.content,
|
|
5719
|
+
priority: m.priority,
|
|
5720
|
+
pinned: m.pinned === 1
|
|
5721
|
+
}));
|
|
5722
|
+
const byCategory = {};
|
|
5723
|
+
for (const mem of formatted2) {
|
|
5724
|
+
if (!byCategory[mem.category]) {
|
|
5725
|
+
byCategory[mem.category] = [];
|
|
6927
5726
|
}
|
|
5727
|
+
byCategory[mem.category].push({ key: mem.key, content: mem.content });
|
|
6928
5728
|
}
|
|
5729
|
+
return successResponse({
|
|
5730
|
+
count: formatted2.length,
|
|
5731
|
+
by_category: byCategory,
|
|
5732
|
+
message: `Active memories: ${formatted2.length} items`
|
|
5733
|
+
});
|
|
6929
5734
|
}
|
|
5735
|
+
const memories = await recallMemory(db, sessionId, { category, key });
|
|
5736
|
+
const formatted = memories.map((m) => ({
|
|
5737
|
+
category: m.category,
|
|
5738
|
+
key: m.key,
|
|
5739
|
+
content: m.content,
|
|
5740
|
+
priority: m.priority,
|
|
5741
|
+
pinned: m.pinned === 1
|
|
5742
|
+
}));
|
|
6930
5743
|
return successResponse({
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
sent_to: input.to_session_id ?? "all sessions (broadcast)",
|
|
6934
|
-
message: "Message sent successfully."
|
|
5744
|
+
count: formatted.length,
|
|
5745
|
+
memories: formatted
|
|
6935
5746
|
});
|
|
6936
5747
|
}
|
|
6937
|
-
case "
|
|
6938
|
-
const
|
|
6939
|
-
|
|
6940
|
-
|
|
5748
|
+
case "collab_memory_clear": {
|
|
5749
|
+
const key = args.key;
|
|
5750
|
+
const category = args.category;
|
|
5751
|
+
const clearAll = args.clear_all;
|
|
5752
|
+
if (!key && !category && !clearAll) {
|
|
5753
|
+
return validationError("One of key, category, or clear_all is required");
|
|
6941
5754
|
}
|
|
6942
|
-
const
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
session_id: input.session_id,
|
|
6947
|
-
unread_only: unreadOnly,
|
|
6948
|
-
mark_as_read: markAsRead
|
|
5755
|
+
const cleared = await clearMemory(db, sessionId, {
|
|
5756
|
+
key,
|
|
5757
|
+
category,
|
|
5758
|
+
clear_all: clearAll
|
|
6949
5759
|
});
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
}
|
|
5760
|
+
let message;
|
|
5761
|
+
if (key) {
|
|
5762
|
+
message = cleared > 0 ? `Memory cleared: ${key}` : `Memory not found: ${key}`;
|
|
5763
|
+
} else if (category) {
|
|
5764
|
+
message = `Cleared ${cleared} memories from category: ${category}`;
|
|
5765
|
+
} else {
|
|
5766
|
+
message = `Cleared all ${cleared} memories`;
|
|
6955
5767
|
}
|
|
6956
|
-
return successResponse({
|
|
6957
|
-
messages: messages.map((m) => ({
|
|
6958
|
-
id: m.id,
|
|
6959
|
-
from_session_id: m.from_session_id,
|
|
6960
|
-
content: m.content,
|
|
6961
|
-
created_at: m.created_at,
|
|
6962
|
-
is_broadcast: m.to_session_id === null
|
|
6963
|
-
})),
|
|
6964
|
-
total: messages.length,
|
|
6965
|
-
marked_as_read: markAsRead
|
|
6966
|
-
}, true);
|
|
5768
|
+
return successResponse({ cleared, message });
|
|
6967
5769
|
}
|
|
6968
5770
|
default:
|
|
6969
|
-
return
|
|
5771
|
+
return errorResponse(ERROR_CODES.UNKNOWN_TOOL, `Unknown memory tool: ${name}`);
|
|
6970
5772
|
}
|
|
6971
5773
|
}
|
|
6972
5774
|
|
|
6973
|
-
// src/mcp/tools/
|
|
6974
|
-
var
|
|
5775
|
+
// src/mcp/tools/protection.ts
|
|
5776
|
+
var protectionTools = [
|
|
6975
5777
|
{
|
|
6976
|
-
name: "
|
|
6977
|
-
description:
|
|
5778
|
+
name: "collab_protect",
|
|
5779
|
+
description: `Unified tool for file protection. Use action parameter to:
|
|
5780
|
+
- "register": Register a plan or file for protection
|
|
5781
|
+
- "check": Check if a file is protected before deleting
|
|
5782
|
+
- "list": List all protected files`,
|
|
6978
5783
|
inputSchema: {
|
|
6979
5784
|
type: "object",
|
|
6980
5785
|
properties: {
|
|
5786
|
+
action: {
|
|
5787
|
+
type: "string",
|
|
5788
|
+
enum: ["register", "check", "list"],
|
|
5789
|
+
description: "Action to perform"
|
|
5790
|
+
},
|
|
6981
5791
|
session_id: {
|
|
6982
5792
|
type: "string",
|
|
6983
5793
|
description: "Your session ID"
|
|
6984
5794
|
},
|
|
6985
|
-
|
|
5795
|
+
file_path: {
|
|
5796
|
+
type: "string",
|
|
5797
|
+
description: "File path (for register/check)"
|
|
5798
|
+
},
|
|
5799
|
+
type: {
|
|
6986
5800
|
type: "string",
|
|
6987
|
-
enum: ["
|
|
6988
|
-
description: "
|
|
5801
|
+
enum: ["plan", "file"],
|
|
5802
|
+
description: "Type of protection (for register). Default: file"
|
|
6989
5803
|
},
|
|
6990
5804
|
title: {
|
|
6991
5805
|
type: "string",
|
|
6992
|
-
description: "
|
|
5806
|
+
description: "Title (for plan registration)"
|
|
6993
5807
|
},
|
|
6994
|
-
|
|
5808
|
+
content_summary: {
|
|
6995
5809
|
type: "string",
|
|
6996
|
-
description: "
|
|
5810
|
+
description: "Summary (for plan registration)"
|
|
6997
5811
|
}
|
|
6998
5812
|
},
|
|
6999
|
-
required: ["
|
|
5813
|
+
required: ["action", "session_id"]
|
|
7000
5814
|
}
|
|
7001
|
-
}
|
|
7002
|
-
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
|
|
7006
|
-
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
|
|
7010
|
-
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
|
|
7014
|
-
|
|
7015
|
-
|
|
5815
|
+
}
|
|
5816
|
+
];
|
|
5817
|
+
async function handleProtectionTool(db, name, args) {
|
|
5818
|
+
if (name !== "collab_protect") {
|
|
5819
|
+
return errorResponse(ERROR_CODES.UNKNOWN_TOOL, `Unknown tool: ${name}`);
|
|
5820
|
+
}
|
|
5821
|
+
const action = args.action;
|
|
5822
|
+
const sessionId = args.session_id;
|
|
5823
|
+
if (!action || !sessionId) {
|
|
5824
|
+
return validationError("action and session_id are required");
|
|
5825
|
+
}
|
|
5826
|
+
const sessionCheck = await validateActiveSession(db, sessionId);
|
|
5827
|
+
if (!sessionCheck.valid) {
|
|
5828
|
+
return sessionCheck.error;
|
|
5829
|
+
}
|
|
5830
|
+
switch (action) {
|
|
5831
|
+
case "register": {
|
|
5832
|
+
const filePath = args.file_path;
|
|
5833
|
+
const type = args.type ?? "file";
|
|
5834
|
+
if (!filePath) {
|
|
5835
|
+
return validationError("file_path is required for register");
|
|
5836
|
+
}
|
|
5837
|
+
if (type === "plan") {
|
|
5838
|
+
const title = args.title;
|
|
5839
|
+
const contentSummary = args.content_summary;
|
|
5840
|
+
if (!title || !contentSummary) {
|
|
5841
|
+
return validationError("title and content_summary are required for plan registration");
|
|
7016
5842
|
}
|
|
5843
|
+
const memory = await registerPlan(db, sessionId, {
|
|
5844
|
+
file_path: filePath,
|
|
5845
|
+
title,
|
|
5846
|
+
content_summary: contentSummary,
|
|
5847
|
+
status: "in_progress"
|
|
5848
|
+
});
|
|
5849
|
+
return successResponse({
|
|
5850
|
+
registered: true,
|
|
5851
|
+
type: "plan",
|
|
5852
|
+
file_path: filePath,
|
|
5853
|
+
title,
|
|
5854
|
+
priority: memory.priority,
|
|
5855
|
+
pinned: memory.pinned === 1,
|
|
5856
|
+
message: `Plan registered and protected: ${title}`
|
|
5857
|
+
});
|
|
5858
|
+
} else {
|
|
5859
|
+
const description = args.description;
|
|
5860
|
+
const memory = await registerCreatedFile(db, sessionId, {
|
|
5861
|
+
file_path: filePath,
|
|
5862
|
+
file_type: "other",
|
|
5863
|
+
description
|
|
5864
|
+
});
|
|
5865
|
+
return successResponse({
|
|
5866
|
+
registered: true,
|
|
5867
|
+
type: "file",
|
|
5868
|
+
file_path: filePath,
|
|
5869
|
+
priority: memory.priority,
|
|
5870
|
+
message: `File registered for protection: ${filePath}`
|
|
5871
|
+
});
|
|
7017
5872
|
}
|
|
7018
5873
|
}
|
|
7019
|
-
|
|
7020
|
-
|
|
7021
|
-
|
|
7022
|
-
|
|
7023
|
-
case "collab_decision_add": {
|
|
7024
|
-
const validation = validateInput(decisionAddSchema, args);
|
|
7025
|
-
if (!validation.success) {
|
|
7026
|
-
return validationError(validation.error);
|
|
5874
|
+
case "check": {
|
|
5875
|
+
const filePath = args.file_path;
|
|
5876
|
+
if (!filePath) {
|
|
5877
|
+
return validationError("file_path is required for check");
|
|
7027
5878
|
}
|
|
7028
|
-
const
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
|
|
5879
|
+
const result = await isFileProtected(db, sessionId, filePath);
|
|
5880
|
+
if (result.protected) {
|
|
5881
|
+
return successResponse({
|
|
5882
|
+
protected: true,
|
|
5883
|
+
reason: result.reason,
|
|
5884
|
+
warning: `\u26A0\uFE0F Protected (${result.reason}). Confirm before deleting.`
|
|
5885
|
+
});
|
|
7032
5886
|
}
|
|
7033
|
-
const decision = await addDecision(db, {
|
|
7034
|
-
session_id: input.session_id,
|
|
7035
|
-
category: input.category,
|
|
7036
|
-
title: input.title,
|
|
7037
|
-
description: input.description
|
|
7038
|
-
});
|
|
7039
5887
|
return successResponse({
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
message: "Decision recorded successfully."
|
|
5888
|
+
protected: false,
|
|
5889
|
+
message: "File is not protected."
|
|
7043
5890
|
});
|
|
7044
5891
|
}
|
|
7045
|
-
case "
|
|
7046
|
-
const
|
|
7047
|
-
|
|
7048
|
-
return validationError(validation.error);
|
|
7049
|
-
}
|
|
7050
|
-
const input = validation.data;
|
|
7051
|
-
const decisions = await listDecisions(db, {
|
|
7052
|
-
category: input.category,
|
|
7053
|
-
limit: input.limit ?? 20
|
|
7054
|
-
});
|
|
5892
|
+
case "list": {
|
|
5893
|
+
const files = await getProtectedFiles(db, sessionId);
|
|
5894
|
+
const plans = await listPlans(db, sessionId, { include_archived: false });
|
|
7055
5895
|
return successResponse({
|
|
7056
|
-
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
5896
|
+
protected_files: files.length,
|
|
5897
|
+
plans: plans.length,
|
|
5898
|
+
files,
|
|
5899
|
+
plan_list: plans.map((p) => ({
|
|
5900
|
+
file_path: p.file_path,
|
|
5901
|
+
title: p.title,
|
|
5902
|
+
status: p.status
|
|
7062
5903
|
})),
|
|
7063
|
-
|
|
7064
|
-
}
|
|
5904
|
+
message: `${files.length} protected file(s), ${plans.length} plan(s)`
|
|
5905
|
+
});
|
|
7065
5906
|
}
|
|
7066
5907
|
default:
|
|
7067
|
-
return
|
|
5908
|
+
return errorResponse(ERROR_CODES.UNKNOWN_TOOL, `Unknown action: ${action}`);
|
|
7068
5909
|
}
|
|
7069
5910
|
}
|
|
7070
5911
|
|
|
7071
|
-
// src/
|
|
7072
|
-
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
7077
|
-
|
|
7078
|
-
|
|
7079
|
-
|
|
7080
|
-
|
|
7081
|
-
|
|
7082
|
-
|
|
7083
|
-
|
|
7084
|
-
|
|
7085
|
-
|
|
7086
|
-
|
|
7087
|
-
|
|
7088
|
-
};
|
|
7089
|
-
var lspTools = [
|
|
7090
|
-
{
|
|
7091
|
-
name: "collab_analyze_symbols",
|
|
7092
|
-
description: `Analyze LSP symbols for conflict detection and reference impact.
|
|
5912
|
+
// src/constants.ts
|
|
5913
|
+
import { readFileSync } from "fs";
|
|
5914
|
+
import { join as join2, dirname as dirname2 } from "path";
|
|
5915
|
+
import { fileURLToPath } from "url";
|
|
5916
|
+
function getVersion() {
|
|
5917
|
+
try {
|
|
5918
|
+
const __dirname3 = dirname2(fileURLToPath(import.meta.url));
|
|
5919
|
+
const pkg = JSON.parse(readFileSync(join2(__dirname3, "..", "package.json"), "utf-8"));
|
|
5920
|
+
return pkg.version;
|
|
5921
|
+
} catch {
|
|
5922
|
+
return "0.0.0";
|
|
5923
|
+
}
|
|
5924
|
+
}
|
|
5925
|
+
var VERSION = getVersion();
|
|
5926
|
+
var SERVER_NAME = "session-collab-mcp";
|
|
5927
|
+
var SERVER_INSTRUCTIONS = `
|
|
5928
|
+
# Session Collaboration MCP
|
|
7093
5929
|
|
|
7094
|
-
|
|
7095
|
-
1. Claude uses LSP.documentSymbol to get symbols from a file
|
|
7096
|
-
2. Claude passes those symbols to this tool
|
|
7097
|
-
3. This tool checks conflicts and returns which symbols are safe/blocked
|
|
5930
|
+
Coordinate sessions and persist context across conversations.
|
|
7098
5931
|
|
|
7099
|
-
|
|
7100
|
-
inputSchema: {
|
|
7101
|
-
type: "object",
|
|
7102
|
-
properties: {
|
|
7103
|
-
session_id: {
|
|
7104
|
-
type: "string",
|
|
7105
|
-
description: "Your session ID (to exclude your own claims)"
|
|
7106
|
-
},
|
|
7107
|
-
files: {
|
|
7108
|
-
type: "array",
|
|
7109
|
-
items: {
|
|
7110
|
-
type: "object",
|
|
7111
|
-
properties: {
|
|
7112
|
-
file: { type: "string", description: "File path" },
|
|
7113
|
-
symbols: {
|
|
7114
|
-
type: "array",
|
|
7115
|
-
items: {
|
|
7116
|
-
type: "object",
|
|
7117
|
-
properties: {
|
|
7118
|
-
name: { type: "string", description: "Symbol name" },
|
|
7119
|
-
kind: { type: "number", description: "LSP SymbolKind" },
|
|
7120
|
-
range: {
|
|
7121
|
-
type: "object",
|
|
7122
|
-
properties: {
|
|
7123
|
-
start: {
|
|
7124
|
-
type: "object",
|
|
7125
|
-
properties: {
|
|
7126
|
-
line: { type: "number" },
|
|
7127
|
-
character: { type: "number" }
|
|
7128
|
-
}
|
|
7129
|
-
},
|
|
7130
|
-
end: {
|
|
7131
|
-
type: "object",
|
|
7132
|
-
properties: {
|
|
7133
|
-
line: { type: "number" },
|
|
7134
|
-
character: { type: "number" }
|
|
7135
|
-
}
|
|
7136
|
-
}
|
|
7137
|
-
}
|
|
7138
|
-
},
|
|
7139
|
-
children: {
|
|
7140
|
-
type: "array",
|
|
7141
|
-
description: "Nested symbols (e.g., methods in a class)"
|
|
7142
|
-
}
|
|
7143
|
-
},
|
|
7144
|
-
required: ["name", "kind"]
|
|
7145
|
-
},
|
|
7146
|
-
description: "LSP DocumentSymbol array from LSP.documentSymbol"
|
|
7147
|
-
}
|
|
7148
|
-
},
|
|
7149
|
-
required: ["file", "symbols"]
|
|
7150
|
-
},
|
|
7151
|
-
description: "Files with their LSP symbols to analyze"
|
|
7152
|
-
},
|
|
7153
|
-
references: {
|
|
7154
|
-
type: "array",
|
|
7155
|
-
items: {
|
|
7156
|
-
type: "object",
|
|
7157
|
-
properties: {
|
|
7158
|
-
symbol: { type: "string", description: "Symbol name" },
|
|
7159
|
-
file: { type: "string", description: "File containing the symbol" },
|
|
7160
|
-
references: {
|
|
7161
|
-
type: "array",
|
|
7162
|
-
items: {
|
|
7163
|
-
type: "object",
|
|
7164
|
-
properties: {
|
|
7165
|
-
file: { type: "string" },
|
|
7166
|
-
line: { type: "number" },
|
|
7167
|
-
context: { type: "string" }
|
|
7168
|
-
},
|
|
7169
|
-
required: ["file", "line"]
|
|
7170
|
-
},
|
|
7171
|
-
description: "Locations that reference this symbol (from LSP.findReferences)"
|
|
7172
|
-
}
|
|
7173
|
-
},
|
|
7174
|
-
required: ["symbol", "file", "references"]
|
|
7175
|
-
},
|
|
7176
|
-
description: "Optional: Reference data from LSP.findReferences for impact analysis"
|
|
7177
|
-
},
|
|
7178
|
-
check_symbols: {
|
|
7179
|
-
type: "array",
|
|
7180
|
-
items: { type: "string" },
|
|
7181
|
-
description: "Optional: Only analyze these specific symbol names (filter)"
|
|
7182
|
-
}
|
|
7183
|
-
},
|
|
7184
|
-
required: ["session_id", "files"]
|
|
7185
|
-
}
|
|
7186
|
-
},
|
|
7187
|
-
{
|
|
7188
|
-
name: "collab_validate_symbols",
|
|
7189
|
-
description: `Validate that symbols exist in a file before claiming them.
|
|
5932
|
+
## 9 Core Tools
|
|
7190
5933
|
|
|
7191
|
-
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
file: {
|
|
7197
|
-
type: "string",
|
|
7198
|
-
description: "File path to validate symbols in"
|
|
7199
|
-
},
|
|
7200
|
-
symbols: {
|
|
7201
|
-
type: "array",
|
|
7202
|
-
items: { type: "string" },
|
|
7203
|
-
description: "Symbol names to validate"
|
|
7204
|
-
},
|
|
7205
|
-
lsp_symbols: {
|
|
7206
|
-
type: "array",
|
|
7207
|
-
items: {
|
|
7208
|
-
type: "object",
|
|
7209
|
-
properties: {
|
|
7210
|
-
name: { type: "string" },
|
|
7211
|
-
kind: { type: "number" }
|
|
7212
|
-
},
|
|
7213
|
-
required: ["name", "kind"]
|
|
7214
|
-
},
|
|
7215
|
-
description: "LSP symbols from the file (from LSP.documentSymbol)"
|
|
7216
|
-
}
|
|
7217
|
-
},
|
|
7218
|
-
required: ["file", "symbols", "lsp_symbols"]
|
|
7219
|
-
}
|
|
7220
|
-
},
|
|
7221
|
-
{
|
|
7222
|
-
name: "collab_store_references",
|
|
7223
|
-
description: `Store symbol reference data from LSP.findReferences for impact tracking.
|
|
5934
|
+
### Session (4 tools)
|
|
5935
|
+
- \`collab_session_start\`: Start session with project_root
|
|
5936
|
+
- \`collab_session_end\`: End session, release claims
|
|
5937
|
+
- \`collab_session_list\`: List active sessions
|
|
5938
|
+
- \`collab_config\`: Configure session behavior
|
|
7224
5939
|
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
type: "object",
|
|
7229
|
-
properties: {
|
|
7230
|
-
session_id: {
|
|
7231
|
-
type: "string",
|
|
7232
|
-
description: "Your session ID"
|
|
7233
|
-
},
|
|
7234
|
-
references: {
|
|
7235
|
-
type: "array",
|
|
7236
|
-
items: {
|
|
7237
|
-
type: "object",
|
|
7238
|
-
properties: {
|
|
7239
|
-
source_file: { type: "string", description: "File containing the symbol definition" },
|
|
7240
|
-
source_symbol: { type: "string", description: "Symbol name" },
|
|
7241
|
-
references: {
|
|
7242
|
-
type: "array",
|
|
7243
|
-
items: {
|
|
7244
|
-
type: "object",
|
|
7245
|
-
properties: {
|
|
7246
|
-
file: { type: "string" },
|
|
7247
|
-
line: { type: "number" },
|
|
7248
|
-
context: { type: "string" }
|
|
7249
|
-
},
|
|
7250
|
-
required: ["file", "line"]
|
|
7251
|
-
}
|
|
7252
|
-
}
|
|
7253
|
-
},
|
|
7254
|
-
required: ["source_file", "source_symbol", "references"]
|
|
7255
|
-
},
|
|
7256
|
-
description: "Reference data from LSP.findReferences"
|
|
7257
|
-
},
|
|
7258
|
-
clear_existing: {
|
|
7259
|
-
type: "boolean",
|
|
7260
|
-
description: "Clear existing references from this session before storing (default: false)"
|
|
7261
|
-
}
|
|
7262
|
-
},
|
|
7263
|
-
required: ["session_id", "references"]
|
|
7264
|
-
}
|
|
7265
|
-
},
|
|
7266
|
-
{
|
|
7267
|
-
name: "collab_impact_analysis",
|
|
7268
|
-
description: `Analyze the impact of modifying a symbol.
|
|
5940
|
+
### Claims (1 tool)
|
|
5941
|
+
- \`collab_claim\`: Unified claim tool
|
|
5942
|
+
- action: "create" | "check" | "release" | "list"
|
|
7269
5943
|
|
|
7270
|
-
|
|
7271
|
-
|
|
7272
|
-
|
|
7273
|
-
|
|
7274
|
-
properties: {
|
|
7275
|
-
session_id: {
|
|
7276
|
-
type: "string",
|
|
7277
|
-
description: "Your session ID (to exclude your own claims)"
|
|
7278
|
-
},
|
|
7279
|
-
file: {
|
|
7280
|
-
type: "string",
|
|
7281
|
-
description: "File containing the symbol"
|
|
7282
|
-
},
|
|
7283
|
-
symbol: {
|
|
7284
|
-
type: "string",
|
|
7285
|
-
description: "Symbol name to analyze"
|
|
7286
|
-
}
|
|
7287
|
-
},
|
|
7288
|
-
required: ["session_id", "file", "symbol"]
|
|
7289
|
-
}
|
|
7290
|
-
}
|
|
7291
|
-
];
|
|
7292
|
-
function lspKindToSymbolType(kind) {
|
|
7293
|
-
return LSP_SYMBOL_KIND_MAP[kind] ?? "other";
|
|
7294
|
-
}
|
|
7295
|
-
function flattenLspSymbols(symbols, file, parentName) {
|
|
7296
|
-
const result = [];
|
|
7297
|
-
for (const sym of symbols) {
|
|
7298
|
-
const fullName = parentName ? `${parentName}.${sym.name}` : sym.name;
|
|
7299
|
-
result.push({
|
|
7300
|
-
name: sym.name,
|
|
7301
|
-
type: lspKindToSymbolType(sym.kind),
|
|
7302
|
-
file,
|
|
7303
|
-
fullName
|
|
7304
|
-
});
|
|
7305
|
-
if (sym.children && sym.children.length > 0) {
|
|
7306
|
-
result.push(...flattenLspSymbols(sym.children, file, fullName));
|
|
7307
|
-
}
|
|
7308
|
-
}
|
|
7309
|
-
return result;
|
|
7310
|
-
}
|
|
7311
|
-
async function handleLspTool(db, name, args) {
|
|
7312
|
-
switch (name) {
|
|
7313
|
-
case "collab_analyze_symbols": {
|
|
7314
|
-
const validation = validateInput(analyzeSymbolsSchema, args);
|
|
7315
|
-
if (!validation.success) {
|
|
7316
|
-
return createToolResult(
|
|
7317
|
-
JSON.stringify({ error: "INVALID_INPUT", message: validation.error }),
|
|
7318
|
-
true
|
|
7319
|
-
);
|
|
7320
|
-
}
|
|
7321
|
-
const input = validation.data;
|
|
7322
|
-
const files = input.files;
|
|
7323
|
-
const references = input.references;
|
|
7324
|
-
const checkSymbols = input.check_symbols;
|
|
7325
|
-
const referenceMap = /* @__PURE__ */ new Map();
|
|
7326
|
-
if (references) {
|
|
7327
|
-
for (const ref of references) {
|
|
7328
|
-
const key = `${ref.file}:${ref.symbol}`;
|
|
7329
|
-
referenceMap.set(key, ref);
|
|
7330
|
-
}
|
|
7331
|
-
}
|
|
7332
|
-
const allSymbols = [];
|
|
7333
|
-
for (const fileInput of files) {
|
|
7334
|
-
const flattened = flattenLspSymbols(fileInput.symbols, fileInput.file);
|
|
7335
|
-
allSymbols.push(...flattened);
|
|
7336
|
-
}
|
|
7337
|
-
let symbolsToAnalyze = allSymbols;
|
|
7338
|
-
if (checkSymbols && checkSymbols.length > 0) {
|
|
7339
|
-
const checkSet = new Set(checkSymbols);
|
|
7340
|
-
symbolsToAnalyze = allSymbols.filter((s) => checkSet.has(s.name) || checkSet.has(s.fullName));
|
|
7341
|
-
}
|
|
7342
|
-
const fileList = [...new Set(symbolsToAnalyze.map((s) => s.file))];
|
|
7343
|
-
const symbolNames = [...new Set(symbolsToAnalyze.map((s) => s.name))];
|
|
7344
|
-
const symbolConflicts = await querySymbolConflicts(db, fileList, symbolNames, input.session_id);
|
|
7345
|
-
const fileConflicts = await queryFileConflicts(db, fileList, input.session_id);
|
|
7346
|
-
const analyzedSymbols = [];
|
|
7347
|
-
const safeSymbols = [];
|
|
7348
|
-
const blockedSymbols = [];
|
|
7349
|
-
for (const sym of symbolsToAnalyze) {
|
|
7350
|
-
const fileConflict = fileConflicts.find((c) => c.file_path === sym.file);
|
|
7351
|
-
const symbolConflict = symbolConflicts.find(
|
|
7352
|
-
(c) => c.file_path === sym.file && c.symbol_name === sym.name
|
|
7353
|
-
);
|
|
7354
|
-
const conflict = fileConflict ?? symbolConflict;
|
|
7355
|
-
const isBlocked = !!conflict;
|
|
7356
|
-
const refKey = `${sym.file}:${sym.name}`;
|
|
7357
|
-
const refData = referenceMap.get(refKey);
|
|
7358
|
-
let impact;
|
|
7359
|
-
if (refData && refData.references.length > 0) {
|
|
7360
|
-
const affectedFiles = [...new Set(refData.references.map((r) => r.file))];
|
|
7361
|
-
impact = {
|
|
7362
|
-
references_count: refData.references.length,
|
|
7363
|
-
affected_files: affectedFiles
|
|
7364
|
-
};
|
|
7365
|
-
}
|
|
7366
|
-
const analyzed = {
|
|
7367
|
-
name: sym.name,
|
|
7368
|
-
type: sym.type,
|
|
7369
|
-
file: sym.file,
|
|
7370
|
-
conflict_status: isBlocked ? "blocked" : "safe",
|
|
7371
|
-
...conflict && {
|
|
7372
|
-
conflict_info: {
|
|
7373
|
-
session_name: conflict.session_name,
|
|
7374
|
-
intent: conflict.intent,
|
|
7375
|
-
claim_id: conflict.claim_id
|
|
7376
|
-
}
|
|
7377
|
-
},
|
|
7378
|
-
...impact && { impact }
|
|
7379
|
-
};
|
|
7380
|
-
analyzedSymbols.push(analyzed);
|
|
7381
|
-
if (isBlocked) {
|
|
7382
|
-
blockedSymbols.push(`${sym.file}:${sym.name}`);
|
|
7383
|
-
} else {
|
|
7384
|
-
safeSymbols.push(`${sym.file}:${sym.name}`);
|
|
7385
|
-
}
|
|
7386
|
-
}
|
|
7387
|
-
const hasBlocked = blockedSymbols.length > 0;
|
|
7388
|
-
const hasSafe = safeSymbols.length > 0;
|
|
7389
|
-
let recommendation;
|
|
7390
|
-
if (!hasBlocked) {
|
|
7391
|
-
recommendation = "proceed_all";
|
|
7392
|
-
} else if (hasSafe) {
|
|
7393
|
-
recommendation = "proceed_safe_only";
|
|
7394
|
-
} else {
|
|
7395
|
-
recommendation = "abort";
|
|
7396
|
-
}
|
|
7397
|
-
let message;
|
|
7398
|
-
if (recommendation === "proceed_all") {
|
|
7399
|
-
message = `All ${safeSymbols.length} symbols are safe to edit. Proceed.`;
|
|
7400
|
-
} else if (recommendation === "proceed_safe_only") {
|
|
7401
|
-
message = `Edit ONLY ${safeSymbols.length} safe symbols. ${blockedSymbols.length} symbols are blocked.`;
|
|
7402
|
-
} else {
|
|
7403
|
-
message = `All ${blockedSymbols.length} symbols are blocked. Coordinate with other sessions.`;
|
|
7404
|
-
}
|
|
7405
|
-
return createToolResult(
|
|
7406
|
-
JSON.stringify(
|
|
7407
|
-
{
|
|
7408
|
-
can_edit: hasSafe,
|
|
7409
|
-
recommendation,
|
|
7410
|
-
summary: {
|
|
7411
|
-
total: symbolsToAnalyze.length,
|
|
7412
|
-
safe: safeSymbols.length,
|
|
7413
|
-
blocked: blockedSymbols.length
|
|
7414
|
-
},
|
|
7415
|
-
symbols: analyzedSymbols,
|
|
7416
|
-
safe_symbols: safeSymbols,
|
|
7417
|
-
blocked_symbols: blockedSymbols,
|
|
7418
|
-
message
|
|
7419
|
-
},
|
|
7420
|
-
null,
|
|
7421
|
-
2
|
|
7422
|
-
)
|
|
7423
|
-
);
|
|
7424
|
-
}
|
|
7425
|
-
case "collab_validate_symbols": {
|
|
7426
|
-
const validation = validateInput(validateSymbolsSchema, args);
|
|
7427
|
-
if (!validation.success) {
|
|
7428
|
-
return createToolResult(
|
|
7429
|
-
JSON.stringify({ error: "INVALID_INPUT", message: validation.error }),
|
|
7430
|
-
true
|
|
7431
|
-
);
|
|
7432
|
-
}
|
|
7433
|
-
const input = validation.data;
|
|
7434
|
-
const availableSymbols = /* @__PURE__ */ new Set();
|
|
7435
|
-
for (const lspSym of input.lsp_symbols) {
|
|
7436
|
-
availableSymbols.add(lspSym.name);
|
|
7437
|
-
}
|
|
7438
|
-
const valid = [];
|
|
7439
|
-
const invalid = [];
|
|
7440
|
-
const suggestions = {};
|
|
7441
|
-
for (const sym of input.symbols) {
|
|
7442
|
-
if (availableSymbols.has(sym)) {
|
|
7443
|
-
valid.push(sym);
|
|
7444
|
-
} else {
|
|
7445
|
-
invalid.push(sym);
|
|
7446
|
-
const similar = Array.from(availableSymbols).filter(
|
|
7447
|
-
(avail) => avail.toLowerCase().includes(sym.toLowerCase()) || sym.toLowerCase().includes(avail.toLowerCase())
|
|
7448
|
-
);
|
|
7449
|
-
if (similar.length > 0) {
|
|
7450
|
-
suggestions[sym] = similar.slice(0, 3);
|
|
7451
|
-
}
|
|
7452
|
-
}
|
|
7453
|
-
}
|
|
7454
|
-
const allValid = invalid.length === 0;
|
|
7455
|
-
return createToolResult(
|
|
7456
|
-
JSON.stringify({
|
|
7457
|
-
valid: allValid,
|
|
7458
|
-
file: input.file,
|
|
7459
|
-
valid_symbols: valid,
|
|
7460
|
-
invalid_symbols: invalid,
|
|
7461
|
-
suggestions: Object.keys(suggestions).length > 0 ? suggestions : void 0,
|
|
7462
|
-
available_symbols: Array.from(availableSymbols),
|
|
7463
|
-
message: allValid ? `All ${valid.length} symbols are valid.` : `${invalid.length} symbol(s) not found: ${invalid.join(", ")}`
|
|
7464
|
-
})
|
|
7465
|
-
);
|
|
7466
|
-
}
|
|
7467
|
-
case "collab_store_references": {
|
|
7468
|
-
const validation = validateInput(storeReferencesSchema, args);
|
|
7469
|
-
if (!validation.success) {
|
|
7470
|
-
return createToolResult(
|
|
7471
|
-
JSON.stringify({ error: "INVALID_INPUT", message: validation.error }),
|
|
7472
|
-
true
|
|
7473
|
-
);
|
|
7474
|
-
}
|
|
7475
|
-
const input = validation.data;
|
|
7476
|
-
let cleared = 0;
|
|
7477
|
-
if (input.clear_existing) {
|
|
7478
|
-
cleared = await clearSessionReferences(db, input.session_id);
|
|
7479
|
-
}
|
|
7480
|
-
const result = await storeReferences(db, input.session_id, input.references);
|
|
7481
|
-
return createToolResult(
|
|
7482
|
-
JSON.stringify({
|
|
7483
|
-
success: true,
|
|
7484
|
-
stored: result.stored,
|
|
7485
|
-
skipped: result.skipped,
|
|
7486
|
-
cleared,
|
|
7487
|
-
message: `Stored ${result.stored} references${cleared > 0 ? ` (cleared ${cleared} existing)` : ""}.`
|
|
7488
|
-
})
|
|
7489
|
-
);
|
|
7490
|
-
}
|
|
7491
|
-
case "collab_impact_analysis": {
|
|
7492
|
-
const validation = validateInput(impactAnalysisSchema, args);
|
|
7493
|
-
if (!validation.success) {
|
|
7494
|
-
return createToolResult(
|
|
7495
|
-
JSON.stringify({ error: "INVALID_INPUT", message: validation.error }),
|
|
7496
|
-
true
|
|
7497
|
-
);
|
|
7498
|
-
}
|
|
7499
|
-
const input = validation.data;
|
|
7500
|
-
const impact = await analyzeClaimImpact(db, input.file, input.symbol, input.session_id);
|
|
7501
|
-
let riskLevel;
|
|
7502
|
-
if (impact.affected_claims.length > 0) {
|
|
7503
|
-
riskLevel = "high";
|
|
7504
|
-
} else if (impact.reference_count > 10) {
|
|
7505
|
-
riskLevel = "medium";
|
|
7506
|
-
} else {
|
|
7507
|
-
riskLevel = "low";
|
|
7508
|
-
}
|
|
7509
|
-
let message;
|
|
7510
|
-
if (impact.affected_claims.length > 0) {
|
|
7511
|
-
const claimNames = impact.affected_claims.map((c) => c.session_name ?? "unknown").join(", ");
|
|
7512
|
-
message = `\u26A0\uFE0F HIGH RISK: ${impact.affected_claims.length} active claim(s) on referencing files (${claimNames}). Coordinate before modifying.`;
|
|
7513
|
-
} else if (impact.reference_count > 0) {
|
|
7514
|
-
message = `${impact.reference_count} references across ${impact.affected_files.length} file(s). No active claims conflict.`;
|
|
7515
|
-
} else {
|
|
7516
|
-
message = "No stored references found. Consider running LSP.findReferences and collab_store_references first.";
|
|
7517
|
-
}
|
|
7518
|
-
return createToolResult(
|
|
7519
|
-
JSON.stringify(
|
|
7520
|
-
{
|
|
7521
|
-
symbol: impact.symbol,
|
|
7522
|
-
file: impact.file,
|
|
7523
|
-
risk_level: riskLevel,
|
|
7524
|
-
reference_count: impact.reference_count,
|
|
7525
|
-
affected_files: impact.affected_files,
|
|
7526
|
-
affected_claims: impact.affected_claims,
|
|
7527
|
-
message
|
|
7528
|
-
},
|
|
7529
|
-
null,
|
|
7530
|
-
2
|
|
7531
|
-
)
|
|
7532
|
-
);
|
|
7533
|
-
}
|
|
7534
|
-
default:
|
|
7535
|
-
return createToolResult(`Unknown LSP tool: ${name}`, true);
|
|
7536
|
-
}
|
|
7537
|
-
}
|
|
7538
|
-
async function querySymbolConflicts(db, files, symbolNames, excludeSessionId) {
|
|
7539
|
-
if (files.length === 0 || symbolNames.length === 0) {
|
|
7540
|
-
return [];
|
|
7541
|
-
}
|
|
7542
|
-
const filePlaceholders = files.map(() => "?").join(",");
|
|
7543
|
-
const symbolPlaceholders = symbolNames.map(() => "?").join(",");
|
|
7544
|
-
const query = `
|
|
7545
|
-
SELECT
|
|
7546
|
-
c.id as claim_id,
|
|
7547
|
-
c.session_id,
|
|
7548
|
-
s.name as session_name,
|
|
7549
|
-
cs.file_path,
|
|
7550
|
-
c.intent,
|
|
7551
|
-
c.scope,
|
|
7552
|
-
c.created_at,
|
|
7553
|
-
cs.symbol_name,
|
|
7554
|
-
cs.symbol_type
|
|
7555
|
-
FROM claim_symbols cs
|
|
7556
|
-
JOIN claims c ON cs.claim_id = c.id
|
|
7557
|
-
JOIN sessions s ON c.session_id = s.id
|
|
7558
|
-
WHERE c.status = 'active'
|
|
7559
|
-
AND s.status = 'active'
|
|
7560
|
-
AND c.session_id != ?
|
|
7561
|
-
AND cs.file_path IN (${filePlaceholders})
|
|
7562
|
-
AND cs.symbol_name IN (${symbolPlaceholders})
|
|
7563
|
-
`;
|
|
7564
|
-
const result = await db.prepare(query).bind(excludeSessionId, ...files, ...symbolNames).all();
|
|
7565
|
-
return result.results.map((r) => ({
|
|
7566
|
-
...r,
|
|
7567
|
-
conflict_level: "symbol"
|
|
7568
|
-
}));
|
|
7569
|
-
}
|
|
7570
|
-
async function queryFileConflicts(db, files, excludeSessionId) {
|
|
7571
|
-
if (files.length === 0) {
|
|
7572
|
-
return [];
|
|
7573
|
-
}
|
|
7574
|
-
const placeholders = files.map(() => "?").join(",");
|
|
7575
|
-
const query = `
|
|
7576
|
-
SELECT
|
|
7577
|
-
c.id as claim_id,
|
|
7578
|
-
c.session_id,
|
|
7579
|
-
s.name as session_name,
|
|
7580
|
-
cf.file_path,
|
|
7581
|
-
c.intent,
|
|
7582
|
-
c.scope,
|
|
7583
|
-
c.created_at
|
|
7584
|
-
FROM claim_files cf
|
|
7585
|
-
JOIN claims c ON cf.claim_id = c.id
|
|
7586
|
-
JOIN sessions s ON c.session_id = s.id
|
|
7587
|
-
WHERE c.status = 'active'
|
|
7588
|
-
AND s.status = 'active'
|
|
7589
|
-
AND c.session_id != ?
|
|
7590
|
-
AND cf.file_path IN (${placeholders})
|
|
7591
|
-
AND NOT EXISTS (
|
|
7592
|
-
SELECT 1 FROM claim_symbols cs WHERE cs.claim_id = c.id AND cs.file_path = cf.file_path
|
|
7593
|
-
)
|
|
7594
|
-
`;
|
|
7595
|
-
const result = await db.prepare(query).bind(excludeSessionId, ...files).all();
|
|
7596
|
-
return result.results.map((r) => ({
|
|
7597
|
-
...r,
|
|
7598
|
-
conflict_level: "file"
|
|
7599
|
-
}));
|
|
7600
|
-
}
|
|
7601
|
-
|
|
7602
|
-
// src/mcp/tools/history.ts
|
|
7603
|
-
var historyTools = [
|
|
7604
|
-
{
|
|
7605
|
-
name: "collab_history_list",
|
|
7606
|
-
description: "List audit history entries. Useful for debugging coordination issues and understanding past actions.",
|
|
7607
|
-
inputSchema: {
|
|
7608
|
-
type: "object",
|
|
7609
|
-
properties: {
|
|
7610
|
-
session_id: {
|
|
7611
|
-
type: "string",
|
|
7612
|
-
description: "Filter by session ID"
|
|
7613
|
-
},
|
|
7614
|
-
action: {
|
|
7615
|
-
type: "string",
|
|
7616
|
-
enum: [
|
|
7617
|
-
"session_started",
|
|
7618
|
-
"session_ended",
|
|
7619
|
-
"claim_created",
|
|
7620
|
-
"claim_released",
|
|
7621
|
-
"conflict_detected",
|
|
7622
|
-
"queue_joined",
|
|
7623
|
-
"queue_left",
|
|
7624
|
-
"priority_changed"
|
|
7625
|
-
],
|
|
7626
|
-
description: "Filter by action type"
|
|
7627
|
-
},
|
|
7628
|
-
entity_type: {
|
|
7629
|
-
type: "string",
|
|
7630
|
-
enum: ["session", "claim", "queue"],
|
|
7631
|
-
description: "Filter by entity type"
|
|
7632
|
-
},
|
|
7633
|
-
entity_id: {
|
|
7634
|
-
type: "string",
|
|
7635
|
-
description: "Filter by specific entity ID"
|
|
7636
|
-
},
|
|
7637
|
-
from_date: {
|
|
7638
|
-
type: "string",
|
|
7639
|
-
description: "Start date (ISO 8601 format)"
|
|
7640
|
-
},
|
|
7641
|
-
to_date: {
|
|
7642
|
-
type: "string",
|
|
7643
|
-
description: "End date (ISO 8601 format)"
|
|
7644
|
-
},
|
|
7645
|
-
limit: {
|
|
7646
|
-
type: "number",
|
|
7647
|
-
description: "Maximum entries to return (default: 50, max: 500)"
|
|
7648
|
-
}
|
|
7649
|
-
}
|
|
7650
|
-
}
|
|
7651
|
-
}
|
|
7652
|
-
];
|
|
7653
|
-
async function handleHistoryTool(db, name, args) {
|
|
7654
|
-
switch (name) {
|
|
7655
|
-
case "collab_history_list": {
|
|
7656
|
-
const validation = validateInput(historyListSchema, args);
|
|
7657
|
-
if (!validation.success) {
|
|
7658
|
-
return validationError(validation.error);
|
|
7659
|
-
}
|
|
7660
|
-
const { session_id, action, entity_type, entity_id, from_date, to_date, limit } = validation.data;
|
|
7661
|
-
await cleanupOldAuditHistory(db, 7);
|
|
7662
|
-
const entries = await listAuditHistory(db, {
|
|
7663
|
-
session_id,
|
|
7664
|
-
action,
|
|
7665
|
-
entity_type,
|
|
7666
|
-
entity_id,
|
|
7667
|
-
from_date,
|
|
7668
|
-
to_date,
|
|
7669
|
-
limit
|
|
7670
|
-
});
|
|
7671
|
-
return successResponse({
|
|
7672
|
-
entries: entries.map((e) => ({
|
|
7673
|
-
id: e.id,
|
|
7674
|
-
session_id: e.session_id,
|
|
7675
|
-
session_name: e.session_name,
|
|
7676
|
-
action: e.action,
|
|
7677
|
-
entity_type: e.entity_type,
|
|
7678
|
-
entity_id: e.entity_id,
|
|
7679
|
-
metadata: e.metadata ? JSON.parse(e.metadata) : null,
|
|
7680
|
-
created_at: e.created_at
|
|
7681
|
-
})),
|
|
7682
|
-
total: entries.length,
|
|
7683
|
-
message: entries.length > 0 ? `Found ${entries.length} audit entries` : "No audit entries found matching the criteria"
|
|
7684
|
-
}, true);
|
|
7685
|
-
}
|
|
7686
|
-
default:
|
|
7687
|
-
return successResponse({ error: `Unknown history tool: ${name}` });
|
|
7688
|
-
}
|
|
7689
|
-
}
|
|
7690
|
-
|
|
7691
|
-
// src/mcp/tools/queue.ts
|
|
7692
|
-
var queueTools = [
|
|
7693
|
-
{
|
|
7694
|
-
name: "collab_queue_join",
|
|
7695
|
-
description: "Join waiting queue for a blocked claim. Use this when you want to work on files that are currently claimed by another session.",
|
|
7696
|
-
inputSchema: {
|
|
7697
|
-
type: "object",
|
|
7698
|
-
properties: {
|
|
7699
|
-
session_id: {
|
|
7700
|
-
type: "string",
|
|
7701
|
-
description: "Your session ID"
|
|
7702
|
-
},
|
|
7703
|
-
claim_id: {
|
|
7704
|
-
type: "string",
|
|
7705
|
-
description: "The claim ID you want to wait for"
|
|
7706
|
-
},
|
|
7707
|
-
intent: {
|
|
7708
|
-
type: "string",
|
|
7709
|
-
description: "What you plan to do when the claim becomes available"
|
|
7710
|
-
},
|
|
7711
|
-
priority: {
|
|
7712
|
-
type: "number",
|
|
7713
|
-
description: "Priority (0-100). Higher priority entries are served first. Default: 50"
|
|
7714
|
-
},
|
|
7715
|
-
scope: {
|
|
7716
|
-
type: "string",
|
|
7717
|
-
enum: ["small", "medium", "large"],
|
|
7718
|
-
description: "Estimated scope of your work: small(<30min), medium(30min-2hr), large(>2hr)"
|
|
7719
|
-
}
|
|
7720
|
-
},
|
|
7721
|
-
required: ["session_id", "claim_id", "intent"]
|
|
7722
|
-
}
|
|
7723
|
-
},
|
|
7724
|
-
{
|
|
7725
|
-
name: "collab_queue_leave",
|
|
7726
|
-
description: "Leave the waiting queue for a claim.",
|
|
7727
|
-
inputSchema: {
|
|
7728
|
-
type: "object",
|
|
7729
|
-
properties: {
|
|
7730
|
-
session_id: {
|
|
7731
|
-
type: "string",
|
|
7732
|
-
description: "Your session ID"
|
|
7733
|
-
},
|
|
7734
|
-
queue_id: {
|
|
7735
|
-
type: "string",
|
|
7736
|
-
description: "Queue entry ID to leave"
|
|
7737
|
-
}
|
|
7738
|
-
},
|
|
7739
|
-
required: ["session_id", "queue_id"]
|
|
7740
|
-
}
|
|
7741
|
-
},
|
|
7742
|
-
{
|
|
7743
|
-
name: "collab_queue_list",
|
|
7744
|
-
description: "List queue entries. View who is waiting for claims.",
|
|
7745
|
-
inputSchema: {
|
|
7746
|
-
type: "object",
|
|
7747
|
-
properties: {
|
|
7748
|
-
claim_id: {
|
|
7749
|
-
type: "string",
|
|
7750
|
-
description: "Filter by claim ID to see who is waiting for a specific claim"
|
|
7751
|
-
},
|
|
7752
|
-
session_id: {
|
|
7753
|
-
type: "string",
|
|
7754
|
-
description: "Filter by session ID to see what claims a session is waiting for"
|
|
7755
|
-
}
|
|
7756
|
-
}
|
|
7757
|
-
}
|
|
7758
|
-
}
|
|
7759
|
-
];
|
|
7760
|
-
async function handleQueueTool(db, name, args) {
|
|
7761
|
-
switch (name) {
|
|
7762
|
-
case "collab_queue_join": {
|
|
7763
|
-
const validation = validateInput(queueJoinSchema, args);
|
|
7764
|
-
if (!validation.success) {
|
|
7765
|
-
return validationError(validation.error);
|
|
7766
|
-
}
|
|
7767
|
-
const { session_id: sessionId, claim_id: claimId, intent, priority, scope } = validation.data;
|
|
7768
|
-
const sessionResult = await validateActiveSession(db, sessionId);
|
|
7769
|
-
if (!sessionResult.valid) {
|
|
7770
|
-
return sessionResult.error;
|
|
7771
|
-
}
|
|
7772
|
-
const claim = await getClaim(db, claimId);
|
|
7773
|
-
if (!claim) {
|
|
7774
|
-
return errorResponse(ERROR_CODES.CLAIM_NOT_FOUND, "Claim not found");
|
|
7775
|
-
}
|
|
7776
|
-
if (claim.session_id === sessionId) {
|
|
7777
|
-
return errorResponse(
|
|
7778
|
-
ERROR_CODES.CANNOT_QUEUE_OWN_CLAIM,
|
|
7779
|
-
"You cannot queue for your own claim"
|
|
7780
|
-
);
|
|
7781
|
-
}
|
|
7782
|
-
if (claim.status !== "active") {
|
|
7783
|
-
return successResponse({
|
|
7784
|
-
message: `Claim is already ${claim.status}. No need to queue - you can claim these files now.`,
|
|
7785
|
-
claim_status: claim.status,
|
|
7786
|
-
files: claim.files
|
|
7787
|
-
});
|
|
7788
|
-
}
|
|
7789
|
-
const existingQueue = await listQueue(db, { claim_id: claimId, session_id: sessionId });
|
|
7790
|
-
if (existingQueue.length > 0) {
|
|
7791
|
-
return errorResponse(
|
|
7792
|
-
ERROR_CODES.ALREADY_IN_QUEUE,
|
|
7793
|
-
"You are already in the queue for this claim"
|
|
7794
|
-
);
|
|
7795
|
-
}
|
|
7796
|
-
const entry = await joinQueue(db, {
|
|
7797
|
-
claim_id: claimId,
|
|
7798
|
-
session_id: sessionId,
|
|
7799
|
-
intent,
|
|
7800
|
-
priority,
|
|
7801
|
-
scope
|
|
7802
|
-
});
|
|
7803
|
-
await logAuditEvent(db, {
|
|
7804
|
-
session_id: sessionId,
|
|
7805
|
-
action: "queue_joined",
|
|
7806
|
-
entity_type: "queue",
|
|
7807
|
-
entity_id: entry.id,
|
|
7808
|
-
metadata: {
|
|
7809
|
-
claim_id: claimId,
|
|
7810
|
-
position: entry.position,
|
|
7811
|
-
priority: entry.priority
|
|
7812
|
-
}
|
|
7813
|
-
});
|
|
7814
|
-
const priorityInfo = getPriorityLevel(entry.priority);
|
|
7815
|
-
return successResponse({
|
|
7816
|
-
queue_id: entry.id,
|
|
7817
|
-
position: entry.position,
|
|
7818
|
-
priority: priorityInfo,
|
|
7819
|
-
estimated_wait_minutes: entry.estimated_wait_minutes,
|
|
7820
|
-
message: `Joined queue at position ${entry.position}. Estimated wait: ${entry.estimated_wait_minutes} minutes.`,
|
|
7821
|
-
claim_owner: claim.session_name,
|
|
7822
|
-
claim_intent: claim.intent,
|
|
7823
|
-
files: claim.files
|
|
7824
|
-
});
|
|
7825
|
-
}
|
|
7826
|
-
case "collab_queue_leave": {
|
|
7827
|
-
const validation = validateInput(queueLeaveSchema, args);
|
|
7828
|
-
if (!validation.success) {
|
|
7829
|
-
return validationError(validation.error);
|
|
7830
|
-
}
|
|
7831
|
-
const { session_id: sessionId, queue_id: queueId } = validation.data;
|
|
7832
|
-
const sessionResult = await validateActiveSession(db, sessionId);
|
|
7833
|
-
if (!sessionResult.valid) {
|
|
7834
|
-
return sessionResult.error;
|
|
7835
|
-
}
|
|
7836
|
-
const entry = await getQueueEntry(db, queueId);
|
|
7837
|
-
if (!entry) {
|
|
7838
|
-
return errorResponse(
|
|
7839
|
-
ERROR_CODES.QUEUE_ENTRY_NOT_FOUND,
|
|
7840
|
-
"Queue entry not found"
|
|
7841
|
-
);
|
|
7842
|
-
}
|
|
7843
|
-
if (entry.session_id !== sessionId) {
|
|
7844
|
-
return errorResponse(
|
|
7845
|
-
ERROR_CODES.NOT_OWNER,
|
|
7846
|
-
"You can only leave your own queue entries"
|
|
7847
|
-
);
|
|
7848
|
-
}
|
|
7849
|
-
await leaveQueue(db, queueId);
|
|
7850
|
-
await logAuditEvent(db, {
|
|
7851
|
-
session_id: sessionId,
|
|
7852
|
-
action: "queue_left",
|
|
7853
|
-
entity_type: "queue",
|
|
7854
|
-
entity_id: queueId,
|
|
7855
|
-
metadata: { claim_id: entry.claim_id }
|
|
7856
|
-
});
|
|
7857
|
-
return successResponse({
|
|
7858
|
-
success: true,
|
|
7859
|
-
message: "Left the queue successfully"
|
|
7860
|
-
});
|
|
7861
|
-
}
|
|
7862
|
-
case "collab_queue_list": {
|
|
7863
|
-
const validation = validateInput(queueListSchema, args);
|
|
7864
|
-
if (!validation.success) {
|
|
7865
|
-
return validationError(validation.error);
|
|
7866
|
-
}
|
|
7867
|
-
const { claim_id: claimId, session_id: sessionId } = validation.data;
|
|
7868
|
-
const entries = await listQueue(db, { claim_id: claimId, session_id: sessionId });
|
|
7869
|
-
return successResponse({
|
|
7870
|
-
entries: entries.map((e) => ({
|
|
7871
|
-
queue_id: e.id,
|
|
7872
|
-
claim_id: e.claim_id,
|
|
7873
|
-
session_id: e.session_id,
|
|
7874
|
-
session_name: e.session_name,
|
|
7875
|
-
intent: e.intent,
|
|
7876
|
-
position: e.position,
|
|
7877
|
-
priority: getPriorityLevel(e.priority),
|
|
7878
|
-
scope: e.scope,
|
|
7879
|
-
estimated_wait_minutes: e.estimated_wait_minutes,
|
|
7880
|
-
claim_owner: e.claim_session_name,
|
|
7881
|
-
claim_intent: e.claim_intent,
|
|
7882
|
-
files: e.claim_files,
|
|
7883
|
-
created_at: e.created_at
|
|
7884
|
-
})),
|
|
7885
|
-
total: entries.length,
|
|
7886
|
-
message: entries.length > 0 ? `Found ${entries.length} queue entries` : "No queue entries found"
|
|
7887
|
-
}, true);
|
|
7888
|
-
}
|
|
7889
|
-
default:
|
|
7890
|
-
return successResponse({ error: `Unknown queue tool: ${name}` });
|
|
7891
|
-
}
|
|
7892
|
-
}
|
|
7893
|
-
|
|
7894
|
-
// src/mcp/tools/notification.ts
|
|
7895
|
-
var notificationTools = [
|
|
7896
|
-
{
|
|
7897
|
-
name: "collab_notifications_list",
|
|
7898
|
-
description: "List notifications for your session. Check periodically for claim releases, queue updates, and messages.",
|
|
7899
|
-
inputSchema: {
|
|
7900
|
-
type: "object",
|
|
7901
|
-
properties: {
|
|
7902
|
-
session_id: {
|
|
7903
|
-
type: "string",
|
|
7904
|
-
description: "Your session ID"
|
|
7905
|
-
},
|
|
7906
|
-
unread_only: {
|
|
7907
|
-
type: "boolean",
|
|
7908
|
-
description: "Only show unread notifications (default: false)"
|
|
7909
|
-
},
|
|
7910
|
-
type: {
|
|
7911
|
-
type: "string",
|
|
7912
|
-
enum: ["claim_released", "queue_ready", "conflict_detected", "session_message"],
|
|
7913
|
-
description: "Filter by notification type"
|
|
7914
|
-
},
|
|
7915
|
-
limit: {
|
|
7916
|
-
type: "number",
|
|
7917
|
-
description: "Maximum notifications to return (default: 50, max: 100)"
|
|
7918
|
-
}
|
|
7919
|
-
},
|
|
7920
|
-
required: ["session_id"]
|
|
7921
|
-
}
|
|
7922
|
-
},
|
|
7923
|
-
{
|
|
7924
|
-
name: "collab_notifications_mark_read",
|
|
7925
|
-
description: "Mark notifications as read.",
|
|
7926
|
-
inputSchema: {
|
|
7927
|
-
type: "object",
|
|
7928
|
-
properties: {
|
|
7929
|
-
session_id: {
|
|
7930
|
-
type: "string",
|
|
7931
|
-
description: "Your session ID"
|
|
7932
|
-
},
|
|
7933
|
-
notification_ids: {
|
|
7934
|
-
type: "array",
|
|
7935
|
-
items: { type: "string" },
|
|
7936
|
-
description: "Notification IDs to mark as read"
|
|
7937
|
-
}
|
|
7938
|
-
},
|
|
7939
|
-
required: ["session_id", "notification_ids"]
|
|
7940
|
-
}
|
|
7941
|
-
}
|
|
7942
|
-
];
|
|
7943
|
-
async function handleNotificationTool(db, name, args) {
|
|
7944
|
-
switch (name) {
|
|
7945
|
-
case "collab_notifications_list": {
|
|
7946
|
-
const validation = validateInput(notificationListSchema, args);
|
|
7947
|
-
if (!validation.success) {
|
|
7948
|
-
return validationError(validation.error);
|
|
7949
|
-
}
|
|
7950
|
-
const { session_id: sessionId, unread_only, type, limit } = validation.data;
|
|
7951
|
-
const sessionResult = await validateActiveSession(db, sessionId);
|
|
7952
|
-
if (!sessionResult.valid) {
|
|
7953
|
-
return sessionResult.error;
|
|
7954
|
-
}
|
|
7955
|
-
const notifications = await listNotifications(db, {
|
|
7956
|
-
session_id: sessionId,
|
|
7957
|
-
unread_only,
|
|
7958
|
-
type,
|
|
7959
|
-
limit
|
|
7960
|
-
});
|
|
7961
|
-
const unreadCount = notifications.filter((n) => !n.read_at).length;
|
|
7962
|
-
return successResponse({
|
|
7963
|
-
notifications: notifications.map((n) => ({
|
|
7964
|
-
id: n.id,
|
|
7965
|
-
type: n.type,
|
|
7966
|
-
title: n.title,
|
|
7967
|
-
message: n.message,
|
|
7968
|
-
reference_type: n.reference_type,
|
|
7969
|
-
reference_id: n.reference_id,
|
|
7970
|
-
metadata: n.metadata ? JSON.parse(n.metadata) : null,
|
|
7971
|
-
is_read: !!n.read_at,
|
|
7972
|
-
created_at: n.created_at
|
|
7973
|
-
})),
|
|
7974
|
-
total: notifications.length,
|
|
7975
|
-
unread_count: unreadCount,
|
|
7976
|
-
message: notifications.length > 0 ? `${notifications.length} notification(s), ${unreadCount} unread` : "No notifications"
|
|
7977
|
-
}, true);
|
|
7978
|
-
}
|
|
7979
|
-
case "collab_notifications_mark_read": {
|
|
7980
|
-
const validation = validateInput(notificationMarkReadSchema, args);
|
|
7981
|
-
if (!validation.success) {
|
|
7982
|
-
return validationError(validation.error);
|
|
7983
|
-
}
|
|
7984
|
-
const { session_id: sessionId, notification_ids: notificationIds } = validation.data;
|
|
7985
|
-
const sessionResult = await validateActiveSession(db, sessionId);
|
|
7986
|
-
if (!sessionResult.valid) {
|
|
7987
|
-
return sessionResult.error;
|
|
7988
|
-
}
|
|
7989
|
-
for (const notifId of notificationIds) {
|
|
7990
|
-
const notif = await getNotification(db, notifId);
|
|
7991
|
-
if (!notif) {
|
|
7992
|
-
return errorResponse(
|
|
7993
|
-
ERROR_CODES.NOTIFICATION_NOT_FOUND,
|
|
7994
|
-
`Notification ${notifId} not found`
|
|
7995
|
-
);
|
|
7996
|
-
}
|
|
7997
|
-
if (notif.session_id !== sessionId) {
|
|
7998
|
-
return errorResponse(
|
|
7999
|
-
ERROR_CODES.NOT_OWNER,
|
|
8000
|
-
"You can only mark your own notifications as read"
|
|
8001
|
-
);
|
|
8002
|
-
}
|
|
8003
|
-
}
|
|
8004
|
-
const markedCount = await markNotificationsRead(db, notificationIds);
|
|
8005
|
-
return successResponse({
|
|
8006
|
-
success: true,
|
|
8007
|
-
marked_count: markedCount,
|
|
8008
|
-
message: `Marked ${markedCount} notification(s) as read`
|
|
8009
|
-
});
|
|
8010
|
-
}
|
|
8011
|
-
default:
|
|
8012
|
-
return successResponse({ error: `Unknown notification tool: ${name}` });
|
|
8013
|
-
}
|
|
8014
|
-
}
|
|
8015
|
-
|
|
8016
|
-
// src/mcp/tools/memory.ts
|
|
8017
|
-
var memoryTools = [
|
|
8018
|
-
{
|
|
8019
|
-
name: "collab_memory_save",
|
|
8020
|
-
description: "Save important context to working memory. Use this to persist findings, decisions, state, or any critical information that should survive context compaction.",
|
|
8021
|
-
inputSchema: {
|
|
8022
|
-
type: "object",
|
|
8023
|
-
properties: {
|
|
8024
|
-
session_id: {
|
|
8025
|
-
type: "string",
|
|
8026
|
-
description: "Your session ID"
|
|
8027
|
-
},
|
|
8028
|
-
category: {
|
|
8029
|
-
type: "string",
|
|
8030
|
-
enum: ["finding", "decision", "state", "todo", "important", "context"],
|
|
8031
|
-
description: "Category of memory: finding (discovered facts), decision (choices made), state (current status), todo (pending work), important (critical info), context (general context)"
|
|
8032
|
-
},
|
|
8033
|
-
key: {
|
|
8034
|
-
type: "string",
|
|
8035
|
-
description: 'Unique identifier for this memory within the category (e.g., "auth_bug_root_cause")'
|
|
8036
|
-
},
|
|
8037
|
-
content: {
|
|
8038
|
-
type: "string",
|
|
8039
|
-
description: "The actual content to remember"
|
|
8040
|
-
},
|
|
8041
|
-
priority: {
|
|
8042
|
-
type: "number",
|
|
8043
|
-
description: "Priority 0-100 (default 50). Higher priority memories are more likely to be recalled."
|
|
8044
|
-
},
|
|
8045
|
-
pinned: {
|
|
8046
|
-
type: "boolean",
|
|
8047
|
-
description: "If true, this memory will always be loaded when recalling active memories."
|
|
8048
|
-
},
|
|
8049
|
-
expires_at: {
|
|
8050
|
-
type: "string",
|
|
8051
|
-
description: "Optional ISO datetime when this memory should expire (e.g., for temporary state)"
|
|
8052
|
-
},
|
|
8053
|
-
related_claim_id: {
|
|
8054
|
-
type: "string",
|
|
8055
|
-
description: "Optional claim ID this memory relates to"
|
|
8056
|
-
},
|
|
8057
|
-
metadata: {
|
|
8058
|
-
type: "object",
|
|
8059
|
-
description: "Optional additional structured data (file_path, line_number, confidence, etc.)"
|
|
8060
|
-
}
|
|
8061
|
-
},
|
|
8062
|
-
required: ["session_id", "category", "key", "content"]
|
|
8063
|
-
}
|
|
8064
|
-
},
|
|
8065
|
-
{
|
|
8066
|
-
name: "collab_memory_recall",
|
|
8067
|
-
description: "Recall memories from working memory. Use this to retrieve previously saved context, findings, or state.",
|
|
8068
|
-
inputSchema: {
|
|
8069
|
-
type: "object",
|
|
8070
|
-
properties: {
|
|
8071
|
-
session_id: {
|
|
8072
|
-
type: "string",
|
|
8073
|
-
description: "Your session ID"
|
|
8074
|
-
},
|
|
8075
|
-
category: {
|
|
8076
|
-
type: "string",
|
|
8077
|
-
enum: ["finding", "decision", "state", "todo", "important", "context"],
|
|
8078
|
-
description: "Filter by category (optional)"
|
|
8079
|
-
},
|
|
8080
|
-
key: {
|
|
8081
|
-
type: "string",
|
|
8082
|
-
description: "Get a specific memory by key (optional)"
|
|
8083
|
-
},
|
|
8084
|
-
pinned_only: {
|
|
8085
|
-
type: "boolean",
|
|
8086
|
-
description: "Only return pinned memories"
|
|
8087
|
-
},
|
|
8088
|
-
limit: {
|
|
8089
|
-
type: "number",
|
|
8090
|
-
description: "Maximum number of memories to return (default: all)"
|
|
8091
|
-
}
|
|
8092
|
-
},
|
|
8093
|
-
required: ["session_id"]
|
|
8094
|
-
}
|
|
8095
|
-
},
|
|
8096
|
-
{
|
|
8097
|
-
name: "collab_memory_update",
|
|
8098
|
-
description: "Update an existing memory entry. Use this to modify content, priority, or other properties.",
|
|
8099
|
-
inputSchema: {
|
|
8100
|
-
type: "object",
|
|
8101
|
-
properties: {
|
|
8102
|
-
session_id: {
|
|
8103
|
-
type: "string",
|
|
8104
|
-
description: "Your session ID"
|
|
8105
|
-
},
|
|
8106
|
-
key: {
|
|
8107
|
-
type: "string",
|
|
8108
|
-
description: "The key of the memory to update"
|
|
8109
|
-
},
|
|
8110
|
-
content: {
|
|
8111
|
-
type: "string",
|
|
8112
|
-
description: "New content (optional)"
|
|
8113
|
-
},
|
|
8114
|
-
priority: {
|
|
8115
|
-
type: "number",
|
|
8116
|
-
description: "New priority 0-100 (optional)"
|
|
8117
|
-
},
|
|
8118
|
-
pinned: {
|
|
8119
|
-
type: "boolean",
|
|
8120
|
-
description: "New pinned status (optional)"
|
|
8121
|
-
},
|
|
8122
|
-
expires_at: {
|
|
8123
|
-
type: "string",
|
|
8124
|
-
description: "New expiration time, or null to remove expiration (optional)"
|
|
8125
|
-
},
|
|
8126
|
-
metadata: {
|
|
8127
|
-
type: "object",
|
|
8128
|
-
description: "New metadata (replaces existing)"
|
|
8129
|
-
}
|
|
8130
|
-
},
|
|
8131
|
-
required: ["session_id", "key"]
|
|
8132
|
-
}
|
|
8133
|
-
},
|
|
8134
|
-
{
|
|
8135
|
-
name: "collab_memory_clear",
|
|
8136
|
-
description: "Clear memories from working memory. Can clear by key, category, or all.",
|
|
8137
|
-
inputSchema: {
|
|
8138
|
-
type: "object",
|
|
8139
|
-
properties: {
|
|
8140
|
-
session_id: {
|
|
8141
|
-
type: "string",
|
|
8142
|
-
description: "Your session ID"
|
|
8143
|
-
},
|
|
8144
|
-
key: {
|
|
8145
|
-
type: "string",
|
|
8146
|
-
description: "Clear a specific memory by key"
|
|
8147
|
-
},
|
|
8148
|
-
category: {
|
|
8149
|
-
type: "string",
|
|
8150
|
-
enum: ["finding", "decision", "state", "todo", "important", "context"],
|
|
8151
|
-
description: "Clear all memories in a category"
|
|
8152
|
-
},
|
|
8153
|
-
clear_all: {
|
|
8154
|
-
type: "boolean",
|
|
8155
|
-
description: "Clear ALL memories for this session (use with caution)"
|
|
8156
|
-
}
|
|
8157
|
-
},
|
|
8158
|
-
required: ["session_id"]
|
|
8159
|
-
}
|
|
8160
|
-
},
|
|
8161
|
-
{
|
|
8162
|
-
name: "collab_memory_pin",
|
|
8163
|
-
description: "Pin or unpin a memory. Pinned memories are always included when loading active memories.",
|
|
8164
|
-
inputSchema: {
|
|
8165
|
-
type: "object",
|
|
8166
|
-
properties: {
|
|
8167
|
-
session_id: {
|
|
8168
|
-
type: "string",
|
|
8169
|
-
description: "Your session ID"
|
|
8170
|
-
},
|
|
8171
|
-
key: {
|
|
8172
|
-
type: "string",
|
|
8173
|
-
description: "The key of the memory to pin/unpin"
|
|
8174
|
-
},
|
|
8175
|
-
pinned: {
|
|
8176
|
-
type: "boolean",
|
|
8177
|
-
description: "True to pin, false to unpin"
|
|
8178
|
-
}
|
|
8179
|
-
},
|
|
8180
|
-
required: ["session_id", "key", "pinned"]
|
|
8181
|
-
}
|
|
8182
|
-
},
|
|
8183
|
-
{
|
|
8184
|
-
name: "collab_memory_stats",
|
|
8185
|
-
description: "Get statistics about working memory usage for this session.",
|
|
8186
|
-
inputSchema: {
|
|
8187
|
-
type: "object",
|
|
8188
|
-
properties: {
|
|
8189
|
-
session_id: {
|
|
8190
|
-
type: "string",
|
|
8191
|
-
description: "Your session ID"
|
|
8192
|
-
}
|
|
8193
|
-
},
|
|
8194
|
-
required: ["session_id"]
|
|
8195
|
-
}
|
|
8196
|
-
},
|
|
8197
|
-
{
|
|
8198
|
-
name: "collab_memory_active",
|
|
8199
|
-
description: "Get all active (pinned + high priority) memories. This is the main function for loading context that should be preserved.",
|
|
8200
|
-
inputSchema: {
|
|
8201
|
-
type: "object",
|
|
8202
|
-
properties: {
|
|
8203
|
-
session_id: {
|
|
8204
|
-
type: "string",
|
|
8205
|
-
description: "Your session ID"
|
|
8206
|
-
},
|
|
8207
|
-
priority_threshold: {
|
|
8208
|
-
type: "number",
|
|
8209
|
-
description: "Minimum priority to include (default: 70)"
|
|
8210
|
-
},
|
|
8211
|
-
max_items: {
|
|
8212
|
-
type: "number",
|
|
8213
|
-
description: "Maximum items to return (default: 20)"
|
|
8214
|
-
}
|
|
8215
|
-
},
|
|
8216
|
-
required: ["session_id"]
|
|
8217
|
-
}
|
|
8218
|
-
}
|
|
8219
|
-
];
|
|
8220
|
-
async function handleMemoryTool(db, name, args) {
|
|
8221
|
-
switch (name) {
|
|
8222
|
-
case "collab_memory_save":
|
|
8223
|
-
return handleMemorySave(db, args);
|
|
8224
|
-
case "collab_memory_recall":
|
|
8225
|
-
return handleMemoryRecall(db, args);
|
|
8226
|
-
case "collab_memory_update":
|
|
8227
|
-
return handleMemoryUpdate(db, args);
|
|
8228
|
-
case "collab_memory_clear":
|
|
8229
|
-
return handleMemoryClear(db, args);
|
|
8230
|
-
case "collab_memory_pin":
|
|
8231
|
-
return handleMemoryPin(db, args);
|
|
8232
|
-
case "collab_memory_stats":
|
|
8233
|
-
return handleMemoryStats(db, args);
|
|
8234
|
-
case "collab_memory_active":
|
|
8235
|
-
return handleMemoryActive(db, args);
|
|
8236
|
-
default:
|
|
8237
|
-
return errorResponse(ERROR_CODES.UNKNOWN_TOOL, `Unknown memory tool: ${name}`);
|
|
8238
|
-
}
|
|
8239
|
-
}
|
|
8240
|
-
async function handleMemorySave(db, args) {
|
|
8241
|
-
const sessionId = args.session_id;
|
|
8242
|
-
const category = args.category;
|
|
8243
|
-
const key = args.key;
|
|
8244
|
-
const content = args.content;
|
|
8245
|
-
if (!sessionId || !category || !key || !content) {
|
|
8246
|
-
return validationError("session_id, category, key, and content are required");
|
|
8247
|
-
}
|
|
8248
|
-
const sessionCheck = await validateActiveSession(db, sessionId);
|
|
8249
|
-
if (!sessionCheck.valid) {
|
|
8250
|
-
return sessionCheck.error;
|
|
8251
|
-
}
|
|
8252
|
-
const memory = await saveMemory(db, sessionId, {
|
|
8253
|
-
category,
|
|
8254
|
-
key,
|
|
8255
|
-
content,
|
|
8256
|
-
priority: args.priority ?? 50,
|
|
8257
|
-
pinned: args.pinned ?? false,
|
|
8258
|
-
expires_at: args.expires_at,
|
|
8259
|
-
related_claim_id: args.related_claim_id,
|
|
8260
|
-
related_decision_id: args.related_decision_id,
|
|
8261
|
-
metadata: args.metadata
|
|
8262
|
-
});
|
|
8263
|
-
return successResponse({
|
|
8264
|
-
saved: true,
|
|
8265
|
-
memory: {
|
|
8266
|
-
id: memory.id,
|
|
8267
|
-
category: memory.category,
|
|
8268
|
-
key: memory.key,
|
|
8269
|
-
priority: memory.priority,
|
|
8270
|
-
pinned: memory.pinned === 1,
|
|
8271
|
-
created_at: memory.created_at,
|
|
8272
|
-
updated_at: memory.updated_at
|
|
8273
|
-
},
|
|
8274
|
-
message: `Memory saved: ${category}/${key}`
|
|
8275
|
-
});
|
|
8276
|
-
}
|
|
8277
|
-
async function handleMemoryRecall(db, args) {
|
|
8278
|
-
const sessionId = args.session_id;
|
|
8279
|
-
if (!sessionId) {
|
|
8280
|
-
return validationError("session_id is required");
|
|
8281
|
-
}
|
|
8282
|
-
const sessionCheck = await validateActiveSession(db, sessionId);
|
|
8283
|
-
if (!sessionCheck.valid) {
|
|
8284
|
-
return sessionCheck.error;
|
|
8285
|
-
}
|
|
8286
|
-
const memories = await recallMemory(db, sessionId, {
|
|
8287
|
-
category: args.category,
|
|
8288
|
-
key: args.key,
|
|
8289
|
-
pinned_only: args.pinned_only,
|
|
8290
|
-
limit: args.limit
|
|
8291
|
-
});
|
|
8292
|
-
const formattedMemories = memories.map((m) => ({
|
|
8293
|
-
category: m.category,
|
|
8294
|
-
key: m.key,
|
|
8295
|
-
content: m.content,
|
|
8296
|
-
priority: m.priority,
|
|
8297
|
-
pinned: m.pinned === 1,
|
|
8298
|
-
created_at: m.created_at,
|
|
8299
|
-
updated_at: m.updated_at,
|
|
8300
|
-
expires_at: m.expires_at,
|
|
8301
|
-
metadata: m.metadata ? JSON.parse(m.metadata) : null
|
|
8302
|
-
}));
|
|
8303
|
-
return successResponse({
|
|
8304
|
-
count: formattedMemories.length,
|
|
8305
|
-
memories: formattedMemories
|
|
8306
|
-
});
|
|
8307
|
-
}
|
|
8308
|
-
async function handleMemoryUpdate(db, args) {
|
|
8309
|
-
const sessionId = args.session_id;
|
|
8310
|
-
const key = args.key;
|
|
8311
|
-
if (!sessionId || !key) {
|
|
8312
|
-
return validationError("session_id and key are required");
|
|
8313
|
-
}
|
|
8314
|
-
const sessionCheck = await validateActiveSession(db, sessionId);
|
|
8315
|
-
if (!sessionCheck.valid) {
|
|
8316
|
-
return sessionCheck.error;
|
|
8317
|
-
}
|
|
8318
|
-
const updates = {};
|
|
8319
|
-
if (args.content !== void 0) updates.content = args.content;
|
|
8320
|
-
if (args.priority !== void 0) updates.priority = args.priority;
|
|
8321
|
-
if (args.pinned !== void 0) updates.pinned = args.pinned;
|
|
8322
|
-
if (args.expires_at !== void 0) updates.expires_at = args.expires_at;
|
|
8323
|
-
if (args.metadata !== void 0) updates.metadata = args.metadata;
|
|
8324
|
-
if (Object.keys(updates).length === 0) {
|
|
8325
|
-
return validationError("At least one field to update is required");
|
|
8326
|
-
}
|
|
8327
|
-
const updated = await updateMemory(db, sessionId, key, updates);
|
|
8328
|
-
if (!updated) {
|
|
8329
|
-
return errorResponse(ERROR_CODES.MEMORY_NOT_FOUND, `Memory not found: ${key}`);
|
|
8330
|
-
}
|
|
8331
|
-
return successResponse({
|
|
8332
|
-
updated: true,
|
|
8333
|
-
key,
|
|
8334
|
-
message: `Memory updated: ${key}`
|
|
8335
|
-
});
|
|
8336
|
-
}
|
|
8337
|
-
async function handleMemoryClear(db, args) {
|
|
8338
|
-
const sessionId = args.session_id;
|
|
8339
|
-
if (!sessionId) {
|
|
8340
|
-
return validationError("session_id is required");
|
|
8341
|
-
}
|
|
8342
|
-
const sessionCheck = await validateActiveSession(db, sessionId);
|
|
8343
|
-
if (!sessionCheck.valid) {
|
|
8344
|
-
return sessionCheck.error;
|
|
8345
|
-
}
|
|
8346
|
-
const key = args.key;
|
|
8347
|
-
const category = args.category;
|
|
8348
|
-
const clearAll = args.clear_all;
|
|
8349
|
-
if (!key && !category && !clearAll) {
|
|
8350
|
-
return validationError("One of key, category, or clear_all is required");
|
|
8351
|
-
}
|
|
8352
|
-
const cleared = await clearMemory(db, sessionId, {
|
|
8353
|
-
key,
|
|
8354
|
-
category,
|
|
8355
|
-
clear_all: clearAll
|
|
8356
|
-
});
|
|
8357
|
-
let message;
|
|
8358
|
-
if (key) {
|
|
8359
|
-
message = cleared > 0 ? `Memory cleared: ${key}` : `Memory not found: ${key}`;
|
|
8360
|
-
} else if (category) {
|
|
8361
|
-
message = `Cleared ${cleared} memories from category: ${category}`;
|
|
8362
|
-
} else {
|
|
8363
|
-
message = `Cleared all ${cleared} memories`;
|
|
8364
|
-
}
|
|
8365
|
-
return successResponse({
|
|
8366
|
-
cleared,
|
|
8367
|
-
message
|
|
8368
|
-
});
|
|
8369
|
-
}
|
|
8370
|
-
async function handleMemoryPin(db, args) {
|
|
8371
|
-
const sessionId = args.session_id;
|
|
8372
|
-
const key = args.key;
|
|
8373
|
-
const pinned = args.pinned;
|
|
8374
|
-
if (!sessionId || !key || pinned === void 0) {
|
|
8375
|
-
return validationError("session_id, key, and pinned are required");
|
|
8376
|
-
}
|
|
8377
|
-
const sessionCheck = await validateActiveSession(db, sessionId);
|
|
8378
|
-
if (!sessionCheck.valid) {
|
|
8379
|
-
return sessionCheck.error;
|
|
8380
|
-
}
|
|
8381
|
-
const updated = await pinMemory(db, sessionId, key, pinned);
|
|
8382
|
-
if (!updated) {
|
|
8383
|
-
return errorResponse(ERROR_CODES.MEMORY_NOT_FOUND, `Memory not found: ${key}`);
|
|
8384
|
-
}
|
|
8385
|
-
return successResponse({
|
|
8386
|
-
updated: true,
|
|
8387
|
-
key,
|
|
8388
|
-
pinned,
|
|
8389
|
-
message: pinned ? `Memory pinned: ${key}` : `Memory unpinned: ${key}`
|
|
8390
|
-
});
|
|
8391
|
-
}
|
|
8392
|
-
async function handleMemoryStats(db, args) {
|
|
8393
|
-
const sessionId = args.session_id;
|
|
8394
|
-
if (!sessionId) {
|
|
8395
|
-
return validationError("session_id is required");
|
|
8396
|
-
}
|
|
8397
|
-
const sessionCheck = await validateActiveSession(db, sessionId);
|
|
8398
|
-
if (!sessionCheck.valid) {
|
|
8399
|
-
return sessionCheck.error;
|
|
8400
|
-
}
|
|
8401
|
-
const stats = await getMemoryStats(db, sessionId);
|
|
8402
|
-
return successResponse({
|
|
8403
|
-
...stats,
|
|
8404
|
-
message: `Working memory: ${stats.total} total, ${stats.pinned_count} pinned`
|
|
8405
|
-
});
|
|
8406
|
-
}
|
|
8407
|
-
async function handleMemoryActive(db, args) {
|
|
8408
|
-
const sessionId = args.session_id;
|
|
8409
|
-
if (!sessionId) {
|
|
8410
|
-
return validationError("session_id is required");
|
|
8411
|
-
}
|
|
8412
|
-
const sessionCheck = await validateActiveSession(db, sessionId);
|
|
8413
|
-
if (!sessionCheck.valid) {
|
|
8414
|
-
return sessionCheck.error;
|
|
8415
|
-
}
|
|
8416
|
-
const memories = await getActiveMemories(db, sessionId, {
|
|
8417
|
-
priority_threshold: args.priority_threshold,
|
|
8418
|
-
max_items: args.max_items
|
|
8419
|
-
});
|
|
8420
|
-
const formattedMemories = memories.map((m) => ({
|
|
8421
|
-
category: m.category,
|
|
8422
|
-
key: m.key,
|
|
8423
|
-
content: m.content,
|
|
8424
|
-
priority: m.priority,
|
|
8425
|
-
pinned: m.pinned === 1
|
|
8426
|
-
}));
|
|
8427
|
-
const byCategory = {};
|
|
8428
|
-
for (const mem of formattedMemories) {
|
|
8429
|
-
if (!byCategory[mem.category]) {
|
|
8430
|
-
byCategory[mem.category] = [];
|
|
8431
|
-
}
|
|
8432
|
-
byCategory[mem.category].push({
|
|
8433
|
-
key: mem.key,
|
|
8434
|
-
content: mem.content,
|
|
8435
|
-
priority: mem.priority,
|
|
8436
|
-
pinned: mem.pinned
|
|
8437
|
-
});
|
|
8438
|
-
}
|
|
8439
|
-
return successResponse({
|
|
8440
|
-
count: formattedMemories.length,
|
|
8441
|
-
by_category: byCategory,
|
|
8442
|
-
memories: formattedMemories,
|
|
8443
|
-
message: `Active memories: ${formattedMemories.length} items (pinned + priority >= threshold)`
|
|
8444
|
-
});
|
|
8445
|
-
}
|
|
8446
|
-
|
|
8447
|
-
// src/mcp/tools/protection.ts
|
|
8448
|
-
var protectionTools = [
|
|
8449
|
-
{
|
|
8450
|
-
name: "collab_plan_register",
|
|
8451
|
-
description: "Register a plan document for protection. Plans are automatically pinned with high priority to prevent context loss.",
|
|
8452
|
-
inputSchema: {
|
|
8453
|
-
type: "object",
|
|
8454
|
-
properties: {
|
|
8455
|
-
session_id: {
|
|
8456
|
-
type: "string",
|
|
8457
|
-
description: "Your session ID"
|
|
8458
|
-
},
|
|
8459
|
-
file_path: {
|
|
8460
|
-
type: "string",
|
|
8461
|
-
description: "Path to the plan file"
|
|
8462
|
-
},
|
|
8463
|
-
title: {
|
|
8464
|
-
type: "string",
|
|
8465
|
-
description: "Title of the plan"
|
|
8466
|
-
},
|
|
8467
|
-
content_summary: {
|
|
8468
|
-
type: "string",
|
|
8469
|
-
description: "Summary of the plan content (key points, steps)"
|
|
8470
|
-
},
|
|
8471
|
-
status: {
|
|
8472
|
-
type: "string",
|
|
8473
|
-
enum: ["draft", "approved", "in_progress"],
|
|
8474
|
-
description: "Initial status of the plan (default: draft)"
|
|
8475
|
-
}
|
|
8476
|
-
},
|
|
8477
|
-
required: ["session_id", "file_path", "title", "content_summary"]
|
|
8478
|
-
}
|
|
8479
|
-
},
|
|
8480
|
-
{
|
|
8481
|
-
name: "collab_plan_update_status",
|
|
8482
|
-
description: "Update plan status. Use this to progress plan through lifecycle: draft \u2192 approved \u2192 in_progress \u2192 completed \u2192 archived",
|
|
8483
|
-
inputSchema: {
|
|
8484
|
-
type: "object",
|
|
8485
|
-
properties: {
|
|
8486
|
-
session_id: {
|
|
8487
|
-
type: "string",
|
|
8488
|
-
description: "Your session ID"
|
|
8489
|
-
},
|
|
8490
|
-
file_path: {
|
|
8491
|
-
type: "string",
|
|
8492
|
-
description: "Path to the plan file"
|
|
8493
|
-
},
|
|
8494
|
-
status: {
|
|
8495
|
-
type: "string",
|
|
8496
|
-
enum: ["draft", "approved", "in_progress", "completed", "archived"],
|
|
8497
|
-
description: "New status for the plan"
|
|
8498
|
-
},
|
|
8499
|
-
summary: {
|
|
8500
|
-
type: "string",
|
|
8501
|
-
description: "Optional updated summary (for completed plans, describe what was achieved)"
|
|
8502
|
-
}
|
|
8503
|
-
},
|
|
8504
|
-
required: ["session_id", "file_path", "status"]
|
|
8505
|
-
}
|
|
8506
|
-
},
|
|
8507
|
-
{
|
|
8508
|
-
name: "collab_plan_get",
|
|
8509
|
-
description: "Get plan information by file path.",
|
|
8510
|
-
inputSchema: {
|
|
8511
|
-
type: "object",
|
|
8512
|
-
properties: {
|
|
8513
|
-
session_id: {
|
|
8514
|
-
type: "string",
|
|
8515
|
-
description: "Your session ID"
|
|
8516
|
-
},
|
|
8517
|
-
file_path: {
|
|
8518
|
-
type: "string",
|
|
8519
|
-
description: "Path to the plan file"
|
|
8520
|
-
}
|
|
8521
|
-
},
|
|
8522
|
-
required: ["session_id", "file_path"]
|
|
8523
|
-
}
|
|
8524
|
-
},
|
|
8525
|
-
{
|
|
8526
|
-
name: "collab_plan_list",
|
|
8527
|
-
description: "List all plans for this session.",
|
|
8528
|
-
inputSchema: {
|
|
8529
|
-
type: "object",
|
|
8530
|
-
properties: {
|
|
8531
|
-
session_id: {
|
|
8532
|
-
type: "string",
|
|
8533
|
-
description: "Your session ID"
|
|
8534
|
-
},
|
|
8535
|
-
status: {
|
|
8536
|
-
type: "string",
|
|
8537
|
-
enum: ["draft", "approved", "in_progress", "completed", "archived"],
|
|
8538
|
-
description: "Filter by status"
|
|
8539
|
-
},
|
|
8540
|
-
include_archived: {
|
|
8541
|
-
type: "boolean",
|
|
8542
|
-
description: "Include archived plans (default: false)"
|
|
8543
|
-
}
|
|
8544
|
-
},
|
|
8545
|
-
required: ["session_id"]
|
|
8546
|
-
}
|
|
8547
|
-
},
|
|
8548
|
-
{
|
|
8549
|
-
name: "collab_file_register",
|
|
8550
|
-
description: "Register a file created in this session for protection. Protected files will trigger warnings before deletion.",
|
|
8551
|
-
inputSchema: {
|
|
8552
|
-
type: "object",
|
|
8553
|
-
properties: {
|
|
8554
|
-
session_id: {
|
|
8555
|
-
type: "string",
|
|
8556
|
-
description: "Your session ID"
|
|
8557
|
-
},
|
|
8558
|
-
file_path: {
|
|
8559
|
-
type: "string",
|
|
8560
|
-
description: "Path to the created file"
|
|
8561
|
-
},
|
|
8562
|
-
file_type: {
|
|
8563
|
-
type: "string",
|
|
8564
|
-
enum: ["plan", "code", "config", "doc", "other"],
|
|
8565
|
-
description: "Type of file (default: other)"
|
|
8566
|
-
},
|
|
8567
|
-
description: {
|
|
8568
|
-
type: "string",
|
|
8569
|
-
description: "Brief description of the file purpose"
|
|
8570
|
-
}
|
|
8571
|
-
},
|
|
8572
|
-
required: ["session_id", "file_path"]
|
|
8573
|
-
}
|
|
8574
|
-
},
|
|
8575
|
-
{
|
|
8576
|
-
name: "collab_file_list_created",
|
|
8577
|
-
description: "List all files created in this session.",
|
|
8578
|
-
inputSchema: {
|
|
8579
|
-
type: "object",
|
|
8580
|
-
properties: {
|
|
8581
|
-
session_id: {
|
|
8582
|
-
type: "string",
|
|
8583
|
-
description: "Your session ID"
|
|
8584
|
-
}
|
|
8585
|
-
},
|
|
8586
|
-
required: ["session_id"]
|
|
8587
|
-
}
|
|
8588
|
-
},
|
|
8589
|
-
{
|
|
8590
|
-
name: "collab_file_check_protected",
|
|
8591
|
-
description: "Check if a file is protected. Use this before deleting or overwriting files to prevent accidental loss.",
|
|
8592
|
-
inputSchema: {
|
|
8593
|
-
type: "object",
|
|
8594
|
-
properties: {
|
|
8595
|
-
session_id: {
|
|
8596
|
-
type: "string",
|
|
8597
|
-
description: "Your session ID"
|
|
8598
|
-
},
|
|
8599
|
-
file_path: {
|
|
8600
|
-
type: "string",
|
|
8601
|
-
description: "Path to check"
|
|
8602
|
-
}
|
|
8603
|
-
},
|
|
8604
|
-
required: ["session_id", "file_path"]
|
|
8605
|
-
}
|
|
8606
|
-
},
|
|
8607
|
-
{
|
|
8608
|
-
name: "collab_file_list_protected",
|
|
8609
|
-
description: "List all protected files in this session (plans + created files).",
|
|
8610
|
-
inputSchema: {
|
|
8611
|
-
type: "object",
|
|
8612
|
-
properties: {
|
|
8613
|
-
session_id: {
|
|
8614
|
-
type: "string",
|
|
8615
|
-
description: "Your session ID"
|
|
8616
|
-
}
|
|
8617
|
-
},
|
|
8618
|
-
required: ["session_id"]
|
|
8619
|
-
}
|
|
8620
|
-
}
|
|
8621
|
-
];
|
|
8622
|
-
async function handleProtectionTool(db, name, args) {
|
|
8623
|
-
switch (name) {
|
|
8624
|
-
case "collab_plan_register":
|
|
8625
|
-
return handlePlanRegister(db, args);
|
|
8626
|
-
case "collab_plan_update_status":
|
|
8627
|
-
return handlePlanUpdateStatus(db, args);
|
|
8628
|
-
case "collab_plan_get":
|
|
8629
|
-
return handlePlanGet(db, args);
|
|
8630
|
-
case "collab_plan_list":
|
|
8631
|
-
return handlePlanList(db, args);
|
|
8632
|
-
case "collab_file_register":
|
|
8633
|
-
return handleFileRegister(db, args);
|
|
8634
|
-
case "collab_file_list_created":
|
|
8635
|
-
return handleFileListCreated(db, args);
|
|
8636
|
-
case "collab_file_check_protected":
|
|
8637
|
-
return handleFileCheckProtected(db, args);
|
|
8638
|
-
case "collab_file_list_protected":
|
|
8639
|
-
return handleFileListProtected(db, args);
|
|
8640
|
-
default:
|
|
8641
|
-
return errorResponse(ERROR_CODES.UNKNOWN_TOOL, `Unknown protection tool: ${name}`);
|
|
8642
|
-
}
|
|
8643
|
-
}
|
|
8644
|
-
async function handlePlanRegister(db, args) {
|
|
8645
|
-
const sessionId = args.session_id;
|
|
8646
|
-
const filePath = args.file_path;
|
|
8647
|
-
const title = args.title;
|
|
8648
|
-
const contentSummary = args.content_summary;
|
|
8649
|
-
const status = args.status ?? "draft";
|
|
8650
|
-
if (!sessionId || !filePath || !title || !contentSummary) {
|
|
8651
|
-
return validationError("session_id, file_path, title, and content_summary are required");
|
|
8652
|
-
}
|
|
8653
|
-
const sessionCheck = await validateActiveSession(db, sessionId);
|
|
8654
|
-
if (!sessionCheck.valid) {
|
|
8655
|
-
return sessionCheck.error;
|
|
8656
|
-
}
|
|
8657
|
-
const memory = await registerPlan(db, sessionId, {
|
|
8658
|
-
file_path: filePath,
|
|
8659
|
-
title,
|
|
8660
|
-
content_summary: contentSummary,
|
|
8661
|
-
status
|
|
8662
|
-
});
|
|
8663
|
-
return successResponse({
|
|
8664
|
-
registered: true,
|
|
8665
|
-
plan: {
|
|
8666
|
-
file_path: filePath,
|
|
8667
|
-
title,
|
|
8668
|
-
status,
|
|
8669
|
-
priority: memory.priority,
|
|
8670
|
-
pinned: memory.pinned === 1
|
|
8671
|
-
},
|
|
8672
|
-
message: `Plan registered and protected: ${title}`
|
|
8673
|
-
});
|
|
8674
|
-
}
|
|
8675
|
-
async function handlePlanUpdateStatus(db, args) {
|
|
8676
|
-
const sessionId = args.session_id;
|
|
8677
|
-
const filePath = args.file_path;
|
|
8678
|
-
const status = args.status;
|
|
8679
|
-
const summary = args.summary;
|
|
8680
|
-
if (!sessionId || !filePath || !status) {
|
|
8681
|
-
return validationError("session_id, file_path, and status are required");
|
|
8682
|
-
}
|
|
8683
|
-
const sessionCheck = await validateActiveSession(db, sessionId);
|
|
8684
|
-
if (!sessionCheck.valid) {
|
|
8685
|
-
return sessionCheck.error;
|
|
8686
|
-
}
|
|
8687
|
-
const updated = await updatePlanStatus(db, sessionId, filePath, status, summary);
|
|
8688
|
-
if (!updated) {
|
|
8689
|
-
return errorResponse(ERROR_CODES.MEMORY_NOT_FOUND, `Plan not found: ${filePath}`);
|
|
8690
|
-
}
|
|
8691
|
-
let message = `Plan status updated to: ${status}`;
|
|
8692
|
-
if (status === "completed") {
|
|
8693
|
-
message += ". Plan protection reduced (unpinned, lower priority).";
|
|
8694
|
-
} else if (status === "archived") {
|
|
8695
|
-
message += ". Plan archived and will be excluded from active memory.";
|
|
8696
|
-
}
|
|
8697
|
-
return successResponse({
|
|
8698
|
-
updated: true,
|
|
8699
|
-
file_path: filePath,
|
|
8700
|
-
new_status: status,
|
|
8701
|
-
message
|
|
8702
|
-
});
|
|
8703
|
-
}
|
|
8704
|
-
async function handlePlanGet(db, args) {
|
|
8705
|
-
const sessionId = args.session_id;
|
|
8706
|
-
const filePath = args.file_path;
|
|
8707
|
-
if (!sessionId || !filePath) {
|
|
8708
|
-
return validationError("session_id and file_path are required");
|
|
8709
|
-
}
|
|
8710
|
-
const sessionCheck = await validateActiveSession(db, sessionId);
|
|
8711
|
-
if (!sessionCheck.valid) {
|
|
8712
|
-
return sessionCheck.error;
|
|
8713
|
-
}
|
|
8714
|
-
const plan = await getPlan(db, sessionId, filePath);
|
|
8715
|
-
if (!plan) {
|
|
8716
|
-
return successResponse({
|
|
8717
|
-
found: false,
|
|
8718
|
-
message: `No plan found for: ${filePath}`
|
|
8719
|
-
});
|
|
8720
|
-
}
|
|
8721
|
-
return successResponse({
|
|
8722
|
-
found: true,
|
|
8723
|
-
plan
|
|
8724
|
-
});
|
|
8725
|
-
}
|
|
8726
|
-
async function handlePlanList(db, args) {
|
|
8727
|
-
const sessionId = args.session_id;
|
|
8728
|
-
const status = args.status;
|
|
8729
|
-
const includeArchived = args.include_archived;
|
|
8730
|
-
if (!sessionId) {
|
|
8731
|
-
return validationError("session_id is required");
|
|
8732
|
-
}
|
|
8733
|
-
const sessionCheck = await validateActiveSession(db, sessionId);
|
|
8734
|
-
if (!sessionCheck.valid) {
|
|
8735
|
-
return sessionCheck.error;
|
|
8736
|
-
}
|
|
8737
|
-
const plans = await listPlans(db, sessionId, { status, include_archived: includeArchived });
|
|
8738
|
-
return successResponse({
|
|
8739
|
-
count: plans.length,
|
|
8740
|
-
plans
|
|
8741
|
-
});
|
|
8742
|
-
}
|
|
8743
|
-
async function handleFileRegister(db, args) {
|
|
8744
|
-
const sessionId = args.session_id;
|
|
8745
|
-
const filePath = args.file_path;
|
|
8746
|
-
const fileType = args.file_type;
|
|
8747
|
-
const description = args.description;
|
|
8748
|
-
if (!sessionId || !filePath) {
|
|
8749
|
-
return validationError("session_id and file_path are required");
|
|
8750
|
-
}
|
|
8751
|
-
const sessionCheck = await validateActiveSession(db, sessionId);
|
|
8752
|
-
if (!sessionCheck.valid) {
|
|
8753
|
-
return sessionCheck.error;
|
|
8754
|
-
}
|
|
8755
|
-
const memory = await registerCreatedFile(db, sessionId, {
|
|
8756
|
-
file_path: filePath,
|
|
8757
|
-
file_type: fileType,
|
|
8758
|
-
description
|
|
8759
|
-
});
|
|
8760
|
-
return successResponse({
|
|
8761
|
-
registered: true,
|
|
8762
|
-
file: {
|
|
8763
|
-
file_path: filePath,
|
|
8764
|
-
file_type: fileType ?? "other",
|
|
8765
|
-
priority: memory.priority,
|
|
8766
|
-
pinned: memory.pinned === 1
|
|
8767
|
-
},
|
|
8768
|
-
message: `File registered for protection: ${filePath}`
|
|
8769
|
-
});
|
|
8770
|
-
}
|
|
8771
|
-
async function handleFileListCreated(db, args) {
|
|
8772
|
-
const sessionId = args.session_id;
|
|
8773
|
-
if (!sessionId) {
|
|
8774
|
-
return validationError("session_id is required");
|
|
8775
|
-
}
|
|
8776
|
-
const sessionCheck = await validateActiveSession(db, sessionId);
|
|
8777
|
-
if (!sessionCheck.valid) {
|
|
8778
|
-
return sessionCheck.error;
|
|
8779
|
-
}
|
|
8780
|
-
const files = await getCreatedFiles(db, sessionId);
|
|
8781
|
-
return successResponse({
|
|
8782
|
-
count: files.length,
|
|
8783
|
-
files
|
|
8784
|
-
});
|
|
8785
|
-
}
|
|
8786
|
-
async function handleFileCheckProtected(db, args) {
|
|
8787
|
-
const sessionId = args.session_id;
|
|
8788
|
-
const filePath = args.file_path;
|
|
8789
|
-
if (!sessionId || !filePath) {
|
|
8790
|
-
return validationError("session_id and file_path are required");
|
|
8791
|
-
}
|
|
8792
|
-
const sessionCheck = await validateActiveSession(db, sessionId);
|
|
8793
|
-
if (!sessionCheck.valid) {
|
|
8794
|
-
return sessionCheck.error;
|
|
8795
|
-
}
|
|
8796
|
-
const result = await isFileProtected(db, sessionId, filePath);
|
|
8797
|
-
if (result.protected) {
|
|
8798
|
-
return successResponse({
|
|
8799
|
-
protected: true,
|
|
8800
|
-
reason: result.reason,
|
|
8801
|
-
details: result.details,
|
|
8802
|
-
warning: `\u26A0\uFE0F This file is protected (${result.reason}). Confirm before deleting or overwriting.`
|
|
8803
|
-
});
|
|
8804
|
-
}
|
|
8805
|
-
return successResponse({
|
|
8806
|
-
protected: false,
|
|
8807
|
-
message: "File is not protected."
|
|
8808
|
-
});
|
|
8809
|
-
}
|
|
8810
|
-
async function handleFileListProtected(db, args) {
|
|
8811
|
-
const sessionId = args.session_id;
|
|
8812
|
-
if (!sessionId) {
|
|
8813
|
-
return validationError("session_id is required");
|
|
8814
|
-
}
|
|
8815
|
-
const sessionCheck = await validateActiveSession(db, sessionId);
|
|
8816
|
-
if (!sessionCheck.valid) {
|
|
8817
|
-
return sessionCheck.error;
|
|
8818
|
-
}
|
|
8819
|
-
const files = await getProtectedFiles(db, sessionId);
|
|
8820
|
-
return successResponse({
|
|
8821
|
-
count: files.length,
|
|
8822
|
-
files,
|
|
8823
|
-
message: files.length > 0 ? `${files.length} protected file(s). Check before deleting.` : "No protected files."
|
|
8824
|
-
});
|
|
8825
|
-
}
|
|
8826
|
-
|
|
8827
|
-
// src/constants.ts
|
|
8828
|
-
import { readFileSync } from "fs";
|
|
8829
|
-
import { join as join2, dirname as dirname2 } from "path";
|
|
8830
|
-
import { fileURLToPath } from "url";
|
|
8831
|
-
function getVersion() {
|
|
8832
|
-
try {
|
|
8833
|
-
const __dirname3 = dirname2(fileURLToPath(import.meta.url));
|
|
8834
|
-
const pkg = JSON.parse(readFileSync(join2(__dirname3, "..", "package.json"), "utf-8"));
|
|
8835
|
-
return pkg.version;
|
|
8836
|
-
} catch {
|
|
8837
|
-
return "0.0.0";
|
|
8838
|
-
}
|
|
8839
|
-
}
|
|
8840
|
-
var VERSION = getVersion();
|
|
8841
|
-
var SERVER_NAME = "session-collab-mcp";
|
|
8842
|
-
var SERVER_INSTRUCTIONS = `
|
|
8843
|
-
# Session Collaboration
|
|
8844
|
-
|
|
8845
|
-
This MCP server coordinates multiple Claude Code sessions working on the same codebase.
|
|
8846
|
-
|
|
8847
|
-
## Required Behaviors
|
|
8848
|
-
|
|
8849
|
-
1. **On conversation start**: Immediately call \`collab_session_start\` with:
|
|
8850
|
-
- project_root: Current working directory
|
|
8851
|
-
- name: Descriptive session name (e.g., "feature-auth", "bugfix-api")
|
|
8852
|
-
|
|
8853
|
-
2. **Before editing any file**: Call \`collab_check\` with the file path to verify no conflicts
|
|
8854
|
-
|
|
8855
|
-
3. **Follow the \`recommendation\` from collab_check automatically**:
|
|
8856
|
-
- \`proceed_all\`: All files/symbols safe. Edit them without asking user.
|
|
8857
|
-
- \`proceed_safe_only\`: Some content blocked. Edit ONLY safe files/symbols, skip blocked. No need to ask user.
|
|
8858
|
-
- \`abort\`: All content blocked. Inform user and suggest coordination.
|
|
8859
|
-
|
|
8860
|
-
4. **For significant changes**: Call \`collab_claim\` before starting work
|
|
8861
|
-
|
|
8862
|
-
5. **When done**: Call \`collab_release\` with YOUR session_id to free them
|
|
8863
|
-
- Or use \`collab_auto_release\` after Edit/Write to release individual files
|
|
8864
|
-
|
|
8865
|
-
6. **On conversation end**: Call \`collab_session_end\` to clean up
|
|
8866
|
-
|
|
8867
|
-
## Auto-Release (NEW in 0.7.1)
|
|
8868
|
-
|
|
8869
|
-
Automatically release claims after editing:
|
|
8870
|
-
|
|
8871
|
-
### Immediate Release
|
|
8872
|
-
After Edit/Write, call \`collab_auto_release\` with:
|
|
8873
|
-
- \`session_id\`: Your session ID
|
|
8874
|
-
- \`file_path\`: The file you just edited
|
|
8875
|
-
- \`force\`: (optional) Force release for medium/large scope claims
|
|
8876
|
-
|
|
8877
|
-
**Behavior by scope:**
|
|
8878
|
-
- \`small\`: Auto-releases immediately
|
|
8879
|
-
- \`medium/large\`: Requires \`force=true\` or \`auto_release_immediate\` config
|
|
8880
|
-
|
|
8881
|
-
### Stale Claim Cleanup
|
|
8882
|
-
Claims are automatically cleaned up when:
|
|
8883
|
-
- Session has \`auto_release_stale: true\` in config
|
|
8884
|
-
- Claim exceeds \`stale_threshold_hours\` (default: 2 hours)
|
|
8885
|
-
|
|
8886
|
-
### Configuration
|
|
8887
|
-
Use \`collab_config\` to enable auto-release:
|
|
8888
|
-
\`\`\`json
|
|
8889
|
-
{
|
|
8890
|
-
"session_id": "...",
|
|
8891
|
-
"auto_release_immediate": true,
|
|
8892
|
-
"auto_release_stale": true,
|
|
8893
|
-
"stale_threshold_hours": 2,
|
|
8894
|
-
"auto_release_delay_minutes": 5
|
|
8895
|
-
}
|
|
8896
|
-
\`\`\`
|
|
8897
|
-
|
|
8898
|
-
## Symbol-Level Claims (Fine-Grained)
|
|
8899
|
-
|
|
8900
|
-
Use symbol-level claims when modifying specific functions/classes, allowing other sessions to work on different parts of the same file.
|
|
8901
|
-
|
|
8902
|
-
**Claim specific symbols:**
|
|
8903
|
-
\`\`\`json
|
|
8904
|
-
{
|
|
8905
|
-
"symbols": [
|
|
8906
|
-
{ "file": "src/auth.ts", "symbols": ["validateToken", "refreshToken"] }
|
|
8907
|
-
],
|
|
8908
|
-
"intent": "Refactoring token validation"
|
|
8909
|
-
}
|
|
8910
|
-
\`\`\`
|
|
8911
|
-
|
|
8912
|
-
**Check specific symbols:**
|
|
8913
|
-
\`\`\`json
|
|
8914
|
-
{
|
|
8915
|
-
"files": ["src/auth.ts"],
|
|
8916
|
-
"symbols": [
|
|
8917
|
-
{ "file": "src/auth.ts", "symbols": ["validateToken"] }
|
|
8918
|
-
]
|
|
8919
|
-
}
|
|
8920
|
-
\`\`\`
|
|
8921
|
-
|
|
8922
|
-
**Conflict levels:**
|
|
8923
|
-
- \`file\`: Whole file is claimed (no symbols specified)
|
|
8924
|
-
- \`symbol\`: Only specific functions/classes are claimed
|
|
8925
|
-
|
|
8926
|
-
**Example scenario:**
|
|
8927
|
-
- Session A claims \`validateToken\` in auth.ts
|
|
8928
|
-
- Session B wants to modify \`refreshToken\` in auth.ts
|
|
8929
|
-
- \u2192 No conflict! Session B can proceed.
|
|
8930
|
-
|
|
8931
|
-
## Auto-Decision Rules
|
|
8932
|
-
|
|
8933
|
-
When \`collab_check\` returns:
|
|
8934
|
-
- \`can_edit: true\` \u2192 Proceed with safe content automatically
|
|
8935
|
-
- \`can_edit: false\` \u2192 Stop and inform user about blocked content
|
|
8936
|
-
|
|
8937
|
-
For symbol-level checks, use \`symbol_status.safe\` and \`symbol_status.blocked\`.
|
|
8938
|
-
|
|
8939
|
-
## Permission Rules
|
|
8940
|
-
|
|
8941
|
-
- You can ONLY release claims that belong to YOUR session
|
|
8942
|
-
- To release another session's claim, you must ask the user and they must explicitly confirm
|
|
8943
|
-
- Use \`force=true\` in \`collab_release\` only after user explicitly confirms
|
|
8944
|
-
|
|
8945
|
-
## Conflict Handling Modes
|
|
8946
|
-
|
|
8947
|
-
Configure your session behavior with \`collab_config\`:
|
|
8948
|
-
|
|
8949
|
-
- **"strict"**: Always ask user, never bypass or auto-release
|
|
8950
|
-
- **"smart"** (default): Auto-proceed with safe content, ask for blocked
|
|
8951
|
-
- **"bypass"**: Proceed despite conflicts (just warn, don't block)
|
|
8952
|
-
|
|
8953
|
-
## LSP Integration (Advanced)
|
|
8954
|
-
|
|
8955
|
-
For precise symbol validation and impact analysis, use LSP tools:
|
|
8956
|
-
|
|
8957
|
-
### Workflow with LSP
|
|
8958
|
-
|
|
8959
|
-
1. **Get symbols from LSP**: Use \`LSP.documentSymbol\` to get actual symbols in a file
|
|
8960
|
-
2. **Validate before claiming**: Use \`collab_validate_symbols\` to verify symbol names
|
|
8961
|
-
3. **Analyze conflicts with context**: Use \`collab_analyze_symbols\` for enhanced conflict detection
|
|
8962
|
-
|
|
8963
|
-
### collab_validate_symbols
|
|
8964
|
-
|
|
8965
|
-
Validate symbol names exist before claiming:
|
|
8966
|
-
|
|
8967
|
-
\`\`\`
|
|
8968
|
-
1. Claude: LSP.documentSymbol("src/auth.ts")
|
|
8969
|
-
2. Claude: collab_validate_symbols({
|
|
8970
|
-
file: "src/auth.ts",
|
|
8971
|
-
symbols: ["validateToken", "refreshTokne"], // typo!
|
|
8972
|
-
lsp_symbols: [/* LSP output */]
|
|
8973
|
-
})
|
|
8974
|
-
3. Response: { invalid_symbols: ["refreshTokne"], suggestions: { "refreshTokne": ["refreshToken"] } }
|
|
8975
|
-
\`\`\`
|
|
8976
|
-
|
|
8977
|
-
### collab_analyze_symbols
|
|
8978
|
-
|
|
8979
|
-
Enhanced conflict detection with LSP data:
|
|
8980
|
-
|
|
8981
|
-
\`\`\`
|
|
8982
|
-
1. Claude: LSP.documentSymbol("src/auth.ts")
|
|
8983
|
-
2. Claude: LSP.findReferences("validateToken")
|
|
8984
|
-
3. Claude: collab_analyze_symbols({
|
|
8985
|
-
session_id: "...",
|
|
8986
|
-
files: [{ file: "src/auth.ts", symbols: [/* LSP symbols */] }],
|
|
8987
|
-
references: [{ symbol: "validateToken", file: "src/auth.ts", references: [...] }]
|
|
8988
|
-
})
|
|
8989
|
-
4. Response: {
|
|
8990
|
-
can_edit: true,
|
|
8991
|
-
recommendation: "proceed_safe_only",
|
|
8992
|
-
symbols: [
|
|
8993
|
-
{ name: "validateToken", conflict_status: "blocked", impact: { references_count: 5, affected_files: [...] } },
|
|
8994
|
-
{ name: "refreshToken", conflict_status: "safe" }
|
|
8995
|
-
]
|
|
8996
|
-
}
|
|
8997
|
-
\`\`\`
|
|
8998
|
-
|
|
8999
|
-
### Benefits of LSP Integration
|
|
9000
|
-
|
|
9001
|
-
- **Accurate symbol names**: No typos in claims
|
|
9002
|
-
- **Impact awareness**: Know which files will be affected by changes
|
|
9003
|
-
- **Smart prioritization**: Focus on low-impact changes first
|
|
9004
|
-
|
|
9005
|
-
## Reference Tracking & Impact Analysis
|
|
9006
|
-
|
|
9007
|
-
Store and query symbol references for smart conflict detection:
|
|
9008
|
-
|
|
9009
|
-
### collab_store_references
|
|
9010
|
-
|
|
9011
|
-
Persist LSP reference data for future impact queries:
|
|
9012
|
-
|
|
9013
|
-
\`\`\`
|
|
9014
|
-
1. Claude: LSP.findReferences("validateToken")
|
|
9015
|
-
2. Claude: collab_store_references({
|
|
9016
|
-
session_id: "...",
|
|
9017
|
-
references: [{
|
|
9018
|
-
source_file: "src/auth.ts",
|
|
9019
|
-
source_symbol: "validateToken",
|
|
9020
|
-
references: [
|
|
9021
|
-
{ file: "src/api/users.ts", line: 15 },
|
|
9022
|
-
{ file: "src/api/orders.ts", line: 23 }
|
|
9023
|
-
]
|
|
9024
|
-
}]
|
|
9025
|
-
})
|
|
9026
|
-
\`\`\`
|
|
9027
|
-
|
|
9028
|
-
### collab_impact_analysis
|
|
9029
|
-
|
|
9030
|
-
Check if modifying a symbol would affect files claimed by others:
|
|
9031
|
-
|
|
9032
|
-
\`\`\`
|
|
9033
|
-
Claude: collab_impact_analysis({
|
|
9034
|
-
session_id: "...",
|
|
9035
|
-
file: "src/auth.ts",
|
|
9036
|
-
symbol: "validateToken"
|
|
9037
|
-
})
|
|
9038
|
-
|
|
9039
|
-
Response: {
|
|
9040
|
-
risk_level: "high",
|
|
9041
|
-
reference_count: 3,
|
|
9042
|
-
affected_files: ["src/api/users.ts", "src/api/orders.ts"],
|
|
9043
|
-
affected_claims: [{ session_name: "other-session", intent: "..." }],
|
|
9044
|
-
message: "HIGH RISK: 1 active claim on referencing files"
|
|
9045
|
-
}
|
|
9046
|
-
\`\`\`
|
|
9047
|
-
|
|
9048
|
-
### Risk Levels
|
|
9049
|
-
|
|
9050
|
-
- **high**: Other sessions have claims on files that reference this symbol
|
|
9051
|
-
- **medium**: Many references (>10) but no active claims conflict
|
|
9052
|
-
- **low**: Few references, no conflicts
|
|
9053
|
-
|
|
9054
|
-
## Priority System
|
|
9055
|
-
|
|
9056
|
-
Claims have priority levels (0-100):
|
|
9057
|
-
- **Critical (90-100)**: Urgent production fixes
|
|
9058
|
-
- **High (70-89)**: Important features
|
|
9059
|
-
- **Normal (40-69)**: Regular work (default: 50)
|
|
9060
|
-
- **Low (0-39)**: Nice-to-have changes
|
|
9061
|
-
|
|
9062
|
-
Set priority when claiming:
|
|
9063
|
-
\`\`\`json
|
|
9064
|
-
{
|
|
9065
|
-
"session_id": "...",
|
|
9066
|
-
"files": ["src/critical-bug.ts"],
|
|
9067
|
-
"intent": "Fix production crash",
|
|
9068
|
-
"priority": 95
|
|
9069
|
-
}
|
|
9070
|
-
\`\`\`
|
|
9071
|
-
|
|
9072
|
-
Use \`collab_claim_update_priority\` to escalate urgent work.
|
|
9073
|
-
|
|
9074
|
-
## Claim Queue
|
|
9075
|
-
|
|
9076
|
-
When blocked by another claim, join the waiting queue:
|
|
9077
|
-
|
|
9078
|
-
1. **Join queue**: \`collab_queue_join\` with the blocked claim's ID
|
|
9079
|
-
2. **Check notifications**: \`collab_notifications_list\` for \`queue_ready\` notification
|
|
9080
|
-
3. **When notified**: Proceed with your work
|
|
9081
|
-
|
|
9082
|
-
Higher priority claims are served first within the queue.
|
|
9083
|
-
|
|
9084
|
-
Queue tools:
|
|
9085
|
-
- \`collab_queue_join\`: Join waiting queue for a blocked claim
|
|
9086
|
-
- \`collab_queue_leave\`: Leave the queue if no longer needed
|
|
9087
|
-
- \`collab_queue_list\`: View queue status
|
|
9088
|
-
|
|
9089
|
-
## Notifications
|
|
9090
|
-
|
|
9091
|
-
Check \`collab_notifications_list\` periodically for:
|
|
9092
|
-
- **claim_released**: Claim you waited for is now available
|
|
9093
|
-
- **queue_ready**: You're next in queue, proceed with your work
|
|
9094
|
-
- **conflict_detected**: Someone claimed files you're interested in
|
|
9095
|
-
- **session_message**: Direct message from another session
|
|
9096
|
-
|
|
9097
|
-
Mark notifications as read with \`collab_notifications_mark_read\`.
|
|
9098
|
-
|
|
9099
|
-
## Audit History
|
|
9100
|
-
|
|
9101
|
-
Use \`collab_history_list\` to debug coordination issues:
|
|
9102
|
-
- Track session starts/ends
|
|
9103
|
-
- View claim creation/release history
|
|
9104
|
-
- Identify conflict patterns
|
|
9105
|
-
- Monitor queue activity
|
|
9106
|
-
|
|
9107
|
-
Filter by session, action type, or date range. Entries auto-deleted after 7 days.
|
|
9108
|
-
|
|
9109
|
-
## Working Memory (Context Persistence)
|
|
9110
|
-
|
|
9111
|
-
Persist important context to survive Claude's automatic compaction/summarization.
|
|
9112
|
-
|
|
9113
|
-
### When to Use
|
|
9114
|
-
|
|
9115
|
-
- **Findings**: Important discoveries during investigation
|
|
9116
|
-
- **Decisions**: Choices made and their rationale
|
|
9117
|
-
- **State**: Current status, file paths, variable values
|
|
9118
|
-
- **Context**: General context that should be remembered
|
|
9119
|
-
|
|
9120
|
-
### Save Important Context
|
|
9121
|
-
|
|
9122
|
-
\`\`\`
|
|
9123
|
-
collab_memory_save({
|
|
9124
|
-
session_id: "...",
|
|
9125
|
-
category: "finding",
|
|
9126
|
-
key: "auth_bug_root_cause",
|
|
9127
|
-
content: "JWT validation skipped in middleware due to order issue",
|
|
9128
|
-
priority: 80,
|
|
9129
|
-
pinned: true
|
|
9130
|
-
})
|
|
9131
|
-
\`\`\`
|
|
9132
|
-
|
|
9133
|
-
### Recall Context
|
|
9134
|
-
|
|
9135
|
-
\`\`\`
|
|
9136
|
-
collab_memory_recall({
|
|
9137
|
-
session_id: "...",
|
|
9138
|
-
category: "finding" // optional filter
|
|
9139
|
-
})
|
|
9140
|
-
\`\`\`
|
|
9141
|
-
|
|
9142
|
-
### Get Active Memories
|
|
9143
|
-
|
|
9144
|
-
Get all pinned + high-priority memories for context restoration:
|
|
9145
|
-
|
|
9146
|
-
\`\`\`
|
|
9147
|
-
collab_memory_active({
|
|
9148
|
-
session_id: "...",
|
|
9149
|
-
priority_threshold: 70
|
|
9150
|
-
})
|
|
9151
|
-
\`\`\`
|
|
9152
|
-
|
|
9153
|
-
### Memory Categories
|
|
9154
|
-
|
|
9155
|
-
- **finding**: Discovered facts, root causes, patterns
|
|
9156
|
-
- **decision**: Architectural choices, implementation decisions
|
|
9157
|
-
- **state**: Current file, line number, tracking variables
|
|
9158
|
-
- **todo**: Pending work items, follow-ups
|
|
9159
|
-
- **important**: Critical information that must not be lost
|
|
9160
|
-
- **context**: General context for understanding
|
|
9161
|
-
|
|
9162
|
-
### Priority Levels
|
|
9163
|
-
|
|
9164
|
-
- **90-100**: Critical - always recall
|
|
9165
|
-
- **70-89**: High - recall by default
|
|
9166
|
-
- **50-69**: Normal - recall on demand
|
|
9167
|
-
- **0-49**: Low - background context
|
|
9168
|
-
|
|
9169
|
-
### Pinned Memories
|
|
9170
|
-
|
|
9171
|
-
Pin critical memories to ensure they are always loaded:
|
|
9172
|
-
|
|
9173
|
-
\`\`\`
|
|
9174
|
-
collab_memory_pin({
|
|
9175
|
-
session_id: "...",
|
|
9176
|
-
key: "root_cause",
|
|
9177
|
-
pinned: true
|
|
9178
|
-
})
|
|
9179
|
-
\`\`\`
|
|
9180
|
-
|
|
9181
|
-
### Memory Tools
|
|
9182
|
-
|
|
9183
|
-
| Tool | Purpose |
|
|
9184
|
-
|------|---------|
|
|
9185
|
-
| \`collab_memory_save\` | Save important context |
|
|
9186
|
-
| \`collab_memory_recall\` | Retrieve saved memories |
|
|
9187
|
-
| \`collab_memory_update\` | Update existing memory |
|
|
9188
|
-
| \`collab_memory_clear\` | Clear memories |
|
|
9189
|
-
| \`collab_memory_pin\` | Pin/unpin a memory |
|
|
9190
|
-
| \`collab_memory_stats\` | Get memory statistics |
|
|
9191
|
-
| \`collab_memory_active\` | Get all active memories |
|
|
9192
|
-
|
|
9193
|
-
## Plan & File Protection
|
|
9194
|
-
|
|
9195
|
-
Protect important files (plans, session-created files) from accidental deletion or overwriting.
|
|
9196
|
-
|
|
9197
|
-
### Register Plans
|
|
9198
|
-
|
|
9199
|
-
After creating a plan document, register it for protection:
|
|
9200
|
-
|
|
9201
|
-
\`\`\`
|
|
9202
|
-
collab_plan_register({
|
|
9203
|
-
session_id: "...",
|
|
9204
|
-
file_path: "docs/implementation-plan.md",
|
|
9205
|
-
title: "Auth System Refactor Plan",
|
|
9206
|
-
content_summary: "1. Migrate to JWT\\n2. Add refresh tokens\\n3. Update middleware",
|
|
9207
|
-
status: "approved"
|
|
9208
|
-
})
|
|
9209
|
-
\`\`\`
|
|
9210
|
-
|
|
9211
|
-
Plans are automatically:
|
|
9212
|
-
- **Pinned** (always appear in active memory)
|
|
9213
|
-
- **High priority** (95/100)
|
|
9214
|
-
- **Protected** from deletion warnings
|
|
9215
|
-
|
|
9216
|
-
### Plan Status Lifecycle
|
|
9217
|
-
|
|
9218
|
-
\`\`\`
|
|
9219
|
-
draft \u2192 approved \u2192 in_progress \u2192 completed \u2192 archived
|
|
9220
|
-
\u2193
|
|
9221
|
-
(Protection reduced)
|
|
9222
|
-
(Priority: 50 \u2192 30)
|
|
9223
|
-
(Unpinned)
|
|
9224
|
-
\`\`\`
|
|
9225
|
-
|
|
9226
|
-
Update plan status as work progresses:
|
|
9227
|
-
|
|
9228
|
-
\`\`\`
|
|
9229
|
-
collab_plan_update_status({
|
|
9230
|
-
session_id: "...",
|
|
9231
|
-
file_path: "docs/implementation-plan.md",
|
|
9232
|
-
status: "completed",
|
|
9233
|
-
summary: "All tasks completed. JWT auth implemented."
|
|
9234
|
-
})
|
|
9235
|
-
\`\`\`
|
|
9236
|
-
|
|
9237
|
-
### Register Created Files
|
|
9238
|
-
|
|
9239
|
-
Track important files created during the session:
|
|
9240
|
-
|
|
9241
|
-
\`\`\`
|
|
9242
|
-
collab_file_register({
|
|
9243
|
-
session_id: "...",
|
|
9244
|
-
file_path: "src/auth/jwt-validator.ts",
|
|
9245
|
-
file_type: "code",
|
|
9246
|
-
description: "New JWT validation utility"
|
|
9247
|
-
})
|
|
9248
|
-
\`\`\`
|
|
9249
|
-
|
|
9250
|
-
### Check Before Deleting
|
|
9251
|
-
|
|
9252
|
-
Before deleting any file, check if it's protected:
|
|
9253
|
-
|
|
9254
|
-
\`\`\`
|
|
9255
|
-
collab_file_check_protected({
|
|
9256
|
-
session_id: "...",
|
|
9257
|
-
file_path: "docs/implementation-plan.md"
|
|
9258
|
-
})
|
|
9259
|
-
|
|
9260
|
-
// Response:
|
|
9261
|
-
{
|
|
9262
|
-
protected: true,
|
|
9263
|
-
reason: "plan",
|
|
9264
|
-
warning: "\u26A0\uFE0F This file is protected (plan). Confirm before deleting."
|
|
9265
|
-
}
|
|
9266
|
-
\`\`\`
|
|
5944
|
+
### Memory (3 tools)
|
|
5945
|
+
- \`collab_memory_save\`: Save context (upsert)
|
|
5946
|
+
- \`collab_memory_recall\`: Recall context (use active=true for restoration)
|
|
5947
|
+
- \`collab_memory_clear\`: Clear memories
|
|
9267
5948
|
|
|
9268
|
-
### Protection
|
|
5949
|
+
### Protection (1 tool)
|
|
5950
|
+
- \`collab_protect\`: Unified protection tool
|
|
5951
|
+
- action: "register" | "check" | "list"
|
|
9269
5952
|
|
|
9270
|
-
|
|
9271
|
-
|------|---------|
|
|
9272
|
-
| \`collab_plan_register\` | Register a plan for protection |
|
|
9273
|
-
| \`collab_plan_update_status\` | Update plan status |
|
|
9274
|
-
| \`collab_plan_get\` | Get plan info |
|
|
9275
|
-
| \`collab_plan_list\` | List all plans |
|
|
9276
|
-
| \`collab_file_register\` | Register created file |
|
|
9277
|
-
| \`collab_file_list_created\` | List created files |
|
|
9278
|
-
| \`collab_file_check_protected\` | Check if file is protected |
|
|
9279
|
-
| \`collab_file_list_protected\` | List all protected files |
|
|
5953
|
+
## Workflow
|
|
9280
5954
|
|
|
9281
|
-
|
|
5955
|
+
1. **On start**: \`collab_session_start\` with project_root
|
|
5956
|
+
2. **Before editing**: \`collab_claim\` action="check"
|
|
5957
|
+
3. **For changes**: \`collab_claim\` action="create"
|
|
5958
|
+
4. **Save context**: \`collab_memory_save\` for important findings
|
|
5959
|
+
5. **When done**: \`collab_claim\` action="release"
|
|
5960
|
+
6. **On end**: \`collab_session_end\`
|
|
9282
5961
|
|
|
9283
|
-
|
|
9284
|
-
- Important plan details may be lost
|
|
9285
|
-
- Session-created files might be forgotten
|
|
9286
|
-
- Claude might accidentally delete recently created files
|
|
5962
|
+
## Memory Categories
|
|
9287
5963
|
|
|
9288
|
-
|
|
9289
|
-
-
|
|
9290
|
-
-
|
|
9291
|
-
-
|
|
9292
|
-
-
|
|
5964
|
+
- **finding**: Discovered facts, root causes
|
|
5965
|
+
- **decision**: Architectural choices
|
|
5966
|
+
- **state**: Current tracking info
|
|
5967
|
+
- **important**: Critical context
|
|
5968
|
+
- **context**: General context
|
|
9293
5969
|
|
|
9294
5970
|
## Best Practices
|
|
9295
5971
|
|
|
9296
|
-
-
|
|
9297
|
-
-
|
|
9298
|
-
-
|
|
9299
|
-
-
|
|
9300
|
-
- **Set appropriate priority** for urgent work (95+ for production fixes)
|
|
9301
|
-
- **Check notifications** when waiting for blocked claims
|
|
9302
|
-
- **Save important findings** to working memory as you discover them
|
|
9303
|
-
- **Pin critical context** that must survive compaction
|
|
9304
|
-
- Claim early, release when done
|
|
9305
|
-
- Use descriptive intents (e.g., "Refactoring validateToken for JWT support")
|
|
5972
|
+
- Save important findings to memory as you discover them
|
|
5973
|
+
- Use active=true in recall to restore context
|
|
5974
|
+
- Register plans with collab_protect for protection
|
|
5975
|
+
- Check files before editing to avoid conflicts
|
|
9306
5976
|
`.trim();
|
|
9307
5977
|
|
|
9308
5978
|
// src/mcp/server.ts
|
|
9309
|
-
var ALL_TOOLS = [...sessionTools, ...claimTools, ...
|
|
5979
|
+
var ALL_TOOLS = [...sessionTools, ...claimTools, ...memoryTools, ...protectionTools];
|
|
9310
5980
|
function getMcpTools() {
|
|
9311
5981
|
return ALL_TOOLS;
|
|
9312
5982
|
}
|
|
9313
5983
|
async function handleMcpRequest(db, name, args) {
|
|
9314
5984
|
try {
|
|
9315
|
-
if (name.startsWith("collab_session_") || name === "
|
|
5985
|
+
if (name.startsWith("collab_session_") || name === "collab_config" || name === "collab_status") {
|
|
9316
5986
|
return await handleSessionTool(db, name, args);
|
|
9317
|
-
} else if (name
|
|
5987
|
+
} else if (name === "collab_claim") {
|
|
9318
5988
|
return await handleClaimTool(db, name, args);
|
|
9319
|
-
} else if (name.startsWith("collab_message_")) {
|
|
9320
|
-
return await handleMessageTool(db, name, args);
|
|
9321
|
-
} else if (name.startsWith("collab_decision_")) {
|
|
9322
|
-
return await handleDecisionTool(db, name, args);
|
|
9323
|
-
} else if (name === "collab_analyze_symbols" || name === "collab_validate_symbols" || name === "collab_store_references" || name === "collab_impact_analysis") {
|
|
9324
|
-
return await handleLspTool(db, name, args);
|
|
9325
|
-
} else if (name === "collab_history_list") {
|
|
9326
|
-
return await handleHistoryTool(db, name, args);
|
|
9327
|
-
} else if (name.startsWith("collab_queue_")) {
|
|
9328
|
-
return await handleQueueTool(db, name, args);
|
|
9329
|
-
} else if (name.startsWith("collab_notifications_")) {
|
|
9330
|
-
return await handleNotificationTool(db, name, args);
|
|
9331
5989
|
} else if (name.startsWith("collab_memory_")) {
|
|
9332
5990
|
return await handleMemoryTool(db, name, args);
|
|
9333
|
-
} else if (name
|
|
5991
|
+
} else if (name === "collab_protect") {
|
|
9334
5992
|
return await handleProtectionTool(db, name, args);
|
|
9335
5993
|
} else {
|
|
9336
5994
|
return createToolResult(`Unknown tool: ${name}`, true);
|