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