@granular-software/sdk 0.4.48 → 0.4.50

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