session-collab-mcp 0.5.4 → 0.6.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 CHANGED
@@ -4409,143 +4409,114 @@ async function listClaims(db, params = {}) {
4409
4409
  }));
4410
4410
  }
4411
4411
  async function checkConflicts(db, files, excludeSessionId, symbols) {
4412
+ if (files.length === 0) {
4413
+ return [];
4414
+ }
4412
4415
  const conflicts = [];
4413
4416
  const symbolsByFile = /* @__PURE__ */ new Map();
4417
+ const allSymbolNames = /* @__PURE__ */ new Set();
4414
4418
  if (symbols && symbols.length > 0) {
4415
4419
  for (const sc of symbols) {
4416
4420
  const existing = symbolsByFile.get(sc.file) ?? /* @__PURE__ */ new Set();
4417
4421
  for (const sym of sc.symbols) {
4418
4422
  existing.add(sym);
4423
+ allSymbolNames.add(sym);
4419
4424
  }
4420
4425
  symbolsByFile.set(sc.file, existing);
4421
4426
  }
4422
4427
  }
4423
- for (const filePath of files) {
4424
- const requestedSymbols = symbolsByFile.get(filePath);
4425
- if (requestedSymbols && requestedSymbols.size > 0) {
4426
- let symbolQuery = `
4427
- SELECT
4428
- c.id as claim_id,
4429
- c.session_id,
4430
- s.name as session_name,
4431
- cs.file_path,
4432
- c.intent,
4433
- c.scope,
4434
- c.created_at,
4435
- cs.symbol_name,
4436
- cs.symbol_type
4437
- FROM claim_symbols cs
4438
- JOIN claims c ON cs.claim_id = c.id
4439
- JOIN sessions s ON c.session_id = s.id
4440
- WHERE c.status = 'active'
4441
- AND s.status = 'active'
4442
- AND cs.file_path = ?
4443
- AND cs.symbol_name IN (${Array.from(requestedSymbols).map(() => "?").join(",")})
4444
- `;
4445
- const symbolBindings = [filePath, ...Array.from(requestedSymbols)];
4446
- if (excludeSessionId) {
4447
- symbolQuery += " AND c.session_id != ?";
4448
- symbolBindings.push(excludeSessionId);
4449
- }
4450
- const symbolResult = await db.prepare(symbolQuery).bind(...symbolBindings).all();
4451
- for (const r of symbolResult.results) {
4452
- conflicts.push({
4453
- ...r,
4454
- conflict_level: "symbol"
4455
- });
4456
- }
4457
- let fileClaimQuery = `
4458
- SELECT
4459
- c.id as claim_id,
4460
- c.session_id,
4461
- s.name as session_name,
4462
- cf.file_path,
4463
- c.intent,
4464
- c.scope,
4465
- c.created_at
4466
- FROM claim_files cf
4467
- JOIN claims c ON cf.claim_id = c.id
4468
- JOIN sessions s ON c.session_id = s.id
4469
- WHERE c.status = 'active'
4470
- AND s.status = 'active'
4471
- AND (cf.file_path = ? OR (cf.is_pattern = 1 AND ? GLOB cf.file_path))
4472
- AND NOT EXISTS (
4473
- SELECT 1 FROM claim_symbols cs WHERE cs.claim_id = c.id AND cs.file_path = cf.file_path
4474
- )
4475
- `;
4476
- const fileClaimBindings = [filePath, filePath];
4477
- if (excludeSessionId) {
4478
- fileClaimQuery += " AND c.session_id != ?";
4479
- fileClaimBindings.push(excludeSessionId);
4480
- }
4481
- const fileClaimResult = await db.prepare(fileClaimQuery).bind(...fileClaimBindings).all();
4482
- for (const r of fileClaimResult.results) {
4483
- conflicts.push({
4484
- ...r,
4485
- conflict_level: "file"
4486
- });
4487
- }
4488
- } else {
4489
- let fileQuery = `
4490
- SELECT
4491
- c.id as claim_id,
4492
- c.session_id,
4493
- s.name as session_name,
4494
- cf.file_path,
4495
- c.intent,
4496
- c.scope,
4497
- c.created_at
4498
- FROM claim_files cf
4499
- JOIN claims c ON cf.claim_id = c.id
4500
- JOIN sessions s ON c.session_id = s.id
4501
- WHERE c.status = 'active'
4502
- AND s.status = 'active'
4503
- AND (cf.file_path = ? OR (cf.is_pattern = 1 AND ? GLOB cf.file_path))
4504
- `;
4505
- const fileBindings = [filePath, filePath];
4506
- if (excludeSessionId) {
4507
- fileQuery += " AND c.session_id != ?";
4508
- fileBindings.push(excludeSessionId);
4509
- }
4510
- const fileResult = await db.prepare(fileQuery).bind(...fileBindings).all();
4511
- for (const r of fileResult.results) {
4512
- conflicts.push({
4513
- ...r,
4514
- conflict_level: "file"
4515
- });
4516
- }
4517
- let symbolOnlyQuery = `
4518
- SELECT DISTINCT
4519
- c.id as claim_id,
4520
- c.session_id,
4521
- s.name as session_name,
4522
- cs.file_path,
4523
- c.intent,
4524
- c.scope,
4525
- c.created_at,
4526
- cs.symbol_name,
4527
- cs.symbol_type
4528
- FROM claim_symbols cs
4529
- JOIN claims c ON cs.claim_id = c.id
4530
- JOIN sessions s ON c.session_id = s.id
4531
- WHERE c.status = 'active'
4532
- AND s.status = 'active'
4533
- AND cs.file_path = ?
4534
- `;
4535
- const symbolOnlyBindings = [filePath];
4536
- if (excludeSessionId) {
4537
- symbolOnlyQuery += " AND c.session_id != ?";
4538
- symbolOnlyBindings.push(excludeSessionId);
4539
- }
4540
- const symbolOnlyResult = await db.prepare(symbolOnlyQuery).bind(...symbolOnlyBindings).all();
4541
- for (const r of symbolOnlyResult.results) {
4542
- conflicts.push({
4543
- ...r,
4544
- conflict_level: "symbol"
4545
- });
4546
- }
4547
- }
4428
+ const hasSymbols = symbolsByFile.size > 0;
4429
+ const sessionFilter = excludeSessionId ? " AND c.session_id != ?" : "";
4430
+ const fileConditions = files.map(() => "(cf.file_path = ? OR (cf.is_pattern = 1 AND ? GLOB cf.file_path))").join(" OR ");
4431
+ let fileQuery = `
4432
+ SELECT DISTINCT
4433
+ c.id as claim_id,
4434
+ c.session_id,
4435
+ s.name as session_name,
4436
+ cf.file_path,
4437
+ c.intent,
4438
+ c.scope,
4439
+ c.created_at,
4440
+ NULL as symbol_name,
4441
+ NULL as symbol_type,
4442
+ 'file' as conflict_level
4443
+ FROM claim_files cf
4444
+ JOIN claims c ON cf.claim_id = c.id
4445
+ JOIN sessions s ON c.session_id = s.id
4446
+ WHERE c.status = 'active'
4447
+ AND s.status = 'active'
4448
+ AND (${fileConditions})
4449
+ ${sessionFilter}
4450
+ `;
4451
+ if (hasSymbols) {
4452
+ fileQuery += `
4453
+ AND NOT EXISTS (
4454
+ SELECT 1 FROM claim_symbols cs
4455
+ WHERE cs.claim_id = c.id AND cs.file_path = cf.file_path
4456
+ )
4457
+ `;
4548
4458
  }
4459
+ const fileBindings = files.flatMap((f) => [f, f]);
4460
+ if (excludeSessionId) {
4461
+ fileBindings.push(excludeSessionId);
4462
+ }
4463
+ const fileResult = await db.prepare(fileQuery).bind(...fileBindings).all();
4464
+ conflicts.push(...fileResult.results);
4465
+ const symbolFilePlaceholders = files.map(() => "?").join(",");
4466
+ let symbolQuery;
4467
+ let symbolBindings;
4468
+ if (hasSymbols && allSymbolNames.size > 0) {
4469
+ const symbolPlaceholders = Array.from(allSymbolNames).map(() => "?").join(",");
4470
+ symbolQuery = `
4471
+ SELECT DISTINCT
4472
+ c.id as claim_id,
4473
+ c.session_id,
4474
+ s.name as session_name,
4475
+ cs.file_path,
4476
+ c.intent,
4477
+ c.scope,
4478
+ c.created_at,
4479
+ cs.symbol_name,
4480
+ cs.symbol_type,
4481
+ 'symbol' as conflict_level
4482
+ FROM claim_symbols cs
4483
+ JOIN claims c ON cs.claim_id = c.id
4484
+ JOIN sessions s ON c.session_id = s.id
4485
+ WHERE c.status = 'active'
4486
+ AND s.status = 'active'
4487
+ AND cs.file_path IN (${symbolFilePlaceholders})
4488
+ AND cs.symbol_name IN (${symbolPlaceholders})
4489
+ ${sessionFilter}
4490
+ `;
4491
+ symbolBindings = [...files, ...Array.from(allSymbolNames)];
4492
+ } else {
4493
+ symbolQuery = `
4494
+ SELECT DISTINCT
4495
+ c.id as claim_id,
4496
+ c.session_id,
4497
+ s.name as session_name,
4498
+ cs.file_path,
4499
+ c.intent,
4500
+ c.scope,
4501
+ c.created_at,
4502
+ cs.symbol_name,
4503
+ cs.symbol_type,
4504
+ 'symbol' as conflict_level
4505
+ FROM claim_symbols cs
4506
+ JOIN claims c ON cs.claim_id = c.id
4507
+ JOIN sessions s ON c.session_id = s.id
4508
+ WHERE c.status = 'active'
4509
+ AND s.status = 'active'
4510
+ AND cs.file_path IN (${symbolFilePlaceholders})
4511
+ ${sessionFilter}
4512
+ `;
4513
+ symbolBindings = [...files];
4514
+ }
4515
+ if (excludeSessionId) {
4516
+ symbolBindings.push(excludeSessionId);
4517
+ }
4518
+ const symbolResult = await db.prepare(symbolQuery).bind(...symbolBindings).all();
4519
+ conflicts.push(...symbolResult.results);
4549
4520
  const seen = /* @__PURE__ */ new Set();
4550
4521
  return conflicts.filter((c) => {
4551
4522
  const key = `${c.claim_id}:${c.file_path}:${c.symbol_name ?? ""}`;
@@ -4589,9 +4560,8 @@ async function listMessages(db, params) {
4589
4560
  if (params.mark_as_read && messages.results.length > 0) {
4590
4561
  const now = (/* @__PURE__ */ new Date()).toISOString();
4591
4562
  const ids = messages.results.map((m) => m.id);
4592
- for (const id of ids) {
4593
- await db.prepare("UPDATE messages SET read_at = ? WHERE id = ? AND read_at IS NULL").bind(now, id).run();
4594
- }
4563
+ const placeholders = ids.map(() => "?").join(",");
4564
+ await db.prepare(`UPDATE messages SET read_at = ? WHERE id IN (${placeholders}) AND read_at IS NULL`).bind(now, ...ids).run();
4595
4565
  }
4596
4566
  return messages.results;
4597
4567
  }
@@ -4624,24 +4594,30 @@ async function listDecisions(db, params = {}) {
4624
4594
  return result.results;
4625
4595
  }
4626
4596
  async function storeReferences(db, sessionId, references) {
4627
- let stored = 0;
4628
- let skipped = 0;
4629
4597
  const now = (/* @__PURE__ */ new Date()).toISOString();
4598
+ const statements = [];
4630
4599
  for (const ref of references) {
4631
4600
  for (const r of ref.references) {
4632
- try {
4633
- await db.prepare(
4601
+ statements.push(
4602
+ db.prepare(
4634
4603
  `INSERT OR IGNORE INTO symbol_references
4635
4604
  (source_file, source_symbol, ref_file, ref_line, ref_context, session_id, created_at)
4636
4605
  VALUES (?, ?, ?, ?, ?, ?, ?)`
4637
- ).bind(ref.source_file, ref.source_symbol, r.file, r.line, r.context ?? null, sessionId, now).run();
4638
- stored++;
4639
- } catch {
4640
- skipped++;
4641
- }
4606
+ ).bind(ref.source_file, ref.source_symbol, r.file, r.line, r.context ?? null, sessionId, now)
4607
+ );
4642
4608
  }
4643
4609
  }
4644
- return { stored, skipped };
4610
+ if (statements.length === 0) {
4611
+ return { stored: 0, skipped: 0 };
4612
+ }
4613
+ try {
4614
+ const results = await db.batch(statements);
4615
+ const stored = results.reduce((acc, r) => acc + r.meta.changes, 0);
4616
+ return { stored, skipped: statements.length - stored };
4617
+ } catch (err) {
4618
+ console.error("[storeReferences] Batch insert failed:", err);
4619
+ return { stored: 0, skipped: statements.length };
4620
+ }
4645
4621
  }
4646
4622
  async function getReferencesForSymbol(db, sourceFile, sourceSymbol) {
4647
4623
  const result = await db.prepare(
@@ -4873,6 +4849,57 @@ function validateInput(schema, data) {
4873
4849
  return { success: false, error: errors };
4874
4850
  }
4875
4851
 
4852
+ // src/utils/response.ts
4853
+ var ERROR_CODES = {
4854
+ INVALID_INPUT: "INVALID_INPUT",
4855
+ SESSION_NOT_FOUND: "SESSION_NOT_FOUND",
4856
+ SESSION_INVALID: "SESSION_INVALID",
4857
+ CLAIM_NOT_FOUND: "CLAIM_NOT_FOUND",
4858
+ CLAIM_ALREADY_RELEASED: "CLAIM_ALREADY_RELEASED",
4859
+ NOT_OWNER: "NOT_OWNER",
4860
+ TARGET_SESSION_INVALID: "TARGET_SESSION_INVALID"
4861
+ };
4862
+ function errorResponse(code, message, data) {
4863
+ return createToolResult(
4864
+ JSON.stringify({ error: code, message, ...data }),
4865
+ true
4866
+ );
4867
+ }
4868
+ function successResponse(data, prettyPrint = false) {
4869
+ return createToolResult(
4870
+ JSON.stringify(data, null, prettyPrint ? 2 : void 0)
4871
+ );
4872
+ }
4873
+ async function validateActiveSession(db, sessionId) {
4874
+ const session = await getSession(db, sessionId);
4875
+ if (!session) {
4876
+ return {
4877
+ valid: false,
4878
+ error: errorResponse(ERROR_CODES.SESSION_NOT_FOUND, "Session not found")
4879
+ };
4880
+ }
4881
+ if (session.status !== "active") {
4882
+ return {
4883
+ valid: false,
4884
+ error: errorResponse(ERROR_CODES.SESSION_INVALID, "Session not found or inactive.")
4885
+ };
4886
+ }
4887
+ return { valid: true, session };
4888
+ }
4889
+ async function validateSessionExists(db, sessionId) {
4890
+ const session = await getSession(db, sessionId);
4891
+ if (!session) {
4892
+ return {
4893
+ valid: false,
4894
+ error: errorResponse(ERROR_CODES.SESSION_NOT_FOUND, "Session not found")
4895
+ };
4896
+ }
4897
+ return { valid: true, session };
4898
+ }
4899
+ function validationError(message) {
4900
+ return errorResponse(ERROR_CODES.INVALID_INPUT, message);
4901
+ }
4902
+
4876
4903
  // src/mcp/tools/session.ts
4877
4904
  var sessionTools = [
4878
4905
  {
@@ -5029,10 +5056,7 @@ async function handleSessionTool(db, name, args, userId) {
5029
5056
  case "collab_session_start": {
5030
5057
  const validation = validateInput(sessionStartSchema, args);
5031
5058
  if (!validation.success) {
5032
- return createToolResult(
5033
- JSON.stringify({ error: "INVALID_INPUT", message: validation.error }),
5034
- true
5035
- );
5059
+ return validationError(validation.error);
5036
5060
  }
5037
5061
  const input = validation.data;
5038
5062
  await cleanupStaleSessions(db, 30);
@@ -5063,34 +5087,23 @@ async function handleSessionTool(db, name, args, userId) {
5063
5087
  case "collab_session_end": {
5064
5088
  const validation = validateInput(sessionEndSchema, args);
5065
5089
  if (!validation.success) {
5066
- return createToolResult(
5067
- JSON.stringify({ error: "INVALID_INPUT", message: validation.error }),
5068
- true
5069
- );
5090
+ return validationError(validation.error);
5070
5091
  }
5071
5092
  const input = validation.data;
5072
- const session = await getSession(db, input.session_id);
5073
- if (!session) {
5074
- return createToolResult(
5075
- JSON.stringify({ error: "SESSION_NOT_FOUND", message: "Session not found" }),
5076
- true
5077
- );
5093
+ const sessionResult = await validateSessionExists(db, input.session_id);
5094
+ if (!sessionResult.valid) {
5095
+ return sessionResult.error;
5078
5096
  }
5079
5097
  await endSession(db, input.session_id, input.release_claims);
5080
- return createToolResult(
5081
- JSON.stringify({
5082
- success: true,
5083
- message: `Session ended. All claims marked as ${input.release_claims === "complete" ? "completed" : "abandoned"}.`
5084
- })
5085
- );
5098
+ return successResponse({
5099
+ success: true,
5100
+ message: `Session ended. All claims marked as ${input.release_claims === "complete" ? "completed" : "abandoned"}.`
5101
+ });
5086
5102
  }
5087
5103
  case "collab_session_list": {
5088
5104
  const validation = validateInput(sessionListSchema, args);
5089
5105
  if (!validation.success) {
5090
- return createToolResult(
5091
- JSON.stringify({ error: "INVALID_INPUT", message: validation.error }),
5092
- true
5093
- );
5106
+ return validationError(validation.error);
5094
5107
  }
5095
5108
  const input = validation.data;
5096
5109
  const sessions = await listSessions(db, {
@@ -5134,10 +5147,7 @@ async function handleSessionTool(db, name, args, userId) {
5134
5147
  case "collab_session_heartbeat": {
5135
5148
  const validation = validateInput(sessionHeartbeatSchema, args);
5136
5149
  if (!validation.success) {
5137
- return createToolResult(
5138
- JSON.stringify({ error: "INVALID_INPUT", message: validation.error }),
5139
- true
5140
- );
5150
+ return validationError(validation.error);
5141
5151
  }
5142
5152
  const input = validation.data;
5143
5153
  const updated = await updateSessionHeartbeat(db, input.session_id, {
@@ -5145,41 +5155,27 @@ async function handleSessionTool(db, name, args, userId) {
5145
5155
  todos: input.todos
5146
5156
  });
5147
5157
  if (!updated) {
5148
- return createToolResult(
5149
- JSON.stringify({
5150
- error: "SESSION_NOT_FOUND",
5151
- message: "Session not found or inactive. Please start a new session."
5152
- }),
5153
- true
5158
+ return errorResponse(
5159
+ ERROR_CODES.SESSION_NOT_FOUND,
5160
+ "Session not found or inactive. Please start a new session."
5154
5161
  );
5155
5162
  }
5156
- return createToolResult(
5157
- JSON.stringify({
5158
- success: true,
5159
- message: "Heartbeat updated",
5160
- status_synced: !!(input.current_task || input.todos)
5161
- })
5162
- );
5163
+ return successResponse({
5164
+ success: true,
5165
+ message: "Heartbeat updated",
5166
+ status_synced: !!(input.current_task || input.todos)
5167
+ });
5163
5168
  }
5164
5169
  case "collab_status_update": {
5165
5170
  const validation = validateInput(statusUpdateSchema, args);
5166
5171
  if (!validation.success) {
5167
- return createToolResult(
5168
- JSON.stringify({ error: "INVALID_INPUT", message: validation.error }),
5169
- true
5170
- );
5172
+ return validationError(validation.error);
5171
5173
  }
5172
5174
  const input = validation.data;
5173
5175
  const todos = input.todos;
5174
- const session = await getSession(db, input.session_id);
5175
- if (!session || session.status !== "active") {
5176
- return createToolResult(
5177
- JSON.stringify({
5178
- error: "SESSION_INVALID",
5179
- message: "Session not found or inactive."
5180
- }),
5181
- true
5182
- );
5176
+ const sessionResult = await validateActiveSession(db, input.session_id);
5177
+ if (!sessionResult.valid) {
5178
+ return sessionResult.error;
5183
5179
  }
5184
5180
  await updateSessionStatus(db, input.session_id, {
5185
5181
  current_task: input.current_task,
@@ -5194,34 +5190,24 @@ async function handleSessionTool(db, name, args, userId) {
5194
5190
  percentage: Math.round(completed / todos.length * 100)
5195
5191
  };
5196
5192
  }
5197
- return createToolResult(
5198
- JSON.stringify({
5199
- success: true,
5200
- message: "Status updated successfully.",
5201
- current_task: input.current_task ?? null,
5202
- progress
5203
- })
5204
- );
5193
+ return successResponse({
5194
+ success: true,
5195
+ message: "Status updated successfully.",
5196
+ current_task: input.current_task ?? null,
5197
+ progress
5198
+ });
5205
5199
  }
5206
5200
  case "collab_config": {
5207
5201
  const validation = validateInput(configSchema, args);
5208
5202
  if (!validation.success) {
5209
- return createToolResult(
5210
- JSON.stringify({ error: "INVALID_INPUT", message: validation.error }),
5211
- true
5212
- );
5203
+ return validationError(validation.error);
5213
5204
  }
5214
5205
  const input = validation.data;
5215
- const session = await getSession(db, input.session_id);
5216
- if (!session || session.status !== "active") {
5217
- return createToolResult(
5218
- JSON.stringify({
5219
- error: "SESSION_INVALID",
5220
- message: "Session not found or inactive."
5221
- }),
5222
- true
5223
- );
5206
+ const sessionResult = await validateActiveSession(db, input.session_id);
5207
+ if (!sessionResult.valid) {
5208
+ return sessionResult.error;
5224
5209
  }
5210
+ const { session } = sessionResult;
5225
5211
  let currentConfig = DEFAULT_SESSION_CONFIG;
5226
5212
  if (session.config) {
5227
5213
  try {
@@ -5236,13 +5222,11 @@ async function handleSessionTool(db, name, args, userId) {
5236
5222
  stale_threshold_hours: input.stale_threshold_hours ?? currentConfig.stale_threshold_hours
5237
5223
  };
5238
5224
  await updateSessionConfig(db, input.session_id, newConfig);
5239
- return createToolResult(
5240
- JSON.stringify({
5241
- success: true,
5242
- message: "Configuration updated.",
5243
- config: newConfig
5244
- })
5245
- );
5225
+ return successResponse({
5226
+ success: true,
5227
+ message: "Configuration updated.",
5228
+ config: newConfig
5229
+ });
5246
5230
  }
5247
5231
  default:
5248
5232
  return createToolResult(`Unknown session tool: ${name}`, true);
@@ -5394,22 +5378,13 @@ async function handleClaimTool(db, name, args) {
5394
5378
  case "collab_claim": {
5395
5379
  const validation = validateInput(claimCreateSchema, args);
5396
5380
  if (!validation.success) {
5397
- return createToolResult(
5398
- JSON.stringify({ error: "INVALID_INPUT", message: validation.error }),
5399
- true
5400
- );
5381
+ return validationError(validation.error);
5401
5382
  }
5402
5383
  const { session_id: sessionId, files, symbols, intent, scope = "medium" } = validation.data;
5403
5384
  const hasSymbols = symbols && symbols.length > 0;
5404
- const session = await getSession(db, sessionId);
5405
- if (!session || session.status !== "active") {
5406
- return createToolResult(
5407
- JSON.stringify({
5408
- error: "SESSION_INVALID",
5409
- message: "Session not found or inactive. Please start a new session."
5410
- }),
5411
- true
5412
- );
5385
+ const sessionResult = await validateActiveSession(db, sessionId);
5386
+ if (!sessionResult.valid) {
5387
+ return sessionResult.error;
5413
5388
  }
5414
5389
  const allFiles = new Set(files ?? []);
5415
5390
  if (hasSymbols) {
@@ -5473,10 +5448,7 @@ async function handleClaimTool(db, name, args) {
5473
5448
  case "collab_check": {
5474
5449
  const validation = validateInput(claimCheckSchema, args);
5475
5450
  if (!validation.success) {
5476
- return createToolResult(
5477
- JSON.stringify({ error: "INVALID_INPUT", message: validation.error }),
5478
- true
5479
- );
5451
+ return validationError(validation.error);
5480
5452
  }
5481
5453
  const { files, symbols, session_id: sessionId } = validation.data;
5482
5454
  let hasInProgressTodo = false;
@@ -5625,20 +5597,14 @@ async function handleClaimTool(db, name, args) {
5625
5597
  case "collab_release": {
5626
5598
  const validation = validateInput(claimReleaseSchema, args);
5627
5599
  if (!validation.success) {
5628
- return createToolResult(
5629
- JSON.stringify({ error: "INVALID_INPUT", message: validation.error }),
5630
- true
5631
- );
5600
+ return validationError(validation.error);
5632
5601
  }
5633
5602
  const { session_id: sessionId, claim_id: claimId, status, summary, force } = validation.data;
5634
5603
  const claim = await getClaim(db, claimId);
5635
5604
  if (!claim) {
5636
- return createToolResult(
5637
- JSON.stringify({
5638
- error: "CLAIM_NOT_FOUND",
5639
- message: "Claim not found. It may have already been released."
5640
- }),
5641
- true
5605
+ return errorResponse(
5606
+ ERROR_CODES.CLAIM_NOT_FOUND,
5607
+ "Claim not found. It may have already been released."
5642
5608
  );
5643
5609
  }
5644
5610
  if (claim.session_id !== sessionId) {
@@ -5673,12 +5639,9 @@ async function handleClaimTool(db, name, args) {
5673
5639
  }
5674
5640
  }
5675
5641
  if (claim.status !== "active") {
5676
- return createToolResult(
5677
- JSON.stringify({
5678
- error: "CLAIM_ALREADY_RELEASED",
5679
- message: `Claim was already ${claim.status}.`
5680
- }),
5681
- true
5642
+ return errorResponse(
5643
+ ERROR_CODES.CLAIM_ALREADY_RELEASED,
5644
+ `Claim was already ${claim.status}.`
5682
5645
  );
5683
5646
  }
5684
5647
  const isOwnClaim = claim.session_id === sessionId;
@@ -5696,33 +5659,24 @@ async function handleClaimTool(db, name, args) {
5696
5659
  case "collab_claims_list": {
5697
5660
  const validation = validateInput(claimListSchema, args);
5698
5661
  if (!validation.success) {
5699
- return createToolResult(
5700
- JSON.stringify({ error: "INVALID_INPUT", message: validation.error }),
5701
- true
5702
- );
5662
+ return validationError(validation.error);
5703
5663
  }
5704
5664
  const { session_id, status = "active", project_root } = validation.data;
5705
5665
  const claims = await listClaims(db, { session_id, status, project_root });
5706
- return createToolResult(
5707
- JSON.stringify(
5708
- {
5709
- claims: claims.map((c) => ({
5710
- id: c.id,
5711
- session_id: c.session_id,
5712
- session_name: c.session_name,
5713
- files: c.files,
5714
- intent: c.intent,
5715
- scope: c.scope,
5716
- status: c.status,
5717
- created_at: c.created_at,
5718
- completed_summary: c.completed_summary
5719
- })),
5720
- total: claims.length
5721
- },
5722
- null,
5723
- 2
5724
- )
5725
- );
5666
+ return successResponse({
5667
+ claims: claims.map((c) => ({
5668
+ id: c.id,
5669
+ session_id: c.session_id,
5670
+ session_name: c.session_name,
5671
+ files: c.files,
5672
+ intent: c.intent,
5673
+ scope: c.scope,
5674
+ status: c.status,
5675
+ created_at: c.created_at,
5676
+ completed_summary: c.completed_summary
5677
+ })),
5678
+ total: claims.length
5679
+ }, true);
5726
5680
  }
5727
5681
  default:
5728
5682
  return createToolResult(`Unknown claim tool: ${name}`, true);
@@ -5781,31 +5735,19 @@ async function handleMessageTool(db, name, args) {
5781
5735
  case "collab_message_send": {
5782
5736
  const validation = validateInput(messageSendSchema, args);
5783
5737
  if (!validation.success) {
5784
- return createToolResult(
5785
- JSON.stringify({ error: "INVALID_INPUT", message: validation.error }),
5786
- true
5787
- );
5738
+ return validationError(validation.error);
5788
5739
  }
5789
5740
  const input = validation.data;
5790
- const fromSession = await getSession(db, input.from_session_id);
5791
- if (!fromSession || fromSession.status !== "active") {
5792
- return createToolResult(
5793
- JSON.stringify({
5794
- error: "SESSION_INVALID",
5795
- message: "Your session is not active."
5796
- }),
5797
- true
5798
- );
5741
+ const senderResult = await validateActiveSession(db, input.from_session_id);
5742
+ if (!senderResult.valid) {
5743
+ return errorResponse(ERROR_CODES.SESSION_INVALID, "Your session is not active.");
5799
5744
  }
5800
5745
  if (input.to_session_id) {
5801
- const toSession = await getSession(db, input.to_session_id);
5802
- if (!toSession || toSession.status !== "active") {
5803
- return createToolResult(
5804
- JSON.stringify({
5805
- error: "TARGET_SESSION_INVALID",
5806
- message: "Target session not found or inactive."
5807
- }),
5808
- true
5746
+ const targetResult = await validateActiveSession(db, input.to_session_id);
5747
+ if (!targetResult.valid) {
5748
+ return errorResponse(
5749
+ ERROR_CODES.TARGET_SESSION_INVALID,
5750
+ "Target session not found or inactive."
5809
5751
  );
5810
5752
  }
5811
5753
  }
@@ -5814,22 +5756,17 @@ async function handleMessageTool(db, name, args) {
5814
5756
  to_session_id: input.to_session_id,
5815
5757
  content: input.content
5816
5758
  });
5817
- return createToolResult(
5818
- JSON.stringify({
5819
- success: true,
5820
- message_id: message.id,
5821
- sent_to: input.to_session_id ?? "all sessions (broadcast)",
5822
- message: "Message sent successfully."
5823
- })
5824
- );
5759
+ return successResponse({
5760
+ success: true,
5761
+ message_id: message.id,
5762
+ sent_to: input.to_session_id ?? "all sessions (broadcast)",
5763
+ message: "Message sent successfully."
5764
+ });
5825
5765
  }
5826
5766
  case "collab_message_list": {
5827
5767
  const validation = validateInput(messageListSchema, args);
5828
5768
  if (!validation.success) {
5829
- return createToolResult(
5830
- JSON.stringify({ error: "INVALID_INPUT", message: validation.error }),
5831
- true
5832
- );
5769
+ return validationError(validation.error);
5833
5770
  }
5834
5771
  const input = validation.data;
5835
5772
  const unreadOnly = input.unread_only ?? true;
@@ -5840,30 +5777,22 @@ async function handleMessageTool(db, name, args) {
5840
5777
  mark_as_read: markAsRead
5841
5778
  });
5842
5779
  if (messages.length === 0) {
5843
- return createToolResult(
5844
- JSON.stringify({
5845
- messages: [],
5846
- message: unreadOnly ? "No unread messages." : "No messages."
5847
- })
5848
- );
5780
+ return successResponse({
5781
+ messages: [],
5782
+ message: unreadOnly ? "No unread messages." : "No messages."
5783
+ });
5849
5784
  }
5850
- return createToolResult(
5851
- JSON.stringify(
5852
- {
5853
- messages: messages.map((m) => ({
5854
- id: m.id,
5855
- from_session_id: m.from_session_id,
5856
- content: m.content,
5857
- created_at: m.created_at,
5858
- is_broadcast: m.to_session_id === null
5859
- })),
5860
- total: messages.length,
5861
- marked_as_read: markAsRead
5862
- },
5863
- null,
5864
- 2
5865
- )
5866
- );
5785
+ return successResponse({
5786
+ messages: messages.map((m) => ({
5787
+ id: m.id,
5788
+ from_session_id: m.from_session_id,
5789
+ content: m.content,
5790
+ created_at: m.created_at,
5791
+ is_broadcast: m.to_session_id === null
5792
+ })),
5793
+ total: messages.length,
5794
+ marked_as_read: markAsRead
5795
+ }, true);
5867
5796
  }
5868
5797
  default:
5869
5798
  return createToolResult(`Unknown message tool: ${name}`, true);
@@ -5923,21 +5852,12 @@ async function handleDecisionTool(db, name, args) {
5923
5852
  case "collab_decision_add": {
5924
5853
  const validation = validateInput(decisionAddSchema, args);
5925
5854
  if (!validation.success) {
5926
- return createToolResult(
5927
- JSON.stringify({ error: "INVALID_INPUT", message: validation.error }),
5928
- true
5929
- );
5855
+ return validationError(validation.error);
5930
5856
  }
5931
5857
  const input = validation.data;
5932
- const session = await getSession(db, input.session_id);
5933
- if (!session || session.status !== "active") {
5934
- return createToolResult(
5935
- JSON.stringify({
5936
- error: "SESSION_INVALID",
5937
- message: "Session not found or inactive."
5938
- }),
5939
- true
5940
- );
5858
+ const sessionResult = await validateActiveSession(db, input.session_id);
5859
+ if (!sessionResult.valid) {
5860
+ return sessionResult.error;
5941
5861
  }
5942
5862
  const decision = await addDecision(db, {
5943
5863
  session_id: input.session_id,
@@ -5945,43 +5865,32 @@ async function handleDecisionTool(db, name, args) {
5945
5865
  title: input.title,
5946
5866
  description: input.description
5947
5867
  });
5948
- return createToolResult(
5949
- JSON.stringify({
5950
- success: true,
5951
- decision_id: decision.id,
5952
- message: "Decision recorded successfully."
5953
- })
5954
- );
5868
+ return successResponse({
5869
+ success: true,
5870
+ decision_id: decision.id,
5871
+ message: "Decision recorded successfully."
5872
+ });
5955
5873
  }
5956
5874
  case "collab_decision_list": {
5957
5875
  const validation = validateInput(decisionListSchema, args);
5958
5876
  if (!validation.success) {
5959
- return createToolResult(
5960
- JSON.stringify({ error: "INVALID_INPUT", message: validation.error }),
5961
- true
5962
- );
5877
+ return validationError(validation.error);
5963
5878
  }
5964
5879
  const input = validation.data;
5965
5880
  const decisions = await listDecisions(db, {
5966
5881
  category: input.category,
5967
5882
  limit: input.limit ?? 20
5968
5883
  });
5969
- return createToolResult(
5970
- JSON.stringify(
5971
- {
5972
- decisions: decisions.map((d) => ({
5973
- id: d.id,
5974
- category: d.category,
5975
- title: d.title,
5976
- description: d.description,
5977
- created_at: d.created_at
5978
- })),
5979
- total: decisions.length
5980
- },
5981
- null,
5982
- 2
5983
- )
5984
- );
5884
+ return successResponse({
5885
+ decisions: decisions.map((d) => ({
5886
+ id: d.id,
5887
+ category: d.category,
5888
+ title: d.title,
5889
+ description: d.description,
5890
+ created_at: d.created_at
5891
+ })),
5892
+ total: decisions.length
5893
+ }, true);
5985
5894
  }
5986
5895
  default:
5987
5896
  return createToolResult(`Unknown decision tool: ${name}`, true);
@@ -6842,14 +6751,14 @@ async function main() {
6842
6751
  break;
6843
6752
  }
6844
6753
  default: {
6845
- const errorResponse = jsonRpcError(id, -32601, `Method not found: ${method}`);
6846
- console.log(errorResponse);
6754
+ const errorResponse2 = jsonRpcError(id, -32601, `Method not found: ${method}`);
6755
+ console.log(errorResponse2);
6847
6756
  }
6848
6757
  }
6849
6758
  } catch (error) {
6850
6759
  const errorMessage = error instanceof Error ? error.message : "Unknown error";
6851
- const errorResponse = jsonRpcError(null, -32700, `Parse error: ${errorMessage}`);
6852
- console.log(errorResponse);
6760
+ const errorResponse2 = jsonRpcError(null, -32700, `Parse error: ${errorMessage}`);
6761
+ console.log(errorResponse2);
6853
6762
  }
6854
6763
  });
6855
6764
  rl.on("close", () => {