@granular-software/sdk 0.4.49 → 0.4.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
  `;
@@ -6329,6 +6333,28 @@ function asString(value) {
6329
6333
  function trimString(value) {
6330
6334
  return typeof value === "string" ? value.trim() : "";
6331
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
+ }
6332
6358
  function normalizeShowRefs(value) {
6333
6359
  const record = asRecord3(value);
6334
6360
  if (!record) return void 0;
@@ -6345,9 +6371,31 @@ function normalizeShowRefs(value) {
6345
6371
  entryPaths: normalizeRefs(record.entryPaths),
6346
6372
  listNames: normalizeRefs(record.listNames),
6347
6373
  variableNames: normalizeRefs(record.variableNames),
6348
- fileIds: normalizeRefs(record.fileIds)
6374
+ fileIds: normalizeRefs(record.fileIds),
6375
+ sessionArtifactIds: normalizeRefs(record.sessionArtifactIds),
6376
+ actionSuggestions: normalizeActionSuggestions(record.actionSuggestions)
6349
6377
  };
6350
- 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;
6351
6399
  }
6352
6400
  function stringifyTranscriptValue(value, fallback = "") {
6353
6401
  if (typeof value === "string") {
@@ -6367,12 +6415,139 @@ function stringifyTranscriptValue(value, fallback = "") {
6367
6415
  return String(value);
6368
6416
  }
6369
6417
  }
6370
- 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) {
6371
6510
  if (!show) return void 0;
6372
- 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]
6373
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");
6374
6549
  }
6375
- function normalizeConversationMessage(raw) {
6550
+ function normalizeConversationMessage(raw, artifactsById) {
6376
6551
  const record = asRecord3(raw);
6377
6552
  if (!record) return null;
6378
6553
  const role = record.role === "user" ? "user" : record.role === "assistant" ? "assistant" : null;
@@ -6384,6 +6559,12 @@ function normalizeConversationMessage(raw) {
6384
6559
  const id = asString(record.id) || crypto.randomUUID();
6385
6560
  const timestamp = asNumber(record.timestamp) || asNumber(record.ts) || 0;
6386
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;
6387
6568
  return {
6388
6569
  id,
6389
6570
  role,
@@ -6392,8 +6573,7 @@ function normalizeConversationMessage(raw) {
6392
6573
  jobId: asString(record.jobId),
6393
6574
  promptId: asString(record.promptId),
6394
6575
  show,
6395
- historyContent: role === "assistant" ? content ? `[Assistant reply]
6396
- ${content}` : buildArtifactHistory(show) : void 0,
6576
+ historyContent,
6397
6577
  source: "conversation"
6398
6578
  };
6399
6579
  }
@@ -6436,7 +6616,7 @@ ${assistantContent}`,
6436
6616
  return entries;
6437
6617
  });
6438
6618
  }
