@granular-software/sdk 0.4.70 → 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.
@@ -1,5 +1,5 @@
1
- import { a as EnvironmentSession, G as Granular } from './client-qgWZ495o.mjs';
2
- import { aE as GranularSpendContext, az as OpenAITokenSpend, n as Prompt, h as RecordObjectOptions, i as ManifestContent, m as SessionHeapSnapshot, T as ToolWithHandler, a_ as ConnectOptions, bo as CreateEnvironmentData, aT as GranularOptions } from './spend-B-IQmp7F.mjs';
1
+ import { a as EnvironmentSession, G as Granular } from './client-p5EhOwpP.mjs';
2
+ import { aI as GranularSpendContext, aD as OpenAITokenSpend, n as Prompt, h as RecordObjectOptions, i as ManifestContent, m as SessionHeapSnapshot, T as ToolWithHandler, b2 as ConnectOptions, bs as CreateEnvironmentData, aX as GranularOptions } from './spend-lJDtuAr6.mjs';
3
3
  import { GranularAgentToolInfo, GeneratedJobCodeIssue, BuildGranularAgentSystemPromptInput, HarnessRenderedPrompt, HarnessRenderedContinuation, HarnessControllerBudgets } from './agent-harness.mjs';
4
4
  import '@automerge/automerge';
5
5
  import '@automerge/automerge/slim';
@@ -1,5 +1,5 @@
1
- import { a as EnvironmentSession, G as Granular } from './client-D1gq4TO4.js';
2
- import { aE as GranularSpendContext, az as OpenAITokenSpend, n as Prompt, h as RecordObjectOptions, i as ManifestContent, m as SessionHeapSnapshot, T as ToolWithHandler, a_ as ConnectOptions, bo as CreateEnvironmentData, aT as GranularOptions } from './spend-B-IQmp7F.js';
1
+ import { a as EnvironmentSession, G as Granular } from './client-JCISbded.js';
2
+ import { aI as GranularSpendContext, aD as OpenAITokenSpend, n as Prompt, h as RecordObjectOptions, i as ManifestContent, m as SessionHeapSnapshot, T as ToolWithHandler, b2 as ConnectOptions, bs as CreateEnvironmentData, aX as GranularOptions } from './spend-lJDtuAr6.js';
3
3
  import { GranularAgentToolInfo, GeneratedJobCodeIssue, BuildGranularAgentSystemPromptInput, HarnessRenderedPrompt, HarnessRenderedContinuation, HarnessControllerBudgets } from './agent-harness.js';
4
4
  import '@automerge/automerge';
5
5
  import '@automerge/automerge/slim';
