@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.
@@ -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") return String(value);
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((field) => field?.name && field.name !== "_realId" && field.name !== "real_id").slice(0, previewFieldLimit).map((field) => `${field.name}=${formatScalar(field.value)}`).join(", ");
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(/await\s+([A-Za-z0-9_$.]+)\.([A-Za-z0-9_]+)\s*\(/g);
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(normalized.indexOf("await loop.confirm("));
86
- const hasPlaceholder = placeholderPatterns.some((pattern) => pattern.test(postConfirm));
87
- const hasSubstantiveAwait = hasSubstantiveAwaitAfterPrompt(normalized, "await loop.confirm(");
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(normalized.indexOf("await loop.ask_user("));
98
- const hasPlaceholder = placeholderPatterns.some((pattern) => pattern.test(postPrompt));
99
- const hasSubstantiveAwait = hasSubstantiveAwaitAfterPrompt(normalized, "await loop.ask_user(");
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((left, right) => (Number(right.createdAt) || 0) - (Number(left.createdAt) || 0));
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((left, right) => getJobTimestamp(right) - getJobTimestamp(left));
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((job) => getJobTimestamp(job) >= boundary.timestamp).slice(0, 6).reverse();
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((left, right) => (Number(right.updatedAt) || Number(right.createdAt) || 0) - (Number(left.updatedAt) || Number(left.createdAt) || 0));
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((left, right) => (Number(right.updatedAt) || Number(right.createdAt) || 0) - (Number(left.updatedAt) || Number(left.createdAt) || 0));
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("- Start from work recorded after the current user request began.");
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("- No prior closed loop recorded; use the latest user request as the boundary.");
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((left, right) => (Number(right.updatedAt) || 0) - (Number(left.updatedAt) || 0));
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(0, 5);
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((left, right) => (Number(right.updatedAt) || Number(right.createdAt) || 0) - (Number(left.updatedAt) || Number(left.createdAt) || 0));
472
- const openDecisions = decisions.filter((decision) => decision.status === "open");
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(`- [open] ${title} (${decisionId})${candidatePreview ? ` \u2014 candidates: ${candidatePreview}` : ""}`);
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((prompt) => Boolean(prompt && prompt.status === "open"))
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((pendingPrompt) => pendingPrompt.id === promptId) : false);
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(uniqueStrings(options?.focus?.variableNames || []));
544
- const focusedListNames = new Set(uniqueStrings(options?.focus?.listNames || []));
545
- const focusedEntryPaths = new Set(uniqueStrings(options?.focus?.entryPaths || []));
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(variable.name && focusedVariableNames.has(variable.name));
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(listsByName[variable.listName]);
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((list) => variables.some((variable) => variable.listName === list.name) || Boolean(list.name && focusedListNames.has(list.name))).sort((left, right) => (right.updatedAt || 0) - (left.updatedAt || 0)).slice(0, maxLists);
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(`- ${variable.name}: scalar = ${formatScalar(variable.value)}`);
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(entriesByPath[variable.entryPath]) : null;
584
- lines.push(`- ${variable.name}: entry<${variable.className || entry?.className || "unknown"}> -> ${entry ? describeHeapEntry(entry) : variable.entryPath || "missing"}`);
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(`- ${variable.name}: list<${variable.className || list?.className || "unknown"}> -> ${(list?.paths || []).length} item(s)`);
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(`- ${list.name}: ${list.className || "unknown"}[${(list.paths || []).length}]`);
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(input.liveDoc, [], input.projectionOptions);
611
- const heapDigest = hashString(projectHeapSummary(asRecord(input.liveDoc?.heap), {
612
- focus: workflowFocus
613
- })) || "00000000";
614
- const loopDigest = hashString(projectWorkflowSummary(input.liveDoc, [], input.projectionOptions)) || "00000000";
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((tool) => Boolean(tool.className && tool.static));
745
- const instanceTools = normalizedTools.filter((tool) => Boolean(tool.className && !tool.static));
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.