@granular-software/sdk 0.4.48 → 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.
package/dist/index.mjs CHANGED
@@ -4016,6 +4016,9 @@ function rpcTimeoutMsForMethod(method) {
4016
4016
  return DOMAIN_PACKAGE_RPC_TIMEOUT_MS;
4017
4017
  case "client.heartbeat":
4018
4018
  case "effects.publishCatalog":
4019
+ case "effects.resetCatalog":
4020
+ case "effects.addCatalog":
4021
+ case "effects.removeCatalog":
4019
4022
  case "effects.refresh":
4020
4023
  return EFFECT_CONTROL_RPC_TIMEOUT_MS;
4021
4024
  case "harness.run":
@@ -4761,6 +4764,9 @@ function resolvePromptAnswer(prompt, answer) {
4761
4764
 
4762
4765
  // src/session.ts
4763
4766
  var PROMPT_TRANSCRIPT_APPEND_TIMEOUT_MS = 5e3;
4767
+ function toPascalCase(value) {
4768
+ return value.split(/[_:\-\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
4769
+ }
4764
4770
  function withPromptTranscriptTimeout(promise) {
4765
4771
  let timeout = null;
4766
4772
  return Promise.race([
@@ -5323,9 +5329,7 @@ var Session = class {
5323
5329
  if (classes && Object.keys(classes).length > 0) {
5324
5330
  let docs2 = "# Domain Documentation\n\n";
5325
5331
  docs2 += "Import concrete classes from `@granular/domain/<Class>` and global backend actions from `@granular/actions/backend`:\n\n";
5326
- const classNames = Object.keys(classes).map(
5327
- (c) => c.charAt(0).toUpperCase() + c.slice(1)
5328
- );
5332
+ const classNames = Object.keys(classes).map(toPascalCase);
5329
5333
  const globalNames = (globalTools || []).map((t) => t.name);
5330
5334
  const importLines = [
5331
5335
  ...classNames.map(
@@ -5339,7 +5343,7 @@ ${importLines.join("\n") || "// No generated domain imports available."}
5339
5343
 
5340
5344
  `;
5341
5345
  for (const [className, cls] of Object.entries(classes)) {
5342
- const TsName = className.charAt(0).toUpperCase() + className.slice(1);
5346
+ const TsName = toPascalCase(className);
5343
5347
  docs2 += `## ${TsName}
5344
5348
 
5345
5349
  `;
@@ -5560,6 +5564,9 @@ import { ${tools[0]?.name || "example"} } from "@granular/actions/backend";
5560
5564
  this.client.on("harness.model_stream", (data) => {
5561
5565
  this.emit("harness:model_stream", data);
5562
5566
  });
5567
+ this.client.on("harness.text_response.delta", (data) => {
5568
+ this.emit("harness:text_response_delta", data);
5569
+ });
5563
5570
  this.client.on("job.agent_message", (data) => {
5564
5571
  const normalized = normalizeJobAgentMessageEnvelope(data);
5565
5572
  if (!normalized) return;
@@ -6326,6 +6333,28 @@ function asString(value) {
6326
6333
  function trimString(value) {
6327
6334
  return typeof value === "string" ? value.trim() : "";
6328
6335
  }
6336
+ function compactJson(value, maxLength = 320) {
6337
+ if (value === void 0 || value === null) return void 0;
6338
+ try {
6339
+ const json = JSON.stringify(value);
6340
+ if (!json || json === "undefined") return void 0;
6341
+ return json.length > maxLength ? `${json.slice(0, maxLength)}...` : json;
6342
+ } catch {
6343
+ return String(value);
6344
+ }
6345
+ }
6346
+ function artifactRecordsById(liveDoc) {
6347
+ const artifacts = asRecord3(liveDoc?.artifacts);
6348
+ const byId = asRecord3(artifacts?.byId) || {};
6349
+ return Object.fromEntries(
6350
+ Object.entries(byId).map(([artifactId, value]) => {
6351
+ const record = asRecord3(value);
6352
+ return record ? [artifactId, record] : null;
6353
+ }).filter(
6354
+ (entry) => Boolean(entry)
6355
+ )
6356
+ );
6357
+ }
6329
6358
  function normalizeShowRefs(value) {
6330
6359
  const record = asRecord3(value);
6331
6360
  if (!record) return void 0;
@@ -6342,9 +6371,31 @@ function normalizeShowRefs(value) {
6342
6371
  entryPaths: normalizeRefs(record.entryPaths),
6343
6372
  listNames: normalizeRefs(record.listNames),
6344
6373
  variableNames: normalizeRefs(record.variableNames),
6345
- fileIds: normalizeRefs(record.fileIds)
6374
+ fileIds: normalizeRefs(record.fileIds),
6375
+ sessionArtifactIds: normalizeRefs(record.sessionArtifactIds),
6376
+ actionSuggestions: normalizeActionSuggestions(record.actionSuggestions)
6346
6377
  };
6347
- return show.entryPaths || show.listNames || show.variableNames || show.fileIds ? show : void 0;
6378
+ return show.entryPaths || show.listNames || show.variableNames || show.fileIds || show.sessionArtifactIds || show.actionSuggestions ? show : void 0;
6379
+ }
6380
+ function normalizeActionSuggestions(value) {
6381
+ if (!Array.isArray(value)) return void 0;
6382
+ const suggestions = [];
6383
+ for (const item of value) {
6384
+ const record = asRecord3(item);
6385
+ if (!record) continue;
6386
+ const label = trimString(record.label);
6387
+ if (!label) continue;
6388
+ const suggestionId = trimString(record.suggestionId) || trimString(record.id) || label;
6389
+ suggestions.push({
6390
+ suggestionId,
6391
+ label,
6392
+ ...typeof record.description === "string" ? { description: record.description } : {},
6393
+ ...asRecord3(record.artifact) ? { artifact: asRecord3(record.artifact) } : {},
6394
+ ...asRecord3(record.target) ? { target: asRecord3(record.target) } : {},
6395
+ ...asRecord3(record.metadata) ? { metadata: asRecord3(record.metadata) } : {}
6396
+ });
6397
+ }
6398
+ return suggestions.length ? suggestions : void 0;
6348
6399
  }
6349
6400
  function stringifyTranscriptValue(value, fallback = "") {
6350
6401
  if (typeof value === "string") {
@@ -6364,12 +6415,139 @@ function stringifyTranscriptValue(value, fallback = "") {
6364
6415
  return String(value);
6365
6416
  }
6366
6417
  }
6367
- function buildArtifactHistory(show) {
6418
+ function latestInputEditSummary(metadata) {
6419
+ const lastInputEdit = asRecord3(metadata.lastInputEdit);
6420
+ if (!lastInputEdit) return null;
6421
+ const source = asString(lastInputEdit.source) || "unknown";
6422
+ const actor = asString(lastInputEdit.actorSubjectId) || asString(lastInputEdit.actorPermissionProfileName) || asString(lastInputEdit.jobId) || null;
6423
+ const inputKeys = Array.isArray(lastInputEdit.changedInputKeys) ? lastInputEdit.changedInputKeys.filter(
6424
+ (key) => typeof key === "string" && key.trim().length > 0
6425
+ ).slice(0, 6) : [];
6426
+ const relationshipKeys = Array.isArray(lastInputEdit.changedRelationshipKeys) ? lastInputEdit.changedRelationshipKeys.filter(
6427
+ (key) => typeof key === "string" && key.trim().length > 0
6428
+ ).slice(0, 6) : [];
6429
+ const changed = [
6430
+ inputKeys.length ? `inputs=${inputKeys.join(",")}` : null,
6431
+ relationshipKeys.length ? `relationships=${relationshipKeys.join(",")}` : null
6432
+ ].filter(Boolean);
6433
+ return `lastEdit=${source}${actor ? ` by ${actor}` : ""}${changed.length ? ` (${changed.join("; ")})` : ""}`;
6434
+ }
6435
+ function artifactIssueSummary(record) {
6436
+ const validation = asRecord3(record.validation);
6437
+ if (!validation) return null;
6438
+ const issues = Array.isArray(validation.issues) ? validation.issues.map((issue) => asRecord3(issue)).filter((issue) => Boolean(issue)).slice(0, 3) : [];
6439
+ if (issues.length > 0) {
6440
+ return `issues=${issues.map((issue) => {
6441
+ const code = asString(issue.code) || asString(issue.kind) || "issue";
6442
+ const path = asString(issue.path);
6443
+ const message = trimString(issue.message);
6444
+ return `${code}${path ? ` at ${path}` : ""}${message ? ` (${message})` : ""}`;
6445
+ }).join("; ")}`;
6446
+ }
6447
+ const error = trimString(validation.error) || trimString(validation.reason) || trimString(validation.message);
6448
+ return error ? `validation=${error}` : null;
6449
+ }
6450
+ function artifactExecutionSummary(metadata) {
6451
+ const execution = asRecord3(metadata.execution);
6452
+ if (!execution) return null;
6453
+ const result = asRecord3(execution.result);
6454
+ const awaiting = asString(result?.awaiting) || asString(execution.awaiting);
6455
+ const pendingTransition = asString(result?.pendingTransition) || asString(execution.pendingTransition);
6456
+ const approval = asRecord3(result?.approval) || asRecord3(execution.approval);
6457
+ const approvalTarget = asString(approval?.permissionProfileName) || asString(approval?.permissionProfileId) || asString(approval?.assigneeSubjectId);
6458
+ const error = trimString(execution.error);
6459
+ const pieces = [
6460
+ awaiting ? `awaiting=${awaiting}` : null,
6461
+ pendingTransition ? `pendingTransition=${pendingTransition}` : null,
6462
+ approvalTarget ? `approvalTarget=${approvalTarget}` : null,
6463
+ error ? `executionError=${error}` : null
6464
+ ].filter(Boolean);
6465
+ return pieces.length ? pieces.join("; ") : null;
6466
+ }
6467
+ function artifactStatePathSummary(metadata) {
6468
+ const statePlan = asRecord3(metadata.statePlan);
6469
+ if (!statePlan) return null;
6470
+ const machineName = asString(statePlan.machineName);
6471
+ const targetState = asString(statePlan.targetState);
6472
+ const objectPath = asString(statePlan.objectPath);
6473
+ const approvedTransitions = Array.isArray(statePlan.approvedTransitions) ? statePlan.approvedTransitions.length : 0;
6474
+ const approvalDecisions = Array.isArray(statePlan.approvalDecisions) ? statePlan.approvalDecisions.length : 0;
6475
+ const pieces = [
6476
+ machineName || targetState ? `statePath=${machineName || "state_machine"}${targetState ? ` -> ${targetState}` : ""}` : null,
6477
+ objectPath ? `objectPath=${objectPath}` : null,
6478
+ approvedTransitions ? `approvedTransitions=${approvedTransitions}` : null,
6479
+ approvalDecisions ? `approvalDecisions=${approvalDecisions}` : null
6480
+ ].filter(Boolean);
6481
+ return pieces.length ? pieces.join("; ") : null;
6482
+ }
6483
+ function artifactSummaryLine(artifactId, record) {
6484
+ if (!record) return `- ${artifactId}: unavailable in session artifact store`;
6485
+ const label = trimString(record.label) || artifactId;
6486
+ const kind = asString(record.kind) || "artifact";
6487
+ const status = asString(record.status) || "unknown";
6488
+ const createdByJobId = asString(record.createdByJobId);
6489
+ const target = asRecord3(record.target);
6490
+ const metadata = asRecord3(record.metadata) || {};
6491
+ const subArtifactIds = Array.isArray(record.subArtifactIds) ? record.subArtifactIds.filter(
6492
+ (id) => typeof id === "string" && id.trim().length > 0
6493
+ ).slice(0, 8) : [];
6494
+ const relationships = compactJson(record.relationships, 220);
6495
+ const pieces = [
6496
+ `kind=${kind}`,
6497
+ `status=${status}`,
6498
+ createdByJobId ? `createdByJob=${createdByJobId}` : null,
6499
+ target ? `target=${asString(target.className) || "record"}:${asString(target.id) || "unknown"}${asString(target.label) ? ` (${asString(target.label)})` : ""}` : null,
6500
+ artifactStatePathSummary(metadata),
6501
+ artifactExecutionSummary(metadata),
6502
+ artifactIssueSummary(record),
6503
+ latestInputEditSummary(metadata),
6504
+ subArtifactIds.length ? `subArtifacts=${subArtifactIds.join(",")}` : null,
6505
+ relationships ? `relationships=${relationships}` : null
6506
+ ].filter(Boolean);
6507
+ return `- ${artifactId}: ${label}${pieces.length ? `; ${pieces.join("; ")}` : ""}`;
6508
+ }
6509
+ function buildArtifactHistory(show, artifactsById) {
6368
6510
  if (!show) return void 0;
6369
- return `[Agent message]
6511
+ const artifactIds = show.sessionArtifactIds || [];
6512
+ const actionSuggestions = show.actionSuggestions || [];
6513
+ if (artifactIds.length === 0 && actionSuggestions.length === 0) {
6514
+ return `[Agent message]
6370
6515
  ${stringifyTranscriptValue({ show }, "")}`;
6516
+ }
6517
+ const lines = artifactIds.slice(0, 8).map(
6518
+ (artifactId) => artifactSummaryLine(artifactId, artifactsById?.[artifactId])
6519
+ );
6520
+ if (artifactIds.length > 8) {
6521
+ lines.push(`- ${artifactIds.length - 8} more artifacts omitted`);
6522
+ }
6523
+ if (actionSuggestions.length > 0) {
6524
+ if (artifactIds.length > 0) lines.push("[Agent suggested actions]");
6525
+ for (const suggestion of actionSuggestions.slice(0, 8)) {
6526
+ lines.push(
6527
+ `- ${suggestion.label}${suggestion.description ? `; ${suggestion.description}` : ""}`
6528
+ );
6529
+ }
6530
+ if (actionSuggestions.length > 8) {
6531
+ lines.push(`- ${actionSuggestions.length - 8} more suggestions omitted`);
6532
+ }
6533
+ }
6534
+ const otherRefs = {
6535
+ entryPaths: show.entryPaths,
6536
+ listNames: show.listNames,
6537
+ variableNames: show.variableNames,
6538
+ fileIds: show.fileIds
6539
+ };
6540
+ const hasOtherRefs = Object.values(otherRefs).some(
6541
+ (value) => Array.isArray(value) && value.length > 0
6542
+ );
6543
+ const title = artifactIds.length > 0 ? "[Agent displayed session artifacts]" : "[Agent suggested actions]";
6544
+ return [
6545
+ title,
6546
+ ...lines,
6547
+ hasOtherRefs ? `Other shown refs: ${stringifyTranscriptValue(otherRefs, "")}` : null
6548
+ ].filter(Boolean).join("\n");
6371
6549
  }
6372
- function normalizeConversationMessage(raw) {
6550
+ function normalizeConversationMessage(raw, artifactsById) {
6373
6551
  const record = asRecord3(raw);
6374
6552
  if (!record) return null;
6375
6553
  const role = record.role === "user" ? "user" : record.role === "assistant" ? "assistant" : null;
@@ -6381,6 +6559,12 @@ function normalizeConversationMessage(raw) {
6381
6559
  const id = asString(record.id) || crypto.randomUUID();
6382
6560
  const timestamp = asNumber(record.timestamp) || asNumber(record.ts) || 0;
6383
6561
  if (!content && !show) return null;
6562
+ const artifactHistory = buildArtifactHistory(show, artifactsById);
6563
+ const historyContent = role === "assistant" ? content && artifactHistory ? `[Assistant reply]
6564
+ ${content}
6565
+
6566
+ ${artifactHistory}` : content ? `[Assistant reply]
6567
+ ${content}` : artifactHistory : void 0;
6384
6568
  return {
6385
6569
  id,
6386
6570
  role,
@@ -6389,8 +6573,7 @@ function normalizeConversationMessage(raw) {
6389
6573
  jobId: asString(record.jobId),
6390
6574
  promptId: asString(record.promptId),
6391
6575
  show,
6392
- historyContent: role === "assistant" ? content ? `[Assistant reply]
6393
- ${content}` : buildArtifactHistory(show) : void 0,
6576
+ historyContent,
6394
6577
  source: "conversation"
6395
6578
  };
6396
6579
  }
@@ -6433,7 +6616,7 @@ ${assistantContent}`,
6433
6616
  return entries;
6434
6617
  });
6435
6618
  }
6436
- function normalizeAgentMessageEntries(jobId, rawMessages) {
6619
+ function normalizeAgentMessageEntries(jobId, rawMessages, artifactsById) {
6437
6620
  return asArray(rawMessages).map((value) => asRecord3(value)).filter((value) => Boolean(value)).sort(
6438
6621
  (left, right) => (asNumber(left.timestamp) || asNumber(left.ts) || 0) - (asNumber(right.timestamp) || asNumber(right.ts) || 0)
6439
6622
  ).flatMap((message) => {
@@ -6464,14 +6647,14 @@ ${reply}`,
6464
6647
  timestamp,
6465
6648
  jobId,
6466
6649
  show,
6467
- historyContent: buildArtifactHistory(show),
6650
+ historyContent: buildArtifactHistory(show, artifactsById),
6468
6651
  source: "job_agent_message"
6469
6652
  });
6470
6653
  }
6471
6654
  return entries;
6472
6655
  });
6473
6656
  }
6474
- function buildJobFallbackEntries(jobId, job, sessionHeap) {
6657
+ function buildJobFallbackEntries(jobId, job, sessionHeap, artifactsById) {
6475
6658
  const timestamp = asNumber(job.finishedAt) || asNumber(job.startedAt) || asNumber(job.submittedAt) || 0;
6476
6659
  const resultPreview = stringifyTranscriptValue(
6477
6660
  job.result,
@@ -6509,7 +6692,7 @@ ${responseText}`,
6509
6692
  timestamp,
6510
6693
  jobId,
6511
6694
  show,
6512
- historyContent: buildArtifactHistory(show),
6695
+ historyContent: buildArtifactHistory(show, artifactsById),
6513
6696
  source: "job_result"
6514
6697
  });
6515
6698
  }
@@ -6563,10 +6746,11 @@ function buildJobCodeEntry(jobId, job) {
6563
6746
  function buildSessionTranscript(input) {
6564
6747
  const liveDoc = input.liveDoc || null;
6565
6748
  const sessionHeap = input.sessionHeap || EMPTY_HEAP;
6749
+ const artifactsById = artifactRecordsById(liveDoc);
6566
6750
  const transcript = [];
6567
6751
  const conversationMessages = asArray(
6568
6752
  asRecord3(liveDoc?.conversation)?.messages
6569
- ).map((message) => normalizeConversationMessage(message)).filter((message) => Boolean(message));
6753
+ ).map((message) => normalizeConversationMessage(message, artifactsById)).filter((message) => Boolean(message));
6570
6754
  const conversationPromptIds = new Set(
6571
6755
  conversationMessages.map((message) => message.promptId).filter((promptId) => Boolean(promptId))
6572
6756
  );
@@ -6593,7 +6777,8 @@ function buildSessionTranscript(input) {
6593
6777
  if (!assistantConversationJobIds.has(jobId)) {
6594
6778
  const agentEntries = normalizeAgentMessageEntries(
6595
6779
  jobId,
6596
- job.agentMessages
6780
+ job.agentMessages,
6781
+ artifactsById
6597
6782
  );
6598
6783
  if (agentEntries.length > 0) {
6599
6784
  transcript.push(...agentEntries);
@@ -6602,7 +6787,8 @@ function buildSessionTranscript(input) {
6602
6787
  ...buildJobFallbackEntries(
6603
6788
  jobId,
6604
6789
  job,
6605
- sessionHeap
6790
+ sessionHeap,
6791
+ artifactsById
6606
6792
  )
6607
6793
  );
6608
6794
  }
@@ -10768,16 +10954,107 @@ var StateMachineStateSchema = external_exports.union([
10768
10954
  external_exports.string(),
10769
10955
  external_exports.object({
10770
10956
  name: external_exports.string().min(1),
10957
+ label: external_exports.string().optional(),
10958
+ description: external_exports.string().optional(),
10771
10959
  isFinal: external_exports.boolean().optional()
10772
10960
  }).strict()
10773
10961
  ]);
10962
+ var StateTransitionInputBindingSchema = external_exports.lazy(
10963
+ () => external_exports.union([
10964
+ external_exports.null(),
10965
+ external_exports.string(),
10966
+ external_exports.number(),
10967
+ external_exports.boolean(),
10968
+ external_exports.array(StateTransitionInputBindingSchema),
10969
+ external_exports.object({
10970
+ const: external_exports.unknown()
10971
+ }).strict(),
10972
+ external_exports.object({
10973
+ from: external_exports.literal("object"),
10974
+ path: external_exports.string().min(1),
10975
+ editable: external_exports.boolean().optional()
10976
+ }).strict(),
10977
+ external_exports.object({
10978
+ from: external_exports.literal("field"),
10979
+ name: external_exports.string().min(1),
10980
+ editable: external_exports.boolean().optional()
10981
+ }).strict(),
10982
+ external_exports.object({
10983
+ from: external_exports.literal("relationship"),
10984
+ name: external_exports.string().min(1),
10985
+ path: external_exports.string().min(1).optional(),
10986
+ many: external_exports.boolean().optional(),
10987
+ editable: external_exports.boolean().optional()
10988
+ }).strict(),
10989
+ external_exports.object({
10990
+ from: external_exports.literal("session"),
10991
+ path: external_exports.string().min(1),
10992
+ editable: external_exports.boolean().optional()
10993
+ }).strict(),
10994
+ external_exports.object({
10995
+ from: external_exports.literal("actor"),
10996
+ path: external_exports.string().min(1),
10997
+ editable: external_exports.boolean().optional()
10998
+ }).strict(),
10999
+ external_exports.record(external_exports.string(), StateTransitionInputBindingSchema)
11000
+ ])
11001
+ );
11002
+ var StateTransitionActionSchema = external_exports.object({
11003
+ effect: external_exports.string().min(1),
11004
+ input: external_exports.record(external_exports.string(), StateTransitionInputBindingSchema).optional()
11005
+ }).strict();
11006
+ var StateTransitionAssigneeSchema = external_exports.object({
11007
+ kind: external_exports.string().min(1),
11008
+ from: StateTransitionInputBindingSchema.optional(),
11009
+ role: external_exports.string().optional(),
11010
+ label: external_exports.string().optional()
11011
+ }).strict();
11012
+ var StateTransitionRelatedStateRequirementSchema = external_exports.object({
11013
+ relationship: external_exports.string().min(1),
11014
+ machine: external_exports.string().min(1),
11015
+ state: external_exports.string().min(1),
11016
+ className: external_exports.string().min(1).optional(),
11017
+ label: external_exports.string().optional(),
11018
+ mode: external_exports.enum(["every", "some", "any"]).optional()
11019
+ }).strict();
11020
+ var StateTransitionRequirementsSchema = external_exports.object({
11021
+ fields: external_exports.array(external_exports.string().min(1)).optional(),
11022
+ relationships: external_exports.array(external_exports.string().min(1)).optional(),
11023
+ relatedStates: external_exports.array(StateTransitionRelatedStateRequirementSchema).optional()
11024
+ }).strict();
11025
+ var StateTransitionPermissionSchema = external_exports.union([
11026
+ external_exports.string().min(1),
11027
+ external_exports.object({
11028
+ profile: external_exports.string().min(1).optional(),
11029
+ profileId: external_exports.string().min(1).optional(),
11030
+ label: external_exports.string().optional(),
11031
+ reason: external_exports.string().optional()
11032
+ }).strict()
11033
+ ]);
11034
+ var StateTransitionExpectedOutcomeSchema = external_exports.union([
11035
+ external_exports.string().min(1),
11036
+ external_exports.object({
11037
+ machine: external_exports.string().min(1).optional(),
11038
+ state: external_exports.string().min(1),
11039
+ summary: external_exports.string().optional()
11040
+ }).strict()
11041
+ ]);
10774
11042
  var StateMachineTransitionSchema = external_exports.object({
10775
11043
  name: external_exports.string().min(1),
10776
11044
  from: external_exports.string().min(1),
10777
- to: external_exports.string().min(1)
11045
+ to: external_exports.string().min(1),
11046
+ label: external_exports.string().optional(),
11047
+ description: external_exports.string().optional(),
11048
+ action: StateTransitionActionSchema.optional(),
11049
+ assignee: StateTransitionAssigneeSchema.optional(),
11050
+ requirements: StateTransitionRequirementsSchema.optional(),
11051
+ permission: StateTransitionPermissionSchema.optional(),
11052
+ risk: external_exports.enum(["low", "medium", "high"]).optional(),
11053
+ expectedOutcome: StateTransitionExpectedOutcomeSchema.optional()
10778
11054
  }).strict();
10779
11055
  external_exports.object({
10780
11056
  name: external_exports.string().min(1),
11057
+ stateField: external_exports.string().min(1).optional(),
10781
11058
  entryState: external_exports.string().min(1),
10782
11059
  states: external_exports.array(StateMachineStateSchema).min(1),
10783
11060
  transitions: external_exports.array(StateMachineTransitionSchema),
@@ -10844,6 +11121,16 @@ var PoliciesSchema = external_exports.object({
10844
11121
  confirmWhen: external_exports.array(PolicyRuleSchema).optional(),
10845
11122
  denyWhen: external_exports.array(PolicyRuleSchema).optional()
10846
11123
  }).strict();
11124
+ var CreatesSchema = external_exports.union([
11125
+ external_exports.string().min(1),
11126
+ external_exports.object({
11127
+ className: external_exports.string().min(1),
11128
+ idPath: external_exports.string().min(1).optional(),
11129
+ pathPath: external_exports.string().min(1).optional(),
11130
+ statePath: external_exports.string().min(1).optional(),
11131
+ classStateHandle: external_exports.boolean().optional()
11132
+ }).strict()
11133
+ ]);
10847
11134
  external_exports.object({
10848
11135
  postCondition: external_exports.union([
10849
11136
  external_exports.string(),
@@ -10874,6 +11161,7 @@ external_exports.object({
10874
11161
  mode: external_exports.string().optional()
10875
11162
  }).strict()
10876
11163
  ]).optional(),
11164
+ creates: CreatesSchema.optional(),
10877
11165
  access: external_exports.enum(["read", "write", "ui"]).optional(),
10878
11166
  effectKind: external_exports.enum(["read", "write", "ui"]).optional(),
10879
11167
  sideEffect: external_exports.enum(["read", "write", "ui", "readonly", "read_only"]).optional(),
@@ -11101,9 +11389,10 @@ function mergeMethodSummaryPatch(target, patch) {
11101
11389
  if (patch.metamodels !== void 0) target.metamodels = patch.metamodels;
11102
11390
  if (patch.effectBehaviors !== void 0)
11103
11391
  target.effectBehaviors = patch.effectBehaviors;
11392
+ if (patch.creates !== void 0) target.creates = patch.creates;
11104
11393
  if (patch.static !== void 0) target.static = patch.static;
11105
11394
  }
11106
- function toPascalCase(value) {
11395
+ function toPascalCase2(value) {
11107
11396
  return value.split(/[_:\-\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
11108
11397
  }
11109
11398
  function normalizeNotesInput(input) {
@@ -11161,29 +11450,60 @@ function normalizeEffectBehaviorSummary(metamodels) {
11161
11450
  }
11162
11451
  return Object.keys(result).length > 0 ? result : null;
11163
11452
  }
11164
- function buildEffectBehaviorDocs(effectBehaviors) {
11165
- if (!effectBehaviors) {
11166
- return [];
11453
+ function normalizeCreationSummary(metamodels) {
11454
+ if (!isObject(metamodels)) return null;
11455
+ let raw = metamodels.creates;
11456
+ if (typeof raw === "string" && raw.trim().length > 0) {
11457
+ const trimmed = raw.trim();
11458
+ if (trimmed.startsWith("{") || trimmed.startsWith('"')) {
11459
+ try {
11460
+ raw = JSON.parse(trimmed);
11461
+ } catch {
11462
+ return { className: trimmed };
11463
+ }
11464
+ } else {
11465
+ return { className: trimmed };
11466
+ }
11167
11467
  }
11468
+ if (typeof raw === "string" && raw.trim().length > 0) {
11469
+ return { className: raw.trim() };
11470
+ }
11471
+ if (!isObject(raw)) return null;
11472
+ const className = typeof raw.className === "string" && raw.className.trim() ? raw.className.trim() : "";
11473
+ if (!className) return null;
11474
+ return {
11475
+ className,
11476
+ ...typeof raw.idPath === "string" && raw.idPath.trim() ? { idPath: raw.idPath.trim() } : {},
11477
+ ...typeof raw.pathPath === "string" && raw.pathPath.trim() ? { pathPath: raw.pathPath.trim() } : {},
11478
+ ...typeof raw.statePath === "string" && raw.statePath.trim() ? { statePath: raw.statePath.trim() } : {},
11479
+ ...typeof raw.classStateHandle === "boolean" ? { classStateHandle: raw.classStateHandle } : {}
11480
+ };
11481
+ }
11482
+ function buildEffectBehaviorDocs(effectBehaviors, creates) {
11168
11483
  const docs = [];
11169
- if (effectBehaviors.approvalRequired?.required) {
11484
+ if (creates) {
11485
+ docs.push(
11486
+ `Creation method: creates ${creates.className}. The agent may use generated class-level new-record action methods for this class.`
11487
+ );
11488
+ }
11489
+ if (effectBehaviors?.approvalRequired?.required) {
11170
11490
  docs.push(
11171
11491
  effectBehaviors.approvalRequired.reason ? `Approval required: ${effectBehaviors.approvalRequired.reason}.` : "Approval required before execution."
11172
11492
  );
11173
11493
  }
11174
- if (effectBehaviors.postCondition) {
11494
+ if (effectBehaviors?.postCondition) {
11175
11495
  docs.push(`Post-condition: ${effectBehaviors.postCondition.condition}.`);
11176
11496
  if (effectBehaviors.postCondition.description) {
11177
11497
  docs.push(effectBehaviors.postCondition.description);
11178
11498
  }
11179
11499
  }
11180
- if (effectBehaviors.dryRun?.enabled) {
11500
+ if (effectBehaviors?.dryRun?.enabled) {
11181
11501
  docs.push("Supports dry run.");
11182
11502
  if (effectBehaviors.dryRun.description) {
11183
11503
  docs.push(effectBehaviors.dryRun.description);
11184
11504
  }
11185
11505
  }
11186
- if (effectBehaviors.reverse) {
11506
+ if (effectBehaviors?.reverse) {
11187
11507
  if (effectBehaviors.reverse.handler) {
11188
11508
  docs.push(`Reverse handler: ${effectBehaviors.reverse.handler}.`);
11189
11509
  } else {
@@ -11242,13 +11562,21 @@ function buildEffectBehaviorMutations(toolPath, spec) {
11242
11562
  query: `mutation { at(path: ${JSON.stringify(toolPath)}) { set_approval_required(${args}) { kind } } }`
11243
11563
  });
11244
11564
  }
11565
+ if (spec.creates !== void 0) {
11566
+ mutations.push({
11567
+ label: `set creates on ${toolPath}`,
11568
+ query: `mutation { at(path: ${JSON.stringify(toolPath)}) { create_submodel(subpath: "creates", label: "creates") { set_string_value(value: ${JSON.stringify(
11569
+ JSON.stringify(spec.creates)
11570
+ )}) { done } } } }`
11571
+ });
11572
+ }
11245
11573
  return mutations;
11246
11574
  }
11247
11575
  function readMethodEffectBehaviors(rawMethod) {
11576
+ const metamodels = isObject(rawMethod.metamodels) ? rawMethod.metamodels : null;
11248
11577
  return {
11249
- effectBehaviors: normalizeEffectBehaviorSummary(
11250
- isObject(rawMethod.metamodels) ? rawMethod.metamodels : null
11251
- )
11578
+ effectBehaviors: normalizeEffectBehaviorSummary(metamodels),
11579
+ creates: normalizeCreationSummary(metamodels)
11252
11580
  };
11253
11581
  }
11254
11582
  var effectBehaviorsMetamodelPackage = defineMetamodelPackage({
@@ -11270,6 +11598,10 @@ var effectBehaviorsMetamodelPackage = defineMetamodelPackage({
11270
11598
  {
11271
11599
  key: "approvalRequired",
11272
11600
  description: "Boolean or `{ required, reason, mode }`."
11601
+ },
11602
+ {
11603
+ key: "creates",
11604
+ description: 'Marks a static method as an allowed creator for a class. Use `creates: "class_name"` or `{ className, idPath, pathPath, statePath, classStateHandle }`.'
11273
11605
  }
11274
11606
  ]
11275
11607
  },
@@ -11389,7 +11721,10 @@ var effectBehaviorsMetamodelPackage = defineMetamodelPackage({
11389
11721
  ...methodIR,
11390
11722
  docs: [
11391
11723
  ...methodIR.docs,
11392
- ...buildEffectBehaviorDocs(methodSummary.effectBehaviors)
11724
+ ...buildEffectBehaviorDocs(
11725
+ methodSummary.effectBehaviors,
11726
+ methodSummary.creates
11727
+ )
11393
11728
  ]
11394
11729
  };
11395
11730
  }
@@ -11630,15 +11965,50 @@ function toRecordSearchResult(className, node) {
11630
11965
  return [];
11631
11966
  }
11632
11967
  ) : [];
11968
+ const graphPathId = extractRecordIdFromGraphPath(path, className);
11969
+ const realIdField = fields.find(
11970
+ (field) => normalizeGraphPathSegment(field.name) === "real_id" && typeof field.value === "string" && field.value.trim()
11971
+ );
11972
+ const id = typeof realIdField?.value === "string" ? realIdField.value.trim() : graphPathId;
11973
+ const rawLabel = typeof node.label === "string" && node.label.trim() ? node.label : "";
11974
+ if (fields.length === 0 && rawLabel && isPlaceholderRecordLabel(rawLabel, graphPathId, path)) {
11975
+ return null;
11976
+ }
11977
+ const fallbackLabel = displayLabelFromFields(fields);
11978
+ const label = rawLabel && !isPlaceholderRecordLabel(rawLabel, id, path) ? rawLabel : fallbackLabel || rawLabel || id;
11633
11979
  return {
11634
11980
  path,
11635
11981
  className,
11636
- id: extractRecordIdFromGraphPath(path, className),
11637
- label: typeof node.label === "string" && node.label.trim() ? node.label : extractRecordIdFromGraphPath(path, className),
11982
+ id,
11983
+ label,
11638
11984
  description: typeof node.description === "string" && node.description.trim() ? node.description : null,
11639
11985
  fields
11640
11986
  };
11641
11987
  }
11988
+ function isPlaceholderRecordLabel(label, id, path) {
11989
+ const normalizedLabel = normalizeGraphPathSegment(label);
11990
+ return normalizedLabel === normalizeGraphPathSegment(id) || normalizedLabel === normalizeGraphPathSegment(path);
11991
+ }
11992
+ function displayLabelFromFields(fields) {
11993
+ const preferredFieldNames = [
11994
+ "name",
11995
+ "title",
11996
+ "label",
11997
+ "display_name",
11998
+ "file_name",
11999
+ "number",
12000
+ "code"
12001
+ ];
12002
+ for (const preferred of preferredFieldNames) {
12003
+ const match = fields.find(
12004
+ (field) => normalizeGraphPathSegment(field.name) === preferred && typeof field.value === "string" && field.value.trim()
12005
+ );
12006
+ if (typeof match?.value === "string") {
12007
+ return match.value.trim();
12008
+ }
12009
+ }
12010
+ return null;
12011
+ }
11642
12012
  function normalizeRecordSearchText(value) {
11643
12013
  return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, " ").replace(/\s+/g, " ").trim();
11644
12014
  }
@@ -12538,15 +12908,47 @@ var searchableMetamodelPackage = defineMetamodelPackage({
12538
12908
 
12539
12909
  // ../metamodel-state-machine/src/index.ts
12540
12910
  function normalizeStateMachines(values) {
12911
+ const parseJsonRecord = (value) => {
12912
+ if (value && typeof value === "object" && !Array.isArray(value)) {
12913
+ return value;
12914
+ }
12915
+ if (typeof value !== "string" || !value.trim()) return null;
12916
+ try {
12917
+ const parsed = JSON.parse(value);
12918
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
12919
+ } catch {
12920
+ return null;
12921
+ }
12922
+ };
12923
+ const parseJsonValue = (value) => {
12924
+ if (value === null || typeof value === "undefined") return null;
12925
+ if (typeof value !== "string") return value;
12926
+ if (!value.trim()) return null;
12927
+ try {
12928
+ return JSON.parse(value);
12929
+ } catch {
12930
+ return value;
12931
+ }
12932
+ };
12541
12933
  return (values || []).map((machine) => {
12542
12934
  const states = (machine?.states || []).map((state) => ({
12543
12935
  name: String(state?.name || ""),
12544
- isFinal: Boolean(state?.is_final)
12936
+ label: typeof state?.label === "string" ? state.label : null,
12937
+ description: typeof state?.description === "string" ? state.description : null,
12938
+ isFinal: Boolean(state?.is_final ?? state?.isFinal)
12545
12939
  })).filter((state) => state.name.length > 0);
12546
12940
  const transitions = (machine?.transitions || []).map((transition) => ({
12547
12941
  name: String(transition?.name || ""),
12548
12942
  from: String(transition?.from?.name || ""),
12549
- to: String(transition?.to?.name || "")
12943
+ to: String(transition?.to?.name || ""),
12944
+ label: typeof transition?.label === "string" ? transition.label : null,
12945
+ description: typeof transition?.description === "string" ? transition.description : null,
12946
+ action: parseJsonRecord(transition?.action) || parseJsonRecord(transition?.action_json),
12947
+ assignee: parseJsonRecord(transition?.assignee) || parseJsonRecord(transition?.assignee_json),
12948
+ requirements: parseJsonRecord(transition?.requirements) || parseJsonRecord(transition?.requirements_json),
12949
+ permission: parseJsonValue(transition?.permission) ?? parseJsonValue(transition?.permission_json),
12950
+ risk: transition?.risk === "low" || transition?.risk === "medium" || transition?.risk === "high" ? transition.risk : null,
12951
+ expectedOutcome: parseJsonValue(transition?.expectedOutcome) ?? parseJsonValue(transition?.expected_outcome_json)
12550
12952
  })).filter(
12551
12953
  (transition) => transition.name.length > 0 && transition.from.length > 0 && transition.to.length > 0
12552
12954
  );
@@ -12560,7 +12962,7 @@ function normalizeStateMachines(values) {
12560
12962
  }).filter((machine) => machine.name.length > 0);
12561
12963
  }
12562
12964
  function stateTypeName(className, machineName) {
12563
- return `${toPascalCase(className)}${toPascalCase(machineName)}`;
12965
+ return `${toPascalCase2(className)}${toPascalCase2(machineName)}`;
12564
12966
  }
12565
12967
  function transitionTypeName(className, machineName) {
12566
12968
  return `${stateTypeName(className, machineName)}Transition`;
@@ -12568,6 +12970,15 @@ function transitionTypeName(className, machineName) {
12568
12970
  function pathTypeName(className, machineName) {
12569
12971
  return `${stateTypeName(className, machineName)}Path`;
12570
12972
  }
12973
+ function methodToken(value) {
12974
+ const token = String(value || "").trim().replace(/[^A-Za-z0-9_]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
12975
+ return token || "state";
12976
+ }
12977
+ function transitionActionsForMachine(machine) {
12978
+ return Object.fromEntries(
12979
+ (machine.transitions || []).filter((transition) => transition.action?.effect).map((transition) => [transition.name, transition.action])
12980
+ );
12981
+ }
12571
12982
  function normalizeStateDefinitions(machine) {
12572
12983
  const finalStates = new Set(machine.finalStates || []);
12573
12984
  const states = /* @__PURE__ */ new Map();
@@ -12581,6 +12992,8 @@ function normalizeStateDefinitions(machine) {
12581
12992
  }
12582
12993
  states.set(rawState.name, {
12583
12994
  name: rawState.name,
12995
+ label: rawState.label,
12996
+ description: rawState.description,
12584
12997
  isFinal: Boolean(rawState.isFinal) || finalStates.has(rawState.name)
12585
12998
  });
12586
12999
  }
@@ -12592,6 +13005,44 @@ function normalizeStateDefinitions(machine) {
12592
13005
  }
12593
13006
  return [...states.values()];
12594
13007
  }
13008
+ function transitionMetadataGraphqlArgs(transition) {
13009
+ const args = [];
13010
+ if (typeof transition.label === "string") {
13011
+ args.push(`label: ${JSON.stringify(transition.label)}`);
13012
+ }
13013
+ if (typeof transition.description === "string") {
13014
+ args.push(`description: ${JSON.stringify(transition.description)}`);
13015
+ }
13016
+ if (transition.action) {
13017
+ args.push(
13018
+ `action_json: ${JSON.stringify(JSON.stringify(transition.action))}`
13019
+ );
13020
+ }
13021
+ if (transition.assignee) {
13022
+ args.push(
13023
+ `assignee_json: ${JSON.stringify(JSON.stringify(transition.assignee))}`
13024
+ );
13025
+ }
13026
+ if (transition.requirements) {
13027
+ args.push(
13028
+ `requirements_json: ${JSON.stringify(JSON.stringify(transition.requirements))}`
13029
+ );
13030
+ }
13031
+ if (transition.permission) {
13032
+ args.push(
13033
+ `permission_json: ${JSON.stringify(JSON.stringify(transition.permission))}`
13034
+ );
13035
+ }
13036
+ if (transition.risk) {
13037
+ args.push(`risk: ${JSON.stringify(transition.risk)}`);
13038
+ }
13039
+ if (transition.expectedOutcome) {
13040
+ args.push(
13041
+ `expected_outcome_json: ${JSON.stringify(JSON.stringify(transition.expectedOutcome))}`
13042
+ );
13043
+ }
13044
+ return args.length > 0 ? `, ${args.join(", ")}` : "";
13045
+ }
12595
13046
  function buildStateMachineModelMutations(modelPath, machines) {
12596
13047
  const mutations = [];
12597
13048
  for (const machine of machines || []) {
@@ -12602,12 +13053,13 @@ function buildStateMachineModelMutations(modelPath, machines) {
12602
13053
  )}, entry_state: ${JSON.stringify(machine.entryState)}) { name } } }`
12603
13054
  });
12604
13055
  for (const state of normalizeStateDefinitions(machine)) {
12605
- if (state.name === machine.entryState && !state.isFinal) continue;
13056
+ if (state.name === machine.entryState && !state.isFinal && !state.label && !state.description)
13057
+ continue;
12606
13058
  mutations.push({
12607
13059
  label: `add state ${state.name} on ${modelPath}.${machine.name}`,
12608
13060
  query: `mutation { at(path: ${JSON.stringify(modelPath)}) { state_machine(name: ${JSON.stringify(
12609
13061
  machine.name
12610
- )}) { add_state(name: ${JSON.stringify(state.name)}, is_final: ${state.isFinal}) { name } } } }`
13062
+ )}) { add_state(name: ${JSON.stringify(state.name)}, is_final: ${state.isFinal}, label: ${JSON.stringify(state.label || null)}, description: ${JSON.stringify(state.description || null)}) { name } } } }`
12611
13063
  });
12612
13064
  }
12613
13065
  for (const transition of machine.transitions || []) {
@@ -12619,7 +13071,7 @@ function buildStateMachineModelMutations(modelPath, machines) {
12619
13071
  transition.name
12620
13072
  )}, from: ${JSON.stringify(transition.from)}, to: ${JSON.stringify(
12621
13073
  transition.to
12622
- )}) { name } } } }`
13074
+ )}${transitionMetadataGraphqlArgs(transition)}) { name } } } }`
12623
13075
  });
12624
13076
  }
12625
13077
  }
@@ -12646,7 +13098,7 @@ function buildMachineMethods(classSummary, machine) {
12646
13098
  const transitionName = transitionTypeName(classSummary.name, machine.name);
12647
13099
  pathTypeName(classSummary.name, machine.name);
12648
13100
  const docsPrefix = `${classSummary.name}.${machine.name}`;
12649
- return [
13101
+ const methods = [
12650
13102
  {
12651
13103
  name: `get_${machine.name}`,
12652
13104
  docs: [`Get the current ${docsPrefix} state.`],
@@ -12669,7 +13121,7 @@ function buildMachineMethods(classSummary, machine) {
12669
13121
  ],
12670
13122
  static: false,
12671
13123
  params: [{ name: "target", type: stateName }],
12672
- returnType: `Promise<${toPascalCase(classSummary.name)}>`,
13124
+ returnType: `Promise<${toPascalCase2(classSummary.name)}>`,
12673
13125
  runtime: {
12674
13126
  kind: "state_machine",
12675
13127
  machineName: machine.name,
@@ -12742,6 +13194,99 @@ function buildMachineMethods(classSummary, machine) {
12742
13194
  }
12743
13195
  }
12744
13196
  ];
13197
+ const creationMethods = (classSummary.methods || []).filter(
13198
+ (method) => method.static === true && Boolean(method.creates) && method.creates?.className === classSummary.name && typeof method.effectKey === "string" && method.effectKey.length > 0
13199
+ );
13200
+ for (const state of machine.states) {
13201
+ const stateNameValue = typeof state === "string" ? state : String(state?.name || "");
13202
+ if (!stateNameValue) continue;
13203
+ const token = methodToken(stateNameValue);
13204
+ methods.push(
13205
+ {
13206
+ name: `reach_${machine.name}_to_${token}`,
13207
+ docs: [`Reach ${docsPrefix} state ${stateNameValue}.`],
13208
+ static: false,
13209
+ params: [],
13210
+ returnType: `Promise<${toPascalCase2(classSummary.name)}>`,
13211
+ runtime: {
13212
+ kind: "state_machine",
13213
+ machineName: machine.name,
13214
+ className: classSummary.name,
13215
+ stateTypeName: stateName,
13216
+ transitionTypeName: transitionName,
13217
+ operation: "reach",
13218
+ targetState: stateNameValue,
13219
+ transitionActions: transitionActionsForMachine(machine)
13220
+ }
13221
+ },
13222
+ {
13223
+ name: `prepare_${machine.name}_to_${token}`,
13224
+ docs: [
13225
+ `Prepare a reviewable artifact that can move ${docsPrefix} to ${stateNameValue}.`
13226
+ ],
13227
+ static: false,
13228
+ params: [],
13229
+ returnType: "Promise<SessionArtifactRecord>",
13230
+ runtime: {
13231
+ kind: "state_machine",
13232
+ machineName: machine.name,
13233
+ className: classSummary.name,
13234
+ stateTypeName: stateName,
13235
+ transitionTypeName: transitionName,
13236
+ operation: "prepare_reach",
13237
+ targetState: stateNameValue,
13238
+ transitionActions: transitionActionsForMachine(machine)
13239
+ }
13240
+ }
13241
+ );
13242
+ for (const creationMethod of creationMethods) {
13243
+ const creationRuntime = {
13244
+ kind: "state_machine",
13245
+ machineName: machine.name,
13246
+ className: classSummary.name,
13247
+ stateTypeName: stateName,
13248
+ transitionTypeName: transitionName,
13249
+ operation: "prepare_create_reach",
13250
+ targetState: stateNameValue,
13251
+ transitionActions: transitionActionsForMachine(machine),
13252
+ creation: {
13253
+ methodName: creationMethod.name,
13254
+ effectKey: creationMethod.effectKey || creationMethod.name,
13255
+ inputSchema: creationMethod.inputSchema,
13256
+ outputSchema: creationMethod.outputSchema,
13257
+ creates: creationMethod.creates
13258
+ }
13259
+ };
13260
+ const viaName = `prepare_${machine.name}_to_${token}_via_${methodToken(creationMethod.name)}`;
13261
+ methods.push({
13262
+ name: viaName,
13263
+ docs: [
13264
+ `Prepare a reviewable artifact that will create a new ${classSummary.name} through ${creationMethod.name}, then move ${docsPrefix} to ${stateNameValue}.`
13265
+ ],
13266
+ static: true,
13267
+ params: [
13268
+ { name: "input", type: "Record<string, any>", optional: true }
13269
+ ],
13270
+ returnType: "Promise<SessionArtifactRecord>",
13271
+ runtime: creationRuntime
13272
+ });
13273
+ if (creationMethods.length === 1) {
13274
+ methods.push({
13275
+ name: `prepare_${machine.name}_to_${token}`,
13276
+ docs: [
13277
+ `Prepare a reviewable artifact that will create a new ${classSummary.name}, then move ${docsPrefix} to ${stateNameValue}.`
13278
+ ],
13279
+ static: true,
13280
+ params: [
13281
+ { name: "input", type: "Record<string, any>", optional: true }
13282
+ ],
13283
+ returnType: "Promise<SessionArtifactRecord>",
13284
+ runtime: creationRuntime
13285
+ });
13286
+ }
13287
+ }
13288
+ }
13289
+ return methods;
12745
13290
  }
12746
13291
  function readStateMachineSummaries(rawClass) {
12747
13292
  return {
@@ -12764,8 +13309,8 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12764
13309
  type StateMachineMutation {
12765
13310
  name: String!
12766
13311
  state_machine: StateMachine!
12767
- add_state(name: String!, is_final: Boolean): StateMachineMutation!
12768
- add_transition(name: String!, from: String!, to: String!): StateMachineMutation!
13312
+ add_state(name: String!, is_final: Boolean, label: String, description: String): StateMachineMutation!
13313
+ 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!
12769
13314
  activate_transition(name: String!): StateMachineMutation!
12770
13315
  }
12771
13316
 
@@ -12782,6 +13327,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12782
13327
  type StateMachineSnapshotMutation {
12783
13328
  snapshot: StateMachineSnapshot!
12784
13329
  activate_transition(name: String!): StateMachineSnapshotMutation!
13330
+ observe_state(state: String!, force: Boolean, source: String): StateMachineSnapshotMutation!
12785
13331
  }
12786
13332
 
12787
13333
  type StateMachine {
@@ -12801,6 +13347,8 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12801
13347
 
12802
13348
  type StateMachineState {
12803
13349
  name: String!
13350
+ label: String
13351
+ description: String
12804
13352
  is_final: Boolean!
12805
13353
  }
12806
13354
 
@@ -12808,6 +13356,14 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12808
13356
  name: String!
12809
13357
  from: StateMachineState!
12810
13358
  to: StateMachineState!
13359
+ label: String
13360
+ description: String
13361
+ action_json: String
13362
+ assignee_json: String
13363
+ requirements_json: String
13364
+ permission_json: String
13365
+ risk: String
13366
+ expected_outcome_json: String
12811
13367
  }
12812
13368
 
12813
13369
  type StateMachinePath {
@@ -12860,23 +13416,47 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12860
13416
  StateMachineMutation: {
12861
13417
  name: (value) => value.name,
12862
13418
  state_machine: async (value) => await run(value.target.state_machine(value.name)),
12863
- add_state: async (value, { name, is_final }) => {
13419
+ add_state: async (value, { name, is_final, label, description }) => {
12864
13420
  await run(
12865
13421
  value.target.add_state_machine_state(
12866
13422
  value.name,
12867
13423
  name,
12868
- is_final ?? false
13424
+ is_final ?? false,
13425
+ label,
13426
+ description
12869
13427
  )
12870
13428
  );
12871
13429
  return value;
12872
13430
  },
12873
- add_transition: async (value, { name, from, to }) => {
13431
+ add_transition: async (value, {
13432
+ name,
13433
+ from,
13434
+ to,
13435
+ label,
13436
+ description,
13437
+ action_json,
13438
+ assignee_json,
13439
+ requirements_json,
13440
+ permission_json,
13441
+ risk,
13442
+ expected_outcome_json
13443
+ }) => {
12874
13444
  await run(
12875
13445
  value.target.add_state_machine_transition(
12876
13446
  value.name,
12877
13447
  name,
12878
13448
  from,
12879
- to
13449
+ to,
13450
+ {
13451
+ label,
13452
+ description,
13453
+ actionJson: action_json,
13454
+ assigneeJson: assignee_json,
13455
+ requirementsJson: requirements_json,
13456
+ permissionJson: permission_json,
13457
+ risk,
13458
+ expectedOutcomeJson: expected_outcome_json
13459
+ }
12880
13460
  )
12881
13461
  );
12882
13462
  return value;
@@ -12895,16 +13475,37 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12895
13475
  value.target.activate_state_machine_transition(value.name, name)
12896
13476
  );
12897
13477
  return value;
13478
+ },
13479
+ observe_state: async (value, { state, force, source }) => {
13480
+ await run(
13481
+ value.target.observe_state_machine_state(
13482
+ value.name,
13483
+ state,
13484
+ force === true,
13485
+ source
13486
+ )
13487
+ );
13488
+ return value;
12898
13489
  }
12899
13490
  },
12900
13491
  StateMachineState: {
12901
13492
  name: (value) => value.name,
13493
+ label: (value) => value.label || null,
13494
+ description: (value) => value.description || null,
12902
13495
  is_final: (value) => value.is_final
12903
13496
  },
12904
13497
  StateMachineTransition: {
12905
13498
  name: (value) => value.name,
12906
13499
  from: (value) => value.from_state || { name: value.from, is_final: false },
12907
- to: (value) => value.to_state || { name: value.to, is_final: false }
13500
+ to: (value) => value.to_state || { name: value.to, is_final: false },
13501
+ label: (value) => value.label || null,
13502
+ description: (value) => value.description || null,
13503
+ action_json: (value) => value.action_json || null,
13504
+ assignee_json: (value) => value.assignee_json || null,
13505
+ requirements_json: (value) => value.requirements_json || null,
13506
+ permission_json: (value) => value.permission_json || null,
13507
+ risk: (value) => value.risk || null,
13508
+ expected_outcome_json: (value) => value.expected_outcome_json || null
12908
13509
  },
12909
13510
  StateMachinePath: {
12910
13511
  states: (value) => value.states,
@@ -12971,6 +13572,14 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12971
13572
  name
12972
13573
  from { name }
12973
13574
  to { name }
13575
+ label
13576
+ description
13577
+ action_json
13578
+ assignee_json
13579
+ requirements_json
13580
+ permission_json
13581
+ risk
13582
+ expected_outcome_json
12974
13583
  }
12975
13584
  }`