@@ -5317,6 +5317,38 @@ function stableDocumentFingerprint(value) {
5317
5317
  (key) => `${JSON.stringify(key)}:${stableDocumentFingerprint(record[key])}`
5318
5318
  ).join(",")}}`;
5319
5319
  }
5320
+ function canonicalDocumentFingerprint(value) {
5321
+ const stripPresentation = (input, path2) => {
5322
+ if (input === null || typeof input !== "object") return input;
5323
+ if (input instanceof Date || input instanceof Uint8Array) return input;
5324
+ if (Array.isArray(input)) {
5325
+ return input.map((entry) => stripPresentation(entry, path2));
5326
+ }
5327
+ const record = input;
5328
+ const output = {};
5329
+ for (const [key, entry] of Object.entries(record)) {
5330
+ if (path2.length === 1 && path2[0] === "feed" && [
5331
+ "transientById",
5332
+ "feedbackUsageByJob",
5333
+ "revision",
5334
+ "lastTransientOrdinal"
5335
+ ].includes(key)) {
5336
+ continue;
5337
+ }
5338
+ if (path2.length === 3 && path2[0] === "jobs" && path2[1] === "byId" && [
5339
+ "actionTrace",
5340
+ "actionSummary",
5341
+ "actionSummaryText",
5342
+ "lastActionAt"
5343
+ ].includes(key)) {
5344
+ continue;
5345
+ }
5346
+ output[key] = stripPresentation(entry, [...path2, key]);
5347
+ }
5348
+ return output;
5349
+ };
5350
+ return stableDocumentFingerprint(stripPresentation(value, []));
5351
+ }
5320
5352
  function debugWs(...args) {
5321
5353
  if (DEBUG_WS) {
5322
5354
  console.log(...args);
@@ -5634,6 +5666,7 @@ var WSClient = class {
5634
5666
  }
5635
5667
  this.lastInboundActivityAt = Date.now();
5636
5668
  this.reconnectAttempts = 0;
5669
+ this.connectPromise = null;
5637
5670
  this.emit("open", {});
5638
5671
  finish();
5639
5672
  };
@@ -5796,7 +5829,7 @@ var WSClient = class {
5796
5829
  this.canonicalDocumentActivated = true;
5797
5830
  }
5798
5831
  }
5799
- canAcceptDocumentReplacement(replacement, strictlyNewer) {
5832
+ canAcceptDocumentReplacement(replacement, strictlyNewer, options = {}) {
5800
5833
  const replacementCanonical = isCanonicalSessionFeedDocument(replacement);
5801
5834
  const currentCanonical = isCanonicalSessionFeedDocument(this.doc);
5802
5835
  if (replacementCanonical) {
@@ -5845,7 +5878,7 @@ var WSClient = class {
5845
5878
  debugWs(`[Granular DEBUG] Rejected session snapshot: ${canonicalError}`);
5846
5879
  return false;
5847
5880
  }
5848
- if (this.canonicalDocumentActivated && incoming.epoch === current.epoch && incoming.revision === current.revision && !readSessionFeedSnapshot(this.doc).error && stableDocumentFingerprint(Automerge__namespace.toJS(replacement)) !== stableDocumentFingerprint(Automerge__namespace.toJS(this.doc))) {
5881
+ if (this.canonicalDocumentActivated && incoming.epoch === current.epoch && incoming.revision === current.revision && !readSessionFeedSnapshot(this.doc).error && (options.allowEqualRevisionPresentation ? canonicalDocumentFingerprint(Automerge__namespace.toJS(replacement)) : stableDocumentFingerprint(Automerge__namespace.toJS(replacement))) !== (options.allowEqualRevisionPresentation ? canonicalDocumentFingerprint(Automerge__namespace.toJS(this.doc)) : stableDocumentFingerprint(Automerge__namespace.toJS(this.doc)))) {
5849
5882
  debugWs(
5850
5883
  "[Granular DEBUG] Rejected divergent canonical snapshot at the accepted document version."
5851
5884
  );
@@ -5967,6 +6000,7 @@ var WSClient = class {
5967
6000
  reconnectScheduled: false
5968
6001
  };
5969
6002
  this.ws = null;
6003
+ this.connectPromise = null;
5970
6004
  this.emit("close", info);
5971
6005
  if (this.reconnectTimer) {
5972
6006
  clearTimeout(this.reconnectTimer);
@@ -6004,13 +6038,89 @@ var WSClient = class {
6004
6038
  );
6005
6039
  return;
6006
6040
  }
6041
+ if ("type" in message && message.type === "job_action_trace") {
6042
+ const update = message;
6043
+ 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(
6044
+ (event) => event && typeof event === "object" && typeof event.id === "string" && typeof event.timestamp === "number" && Number.isFinite(event.timestamp)
6045
+ ) || !Array.isArray(update.actionSummary) || update.actionSummary.length > 16 || !update.actionSummary.every((line) => typeof line === "string") || typeof update.actionSummaryText !== "string" || update.actionSummaryText.length > 8e3) {
6046
+ console.warn("[Granular] Ignoring invalid job_action_trace message");
6047
+ return;
6048
+ }
6049
+ const currentVersion = documentVersion(this.doc);
6050
+ if (update.documentEpoch < currentVersion.epoch || update.documentEpoch === currentVersion.epoch && update.documentRevision < currentVersion.revision) {
6051
+ return;
6052
+ }
6053
+ if (update.documentEpoch > currentVersion.epoch || update.documentEpoch === currentVersion.epoch && update.documentRevision > currentVersion.revision) {
6054
+ return;
6055
+ }
6056
+ const current = Automerge__namespace.toJS(this.doc);
6057
+ const job = current.jobs?.byId?.[update.jobId];
6058
+ if (job && typeof job === "object" && !Array.isArray(job)) {
6059
+ job.actionTrace = update.actionTrace;
6060
+ job.actionSummary = update.actionSummary;
6061
+ job.actionSummaryText = update.actionSummaryText;
6062
+ const lastEvent = update.actionTrace[update.actionTrace.length - 1];
6063
+ if (lastEvent && typeof lastEvent.timestamp === "number") {
6064
+ job.lastActionAt = lastEvent.timestamp;
6065
+ }
6066
+ this.doc = Automerge__namespace.from(current);
6067
+ this.emit("job_action_trace", update);
6068
+ this.emit("sync", this.doc);
6069
+ }
6070
+ return;
6071
+ }
6072
+ if ("type" in message && message.type === "feed_transient_patch") {
6073
+ const update = message;
6074
+ 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) => {
6075
+ if (!change || typeof change !== "object" || typeof change.transientId !== "string" || change.transientId.length === 0) {
6076
+ return false;
6077
+ }
6078
+ if (change.removed === true) return change.item === void 0;
6079
+ const item = change.item;
6080
+ return Boolean(
6081
+ item && typeof item === "object" && !Array.isArray(item) && item.id === change.transientId
6082
+ );
6083
+ })) {
6084
+ console.warn("[Granular] Ignoring invalid feed_transient_patch");
6085
+ return;
6086
+ }
6087
+ const currentVersion = documentVersion(this.doc);
6088
+ 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) {
6089
+ return;
6090
+ }
6091
+ const current = Automerge__namespace.toJS(this.doc);
6092
+ const feed = current.feed;
6093
+ if (!feed || typeof feed !== "object" || Array.isArray(feed)) return;
6094
+ if (Number.isSafeInteger(feed.revision) && feed.revision > update.feedRevision) {
6095
+ return;
6096
+ }
6097
+ if (Number.isSafeInteger(feed.lastTransientOrdinal) && feed.lastTransientOrdinal > update.lastTransientOrdinal) {
6098
+ return;
6099
+ }
6100
+ if (!feed.transientById || typeof feed.transientById !== "object") {
6101
+ feed.transientById = {};
6102
+ }
6103
+ for (const change of update.changes) {
6104
+ if (change.removed === true) {
6105
+ delete feed.transientById[change.transientId];
6106
+ } else {
6107
+ feed.transientById[change.transientId] = change.item;
6108
+ }
6109
+ }
6110
+ feed.revision = update.feedRevision;
6111
+ feed.lastTransientOrdinal = update.lastTransientOrdinal;
6112
+ this.doc = Automerge__namespace.from(current);
6113
+ this.emit("feed_transient_patch", update);
6114
+ this.emit("sync", this.doc);
6115
+ return;
6116
+ }
6007
6117
  if ("type" in message && message.type === "snapshot_reset") {
6008
6118
  const resetMessage = message;
6009
6119
  try {
6010
- if (!Number.isSafeInteger(resetMessage.documentEpoch) || resetMessage.documentEpoch <= 0 || !Number.isSafeInteger(resetMessage.documentRevision) || resetMessage.documentRevision < 0 || !Array.isArray(resetMessage.data)) {
6120
+ 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))) {
6011
6121
  throw new Error("snapshot_reset metadata is invalid");
6012
6122
  }
6013
- const replacement = Automerge__namespace.load(
6123
+ const replacement = resetMessage.document ? Automerge__namespace.from(resetMessage.document) : Automerge__namespace.load(
6014
6124
  this.decodeDocumentBytes(
6015
6125
  resetMessage.data,
6016
6126
  "Automerge snapshot_reset"
@@ -6024,7 +6134,10 @@ var WSClient = class {
6024
6134
  "snapshot_reset metadata does not match the saved document"
6025
6135
  );
6026
6136
  }
6027
- if (!this.canAcceptDocumentReplacement(replacement, true)) {
6137
+ const isJsonReplacement = Boolean(resetMessage.document);
6138
+ if (!this.canAcceptDocumentReplacement(replacement, !isJsonReplacement, {
6139
+ allowEqualRevisionPresentation: isJsonReplacement
6140
+ })) {
6028
6141
  if (this.documentRepairRequired) {
6029
6142
  this.requireDocumentResync(
6030
6143
  "Stale Automerge snapshot_reset during document repair",
@@ -12369,6 +12482,7 @@ var StateMachineTransitionSchema = external_exports.object({
12369
12482
  to: external_exports.string().min(1),
12370
12483
  label: external_exports.string().optional(),
12371
12484
  description: external_exports.string().optional(),
12485
+ userVisible: external_exports.boolean().optional(),
12372
12486
  action: StateTransitionActionSchema.optional(),
12373
12487
  assignee: StateTransitionAssigneeSchema.optional(),
12374
12488
  requirements: StateTransitionRequirementsSchema.optional(),
@@ -12453,6 +12567,7 @@ var CreatesSchema = external_exports.union([
12453
12567
  idPath: external_exports.string().min(1).optional(),
12454
12568
  pathPath: external_exports.string().min(1).optional(),
12455
12569
  statePath: external_exports.string().min(1).optional(),
12570
+ completionState: external_exports.string().min(1).optional(),
12456
12571
  classStateHandle: external_exports.boolean().optional()
12457
12572
  }).strict()
12458
12573
  ]);
@@ -12802,6 +12917,7 @@ function normalizeCreationSummary(metamodels) {
12802
12917
  ...typeof raw.idPath === "string" && raw.idPath.trim() ? { idPath: raw.idPath.trim() } : {},
12803
12918
  ...typeof raw.pathPath === "string" && raw.pathPath.trim() ? { pathPath: raw.pathPath.trim() } : {},
12804
12919
  ...typeof raw.statePath === "string" && raw.statePath.trim() ? { statePath: raw.statePath.trim() } : {},
12920
+ ...typeof raw.completionState === "string" && raw.completionState.trim() ? { completionState: raw.completionState.trim() } : {},
12805
12921
  ...typeof raw.classStateHandle === "boolean" ? { classStateHandle: raw.classStateHandle } : {}
12806
12922
  };
12807
12923
  }
@@ -12927,7 +13043,7 @@ var effectBehaviorsMetamodelPackage = defineMetamodelPackage({
12927
13043
  },
12928
13044
  {
12929
13045
  key: "creates",
12930
- description: 'Marks a static method as an allowed creator for a class. Use `creates: "class_name"` or `{ className, idPath, pathPath, statePath, classStateHandle }`.'
13046
+ description: 'Marks a static method as an allowed creator for a class. Use `creates: "class_name"` or `{ className, idPath, pathPath, statePath, completionState, classStateHandle }`.'
12931
13047
  }
12932
13048
  ]
12933
13049
  },
@@ -13160,6 +13276,11 @@ function validateProjectionResult(declaration, projection) {
13160
13276
  "projection.source.version"
13161
13277
  );
13162
13278
  }
13279
+ if (projection.source.versionScope !== void 0 && projection.source.versionScope !== "global" && projection.source.versionScope !== "target") {
13280
+ throw new Error(
13281
+ "projection.source.versionScope must be 'global' or 'target'"
13282
+ );
13283
+ }
13163
13284
  if (!Array.isArray(projection.changes)) {
13164
13285
  throw new Error("projection.changes must be an array");
13165
13286
  }
@@ -14817,6 +14938,7 @@ function normalizeStateMachines(values) {
14817
14938
  to: String(transition?.to?.name || ""),
14818
14939
  label: typeof transition?.label === "string" ? transition.label : null,
14819
14940
  description: typeof transition?.description === "string" ? transition.description : null,
14941
+ userVisible: typeof transition?.user_visible === "boolean" ? transition.user_visible : typeof transition?.userVisible === "boolean" ? transition.userVisible : null,
14820
14942
  action: parseJsonRecord(transition?.action) || parseJsonRecord(transition?.action_json),
14821
14943
  assignee: parseJsonRecord(transition?.assignee) || parseJsonRecord(transition?.assignee_json),
14822
14944
  requirements: parseJsonRecord(transition?.requirements) || parseJsonRecord(transition?.requirements_json),
@@ -14888,6 +15010,9 @@ function transitionMetadataGraphqlArgs(transition) {
14888
15010
  if (typeof transition.description === "string") {
14889
15011
  args.push(`description: ${JSON.stringify(transition.description)}`);
14890
15012
  }
15013
+ if (typeof transition.userVisible === "boolean") {
15014
+ args.push(`user_visible: ${transition.userVisible}`);
15015
+ }
14891
15016
  if (transition.action) {
14892
15017
  args.push(
14893
15018
  `action_json: ${JSON.stringify(JSON.stringify(transition.action))}`
@@ -15199,7 +15324,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15199
15324
  name: String!
15200
15325
  state_machine: StateMachine!
15201
15326
  add_state(name: String!, is_final: Boolean, label: String, description: String): StateMachineMutation!
15202
- 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!
15327
+ 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!
15203
15328
  activate_transition(name: String!): StateMachineMutation!
15204
15329
  }
15205
15330
 
@@ -15257,6 +15382,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15257
15382
  to: StateMachineState!
15258
15383
  label: String
15259
15384
  description: String
15385
+ user_visible: Boolean
15260
15386
  action_json: String
15261
15387
  assignee_json: String
15262
15388
  requirements_json: String
@@ -15338,6 +15464,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15338
15464
  to,
15339
15465
  label,
15340
15466
  description,
15467
+ user_visible,
15341
15468
  action_json,
15342
15469
  assignee_json,
15343
15470
  requirements_json,
@@ -15355,6 +15482,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15355
15482
  {
15356
15483
  label,
15357
15484
  description,
15485
+ userVisible: user_visible,
15358
15486
  actionJson: action_json,
15359
15487
  assigneeJson: assignee_json,
15360
15488
  requirementsJson: requirements_json,
@@ -15419,6 +15547,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15419
15547
  to: (value) => value.to_state || { name: value.to, is_final: false },
15420
15548
  label: (value) => value.label || null,
15421
15549
  description: (value) => value.description || null,
15550
+ user_visible: (value) => value.user_visible ?? null,
15422
15551
  action_json: (value) => value.action_json || null,
15423
15552
  assignee_json: (value) => value.assignee_json || null,
15424
15553
  requirements_json: (value) => value.requirements_json || null,
@@ -15505,6 +15634,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15505
15634
  to { name }
15506
15635
  label
15507
15636
  description
15637
+ user_visible
15508
15638
  action_json
15509
15639
  assignee_json
15510
15640
  requirements_json
@@ -15814,6 +15944,104 @@ function buildEffectMetamodelMutations(toolPath, spec) {
15814
15944
  );
15815
15945
  }
15816
15946
 
15947
+ // src/artifact-workflow.ts
15948
+ var TERMINAL_WORKFLOW_STEP_STATUSES = /* @__PURE__ */ new Set([
15949
+ "completed",
15950
+ "skipped",
15951
+ "canceled"
15952
+ ]);
15953
+ function isArtifactWorkflowNodeTerminal(nodeOrStatus) {
15954
+ const status = typeof nodeOrStatus === "string" ? nodeOrStatus : nodeOrStatus.status;
15955
+ return TERMINAL_WORKFLOW_STEP_STATUSES.has(status);
15956
+ }
15957
+ function isNavigationOnlyOperation(workflow, node) {
15958
+ const hasExplicitTransitionLeaf = getArtifactWorkflowNodes(workflow).some(
15959
+ (candidate) => candidate.childIds.length === 0 && candidate.kind === "transition"
15960
+ );
15961
+ return hasExplicitTransitionLeaf && node.childIds.length === 0 && node.kind === "operation" && // A terminal operation is redundant once authored state-path leaves are
15962
+ // available. While it is still delivering/synchronizing, however, it is
15963
+ // the only honest representation of the work still in progress.
15964
+ isArtifactWorkflowNodeTerminal(node);
15965
+ }
15966
+ function isArtifactWorkflowNodeNavigableLeaf(workflow, node) {
15967
+ return node.childIds.length === 0 && !isNavigationOnlyOperation(workflow, node);
15968
+ }
15969
+ function isArtifactWorkflowNodeActionable(node) {
15970
+ return node.availability === "available" && ["current", "needs_input", "working", "blocked", "failed"].includes(
15971
+ node.status
15972
+ );
15973
+ }
15974
+ function getArtifactWorkflowNodes(workflow) {
15975
+ const visit = (id) => {
15976
+ const node = workflow.nodesById[id];
15977
+ return node ? [node, ...node.childIds.flatMap(visit)] : [];
15978
+ };
15979
+ return workflow.rootNodeIds.flatMap(visit);
15980
+ }
15981
+ function assertArtifactWorkflow(workflow) {
15982
+ if (workflow.schemaVersion !== 1)
15983
+ throw new Error("Unsupported artifact workflow schema");
15984
+ if (!Number.isSafeInteger(workflow.revision) || workflow.revision < 1) {
15985
+ throw new Error("Artifact workflow revision must be a positive integer");
15986
+ }
15987
+ const visited = /* @__PURE__ */ new Set();
15988
+ const visiting = /* @__PURE__ */ new Set();
15989
+ const nodes = [];
15990
+ const visit = (id, expectedParentId) => {
15991
+ const node = workflow.nodesById[id];
15992
+ if (!node) throw new Error(`Artifact workflow node ${id} does not exist`);
15993
+ if (visiting.has(id)) throw new Error("Artifact workflow contains a cycle");
15994
+ if (visited.has(id))
15995
+ throw new Error("Artifact workflow contains a duplicate node reference");
15996
+ if (node.parentId !== expectedParentId) {
15997
+ throw new Error(`Artifact workflow node ${id} has an invalid parent`);
15998
+ }
15999
+ visiting.add(id);
16000
+ nodes.push(node);
16001
+ node.childIds.forEach((childId) => visit(childId, id));
16002
+ visiting.delete(id);
16003
+ visited.add(id);
16004
+ };
16005
+ workflow.rootNodeIds.forEach((id) => visit(id, null));
16006
+ if (visited.size !== Object.keys(workflow.nodesById).length) {
16007
+ throw new Error("Artifact workflow contains unreachable nodes");
16008
+ }
16009
+ if (workflow.currentNodeId && !workflow.nodesById[workflow.currentNodeId]) {
16010
+ throw new Error("Artifact workflow current node does not exist");
16011
+ }
16012
+ const currentNode = workflow.currentNodeId ? workflow.nodesById[workflow.currentNodeId] : null;
16013
+ if (currentNode && currentNode.childIds.length > 0) {
16014
+ throw new Error("Artifact workflow current node must be actionable leaf");
16015
+ }
16016
+ const actionable = nodes.filter(
16017
+ (node) => isArtifactWorkflowNodeNavigableLeaf(workflow, node) && isArtifactWorkflowNodeActionable(node)
16018
+ );
16019
+ if (actionable.length > 1)
16020
+ throw new Error("Artifact workflow has multiple current nodes");
16021
+ if (actionable.length === 1 && actionable[0].id !== workflow.currentNodeId) {
16022
+ throw new Error("Artifact workflow current node does not own its action");
16023
+ }
16024
+ if (workflow.currentNodeId && !actionable.length && !isArtifactWorkflowNodeTerminal(currentNode)) {
16025
+ throw new Error("Artifact workflow current node is not actionable");
16026
+ }
16027
+ }
16028
+ function requireArtifactWorkflow(artifact) {
16029
+ if (!artifact.workflow) {
16030
+ throw new Error(
16031
+ `Artifact ${artifact.artifactId} has no canonical workflow`
16032
+ );
16033
+ }
16034
+ assertArtifactWorkflow(artifact.workflow);
16035
+ if (artifact.workflow.artifactId !== artifact.artifactId) {
16036
+ throw new Error("Artifact workflow belongs to a different artifact");
16037
+ }
16038
+ return artifact;
16039
+ }
16040
+ function requireArtifactWorkflowResult(result) {
16041
+ requireArtifactWorkflow(result.artifact);
16042
+ return result;
16043
+ }
16044
+
15817
16045
  // src/client.ts
15818
16046
  var DEFAULT_CONVERSATION_SESSION_LIST_LIMIT = 100;
15819
16047
  var MAX_CONVERSATION_SESSION_LIST_LIMIT = 500;
@@ -17657,6 +17885,7 @@ var EnvironmentSession = class extends Session {
17657
17885
  sessionDataHeaders;
17658
17886
  beforeMutationSend;
17659
17887
  heartbeatTimer = null;
17888
+ heartbeatProbeInFlight = false;
17660
17889
  /** The last known graph container status, updated by checkReadiness(). */
17661
17890
  graphContainerStatus = null;
17662
17891
  constructor(client, environment, clientId, options = {}) {
@@ -17676,16 +17905,34 @@ var EnvironmentSession = class extends Session {
17676
17905
  * an application RPC may be delayed by Durable Object work even while the
17677
17906
  * underlying WebSocket is healthy.
17678
17907
  */
17679
- startHeartbeat() {
17908
+ startHeartbeat(options = {}) {
17680
17909
  if (this.heartbeatTimer) return;
17681
- this.heartbeatTimer = setInterval(() => {
17682
- if (!this.client.isTransportOpen()) return;
17683
- if (this.client.hasRecentInboundActivity(3e4)) return;
17684
- if (this.client.hasRecentlyPendingRpcPeriod(6e4)) return;
17685
- this.client.reportTransportFailure(
17686
- new Error("No server activity received for 30 seconds")
17687
- );
17688
- }, 5e3);
17910
+ const probeServer = options.probeServer === true;
17911
+ this.heartbeatTimer = setInterval(
17912
+ () => {
17913
+ if (!this.client.isTransportOpen()) return;
17914
+ if (probeServer) {
17915
+ if (this.heartbeatProbeInFlight) return;
17916
+ if (this.client.hasPendingRpcRequests()) return;
17917
+ if (this.client.hasRecentInboundActivity(1e4)) return;
17918
+ this.heartbeatProbeInFlight = true;
17919
+ void this.client.call("client.heartbeat", {}).catch((error) => {
17920
+ if (this.client.isTransportOpen()) {
17921
+ this.client.reportTransportFailure(error);
17922
+ }
17923
+ }).finally(() => {
17924
+ this.heartbeatProbeInFlight = false;
17925
+ });
17926
+ return;
17927
+ }
17928
+ if (this.client.hasRecentInboundActivity(3e4)) return;
17929
+ if (this.client.hasRecentlyPendingRpcPeriod(6e4)) return;
17930
+ this.client.reportTransportFailure(
17931
+ new Error("No server activity received for 30 seconds")
17932
+ );
17933
+ },
17934
+ probeServer ? 1e4 : 5e3
17935
+ );
17689
17936
  this.heartbeatTimer.unref?.();
17690
17937
  }
17691
17938
  stopHeartbeat() {
@@ -17693,10 +17940,15 @@ var EnvironmentSession = class extends Session {
17693
17940
  clearInterval(this.heartbeatTimer);
17694
17941
  this.heartbeatTimer = null;
17695
17942
  }
17943
+ this.heartbeatProbeInFlight = false;
17696
17944
  }
17697
17945
  async hello() {
17698
17946
  const result = await super.hello();
17699
- this.startHeartbeat();
17947
+ if (result.features?.serverHeartbeat === false) {
17948
+ this.startHeartbeat({ probeServer: true });
17949
+ } else {
17950
+ this.startHeartbeat();
17951
+ }
17700
17952
  return result;
17701
17953
  }
17702
17954
  get environmentId() {
@@ -17882,7 +18134,10 @@ var EnvironmentSession = class extends Session {
17882
18134
  queryOptions.targetId = options.target.id;
17883
18135
  delete queryOptions.target;
17884
18136
  }
17885
- return this.sessionDataRequest("/artifacts", queryOptions);
18137
+ return this.sessionDataRequest("/artifacts", queryOptions).then((page) => ({
18138
+ ...page,
18139
+ items: page.items.map(requireArtifactWorkflow)
18140
+ }));
17886
18141
  },
17887
18142
  listForLatestJob: (options = {}) => this.artifacts.list({
17888
18143
  ...options,
@@ -17895,7 +18150,7 @@ var EnvironmentSession = class extends Session {
17895
18150
  // `cache: no-store`; this also bypasses intermediaries that ignore
17896
18151
  // the Fetch cache directive for an otherwise identical GET URL.
17897
18152
  { _granularLiveRead: Date.now() }
17898
- ),
18153
+ ).then(requireArtifactWorkflow),
17899
18154
  create: (artifact) => this.sessionDataRequest(
17900
18155
  "/artifacts",
17901
18156
  void 0,
@@ -17903,7 +18158,7 @@ var EnvironmentSession = class extends Session {
17903
18158
  method: "POST",
17904
18159
  body: artifact
17905
18160
  }
17906
- ),
18161
+ ).then(requireArtifactWorkflow),
17907
18162
  acceptSuggestion: (sequence) => this.sessionDataRequest(
17908
18163
  "/artifacts/suggestions/accept",
17909
18164
  void 0,
@@ -17911,7 +18166,7 @@ var EnvironmentSession = class extends Session {
17911
18166
  method: "POST",
17912
18167
  body: { sequence }
17913
18168
  }
17914
- ),
18169
+ ).then(requireArtifactWorkflow),
17915
18170
  updateInputs: (artifactId, patch) => this.sessionDataRequest(
17916
18171
  `/artifacts/${encodeURIComponent(artifactId)}`,
17917
18172
  void 0,
@@ -17919,7 +18174,7 @@ var EnvironmentSession = class extends Session {
17919
18174
  method: "PATCH",
17920
18175
  body: patch
17921
18176
  }
17922
- ),
18177
+ ).then(requireArtifactWorkflow),
17923
18178
  relationshipOptions: (artifactId, input) => this.sessionDataRequest(
17924
18179
  `/artifacts/${encodeURIComponent(artifactId)}/relationship-options`,
17925
18180
  void 0,
@@ -17929,27 +18184,27 @@ var EnvironmentSession = class extends Session {
17929
18184
  `/artifacts/${encodeURIComponent(artifactId)}/relationship-create`,
17930
18185
  void 0,
17931
18186
  { method: "POST", body: input }
17932
- ),
18187
+ ).then(requireArtifactWorkflow),
17933
18188
  validate: (artifactId) => this.sessionDataRequest(
17934
18189
  `/artifacts/${encodeURIComponent(artifactId)}/validate`,
17935
18190
  void 0,
17936
18191
  { method: "POST" }
17937
- ),
18192
+ ).then(requireArtifactWorkflowResult),
17938
18193
  execute: (artifactId, options) => this.sessionDataRequest(
17939
18194
  `/artifacts/${encodeURIComponent(artifactId)}/execute`,
17940
18195
  void 0,
17941
18196
  { method: "POST", body: options }
17942
- ),
18197
+ ).then(requireArtifactWorkflowResult),
17943
18198
  approve: (artifactId, options) => this.sessionDataRequest(
17944
18199
  `/artifacts/${encodeURIComponent(artifactId)}/approve`,
17945
18200
  void 0,
17946
18201
  { method: "POST", body: options }
17947
- ),
18202
+ ).then(requireArtifactWorkflowResult),
17948
18203
  cancel: (artifactId) => this.sessionDataRequest(
17949
18204
  `/artifacts/${encodeURIComponent(artifactId)}/cancel`,
17950
18205
  void 0,
17951
18206
  { method: "POST" }
17952
- )
18207
+ ).then(requireArtifactWorkflow)
17953
18208
  };
17954
18209
  }
17955
18210
  get manualActions() {
@@ -22663,6 +22918,10 @@ ${actionIndex}
22663
22918
  - 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.
22664
22919
  - 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.
22665
22920
  - 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.
22921
+ - 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.
22922
+ - 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.
22923
+ - 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.
22924
+ - 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.
22666
22925
  - 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.
22667
22926
  - 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.
22668
22927
  - 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.