@granular-software/sdk 0.4.49 → 0.4.50

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.
@@ -4019,6 +4019,9 @@ function rpcTimeoutMsForMethod(method) {
4019
4019
  return DOMAIN_PACKAGE_RPC_TIMEOUT_MS;
4020
4020
  case "client.heartbeat":
4021
4021
  case "effects.publishCatalog":
4022
+ case "effects.resetCatalog":
4023
+ case "effects.addCatalog":
4024
+ case "effects.removeCatalog":
4022
4025
  case "effects.refresh":
4023
4026
  return EFFECT_CONTROL_RPC_TIMEOUT_MS;
4024
4027
  case "harness.run":
@@ -4764,6 +4767,9 @@ function resolvePromptAnswer(prompt, answer) {
4764
4767
 
4765
4768
  // src/session.ts
4766
4769
  var PROMPT_TRANSCRIPT_APPEND_TIMEOUT_MS = 5e3;
4770
+ function toPascalCase(value) {
4771
+ return value.split(/[_:\-\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
4772
+ }
4767
4773
  function withPromptTranscriptTimeout(promise) {
4768
4774
  let timeout = null;
4769
4775
  return Promise.race([
@@ -5326,9 +5332,7 @@ var Session = class {
5326
5332
  if (classes && Object.keys(classes).length > 0) {
5327
5333
  let docs2 = "# Domain Documentation\n\n";
5328
5334
  docs2 += "Import concrete classes from `@granular/domain/<Class>` and global backend actions from `@granular/actions/backend`:\n\n";
5329
- const classNames = Object.keys(classes).map(
5330
- (c) => c.charAt(0).toUpperCase() + c.slice(1)
5331
- );
5335
+ const classNames = Object.keys(classes).map(toPascalCase);
5332
5336
  const globalNames = (globalTools || []).map((t) => t.name);
5333
5337
  const importLines = [
5334
5338
  ...classNames.map(
@@ -5342,7 +5346,7 @@ ${importLines.join("\n") || "// No generated domain imports available."}
5342
5346
 
5343
5347
  `;
5344
5348
  for (const [className, cls] of Object.entries(classes)) {
5345
- const TsName = className.charAt(0).toUpperCase() + className.slice(1);
5349
+ const TsName = toPascalCase(className);
5346
5350
  docs2 += `## ${TsName}
5347
5351
 
5348
5352
  `;
@@ -6332,6 +6336,28 @@ function asString(value) {
6332
6336
  function trimString(value) {
6333
6337
  return typeof value === "string" ? value.trim() : "";
6334
6338
  }
6339
+ function compactJson(value, maxLength = 320) {
6340
+ if (value === void 0 || value === null) return void 0;
6341
+ try {
6342
+ const json = JSON.stringify(value);
6343
+ if (!json || json === "undefined") return void 0;
6344
+ return json.length > maxLength ? `${json.slice(0, maxLength)}...` : json;
6345
+ } catch {
6346
+ return String(value);
6347
+ }
6348
+ }
6349
+ function artifactRecordsById(liveDoc) {
6350
+ const artifacts = asRecord3(liveDoc?.artifacts);
6351
+ const byId = asRecord3(artifacts?.byId) || {};
6352
+ return Object.fromEntries(
6353
+ Object.entries(byId).map(([artifactId, value]) => {
6354
+ const record = asRecord3(value);
6355
+ return record ? [artifactId, record] : null;
6356
+ }).filter(
6357
+ (entry) => Boolean(entry)
6358
+ )
6359
+ );
6360
+ }
6335
6361
  function normalizeShowRefs(value) {
6336
6362
  const record = asRecord3(value);
6337
6363
  if (!record) return void 0;
@@ -6348,9 +6374,31 @@ function normalizeShowRefs(value) {
6348
6374
  entryPaths: normalizeRefs(record.entryPaths),
6349
6375
  listNames: normalizeRefs(record.listNames),
6350
6376
  variableNames: normalizeRefs(record.variableNames),
6351
- fileIds: normalizeRefs(record.fileIds)
6377
+ fileIds: normalizeRefs(record.fileIds),
6378
+ sessionArtifactIds: normalizeRefs(record.sessionArtifactIds),
6379
+ actionSuggestions: normalizeActionSuggestions(record.actionSuggestions)
6352
6380
  };
6353
- return show.entryPaths || show.listNames || show.variableNames || show.fileIds ? show : void 0;
6381
+ return show.entryPaths || show.listNames || show.variableNames || show.fileIds || show.sessionArtifactIds || show.actionSuggestions ? show : void 0;
6382
+ }
6383
+ function normalizeActionSuggestions(value) {
6384
+ if (!Array.isArray(value)) return void 0;
6385
+ const suggestions = [];
6386
+ for (const item of value) {
6387
+ const record = asRecord3(item);
6388
+ if (!record) continue;
6389
+ const label = trimString(record.label);
6390
+ if (!label) continue;
6391
+ const suggestionId = trimString(record.suggestionId) || trimString(record.id) || label;
6392
+ suggestions.push({
6393
+ suggestionId,
6394
+ label,
6395
+ ...typeof record.description === "string" ? { description: record.description } : {},
6396
+ ...asRecord3(record.artifact) ? { artifact: asRecord3(record.artifact) } : {},
6397
+ ...asRecord3(record.target) ? { target: asRecord3(record.target) } : {},
6398
+ ...asRecord3(record.metadata) ? { metadata: asRecord3(record.metadata) } : {}
6399
+ });
6400
+ }
6401
+ return suggestions.length ? suggestions : void 0;
6354
6402
  }
6355
6403
  function stringifyTranscriptValue(value, fallback = "") {
6356
6404
  if (typeof value === "string") {
@@ -6370,12 +6418,139 @@ function stringifyTranscriptValue(value, fallback = "") {
6370
6418
  return String(value);
6371
6419
  }
6372
6420
  }
6373
- function buildArtifactHistory(show) {
6421
+ function latestInputEditSummary(metadata) {
6422
+ const lastInputEdit = asRecord3(metadata.lastInputEdit);
6423
+ if (!lastInputEdit) return null;
6424
+ const source = asString(lastInputEdit.source) || "unknown";
6425
+ const actor = asString(lastInputEdit.actorSubjectId) || asString(lastInputEdit.actorPermissionProfileName) || asString(lastInputEdit.jobId) || null;
6426
+ const inputKeys = Array.isArray(lastInputEdit.changedInputKeys) ? lastInputEdit.changedInputKeys.filter(
6427
+ (key) => typeof key === "string" && key.trim().length > 0
6428
+ ).slice(0, 6) : [];
6429
+ const relationshipKeys = Array.isArray(lastInputEdit.changedRelationshipKeys) ? lastInputEdit.changedRelationshipKeys.filter(
6430
+ (key) => typeof key === "string" && key.trim().length > 0
6431
+ ).slice(0, 6) : [];
6432
+ const changed = [
6433
+ inputKeys.length ? `inputs=${inputKeys.join(",")}` : null,
6434
+ relationshipKeys.length ? `relationships=${relationshipKeys.join(",")}` : null
6435
+ ].filter(Boolean);
6436
+ return `lastEdit=${source}${actor ? ` by ${actor}` : ""}${changed.length ? ` (${changed.join("; ")})` : ""}`;
6437
+ }
6438
+ function artifactIssueSummary(record) {
6439
+ const validation = asRecord3(record.validation);
6440
+ if (!validation) return null;
6441
+ const issues = Array.isArray(validation.issues) ? validation.issues.map((issue) => asRecord3(issue)).filter((issue) => Boolean(issue)).slice(0, 3) : [];
6442
+ if (issues.length > 0) {
6443
+ return `issues=${issues.map((issue) => {
6444
+ const code = asString(issue.code) || asString(issue.kind) || "issue";
6445
+ const path2 = asString(issue.path);
6446
+ const message = trimString(issue.message);
6447
+ return `${code}${path2 ? ` at ${path2}` : ""}${message ? ` (${message})` : ""}`;
6448
+ }).join("; ")}`;
6449
+ }
6450
+ const error = trimString(validation.error) || trimString(validation.reason) || trimString(validation.message);
6451
+ return error ? `validation=${error}` : null;
6452
+ }
6453
+ function artifactExecutionSummary(metadata) {
6454
+ const execution = asRecord3(metadata.execution);
6455
+ if (!execution) return null;
6456
+ const result = asRecord3(execution.result);
6457
+ const awaiting = asString(result?.awaiting) || asString(execution.awaiting);
6458
+ const pendingTransition = asString(result?.pendingTransition) || asString(execution.pendingTransition);
6459
+ const approval = asRecord3(result?.approval) || asRecord3(execution.approval);
6460
+ const approvalTarget = asString(approval?.permissionProfileName) || asString(approval?.permissionProfileId) || asString(approval?.assigneeSubjectId);
6461
+ const error = trimString(execution.error);
6462
+ const pieces = [
6463
+ awaiting ? `awaiting=${awaiting}` : null,
6464
+ pendingTransition ? `pendingTransition=${pendingTransition}` : null,
6465
+ approvalTarget ? `approvalTarget=${approvalTarget}` : null,
6466
+ error ? `executionError=${error}` : null
6467
+ ].filter(Boolean);
6468
+ return pieces.length ? pieces.join("; ") : null;
6469
+ }
6470
+ function artifactStatePathSummary(metadata) {
6471
+ const statePlan = asRecord3(metadata.statePlan);
6472
+ if (!statePlan) return null;
6473
+ const machineName = asString(statePlan.machineName);
6474
+ const targetState = asString(statePlan.targetState);
6475
+ const objectPath = asString(statePlan.objectPath);
6476
+ const approvedTransitions = Array.isArray(statePlan.approvedTransitions) ? statePlan.approvedTransitions.length : 0;
6477
+ const approvalDecisions = Array.isArray(statePlan.approvalDecisions) ? statePlan.approvalDecisions.length : 0;
6478
+ const pieces = [
6479
+ machineName || targetState ? `statePath=${machineName || "state_machine"}${targetState ? ` -> ${targetState}` : ""}` : null,
6480
+ objectPath ? `objectPath=${objectPath}` : null,
6481
+ approvedTransitions ? `approvedTransitions=${approvedTransitions}` : null,
6482
+ approvalDecisions ? `approvalDecisions=${approvalDecisions}` : null
6483
+ ].filter(Boolean);
6484
+ return pieces.length ? pieces.join("; ") : null;
6485
+ }
6486
+ function artifactSummaryLine(artifactId, record) {
6487
+ if (!record) return `- ${artifactId}: unavailable in session artifact store`;
6488
+ const label = trimString(record.label) || artifactId;
6489
+ const kind = asString(record.kind) || "artifact";
6490
+ const status = asString(record.status) || "unknown";
6491
+ const createdByJobId = asString(record.createdByJobId);
6492
+ const target = asRecord3(record.target);
6493
+ const metadata = asRecord3(record.metadata) || {};
6494
+ const subArtifactIds = Array.isArray(record.subArtifactIds) ? record.subArtifactIds.filter(
6495
+ (id) => typeof id === "string" && id.trim().length > 0
6496
+ ).slice(0, 8) : [];
6497
+ const relationships = compactJson(record.relationships, 220);
6498
+ const pieces = [
6499
+ `kind=${kind}`,
6500
+ `status=${status}`,
6501
+ createdByJobId ? `createdByJob=${createdByJobId}` : null,
6502
+ target ? `target=${asString(target.className) || "record"}:${asString(target.id) || "unknown"}${asString(target.label) ? ` (${asString(target.label)})` : ""}` : null,
6503
+ artifactStatePathSummary(metadata),
6504
+ artifactExecutionSummary(metadata),
6505
+ artifactIssueSummary(record),
6506
+ latestInputEditSummary(metadata),
6507
+ subArtifactIds.length ? `subArtifacts=${subArtifactIds.join(",")}` : null,
6508
+ relationships ? `relationships=${relationships}` : null
6509
+ ].filter(Boolean);
6510
+ return `- ${artifactId}: ${label}${pieces.length ? `; ${pieces.join("; ")}` : ""}`;
6511
+ }
6512
+ function buildArtifactHistory(show, artifactsById) {
6374
6513
  if (!show) return void 0;
6375
- return `[Agent message]
6514
+ const artifactIds = show.sessionArtifactIds || [];
6515
+ const actionSuggestions = show.actionSuggestions || [];
6516
+ if (artifactIds.length === 0 && actionSuggestions.length === 0) {
6517
+ return `[Agent message]
6376
6518
  ${stringifyTranscriptValue({ show }, "")}`;
6519
+ }
6520
+ const lines = artifactIds.slice(0, 8).map(
6521
+ (artifactId) => artifactSummaryLine(artifactId, artifactsById?.[artifactId])
6522
+ );
6523
+ if (artifactIds.length > 8) {
6524
+ lines.push(`- ${artifactIds.length - 8} more artifacts omitted`);
6525
+ }
6526
+ if (actionSuggestions.length > 0) {
6527
+ if (artifactIds.length > 0) lines.push("[Agent suggested actions]");
6528
+ for (const suggestion of actionSuggestions.slice(0, 8)) {
6529
+ lines.push(
6530
+ `- ${suggestion.label}${suggestion.description ? `; ${suggestion.description}` : ""}`
6531
+ );
6532
+ }
6533
+ if (actionSuggestions.length > 8) {
6534
+ lines.push(`- ${actionSuggestions.length - 8} more suggestions omitted`);
6535
+ }
6536
+ }
6537
+ const otherRefs = {
6538
+ entryPaths: show.entryPaths,
6539
+ listNames: show.listNames,
6540
+ variableNames: show.variableNames,
6541
+ fileIds: show.fileIds
6542
+ };
6543
+ const hasOtherRefs = Object.values(otherRefs).some(
6544
+ (value) => Array.isArray(value) && value.length > 0
6545
+ );
6546
+ const title = artifactIds.length > 0 ? "[Agent displayed session artifacts]" : "[Agent suggested actions]";
6547
+ return [
6548
+ title,
6549
+ ...lines,
6550
+ hasOtherRefs ? `Other shown refs: ${stringifyTranscriptValue(otherRefs, "")}` : null
6551
+ ].filter(Boolean).join("\n");
6377
6552
  }
6378
- function normalizeConversationMessage(raw) {
6553
+ function normalizeConversationMessage(raw, artifactsById) {
6379
6554
  const record = asRecord3(raw);
6380
6555
  if (!record) return null;
6381
6556
  const role = record.role === "user" ? "user" : record.role === "assistant" ? "assistant" : null;
@@ -6387,6 +6562,12 @@ function normalizeConversationMessage(raw) {
6387
6562
  const id = asString(record.id) || crypto.randomUUID();
6388
6563
  const timestamp = asNumber(record.timestamp) || asNumber(record.ts) || 0;
6389
6564
  if (!content && !show) return null;
6565
+ const artifactHistory = buildArtifactHistory(show, artifactsById);
6566
+ const historyContent = role === "assistant" ? content && artifactHistory ? `[Assistant reply]
6567
+ ${content}
6568
+
6569
+ ${artifactHistory}` : content ? `[Assistant reply]
6570
+ ${content}` : artifactHistory : void 0;
6390
6571
  return {
6391
6572
  id,
6392
6573
  role,
@@ -6395,8 +6576,7 @@ function normalizeConversationMessage(raw) {
6395
6576
  jobId: asString(record.jobId),
6396
6577
  promptId: asString(record.promptId),
6397
6578
  show,
6398
- historyContent: role === "assistant" ? content ? `[Assistant reply]
6399
- ${content}` : buildArtifactHistory(show) : void 0,
6579
+ historyContent,
6400
6580
  source: "conversation"
6401
6581
  };
6402
6582
  }
@@ -6439,7 +6619,7 @@ ${assistantContent}`,
6439
6619
  return entries;
6440
6620
  });
6441
6621
  }
6442
- function normalizeAgentMessageEntries(jobId, rawMessages) {
6622
+ function normalizeAgentMessageEntries(jobId, rawMessages, artifactsById) {
6443
6623
  return asArray(rawMessages).map((value) => asRecord3(value)).filter((value) => Boolean(value)).sort(
6444
6624
  (left, right) => (asNumber(left.timestamp) || asNumber(left.ts) || 0) - (asNumber(right.timestamp) || asNumber(right.ts) || 0)
6445
6625
  ).flatMap((message) => {
@@ -6470,14 +6650,14 @@ ${reply}`,
6470
6650
  timestamp,
6471
6651
  jobId,
6472
6652
  show,
6473
- historyContent: buildArtifactHistory(show),
6653
+ historyContent: buildArtifactHistory(show, artifactsById),
6474
6654
  source: "job_agent_message"
6475
6655
  });
6476
6656
  }
6477
6657
  return entries;
6478
6658
  });
6479
6659
  }
6480
- function buildJobFallbackEntries(jobId, job, sessionHeap) {
6660
+ function buildJobFallbackEntries(jobId, job, sessionHeap, artifactsById) {
6481
6661
  const timestamp = asNumber(job.finishedAt) || asNumber(job.startedAt) || asNumber(job.submittedAt) || 0;
6482
6662
  const resultPreview = stringifyTranscriptValue(
6483
6663
  job.result,
@@ -6515,7 +6695,7 @@ ${responseText}`,
6515
6695
  timestamp,
6516
6696
  jobId,
6517
6697
  show,
6518
- historyContent: buildArtifactHistory(show),
6698
+ historyContent: buildArtifactHistory(show, artifactsById),
6519
6699
  source: "job_result"
6520
6700
  });
6521
6701
  }
@@ -6569,10 +6749,11 @@ function buildJobCodeEntry(jobId, job) {
6569
6749
  function buildSessionTranscript(input) {
6570
6750
  const liveDoc = input.liveDoc || null;
6571
6751
  const sessionHeap = input.sessionHeap || EMPTY_HEAP;
6752
+ const artifactsById = artifactRecordsById(liveDoc);
6572
6753
  const transcript = [];
6573
6754
  const conversationMessages = asArray(
6574
6755
  asRecord3(liveDoc?.conversation)?.messages
6575
- ).map((message) => normalizeConversationMessage(message)).filter((message) => Boolean(message));
6756
+ ).map((message) => normalizeConversationMessage(message, artifactsById)).filter((message) => Boolean(message));
6576
6757
  const conversationPromptIds = new Set(
6577
6758
  conversationMessages.map((message) => message.promptId).filter((promptId) => Boolean(promptId))
6578
6759
  );
@@ -6599,7 +6780,8 @@ function buildSessionTranscript(input) {
6599
6780
  if (!assistantConversationJobIds.has(jobId)) {
6600
6781
  const agentEntries = normalizeAgentMessageEntries(
6601
6782
  jobId,
6602
- job.agentMessages
6783
+ job.agentMessages,
6784
+ artifactsById
6603
6785
  );
6604
6786
  if (agentEntries.length > 0) {
6605
6787
  transcript.push(...agentEntries);
@@ -6608,7 +6790,8 @@ function buildSessionTranscript(input) {
6608
6790
  ...buildJobFallbackEntries(
6609
6791
  jobId,
6610
6792
  job,
6611
- sessionHeap
6793
+ sessionHeap,
6794
+ artifactsById
6612
6795
  )
6613
6796
  );
6614
6797
  }
@@ -10774,16 +10957,107 @@ var StateMachineStateSchema = external_exports.union([
10774
10957
  external_exports.string(),
10775
10958
  external_exports.object({
10776
10959
  name: external_exports.string().min(1),
10960
+ label: external_exports.string().optional(),
10961
+ description: external_exports.string().optional(),
10777
10962
  isFinal: external_exports.boolean().optional()
10778
10963
  }).strict()
10779
10964
  ]);
10965
+ var StateTransitionInputBindingSchema = external_exports.lazy(
10966
+ () => external_exports.union([
10967
+ external_exports.null(),
10968
+ external_exports.string(),
10969
+ external_exports.number(),
10970
+ external_exports.boolean(),
10971
+ external_exports.array(StateTransitionInputBindingSchema),
10972
+ external_exports.object({
10973
+ const: external_exports.unknown()
10974
+ }).strict(),
10975
+ external_exports.object({
10976
+ from: external_exports.literal("object"),
10977
+ path: external_exports.string().min(1),
10978
+ editable: external_exports.boolean().optional()
10979
+ }).strict(),
10980
+ external_exports.object({
10981
+ from: external_exports.literal("field"),
10982
+ name: external_exports.string().min(1),
10983
+ editable: external_exports.boolean().optional()
10984
+ }).strict(),
10985
+ external_exports.object({
10986
+ from: external_exports.literal("relationship"),
10987
+ name: external_exports.string().min(1),
10988
+ path: external_exports.string().min(1).optional(),
10989
+ many: external_exports.boolean().optional(),
10990
+ editable: external_exports.boolean().optional()
10991
+ }).strict(),
10992
+ external_exports.object({
10993
+ from: external_exports.literal("session"),
10994
+ path: external_exports.string().min(1),
10995
+ editable: external_exports.boolean().optional()
10996
+ }).strict(),
10997
+ external_exports.object({
10998
+ from: external_exports.literal("actor"),
10999
+ path: external_exports.string().min(1),
11000
+ editable: external_exports.boolean().optional()
11001
+ }).strict(),
11002
+ external_exports.record(external_exports.string(), StateTransitionInputBindingSchema)
11003
+ ])
11004
+ );
11005
+ var StateTransitionActionSchema = external_exports.object({
11006
+ effect: external_exports.string().min(1),
11007
+ input: external_exports.record(external_exports.string(), StateTransitionInputBindingSchema).optional()
11008
+ }).strict();
11009
+ var StateTransitionAssigneeSchema = external_exports.object({
11010
+ kind: external_exports.string().min(1),
11011
+ from: StateTransitionInputBindingSchema.optional(),
11012
+ role: external_exports.string().optional(),
11013
+ label: external_exports.string().optional()
11014
+ }).strict();
11015
+ var StateTransitionRelatedStateRequirementSchema = external_exports.object({
11016
+ relationship: external_exports.string().min(1),
11017
+ machine: external_exports.string().min(1),
11018
+ state: external_exports.string().min(1),
11019
+ className: external_exports.string().min(1).optional(),
11020
+ label: external_exports.string().optional(),
11021
+ mode: external_exports.enum(["every", "some", "any"]).optional()
11022
+ }).strict();
11023
+ var StateTransitionRequirementsSchema = external_exports.object({
11024
+ fields: external_exports.array(external_exports.string().min(1)).optional(),
11025
+ relationships: external_exports.array(external_exports.string().min(1)).optional(),
11026
+ relatedStates: external_exports.array(StateTransitionRelatedStateRequirementSchema).optional()
11027
+ }).strict();
11028
+ var StateTransitionPermissionSchema = external_exports.union([
11029
+ external_exports.string().min(1),
11030
+ external_exports.object({
11031
+ profile: external_exports.string().min(1).optional(),
11032
+ profileId: external_exports.string().min(1).optional(),
11033
+ label: external_exports.string().optional(),
11034
+ reason: external_exports.string().optional()
11035
+ }).strict()
11036
+ ]);
11037
+ var StateTransitionExpectedOutcomeSchema = external_exports.union([
11038
+ external_exports.string().min(1),
11039
+ external_exports.object({
11040
+ machine: external_exports.string().min(1).optional(),
11041
+ state: external_exports.string().min(1),
11042
+ summary: external_exports.string().optional()
11043
+ }).strict()
11044
+ ]);
10780
11045
  var StateMachineTransitionSchema = external_exports.object({
10781
11046
  name: external_exports.string().min(1),
10782
11047
  from: external_exports.string().min(1),
10783
- to: external_exports.string().min(1)
11048
+ to: external_exports.string().min(1),
11049
+ label: external_exports.string().optional(),
11050
+ description: external_exports.string().optional(),
11051
+ action: StateTransitionActionSchema.optional(),
11052
+ assignee: StateTransitionAssigneeSchema.optional(),
11053
+ requirements: StateTransitionRequirementsSchema.optional(),
11054
+ permission: StateTransitionPermissionSchema.optional(),
11055
+ risk: external_exports.enum(["low", "medium", "high"]).optional(),
11056
+ expectedOutcome: StateTransitionExpectedOutcomeSchema.optional()
10784
11057
  }).strict();
10785
11058
  external_exports.object({
10786
11059
  name: external_exports.string().min(1),
11060
+ stateField: external_exports.string().min(1).optional(),
10787
11061
  entryState: external_exports.string().min(1),
10788
11062
  states: external_exports.array(StateMachineStateSchema).min(1),
10789
11063
  transitions: external_exports.array(StateMachineTransitionSchema),
@@ -10850,6 +11124,16 @@ var PoliciesSchema = external_exports.object({
10850
11124
  confirmWhen: external_exports.array(PolicyRuleSchema).optional(),
10851
11125
  denyWhen: external_exports.array(PolicyRuleSchema).optional()
10852
11126
  }).strict();
11127
+ var CreatesSchema = external_exports.union([
11128
+ external_exports.string().min(1),
11129
+ external_exports.object({
11130
+ className: external_exports.string().min(1),
11131
+ idPath: external_exports.string().min(1).optional(),
11132
+ pathPath: external_exports.string().min(1).optional(),
11133
+ statePath: external_exports.string().min(1).optional(),
11134
+ classStateHandle: external_exports.boolean().optional()
11135
+ }).strict()
11136
+ ]);
10853
11137
  external_exports.object({
10854
11138
  postCondition: external_exports.union([
10855
11139
  external_exports.string(),
@@ -10880,6 +11164,7 @@ external_exports.object({
10880
11164
  mode: external_exports.string().optional()
10881
11165
  }).strict()
10882
11166
  ]).optional(),
11167
+ creates: CreatesSchema.optional(),
10883
11168
  access: external_exports.enum(["read", "write", "ui"]).optional(),
10884
11169
  effectKind: external_exports.enum(["read", "write", "ui"]).optional(),
10885
11170
  sideEffect: external_exports.enum(["read", "write", "ui", "readonly", "read_only"]).optional(),
@@ -11107,9 +11392,10 @@ function mergeMethodSummaryPatch(target, patch) {
11107
11392
  if (patch.metamodels !== void 0) target.metamodels = patch.metamodels;
11108
11393
  if (patch.effectBehaviors !== void 0)
11109
11394
  target.effectBehaviors = patch.effectBehaviors;
11395
+ if (patch.creates !== void 0) target.creates = patch.creates;
11110
11396
  if (patch.static !== void 0) target.static = patch.static;
11111
11397
  }
11112
- function toPascalCase(value) {
11398
+ function toPascalCase2(value) {
11113
11399
  return value.split(/[_:\-\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
11114
11400
  }
11115
11401
  function normalizeNotesInput(input) {
@@ -11167,29 +11453,60 @@ function normalizeEffectBehaviorSummary(metamodels) {
11167
11453
  }
11168
11454
  return Object.keys(result).length > 0 ? result : null;
11169
11455
  }
11170
- function buildEffectBehaviorDocs(effectBehaviors) {
11171
- if (!effectBehaviors) {
11172
- return [];
11456
+ function normalizeCreationSummary(metamodels) {
11457
+ if (!isObject(metamodels)) return null;
11458
+ let raw = metamodels.creates;
11459
+ if (typeof raw === "string" && raw.trim().length > 0) {
11460
+ const trimmed = raw.trim();
11461
+ if (trimmed.startsWith("{") || trimmed.startsWith('"')) {
11462
+ try {
11463
+ raw = JSON.parse(trimmed);
11464
+ } catch {
11465
+ return { className: trimmed };
11466
+ }
11467
+ } else {
11468
+ return { className: trimmed };
11469
+ }
11470
+ }
11471
+ if (typeof raw === "string" && raw.trim().length > 0) {
11472
+ return { className: raw.trim() };
11173
11473
  }
11474
+ if (!isObject(raw)) return null;
11475
+ const className = typeof raw.className === "string" && raw.className.trim() ? raw.className.trim() : "";
11476
+ if (!className) return null;
11477
+ return {
11478
+ className,
11479
+ ...typeof raw.idPath === "string" && raw.idPath.trim() ? { idPath: raw.idPath.trim() } : {},
11480
+ ...typeof raw.pathPath === "string" && raw.pathPath.trim() ? { pathPath: raw.pathPath.trim() } : {},
11481
+ ...typeof raw.statePath === "string" && raw.statePath.trim() ? { statePath: raw.statePath.trim() } : {},
11482
+ ...typeof raw.classStateHandle === "boolean" ? { classStateHandle: raw.classStateHandle } : {}
11483
+ };
11484
+ }
11485
+ function buildEffectBehaviorDocs(effectBehaviors, creates) {
11174
11486
  const docs = [];
11175
- if (effectBehaviors.approvalRequired?.required) {
11487
+ if (creates) {
11488
+ docs.push(
11489
+ `Creation method: creates ${creates.className}. The agent may use generated class-level new-record action methods for this class.`
11490
+ );
11491
+ }
11492
+ if (effectBehaviors?.approvalRequired?.required) {
11176
11493
  docs.push(
11177
11494
  effectBehaviors.approvalRequired.reason ? `Approval required: ${effectBehaviors.approvalRequired.reason}.` : "Approval required before execution."
11178
11495
  );
11179
11496
  }
11180
- if (effectBehaviors.postCondition) {
11497
+ if (effectBehaviors?.postCondition) {
11181
11498
  docs.push(`Post-condition: ${effectBehaviors.postCondition.condition}.`);
11182
11499
  if (effectBehaviors.postCondition.description) {
11183
11500
  docs.push(effectBehaviors.postCondition.description);
11184
11501
  }
11185
11502
  }
11186
- if (effectBehaviors.dryRun?.enabled) {
11503
+ if (effectBehaviors?.dryRun?.enabled) {
11187
11504
  docs.push("Supports dry run.");
11188
11505
  if (effectBehaviors.dryRun.description) {
11189
11506
  docs.push(effectBehaviors.dryRun.description);
11190
11507
  }
11191
11508
  }
11192
- if (effectBehaviors.reverse) {
11509
+ if (effectBehaviors?.reverse) {
11193
11510
  if (effectBehaviors.reverse.handler) {
11194
11511
  docs.push(`Reverse handler: ${effectBehaviors.reverse.handler}.`);
11195
11512
  } else {
@@ -11248,13 +11565,21 @@ function buildEffectBehaviorMutations(toolPath, spec) {
11248
11565
  query: `mutation { at(path: ${JSON.stringify(toolPath)}) { set_approval_required(${args}) { kind } } }`
11249
11566
  });
11250
11567
  }
11568
+ if (spec.creates !== void 0) {
11569
+ mutations.push({
11570
+ label: `set creates on ${toolPath}`,
11571
+ query: `mutation { at(path: ${JSON.stringify(toolPath)}) { create_submodel(subpath: "creates", label: "creates") { set_string_value(value: ${JSON.stringify(
11572
+ JSON.stringify(spec.creates)
11573
+ )}) { done } } } }`
11574
+ });
11575
+ }
11251
11576
  return mutations;
11252
11577
  }
11253
11578
  function readMethodEffectBehaviors(rawMethod) {
11579
+ const metamodels = isObject(rawMethod.metamodels) ? rawMethod.metamodels : null;
11254
11580
  return {
11255
- effectBehaviors: normalizeEffectBehaviorSummary(
11256
- isObject(rawMethod.metamodels) ? rawMethod.metamodels : null
11257
- )
11581
+ effectBehaviors: normalizeEffectBehaviorSummary(metamodels),
11582
+ creates: normalizeCreationSummary(metamodels)
11258
11583
  };
11259
11584
  }
11260
11585
  var effectBehaviorsMetamodelPackage = defineMetamodelPackage({
@@ -11276,6 +11601,10 @@ var effectBehaviorsMetamodelPackage = defineMetamodelPackage({
11276
11601
  {
11277
11602
  key: "approvalRequired",
11278
11603
  description: "Boolean or `{ required, reason, mode }`."
11604
+ },
11605
+ {
11606
+ key: "creates",
11607
+ description: 'Marks a static method as an allowed creator for a class. Use `creates: "class_name"` or `{ className, idPath, pathPath, statePath, classStateHandle }`.'
11279
11608
  }
11280
11609
  ]
11281
11610
  },
@@ -11395,7 +11724,10 @@ var effectBehaviorsMetamodelPackage = defineMetamodelPackage({
11395
11724
  ...methodIR,
11396
11725
  docs: [
11397
11726
  ...methodIR.docs,
11398
- ...buildEffectBehaviorDocs(methodSummary.effectBehaviors)
11727
+ ...buildEffectBehaviorDocs(
11728
+ methodSummary.effectBehaviors,
11729
+ methodSummary.creates
11730
+ )
11399
11731
  ]
11400
11732
  };
11401
11733
  }
@@ -11636,15 +11968,50 @@ function toRecordSearchResult(className, node) {
11636
11968
  return [];
11637
11969
  }
11638
11970
  ) : [];
11971
+ const graphPathId = extractRecordIdFromGraphPath(path2, className);
11972
+ const realIdField = fields.find(
11973
+ (field) => normalizeGraphPathSegment(field.name) === "real_id" && typeof field.value === "string" && field.value.trim()
11974
+ );
11975
+ const id = typeof realIdField?.value === "string" ? realIdField.value.trim() : graphPathId;
11976
+ const rawLabel = typeof node.label === "string" && node.label.trim() ? node.label : "";
11977
+ if (fields.length === 0 && rawLabel && isPlaceholderRecordLabel(rawLabel, graphPathId, path2)) {
11978
+ return null;
11979
+ }
11980
+ const fallbackLabel = displayLabelFromFields(fields);
11981
+ const label = rawLabel && !isPlaceholderRecordLabel(rawLabel, id, path2) ? rawLabel : fallbackLabel || rawLabel || id;
11639
11982
  return {
11640
11983
  path: path2,
11641
11984
  className,
11642
- id: extractRecordIdFromGraphPath(path2, className),
11643
- label: typeof node.label === "string" && node.label.trim() ? node.label : extractRecordIdFromGraphPath(path2, className),
11985
+ id,
11986
+ label,
11644
11987
  description: typeof node.description === "string" && node.description.trim() ? node.description : null,
11645
11988
  fields
11646
11989
  };
11647
11990
  }
11991
+ function isPlaceholderRecordLabel(label, id, path2) {
11992
+ const normalizedLabel = normalizeGraphPathSegment(label);
11993
+ return normalizedLabel === normalizeGraphPathSegment(id) || normalizedLabel === normalizeGraphPathSegment(path2);
11994
+ }
11995
+ function displayLabelFromFields(fields) {
11996
+ const preferredFieldNames = [
11997
+ "name",
11998
+ "title",
11999
+ "label",
12000
+ "display_name",
12001
+ "file_name",
12002
+ "number",
12003
+ "code"
12004
+ ];
12005
+ for (const preferred of preferredFieldNames) {
12006
+ const match = fields.find(
12007
+ (field) => normalizeGraphPathSegment(field.name) === preferred && typeof field.value === "string" && field.value.trim()
12008
+ );
12009
+ if (typeof match?.value === "string") {
12010
+ return match.value.trim();
12011
+ }
12012
+ }
12013
+ return null;
12014
+ }
11648
12015
  function normalizeRecordSearchText(value) {
11649
12016
  return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, " ").replace(/\s+/g, " ").trim();
11650
12017
  }
@@ -12544,15 +12911,47 @@ var searchableMetamodelPackage = defineMetamodelPackage({
12544
12911
 
12545
12912
  // ../metamodel-state-machine/src/index.ts
12546
12913
  function normalizeStateMachines(values) {
12914
+ const parseJsonRecord = (value) => {
12915
+ if (value && typeof value === "object" && !Array.isArray(value)) {
12916
+ return value;
12917
+ }
12918
+ if (typeof value !== "string" || !value.trim()) return null;
12919
+ try {
12920
+ const parsed = JSON.parse(value);
12921
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
12922
+ } catch {
12923
+ return null;
12924
+ }
12925
+ };
12926
+ const parseJsonValue = (value) => {
12927
+ if (value === null || typeof value === "undefined") return null;
12928
+ if (typeof value !== "string") return value;
12929
+ if (!value.trim()) return null;
12930
+ try {
12931
+ return JSON.parse(value);
12932
+ } catch {
12933
+ return value;
12934
+ }
12935
+ };
12547
12936
  return (values || []).map((machine) => {
12548
12937
  const states = (machine?.states || []).map((state) => ({
12549
12938
  name: String(state?.name || ""),
12550
- isFinal: Boolean(state?.is_final)
12939
+ label: typeof state?.label === "string" ? state.label : null,
12940
+ description: typeof state?.description === "string" ? state.description : null,
12941
+ isFinal: Boolean(state?.is_final ?? state?.isFinal)
12551
12942
  })).filter((state) => state.name.length > 0);
12552
12943
  const transitions = (machine?.transitions || []).map((transition) => ({
12553
12944
  name: String(transition?.name || ""),
12554
12945
  from: String(transition?.from?.name || ""),
12555
- to: String(transition?.to?.name || "")
12946
+ to: String(transition?.to?.name || ""),
12947
+ label: typeof transition?.label === "string" ? transition.label : null,
12948
+ description: typeof transition?.description === "string" ? transition.description : null,
12949
+ action: parseJsonRecord(transition?.action) || parseJsonRecord(transition?.action_json),
12950
+ assignee: parseJsonRecord(transition?.assignee) || parseJsonRecord(transition?.assignee_json),
12951
+ requirements: parseJsonRecord(transition?.requirements) || parseJsonRecord(transition?.requirements_json),
12952
+ permission: parseJsonValue(transition?.permission) ?? parseJsonValue(transition?.permission_json),
12953
+ risk: transition?.risk === "low" || transition?.risk === "medium" || transition?.risk === "high" ? transition.risk : null,
12954
+ expectedOutcome: parseJsonValue(transition?.expectedOutcome) ?? parseJsonValue(transition?.expected_outcome_json)
12556
12955
  })).filter(
12557
12956
  (transition) => transition.name.length > 0 && transition.from.length > 0 && transition.to.length > 0
12558
12957
  );
@@ -12566,7 +12965,7 @@ function normalizeStateMachines(values) {
12566
12965
  }).filter((machine) => machine.name.length > 0);
12567
12966
  }
12568
12967
  function stateTypeName(className, machineName) {
12569
- return `${toPascalCase(className)}${toPascalCase(machineName)}`;
12968
+ return `${toPascalCase2(className)}${toPascalCase2(machineName)}`;
12570
12969
  }
12571
12970
  function transitionTypeName(className, machineName) {
12572
12971
  return `${stateTypeName(className, machineName)}Transition`;
@@ -12574,6 +12973,15 @@ function transitionTypeName(className, machineName) {
12574
12973
  function pathTypeName(className, machineName) {
12575
12974
  return `${stateTypeName(className, machineName)}Path`;
12576
12975
  }
12976
+ function methodToken(value) {
12977
+ const token = String(value || "").trim().replace(/[^A-Za-z0-9_]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
12978
+ return token || "state";
12979
+ }
12980
+ function transitionActionsForMachine(machine) {
12981
+ return Object.fromEntries(
12982
+ (machine.transitions || []).filter((transition) => transition.action?.effect).map((transition) => [transition.name, transition.action])
12983
+ );
12984
+ }
12577
12985
  function normalizeStateDefinitions(machine) {
12578
12986
  const finalStates = new Set(machine.finalStates || []);
12579
12987
  const states = /* @__PURE__ */ new Map();
@@ -12587,6 +12995,8 @@ function normalizeStateDefinitions(machine) {
12587
12995
  }
12588
12996
  states.set(rawState.name, {
12589
12997
  name: rawState.name,
12998
+ label: rawState.label,
12999
+ description: rawState.description,
12590
13000
  isFinal: Boolean(rawState.isFinal) || finalStates.has(rawState.name)
12591
13001
  });
12592
13002
  }
@@ -12598,6 +13008,44 @@ function normalizeStateDefinitions(machine) {
12598
13008
  }
12599
13009
  return [...states.values()];
12600
13010
  }
13011
+ function transitionMetadataGraphqlArgs(transition) {
13012
+ const args = [];
13013
+ if (typeof transition.label === "string") {
13014
+ args.push(`label: ${JSON.stringify(transition.label)}`);
13015
+ }
13016
+ if (typeof transition.description === "string") {
13017
+ args.push(`description: ${JSON.stringify(transition.description)}`);
13018
+ }
13019
+ if (transition.action) {
13020
+ args.push(
13021
+ `action_json: ${JSON.stringify(JSON.stringify(transition.action))}`
13022
+ );
13023
+ }
13024
+ if (transition.assignee) {
13025
+ args.push(
13026
+ `assignee_json: ${JSON.stringify(JSON.stringify(transition.assignee))}`
13027
+ );
13028
+ }
13029
+ if (transition.requirements) {
13030
+ args.push(
13031
+ `requirements_json: ${JSON.stringify(JSON.stringify(transition.requirements))}`
13032
+ );
13033
+ }
13034
+ if (transition.permission) {
13035
+ args.push(
13036
+ `permission_json: ${JSON.stringify(JSON.stringify(transition.permission))}`
13037
+ );
13038
+ }
13039
+ if (transition.risk) {
13040
+ args.push(`risk: ${JSON.stringify(transition.risk)}`);
13041
+ }
13042
+ if (transition.expectedOutcome) {
13043
+ args.push(
13044
+ `expected_outcome_json: ${JSON.stringify(JSON.stringify(transition.expectedOutcome))}`
13045
+ );
13046
+ }
13047
+ return args.length > 0 ? `, ${args.join(", ")}` : "";
13048
+ }
12601
13049
  function buildStateMachineModelMutations(modelPath, machines) {
12602
13050
  const mutations = [];
12603
13051
  for (const machine of machines || []) {
@@ -12608,12 +13056,13 @@ function buildStateMachineModelMutations(modelPath, machines) {
12608
13056
  )}, entry_state: ${JSON.stringify(machine.entryState)}) { name } } }`
12609
13057
  });
12610
13058
  for (const state of normalizeStateDefinitions(machine)) {
12611
- if (state.name === machine.entryState && !state.isFinal) continue;
13059
+ if (state.name === machine.entryState && !state.isFinal && !state.label && !state.description)
13060
+ continue;
12612
13061
  mutations.push({
12613
13062
  label: `add state ${state.name} on ${modelPath}.${machine.name}`,
12614
13063
  query: `mutation { at(path: ${JSON.stringify(modelPath)}) { state_machine(name: ${JSON.stringify(
12615
13064
  machine.name
12616
- )}) { add_state(name: ${JSON.stringify(state.name)}, is_final: ${state.isFinal}) { name } } } }`
13065
+ )}) { add_state(name: ${JSON.stringify(state.name)}, is_final: ${state.isFinal}, label: ${JSON.stringify(state.label || null)}, description: ${JSON.stringify(state.description || null)}) { name } } } }`
12617
13066
  });
12618
13067
  }
12619
13068
  for (const transition of machine.transitions || []) {
@@ -12625,7 +13074,7 @@ function buildStateMachineModelMutations(modelPath, machines) {
12625
13074
  transition.name
12626
13075
  )}, from: ${JSON.stringify(transition.from)}, to: ${JSON.stringify(
12627
13076
  transition.to
12628
- )}) { name } } } }`
13077
+ )}${transitionMetadataGraphqlArgs(transition)}) { name } } } }`
12629
13078
  });
12630
13079
  }
12631
13080
  }
@@ -12652,7 +13101,7 @@ function buildMachineMethods(classSummary, machine) {
12652
13101
  const transitionName = transitionTypeName(classSummary.name, machine.name);
12653
13102
  pathTypeName(classSummary.name, machine.name);
12654
13103
  const docsPrefix = `${classSummary.name}.${machine.name}`;
12655
- return [
13104
+ const methods = [
12656
13105
  {
12657
13106
  name: `get_${machine.name}`,
12658
13107
  docs: [`Get the current ${docsPrefix} state.`],
@@ -12675,7 +13124,7 @@ function buildMachineMethods(classSummary, machine) {
12675
13124
  ],
12676
13125
  static: false,
12677
13126
  params: [{ name: "target", type: stateName }],
12678
- returnType: `Promise<${toPascalCase(classSummary.name)}>`,
13127
+ returnType: `Promise<${toPascalCase2(classSummary.name)}>`,
12679
13128
  runtime: {
12680
13129
  kind: "state_machine",
12681
13130
  machineName: machine.name,
@@ -12748,6 +13197,99 @@ function buildMachineMethods(classSummary, machine) {
12748
13197
  }
12749
13198
  }
12750
13199
  ];
13200
+ const creationMethods = (classSummary.methods || []).filter(
13201
+ (method) => method.static === true && Boolean(method.creates) && method.creates?.className === classSummary.name && typeof method.effectKey === "string" && method.effectKey.length > 0
13202
+ );
13203
+ for (const state of machine.states) {
13204
+ const stateNameValue = typeof state === "string" ? state : String(state?.name || "");
13205
+ if (!stateNameValue) continue;
13206
+ const token = methodToken(stateNameValue);
13207
+ methods.push(
13208
+ {
13209
+ name: `reach_${machine.name}_to_${token}`,
13210
+ docs: [`Reach ${docsPrefix} state ${stateNameValue}.`],
13211
+ static: false,
13212
+ params: [],
13213
+ returnType: `Promise<${toPascalCase2(classSummary.name)}>`,
13214
+ runtime: {
13215
+ kind: "state_machine",
13216
+ machineName: machine.name,
13217
+ className: classSummary.name,
13218
+ stateTypeName: stateName,
13219
+ transitionTypeName: transitionName,
13220
+ operation: "reach",
13221
+ targetState: stateNameValue,
13222
+ transitionActions: transitionActionsForMachine(machine)
13223
+ }
13224
+ },
13225
+ {
13226
+ name: `prepare_${machine.name}_to_${token}`,
13227
+ docs: [
13228
+ `Prepare a reviewable artifact that can move ${docsPrefix} to ${stateNameValue}.`
13229
+ ],
13230
+ static: false,
13231
+ params: [],
13232
+ returnType: "Promise<SessionArtifactRecord>",
13233
+ runtime: {
13234
+ kind: "state_machine",
13235
+ machineName: machine.name,
13236
+ className: classSummary.name,
13237
+ stateTypeName: stateName,
13238
+ transitionTypeName: transitionName,
13239
+ operation: "prepare_reach",
13240
+ targetState: stateNameValue,
13241
+ transitionActions: transitionActionsForMachine(machine)
13242
+ }
13243
+ }
13244
+ );
13245
+ for (const creationMethod of creationMethods) {
13246
+ const creationRuntime = {
13247
+ kind: "state_machine",
13248
+ machineName: machine.name,
13249
+ className: classSummary.name,
13250
+ stateTypeName: stateName,
13251
+ transitionTypeName: transitionName,
13252
+ operation: "prepare_create_reach",
13253
+ targetState: stateNameValue,
13254
+ transitionActions: transitionActionsForMachine(machine),
13255
+ creation: {
13256
+ methodName: creationMethod.name,
13257
+ effectKey: creationMethod.effectKey || creationMethod.name,
13258
+ inputSchema: creationMethod.inputSchema,
13259
+ outputSchema: creationMethod.outputSchema,
13260
+ creates: creationMethod.creates
13261
+ }
13262
+ };
13263
+ const viaName = `prepare_${machine.name}_to_${token}_via_${methodToken(creationMethod.name)}`;
13264
+ methods.push({
13265
+ name: viaName,
13266
+ docs: [
13267
+ `Prepare a reviewable artifact that will create a new ${classSummary.name} through ${creationMethod.name}, then move ${docsPrefix} to ${stateNameValue}.`
13268
+ ],
13269
+ static: true,
13270
+ params: [
13271
+ { name: "input", type: "Record<string, any>", optional: true }
13272
+ ],
13273
+ returnType: "Promise<SessionArtifactRecord>",
13274
+ runtime: creationRuntime
13275
+ });
13276
+ if (creationMethods.length === 1) {
13277
+ methods.push({
13278
+ name: `prepare_${machine.name}_to_${token}`,
13279
+ docs: [
13280
+ `Prepare a reviewable artifact that will create a new ${classSummary.name}, then move ${docsPrefix} to ${stateNameValue}.`
13281
+ ],
13282
+ static: true,
13283
+ params: [
13284
+ { name: "input", type: "Record<string, any>", optional: true }
13285
+ ],
13286
+ returnType: "Promise<SessionArtifactRecord>",
13287
+ runtime: creationRuntime
13288
+ });
13289
+ }
13290
+ }
13291
+ }
13292
+ return methods;
12751
13293
  }
12752
13294
  function readStateMachineSummaries(rawClass) {
12753
13295
  return {
@@ -12770,8 +13312,8 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12770
13312
  type StateMachineMutation {
12771
13313
  name: String!
12772
13314
  state_machine: StateMachine!
12773
- add_state(name: String!, is_final: Boolean): StateMachineMutation!
12774
- add_transition(name: String!, from: String!, to: String!): StateMachineMutation!
13315
+ add_state(name: String!, is_final: Boolean, label: String, description: String): StateMachineMutation!
13316
+ add_transition(name: String!, from: String!, to: String!, label: String, description: String, action_json: String, assignee_json: String, requirements_json: String, permission_json: String, risk: String, expected_outcome_json: String): StateMachineMutation!
12775
13317
  activate_transition(name: String!): StateMachineMutation!
12776
13318
  }
12777
13319
 
@@ -12788,6 +13330,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12788
13330
  type StateMachineSnapshotMutation {
12789
13331
  snapshot: StateMachineSnapshot!
12790
13332
  activate_transition(name: String!): StateMachineSnapshotMutation!
13333
+ observe_state(state: String!, force: Boolean, source: String): StateMachineSnapshotMutation!
12791
13334
  }
12792
13335
 
12793
13336
  type StateMachine {
@@ -12807,6 +13350,8 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12807
13350
 
12808
13351
  type StateMachineState {
12809
13352
  name: String!
13353
+ label: String
13354
+ description: String
12810
13355
  is_final: Boolean!
12811
13356
  }
12812
13357
 
@@ -12814,6 +13359,14 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12814
13359
  name: String!
12815
13360
  from: StateMachineState!
12816
13361
  to: StateMachineState!
13362
+ label: String
13363
+ description: String
13364
+ action_json: String
13365
+ assignee_json: String
13366
+ requirements_json: String
13367
+ permission_json: String
13368
+ risk: String
13369
+ expected_outcome_json: String
12817
13370
  }
12818
13371
 
12819
13372
  type StateMachinePath {
@@ -12866,23 +13419,47 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12866
13419
  StateMachineMutation: {
12867
13420
  name: (value) => value.name,
12868
13421
  state_machine: async (value) => await run(value.target.state_machine(value.name)),
12869
- add_state: async (value, { name, is_final }) => {
13422
+ add_state: async (value, { name, is_final, label, description }) => {
12870
13423
  await run(
12871
13424
  value.target.add_state_machine_state(
12872
13425
  value.name,
12873
13426
  name,
12874
- is_final ?? false
13427
+ is_final ?? false,
13428
+ label,
13429
+ description
12875
13430
  )
12876
13431
  );
12877
13432
  return value;
12878
13433
  },
12879
- add_transition: async (value, { name, from, to }) => {
13434
+ add_transition: async (value, {
13435
+ name,
13436
+ from,
13437
+ to,
13438
+ label,
13439
+ description,
13440
+ action_json,
13441
+ assignee_json,
13442
+ requirements_json,
13443
+ permission_json,
13444
+ risk,
13445
+ expected_outcome_json
13446
+ }) => {
12880
13447
  await run(
12881
13448
  value.target.add_state_machine_transition(
12882
13449
  value.name,
12883
13450
  name,
12884
13451
  from,
12885
- to
13452
+ to,
13453
+ {
13454
+ label,
13455
+ description,
13456
+ actionJson: action_json,
13457
+ assigneeJson: assignee_json,
13458
+ requirementsJson: requirements_json,
13459
+ permissionJson: permission_json,
13460
+ risk,
13461
+ expectedOutcomeJson: expected_outcome_json
13462
+ }
12886
13463
  )
12887
13464
  );
12888
13465
  return value;
@@ -12901,16 +13478,37 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12901
13478
  value.target.activate_state_machine_transition(value.name, name)
12902
13479
  );
12903
13480
  return value;
13481
+ },
13482
+ observe_state: async (value, { state, force, source }) => {
13483
+ await run(
13484
+ value.target.observe_state_machine_state(
13485
+ value.name,
13486
+ state,
13487
+ force === true,
13488
+ source
13489
+ )
13490
+ );
13491
+ return value;
12904
13492
  }
12905
13493
  },
12906
13494
  StateMachineState: {
12907
13495
  name: (value) => value.name,
13496
+ label: (value) => value.label || null,
13497
+ description: (value) => value.description || null,
12908
13498
  is_final: (value) => value.is_final
12909
13499
  },
12910
13500
  StateMachineTransition: {
12911
13501
  name: (value) => value.name,
12912
13502
  from: (value) => value.from_state || { name: value.from, is_final: false },
12913
- to: (value) => value.to_state || { name: value.to, is_final: false }
13503
+ to: (value) => value.to_state || { name: value.to, is_final: false },
13504
+ label: (value) => value.label || null,
13505
+ description: (value) => value.description || null,
13506
+ action_json: (value) => value.action_json || null,
13507
+ assignee_json: (value) => value.assignee_json || null,
13508
+ requirements_json: (value) => value.requirements_json || null,
13509
+ permission_json: (value) => value.permission_json || null,
13510
+ risk: (value) => value.risk || null,
13511
+ expected_outcome_json: (value) => value.expected_outcome_json || null
12914
13512
  },
12915
13513
  StateMachinePath: {
12916
13514
  states: (value) => value.states,
@@ -12977,6 +13575,14 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12977
13575
  name
12978
13576
  from { name }
12979
13577
  to { name }
13578
+ label
13579
+ description
13580
+ action_json
13581
+ assignee_json
13582
+ requirements_json
13583
+ permission_json
13584
+ risk
13585
+ expected_outcome_json
12980
13586
  }
12981
13587
  }`
