@mindstudio-ai/remy 0.1.137 → 0.1.139
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/automatedActions/buildFromInitialSpec.md +1 -1
- package/dist/automatedActions/buildFromRoadmap.md +1 -1
- package/dist/headless.js +139 -68
- package/dist/index.js +2021 -1932
- package/package.json +1 -1
|
@@ -29,4 +29,4 @@ The initial build prioritizes getting everything connected and functional, but t
|
|
|
29
29
|
|
|
30
30
|
Then, ask the `visualDesignExpert` to take a screenshot and verity that the visual design looks correct. Fix any issues it flags - we want the user's first time seeing the finished product to truly wow them.
|
|
31
31
|
|
|
32
|
-
When everything is working, use `productVision` to mark the MVP roadmap item as done, then call `setProjectOnboardingState({ state: "onboardingFinished" })`.
|
|
32
|
+
When everything is working, use `productVision` to mark the MVP roadmap item as done, then call `setProjectOnboardingState({ state: "onboardingFinished" })`. Finally, call `compactConversation` to summarize the build session and free up context for the next phase of work.
|
|
@@ -12,4 +12,4 @@ Then, put together a plan to build out the feature. Present the plan to the user
|
|
|
12
12
|
|
|
13
13
|
When they've approved the plan, be sure to update the spec first - remember, the spec is the source of truth about the product. Then, build everything in one turn, using the spec as the master plan.
|
|
14
14
|
|
|
15
|
-
When you're finished, verify your work, then tell`productVision` what was done so it can update the roadmap to reflect the progress. Give the user a summary of what was done.
|
|
15
|
+
When you're finished, verify your work, then tell `productVision` what was done so it can update the roadmap to reflect the progress. Give the user a summary of what was done, then call `compactConversation` to summarize the build session and free up context.
|
package/dist/headless.js
CHANGED
|
@@ -1739,6 +1739,29 @@ var setProjectMetadataTool = {
|
|
|
1739
1739
|
}
|
|
1740
1740
|
};
|
|
1741
1741
|
|
|
1742
|
+
// src/tools/common/compactConversation.ts
|
|
1743
|
+
var compactConversationTool = {
|
|
1744
|
+
clearable: false,
|
|
1745
|
+
definition: {
|
|
1746
|
+
name: "compactConversation",
|
|
1747
|
+
description: "Compact the conversation history by summarizing older messages into a checkpoint. The summary preserves key decisions, what was built, and the current state of the project, but drops the verbose tool results, diffs, and intermediate steps that are no longer useful. Use this when you have just finished a large block of mechanical work (building, refactoring, debugging) and are about to shift back into conversational mode with the user. Runs in the background. Do not use after small changes like fixing a bug or editing copy.",
|
|
1748
|
+
inputSchema: {
|
|
1749
|
+
type: "object",
|
|
1750
|
+
properties: {}
|
|
1751
|
+
}
|
|
1752
|
+
},
|
|
1753
|
+
async execute(_input, context) {
|
|
1754
|
+
if (!context?.conversationMessages || !context.apiConfig) {
|
|
1755
|
+
return "Error: compaction requires execution context.";
|
|
1756
|
+
}
|
|
1757
|
+
triggerCompaction(
|
|
1758
|
+
{ messages: context.conversationMessages },
|
|
1759
|
+
context.apiConfig
|
|
1760
|
+
);
|
|
1761
|
+
return "Compaction started in the background.";
|
|
1762
|
+
}
|
|
1763
|
+
};
|
|
1764
|
+
|
|
1742
1765
|
// src/tools/code/readFile.ts
|
|
1743
1766
|
import fs9 from "fs/promises";
|
|
1744
1767
|
var DEFAULT_MAX_LINES2 = 500;
|
|
@@ -2842,8 +2865,10 @@ async function runSubAgent(config) {
|
|
|
2842
2865
|
requestId,
|
|
2843
2866
|
history,
|
|
2844
2867
|
background,
|
|
2845
|
-
onBackgroundComplete
|
|
2868
|
+
onBackgroundComplete,
|
|
2869
|
+
captureArtifacts
|
|
2846
2870
|
} = config;
|
|
2871
|
+
const artifacts = {};
|
|
2847
2872
|
const bgAbort = background ? new AbortController() : null;
|
|
2848
2873
|
const signal = background ? bgAbort.signal : parentSignal;
|
|
2849
2874
|
const agentName = subAgentId || "sub-agent";
|
|
@@ -3015,7 +3040,12 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3015
3040
|
if (stopReason !== "tool_use" || toolCalls.length === 0) {
|
|
3016
3041
|
statusWatcher.stop();
|
|
3017
3042
|
const text = getPartialText(contentBlocks);
|
|
3018
|
-
|
|
3043
|
+
const hasArtifacts = Object.keys(artifacts).length > 0;
|
|
3044
|
+
return {
|
|
3045
|
+
text,
|
|
3046
|
+
messages: thisInvocation(),
|
|
3047
|
+
...hasArtifacts ? { artifacts } : {}
|
|
3048
|
+
};
|
|
3019
3049
|
}
|
|
3020
3050
|
log5.info("Tools executing", {
|
|
3021
3051
|
requestId,
|
|
@@ -3126,6 +3156,12 @@ ${partial}` : "[INTERRUPTED] Agent was interrupted before producing output.",
|
|
|
3126
3156
|
if (innerMsgs) {
|
|
3127
3157
|
block.subAgentMessages = innerMsgs;
|
|
3128
3158
|
}
|
|
3159
|
+
if (captureArtifacts?.includes(block.name) && !r.isError) {
|
|
3160
|
+
try {
|
|
3161
|
+
artifacts[block.name] = JSON.parse(r.result);
|
|
3162
|
+
} catch {
|
|
3163
|
+
}
|
|
3164
|
+
}
|
|
3129
3165
|
}
|
|
3130
3166
|
messages.push({
|
|
3131
3167
|
role: "user",
|
|
@@ -3457,13 +3493,22 @@ var browserAutomationTool = {
|
|
|
3457
3493
|
}
|
|
3458
3494
|
return result2;
|
|
3459
3495
|
},
|
|
3460
|
-
toolRegistry: context.toolRegistry
|
|
3496
|
+
toolRegistry: context.toolRegistry,
|
|
3497
|
+
captureArtifacts: ["screenshotFullPage"]
|
|
3461
3498
|
});
|
|
3462
3499
|
try {
|
|
3463
3500
|
await sidecarRequest("/reset-browser", {}, { timeout: 5e3 });
|
|
3464
3501
|
} catch {
|
|
3465
3502
|
}
|
|
3466
3503
|
context.subAgentMessages?.set(context.toolCallId, result.messages);
|
|
3504
|
+
const ss = result.artifacts?.screenshotFullPage;
|
|
3505
|
+
if (ss?.url) {
|
|
3506
|
+
return JSON.stringify({
|
|
3507
|
+
text: result.text,
|
|
3508
|
+
screenshotUrl: ss.url,
|
|
3509
|
+
...ss.styleMap ? { styleMap: ss.styleMap } : {}
|
|
3510
|
+
});
|
|
3511
|
+
}
|
|
3467
3512
|
return result.text;
|
|
3468
3513
|
} finally {
|
|
3469
3514
|
release();
|
|
@@ -3511,19 +3556,18 @@ var screenshotTool = {
|
|
|
3511
3556
|
if (input.instructions && context) {
|
|
3512
3557
|
const task = input.path ? `Navigate to "${input.path}", then: ${input.instructions}. After completing these steps, take a full-page screenshot.` : `${input.instructions}. After completing these steps, take a full-page screenshot.`;
|
|
3513
3558
|
const result = await browserAutomationTool.execute({ task }, context);
|
|
3514
|
-
const
|
|
3515
|
-
|
|
3516
|
-
);
|
|
3517
|
-
if (!urlMatch) {
|
|
3518
|
-
return `Error: browser navigation completed but no screenshot URL was returned. Agent output: ${result}`;
|
|
3519
|
-
}
|
|
3520
|
-
const url = urlMatch[0];
|
|
3559
|
+
const resultStr = result;
|
|
3560
|
+
let url;
|
|
3521
3561
|
let styleMap;
|
|
3522
3562
|
try {
|
|
3523
|
-
const parsed = JSON.parse(
|
|
3524
|
-
|
|
3563
|
+
const parsed = JSON.parse(resultStr);
|
|
3564
|
+
url = parsed.screenshotUrl;
|
|
3565
|
+
styleMap = parsed.styleMap;
|
|
3525
3566
|
} catch {
|
|
3526
3567
|
}
|
|
3568
|
+
if (!url) {
|
|
3569
|
+
return `Error: browser navigation completed but no screenshot URL was returned. Agent output: ${resultStr}`;
|
|
3570
|
+
}
|
|
3527
3571
|
const analysisPrompt = buildScreenshotAnalysisPrompt({
|
|
3528
3572
|
prompt: input.prompt,
|
|
3529
3573
|
styleMap
|
|
@@ -3847,19 +3891,18 @@ async function execute5(input, onLog, context) {
|
|
|
3847
3891
|
try {
|
|
3848
3892
|
const task = input.path ? `Navigate to "${input.path}", then: ${input.instructions}. After completing these steps, take a full-page screenshot.` : `${input.instructions}. After completing these steps, take a full-page screenshot.`;
|
|
3849
3893
|
const result = await browserAutomationTool.execute({ task }, context);
|
|
3850
|
-
const
|
|
3851
|
-
|
|
3852
|
-
);
|
|
3853
|
-
if (!urlMatch) {
|
|
3854
|
-
return `Error: browser navigation completed but no screenshot URL was returned. Agent output: ${result}`;
|
|
3855
|
-
}
|
|
3856
|
-
const url = urlMatch[0];
|
|
3894
|
+
const resultStr = result;
|
|
3895
|
+
let url;
|
|
3857
3896
|
let styleMap;
|
|
3858
3897
|
try {
|
|
3859
|
-
const parsed = JSON.parse(
|
|
3860
|
-
|
|
3898
|
+
const parsed = JSON.parse(resultStr);
|
|
3899
|
+
url = parsed.screenshotUrl;
|
|
3900
|
+
styleMap = parsed.styleMap;
|
|
3861
3901
|
} catch {
|
|
3862
3902
|
}
|
|
3903
|
+
if (!url) {
|
|
3904
|
+
return `Error: browser navigation completed but no screenshot URL was returned. Agent output: ${resultStr}`;
|
|
3905
|
+
}
|
|
3863
3906
|
const analysisPrompt = buildScreenshotAnalysisPrompt({
|
|
3864
3907
|
prompt: input.prompt,
|
|
3865
3908
|
styleMap
|
|
@@ -4997,6 +5040,7 @@ var ALL_TOOLS = [
|
|
|
4997
5040
|
designExpertTool,
|
|
4998
5041
|
productVisionTool,
|
|
4999
5042
|
codeSanityCheckTool,
|
|
5043
|
+
compactConversationTool,
|
|
5000
5044
|
// Post-onboarding
|
|
5001
5045
|
clearSyncStatusTool,
|
|
5002
5046
|
presentSyncPlanTool,
|
|
@@ -5115,6 +5159,24 @@ function clearSession(state) {
|
|
|
5115
5159
|
}
|
|
5116
5160
|
}
|
|
5117
5161
|
|
|
5162
|
+
// src/compaction/trigger.ts
|
|
5163
|
+
var log8 = createLogger("compaction:trigger");
|
|
5164
|
+
function triggerCompaction(state, apiConfig, callbacks) {
|
|
5165
|
+
callbacks?.onStart?.();
|
|
5166
|
+
const system = buildSystemPrompt("onboardingFinished");
|
|
5167
|
+
const tools2 = getToolDefinitions("onboardingFinished");
|
|
5168
|
+
compactConversation(state, apiConfig, system, tools2).then(() => {
|
|
5169
|
+
saveSession(state);
|
|
5170
|
+
callbacks?.onComplete?.();
|
|
5171
|
+
log8.info("Compaction complete");
|
|
5172
|
+
}).catch((err) => {
|
|
5173
|
+
callbacks?.onError?.(err.message || "Compaction failed");
|
|
5174
|
+
log8.error("Compaction failed", { error: err.message });
|
|
5175
|
+
}).finally(() => {
|
|
5176
|
+
callbacks?.onFinally?.();
|
|
5177
|
+
});
|
|
5178
|
+
}
|
|
5179
|
+
|
|
5118
5180
|
// src/parsePartialJson.ts
|
|
5119
5181
|
var PartialJSON = class extends Error {
|
|
5120
5182
|
};
|
|
@@ -5305,7 +5367,7 @@ function friendlyError(raw) {
|
|
|
5305
5367
|
}
|
|
5306
5368
|
|
|
5307
5369
|
// src/agent.ts
|
|
5308
|
-
var
|
|
5370
|
+
var log9 = createLogger("agent");
|
|
5309
5371
|
function getTextContent(blocks) {
|
|
5310
5372
|
return blocks.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
5311
5373
|
}
|
|
@@ -5350,7 +5412,7 @@ async function runTurn(params) {
|
|
|
5350
5412
|
} = params;
|
|
5351
5413
|
const tools2 = getToolDefinitions(onboardingState);
|
|
5352
5414
|
const excludeToolsFromClearing = tools2.filter((t) => !CLEARABLE_TOOLS.has(t.name)).map((t) => t.name);
|
|
5353
|
-
|
|
5415
|
+
log9.info("Turn started", {
|
|
5354
5416
|
requestId,
|
|
5355
5417
|
model,
|
|
5356
5418
|
toolCount: tools2.length,
|
|
@@ -5574,7 +5636,7 @@ async function runTurn(params) {
|
|
|
5574
5636
|
const tool = getToolByName(event.name);
|
|
5575
5637
|
const wasStreamed = acc?.started ?? false;
|
|
5576
5638
|
const isInputStreaming = !!tool?.streaming?.partialInput;
|
|
5577
|
-
|
|
5639
|
+
log9.info("Tool received", {
|
|
5578
5640
|
requestId,
|
|
5579
5641
|
toolCallId: event.id,
|
|
5580
5642
|
name: event.name
|
|
@@ -5621,7 +5683,14 @@ async function runTurn(params) {
|
|
|
5621
5683
|
});
|
|
5622
5684
|
state.messages.push({
|
|
5623
5685
|
role: "assistant",
|
|
5624
|
-
content: [...contentBlocks].sort((a, b) => a.startedAt - b.startedAt)
|
|
5686
|
+
content: [...contentBlocks].sort((a, b) => a.startedAt - b.startedAt),
|
|
5687
|
+
usage: {
|
|
5688
|
+
inputTokens: turnInputTokens,
|
|
5689
|
+
outputTokens: turnOutputTokens,
|
|
5690
|
+
cacheCreationTokens: turnCacheCreation || void 0,
|
|
5691
|
+
cacheReadTokens: turnCacheRead || void 0,
|
|
5692
|
+
llmCalls: turnLlmCalls
|
|
5693
|
+
}
|
|
5625
5694
|
});
|
|
5626
5695
|
}
|
|
5627
5696
|
onEvent({ type: "turn_cancelled" });
|
|
@@ -5631,7 +5700,14 @@ async function runTurn(params) {
|
|
|
5631
5700
|
if (contentBlocks.length > 0) {
|
|
5632
5701
|
state.messages.push({
|
|
5633
5702
|
role: "assistant",
|
|
5634
|
-
content: [...contentBlocks].sort((a, b) => a.startedAt - b.startedAt)
|
|
5703
|
+
content: [...contentBlocks].sort((a, b) => a.startedAt - b.startedAt),
|
|
5704
|
+
usage: {
|
|
5705
|
+
inputTokens: turnInputTokens,
|
|
5706
|
+
outputTokens: turnOutputTokens,
|
|
5707
|
+
cacheCreationTokens: turnCacheCreation || void 0,
|
|
5708
|
+
cacheReadTokens: turnCacheRead || void 0,
|
|
5709
|
+
llmCalls: turnLlmCalls
|
|
5710
|
+
}
|
|
5635
5711
|
});
|
|
5636
5712
|
}
|
|
5637
5713
|
const toolCalls = getToolCalls(contentBlocks);
|
|
@@ -5653,7 +5729,7 @@ async function runTurn(params) {
|
|
|
5653
5729
|
});
|
|
5654
5730
|
return;
|
|
5655
5731
|
}
|
|
5656
|
-
|
|
5732
|
+
log9.info("Tools executing", {
|
|
5657
5733
|
requestId,
|
|
5658
5734
|
count: toolCalls.length,
|
|
5659
5735
|
tools: toolCalls.map((tc) => tc.name)
|
|
@@ -5700,7 +5776,7 @@ async function runTurn(params) {
|
|
|
5700
5776
|
let result;
|
|
5701
5777
|
if (EXTERNAL_TOOLS.has(tc.name) && resolveExternalTool) {
|
|
5702
5778
|
saveSession(state);
|
|
5703
|
-
|
|
5779
|
+
log9.info("Waiting for external tool result", {
|
|
5704
5780
|
requestId,
|
|
5705
5781
|
toolCallId: tc.id,
|
|
5706
5782
|
name: tc.name
|
|
@@ -5757,7 +5833,7 @@ async function runTurn(params) {
|
|
|
5757
5833
|
if (!tc.input.background) {
|
|
5758
5834
|
toolRegistry?.unregister(tc.id);
|
|
5759
5835
|
}
|
|
5760
|
-
|
|
5836
|
+
log9.info("Tool completed", {
|
|
5761
5837
|
requestId,
|
|
5762
5838
|
toolCallId: tc.id,
|
|
5763
5839
|
name: tc.name,
|
|
@@ -5812,7 +5888,7 @@ async function runTurn(params) {
|
|
|
5812
5888
|
}
|
|
5813
5889
|
|
|
5814
5890
|
// src/toolRegistry.ts
|
|
5815
|
-
var
|
|
5891
|
+
var log10 = createLogger("tool-registry");
|
|
5816
5892
|
var ToolRegistry = class {
|
|
5817
5893
|
entries = /* @__PURE__ */ new Map();
|
|
5818
5894
|
onEvent;
|
|
@@ -5838,7 +5914,7 @@ var ToolRegistry = class {
|
|
|
5838
5914
|
if (!entry) {
|
|
5839
5915
|
return false;
|
|
5840
5916
|
}
|
|
5841
|
-
|
|
5917
|
+
log10.info("Tool stopped", { toolCallId: id, name: entry.name, mode });
|
|
5842
5918
|
entry.abortController.abort(mode);
|
|
5843
5919
|
if (mode === "graceful") {
|
|
5844
5920
|
const partial = entry.getPartialResult?.() ?? "";
|
|
@@ -5871,7 +5947,7 @@ ${partial}` : "[INTERRUPTED] Tool execution was stopped.";
|
|
|
5871
5947
|
if (!entry) {
|
|
5872
5948
|
return false;
|
|
5873
5949
|
}
|
|
5874
|
-
|
|
5950
|
+
log10.info("Tool restarted", { toolCallId: id, name: entry.name });
|
|
5875
5951
|
entry.abortController.abort("restart");
|
|
5876
5952
|
const newInput = patchedInput ? { ...entry.input, ...patchedInput } : entry.input;
|
|
5877
5953
|
this.onEvent?.({
|
|
@@ -5916,7 +5992,7 @@ ${body}`;
|
|
|
5916
5992
|
}
|
|
5917
5993
|
|
|
5918
5994
|
// src/headless.ts
|
|
5919
|
-
var
|
|
5995
|
+
var log11 = createLogger("headless");
|
|
5920
5996
|
function emit(event, data, requestId) {
|
|
5921
5997
|
const payload = { event, ...data };
|
|
5922
5998
|
if (requestId) {
|
|
@@ -6032,7 +6108,7 @@ ${xmlParts}
|
|
|
6032
6108
|
}
|
|
6033
6109
|
function onBackgroundComplete(toolCallId, name, result, subAgentMessages) {
|
|
6034
6110
|
pendingBlockUpdates.push({ toolCallId, result, subAgentMessages });
|
|
6035
|
-
|
|
6111
|
+
log11.info("Background complete", {
|
|
6036
6112
|
toolCallId,
|
|
6037
6113
|
name,
|
|
6038
6114
|
requestId: currentRequestId
|
|
@@ -6304,7 +6380,7 @@ ${xmlParts}
|
|
|
6304
6380
|
requestId
|
|
6305
6381
|
);
|
|
6306
6382
|
}
|
|
6307
|
-
|
|
6383
|
+
log11.info("Turn complete", {
|
|
6308
6384
|
requestId,
|
|
6309
6385
|
durationMs: Date.now() - turnStart
|
|
6310
6386
|
});
|
|
@@ -6313,7 +6389,7 @@ ${xmlParts}
|
|
|
6313
6389
|
emit("error", { error: err.message }, requestId);
|
|
6314
6390
|
emit("completed", { success: false, error: err.message }, requestId);
|
|
6315
6391
|
}
|
|
6316
|
-
|
|
6392
|
+
log11.warn("Command failed", {
|
|
6317
6393
|
action: "message",
|
|
6318
6394
|
requestId,
|
|
6319
6395
|
error: err.message
|
|
@@ -6333,7 +6409,7 @@ ${xmlParts}
|
|
|
6333
6409
|
return;
|
|
6334
6410
|
}
|
|
6335
6411
|
const { action, requestId } = parsed;
|
|
6336
|
-
|
|
6412
|
+
log11.info("Command received", { action, requestId });
|
|
6337
6413
|
if (action === "tool_result" && parsed.id) {
|
|
6338
6414
|
const id = parsed.id;
|
|
6339
6415
|
const result = parsed.result ?? "";
|
|
@@ -6342,7 +6418,7 @@ ${xmlParts}
|
|
|
6342
6418
|
pendingTools.delete(id);
|
|
6343
6419
|
pending.resolve(result);
|
|
6344
6420
|
} else if (!running) {
|
|
6345
|
-
|
|
6421
|
+
log11.info("Late tool_result while idle, dismissing", { id });
|
|
6346
6422
|
emit("completed", { success: true }, requestId);
|
|
6347
6423
|
} else {
|
|
6348
6424
|
earlyResults.set(id, result);
|
|
@@ -6398,36 +6474,31 @@ ${xmlParts}
|
|
|
6398
6474
|
return;
|
|
6399
6475
|
}
|
|
6400
6476
|
if (action === "compact") {
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
"compaction_complete",
|
|
6416
|
-
{
|
|
6417
|
-
|
|
6418
|
-
)
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
sessionStats.messageCount = state.messages.length;
|
|
6427
|
-
sessionStats.updatedAt = Date.now();
|
|
6428
|
-
try {
|
|
6429
|
-
writeFileSync(".remy-stats.json", JSON.stringify(sessionStats));
|
|
6430
|
-
} catch {
|
|
6477
|
+
triggerCompaction(state, config, {
|
|
6478
|
+
onStart: () => {
|
|
6479
|
+
sessionStats.compactionInProgress = true;
|
|
6480
|
+
sessionStats.updatedAt = Date.now();
|
|
6481
|
+
try {
|
|
6482
|
+
writeFileSync(".remy-stats.json", JSON.stringify(sessionStats));
|
|
6483
|
+
} catch {
|
|
6484
|
+
}
|
|
6485
|
+
},
|
|
6486
|
+
onComplete: () => {
|
|
6487
|
+
emit("compaction_complete", {}, requestId);
|
|
6488
|
+
emit("completed", { success: true }, requestId);
|
|
6489
|
+
},
|
|
6490
|
+
onError: (error) => {
|
|
6491
|
+
emit("compaction_complete", { error }, requestId);
|
|
6492
|
+
emit("completed", { success: false, error }, requestId);
|
|
6493
|
+
},
|
|
6494
|
+
onFinally: () => {
|
|
6495
|
+
sessionStats.compactionInProgress = false;
|
|
6496
|
+
sessionStats.messageCount = state.messages.length;
|
|
6497
|
+
sessionStats.updatedAt = Date.now();
|
|
6498
|
+
try {
|
|
6499
|
+
writeFileSync(".remy-stats.json", JSON.stringify(sessionStats));
|
|
6500
|
+
} catch {
|
|
6501
|
+
}
|
|
6431
6502
|
}
|
|
6432
6503
|
});
|
|
6433
6504
|
return;
|