@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.js CHANGED
@@ -4038,6 +4038,9 @@ function rpcTimeoutMsForMethod(method) {
4038
4038
  return DOMAIN_PACKAGE_RPC_TIMEOUT_MS;
4039
4039
  case "client.heartbeat":
4040
4040
  case "effects.publishCatalog":
4041
+ case "effects.resetCatalog":
4042
+ case "effects.addCatalog":
4043
+ case "effects.removeCatalog":
4041
4044
  case "effects.refresh":
4042
4045
  return EFFECT_CONTROL_RPC_TIMEOUT_MS;
4043
4046
  case "harness.run":
@@ -4783,6 +4786,9 @@ function resolvePromptAnswer(prompt, answer) {
4783
4786
 
4784
4787
  // src/session.ts
4785
4788
  var PROMPT_TRANSCRIPT_APPEND_TIMEOUT_MS = 5e3;
4789
+ function toPascalCase(value) {
4790
+ return value.split(/[_:\-\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
4791
+ }
4786
4792
  function withPromptTranscriptTimeout(promise) {
4787
4793
  let timeout = null;
4788
4794
  return Promise.race([
@@ -5345,9 +5351,7 @@ var Session = class {
5345
5351
  if (classes && Object.keys(classes).length > 0) {
5346
5352
  let docs2 = "# Domain Documentation\n\n";
5347
5353
  docs2 += "Import concrete classes from `@granular/domain/<Class>` and global backend actions from `@granular/actions/backend`:\n\n";
5348
- const classNames = Object.keys(classes).map(
5349
- (c) => c.charAt(0).toUpperCase() + c.slice(1)
5350
- );
5354
+ const classNames = Object.keys(classes).map(toPascalCase);
5351
5355
  const globalNames = (globalTools || []).map((t) => t.name);
5352
5356
  const importLines = [
5353
5357
  ...classNames.map(
@@ -5361,7 +5365,7 @@ ${importLines.join("\n") || "// No generated domain imports available."}
5361
5365
 
5362
5366
  `;
5363
5367
  for (const [className, cls] of Object.entries(classes)) {
5364
- const TsName = className.charAt(0).toUpperCase() + className.slice(1);
5368
+ const TsName = toPascalCase(className);
5365
5369
  docs2 += `## ${TsName}
5366
5370
 
5367
5371
  `;
@@ -6351,6 +6355,28 @@ function asString(value) {
6351
6355
  function trimString(value) {
6352
6356
  return typeof value === "string" ? value.trim() : "";
6353
6357
  }
6358
+ function compactJson(value, maxLength = 320) {
6359
+ if (value === void 0 || value === null) return void 0;
6360
+ try {
6361
+ const json = JSON.stringify(value);
6362
+ if (!json || json === "undefined") return void 0;
6363
+ return json.length > maxLength ? `${json.slice(0, maxLength)}...` : json;
6364
+ } catch {
6365
+ return String(value);
6366
+ }
6367
+ }
6368
+ function artifactRecordsById(liveDoc) {
6369
+ const artifacts = asRecord3(liveDoc?.artifacts);
6370
+ const byId = asRecord3(artifacts?.byId) || {};
6371
+ return Object.fromEntries(
6372
+ Object.entries(byId).map(([artifactId, value]) => {
6373
+ const record = asRecord3(value);
6374
+ return record ? [artifactId, record] : null;
6375
+ }).filter(
6376
+ (entry) => Boolean(entry)
6377
+ )
6378
+ );
6379
+ }
6354
6380
  function normalizeShowRefs(value) {
6355
6381
  const record = asRecord3(value);
6356
6382
  if (!record) return void 0;
@@ -6367,9 +6393,31 @@ function normalizeShowRefs(value) {
6367
6393
  entryPaths: normalizeRefs(record.entryPaths),
6368
6394
  listNames: normalizeRefs(record.listNames),
6369
6395
  variableNames: normalizeRefs(record.variableNames),
6370
- fileIds: normalizeRefs(record.fileIds)
6396
+ fileIds: normalizeRefs(record.fileIds),
6397
+ sessionArtifactIds: normalizeRefs(record.sessionArtifactIds),
6398
+ actionSuggestions: normalizeActionSuggestions(record.actionSuggestions)
6371
6399
  };
6372
- return show.entryPaths || show.listNames || show.variableNames || show.fileIds ? show : void 0;
6400
+ return show.entryPaths || show.listNames || show.variableNames || show.fileIds || show.sessionArtifactIds || show.actionSuggestions ? show : void 0;
6401
+ }
6402
+ function normalizeActionSuggestions(value) {
6403
+ if (!Array.isArray(value)) return void 0;
6404
+ const suggestions = [];
6405
+ for (const item of value) {
6406
+ const record = asRecord3(item);
6407
+ if (!record) continue;
6408
+ const label = trimString(record.label);
6409
+ if (!label) continue;
6410
+ const suggestionId = trimString(record.suggestionId) || trimString(record.id) || label;
6411
+ suggestions.push({
6412
+ suggestionId,
6413
+ label,
6414
+ ...typeof record.description === "string" ? { description: record.description } : {},
6415
+ ...asRecord3(record.artifact) ? { artifact: asRecord3(record.artifact) } : {},
6416
+ ...asRecord3(record.target) ? { target: asRecord3(record.target) } : {},
6417
+ ...asRecord3(record.metadata) ? { metadata: asRecord3(record.metadata) } : {}
6418
+ });
6419
+ }
6420
+ return suggestions.length ? suggestions : void 0;
6373
6421
  }
6374
6422
  function stringifyTranscriptValue(value, fallback = "") {
6375
6423
  if (typeof value === "string") {
@@ -6389,12 +6437,139 @@ function stringifyTranscriptValue(value, fallback = "") {
6389
6437
  return String(value);
6390
6438
  }
6391
6439
  }
6392
- function buildArtifactHistory(show) {
6440
+ function latestInputEditSummary(metadata) {
6441
+ const lastInputEdit = asRecord3(metadata.lastInputEdit);
6442
+ if (!lastInputEdit) return null;
6443
+ const source = asString(lastInputEdit.source) || "unknown";
6444
+ const actor = asString(lastInputEdit.actorSubjectId) || asString(lastInputEdit.actorPermissionProfileName) || asString(lastInputEdit.jobId) || null;
6445
+ const inputKeys = Array.isArray(lastInputEdit.changedInputKeys) ? lastInputEdit.changedInputKeys.filter(
6446
+ (key) => typeof key === "string" && key.trim().length > 0
6447
+ ).slice(0, 6) : [];
6448
+ const relationshipKeys = Array.isArray(lastInputEdit.changedRelationshipKeys) ? lastInputEdit.changedRelationshipKeys.filter(
6449
+ (key) => typeof key === "string" && key.trim().length > 0
6450
+ ).slice(0, 6) : [];
6451
+ const changed = [
6452
+ inputKeys.length ? `inputs=${inputKeys.join(",")}` : null,
6453
+ relationshipKeys.length ? `relationships=${relationshipKeys.join(",")}` : null
6454
+ ].filter(Boolean);
6455
+ return `lastEdit=${source}${actor ? ` by ${actor}` : ""}${changed.length ? ` (${changed.join("; ")})` : ""}`;
6456
+ }
6457
+ function artifactIssueSummary(record) {
6458
+ const validation = asRecord3(record.validation);
6459
+ if (!validation) return null;
6460
+ const issues = Array.isArray(validation.issues) ? validation.issues.map((issue) => asRecord3(issue)).filter((issue) => Boolean(issue)).slice(0, 3) : [];
6461
+ if (issues.length > 0) {
6462
+ return `issues=${issues.map((issue) => {
6463
+ const code = asString(issue.code) || asString(issue.kind) || "issue";
6464
+ const path = asString(issue.path);
6465
+ const message = trimString(issue.message);
6466
+ return `${code}${path ? ` at ${path}` : ""}${message ? ` (${message})` : ""}`;
6467
+ }).join("; ")}`;
6468
+ }
6469
+ const error = trimString(validation.error) || trimString(validation.reason) || trimString(validation.message);
6470
+ return error ? `validation=${error}` : null;
6471
+ }
6472
+ function artifactExecutionSummary(metadata) {
6473
+ const execution = asRecord3(metadata.execution);
6474
+ if (!execution) return null;
6475
+ const result = asRecord3(execution.result);
6476
+ const awaiting = asString(result?.awaiting) || asString(execution.awaiting);
6477
+ const pendingTransition = asString(result?.pendingTransition) || asString(execution.pendingTransition);
6478
+ const approval = asRecord3(result?.approval) || asRecord3(execution.approval);
6479
+ const approvalTarget = asString(approval?.permissionProfileName) || asString(approval?.permissionProfileId) || asString(approval?.assigneeSubjectId);
6480
+ const error = trimString(execution.error);
6481
+ const pieces = [
6482
+ awaiting ? `awaiting=${awaiting}` : null,
6483
+ pendingTransition ? `pendingTransition=${pendingTransition}` : null,
6484
+ approvalTarget ? `approvalTarget=${approvalTarget}` : null,
6485
+ error ? `executionError=${error}` : null
6486
+ ].filter(Boolean);
6487
+ return pieces.length ? pieces.join("; ") : null;
6488
+ }
6489
+ function artifactStatePathSummary(metadata) {
6490
+ const statePlan = asRecord3(metadata.statePlan);
6491
+ if (!statePlan) return null;
6492
+ const machineName = asString(statePlan.machineName);
6493
+ const targetState = asString(statePlan.targetState);
6494
+ const objectPath = asString(statePlan.objectPath);
6495
+ const approvedTransitions = Array.isArray(statePlan.approvedTransitions) ? statePlan.approvedTransitions.length : 0;
6496
+ const approvalDecisions = Array.isArray(statePlan.approvalDecisions) ? statePlan.approvalDecisions.length : 0;
6497
+ const pieces = [
6498
+ machineName || targetState ? `statePath=${machineName || "state_machine"}${targetState ? ` -> ${targetState}` : ""}` : null,
6499
+ objectPath ? `objectPath=${objectPath}` : null,
6500
+ approvedTransitions ? `approvedTransitions=${approvedTransitions}` : null,
6501
+ approvalDecisions ? `approvalDecisions=${approvalDecisions}` : null
6502
+ ].filter(Boolean);
6503
+ return pieces.length ? pieces.join("; ") : null;
6504
+ }
6505
+ function artifactSummaryLine(artifactId, record) {
6506
+ if (!record) return `- ${artifactId}: unavailable in session artifact store`;
6507
+ const label = trimString(record.label) || artifactId;
6508
+ const kind = asString(record.kind) || "artifact";
6509
+ const status = asString(record.status) || "unknown";
6510
+ const createdByJobId = asString(record.createdByJobId);
6511
+ const target = asRecord3(record.target);
6512
+ const metadata = asRecord3(record.metadata) || {};
6513
+ const subArtifactIds = Array.isArray(record.subArtifactIds) ? record.subArtifactIds.filter(
6514
+ (id) => typeof id === "string" && id.trim().length > 0
6515
+ ).slice(0, 8) : [];
6516
+ const relationships = compactJson(record.relationships, 220);
6517
+ const pieces = [
6518
+ `kind=${kind}`,
6519
+ `status=${status}`,
6520
+ createdByJobId ? `createdByJob=${createdByJobId}` : null,
6521
+ target ? `target=${asString(target.className) || "record"}:${asString(target.id) || "unknown"}${asString(target.label) ? ` (${asString(target.label)})` : ""}` : null,
6522
+ artifactStatePathSummary(metadata),
6523
+ artifactExecutionSummary(metadata),
6524
+ artifactIssueSummary(record),
6525
+ latestInputEditSummary(metadata),
6526
+ subArtifactIds.length ? `subArtifacts=${subArtifactIds.join(",")}` : null,
6527
+ relationships ? `relationships=${relationships}` : null
6528
+ ].filter(Boolean);
6529
+ return `- ${artifactId}: ${label}${pieces.length ? `; ${pieces.join("; ")}` : ""}`;
6530
+ }
6531
+ function buildArtifactHistory(show, artifactsById) {
6393
6532
  if (!show) return void 0;
6394
- return `[Agent message]
6533
+ const artifactIds = show.sessionArtifactIds || [];
6534
+ const actionSuggestions = show.actionSuggestions || [];
6535
+ if (artifactIds.length === 0 && actionSuggestions.length === 0) {
6536
+ return `[Agent message]
6395
6537
  ${stringifyTranscriptValue({ show }, "")}`;
6538
+ }
6539
+ const lines = artifactIds.slice(0, 8).map(
6540
+ (artifactId) => artifactSummaryLine(artifactId, artifactsById?.[artifactId])
6541
+ );
6542
+ if (artifactIds.length > 8) {
6543
+ lines.push(`- ${artifactIds.length - 8} more artifacts omitted`);
6544
+ }
6545
+ if (actionSuggestions.length > 0) {
6546
+ if (artifactIds.length > 0) lines.push("[Agent suggested actions]");
6547
+ for (const suggestion of actionSuggestions.slice(0, 8)) {
6548
+ lines.push(
6549
+ `- ${suggestion.label}${suggestion.description ? `; ${suggestion.description}` : ""}`
6550
+ );
6551
+ }
6552
+ if (actionSuggestions.length > 8) {
6553
+ lines.push(`- ${actionSuggestions.length - 8} more suggestions omitted`);
6554
+ }
6555
+ }
6556
+ const otherRefs = {
6557
+ entryPaths: show.entryPaths,
6558
+ listNames: show.listNames,
6559
+ variableNames: show.variableNames,
6560
+ fileIds: show.fileIds
6561
+ };
6562
+ const hasOtherRefs = Object.values(otherRefs).some(
6563
+ (value) => Array.isArray(value) && value.length > 0
6564
+ );
6565
+ const title = artifactIds.length > 0 ? "[Agent displayed session artifacts]" : "[Agent suggested actions]";
6566
+ return [
6567
+ title,
6568
+ ...lines,
6569
+ hasOtherRefs ? `Other shown refs: ${stringifyTranscriptValue(otherRefs, "")}` : null
6570
+ ].filter(Boolean).join("\n");
6396
6571
  }
6397
- function normalizeConversationMessage(raw) {
6572
+ function normalizeConversationMessage(raw, artifactsById) {
6398
6573
  const record = asRecord3(raw);
6399
6574
  if (!record) return null;
6400
6575
  const role = record.role === "user" ? "user" : record.role === "assistant" ? "assistant" : null;
@@ -6406,6 +6581,12 @@ function normalizeConversationMessage(raw) {
6406
6581
  const id = asString(record.id) || crypto.randomUUID();
6407
6582
  const timestamp = asNumber(record.timestamp) || asNumber(record.ts) || 0;
6408
6583
  if (!content && !show) return null;
6584
+ const artifactHistory = buildArtifactHistory(show, artifactsById);
6585
+ const historyContent = role === "assistant" ? content && artifactHistory ? `[Assistant reply]
6586
+ ${content}
6587
+
6588
+ ${artifactHistory}` : content ? `[Assistant reply]
6589
+ ${content}` : artifactHistory : void 0;
6409
6590
  return {
6410
6591
  id,
6411
6592
  role,
@@ -6414,8 +6595,7 @@ function normalizeConversationMessage(raw) {
6414
6595
  jobId: asString(record.jobId),
6415
6596
  promptId: asString(record.promptId),
6416
6597
  show,
6417
- historyContent: role === "assistant" ? content ? `[Assistant reply]
6418
- ${content}` : buildArtifactHistory(show) : void 0,
6598
+ historyContent,
6419
6599
  source: "conversation"
6420
6600
  };
6421
6601
  }
@@ -6458,7 +6638,7 @@ ${assistantContent}`,
6458
6638
  return entries;
6459
6639
  });
6460
6640
  }
6461
- function normalizeAgentMessageEntries(jobId, rawMessages) {
6641
+ function normalizeAgentMessageEntries(jobId, rawMessages, artifactsById) {
6462
6642
  return asArray(rawMessages).map((value) => asRecord3(value)).filter((value) => Boolean(value)).sort(
6463
6643
  (left, right) => (asNumber(left.timestamp) || asNumber(left.ts) || 0) - (asNumber(right.timestamp) || asNumber(right.ts) || 0)
6464
6644
  ).flatMap((message) => {
@@ -6489,14 +6669,14 @@ ${reply}`,
6489
6669
  timestamp,
6490
6670
  jobId,
6491
6671
  show,
6492
- historyContent: buildArtifactHistory(show),
6672
+ historyContent: buildArtifactHistory(show, artifactsById),
6493
6673
  source: "job_agent_message"
6494
6674
  });
6495
6675
  }
6496
6676
  return entries;
6497
6677
  });
6498
6678
  }
6499
- function buildJobFallbackEntries(jobId, job, sessionHeap) {
6679
+ function buildJobFallbackEntries(jobId, job, sessionHeap, artifactsById) {
6500
6680
  const timestamp = asNumber(job.finishedAt) || asNumber(job.startedAt) || asNumber(job.submittedAt) || 0;
6501
6681
  const resultPreview = stringifyTranscriptValue(
6502
6682
  job.result,
@@ -6534,7 +6714,7 @@ ${responseText}`,
6534
6714
  timestamp,
6535
6715
  jobId,
6536
6716
  show,
6537
- historyContent: buildArtifactHistory(show),
6717
+ historyContent: buildArtifactHistory(show, artifactsById),
6538
6718
  source: "job_result"
6539
6719
  });
6540
6720
  }
@@ -6588,10 +6768,11 @@ function buildJobCodeEntry(jobId, job) {
6588
6768
  function buildSessionTranscript(input) {
6589
6769
  const liveDoc = input.liveDoc || null;
6590
6770
  const sessionHeap = input.sessionHeap || EMPTY_HEAP;
6771
+ const artifactsById = artifactRecordsById(liveDoc);
6591
6772
  const transcript = [];
6592
6773
  const conversationMessages = asArray(
6593
6774
  asRecord3(liveDoc?.conversation)?.messages
6594
- ).map((message) => normalizeConversationMessage(message)).filter((message) => Boolean(message));
6775
+ ).map((message) => normalizeConversationMessage(message, artifactsById)).filter((message) => Boolean(message));
6595
6776
  const conversationPromptIds = new Set(
6596
6777
  conversationMessages.map((message) => message.promptId).filter((promptId) => Boolean(promptId))
6597
6778
  );
@@ -6618,7 +6799,8 @@ function buildSessionTranscript(input) {
6618
6799
  if (!assistantConversationJobIds.has(jobId)) {
6619
6800
  const agentEntries = normalizeAgentMessageEntries(
6620
6801
  jobId,
6621
- job.agentMessages
6802
+ job.agentMessages,
6803
+ artifactsById
6622
6804
  );
6623
6805
  if (agentEntries.length > 0) {
6624
6806
  transcript.push(...agentEntries);
@@ -6627,7 +6809,8 @@ function buildSessionTranscript(input) {
6627
6809
  ...buildJobFallbackEntries(
6628
6810
  jobId,
6629
6811
  job,
6630
- sessionHeap
6812
+ sessionHeap,
6813
+ artifactsById
6631
6814
  )
6632
6815
  );
6633
6816
  }
@@ -10793,16 +10976,107 @@ var StateMachineStateSchema = external_exports.union([
10793
10976
  external_exports.string(),
10794
10977
  external_exports.object({
10795
10978
  name: external_exports.string().min(1),
10979
+ label: external_exports.string().optional(),
10980
+ description: external_exports.string().optional(),
10796
10981
  isFinal: external_exports.boolean().optional()
10797
10982
  }).strict()
10798
10983
  ]);
10984
+ var StateTransitionInputBindingSchema = external_exports.lazy(
10985
+ () => external_exports.union([
10986
+ external_exports.null(),
10987
+ external_exports.string(),
10988
+ external_exports.number(),
10989
+ external_exports.boolean(),
10990
+ external_exports.array(StateTransitionInputBindingSchema),
10991
+ external_exports.object({
10992
+ const: external_exports.unknown()
10993
+ }).strict(),
10994
+ external_exports.object({
10995
+ from: external_exports.literal("object"),
10996
+ path: external_exports.string().min(1),
10997
+ editable: external_exports.boolean().optional()
10998
+ }).strict(),
10999
+ external_exports.object({
11000
+ from: external_exports.literal("field"),
11001
+ name: external_exports.string().min(1),
11002
+ editable: external_exports.boolean().optional()
11003
+ }).strict(),
11004
+ external_exports.object({
11005
+ from: external_exports.literal("relationship"),
11006
+ name: external_exports.string().min(1),
11007
+ path: external_exports.string().min(1).optional(),
11008
+ many: external_exports.boolean().optional(),
11009
+ editable: external_exports.boolean().optional()
11010
+ }).strict(),
11011
+ external_exports.object({
11012
+ from: external_exports.literal("session"),
11013
+ path: external_exports.string().min(1),
11014
+ editable: external_exports.boolean().optional()
11015
+ }).strict(),
11016
+ external_exports.object({
11017
+ from: external_exports.literal("actor"),
11018
+ path: external_exports.string().min(1),
11019
+ editable: external_exports.boolean().optional()
11020
+ }).strict(),
11021
+ external_exports.record(external_exports.string(), StateTransitionInputBindingSchema)
11022
+ ])
11023
+ );
11024
+ var StateTransitionActionSchema = external_exports.object({
11025
+ effect: external_exports.string().min(1),
11026
+ input: external_exports.record(external_exports.string(), StateTransitionInputBindingSchema).optional()
11027
+ }).strict();
11028
+ var StateTransitionAssigneeSchema = external_exports.object({
11029
+ kind: external_exports.string().min(1),
11030
+ from: StateTransitionInputBindingSchema.optional(),
11031
+ role: external_exports.string().optional(),
11032
+ label: external_exports.string().optional()
11033
+ }).strict();
11034
+ var StateTransitionRelatedStateRequirementSchema = external_exports.object({
11035
+ relationship: external_exports.string().min(1),
11036
+ machine: external_exports.string().min(1),
11037
+ state: external_exports.string().min(1),
11038
+ className: external_exports.string().min(1).optional(),
11039
+ label: external_exports.string().optional(),
11040
+ mode: external_exports.enum(["every", "some", "any"]).optional()
11041
+ }).strict();
11042
+ var StateTransitionRequirementsSchema = external_exports.object({
11043
+ fields: external_exports.array(external_exports.string().min(1)).optional(),
11044
+ relationships: external_exports.array(external_exports.string().min(1)).optional(),
11045
+ relatedStates: external_exports.array(StateTransitionRelatedStateRequirementSchema).optional()
11046
+ }).strict();
11047
+ var StateTransitionPermissionSchema = external_exports.union([
11048
+ external_exports.string().min(1),
11049
+ external_exports.object({
11050
+ profile: external_exports.string().min(1).optional(),
11051
+ profileId: external_exports.string().min(1).optional(),
11052
+ label: external_exports.string().optional(),
11053
+ reason: external_exports.string().optional()
11054
+ }).strict()
11055
+ ]);
11056
+ var StateTransitionExpectedOutcomeSchema = external_exports.union([
11057
+ external_exports.string().min(1),
11058
+ external_exports.object({
11059
+ machine: external_exports.string().min(1).optional(),
11060
+ state: external_exports.string().min(1),
11061
+ summary: external_exports.string().optional()
11062
+ }).strict()
11063
+ ]);
10799
11064
  var StateMachineTransitionSchema = external_exports.object({
10800
11065
  name: external_exports.string().min(1),
10801
11066
  from: external_exports.string().min(1),
10802
- to: external_exports.string().min(1)
11067
+ to: external_exports.string().min(1),
11068
+ label: external_exports.string().optional(),
11069
+ description: external_exports.string().optional(),
11070
+ action: StateTransitionActionSchema.optional(),
11071
+ assignee: StateTransitionAssigneeSchema.optional(),
11072
+ requirements: StateTransitionRequirementsSchema.optional(),
11073
+ permission: StateTransitionPermissionSchema.optional(),
11074
+ risk: external_exports.enum(["low", "medium", "high"]).optional(),
11075
+ expectedOutcome: StateTransitionExpectedOutcomeSchema.optional()
10803
11076
  }).strict();
10804
11077
  external_exports.object({
10805
11078
  name: external_exports.string().min(1),
11079
+ stateField: external_exports.string().min(1).optional(),
10806
11080
  entryState: external_exports.string().min(1),
10807
11081
  states: external_exports.array(StateMachineStateSchema).min(1),
10808
11082
  transitions: external_exports.array(StateMachineTransitionSchema),
@@ -10869,6 +11143,16 @@ var PoliciesSchema = external_exports.object({
10869
11143
  confirmWhen: external_exports.array(PolicyRuleSchema).optional(),
10870
11144
  denyWhen: external_exports.array(PolicyRuleSchema).optional()
10871
11145
  }).strict();
11146
+ var CreatesSchema = external_exports.union([
11147
+ external_exports.string().min(1),
11148
+ external_exports.object({
11149
+ className: external_exports.string().min(1),
11150
+ idPath: external_exports.string().min(1).optional(),
11151
+ pathPath: external_exports.string().min(1).optional(),
11152
+ statePath: external_exports.string().min(1).optional(),
11153
+ classStateHandle: external_exports.boolean().optional()
11154
+ }).strict()
11155
+ ]);
10872
11156
  external_exports.object({
10873
11157
  postCondition: external_exports.union([
10874
11158
  external_exports.string(),
@@ -10899,6 +11183,7 @@ external_exports.object({
10899
11183
  mode: external_exports.string().optional()
10900
11184
  }).strict()
10901
11185
  ]).optional(),
11186
+ creates: CreatesSchema.optional(),
10902
11187
  access: external_exports.enum(["read", "write", "ui"]).optional(),
10903
11188
  effectKind: external_exports.enum(["read", "write", "ui"]).optional(),
10904
11189
  sideEffect: external_exports.enum(["read", "write", "ui", "readonly", "read_only"]).optional(),
@@ -11126,9 +11411,10 @@ function mergeMethodSummaryPatch(target, patch) {
11126
11411
  if (patch.metamodels !== void 0) target.metamodels = patch.metamodels;
11127
11412
  if (patch.effectBehaviors !== void 0)
11128
11413
  target.effectBehaviors = patch.effectBehaviors;
11414
+ if (patch.creates !== void 0) target.creates = patch.creates;
11129
11415
  if (patch.static !== void 0) target.static = patch.static;
11130
11416
  }
11131
- function toPascalCase(value) {
11417
+ function toPascalCase2(value) {
11132
11418
  return value.split(/[_:\-\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
11133
11419
  }
11134
11420
  function normalizeNotesInput(input) {
@@ -11186,29 +11472,60 @@ function normalizeEffectBehaviorSummary(metamodels) {
11186
11472
  }
11187
11473
  return Object.keys(result).length > 0 ? result : null;
11188
11474
  }
11189
- function buildEffectBehaviorDocs(effectBehaviors) {
11190
- if (!effectBehaviors) {
11191
- return [];
11475
+ function normalizeCreationSummary(metamodels) {
11476
+ if (!isObject(metamodels)) return null;
11477
+ let raw = metamodels.creates;
11478
+ if (typeof raw === "string" && raw.trim().length > 0) {
11479
+ const trimmed = raw.trim();
11480
+ if (trimmed.startsWith("{") || trimmed.startsWith('"')) {
11481
+ try {
11482
+ raw = JSON.parse(trimmed);
11483
+ } catch {
11484
+ return { className: trimmed };
11485
+ }
11486
+ } else {
11487
+ return { className: trimmed };
11488
+ }
11192
11489
  }
11490
+ if (typeof raw === "string" && raw.trim().length > 0) {
11491
+ return { className: raw.trim() };
11492
+ }
11493
+ if (!isObject(raw)) return null;
11494
+ const className = typeof raw.className === "string" && raw.className.trim() ? raw.className.trim() : "";
11495
+ if (!className) return null;
11496
+ return {
11497
+ className,
11498
+ ...typeof raw.idPath === "string" && raw.idPath.trim() ? { idPath: raw.idPath.trim() } : {},
11499
+ ...typeof raw.pathPath === "string" && raw.pathPath.trim() ? { pathPath: raw.pathPath.trim() } : {},
11500
+ ...typeof raw.statePath === "string" && raw.statePath.trim() ? { statePath: raw.statePath.trim() } : {},
11501
+ ...typeof raw.classStateHandle === "boolean" ? { classStateHandle: raw.classStateHandle } : {}
11502
+ };
11503
+ }
11504
+ function buildEffectBehaviorDocs(effectBehaviors, creates) {
11193
11505
  const docs = [];
11194
- if (effectBehaviors.approvalRequired?.required) {
11506
+ if (creates) {
11507
+ docs.push(
11508
+ `Creation method: creates ${creates.className}. The agent may use generated class-level new-record action methods for this class.`
11509
+ );
11510
+ }
11511
+ if (effectBehaviors?.approvalRequired?.required) {
11195
11512
  docs.push(
11196
11513
  effectBehaviors.approvalRequired.reason ? `Approval required: ${effectBehaviors.approvalRequired.reason}.` : "Approval required before execution."
11197
11514
  );
11198
11515
  }
11199
- if (effectBehaviors.postCondition) {
11516
+ if (effectBehaviors?.postCondition) {
11200
11517
  docs.push(`Post-condition: ${effectBehaviors.postCondition.condition}.`);
11201
11518
  if (effectBehaviors.postCondition.description) {
11202
11519
  docs.push(effectBehaviors.postCondition.description);
11203
11520
  }
11204
11521
  }
11205
- if (effectBehaviors.dryRun?.enabled) {
11522
+ if (effectBehaviors?.dryRun?.enabled) {
11206
11523
  docs.push("Supports dry run.");
11207
11524
  if (effectBehaviors.dryRun.description) {
11208
11525
  docs.push(effectBehaviors.dryRun.description);
11209
11526
  }
11210
11527
  }
11211
- if (effectBehaviors.reverse) {
11528
+ if (effectBehaviors?.reverse) {
11212
11529
  if (effectBehaviors.reverse.handler) {
11213
11530
  docs.push(`Reverse handler: ${effectBehaviors.reverse.handler}.`);
11214
11531
  } else {
@@ -11267,13 +11584,21 @@ function buildEffectBehaviorMutations(toolPath, spec) {
11267
11584
  query: `mutation { at(path: ${JSON.stringify(toolPath)}) { set_approval_required(${args}) { kind } } }`
11268
11585
  });
11269
11586
  }
11587
+ if (spec.creates !== void 0) {
11588
+ mutations.push({
11589
+ label: `set creates on ${toolPath}`,
11590
+ query: `mutation { at(path: ${JSON.stringify(toolPath)}) { create_submodel(subpath: "creates", label: "creates") { set_string_value(value: ${JSON.stringify(
11591
+ JSON.stringify(spec.creates)
11592
+ )}) { done } } } }`
11593
+ });
11594
+ }
11270
11595
  return mutations;
11271
11596
  }
11272
11597
  function readMethodEffectBehaviors(rawMethod) {
11598
+ const metamodels = isObject(rawMethod.metamodels) ? rawMethod.metamodels : null;
11273
11599
  return {
11274
- effectBehaviors: normalizeEffectBehaviorSummary(
11275
- isObject(rawMethod.metamodels) ? rawMethod.metamodels : null
11276
- )
11600
+ effectBehaviors: normalizeEffectBehaviorSummary(metamodels),
11601
+ creates: normalizeCreationSummary(metamodels)
11277
11602
  };
11278
11603
  }
11279
11604
  var effectBehaviorsMetamodelPackage = defineMetamodelPackage({
@@ -11295,6 +11620,10 @@ var effectBehaviorsMetamodelPackage = defineMetamodelPackage({
11295
11620
  {
11296
11621
  key: "approvalRequired",
11297
11622
  description: "Boolean or `{ required, reason, mode }`."
11623
+ },
11624
+ {
11625
+ key: "creates",
11626
+ description: 'Marks a static method as an allowed creator for a class. Use `creates: "class_name"` or `{ className, idPath, pathPath, statePath, classStateHandle }`.'
11298
11627
  }
11299
11628
  ]
11300
11629
  },
@@ -11414,7 +11743,10 @@ var effectBehaviorsMetamodelPackage = defineMetamodelPackage({
11414
11743
  ...methodIR,
11415
11744
  docs: [
11416
11745
  ...methodIR.docs,
11417
- ...buildEffectBehaviorDocs(methodSummary.effectBehaviors)
11746
+ ...buildEffectBehaviorDocs(
11747
+ methodSummary.effectBehaviors,
11748
+ methodSummary.creates
11749
+ )
11418
11750
  ]
11419
11751
  };
11420
11752
  }
@@ -11655,15 +11987,50 @@ function toRecordSearchResult(className, node) {
11655
11987
  return [];
11656
11988
  }
11657
11989
  ) : [];
11990
+ const graphPathId = extractRecordIdFromGraphPath(path, className);
11991
+ const realIdField = fields.find(
11992
+ (field) => normalizeGraphPathSegment(field.name) === "real_id" && typeof field.value === "string" && field.value.trim()
11993
+ );
11994
+ const id = typeof realIdField?.value === "string" ? realIdField.value.trim() : graphPathId;
11995
+ const rawLabel = typeof node.label === "string" && node.label.trim() ? node.label : "";
11996
+ if (fields.length === 0 && rawLabel && isPlaceholderRecordLabel(rawLabel, graphPathId, path)) {
11997
+ return null;
11998
+ }
11999
+ const fallbackLabel = displayLabelFromFields(fields);
12000
+ const label = rawLabel && !isPlaceholderRecordLabel(rawLabel, id, path) ? rawLabel : fallbackLabel || rawLabel || id;
11658
12001
  return {
11659
12002
  path,
11660
12003
  className,
11661
- id: extractRecordIdFromGraphPath(path, className),
11662
- label: typeof node.label === "string" && node.label.trim() ? node.label : extractRecordIdFromGraphPath(path, className),
12004
+ id,
12005
+ label,
11663
12006
  description: typeof node.description === "string" && node.description.trim() ? node.description : null,
11664
12007
  fields
11665
12008
  };
11666
12009
  }
12010
+ function isPlaceholderRecordLabel(label, id, path) {
12011
+ const normalizedLabel = normalizeGraphPathSegment(label);
12012
+ return normalizedLabel === normalizeGraphPathSegment(id) || normalizedLabel === normalizeGraphPathSegment(path);
12013
+ }
12014
+ function displayLabelFromFields(fields) {
12015
+ const preferredFieldNames = [
12016
+ "name",
12017
+ "title",
12018
+ "label",
12019
+ "display_name",
12020
+ "file_name",
12021
+ "number",
12022
+ "code"
12023
+ ];
12024
+ for (const preferred of preferredFieldNames) {
12025
+ const match = fields.find(
12026
+ (field) => normalizeGraphPathSegment(field.name) === preferred && typeof field.value === "string" && field.value.trim()
12027
+ );
12028
+ if (typeof match?.value === "string") {
12029
+ return match.value.trim();
12030
+ }
12031
+ }
12032
+ return null;
12033
+ }
11667
12034
  function normalizeRecordSearchText(value) {
11668
12035
  return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, " ").replace(/\s+/g, " ").trim();
11669
12036
  }
@@ -12563,15 +12930,47 @@ var searchableMetamodelPackage = defineMetamodelPackage({
12563
12930
 
12564
12931
  // ../metamodel-state-machine/src/index.ts
12565
12932
  function normalizeStateMachines(values) {
12933
+ const parseJsonRecord = (value) => {
12934
+ if (value && typeof value === "object" && !Array.isArray(value)) {
12935
+ return value;
12936
+ }
12937
+ if (typeof value !== "string" || !value.trim()) return null;
12938
+ try {
12939
+ const parsed = JSON.parse(value);
12940
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
12941
+ } catch {
12942
+ return null;
12943
+ }
12944
+ };
12945
+ const parseJsonValue = (value) => {
12946
+ if (value === null || typeof value === "undefined") return null;
12947
+ if (typeof value !== "string") return value;
12948
+ if (!value.trim()) return null;
12949
+ try {
12950
+ return JSON.parse(value);
12951
+ } catch {
12952
+ return value;
12953
+ }
12954
+ };
12566
12955
  return (values || []).map((machine) => {
12567
12956
  const states = (machine?.states || []).map((state) => ({
12568
12957
  name: String(state?.name || ""),
12569
- isFinal: Boolean(state?.is_final)
12958
+ label: typeof state?.label === "string" ? state.label : null,
12959
+ description: typeof state?.description === "string" ? state.description : null,
12960
+ isFinal: Boolean(state?.is_final ?? state?.isFinal)
12570
12961
  })).filter((state) => state.name.length > 0);
12571
12962
  const transitions = (machine?.transitions || []).map((transition) => ({
12572
12963
  name: String(transition?.name || ""),
12573
12964
  from: String(transition?.from?.name || ""),
12574
- to: String(transition?.to?.name || "")
12965
+ to: String(transition?.to?.name || ""),
12966
+ label: typeof transition?.label === "string" ? transition.label : null,
12967
+ description: typeof transition?.description === "string" ? transition.description : null,
12968
+ action: parseJsonRecord(transition?.action) || parseJsonRecord(transition?.action_json),
12969
+ assignee: parseJsonRecord(transition?.assignee) || parseJsonRecord(transition?.assignee_json),
12970
+ requirements: parseJsonRecord(transition?.requirements) || parseJsonRecord(transition?.requirements_json),
12971
+ permission: parseJsonValue(transition?.permission) ?? parseJsonValue(transition?.permission_json),
12972
+ risk: transition?.risk === "low" || transition?.risk === "medium" || transition?.risk === "high" ? transition.risk : null,
12973
+ expectedOutcome: parseJsonValue(transition?.expectedOutcome) ?? parseJsonValue(transition?.expected_outcome_json)
12575
12974
  })).filter(
12576
12975
  (transition) => transition.name.length > 0 && transition.from.length > 0 && transition.to.length > 0
12577
12976
  );
@@ -12585,7 +12984,7 @@ function normalizeStateMachines(values) {
12585
12984
  }).filter((machine) => machine.name.length > 0);
12586
12985
  }
12587
12986
  function stateTypeName(className, machineName) {
12588
- return `${toPascalCase(className)}${toPascalCase(machineName)}`;
12987
+ return `${toPascalCase2(className)}${toPascalCase2(machineName)}`;
12589
12988
  }
12590
12989
  function transitionTypeName(className, machineName) {
12591
12990
  return `${stateTypeName(className, machineName)}Transition`;
@@ -12593,6 +12992,15 @@ function transitionTypeName(className, machineName) {
12593
12992
  function pathTypeName(className, machineName) {
12594
12993
  return `${stateTypeName(className, machineName)}Path`;
12595
12994
  }
12995
+ function methodToken(value) {
12996
+ const token = String(value || "").trim().replace(/[^A-Za-z0-9_]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
12997
+ return token || "state";
12998
+ }
12999
+ function transitionActionsForMachine(machine) {
13000
+ return Object.fromEntries(
13001
+ (machine.transitions || []).filter((transition) => transition.action?.effect).map((transition) => [transition.name, transition.action])
13002
+ );
13003
+ }
12596
13004
  function normalizeStateDefinitions(machine) {
12597
13005
  const finalStates = new Set(machine.finalStates || []);
12598
13006
  const states = /* @__PURE__ */ new Map();
@@ -12606,6 +13014,8 @@ function normalizeStateDefinitions(machine) {
12606
13014
  }
12607
13015
  states.set(rawState.name, {
12608
13016
  name: rawState.name,
13017
+ label: rawState.label,
13018
+ description: rawState.description,
12609
13019
  isFinal: Boolean(rawState.isFinal) || finalStates.has(rawState.name)
12610
13020
  });
12611
13021
  }
@@ -12617,6 +13027,44 @@ function normalizeStateDefinitions(machine) {
12617
13027
  }
12618
13028
  return [...states.values()];
12619
13029
  }
13030
+ function transitionMetadataGraphqlArgs(transition) {
13031
+ const args = [];
13032
+ if (typeof transition.label === "string") {
13033
+ args.push(`label: ${JSON.stringify(transition.label)}`);
13034
+ }
13035
+ if (typeof transition.description === "string") {
13036
+ args.push(`description: ${JSON.stringify(transition.description)}`);
13037
+ }
13038
+ if (transition.action) {
13039
+ args.push(
13040
+ `action_json: ${JSON.stringify(JSON.stringify(transition.action))}`
13041
+ );
13042
+ }
13043
+ if (transition.assignee) {
13044
+ args.push(
13045
+ `assignee_json: ${JSON.stringify(JSON.stringify(transition.assignee))}`
13046
+ );
13047
+ }
13048
+ if (transition.requirements) {
13049
+ args.push(
13050
+ `requirements_json: ${JSON.stringify(JSON.stringify(transition.requirements))}`
13051
+ );
13052
+ }
13053
+ if (transition.permission) {
13054
+ args.push(
13055
+ `permission_json: ${JSON.stringify(JSON.stringify(transition.permission))}`
13056
+ );
13057
+ }
13058
+ if (transition.risk) {
13059
+ args.push(`risk: ${JSON.stringify(transition.risk)}`);
13060
+ }
13061
+ if (transition.expectedOutcome) {
13062
+ args.push(
13063
+ `expected_outcome_json: ${JSON.stringify(JSON.stringify(transition.expectedOutcome))}`
13064
+ );
13065
+ }
13066
+ return args.length > 0 ? `, ${args.join(", ")}` : "";
13067
+ }
12620
13068
  function buildStateMachineModelMutations(modelPath, machines) {
12621
13069
  const mutations = [];
12622
13070
  for (const machine of machines || []) {
@@ -12627,12 +13075,13 @@ function buildStateMachineModelMutations(modelPath, machines) {
12627
13075
  )}, entry_state: ${JSON.stringify(machine.entryState)}) { name } } }`
12628
13076
  });
12629
13077
  for (const state of normalizeStateDefinitions(machine)) {
12630
- if (state.name === machine.entryState && !state.isFinal) continue;
13078
+ if (state.name === machine.entryState && !state.isFinal && !state.label && !state.description)
13079
+ continue;
12631
13080
  mutations.push({
12632
13081
  label: `add state ${state.name} on ${modelPath}.${machine.name}`,
12633
13082
  query: `mutation { at(path: ${JSON.stringify(modelPath)}) { state_machine(name: ${JSON.stringify(
12634
13083
  machine.name
12635
- )}) { add_state(name: ${JSON.stringify(state.name)}, is_final: ${state.isFinal}) { name } } } }`
13084
+ )}) { add_state(name: ${JSON.stringify(state.name)}, is_final: ${state.isFinal}, label: ${JSON.stringify(state.label || null)}, description: ${JSON.stringify(state.description || null)}) { name } } } }`
12636
13085
  });
12637
13086
  }
12638
13087
  for (const transition of machine.transitions || []) {
@@ -12644,7 +13093,7 @@ function buildStateMachineModelMutations(modelPath, machines) {
12644
13093
  transition.name
12645
13094
  )}, from: ${JSON.stringify(transition.from)}, to: ${JSON.stringify(
12646
13095
  transition.to
12647
- )}) { name } } } }`
13096
+ )}${transitionMetadataGraphqlArgs(transition)}) { name } } } }`
12648
13097
  });
12649
13098
  }
12650
13099
  }
@@ -12671,7 +13120,7 @@ function buildMachineMethods(classSummary, machine) {
12671
13120
  const transitionName = transitionTypeName(classSummary.name, machine.name);
12672
13121
  pathTypeName(classSummary.name, machine.name);
12673
13122
  const docsPrefix = `${classSummary.name}.${machine.name}`;
12674
- return [
13123
+ const methods = [
12675
13124
  {
12676
13125
  name: `get_${machine.name}`,
12677
13126
  docs: [`Get the current ${docsPrefix} state.`],
@@ -12694,7 +13143,7 @@ function buildMachineMethods(classSummary, machine) {
12694
13143
  ],
12695
13144
  static: false,
12696
13145
  params: [{ name: "target", type: stateName }],
12697
- returnType: `Promise<${toPascalCase(classSummary.name)}>`,
13146
+ returnType: `Promise<${toPascalCase2(classSummary.name)}>`,
12698
13147
  runtime: {
12699
13148
  kind: "state_machine",
12700
13149
  machineName: machine.name,
@@ -12767,6 +13216,99 @@ function buildMachineMethods(classSummary, machine) {
12767
13216
  }
12768
13217
  }
12769
13218
  ];
13219
+ const creationMethods = (classSummary.methods || []).filter(
13220
+ (method) => method.static === true && Boolean(method.creates) && method.creates?.className === classSummary.name && typeof method.effectKey === "string" && method.effectKey.length > 0
13221
+ );
13222
+ for (const state of machine.states) {
13223
+ const stateNameValue = typeof state === "string" ? state : String(state?.name || "");
13224
+ if (!stateNameValue) continue;
13225
+ const token = methodToken(stateNameValue);
13226
+ methods.push(
13227
+ {
13228
+ name: `reach_${machine.name}_to_${token}`,
13229
+ docs: [`Reach ${docsPrefix} state ${stateNameValue}.`],
13230
+ static: false,
13231
+ params: [],
13232
+ returnType: `Promise<${toPascalCase2(classSummary.name)}>`,
13233
+ runtime: {
13234
+ kind: "state_machine",
13235
+ machineName: machine.name,
13236
+ className: classSummary.name,
13237
+ stateTypeName: stateName,
13238
+ transitionTypeName: transitionName,
13239
+ operation: "reach",
13240
+ targetState: stateNameValue,
13241
+ transitionActions: transitionActionsForMachine(machine)
13242
+ }
13243
+ },
13244
+ {
13245
+ name: `prepare_${machine.name}_to_${token}`,
13246
+ docs: [
13247
+ `Prepare a reviewable artifact that can move ${docsPrefix} to ${stateNameValue}.`
13248
+ ],
13249
+ static: false,
13250
+ params: [],
13251
+ returnType: "Promise<SessionArtifactRecord>",
13252
+ runtime: {
13253
+ kind: "state_machine",
13254
+ machineName: machine.name,
13255
+ className: classSummary.name,
13256
+ stateTypeName: stateName,
13257
+ transitionTypeName: transitionName,
13258
+ operation: "prepare_reach",
13259
+ targetState: stateNameValue,
13260
+ transitionActions: transitionActionsForMachine(machine)
13261
+ }
13262
+ }
13263
+ );
13264
+ for (const creationMethod of creationMethods) {
13265
+ const creationRuntime = {
13266
+ kind: "state_machine",
13267
+ machineName: machine.name,
13268
+ className: classSummary.name,
13269
+ stateTypeName: stateName,
13270
+ transitionTypeName: transitionName,
13271
+ operation: "prepare_create_reach",
13272
+ targetState: stateNameValue,
13273
+ transitionActions: transitionActionsForMachine(machine),
13274
+ creation: {
13275
+ methodName: creationMethod.name,
13276
+ effectKey: creationMethod.effectKey || creationMethod.name,
13277
+ inputSchema: creationMethod.inputSchema,
13278
+ outputSchema: creationMethod.outputSchema,
13279
+ creates: creationMethod.creates
13280
+ }
13281
+ };
13282
+ const viaName = `prepare_${machine.name}_to_${token}_via_${methodToken(creationMethod.name)}`;
13283
+ methods.push({
13284
+ name: viaName,
13285
+ docs: [
13286
+ `Prepare a reviewable artifact that will create a new ${classSummary.name} through ${creationMethod.name}, then move ${docsPrefix} to ${stateNameValue}.`
13287
+ ],
13288
+ static: true,
13289
+ params: [
13290
+ { name: "input", type: "Record<string, any>", optional: true }
13291
+ ],
13292
+ returnType: "Promise<SessionArtifactRecord>",
13293
+ runtime: creationRuntime
13294
+ });
13295
+ if (creationMethods.length === 1) {
13296
+ methods.push({
13297
+ name: `prepare_${machine.name}_to_${token}`,
13298
+ docs: [
13299
+ `Prepare a reviewable artifact that will create a new ${classSummary.name}, then move ${docsPrefix} to ${stateNameValue}.`
13300
+ ],
13301
+ static: true,
13302
+ params: [
13303
+ { name: "input", type: "Record<string, any>", optional: true }
13304
+ ],
13305
+ returnType: "Promise<SessionArtifactRecord>",
13306
+ runtime: creationRuntime
13307
+ });
13308
+ }
13309
+ }
13310
+ }
13311
+ return methods;
12770
13312
  }
12771
13313
  function readStateMachineSummaries(rawClass) {
12772
13314
  return {
@@ -12789,8 +13331,8 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12789
13331
  type StateMachineMutation {
12790
13332
  name: String!
12791
13333
  state_machine: StateMachine!
12792
- add_state(name: String!, is_final: Boolean): StateMachineMutation!
12793
- add_transition(name: String!, from: String!, to: String!): StateMachineMutation!
13334
+ add_state(name: String!, is_final: Boolean, label: String, description: String): StateMachineMutation!
13335
+ 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!
12794
13336
  activate_transition(name: String!): StateMachineMutation!
12795
13337
  }
12796
13338
 
@@ -12807,6 +13349,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12807
13349
  type StateMachineSnapshotMutation {
12808
13350
  snapshot: StateMachineSnapshot!
12809
13351
  activate_transition(name: String!): StateMachineSnapshotMutation!
13352
+ observe_state(state: String!, force: Boolean, source: String): StateMachineSnapshotMutation!
12810
13353
  }
12811
13354
 
12812
13355
  type StateMachine {
@@ -12826,6 +13369,8 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12826
13369
 
12827
13370
  type StateMachineState {
12828
13371
  name: String!
13372
+ label: String
13373
+ description: String
12829
13374
  is_final: Boolean!
12830
13375
  }
12831
13376
 
@@ -12833,6 +13378,14 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12833
13378
  name: String!
12834
13379
  from: StateMachineState!
12835
13380
  to: StateMachineState!
13381
+ label: String
13382
+ description: String
13383
+ action_json: String
13384
+ assignee_json: String
13385
+ requirements_json: String
13386
+ permission_json: String
13387
+ risk: String
13388
+ expected_outcome_json: String
12836
13389
  }
12837
13390
 
12838
13391
  type StateMachinePath {
@@ -12885,23 +13438,47 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12885
13438
  StateMachineMutation: {
12886
13439
  name: (value) => value.name,
12887
13440
  state_machine: async (value) => await run(value.target.state_machine(value.name)),
12888
- add_state: async (value, { name, is_final }) => {
13441
+ add_state: async (value, { name, is_final, label, description }) => {
12889
13442
  await run(
12890
13443
  value.target.add_state_machine_state(
12891
13444
  value.name,
12892
13445
  name,
12893
- is_final ?? false
13446
+ is_final ?? false,
13447
+ label,
13448
+ description
12894
13449
  )
12895
13450
  );
12896
13451
  return value;
12897
13452
  },
12898
- add_transition: async (value, { name, from, to }) => {
13453
+ add_transition: async (value, {
13454
+ name,
13455
+ from,
13456
+ to,
13457
+ label,
13458
+ description,
13459
+ action_json,
13460
+ assignee_json,
13461
+ requirements_json,
13462
+ permission_json,
13463
+ risk,
13464
+ expected_outcome_json
13465
+ }) => {
12899
13466
  await run(
12900
13467
  value.target.add_state_machine_transition(
12901
13468
  value.name,
12902
13469
  name,
12903
13470
  from,
12904
- to
13471
+ to,
13472
+ {
13473
+ label,
13474
+ description,
13475
+ actionJson: action_json,
13476
+ assigneeJson: assignee_json,
13477
+ requirementsJson: requirements_json,
13478
+ permissionJson: permission_json,
13479
+ risk,
13480
+ expectedOutcomeJson: expected_outcome_json
13481
+ }
12905
13482
  )
12906
13483
  );
12907
13484
  return value;
@@ -12920,16 +13497,37 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12920
13497
  value.target.activate_state_machine_transition(value.name, name)
12921
13498
  );
12922
13499
  return value;
13500
+ },
13501
+ observe_state: async (value, { state, force, source }) => {
13502
+ await run(
13503
+ value.target.observe_state_machine_state(
13504
+ value.name,
13505
+ state,
13506
+ force === true,
13507
+ source
13508
+ )
13509
+ );
13510
+ return value;
12923
13511
  }
12924
13512
  },
12925
13513
  StateMachineState: {
12926
13514
  name: (value) => value.name,
13515
+ label: (value) => value.label || null,
13516
+ description: (value) => value.description || null,
12927
13517
  is_final: (value) => value.is_final
12928
13518
  },
12929
13519
  StateMachineTransition: {
12930
13520
  name: (value) => value.name,
12931
13521
  from: (value) => value.from_state || { name: value.from, is_final: false },
12932
- to: (value) => value.to_state || { name: value.to, is_final: false }
13522
+ to: (value) => value.to_state || { name: value.to, is_final: false },
13523
+ label: (value) => value.label || null,
13524
+ description: (value) => value.description || null,
13525
+ action_json: (value) => value.action_json || null,
13526
+ assignee_json: (value) => value.assignee_json || null,
13527
+ requirements_json: (value) => value.requirements_json || null,
13528
+ permission_json: (value) => value.permission_json || null,
13529
+ risk: (value) => value.risk || null,
13530
+ expected_outcome_json: (value) => value.expected_outcome_json || null
12933
13531
  },
12934
13532
  StateMachinePath: {
12935
13533
  states: (value) => value.states,
@@ -12996,6 +13594,14 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
12996
13594
  name
12997
13595
  from { name }
12998
13596
  to { name }
13597
+ label
13598
+ description
13599
+ action_json
13600
+ assignee_json
13601
+ requirements_json
13602
+ permission_json
13603
+ risk
13604
+ expected_outcome_json
12999
13605
  }
13000
13606
  }`