12982
13588
  ]
@@ -13230,6 +13836,26 @@ var STANDARD_MODULES_OPERATIONS = [
13230
13836
  var BUILTIN_MODULES = {
13231
13837
  standard_modules: STANDARD_MODULES_OPERATIONS
13232
13838
  };
13839
+ function stateNameFromMethodName(methodName) {
13840
+ const raw = methodName.startsWith("to") ? methodName.slice(2) : methodName;
13841
+ return raw.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
13842
+ }
13843
+ function appendQueryOptions(searchParams, query) {
13844
+ for (const [key, value] of Object.entries(query || {})) {
13845
+ if (value === null || typeof value === "undefined" || value === "") {
13846
+ continue;
13847
+ }
13848
+ if (value instanceof Date) {
13849
+ searchParams.set(key, value.toISOString());
13850
+ continue;
13851
+ }
13852
+ if (Array.isArray(value)) {
13853
+ if (value.length > 0) searchParams.set(key, value.join(","));
13854
+ continue;
13855
+ }
13856
+ searchParams.set(key, String(value));
13857
+ }
13858
+ }
13233
13859
  var DEFAULT_DIRECT_RECORD_OBJECTS_REQUEST_BATCH_SIZE = 100;
13234
13860
  var MAX_RECORD_OBJECTS_CONCURRENCY = 16;
13235
13861
  var DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_COUNT = 3;
@@ -13260,8 +13886,20 @@ function bodyInitFromSessionFileUpload(body) {
13260
13886
  return body;
13261
13887
  }
13262
13888
  var EFFECT_CATALOG_SYNC_TIMEOUT_MS = 12e4;
13889
+ var EFFECT_CATALOG_SYNC_BATCH_SIZE = 20;
13263
13890
  var EFFECT_CATALOG_SYNC_RETRY_COUNT = 3;
13264
13891
  var EFFECT_CATALOG_SYNC_RETRY_DELAY_MS = 1e3;
13892
+ function chunkItems(items, batchSize) {
13893
+ const chunks = [];
13894
+ for (let offset = 0; offset < items.length; offset += batchSize) {
13895
+ chunks.push(items.slice(offset, offset + batchSize));
13896
+ }
13897
+ return chunks;
13898
+ }
13899
+ function isUnsupportedEffectCatalogMutation(error) {
13900
+ const message = error instanceof Error ? error.message : String(error);
13901
+ return message.includes("Unknown RPC method: effects.resetCatalog") || message.includes("Unknown RPC method: effects.addCatalog") || message.includes("Method not found: effects.resetCatalog") || message.includes("Method not found: effects.addCatalog");
13902
+ }
13265
13903
  function planRecordObjectsChunks(records, batchSize) {
13266
13904
  const total = records.length;
13267
13905
  const size = Math.max(1, Math.min(batchSize, total));
@@ -13273,6 +13911,23 @@ function planRecordObjectsChunks(records, batchSize) {
13273
13911
  }
13274
13912
  return plans;
13275
13913
  }
13914
+ function preserveRecordObjectRealId(record) {
13915
+ const realId = record.id.trim();
13916
+ if (!realId) {
13917
+ return record;
13918
+ }
13919
+ const fields = record.fields || {};
13920
+ if (typeof fields.real_id === "string" && fields.real_id.trim()) {
13921
+ return record;
13922
+ }
13923
+ return {
13924
+ ...record,
13925
+ fields: {
13926
+ ...fields,
13927
+ real_id: realId
13928
+ }
13929
+ };
13930
+ }
13276
13931
  function computeEffectKey2(effect) {
13277
13932
  const attachedClass = effect.className?.trim();
13278
13933
  if (!attachedClass) {
@@ -13510,11 +14165,105 @@ var Environment = class _Environment {
13510
14165
  getAwaitingCount: async () => this.getAwaitingRecordCount()
13511
14166
  };
13512
14167
  }
14168
+ /**
14169
+ * Mirror product-owned workflow state into Granular without making Granular
14170
+ * own the customer application's state machine.
14171
+ */
14172
+ async recordState(input) {
14173
+ const { machine, state, ...target } = input;
14174
+ if (!machine.trim()) {
14175
+ throw new Error("State update requires a machine name");
14176
+ }
14177
+ if (!state.trim()) {
14178
+ throw new Error("State update requires a state");
14179
+ }
14180
+ return this.recordObject({
14181
+ className: target.className,
14182
+ id: target.id,
14183
+ ...target.label ? { label: target.label } : {},
14184
+ ...target.fields ? { fields: target.fields } : {},
14185
+ ...target.relationships ? { relationships: target.relationships } : {},
14186
+ states: {
14187
+ [machine.trim()]: {
14188
+ state: state.trim(),
14189
+ ...target.source ? { source: target.source } : {},
14190
+ ...target.cause ? { cause: target.cause } : {},
14191
+ ...target.actorId ? { actorId: target.actorId } : {},
14192
+ ...target.observedAt !== void 0 ? { observedAt: target.observedAt } : {},
14193
+ ...target.force !== void 0 ? { force: target.force } : {},
14194
+ ...target.metadata ? { metadata: target.metadata } : {}
14195
+ }
14196
+ }
14197
+ });
14198
+ }
14199
+ /**
14200
+ * Mirror product-owned workflow state into Granular without making Granular
14201
+ * own the customer application's state machine.
14202
+ *
14203
+ * Example:
14204
+ * `await env.recordState({ className: "spend_request", id, machine: "lifecycle", state: "policy_review", source: "customer_backend" })`
14205
+ */
14206
+ state(target) {
14207
+ const observe = async (machineName, stateName, input = {}) => {
14208
+ const observedState = input.observedState || input.state || stateName;
14209
+ if (!observedState) {
14210
+ throw new Error("State observation requires a target state");
14211
+ }
14212
+ return this.recordState({
14213
+ ...target,
14214
+ machine: machineName,
14215
+ state: observedState,
14216
+ ...input.source ? { source: input.source } : {},
14217
+ ...input.cause ? { cause: input.cause } : {},
14218
+ ...input.actorId ? { actorId: input.actorId } : {},
14219
+ ...input.observedAt !== void 0 ? { observedAt: input.observedAt } : {},
14220
+ ...input.force !== void 0 ? { force: input.force } : {},
14221
+ ...input.metadata ? { metadata: input.metadata } : {}
14222
+ });
14223
+ };
14224
+ return new Proxy(
14225
+ {},
14226
+ {
14227
+ get: (_target, machineProperty) => {
14228
+ if (typeof machineProperty !== "string") return void 0;
14229
+ return new Proxy(
14230
+ {},
14231
+ {
14232
+ get: (_machineTarget, stateProperty) => {
14233
+ if (stateProperty === "to") {
14234
+ return (stateName, input) => observe(machineProperty, stateName, input || {});
14235
+ }
14236
+ if (typeof stateProperty !== "string") return void 0;
14237
+ return (input) => observe(
14238
+ machineProperty,
14239
+ stateNameFromMethodName(stateProperty),
14240
+ input || {}
14241
+ );
14242
+ }
14243
+ }
14244
+ );
14245
+ }
14246
+ }
14247
+ );
14248
+ }
13513
14249
  get feedback() {
13514
14250
  return {
13515
14251
  list: async () => this.listFeedback()
13516
14252
  };
13517
14253
  }
14254
+ get manualActions() {
14255
+ return {
14256
+ record: (input) => this.recordManualAction(input),
14257
+ list: (options = {}) => this.listManualActions(options),
14258
+ suggest: (options = {}) => this.suggestManualActions(options)
14259
+ };
14260
+ }
14261
+ get artifactApprovals() {
14262
+ return {
14263
+ list: (options = {}) => this.listArtifactApprovals(options),
14264
+ decide: (approvalTaskId, input) => this.decideArtifactApproval(approvalTaskId, input)
14265
+ };
14266
+ }
13518
14267
  /**
13519
14268
  * Sessionless environments do not own a live transport, so disconnecting the
13520
14269
  * environment handle itself is a no-op. This keeps the public surface
@@ -13593,6 +14342,50 @@ var Environment = class _Environment {
13593
14342
  const response = await this.controlPlaneRequest(`/control/environments/${this.environmentId}/feedback`);
13594
14343
  return Array.isArray(response.items) ? response.items : [];
13595
14344
  }
14345
+ async recordManualAction(input) {
14346
+ const body = {
14347
+ ...input,
14348
+ ...input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {}
14349
+ };
14350
+ return this.controlPlaneRequest(
14351
+ `/control/environments/${this.environmentId}/manual-actions`,
14352
+ {
14353
+ method: "POST",
14354
+ body: JSON.stringify(body)
14355
+ }
14356
+ );
14357
+ }
14358
+ async listManualActions(options = {}) {
14359
+ const query = new URLSearchParams();
14360
+ appendQueryOptions(query, options);
14361
+ const suffix = query.toString() ? `?${query.toString()}` : "";
14362
+ return this.controlPlaneRequest(`/control/environments/${this.environmentId}/manual-actions${suffix}`);
14363
+ }
14364
+ async suggestManualActions(options = {}) {
14365
+ const query = new URLSearchParams();
14366
+ appendQueryOptions(query, options);
14367
+ const suffix = query.toString() ? `?${query.toString()}` : "";
14368
+ return this.controlPlaneRequest(
14369
+ `/control/environments/${this.environmentId}/manual-actions/suggestions${suffix}`
14370
+ );
14371
+ }
14372
+ async listArtifactApprovals(options = {}) {
14373
+ const query = new URLSearchParams();
14374
+ appendQueryOptions(query, options);
14375
+ const suffix = query.toString() ? `?${query.toString()}` : "";
14376
+ return this.controlPlaneRequest(
14377
+ `/control/environments/${this.environmentId}/artifact-approvals${suffix}`
14378
+ );
14379
+ }
14380
+ async decideArtifactApproval(approvalTaskId, input) {
14381
+ return this.controlPlaneRequest(
14382
+ `/control/environments/${this.environmentId}/artifact-approvals/${encodeURIComponent(approvalTaskId)}/decide`,
14383
+ {
14384
+ method: "POST",
14385
+ body: JSON.stringify(input)
14386
+ }
14387
+ );
14388
+ }
13596
14389
  getRuntimeBaseUrl() {
13597
14390
  return deriveRuntimeBaseUrl(this._apiEndpoint);
13598
14391
  }
@@ -14417,10 +15210,11 @@ var Environment = class _Environment {
14417
15210
  if (!Array.isArray(records) || records.length === 0) {
14418
15211
  return [];
14419
15212
  }
15213
+ const recordsToWrite = records.map(preserveRecordObjectRealId);
14420
15214
  const batchSize = Math.max(
14421
15215
  1,
14422
15216
  Math.min(
14423
- records.length,
15217
+ recordsToWrite.length,
14424
15218
  options?.batchSize ?? DEFAULT_DIRECT_RECORD_OBJECTS_REQUEST_BATCH_SIZE
14425
15219
  )
14426
15220
  );
@@ -14428,8 +15222,8 @@ var Environment = class _Environment {
14428
15222
  MAX_RECORD_OBJECTS_CONCURRENCY,
14429
15223
  Math.max(1, options?.concurrency ?? 1)
14430
15224
  );
14431
- const plans = planRecordObjectsChunks(records, batchSize);
14432
- const total = records.length;
15225
+ const plans = planRecordObjectsChunks(recordsToWrite, batchSize);
15226
+ const total = recordsToWrite.length;
14433
15227
  const results = new Array(total);
14434
15228
  const onChunk = options?.onChunkComplete;
14435
15229
  for (let waveStart = 0; waveStart < plans.length; waveStart += concurrency) {
@@ -14500,12 +15294,13 @@ var Environment = class _Environment {
14500
15294
  * synchronous upserts and fine-grained chunk progress via **`onChunkComplete`**.
14501
15295
  */
14502
15296
  async enqueueRecordImport(records, options = {}) {
15297
+ const recordsToImport = records.map(preserveRecordObjectRealId);
14503
15298
  return this.controlPlaneRequest(
14504
15299
  `/control/environments/${this.environmentId}/record-imports`,
14505
15300
  {
14506
15301
  method: "POST",
14507
15302
  body: JSON.stringify({
14508
- records,
15303
+ records: recordsToImport,
14509
15304
  batchSize: options.batchSize,
14510
15305
  setupRunId: options.setupRunId,
14511
15306
  writeMode: options.writeMode
@@ -14613,11 +15408,7 @@ var EnvironmentSession = class extends Session {
14613
15408
  }
14614
15409
  buildSessionDataUrl(path2, query) {
14615
15410
  const searchParams = new URLSearchParams();
14616
- for (const [key, value] of Object.entries(query || {})) {
14617
- if (value !== null && typeof value !== "undefined" && value !== "") {
14618
- searchParams.set(key, String(value));
14619
- }
14620
- }
15411
+ appendQueryOptions(searchParams, query);
14621
15412
  const queryString = searchParams.toString();
14622
15413
  return `${this.environment.runtimeBaseUrl}${this.sessionDataRoutePrefix}/${encodeURIComponent(this.sessionId)}${path2}${queryString ? `?${queryString}` : ""}`;
14623
15414
  }
@@ -14700,9 +15491,108 @@ var EnvironmentSession = class extends Session {
14700
15491
  ),
14701
15492
  get: (jobId) => this.sessionDataRequest(
14702
15493
  `/jobs/${encodeURIComponent(jobId)}`
15494
+ ),
15495
+ latest: async (options = {}) => {
15496
+ const page = await this.sessionDataRequest("/jobs", {
15497
+ status: options.status || "all",
15498
+ latest: true,
15499
+ limit: 1
15500
+ });
15501
+ return page.items[0] || null;
15502
+ }
15503
+ };
15504
+ }
15505
+ get artifacts() {
15506
+ return {
15507
+ list: (options = {}) => {
15508
+ const queryOptions = { ...options };
15509
+ if (options.target) {
15510
+ queryOptions.targetClassName = options.target.className;
15511
+ queryOptions.targetId = options.target.id;
15512
+ delete queryOptions.target;
15513
+ }
15514
+ return this.sessionDataRequest("/artifacts", queryOptions);
15515
+ },
15516
+ listForLatestJob: (options = {}) => this.artifacts.list({
15517
+ ...options,
15518
+ latestJob: true
15519
+ }),
15520
+ get: (artifactId) => this.sessionDataRequest(
15521
+ `/artifacts/${encodeURIComponent(artifactId)}`
15522
+ ),
15523
+ create: (artifact) => this.sessionDataRequest(
15524
+ "/artifacts",
15525
+ void 0,
15526
+ {
15527
+ method: "POST",
15528
+ body: artifact
15529
+ }
15530
+ ),
15531
+ updateInputs: (artifactId, patch) => this.sessionDataRequest(
15532
+ `/artifacts/${encodeURIComponent(artifactId)}`,
15533
+ void 0,
15534
+ {
15535
+ method: "PATCH",
15536
+ body: patch
15537
+ }
15538
+ ),
15539
+ validate: (artifactId) => this.sessionDataRequest(
15540
+ `/artifacts/${encodeURIComponent(artifactId)}/validate`,
15541
+ void 0,
15542
+ { method: "POST" }
15543
+ ),
15544
+ execute: (artifactId, options) => this.sessionDataRequest(
15545
+ `/artifacts/${encodeURIComponent(artifactId)}/execute`,
15546
+ void 0,
15547
+ { method: "POST", body: options }
15548
+ ),
15549
+ approve: (artifactId, options) => this.sessionDataRequest(
15550
+ `/artifacts/${encodeURIComponent(artifactId)}/approve`,
15551
+ void 0,
15552
+ { method: "POST", body: options }
15553
+ ),
15554
+ cancel: (artifactId) => this.sessionDataRequest(
15555
+ `/artifacts/${encodeURIComponent(artifactId)}/cancel`,
15556
+ void 0,
15557
+ { method: "POST" }
14703
15558
  )
14704
15559
  };
14705
15560
  }
15561
+ get manualActions() {
15562
+ const useDelegatedBrowserRoute = this.sessionDataRoutePrefix === "/sdk/browser-sessions";
15563
+ return {
15564
+ record: (input) => useDelegatedBrowserRoute ? this.sessionDataRequest(
15565
+ "/manual-actions",
15566
+ void 0,
15567
+ {
15568
+ method: "POST",
15569
+ body: { ...input, sessionId: this.sessionId }
15570
+ }
15571
+ ) : this.environment.manualActions.record({
15572
+ ...input,
15573
+ sessionId: this.sessionId
15574
+ }),
15575
+ list: (options = {}) => useDelegatedBrowserRoute ? this.sessionDataRequest("/manual-actions", { ...options, sessionId: this.sessionId }) : this.environment.manualActions.list({
15576
+ ...options,
15577
+ sessionId: this.sessionId
15578
+ }),
15579
+ suggest: (options = {}) => useDelegatedBrowserRoute ? this.sessionDataRequest(
15580
+ "/manual-actions/suggestions",
15581
+ options
15582
+ ) : this.environment.manualActions.suggest(options)
15583
+ };
15584
+ }
15585
+ get artifactApprovals() {
15586
+ const useDelegatedBrowserRoute = this.sessionDataRoutePrefix === "/sdk/browser-sessions";
15587
+ return {
15588
+ list: (options = {}) => useDelegatedBrowserRoute ? this.sessionDataRequest("/artifact-approvals", options) : this.environment.artifactApprovals.list(options),
15589
+ decide: (approvalTaskId, input) => useDelegatedBrowserRoute ? this.sessionDataRequest(
15590
+ `/artifact-approvals/${encodeURIComponent(approvalTaskId)}/decide`,
15591
+ void 0,
15592
+ { method: "POST", body: input }
15593
+ ) : this.environment.artifactApprovals.decide(approvalTaskId, input)
15594
+ };
15595
+ }
14706
15596
  get files() {
14707
15597
  return {
14708
15598
  list: (options = {}) => this.sessionDataRequest(
@@ -14783,13 +15673,16 @@ var EnvironmentSession = class extends Session {
14783
15673
  get transcript() {
14784
15674
  return {
14785
15675
  list: async (options = {}) => {
14786
- const [messages, jobs, entries, lists] = await Promise.all([
15676
+ const [messages, jobs, entries, lists, artifacts] = await Promise.all([
14787
15677
  this.collectAllSessionItems(this.messages.list),
14788
15678
  this.collectAllSessionItems(
14789
15679
  (pageOptions) => this.jobs.list({ ...pageOptions, status: "all" })
14790
15680
  ),
14791
15681
  this.collectAllSessionItems(this.heap.entries.list),
14792
- this.collectAllSessionItems(this.heap.lists.list)
15682
+ this.collectAllSessionItems(this.heap.lists.list),
15683
+ this.collectAllSessionItems(
15684
+ (pageOptions) => this.artifacts.list({ ...pageOptions, status: "all" })
15685
+ )
14793
15686
  ]);
14794
15687
  const liveDoc = {
14795
15688
  conversation: { messages },
@@ -14803,6 +15696,21 @@ var EnvironmentSession = class extends Session {
14803
15696
  (entry) => Boolean(entry)
14804
15697
  )
14805
15698
  )
15699
+ },
15700
+ artifacts: {
15701
+ byId: Object.fromEntries(
15702
+ artifacts.map((artifact) => {
15703
+ return artifact?.artifactId ? [
15704
+ artifact.artifactId,
15705
+ artifact
15706
+ ] : null;
15707
+ }).filter(
15708
+ (entry) => Boolean(entry)
15709
+ )
15710
+ ),
15711
+ order: artifacts.map((artifact) => artifact?.artifactId).filter(
15712
+ (artifactId) => Boolean(artifactId)
15713
+ )
14806
15714
  }
14807
15715
  };
14808
15716
  const heap = normalizeHeapSnapshot({
@@ -14879,6 +15787,12 @@ var EnvironmentSession = class extends Session {
14879
15787
  async recordObject(options) {
14880
15788
  return this.environment.recordObject(options);
14881
15789
  }
15790
+ async recordState(input) {
15791
+ return this.environment.recordState(input);
15792
+ }
15793
+ state(target) {
15794
+ return this.environment.state(target);
15795
+ }
14882
15796
  async recordObjects(records, options) {
14883
15797
  return this.environment.recordObjects(records, options);
14884
15798
  }
@@ -15706,15 +16620,43 @@ var Granular = class _Granular {
15706
16620
  const effects = Array.from(
15707
16621
  this.getSandboxEffectMap(host.sandboxId).values()
15708
16622
  ).map((effect) => this.serializeEffect(effect));
15709
- const result = await withTimeout(
15710
- host.wsClient.call("effects.publishCatalog", {
15711
- effects
15712
- }),
15713
- EFFECT_CATALOG_SYNC_TIMEOUT_MS,
15714
- `effects.publishCatalog for sandbox ${host.sandboxId}`
15715
- );
15716
- const acceptedCount = typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
15717
- const rejected = Array.isArray(result?.rejected) ? result.rejected : [];
16623
+ let acceptedCount = 0;
16624
+ const rejected = [];
16625
+ try {
16626
+ await withTimeout(
16627
+ host.wsClient.call("effects.resetCatalog", {}),
16628
+ EFFECT_CATALOG_SYNC_TIMEOUT_MS,
16629
+ `effects.resetCatalog for sandbox ${host.sandboxId}`
16630
+ );
16631
+ for (const batch of chunkItems(effects, EFFECT_CATALOG_SYNC_BATCH_SIZE)) {
16632
+ const result = await withTimeout(
16633
+ host.wsClient.call("effects.addCatalog", {
16634
+ effects: batch
16635
+ }),
16636
+ EFFECT_CATALOG_SYNC_TIMEOUT_MS,
16637
+ `effects.addCatalog for sandbox ${host.sandboxId}`
16638
+ );
16639
+ acceptedCount += typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
16640
+ if (Array.isArray(result?.rejected)) {
16641
+ rejected.push(...result.rejected);
16642
+ }
16643
+ }
16644
+ } catch (error) {
16645
+ if (!isUnsupportedEffectCatalogMutation(error)) {
16646
+ throw error;
16647
+ }
16648
+ const result = await withTimeout(
16649
+ host.wsClient.call("effects.publishCatalog", {
16650
+ effects
16651
+ }),
16652
+ EFFECT_CATALOG_SYNC_TIMEOUT_MS,
16653
+ `effects.publishCatalog for sandbox ${host.sandboxId}`
16654
+ );
16655
+ acceptedCount = typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
16656
+ if (Array.isArray(result?.rejected)) {
16657
+ rejected.push(...result.rejected);
16658
+ }
16659
+ }
15718
16660
  if (acceptedCount === 0 && rejected.length > 0) {
15719
16661
  const detail = rejected.map(
15720
16662
  (entry) => `${entry.name || "unknown"}: ${entry.reason || "rejected"}`
@@ -16940,6 +17882,7 @@ var HARNESS_V3_FRONTEND_ACTIONS_MODULE = "@granular/actions/frontend";
16940
17882
  var HARNESS_V3_CSV_MODULE = "@granular/utils/csv";
16941
17883
  var HARNESS_V3_XLSX_MODULE = "@granular/utils/xlsx";
16942
17884
  var LEGACY_SANDBOX_TOOLS_MODULE_PATTERN = "\\.\\/sandbox-tools(?:\\.js)?";
17885
+ var HARNESS_V3_RUNTIME_MODULE_PATTERN = "@granular/(?:agent|session|domain(?:/[A-Za-z_$][\\w$]*)?|actions/(?:backend|frontend)|utils/(?:csv|xlsx))";
16943
17886
  function hasNamedModuleImport(source, moduleName, name) {
16944
17887
  const escapedModule = moduleName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
16945
17888
  const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -16998,6 +17941,15 @@ function reviewGeneratedJobCode(code, _options = {}) {
16998
17941
  message: "Generated code must use static top-level ESM imports from the Harness v3 runtime modules. Do not use dynamic import(...)."
16999
17942
  });
17000
17943
  }
17944
+ if (new RegExp(
17945
+ `import\\s+\\*\\s+as\\s+[A-Za-z_$][\\w$]*\\s+from\\s*['"]${HARNESS_V3_RUNTIME_MODULE_PATTERN}['"]`
17946
+ ).test(normalized)) {
17947
+ issues.push({
17948
+ code: "runtime_namespace_import",
17949
+ severity: "error",
17950
+ message: 'Generated code must use named imports from Harness runtime modules. Do not use namespace imports such as `import * as agent from "@granular/agent"`; use `import { replyToUser, artifacts } from "@granular/agent"`.'
17951
+ });
17952
+ }
17001
17953
  if (/\bprocess\.exit\s*\(/.test(normalized)) {
17002
17954
  issues.push({
17003
17955
  code: "process_exit",
@@ -18017,6 +18969,31 @@ function buildGranularAgentHeapBlock(heapSummary) {
18017
18969
  entries: {}
18018
18970
  });
18019
18971
  }
18972
+ function buildGranularAgentManualActionMemorySummary(input) {
18973
+ const maxItems = Math.max(1, Math.min(12, input.maxItems ?? 8));
18974
+ const suggestions = (input.suggestions || []).filter((suggestion) => suggestion?.actionKey).slice(0, maxItems).map((suggestion) => ({
18975
+ actionKey: suggestion.actionKey,
18976
+ label: suggestion.label || null,
18977
+ targetClassName: suggestion.targetClassName || null,
18978
+ count: typeof suggestion.count === "number" && Number.isFinite(suggestion.count) ? suggestion.count : null,
18979
+ subjectCount: typeof suggestion.subjectCount === "number" && Number.isFinite(suggestion.subjectCount) ? suggestion.subjectCount : null,
18980
+ successCount: typeof suggestion.successCount === "number" && Number.isFinite(suggestion.successCount) ? suggestion.successCount : null,
18981
+ failureCount: typeof suggestion.failureCount === "number" && Number.isFinite(suggestion.failureCount) ? suggestion.failureCount : null,
18982
+ lastOccurredAt: typeof suggestion.lastOccurredAt === "number" && Number.isFinite(suggestion.lastOccurredAt) ? suggestion.lastOccurredAt : null,
18983
+ sampleTargetIds: Array.isArray(suggestion.sampleTargetIds) ? suggestion.sampleTargetIds.filter(
18984
+ (id) => typeof id === "string" && id.trim().length > 0
18985
+ ).slice(0, 6) : []
18986
+ }));
18987
+ return [
18988
+ renderConstBlock("manualActionMemory", {
18989
+ suggestions
18990
+ }),
18991
+ "Use manualActionMemory only as behavioral context for likely next actions. Ground the current target and validate permissions before creating or running prepared actions."
18992
+ ].join("\n");
18993
+ }
18994
+ function buildGranularAgentManualActionBlock(manualActionSummary) {
18995
+ return manualActionSummary?.trim() || buildGranularAgentManualActionMemorySummary({ suggestions: [] });
18996
+ }
18020
18997
  function projectSessionFileSummary(liveDoc) {
18021
18998
  const files = asRecord4(liveDoc?.files);
18022
18999
  const byId = asRecord4(files?.byId) || {};
@@ -18048,8 +19025,12 @@ function buildGranularAgentFileBlock(fileSummary) {
18048
19025
  function extractRuntimeContractExports(domainBlock) {
18049
19026
  const classes = /* @__PURE__ */ new Set();
18050
19027
  const actions = /* @__PURE__ */ new Set();
18051
- const classPattern = /export\s+declare\s+(?:const|class)\s+([A-Za-z_$][\w$]*)/g;
18052
- for (const match of domainBlock.matchAll(classPattern)) {
19028
+ const classConstPattern = /export\s+declare\s+const\s+([A-Za-z_$][\w$]*)\s*:\s*EntityClass\b/g;
19029
+ for (const match of domainBlock.matchAll(classConstPattern)) {
19030
+ classes.add(match[1]);
19031
+ }
19032
+ const classDeclPattern = /export\s+declare\s+class\s+([A-Za-z_$][\w$]*)\b/g;
19033
+ for (const match of domainBlock.matchAll(classDeclPattern)) {
18053
19034
  classes.add(match[1]);
18054
19035
  }
18055
19036
  const actionPattern = /export\s+declare\s+function\s+([A-Za-z_$][\w$]*)/g;
@@ -18554,6 +19535,9 @@ function buildGranularAgentSystemPrompt(input) {
18554
19535
  });
18555
19536
  const referentBlock = buildGranularAgentReferentBlock(input.referentSummary);
18556
19537
  const loopBlock = buildGranularAgentLoopBlock(input.loopSummary);
19538
+ const manualActionBlock = buildGranularAgentManualActionBlock(
19539
+ input.manualActionSummary
19540
+ );
18557
19541
  const knownFactsBlock = renderConstBlock(
18558
19542
  "knownFacts",
18559
19543
  buildKnownFactsFromCheckpoint(input.checkpoint)
@@ -18567,16 +19551,15 @@ function buildGranularAgentSystemPrompt(input) {
18567
19551
  - \`replyToUser(...)\` displays text directly to the user in the host UI. Treat it as the user-facing progress and reply channel, not as a debug log.
18568
19552
  - For long-running or multi-step jobs, send several short \`replyToUser(...)\` updates as useful milestones are reached so the user can see what is happening instead of waiting in silence.
18569
19553
  - Write \`replyToUser(...)\` content in a friendly, readable product-assistant style: concrete, concise, and natural. Avoid robotic status dumps, raw implementation names, and unexplained IDs unless the ID helps the user.
18570
- - When \`replyToUser(...)\` mentions a grounded record that should remain clickable/referable, wrap only the visible record label in a self-closing inline reference tag: \`<granular-object class="class_name" id="stable_id_or_path" label="Visible label" />\`. Use the actual class name and stable id/path from the runtime record or effect result; do not invent ids, field names, or snake/camel-case aliases that are not present in the type declarations or returned object.
18571
- - Treat \`showObjects(...)\` as the UI display call for user-visible records, not as a general storage helper. Do not wrap records under an \`items\` key.
18572
- - When records should remain reusable for follow-ups, first save the runtime record or ordered record array with \`await groundedObjects.save("stable_selection_name", value)\`, then display that saved selection exactly once with \`showObjects({ variableNames: ["stable_selection_name"] })\`.
18573
- - \`groundedObjects.save(...)\` only accepts scalar values, runtime records/sandbox instances, or arrays of runtime records/sandbox instances from one class. Do not save plain action/effect result objects or arrays of JSON summaries returned by actions. If an action returns ids/paths for records that should remain referable or displayed as records, fetch the matching runtime records first with the generated class \`.get(...)\`/query API, then save/display those fetched records. If the action returned only structured summaries, answer from those summaries with \`replyToUser(...)\`.
18574
- - Do not use \`showObjects({ entries: [...] })\` or \`showObjects({ saveAs, entries })\` as a shortcut for ordered pages, queues, search results, or ranked lists; those forms can create duplicate or poorly labelled displays. Save the selection with \`groundedObjects.save(...)\` and display it via \`variableNames\` instead.
18575
- - Use \`entryPaths\` only for a few already-known individual records and \`listNames\` only for a host-created list that you intentionally want to show. Do not display both an entry/list selection and a heap variable for the same records.
19554
+ - When \`replyToUser(...)\` mentions a grounded record that should remain clickable/referable, wrap only the visible record label in a self-closing inline reference tag using the actual class name and id/path.
19555
+ - Treat \`showObjects(...)\` as the UI display call for user-visible records. When records should remain reusable for follow-ups, first save the runtime record or ordered record array with \`await groundedObjects.save("stable_selection_name", value)\`, then display it once with \`showObjects({ variableNames: ["stable_selection_name"] })\`.
19556
+ - \`groundedObjects.save(...)\` only accepts scalar values, session files, runtime records/sandbox instances, or arrays of runtime records/sandbox instances from one class. Do not save plain action/effect result objects; fetch affected records first or answer from summaries with \`replyToUser(...)\`.
19557
+ - Do not use \`showObjects({ entries: [...] })\` or \`showObjects({ saveAs, entries })\`. Save ordered pages, queues, search results, or ranked lists with \`groundedObjects.save(...)\`, then call \`showObjects({ variableNames: [...] })\` once.
19558
+ - Use \`showAgentResponse({ reply, show: [record, action] })\` when one assistant message should combine text, grounded records, files, prepared actions, or action suggestions. The \`action\` can be a state handle such as \`record.lifecycle.approved\` or an action handle such as \`record.lifecycle.approved.reach()\`.
19559
+ - Do not use deprecated side-channel helpers such as \`agent_text_message(...)\`, \`agent_heap_objects(...)\`, or \`agent_message(...)\` unless the generated types expose no Harness v3 helper alternative.
18576
19560
  - When the user asks to show, list, display, open, or "show them" for records you found, call \`showObjects(...)\`; do not answer only with a count or text summary.
18577
19561
  - For count-only questions such as "how many", "how many X do I have", or "what is the total number of X", call the entity \`.count(...)\` or use page \`totalCount\` only when a page is already needed for other reasons. Answer with \`replyToUser(...)\` only. Do not call \`showObjects(...)\`, \`saveAs\`, or \`groundedObjects.save(...)\` unless the user also asked to see records or a later requested action needs a reusable record selection.
18578
- - Any job that identifies a specific record in the visible answer must also display that grounded record with \`showObjects(...)\` when the user should see/open it, or save it with \`groundedObjects.save(...)\` when it is only needed for follow-up resolution.
18579
- - For ordered record slices, pages, queues, search results, or ranked lists, save the slice with \`groundedObjects.save(...)\` and then call \`showObjects({ variableNames: [...] })\` once. Use a stable name that preserves the slice identity and ordering so later references such as "the second item" or "back on the first slice" resolve to the correct earlier slice, not merely the most recent record.
19562
+ - Use stable saved list names that preserve identity and ordering so later references such as "the second item" or "back on the first slice" resolve to the correct earlier slice, not merely the most recent record.
18580
19563
  - 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.` : `- Every job that answers the user must emit \`replyToUser(...)\` from \`@granular/agent\`.
18581
19564
  - \`replyToUser(...)\` displays text directly to the user in the host UI. Treat it as the user-facing progress and reply channel, not as a debug log.
18582
19565
  - For long-running or multi-step jobs, send several short \`replyToUser(...)\` updates as useful milestones are reached so the user can see what is happening instead of waiting in silence.
@@ -18587,7 +19570,7 @@ function buildGranularAgentSystemPrompt(input) {
18587
19570
  - When using code, assistant text must be empty or one brief summary.
18588
19571
  - Code must be plain runnable JavaScript with top-level await.
18589
19572
  - Use [Runtime Imports] as the authoritative module map. Import only listed module exports.
18590
- - Use static top-level imports such as \`import { Foo } from "@granular/domain/Foo"; import { replyToUser } from "@granular/agent";\`. Do not use dynamic imports for runtime modules.
19573
+ - Use static top-level named imports such as \`import { Foo } from "@granular/domain/Foo"; import { replyToUser, artifacts } from "@granular/agent";\`. Do not use dynamic imports or namespace imports like \`import * as agent from "@granular/agent"\` for runtime modules.
18591
19574
  - Read and write session files through the virtual filesystem modules listed in [Runtime Imports]. Input files are mounted under \`/session/input\`; files written under \`/session/output\` are persisted as agent-created session files.
18592
19575
  - Do not ask the user to provide virtual filesystem paths. Users attach or mention files by name in the UI; resolve the right file from \`sessionFileManifest.files\` or the current attachment context, then use its provided path internally.
18593
19576
  - The \`sessionFileManifest\` block is prompt context, not an imported module or runtime variable. For dynamic file lookup, import \`files\` from \`@granular/session\` and match \`filename\` to a returned file's \`path\`.
@@ -18650,6 +19633,7 @@ ${workflowRules}
18650
19633
  High-priority execution rules:
18651
19634
  - Treat a human reference as something to ground, not as missing data. When the user names or describes a record, group, queue, parent, relationship, or prior result and asks to inspect, decide, update, schedule, approve, send, or otherwise act on session data, run a code job to ground it before asking the user for more details.
18652
19635
  - For a human-described primary anchor, a no-match answer is only justified after more than one distinct grounding attempt, such as owner/container grounding, relationship traversal, exact id/path lookup, or shorter target-local search. Before the primary no-match return, retry that same anchor with fewer text constraints or a distinct grounding strategy; do not stop after one zero-result list/find/page call.
19636
+ - If the latest user wording names an entity type that has an importable class, ground that class first. A previously installed parent class or related action method is not a substitute; after zero results in one class, pivot to the latest named importable class before reporting no match.
18653
19637
  - A confirmation requirement is not a reason to stay text-only. Do all safe read-only grounding and availability/status checks first, then call \`userInteraction.askConfirmation(...)\` or \`userInteraction.askChoice(...)\` before the mutation.
18654
19638
  - In any code branch where a requested action or mutation has multiple possible targets, import \`userInteraction\` from \`@granular/session\` and use \`await userInteraction.askChoice(...)\` in that branch. This includes ambiguity discovered after a query returns several records. A branch that only shows candidates, asks in text, and returns leaves the requested action unfinished.
18655
19639
  - Before any mutation, know whether the target is one record or several. A singular phrase like "the item" is not proof of uniqueness after a query finds multiple matching records. If the user did not give an exact identifier or explicit selection criterion, call \`userInteraction.askChoice({ options, ... })\` with grounded choices; do not choose by age, amount, priority, order, or convenience on your own. Resolve the target before any yes/no confirmation.
@@ -18693,7 +19677,7 @@ Intent resolution:
18693
19677
  - For follow-up words like "other", "another", or "remaining" after the user selected one candidate from a previous choice, resolve within the active contrast from that choice and the user's answer. Exclude the selected item, preserve descriptors such as larger, smaller, next, older, different, or same status, and do not take the first leftover from a wider saved list when the contrast narrows the intended set.
18694
19678
  - Before any mutation, prove the target resolves to exactly one grounded record. If the request describes a set, category, relationship, prior result group, or other non-unique scope, gather the candidate records first; when more than one candidate remains, ask the user to choose before calling the action.
18695
19679
  - For ambiguous choice prompts before a mutation, every option that describes a different candidate must carry a distinct grounded record value/path. After the answer, do not fall back to the first candidate if matching fails; ask again or stop without mutating.
18696
- - The [State] constants and [Runtime Imports] map are prompt context, not runtime variables. Never reference \`runtimeImports\`, \`savedData\`, \`sessionFileManifest\`, \`recentReferences\`, \`workflowContext\`, \`workflowState\`, or \`capabilities\` as variables in generated code. When using a recent reference or file path, copy its path string into code and fetch/read it with the relevant runtime API.
19680
+ - The [State] constants and [Runtime Imports] map are prompt context, not runtime variables. Never reference \`runtimeImports\`, \`savedData\`, \`manualActionMemory\`, \`sessionFileManifest\`, \`recentReferences\`, \`workflowContext\`, \`workflowState\`, or \`capabilities\` as variables in generated code. When using a recent reference, manual-action pattern, or file path, copy concrete ids/values into code and fetch/read it with the relevant runtime API.
18697
19681
  - Never write placeholder grounding code such as \`const path = null\`, \`const groundedPath = ""\`, or \`const recordPath = ""\`. If no saved reference is available, delete that branch entirely and execute the fallback lookup directly.
18698
19682
  - Never call \`.get({ path: "" })\`; an empty path is not a saved reference.
18699
19683
  - For ordinal references to earlier pages, slices, lists, or ranked results, use the saved list/recent references first. If no saved list is available, rerun the exact same ordered query and select the ordinal index from its returned \`items\`; never invent a record path from a label or ordinal.
@@ -18820,20 +19804,10 @@ Ask the user when:
18820
19804
  - the target is unique but the requested action is unclear
18821
19805
 
18822
19806
  Relationship filters:
18823
- - One-record relationships use \`is\`.
18824
- - Multi-record relationships use \`some\`.
18825
- - Never guess relationship cardinality from wording. Check the generated TypeScript filter type for the field before writing a relationship filter; if you are not sure, use declared relationship getters from already grounded records instead of a relationship filter.
18826
- - If a relationship filter type or field is one-record/singular, never use \`some\` on that field. Match by \`id\`, \`path\`, or \`is\`, or fetch the related record and continue through declared getters when you need to traverse farther.
18827
- - Do not invent nested operators under relationship fields. A one-record relationship filter accepts only its documented operators such as \`id\`, \`path\`, \`is\`, \`null\`, and \`not_null\`; deeper conditions must go under \`is\` or be handled by fetching records and following getters.
18828
- - Never use \`some\` on one-record fields. If the generated TypeScript type says \`OneRelationFilter\`, valid operators are \`id\`, \`path\`, \`is\`, \`null\`, and \`not_null\`; \`some\` is invalid.
18829
- - Use \`some\` only when the generated TypeScript type says \`ManyRelationFilter\`.
18830
- - For a singular relationship that points to an intermediate record, nested filters still use \`is\` at the singular hop. Do not use \`some\` because the nested condition names another related record.
18831
- - Use \`{ relationship: { id: "record_id" } }\` or \`{ relationship: { path: "class_record_id" } }\` when matching a known related record.
18832
- - When you already fetched the related record, use \`{ relationship: { path: record._graphPath } }\` or \`{ relationship: { id: record.id } }\`; do not wrap a known id/path under \`is\`.
18833
- - The path used in a relationship filter must be the path of the relationship target. For same-queue follow-ups from an item/batch/ticket, fetch that item's related unit/site/depot first and use the related unit/site/depot path; do not use the item path as a unit/site/depot path.
18834
- - Use \`{ relationship: { is: { field: { equal_to: value } } } }\` only for nested field filters. Never put \`id\` or \`path\` inside \`is\`.
18835
- - Do not write \`{ relationship: { some: ... } }\` unless the generated filter type for that exact relationship says it is a many/collection relationship. For one-record, parent, owner, or many-to-one relationships, use \`path\`, \`id\`, \`is\`, or getter traversal.
18836
- - Do not pass a full record instance into a filter; if you already fetched a record, filter by its id or path instead.
19807
+ - Use the generated filter type as the authority: \`OneRelationFilter\` supports \`id\`, \`path\`, \`is\`, \`null\`, \`not_null\`; \`ManyRelationFilter\` supports those plus \`some\`.
19808
+ - Use \`id\` or \`path\` for a known related record; use \`is\` or \`some\` only for nested target-field filters.
19809
+ - Relationship paths must belong to the relationship target type. If you have a parent/container/item of another type, fetch the declared related record first and filter with that related record's id/path.
19810
+ - Never pass a full record instance into a filter. Use its id/path or a declared relationship getter.
18837
19811
  ${domainSections.docs ? `
18838
19812
  Domain notes:
18839
19813
  ${domainSections.docs}
@@ -18844,6 +19818,23 @@ ${actionIndex}
18844
19818
  - Global backend actions are executable functions exported by \`@granular/actions/backend\`; import each backend action you call, e.g. \`import { some_action } from "@granular/actions/backend"; await some_action(...)\`. Frontend actions are exported by \`@granular/actions/frontend\` when the action index marks them as frontend actions.
18845
19819
  - Actions listed under "Record-level" are instance methods. First fetch or find the specific record, then call the action on that instance, e.g. \`const item = await Item.get({ path }); await item.action_name(...)\`.
18846
19820
  - Actions listed under "Class-level" are class/static methods. Call them on the imported class, e.g. \`await Item.action_name(...)\`.
19821
+ - State-machine handles are the preferred workflow surface. For an existing record, use \`record.lifecycle.targetState\`; for a not-yet-created record, use class-level handles such as \`SpendRequest.lifecycle.policy_review\` only to prepare a provisional new-record action flow.
19822
+ - For pure field-collection requests, target the class-level entry state handle; for submit/review requests, target the nearest requested later state.
19823
+ - Choose the nearest target state that matches the user's words. Do not aim at a later state just because it is reachable.
19824
+ - For grounded stateful records, choose the target state handle from the user's requested outcome and handle docs. Use \`plan()\`, \`blockers()\`, or \`permissions()\` to explain reachability, missing requirements, stale state, and who can run the action; do not derive workflow meaning or adjacent actions from raw \`record.status\` or \`current().state\`.
19825
+ - Before opening a stateful action, call \`const plan = await handle.plan()\`. If the plan or opened artifact shows blockers, stale state, missing relationships, permissions, or no reachable path, explain those structured results and display the record/artifact. Do not skip to a later state or build your own status ladder.
19826
+ - Use typed plan fields and helpers directly. For visible blocker text, use the declared blocker-formatting helper when available instead of hand-written object casts.
19827
+ - A suggestion is a recommendation button: \`await stateHandle.suggest(message)\`. A prepared action is the editable form the user can review/run: \`const action = stateHandle.reach(); const prepared = await action.open()\`.
19828
+ - Use suggestions when the user asks "what can I do next?", asks for options, or gives an unclear intent. Prefer 2 to 5 concrete suggestions and keep text short.
19829
+ - Open a prepared action when the user asks for one clear action. Prefill only values grounded in the user request, conversation, files, selected records, or fresh reads. Leave unknown fields empty; do not invent them.
19830
+ - When a grounded related/context record can satisfy the prepared action through declared relationships, use those relationships to fill required relationship inputs before opening or updating the action. If a required relationship remains empty, continue through declared relationship chains from the grounded object when the next hop can fill that slot. Do not only save the context record in memory while leaving derivable relationship slots blank.
19831
+ - A derived intermediate relationship is not enough when another required relationship is still reachable from it. For example, if a team gives a cost center and the action also requires a budget, traverse the cost center's declared budget relationship before opening the action.
19832
+ - If the user says they have a document/work item/event but no matching record is found, check for a declared class-level new-record state/action handle or importable backend create/preparation action for that named class before giving up. Use grounded required fields to open the prepared action or call the create/preparation action; if required values are still missing and no prepared action can collect them, ask only for those values. Do not claim the record already exists.
19833
+ - Do not ask for confirmation before opening a prepared action. The prepared action is itself reviewable. Use \`userInteraction.askConfirmation\` only when the user explicitly asks for yes/no approval, the selected action is ambiguous after grounding, or the domain/runtime asks for confirmation.
19834
+ - If the action cannot continue because of missing input, stale state, missing relationships, related-state requirements, or permissions, keep/show the prepared action at that blocker and explain the next needed person, record, or value. Do not skip workflow steps or target a later state.
19835
+ - Reuse an already-open prepared action for the same target/action when available: update it, show it again, or explain what is still needed instead of creating a duplicate.
19836
+ - If an open prepared action needs edits, prefer the returned record helper: \`const prepared = await action.open(); await prepared.updateInputs({ inputValues, relationships });\`. Use \`artifacts.updateInputs(id, patch)\` only when you only have an id.
19837
+ - Use \`await prepared.show()\` or \`await actions.show(prepared)\` only to display an already-created prepared action again.
18847
19838
  - The action index is the visibility contract. If an action is listed for a class, call it directly on fetched/listed instances of that class; do not use \`typeof record.action_name === "function"\` as a discovery gate. If an action is not listed, do not call it.
18848
19839
  - Never call a record-level action as \`Class.action_name(...)\`; that method will not exist.
18849
19840
  - For action inputs, use the exact property names from the generated TypeScript method signature or the input schema shown in the action list. Do not invent synonym fields for required inputs.
@@ -18874,6 +19865,8 @@ ${loopBlock}
18874
19865
 
18875
19866
  ${knownFactsBlock}
18876
19867
 
19868
+ ${manualActionBlock}
19869
+
18877
19870
  [Request]
18878
19871
  ${input.request?.trim() || "Use the latest user message in the conversation."}`;
18879
19872
  }
@@ -19624,6 +20617,8 @@ function modelOutputInstruction() {
19624
20617
  'Do not use "action":"reply" to say a record is not grounded yet; if the request names or describes a domain record, use "action":"job" and ground it from session state, relationships, searches, or visible read-only actions first.',
19625
20618
  'Before claiming you lack access, inspect the visible action list. If a visible read-only search, lookup, list, guidance, note, policy, or knowledge action can satisfy a "check", "find", "look up", or "whether we have guidance" request, choose "action":"job" and call it.',
19626
20619
  "Generated code must not report no matches for the primary human-described anchor after a single zero-result list/find/page call. Before that primary no-match return, retry the primary anchor with fewer text constraints or a distinct fallback such as owner/container grounding, relationship traversal, exact-id/path lookup, or shorter target-local search.",
20620
+ "If the latest request names an importable entity type, generated code must ground that class first; a previously installed parent class or related action method is not a substitute. After zero results in one class, pivot to the latest named importable class before reporting no match.",
20621
+ "When the user says they have a document/work item/event but no matching record exists yet, generated code must check the visible class-level new-record/create/preparation surface before returning no-match; if required create inputs are still missing and no prepared action can collect them, ask only for those inputs.",
19627
20622
  'Use "action":"job" when the next step should run code or mutate workflow state.',
19628
20623
  'When action is "job", include runnable code in "code" and emit the "code" field before any non-empty "reply" field so generated code comments can stream as progress.',
19629
20624
  "Generated job code must use plain ASCII punctuation in string literals and comments. Do not use curly quotes, smart apostrophes, en dashes, em dashes, or other typographic punctuation in code.",
@@ -19693,7 +20688,12 @@ function createOpenAIChatTurnGenerator(options) {
19693
20688
 
19694
20689
  ${modelOutputInstruction()}`
19695
20690
  },
19696
- ...input.history,
20691
+ ...input.history.map(
20692
+ (message) => ({
20693
+ role: message.role,
20694
+ content: message.content
20695
+ })
20696
+ ),
19697
20697
  { role: "user", content: input.request }
19698
20698
  ];
19699
20699
  const payload = {
@@ -19712,11 +20712,12 @@ ${modelOutputInstruction()}`
19712
20712
  let usage = null;
19713
20713
  let requestId = null;
19714
20714
  if (input.onTextDelta || input.onReplyDelta || input.onCodeDelta) {
19715
- const stream = await client.chat.completions.create({
20715
+ const streamPayload = {
19716
20716
  ...payload,
19717
20717
  stream: true,
19718
20718
  stream_options: { include_usage: true }
19719
- });
20719
+ };
20720
+ const stream = await client.chat.completions.create(streamPayload);
19720
20721
  let streamedReply = "";
19721
20722
  let streamedCode = "";
19722
20723
  const emitReplyDelta = async () => {
@@ -19742,7 +20743,8 @@ ${modelOutputInstruction()}`
19742
20743
  await input.onCodeDelta(delta);
19743
20744
  };
19744
20745
  for await (const event of stream) {
19745
- requestId = requestId || event.id || event._request_id || null;
20746
+ const eventRecord = event;
20747
+ requestId = requestId || event.id || (typeof eventRecord._request_id === "string" ? eventRecord._request_id : null);
19746
20748
  usage = event.usage || usage;
19747
20749
  const delta = event.choices?.[0]?.delta?.content;
19748
20750
  const deltaText = typeof delta === "string" ? delta : Array.isArray(delta) ? delta.map((part) => asRecord6(part)?.text || "").join("") : "";
@@ -19756,14 +20758,13 @@ ${modelOutputInstruction()}`
19756
20758
  await emitCodeDelta();
19757
20759
  raw = { streamed: true, model, usage, request_id: requestId };
19758
20760
  } else {
19759
- const completion = await client.chat.completions.create(
19760
- payload
19761
- );
20761
+ const completion = await client.chat.completions.create(payload);
20762
+ const completionRecord = completion;
19762
20763
  raw = completion;
19763
20764
  usage = completion.usage;
19764
- requestId = (typeof completion.id === "string" ? completion.id : null) || (typeof completion._request_id === "string" ? completion._request_id : null);
20765
+ requestId = (typeof completion.id === "string" ? completion.id : null) || (typeof completionRecord._request_id === "string" ? completionRecord._request_id : null);
19765
20766
  const content = asRecord6(
19766
- asRecord6(completion.choices?.[0])?.message
20767
+ asRecord6(completion.choices[0])?.message
19767
20768
  )?.content;
19768
20769
  text = typeof content === "string" ? content : Array.isArray(content) ? content.map((part) => asRecord6(part)?.text || "").join("") : "";
19769
20770
  }
@@ -20100,6 +21101,9 @@ async function generateTurnWithRepair(generator, input) {
20100
21101
  "",
20101
21102
  "The previous generated job code failed preflight review against [Runtime Imports] and the runtime contract.",
20102
21103
  "Return a corrected JSON object. Keep the user's requested behavior, but fix every issue below before execution.",
21104
+ "If an issue says workflow path, blockers, permissions, or readiness were derived from raw status fields, remove the status/current-state ladder. Pick the declared state handle that matches the user's requested outcome, call plan()/blockers()/permissions() first, then open/show that handle or explain its structured blockers.",
21105
+ "If an issue says an unsafe type assertion was used, remove the cast and use declared fields or typed helpers directly. For state plans, use plan.blockers, plan.permissions, plan.steps, plan.summary, or blocker-formatting helpers without hand-written object casts.",
21106
+ "If the previous code stopped after no matching record for a user-supplied document/work item, preserve the lookup but check declared new-record/create/preparation surfaces next. Import the backend action module if needed, or ask only for missing required create inputs when no prepared action can collect them.",
20103
21107
  "",
20104
21108
  "Preflight issues:",
20105
21109
  ...issues.map((issue) => `- ${issue.code}: ${issue.message}`),