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