13001
13607
  ]
@@ -13027,6 +13633,104 @@ function describeRule(rule) {
13027
13633
  return `${rule.operator} ${String(rule.booleanValue)}`;
13028
13634
  return rule.operator;
13029
13635
  }
13636
+ function valueAsString(value) {
13637
+ if (typeof value === "string") return value;
13638
+ if (typeof value === "number" || typeof value === "boolean") {
13639
+ return String(value);
13640
+ }
13641
+ return "";
13642
+ }
13643
+ function valueAsNumber(value) {
13644
+ if (typeof value === "number") return value;
13645
+ if (typeof value === "string" && value.trim().length > 0) {
13646
+ const parsed = Number(value);
13647
+ return Number.isFinite(parsed) ? parsed : NaN;
13648
+ }
13649
+ return NaN;
13650
+ }
13651
+ function ruleStringValue(rule) {
13652
+ return rule.stringValue ?? rule.string_value ?? "";
13653
+ }
13654
+ function ruleNumberValue(rule) {
13655
+ return rule.numberValue ?? rule.number_value;
13656
+ }
13657
+ function ruleBooleanValue(rule) {
13658
+ return rule.booleanValue ?? rule.boolean_value;
13659
+ }
13660
+ function evaluateValidationRule(value, rule) {
13661
+ const operator = typeof rule.operator === "string" ? rule.operator : "";
13662
+ const stringValue2 = ruleStringValue(rule);
13663
+ const numberValue = ruleNumberValue(rule);
13664
+ const booleanValue = ruleBooleanValue(rule);
13665
+ let passed = true;
13666
+ switch (operator) {
13667
+ case "eq":
13668
+ if (numberValue !== void 0) {
13669
+ passed = valueAsNumber(value) === numberValue;
13670
+ } else if (booleanValue !== void 0) {
13671
+ passed = value === booleanValue;
13672
+ } else {
13673
+ passed = valueAsString(value) === stringValue2;
13674
+ }
13675
+ break;
13676
+ case "neq":
13677
+ if (numberValue !== void 0) {
13678
+ passed = valueAsNumber(value) !== numberValue;
13679
+ } else if (booleanValue !== void 0) {
13680
+ passed = value !== booleanValue;
13681
+ } else {
13682
+ passed = valueAsString(value) !== stringValue2;
13683
+ }
13684
+ break;
13685
+ case "gt":
13686
+ passed = valueAsNumber(value) > (numberValue ?? NaN);
13687
+ break;
13688
+ case "gte":
13689
+ passed = valueAsNumber(value) >= (numberValue ?? NaN);
13690
+ break;
13691
+ case "lt":
13692
+ passed = valueAsNumber(value) < (numberValue ?? NaN);
13693
+ break;
13694
+ case "lte":
13695
+ passed = valueAsNumber(value) <= (numberValue ?? NaN);
13696
+ break;
13697
+ case "true":
13698
+ passed = value === true;
13699
+ break;
13700
+ case "false":
13701
+ passed = value === false;
13702
+ break;
13703
+ case "regex":
13704
+ try {
13705
+ passed = new RegExp(stringValue2).test(valueAsString(value));
13706
+ } catch {
13707
+ passed = false;
13708
+ }
13709
+ break;
13710
+ case "contains":
13711
+ passed = valueAsString(value).includes(stringValue2);
13712
+ break;
13713
+ case "not_contains":
13714
+ passed = !valueAsString(value).includes(stringValue2);
13715
+ break;
13716
+ case "starts_with":
13717
+ passed = valueAsString(value).startsWith(stringValue2);
13718
+ break;
13719
+ case "ends_with":
13720
+ passed = valueAsString(value).endsWith(stringValue2);
13721
+ break;
13722
+ default:
13723
+ passed = true;
13724
+ }
13725
+ return {
13726
+ passed,
13727
+ operator,
13728
+ message: rule.message ?? null
13729
+ };
13730
+ }
13731
+ function validationRuleFailureMessage(path, rule) {
13732
+ return rule.message || `${path} failed ${rule.operator} validation`;
13733
+ }
13030
13734
  function normalizeRule(rule) {
13031
13735
  const operator = typeof rule?.operator === "string" ? rule.operator : "";
13032
13736
  if (operator.length === 0) return null;
@@ -13249,6 +13953,26 @@ var STANDARD_MODULES_OPERATIONS = [
13249
13953
  var BUILTIN_MODULES = {
13250
13954
  standard_modules: STANDARD_MODULES_OPERATIONS
13251
13955
  };
13956
+ function stateNameFromMethodName(methodName) {
13957
+ const raw = methodName.startsWith("to") ? methodName.slice(2) : methodName;
13958
+ return raw.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
13959
+ }
13960
+ function appendQueryOptions(searchParams, query) {
13961
+ for (const [key, value] of Object.entries(query || {})) {
13962
+ if (value === null || typeof value === "undefined" || value === "") {
13963
+ continue;
13964
+ }
13965
+ if (value instanceof Date) {
13966
+ searchParams.set(key, value.toISOString());
13967
+ continue;
13968
+ }
13969
+ if (Array.isArray(value)) {
13970
+ if (value.length > 0) searchParams.set(key, value.join(","));
13971
+ continue;
13972
+ }
13973
+ searchParams.set(key, String(value));
13974
+ }
13975
+ }
13252
13976
  var DEFAULT_DIRECT_RECORD_OBJECTS_REQUEST_BATCH_SIZE = 100;
13253
13977
  var MAX_RECORD_OBJECTS_CONCURRENCY = 16;
13254
13978
  var DEFAULT_DIRECT_RECORD_OBJECTS_RETRY_COUNT = 3;
@@ -13279,8 +14003,20 @@ function bodyInitFromSessionFileUpload(body) {
13279
14003
  return body;
13280
14004
  }
13281
14005
  var EFFECT_CATALOG_SYNC_TIMEOUT_MS = 12e4;
14006
+ var EFFECT_CATALOG_SYNC_BATCH_SIZE = 20;
13282
14007
  var EFFECT_CATALOG_SYNC_RETRY_COUNT = 3;
13283
14008
  var EFFECT_CATALOG_SYNC_RETRY_DELAY_MS = 1e3;
14009
+ function chunkItems(items, batchSize) {
14010
+ const chunks = [];
14011
+ for (let offset = 0; offset < items.length; offset += batchSize) {
14012
+ chunks.push(items.slice(offset, offset + batchSize));
14013
+ }
14014
+ return chunks;
14015
+ }
14016
+ function isUnsupportedEffectCatalogMutation(error) {
14017
+ const message = error instanceof Error ? error.message : String(error);
14018
+ 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");
14019
+ }
13284
14020
  function planRecordObjectsChunks(records, batchSize) {
13285
14021
  const total = records.length;
13286
14022
  const size = Math.max(1, Math.min(batchSize, total));
@@ -13292,6 +14028,23 @@ function planRecordObjectsChunks(records, batchSize) {
13292
14028
  }
13293
14029
  return plans;
13294
14030
  }
14031
+ function preserveRecordObjectRealId(record) {
14032
+ const realId = record.id.trim();
14033
+ if (!realId) {
14034
+ return record;
14035
+ }
14036
+ const fields = record.fields || {};
14037
+ if (typeof fields.real_id === "string" && fields.real_id.trim()) {
14038
+ return record;
14039
+ }
14040
+ return {
14041
+ ...record,
14042
+ fields: {
14043
+ ...fields,
14044
+ real_id: realId
14045
+ }
14046
+ };
14047
+ }
13295
14048
  function computeEffectKey2(effect) {
13296
14049
  const attachedClass = effect.className?.trim();
13297
14050
  if (!attachedClass) {
@@ -13529,11 +14282,105 @@ var Environment = class _Environment {
13529
14282
  getAwaitingCount: async () => this.getAwaitingRecordCount()
13530
14283
  };
13531
14284
  }
14285
+ /**
14286
+ * Mirror product-owned workflow state into Granular without making Granular
14287
+ * own the customer application's state machine.
14288
+ */
14289
+ async recordState(input) {
14290
+ const { machine, state, ...target } = input;
14291
+ if (!machine.trim()) {
14292
+ throw new Error("State update requires a machine name");
14293
+ }
14294
+ if (!state.trim()) {
14295
+ throw new Error("State update requires a state");
14296
+ }
14297
+ return this.recordObject({
14298
+ className: target.className,
14299
+ id: target.id,
14300
+ ...target.label ? { label: target.label } : {},
14301
+ ...target.fields ? { fields: target.fields } : {},
14302
+ ...target.relationships ? { relationships: target.relationships } : {},
14303
+ states: {
14304
+ [machine.trim()]: {
14305
+ state: state.trim(),
14306
+ ...target.source ? { source: target.source } : {},
14307
+ ...target.cause ? { cause: target.cause } : {},
14308
+ ...target.actorId ? { actorId: target.actorId } : {},
14309
+ ...target.observedAt !== void 0 ? { observedAt: target.observedAt } : {},
14310
+ ...target.force !== void 0 ? { force: target.force } : {},
14311
+ ...target.metadata ? { metadata: target.metadata } : {}
14312
+ }
14313
+ }
14314
+ });
14315
+ }
14316
+ /**
14317
+ * Mirror product-owned workflow state into Granular without making Granular
14318
+ * own the customer application's state machine.
14319
+ *
14320
+ * Example:
14321
+ * `await env.recordState({ className: "spend_request", id, machine: "lifecycle", state: "policy_review", source: "customer_backend" })`
14322
+ */
14323
+ state(target) {
14324
+ const observe = async (machineName, stateName, input = {}) => {
14325
+ const observedState = input.observedState || input.state || stateName;
14326
+ if (!observedState) {
14327
+ throw new Error("State observation requires a target state");
14328
+ }
14329
+ return this.recordState({
14330
+ ...target,
14331
+ machine: machineName,
14332
+ state: observedState,
14333
+ ...input.source ? { source: input.source } : {},
14334
+ ...input.cause ? { cause: input.cause } : {},
14335
+ ...input.actorId ? { actorId: input.actorId } : {},
14336
+ ...input.observedAt !== void 0 ? { observedAt: input.observedAt } : {},
14337
+ ...input.force !== void 0 ? { force: input.force } : {},
14338
+ ...input.metadata ? { metadata: input.metadata } : {}
14339
+ });
14340
+ };
14341
+ return new Proxy(
14342
+ {},
14343
+ {
14344
+ get: (_target, machineProperty) => {
14345
+ if (typeof machineProperty !== "string") return void 0;
14346
+ return new Proxy(
14347
+ {},
14348
+ {
14349
+ get: (_machineTarget, stateProperty) => {
14350
+ if (stateProperty === "to") {
14351
+ return (stateName, input) => observe(machineProperty, stateName, input || {});
14352
+ }
14353
+ if (typeof stateProperty !== "string") return void 0;
14354
+ return (input) => observe(
14355
+ machineProperty,
14356
+ stateNameFromMethodName(stateProperty),
14357
+ input || {}
14358
+ );
14359
+ }
14360
+ }
14361
+ );
14362
+ }
14363
+ }
14364
+ );
14365
+ }
13532
14366
  get feedback() {
13533
14367
  return {
13534
14368
  list: async () => this.listFeedback()
13535
14369
  };
13536
14370
  }
14371
+ get manualActions() {
14372
+ return {
14373
+ record: (input) => this.recordManualAction(input),
14374
+ list: (options = {}) => this.listManualActions(options),
14375
+ suggest: (options = {}) => this.suggestManualActions(options)
14376
+ };
14377
+ }
14378
+ get artifactApprovals() {
14379
+ return {
14380
+ list: (options = {}) => this.listArtifactApprovals(options),
14381
+ decide: (approvalTaskId, input) => this.decideArtifactApproval(approvalTaskId, input)
14382
+ };
14383
+ }
13537
14384
  /**
13538
14385
  * Sessionless environments do not own a live transport, so disconnecting the
13539
14386
  * environment handle itself is a no-op. This keeps the public surface
@@ -13612,6 +14459,50 @@ var Environment = class _Environment {
13612
14459
  const response = await this.controlPlaneRequest(`/control/environments/${this.environmentId}/feedback`);
13613
14460
  return Array.isArray(response.items) ? response.items : [];
13614
14461
  }
14462
+ async recordManualAction(input) {
14463
+ const body = {
14464
+ ...input,
14465
+ ...input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {}
14466
+ };
14467
+ return this.controlPlaneRequest(
14468
+ `/control/environments/${this.environmentId}/manual-actions`,
14469
+ {
14470
+ method: "POST",
14471
+ body: JSON.stringify(body)
14472
+ }
14473
+ );
14474
+ }
14475
+ async listManualActions(options = {}) {
14476
+ const query = new URLSearchParams();
14477
+ appendQueryOptions(query, options);
14478
+ const suffix = query.toString() ? `?${query.toString()}` : "";
14479
+ return this.controlPlaneRequest(`/control/environments/${this.environmentId}/manual-actions${suffix}`);
14480
+ }
14481
+ async suggestManualActions(options = {}) {
14482
+ const query = new URLSearchParams();
14483
+ appendQueryOptions(query, options);
14484
+ const suffix = query.toString() ? `?${query.toString()}` : "";
14485
+ return this.controlPlaneRequest(
14486
+ `/control/environments/${this.environmentId}/manual-actions/suggestions${suffix}`
14487
+ );
14488
+ }
14489
+ async listArtifactApprovals(options = {}) {
14490
+ const query = new URLSearchParams();
14491
+ appendQueryOptions(query, options);
14492
+ const suffix = query.toString() ? `?${query.toString()}` : "";
14493
+ return this.controlPlaneRequest(
14494
+ `/control/environments/${this.environmentId}/artifact-approvals${suffix}`
14495
+ );
14496
+ }
14497
+ async decideArtifactApproval(approvalTaskId, input) {
14498
+ return this.controlPlaneRequest(
14499
+ `/control/environments/${this.environmentId}/artifact-approvals/${encodeURIComponent(approvalTaskId)}/decide`,
14500
+ {
14501
+ method: "POST",
14502
+ body: JSON.stringify(input)
14503
+ }
14504
+ );
14505
+ }
13615
14506
  getRuntimeBaseUrl() {
13616
14507
  return deriveRuntimeBaseUrl(this._apiEndpoint);
13617
14508
  }
@@ -14436,10 +15327,11 @@ var Environment = class _Environment {
14436
15327
  if (!Array.isArray(records) || records.length === 0) {
14437
15328
  return [];
14438
15329
  }
15330
+ const recordsToWrite = records.map(preserveRecordObjectRealId);
14439
15331
  const batchSize = Math.max(
14440
15332
  1,
14441
15333
  Math.min(
14442
- records.length,
15334
+ recordsToWrite.length,
14443
15335
  options?.batchSize ?? DEFAULT_DIRECT_RECORD_OBJECTS_REQUEST_BATCH_SIZE
14444
15336
  )
14445
15337
  );
@@ -14447,8 +15339,8 @@ var Environment = class _Environment {
14447
15339
  MAX_RECORD_OBJECTS_CONCURRENCY,
14448
15340
  Math.max(1, options?.concurrency ?? 1)
14449
15341
  );
14450
- const plans = planRecordObjectsChunks(records, batchSize);
14451
- const total = records.length;
15342
+ const plans = planRecordObjectsChunks(recordsToWrite, batchSize);
15343
+ const total = recordsToWrite.length;
14452
15344
  const results = new Array(total);
14453
15345
  const onChunk = options?.onChunkComplete;
14454
15346
  for (let waveStart = 0; waveStart < plans.length; waveStart += concurrency) {
@@ -14519,12 +15411,13 @@ var Environment = class _Environment {
14519
15411
  * synchronous upserts and fine-grained chunk progress via **`onChunkComplete`**.
14520
15412
  */
14521
15413
  async enqueueRecordImport(records, options = {}) {
15414
+ const recordsToImport = records.map(preserveRecordObjectRealId);
14522
15415
  return this.controlPlaneRequest(
14523
15416
  `/control/environments/${this.environmentId}/record-imports`,
14524
15417
  {
14525
15418
  method: "POST",
14526
15419
  body: JSON.stringify({
14527
- records,
15420
+ records: recordsToImport,
14528
15421
  batchSize: options.batchSize,
14529
15422
  setupRunId: options.setupRunId,
14530
15423
  writeMode: options.writeMode
@@ -14632,11 +15525,7 @@ var EnvironmentSession = class extends Session {
14632
15525
  }
14633
15526
  buildSessionDataUrl(path, query) {
14634
15527
  const searchParams = new URLSearchParams();
14635
- for (const [key, value] of Object.entries(query || {})) {
14636
- if (value !== null && typeof value !== "undefined" && value !== "") {
14637
- searchParams.set(key, String(value));
14638
- }
14639
- }
15528
+ appendQueryOptions(searchParams, query);
14640
15529
  const queryString = searchParams.toString();
14641
15530
  return `${this.environment.runtimeBaseUrl}${this.sessionDataRoutePrefix}/${encodeURIComponent(this.sessionId)}${path}${queryString ? `?${queryString}` : ""}`;
14642
15531
  }
@@ -14719,9 +15608,108 @@ var EnvironmentSession = class extends Session {
14719
15608
  ),
14720
15609
  get: (jobId) => this.sessionDataRequest(
14721
15610
  `/jobs/${encodeURIComponent(jobId)}`
15611
+ ),
15612
+ latest: async (options = {}) => {
15613
+ const page = await this.sessionDataRequest("/jobs", {
15614
+ status: options.status || "all",
15615
+ latest: true,
15616
+ limit: 1
15617
+ });
15618
+ return page.items[0] || null;
15619
+ }
15620
+ };
15621
+ }
15622
+ get artifacts() {
15623
+ return {
15624
+ list: (options = {}) => {
15625
+ const queryOptions = { ...options };
15626
+ if (options.target) {
15627
+ queryOptions.targetClassName = options.target.className;
15628
+ queryOptions.targetId = options.target.id;
15629
+ delete queryOptions.target;
15630
+ }
15631
+ return this.sessionDataRequest("/artifacts", queryOptions);
15632
+ },
15633
+ listForLatestJob: (options = {}) => this.artifacts.list({
15634
+ ...options,
15635
+ latestJob: true
15636
+ }),
15637
+ get: (artifactId) => this.sessionDataRequest(
15638
+ `/artifacts/${encodeURIComponent(artifactId)}`
15639
+ ),
15640
+ create: (artifact) => this.sessionDataRequest(
15641
+ "/artifacts",
15642
+ void 0,
15643
+ {
15644
+ method: "POST",
15645
+ body: artifact
15646
+ }
15647
+ ),
15648
+ updateInputs: (artifactId, patch) => this.sessionDataRequest(
15649
+ `/artifacts/${encodeURIComponent(artifactId)}`,
15650
+ void 0,
15651
+ {
15652
+ method: "PATCH",
15653
+ body: patch
15654
+ }
15655
+ ),
15656
+ validate: (artifactId) => this.sessionDataRequest(
15657
+ `/artifacts/${encodeURIComponent(artifactId)}/validate`,
15658
+ void 0,
15659
+ { method: "POST" }
15660
+ ),
15661
+ execute: (artifactId, options) => this.sessionDataRequest(
15662
+ `/artifacts/${encodeURIComponent(artifactId)}/execute`,
15663
+ void 0,
15664
+ { method: "POST", body: options }
15665
+ ),
15666
+ approve: (artifactId, options) => this.sessionDataRequest(
15667
+ `/artifacts/${encodeURIComponent(artifactId)}/approve`,
15668
+ void 0,
15669
+ { method: "POST", body: options }
15670
+ ),
15671
+ cancel: (artifactId) => this.sessionDataRequest(
15672
+ `/artifacts/${encodeURIComponent(artifactId)}/cancel`,
15673
+ void 0,
15674
+ { method: "POST" }
14722
15675
  )
14723
15676
  };
14724
15677
  }
15678
+ get manualActions() {
15679
+ const useDelegatedBrowserRoute = this.sessionDataRoutePrefix === "/sdk/browser-sessions";
15680
+ return {
15681
+ record: (input) => useDelegatedBrowserRoute ? this.sessionDataRequest(
15682
+ "/manual-actions",
15683
+ void 0,
15684
+ {
15685
+ method: "POST",
15686
+ body: { ...input, sessionId: this.sessionId }
15687
+ }
15688
+ ) : this.environment.manualActions.record({
15689
+ ...input,
15690
+ sessionId: this.sessionId
15691
+ }),
15692
+ list: (options = {}) => useDelegatedBrowserRoute ? this.sessionDataRequest("/manual-actions", { ...options, sessionId: this.sessionId }) : this.environment.manualActions.list({
15693
+ ...options,
15694
+ sessionId: this.sessionId
15695
+ }),
15696
+ suggest: (options = {}) => useDelegatedBrowserRoute ? this.sessionDataRequest(
15697
+ "/manual-actions/suggestions",
15698
+ options
15699
+ ) : this.environment.manualActions.suggest(options)
15700
+ };
15701
+ }
15702
+ get artifactApprovals() {
15703
+ const useDelegatedBrowserRoute = this.sessionDataRoutePrefix === "/sdk/browser-sessions";
15704
+ return {
15705
+ list: (options = {}) => useDelegatedBrowserRoute ? this.sessionDataRequest("/artifact-approvals", options) : this.environment.artifactApprovals.list(options),
15706
+ decide: (approvalTaskId, input) => useDelegatedBrowserRoute ? this.sessionDataRequest(
15707
+ `/artifact-approvals/${encodeURIComponent(approvalTaskId)}/decide`,
15708
+ void 0,
15709
+ { method: "POST", body: input }
15710
+ ) : this.environment.artifactApprovals.decide(approvalTaskId, input)
15711
+ };
15712
+ }
14725
15713
  get files() {
14726
15714
  return {
14727
15715
  list: (options = {}) => this.sessionDataRequest(
@@ -14802,13 +15790,16 @@ var EnvironmentSession = class extends Session {
14802
15790
  get transcript() {
14803
15791
  return {
14804
15792
  list: async (options = {}) => {
14805
- const [messages, jobs, entries, lists] = await Promise.all([
15793
+ const [messages, jobs, entries, lists, artifacts] = await Promise.all([
14806
15794
  this.collectAllSessionItems(this.messages.list),
14807
15795
  this.collectAllSessionItems(
14808
15796
  (pageOptions) => this.jobs.list({ ...pageOptions, status: "all" })
14809
15797
  ),
14810
15798
  this.collectAllSessionItems(this.heap.entries.list),
14811
- this.collectAllSessionItems(this.heap.lists.list)
15799
+ this.collectAllSessionItems(this.heap.lists.list),
15800
+ this.collectAllSessionItems(
15801
+ (pageOptions) => this.artifacts.list({ ...pageOptions, status: "all" })
15802
+ )
14812
15803
  ]);
14813
15804
  const liveDoc = {
14814
15805
  conversation: { messages },
@@ -14822,6 +15813,21 @@ var EnvironmentSession = class extends Session {
14822
15813
  (entry) => Boolean(entry)
14823
15814
  )
14824
15815
  )
15816
+ },
15817
+ artifacts: {
15818
+ byId: Object.fromEntries(
15819
+ artifacts.map((artifact) => {
15820
+ return artifact?.artifactId ? [
15821
+ artifact.artifactId,
15822
+ artifact
15823
+ ] : null;
15824
+ }).filter(
15825
+ (entry) => Boolean(entry)
15826
+ )
15827
+ ),
15828
+ order: artifacts.map((artifact) => artifact?.artifactId).filter(
15829
+ (artifactId) => Boolean(artifactId)
15830
+ )
14825
15831
  }
14826
15832
  };
14827
15833
  const heap = normalizeHeapSnapshot({
@@ -14898,6 +15904,12 @@ var EnvironmentSession = class extends Session {
14898
15904
  async recordObject(options) {
14899
15905
  return this.environment.recordObject(options);
14900
15906
  }
15907
+ async recordState(input) {
15908
+ return this.environment.recordState(input);
15909
+ }
15910
+ state(target) {
15911
+ return this.environment.state(target);
15912
+ }
14901
15913
  async recordObjects(records, options) {
14902
15914
  return this.environment.recordObjects(records, options);
14903
15915
  }
@@ -15725,15 +16737,43 @@ var Granular = class _Granular {
15725
16737
  const effects = Array.from(
15726
16738
  this.getSandboxEffectMap(host.sandboxId).values()
15727
16739
  ).map((effect) => this.serializeEffect(effect));
15728
- const result = await withTimeout(
15729
- host.wsClient.call("effects.publishCatalog", {
15730
- effects
15731
- }),
15732
- EFFECT_CATALOG_SYNC_TIMEOUT_MS,
15733
- `effects.publishCatalog for sandbox ${host.sandboxId}`
15734
- );
15735
- const acceptedCount = typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
15736
- const rejected = Array.isArray(result?.rejected) ? result.rejected : [];
16740
+ let acceptedCount = 0;
16741
+ const rejected = [];
16742
+ try {
16743
+ await withTimeout(
16744
+ host.wsClient.call("effects.resetCatalog", {}),
16745
+ EFFECT_CATALOG_SYNC_TIMEOUT_MS,
16746
+ `effects.resetCatalog for sandbox ${host.sandboxId}`
16747
+ );
16748
+ for (const batch of chunkItems(effects, EFFECT_CATALOG_SYNC_BATCH_SIZE)) {
16749
+ const result = await withTimeout(
16750
+ host.wsClient.call("effects.addCatalog", {
16751
+ effects: batch
16752
+ }),
16753
+ EFFECT_CATALOG_SYNC_TIMEOUT_MS,
16754
+ `effects.addCatalog for sandbox ${host.sandboxId}`
16755
+ );
16756
+ acceptedCount += typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
16757
+ if (Array.isArray(result?.rejected)) {
16758
+ rejected.push(...result.rejected);
16759
+ }
16760
+ }
16761
+ } catch (error) {
16762
+ if (!isUnsupportedEffectCatalogMutation(error)) {
16763
+ throw error;
16764
+ }
16765
+ const result = await withTimeout(
16766
+ host.wsClient.call("effects.publishCatalog", {
16767
+ effects
16768
+ }),
16769
+ EFFECT_CATALOG_SYNC_TIMEOUT_MS,
16770
+ `effects.publishCatalog for sandbox ${host.sandboxId}`
16771
+ );
16772
+ acceptedCount = typeof result?.acceptedCount === "number" ? result.acceptedCount : 0;
16773
+ if (Array.isArray(result?.rejected)) {
16774
+ rejected.push(...result.rejected);
16775
+ }
16776
+ }
15737
16777
  if (acceptedCount === 0 && rejected.length > 0) {
15738
16778
  const detail = rejected.map(
15739
16779
  (entry) => `${entry.name || "unknown"}: ${entry.reason || "rejected"}`
@@ -16965,6 +18005,7 @@ var HARNESS_V3_FRONTEND_ACTIONS_MODULE = "@granular/actions/frontend";
16965
18005
  var HARNESS_V3_CSV_MODULE = "@granular/utils/csv";
16966
18006
  var HARNESS_V3_XLSX_MODULE = "@granular/utils/xlsx";
16967
18007
  var LEGACY_SANDBOX_TOOLS_MODULE_PATTERN = "\\.\\/sandbox-tools(?:\\.js)?";
18008
+ var HARNESS_V3_RUNTIME_MODULE_PATTERN = "@granular/(?:agent|session|domain(?:/[A-Za-z_$][\\w$]*)?|actions/(?:backend|frontend)|utils/(?:csv|xlsx))";
16968
18009
  function hasNamedModuleImport(source, moduleName, name) {
16969
18010
  const escapedModule = moduleName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
16970
18011
  const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -17023,6 +18064,15 @@ function reviewGeneratedJobCode(code, _options = {}) {
17023
18064
  message: "Generated code must use static top-level ESM imports from the Harness v3 runtime modules. Do not use dynamic import(...)."
17024
18065
  });
17025
18066
  }
18067
+ if (new RegExp(
18068
+ `import\\s+\\*\\s+as\\s+[A-Za-z_$][\\w$]*\\s+from\\s*['"]${HARNESS_V3_RUNTIME_MODULE_PATTERN}['"]`
18069
+ ).test(normalized)) {
18070
+ issues.push({
18071
+ code: "runtime_namespace_import",
18072
+ severity: "error",
18073
+ 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"`.'
18074
+ });
18075
+ }
17026
18076
  if (/\bprocess\.exit\s*\(/.test(normalized)) {
17027
18077
  issues.push({
17028
18078
  code: "process_exit",
@@ -18047,6 +19097,31 @@ function buildGranularAgentHeapBlock(heapSummary) {
18047
19097
  entries: {}
18048
19098
  });
18049
19099
  }
19100
+ function buildGranularAgentManualActionMemorySummary(input) {
19101
+ const maxItems = Math.max(1, Math.min(12, input.maxItems ?? 8));
19102
+ const suggestions = (input.suggestions || []).filter((suggestion) => suggestion?.actionKey).slice(0, maxItems).map((suggestion) => ({
19103
+ actionKey: suggestion.actionKey,
19104
+ label: suggestion.label || null,
19105
+ targetClassName: suggestion.targetClassName || null,
19106
+ count: typeof suggestion.count === "number" && Number.isFinite(suggestion.count) ? suggestion.count : null,
19107
+ subjectCount: typeof suggestion.subjectCount === "number" && Number.isFinite(suggestion.subjectCount) ? suggestion.subjectCount : null,
19108
+ successCount: typeof suggestion.successCount === "number" && Number.isFinite(suggestion.successCount) ? suggestion.successCount : null,
19109
+ failureCount: typeof suggestion.failureCount === "number" && Number.isFinite(suggestion.failureCount) ? suggestion.failureCount : null,
19110
+ lastOccurredAt: typeof suggestion.lastOccurredAt === "number" && Number.isFinite(suggestion.lastOccurredAt) ? suggestion.lastOccurredAt : null,
19111
+ sampleTargetIds: Array.isArray(suggestion.sampleTargetIds) ? suggestion.sampleTargetIds.filter(
19112
+ (id) => typeof id === "string" && id.trim().length > 0
19113
+ ).slice(0, 6) : []
19114
+ }));
19115
+ return [
19116
+ renderConstBlock("manualActionMemory", {
19117
+ suggestions
19118
+ }),
19119
+ "Use manualActionMemory only as behavioral context for likely next actions. Ground the current target and validate permissions before creating or running prepared actions."
19120
+ ].join("\n");
19121
+ }
19122
+ function buildGranularAgentManualActionBlock(manualActionSummary) {
19123
+ return manualActionSummary?.trim() || buildGranularAgentManualActionMemorySummary({ suggestions: [] });
19124
+ }
18050
19125
  function projectSessionFileSummary(liveDoc) {
18051
19126
  const files = asRecord4(liveDoc?.files);
18052
19127
  const byId = asRecord4(files?.byId) || {};
@@ -18078,8 +19153,12 @@ function buildGranularAgentFileBlock(fileSummary) {
18078
19153
  function extractRuntimeContractExports(domainBlock) {
18079
19154
  const classes = /* @__PURE__ */ new Set();
18080
19155
  const actions = /* @__PURE__ */ new Set();
18081
- const classPattern = /export\s+declare\s+(?:const|class)\s+([A-Za-z_$][\w$]*)/g;
18082
- for (const match of domainBlock.matchAll(classPattern)) {
19156
+ const classConstPattern = /export\s+declare\s+const\s+([A-Za-z_$][\w$]*)\s*:\s*EntityClass\b/g;
19157
+ for (const match of domainBlock.matchAll(classConstPattern)) {
19158
+ classes.add(match[1]);
19159
+ }
19160
+ const classDeclPattern = /export\s+declare\s+class\s+([A-Za-z_$][\w$]*)\b/g;
19161
+ for (const match of domainBlock.matchAll(classDeclPattern)) {
18083
19162
  classes.add(match[1]);
18084
19163
  }
18085
19164
  const actionPattern = /export\s+declare\s+function\s+([A-Za-z_$][\w$]*)/g;
@@ -18584,6 +19663,9 @@ function buildGranularAgentSystemPrompt(input) {
18584
19663
  });
18585
19664
  const referentBlock = buildGranularAgentReferentBlock(input.referentSummary);
18586
19665
  const loopBlock = buildGranularAgentLoopBlock(input.loopSummary);
19666
+ const manualActionBlock = buildGranularAgentManualActionBlock(
19667
+ input.manualActionSummary
19668
+ );
18587
19669
  const knownFactsBlock = renderConstBlock(
18588
19670
  "knownFacts",
18589
19671
  buildKnownFactsFromCheckpoint(input.checkpoint)
@@ -18597,16 +19679,15 @@ function buildGranularAgentSystemPrompt(input) {
18597
19679
  - \`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.
18598
19680
  - 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.
18599
19681
  - 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.
18600
- - 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.
18601
- - 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.
18602
- - 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"] })\`.
18603
- - \`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(...)\`.
18604
- - 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.
18605
- - 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.
19682
+ - 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.
19683
+ - 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"] })\`.
19684
+ - \`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(...)\`.
19685
+ - 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.
19686
+ - 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()\`.
19687
+ - 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.
18606
19688
  - 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.
18607
19689
  - 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.
18608
- - 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.
18609
- - 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.
19690
+ - 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.
18610
19691
  - 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\`.
18611
19692
  - \`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.
18612
19693
  - 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.
@@ -18617,7 +19698,7 @@ function buildGranularAgentSystemPrompt(input) {
18617
19698
  - When using code, assistant text must be empty or one brief summary.
18618
19699
  - Code must be plain runnable JavaScript with top-level await.
18619
19700
  - Use [Runtime Imports] as the authoritative module map. Import only listed module exports.
18620
- - 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.
19701
+ - 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.
18621
19702
  - 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.
18622
19703
  - 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.
18623
19704
  - 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\`.
@@ -18680,6 +19761,7 @@ ${workflowRules}
18680
19761
  High-priority execution rules:
18681
19762
  - 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.
18682
19763
  - 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.
19764
+ - 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.
18683
19765
  - 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.
18684
19766
  - 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.
18685
19767
  - 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.
@@ -18723,7 +19805,7 @@ Intent resolution:
18723
19805
  - 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.
18724
19806
  - 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.
18725
19807
  - 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.
18726
- - 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.
19808
+ - 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.
18727
19809
  - 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.
18728
19810
  - Never call \`.get({ path: "" })\`; an empty path is not a saved reference.
18729
19811
  - 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.
@@ -18850,20 +19932,10 @@ Ask the user when:
18850
19932
  - the target is unique but the requested action is unclear
18851
19933
 
18852
19934
  Relationship filters:
18853
- - One-record relationships use \`is\`.
18854
- - Multi-record relationships use \`some\`.
18855
- - 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.
18856
- - 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.
18857
- - 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.
18858
- - 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.
18859
- - Use \`some\` only when the generated TypeScript type says \`ManyRelationFilter\`.
18860
- - 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.
18861
- - Use \`{ relationship: { id: "record_id" } }\` or \`{ relationship: { path: "class_record_id" } }\` when matching a known related record.
18862
- - 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\`.
18863
- - 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.
18864
- - Use \`{ relationship: { is: { field: { equal_to: value } } } }\` only for nested field filters. Never put \`id\` or \`path\` inside \`is\`.
18865
- - 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.
18866
- - Do not pass a full record instance into a filter; if you already fetched a record, filter by its id or path instead.
19935
+ - Use the generated filter type as the authority: \`OneRelationFilter\` supports \`id\`, \`path\`, \`is\`, \`null\`, \`not_null\`; \`ManyRelationFilter\` supports those plus \`some\`.
19936
+ - Use \`id\` or \`path\` for a known related record; use \`is\` or \`some\` only for nested target-field filters.
19937
+ - 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.
19938
+ - Never pass a full record instance into a filter. Use its id/path or a declared relationship getter.
18867
19939
  ${domainSections.docs ? `
18868
19940
  Domain notes:
18869
19941
  ${domainSections.docs}
@@ -18874,6 +19946,23 @@ ${actionIndex}
18874
19946
  - 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.
18875
19947
  - 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(...)\`.
18876
19948
  - Actions listed under "Class-level" are class/static methods. Call them on the imported class, e.g. \`await Item.action_name(...)\`.
19949
+ - 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.
19950
+ - For pure field-collection requests, target the class-level entry state handle; for submit/review requests, target the nearest requested later state.
19951
+ - Choose the nearest target state that matches the user's words. Do not aim at a later state just because it is reachable.
19952
+ - 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\`.
19953
+ - 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.
19954
+ - 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.
19955
+ - 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()\`.
19956
+ - 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.
19957
+ - 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.
19958
+ - 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.
19959
+ - 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.
19960
+ - 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.
19961
+ - 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.
19962
+ - 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.
19963
+ - 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.
19964
+ - 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.
19965
+ - Use \`await prepared.show()\` or \`await actions.show(prepared)\` only to display an already-created prepared action again.
18877
19966
  - 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.
18878
19967
  - Never call a record-level action as \`Class.action_name(...)\`; that method will not exist.
18879
19968
  - 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.
@@ -18904,6 +19993,8 @@ ${loopBlock}
18904
19993
 
18905
19994
  ${knownFactsBlock}
18906
19995
 
19996
+ ${manualActionBlock}
19997
+
18907
19998
  [Request]
18908
19999
  ${input.request?.trim() || "Use the latest user message in the conversation."}`;
18909
20000
  }
@@ -19205,6 +20296,8 @@ exports.buildGranularAgentDomainBlock = buildGranularAgentDomainBlock;
19205
20296
  exports.buildGranularAgentFileBlock = buildGranularAgentFileBlock;
19206
20297
  exports.buildGranularAgentHeapBlock = buildGranularAgentHeapBlock;
19207
20298
  exports.buildGranularAgentLoopBlock = buildGranularAgentLoopBlock;
20299
+ exports.buildGranularAgentManualActionBlock = buildGranularAgentManualActionBlock;
20300
+ exports.buildGranularAgentManualActionMemorySummary = buildGranularAgentManualActionMemorySummary;
19208
20301
  exports.buildGranularAgentReferentBlock = buildGranularAgentReferentBlock;
19209
20302
  exports.buildGranularAgentRuntimeImportsBlock = buildGranularAgentRuntimeImportsBlock;
19210
20303
  exports.buildGranularAgentSessionBlock = buildGranularAgentSessionBlock;
@@ -19219,6 +20312,7 @@ exports.consumeGranularReasoningOnlyChunk = consumeGranularReasoningOnlyChunk;
19219
20312
  exports.consumeGranularReasoningTraceChunk = consumeGranularReasoningTraceChunk;
19220
20313
  exports.createHarnessVerifierSnapshot = createHarnessVerifierSnapshot;
19221
20314
  exports.evaluateContinuation = evaluateContinuation;
20315
+ exports.evaluateValidationRule = evaluateValidationRule;
19222
20316
  exports.extractPromptTokens = extractPromptTokens;
19223
20317
  exports.getCurrentClosureId = getCurrentClosureId;
19224
20318
  exports.getDefaultHarnessTemplateId = getDefaultHarnessTemplateId;
@@ -19255,5 +20349,6 @@ exports.scorePromptChoiceMatch = scorePromptChoiceMatch;
19255
20349
  exports.stripGranularReasoningTrace = stripGranularReasoningTrace;
19256
20350
  exports.toGranularHttpBase = toGranularHttpBase;
19257
20351
  exports.validateHarnessTemplateManifest = validateHarnessTemplateManifest;
20352
+ exports.validationRuleFailureMessage = validationRuleFailureMessage;
19258
20353
  //# sourceMappingURL=index.js.map
19259
20354
  //# sourceMappingURL=index.js.map