12976
13585
  ]
@@ -13002,6 +13611,104 @@ function describeRule(rule) {
13002
13611
  return `${rule.operator} ${String(rule.booleanValue)}`;
13003
13612
  return rule.operator;
13004
13613
  }
13614
+ function valueAsString(value) {
13615
+ if (typeof value === "string") return value;
13616
+ if (typeof value === "number" || typeof value === "boolean") {
13617
+ return String(value);
13618
+ }
13619
+ return "";
13620
+ }
13621
+ function valueAsNumber(value) {
13622
+ if (typeof value === "number") return value;
13623
+ if (typeof value === "string" && value.trim().length > 0) {
13624
+ const parsed = Number(value);
13625
+ return Number.isFinite(parsed) ? parsed : NaN;
13626
+ }
13627
+ return NaN;
13628
+ }
13629
+ function ruleStringValue(rule) {
13630
+ return rule.stringValue ?? rule.string_value ?? "";
13631
+ }
13632
+ function ruleNumberValue(rule) {
13633
+ return rule.numberValue ?? rule.number_value;
13634
+ }
13635
+ function ruleBooleanValue(rule) {
13636
+ return rule.booleanValue ?? rule.boolean_value;
13637
+ }
13638
+ function evaluateValidationRule(value, rule) {
13639
+ const operator = typeof rule.operator === "string" ? rule.operator : "";
13640
+ const stringValue2 = ruleStringValue(rule);
13641
+ const numberValue = ruleNumberValue(rule);
13642
+ const booleanValue = ruleBooleanValue(rule);
13643
+ let passed = true;
13644
+ switch (operator) {
13645
+ case "eq":
13646
+ if (numberValue !== void 0) {
13647
+ passed = valueAsNumber(value) === numberValue;
13648
+ } else if (booleanValue !== void 0) {
13649
+ passed = value === booleanValue;
13650
+ } else {
13651
+ passed = valueAsString(value) === stringValue2;
13652
+ }
13653
+ break;
13654
+ case "neq":
13655
+ if (numberValue !== void 0) {
13656
+ passed = valueAsNumber(value) !== numberValue;
13657
+ } else if (booleanValue !== void 0) {
13658
+ passed = value !== booleanValue;
13659
+ } else {
13660
+ passed = valueAsString(value) !== stringValue2;
13661
+ }
13662
+ break;
13663
+ case "gt":
13664
+ passed = valueAsNumber(value) > (numberValue ?? NaN);
13665
+ break;
13666
+ case "gte":
13667
+ passed = valueAsNumber(value) >= (numberValue ?? NaN);
13668
+ break;
13669
+ case "lt":
13670
+ passed = valueAsNumber(value) < (numberValue ?? NaN);
13671
+ break;
13672
+ case "lte":
13673
+ passed = valueAsNumber(value) <= (numberValue ?? NaN);
13674
+ break;
13675
+ case "true":
13676
+ passed = value === true;
13677
+ break;
13678
+ case "false":
13679
+ passed = value === false;
13680
+ break;
13681
+ case "regex":
13682
+ try {
13683
+ passed = new RegExp(stringValue2).test(valueAsString(value));
13684
+ } catch {
13685
+ passed = false;
13686
+ }
13687
+ break;
13688
+ case "contains":
13689
+ passed = valueAsString(value).includes(stringValue2);
13690
+ break;
13691
+ case "not_contains":
13692
+ passed = !valueAsString(value).includes(stringValue2);
13693
+ break;
13694
+ case "starts_with":
13695
+ passed = valueAsString(value).startsWith(stringValue2);
13696
+ break;
13697
+ case "ends_with":
13698
+ passed = valueAsString(value).endsWith(stringValue2);
13699
+ break;
13700
+ default:
13701
+ passed = true;
13702
+ }
13703
+ return {
13704
+ passed,
13705
+ operator,
13706
+ message: rule.message ?? null
13707
+ };
13708
+ }
13709
+ function validationRuleFailureMessage(path, rule) {
13710
+ return rule.message || `${path} failed ${rule.operator} validation`;
13711
+ }
13005
13712
  function normalizeRule(rule) {
13006
13713
  const operator = typeof rule?.operator === "string" ? rule.operator : "";
13007
13714
  if (operator.length === 0) return null;
@@ -13224,6 +13931,26 @@ var STANDARD_MODULES_OPERATIONS = [
13224
13931
  var BUILTIN_MODULES = {
13225
13932
  standard_modules: STANDARD_MODULES_OPERATIONS
13226
13933
  };
13934
+ function stateNameFromMethodName(methodName) {
13935
+ const raw = methodName.startsWith("to") ? methodName.slice(2) : methodName;
13936
+ return raw.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
13937
+ }
13938
+ function appendQueryOptions(searchParams, query) {
13939
+ for (const [key, value] of Object.entries(query || {})) {
13940
+ if (value === null || typeof value === "undefined" || value === "") {
13941
+ continue;
13942
+ }
13943
+ if (value instanceof Date) {
13944
+ searchParams.set(key, value.toISOString());
13945
+ continue;
13946
+ }
13947
+ if (Array.isArray(value)) {
13948
+ if (value.length > 0) searchParams.set(key, value.join(","));
13949
+ continue;
13950
+ }
13951
+ searchParams.set(key, String(value));
13952
+ }
13953
+ }
13227
13954
  var DEFAULT_DIRECT_RECORD_OBJECTS_REQUEST_BATCH_SIZE = 100;
13228
13955
  var MAX_RECORD_OBJECTS_CONCURRENCY = 16;
13229
13956
  var DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_COUNT = 3;
@@ -13254,8 +13981,20 @@ function bodyInitFromSessionFileUpload(body) {
13254
13981
  return body;
13255
13982
  }
13256
13983
  var EFFECT_CATALOG_SYNC_TIMEOUT_MS = 12e4;
13984
+ var EFFECT_CATALOG_SYNC_BATCH_SIZE = 20;
13257
13985
  var EFFECT_CATALOG_SYNC_RETRY_COUNT = 3;
13258
13986
  var EFFECT_CATALOG_SYNC_RETRY_DELAY_MS = 1e3;
13987
+ function chunkItems(items, batchSize) {
13988
+ const chunks = [];
13989
+ for (let offset = 0; offset < items.length; offset += batchSize) {
13990
+ chunks.push(items.slice(offset, offset + batchSize));
13991
+ }
13992
+ return chunks;
13993
+ }
13994
+ function isUnsupportedEffectCatalogMutation(error) {
13995
+ const message = error instanceof Error ? error.message : String(error);
13996
+ 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");
13997
+ }
13259
13998
  function planRecordObjectsChunks(records, batchSize) {
13260
13999
  const total = records.length;
13261
14000
  const size = Math.max(1, Math.min(batchSize, total));
@@ -13267,6 +14006,23 @@ function planRecordObjectsChunks(records, batchSize) {
13267
14006
  }
13268
14007
  return plans;
13269
14008
  }
14009
+ function preserveRecordObjectRealId(record) {
14010
+ const realId = record.id.trim();
14011
+ if (!realId) {
14012
+ return record;
14013
+ }
14014
+ const fields = record.fields || {};
14015
+ if (typeof fields.real_id === "string" && fields.real_id.trim()) {
14016
+ return record;
14017
+ }
14018
+ return {
14019
+ ...record,
14020
+ fields: {
14021
+ ...fields,
14022
+ real_id: realId
14023
+ }
14024
+ };
14025
+ }
13270
14026
  function computeEffectKey2(effect) {
13271
14027
  const attachedClass = effect.className?.trim();
13272
14028
  if (!attachedClass) {
@@ -13504,11 +14260,105 @@ var Environment = class _Environment {
13504
14260
  getAwaitingCount: async () => this.getAwaitingRecordCount()
13505
14261
  };
13506
14262
  }
14263
+ /**
14264
+ * Mirror product-owned workflow state into Granular without making Granular
14265
+ * own the customer application's state machine.
14266
+ */
14267
+ async recordState(input) {
14268
+ const { machine, state, ...target } = input;
14269
+ if (!machine.trim()) {
14270
+ throw new Error("State update requires a machine name");
14271
+ }
14272
+ if (!state.trim()) {
14273
+ throw new Error("State update requires a state");
14274
+ }
14275
+ return this.recordObject({
14276
+ className: target.className,
14277
+ id: target.id,
14278
+ ...target.label ? { label: target.label } : {},
14279
+ ...target.fields ? { fields: target.fields } : {},
14280
+ ...target.relationships ? { relationships: target.relationships } : {},
14281
+ states: {
14282
+ [machine.trim()]: {
14283
+ state: state.trim(),
14284
+ ...target.source ? { source: target.source } : {},
14285
+ ...target.cause ? { cause: target.cause } : {},
14286
+ ...target.actorId ? { actorId: target.actorId } : {},
14287
+ ...target.observedAt !== void 0 ? { observedAt: target.observedAt } : {},
14288
+ ...target.force !== void 0 ? { force: target.force } : {},
14289
+ ...target.metadata ? { metadata: target.metadata } : {}
14290
+ }
14291
+ }
14292
+ });
14293
+ }
14294
+ /**
14295
+ * Mirror product-owned workflow state into Granular without making Granular
14296
+ * own the customer application's state machine.
14297
+ *
14298
+ * Example:
14299
+ * `await env.recordState({ className: "spend_request", id, machine: "lifecycle", state: "policy_review", source: "customer_backend" })`
14300
+ */
14301
+ state(target) {
14302
+ const observe = async (machineName, stateName, input = {}) => {
14303
+ const observedState = input.observedState || input.state || stateName;
14304
+ if (!observedState) {
14305
+ throw new Error("State observation requires a target state");
14306
+ }
14307
+ return this.recordState({
14308
+ ...target,
14309
+ machine: machineName,
14310
+ state: observedState,
14311
+ ...input.source ? { source: input.source } : {},
14312
+ ...input.cause ? { cause: input.cause } : {},
14313
+ ...input.actorId ? { actorId: input.actorId } : {},
14314
+ ...input.observedAt !== void 0 ? { observedAt: input.observedAt } : {},
14315
+ ...input.force !== void 0 ? { force: input.force } : {},
14316
+ ...input.metadata ? { metadata: input.metadata } : {}
14317
+ });
14318
+ };
14319
+ return new Proxy(
14320
+ {},
14321
+ {
14322
+ get: (_target, machineProperty) => {
14323
+ if (typeof machineProperty !== "string") return void 0;
14324
+ return new Proxy(
14325
+ {},
14326
+ {
14327
+ get: (_machineTarget, stateProperty) => {
14328
+ if (stateProperty === "to") {
14329
+ return (stateName, input) => observe(machineProperty, stateName, input || {});
14330
+ }
14331
+ if (typeof stateProperty !== "string") return void 0;
14332
+ return (input) => observe(
14333
+ machineProperty,
14334
+ stateNameFromMethodName(stateProperty),
14335
+ input || {}
14336
+ );
14337
+ }
14338
+ }
14339
+ );
14340
+ }
14341
+ }
14342
+ );
14343
+ }
13507
14344
  get feedback() {
13508
14345
  return {
13509
14346
  list: async () => this.listFeedback()
13510
14347
  };
13511
14348
  }
14349
+ get manualActions() {
14350
+ return {
14351
+ record: (input) => this.recordManualAction(input),
14352
+ list: (options = {}) => this.listManualActions(options),
14353
+ suggest: (options = {}) => this.suggestManualActions(options)
14354
+ };
14355
+ }
14356
+ get artifactApprovals() {
14357
+ return {
14358
+ list: (options = {}) => this.listArtifactApprovals(options),
14359
+ decide: (approvalTaskId, input) => this.decideArtifactApproval(approvalTaskId, input)
14360
+ };
14361
+ }
13512
14362
  /**
13513
14363
  * Sessionless environments do not own a live transport, so disconnecting the
13514
14364
  * environment handle itself is a no-op. This keeps the public surface
@@ -13587,6 +14437,50 @@ var Environment = class _Environment {
13587
14437
  const response = await this.controlPlaneRequest(`/control/environments/${this.environmentId}/feedback`);
13588
14438
  return Array.isArray(response.items) ? response.items : [];
13589
14439
  }
14440
+ async recordManualAction(input) {
14441
+ const body = {
14442
+ ...input,
14443
+ ...input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {}
14444
+ };
14445
+ return this.controlPlaneRequest(
14446
+ `/control/environments/${this.environmentId}/manual-actions`,
14447
+ {
14448
+ method: "POST",
14449
+ body: JSON.stringify(body)
14450
+ }
14451
+ );
14452
+ }
14453
+ async listManualActions(options = {}) {
14454
+ const query = new URLSearchParams();
14455
+ appendQueryOptions(query, options);
14456
+ const suffix = query.toString() ? `?${query.toString()}` : "";
14457
+ return this.controlPlaneRequest(`/control/environments/${this.environmentId}/manual-actions${suffix}`);
14458
+ }
14459
+ async suggestManualActions(options = {}) {
14460
+ const query = new URLSearchParams();
14461
+ appendQueryOptions(query, options);
14462
+ const suffix = query.toString() ? `?${query.toString()}` : "";
14463
+ return this.controlPlaneRequest(
14464
+ `/control/environments/${this.environmentId}/manual-actions/suggestions${suffix}`
14465
+ );
14466
+ }
14467
+ async listArtifactApprovals(options = {}) {
14468
+ const query = new URLSearchParams();
14469
+ appendQueryOptions(query, options);
14470
+ const suffix = query.toString() ? `?${query.toString()}` : "";
14471
+ return this.controlPlaneRequest(
14472
+ `/control/environments/${this.environmentId}/artifact-approvals${suffix}`
14473
+ );
14474
+ }
14475
+ async decideArtifactApproval(approvalTaskId, input) {
14476
+ return this.controlPlaneRequest(
14477
+ `/control/environments/${this.environmentId}/artifact-approvals/${encodeURIComponent(approvalTaskId)}/decide`,
14478
+ {
14479
+ method: "POST",
14480
+ body: JSON.stringify(input)
14481
+ }
14482
+ );
14483
+ }
13590
14484
  getRuntimeBaseUrl() {
13591
14485
  return deriveRuntimeBaseUrl(this._apiEndpoint);
13592
14486
  }
@@ -14411,10 +15305,11 @@ var Environment = class _Environment {
14411
15305
  if (!Array.isArray(records) || records.length === 0) {
14412
15306
  return [];
14413
15307
  }
15308
+ const recordsToWrite = records.map(preserveRecordObjectRealId);
14414
15309
  const batchSize = Math.max(
14415
15310
  1,
14416
15311
  Math.min(
14417
- records.length,
15312
+ recordsToWrite.length,
14418
15313
  options?.batchSize ?? DEFAULT_DIRECT_RECORD_OBJECTS_REQUEST_BATCH_SIZE
14419
15314
  )
14420
15315
  );
@@ -14422,8 +15317,8 @@ var Environment = class _Environment {
14422
15317
  MAX_RECORD_OBJECTS_CONCURRENCY,
14423
15318
  Math.max(1, options?.concurrency ?? 1)
14424
15319
  );
14425
- const plans = planRecordObjectsChunks(records, batchSize);
14426
- const total = records.length;
15320
+ const plans = planRecordObjectsChunks(recordsToWrite, batchSize);
15321
+ const total = recordsToWrite.length;
14427
15322
  const results = new Array(total);
14428
15323
  const onChunk = options?.onChunkComplete;
14429
15324
  for (let waveStart = 0; waveStart < plans.length; waveStart += concurrency) {
@@ -14494,12 +15389,13 @@ var Environment = class _Environment {
14494
15389
  * synchronous upserts and fine-grained chunk progress via **`onChunkComplete`**.
14495
15390
  */
14496
15391
  async enqueueRecordImport(records, options = {}) {
15392
+ const recordsToImport = records.map(preserveRecordObjectRealId);
14497
15393
  return this.controlPlaneRequest(
14498
15394
  `/control/environments/${this.environmentId}/record-imports`,
14499
15395
  {
14500
15396
  method: "POST",
14501
15397
  body: JSON.stringify({
14502
- records,
15398
+ records: recordsToImport,
14503
15399
  batchSize: options.batchSize,
14504
15400
  setupRunId: options.setupRunId,
14505
15401
  writeMode: options.writeMode
@@ -14607,11 +15503,7 @@ var EnvironmentSession = class extends Session {
14607
15503
  }
14608
15504
  buildSessionDataUrl(path, query) {
14609
15505
  const searchParams = new URLSearchParams();
14610
- for (const [key, value] of Object.entries(query || {})) {
14611
- if (value !== null && typeof value !== "undefined" && value !== "") {
14612
- searchParams.set(key, String(value));
14613
- }
14614
- }
15506
+ appendQueryOptions(searchParams, query);
14615
15507
  const queryString = searchParams.toString();
14616
15508
  return `${this.environment.runtimeBaseUrl}${this.sessionDataRoutePrefix}/${encodeURIComponent(this.sessionId)}${path}${queryString ? `?${queryString}` : ""}`;
14617
15509
  }
@@ -14694,9 +15586,108 @@ var EnvironmentSession = class extends Session {
14694
15586
  ),
14695
15587
  get: (jobId) => this.sessionDataRequest(
14696
15588
  `/jobs/${encodeURIComponent(jobId)}`
15589
+ ),
15590
+ latest: async (options = {}) => {
15591
+ const page = await this.sessionDataRequest("/jobs", {
15592
+ status: options.status || "all",
15593
+ latest: true,
15594
+ limit: 1
15595
+ });
15596
+ return page.items[0] || null;
15597
+ }
15598
+ };
15599
+ }
15600
+ get artifacts() {
15601
+ return {
15602
+ list: (options = {}) => {
15603
+ const queryOptions = { ...options };
15604
+ if (options.target) {
15605
+ queryOptions.targetClassName = options.target.className;
15606
+ queryOptions.targetId = options.target.id;
15607
+ delete queryOptions.target;
15608
+ }
15609
+ return this.sessionDataRequest("/artifacts", queryOptions);
15610
+ },
15611
+ listForLatestJob: (options = {}) => this.artifacts.list({
15612
+ ...options,
15613
+ latestJob: true
15614
+ }),
15615
+ get: (artifactId) => this.sessionDataRequest(
15616
+ `/artifacts/${encodeURIComponent(artifactId)}`
15617
+ ),
15618
+ create: (artifact) => this.sessionDataRequest(
15619
+ "/artifacts",
15620
+ void 0,
15621
+ {
15622
+ method: "POST",
15623
+ body: artifact
15624
+ }
15625
+ ),
15626
+ updateInputs: (artifactId, patch) => this.sessionDataRequest(
15627
+ `/artifacts/${encodeURIComponent(artifactId)}`,
15628
+ void 0,
15629
+ {
15630
+ method: "PATCH",
15631
+ body: patch
15632
+ }
15633
+ ),
15634
+ validate: (artifactId) => this.sessionDataRequest(
15635
+ `/artifacts/${encodeURIComponent(artifactId)}/validate`,
15636
+ void 0,
15637
+ { method: "POST" }
15638
+ ),
15639
+ execute: (artifactId, options) => this.sessionDataRequest(
15640
+ `/artifacts/${encodeURIComponent(artifactId)}/execute`,
15641
+ void 0,
15642
+ { method: "POST", body: options }
15643
+ ),
15644
+ approve: (artifactId, options) => this.sessionDataRequest(
15645
+ `/artifacts/${encodeURIComponent(artifactId)}/approve`,
15646
+ void 0,
15647
+ { method: "POST", body: options }
15648
+ ),
15649
+ cancel: (artifactId) => this.sessionDataRequest(
15650
+ `/artifacts/${encodeURIComponent(artifactId)}/cancel`,
15651
+ void 0,
15652
+ { method: "POST" }
14697
15653
  )
14698
15654
  };
14699
15655
  }
15656
+ get manualActions() {
15657
+ const useDelegatedBrowserRoute = this.sessionDataRoutePrefix === "/sdk/browser-sessions";
15658
+ return {
15659
+ record: (input) => useDelegatedBrowserRoute ? this.sessionDataRequest(
15660
+ "/manual-actions",
15661
+ void 0,
15662
+ {
15663
+ method: "POST",
15664
+ body: { ...input, sessionId: this.sessionId }
15665
+ }
15666
+ ) : this.environment.manualActions.record({
15667
+ ...input,
15668
+ sessionId: this.sessionId
15669
+ }),
15670
+ list: (options = {}) => useDelegatedBrowserRoute ? this.sessionDataRequest("/manual-actions", { ...options, sessionId: this.sessionId }) : this.environment.manualActions.list({
15671
+ ...options,
15672
+ sessionId: this.sessionId
15673
+ }),
15674
+ suggest: (options = {}) => useDelegatedBrowserRoute ? this.sessionDataRequest(
15675
+ "/manual-actions/suggestions",
15676
+ options
15677
+ ) : this.environment.manualActions.suggest(options)
15678
+ };
15679
+ }
15680
+ get artifactApprovals() {
15681
+ const useDelegatedBrowserRoute = this.sessionDataRoutePrefix === "/sdk/browser-sessions";
15682
+ return {
15683
+ list: (options = {}) => useDelegatedBrowserRoute ? this.sessionDataRequest("/artifact-approvals", options) : this.environment.artifactApprovals.list(options),
15684
+ decide: (approvalTaskId, input) => useDelegatedBrowserRoute ? this.sessionDataRequest(
15685
+ `/artifact-approvals/${encodeURIComponent(approvalTaskId)}/decide`,
15686
+ void 0,
15687
+ { method: "POST", body: input }
15688
+ ) : this.environment.artifactApprovals.decide(approvalTaskId, input)
15689
+ };
15690
+ }
14700
15691
  get files() {
14701
15692
  return {
14702
15693
  list: (options = {}) => this.sessionDataRequest(
@@ -14777,13 +15768,16 @@ var EnvironmentSession = class extends Session {
14777
15768
  get transcript() {
14778
15769
  return {
14779
15770
  list: async (options = {}) => {
14780
- const [messages, jobs, entries, lists] = await Promise.all([
15771
+ const [messages, jobs, entries, lists, artifacts] = await Promise.all([
14781
15772
  this.collectAllSessionItems(this.messages.list),
14782
15773
  this.collectAllSessionItems(
14783
15774
  (pageOptions) => this.jobs.list({ ...pageOptions, status: "all" })
14784
15775
  ),
14785
15776
  this.collectAllSessionItems(this.heap.entries.list),
14786
- this.collectAllSessionItems(this.heap.lists.list)
15777
+ this.collectAllSessionItems(this.heap.lists.list),
15778
+ this.collectAllSessionItems(
15779
+ (pageOptions) => this.artifacts.list({ ...pageOptions, status: "all" })
15780
+ )
14787
15781
  ]);
14788
15782
  const liveDoc = {
14789
15783
  conversation: { messages },
@@ -14797,6 +15791,21 @@ var EnvironmentSession = class extends Session {
14797
15791
  (entry) => Boolean(entry)
14798
15792
  )
14799
15793
  )
15794
+ },
15795
+ artifacts: {
15796
+ byId: Object.fromEntries(
15797
+ artifacts.map((artifact) => {
15798
+ return artifact?.artifactId ? [
15799
+ artifact.artifactId,
15800
+ artifact
15801
+ ] : null;
15802
+ }).filter(
15803
+ (entry) => Boolean(entry)
15804
+ )
15805
+ ),
15806
+ order: artifacts.map((artifact) => artifact?.artifactId).filter(
15807
+ (artifactId) => Boolean(artifactId)
15808
+ )
14800
15809
  }
14801
15810
  };
14802
15811
  const heap = normalizeHeapSnapshot({
@@ -14873,6 +15882,12 @@ var EnvironmentSession = class extends Session {
14873
15882
  async recordObject(options) {
14874
15883
  return this.environment.recordObject(options);
14875
15884
  }
15885
+ async recordState(input) {
15886
+ return this.environment.recordState(input);
15887
+ }
15888
+ state(target) {
15889
+ return this.environment.state(target);
15890
+ }
14876
15891
  async recordObjects(records, options) {
14877
15892
  return this.environment.recordObjects(records, options);
14878
15893
  }
@@ -15700,15 +16715,43 @@ var Granular = class _Granular {
15700
16715
  const effects = Array.from(
15701
16716
  this.getSandboxEffectMap(host.sandboxId).values()
15702
16717
  ).map((effect) => this.serializeEffect(effect));
15703
- const result = await withTimeout(
15704
- host.wsClient.call("effects.publishCatalog", {
15705
- effects
15706
- }),
15707
- EFFECT_CATALOG_SYNC_TIMEOUT_MS,
15708
- `effects.publishCatalog for sandbox ${host.sandboxId}`
15709
- );
15710
- const acceptedCount = typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
15711
- const rejected = Array.isArray(result?.rejected) ? result.rejected : [];
16718
+ let acceptedCount = 0;
16719
+ const rejected = [];
16720
+ try {
16721
+ await withTimeout(
16722
+ host.wsClient.call("effects.resetCatalog", {}),
16723
+ EFFECT_CATALOG_SYNC_TIMEOUT_MS,
16724
+ `effects.resetCatalog for sandbox ${host.sandboxId}`
16725
+ );
16726
+ for (const batch of chunkItems(effects, EFFECT_CATALOG_SYNC_BATCH_SIZE)) {
16727
+ const result = await withTimeout(
16728
+ host.wsClient.call("effects.addCatalog", {
16729
+ effects: batch
16730
+ }),
16731
+ EFFECT_CATALOG_SYNC_TIMEOUT_MS,
16732
+ `effects.addCatalog for sandbox ${host.sandboxId}`
16733
+ );
16734
+ acceptedCount += typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
16735
+ if (Array.isArray(result?.rejected)) {
16736
+ rejected.push(...result.rejected);
16737
+ }
16738
+ }
16739
+ } catch (error) {
16740
+ if (!isUnsupportedEffectCatalogMutation(error)) {
16741
+ throw error;
16742
+ }
16743
+ const result = await withTimeout(
16744
+ host.wsClient.call("effects.publishCatalog", {
16745
+ effects
16746
+ }),
16747
+ EFFECT_CATALOG_SYNC_TIMEOUT_MS,
16748
+ `effects.publishCatalog for sandbox ${host.sandboxId}`
16749
+ );
16750
+ acceptedCount = typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
16751
+ if (Array.isArray(result?.rejected)) {
16752
+ rejected.push(...result.rejected);
16753
+ }
16754
+ }
15712
16755
  if (acceptedCount === 0 && rejected.length > 0) {
15713
16756
  const detail = rejected.map(
15714
16757
  (entry) => `${entry.name || "unknown"}: ${entry.reason || "rejected"}`
@@ -16940,6 +17983,7 @@ var HARNESS_V3_FRONTEND_ACTIONS_MODULE = "@granular/actions/frontend";
16940
17983
  var HARNESS_V3_CSV_MODULE = "@granular/utils/csv";
16941
17984
  var HARNESS_V3_XLSX_MODULE = "@granular/utils/xlsx";
16942
17985
  var LEGACY_SANDBOX_TOOLS_MODULE_PATTERN = "\\.\\/sandbox-tools(?:\\.js)?";
17986
+ var HARNESS_V3_RUNTIME_MODULE_PATTERN = "@granular/(?:agent|session|domain(?:/[A-Za-z_$][\\w$]*)?|actions/(?:backend|frontend)|utils/(?:csv|xlsx))";
16943
17987
  function hasNamedModuleImport(source, moduleName, name) {
16944
17988
  const escapedModule = moduleName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
16945
17989
  const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -16998,6 +18042,15 @@ function reviewGeneratedJobCode(code, _options = {}) {
16998
18042
  message: "Generated code must use static top-level ESM imports from the Harness v3 runtime modules. Do not use dynamic import(...)."
16999
18043
  });
17000
18044
  }
18045
+ if (new RegExp(
18046
+ `import\\s+\\*\\s+as\\s+[A-Za-z_$][\\w$]*\\s+from\\s*['"]${HARNESS_V3_RUNTIME_MODULE_PATTERN}['"]`
18047
+ ).test(normalized)) {
18048
+ issues.push({
18049
+ code: "runtime_namespace_import",
18050
+ severity: "error",
18051
+ 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"`.'
18052
+ });
18053
+ }
17001
18054
  if (/\bprocess\.exit\s*\(/.test(normalized)) {
17002
18055
  issues.push({
17003
18056
  code: "process_exit",
@@ -18022,6 +19075,31 @@ function buildGranularAgentHeapBlock(heapSummary) {
18022
19075
  entries: {}
18023
19076
  });
18024
19077
  }
19078
+ function buildGranularAgentManualActionMemorySummary(input) {
19079
+ const maxItems = Math.max(1, Math.min(12, input.maxItems ?? 8));
19080
+ const suggestions = (input.suggestions || []).filter((suggestion) => suggestion?.actionKey).slice(0, maxItems).map((suggestion) => ({
19081
+ actionKey: suggestion.actionKey,
19082
+ label: suggestion.label || null,
19083
+ targetClassName: suggestion.targetClassName || null,
19084
+ count: typeof suggestion.count === "number" && Number.isFinite(suggestion.count) ? suggestion.count : null,
19085
+ subjectCount: typeof suggestion.subjectCount === "number" && Number.isFinite(suggestion.subjectCount) ? suggestion.subjectCount : null,
19086
+ successCount: typeof suggestion.successCount === "number" && Number.isFinite(suggestion.successCount) ? suggestion.successCount : null,
19087
+ failureCount: typeof suggestion.failureCount === "number" && Number.isFinite(suggestion.failureCount) ? suggestion.failureCount : null,
19088
+ lastOccurredAt: typeof suggestion.lastOccurredAt === "number" && Number.isFinite(suggestion.lastOccurredAt) ? suggestion.lastOccurredAt : null,
19089
+ sampleTargetIds: Array.isArray(suggestion.sampleTargetIds) ? suggestion.sampleTargetIds.filter(
19090
+ (id) => typeof id === "string" && id.trim().length > 0
19091
+ ).slice(0, 6) : []
19092
+ }));
19093
+ return [
19094
+ renderConstBlock("manualActionMemory", {
19095
+ suggestions
19096
+ }),
19097
+ "Use manualActionMemory only as behavioral context for likely next actions. Ground the current target and validate permissions before creating or running prepared actions."
19098
+ ].join("\n");
19099
+ }
19100
+ function buildGranularAgentManualActionBlock(manualActionSummary) {
19101
+ return manualActionSummary?.trim() || buildGranularAgentManualActionMemorySummary({ suggestions: [] });
19102
+ }
18025
19103
  function projectSessionFileSummary(liveDoc) {
18026
19104
  const files = asRecord4(liveDoc?.files);
18027
19105
  const byId = asRecord4(files?.byId) || {};
@@ -18053,8 +19131,12 @@ function buildGranularAgentFileBlock(fileSummary) {
18053
19131
  function extractRuntimeContractExports(domainBlock) {
18054
19132
  const classes = /* @__PURE__ */ new Set();
18055
19133
  const actions = /* @__PURE__ */ new Set();
18056
- const classPattern = /export\s+declare\s+(?:const|class)\s+([A-Za-z_$][\w$]*)/g;
18057
- for (const match of domainBlock.matchAll(classPattern)) {
19134
+ const classConstPattern = /export\s+declare\s+const\s+([A-Za-z_$][\w$]*)\s*:\s*EntityClass\b/g;
19135
+ for (const match of domainBlock.matchAll(classConstPattern)) {
19136
+ classes.add(match[1]);
19137
+ }
19138
+ const classDeclPattern = /export\s+declare\s+class\s+([A-Za-z_$][\w$]*)\b/g;
19139
+ for (const match of domainBlock.matchAll(classDeclPattern)) {
18058
19140
  classes.add(match[1]);
18059
19141
  }
18060
19142
  const actionPattern = /export\s+declare\s+function\s+([A-Za-z_$][\w$]*)/g;
@@ -18559,6 +19641,9 @@ function buildGranularAgentSystemPrompt(input) {
18559
19641
  });
18560
19642
  const referentBlock = buildGranularAgentReferentBlock(input.referentSummary);
18561
19643
  const loopBlock = buildGranularAgentLoopBlock(input.loopSummary);
19644
+ const manualActionBlock = buildGranularAgentManualActionBlock(
19645
+ input.manualActionSummary
19646
+ );
18562
19647
  const knownFactsBlock = renderConstBlock(
18563
19648
  "knownFacts",
18564
19649
  buildKnownFactsFromCheckpoint(input.checkpoint)
@@ -18572,16 +19657,15 @@ function buildGranularAgentSystemPrompt(input) {
18572
19657
  - \`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.
18573
19658
  - 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.
18574
19659
  - 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.
18575
- - 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.
18576
- - 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.
18577
- - 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"] })\`.
18578
- - \`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(...)\`.
18579
- - 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.
18580
- - 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.
19660
+ - 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.
19661
+ - 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"] })\`.
19662
+ - \`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(...)\`.
19663
+ - 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.
19664
+ - 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()\`.
19665
+ - 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.
18581
19666
  - 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.
18582
19667
  - 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.
18583
- - 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.
18584
- - 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.
19668
+ - 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.
18585
19669
  - 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\`.
18586
19670
  - \`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.
18587
19671
  - 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.
@@ -18592,7 +19676,7 @@ function buildGranularAgentSystemPrompt(input) {
18592
19676
  - When using code, assistant text must be empty or one brief summary.
18593
19677
  - Code must be plain runnable JavaScript with top-level await.
18594
19678
  - Use [Runtime Imports] as the authoritative module map. Import only listed module exports.
18595
- - 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.
19679
+ - 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.
18596
19680
  - 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.
18597
19681
  - 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.
18598
19682
  - 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\`.
@@ -18655,6 +19739,7 @@ ${workflowRules}
18655
19739
  High-priority execution rules:
18656
19740
  - 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.
18657
19741
  - 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.
19742
+ - 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.
18658
19743
  - 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.
18659
19744
  - 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.
18660
19745
  - 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.
@@ -18698,7 +19783,7 @@ Intent resolution:
18698
19783
  - 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.
18699
19784
  - 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.
18700
19785
  - 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.
18701
- - 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.
19786
+ - 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.
18702
19787
  - 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.
18703
19788
  - Never call \`.get({ path: "" })\`; an empty path is not a saved reference.
18704
19789
  - 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.
@@ -18825,20 +19910,10 @@ Ask the user when:
18825
19910
  - the target is unique but the requested action is unclear
18826
19911
 
18827
19912
  Relationship filters:
18828
- - One-record relationships use \`is\`.
18829
- - Multi-record relationships use \`some\`.
18830
- - 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.
18831
- - 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.
18832
- - 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.
18833
- - 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.
18834
- - Use \`some\` only when the generated TypeScript type says \`ManyRelationFilter\`.
18835
- - 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.
18836
- - Use \`{ relationship: { id: "record_id" } }\` or \`{ relationship: { path: "class_record_id" } }\` when matching a known related record.
18837
- - 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\`.
18838
- - 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.
18839
- - Use \`{ relationship: { is: { field: { equal_to: value } } } }\` only for nested field filters. Never put \`id\` or \`path\` inside \`is\`.
18840
- - 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.
18841
- - Do not pass a full record instance into a filter; if you already fetched a record, filter by its id or path instead.
19913
+ - Use the generated filter type as the authority: \`OneRelationFilter\` supports \`id\`, \`path\`, \`is\`, \`null\`, \`not_null\`; \`ManyRelationFilter\` supports those plus \`some\`.
19914
+ - Use \`id\` or \`path\` for a known related record; use \`is\` or \`some\` only for nested target-field filters.
19915
+ - 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.
19916
+ - Never pass a full record instance into a filter. Use its id/path or a declared relationship getter.
18842
19917
  ${domainSections.docs ? `
18843
19918
  Domain notes:
18844
19919
  ${domainSections.docs}
@@ -18849,6 +19924,23 @@ ${actionIndex}
18849
19924
  - 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.
18850
19925
  - 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(...)\`.
18851
19926
  - Actions listed under "Class-level" are class/static methods. Call them on the imported class, e.g. \`await Item.action_name(...)\`.
19927
+ - 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.
19928
+ - For pure field-collection requests, target the class-level entry state handle; for submit/review requests, target the nearest requested later state.
19929
+ - Choose the nearest target state that matches the user's words. Do not aim at a later state just because it is reachable.
19930
+ - 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\`.
19931
+ - 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.
19932
+ - 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.
19933
+ - 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()\`.
19934
+ - 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.
19935
+ - 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.
19936
+ - 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.
19937
+ - 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.
19938
+ - 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.
19939
+ - 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.
19940
+ - 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.
19941
+ - 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.
19942
+ - 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.
19943
+ - Use \`await prepared.show()\` or \`await actions.show(prepared)\` only to display an already-created prepared action again.
18852
19944
  - 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.
18853
19945
  - Never call a record-level action as \`Class.action_name(...)\`; that method will not exist.
18854
19946
  - 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.
@@ -18879,6 +19971,8 @@ ${loopBlock}
18879
19971
 
18880
19972
  ${knownFactsBlock}
18881
19973
 
19974
+ ${manualActionBlock}
19975
+
18882
19976
  [Request]
18883
19977
  ${input.request?.trim() || "Use the latest user message in the conversation."}`;
18884
19978
  }
@@ -19166,6 +20260,6 @@ function calculateOpenAITokenSpend(model, rawUsage) {
19166
20260
  };
19167
20261
  }
19168
20262
 
19169
- export { Environment, EnvironmentSession, Granular, OPENAI_MODEL_PRICING_USD_PER_MILLION, OntologyHandle, Session, WSClient, buildContinuationInstruction, buildContinuationInstructionFromTemplate, buildGranularAgentCheckpointBlock, buildGranularAgentDomainBlock, buildGranularAgentFileBlock, buildGranularAgentHeapBlock, buildGranularAgentLoopBlock, buildGranularAgentReferentBlock, buildGranularAgentRuntimeImportsBlock, buildGranularAgentSessionBlock, buildGranularAgentSystemPrompt, buildGranularAgentSystemPromptFromTemplate, buildGranularAgentToolBlock, buildGranularAgentWorkflowBlock, buildOpenAISpendEventId, buildSessionTranscript, calculateOpenAITokenSpend, consumeGranularReasoningOnlyChunk, consumeGranularReasoningTraceChunk, createHarnessVerifierSnapshot, evaluateContinuation, extractPromptTokens, getCurrentClosureId, getDefaultHarnessTemplateId, getExclusivePromptTarget, getOpenAIModelPricing, hasOpenPrompt, hashHarnessTemplateValue, invokeRegisteredEffect, isLocalApiUrl, listHarnessTemplates, normalizeEffectBehaviors, normalizeOpenAIUsage, normalizePrompt, normalizePromptChoiceOption, normalizePromptText, normalizePromptType, projectConversationReferentFocus, projectConversationReferentSummary, projectHeapSummary, projectLoopSummary, projectSessionFileSummary, projectWorkflowFocus, projectWorkflowSummary, recordOpenAIUsageSpend, renderContinuationInstructionFromTemplate, renderGranularAgentSystemPromptFromTemplate, resolveApiUrl, resolveAuthTokenForApiUrl, resolveHarnessTemplate, resolveJobPresentation, resolvePromptAnswer, reviewGeneratedJobCode, scorePromptChoiceMatch, stripGranularReasoningTrace, toGranularHttpBase, validateHarnessTemplateManifest };
20263
+ export { Environment, EnvironmentSession, Granular, OPENAI_MODEL_PRICING_USD_PER_MILLION, OntologyHandle, Session, WSClient, buildContinuationInstruction, buildContinuationInstructionFromTemplate, buildGranularAgentCheckpointBlock, buildGranularAgentDomainBlock, buildGranularAgentFileBlock, buildGranularAgentHeapBlock, buildGranularAgentLoopBlock, buildGranularAgentManualActionBlock, buildGranularAgentManualActionMemorySummary, buildGranularAgentReferentBlock, buildGranularAgentRuntimeImportsBlock, buildGranularAgentSessionBlock, buildGranularAgentSystemPrompt, buildGranularAgentSystemPromptFromTemplate, buildGranularAgentToolBlock, buildGranularAgentWorkflowBlock, buildOpenAISpendEventId, buildSessionTranscript, calculateOpenAITokenSpend, consumeGranularReasoningOnlyChunk, consumeGranularReasoningTraceChunk, createHarnessVerifierSnapshot, evaluateContinuation, evaluateValidationRule, extractPromptTokens, getCurrentClosureId, getDefaultHarnessTemplateId, getExclusivePromptTarget, getOpenAIModelPricing, hasOpenPrompt, hashHarnessTemplateValue, invokeRegisteredEffect, isLocalApiUrl, listHarnessTemplates, normalizeEffectBehaviors, normalizeOpenAIUsage, normalizePrompt, normalizePromptChoiceOption, normalizePromptText, normalizePromptType, projectConversationReferentFocus, projectConversationReferentSummary, projectHeapSummary, projectLoopSummary, projectSessionFileSummary, projectWorkflowFocus, projectWorkflowSummary, recordOpenAIUsageSpend, renderContinuationInstructionFromTemplate, renderGranularAgentSystemPromptFromTemplate, resolveApiUrl, resolveAuthTokenForApiUrl, resolveHarnessTemplate, resolveJobPresentation, resolvePromptAnswer, reviewGeneratedJobCode, scorePromptChoiceMatch, stripGranularReasoningTrace, toGranularHttpBase, validateHarnessTemplateManifest, validationRuleFailureMessage };
19170
20264
  //# sourceMappingURL=index.mjs.map
19171
20265
  //# sourceMappingURL=index.mjs.map