6439
- function normalizeAgentMessageEntries(jobId, rawMessages) {
6619
+ function normalizeAgentMessageEntries(jobId, rawMessages, artifactsById) {
6440
6620
  return asArray(rawMessages).map((value) => asRecord3(value)).filter((value) => Boolean(value)).sort(
6441
6621
  (left, right) => (asNumber(left.timestamp) || asNumber(left.ts) || 0) - (asNumber(right.timestamp) || asNumber(right.ts) || 0)
6442
6622
  ).flatMap((message) => {
@@ -6467,14 +6647,14 @@ ${reply}`,
6467
6647
  timestamp,
6468
6648
  jobId,
6469
6649
  show,
6470
- historyContent: buildArtifactHistory(show),
6650
+ historyContent: buildArtifactHistory(show, artifactsById),
6471
6651
  source: "job_agent_message"
6472
6652
  });
6473
6653
  }
6474
6654
  return entries;
6475
6655
  });
6476
6656
  }
6477
- function buildJobFallbackEntries(jobId, job, sessionHeap) {
6657
+ function buildJobFallbackEntries(jobId, job, sessionHeap, artifactsById) {
6478
6658
  const timestamp = asNumber(job.finishedAt) || asNumber(job.startedAt) || asNumber(job.submittedAt) || 0;
6479
6659
  const resultPreview = stringifyTranscriptValue(
6480
6660
  job.result,
@@ -6512,7 +6692,7 @@ ${responseText}`,
6512
6692
  timestamp,
6513
6693
  jobId,
6514
6694
  show,
6515
- historyContent: buildArtifactHistory(show),
6695
+ historyContent: buildArtifactHistory(show, artifactsById),
6516
6696
  source: "job_result"
6517
6697
  });
6518
6698
  }
@@ -6566,10 +6746,11 @@ function buildJobCodeEntry(jobId, job) {
6566
6746
  function buildSessionTranscript(input) {
6567
6747
  const liveDoc = input.liveDoc || null;
6568
6748
  const sessionHeap = input.sessionHeap || EMPTY_HEAP;
6749
+ const artifactsById = artifactRecordsById(liveDoc);
6569
6750
  const transcript = [];
6570
6751
  const conversationMessages = asArray(
6571
6752
  asRecord3(liveDoc?.conversation)?.messages
6572
- ).map((message) => normalizeConversationMessage(message)).filter((message) => Boolean(message));
6753
+ ).map((message) => normalizeConversationMessage(message, artifactsById)).filter((message) => Boolean(message));
6573
6754
  const conversationPromptIds = new Set(
6574
6755
  conversationMessages.map((message) => message.promptId).filter((promptId) => Boolean(promptId))
6575
6756
  );
@@ -6596,7 +6777,8 @@ function buildSessionTranscript(input) {
6596
6777
  if (!assistantConversationJobIds.has(jobId)) {
6597
6778
  const agentEntries = normalizeAgentMessageEntries(
6598
6779
  jobId,
6599
- job.agentMessages
6780
+ job.agentMessages,
6781
+ artifactsById
6600
6782
  );
6601
6783
  if (agentEntries.length > 0) {
6602
6784
  transcript.push(...agentEntries);
@@ -6605,7 +6787,8 @@ function buildSessionTranscript(input) {
6605
6787
  ...buildJobFallbackEntries(
6606
6788
  jobId,
6607
6789
  job,
6608
- sessionHeap
6790
+ sessionHeap,
6791
+ artifactsById
6609
6792
  )
6610
6793
  );
6611
6794
  }
@@ -10771,16 +10954,107 @@ var StateMachineStateSchema = external_exports.union([
10771
10954
  external_exports.string(),
10772
10955
  external_exports.object({
10773
10956
  name: external_exports.string().min(1),
10957
+ label: external_exports.string().optional(),
10958
+ description: external_exports.string().optional(),
10774
10959
  isFinal: external_exports.boolean().optional()
10775
10960
  }).strict()
10776
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
+ ]);
10777
11042
  var StateMachineTransitionSchema = external_exports.object({
10778
11043
  name: external_exports.string().min(1),
10779
11044
  from: external_exports.string().min(1),
10780
- 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()
10781
11054
  }).strict();
10782
11055
  external_exports.object({
10783
11056
  name: external_exports.string().min(1),
11057
+ stateField: external_exports.string().min(1).optional(),
10784
11058
  entryState: external_exports.string().min(1),
10785
11059
  states: external_exports.array(StateMachineStateSchema).min(1),
10786
11060
  transitions: external_exports.array(StateMachineTransitionSchema),
@@ -10847,6 +11121,16 @@ var PoliciesSchema = external_exports.object({
10847
11121
  confirmWhen: external_exports.array(PolicyRuleSchema).optional(),
10848
11122
  denyWhen: external_exports.array(PolicyRuleSchema).optional()
10849
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
+ ]);
10850
11134
  external_exports.object({
10851
11135
  postCondition: external_exports.union([
10852
11136
  external_exports.string(),
@@ -10877,6 +11161,7 @@ external_exports.object({
10877
11161
  mode: external_exports.string().optional()
10878
11162
  }).strict()
10879
11163
  ]).optional(),
11164
+ creates: CreatesSchema.optional(),
10880
11165
  access: external_exports.enum(["read", "write", "ui"]).optional(),
10881
11166
  effectKind: external_exports.enum(["read", "write", "ui"]).optional(),
10882
11167
  sideEffect: external_exports.enum(["read", "write", "ui", "readonly", "read_only"]).optional(),
@@ -11104,9 +11389,10 @@ function mergeMethodSummaryPatch(target, patch) {
11104
11389
  if (patch.metamodels !== void 0) target.metamodels = patch.metamodels;
11105
11390
  if (patch.effectBehaviors !== void 0)
11106
11391
  target.effectBehaviors = patch.effectBehaviors;
11392
+ if (patch.creates !== void 0) target.creates = patch.creates;
11107
11393
  if (patch.static !== void 0) target.static = patch.static;
11108
11394
  }
11109
- function toPascalCase(value) {
11395
+ function toPascalCase2(value) {
11110
11396
  return value.split(/[_:\-\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
11111
11397
  }
11112
11398
  function normalizeNotesInput(input) {
@@ -11164,29 +11450,60 @@ function normalizeEffectBehaviorSummary(metamodels) {
11164
11450
  }
11165
11451
  return Object.keys(result).length > 0 ? result : null;
11166
11452
  }
11167
- function buildEffectBehaviorDocs(effectBehaviors) {
11168
- if (!effectBehaviors) {
11169
- 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
+ }
11170
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) {
11171
11483
  const docs = [];
11172
- 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) {
11173
11490
  docs.push(
11174
11491
  effectBehaviors.approvalRequired.reason ? `Approval required: ${effectBehaviors.approvalRequired.reason}.` : "Approval required before execution."
11175
11492
  );
11176
11493
  }
11177
- if (effectBehaviors.postCondition) {
11494
+ if (effectBehaviors?.postCondition) {
11178
11495
  docs.push(`Post-condition: ${effectBehaviors.postCondition.condition}.`);
11179
11496
  if (effectBehaviors.postCondition.description) {
11180
11497
  docs.push(effectBehaviors.postCondition.description);
11181
11498
  }
11182
11499
  }
11183
- if (effectBehaviors.dryRun?.enabled) {
11500
+ if (effectBehaviors?.dryRun?.enabled) {
11184
11501
  docs.push("Supports dry run.");
11185
11502
  if (effectBehaviors.dryRun.description) {
11186
11503
  docs.push(effectBehaviors.dryRun.description);
11187
11504
  }
11188
11505
  }
11189
- if (effectBehaviors.reverse) {
11506
+ if (effectBehaviors?.reverse) {
11190
11507
  if (effectBehaviors.reverse.handler) {
11191
11508
  docs.push(`Reverse handler: ${effectBehaviors.reverse.handler}.`);
11192
11509
  } else {
@@ -11245,13 +11562,21 @@ function buildEffectBehaviorMutations(toolPath, spec) {
11245
11562
  query: `mutation { at(path: ${JSON.stringify(toolPath)}) { set_approval_required(${args}) { kind } } }`
11246
11563
  });
11247
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
+ }
11248
11573
  return mutations;
11249
11574
  }
11250
11575
  function readMethodEffectBehaviors(rawMethod) {
11576
+ const metamodels = isObject(rawMethod.metamodels) ? rawMethod.metamodels : null;
11251
11577
  return {
11252
- effectBehaviors: normalizeEffectBehaviorSummary(
11253
- isObject(rawMethod.metamodels) ? rawMethod.metamodels : null
11254
- )
11578
+ effectBehaviors: normalizeEffectBehaviorSummary(metamodels),
11579
+ creates: normalizeCreationSummary(metamodels)
11255
11580
  };
11256
11581
  }
11257
11582
  var effectBehaviorsMetamodelPackage = defineMetamodelPackage({
@@ -11273,6 +11598,10 @@ var effectBehaviorsMetamodelPackage = defineMetamodelPackage({
11273
11598
  {
11274
11599
  key: "approvalRequired",
11275
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 }`.'
11276
11605
  }
11277
11606
  ]
11278
11607
  },
@@ -11392,7 +11721,10 @@ var effectBehaviorsMetamodelPackage = defineMetamodelPackage({
11392
11721
  ...methodIR,
11393
11722
  docs: [
11394
11723
  ...methodIR.docs,
11395
- ...buildEffectBehaviorDocs(methodSummary.effectBehaviors)
11724
+ ...buildEffectBehaviorDocs(
11725
+ methodSummary.effectBehaviors,
11726
+ methodSummary.creates
11727
+ )
11396
11728
  ]
11397
11729
  };
11398
11730
  }
@@ -11633,15 +11965,50 @@ function toRecordSearchResult(className, node) {
11633
11965
  return [];
11634
11966
  }
11635
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;
11636
11979
  return {
11637
11980
  path,
11638
11981
  className,
11639
- id: extractRecordIdFromGraphPath(path, className),
11640
- label: typeof node.label === "string" && node.label.trim() ? node.label : extractRecordIdFromGraphPath(path, className),
11982
+ id,
11983
+ label,
11641
11984
  description: typeof node.description === "string" && node.description.trim() ? node.description : null,
11642
11985
  fields
11643
11986
  };
11644
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
+ }
11645
12012
  function normalizeRecordSearchText(value) {
11646
12013
  return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, " ").replace(/\s+/g, " ").trim();
11647
12014
  }
@@ -12541,15 +12908,47 @@ var searchableMetamodelPackage = defineMetamodelPackage({
12541
12908
 
12542
12909
  // ../metamodel-state-machine/src/index.ts
12543
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
+ };
12544
12933
  return (values || []).map((machine) => {
12545
12934
  const states = (machine?.states || []).map((state) => ({
12546
12935
  name: String(state?.name || ""),
12547
- 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)
12548
12939
  })).filter((state) => state.name.length > 0);
12549
12940
  const transitions = (machine?.transitions || []).map((transition) => ({
12550
12941
  name: String(transition?.name || ""),
12551
12942
  from: String(transition?.from?.name || ""),
12552
- 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)
12553
12952
  })).filter(
12554
12953
  (transition) => transition.name.length > 0 && transition.from.length > 0 && transition.to.length > 0
12555
12954
  );
@@ -12563,7 +12962,7 @@ function normalizeStateMachines(values) {
12563
12962
  }).filter((machine) => machine.name.length > 0);
12564
12963
  }
12565
12964
  function stateTypeName(className, machineName) {
12566
- return `${toPascalCase(className)}${toPascalCase(machineName)}`;
12965
+ return `${toPascalCase2(className)}${toPascalCase2(machineName)}`;
12567
12966
  }
12568
12967
  function transitionTypeName(className, machineName) {
12569
12968
  return `${stateTypeName(className, machineName)}Transition`;
@@ -12571,6 +12970,15 @@ function transitionTypeName(className, machineName) {
12571
12970
  function pathTypeName(className, machineName) {
12572
12971
  return `${stateTypeName(className, machineName)}Path`;
12573
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
+ }
12574
12982
  function normalizeStateDefinitions(machine) {
12575
12983
  const finalStates = new Set(machine.finalStates || []);
12576
12984
  const states = /* @__PURE__ */ new Map();
@@ -12584,6 +12992,8 @@ function normalizeStateDefinitions(machine) {
12584
12992
  }
12585
12993
  states.set(rawState.name, {
12586
12994
  name: rawState.name,
12995
+ label: rawState.label,
12996
+ description: rawState.description,
12587
12997
  isFinal: Boolean(rawState.isFinal) || finalStates.has(rawState.name)
12588
12998
  });
12589
12999
  }
@@ -12595,6 +13005,44 @@ function normalizeStateDefinitions(machine) {
12595
13005
  }
12596
13006
  return [...states.values()];
12597
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
+ }
12598
13046
  function buildStateMachineModelMutations(modelPath, machines) {
12599
13047
  const mutations = [];
12600
13048
  for (const machine of machines || []) {
@@ -12605,12 +13053,13 @@ function buildStateMachineModelMutations(modelPath, machines) {
12605
13053
  )}, entry_state: ${JSON.stringify(machine.entryState)}) { name } } }`
12606
13054
  });
12607
13055
  for (const state of normalizeStateDefinitions(machine)) {
12608
- if (state.name === machine.entryState && !state.isFinal) continue;
13056
+ if (state.name === machine.entryState && !state.isFinal && !state.label && !state.description)
13057
+ continue;
12609
13058
  mutations.push({
12610
13059
  label: `add state ${state.name} on ${modelPath}.${machine.name}`,
12611
13060
  query: `mutation { at(path: ${JSON.stringify(modelPath)}) { state_machine(name: ${JSON.stringify(
12612
13061
  machine.name
12613
- )}) { 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 } } } }`
12614
13063
  });
12615
13064
  }
12616
13065
  for (const transition of machine.transitions || []) {
@@ -12622,7 +13071,7 @@ function buildStateMachineModelMutations(modelPath, machines) {
12622
13071
  transition.name
12623
13072
  )}, from: ${JSON.stringify(transition.from)}, to: ${JSON.stringify(
12624
13073
  transition.to
12625
- )}) { name } } } }`
13074
+ )}${transitionMetadataGraphqlArgs(transition)}) { name } } } }`
12626
13075
  });
12627
13076
  }
12628
13077
  }
@@ -12649,7 +13098,7 @@ function buildMachineMethods(classSummary, machine) {
12649
13098
  const transitionName = transitionTypeName(classSummary.name, machine.name);
12650
13099
  pathTypeName(classSummary.name, machine.name);
12651
13100
  const docsPrefix = `${classSummary.name}.${machine.name}`;
12652
- return [
13101
+ const methods = [
12653
13102
  {
12654
13103
  name: `get_${machine.name}`,
12655
13104
  docs: [`Get the current ${docsPrefix} state.`],
@@ -12672,7 +13121,7 @@ function buildMachineMethods(classSummary, machine) {
12672
13121
  ],
12673
13122
  static: false,
12674
13123
  params: [{ name: "target", type: stateName }],
12675
- returnType: `Promise<${toPascalCase(classSummary.name)}>`,
13124
+ returnType: `Promise<${toPascalCase2(classSummary.name)}>`,
12676
13125
  runtime: {
12677
13126
  kind: "state_machine",
12678
13127
  machineName: machine.name,
@@ -12745,6 +13194,99 @@ function buildMachineMethods(classSummary, machine) {
12745
13194
  }
12746
13195
  }
12747
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;
12748
13290
  }
12749
13291
  function readStateMachineSummaries(rawClass) {
12750
13292
  return {
@@ -12767,8 +13309,8 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12767
13309
  type StateMachineMutation {
12768
13310
  name: String!
12769
13311
  state_machine: StateMachine!
12770
- add_state(name: String!, is_final: Boolean): StateMachineMutation!
12771
- 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!
12772
13314
  activate_transition(name: String!): StateMachineMutation!
12773
13315
  }
12774
13316
 
@@ -12785,6 +13327,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12785
13327
  type StateMachineSnapshotMutation {
12786
13328
  snapshot: StateMachineSnapshot!
12787
13329
  activate_transition(name: String!): StateMachineSnapshotMutation!
13330
+ observe_state(state: String!, force: Boolean, source: String): StateMachineSnapshotMutation!
12788
13331
  }
12789
13332
 
12790
13333
  type StateMachine {
@@ -12804,6 +13347,8 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12804
13347
 
12805
13348
  type StateMachineState {
12806
13349
  name: String!
13350
+ label: String
13351
+ description: String
12807
13352
  is_final: Boolean!
12808
13353
  }
12809
13354
 
@@ -12811,6 +13356,14 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12811
13356
  name: String!
12812
13357
  from: StateMachineState!
12813
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
12814
13367
  }
12815
13368
 
12816
13369
  type StateMachinePath {
@@ -12863,23 +13416,47 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12863
13416
  StateMachineMutation: {
12864
13417
  name: (value) => value.name,
12865
13418
  state_machine: async (value) => await run(value.target.state_machine(value.name)),
12866
- add_state: async (value, { name, is_final }) => {
13419
+ add_state: async (value, { name, is_final, label, description }) => {
12867
13420
  await run(
12868
13421
  value.target.add_state_machine_state(
12869
13422
  value.name,
12870
13423
  name,
12871
- is_final ?? false
13424
+ is_final ?? false,
13425
+ label,
13426
+ description
12872
13427
  )
12873
13428
  );
12874
13429
  return value;
12875
13430
  },
12876
- 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
+ }) => {
12877
13444
  await run(
12878
13445
  value.target.add_state_machine_transition(
12879
13446
  value.name,
12880
13447
  name,
12881
13448
  from,
12882
- 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
+ }
12883
13460
  )
12884
13461
  );
12885
13462
  return value;
@@ -12898,16 +13475,37 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12898
13475
  value.target.activate_state_machine_transition(value.name, name)
12899
13476
  );
12900
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;
12901
13489
  }
12902
13490
  },
12903
13491
  StateMachineState: {
12904
13492
  name: (value) => value.name,
13493
+ label: (value) => value.label || null,
13494
+ description: (value) => value.description || null,
12905
13495
  is_final: (value) => value.is_final
12906
13496
  },
12907
13497
  StateMachineTransition: {
12908
13498
  name: (value) => value.name,
12909
13499
  from: (value) => value.from_state || { name: value.from, is_final: false },
12910
- 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
12911
13509
  },
12912
13510
  StateMachinePath: {
12913
13511
  states: (value) => value.states,
@@ -12974,6 +13572,14 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12974
13572
  name
12975
13573
  from { name }
12976
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
12977
13583
  }
12978
13584
  }`
12979
13585
  ]
@@ -13005,6 +13611,104 @@ function describeRule(rule) {
13005
13611
  return `${rule.operator} ${String(rule.booleanValue)}`;
13006
13612
  return rule.operator;
13007
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
+ }
13008
13712
  function normalizeRule(rule) {
13009
13713
  const operator = typeof rule?.operator === "string" ? rule.operator : "";
13010
13714
  if (operator.length === 0) return null;
@@ -13227,6 +13931,26 @@ var STANDARD_MODULES_OPERATIONS = [
13227
13931
  var BUILTIN_MODULES = {
13228
13932
  standard_modules: STANDARD_MODULES_OPERATIONS
13229
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
+ }
13230
13954
  var DEFAULT_DIRECT_RECORD_OBJECTS_REQUEST_BATCH_SIZE = 100;
13231
13955
  var MAX_RECORD_OBJECTS_CONCURRENCY = 16;
13232
13956
  var DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_COUNT = 3;
@@ -13257,8 +13981,20 @@ function bodyInitFromSessionFileUpload(body) {
13257
13981
  return body;
13258
13982
  }
13259
13983
  var EFFECT_CATALOG_SYNC_TIMEOUT_MS = 12e4;
13984
+ var EFFECT_CATALOG_SYNC_BATCH_SIZE = 20;
13260
13985
  var EFFECT_CATALOG_SYNC_RETRY_COUNT = 3;
13261
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
+ }
13262
13998
  function planRecordObjectsChunks(records, batchSize) {
13263
13999
  const total = records.length;
13264
14000
  const size = Math.max(1, Math.min(batchSize, total));
@@ -13270,6 +14006,23 @@ function planRecordObjectsChunks(records, batchSize) {
13270
14006
  }
13271
14007
  return plans;
13272
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
+ }
13273
14026
  function computeEffectKey2(effect) {
13274
14027
  const attachedClass = effect.className?.trim();
13275
14028
  if (!attachedClass) {
@@ -13507,11 +14260,105 @@ var Environment = class _Environment {
13507
14260
  getAwaitingCount: async () => this.getAwaitingRecordCount()
13508
14261
  };
13509
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
+ }
13510
14344
  get feedback() {
13511
14345
  return {
13512
14346
  list: async () => this.listFeedback()
13513
14347
  };
13514
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
+ }
13515
14362
  /**
13516
14363
  * Sessionless environments do not own a live transport, so disconnecting the
13517
14364
  * environment handle itself is a no-op. This keeps the public surface
@@ -13590,6 +14437,50 @@ var Environment = class _Environment {
13590
14437
  const response = await this.controlPlaneRequest(`/control/environments/${this.environmentId}/feedback`);
13591
14438
  return Array.isArray(response.items) ? response.items : [];
13592
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
+ }
13593
14484
  getRuntimeBaseUrl() {
13594
14485
  return deriveRuntimeBaseUrl(this._apiEndpoint);
13595
14486
  }
@@ -14414,10 +15305,11 @@ var Environment = class _Environment {
14414
15305
  if (!Array.isArray(records) || records.length === 0) {
14415
15306
  return [];
14416
15307
  }
15308
+ const recordsToWrite = records.map(preserveRecordObjectRealId);
14417
15309
  const batchSize = Math.max(
14418
15310
  1,
14419
15311
  Math.min(
14420
- records.length,
15312
+ recordsToWrite.length,
14421
15313
  options?.batchSize ?? DEFAULT_DIRECT_RECORD_OBJECTS_REQUEST_BATCH_SIZE
14422
15314
  )
14423
15315
  );
@@ -14425,8 +15317,8 @@ var Environment = class _Environment {
14425
15317
  MAX_RECORD_OBJECTS_CONCURRENCY,
14426
15318
  Math.max(1, options?.concurrency ?? 1)
14427
15319
  );
14428
- const plans = planRecordObjectsChunks(records, batchSize);
14429
- const total = records.length;
15320
+ const plans = planRecordObjectsChunks(recordsToWrite, batchSize);
15321
+ const total = recordsToWrite.length;
14430
15322
  const results = new Array(total);
14431
15323
  const onChunk = options?.onChunkComplete;
14432
15324
  for (let waveStart = 0; waveStart < plans.length; waveStart += concurrency) {
@@ -14497,12 +15389,13 @@ var Environment = class _Environment {
14497
15389
  * synchronous upserts and fine-grained chunk progress via **`onChunkComplete`**.
14498
15390
  */
14499
15391
  async enqueueRecordImport(records, options = {}) {
15392
+ const recordsToImport = records.map(preserveRecordObjectRealId);
14500
15393
  return this.controlPlaneRequest(
14501
15394
  `/control/environments/${this.environmentId}/record-imports`,
14502
15395
  {
14503
15396
  method: "POST",
14504
15397
  body: JSON.stringify({
14505
- records,
15398
+ records: recordsToImport,
14506
15399
  batchSize: options.batchSize,
14507
15400
  setupRunId: options.setupRunId,
14508
15401
  writeMode: options.writeMode
@@ -14610,11 +15503,7 @@ var EnvironmentSession = class extends Session {
14610
15503
  }
14611
15504
  buildSessionDataUrl(path, query) {
14612
15505
  const searchParams = new URLSearchParams();
14613
- for (const [key, value] of Object.entries(query || {})) {
14614
- if (value !== null && typeof value !== "undefined" && value !== "") {
14615
- searchParams.set(key, String(value));
14616
- }
14617
- }
15506
+ appendQueryOptions(searchParams, query);
14618
15507
  const queryString = searchParams.toString();
14619
15508
  return `${this.environment.runtimeBaseUrl}${this.sessionDataRoutePrefix}/${encodeURIComponent(this.sessionId)}${path}${queryString ? `?${queryString}` : ""}`;
14620
15509
  }
@@ -14697,9 +15586,108 @@ var EnvironmentSession = class extends Session {
14697
15586
  ),
14698
15587
  get: (jobId) => this.sessionDataRequest(
14699
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" }
14700
15653
  )
14701
15654
  };
14702
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
+ }
14703
15691
  get files() {
14704
15692
  return {
14705
15693
  list: (options = {}) => this.sessionDataRequest(
@@ -14780,13 +15768,16 @@ var EnvironmentSession = class extends Session {
14780
15768
  get transcript() {
14781
15769
  return {
14782
15770
  list: async (options = {}) => {
14783
- const [messages, jobs, entries, lists] = await Promise.all([
15771
+ const [messages, jobs, entries, lists, artifacts] = await Promise.all([
14784
15772
  this.collectAllSessionItems(this.messages.list),
14785
15773
  this.collectAllSessionItems(
14786
15774
  (pageOptions) => this.jobs.list({ ...pageOptions, status: "all" })
14787
15775
  ),
14788
15776
  this.collectAllSessionItems(this.heap.entries.list),
14789
- 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
+ )
14790
15781
  ]);
14791
15782
  const liveDoc = {
14792
15783
  conversation: { messages },
@@ -14800,6 +15791,21 @@ var EnvironmentSession = class extends Session {
14800
15791
  (entry) => Boolean(entry)
14801
15792
  )
14802
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
+ )
14803
15809
  }
14804
15810
  };
14805
15811
  const heap = normalizeHeapSnapshot({
@@ -14876,6 +15882,12 @@ var EnvironmentSession = class extends Session {
14876
15882
  async recordObject(options) {
14877
15883
  return this.environment.recordObject(options);
14878
15884
  }
15885
+ async recordState(input) {
15886
+ return this.environment.recordState(input);
15887
+ }
15888
+ state(target) {
15889
+ return this.environment.state(target);
15890
+ }
14879
15891
  async recordObjects(records, options) {
14880
15892
  return this.environment.recordObjects(records, options);
14881
15893
  }
@@ -15703,15 +16715,43 @@ var Granular = class _Granular {
15703
16715
  const effects = Array.from(
15704
16716
  this.getSandboxEffectMap(host.sandboxId).values()
15705
16717
  ).map((effect) => this.serializeEffect(effect));
15706
- const result = await withTimeout(
15707
- host.wsClient.call("effects.publishCatalog", {
15708
- effects
15709
- }),
15710
- EFFECT_CATALOG_SYNC_TIMEOUT_MS,
15711
- `effects.publishCatalog for sandbox ${host.sandboxId}`
15712
- );
15713
- const acceptedCount = typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
15714
- 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
+ }
15715
16755
  if (acceptedCount === 0 && rejected.length > 0) {
15716
16756
  const detail = rejected.map(
15717
16757
  (entry) => `${entry.name || "unknown"}: ${entry.reason || "rejected"}`
@@ -16943,6 +17983,7 @@ var HARNESS_V3_FRONTEND_ACTIONS_MODULE = "@granular/actions/frontend";
16943
17983
  var HARNESS_V3_CSV_MODULE = "@granular/utils/csv";
16944
17984
  var HARNESS_V3_XLSX_MODULE = "@granular/utils/xlsx";
16945
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))";
16946
17987
  function hasNamedModuleImport(source, moduleName, name) {
16947
17988
  const escapedModule = moduleName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
16948
17989
  const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -17001,6 +18042,15 @@ function reviewGeneratedJobCode(code, _options = {}) {
17001
18042
  message: "Generated code must use static top-level ESM imports from the Harness v3 runtime modules. Do not use dynamic import(...)."
17002
18043
  });
17003
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
+ }
17004
18054
  if (/\bprocess\.exit\s*\(/.test(normalized)) {
17005
18055
  issues.push({
17006
18056
  code: "process_exit",
@@ -18025,6 +19075,31 @@ function buildGranularAgentHeapBlock(heapSummary) {
18025
19075
  entries: {}
18026
19076
  });
18027
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
+ }
18028
19103
  function projectSessionFileSummary(liveDoc) {
18029
19104
  const files = asRecord4(liveDoc?.files);
18030
19105
  const byId = asRecord4(files?.byId) || {};
@@ -18056,8 +19131,12 @@ function buildGranularAgentFileBlock(fileSummary) {
18056
19131
  function extractRuntimeContractExports(domainBlock) {
18057
19132
  const classes = /* @__PURE__ */ new Set();
18058
19133
  const actions = /* @__PURE__ */ new Set();
18059
- const classPattern = /export\s+declare\s+(?:const|class)\s+([A-Za-z_$][\w$]*)/g;
18060
- 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)) {
18061
19140
  classes.add(match[1]);
18062
19141
  }
18063
19142
  const actionPattern = /export\s+declare\s+function\s+([A-Za-z_$][\w$]*)/g;
@@ -18562,6 +19641,9 @@ function buildGranularAgentSystemPrompt(input) {
18562
19641
  });
18563
19642
  const referentBlock = buildGranularAgentReferentBlock(input.referentSummary);
18564
19643
  const loopBlock = buildGranularAgentLoopBlock(input.loopSummary);
19644
+ const manualActionBlock = buildGranularAgentManualActionBlock(
19645
+ input.manualActionSummary
19646
+ );
18565
19647
  const knownFactsBlock = renderConstBlock(
18566
19648
  "knownFacts",
18567
19649
  buildKnownFactsFromCheckpoint(input.checkpoint)
@@ -18575,16 +19657,15 @@ function buildGranularAgentSystemPrompt(input) {
18575
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.
18576
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.
18577
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.
18578
- - 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.
18579
- - 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.
18580
- - 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"] })\`.
18581
- - \`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(...)\`.
18582
- - 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.
18583
- - 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.
18584
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.
18585
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.
18586
- - 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.
18587
- - 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.
18588
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\`.
18589
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.
18590
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.
@@ -18595,7 +19676,7 @@ function buildGranularAgentSystemPrompt(input) {
18595
19676
  - When using code, assistant text must be empty or one brief summary.
18596
19677
  - Code must be plain runnable JavaScript with top-level await.
18597
19678
  - Use [Runtime Imports] as the authoritative module map. Import only listed module exports.
18598
- - 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.
18599
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.
18600
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.
18601
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\`.
@@ -18658,6 +19739,7 @@ ${workflowRules}
18658
19739
  High-priority execution rules:
18659
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.
18660
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.
18661
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.
18662
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.
18663
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.
@@ -18701,7 +19783,7 @@ Intent resolution:
18701
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.
18702
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.
18703
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.
18704
- - 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.
18705
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.
18706
19788
  - Never call \`.get({ path: "" })\`; an empty path is not a saved reference.
18707
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.
@@ -18828,20 +19910,10 @@ Ask the user when:
18828
19910
  - the target is unique but the requested action is unclear
18829
19911
 
18830
19912
  Relationship filters:
18831
- - One-record relationships use \`is\`.
18832
- - Multi-record relationships use \`some\`.
18833
- - 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.
18834
- - 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.
18835
- - 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.
18836
- - 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.
18837
- - Use \`some\` only when the generated TypeScript type says \`ManyRelationFilter\`.
18838
- - 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.
18839
- - Use \`{ relationship: { id: "record_id" } }\` or \`{ relationship: { path: "class_record_id" } }\` when matching a known related record.
18840
- - 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\`.
18841
- - 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.
18842
- - Use \`{ relationship: { is: { field: { equal_to: value } } } }\` only for nested field filters. Never put \`id\` or \`path\` inside \`is\`.
18843
- - 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.
18844
- - 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.
18845
19917
  ${domainSections.docs ? `
18846
19918
  Domain notes:
18847
19919
  ${domainSections.docs}
@@ -18852,6 +19924,23 @@ ${actionIndex}
18852
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.
18853
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(...)\`.
18854
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.
18855
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.
18856
19945
  - Never call a record-level action as \`Class.action_name(...)\`; that method will not exist.
18857
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.
@@ -18882,6 +19971,8 @@ ${loopBlock}
18882
19971
 
18883
19972
  ${knownFactsBlock}
18884
19973
 
19974
+ ${manualActionBlock}
19975
+
18885
19976
  [Request]
18886
19977
  ${input.request?.trim() || "Use the latest user message in the conversation."}`;
18887
19978
  }
@@ -19169,6 +20260,6 @@ function calculateOpenAITokenSpend(model, rawUsage) {
19169
20260
  };
19170
20261
  }
19171
20262
 
19172
- 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 };
19173
20264
  //# sourceMappingURL=index.mjs.map
19174
20265
  //# sourceMappingURL=index.mjs.map