@granular-software/sdk 0.4.29 → 0.4.31

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.
@@ -123,21 +123,6 @@ function reviewGeneratedJobCode(code) {
123
123
  message: "After await loop.ask_user(...) returns a usable answer, continue the workflow in the same resumed run instead of stopping with placeholder text about doing the work later."
124
124
  });
125
125
  }
126
- const askUserCalls = normalized.match(/await\s+loop\.ask_user\s*\(\s*\{[\s\S]*?\}\s*\)/g) || [];
127
- for (const call of askUserCalls) {
128
- const usesChoiceType = /type\s*:\s*['"]choice['"]/.test(call);
129
- const usesInputType = /type\s*:\s*['"]input['"]/.test(call);
130
- const hasDisambiguationLanguage = /(which|choose|pick|select)/i.test(call) && /(invoice|order|shipment|request|case|work[\s_-]?order)/i.test(call);
131
- const includesShortlistOptions = /options\s*:\s*\[/.test(call);
132
- if (!usesChoiceType && (usesInputType || hasDisambiguationLanguage || includesShortlistOptions)) {
133
- issues.push({
134
- code: "disambiguation_requires_choice",
135
- severity: "error",
136
- message: "When asking the user to choose between known concrete records such as invoices, orders, shipments, requests, cases, or work orders, use loop.ask_user({ type: 'choice', options: [...] }) with a short explicit shortlist instead of a free-text input."
137
- });
138
- break;
139
- }
140
- }
141
126
  }
142
127
  const hasConversationalReturn = /return\s+[`'"]/.test(normalized) || /\breply\s*:/.test(normalized) || /\bagent_message\s*\(/.test(normalized) || /\bagent_text_message\s*\(/.test(normalized);
143
128
  const returnsObjectLiteral = /return\s+\{[\s\S]*?\}/.test(normalized);
@@ -183,6 +168,184 @@ function extractFocusHintsFromActionSummary(actionSummaryLines) {
183
168
  entryPaths: uniqueStrings(entryPaths, 8)
184
169
  };
185
170
  }
171
+ function normalizeActionSummaryForPrompt(line) {
172
+ return line.replace(/\blimit=/g, "perPage=").replace(/\blimit:/g, "perPage:");
173
+ }
174
+ function collectConversationReferents(liveDoc) {
175
+ const conversation = asRecord(liveDoc?.conversation);
176
+ const persistedReferents = asArray(conversation?.referents).map((value) => asRecord(value)).filter((value) => Boolean(value));
177
+ if (persistedReferents.length > 0) {
178
+ return persistedReferents.slice().sort((left, right) => (right.ts || 0) - (left.ts || 0));
179
+ }
180
+ const heap = asRecord(liveDoc?.heap);
181
+ const entriesByPath = asRecord(heap?.entriesByPath) || {};
182
+ const listsByName = asRecord(heap?.listsByName) || {};
183
+ const variablesByName = asRecord(heap?.variablesByName) || {};
184
+ const messages = asArray(conversation?.messages).map((value) => asRecord(value)).filter((value) => Boolean(value)).slice().sort((left, right) => (Number(right.ts) || 0) - (Number(left.ts) || 0));
185
+ const referents = [];
186
+ const seen = /* @__PURE__ */ new Set();
187
+ const pushReferent = (referent) => {
188
+ if (!referent?.kind || !referent.ref) return;
189
+ const key = `${referent.kind}:${referent.ref}`;
190
+ if (seen.has(key)) return;
191
+ seen.add(key);
192
+ referents.push(referent);
193
+ };
194
+ for (const message of messages) {
195
+ if (message.role !== "assistant") continue;
196
+ const show = asRecord(message.show);
197
+ if (!show) continue;
198
+ const ts = Number(message.ts) || 0;
199
+ const messageId = typeof message.id === "string" ? message.id : void 0;
200
+ const jobId = typeof message.jobId === "string" ? message.jobId : void 0;
201
+ for (const entryPath of uniqueStrings(asArray(show.entryPaths))) {
202
+ const entry = asRecord(entriesByPath[entryPath]);
203
+ pushReferent({
204
+ id: `entry:${entryPath}`,
205
+ kind: "entry",
206
+ ref: entryPath,
207
+ entryPath,
208
+ className: typeof entry?.className === "string" ? entry.className : void 0,
209
+ label: typeof entry?.label === "string" && entry.label.trim() ? entry.label.trim() : typeof entry?.id === "string" && entry.id.trim() ? entry.id.trim() : entryPath,
210
+ messageId,
211
+ jobId,
212
+ ts
213
+ });
214
+ }
215
+ for (const listName of uniqueStrings(asArray(show.listNames))) {
216
+ const list = asRecord(listsByName[listName]);
217
+ pushReferent({
218
+ id: `list:${listName}`,
219
+ kind: "list",
220
+ ref: listName,
221
+ listName,
222
+ className: typeof list?.className === "string" ? list.className : void 0,
223
+ count: Array.isArray(list?.paths) ? list.paths.length : null,
224
+ messageId,
225
+ jobId,
226
+ ts
227
+ });
228
+ }
229
+ for (const variableName of uniqueStrings(
230
+ asArray(show.variableNames)
231
+ )) {
232
+ const variable = asRecord(variablesByName[variableName]);
233
+ const entryPath = typeof variable?.entryPath === "string" ? variable.entryPath : void 0;
234
+ const listName = typeof variable?.listName === "string" ? variable.listName : void 0;
235
+ const entry = entryPath ? asRecord(entriesByPath[entryPath]) : null;
236
+ const list = listName ? asRecord(listsByName[listName]) : null;
237
+ pushReferent({
238
+ id: `variable:${variableName}`,
239
+ kind: "variable",
240
+ ref: variableName,
241
+ variableName,
242
+ variableKind: typeof variable?.kind === "string" ? variable.kind : void 0,
243
+ entryPath,
244
+ listName,
245
+ className: typeof variable?.className === "string" ? variable.className : typeof entry?.className === "string" ? entry.className : typeof list?.className === "string" ? list.className : void 0,
246
+ label: typeof entry?.label === "string" && entry.label.trim() ? entry.label.trim() : typeof entry?.id === "string" && entry.id.trim() ? entry.id.trim() : null,
247
+ count: variable?.kind === "list" && Array.isArray(list?.paths) ? list.paths.length : null,
248
+ scalarValue: variable?.kind === "scalar" && (typeof variable.value === "string" || typeof variable.value === "number" || typeof variable.value === "boolean" || variable.value === null) ? variable.value : void 0,
249
+ messageId,
250
+ jobId,
251
+ ts
252
+ });
253
+ }
254
+ }
255
+ return referents;
256
+ }
257
+ function projectConversationReferentFocus(liveDoc) {
258
+ const heap = asRecord(liveDoc?.heap);
259
+ const listsByName = asRecord(heap?.listsByName) || {};
260
+ const referents = collectConversationReferents(liveDoc);
261
+ const entryPaths = [];
262
+ const listNames = [];
263
+ const variableNames = [];
264
+ for (const referent of referents.slice(0, 8)) {
265
+ if (referent.kind === "entry" && typeof referent.entryPath === "string") {
266
+ entryPaths.push(referent.entryPath);
267
+ continue;
268
+ }
269
+ if (referent.kind === "list" && typeof referent.listName === "string") {
270
+ listNames.push(referent.listName);
271
+ const list = asRecord(listsByName[referent.listName]);
272
+ entryPaths.push(...asArray(list?.paths).slice(0, 4));
273
+ continue;
274
+ }
275
+ if (referent.kind === "variable" && typeof referent.variableName === "string") {
276
+ variableNames.push(referent.variableName);
277
+ if (typeof referent.entryPath === "string") {
278
+ entryPaths.push(referent.entryPath);
279
+ }
280
+ if (typeof referent.listName === "string") {
281
+ listNames.push(referent.listName);
282
+ const list = asRecord(listsByName[referent.listName]);
283
+ entryPaths.push(...asArray(list?.paths).slice(0, 4));
284
+ }
285
+ }
286
+ }
287
+ return {
288
+ entryPaths: uniqueStrings(entryPaths, 8),
289
+ listNames: uniqueStrings(listNames, 4),
290
+ variableNames: uniqueStrings(variableNames, 4)
291
+ };
292
+ }
293
+ function projectConversationReferentSummary(liveDoc) {
294
+ const referents = collectConversationReferents(liveDoc).slice(0, 8);
295
+ if (referents.length === 0) {
296
+ return "No recent referents recorded from prior assistant replies.";
297
+ }
298
+ const entryLines = [];
299
+ const listLines = [];
300
+ const variableLines = [];
301
+ for (const referent of referents) {
302
+ if (referent.kind === "entry" && referent.entryPath) {
303
+ const label = referent.label || referent.entryPath;
304
+ const classLabel = referent.className || "unknown";
305
+ entryLines.push(`- ${label} <${referent.entryPath}> [${classLabel}]`);
306
+ continue;
307
+ }
308
+ if (referent.kind === "list" && referent.listName) {
309
+ const classLabel = referent.className || "unknown";
310
+ const countLabel = typeof referent.count === "number" ? referent.count : "?";
311
+ listLines.push(
312
+ `- ${referent.listName}: list<${classLabel}> -> ${countLabel} item(s)`
313
+ );
314
+ continue;
315
+ }
316
+ if (referent.kind === "variable" && referent.variableName) {
317
+ if (referent.variableKind === "entry" && referent.entryPath && referent.className) {
318
+ const label = referent.label || referent.entryPath;
319
+ variableLines.push(
320
+ `- ${referent.variableName}: entry<${referent.className}> -> ${label} <${referent.entryPath}>`
321
+ );
322
+ continue;
323
+ }
324
+ if (referent.variableKind === "list" && referent.listName && referent.className) {
325
+ const countLabel = typeof referent.count === "number" ? referent.count : "?";
326
+ variableLines.push(
327
+ `- ${referent.variableName}: list<${referent.className}> -> ${countLabel} item(s) via ${referent.listName}`
328
+ );
329
+ continue;
330
+ }
331
+ if (referent.variableKind === "scalar") {
332
+ variableLines.push(
333
+ `- ${referent.variableName}: scalar = ${formatScalar(referent.scalarValue)}`
334
+ );
335
+ continue;
336
+ }
337
+ variableLines.push(`- ${referent.variableName}`);
338
+ }
339
+ }
340
+ const lines = [];
341
+ lines.push("Entries:");
342
+ lines.push(...entryLines.length > 0 ? entryLines : ["- none"]);
343
+ lines.push("", "Lists:");
344
+ lines.push(...listLines.length > 0 ? listLines : ["- none"]);
345
+ lines.push("", "Variables:");
346
+ lines.push(...variableLines.length > 0 ? variableLines : ["- none"]);
347
+ return lines.join("\n");
348
+ }
186
349
  function getCurrentClosureId(liveDoc) {
187
350
  const loop = asRecord(liveDoc?.loop);
188
351
  return typeof loop?.currentClosureId === "string" ? loop.currentClosureId : null;
@@ -395,7 +558,9 @@ function projectWorkflowFocus(liveDoc, pendingPrompts = [], options) {
395
558
  variableNames: uniqueStrings(variableNames, 4),
396
559
  listNames: uniqueStrings(listNames, 4),
397
560
  entryPaths: uniqueStrings(entryPaths, 6),
398
- recentActionSummary: uniqueStrings(actionSummaryLines, 8)
561
+ recentActionSummary: uniqueStrings(actionSummaryLines, 8).map(
562
+ normalizeActionSummaryForPrompt
563
+ )
399
564
  };
400
565
  }
401
566
  function projectWorkflowSummary(liveDoc, pendingPrompts = [], options) {
@@ -792,17 +957,14 @@ ${resultPreview}` : null
792
957
  ].filter(Boolean).join("\n\n");
793
958
  }
794
959
  function buildGranularAgentDomainBlock(domainDocumentation) {
795
- return domainDocumentation?.trim() || "No domain types available. The graph may not be ready yet.";
960
+ return domainDocumentation?.trim() || "No domain reference available. The graph may not be ready yet.";
796
961
  }
797
962
  function buildGranularAgentSessionBlock(sessionContext) {
798
963
  if (!sessionContext) return "No session metadata available.";
799
964
  const rows = [
800
965
  ["sandboxId", sessionContext.sandboxId],
801
966
  ["environmentId", sessionContext.environmentId],
802
- ["userId", sessionContext.userId],
803
- ["granularId", sessionContext.granularId],
804
- ["userName", sessionContext.userName],
805
- ["domainRevision", sessionContext.domainRevision]
967
+ ["userName", sessionContext.userName]
806
968
  ];
807
969
  const activeRows = rows.filter(([, value]) => Boolean(value));
808
970
  if (activeRows.length === 0) return "No session metadata available.";
@@ -811,6 +973,9 @@ function buildGranularAgentSessionBlock(sessionContext) {
811
973
  function buildGranularAgentHeapBlock(heapSummary) {
812
974
  return heapSummary?.trim() || "Heap is empty for this session.";
813
975
  }
976
+ function buildGranularAgentReferentBlock(referentSummary) {
977
+ return referentSummary?.trim() || "No recent referents recorded from prior assistant replies.";
978
+ }
814
979
  function buildGranularAgentLoopBlock(loopSummary) {
815
980
  return loopSummary?.trim() || "No active loop state recorded for this session.";
816
981
  }
@@ -834,7 +999,7 @@ function buildGranularAgentToolBlock(tools) {
834
999
  (tool) => Boolean(tool.className && !tool.static)
835
1000
  );
836
1001
  const lines = [
837
- "Treat this block as the planning map. Use DOMAIN TYPES below for exact signatures."
1002
+ "Treat this block as the planning map. Use DOMAIN REFERENCE below for exact signatures and query examples."
838
1003
  ];
839
1004
  const appendGroup = (title, group) => {
840
1005
  lines.push(`- ${title}:`);
@@ -882,7 +1047,10 @@ function buildGranularAgentCheckpointBlock(checkpoint) {
882
1047
  if (Array.isArray(checkpoint.latestActionSummary) && checkpoint.latestActionSummary.length > 0) {
883
1048
  lines.push("latestActionSummary:");
884
1049
  for (const line of checkpoint.latestActionSummary.slice(0, 8)) {
885
- lines.push(line.startsWith("- ") ? line : `- ${line}`);
1050
+ const normalizedLine = normalizeActionSummaryForPrompt(line);
1051
+ lines.push(
1052
+ normalizedLine.startsWith("- ") ? normalizedLine : `- ${normalizedLine}`
1053
+ );
886
1054
  }
887
1055
  }
888
1056
  if (checkpoint.latestJobResult?.trim()) {
@@ -898,9 +1066,10 @@ function buildGranularAgentSystemPrompt(input) {
898
1066
  const workflowBlock = buildGranularAgentWorkflowBlock(input.workflowSummary);
899
1067
  const checkpointBlock = buildGranularAgentCheckpointBlock(input.checkpoint);
900
1068
  const heapBlock = buildGranularAgentHeapBlock(input.heapSummary);
1069
+ const referentBlock = buildGranularAgentReferentBlock(input.referentSummary);
901
1070
  const loopBlock = buildGranularAgentLoopBlock(input.loopSummary);
902
1071
  return `You are an AI assistant for a live Granular session.
903
- You can help the user understand the domain, answer questions, or generate and execute TypeScript code.
1072
+ You can help the user understand the domain, answer questions, or generate and execute code against the live session.
904
1073
  Your tone must be natural and human-like.
905
1074
 
906
1075
  Call the \`execute_code\` effect ONLY when the user's intent matches the domain's capabilities and requires executing code against the live session. If the user is just asking a general question or if their request doesn't match the available effects or domain types, respond with text to explain.
@@ -909,6 +1078,8 @@ When you call \`execute_code\`, additional assistant text must be either:
909
1078
  - a brief summary of the actions the generated code will perform.
910
1079
  Do not include any other kind of commentary when calling \`execute_code\`.
911
1080
  - If the next step needs to create or update workflow state in the live session, you must call \`execute_code\`. This includes \`loop.ask_user(...)\`, \`loop.confirm(...)\`, \`loop.open_decision(...)\`, \`loop.close_decision(...)\`, \`loop.create_task(...)\`, \`loop.update_task(...)\`, \`loop.complete_task(...)\`, and \`loop.close_loop(...)\`.
1081
+ - If the next step is an interactive clarification that should be resumable in the live workflow, you must call \`execute_code\`. A missing preference, rule, metric, target, or option selection is not a plain-text reply when the answer should drive the next live step.
1082
+ - If you can offer a short grounded shortlist, that clarification should usually be \`loop.ask_user({ type: 'choice', ... })\` instead of a plain-text question with bullet options.
912
1083
  - Never simulate a live prompt, confirmation, decision, task change, or loop closure in plain text. Plain-text replies are only for conversational answers that do not need to mutate session state.
913
1084
 
914
1085
  \u2500\u2500\u2500 STREAMING COMMENT RULES \u2500\u2500\u2500
@@ -928,6 +1099,7 @@ Do not include any other kind of commentary when calling \`execute_code\`.
928
1099
  - Do not say "sandbox" in user-facing text unless the user is explicitly asking about the runtime environment itself.
929
1100
  - If you need clarification, ask in everyday language.
930
1101
  - If the missing information should pause the live workflow for later continuation, ask through \`loop.ask_user(...)\` in generated code rather than with a plain-text question.
1102
+ - If you are asking the user to pick from explicit options, prefer a live \`loop.ask_user({ type: 'choice', ... })\` prompt over a direct reply that lists those options in text.
931
1103
  - Keep replies concise and clear.
932
1104
  - This is a conversation UI, not an API console. Favor human answers over machine-shaped payloads.
933
1105
 
@@ -937,9 +1109,9 @@ ${sessionBlock}
937
1109
  \u2500\u2500\u2500 CAPABILITY SNAPSHOT \u2500\u2500\u2500
938
1110
  ${toolBlock}
939
1111
 
940
- \u2500\u2500\u2500 DOMAIN TYPES (TypeScript declarations from ./sandbox-tools) \u2500\u2500\u2500
1112
+ \u2500\u2500\u2500 DOMAIN REFERENCE (from ./sandbox-tools) \u2500\u2500\u2500
941
1113
  Import classes and effect functions from \`./sandbox-tools\` in generated code.
942
- Published effects appear as instance or static methods on the classes below, or as top-level \`export declare function\` entries for global effects.
1114
+ Use the TypeScript declarations for exact signatures. When present, the generated usage notes below them show query patterns and examples.
943
1115
 
944
1116
  ${domainBlock}
945
1117
 
@@ -949,6 +1121,9 @@ ${checkpointBlock}
949
1121
  \u2500\u2500\u2500 WORKFLOW SNAPSHOT \u2500\u2500\u2500
950
1122
  ${workflowBlock}
951
1123
 
1124
+ \u2500\u2500\u2500 RECENT REFERENTS \u2500\u2500\u2500
1125
+ ${referentBlock}
1126
+
952
1127
  \u2500\u2500\u2500 SESSION HEAP \u2500\u2500\u2500
953
1128
  ${heapBlock}
954
1129
 
@@ -956,109 +1131,54 @@ ${heapBlock}
956
1131
  ${loopBlock}
957
1132
 
958
1133
  \u2500\u2500\u2500 LOOP PLAYBOOK \u2500\u2500\u2500
959
- - A single user request may span several assistant turns and several jobs. Continue from the latest structured session state instead of restarting.
960
- - Treat WORKFLOW SNAPSHOT, EXECUTION CHECKPOINT, SESSION HEAP, and AGENT LOOP STATE as the authoritative working memory for the current request.
961
- - Use CAPABILITY SNAPSHOT to choose the next step quickly, then use DOMAIN TYPES to write exact valid code.
962
- - Use WORKFLOW SNAPSHOT to understand the current boundary, recent actions, and working set before fetching more data.
963
- - If the user names a concrete customer, case, order, shipment, or other record that is not already in the heap, fetch it from the graph. "Not in the current context" is not a sufficient reason to stop.
964
- - Treat user-provided names as human references, not exact database keys. If the user says "Northwind", "the Alpine compressor case", or another shorthand, prefer sensible case-insensitive partial matching across likely records before concluding that nothing matches.
965
- - If exactly one strong partial-name match exists, use it. If several plausible partial matches exist, ask the user to choose instead of failing on an exact-equality lookup.
966
- - Take the minimum next step that directly advances the user's request. Do not do speculative cleanup, enrichment, or bookkeeping.
967
- - Reuse exact \`taskId\`, \`decisionId\`, and \`closureId\` values from the AGENT LOOP STATE block. Never guess or slugify IDs.
968
- - If the request is ambiguous or clearly multi-step, start by creating 2-4 meaningful user-visible tasks. Do not create a detailed internal checklist.
969
- - Keep tasks updated as the workflow advances. Complete tasks as soon as they are actually done.
970
- - Write the smallest straightforward code that fits the current step. Avoid defensive branches for hypothetical states that are not currently true.
971
- - Before asking a new question, check whether the answer is already present in the current heap, open decisions, or checkpoint.
972
- - If the previous step made no progress, prefer a different concrete action, a narrower fetch, or a user question instead of repeating equivalent code.
973
- - Use \`loop.open_decision(...)\` in one job to store grounded candidates, then \`loop.close_decision(...)\` in a later job to pick one stored candidate with \`selectedId\`.
974
- - Use \`loop.ask_user({ type: 'input', ... })\` only for open-ended preferences or missing free-form text that cannot be represented as a short explicit shortlist.
975
- - If you already have a short concrete shortlist, usually 2-7 candidates, generate code and call \`loop.ask_user({ type: 'choice', ... })\`. Do not downgrade that to a text input.
976
- - If multiple concrete records match a singular user reference such as "the invoice", "the order", "the shipment", or "the request", do not silently choose one by heuristic. Ask the user to choose unless the request already uniquely identifies the record.
977
- - For disambiguation between concrete known records, prefer \`type: 'choice'\` over \`type: 'input'\`. This is especially important for invoices, orders, shipments, requests, work orders, and cases.
978
- - For \`type: 'choice'\` prompts, make the options directly pickable by a human: use a stable value and a readable label that includes the identifier or title they are likely to recognize.
979
- - If a shortlist already exists, do not ask the user to type an exact database key or identifier manually. Present the shortlist as clickable choices instead.
980
- - When reasoning about free-form status strings, do not use brittle substring checks such as \`status.includes("paid")\` because values like \`"unpaid"\` would be misclassified. Prefer explicit positive matches such as \`unpaid\`, \`open\`, or \`overdue\`, or exact normalized comparisons.
981
- - When multiple concrete records match and \`loop.ask_user(...)\` is available, do not stop with a plain-text question like "Which invoice do you mean?". Persist the live workflow and ask through \`await loop.ask_user(...)\` instead.
982
- - If the user could reasonably answer with a partial identifier such as \`abcd\` for \`INV-abcd\`, that is another sign the question should be a \`type: 'choice'\` prompt with visible options rather than a free-text input.
983
- - Never ask for approval in plain text when \`loop.confirm(...)\` is available. Use \`loop.confirm(...)\` for consequential approval.
984
- - When the correct next step is a loop helper action, generate code and call that helper. Do not replace it with a conversational reply.
985
- - If you ask the user a new question in the current job, do not also call \`loop.close_loop(...)\` in that same job.
986
- - When you need user input or approval, await \`loop.ask_user(...)\` or \`loop.confirm(...)\`. The job will pause until the user answers, then resume from that awaited call.
987
- - It is valid to branch on the value returned by \`await loop.ask_user(...)\` or \`await loop.confirm(...)\` after the job resumes.
988
- - After \`await loop.ask_user(...)\` returns a concrete choice, continue the workflow in the same resumed job whenever that answer is enough to act. Do not stop with placeholder text such as "I can do that next" or "I'm ready to continue".
989
- - After \`await loop.confirm(...)\` returns \`true\`, execute the approved mutation in that same resumed job before returning. Do not end with placeholder text like "Approved, ready to make the change next."
990
- - Only stop immediately after a resumed prompt when the user declined, the workflow is now blocked, or you truly still need another missing piece of information.
991
- - If the user says stop, enough, or no further action, close the loop and end cleanly without asking another question.
992
- - If one clear item is already selected and the next step matters, prefer \`loop.confirm(...)\` over another exploratory question.
993
- - If one clear item is already selected and the only missing input is approval to proceed, use \`loop.confirm(...)\` rather than \`loop.ask_user(...)\`.
994
- - If the user already gave a usable scheduling window such as "Tuesday morning", treat that as enough to choose a reasonable concrete slot. Do not open another menu just to choose between nearby sub-slots unless a real conflict or hard business rule forces that follow-up.
995
- - For schedule changes, prefer one grounded recommendation plus one approval prompt. Avoid a second prompt for optional time-window micro-choices when you can pick a sensible default that still satisfies the user request.
996
- - If the user explicitly instructs you to perform a consequential action now, that instruction counts as approval. Do not add an extra confirmation step unless the user expressed hesitation, ambiguity, or asked you not to execute yet.
997
- - Direct imperatives such as "cancel this order", "send the reminder now", "approve this refund", or "charge it now" already authorize that exact step. Execute them directly instead of inserting \`loop.confirm(...)\`.
998
- - If the user says not to do anything irreversible yet, stop at recommendation, review, or approval. Do not collect checkout-only details like quantity, delivery notes, gift message, or optional preferences unless the user explicitly asks to move closer to purchase.
999
- - Once you have one solid recommendation, prefer summarizing it and asking for approval over gathering more optional preferences.
1000
- - Prefer asking the user for the next missing input over fetching extra related data they did not ask for yet.
1001
- - Avoid serial menus. After one clarifying choice, prefer acting on it, asking one short text question, or confirming rather than opening another menu.
1002
- - When the user asks for a summary "including" concrete records such as unpaid invoices, open cases, orders, or shipments, include the actual identifiers or titles of those records in the reply, not just aggregate counts.
1003
- - Call \`loop.close_loop(...)\` before stopping whenever the current workflow is completed, canceled, or clearly blocked.
1134
+ - Continue from the latest structured state. Treat WORKFLOW SNAPSHOT, EXECUTION CHECKPOINT, RECENT REFERENTS, SESSION HEAP, and AGENT LOOP STATE as the working memory for this request.
1135
+ - Use CAPABILITY SNAPSHOT to choose the next step, then use DOMAIN REFERENCE for exact signatures and query shapes.
1136
+ - Take the minimum next step that directly helps the user. Avoid duplicate work, speculative cleanup, or extra fetching that is not needed yet.
1137
+ - If the user names a record that is not already in the heap, fetch it from the graph instead of saying it is not in context.
1138
+ - Treat user-provided names as human references, not exact keys. If one strong partial match exists, use it. If several plausible matches exist, ask the user to choose.
1139
+ - Use RECENT REFERENTS to resolve follow-up references across turns, such as "that invoice", "that customer", "those products", or "the other one".
1140
+ - If the request has more than one reasonable interpretation, ask the user to clarify instead of guessing.
1141
+ - For comparisons, rankings, selections, or summaries, first identify the rule you are using. If that rule is not clear from the user request and DOMAIN REFERENCE, ask the user before choosing anything.
1142
+ - When the ranking, comparison, or selection rule is unclear, the minimum next step is the clarification itself. Do not run a placeholder query for a provisional winner before asking.
1143
+ - Reuse exact \`taskId\`, \`decisionId\`, and \`closureId\` values from AGENT LOOP STATE. Never invent or rewrite them.
1144
+ - If the request is ambiguous or clearly multi-step, create 2-4 short user-visible tasks and keep them updated as the workflow advances.
1145
+ - Use \`loop.ask_user({ type: 'choice', options: [...] })\` when you have a short, grounded shortlist the user can choose from. Otherwise use \`type: 'input'\`.
1146
+ - For an unclear ranking, comparison, or selection rule, prefer \`type: 'choice'\` when you can offer a short grounded list of plausible interpretations from the domain or nearby context.
1147
+ - When \`type: 'choice'\` fits, do not ask the same question as plain text with bullets such as "Common options:" or "Choose one of these:".
1148
+ - Use \`loop.confirm(...)\` for consequential approval unless the user already clearly instructed you to perform that exact action now.
1149
+ - Await \`loop.ask_user(...)\` and \`loop.confirm(...)\`. After the job resumes, continue in the same job whenever the answer is enough to act.
1150
+ - Use \`loop.open_decision(...)\` to persist grounded candidates, \`loop.close_decision(...)\` to resolve one, and \`loop.close_loop(...)\` when the workflow is completed, canceled, or blocked.
1151
+ - If you ask a new question in the current job, do not also close the loop in that same job.
1004
1152
 
1005
1153
  \u2500\u2500\u2500 CODE RULES \u2500\u2500\u2500
1006
1154
  - Import from \`./sandbox-tools\`.
1007
- - If you use \`heap\`, \`loop\`, \`agent_text_message\`, \`agent_heap_objects\`, or legacy \`agent_message\`, import them explicitly from \`./sandbox-tools\`.
1155
+ - If you use \`heap\`, \`loop\`, \`agent_text_message\`, or \`agent_heap_objects\`, import them explicitly from \`./sandbox-tools\`.
1008
1156
  - Write top-level executable code with \`await\` at top level.
1009
- - The generated job body must be plain runnable JavaScript. The DOMAIN TYPES block is only a reference for shapes and available methods.
1010
- - Do not write TypeScript-only syntax in executable code: no type annotations, no interfaces, no enums, no \`as Type\` casts, no \`satisfies\`, and no generic type parameters in code.
1011
- - Generated code must be valid against the DOMAIN TYPES block above.
1012
- - Only use classes, methods, and parameter shapes that are explicitly declared in those typedefs.
1013
- - Never invent helper methods such as \`find(...)\` or unsupported parameters such as \`id\` when the typedefs require \`path\`.
1014
- - Use \`ClassName.get({ path })\` only when you already know an object's graph path.
1015
- - Use \`ClassName.count()\` when you only need a total.
1016
- - Use \`ClassName.page({ page, perPage, saveAs })\` when you need both records and pagination metadata like \`totalCount\` or \`hasMore\`. \`perPage\` defaults to \`100\` and larger values are clamped to \`100\`.
1017
- - Use \`ClassName.list({ page, perPage, saveAs })\` to load one typed page of records. \`limit\` is only a legacy alias for \`perPage\`, \`perPage\` defaults to \`100\`, and larger values are clamped to \`100\`.
1018
- - Use \`for await (const item of ClassName.iterate({ perPage, maxItems }))\` for large batch jobs so you do not materialize the whole result set at once. \`perPage\` defaults to \`100\` and larger values are clamped to \`100\`.
1019
- - Instance methods: \`await instance.method_name(params)\`.
1020
- - Static methods: \`await ClassName.static_method(params)\`.
1021
- - Global effects: \`await effect_name(params)\`.
1022
- - When a child record has sparse fields, identify it through nearby graph context instead of only string-matching that child\u2019s local fields. Prefer traversing linked customer, case, work order, part request, and shipment records over broad guesswork.
1023
- - For blocker, delay, ETA, or "what is holding this up?" questions, do not stop at a parent status like \`in_progress\` or \`scheduled\` if linked dependencies exist. Trace into the likely dependency chain first: approval -> work order -> part request -> shipment -> carrier update.
1024
- - A generic parent status is not a sufficient blocker explanation when a linked part request, approval, shipment, customs hold, or vendor delay may be the real cause.
1025
- - If a case summary or latest customer message mentions a part, shipment, ETA, customs, vendor, approval, regulator, kit, or delay, treat that as a strong hint to inspect the linked dependency records before answering.
1026
- - When you already found the correct parent case, inspect its linked child records even if the child summaries use different wording. Do not require a work order, part request, or shipment description to repeat the exact phrase that identified the parent case.
1027
- - Status fields are free-form operational strings, not strict enums. Normalize spelling mentally and do not rely on brittle hard-coded sets that miss variants like \`in-progress\`, \`in_progress\`, \`awaiting-part\`, or \`approval-submitted\`.
1028
- - Do not discard a case, work order, part request, or shipment only because its status string does not match your preferred "open" spelling. If the record is otherwise the clear match, inspect it.
1029
- - Reuse \`heap.getVar(name)\`, \`heap.setVar(name, value)\`, and \`heap.deleteVar(name)\` only when it clearly helps the next step. Do not mirror data into the heap just for completeness.
1030
- - Prefer \`heap.setVar(name, value)\` for scalars or one selected instance. Prefer \`ClassName.list({ page, perPage, saveAs })\` for reusable list pages instead of \`heap.setVar(name, array)\`.
1031
- - Never write an empty array into the heap. If a filtered list is empty, keep it local or clear the previous heap value with \`heap.deleteVar(name)\`.
1032
- - Prefer heap-backed state that represents the current choice or recommendation. Avoid storing extra scalar bookkeeping unless it is needed for the next concrete step.
1033
- - Only store true sandbox instances, typed lists of sandbox instances, or scalars in the heap. Results returned by static effects like availability/search helpers are often plain JSON, not sandbox instances.
1034
- - If a helper returns plain JSON candidates, keep them local, store only a scalar like the chosen id, or resolve the matching sandbox instance before writing it into the heap.
1035
- - When reading heap values, prefer generated generic typings such as \`await heap.getVar<Book[]>("my_books")\` or \`await heap.getVar<Book>("selected_book")\`.
1036
- - If a focused heap variable already points to a known class, read it with that exact generic type and act on it directly. Do not use \`heap.getVar<any>(...)\` or cast through \`any\` when the class is already clear from the prompt. For example, prefer \`await heap.getVar<Order>("selected_order")\` over \`await heap.getVar<any>("selected_order")\`.
1037
- - Use the injected \`loop\` helpers when you need to manage the workflow itself:
1038
- \`loop.ask_user(...)\`, \`loop.confirm(...)\`, \`loop.open_decision(...)\`, \`loop.close_decision(...)\`,
1039
- \`loop.create_task(...)\`, \`loop.update_task(...)\`, \`loop.complete_task(...)\`, and \`loop.close_loop(...)\`.
1040
- - Loop helper semantics:
1041
- - \`loop.open_decision(...)\`: store explicit candidates from the current job so a later job can revisit the same decision. Keep and reuse the returned \`decisionId\`.
1042
- - \`loop.close_decision(...)\`: resolve an open decision by choosing one stored candidate with \`selectedId\` and recording why. Candidates may be any JSON objects, but each one must have an \`id\`.
1043
- - \`loop.ask_user(...)\`: pause the job and ask the user for missing input. Default to \`type: 'input'\`; use \`type: 'choice'\` only for a short explicit shortlist. Write \`const answer = await loop.ask_user(...)\`, then continue the same job once the user answers.
1044
- - \`loop.confirm(...)\`: pause the job for approval before a consequential action. Do not simulate confirmation in plain text. Write \`const approved = await loop.confirm(...)\`, then branch on that approval once the job resumes.
1045
- - \`loop.create_task(...)\`, \`loop.update_task(...)\`, \`loop.complete_task(...)\`: keep a short task list that later jobs can continue and finish.
1046
- - \`loop.close_loop(...)\`: record the current workflow outcome with a short summary before stopping. Do not call it in the same job that opens a new user prompt unless the workflow is explicitly blocked. This does not end the session forever.
1047
- - Avoid \`as any\` and other broad casts when the DOMAIN TYPES block already tells you the correct class or list type.
1048
- - Prefer manipulating heap-backed instances and typed lists instead of returning raw JSON blobs or object IDs unless the user explicitly asks for them.
1049
- - Every job that intends to answer the user must emit at least one explicit UI message with \`agent_text_message(...)\` and/or \`agent_heap_objects(...)\`.
1050
- - Use \`agent_text_message("...")\` for all user-visible text shown in the UI.
1051
- - Use \`agent_heap_objects(...)\` only when you want the UI to render heap-backed records or lists.
1052
- - If you want to show both text and records, call \`agent_text_message(...)\` and \`agent_heap_objects(...)\` separately in whatever order fits the interaction.
1053
- - \`agent_text_message(...)\` should be used with a plain text string in normal generated code.
1054
- - \`agent_heap_objects(...)\` should point at heap-backed values: explicit \`entryPaths\` / \`listNames\` / \`variableNames\`, a named list saved with \`saveAs\`, or values read back from \`heap.getVar(...)\`.
1055
- - If you just fetched records and want to show them in the UI, save or reference them through the heap first, then call \`agent_heap_objects(...)\`. Do not try to hand-build UI payloads in job code.
1056
- - Do not assume heap changes will be displayed automatically. If records should appear in the UI, you must call \`agent_heap_objects(...)\`.
1057
- - Never write \`return { reply, show }\` or \`return { show: ... }\` for UI. If you want the UI to render records or lists, call \`agent_heap_objects(...)\` instead.
1058
- - Do not rely on the final return value for user-visible output. A plain return value is not considered a displayed UI answer.
1059
- - \`agent_message(...)\` remains available as a legacy compatibility alias, but prefer \`agent_text_message(...)\` and \`agent_heap_objects(...)\` in new code.
1060
- - Do not return bare structured JSON, low-level diagnostics, or database-shaped payloads as the final answer unless the user explicitly asks for them.
1061
- - Prefer simple executable JavaScript over clever interpolation. Avoid nested template literals or unusually dense inline expressions when a small temporary variable or string concatenation would be clearer and safer.
1157
+ - The generated job body must be plain runnable JavaScript. Do not use TypeScript-only syntax.
1158
+ - Follow the exact classes, methods, and parameter shapes in DOMAIN REFERENCE. Do not invent helpers or unsupported arguments.
1159
+ - Use \`ClassName.get({ path })\` only for known graph paths when you want a direct graph fetch.
1160
+ - Use \`ClassName.count()\` for totals, \`ClassName.page({ page, perPage, saveAs })\` when you need \`items\` plus \`totalCount\` or \`hasMore\`, \`ClassName.list({ page, perPage, saveAs })\` for one page of records, and \`ClassName.iterate({ perPage, maxItems })\` for large scans.
1161
+ - \`perPage\` defaults to \`100\` and is capped at \`100\`.
1162
+ - Push \`filter\`, \`search\`, and \`sort\` into graph queries instead of fetching a page and processing it locally.
1163
+ - A property appearing on a record does not make it valid in \`filter\` or \`sort\`; only use fields and operators that are explicitly exposed in DOMAIN REFERENCE.
1164
+ - Choose \`sort.field\` verbatim from the sortable fields listed in DOMAIN REFERENCE. Do not sort by relationship names, related-record collections, counts, totals, or other derived metrics unless they are explicitly listed as sortable.
1165
+ - If ordering alone answers the request, use \`sort\` without inventing a \`filter\`.
1166
+ - Do not invent proxy metrics, fallback heuristics, or made-up tie-breakers to resolve ambiguity. If the rule is unclear, ask the user with \`loop.ask_user(...)\`.
1167
+ - Do not fetch, sort, or show a provisional record just to have something to display while the real ranking or selection rule is still ambiguous.
1168
+ - Call instance methods on instances, static methods on classes, and global effects by name.
1169
+ - Use \`heap.getEntry(path)\` for remembered heap entries, \`heap.getList(name)\` for remembered lists, and \`heap.getVar(name)\` only for named variables.
1170
+ - Use \`heap.setVar(...)\` and \`heap.deleteVar(...)\` only when they help the next step.
1171
+ - Prefer \`heap.setVar(...)\` for scalars or one selected instance. Prefer \`ClassName.list({ saveAs })\` for reusable typed lists. Empty arrays are allowed.
1172
+ - Only store sandbox instances, typed lists, or scalars in the heap. If a helper returns plain JSON, keep it local or store only the chosen scalar.
1173
+ - Use the \`loop\` helpers to manage workflow state: \`ask_user\`, \`confirm\`, \`open_decision\`, \`close_decision\`, \`create_task\`, \`update_task\`, \`complete_task\`, and \`close_loop\`.
1174
+ - Use \`type: 'choice'\` only for short grounded options. Use \`type: 'input'\` when the answer should stay open-ended.
1175
+ - \`loop.confirm(...)\` is for consequential approval. Do not ask for approval in plain text.
1176
+ - After \`await loop.ask_user(...)\` or \`await loop.confirm(...)\`, continue in the same resumed job when the answer is enough to act.
1177
+ - Every job that answers the user must emit \`agent_text_message(...)\` and/or \`agent_heap_objects(...)\`.
1178
+ - Use \`agent_text_message(...)\` for user-visible text.
1179
+ - Use \`agent_heap_objects(...)\` for user-visible records. You may pass sandbox instances directly, or heap-backed \`entryPaths\`, \`listNames\`, and \`variableNames\` when you already have them. Use \`saveAs\` or \`heap.setVar(...)\` when you need a reusable named selection.
1180
+ - Do not rely on the final return value for UI output. Do not return ad-hoc \`reply\` / \`show\` payloads instead of explicit agent message calls.
1181
+ - Keep the code small and direct. Avoid speculative branches, broad casts, and raw JSON dumps unless the user asked for them.
1062
1182
  - Use \`console.log()\` only for intermediate diagnostics, not for the final user-facing answer.`;
1063
1183
  }
1064
1184
 
@@ -1067,6 +1187,7 @@ exports.buildGranularAgentCheckpointBlock = buildGranularAgentCheckpointBlock;
1067
1187
  exports.buildGranularAgentDomainBlock = buildGranularAgentDomainBlock;
1068
1188
  exports.buildGranularAgentHeapBlock = buildGranularAgentHeapBlock;
1069
1189
  exports.buildGranularAgentLoopBlock = buildGranularAgentLoopBlock;
1190
+ exports.buildGranularAgentReferentBlock = buildGranularAgentReferentBlock;
1070
1191
  exports.buildGranularAgentSessionBlock = buildGranularAgentSessionBlock;
1071
1192
  exports.buildGranularAgentSystemPrompt = buildGranularAgentSystemPrompt;
1072
1193
  exports.buildGranularAgentToolBlock = buildGranularAgentToolBlock;
@@ -1076,6 +1197,8 @@ exports.evaluateContinuation = evaluateContinuation;
1076
1197
  exports.getCurrentClosureId = getCurrentClosureId;
1077
1198
  exports.getExclusivePromptTarget = getExclusivePromptTarget;
1078
1199
  exports.hasOpenPrompt = hasOpenPrompt;
1200
+ exports.projectConversationReferentFocus = projectConversationReferentFocus;
1201
+ exports.projectConversationReferentSummary = projectConversationReferentSummary;
1079
1202
  exports.projectHeapSummary = projectHeapSummary;
1080
1203
  exports.projectLoopSummary = projectLoopSummary;
1081
1204
  exports.projectWorkflowFocus = projectWorkflowFocus;