@devness/useai 0.5.27 → 0.5.28

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.
Files changed (2) hide show
  1. package/dist/index.js +298 -111
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2681,7 +2681,7 @@ var VERSION;
2681
2681
  var init_version = __esm({
2682
2682
  "../shared/dist/constants/version.js"() {
2683
2683
  "use strict";
2684
- VERSION = "0.5.27";
2684
+ VERSION = "0.5.28";
2685
2685
  }
2686
2686
  });
2687
2687
 
@@ -17070,11 +17070,11 @@ function showManualHints(installedTools) {
17070
17070
  for (const { name, hint } of hints) {
17071
17071
  console.log(` ${source_default.bold(name)}: ${hint}`);
17072
17072
  }
17073
- console.log(source_default.dim("\n \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
17073
+ console.log();
17074
17074
  for (const line of USEAI_INSTRUCTIONS_TEXT.split("\n")) {
17075
- console.log(source_default.dim(" \u2502 ") + line);
17075
+ console.log(` ${line}`);
17076
17076
  }
17077
- console.log(source_default.dim(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n"));
17077
+ console.log();
17078
17078
  }
17079
17079
  async function daemonInstallFlow(tools, autoYes, explicit) {
17080
17080
  console.log(source_default.dim(" Ensuring UseAI daemon is running..."));
@@ -37418,32 +37418,95 @@ function parseBody(req) {
37418
37418
  req.on("error", reject);
37419
37419
  });
37420
37420
  }
37421
- function tryRecoverEndSession(staleMcpSessionId, body, res) {
37421
+ function sendJsonRpcResult(res, rpcId, text) {
37422
+ res.writeHead(200, { "Content-Type": "application/json" });
37423
+ res.end(JSON.stringify({
37424
+ jsonrpc: "2.0",
37425
+ result: { content: [{ type: "text", text }] },
37426
+ id: rpcId
37427
+ }));
37428
+ }
37429
+ function readChainMetadata(useaiSessionId) {
37430
+ const activePath = join10(ACTIVE_DIR, `${useaiSessionId}.jsonl`);
37431
+ const sealedPath = join10(SEALED_DIR, `${useaiSessionId}.jsonl`);
37432
+ const chainPath = existsSync11(activePath) ? activePath : existsSync11(sealedPath) ? sealedPath : null;
37433
+ if (!chainPath) return null;
37434
+ try {
37435
+ const firstLine = readFileSync5(chainPath, "utf-8").split("\n")[0];
37436
+ if (!firstLine) return null;
37437
+ const record2 = JSON.parse(firstLine);
37438
+ const d = record2.data;
37439
+ return {
37440
+ client: d["client"] ?? "unknown",
37441
+ startTime: record2.timestamp,
37442
+ taskType: d["task_type"] ?? "coding",
37443
+ title: d["title"] ?? void 0,
37444
+ privateTitle: d["private_title"] ?? void 0,
37445
+ project: d["project"] ?? void 0,
37446
+ convId: d["conversation_id"] ?? void 0,
37447
+ convIdx: d["conversation_index"] ?? void 0
37448
+ };
37449
+ } catch {
37450
+ return null;
37451
+ }
37452
+ }
37453
+ function recoverStartSession(staleMcpSessionId, args, rpcId, res) {
37454
+ const map = readMcpMap();
37455
+ const prevSessionId = map[staleMcpSessionId];
37456
+ if (prevSessionId) {
37457
+ const prevActivePath = join10(ACTIVE_DIR, `${prevSessionId}.jsonl`);
37458
+ if (existsSync11(prevActivePath)) {
37459
+ sealOrphanFile(prevSessionId);
37460
+ }
37461
+ }
37462
+ let client = "unknown";
37463
+ if (prevSessionId) {
37464
+ const meta = readChainMetadata(prevSessionId);
37465
+ if (meta) client = meta.client;
37466
+ }
37467
+ const newSessionId = randomUUID4();
37468
+ const taskType = args["task_type"] ?? "coding";
37469
+ const title = args["title"];
37470
+ const privateTitle = args["private_title"];
37471
+ const project = args["project"];
37472
+ const convId = prevSessionId ? readChainMetadata(prevSessionId)?.convId ?? randomUUID4() : randomUUID4();
37473
+ const chainData = {
37474
+ client,
37475
+ task_type: taskType,
37476
+ project,
37477
+ conversation_id: convId,
37478
+ version: VERSION,
37479
+ recovered: true
37480
+ };
37481
+ if (title) chainData["title"] = title;
37482
+ if (privateTitle) chainData["private_title"] = privateTitle;
37483
+ const record2 = buildChainRecord("session_start", newSessionId, chainData, "GENESIS", daemonSigningKey);
37484
+ const chainPath = join10(ACTIVE_DIR, `${newSessionId}.jsonl`);
37485
+ appendFileSync2(chainPath, JSON.stringify(record2) + "\n");
37486
+ writeMcpMapping(staleMcpSessionId, newSessionId);
37487
+ sendJsonRpcResult(
37488
+ res,
37489
+ rpcId,
37490
+ `useai session started \u2014 ${taskType} on ${client} \xB7 ${newSessionId.slice(0, 8)} \xB7 conv ${convId.slice(0, 8)} \xB7 recovered \xB7 ${daemonSigningKey ? "signed" : "unsigned"}`
37491
+ );
37492
+ console.log(`Recovered useai_start: new session ${newSessionId.slice(0, 8)} (MCP ${staleMcpSessionId.slice(0, 8)})`);
37493
+ return true;
37494
+ }
37495
+ function recoverHeartbeat(staleMcpSessionId, rpcId, res) {
37496
+ const map = readMcpMap();
37497
+ const useaiSessionId = map[staleMcpSessionId];
37498
+ if (!useaiSessionId) return false;
37499
+ const chainPath = join10(ACTIVE_DIR, `${useaiSessionId}.jsonl`);
37500
+ if (!existsSync11(chainPath)) {
37501
+ sendJsonRpcResult(res, rpcId, "Session already ended (recovered).");
37502
+ return true;
37503
+ }
37422
37504
  try {
37423
- const rpc = body;
37424
- if (rpc?.method !== "tools/call" || rpc?.params?.name !== "useai_end") return false;
37425
- const args = rpc.params?.arguments ?? {};
37426
- const rpcId = rpc.id;
37427
- const map = readMcpMap();
37428
- const useaiSessionId = map[staleMcpSessionId];
37429
- if (!useaiSessionId) return false;
37430
- const chainPath = join10(ACTIVE_DIR, `${useaiSessionId}.jsonl`);
37431
- if (!existsSync11(chainPath)) return false;
37432
37505
  const content = readFileSync5(chainPath, "utf-8").trim();
37433
- if (!content) return false;
37434
37506
  const lines = content.split("\n").filter(Boolean);
37435
37507
  if (lines.length === 0) return false;
37436
37508
  const firstRecord = JSON.parse(lines[0]);
37437
37509
  const lastRecord = JSON.parse(lines[lines.length - 1]);
37438
- if (lastRecord.type === "session_end" || lastRecord.type === "session_seal") return false;
37439
- const startData = firstRecord.data;
37440
- const client = startData["client"] ?? "unknown";
37441
- const startTime = firstRecord.timestamp;
37442
- const sessionTitle = startData["title"] ?? void 0;
37443
- const sessionPrivateTitle = startData["private_title"] ?? void 0;
37444
- const sessionProject = startData["project"] ?? void 0;
37445
- const convId = startData["conversation_id"] ?? void 0;
37446
- const convIdx = startData["conversation_index"] ?? void 0;
37447
37510
  let heartbeatCount = 0;
37448
37511
  for (const line of lines) {
37449
37512
  try {
@@ -37451,34 +37514,58 @@ function tryRecoverEndSession(staleMcpSessionId, body, res) {
37451
37514
  } catch {
37452
37515
  }
37453
37516
  }
37454
- let chainTip = lastRecord.hash;
37455
- const duration3 = Math.round((Date.now() - new Date(startTime).getTime()) / 1e3);
37456
- const now = (/* @__PURE__ */ new Date()).toISOString();
37457
- let recordCount = lines.length;
37458
- const taskType = args["task_type"] ?? startData["task_type"] ?? "coding";
37459
- const languages = args["languages"] ?? [];
37460
- const filesTouched = args["files_touched_count"] ?? 0;
37461
- const milestonesInput = args["milestones"];
37462
- const evaluation = args["evaluation"];
37463
- let milestoneCount = 0;
37517
+ heartbeatCount++;
37518
+ const duration3 = Math.round((Date.now() - new Date(firstRecord.timestamp).getTime()) / 1e3);
37519
+ const record2 = buildChainRecord("heartbeat", useaiSessionId, {
37520
+ heartbeat_number: heartbeatCount,
37521
+ cumulative_seconds: duration3,
37522
+ recovered: true
37523
+ }, lastRecord.hash, daemonSigningKey);
37524
+ appendFileSync2(chainPath, JSON.stringify(record2) + "\n");
37525
+ sendJsonRpcResult(res, rpcId, `Heartbeat recorded. Session active for ${formatDuration(duration3)}.`);
37526
+ return true;
37527
+ } catch {
37528
+ return false;
37529
+ }
37530
+ }
37531
+ function recoverEndSession(staleMcpSessionId, args, rpcId, res) {
37532
+ const map = readMcpMap();
37533
+ const useaiSessionId = map[staleMcpSessionId];
37534
+ if (!useaiSessionId) return false;
37535
+ const activePath = join10(ACTIVE_DIR, `${useaiSessionId}.jsonl`);
37536
+ const sealedPath = join10(SEALED_DIR, `${useaiSessionId}.jsonl`);
37537
+ const chainPath = existsSync11(activePath) ? activePath : existsSync11(sealedPath) ? sealedPath : null;
37538
+ if (!chainPath) return false;
37539
+ const isAlreadySealed = chainPath === sealedPath;
37540
+ const content = readFileSync5(chainPath, "utf-8").trim();
37541
+ if (!content) return false;
37542
+ const lines = content.split("\n").filter(Boolean);
37543
+ if (lines.length === 0) return false;
37544
+ const firstRecord = JSON.parse(lines[0]);
37545
+ const startData = firstRecord.data;
37546
+ const client = startData["client"] ?? "unknown";
37547
+ const startTime = firstRecord.timestamp;
37548
+ const sessionTitle = startData["title"] ?? void 0;
37549
+ const sessionPrivateTitle = startData["private_title"] ?? void 0;
37550
+ const sessionProject = startData["project"] ?? void 0;
37551
+ const convId = startData["conversation_id"] ?? void 0;
37552
+ const convIdx = startData["conversation_index"] ?? void 0;
37553
+ const taskType = args["task_type"] ?? startData["task_type"] ?? "coding";
37554
+ const languages = args["languages"] ?? [];
37555
+ const filesTouched = args["files_touched_count"] ?? 0;
37556
+ const milestonesInput = args["milestones"];
37557
+ const evaluation = args["evaluation"];
37558
+ const now = (/* @__PURE__ */ new Date()).toISOString();
37559
+ const duration3 = Math.round((Date.now() - new Date(startTime).getTime()) / 1e3);
37560
+ if (isAlreadySealed) {
37561
+ let milestoneCount2 = 0;
37464
37562
  if (milestonesInput && milestonesInput.length > 0) {
37465
37563
  const config2 = readJson(CONFIG_FILE, { milestone_tracking: true, auto_sync: true });
37466
37564
  if (config2.milestone_tracking) {
37467
37565
  const durationMinutes = Math.round(duration3 / 60);
37468
37566
  const allMilestones = readJson(MILESTONES_FILE, []);
37469
37567
  for (const m of milestonesInput) {
37470
- const mRecord = buildChainRecord("milestone", useaiSessionId, {
37471
- title: m.title,
37472
- private_title: m.private_title,
37473
- category: m.category,
37474
- complexity: m.complexity ?? "medium",
37475
- duration_minutes: durationMinutes,
37476
- languages
37477
- }, chainTip, daemonSigningKey);
37478
- appendFileSync2(chainPath, JSON.stringify(mRecord) + "\n");
37479
- chainTip = mRecord.hash;
37480
- recordCount++;
37481
- const milestone = {
37568
+ allMilestones.push({
37482
37569
  id: `m_${randomUUID4().slice(0, 8)}`,
37483
37570
  session_id: useaiSessionId,
37484
37571
  title: m.title,
@@ -37492,26 +37579,14 @@ function tryRecoverEndSession(staleMcpSessionId, body, res) {
37492
37579
  created_at: now,
37493
37580
  published: false,
37494
37581
  published_at: null,
37495
- chain_hash: mRecord.hash
37496
- };
37497
- allMilestones.push(milestone);
37498
- milestoneCount++;
37582
+ chain_hash: ""
37583
+ });
37584
+ milestoneCount2++;
37499
37585
  }
37500
37586
  writeJson(MILESTONES_FILE, allMilestones);
37501
37587
  }
37502
37588
  }
37503
- const endRecord = buildChainRecord("session_end", useaiSessionId, {
37504
- duration_seconds: duration3,
37505
- task_type: taskType,
37506
- languages,
37507
- files_touched: filesTouched,
37508
- heartbeat_count: heartbeatCount,
37509
- recovered: true,
37510
- ...evaluation ? { evaluation } : {}
37511
- }, chainTip, daemonSigningKey);
37512
- appendFileSync2(chainPath, JSON.stringify(endRecord) + "\n");
37513
- recordCount++;
37514
- const sealData = JSON.stringify({
37589
+ const richSeal = {
37515
37590
  session_id: useaiSessionId,
37516
37591
  conversation_id: convId,
37517
37592
  conversation_index: convIdx,
@@ -37526,60 +37601,172 @@ function tryRecoverEndSession(staleMcpSessionId, body, res) {
37526
37601
  started_at: startTime,
37527
37602
  ended_at: now,
37528
37603
  duration_seconds: duration3,
37529
- heartbeat_count: heartbeatCount,
37530
- record_count: recordCount + 1,
37531
- // +1 for the seal record itself
37532
- chain_end_hash: endRecord.hash
37533
- });
37534
- const sealSignature = signHash(
37535
- createHash4("sha256").update(sealData).digest("hex"),
37536
- daemonSigningKey
37604
+ heartbeat_count: 0,
37605
+ record_count: lines.length,
37606
+ chain_start_hash: firstRecord.prev_hash,
37607
+ chain_end_hash: "",
37608
+ seal_signature: ""
37609
+ };
37610
+ upsertSessionSeal(richSeal);
37611
+ removeMcpMapping(staleMcpSessionId);
37612
+ const durationStr2 = formatDuration(duration3);
37613
+ sendJsonRpcResult(
37614
+ res,
37615
+ rpcId,
37616
+ `Session ended (recovered): ${durationStr2} ${taskType}` + (milestoneCount2 > 0 ? ` \xB7 ${milestoneCount2} milestone${milestoneCount2 > 1 ? "s" : ""} recorded` : "") + (evaluation ? ` \xB7 eval: ${evaluation.task_outcome}` : "")
37537
37617
  );
37538
- const sealRecord = buildChainRecord("session_seal", useaiSessionId, {
37618
+ console.log(`Recovered useai_end for already-sealed session ${useaiSessionId.slice(0, 8)} (MCP ${staleMcpSessionId.slice(0, 8)})`);
37619
+ return true;
37620
+ }
37621
+ const lastRecord = JSON.parse(lines[lines.length - 1]);
37622
+ if (lastRecord.type === "session_end" || lastRecord.type === "session_seal") {
37623
+ removeMcpMapping(staleMcpSessionId);
37624
+ sendJsonRpcResult(res, rpcId, "Session already ended.");
37625
+ return true;
37626
+ }
37627
+ let heartbeatCount = 0;
37628
+ for (const line of lines) {
37629
+ try {
37630
+ if (JSON.parse(line).type === "heartbeat") heartbeatCount++;
37631
+ } catch {
37632
+ }
37633
+ }
37634
+ let chainTip = lastRecord.hash;
37635
+ let recordCount = lines.length;
37636
+ let milestoneCount = 0;
37637
+ if (milestonesInput && milestonesInput.length > 0) {
37638
+ const config2 = readJson(CONFIG_FILE, { milestone_tracking: true, auto_sync: true });
37639
+ if (config2.milestone_tracking) {
37640
+ const durationMinutes = Math.round(duration3 / 60);
37641
+ const allMilestones = readJson(MILESTONES_FILE, []);
37642
+ for (const m of milestonesInput) {
37643
+ const mRecord = buildChainRecord("milestone", useaiSessionId, {
37644
+ title: m.title,
37645
+ private_title: m.private_title,
37646
+ category: m.category,
37647
+ complexity: m.complexity ?? "medium",
37648
+ duration_minutes: durationMinutes,
37649
+ languages
37650
+ }, chainTip, daemonSigningKey);
37651
+ appendFileSync2(activePath, JSON.stringify(mRecord) + "\n");
37652
+ chainTip = mRecord.hash;
37653
+ recordCount++;
37654
+ allMilestones.push({
37655
+ id: `m_${randomUUID4().slice(0, 8)}`,
37656
+ session_id: useaiSessionId,
37657
+ title: m.title,
37658
+ private_title: m.private_title,
37659
+ project: sessionProject,
37660
+ category: m.category,
37661
+ complexity: m.complexity ?? "medium",
37662
+ duration_minutes: durationMinutes,
37663
+ languages,
37664
+ client,
37665
+ created_at: now,
37666
+ published: false,
37667
+ published_at: null,
37668
+ chain_hash: mRecord.hash
37669
+ });
37670
+ milestoneCount++;
37671
+ }
37672
+ writeJson(MILESTONES_FILE, allMilestones);
37673
+ }
37674
+ }
37675
+ const endRecord = buildChainRecord("session_end", useaiSessionId, {
37676
+ duration_seconds: duration3,
37677
+ task_type: taskType,
37678
+ languages,
37679
+ files_touched: filesTouched,
37680
+ heartbeat_count: heartbeatCount,
37681
+ recovered: true,
37682
+ ...evaluation ? { evaluation } : {}
37683
+ }, chainTip, daemonSigningKey);
37684
+ appendFileSync2(activePath, JSON.stringify(endRecord) + "\n");
37685
+ recordCount++;
37686
+ const sealData = JSON.stringify({
37687
+ session_id: useaiSessionId,
37688
+ conversation_id: convId,
37689
+ conversation_index: convIdx,
37690
+ client,
37691
+ task_type: taskType,
37692
+ languages,
37693
+ files_touched: filesTouched,
37694
+ project: sessionProject,
37695
+ title: sessionTitle,
37696
+ private_title: sessionPrivateTitle,
37697
+ evaluation: evaluation ?? void 0,
37698
+ started_at: startTime,
37699
+ ended_at: now,
37700
+ duration_seconds: duration3,
37701
+ heartbeat_count: heartbeatCount,
37702
+ record_count: recordCount + 1,
37703
+ chain_end_hash: endRecord.hash
37704
+ });
37705
+ const sealSignature = signHash(
37706
+ createHash4("sha256").update(sealData).digest("hex"),
37707
+ daemonSigningKey
37708
+ );
37709
+ appendFileSync2(activePath, JSON.stringify(
37710
+ buildChainRecord("session_seal", useaiSessionId, {
37539
37711
  seal: sealData,
37540
37712
  seal_signature: sealSignature,
37541
37713
  recovered: true
37542
- }, endRecord.hash, daemonSigningKey);
37543
- appendFileSync2(chainPath, JSON.stringify(sealRecord) + "\n");
37544
- try {
37545
- renameSync3(chainPath, join10(SEALED_DIR, `${useaiSessionId}.jsonl`));
37546
- } catch {
37714
+ }, endRecord.hash, daemonSigningKey)
37715
+ ) + "\n");
37716
+ try {
37717
+ renameSync3(activePath, sealedPath);
37718
+ } catch {
37719
+ }
37720
+ upsertSessionSeal({
37721
+ session_id: useaiSessionId,
37722
+ conversation_id: convId,
37723
+ conversation_index: convIdx,
37724
+ client,
37725
+ task_type: taskType,
37726
+ languages,
37727
+ files_touched: filesTouched,
37728
+ project: sessionProject,
37729
+ title: sessionTitle,
37730
+ private_title: sessionPrivateTitle,
37731
+ evaluation: evaluation ?? void 0,
37732
+ started_at: startTime,
37733
+ ended_at: now,
37734
+ duration_seconds: duration3,
37735
+ heartbeat_count: heartbeatCount,
37736
+ record_count: recordCount + 1,
37737
+ chain_start_hash: firstRecord.prev_hash,
37738
+ chain_end_hash: endRecord.hash,
37739
+ seal_signature: sealSignature
37740
+ });
37741
+ removeMcpMapping(staleMcpSessionId);
37742
+ const durationStr = formatDuration(duration3);
37743
+ sendJsonRpcResult(
37744
+ res,
37745
+ rpcId,
37746
+ `Session ended (recovered): ${durationStr} ${taskType}` + (milestoneCount > 0 ? ` \xB7 ${milestoneCount} milestone${milestoneCount > 1 ? "s" : ""} recorded` : "") + (evaluation ? ` \xB7 eval: ${evaluation.task_outcome}` : "")
37747
+ );
37748
+ console.log(`Recovered useai_end for stale session ${useaiSessionId.slice(0, 8)} (MCP ${staleMcpSessionId.slice(0, 8)})`);
37749
+ return true;
37750
+ }
37751
+ function tryRecoverStaleSession(staleMcpSessionId, body, res) {
37752
+ try {
37753
+ const rpc = body;
37754
+ if (rpc?.method !== "tools/call") return false;
37755
+ const toolName = rpc.params?.name;
37756
+ const args = rpc.params?.arguments ?? {};
37757
+ const rpcId = rpc.id;
37758
+ switch (toolName) {
37759
+ case "useai_start":
37760
+ return recoverStartSession(staleMcpSessionId, args, rpcId, res);
37761
+ case "useai_heartbeat":
37762
+ return recoverHeartbeat(staleMcpSessionId, rpcId, res);
37763
+ case "useai_end":
37764
+ return recoverEndSession(staleMcpSessionId, args, rpcId, res);
37765
+ default:
37766
+ return false;
37547
37767
  }
37548
- const seal = {
37549
- session_id: useaiSessionId,
37550
- conversation_id: convId,
37551
- conversation_index: convIdx,
37552
- client,
37553
- task_type: taskType,
37554
- languages,
37555
- files_touched: filesTouched,
37556
- project: sessionProject,
37557
- title: sessionTitle,
37558
- private_title: sessionPrivateTitle,
37559
- evaluation: evaluation ?? void 0,
37560
- started_at: startTime,
37561
- ended_at: now,
37562
- duration_seconds: duration3,
37563
- heartbeat_count: heartbeatCount,
37564
- record_count: recordCount + 1,
37565
- chain_start_hash: firstRecord.prev_hash,
37566
- chain_end_hash: endRecord.hash,
37567
- seal_signature: sealSignature
37568
- };
37569
- upsertSessionSeal(seal);
37570
- removeMcpMapping(staleMcpSessionId);
37571
- const durationMin = Math.round(duration3 / 60);
37572
- const responseText = `Session ended (recovered): ${durationMin}m ${taskType}` + (milestoneCount > 0 ? ` \xB7 ${milestoneCount} milestone${milestoneCount > 1 ? "s" : ""} recorded` : "") + (evaluation ? ` \xB7 eval: ${evaluation.task_outcome}` : "");
37573
- res.writeHead(200, { "Content-Type": "application/json" });
37574
- res.end(JSON.stringify({
37575
- jsonrpc: "2.0",
37576
- result: { content: [{ type: "text", text: responseText }] },
37577
- id: rpcId
37578
- }));
37579
- console.log(`Recovered useai_end for stale session ${useaiSessionId.slice(0, 8)} (MCP ${staleMcpSessionId.slice(0, 8)})`);
37580
- return true;
37581
37768
  } catch (err) {
37582
- console.error("Recovery failed:", err.message);
37769
+ console.error("Stale session recovery failed:", err.message);
37583
37770
  return false;
37584
37771
  }
37585
37772
  }
@@ -37749,7 +37936,7 @@ async function startDaemon(port) {
37749
37936
  resetIdleTimer(sid);
37750
37937
  await sessions.get(sid).transport.handleRequest(req, res, body);
37751
37938
  } else if (sid && !sessions.has(sid)) {
37752
- if (!tryRecoverEndSession(sid, body, res)) {
37939
+ if (!tryRecoverStaleSession(sid, body, res)) {
37753
37940
  res.writeHead(404, { "Content-Type": "application/json" });
37754
37941
  res.end(JSON.stringify({
37755
37942
  jsonrpc: "2.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devness/useai",
3
- "version": "0.5.27",
3
+ "version": "0.5.28",
4
4
  "description": "Track your AI-assisted development workflow. MCP server that records usage metrics across all your AI tools.",
5
5
  "keywords": [
6
6
  "mcp",