@devness/useai 0.5.29 → 0.5.30
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 +72 -44
- 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.30";
|
|
2685
2685
|
}
|
|
2686
2686
|
});
|
|
2687
2687
|
|
|
@@ -36341,11 +36341,17 @@ var init_session_state = __esm({
|
|
|
36341
36341
|
conversationIndex;
|
|
36342
36342
|
/** MCP transport session ID (set in daemon mode). Survives reset(). */
|
|
36343
36343
|
mcpSessionId;
|
|
36344
|
+
/** Self-reported model ID from useai_start (e.g. "claude-opus-4-6"). */
|
|
36345
|
+
modelId;
|
|
36346
|
+
/** Token estimates for the useai_start tool call. */
|
|
36347
|
+
startCallTokensEst;
|
|
36344
36348
|
constructor() {
|
|
36345
36349
|
this.sessionId = generateSessionId();
|
|
36346
36350
|
this.conversationId = generateSessionId();
|
|
36347
36351
|
this.conversationIndex = 0;
|
|
36348
36352
|
this.mcpSessionId = null;
|
|
36353
|
+
this.modelId = null;
|
|
36354
|
+
this.startCallTokensEst = null;
|
|
36349
36355
|
this.sessionStartTime = Date.now();
|
|
36350
36356
|
this.heartbeatCount = 0;
|
|
36351
36357
|
this.sessionRecordCount = 0;
|
|
@@ -36370,6 +36376,8 @@ var init_session_state = __esm({
|
|
|
36370
36376
|
this.sessionTitle = null;
|
|
36371
36377
|
this.sessionPrivateTitle = null;
|
|
36372
36378
|
this.sessionPromptWordCount = null;
|
|
36379
|
+
this.modelId = null;
|
|
36380
|
+
this.startCallTokensEst = null;
|
|
36373
36381
|
this.detectProject();
|
|
36374
36382
|
}
|
|
36375
36383
|
detectProject() {
|
|
@@ -36393,6 +36401,9 @@ var init_session_state = __esm({
|
|
|
36393
36401
|
setPromptWordCount(count) {
|
|
36394
36402
|
this.sessionPromptWordCount = count;
|
|
36395
36403
|
}
|
|
36404
|
+
setModel(id) {
|
|
36405
|
+
this.modelId = id;
|
|
36406
|
+
}
|
|
36396
36407
|
incrementHeartbeat() {
|
|
36397
36408
|
this.heartbeatCount++;
|
|
36398
36409
|
}
|
|
@@ -36447,25 +36458,6 @@ function writeMcpMapping(mcpSessionId, useaiSessionId) {
|
|
|
36447
36458
|
map[mcpSessionId] = useaiSessionId;
|
|
36448
36459
|
writeJson(mcpMapPath(), map);
|
|
36449
36460
|
}
|
|
36450
|
-
function removeMcpMapping(mcpSessionId) {
|
|
36451
|
-
if (!mcpSessionId) return;
|
|
36452
|
-
const map = readMcpMap();
|
|
36453
|
-
if (mcpSessionId in map) {
|
|
36454
|
-
delete map[mcpSessionId];
|
|
36455
|
-
writeJson(mcpMapPath(), map);
|
|
36456
|
-
}
|
|
36457
|
-
}
|
|
36458
|
-
function removeMcpMappingByUseaiId(useaiSessionId) {
|
|
36459
|
-
const map = readMcpMap();
|
|
36460
|
-
let changed = false;
|
|
36461
|
-
for (const [mcpId, sessionId] of Object.entries(map)) {
|
|
36462
|
-
if (sessionId === useaiSessionId) {
|
|
36463
|
-
delete map[mcpId];
|
|
36464
|
-
changed = true;
|
|
36465
|
-
}
|
|
36466
|
-
}
|
|
36467
|
-
if (changed) writeJson(mcpMapPath(), map);
|
|
36468
|
-
}
|
|
36469
36461
|
var init_mcp_map = __esm({
|
|
36470
36462
|
"src/mcp-map.ts"() {
|
|
36471
36463
|
"use strict";
|
|
@@ -36510,15 +36502,17 @@ function registerTools(server2, session2, opts) {
|
|
|
36510
36502
|
task_type: external_exports.enum(["coding", "debugging", "testing", "planning", "reviewing", "documenting", "learning", "other"]).optional().describe("What kind of task is the developer working on?"),
|
|
36511
36503
|
title: external_exports.string().optional().describe(`Short public session title derived from the user's prompt. No project names, file paths, or identifying details. Example: "Fix authentication bug"`),
|
|
36512
36504
|
private_title: external_exports.string().optional().describe('Detailed session title for private records. Can include project names and specifics. Example: "Fix JWT refresh in UseAI login flow"'),
|
|
36513
|
-
project: external_exports.string().optional().describe('Project name for this session. Typically the root directory name of the codebase being worked on. Example: "goodpass", "useai"')
|
|
36505
|
+
project: external_exports.string().optional().describe('Project name for this session. Typically the root directory name of the codebase being worked on. Example: "goodpass", "useai"'),
|
|
36506
|
+
model: external_exports.string().optional().describe('The AI model ID running this session. Example: "claude-opus-4-6", "claude-sonnet-4-6"')
|
|
36514
36507
|
},
|
|
36515
|
-
async ({ task_type, title, private_title, project }) => {
|
|
36508
|
+
async ({ task_type, title, private_title, project, model }) => {
|
|
36516
36509
|
if (session2.sessionRecordCount > 0 && opts?.sealBeforeReset) {
|
|
36517
36510
|
opts.sealBeforeReset();
|
|
36518
36511
|
}
|
|
36519
36512
|
session2.reset();
|
|
36520
36513
|
resolveClient(server2, session2);
|
|
36521
36514
|
if (project) session2.setProject(project);
|
|
36515
|
+
if (model) session2.setModel(model);
|
|
36522
36516
|
session2.setTaskType(task_type ?? "coding");
|
|
36523
36517
|
session2.setTitle(title ?? null);
|
|
36524
36518
|
session2.setPrivateTitle(private_title ?? null);
|
|
@@ -36532,13 +36526,20 @@ function registerTools(server2, session2, opts) {
|
|
|
36532
36526
|
};
|
|
36533
36527
|
if (title) chainData.title = title;
|
|
36534
36528
|
if (private_title) chainData.private_title = private_title;
|
|
36529
|
+
if (model) chainData.model = model;
|
|
36535
36530
|
const record2 = session2.appendToChain("session_start", chainData);
|
|
36536
36531
|
writeMcpMapping(session2.mcpSessionId, session2.sessionId);
|
|
36532
|
+
const responseText = `useai session started \u2014 ${session2.sessionTaskType} on ${session2.clientName} \xB7 ${session2.sessionId.slice(0, 8)} \xB7 conv ${session2.conversationId.slice(0, 8)}#${session2.conversationIndex} \xB7 ${session2.signingAvailable ? "signed" : "unsigned"}`;
|
|
36533
|
+
const paramsJson = JSON.stringify({ task_type, title, private_title, project, model });
|
|
36534
|
+
session2.startCallTokensEst = {
|
|
36535
|
+
output: Math.ceil(paramsJson.length / 4),
|
|
36536
|
+
input: Math.ceil(responseText.length / 4)
|
|
36537
|
+
};
|
|
36537
36538
|
return {
|
|
36538
36539
|
content: [
|
|
36539
36540
|
{
|
|
36540
36541
|
type: "text",
|
|
36541
|
-
text:
|
|
36542
|
+
text: responseText
|
|
36542
36543
|
}
|
|
36543
36544
|
]
|
|
36544
36545
|
};
|
|
@@ -36639,14 +36640,18 @@ function registerTools(server2, session2, opts) {
|
|
|
36639
36640
|
writeJson(MILESTONES_FILE, allMilestones);
|
|
36640
36641
|
}
|
|
36641
36642
|
}
|
|
36643
|
+
const endParamsJson = JSON.stringify({ task_type, languages, files_touched_count, milestones: milestonesInput, evaluation });
|
|
36644
|
+
const endOutputTokensEst = Math.ceil(endParamsJson.length / 4);
|
|
36642
36645
|
const endRecord = session2.appendToChain("session_end", {
|
|
36643
36646
|
duration_seconds: duration3,
|
|
36644
36647
|
task_type: finalTaskType,
|
|
36645
36648
|
languages: languages ?? [],
|
|
36646
36649
|
files_touched: files_touched_count ?? 0,
|
|
36647
36650
|
heartbeat_count: session2.heartbeatCount,
|
|
36648
|
-
...evaluation ? { evaluation } : {}
|
|
36651
|
+
...evaluation ? { evaluation } : {},
|
|
36652
|
+
...session2.modelId ? { model: session2.modelId } : {}
|
|
36649
36653
|
});
|
|
36654
|
+
const startEst = session2.startCallTokensEst ?? { input: 0, output: 0 };
|
|
36650
36655
|
const sealData = JSON.stringify({
|
|
36651
36656
|
session_id: session2.sessionId,
|
|
36652
36657
|
conversation_id: session2.conversationId,
|
|
@@ -36659,6 +36664,7 @@ function registerTools(server2, session2, opts) {
|
|
|
36659
36664
|
title: session2.sessionTitle ?? void 0,
|
|
36660
36665
|
private_title: session2.sessionPrivateTitle ?? void 0,
|
|
36661
36666
|
prompt_word_count: session2.sessionPromptWordCount ?? void 0,
|
|
36667
|
+
model: session2.modelId ?? void 0,
|
|
36662
36668
|
evaluation: evaluation ?? void 0,
|
|
36663
36669
|
started_at: new Date(session2.sessionStartTime).toISOString(),
|
|
36664
36670
|
ended_at: now,
|
|
@@ -36683,6 +36689,17 @@ function registerTools(server2, session2, opts) {
|
|
|
36683
36689
|
}
|
|
36684
36690
|
} catch {
|
|
36685
36691
|
}
|
|
36692
|
+
const durationStr = formatDuration(duration3);
|
|
36693
|
+
const langStr = languages && languages.length > 0 ? ` using ${languages.join(", ")}` : "";
|
|
36694
|
+
const milestoneStr = milestoneCount > 0 ? ` \xB7 ${milestoneCount} milestone${milestoneCount > 1 ? "s" : ""} recorded` : "";
|
|
36695
|
+
const evalStr = evaluation ? ` \xB7 eval: ${evaluation.task_outcome} (prompt: ${evaluation.prompt_quality}/5)` : "";
|
|
36696
|
+
const responseText = `Session ended: ${durationStr} ${finalTaskType}${langStr}${milestoneStr}${evalStr}`;
|
|
36697
|
+
const endInputTokensEst = Math.ceil(responseText.length / 4);
|
|
36698
|
+
const toolOverhead = {
|
|
36699
|
+
start: { input_tokens_est: startEst.input, output_tokens_est: startEst.output },
|
|
36700
|
+
end: { input_tokens_est: endInputTokensEst, output_tokens_est: endOutputTokensEst },
|
|
36701
|
+
total_tokens_est: startEst.input + startEst.output + endInputTokensEst + endOutputTokensEst
|
|
36702
|
+
};
|
|
36686
36703
|
const seal = {
|
|
36687
36704
|
session_id: session2.sessionId,
|
|
36688
36705
|
conversation_id: session2.conversationId,
|
|
@@ -36695,7 +36712,9 @@ function registerTools(server2, session2, opts) {
|
|
|
36695
36712
|
title: session2.sessionTitle ?? void 0,
|
|
36696
36713
|
private_title: session2.sessionPrivateTitle ?? void 0,
|
|
36697
36714
|
prompt_word_count: session2.sessionPromptWordCount ?? void 0,
|
|
36715
|
+
model: session2.modelId ?? void 0,
|
|
36698
36716
|
evaluation: evaluation ?? void 0,
|
|
36717
|
+
tool_overhead: toolOverhead,
|
|
36699
36718
|
started_at: new Date(session2.sessionStartTime).toISOString(),
|
|
36700
36719
|
ended_at: now,
|
|
36701
36720
|
duration_seconds: duration3,
|
|
@@ -36708,16 +36727,11 @@ function registerTools(server2, session2, opts) {
|
|
|
36708
36727
|
const sessions2 = getSessions().filter((s) => s.session_id !== seal.session_id);
|
|
36709
36728
|
sessions2.push(seal);
|
|
36710
36729
|
writeJson(SESSIONS_FILE, sessions2);
|
|
36711
|
-
removeMcpMapping(session2.mcpSessionId);
|
|
36712
|
-
const durationStr = formatDuration(duration3);
|
|
36713
|
-
const langStr = languages && languages.length > 0 ? ` using ${languages.join(", ")}` : "";
|
|
36714
|
-
const milestoneStr = milestoneCount > 0 ? ` \xB7 ${milestoneCount} milestone${milestoneCount > 1 ? "s" : ""} recorded` : "";
|
|
36715
|
-
const evalStr = evaluation ? ` \xB7 eval: ${evaluation.task_outcome} (prompt: ${evaluation.prompt_quality}/5)` : "";
|
|
36716
36730
|
return {
|
|
36717
36731
|
content: [
|
|
36718
36732
|
{
|
|
36719
36733
|
type: "text",
|
|
36720
|
-
text:
|
|
36734
|
+
text: responseText
|
|
36721
36735
|
}
|
|
36722
36736
|
]
|
|
36723
36737
|
};
|
|
@@ -37167,7 +37181,6 @@ function sealOrphanFile(sessionId) {
|
|
|
37167
37181
|
renameSync3(filePath, join10(SEALED_DIR, `${sessionId}.jsonl`));
|
|
37168
37182
|
} catch {
|
|
37169
37183
|
}
|
|
37170
|
-
removeMcpMappingByUseaiId(sessionId);
|
|
37171
37184
|
const convId = startData["conversation_id"] ?? void 0;
|
|
37172
37185
|
const convIdx = startData["conversation_index"] ?? void 0;
|
|
37173
37186
|
const seal = {
|
|
@@ -37418,6 +37431,17 @@ function parseBody(req) {
|
|
|
37418
37431
|
req.on("error", reject);
|
|
37419
37432
|
});
|
|
37420
37433
|
}
|
|
37434
|
+
function detectClientFromHeaders(req) {
|
|
37435
|
+
const ua = (req.headers["user-agent"] ?? "").toLowerCase();
|
|
37436
|
+
if (ua.includes("claude-code") || ua.includes("claudecode")) return "claude-code";
|
|
37437
|
+
if (ua.includes("cursor")) return "cursor";
|
|
37438
|
+
if (ua.includes("windsurf") || ua.includes("codeium")) return "windsurf";
|
|
37439
|
+
if (ua.includes("vscode") || ua.includes("visual studio code")) return "vscode";
|
|
37440
|
+
if (ua.includes("codex")) return "codex";
|
|
37441
|
+
if (ua.includes("gemini")) return "gemini-cli";
|
|
37442
|
+
if (ua.includes("zed")) return "zed";
|
|
37443
|
+
return "unknown";
|
|
37444
|
+
}
|
|
37421
37445
|
function sendJsonRpcResult(res, rpcId, text) {
|
|
37422
37446
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
37423
37447
|
res.end(JSON.stringify({
|
|
@@ -37450,21 +37474,22 @@ function readChainMetadata(useaiSessionId) {
|
|
|
37450
37474
|
return null;
|
|
37451
37475
|
}
|
|
37452
37476
|
}
|
|
37453
|
-
function recoverStartSession(staleMcpSessionId, args, rpcId, res) {
|
|
37477
|
+
function recoverStartSession(staleMcpSessionId, args, rpcId, res, req) {
|
|
37454
37478
|
const map = readMcpMap();
|
|
37455
37479
|
const prevSessionId = map[staleMcpSessionId];
|
|
37456
|
-
|
|
37457
|
-
|
|
37458
|
-
if (existsSync11(prevActivePath)) {
|
|
37480
|
+
const prevActivePath = prevSessionId ? join10(ACTIVE_DIR, `${prevSessionId}.jsonl`) : null;
|
|
37481
|
+
if (prevActivePath && existsSync11(prevActivePath)) {
|
|
37459
37482
|
sealOrphanFile(prevSessionId);
|
|
37460
37483
|
}
|
|
37461
|
-
const meta = readChainMetadata(prevSessionId);
|
|
37462
|
-
const
|
|
37484
|
+
const meta = prevSessionId ? readChainMetadata(prevSessionId) : null;
|
|
37485
|
+
const chainClient = meta?.client;
|
|
37486
|
+
const client = chainClient && chainClient !== "unknown" ? chainClient : detectClientFromHeaders(req);
|
|
37463
37487
|
const newSessionId = randomUUID4();
|
|
37464
37488
|
const taskType = args["task_type"] ?? "coding";
|
|
37465
37489
|
const title = args["title"];
|
|
37466
37490
|
const privateTitle = args["private_title"];
|
|
37467
37491
|
const project = args["project"];
|
|
37492
|
+
const model = args["model"];
|
|
37468
37493
|
const convId = meta?.convId ?? randomUUID4();
|
|
37469
37494
|
const chainData = {
|
|
37470
37495
|
client,
|
|
@@ -37476,6 +37501,7 @@ function recoverStartSession(staleMcpSessionId, args, rpcId, res) {
|
|
|
37476
37501
|
};
|
|
37477
37502
|
if (title) chainData["title"] = title;
|
|
37478
37503
|
if (privateTitle) chainData["private_title"] = privateTitle;
|
|
37504
|
+
if (model) chainData["model"] = model;
|
|
37479
37505
|
const record2 = buildChainRecord("session_start", newSessionId, chainData, "GENESIS", daemonSigningKey);
|
|
37480
37506
|
const chainPath = join10(ACTIVE_DIR, `${newSessionId}.jsonl`);
|
|
37481
37507
|
appendFileSync2(chainPath, JSON.stringify(record2) + "\n");
|
|
@@ -37544,6 +37570,7 @@ function recoverEndSession(staleMcpSessionId, args, rpcId, res) {
|
|
|
37544
37570
|
const sessionTitle = startData["title"] ?? void 0;
|
|
37545
37571
|
const sessionPrivateTitle = startData["private_title"] ?? void 0;
|
|
37546
37572
|
const sessionProject = startData["project"] ?? void 0;
|
|
37573
|
+
const sessionModel = startData["model"] ?? void 0;
|
|
37547
37574
|
const convId = startData["conversation_id"] ?? void 0;
|
|
37548
37575
|
const convIdx = startData["conversation_index"] ?? void 0;
|
|
37549
37576
|
const taskType = args["task_type"] ?? startData["task_type"] ?? "coding";
|
|
@@ -37593,6 +37620,7 @@ function recoverEndSession(staleMcpSessionId, args, rpcId, res) {
|
|
|
37593
37620
|
project: sessionProject,
|
|
37594
37621
|
title: sessionTitle,
|
|
37595
37622
|
private_title: sessionPrivateTitle,
|
|
37623
|
+
model: sessionModel,
|
|
37596
37624
|
evaluation: evaluation ?? void 0,
|
|
37597
37625
|
started_at: startTime,
|
|
37598
37626
|
ended_at: now,
|
|
@@ -37604,7 +37632,6 @@ function recoverEndSession(staleMcpSessionId, args, rpcId, res) {
|
|
|
37604
37632
|
seal_signature: ""
|
|
37605
37633
|
};
|
|
37606
37634
|
upsertSessionSeal(richSeal);
|
|
37607
|
-
removeMcpMapping(staleMcpSessionId);
|
|
37608
37635
|
const durationStr2 = formatDuration(duration3);
|
|
37609
37636
|
sendJsonRpcResult(
|
|
37610
37637
|
res,
|
|
@@ -37616,7 +37643,6 @@ function recoverEndSession(staleMcpSessionId, args, rpcId, res) {
|
|
|
37616
37643
|
}
|
|
37617
37644
|
const lastRecord = JSON.parse(lines[lines.length - 1]);
|
|
37618
37645
|
if (lastRecord.type === "session_end" || lastRecord.type === "session_seal") {
|
|
37619
|
-
removeMcpMapping(staleMcpSessionId);
|
|
37620
37646
|
sendJsonRpcResult(res, rpcId, "Session already ended.");
|
|
37621
37647
|
return true;
|
|
37622
37648
|
}
|
|
@@ -37675,7 +37701,8 @@ function recoverEndSession(staleMcpSessionId, args, rpcId, res) {
|
|
|
37675
37701
|
files_touched: filesTouched,
|
|
37676
37702
|
heartbeat_count: heartbeatCount,
|
|
37677
37703
|
recovered: true,
|
|
37678
|
-
...evaluation ? { evaluation } : {}
|
|
37704
|
+
...evaluation ? { evaluation } : {},
|
|
37705
|
+
...sessionModel ? { model: sessionModel } : {}
|
|
37679
37706
|
}, chainTip, daemonSigningKey);
|
|
37680
37707
|
appendFileSync2(activePath, JSON.stringify(endRecord) + "\n");
|
|
37681
37708
|
recordCount++;
|
|
@@ -37690,6 +37717,7 @@ function recoverEndSession(staleMcpSessionId, args, rpcId, res) {
|
|
|
37690
37717
|
project: sessionProject,
|
|
37691
37718
|
title: sessionTitle,
|
|
37692
37719
|
private_title: sessionPrivateTitle,
|
|
37720
|
+
model: sessionModel,
|
|
37693
37721
|
evaluation: evaluation ?? void 0,
|
|
37694
37722
|
started_at: startTime,
|
|
37695
37723
|
ended_at: now,
|
|
@@ -37724,6 +37752,7 @@ function recoverEndSession(staleMcpSessionId, args, rpcId, res) {
|
|
|
37724
37752
|
project: sessionProject,
|
|
37725
37753
|
title: sessionTitle,
|
|
37726
37754
|
private_title: sessionPrivateTitle,
|
|
37755
|
+
model: sessionModel,
|
|
37727
37756
|
evaluation: evaluation ?? void 0,
|
|
37728
37757
|
started_at: startTime,
|
|
37729
37758
|
ended_at: now,
|
|
@@ -37734,7 +37763,6 @@ function recoverEndSession(staleMcpSessionId, args, rpcId, res) {
|
|
|
37734
37763
|
chain_end_hash: endRecord.hash,
|
|
37735
37764
|
seal_signature: sealSignature
|
|
37736
37765
|
});
|
|
37737
|
-
removeMcpMapping(staleMcpSessionId);
|
|
37738
37766
|
const durationStr = formatDuration(duration3);
|
|
37739
37767
|
sendJsonRpcResult(
|
|
37740
37768
|
res,
|
|
@@ -37744,7 +37772,7 @@ function recoverEndSession(staleMcpSessionId, args, rpcId, res) {
|
|
|
37744
37772
|
console.log(`Recovered useai_end for stale session ${useaiSessionId.slice(0, 8)} (MCP ${staleMcpSessionId.slice(0, 8)})`);
|
|
37745
37773
|
return true;
|
|
37746
37774
|
}
|
|
37747
|
-
function tryRecoverStaleSession(staleMcpSessionId, body, res) {
|
|
37775
|
+
function tryRecoverStaleSession(staleMcpSessionId, body, res, req) {
|
|
37748
37776
|
try {
|
|
37749
37777
|
const rpc = body;
|
|
37750
37778
|
if (rpc?.method !== "tools/call") return false;
|
|
@@ -37753,7 +37781,7 @@ function tryRecoverStaleSession(staleMcpSessionId, body, res) {
|
|
|
37753
37781
|
const rpcId = rpc.id;
|
|
37754
37782
|
switch (toolName) {
|
|
37755
37783
|
case "useai_start":
|
|
37756
|
-
return recoverStartSession(staleMcpSessionId, args, rpcId, res);
|
|
37784
|
+
return recoverStartSession(staleMcpSessionId, args, rpcId, res, req);
|
|
37757
37785
|
case "useai_heartbeat":
|
|
37758
37786
|
return recoverHeartbeat(staleMcpSessionId, rpcId, res);
|
|
37759
37787
|
case "useai_end":
|
|
@@ -37932,7 +37960,7 @@ async function startDaemon(port) {
|
|
|
37932
37960
|
resetIdleTimer(sid);
|
|
37933
37961
|
await sessions.get(sid).transport.handleRequest(req, res, body);
|
|
37934
37962
|
} else if (sid && !sessions.has(sid)) {
|
|
37935
|
-
if (!tryRecoverStaleSession(sid, body, res)) {
|
|
37963
|
+
if (!tryRecoverStaleSession(sid, body, res, req)) {
|
|
37936
37964
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
37937
37965
|
res.end(JSON.stringify({
|
|
37938
37966
|
jsonrpc: "2.0",
|