@granular-software/sdk 0.4.21 → 0.4.23
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/README.md +241 -151
- package/dist/agent-evals.d.mts +1 -1
- package/dist/agent-evals.d.ts +1 -1
- package/dist/agent-evals.js +240 -65
- package/dist/agent-evals.js.map +1 -1
- package/dist/agent-evals.mjs +240 -65
- package/dist/agent-evals.mjs.map +1 -1
- package/dist/agent-harness.js +126 -41
- package/dist/agent-harness.js.map +1 -1
- package/dist/agent-harness.mjs +126 -41
- package/dist/agent-harness.mjs.map +1 -1
- package/dist/cli/index.js +566 -191
- package/dist/{client-DLGC0mJk.d.mts → client-CQFKsCTd.d.mts} +1 -1
- package/dist/{client-DLGC0mJk.d.ts → client-CQFKsCTd.d.ts} +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +240 -65
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +240 -65
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/agent-harness.js
CHANGED
|
@@ -26,7 +26,8 @@ function uniqueStrings(values, maxCount) {
|
|
|
26
26
|
}
|
|
27
27
|
function formatScalar(value) {
|
|
28
28
|
if (typeof value === "string") return JSON.stringify(value);
|
|
29
|
-
if (typeof value === "number" || typeof value === "boolean")
|
|
29
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
30
|
+
return String(value);
|
|
30
31
|
if (value === null) return "null";
|
|
31
32
|
return "unknown";
|
|
32
33
|
}
|
|
@@ -34,7 +35,9 @@ function describeHeapEntry(entry, previewFieldLimit = 3) {
|
|
|
34
35
|
const headline = entry.label || entry.id || entry.path || "Unknown";
|
|
35
36
|
const pathLabel = entry.path && entry.path !== headline ? ` <${entry.path}>` : "";
|
|
36
37
|
const classLabel = entry.className || "unknown";
|
|
37
|
-
const preview = asArray(entry.fields).filter(
|
|
38
|
+
const preview = asArray(entry.fields).filter(
|
|
39
|
+
(field) => field?.name && field.name !== "_realId" && field.name !== "real_id"
|
|
40
|
+
).slice(0, previewFieldLimit).map((field) => `${field.name}=${formatScalar(field.value)}`).join(", ");
|
|
38
41
|
return preview ? `${headline}${pathLabel} [${classLabel}] ${preview}` : `${headline}${pathLabel} [${classLabel}]`;
|
|
39
42
|
}
|
|
40
43
|
function hashString(value) {
|
|
@@ -50,7 +53,9 @@ function hasSubstantiveAwaitAfterPrompt(code, marker) {
|
|
|
50
53
|
const startIndex = code.indexOf(marker);
|
|
51
54
|
if (startIndex === -1) return true;
|
|
52
55
|
const segment = code.slice(startIndex + marker.length);
|
|
53
|
-
const callMatches = segment.matchAll(
|
|
56
|
+
const callMatches = segment.matchAll(
|
|
57
|
+
/await\s+([A-Za-z0-9_$.]+)\.([A-Za-z0-9_]+)\s*\(/g
|
|
58
|
+
);
|
|
54
59
|
for (const match of callMatches) {
|
|
55
60
|
const receiver = match[1] || "";
|
|
56
61
|
const method = match[2] || "";
|
|
@@ -82,9 +87,16 @@ function reviewGeneratedJobCode(code) {
|
|
|
82
87
|
/approved\./i
|
|
83
88
|
];
|
|
84
89
|
if (normalized.includes("await loop.confirm(")) {
|
|
85
|
-
const postConfirm = normalized.slice(
|
|
86
|
-
|
|
87
|
-
|
|
90
|
+
const postConfirm = normalized.slice(
|
|
91
|
+
normalized.indexOf("await loop.confirm(")
|
|
92
|
+
);
|
|
93
|
+
const hasPlaceholder = placeholderPatterns.some(
|
|
94
|
+
(pattern) => pattern.test(postConfirm)
|
|
95
|
+
);
|
|
96
|
+
const hasSubstantiveAwait = hasSubstantiveAwaitAfterPrompt(
|
|
97
|
+
normalized,
|
|
98
|
+
"await loop.confirm("
|
|
99
|
+
);
|
|
88
100
|
if (!hasSubstantiveAwait || hasPlaceholder) {
|
|
89
101
|
issues.push({
|
|
90
102
|
code: "placeholder_after_confirm",
|
|
@@ -94,9 +106,16 @@ function reviewGeneratedJobCode(code) {
|
|
|
94
106
|
}
|
|
95
107
|
}
|
|
96
108
|
if (normalized.includes("await loop.ask_user(")) {
|
|
97
|
-
const postPrompt = normalized.slice(
|
|
98
|
-
|
|
99
|
-
|
|
109
|
+
const postPrompt = normalized.slice(
|
|
110
|
+
normalized.indexOf("await loop.ask_user(")
|
|
111
|
+
);
|
|
112
|
+
const hasPlaceholder = placeholderPatterns.some(
|
|
113
|
+
(pattern) => pattern.test(postPrompt)
|
|
114
|
+
);
|
|
115
|
+
const hasSubstantiveAwait = hasSubstantiveAwaitAfterPrompt(
|
|
116
|
+
normalized,
|
|
117
|
+
"await loop.ask_user("
|
|
118
|
+
);
|
|
100
119
|
if (hasPlaceholder && !hasSubstantiveAwait) {
|
|
101
120
|
issues.push({
|
|
102
121
|
code: "placeholder_after_ask_user",
|
|
@@ -177,7 +196,9 @@ function getLatestClosure(liveDoc) {
|
|
|
177
196
|
if (!record) continue;
|
|
178
197
|
closures.push({ ...record, closureId });
|
|
179
198
|
}
|
|
180
|
-
closures.sort(
|
|
199
|
+
closures.sort(
|
|
200
|
+
(left, right) => (Number(right.createdAt) || 0) - (Number(left.createdAt) || 0)
|
|
201
|
+
);
|
|
181
202
|
return closures[0] || null;
|
|
182
203
|
}
|
|
183
204
|
function getWorkflowBoundary(liveDoc, options) {
|
|
@@ -215,7 +236,9 @@ function getJobRecords(liveDoc) {
|
|
|
215
236
|
if (!record) continue;
|
|
216
237
|
jobs.push({ ...record, jobId });
|
|
217
238
|
}
|
|
218
|
-
jobs.sort(
|
|
239
|
+
jobs.sort(
|
|
240
|
+
(left, right) => getJobTimestamp(right) - getJobTimestamp(left)
|
|
241
|
+
);
|
|
219
242
|
return jobs;
|
|
220
243
|
}
|
|
221
244
|
function getPromptRecordsFromJobs(liveDoc) {
|
|
@@ -223,7 +246,9 @@ function getPromptRecordsFromJobs(liveDoc) {
|
|
|
223
246
|
}
|
|
224
247
|
function projectWorkflowFocus(liveDoc, pendingPrompts = [], options) {
|
|
225
248
|
const boundary = getWorkflowBoundary(liveDoc, options);
|
|
226
|
-
const jobs = getJobRecords(liveDoc).filter(
|
|
249
|
+
const jobs = getJobRecords(liveDoc).filter(
|
|
250
|
+
(job) => getJobTimestamp(job) >= boundary.timestamp
|
|
251
|
+
).slice(0, 6).reverse();
|
|
227
252
|
const actionSummaryLines = [];
|
|
228
253
|
const variableNames = [];
|
|
229
254
|
const listNames = [];
|
|
@@ -275,7 +300,9 @@ function projectWorkflowFocus(liveDoc, pendingPrompts = [], options) {
|
|
|
275
300
|
return updatedAt >= boundary.timestamp;
|
|
276
301
|
}
|
|
277
302
|
return status !== "completed" && status !== "canceled" ? true : updatedAt >= boundary.timestamp;
|
|
278
|
-
}).sort(
|
|
303
|
+
}).sort(
|
|
304
|
+
(left, right) => (Number(right.updatedAt) || Number(right.createdAt) || 0) - (Number(left.updatedAt) || Number(left.createdAt) || 0)
|
|
305
|
+
);
|
|
279
306
|
for (const task of tasks.slice(0, 4)) {
|
|
280
307
|
if (typeof task.taskId === "string") {
|
|
281
308
|
activeTaskIds.push(task.taskId);
|
|
@@ -287,7 +314,9 @@ function projectWorkflowFocus(liveDoc, pendingPrompts = [], options) {
|
|
|
287
314
|
return updatedAt >= boundary.timestamp;
|
|
288
315
|
}
|
|
289
316
|
return decision.status === "open" || updatedAt >= boundary.timestamp;
|
|
290
|
-
}).sort(
|
|
317
|
+
}).sort(
|
|
318
|
+
(left, right) => (Number(right.updatedAt) || Number(right.createdAt) || 0) - (Number(left.updatedAt) || Number(left.createdAt) || 0)
|
|
319
|
+
);
|
|
291
320
|
for (const decision of decisions.slice(0, 3)) {
|
|
292
321
|
if (typeof decision.decisionId === "string" && decision.status === "open") {
|
|
293
322
|
openDecisionIds.push(decision.decisionId);
|
|
@@ -366,11 +395,15 @@ function projectWorkflowSummary(liveDoc, pendingPrompts = [], options) {
|
|
|
366
395
|
const lines = [];
|
|
367
396
|
lines.push("Workflow Boundary:");
|
|
368
397
|
if (focus.boundaryReason === "request_start") {
|
|
369
|
-
lines.push(
|
|
398
|
+
lines.push(
|
|
399
|
+
"- Start from work recorded after the current user request began."
|
|
400
|
+
);
|
|
370
401
|
} else if (focus.boundaryReason === "last_closed_loop" && focus.latestClosureId) {
|
|
371
402
|
lines.push(`- Start from work recorded after ${focus.latestClosureId}.`);
|
|
372
403
|
} else {
|
|
373
|
-
lines.push(
|
|
404
|
+
lines.push(
|
|
405
|
+
"- No prior closed loop recorded; use the latest user request as the boundary."
|
|
406
|
+
);
|
|
374
407
|
}
|
|
375
408
|
lines.push("", "Recent Actions:");
|
|
376
409
|
if (focus.recentActionSummary.length === 0) {
|
|
@@ -439,12 +472,17 @@ function projectLoopSummary(liveDoc, pendingPrompts = [], options) {
|
|
|
439
472
|
return updatedAt >= boundary.timestamp;
|
|
440
473
|
}
|
|
441
474
|
return status !== "completed" && status !== "canceled" ? true : updatedAt >= boundary.timestamp;
|
|
442
|
-
}).sort(
|
|
475
|
+
}).sort(
|
|
476
|
+
(left, right) => (Number(right.updatedAt) || 0) - (Number(left.updatedAt) || 0)
|
|
477
|
+
);
|
|
443
478
|
const activeTasks = tasks.filter((task) => {
|
|
444
479
|
const status = typeof task.status === "string" ? task.status : "pending";
|
|
445
480
|
return status !== "completed" && status !== "canceled";
|
|
446
481
|
});
|
|
447
|
-
const visibleTasks = (activeTasks.length > 0 ? activeTasks : tasks).slice(
|
|
482
|
+
const visibleTasks = (activeTasks.length > 0 ? activeTasks : tasks).slice(
|
|
483
|
+
0,
|
|
484
|
+
5
|
|
485
|
+
);
|
|
448
486
|
const hiddenTaskCount = Math.max(0, activeTasks.length - visibleTasks.length);
|
|
449
487
|
lines.push("Tasks:");
|
|
450
488
|
if (visibleTasks.length === 0) {
|
|
@@ -468,8 +506,12 @@ function projectLoopSummary(liveDoc, pendingPrompts = [], options) {
|
|
|
468
506
|
return updatedAt >= boundary.timestamp;
|
|
469
507
|
}
|
|
470
508
|
return decision.status === "open" || updatedAt >= boundary.timestamp;
|
|
471
|
-
}).sort(
|
|
472
|
-
|
|
509
|
+
}).sort(
|
|
510
|
+
(left, right) => (Number(right.updatedAt) || Number(right.createdAt) || 0) - (Number(left.updatedAt) || Number(left.createdAt) || 0)
|
|
511
|
+
);
|
|
512
|
+
const openDecisions = decisions.filter(
|
|
513
|
+
(decision) => decision.status === "open"
|
|
514
|
+
);
|
|
473
515
|
const visibleDecisions = (openDecisions.length > 0 ? openDecisions : decisions.slice(0, 1)).slice(0, 3);
|
|
474
516
|
lines.push("", "Recent Decisions:");
|
|
475
517
|
if (visibleDecisions.length === 0) {
|
|
@@ -488,7 +530,9 @@ function projectLoopSummary(liveDoc, pendingPrompts = [], options) {
|
|
|
488
530
|
const candidateLabel = typeof record.label === "string" && record.label.trim() ? record.label.trim() : candidateId;
|
|
489
531
|
return candidateLabel === candidateId ? candidateId : `${candidateLabel} (${candidateId})`;
|
|
490
532
|
}).filter((value) => Boolean(value)).join(", ");
|
|
491
|
-
lines.push(
|
|
533
|
+
lines.push(
|
|
534
|
+
`- [open] ${title} (${decisionId})${candidatePreview ? ` \u2014 candidates: ${candidatePreview}` : ""}`
|
|
535
|
+
);
|
|
492
536
|
} else {
|
|
493
537
|
const selected = asRecord(decision.selected);
|
|
494
538
|
const label = typeof selected?.label === "string" ? selected.label : typeof selected?.id === "string" ? selected.id : "unknown";
|
|
@@ -503,13 +547,17 @@ function projectLoopSummary(liveDoc, pendingPrompts = [], options) {
|
|
|
503
547
|
title: prompt.title,
|
|
504
548
|
message: prompt.message
|
|
505
549
|
})),
|
|
506
|
-
...Object.values(asRecord(asRecord(liveDoc?.jobs)?.byId) || {}).flatMap((job) => Object.values(asRecord(asRecord(job)?.prompts) || {})).map((prompt) => asRecord(prompt)).filter(
|
|
550
|
+
...Object.values(asRecord(asRecord(liveDoc?.jobs)?.byId) || {}).flatMap((job) => Object.values(asRecord(asRecord(job)?.prompts) || {})).map((prompt) => asRecord(prompt)).filter(
|
|
551
|
+
(prompt) => Boolean(prompt && prompt.status === "open")
|
|
552
|
+
)
|
|
507
553
|
];
|
|
508
554
|
const visiblePrompts = boundary.reason === "request_start" ? openPrompts.filter((prompt) => {
|
|
509
555
|
const promptRecord = asRecord(prompt);
|
|
510
556
|
const openedAt = Number(promptRecord?.openedAt) || 0;
|
|
511
557
|
const promptId = typeof promptRecord?.id === "string" ? promptRecord.id : typeof prompt.id === "string" ? prompt.id : null;
|
|
512
|
-
return openedAt >= boundary.timestamp || (promptId ? pendingPrompts.some(
|
|
558
|
+
return openedAt >= boundary.timestamp || (promptId ? pendingPrompts.some(
|
|
559
|
+
(pendingPrompt) => pendingPrompt.id === promptId
|
|
560
|
+
) : false);
|
|
513
561
|
}) : openPrompts;
|
|
514
562
|
lines.push("", "Open Prompts:");
|
|
515
563
|
if (visiblePrompts.length === 0) {
|
|
@@ -540,9 +588,15 @@ function projectHeapSummary(heap, options) {
|
|
|
540
588
|
const entriesByPath = asRecord(heapRecord.entriesByPath) || {};
|
|
541
589
|
const listsByName = asRecord(heapRecord.listsByName) || {};
|
|
542
590
|
const variablesByName = asRecord(heapRecord.variablesByName) || {};
|
|
543
|
-
const focusedVariableNames = new Set(
|
|
544
|
-
|
|
545
|
-
|
|
591
|
+
const focusedVariableNames = new Set(
|
|
592
|
+
uniqueStrings(options?.focus?.variableNames || [])
|
|
593
|
+
);
|
|
594
|
+
const focusedListNames = new Set(
|
|
595
|
+
uniqueStrings(options?.focus?.listNames || [])
|
|
596
|
+
);
|
|
597
|
+
const focusedEntryPaths = new Set(
|
|
598
|
+
uniqueStrings(options?.focus?.entryPaths || [])
|
|
599
|
+
);
|
|
546
600
|
const hasFocus = focusedVariableNames.size > 0 || focusedListNames.size > 0 || focusedEntryPaths.size > 0;
|
|
547
601
|
const suppressRecentFallback = Boolean(options?.focus) && !hasFocus;
|
|
548
602
|
const maxVariables = options?.maxVariables ?? (hasFocus ? 4 : 6);
|
|
@@ -554,20 +608,26 @@ function projectHeapSummary(heap, options) {
|
|
|
554
608
|
return rightFocused - leftFocused || (right.updatedAt || 0) - (left.updatedAt || 0);
|
|
555
609
|
}).filter((variable, index) => {
|
|
556
610
|
if (index < maxVariables) return true;
|
|
557
|
-
return Boolean(
|
|
611
|
+
return Boolean(
|
|
612
|
+
variable.name && focusedVariableNames.has(variable.name)
|
|
613
|
+
);
|
|
558
614
|
}).slice(0, maxVariables);
|
|
559
615
|
const referencedPaths = /* @__PURE__ */ new Set();
|
|
560
616
|
for (const variable of variables) {
|
|
561
617
|
if (variable.entryPath) referencedPaths.add(variable.entryPath);
|
|
562
618
|
if (variable.listName) {
|
|
563
|
-
const list = asRecord(
|
|
619
|
+
const list = asRecord(
|
|
620
|
+
listsByName[variable.listName]
|
|
621
|
+
);
|
|
564
622
|
for (const path of list?.paths || []) referencedPaths.add(path);
|
|
565
623
|
}
|
|
566
624
|
}
|
|
567
625
|
for (const path of focusedEntryPaths) {
|
|
568
626
|
referencedPaths.add(path);
|
|
569
627
|
}
|
|
570
|
-
const visibleLists = Object.values(listsByName).map((value) => asRecord(value)).filter((value) => Boolean(value)).filter(
|
|
628
|
+
const visibleLists = Object.values(listsByName).map((value) => asRecord(value)).filter((value) => Boolean(value)).filter(
|
|
629
|
+
(list) => variables.some((variable) => variable.listName === list.name) || Boolean(list.name && focusedListNames.has(list.name))
|
|
630
|
+
).sort((left, right) => (right.updatedAt || 0) - (left.updatedAt || 0)).slice(0, maxLists);
|
|
571
631
|
const visibleEntries = Object.values(entriesByPath).map((value) => asRecord(value)).filter((value) => Boolean(value)).filter((entry) => entry.path && referencedPaths.has(entry.path)).sort((left, right) => (right.updatedAt || 0) - (left.updatedAt || 0)).slice(0, maxEntries);
|
|
572
632
|
const lines = [];
|
|
573
633
|
lines.push("Variables:");
|
|
@@ -576,16 +636,24 @@ function projectHeapSummary(heap, options) {
|
|
|
576
636
|
} else {
|
|
577
637
|
for (const variable of variables) {
|
|
578
638
|
if (variable.kind === "scalar") {
|
|
579
|
-
lines.push(
|
|
639
|
+
lines.push(
|
|
640
|
+
`- ${variable.name}: scalar = ${formatScalar(variable.value)}`
|
|
641
|
+
);
|
|
580
642
|
continue;
|
|
581
643
|
}
|
|
582
644
|
if (variable.kind === "entry") {
|
|
583
|
-
const entry = variable.entryPath ? asRecord(
|
|
584
|
-
|
|
645
|
+
const entry = variable.entryPath ? asRecord(
|
|
646
|
+
entriesByPath[variable.entryPath]
|
|
647
|
+
) : null;
|
|
648
|
+
lines.push(
|
|
649
|
+
`- ${variable.name}: entry<${variable.className || entry?.className || "unknown"}> -> ${entry ? describeHeapEntry(entry) : variable.entryPath || "missing"}`
|
|
650
|
+
);
|
|
585
651
|
continue;
|
|
586
652
|
}
|
|
587
653
|
const list = variable.listName ? asRecord(listsByName[variable.listName]) : null;
|
|
588
|
-
lines.push(
|
|
654
|
+
lines.push(
|
|
655
|
+
`- ${variable.name}: list<${variable.className || list?.className || "unknown"}> -> ${(list?.paths || []).length} item(s)`
|
|
656
|
+
);
|
|
589
657
|
}
|
|
590
658
|
}
|
|
591
659
|
lines.push("", "Named Lists:");
|
|
@@ -593,7 +661,9 @@ function projectHeapSummary(heap, options) {
|
|
|
593
661
|
lines.push("- none");
|
|
594
662
|
} else {
|
|
595
663
|
for (const list of visibleLists) {
|
|
596
|
-
lines.push(
|
|
664
|
+
lines.push(
|
|
665
|
+
`- ${list.name}: ${list.className || "unknown"}[${(list.paths || []).length}]`
|
|
666
|
+
);
|
|
597
667
|
}
|
|
598
668
|
}
|
|
599
669
|
lines.push("", "Active Entries:");
|
|
@@ -607,11 +677,19 @@ function projectHeapSummary(heap, options) {
|
|
|
607
677
|
return lines.join("\n");
|
|
608
678
|
}
|
|
609
679
|
function createHarnessVerifierSnapshot(input) {
|
|
610
|
-
const workflowFocus = projectWorkflowFocus(
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
680
|
+
const workflowFocus = projectWorkflowFocus(
|
|
681
|
+
input.liveDoc,
|
|
682
|
+
[],
|
|
683
|
+
input.projectionOptions
|
|
684
|
+
);
|
|
685
|
+
const heapDigest = hashString(
|
|
686
|
+
projectHeapSummary(asRecord(input.liveDoc?.heap), {
|
|
687
|
+
focus: workflowFocus
|
|
688
|
+
})
|
|
689
|
+
) || "00000000";
|
|
690
|
+
const loopDigest = hashString(
|
|
691
|
+
projectWorkflowSummary(input.liveDoc, [], input.projectionOptions)
|
|
692
|
+
) || "00000000";
|
|
615
693
|
return {
|
|
616
694
|
codeDigest: hashString(input.finalCode?.trim()),
|
|
617
695
|
resultDigest: hashString(input.resultPreview?.trim()),
|
|
@@ -741,8 +819,12 @@ function buildGranularAgentToolBlock(tools) {
|
|
|
741
819
|
return "No live effects are available in this session yet.";
|
|
742
820
|
}
|
|
743
821
|
const globalTools = normalizedTools.filter((tool) => !tool.className);
|
|
744
|
-
const staticTools = normalizedTools.filter(
|
|
745
|
-
|
|
822
|
+
const staticTools = normalizedTools.filter(
|
|
823
|
+
(tool) => Boolean(tool.className && tool.static)
|
|
824
|
+
);
|
|
825
|
+
const instanceTools = normalizedTools.filter(
|
|
826
|
+
(tool) => Boolean(tool.className && !tool.static)
|
|
827
|
+
);
|
|
746
828
|
const lines = [
|
|
747
829
|
"Treat this block as the planning map. Use DOMAIN TYPES below for exact signatures."
|
|
748
830
|
];
|
|
@@ -957,7 +1039,10 @@ ${loopBlock}
|
|
|
957
1039
|
- Avoid \`as any\` and other broad casts when the DOMAIN TYPES block already tells you the correct class or list type.
|
|
958
1040
|
- Prefer manipulating heap-backed instances and typed lists instead of returning raw JSON blobs or object IDs unless the user explicitly asks for them.
|
|
959
1041
|
- If the user expects an answer after the job runs, the final \`return\` value must be either a short natural-language string or an object with a top-level \`reply\` string.
|
|
1042
|
+
- You may call \`agent_message(...)\` multiple times in one job to post several assistant messages while the job is still running.
|
|
960
1043
|
- Prefer \`agent_message({ reply, show })\` when you want to leave a user-facing answer and optionally show heap-backed records in the UI.
|
|
1044
|
+
- \`agent_message(...)\` also accepts \`content\`, \`message\`, or \`text\` instead of \`reply\`.
|
|
1045
|
+
- \`agent_message({ show })\` may receive explicit refs or sandbox instances and arrays of sandbox instances. The runtime will convert those into UI references.
|
|
961
1046
|
- When it helps the UI show specific heap-backed results, you may instead return:
|
|
962
1047
|
\`{ reply: string, show: { entryPaths?: string[], listNames?: string[], variableNames?: string[] } }\`
|
|
963
1048
|
- If you create or load objects the user should see, save them in the heap and return references to them through \`show\` instead of serializing full objects.
|