@granular-software/sdk 0.4.69 → 0.4.71

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.
@@ -5291,6 +5291,38 @@ function stableDocumentFingerprint(value) {
5291
5291
  (key) => `${JSON.stringify(key)}:${stableDocumentFingerprint(record[key])}`
5292
5292
  ).join(",")}}`;
5293
5293
  }
5294
+ function canonicalDocumentFingerprint(value) {
5295
+ const stripPresentation = (input, path2) => {
5296
+ if (input === null || typeof input !== "object") return input;
5297
+ if (input instanceof Date || input instanceof Uint8Array) return input;
5298
+ if (Array.isArray(input)) {
5299
+ return input.map((entry) => stripPresentation(entry, path2));
5300
+ }
5301
+ const record = input;
5302
+ const output = {};
5303
+ for (const [key, entry] of Object.entries(record)) {
5304
+ if (path2.length === 1 && path2[0] === "feed" && [
5305
+ "transientById",
5306
+ "feedbackUsageByJob",
5307
+ "revision",
5308
+ "lastTransientOrdinal"
5309
+ ].includes(key)) {
5310
+ continue;
5311
+ }
5312
+ if (path2.length === 3 && path2[0] === "jobs" && path2[1] === "byId" && [
5313
+ "actionTrace",
5314
+ "actionSummary",
5315
+ "actionSummaryText",
5316
+ "lastActionAt"
5317
+ ].includes(key)) {
5318
+ continue;
5319
+ }
5320
+ output[key] = stripPresentation(entry, [...path2, key]);
5321
+ }
5322
+ return output;
5323
+ };
5324
+ return stableDocumentFingerprint(stripPresentation(value, []));
5325
+ }
5294
5326
  function debugWs(...args) {
5295
5327
  if (DEBUG_WS) {
5296
5328
  console.log(...args);
@@ -5608,6 +5640,7 @@ var WSClient = class {
5608
5640
  }
5609
5641
  this.lastInboundActivityAt = Date.now();
5610
5642
  this.reconnectAttempts = 0;
5643
+ this.connectPromise = null;
5611
5644
  this.emit("open", {});
5612
5645
  finish();
5613
5646
  };
@@ -5770,7 +5803,7 @@ var WSClient = class {
5770
5803
  this.canonicalDocumentActivated = true;
5771
5804
  }
5772
5805
  }
5773
- canAcceptDocumentReplacement(replacement, strictlyNewer) {
5806
+ canAcceptDocumentReplacement(replacement, strictlyNewer, options = {}) {
5774
5807
  const replacementCanonical = isCanonicalSessionFeedDocument(replacement);
5775
5808
  const currentCanonical = isCanonicalSessionFeedDocument(this.doc);
5776
5809
  if (replacementCanonical) {
@@ -5819,7 +5852,7 @@ var WSClient = class {
5819
5852
  debugWs(`[Granular DEBUG] Rejected session snapshot: ${canonicalError}`);
5820
5853
  return false;
5821
5854
  }
5822
- if (this.canonicalDocumentActivated && incoming.epoch === current.epoch && incoming.revision === current.revision && !readSessionFeedSnapshot(this.doc).error && stableDocumentFingerprint(Automerge.toJS(replacement)) !== stableDocumentFingerprint(Automerge.toJS(this.doc))) {
5855
+ if (this.canonicalDocumentActivated && incoming.epoch === current.epoch && incoming.revision === current.revision && !readSessionFeedSnapshot(this.doc).error && (options.allowEqualRevisionPresentation ? canonicalDocumentFingerprint(Automerge.toJS(replacement)) : stableDocumentFingerprint(Automerge.toJS(replacement))) !== (options.allowEqualRevisionPresentation ? canonicalDocumentFingerprint(Automerge.toJS(this.doc)) : stableDocumentFingerprint(Automerge.toJS(this.doc)))) {
5823
5856
  debugWs(
5824
5857
  "[Granular DEBUG] Rejected divergent canonical snapshot at the accepted document version."
5825
5858
  );
@@ -5941,6 +5974,7 @@ var WSClient = class {
5941
5974
  reconnectScheduled: false
5942
5975
  };
5943
5976
  this.ws = null;
5977
+ this.connectPromise = null;
5944
5978
  this.emit("close", info);
5945
5979
  if (this.reconnectTimer) {
5946
5980
  clearTimeout(this.reconnectTimer);
@@ -5978,13 +6012,89 @@ var WSClient = class {
5978
6012
  );
5979
6013
  return;
5980
6014
  }
6015
+ if ("type" in message && message.type === "job_action_trace") {
6016
+ const update = message;
6017
+ if (typeof update.jobId !== "string" || update.jobId.length === 0 || !Number.isSafeInteger(update.documentEpoch) || update.documentEpoch < 0 || !Number.isSafeInteger(update.documentRevision) || update.documentRevision < 0 || !Array.isArray(update.actionTrace) || update.actionTrace.length > 128 || !update.actionTrace.every(
6018
+ (event) => event && typeof event === "object" && typeof event.id === "string" && typeof event.timestamp === "number" && Number.isFinite(event.timestamp)
6019
+ ) || !Array.isArray(update.actionSummary) || update.actionSummary.length > 16 || !update.actionSummary.every((line) => typeof line === "string") || typeof update.actionSummaryText !== "string" || update.actionSummaryText.length > 8e3) {
6020
+ console.warn("[Granular] Ignoring invalid job_action_trace message");
6021
+ return;
6022
+ }
6023
+ const currentVersion = documentVersion(this.doc);
6024
+ if (update.documentEpoch < currentVersion.epoch || update.documentEpoch === currentVersion.epoch && update.documentRevision < currentVersion.revision) {
6025
+ return;
6026
+ }
6027
+ if (update.documentEpoch > currentVersion.epoch || update.documentEpoch === currentVersion.epoch && update.documentRevision > currentVersion.revision) {
6028
+ return;
6029
+ }
6030
+ const current = Automerge.toJS(this.doc);
6031
+ const job = current.jobs?.byId?.[update.jobId];
6032
+ if (job && typeof job === "object" && !Array.isArray(job)) {
6033
+ job.actionTrace = update.actionTrace;
6034
+ job.actionSummary = update.actionSummary;
6035
+ job.actionSummaryText = update.actionSummaryText;
6036
+ const lastEvent = update.actionTrace[update.actionTrace.length - 1];
6037
+ if (lastEvent && typeof lastEvent.timestamp === "number") {
6038
+ job.lastActionAt = lastEvent.timestamp;
6039
+ }
6040
+ this.doc = Automerge.from(current);
6041
+ this.emit("job_action_trace", update);
6042
+ this.emit("sync", this.doc);
6043
+ }
6044
+ return;
6045
+ }
6046
+ if ("type" in message && message.type === "feed_transient_patch") {
6047
+ const update = message;
6048
+ if (!Number.isSafeInteger(update.documentEpoch) || update.documentEpoch < 0 || !Number.isSafeInteger(update.documentRevision) || update.documentRevision < 0 || !Number.isSafeInteger(update.feedRevision) || update.feedRevision < 0 || !Number.isSafeInteger(update.lastTransientOrdinal) || update.lastTransientOrdinal < 0 || !Array.isArray(update.changes) || update.changes.length > 128 || !update.changes.every((change) => {
6049
+ if (!change || typeof change !== "object" || typeof change.transientId !== "string" || change.transientId.length === 0) {
6050
+ return false;
6051
+ }
6052
+ if (change.removed === true) return change.item === void 0;
6053
+ const item = change.item;
6054
+ return Boolean(
6055
+ item && typeof item === "object" && !Array.isArray(item) && item.id === change.transientId
6056
+ );
6057
+ })) {
6058
+ console.warn("[Granular] Ignoring invalid feed_transient_patch");
6059
+ return;
6060
+ }
6061
+ const currentVersion = documentVersion(this.doc);
6062
+ if (update.documentEpoch > currentVersion.epoch || update.documentEpoch === currentVersion.epoch && update.documentRevision > currentVersion.revision || update.documentEpoch < currentVersion.epoch || update.documentEpoch === currentVersion.epoch && update.documentRevision < currentVersion.revision) {
6063
+ return;
6064
+ }
6065
+ const current = Automerge.toJS(this.doc);
6066
+ const feed = current.feed;
6067
+ if (!feed || typeof feed !== "object" || Array.isArray(feed)) return;
6068
+ if (Number.isSafeInteger(feed.revision) && feed.revision > update.feedRevision) {
6069
+ return;
6070
+ }
6071
+ if (Number.isSafeInteger(feed.lastTransientOrdinal) && feed.lastTransientOrdinal > update.lastTransientOrdinal) {
6072
+ return;
6073
+ }
6074
+ if (!feed.transientById || typeof feed.transientById !== "object") {
6075
+ feed.transientById = {};
6076
+ }
6077
+ for (const change of update.changes) {
6078
+ if (change.removed === true) {
6079
+ delete feed.transientById[change.transientId];
6080
+ } else {
6081
+ feed.transientById[change.transientId] = change.item;
6082
+ }
6083
+ }
6084
+ feed.revision = update.feedRevision;
6085
+ feed.lastTransientOrdinal = update.lastTransientOrdinal;
6086
+ this.doc = Automerge.from(current);
6087
+ this.emit("feed_transient_patch", update);
6088
+ this.emit("sync", this.doc);
6089
+ return;
6090
+ }
5981
6091
  if ("type" in message && message.type === "snapshot_reset") {
5982
6092
  const resetMessage = message;
5983
6093
  try {
5984
- if (!Number.isSafeInteger(resetMessage.documentEpoch) || resetMessage.documentEpoch <= 0 || !Number.isSafeInteger(resetMessage.documentRevision) || resetMessage.documentRevision < 0 || !Array.isArray(resetMessage.data)) {
6094
+ if (!Number.isSafeInteger(resetMessage.documentEpoch) || resetMessage.documentEpoch <= 0 || !Number.isSafeInteger(resetMessage.documentRevision) || resetMessage.documentRevision < 0 || !Array.isArray(resetMessage.data) && (!resetMessage.document || typeof resetMessage.document !== "object" || Array.isArray(resetMessage.document))) {
5985
6095
  throw new Error("snapshot_reset metadata is invalid");
5986
6096
  }
5987
- const replacement = Automerge.load(
6097
+ const replacement = resetMessage.document ? Automerge.from(resetMessage.document) : Automerge.load(
5988
6098
  this.decodeDocumentBytes(
5989
6099
  resetMessage.data,
5990
6100
  "Automerge snapshot_reset"
@@ -5998,7 +6108,10 @@ var WSClient = class {
5998
6108
  "snapshot_reset metadata does not match the saved document"
5999
6109
  );
6000
6110
  }
6001
- if (!this.canAcceptDocumentReplacement(replacement, true)) {
6111
+ const isJsonReplacement = Boolean(resetMessage.document);
6112
+ if (!this.canAcceptDocumentReplacement(replacement, !isJsonReplacement, {
6113
+ allowEqualRevisionPresentation: isJsonReplacement
6114
+ })) {
6002
6115
  if (this.documentRepairRequired) {
6003
6116
  this.requireDocumentResync(
6004
6117
  "Stale Automerge snapshot_reset during document repair",
@@ -12343,6 +12456,7 @@ var StateMachineTransitionSchema = external_exports.object({
12343
12456
  to: external_exports.string().min(1),
12344
12457
  label: external_exports.string().optional(),
12345
12458
  description: external_exports.string().optional(),
12459
+ userVisible: external_exports.boolean().optional(),
12346
12460
  action: StateTransitionActionSchema.optional(),
12347
12461
  assignee: StateTransitionAssigneeSchema.optional(),
12348
12462
  requirements: StateTransitionRequirementsSchema.optional(),
@@ -12427,6 +12541,7 @@ var CreatesSchema = external_exports.union([
12427
12541
  idPath: external_exports.string().min(1).optional(),
12428
12542
  pathPath: external_exports.string().min(1).optional(),
12429
12543
  statePath: external_exports.string().min(1).optional(),
12544
+ completionState: external_exports.string().min(1).optional(),
12430
12545
  classStateHandle: external_exports.boolean().optional()
12431
12546
  }).strict()
12432
12547
  ]);
@@ -12776,6 +12891,7 @@ function normalizeCreationSummary(metamodels) {
12776
12891
  ...typeof raw.idPath === "string" && raw.idPath.trim() ? { idPath: raw.idPath.trim() } : {},
12777
12892
  ...typeof raw.pathPath === "string" && raw.pathPath.trim() ? { pathPath: raw.pathPath.trim() } : {},
12778
12893
  ...typeof raw.statePath === "string" && raw.statePath.trim() ? { statePath: raw.statePath.trim() } : {},
12894
+ ...typeof raw.completionState === "string" && raw.completionState.trim() ? { completionState: raw.completionState.trim() } : {},
12779
12895
  ...typeof raw.classStateHandle === "boolean" ? { classStateHandle: raw.classStateHandle } : {}
12780
12896
  };
12781
12897
  }
@@ -12901,7 +13017,7 @@ var effectBehaviorsMetamodelPackage = defineMetamodelPackage({
12901
13017
  },
12902
13018
  {
12903
13019
  key: "creates",
12904
- description: 'Marks a static method as an allowed creator for a class. Use `creates: "class_name"` or `{ className, idPath, pathPath, statePath, classStateHandle }`.'
13020
+ description: 'Marks a static method as an allowed creator for a class. Use `creates: "class_name"` or `{ className, idPath, pathPath, statePath, completionState, classStateHandle }`.'
12905
13021
  }
12906
13022
  ]
12907
13023
  },
@@ -13134,6 +13250,11 @@ function validateProjectionResult(declaration, projection) {
13134
13250
  "projection.source.version"
13135
13251
  );
13136
13252
  }
13253
+ if (projection.source.versionScope !== void 0 && projection.source.versionScope !== "global" && projection.source.versionScope !== "target") {
13254
+ throw new Error(
13255
+ "projection.source.versionScope must be 'global' or 'target'"
13256
+ );
13257
+ }
13137
13258
  if (!Array.isArray(projection.changes)) {
13138
13259
  throw new Error("projection.changes must be an array");
13139
13260
  }
@@ -14791,6 +14912,7 @@ function normalizeStateMachines(values) {
14791
14912
  to: String(transition?.to?.name || ""),
14792
14913
  label: typeof transition?.label === "string" ? transition.label : null,
14793
14914
  description: typeof transition?.description === "string" ? transition.description : null,
14915
+ userVisible: typeof transition?.user_visible === "boolean" ? transition.user_visible : typeof transition?.userVisible === "boolean" ? transition.userVisible : null,
14794
14916
  action: parseJsonRecord(transition?.action) || parseJsonRecord(transition?.action_json),
14795
14917
  assignee: parseJsonRecord(transition?.assignee) || parseJsonRecord(transition?.assignee_json),
14796
14918
  requirements: parseJsonRecord(transition?.requirements) || parseJsonRecord(transition?.requirements_json),
@@ -14862,6 +14984,9 @@ function transitionMetadataGraphqlArgs(transition) {
14862
14984
  if (typeof transition.description === "string") {
14863
14985
  args.push(`description: ${JSON.stringify(transition.description)}`);
14864
14986
  }
14987
+ if (typeof transition.userVisible === "boolean") {
14988
+ args.push(`user_visible: ${transition.userVisible}`);
14989
+ }
14865
14990
  if (transition.action) {
14866
14991
  args.push(
14867
14992
  `action_json: ${JSON.stringify(JSON.stringify(transition.action))}`
@@ -15173,7 +15298,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15173
15298
  name: String!
15174
15299
  state_machine: StateMachine!
15175
15300
  add_state(name: String!, is_final: Boolean, label: String, description: String): StateMachineMutation!
15176
- 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, outcomes_json: String): StateMachineMutation!
15301
+ add_transition(name: String!, from: String!, to: String!, label: String, description: String, user_visible: Boolean, action_json: String, assignee_json: String, requirements_json: String, permission_json: String, risk: String, expected_outcome_json: String, outcomes_json: String): StateMachineMutation!
15177
15302
  activate_transition(name: String!): StateMachineMutation!
15178
15303
  }
15179
15304
 
@@ -15231,6 +15356,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15231
15356
  to: StateMachineState!
15232
15357
  label: String
15233
15358
  description: String
15359
+ user_visible: Boolean
15234
15360
  action_json: String
15235
15361
  assignee_json: String
15236
15362
  requirements_json: String
@@ -15312,6 +15438,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15312
15438
  to,
15313
15439
  label,
15314
15440
  description,
15441
+ user_visible,
15315
15442
  action_json,
15316
15443
  assignee_json,
15317
15444
  requirements_json,
@@ -15329,6 +15456,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15329
15456
  {
15330
15457
  label,
15331
15458
  description,
15459
+ userVisible: user_visible,
15332
15460
  actionJson: action_json,
15333
15461
  assigneeJson: assignee_json,
15334
15462
  requirementsJson: requirements_json,
@@ -15393,6 +15521,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15393
15521
  to: (value) => value.to_state || { name: value.to, is_final: false },
15394
15522
  label: (value) => value.label || null,
15395
15523
  description: (value) => value.description || null,
15524
+ user_visible: (value) => value.user_visible ?? null,
15396
15525
  action_json: (value) => value.action_json || null,
15397
15526
  assignee_json: (value) => value.assignee_json || null,
15398
15527
  requirements_json: (value) => value.requirements_json || null,
@@ -15479,6 +15608,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15479
15608
  to { name }
15480
15609
  label
15481
15610
  description
15611
+ user_visible
15482
15612
  action_json
15483
15613
  assignee_json
15484
15614
  requirements_json
@@ -15788,6 +15918,104 @@ function buildEffectMetamodelMutations(toolPath, spec) {
15788
15918
  );
15789
15919
  }
15790
15920
 
15921
+ // src/artifact-workflow.ts
15922
+ var TERMINAL_WORKFLOW_STEP_STATUSES = /* @__PURE__ */ new Set([
15923
+ "completed",
15924
+ "skipped",
15925
+ "canceled"
15926
+ ]);
15927
+ function isArtifactWorkflowNodeTerminal(nodeOrStatus) {
15928
+ const status = typeof nodeOrStatus === "string" ? nodeOrStatus : nodeOrStatus.status;
15929
+ return TERMINAL_WORKFLOW_STEP_STATUSES.has(status);
15930
+ }
15931
+ function isNavigationOnlyOperation(workflow, node) {
15932
+ const hasExplicitTransitionLeaf = getArtifactWorkflowNodes(workflow).some(
15933
+ (candidate) => candidate.childIds.length === 0 && candidate.kind === "transition"
15934
+ );
15935
+ return hasExplicitTransitionLeaf && node.childIds.length === 0 && node.kind === "operation" && // A terminal operation is redundant once authored state-path leaves are
15936
+ // available. While it is still delivering/synchronizing, however, it is
15937
+ // the only honest representation of the work still in progress.
15938
+ isArtifactWorkflowNodeTerminal(node);
15939
+ }
15940
+ function isArtifactWorkflowNodeNavigableLeaf(workflow, node) {
15941
+ return node.childIds.length === 0 && !isNavigationOnlyOperation(workflow, node);
15942
+ }
15943
+ function isArtifactWorkflowNodeActionable(node) {
15944
+ return node.availability === "available" && ["current", "needs_input", "working", "blocked", "failed"].includes(
15945
+ node.status
15946
+ );
15947
+ }
15948
+ function getArtifactWorkflowNodes(workflow) {
15949
+ const visit = (id) => {
15950
+ const node = workflow.nodesById[id];
15951
+ return node ? [node, ...node.childIds.flatMap(visit)] : [];
15952
+ };
15953
+ return workflow.rootNodeIds.flatMap(visit);
15954
+ }
15955
+ function assertArtifactWorkflow(workflow) {
15956
+ if (workflow.schemaVersion !== 1)
15957
+ throw new Error("Unsupported artifact workflow schema");
15958
+ if (!Number.isSafeInteger(workflow.revision) || workflow.revision < 1) {
15959
+ throw new Error("Artifact workflow revision must be a positive integer");
15960
+ }
15961
+ const visited = /* @__PURE__ */ new Set();
15962
+ const visiting = /* @__PURE__ */ new Set();
15963
+ const nodes = [];
15964
+ const visit = (id, expectedParentId) => {
15965
+ const node = workflow.nodesById[id];
15966
+ if (!node) throw new Error(`Artifact workflow node ${id} does not exist`);
15967
+ if (visiting.has(id)) throw new Error("Artifact workflow contains a cycle");
15968
+ if (visited.has(id))
15969
+ throw new Error("Artifact workflow contains a duplicate node reference");
15970
+ if (node.parentId !== expectedParentId) {
15971
+ throw new Error(`Artifact workflow node ${id} has an invalid parent`);
15972
+ }
15973
+ visiting.add(id);
15974
+ nodes.push(node);
15975
+ node.childIds.forEach((childId) => visit(childId, id));
15976
+ visiting.delete(id);
15977
+ visited.add(id);
15978
+ };
15979
+ workflow.rootNodeIds.forEach((id) => visit(id, null));
15980
+ if (visited.size !== Object.keys(workflow.nodesById).length) {
15981
+ throw new Error("Artifact workflow contains unreachable nodes");
15982
+ }
15983
+ if (workflow.currentNodeId && !workflow.nodesById[workflow.currentNodeId]) {
15984
+ throw new Error("Artifact workflow current node does not exist");
15985
+ }
15986
+ const currentNode = workflow.currentNodeId ? workflow.nodesById[workflow.currentNodeId] : null;
15987
+ if (currentNode && currentNode.childIds.length > 0) {
15988
+ throw new Error("Artifact workflow current node must be actionable leaf");
15989
+ }
15990
+ const actionable = nodes.filter(
15991
+ (node) => isArtifactWorkflowNodeNavigableLeaf(workflow, node) && isArtifactWorkflowNodeActionable(node)
15992
+ );
15993
+ if (actionable.length > 1)
15994
+ throw new Error("Artifact workflow has multiple current nodes");
15995
+ if (actionable.length === 1 && actionable[0].id !== workflow.currentNodeId) {
15996
+ throw new Error("Artifact workflow current node does not own its action");
15997
+ }
15998
+ if (workflow.currentNodeId && !actionable.length && !isArtifactWorkflowNodeTerminal(currentNode)) {
15999
+ throw new Error("Artifact workflow current node is not actionable");
16000
+ }
16001
+ }
16002
+ function requireArtifactWorkflow(artifact) {
16003
+ if (!artifact.workflow) {
16004
+ throw new Error(
16005
+ `Artifact ${artifact.artifactId} has no canonical workflow`
16006
+ );
16007
+ }
16008
+ assertArtifactWorkflow(artifact.workflow);
16009
+ if (artifact.workflow.artifactId !== artifact.artifactId) {
16010
+ throw new Error("Artifact workflow belongs to a different artifact");
16011
+ }
16012
+ return artifact;
16013
+ }
16014
+ function requireArtifactWorkflowResult(result) {
16015
+ requireArtifactWorkflow(result.artifact);
16016
+ return result;
16017
+ }
16018
+
15791
16019
  // src/client.ts
15792
16020
  var DEFAULT_CONVERSATION_SESSION_LIST_LIMIT = 100;
15793
16021
  var MAX_CONVERSATION_SESSION_LIST_LIMIT = 500;
@@ -17631,6 +17859,7 @@ var EnvironmentSession = class extends Session {
17631
17859
  sessionDataHeaders;
17632
17860
  beforeMutationSend;
17633
17861
  heartbeatTimer = null;
17862
+ heartbeatProbeInFlight = false;
17634
17863
  /** The last known graph container status, updated by checkReadiness(). */
17635
17864
  graphContainerStatus = null;
17636
17865
  constructor(client, environment, clientId, options = {}) {
@@ -17650,16 +17879,34 @@ var EnvironmentSession = class extends Session {
17650
17879
  * an application RPC may be delayed by Durable Object work even while the
17651
17880
  * underlying WebSocket is healthy.
17652
17881
  */
17653
- startHeartbeat() {
17882
+ startHeartbeat(options = {}) {
17654
17883
  if (this.heartbeatTimer) return;
17655
- this.heartbeatTimer = setInterval(() => {
17656
- if (!this.client.isTransportOpen()) return;
17657
- if (this.client.hasRecentInboundActivity(3e4)) return;
17658
- if (this.client.hasRecentlyPendingRpcPeriod(6e4)) return;
17659
- this.client.reportTransportFailure(
17660
- new Error("No server activity received for 30 seconds")
17661
- );
17662
- }, 5e3);
17884
+ const probeServer = options.probeServer === true;
17885
+ this.heartbeatTimer = setInterval(
17886
+ () => {
17887
+ if (!this.client.isTransportOpen()) return;
17888
+ if (probeServer) {
17889
+ if (this.heartbeatProbeInFlight) return;
17890
+ if (this.client.hasPendingRpcRequests()) return;
17891
+ if (this.client.hasRecentInboundActivity(1e4)) return;
17892
+ this.heartbeatProbeInFlight = true;
17893
+ void this.client.call("client.heartbeat", {}).catch((error) => {
17894
+ if (this.client.isTransportOpen()) {
17895
+ this.client.reportTransportFailure(error);
17896
+ }
17897
+ }).finally(() => {
17898
+ this.heartbeatProbeInFlight = false;
17899
+ });
17900
+ return;
17901
+ }
17902
+ if (this.client.hasRecentInboundActivity(3e4)) return;
17903
+ if (this.client.hasRecentlyPendingRpcPeriod(6e4)) return;
17904
+ this.client.reportTransportFailure(
17905
+ new Error("No server activity received for 30 seconds")
17906
+ );
17907
+ },
17908
+ probeServer ? 1e4 : 5e3
17909
+ );
17663
17910
  this.heartbeatTimer.unref?.();
17664
17911
  }
17665
17912
  stopHeartbeat() {
@@ -17667,10 +17914,15 @@ var EnvironmentSession = class extends Session {
17667
17914
  clearInterval(this.heartbeatTimer);
17668
17915
  this.heartbeatTimer = null;
17669
17916
  }
17917
+ this.heartbeatProbeInFlight = false;
17670
17918
  }
17671
17919
  async hello() {
17672
17920
  const result = await super.hello();
17673
- this.startHeartbeat();
17921
+ if (result.features?.serverHeartbeat === false) {
17922
+ this.startHeartbeat({ probeServer: true });
17923
+ } else {
17924
+ this.startHeartbeat();
17925
+ }
17674
17926
  return result;
17675
17927
  }
17676
17928
  get environmentId() {
@@ -17856,7 +18108,10 @@ var EnvironmentSession = class extends Session {
17856
18108
  queryOptions.targetId = options.target.id;
17857
18109
  delete queryOptions.target;
17858
18110
  }
17859
- return this.sessionDataRequest("/artifacts", queryOptions);
18111
+ return this.sessionDataRequest("/artifacts", queryOptions).then((page) => ({
18112
+ ...page,
18113
+ items: page.items.map(requireArtifactWorkflow)
18114
+ }));
17860
18115
  },
17861
18116
  listForLatestJob: (options = {}) => this.artifacts.list({
17862
18117
  ...options,
@@ -17869,7 +18124,7 @@ var EnvironmentSession = class extends Session {
17869
18124
  // `cache: no-store`; this also bypasses intermediaries that ignore
17870
18125
  // the Fetch cache directive for an otherwise identical GET URL.
17871
18126
  { _granularLiveRead: Date.now() }
17872
- ),
18127
+ ).then(requireArtifactWorkflow),
17873
18128
  create: (artifact) => this.sessionDataRequest(
17874
18129
  "/artifacts",
17875
18130
  void 0,
@@ -17877,7 +18132,7 @@ var EnvironmentSession = class extends Session {
17877
18132
  method: "POST",
17878
18133
  body: artifact
17879
18134
  }
17880
- ),
18135
+ ).then(requireArtifactWorkflow),
17881
18136
  acceptSuggestion: (sequence) => this.sessionDataRequest(
17882
18137
  "/artifacts/suggestions/accept",
17883
18138
  void 0,
@@ -17885,7 +18140,7 @@ var EnvironmentSession = class extends Session {
17885
18140
  method: "POST",
17886
18141
  body: { sequence }
17887
18142
  }
17888
- ),
18143
+ ).then(requireArtifactWorkflow),
17889
18144
  updateInputs: (artifactId, patch) => this.sessionDataRequest(
17890
18145
  `/artifacts/${encodeURIComponent(artifactId)}`,
17891
18146
  void 0,
@@ -17893,7 +18148,7 @@ var EnvironmentSession = class extends Session {
17893
18148
  method: "PATCH",
17894
18149
  body: patch
17895
18150
  }
17896
- ),
18151
+ ).then(requireArtifactWorkflow),
17897
18152
  relationshipOptions: (artifactId, input) => this.sessionDataRequest(
17898
18153
  `/artifacts/${encodeURIComponent(artifactId)}/relationship-options`,
17899
18154
  void 0,
@@ -17903,27 +18158,27 @@ var EnvironmentSession = class extends Session {
17903
18158
  `/artifacts/${encodeURIComponent(artifactId)}/relationship-create`,
17904
18159
  void 0,
17905
18160
  { method: "POST", body: input }
17906
- ),
18161
+ ).then(requireArtifactWorkflow),
17907
18162
  validate: (artifactId) => this.sessionDataRequest(
17908
18163
  `/artifacts/${encodeURIComponent(artifactId)}/validate`,
17909
18164
  void 0,
17910
18165
  { method: "POST" }
17911
- ),
18166
+ ).then(requireArtifactWorkflowResult),
17912
18167
  execute: (artifactId, options) => this.sessionDataRequest(
17913
18168
  `/artifacts/${encodeURIComponent(artifactId)}/execute`,
17914
18169
  void 0,
17915
18170
  { method: "POST", body: options }
17916
- ),
18171
+ ).then(requireArtifactWorkflowResult),
17917
18172
  approve: (artifactId, options) => this.sessionDataRequest(
17918
18173
  `/artifacts/${encodeURIComponent(artifactId)}/approve`,
17919
18174
  void 0,
17920
18175
  { method: "POST", body: options }
17921
- ),
18176
+ ).then(requireArtifactWorkflowResult),
17922
18177
  cancel: (artifactId) => this.sessionDataRequest(
17923
18178
  `/artifacts/${encodeURIComponent(artifactId)}/cancel`,
17924
18179
  void 0,
17925
18180
  { method: "POST" }
17926
- )
18181
+ ).then(requireArtifactWorkflow)
17927
18182
  };
17928
18183
  }
17929
18184
  get manualActions() {
@@ -22637,6 +22892,10 @@ ${actionIndex}
22637
22892
  - 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.
22638
22893
  - Opening a prepared action does not execute the underlying mutation. Use \`.open()\` only when the user asks to prepare, review, show, or edit an action before running it, when a new record must be prepared, or when a reviewable form must collect missing editable inputs. Prefill only grounded values and leave unknown fields empty.
22639
22894
  - For creation inputs, use the declared action schema and its field descriptions as the mapping authority. Copy every compatible value explicitly present in the current request, conversation, selected/saved object, file, fresh read, or declared ontology default, including values for fields with arbitrary names. Omit only values for which that context provides no evidence.
22895
+ - Preserve explicit qualifiers from the user's requested object identity, including quantities, product names, and named organizational scope. Do not shorten a title in a way that drops those qualifiers when they distinguish what is being created.
22896
+ - A contextual or ontology default may fill a value only when the user supplied no conflicting intent for that semantic role. If the user names a team, owner, account, location, or other scope that cannot be grounded, leave that relationship unresolved; never substitute the current actor's default or another convenient visible record.
22897
+ - A missing or inaccessible required relationship is not a reason to replace a reviewable action with a lookup-failure reply. Call the reviewable action with every grounded non-relationship value, omit the unresolved relationship, and let the artifact collect it from the user. This applies even when the user explicitly named the unavailable relationship.
22898
+ - Choose enum values from the request's meaning and the ontology-authored option semantics. Do not choose an enum merely because it makes an available default relationship, budget, policy, or downstream option compatible. If no enum is semantically supported, leave it unresolved or ask.
22640
22899
  - Never fill an input because of its field name, a regular expression or other string pattern, its primitive type, a schema minimum, an example value, or the first enum option. If the evidence is ambiguous, leave the field unresolved or ask the user.
22641
22900
  - When the user explicitly commands an existing-record mutation such as approve, reject, block, route, send, or update, and one visible domain action uniquely matches, call that domain action directly. Do not substitute a state-handle \`.open()\` artifact for execution. If runtime policy requires confirmation, invoke the action once and let the runtime pause and resume that same invocation.
22642
22901
  - 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.