@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.
package/dist/index.mjs CHANGED
@@ -4278,8 +4278,8 @@ function normalizeFeedItems(values) {
4278
4278
  return { items: Object.freeze(items), error };
4279
4279
  }
4280
4280
  function normalizeTransients(values) {
4281
- const record = asRecord(values);
4282
- if (!record) {
4281
+ const record2 = asRecord(values);
4282
+ if (!record2) {
4283
4283
  return {
4284
4284
  items: [],
4285
4285
  error: new Error("Canonical feed transientById is not an object.")
@@ -4288,7 +4288,7 @@ function normalizeTransients(values) {
4288
4288
  let error = null;
4289
4289
  const byId = /* @__PURE__ */ new Map();
4290
4290
  const idByOrdinal = /* @__PURE__ */ new Map();
4291
- for (const [key, value] of Object.entries(record)) {
4291
+ for (const [key, value] of Object.entries(record2)) {
4292
4292
  const item = normalizeTransientFeedItem(value);
4293
4293
  if (!item || item.id !== key) {
4294
4294
  error ||= new Error("Canonical feed contains an invalid transient item.");
@@ -5161,11 +5161,11 @@ function operationId(prefix) {
5161
5161
  return randomUuid ? `${prefix}_${randomUuid}` : `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2)}`;
5162
5162
  }
5163
5163
  function unwrapPublishedItem(value) {
5164
- const record = asRecord(value);
5165
- const directItems = Array.isArray(record?.items) ? record.items : [];
5166
- const durable = asRecord(record?.durable);
5164
+ const record2 = asRecord(value);
5165
+ const directItems = Array.isArray(record2?.items) ? record2.items : [];
5166
+ const durable = asRecord(record2?.durable);
5167
5167
  const durableItems = Array.isArray(durable?.items) ? durable.items : [];
5168
- return record?.item || directItems[0] || durableItems[0] || value;
5168
+ return record2?.item || directItems[0] || durableItems[0] || value;
5169
5169
  }
5170
5170
  function requireFeedbackItem(value) {
5171
5171
  const item = normalizeFeedItem(unwrapPublishedItem(value));
@@ -5205,14 +5205,14 @@ function createFeedPublisher(publish) {
5205
5205
  get revision() {
5206
5206
  return current.revision;
5207
5207
  },
5208
- update(text, options = {}) {
5208
+ update(text2, options = {}) {
5209
5209
  const reservedOperationId = options.operationId || operationId("feed_transient_update");
5210
5210
  const reservedOptions = { ...options };
5211
5211
  return enqueueMutation(async () => {
5212
5212
  const response = await publish("feed.transient.update", {
5213
5213
  transientId: current.id,
5214
5214
  expectedRevision: current.revision,
5215
- text,
5215
+ text: text2,
5216
5216
  ...reservedOptions,
5217
5217
  operationId: reservedOperationId
5218
5218
  });
@@ -5220,14 +5220,14 @@ function createFeedPublisher(publish) {
5220
5220
  return handle;
5221
5221
  });
5222
5222
  },
5223
- settle(text, options = {}) {
5223
+ settle(text2, options = {}) {
5224
5224
  const reservedOperationId = options.operationId || operationId("feed_transient_settle");
5225
5225
  const reservedOptions = { ...options };
5226
5226
  return enqueueMutation(async () => {
5227
5227
  const response = await publish("feed.transient.settle", {
5228
5228
  transientId: current.id,
5229
5229
  expectedRevision: current.revision,
5230
- ...text === void 0 ? {} : { text },
5230
+ ...text2 === void 0 ? {} : { text: text2 },
5231
5231
  ...reservedOptions,
5232
5232
  operationId: reservedOperationId
5233
5233
  });
@@ -5245,19 +5245,19 @@ function createFeedPublisher(publish) {
5245
5245
  return handle;
5246
5246
  };
5247
5247
  return {
5248
- async feedback(text, options = {}) {
5248
+ async feedback(text2, options = {}) {
5249
5249
  return requireFeedbackItem(
5250
5250
  await publish("feed.feedback", {
5251
- text,
5251
+ text: text2,
5252
5252
  ...options,
5253
5253
  operationId: options.operationId || operationId("feed_feedback")
5254
5254
  })
5255
5255
  );
5256
5256
  },
5257
- async transientFeedback(text, options = {}) {
5257
+ async transientFeedback(text2, options = {}) {
5258
5258
  const item = requireTransientFeedbackItem(
5259
5259
  await publish("feed.transient.create", {
5260
- text,
5260
+ text: text2,
5261
5261
  ...options,
5262
5262
  operationId: options.operationId || operationId("feed_transient_create")
5263
5263
  })
@@ -5287,9 +5287,9 @@ var DEFAULT_MAX_RECONNECT_ATTEMPTS = 5;
5287
5287
  var DOCUMENT_RESYNC_CLOSE_CODE = 4e3;
5288
5288
  var DOCUMENT_RESYNC_CLOSE_REASON = "Document resync required";
5289
5289
  function documentVersion(document) {
5290
- const record = document;
5291
- const epochValue = record.documentEpoch;
5292
- const revisionValue = record.documentRevision;
5290
+ const record2 = document;
5291
+ const epochValue = record2.documentEpoch;
5292
+ const revisionValue = record2.documentRevision;
5293
5293
  const epoch = epochValue === void 0 ? 0 : epochValue;
5294
5294
  const revision = revisionValue === void 0 ? 0 : revisionValue;
5295
5295
  if (!Number.isSafeInteger(epoch) || Number(epoch) < 0 || !Number.isSafeInteger(revision) || Number(revision) < 0) {
@@ -5311,11 +5311,43 @@ function stableDocumentFingerprint(value) {
5311
5311
  if (Array.isArray(value)) {
5312
5312
  return `[${value.map(stableDocumentFingerprint).join(",")}]`;
5313
5313
  }
5314
- const record = value;
5315
- return `{${Object.keys(record).sort().map(
5316
- (key) => `${JSON.stringify(key)}:${stableDocumentFingerprint(record[key])}`
5314
+ const record2 = value;
5315
+ return `{${Object.keys(record2).sort().map(
5316
+ (key) => `${JSON.stringify(key)}:${stableDocumentFingerprint(record2[key])}`
5317
5317
  ).join(",")}}`;
5318
5318
  }
5319
+ function canonicalDocumentFingerprint(value) {
5320
+ const stripPresentation = (input, path) => {
5321
+ if (input === null || typeof input !== "object") return input;
5322
+ if (input instanceof Date || input instanceof Uint8Array) return input;
5323
+ if (Array.isArray(input)) {
5324
+ return input.map((entry) => stripPresentation(entry, path));
5325
+ }
5326
+ const record2 = input;
5327
+ const output = {};
5328
+ for (const [key, entry] of Object.entries(record2)) {
5329
+ if (path.length === 1 && path[0] === "feed" && [
5330
+ "transientById",
5331
+ "feedbackUsageByJob",
5332
+ "revision",
5333
+ "lastTransientOrdinal"
5334
+ ].includes(key)) {
5335
+ continue;
5336
+ }
5337
+ if (path.length === 3 && path[0] === "jobs" && path[1] === "byId" && [
5338
+ "actionTrace",
5339
+ "actionSummary",
5340
+ "actionSummaryText",
5341
+ "lastActionAt"
5342
+ ].includes(key)) {
5343
+ continue;
5344
+ }
5345
+ output[key] = stripPresentation(entry, [...path, key]);
5346
+ }
5347
+ return output;
5348
+ };
5349
+ return stableDocumentFingerprint(stripPresentation(value, []));
5350
+ }
5319
5351
  function debugWs(...args) {
5320
5352
  if (DEBUG_WS) {
5321
5353
  console.log(...args);
@@ -5633,6 +5665,7 @@ var WSClient = class {
5633
5665
  }
5634
5666
  this.lastInboundActivityAt = Date.now();
5635
5667
  this.reconnectAttempts = 0;
5668
+ this.connectPromise = null;
5636
5669
  this.emit("open", {});
5637
5670
  finish();
5638
5671
  };
@@ -5640,8 +5673,8 @@ var WSClient = class {
5640
5673
  if (!isCurrent()) return;
5641
5674
  this.lastInboundActivityAt = Date.now();
5642
5675
  try {
5643
- const text = typeof data === "string" ? data : data && typeof data === "object" && "toString" in data ? String(data.toString()) : "";
5644
- this.handleMessage(JSON.parse(text));
5676
+ const text2 = typeof data === "string" ? data : data && typeof data === "object" && "toString" in data ? String(data.toString()) : "";
5677
+ this.handleMessage(JSON.parse(text2));
5645
5678
  } catch (error) {
5646
5679
  console.error("[Granular] Failed to parse message:", error);
5647
5680
  }
@@ -5699,8 +5732,8 @@ var WSClient = class {
5699
5732
  if (typeof reason === "string") return reason;
5700
5733
  if (typeof reason === "object" && reason && "toString" in reason) {
5701
5734
  try {
5702
- const text = String(reason.toString());
5703
- return text || void 0;
5735
+ const text2 = String(reason.toString());
5736
+ return text2 || void 0;
5704
5737
  } catch {
5705
5738
  return void 0;
5706
5739
  }
@@ -5795,7 +5828,7 @@ var WSClient = class {
5795
5828
  this.canonicalDocumentActivated = true;
5796
5829
  }
5797
5830
  }
5798
- canAcceptDocumentReplacement(replacement, strictlyNewer) {
5831
+ canAcceptDocumentReplacement(replacement, strictlyNewer, options = {}) {
5799
5832
  const replacementCanonical = isCanonicalSessionFeedDocument(replacement);
5800
5833
  const currentCanonical = isCanonicalSessionFeedDocument(this.doc);
5801
5834
  if (replacementCanonical) {
@@ -5844,7 +5877,7 @@ var WSClient = class {
5844
5877
  debugWs(`[Granular DEBUG] Rejected session snapshot: ${canonicalError}`);
5845
5878
  return false;
5846
5879
  }
5847
- if (this.canonicalDocumentActivated && incoming.epoch === current.epoch && incoming.revision === current.revision && !readSessionFeedSnapshot(this.doc).error && stableDocumentFingerprint(Automerge.toJS(replacement)) !== stableDocumentFingerprint(Automerge.toJS(this.doc))) {
5880
+ 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)))) {
5848
5881
  debugWs(
5849
5882
  "[Granular DEBUG] Rejected divergent canonical snapshot at the accepted document version."
5850
5883
  );
@@ -5966,6 +5999,7 @@ var WSClient = class {
5966
5999
  reconnectScheduled: false
5967
6000
  };
5968
6001
  this.ws = null;
6002
+ this.connectPromise = null;
5969
6003
  this.emit("close", info);
5970
6004
  if (this.reconnectTimer) {
5971
6005
  clearTimeout(this.reconnectTimer);
@@ -6003,13 +6037,89 @@ var WSClient = class {
6003
6037
  );
6004
6038
  return;
6005
6039
  }
6040
+ if ("type" in message && message.type === "job_action_trace") {
6041
+ const update = message;
6042
+ 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(
6043
+ (event) => event && typeof event === "object" && typeof event.id === "string" && typeof event.timestamp === "number" && Number.isFinite(event.timestamp)
6044
+ ) || !Array.isArray(update.actionSummary) || update.actionSummary.length > 16 || !update.actionSummary.every((line) => typeof line === "string") || typeof update.actionSummaryText !== "string" || update.actionSummaryText.length > 8e3) {
6045
+ console.warn("[Granular] Ignoring invalid job_action_trace message");
6046
+ return;
6047
+ }
6048
+ const currentVersion = documentVersion(this.doc);
6049
+ if (update.documentEpoch < currentVersion.epoch || update.documentEpoch === currentVersion.epoch && update.documentRevision < currentVersion.revision) {
6050
+ return;
6051
+ }
6052
+ if (update.documentEpoch > currentVersion.epoch || update.documentEpoch === currentVersion.epoch && update.documentRevision > currentVersion.revision) {
6053
+ return;
6054
+ }
6055
+ const current = Automerge.toJS(this.doc);
6056
+ const job = current.jobs?.byId?.[update.jobId];
6057
+ if (job && typeof job === "object" && !Array.isArray(job)) {
6058
+ job.actionTrace = update.actionTrace;
6059
+ job.actionSummary = update.actionSummary;
6060
+ job.actionSummaryText = update.actionSummaryText;
6061
+ const lastEvent = update.actionTrace[update.actionTrace.length - 1];
6062
+ if (lastEvent && typeof lastEvent.timestamp === "number") {
6063
+ job.lastActionAt = lastEvent.timestamp;
6064
+ }
6065
+ this.doc = Automerge.from(current);
6066
+ this.emit("job_action_trace", update);
6067
+ this.emit("sync", this.doc);
6068
+ }
6069
+ return;
6070
+ }
6071
+ if ("type" in message && message.type === "feed_transient_patch") {
6072
+ const update = message;
6073
+ 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) => {
6074
+ if (!change || typeof change !== "object" || typeof change.transientId !== "string" || change.transientId.length === 0) {
6075
+ return false;
6076
+ }
6077
+ if (change.removed === true) return change.item === void 0;
6078
+ const item = change.item;
6079
+ return Boolean(
6080
+ item && typeof item === "object" && !Array.isArray(item) && item.id === change.transientId
6081
+ );
6082
+ })) {
6083
+ console.warn("[Granular] Ignoring invalid feed_transient_patch");
6084
+ return;
6085
+ }
6086
+ const currentVersion = documentVersion(this.doc);
6087
+ 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) {
6088
+ return;
6089
+ }
6090
+ const current = Automerge.toJS(this.doc);
6091
+ const feed = current.feed;
6092
+ if (!feed || typeof feed !== "object" || Array.isArray(feed)) return;
6093
+ if (Number.isSafeInteger(feed.revision) && feed.revision > update.feedRevision) {
6094
+ return;
6095
+ }
6096
+ if (Number.isSafeInteger(feed.lastTransientOrdinal) && feed.lastTransientOrdinal > update.lastTransientOrdinal) {
6097
+ return;
6098
+ }
6099
+ if (!feed.transientById || typeof feed.transientById !== "object") {
6100
+ feed.transientById = {};
6101
+ }
6102
+ for (const change of update.changes) {
6103
+ if (change.removed === true) {
6104
+ delete feed.transientById[change.transientId];
6105
+ } else {
6106
+ feed.transientById[change.transientId] = change.item;
6107
+ }
6108
+ }
6109
+ feed.revision = update.feedRevision;
6110
+ feed.lastTransientOrdinal = update.lastTransientOrdinal;
6111
+ this.doc = Automerge.from(current);
6112
+ this.emit("feed_transient_patch", update);
6113
+ this.emit("sync", this.doc);
6114
+ return;
6115
+ }
6006
6116
  if ("type" in message && message.type === "snapshot_reset") {
6007
6117
  const resetMessage = message;
6008
6118
  try {
6009
- if (!Number.isSafeInteger(resetMessage.documentEpoch) || resetMessage.documentEpoch <= 0 || !Number.isSafeInteger(resetMessage.documentRevision) || resetMessage.documentRevision < 0 || !Array.isArray(resetMessage.data)) {
6119
+ 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))) {
6010
6120
  throw new Error("snapshot_reset metadata is invalid");
6011
6121
  }
6012
- const replacement = Automerge.load(
6122
+ const replacement = resetMessage.document ? Automerge.from(resetMessage.document) : Automerge.load(
6013
6123
  this.decodeDocumentBytes(
6014
6124
  resetMessage.data,
6015
6125
  "Automerge snapshot_reset"
@@ -6023,7 +6133,10 @@ var WSClient = class {
6023
6133
  "snapshot_reset metadata does not match the saved document"
6024
6134
  );
6025
6135
  }
6026
- if (!this.canAcceptDocumentReplacement(replacement, true)) {
6136
+ const isJsonReplacement = Boolean(resetMessage.document);
6137
+ if (!this.canAcceptDocumentReplacement(replacement, !isJsonReplacement, {
6138
+ allowEqualRevisionPresentation: isJsonReplacement
6139
+ })) {
6027
6140
  if (this.documentRepairRequired) {
6028
6141
  this.requireDocumentResync(
6029
6142
  "Stale Automerge snapshot_reset during document repair",
@@ -6316,30 +6429,30 @@ function parseJsonPromptChoiceOption(option) {
6316
6429
  }
6317
6430
  function normalizePromptChoiceOption(option) {
6318
6431
  if (typeof option === "string") {
6319
- const record2 = parseJsonPromptChoiceOption(option);
6320
- if (!record2) {
6432
+ const record3 = parseJsonPromptChoiceOption(option);
6433
+ if (!record3) {
6321
6434
  return { value: option, label: option };
6322
6435
  }
6323
- const value2 = typeof record2.value === "string" ? record2.value : typeof record2.id === "string" ? record2.id : typeof record2.label === "string" ? record2.label : JSON.stringify(record2);
6436
+ const value2 = typeof record3.value === "string" ? record3.value : typeof record3.id === "string" ? record3.id : typeof record3.label === "string" ? record3.label : JSON.stringify(record3);
6324
6437
  return {
6325
6438
  value: value2,
6326
- label: typeof record2.label === "string" ? record2.label : value2,
6327
- description: typeof record2.description === "string" ? record2.description : void 0
6439
+ label: typeof record3.label === "string" ? record3.label : value2,
6440
+ description: typeof record3.description === "string" ? record3.description : void 0
6328
6441
  };
6329
6442
  }
6330
- const record = option;
6331
- if (!record) {
6443
+ const record2 = option;
6444
+ if (!record2) {
6332
6445
  return { value: "", label: "" };
6333
6446
  }
6334
- const nestedJson = (typeof record.value === "string" ? parseJsonPromptChoiceOption(record.value) : null) || (typeof record.label === "string" ? parseJsonPromptChoiceOption(record.label) : null);
6447
+ const nestedJson = (typeof record2.value === "string" ? parseJsonPromptChoiceOption(record2.value) : null) || (typeof record2.label === "string" ? parseJsonPromptChoiceOption(record2.label) : null);
6335
6448
  if (nestedJson) {
6336
6449
  return normalizePromptChoiceOption(nestedJson);
6337
6450
  }
6338
- const value = typeof record.value === "string" ? record.value : typeof record.label === "string" ? record.label : JSON.stringify(record);
6451
+ const value = typeof record2.value === "string" ? record2.value : typeof record2.label === "string" ? record2.label : JSON.stringify(record2);
6339
6452
  return {
6340
6453
  value,
6341
- label: typeof record.label === "string" ? record.label : value,
6342
- description: typeof record.description === "string" ? record.description : void 0
6454
+ label: typeof record2.label === "string" ? record2.label : value,
6455
+ description: typeof record2.description === "string" ? record2.description : void 0
6343
6456
  };
6344
6457
  }
6345
6458
  function scorePromptChoiceMatch(answer, answerTokens, option) {
@@ -6631,18 +6744,18 @@ var Session = class {
6631
6744
  get revision() {
6632
6745
  return current.revision;
6633
6746
  },
6634
- async update(text, options = {}) {
6747
+ async update(text2, options = {}) {
6635
6748
  current = await track(
6636
- current.update(text, {
6749
+ current.update(text2, {
6637
6750
  ...options,
6638
6751
  operationId: options.operationId || nextOperationId("transient-update")
6639
6752
  })
6640
6753
  );
6641
6754
  return wrapped;
6642
6755
  },
6643
- settle(text, options = {}) {
6756
+ settle(text2, options = {}) {
6644
6757
  return track(
6645
- current.settle(text, {
6758
+ current.settle(text2, {
6646
6759
  ...options,
6647
6760
  operationId: options.operationId || nextOperationId("transient-settle")
6648
6761
  })
@@ -6652,14 +6765,14 @@ var Session = class {
6652
6765
  return wrapped;
6653
6766
  };
6654
6767
  return {
6655
- feedback: (text, options = {}) => track(
6656
- publisher.feedback(text, {
6768
+ feedback: (text2, options = {}) => track(
6769
+ publisher.feedback(text2, {
6657
6770
  ...options,
6658
6771
  operationId: options.operationId || nextOperationId("feedback")
6659
6772
  })
6660
6773
  ),
6661
- transientFeedback: (text, options = {}) => track(
6662
- publisher.transientFeedback(text, {
6774
+ transientFeedback: (text2, options = {}) => track(
6775
+ publisher.transientFeedback(text2, {
6663
6776
  ...options,
6664
6777
  operationId: options.operationId || nextOperationId("transient-create")
6665
6778
  }).then(wrapTransientHandle)
@@ -7847,8 +7960,8 @@ function artifactRecordsById(liveDoc) {
7847
7960
  const byId = asRecord3(artifacts?.byId) || {};
7848
7961
  return Object.fromEntries(
7849
7962
  Object.entries(byId).map(([artifactId, value]) => {
7850
- const record = asRecord3(value);
7851
- return record ? [artifactId, record] : null;
7963
+ const record2 = asRecord3(value);
7964
+ return record2 ? [artifactId, record2] : null;
7852
7965
  }).filter((entry) => Boolean(entry))
7853
7966
  );
7854
7967
  }
@@ -7881,8 +7994,8 @@ function latestInputEditSummary(metadata) {
7881
7994
  ].filter(Boolean);
7882
7995
  return `lastEdit=${source}${actor ? ` by ${actor}` : ""}${changed.length ? ` (${changed.join("; ")})` : ""}`;
7883
7996
  }
7884
- function artifactIssueSummary(record) {
7885
- const validation = asRecord3(record.validation);
7997
+ function artifactIssueSummary(record2) {
7998
+ const validation = asRecord3(record2.validation);
7886
7999
  if (!validation) return null;
7887
8000
  const issues = Array.isArray(validation.issues) ? validation.issues.map((issue) => asRecord3(issue)).filter((issue) => Boolean(issue)).slice(0, 3) : [];
7888
8001
  if (issues.length > 0) {
@@ -7929,18 +8042,18 @@ function artifactStatePathSummary(metadata) {
7929
8042
  ].filter(Boolean);
7930
8043
  return pieces.length ? pieces.join("; ") : null;
7931
8044
  }
7932
- function artifactSummaryLine(artifactId, record) {
7933
- if (!record) return `- ${artifactId}: unavailable in session artifact store`;
7934
- const label = trimString(record.label) || artifactId;
7935
- const kind = asString(record.kind) || "artifact";
7936
- const status = asString(record.status) || "unknown";
7937
- const createdByJobId = asString(record.createdByJobId);
7938
- const target = asRecord3(record.target);
7939
- const metadata = asRecord3(record.metadata) || {};
7940
- const subArtifactIds = Array.isArray(record.subArtifactIds) ? record.subArtifactIds.filter(
8045
+ function artifactSummaryLine(artifactId, record2) {
8046
+ if (!record2) return `- ${artifactId}: unavailable in session artifact store`;
8047
+ const label = trimString(record2.label) || artifactId;
8048
+ const kind = asString(record2.kind) || "artifact";
8049
+ const status = asString(record2.status) || "unknown";
8050
+ const createdByJobId = asString(record2.createdByJobId);
8051
+ const target = asRecord3(record2.target);
8052
+ const metadata = asRecord3(record2.metadata) || {};
8053
+ const subArtifactIds = Array.isArray(record2.subArtifactIds) ? record2.subArtifactIds.filter(
7941
8054
  (id) => typeof id === "string" && id.trim().length > 0
7942
8055
  ).slice(0, 8) : [];
7943
- const relationships = compactJson(record.relationships, 220);
8056
+ const relationships = compactJson(record2.relationships, 220);
7944
8057
  const pieces = [
7945
8058
  `kind=${kind}`,
7946
8059
  `status=${status}`,
@@ -7948,7 +8061,7 @@ function artifactSummaryLine(artifactId, record) {
7948
8061
  target ? `target=${asString(target.className) || "record"}:${asString(target.id) || "unknown"}${asString(target.label) ? ` (${asString(target.label)})` : ""}` : null,
7949
8062
  artifactStatePathSummary(metadata),
7950
8063
  artifactExecutionSummary(metadata),
7951
- artifactIssueSummary(record),
8064
+ artifactIssueSummary(record2),
7952
8065
  latestInputEditSummary(metadata),
7953
8066
  subArtifactIds.length ? `subArtifacts=${subArtifactIds.join(",")}` : null,
7954
8067
  relationships ? `relationships=${relationships}` : null
@@ -12368,6 +12481,7 @@ var StateMachineTransitionSchema = external_exports.object({
12368
12481
  to: external_exports.string().min(1),
12369
12482
  label: external_exports.string().optional(),
12370
12483
  description: external_exports.string().optional(),
12484
+ userVisible: external_exports.boolean().optional(),
12371
12485
  action: StateTransitionActionSchema.optional(),
12372
12486
  assignee: StateTransitionAssigneeSchema.optional(),
12373
12487
  requirements: StateTransitionRequirementsSchema.optional(),
@@ -12452,6 +12566,7 @@ var CreatesSchema = external_exports.union([
12452
12566
  idPath: external_exports.string().min(1).optional(),
12453
12567
  pathPath: external_exports.string().min(1).optional(),
12454
12568
  statePath: external_exports.string().min(1).optional(),
12569
+ completionState: external_exports.string().min(1).optional(),
12455
12570
  classStateHandle: external_exports.boolean().optional()
12456
12571
  }).strict()
12457
12572
  ]);
@@ -12801,6 +12916,7 @@ function normalizeCreationSummary(metamodels) {
12801
12916
  ...typeof raw.idPath === "string" && raw.idPath.trim() ? { idPath: raw.idPath.trim() } : {},
12802
12917
  ...typeof raw.pathPath === "string" && raw.pathPath.trim() ? { pathPath: raw.pathPath.trim() } : {},
12803
12918
  ...typeof raw.statePath === "string" && raw.statePath.trim() ? { statePath: raw.statePath.trim() } : {},
12919
+ ...typeof raw.completionState === "string" && raw.completionState.trim() ? { completionState: raw.completionState.trim() } : {},
12804
12920
  ...typeof raw.classStateHandle === "boolean" ? { classStateHandle: raw.classStateHandle } : {}
12805
12921
  };
12806
12922
  }
@@ -12926,7 +13042,7 @@ var effectBehaviorsMetamodelPackage = defineMetamodelPackage({
12926
13042
  },
12927
13043
  {
12928
13044
  key: "creates",
12929
- description: 'Marks a static method as an allowed creator for a class. Use `creates: "class_name"` or `{ className, idPath, pathPath, statePath, classStateHandle }`.'
13045
+ description: 'Marks a static method as an allowed creator for a class. Use `creates: "class_name"` or `{ className, idPath, pathPath, statePath, completionState, classStateHandle }`.'
12930
13046
  }
12931
13047
  ]
12932
13048
  },
@@ -13149,18 +13265,18 @@ function validateScalarRecord(value, path) {
13149
13265
  }
13150
13266
  function validateProjectedRecord(value, path) {
13151
13267
  validateObjectReference(value, path);
13152
- const record = value;
13153
- if (record.label !== void 0) {
13154
- if (typeof record.label !== "string") {
13268
+ const record2 = value;
13269
+ if (record2.label !== void 0) {
13270
+ if (typeof record2.label !== "string") {
13155
13271
  throw new Error(`${path}.label must be a string`);
13156
13272
  }
13157
- if (record.label.length > MAX_SAFE_TEXT_LENGTH) {
13273
+ if (record2.label.length > MAX_SAFE_TEXT_LENGTH) {
13158
13274
  throw new Error(
13159
13275
  `${path}.label exceeds ${MAX_SAFE_TEXT_LENGTH} characters`
13160
13276
  );
13161
13277
  }
13162
13278
  }
13163
- validateScalarRecord(record.fields, `${path}.fields`);
13279
+ validateScalarRecord(record2.fields, `${path}.fields`);
13164
13280
  }
13165
13281
  function validateBoundedJson(value, path, depth = 0) {
13166
13282
  if (depth > 12) throw new Error(`${path} is nested too deeply`);
@@ -13210,6 +13326,11 @@ function validateProjectionResult(declaration, projection) {
13210
13326
  "projection.source.version"
13211
13327
  );
13212
13328
  }
13329
+ if (projection.source.versionScope !== void 0 && projection.source.versionScope !== "global" && projection.source.versionScope !== "target") {
13330
+ throw new Error(
13331
+ "projection.source.versionScope must be 'global' or 'target'"
13332
+ );
13333
+ }
13213
13334
  if (!Array.isArray(projection.changes)) {
13214
13335
  throw new Error("projection.changes must be an array");
13215
13336
  }
@@ -13573,18 +13694,18 @@ function createInvocationFeedbackContext(bridge) {
13573
13694
  get revision() {
13574
13695
  return current.revision;
13575
13696
  },
13576
- async update(text, options = {}) {
13697
+ async update(text2, options = {}) {
13577
13698
  current = await track(
13578
- current.update(text, {
13699
+ current.update(text2, {
13579
13700
  ...options,
13580
13701
  operationId: options.operationId || nextOperationId("transient-update")
13581
13702
  })
13582
13703
  );
13583
13704
  return wrapped;
13584
13705
  },
13585
- settle(text, options = {}) {
13706
+ settle(text2, options = {}) {
13586
13707
  return track(
13587
- current.settle(text, {
13708
+ current.settle(text2, {
13588
13709
  ...options,
13589
13710
  operationId: options.operationId || nextOperationId("transient-settle")
13590
13711
  })
@@ -13594,17 +13715,17 @@ function createInvocationFeedbackContext(bridge) {
13594
13715
  return wrapped;
13595
13716
  };
13596
13717
  return {
13597
- feedback(text, options = {}) {
13718
+ feedback(text2, options = {}) {
13598
13719
  return track(
13599
- publisher.feedback(text, {
13720
+ publisher.feedback(text2, {
13600
13721
  ...options,
13601
13722
  operationId: options.operationId || nextOperationId("feedback")
13602
13723
  })
13603
13724
  );
13604
13725
  },
13605
- transientFeedback(text, options = {}) {
13726
+ transientFeedback(text2, options = {}) {
13606
13727
  return track(
13607
- publisher.transientFeedback(text, {
13728
+ publisher.transientFeedback(text2, {
13608
13729
  ...options,
13609
13730
  operationId: options.operationId || nextOperationId("transient-create")
13610
13731
  }).then(wrapTransientHandle)
@@ -14452,9 +14573,9 @@ var noteMetamodelPackage = defineMetamodelPackage({
14452
14573
  notes: async (ant) => await run(ant.notes())
14453
14574
  },
14454
14575
  ModelMutation: {
14455
- add_note: async (ant, { text }) => {
14456
- const model = await run(ant.add_note(text));
14457
- return { model, text };
14576
+ add_note: async (ant, { text: text2 }) => {
14577
+ const model = await run(ant.add_note(text2));
14578
+ return { model, text: text2 };
14458
14579
  }
14459
14580
  }
14460
14581
  };
@@ -14867,6 +14988,7 @@ function normalizeStateMachines(values) {
14867
14988
  to: String(transition?.to?.name || ""),
14868
14989
  label: typeof transition?.label === "string" ? transition.label : null,
14869
14990
  description: typeof transition?.description === "string" ? transition.description : null,
14991
+ userVisible: typeof transition?.user_visible === "boolean" ? transition.user_visible : typeof transition?.userVisible === "boolean" ? transition.userVisible : null,
14870
14992
  action: parseJsonRecord(transition?.action) || parseJsonRecord(transition?.action_json),
14871
14993
  assignee: parseJsonRecord(transition?.assignee) || parseJsonRecord(transition?.assignee_json),
14872
14994
  requirements: parseJsonRecord(transition?.requirements) || parseJsonRecord(transition?.requirements_json),
@@ -14938,6 +15060,9 @@ function transitionMetadataGraphqlArgs(transition) {
14938
15060
  if (typeof transition.description === "string") {
14939
15061
  args.push(`description: ${JSON.stringify(transition.description)}`);
14940
15062
  }
15063
+ if (typeof transition.userVisible === "boolean") {
15064
+ args.push(`user_visible: ${transition.userVisible}`);
15065
+ }
14941
15066
  if (transition.action) {
14942
15067
  args.push(
14943
15068
  `action_json: ${JSON.stringify(JSON.stringify(transition.action))}`
@@ -15249,7 +15374,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15249
15374
  name: String!
15250
15375
  state_machine: StateMachine!
15251
15376
  add_state(name: String!, is_final: Boolean, label: String, description: String): StateMachineMutation!
15252
- 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!
15377
+ 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!
15253
15378
  activate_transition(name: String!): StateMachineMutation!
15254
15379
  }
15255
15380
 
@@ -15307,6 +15432,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15307
15432
  to: StateMachineState!
15308
15433
  label: String
15309
15434
  description: String
15435
+ user_visible: Boolean
15310
15436
  action_json: String
15311
15437
  assignee_json: String
15312
15438
  requirements_json: String
@@ -15388,6 +15514,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15388
15514
  to,
15389
15515
  label,
15390
15516
  description,
15517
+ user_visible,
15391
15518
  action_json,
15392
15519
  assignee_json,
15393
15520
  requirements_json,
@@ -15405,6 +15532,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15405
15532
  {
15406
15533
  label,
15407
15534
  description,
15535
+ userVisible: user_visible,
15408
15536
  actionJson: action_json,
15409
15537
  assigneeJson: assignee_json,
15410
15538
  requirementsJson: requirements_json,
@@ -15469,6 +15597,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15469
15597
  to: (value) => value.to_state || { name: value.to, is_final: false },
15470
15598
  label: (value) => value.label || null,
15471
15599
  description: (value) => value.description || null,
15600
+ user_visible: (value) => value.user_visible ?? null,
15472
15601
  action_json: (value) => value.action_json || null,
15473
15602
  assignee_json: (value) => value.assignee_json || null,
15474
15603
  requirements_json: (value) => value.requirements_json || null,
@@ -15555,6 +15684,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15555
15684
  to { name }
15556
15685
  label
15557
15686
  description
15687
+ user_visible
15558
15688
  action_json
15559
15689
  assignee_json
15560
15690
  requirements_json
@@ -15962,6 +16092,786 @@ function buildEffectMetamodelMutations(toolPath, spec) {
15962
16092
  );
15963
16093
  }
15964
16094
 
16095
+ // src/artifact-workflow.ts
16096
+ var TERMINAL_ARTIFACT_STATUSES = /* @__PURE__ */ new Set([
16097
+ "completed",
16098
+ "partially_completed",
16099
+ "failed",
16100
+ "canceled"
16101
+ ]);
16102
+ function record(value) {
16103
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
16104
+ }
16105
+ function text(value) {
16106
+ return typeof value === "string" && value.trim() ? value.trim() : null;
16107
+ }
16108
+ function humanize(value) {
16109
+ const words = value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
16110
+ return words ? words[0].toUpperCase() + words.slice(1) : "Details";
16111
+ }
16112
+ function stableToken(value) {
16113
+ return encodeURIComponent(value.trim().toLowerCase());
16114
+ }
16115
+ function isMissing(value) {
16116
+ return value === null || value === void 0 || typeof value === "string" && !value.trim() || Array.isArray(value) && value.length === 0;
16117
+ }
16118
+ function displayValue(value) {
16119
+ if (typeof value === "string") return value;
16120
+ if (typeof value === "number" || typeof value === "boolean") {
16121
+ return String(value);
16122
+ }
16123
+ if (Array.isArray(value)) {
16124
+ return value.map(displayValue).filter(Boolean).join(", ");
16125
+ }
16126
+ return "Provided";
16127
+ }
16128
+ function artifactFields(artifact) {
16129
+ return Array.isArray(artifact.inputSchema?.fields) ? artifact.inputSchema.fields.filter(
16130
+ (field) => Boolean(record(field))
16131
+ ) : [];
16132
+ }
16133
+ function declaredSections(artifact) {
16134
+ const fields = artifactFields(artifact);
16135
+ if (!fields.length) return [];
16136
+ const knownNames = new Set(
16137
+ fields.map((field) => text(field.name)).filter((name) => Boolean(name))
16138
+ );
16139
+ const ui = record(artifact.inputSchema?.ui);
16140
+ const workflow = record(ui?.workflow);
16141
+ const rawSections = Array.isArray(workflow?.steps) ? workflow.steps : Array.isArray(ui?.sections) ? ui.sections : [];
16142
+ const used = /* @__PURE__ */ new Set();
16143
+ const sections = rawSections.flatMap((value) => {
16144
+ const section = record(value);
16145
+ const id = text(section?.id);
16146
+ const label = text(section?.label);
16147
+ const fieldNames = Array.isArray(section?.fieldNames) ? section.fieldNames.map(text).filter(
16148
+ (name) => name !== null && knownNames.has(name) && !used.has(name)
16149
+ ) : [];
16150
+ if (!id || !label || !fieldNames.length) return [];
16151
+ fieldNames.forEach((name) => used.add(name));
16152
+ return [{ id, label, description: text(section?.description), fieldNames }];
16153
+ });
16154
+ const remaining = [...knownNames].filter((name) => !used.has(name));
16155
+ const chunkSize = 5;
16156
+ const chunkCount = Math.ceil(remaining.length / chunkSize);
16157
+ const hasDeclaredSections = sections.length > 0;
16158
+ for (let index = 0; index < chunkCount; index += 1) {
16159
+ const baseLabel = hasDeclaredSections ? "Other details" : "Details";
16160
+ sections.push({
16161
+ id: chunkCount === 1 ? hasDeclaredSections ? "other-details" : "details" : `${hasDeclaredSections ? "other-details" : "details"}-${index + 1}`,
16162
+ label: chunkCount === 1 ? baseLabel : `${baseLabel} ${index + 1}`,
16163
+ description: null,
16164
+ fieldNames: remaining.slice(index * chunkSize, (index + 1) * chunkSize)
16165
+ });
16166
+ }
16167
+ return sections;
16168
+ }
16169
+ function fieldValue(artifact, fieldName) {
16170
+ const field = artifactFields(artifact).find(
16171
+ (candidate) => text(candidate.name) === fieldName
16172
+ );
16173
+ const kind = text(field?.kind);
16174
+ return kind === "relationshipOne" || kind === "relationshipMany" ? artifact.relationships?.[fieldName] : artifact.inputValues?.[fieldName];
16175
+ }
16176
+ function conditionallyRequired(artifact, field) {
16177
+ const raw = field.requiredWhen ?? field.required_when;
16178
+ const conditions = Array.isArray(raw) ? raw : raw ? [raw] : [];
16179
+ return conditions.length > 0 && conditions.every((value) => {
16180
+ const condition = record(value);
16181
+ const dependency = text(condition?.field);
16182
+ return Boolean(dependency) && Object.prototype.hasOwnProperty.call(condition || {}, "equals") && Object.is(fieldValue(artifact, dependency), condition.equals);
16183
+ });
16184
+ }
16185
+ function fieldValidationRules(field) {
16186
+ const raw = Array.isArray(field.validationRules) ? field.validationRules : Array.isArray(field.validation_rules) ? field.validation_rules : Array.isArray(field.validate) ? field.validate : [];
16187
+ return raw.filter(
16188
+ (rule) => Boolean(record(rule)) && typeof record(rule)?.operator === "string"
16189
+ );
16190
+ }
16191
+ function fieldProvenanceNeedsReview(artifact, field) {
16192
+ const fieldName = text(field.name);
16193
+ if (!fieldName) return false;
16194
+ const namespace = text(field.kind)?.startsWith("relationship") ? "relationships" : "inputValues";
16195
+ const provenance = artifact.fieldProvenance?.[`${namespace}.${fieldName}`];
16196
+ if (!provenance) return false;
16197
+ const confirmedAt = typeof provenance.confirmedAt === "number" ? provenance.confirmedAt : null;
16198
+ const updatedAt = typeof provenance.updatedAt === "number" ? provenance.updatedAt : null;
16199
+ const invalidatedAt = typeof provenance.invalidatedAt === "number" ? provenance.invalidatedAt : null;
16200
+ const explicitlyConfirmed = confirmedAt !== null && (updatedAt === null || confirmedAt >= updatedAt) && (invalidatedAt === null || confirmedAt >= invalidatedAt);
16201
+ if (explicitlyConfirmed) return false;
16202
+ const invalidated = invalidatedAt !== null && (confirmedAt === null || confirmedAt < invalidatedAt);
16203
+ const assistantSource = [
16204
+ "user_statement",
16205
+ "agent_extraction",
16206
+ "agent_inference",
16207
+ "provider_lookup",
16208
+ "ontology_default"
16209
+ ].includes(provenance.source);
16210
+ return invalidated || assistantSource && provenance.confidence === "ambiguous";
16211
+ }
16212
+ function sectionComplete(artifact, fieldNames) {
16213
+ const schema = record(artifact.inputSchema);
16214
+ const declaredRequiredNames = new Set(
16215
+ [
16216
+ ...Array.isArray(schema?.required) ? schema.required : [],
16217
+ ...Array.isArray(schema?.["x-granular-artifact-required"]) ? schema["x-granular-artifact-required"] : []
16218
+ ].map(text).filter((name) => Boolean(name))
16219
+ );
16220
+ const validation = record(artifact.validation);
16221
+ const invalidFieldNames = new Set(
16222
+ (Array.isArray(validation?.issues) ? validation.issues : []).map(record).filter((issue) => Boolean(issue)).map((issue) => text(issue.path) || text(issue.field)).filter((path) => Boolean(path)).map((path) => path.replace(/^(inputValues|relationships)\./, ""))
16223
+ );
16224
+ const fieldsByName = new Map(
16225
+ artifactFields(artifact).map((field) => [text(field.name), field])
16226
+ );
16227
+ return fieldNames.every((fieldName) => {
16228
+ if (invalidFieldNames.has(fieldName)) return false;
16229
+ const field = fieldsByName.get(fieldName);
16230
+ if (!field) return true;
16231
+ if (fieldProvenanceNeedsReview(artifact, field)) return false;
16232
+ const required = declaredRequiredNames.has(fieldName) || field.required === true || conditionallyRequired(artifact, field);
16233
+ const value = fieldValue(artifact, fieldName);
16234
+ const provenance = artifact.fieldProvenance?.[`${text(field.kind)?.startsWith("relationship") ? "relationships" : "inputValues"}.${fieldName}`];
16235
+ if (provenance?.invalidatedAt) return false;
16236
+ if (text(field.kind)?.startsWith("relationship")) {
16237
+ const resolvers = record(
16238
+ record(artifact.metadata)?.relationshipResolvers
16239
+ );
16240
+ const resolverStatus = text(record(resolvers?.[fieldName])?.status);
16241
+ if (resolverStatus === "missing") return false;
16242
+ if (resolverStatus === "choice" && isMissing(value)) return false;
16243
+ }
16244
+ if (required && isMissing(value)) return false;
16245
+ if (isMissing(value)) return true;
16246
+ return fieldValidationRules(field).every(
16247
+ (rule) => evaluateValidationRule(value, rule).passed
16248
+ );
16249
+ });
16250
+ }
16251
+ function sectionRecap(artifact, fieldNames) {
16252
+ const fields = artifactFields(artifact);
16253
+ const fieldsByName = new Map(
16254
+ fields.map((field) => [text(field.name), field])
16255
+ );
16256
+ const labels = new Map(
16257
+ fields.map((field) => [
16258
+ text(field.name),
16259
+ text(field.label) || humanize(text(field.name) || "detail")
16260
+ ])
16261
+ );
16262
+ const items = fieldNames.flatMap((fieldName) => {
16263
+ const value = fieldValue(artifact, fieldName);
16264
+ if (isMissing(value)) return [];
16265
+ const field = fieldsByName.get(fieldName);
16266
+ const relationshipLabels = record(
16267
+ record(artifact.metadata)?.relationshipLabels
16268
+ );
16269
+ const fieldLabels = record(relationshipLabels?.[fieldName]);
16270
+ const resolvers = record(record(artifact.metadata)?.relationshipResolvers);
16271
+ const candidates = Array.isArray(record(resolvers?.[fieldName])?.candidates) ? record(resolvers?.[fieldName])?.candidates : [];
16272
+ const candidateLabel = candidates.map(record).find((candidate) => text(candidate?.id) === value)?.label;
16273
+ const rawOptions = Array.isArray(field?.enumOptions) ? field.enumOptions : Array.isArray(field?.options) ? field.options : [];
16274
+ const enumLabel = rawOptions.map(
16275
+ (option) => typeof option === "string" ? { value: option, label: humanize(option) } : record(option)
16276
+ ).find((option) => text(option?.value) === value)?.label;
16277
+ const rendered = typeof value === "string" && text(fieldLabels?.[value]) ? text(fieldLabels?.[value]) : typeof value === "string" && text(candidateLabel) ? text(candidateLabel) : typeof value === "string" && text(enumLabel) ? text(enumLabel) : typeof value === "string" && (text(field?.kind) === "enum" || text(field?.kind)?.startsWith("relationship")) ? humanize(value) : displayValue(value);
16278
+ return [
16279
+ { label: labels.get(fieldName) || humanize(fieldName), value: rendered }
16280
+ ];
16281
+ });
16282
+ return {
16283
+ summary: items.length ? items.map((item) => `${item.label}: ${item.value}`).join(" \xB7 ") : "Completed",
16284
+ items
16285
+ };
16286
+ }
16287
+ function operationPhase(artifact) {
16288
+ const execution = record(record(artifact.metadata)?.execution);
16289
+ const phase = text(execution?.phase);
16290
+ if (artifact.status === "completed") return "completed";
16291
+ if (artifact.status === "failed") return "failed";
16292
+ if (artifact.status === "awaiting_human") return "waiting_for_approval";
16293
+ if (phase === "preparing") return "reviewing";
16294
+ if (phase === "syncing_record") return "synchronizing";
16295
+ if (phase === "verifying_target") return "verifying";
16296
+ if (artifact.status === "running") return "sending";
16297
+ return null;
16298
+ }
16299
+ function operationLabel(artifact) {
16300
+ const ui = record(artifact.inputSchema?.ui);
16301
+ const workflow = record(ui?.workflow);
16302
+ const effect = record(record(artifact.metadata)?.effect);
16303
+ return text(workflow?.operationLabel) || text(ui?.submitLabel) || text(effect?.activityLabel) || text(effect?.label) || artifact.label;
16304
+ }
16305
+ function relationshipValueContainsRecordId(value, recordId) {
16306
+ return Array.isArray(value) ? value.some((item) => item === recordId) : value === recordId;
16307
+ }
16308
+ function relationshipChildLinkPending(parent, fieldName, child) {
16309
+ if (child.status !== "completed") return false;
16310
+ const parentMetadata = record(parent.metadata);
16311
+ const linkMarker = record(
16312
+ record(parentMetadata?.relationshipLinkProvenance)?.[fieldName]
16313
+ );
16314
+ const relationshipValue = parent.relationships?.[fieldName];
16315
+ const childExecution = record(record(child.metadata)?.execution);
16316
+ const childResult = record(childExecution?.result);
16317
+ const childCommit = record(childResult?.commit);
16318
+ const primaryTarget2 = record(childCommit?.primaryTarget);
16319
+ const committedRecordId = text(primaryTarget2?.id);
16320
+ const markerRecordId = text(linkMarker?.recordId);
16321
+ const exactRecordId = markerRecordId || committedRecordId;
16322
+ if (exactRecordId) {
16323
+ return !relationshipValueContainsRecordId(relationshipValue, exactRecordId);
16324
+ }
16325
+ return isMissing(relationshipValue);
16326
+ }
16327
+ function stateTransitionSteps(artifact) {
16328
+ if (artifact.kind !== "state_path") return [];
16329
+ const statePlan = record(record(artifact.metadata)?.statePlan);
16330
+ const plan = record(statePlan?.plan);
16331
+ const transitionMetadata = record(statePlan?.transitionMetadata);
16332
+ const rawSteps = Array.isArray(statePlan?.steps) ? statePlan.steps : Array.isArray(plan?.steps) ? plan.steps : [];
16333
+ return Array.from(rawSteps).flatMap((value, fallbackIndex) => {
16334
+ const step = record(value);
16335
+ const transition = text(step?.transition);
16336
+ if (!step || !transition) return [];
16337
+ const transitionDescription = record(transitionMetadata?.[transition]);
16338
+ const userVisible = typeof step.userVisible === "boolean" ? step.userVisible : typeof transitionDescription?.userVisible === "boolean" ? transitionDescription.userVisible : true;
16339
+ const blockers = Array.isArray(step.blockers) ? step.blockers.filter(
16340
+ (blocker) => Boolean(record(blocker))
16341
+ ) : [];
16342
+ return [
16343
+ {
16344
+ index: typeof step.index === "number" ? step.index : fallbackIndex,
16345
+ transition,
16346
+ label: text(step.label) || humanize(transition),
16347
+ description: text(step.description) || text(record(step.expectedOutcome)?.summary) || null,
16348
+ status: text(step.status) || "pending",
16349
+ // State plans may contain bookkeeping transitions that are necessary
16350
+ // for execution/audit but are already represented by an actionable
16351
+ // form or by the single final operation. Producers must opt those
16352
+ // transitions out explicitly; the client never infers this from a
16353
+ // label or transition name.
16354
+ userVisible,
16355
+ blockers
16356
+ }
16357
+ ];
16358
+ });
16359
+ }
16360
+ function stateTransitionGroupLabel(artifact) {
16361
+ if (artifact.kind !== "state_path") return null;
16362
+ const statePlan = record(record(artifact.metadata)?.statePlan);
16363
+ const classLabel = text(statePlan?.classLabel);
16364
+ return classLabel ? `${classLabel} workflow` : null;
16365
+ }
16366
+ function workflowGroupStatus(nodes) {
16367
+ const active = nodes.find(
16368
+ (node) => ["current", "needs_input", "working", "blocked", "failed"].includes(
16369
+ node.status
16370
+ )
16371
+ );
16372
+ if (active) return active.status;
16373
+ if (nodes.some((node) => node.status === "upcoming")) return "upcoming";
16374
+ const lastTerminal = [...nodes].reverse().find((node) => isTerminalWorkflowStepStatus(node.status));
16375
+ return lastTerminal?.status || nodes[0]?.status || "upcoming";
16376
+ }
16377
+ function transitionNodeStatus(artifact, step) {
16378
+ const statePlan = record(record(artifact.metadata)?.statePlan);
16379
+ const currentStep = record(statePlan?.currentStep);
16380
+ const execution = record(record(artifact.metadata)?.execution);
16381
+ const currentTransition = text(execution?.currentTransition);
16382
+ const completedTransitions = new Set(
16383
+ Array.isArray(execution?.completedTransitions) ? execution.completedTransitions.map(text).filter((value) => Boolean(value)) : []
16384
+ );
16385
+ const isCurrent = currentTransition === step.transition || currentStep?.index === step.index;
16386
+ if (completedTransitions.has(step.transition) || step.status === "completed") {
16387
+ return "completed";
16388
+ }
16389
+ if (isCurrent && artifact.status === "failed") return "failed";
16390
+ if (isCurrent && artifact.status === "canceled") return "canceled";
16391
+ if (isCurrent && artifact.status === "running") return "working";
16392
+ if (step.status === "failed") return "failed";
16393
+ if (artifact.status === "canceled") return "canceled";
16394
+ if (step.status === "blocked") return "blocked";
16395
+ if (step.status === "needs_other_user" || step.status === "needs_input") {
16396
+ return "needs_input";
16397
+ }
16398
+ if (isCurrent || step.status === "ready" || step.status === "current") {
16399
+ return "current";
16400
+ }
16401
+ if (step.status === "skipped") return "skipped";
16402
+ if (step.status === "canceled") return "canceled";
16403
+ return "upcoming";
16404
+ }
16405
+ var TERMINAL_WORKFLOW_STEP_STATUSES = /* @__PURE__ */ new Set([
16406
+ "completed",
16407
+ "skipped",
16408
+ "canceled"
16409
+ ]);
16410
+ function isArtifactWorkflowNodeTerminal(nodeOrStatus) {
16411
+ const status = typeof nodeOrStatus === "string" ? nodeOrStatus : nodeOrStatus.status;
16412
+ return TERMINAL_WORKFLOW_STEP_STATUSES.has(status);
16413
+ }
16414
+ function isTerminalWorkflowStepStatus(status) {
16415
+ return isArtifactWorkflowNodeTerminal(status);
16416
+ }
16417
+ function firstTransitionStatus(artifact, status) {
16418
+ if (status !== "upcoming") return status;
16419
+ if (artifact.status === "canceled") return "canceled";
16420
+ if (artifact.status === "failed") return "failed";
16421
+ if (artifact.status === "blocked" || artifact.status === "stale" || artifact.status === "partially_completed") {
16422
+ return "blocked";
16423
+ }
16424
+ if (artifact.status === "running") return "working";
16425
+ if (artifact.status === "awaiting_human") return "needs_input";
16426
+ return "current";
16427
+ }
16428
+ function operationStatus(artifact, transitionStatuses = []) {
16429
+ if (artifact.status === "completed") {
16430
+ if (transitionStatuses.some(
16431
+ (status) => !TERMINAL_WORKFLOW_STEP_STATUSES.has(status)
16432
+ )) {
16433
+ if (transitionStatuses.includes("failed")) return "failed";
16434
+ if (transitionStatuses.includes("needs_input")) return "needs_input";
16435
+ if (transitionStatuses.includes("working")) return "working";
16436
+ if (transitionStatuses.includes("current")) return "current";
16437
+ return "blocked";
16438
+ }
16439
+ return "completed";
16440
+ }
16441
+ if (artifact.status === "failed") return "failed";
16442
+ if (artifact.status === "canceled") return "canceled";
16443
+ if (artifact.status === "partially_completed") return "blocked";
16444
+ if (artifact.status === "blocked" || artifact.status === "stale")
16445
+ return "blocked";
16446
+ if (artifact.status === "running") return "working";
16447
+ if (artifact.status === "awaiting_human") return "needs_input";
16448
+ if (artifact.status === "ready") return "current";
16449
+ return "upcoming";
16450
+ }
16451
+ function semanticWorkflow(workflow) {
16452
+ const { revision: _revision, updatedAt: _updatedAt, ...semantic } = workflow;
16453
+ return semantic;
16454
+ }
16455
+ function isNavigationOnlyOperation(workflow, node) {
16456
+ const hasExplicitTransitionLeaf = getArtifactWorkflowNodes(workflow).some(
16457
+ (candidate) => candidate.childIds.length === 0 && candidate.kind === "transition"
16458
+ );
16459
+ return hasExplicitTransitionLeaf && node.childIds.length === 0 && node.kind === "operation" && // A terminal operation is redundant once authored state-path leaves are
16460
+ // available. While it is still delivering/synchronizing, however, it is
16461
+ // the only honest representation of the work still in progress.
16462
+ isArtifactWorkflowNodeTerminal(node);
16463
+ }
16464
+ function getArtifactWorkflowNavigationLeaves(workflow) {
16465
+ return getArtifactWorkflowNodes(workflow).filter(
16466
+ (node) => isArtifactWorkflowNodeNavigableLeaf(workflow, node)
16467
+ );
16468
+ }
16469
+ function isArtifactWorkflowNodeNavigableLeaf(workflow, node) {
16470
+ return node.childIds.length === 0 && !isNavigationOnlyOperation(workflow, node);
16471
+ }
16472
+ function isArtifactWorkflowNodeActionable(node) {
16473
+ return node.availability === "available" && ["current", "needs_input", "working", "blocked", "failed"].includes(
16474
+ node.status
16475
+ );
16476
+ }
16477
+ function getArtifactWorkflowNavigationTarget(workflow) {
16478
+ const leaves = getArtifactWorkflowNavigationLeaves(workflow);
16479
+ return leaves.find(
16480
+ (node) => !isArtifactWorkflowNodeTerminal(node) && isArtifactWorkflowNodeActionable(node)
16481
+ ) || leaves.at(-1) || null;
16482
+ }
16483
+ function projectArtifactWorkflow(artifact, allArtifacts = [artifact], now = artifact.updatedAt) {
16484
+ const nodesById = {};
16485
+ const rootNodeIds = [];
16486
+ const sections = declaredSections(artifact);
16487
+ const children = new Map(
16488
+ allArtifacts.filter((candidate2) => candidate2.parentArtifactId === artifact.artifactId).map((candidate2) => [candidate2.artifactId, candidate2])
16489
+ );
16490
+ const relationshipChildren = record(
16491
+ record(artifact.metadata)?.relationshipChildren
16492
+ );
16493
+ const operationId2 = `${artifact.artifactId}:operation`;
16494
+ const validationId = `${artifact.artifactId}:validation`;
16495
+ const sectionNodeIds = [];
16496
+ let currentNodeId = null;
16497
+ let foundIncompleteSection = false;
16498
+ let foundActiveLeaf = false;
16499
+ let transitionHasActiveStep = false;
16500
+ let hasVisibleTransition = false;
16501
+ const projectedTransitionStatuses = [];
16502
+ const transitionNodeIds = [];
16503
+ const transitionGroupLabel = stateTransitionGroupLabel(artifact);
16504
+ const transitionGroupId = `${artifact.artifactId}:transition-group`;
16505
+ for (const section of sections) {
16506
+ const id = `${artifact.artifactId}:section:${stableToken(section.id)}`;
16507
+ const complete = sectionComplete(artifact, section.fieldNames);
16508
+ const blockedByEarlierSection = foundIncompleteSection;
16509
+ const blockedByEarlierAction = foundIncompleteSection || foundActiveLeaf;
16510
+ const relationshipFields = artifactFields(artifact).filter(
16511
+ (field) => section.fieldNames.includes(text(field.name) || "") && ["relationshipOne", "relationshipMany"].includes(
16512
+ text(field.kind) || ""
16513
+ )
16514
+ );
16515
+ const relationshipField = relationshipFields[0];
16516
+ const relationshipChildrenInSection = relationshipFields.flatMap(
16517
+ (field) => {
16518
+ const relationshipName = text(field.name);
16519
+ const childArtifactId = relationshipName ? text(relationshipChildren?.[relationshipName]) : null;
16520
+ const child = childArtifactId ? children.get(childArtifactId) : null;
16521
+ if (!relationshipName || !child) return [];
16522
+ return [
16523
+ {
16524
+ relationshipName,
16525
+ child,
16526
+ childLinkPending: relationshipChildLinkPending(
16527
+ artifact,
16528
+ relationshipName,
16529
+ child
16530
+ ),
16531
+ childId: `${artifact.artifactId}:child:${stableToken(child.artifactId)}`
16532
+ }
16533
+ ];
16534
+ }
16535
+ );
16536
+ const relationshipChildFieldNames = new Set(
16537
+ relationshipChildrenInSection.map(
16538
+ ({ relationshipName }) => relationshipName
16539
+ )
16540
+ );
16541
+ const residualFieldNames = section.fieldNames.filter(
16542
+ (fieldName) => !relationshipChildFieldNames.has(fieldName)
16543
+ );
16544
+ const residualComplete = sectionComplete(artifact, residualFieldNames);
16545
+ const residualId = `${id}:details`;
16546
+ const childActive = relationshipChildrenInSection.some(
16547
+ ({ child, childLinkPending }) => childLinkPending || !TERMINAL_ARTIFACT_STATUSES.has(child.status)
16548
+ );
16549
+ const childIsWorking = relationshipChildrenInSection.some(
16550
+ ({ child, childLinkPending }) => childLinkPending || child.status === "running"
16551
+ );
16552
+ const sectionStatus = complete && !blockedByEarlierSection ? "completed" : blockedByEarlierAction ? "upcoming" : childActive ? childIsWorking ? "working" : "needs_input" : !TERMINAL_ARTIFACT_STATUSES.has(artifact.status) ? "needs_input" : "upcoming";
16553
+ if (!complete) foundIncompleteSection = true;
16554
+ nodesById[id] = {
16555
+ id,
16556
+ artifactId: artifact.artifactId,
16557
+ parentId: operationId2,
16558
+ childIds: [
16559
+ ...relationshipChildrenInSection.map(({ childId }) => childId),
16560
+ ...relationshipChildrenInSection.length > 0 && residualFieldNames.length > 0 && !residualComplete ? [residualId] : []
16561
+ ],
16562
+ kind: relationshipChildrenInSection.length > 0 ? "group" : relationshipField ? "relationship" : "input",
16563
+ label: section.label,
16564
+ description: section.description,
16565
+ status: sectionStatus,
16566
+ availability: sectionStatus === "completed" ? "readonly" : sectionStatus === "upcoming" ? "unavailable" : "available",
16567
+ fieldNames: relationshipChildrenInSection.length > 0 ? void 0 : section.fieldNames,
16568
+ recap: sectionStatus === "completed" ? sectionRecap(artifact, section.fieldNames) : null,
16569
+ completedAt: sectionStatus === "completed" ? artifact.updatedAt : null
16570
+ };
16571
+ sectionNodeIds.push(id);
16572
+ const childStatuses = [];
16573
+ for (const {
16574
+ child,
16575
+ childId,
16576
+ childLinkPending
16577
+ } of relationshipChildrenInSection) {
16578
+ const authoritativeChildStatus = childLinkPending ? "working" : child.status === "completed" ? "completed" : child.status === "failed" ? "failed" : child.status === "canceled" ? "canceled" : child.status === "running" ? "working" : child.status === "blocked" || child.status === "stale" ? "blocked" : child.status === "ready" ? "current" : "needs_input";
16579
+ const childStatus = blockedByEarlierSection ? "upcoming" : foundActiveLeaf && !isTerminalWorkflowStepStatus(authoritativeChildStatus) ? "upcoming" : authoritativeChildStatus;
16580
+ childStatuses.push(childStatus);
16581
+ nodesById[childId] = {
16582
+ id: childId,
16583
+ artifactId: artifact.artifactId,
16584
+ parentId: id,
16585
+ childIds: [],
16586
+ kind: "child_artifact",
16587
+ label: child.label,
16588
+ description: child.description,
16589
+ status: childStatus,
16590
+ availability: childStatus === "completed" ? "readonly" : childStatus === "upcoming" ? "unavailable" : "available",
16591
+ childArtifactId: child.artifactId,
16592
+ recap: childStatus === "completed" ? { summary: `${child.label} completed`, items: [] } : null,
16593
+ attention: childStatus === "failed" ? { title: `${child.label} failed`, retryable: true } : null,
16594
+ startedAt: child.executedAt || null,
16595
+ completedAt: childLinkPending ? null : child.completedAt || null
16596
+ };
16597
+ if (!blockedByEarlierSection && !isArtifactWorkflowNodeTerminal(childStatus)) {
16598
+ if (!foundActiveLeaf) {
16599
+ currentNodeId = childId;
16600
+ foundActiveLeaf = true;
16601
+ }
16602
+ }
16603
+ }
16604
+ if (relationshipChildrenInSection.length > 0 && residualFieldNames.length > 0 && !residualComplete) {
16605
+ const residualStatus = residualComplete ? "completed" : blockedByEarlierSection || foundActiveLeaf ? "upcoming" : !TERMINAL_ARTIFACT_STATUSES.has(artifact.status) ? "needs_input" : "upcoming";
16606
+ const residualFields = artifactFields(artifact).filter(
16607
+ (field) => residualFieldNames.includes(text(field.name) || "")
16608
+ );
16609
+ const residualLabel = residualFields.length === 1 ? text(residualFields[0]?.label) || section.label : "Details";
16610
+ childStatuses.push(residualStatus);
16611
+ nodesById[residualId] = {
16612
+ id: residualId,
16613
+ artifactId: artifact.artifactId,
16614
+ parentId: id,
16615
+ childIds: [],
16616
+ kind: residualFields.some(
16617
+ (field) => ["relationshipOne", "relationshipMany"].includes(
16618
+ text(field.kind) || ""
16619
+ )
16620
+ ) ? "relationship" : "input",
16621
+ label: residualLabel,
16622
+ description: section.description,
16623
+ status: residualStatus,
16624
+ availability: residualStatus === "completed" ? "readonly" : residualStatus === "upcoming" ? "unavailable" : "available",
16625
+ fieldNames: residualFieldNames,
16626
+ recap: residualComplete ? sectionRecap(artifact, residualFieldNames) : null,
16627
+ completedAt: residualComplete ? artifact.updatedAt : null
16628
+ };
16629
+ if (!foundActiveLeaf && ["needs_input", "working", "blocked", "failed"].includes(residualStatus)) {
16630
+ currentNodeId = residualId;
16631
+ foundActiveLeaf = true;
16632
+ }
16633
+ }
16634
+ if (childStatuses.length > 0) {
16635
+ const representativeChildStatus = childStatuses.find(
16636
+ (status) => !isArtifactWorkflowNodeTerminal(status)
16637
+ ) ?? (complete ? childStatuses.at(-1) : null);
16638
+ if (representativeChildStatus) {
16639
+ nodesById[id].status = representativeChildStatus;
16640
+ }
16641
+ nodesById[id].availability = "readonly";
16642
+ }
16643
+ if (!foundActiveLeaf && relationshipChildrenInSection.length === 0 && ["needs_input", "working", "blocked", "failed"].includes(sectionStatus)) {
16644
+ currentNodeId = id;
16645
+ foundActiveLeaf = true;
16646
+ }
16647
+ }
16648
+ const transitionsAreGatedByForm = (foundIncompleteSection || foundActiveLeaf) && !TERMINAL_ARTIFACT_STATUSES.has(artifact.status);
16649
+ for (const step of stateTransitionSteps(artifact)) {
16650
+ if (!step.userVisible) continue;
16651
+ hasVisibleTransition = true;
16652
+ const id = `${artifact.artifactId}:transition:${stableToken(step.transition)}`;
16653
+ const rawStatus = transitionNodeStatus(artifact, step);
16654
+ const status = isTerminalWorkflowStepStatus(rawStatus) ? rawStatus : transitionsAreGatedByForm || transitionHasActiveStep ? "upcoming" : firstTransitionStatus(artifact, rawStatus);
16655
+ projectedTransitionStatuses.push(status);
16656
+ if (!isTerminalWorkflowStepStatus(status)) {
16657
+ transitionHasActiveStep = true;
16658
+ }
16659
+ const blocker = step.blockers[0];
16660
+ nodesById[id] = {
16661
+ id,
16662
+ artifactId: artifact.artifactId,
16663
+ parentId: transitionGroupLabel ? transitionGroupId : operationId2,
16664
+ childIds: [],
16665
+ kind: "transition",
16666
+ label: step.label,
16667
+ description: step.description,
16668
+ status,
16669
+ availability: status === "completed" ? "readonly" : status === "upcoming" ? "unavailable" : "available",
16670
+ operationPhase: step.status === "needs_other_user" ? "waiting_for_approval" : status === "working" ? operationPhase(artifact) : null,
16671
+ operationDetail: text(record(record(artifact.metadata)?.execution)?.message) || null,
16672
+ recap: status === "completed" ? { summary: `${step.label} completed`, items: [] } : null,
16673
+ attention: status === "failed" || status === "blocked" ? {
16674
+ title: text(blocker?.label) || `${step.label} needs attention`,
16675
+ detail: text(blocker?.detail) || text(blocker?.description) || null,
16676
+ retryable: status === "failed"
16677
+ } : null,
16678
+ startedAt: status === "working" ? artifact.executedAt || artifact.updatedAt : null,
16679
+ completedAt: status === "completed" ? artifact.updatedAt : null
16680
+ };
16681
+ transitionNodeIds.push(id);
16682
+ if (!currentNodeId && !["completed", "upcoming", "skipped", "canceled"].includes(status)) {
16683
+ currentNodeId = id;
16684
+ }
16685
+ }
16686
+ if (transitionNodeIds.length) {
16687
+ const transitionNodes = transitionNodeIds.map((id) => nodesById[id]).filter((node) => Boolean(node));
16688
+ if (transitionGroupLabel) {
16689
+ const groupStatus = workflowGroupStatus(transitionNodes);
16690
+ const activeTransition = transitionNodes.find(
16691
+ (node) => ["current", "needs_input", "working", "blocked", "failed"].includes(
16692
+ node.status
16693
+ )
16694
+ );
16695
+ nodesById[transitionGroupId] = {
16696
+ id: transitionGroupId,
16697
+ artifactId: artifact.artifactId,
16698
+ parentId: operationId2,
16699
+ childIds: transitionNodeIds,
16700
+ kind: "group",
16701
+ label: transitionGroupLabel,
16702
+ description: null,
16703
+ status: groupStatus,
16704
+ availability: groupStatus === "upcoming" ? "unavailable" : groupStatus === "completed" ? "readonly" : "available",
16705
+ operationPhase: activeTransition?.operationPhase || null,
16706
+ startedAt: activeTransition?.startedAt || null,
16707
+ completedAt: groupStatus === "completed" ? transitionNodes.at(-1)?.completedAt || artifact.updatedAt : null
16708
+ };
16709
+ sectionNodeIds.push(transitionGroupId);
16710
+ } else {
16711
+ sectionNodeIds.push(...transitionNodeIds);
16712
+ }
16713
+ }
16714
+ const projectedOperationStatus = operationStatus(
16715
+ artifact,
16716
+ projectedTransitionStatuses
16717
+ );
16718
+ if (projectedOperationStatus === "completed") {
16719
+ for (const transitionNodeId of transitionNodeIds) {
16720
+ const transition = nodesById[transitionNodeId];
16721
+ if (transition?.kind === "transition" && transition.status === "completed") {
16722
+ transition.completionState = "graph_propagated";
16723
+ }
16724
+ }
16725
+ }
16726
+ const phase = operationPhase(artifact);
16727
+ const activeDescendantStatus = currentNodeId ? nodesById[currentNodeId]?.status : null;
16728
+ const finalStatus = activeDescendantStatus && !isTerminalWorkflowStepStatus(activeDescendantStatus) ? activeDescendantStatus : projectedOperationStatus;
16729
+ const validationStatus = currentNodeId ? "upcoming" : projectedOperationStatus === "upcoming" ? "current" : projectedOperationStatus;
16730
+ nodesById[validationId] = {
16731
+ id: validationId,
16732
+ artifactId: artifact.artifactId,
16733
+ parentId: operationId2,
16734
+ childIds: [],
16735
+ kind: "operation",
16736
+ label: operationLabel(artifact),
16737
+ description: artifact.description || null,
16738
+ status: validationStatus,
16739
+ availability: validationStatus === "upcoming" ? "unavailable" : validationStatus === "completed" ? "readonly" : "available",
16740
+ operationPhase: phase,
16741
+ completionState: projectedOperationStatus === "completed" ? "graph_propagated" : phase === "synchronizing" || phase === "verifying" ? "effect_applied" : null,
16742
+ operationDetail: text(
16743
+ record(record(artifact.metadata)?.execution)?.message
16744
+ ),
16745
+ recap: validationStatus === "completed" ? { summary: `${artifact.label} completed`, items: [] } : null,
16746
+ attention: validationStatus === "failed" ? { title: `${artifact.label} failed`, retryable: true } : validationStatus === "blocked" ? {
16747
+ title: `${artifact.label} needs attention`,
16748
+ retryable: artifact.status === "stale"
16749
+ } : null,
16750
+ startedAt: artifact.executedAt || null,
16751
+ completedAt: artifact.completedAt || null
16752
+ };
16753
+ sectionNodeIds.push(validationId);
16754
+ if (!currentNodeId && !hasVisibleTransition && !["completed", "canceled"].includes(finalStatus)) {
16755
+ currentNodeId = validationId;
16756
+ }
16757
+ const rootStatus = currentNodeId === validationId ? nodesById[validationId].status : finalStatus;
16758
+ nodesById[operationId2] = {
16759
+ id: operationId2,
16760
+ artifactId: artifact.artifactId,
16761
+ parentId: null,
16762
+ childIds: sectionNodeIds,
16763
+ kind: "group",
16764
+ label: artifact.label,
16765
+ description: artifact.description || null,
16766
+ status: rootStatus,
16767
+ availability: rootStatus === "upcoming" ? "unavailable" : rootStatus === "completed" ? "readonly" : "available",
16768
+ operationPhase: null,
16769
+ startedAt: artifact.executedAt || null,
16770
+ completedAt: artifact.completedAt || null
16771
+ };
16772
+ rootNodeIds.push(operationId2);
16773
+ const candidateBeforeTarget = {
16774
+ schemaVersion: 1,
16775
+ revision: artifact.workflow?.revision || 1,
16776
+ artifactId: artifact.artifactId,
16777
+ rootNodeIds,
16778
+ nodesById,
16779
+ currentNodeId: null,
16780
+ updatedAt: now
16781
+ };
16782
+ currentNodeId = getArtifactWorkflowNavigationTarget(candidateBeforeTarget)?.id || null;
16783
+ const automaticTarget = currentNodeId ? nodesById[currentNodeId] || null : null;
16784
+ const canonicalRootStatus = automaticTarget && !isArtifactWorkflowNodeTerminal(automaticTarget) ? automaticTarget.status : projectedOperationStatus;
16785
+ nodesById[operationId2].status = canonicalRootStatus;
16786
+ nodesById[operationId2].availability = canonicalRootStatus === "upcoming" ? "unavailable" : canonicalRootStatus === "completed" ? "readonly" : "available";
16787
+ const candidate = {
16788
+ ...candidateBeforeTarget,
16789
+ currentNodeId
16790
+ };
16791
+ if (artifact.workflow && JSON.stringify(semanticWorkflow(artifact.workflow)) !== JSON.stringify(semanticWorkflow(candidate))) {
16792
+ candidate.revision = artifact.workflow.revision + 1;
16793
+ } else if (artifact.workflow) {
16794
+ candidate.updatedAt = artifact.workflow.updatedAt;
16795
+ }
16796
+ return candidate;
16797
+ }
16798
+ function getCurrentArtifactWorkflowNode(workflow) {
16799
+ return workflow.currentNodeId ? workflow.nodesById[workflow.currentNodeId] || null : null;
16800
+ }
16801
+ function getArtifactWorkflowNodes(workflow) {
16802
+ const visit = (id) => {
16803
+ const node = workflow.nodesById[id];
16804
+ return node ? [node, ...node.childIds.flatMap(visit)] : [];
16805
+ };
16806
+ return workflow.rootNodeIds.flatMap(visit);
16807
+ }
16808
+ function canInspectArtifactWorkflowNode(node) {
16809
+ return node.childIds.length === 0 && node.availability !== "unavailable";
16810
+ }
16811
+ function assertArtifactWorkflow(workflow) {
16812
+ if (workflow.schemaVersion !== 1)
16813
+ throw new Error("Unsupported artifact workflow schema");
16814
+ if (!Number.isSafeInteger(workflow.revision) || workflow.revision < 1) {
16815
+ throw new Error("Artifact workflow revision must be a positive integer");
16816
+ }
16817
+ const visited = /* @__PURE__ */ new Set();
16818
+ const visiting = /* @__PURE__ */ new Set();
16819
+ const nodes = [];
16820
+ const visit = (id, expectedParentId) => {
16821
+ const node = workflow.nodesById[id];
16822
+ if (!node) throw new Error(`Artifact workflow node ${id} does not exist`);
16823
+ if (visiting.has(id)) throw new Error("Artifact workflow contains a cycle");
16824
+ if (visited.has(id))
16825
+ throw new Error("Artifact workflow contains a duplicate node reference");
16826
+ if (node.parentId !== expectedParentId) {
16827
+ throw new Error(`Artifact workflow node ${id} has an invalid parent`);
16828
+ }
16829
+ visiting.add(id);
16830
+ nodes.push(node);
16831
+ node.childIds.forEach((childId) => visit(childId, id));
16832
+ visiting.delete(id);
16833
+ visited.add(id);
16834
+ };
16835
+ workflow.rootNodeIds.forEach((id) => visit(id, null));
16836
+ if (visited.size !== Object.keys(workflow.nodesById).length) {
16837
+ throw new Error("Artifact workflow contains unreachable nodes");
16838
+ }
16839
+ if (workflow.currentNodeId && !workflow.nodesById[workflow.currentNodeId]) {
16840
+ throw new Error("Artifact workflow current node does not exist");
16841
+ }
16842
+ const currentNode = workflow.currentNodeId ? workflow.nodesById[workflow.currentNodeId] : null;
16843
+ if (currentNode && currentNode.childIds.length > 0) {
16844
+ throw new Error("Artifact workflow current node must be actionable leaf");
16845
+ }
16846
+ const actionable = nodes.filter(
16847
+ (node) => isArtifactWorkflowNodeNavigableLeaf(workflow, node) && isArtifactWorkflowNodeActionable(node)
16848
+ );
16849
+ if (actionable.length > 1)
16850
+ throw new Error("Artifact workflow has multiple current nodes");
16851
+ if (actionable.length === 1 && actionable[0].id !== workflow.currentNodeId) {
16852
+ throw new Error("Artifact workflow current node does not own its action");
16853
+ }
16854
+ if (workflow.currentNodeId && !actionable.length && !isArtifactWorkflowNodeTerminal(currentNode)) {
16855
+ throw new Error("Artifact workflow current node is not actionable");
16856
+ }
16857
+ }
16858
+ function requireArtifactWorkflow(artifact) {
16859
+ if (!artifact.workflow) {
16860
+ throw new Error(
16861
+ `Artifact ${artifact.artifactId} has no canonical workflow`
16862
+ );
16863
+ }
16864
+ assertArtifactWorkflow(artifact.workflow);
16865
+ if (artifact.workflow.artifactId !== artifact.artifactId) {
16866
+ throw new Error("Artifact workflow belongs to a different artifact");
16867
+ }
16868
+ return artifact;
16869
+ }
16870
+ function requireArtifactWorkflowResult(result) {
16871
+ requireArtifactWorkflow(result.artifact);
16872
+ return result;
16873
+ }
16874
+
15965
16875
  // src/client.ts
15966
16876
  var DEFAULT_CONVERSATION_SESSION_LIST_LIMIT = 100;
15967
16877
  var MAX_CONVERSATION_SESSION_LIST_LIMIT = 500;
@@ -16115,17 +17025,17 @@ function planRecordObjectsChunks(records, batchSize) {
16115
17025
  }
16116
17026
  return plans;
16117
17027
  }
16118
- function preserveRecordObjectRealId(record) {
16119
- const realId = record.id.trim();
17028
+ function preserveRecordObjectRealId(record2) {
17029
+ const realId = record2.id.trim();
16120
17030
  if (!realId) {
16121
- return record;
17031
+ return record2;
16122
17032
  }
16123
- const fields = record.fields || {};
17033
+ const fields = record2.fields || {};
16124
17034
  if (typeof fields.real_id === "string" && fields.real_id.trim()) {
16125
- return record;
17035
+ return record2;
16126
17036
  }
16127
17037
  return {
16128
- ...record,
17038
+ ...record2,
16129
17039
  fields: {
16130
17040
  ...fields,
16131
17041
  real_id: realId
@@ -16457,7 +17367,7 @@ var Environment = class _Environment {
16457
17367
  }
16458
17368
  get data() {
16459
17369
  return {
16460
- record: async (record) => this.recordObject(record),
17370
+ record: async (record2) => this.recordObject(record2),
16461
17371
  recordMany: async (records, options) => this.recordObjects(records, options),
16462
17372
  import: async (records, options) => this.enqueueRecordImport(records, options),
16463
17373
  listImports: async (status) => this.listRecordImports(status),
@@ -17805,6 +18715,7 @@ var EnvironmentSession = class extends Session {
17805
18715
  sessionDataHeaders;
17806
18716
  beforeMutationSend;
17807
18717
  heartbeatTimer = null;
18718
+ heartbeatProbeInFlight = false;
17808
18719
  /** The last known graph container status, updated by checkReadiness(). */
17809
18720
  graphContainerStatus = null;
17810
18721
  constructor(client, environment, clientId, options = {}) {
@@ -17824,16 +18735,34 @@ var EnvironmentSession = class extends Session {
17824
18735
  * an application RPC may be delayed by Durable Object work even while the
17825
18736
  * underlying WebSocket is healthy.
17826
18737
  */
17827
- startHeartbeat() {
18738
+ startHeartbeat(options = {}) {
17828
18739
  if (this.heartbeatTimer) return;
17829
- this.heartbeatTimer = setInterval(() => {
17830
- if (!this.client.isTransportOpen()) return;
17831
- if (this.client.hasRecentInboundActivity(3e4)) return;
17832
- if (this.client.hasRecentlyPendingRpcPeriod(6e4)) return;
17833
- this.client.reportTransportFailure(
17834
- new Error("No server activity received for 30 seconds")
17835
- );
17836
- }, 5e3);
18740
+ const probeServer = options.probeServer === true;
18741
+ this.heartbeatTimer = setInterval(
18742
+ () => {
18743
+ if (!this.client.isTransportOpen()) return;
18744
+ if (probeServer) {
18745
+ if (this.heartbeatProbeInFlight) return;
18746
+ if (this.client.hasPendingRpcRequests()) return;
18747
+ if (this.client.hasRecentInboundActivity(1e4)) return;
18748
+ this.heartbeatProbeInFlight = true;
18749
+ void this.client.call("client.heartbeat", {}).catch((error) => {
18750
+ if (this.client.isTransportOpen()) {
18751
+ this.client.reportTransportFailure(error);
18752
+ }
18753
+ }).finally(() => {
18754
+ this.heartbeatProbeInFlight = false;
18755
+ });
18756
+ return;
18757
+ }
18758
+ if (this.client.hasRecentInboundActivity(3e4)) return;
18759
+ if (this.client.hasRecentlyPendingRpcPeriod(6e4)) return;
18760
+ this.client.reportTransportFailure(
18761
+ new Error("No server activity received for 30 seconds")
18762
+ );
18763
+ },
18764
+ probeServer ? 1e4 : 5e3
18765
+ );
17837
18766
  this.heartbeatTimer.unref?.();
17838
18767
  }
17839
18768
  stopHeartbeat() {
@@ -17841,10 +18770,15 @@ var EnvironmentSession = class extends Session {
17841
18770
  clearInterval(this.heartbeatTimer);
17842
18771
  this.heartbeatTimer = null;
17843
18772
  }
18773
+ this.heartbeatProbeInFlight = false;
17844
18774
  }
17845
18775
  async hello() {
17846
18776
  const result = await super.hello();
17847
- this.startHeartbeat();
18777
+ if (result.features?.serverHeartbeat === false) {
18778
+ this.startHeartbeat({ probeServer: true });
18779
+ } else {
18780
+ this.startHeartbeat();
18781
+ }
17848
18782
  return result;
17849
18783
  }
17850
18784
  get environmentId() {
@@ -17979,13 +18913,13 @@ var EnvironmentSession = class extends Session {
17979
18913
  input.operationId,
17980
18914
  "operationId"
17981
18915
  );
17982
- const text = requiredAssistantReplyString(input.text, "text");
18916
+ const text2 = requiredAssistantReplyString(input.text, "text");
17983
18917
  return this.sessionDataRequest(
17984
18918
  "/assistant-replies",
17985
18919
  void 0,
17986
18920
  {
17987
18921
  method: "POST",
17988
- body: { id, operationId: operationId2, text }
18922
+ body: { id, operationId: operationId2, text: text2 }
17989
18923
  }
17990
18924
  );
17991
18925
  }
@@ -18030,7 +18964,10 @@ var EnvironmentSession = class extends Session {
18030
18964
  queryOptions.targetId = options.target.id;
18031
18965
  delete queryOptions.target;
18032
18966
  }
18033
- return this.sessionDataRequest("/artifacts", queryOptions);
18967
+ return this.sessionDataRequest("/artifacts", queryOptions).then((page) => ({
18968
+ ...page,
18969
+ items: page.items.map(requireArtifactWorkflow)
18970
+ }));
18034
18971
  },
18035
18972
  listForLatestJob: (options = {}) => this.artifacts.list({
18036
18973
  ...options,
@@ -18043,7 +18980,7 @@ var EnvironmentSession = class extends Session {
18043
18980
  // `cache: no-store`; this also bypasses intermediaries that ignore
18044
18981
  // the Fetch cache directive for an otherwise identical GET URL.
18045
18982
  { _granularLiveRead: Date.now() }
18046
- ),
18983
+ ).then(requireArtifactWorkflow),
18047
18984
  create: (artifact) => this.sessionDataRequest(
18048
18985
  "/artifacts",
18049
18986
  void 0,
@@ -18051,7 +18988,7 @@ var EnvironmentSession = class extends Session {
18051
18988
  method: "POST",
18052
18989
  body: artifact
18053
18990
  }
18054
- ),
18991
+ ).then(requireArtifactWorkflow),
18055
18992
  acceptSuggestion: (sequence) => this.sessionDataRequest(
18056
18993
  "/artifacts/suggestions/accept",
18057
18994
  void 0,
@@ -18059,7 +18996,7 @@ var EnvironmentSession = class extends Session {
18059
18996
  method: "POST",
18060
18997
  body: { sequence }
18061
18998
  }
18062
- ),
18999
+ ).then(requireArtifactWorkflow),
18063
19000
  updateInputs: (artifactId, patch) => this.sessionDataRequest(
18064
19001
  `/artifacts/${encodeURIComponent(artifactId)}`,
18065
19002
  void 0,
@@ -18067,7 +19004,7 @@ var EnvironmentSession = class extends Session {
18067
19004
  method: "PATCH",
18068
19005
  body: patch
18069
19006
  }
18070
- ),
19007
+ ).then(requireArtifactWorkflow),
18071
19008
  relationshipOptions: (artifactId, input) => this.sessionDataRequest(
18072
19009
  `/artifacts/${encodeURIComponent(artifactId)}/relationship-options`,
18073
19010
  void 0,
@@ -18077,27 +19014,27 @@ var EnvironmentSession = class extends Session {
18077
19014
  `/artifacts/${encodeURIComponent(artifactId)}/relationship-create`,
18078
19015
  void 0,
18079
19016
  { method: "POST", body: input }
18080
- ),
19017
+ ).then(requireArtifactWorkflow),
18081
19018
  validate: (artifactId) => this.sessionDataRequest(
18082
19019
  `/artifacts/${encodeURIComponent(artifactId)}/validate`,
18083
19020
  void 0,
18084
19021
  { method: "POST" }
18085
- ),
19022
+ ).then(requireArtifactWorkflowResult),
18086
19023
  execute: (artifactId, options) => this.sessionDataRequest(
18087
19024
  `/artifacts/${encodeURIComponent(artifactId)}/execute`,
18088
19025
  void 0,
18089
19026
  { method: "POST", body: options }
18090
- ),
19027
+ ).then(requireArtifactWorkflowResult),
18091
19028
  approve: (artifactId, options) => this.sessionDataRequest(
18092
19029
  `/artifacts/${encodeURIComponent(artifactId)}/approve`,
18093
19030
  void 0,
18094
19031
  { method: "POST", body: options }
18095
- ),
19032
+ ).then(requireArtifactWorkflowResult),
18096
19033
  cancel: (artifactId) => this.sessionDataRequest(
18097
19034
  `/artifacts/${encodeURIComponent(artifactId)}/cancel`,
18098
19035
  void 0,
18099
19036
  { method: "POST" }
18100
- )
19037
+ ).then(requireArtifactWorkflow)
18101
19038
  };
18102
19039
  }
18103
19040
  get manualActions() {
@@ -20085,13 +21022,13 @@ var Granular = class _Granular {
20085
21022
  * Permission Profile management for sandboxes
20086
21023
  */
20087
21024
  get permissionProfiles() {
20088
- const profileSourceFromRecord = (record) => {
20089
- const profile = record.profile || record.rules || {};
21025
+ const profileSourceFromRecord = (record2) => {
21026
+ const profile = record2.profile || record2.rules || {};
20090
21027
  return {
20091
21028
  ...profile,
20092
21029
  schemaVersion: profile.schemaVersion || 1,
20093
- name: profile.name || record.name,
20094
- description: profile.description || record.description
21030
+ name: profile.name || record2.name,
21031
+ description: profile.description || record2.description
20095
21032
  };
20096
21033
  };
20097
21034
  return {
@@ -20674,18 +21611,18 @@ function stableValue(value) {
20674
21611
  function stableJson(value) {
20675
21612
  return JSON.stringify(stableValue(value));
20676
21613
  }
20677
- function recordKey(record) {
20678
- return `${record.className}\0${record.id}`;
21614
+ function recordKey(record2) {
21615
+ return `${record2.className}\0${record2.id}`;
20679
21616
  }
20680
21617
  function recordReference(className, id) {
20681
21618
  return { className, id };
20682
21619
  }
20683
21620
  function changedRecordSnapshots(beforeRecords, afterRecords) {
20684
21621
  const before = new Map(
20685
- beforeRecords.map((record) => [recordKey(record), record])
21622
+ beforeRecords.map((record2) => [recordKey(record2), record2])
20686
21623
  );
20687
21624
  const after = new Map(
20688
- afterRecords.map((record) => [recordKey(record), record])
21625
+ afterRecords.map((record2) => [recordKey(record2), record2])
20689
21626
  );
20690
21627
  const changedKeys = /* @__PURE__ */ new Set();
20691
21628
  for (const key of /* @__PURE__ */ new Set([...before.keys(), ...after.keys()])) {
@@ -20695,10 +21632,10 @@ function changedRecordSnapshots(beforeRecords, afterRecords) {
20695
21632
  }
20696
21633
  return {
20697
21634
  beforeRecords: beforeRecords.filter(
20698
- (record) => changedKeys.has(recordKey(record))
21635
+ (record2) => changedKeys.has(recordKey(record2))
20699
21636
  ),
20700
21637
  afterRecords: afterRecords.filter(
20701
- (record) => changedKeys.has(recordKey(record))
21638
+ (record2) => changedKeys.has(recordKey(record2))
20702
21639
  )
20703
21640
  };
20704
21641
  }
@@ -20747,35 +21684,35 @@ function changedObjectProjection(before, after) {
20747
21684
  }
20748
21685
  function projectionChanges(input) {
20749
21686
  const before = new Map(
20750
- input.beforeRecords.map((record) => [recordKey(record), record])
21687
+ input.beforeRecords.map((record2) => [recordKey(record2), record2])
20751
21688
  );
20752
21689
  const after = new Map(
20753
- input.afterRecords.map((record) => [recordKey(record), record])
21690
+ input.afterRecords.map((record2) => [recordKey(record2), record2])
20754
21691
  );
20755
21692
  const targets = relationshipTargets(input.manifest);
20756
21693
  const changes = [];
20757
- for (const record of input.afterRecords) {
20758
- const previous = before.get(recordKey(record));
20759
- const objectChange = changedObjectProjection(previous, record);
21694
+ for (const record2 of input.afterRecords) {
21695
+ const previous = before.get(recordKey(record2));
21696
+ const objectChange = changedObjectProjection(previous, record2);
20760
21697
  if (objectChange) changes.push(objectChange);
20761
21698
  const relationshipNames = /* @__PURE__ */ new Set([
20762
21699
  ...Object.keys(previous?.relationships || {}),
20763
- ...Object.keys(record.relationships || {})
21700
+ ...Object.keys(record2.relationships || {})
20764
21701
  ]);
20765
21702
  for (const relationshipName of [...relationshipNames].sort()) {
20766
21703
  const target = targets.get(
20767
- `${record.className}\0${relationshipName}`
21704
+ `${record2.className}\0${relationshipName}`
20768
21705
  );
20769
21706
  if (!target) {
20770
21707
  throw new Error(
20771
- `No ontology relationship target is declared for ${record.className}.${relationshipName}`
21708
+ `No ontology relationship target is declared for ${record2.className}.${relationshipName}`
20772
21709
  );
20773
21710
  }
20774
21711
  const previousIds = new Set(
20775
21712
  asIds(previous?.relationships?.[relationshipName])
20776
21713
  );
20777
21714
  const currentIds = new Set(
20778
- asIds(record.relationships?.[relationshipName])
21715
+ asIds(record2.relationships?.[relationshipName])
20779
21716
  );
20780
21717
  for (const id of [...previousIds].sort()) {
20781
21718
  if (currentIds.has(id)) continue;
@@ -20783,7 +21720,7 @@ function projectionChanges(input) {
20783
21720
  kind: "relationship",
20784
21721
  operation: "disconnected",
20785
21722
  relationship: relationshipName,
20786
- from: recordReference(record.className, record.id),
21723
+ from: recordReference(record2.className, record2.id),
20787
21724
  to: recordReference(target.className, id)
20788
21725
  });
20789
21726
  }
@@ -20793,37 +21730,37 @@ function projectionChanges(input) {
20793
21730
  kind: "relationship",
20794
21731
  operation: "connected",
20795
21732
  relationship: relationshipName,
20796
- from: recordReference(record.className, record.id),
21733
+ from: recordReference(record2.className, record2.id),
20797
21734
  to: recordReference(target.className, id)
20798
21735
  });
20799
21736
  }
20800
21737
  }
20801
21738
  const machines = /* @__PURE__ */ new Set([
20802
21739
  ...Object.keys(previous?.states || {}),
20803
- ...Object.keys(record.states || {})
21740
+ ...Object.keys(record2.states || {})
20804
21741
  ]);
20805
21742
  for (const machine of [...machines].sort()) {
20806
- const next = record.states?.[machine];
21743
+ const next = record2.states?.[machine];
20807
21744
  if (next === void 0) continue;
20808
21745
  const prior = previous?.states?.[machine];
20809
21746
  if (prior !== void 0 && stateName(prior) === stateName(next)) continue;
20810
- if (input.transition && input.transition.className === record.className && input.transition.objectId === record.id && input.transition.machine === machine) {
21747
+ if (input.transition && input.transition.className === record2.className && input.transition.objectId === record2.id && input.transition.machine === machine) {
20811
21748
  continue;
20812
21749
  }
20813
21750
  changes.push({
20814
21751
  kind: "state_observation",
20815
- target: recordReference(record.className, record.id),
21752
+ target: recordReference(record2.className, record2.id),
20816
21753
  machine,
20817
21754
  state: stateName(next)
20818
21755
  });
20819
21756
  }
20820
21757
  }
20821
- for (const record of input.beforeRecords) {
20822
- if (after.has(recordKey(record))) continue;
21758
+ for (const record2 of input.beforeRecords) {
21759
+ if (after.has(recordKey(record2))) continue;
20823
21760
  changes.push({
20824
21761
  kind: "object",
20825
21762
  operation: "deleted",
20826
- target: recordReference(record.className, record.id)
21763
+ target: recordReference(record2.className, record2.id)
20827
21764
  });
20828
21765
  }
20829
21766
  return changes;
@@ -20868,7 +21805,7 @@ function transitionOutcome(transition, afterRecords) {
20868
21805
  throw new Error("A transition commit has no authored transition context.");
20869
21806
  }
20870
21807
  const target = afterRecords.find(
20871
- (record) => record.className === transition.className && record.id === transition.objectId
21808
+ (record2) => record2.className === transition.className && record2.id === transition.objectId
20872
21809
  );
20873
21810
  const observed = target?.states?.[transition.machine];
20874
21811
  if (!observed) {
@@ -20914,7 +21851,8 @@ function projectRecordSnapshotMutation(input) {
20914
21851
  const base = {
20915
21852
  source: {
20916
21853
  reference: input.source.reference,
20917
- ...input.source.version ? { version: input.source.version } : {}
21854
+ ...input.source.version ? { version: input.source.version } : {},
21855
+ ...input.source.versionScope ? { versionScope: input.source.versionScope } : {}
20918
21856
  },
20919
21857
  ...target ? { primaryTarget: target } : {},
20920
21858
  changes,
@@ -21085,29 +22023,29 @@ function requireRecord(value, context) {
21085
22023
  }
21086
22024
  return value;
21087
22025
  }
21088
- function requiredString(record, key, context) {
21089
- const value = record[key];
22026
+ function requiredString(record2, key, context) {
22027
+ const value = record2[key];
21090
22028
  if (typeof value !== "string" || !value.trim()) {
21091
22029
  throw new Error(`${context}.${key} must be a non-empty string.`);
21092
22030
  }
21093
22031
  return value;
21094
22032
  }
21095
- function requiredNumber(record, key, context) {
21096
- const value = record[key];
22033
+ function requiredNumber(record2, key, context) {
22034
+ const value = record2[key];
21097
22035
  if (typeof value !== "number" || !Number.isFinite(value)) {
21098
22036
  throw new Error(`${context}.${key} must be a finite number.`);
21099
22037
  }
21100
22038
  return value;
21101
22039
  }
21102
- function requiredStringArray(record, key, context) {
21103
- const value = record[key];
22040
+ function requiredStringArray(record2, key, context) {
22041
+ const value = record2[key];
21104
22042
  if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || !item.trim())) {
21105
22043
  throw new Error(`${context}.${key} must be an array of non-empty strings.`);
21106
22044
  }
21107
22045
  return [...value];
21108
22046
  }
21109
- function requiredNumberRecord(record, key, context) {
21110
- const value = requireRecord(record[key], `${context}.${key}`);
22047
+ function requiredNumberRecord(record2, key, context) {
22048
+ const value = requireRecord(record2[key], `${context}.${key}`);
21111
22049
  const output = {};
21112
22050
  for (const [entryKey, entryValue] of Object.entries(value)) {
21113
22051
  if (typeof entryValue !== "number" || !Number.isFinite(entryValue) || entryValue < 0 || entryValue > 1) {
@@ -21120,22 +22058,22 @@ function requiredNumberRecord(record, key, context) {
21120
22058
  return output;
21121
22059
  }
21122
22060
  function validateHarnessTemplateManifest(value, context = "HarnessTemplateManifest") {
21123
- const record = requireRecord(value, context);
21124
- const status = requiredString(record, "status", context);
22061
+ const record2 = requireRecord(value, context);
22062
+ const status = requiredString(record2, "status", context);
21125
22063
  if (!HARNESS_TEMPLATE_STATUSES.includes(status)) {
21126
22064
  throw new Error(
21127
22065
  `${context}.status must be one of ${HARNESS_TEMPLATE_STATUSES.join(", ")}.`
21128
22066
  );
21129
22067
  }
21130
- const createdAt = requiredString(record, "createdAt", context);
22068
+ const createdAt = requiredString(record2, "createdAt", context);
21131
22069
  if (Number.isNaN(Date.parse(createdAt))) {
21132
22070
  throw new Error(`${context}.createdAt must be an ISO timestamp.`);
21133
22071
  }
21134
22072
  const compatibility = requireRecord(
21135
- record.compatibility,
22073
+ record2.compatibility,
21136
22074
  `${context}.compatibility`
21137
22075
  );
21138
- const evalGates = requireRecord(record.evalGates, `${context}.evalGates`);
22076
+ const evalGates = requireRecord(record2.evalGates, `${context}.evalGates`);
21139
22077
  const maxRegressionPct = requiredNumber(
21140
22078
  evalGates,
21141
22079
  "maxRegressionPct",
@@ -21147,23 +22085,23 @@ function validateHarnessTemplateManifest(value, context = "HarnessTemplateManife
21147
22085
  );
21148
22086
  }
21149
22087
  return {
21150
- id: requiredString(record, "id", context),
21151
- version: requiredString(record, "version", context),
22088
+ id: requiredString(record2, "id", context),
22089
+ version: requiredString(record2, "version", context),
21152
22090
  status,
21153
- owner: requiredString(record, "owner", context),
22091
+ owner: requiredString(record2, "owner", context),
21154
22092
  createdAt,
21155
- changelog: requiredString(record, "changelog", context),
21156
- promptBuilder: requiredString(record, "promptBuilder", context),
21157
- continuationBuilder: requiredString(record, "continuationBuilder", context),
22093
+ changelog: requiredString(record2, "changelog", context),
22094
+ promptBuilder: requiredString(record2, "promptBuilder", context),
22095
+ continuationBuilder: requiredString(record2, "continuationBuilder", context),
21158
22096
  modelOutputInstruction: requiredString(
21159
- record,
22097
+ record2,
21160
22098
  "modelOutputInstruction",
21161
22099
  context
21162
22100
  ),
21163
- codeReviewPolicy: requiredString(record, "codeReviewPolicy", context),
21164
- defaultModel: requiredString(record, "defaultModel", context),
21165
- modelMatrix: requiredStringArray(record, "modelMatrix", context),
21166
- temperature: requiredNumber(record, "temperature", context),
22101
+ codeReviewPolicy: requiredString(record2, "codeReviewPolicy", context),
22102
+ defaultModel: requiredString(record2, "defaultModel", context),
22103
+ modelMatrix: requiredStringArray(record2, "modelMatrix", context),
22104
+ temperature: requiredNumber(record2, "temperature", context),
21167
22105
  compatibility: {
21168
22106
  minSdkVersion: requiredString(
21169
22107
  compatibility,
@@ -21217,27 +22155,27 @@ var DEFAULT_LOW_SIGNAL_REASONING_LINES = [
21217
22155
  function parseReasoningCommentLine(line, options = {}) {
21218
22156
  const trimmed = line.trimStart();
21219
22157
  if (!trimmed.startsWith("//")) return null;
21220
- const text = trimmed.replace(/^\/\/\s?/, "").trim();
21221
- if (!text) return { kind: "ignored" };
22158
+ const text2 = trimmed.replace(/^\/\/\s?/, "").trim();
22159
+ if (!text2) return { kind: "ignored" };
21222
22160
  const ignoredDirectives = options.ignoredCommentDirectives || DEFAULT_IGNORED_REASONING_COMMENT_DIRECTIVES;
21223
- if (ignoredDirectives.some((pattern) => pattern.test(text))) {
22161
+ if (ignoredDirectives.some((pattern) => pattern.test(text2))) {
21224
22162
  return { kind: "ignored" };
21225
22163
  }
21226
22164
  const lowSignalLines = options.lowSignalReasoningLines || DEFAULT_LOW_SIGNAL_REASONING_LINES;
21227
- if (lowSignalLines.some((pattern) => pattern.test(text))) {
22165
+ if (lowSignalLines.some((pattern) => pattern.test(text2))) {
21228
22166
  return { kind: "ignored" };
21229
22167
  }
21230
- return { kind: "reasoning", text };
22168
+ return { kind: "reasoning", text: text2 };
21231
22169
  }
21232
22170
  function consumeGranularReasoningTraceChunk(buffer, chunk, options = {}) {
21233
- let text = buffer + chunk;
22171
+ let text2 = buffer + chunk;
21234
22172
  let visibleText = "";
21235
22173
  const reasoningLines = [];
21236
22174
  while (true) {
21237
- const newlineIndex = text.indexOf("\n");
22175
+ const newlineIndex = text2.indexOf("\n");
21238
22176
  if (newlineIndex === -1) break;
21239
- const rawLine = text.slice(0, newlineIndex);
21240
- text = text.slice(newlineIndex + 1);
22177
+ const rawLine = text2.slice(0, newlineIndex);
22178
+ text2 = text2.slice(newlineIndex + 1);
21241
22179
  const comment = parseReasoningCommentLine(
21242
22180
  rawLine.replace(/\r$/, ""),
21243
22181
  options
@@ -21251,19 +22189,19 @@ function consumeGranularReasoningTraceChunk(buffer, chunk, options = {}) {
21251
22189
  `;
21252
22190
  }
21253
22191
  }
21254
- if (options.final && text.length > 0) {
21255
- const comment = parseReasoningCommentLine(text.replace(/\r$/, ""), options);
22192
+ if (options.final && text2.length > 0) {
22193
+ const comment = parseReasoningCommentLine(text2.replace(/\r$/, ""), options);
21256
22194
  if (comment?.kind === "reasoning") {
21257
22195
  reasoningLines.push(comment.text);
21258
- text = "";
22196
+ text2 = "";
21259
22197
  } else if (comment?.kind === "ignored") {
21260
- text = "";
22198
+ text2 = "";
21261
22199
  } else {
21262
- visibleText += text;
21263
- text = "";
22200
+ visibleText += text2;
22201
+ text2 = "";
21264
22202
  }
21265
22203
  }
21266
- return { buffer: text, visibleText, reasoningLines };
22204
+ return { buffer: text2, visibleText, reasoningLines };
21267
22205
  }
21268
22206
  function consumeGranularReasoningOnlyChunk(buffer, chunk, options = {}) {
21269
22207
  const result = consumeGranularReasoningTraceChunk(buffer, chunk, options);
@@ -21272,8 +22210,8 @@ function consumeGranularReasoningOnlyChunk(buffer, chunk, options = {}) {
21272
22210
  reasoningLines: result.reasoningLines
21273
22211
  };
21274
22212
  }
21275
- function stripGranularReasoningTrace(text, options = {}) {
21276
- return consumeGranularReasoningTraceChunk("", text, {
22213
+ function stripGranularReasoningTrace(text2, options = {}) {
22214
+ return consumeGranularReasoningTraceChunk("", text2, {
21277
22215
  ...options,
21278
22216
  final: true
21279
22217
  }).visibleText.trim();
@@ -21308,8 +22246,8 @@ function stableStringify(value) {
21308
22246
  if (Array.isArray(value)) {
21309
22247
  return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
21310
22248
  }
21311
- const record = value;
21312
- return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`).join(",")}}`;
22249
+ const record2 = value;
22250
+ return `{${Object.keys(record2).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(record2[key])}`).join(",")}}`;
21313
22251
  }
21314
22252
  function renderConstBlock(name, value) {
21315
22253
  return `const ${name} = ${JSON.stringify(value, null, 2)} as const;`;
@@ -21905,9 +22843,9 @@ function getLatestClosure(liveDoc) {
21905
22843
  }
21906
22844
  const closures = [];
21907
22845
  for (const [closureId, value] of Object.entries(closuresById)) {
21908
- const record = asRecord4(value);
21909
- if (!record) continue;
21910
- closures.push({ ...record, closureId });
22846
+ const record2 = asRecord4(value);
22847
+ if (!record2) continue;
22848
+ closures.push({ ...record2, closureId });
21911
22849
  }
21912
22850
  closures.sort(
21913
22851
  (left, right) => (Number(right.createdAt) || 0) - (Number(left.createdAt) || 0)
@@ -21945,9 +22883,9 @@ function getJobRecords(liveDoc) {
21945
22883
  const jobsById = asRecord4(asRecord4(liveDoc?.jobs)?.byId) || {};
21946
22884
  const jobs = [];
21947
22885
  for (const [jobId, value] of Object.entries(jobsById)) {
21948
- const record = asRecord4(value);
21949
- if (!record) continue;
21950
- jobs.push({ ...record, jobId });
22886
+ const record2 = asRecord4(value);
22887
+ if (!record2) continue;
22888
+ jobs.push({ ...record2, jobId });
21951
22889
  }
21952
22890
  jobs.sort(
21953
22891
  (left, right) => getJobTimestamp(right) - getJobTimestamp(left)
@@ -22133,8 +23071,8 @@ function hasOpenPrompt(liveDoc, pendingPrompts) {
22133
23071
  const prompts = asRecord4(asRecord4(job)?.prompts);
22134
23072
  if (!prompts) continue;
22135
23073
  for (const prompt of Object.values(prompts)) {
22136
- const record = asRecord4(prompt);
22137
- if (record?.status === "open") return true;
23074
+ const record2 = asRecord4(prompt);
23075
+ if (record2?.status === "open") return true;
22138
23076
  }
22139
23077
  }
22140
23078
  return false;
@@ -22193,13 +23131,13 @@ function projectLoopSummary(liveDoc, pendingPrompts = [], options) {
22193
23131
  title: typeof decision.title === "string" && decision.title.trim() ? decision.title.trim() : "Decision",
22194
23132
  status,
22195
23133
  candidates: status === "open" ? asArray(decision.candidates).slice(0, 5).map((candidate) => {
22196
- const record = asRecord4(candidate);
22197
- if (!record) return null;
23134
+ const record2 = asRecord4(candidate);
23135
+ if (!record2) return null;
22198
23136
  return {
22199
- id: typeof record.id === "string" ? record.id : "unknown",
22200
- label: typeof record.label === "string" && record.label.trim() ? record.label.trim() : null,
22201
- description: typeof record.description === "string" && record.description.trim() ? record.description.trim() : null,
22202
- metadata: asRecord4(record.metadata)
23137
+ id: typeof record2.id === "string" ? record2.id : "unknown",
23138
+ label: typeof record2.label === "string" && record2.label.trim() ? record2.label.trim() : null,
23139
+ description: typeof record2.description === "string" && record2.description.trim() ? record2.description.trim() : null,
23140
+ metadata: asRecord4(record2.metadata)
22203
23141
  };
22204
23142
  }).filter(Boolean) : [],
22205
23143
  selected: status === "open" ? null : {
@@ -22834,11 +23772,11 @@ function normalizeJsonSchema(value) {
22834
23772
  return asRecord4(value);
22835
23773
  }
22836
23774
  function jsonSchemaTypeName(schema) {
22837
- const record = normalizeJsonSchema(schema);
22838
- if (!record) return "unknown";
22839
- const type = record.type;
22840
- if (record.format === "date-time") return "date-time";
22841
- if (record.format === "date" || type === "date" || Array.isArray(type) && type.includes("date")) {
23775
+ const record2 = normalizeJsonSchema(schema);
23776
+ if (!record2) return "unknown";
23777
+ const type = record2.type;
23778
+ if (record2.format === "date-time") return "date-time";
23779
+ if (record2.format === "date" || type === "date" || Array.isArray(type) && type.includes("date")) {
22842
23780
  return "date";
22843
23781
  }
22844
23782
  if (typeof type === "string") {
@@ -22849,12 +23787,12 @@ function jsonSchemaTypeName(schema) {
22849
23787
  return "unknown";
22850
23788
  }
22851
23789
  function summarizeObjectSchema(schema) {
22852
- const record = normalizeJsonSchema(schema);
22853
- const properties = asRecord4(record?.properties);
23790
+ const record2 = normalizeJsonSchema(schema);
23791
+ const properties = asRecord4(record2?.properties);
22854
23792
  if (!properties || Object.keys(properties).length === 0) {
22855
- return record ? "{}" : null;
23793
+ return record2 ? "{}" : null;
22856
23794
  }
22857
- const required = new Set(asArray(record?.required));
23795
+ const required = new Set(asArray(record2?.required));
22858
23796
  const fields = Object.entries(properties).slice(0, 8).map(([name, property]) => {
22859
23797
  const marker = required.has(name) ? "*" : "?";
22860
23798
  return `${name}${marker}: ${jsonSchemaTypeName(property)}`;
@@ -23343,6 +24281,10 @@ ${actionIndex}
23343
24281
  - 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.
23344
24282
  - 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.
23345
24283
  - 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.
24284
+ - 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.
24285
+ - 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.
24286
+ - 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.
24287
+ - 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.
23346
24288
  - 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.
23347
24289
  - 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.
23348
24290
  - 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.
@@ -23596,10 +24538,10 @@ function responseTextFromFeedItems(feedItems, jobId) {
23596
24538
  if (item.kind !== "message" || item.payload.role !== "assistant" || item.source?.jobId !== jobId || item.sequence <= latestSequence) {
23597
24539
  continue;
23598
24540
  }
23599
- const text = item.payload.text.trim();
23600
- if (!text) continue;
24541
+ const text2 = item.payload.text.trim();
24542
+ if (!text2) continue;
23601
24543
  latestSequence = item.sequence;
23602
- latestText = text;
24544
+ latestText = text2;
23603
24545
  }
23604
24546
  return latestText;
23605
24547
  }
@@ -23614,39 +24556,39 @@ function pushStringArray(target, value) {
23614
24556
  pushString(target, item);
23615
24557
  }
23616
24558
  }
23617
- function collectReferencesFromRecord(record, refs) {
24559
+ function collectReferencesFromRecord(record2, refs) {
23618
24560
  for (const key of ENTRY_KEY_CANDIDATES)
23619
- pushString(refs.entryPaths, record[key]);
24561
+ pushString(refs.entryPaths, record2[key]);
23620
24562
  for (const key of ENTRY_ARRAY_KEY_CANDIDATES)
23621
- pushStringArray(refs.entryPaths, record[key]);
24563
+ pushStringArray(refs.entryPaths, record2[key]);
23622
24564
  for (const key of LIST_KEY_CANDIDATES)
23623
- pushString(refs.listNames, record[key]);
24565
+ pushString(refs.listNames, record2[key]);
23624
24566
  for (const key of LIST_ARRAY_KEY_CANDIDATES)
23625
- pushStringArray(refs.listNames, record[key]);
24567
+ pushStringArray(refs.listNames, record2[key]);
23626
24568
  for (const key of VARIABLE_KEY_CANDIDATES)
23627
- pushString(refs.variableNames, record[key]);
24569
+ pushString(refs.variableNames, record2[key]);
23628
24570
  for (const key of VARIABLE_ARRAY_KEY_CANDIDATES)
23629
- pushStringArray(refs.variableNames, record[key]);
24571
+ pushStringArray(refs.variableNames, record2[key]);
23630
24572
  }
23631
- function stringValue(record, keys) {
24573
+ function stringValue(record2, keys) {
23632
24574
  for (const key of keys) {
23633
- const value = record[key];
24575
+ const value = record2[key];
23634
24576
  if (typeof value === "string" && value.trim()) {
23635
24577
  return value.trim();
23636
24578
  }
23637
24579
  }
23638
24580
  return null;
23639
24581
  }
23640
- function findEntryPathForRecord(record, heap) {
23641
- const directPath = stringValue(record, ["entryPath", "path"]);
24582
+ function findEntryPathForRecord(record2, heap) {
24583
+ const directPath = stringValue(record2, ["entryPath", "path"]);
23642
24584
  if (directPath && heap.entriesByPath?.[directPath]) {
23643
24585
  return directPath;
23644
24586
  }
23645
- const id = stringValue(record, ["id", "_id", "recordId", "objectId"]);
24587
+ const id = stringValue(record2, ["id", "_id", "recordId", "objectId"]);
23646
24588
  if (!id) {
23647
24589
  return null;
23648
24590
  }
23649
- const className = stringValue(record, [
24591
+ const className = stringValue(record2, [
23650
24592
  "className",
23651
24593
  "_className",
23652
24594
  "__className",
@@ -23680,17 +24622,17 @@ function scanForHeapReferences(value, heap, refs, depth = 0, seen = /* @__PURE__
23680
24622
  }
23681
24623
  return;
23682
24624
  }
23683
- const record = asRecord5(value);
23684
- if (!record) return;
24625
+ const record2 = asRecord5(value);
24626
+ if (!record2) return;
23685
24627
  seen.add(value);
23686
- const entryPath = findEntryPathForRecord(record, heap);
24628
+ const entryPath = findEntryPathForRecord(record2, heap);
23687
24629
  if (entryPath) refs.entryPaths.add(entryPath);
23688
- collectReferencesFromRecord(record, refs);
24630
+ collectReferencesFromRecord(record2, refs);
23689
24631
  for (const key of UI_CONTAINER_KEYS) {
23690
- const nested = asRecord5(record[key]);
24632
+ const nested = asRecord5(record2[key]);
23691
24633
  if (nested) collectReferencesFromRecord(nested, refs);
23692
24634
  }
23693
- for (const nested of Object.values(record).slice(0, 24)) {
24635
+ for (const nested of Object.values(record2).slice(0, 24)) {
23694
24636
  scanForHeapReferences(nested, heap, refs, depth + 1, seen);
23695
24637
  }
23696
24638
  }
@@ -23839,8 +24781,8 @@ var OPENAI_MODEL_PRICING_USD_PER_MILLION = {
23839
24781
  function asRecord6(value) {
23840
24782
  return value && typeof value === "object" ? value : null;
23841
24783
  }
23842
- function numberField(record, key) {
23843
- const value = record?.[key];
24784
+ function numberField(record2, key) {
24785
+ const value = record2?.[key];
23844
24786
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
23845
24787
  }
23846
24788
  function microsPerMillion(usdPerMillion) {
@@ -23944,6 +24886,216 @@ function calculateOpenAITokenSpend(model, rawUsage) {
23944
24886
  };
23945
24887
  }
23946
24888
 
23947
- export { Environment, EnvironmentSession, GRANULAR_FEED_DIAGNOSTIC_EVENT, Granular, OPENAI_MODEL_PRICING_USD_PER_MILLION, OntologyHandle, Session, SessionFeedController, WSClient, buildContinuationInstruction, buildContinuationInstructionFromTemplate, buildGranularAgentCheckpointBlock, buildGranularAgentDomainBlock, buildGranularAgentFileBlock, buildGranularAgentHeapBlock, buildGranularAgentLoopBlock, buildGranularAgentManualActionBlock, buildGranularAgentManualActionMemorySummary, buildGranularAgentReferentBlock, buildGranularAgentRuntimeImportsBlock, buildGranularAgentSessionBlock, buildGranularAgentSystemPrompt, buildGranularAgentSystemPromptFromTemplate, buildGranularAgentToolBlock, buildGranularAgentWorkflowBlock, buildOpenAISpendEventId, buildSessionTranscript, buildSessionTranscriptFromFeedItems, calculateOpenAITokenSpend, canonicalizeCommitValue, changedRecordSnapshots, consumeGranularReasoningOnlyChunk, consumeGranularReasoningTraceChunk, createFeedPublisher, createHarnessVerifierSnapshot, defineEffect, defineProjection, defineStateMachine, emitFeedDiagnostic, emitFeedDiagnosticToDefaultSink, emptyFeedSnapshot, environmentSetupProgressFromError, environmentSetupProgressFromResponse, evaluateContinuation, evaluateValidationRule, extractPromptTokens, getCurrentClosureId, getDefaultHarnessTemplateId, getExclusivePromptTarget, getOpenAIModelPricing, hasCanonicalSessionFeedActivation, hasOpenPrompt, hashHarnessTemplateValue, invokeRegisteredEffect, isCanonicalSessionFeedDocument, isGraphQLMutationDocument, isLocalApiUrl, listHarnessTemplates, mergeFeedItemsBySequence, normalizeEffectBehaviors, normalizeFeedDiagnostic, normalizeFeedDiagnosticKind, normalizeFeedPage, normalizeOpenAIUsage, normalizePrompt, normalizePromptChoiceOption, normalizePromptText, normalizePromptType, orderTransientFeedItems, pollEnvironmentSetup, projectConversationReferentFocus, projectConversationReferentSummary, projectHeapSummary, projectLoopSummary, projectRecordSnapshotMutation, projectSessionFileSummary, projectWorkflowFocus, projectWorkflowSummary, readSessionFeedSnapshot, recordOpenAIUsageSpend, renderContinuationInstructionFromTemplate, renderGranularAgentSystemPromptFromTemplate, resolveApiUrl, resolveAuthTokenForApiUrl, resolveHarnessTemplate, resolveJobPresentation, resolvePromptAnswer, reviewGeneratedJobCode, scorePromptChoiceMatch, stripGranularReasoningTrace, toGranularHttpBase, validateHarnessTemplateManifest, validateProjectionResult, validationRuleFailureMessage };
24889
+ // src/artifact-surface.ts
24890
+ function operationNode(workflow) {
24891
+ return getArtifactWorkflowNodes(workflow).find(
24892
+ (node) => node.kind === "operation"
24893
+ ) || null;
24894
+ }
24895
+ function operationStatusLabel(phase) {
24896
+ if (phase === "reviewing") return "Preparing";
24897
+ if (phase === "sending") return "In progress";
24898
+ if (phase === "synchronizing") return "Saving";
24899
+ if (phase === "verifying") return "Verifying";
24900
+ if (phase === "waiting_for_approval") return "Awaiting approval";
24901
+ if (phase === "completed") return "Completed";
24902
+ if (phase === "failed") return "Failed";
24903
+ return null;
24904
+ }
24905
+ function action(id, label, tone, disabled, disabledReason = null) {
24906
+ return { id, label, tone, disabled, disabledReason };
24907
+ }
24908
+ function navigationIsUseful(workflow) {
24909
+ const nodes = getArtifactWorkflowNodes(workflow).filter(
24910
+ (node) => node.kind !== "operation" && node.kind !== "group"
24911
+ );
24912
+ return nodes.length > 1 || nodes.some((node) => node.kind === "child_artifact");
24913
+ }
24914
+ function inputNode(workflow, activeEditorNodeId) {
24915
+ if (activeEditorNodeId) {
24916
+ const active = workflow.nodesById[activeEditorNodeId];
24917
+ if (active && (active.kind === "input" || active.kind === "relationship")) {
24918
+ return active;
24919
+ }
24920
+ }
24921
+ const current = getCurrentArtifactWorkflowNode(workflow);
24922
+ return current?.kind === "input" || current?.kind === "relationship" ? current : null;
24923
+ }
24924
+ function selectArtifactSurface(artifact, context = {}) {
24925
+ const workflow = artifact.workflow || projectArtifactWorkflow(artifact);
24926
+ const operation = operationNode(workflow);
24927
+ const workflowStatus = operation?.status || null;
24928
+ const terminal = workflowStatus === "completed" || workflowStatus === "canceled";
24929
+ const requestedEditorNodeId = terminal ? null : context.activeEditorNodeId || null;
24930
+ const current = getCurrentArtifactWorkflowNode(workflow);
24931
+ const activeInput = inputNode(workflow, requestedEditorNodeId);
24932
+ const mutationsDisabled = context.mutationsDisabled === true;
24933
+ const busy = Boolean(context.busyAction);
24934
+ const disabledReason = mutationsDisabled ? "Reconnect to continue." : busy ? "This action is already in progress." : null;
24935
+ const actionDisabled = Boolean(disabledReason);
24936
+ const transportStatus = context.transportStatus || "connected";
24937
+ let kind;
24938
+ if (artifact.status === "completed" && workflowStatus === "completed") {
24939
+ kind = "result";
24940
+ } else if (artifact.status === "completed" && workflowStatus === "failed") {
24941
+ kind = "failure";
24942
+ } else if (artifact.status === "completed" && workflowStatus === "working") {
24943
+ kind = "executing";
24944
+ } else if (artifact.status === "completed" && workflowStatus && workflowStatus !== "canceled") {
24945
+ kind = "blocked";
24946
+ } else if (artifact.status === "completed") kind = "result";
24947
+ else if (artifact.status === "canceled") kind = "canceled";
24948
+ else if (artifact.status === "stale") kind = "stale";
24949
+ else if (artifact.status === "partially_completed") kind = "partial";
24950
+ else if (artifact.status === "failed" || context.directError)
24951
+ kind = "failure";
24952
+ else if (artifact.status === "blocked") kind = "blocked";
24953
+ else if (artifact.status === "awaiting_human") kind = "approval";
24954
+ else if (artifact.status === "running" || busy) kind = "executing";
24955
+ else if (requestedEditorNodeId || artifact.status === "draft" || artifact.status === "needs_edit") {
24956
+ kind = "input";
24957
+ } else {
24958
+ kind = "review";
24959
+ }
24960
+ let title = artifact.label;
24961
+ let description = artifact.description || null;
24962
+ let statusLabel = null;
24963
+ let primaryAction = null;
24964
+ let secondaryActions = [];
24965
+ if (kind === "input") {
24966
+ title = activeInput?.label || artifact.label;
24967
+ description = activeInput?.description || artifact.description || null;
24968
+ statusLabel = activeInput?.status === "needs_input" ? "Needs input" : null;
24969
+ primaryAction = action(
24970
+ "continue",
24971
+ "Continue",
24972
+ "primary",
24973
+ actionDisabled,
24974
+ disabledReason
24975
+ );
24976
+ } else if (kind === "review") {
24977
+ title = artifact.label;
24978
+ description = artifact.description || operation?.description || null;
24979
+ primaryAction = action(
24980
+ "execute",
24981
+ operation?.label || "Continue",
24982
+ "primary",
24983
+ actionDisabled || context.canExecute === false,
24984
+ disabledReason
24985
+ );
24986
+ if (context.canEdit) {
24987
+ secondaryActions = [
24988
+ action(
24989
+ "edit",
24990
+ "Edit details",
24991
+ "secondary",
24992
+ actionDisabled,
24993
+ disabledReason
24994
+ )
24995
+ ];
24996
+ }
24997
+ } else if (kind === "executing") {
24998
+ title = operation?.operationDetail || operation?.label || artifact.label;
24999
+ description = operation?.operationDetail ? artifact.label : null;
25000
+ statusLabel = operationStatusLabel(operation?.operationPhase) || "In progress";
25001
+ } else if (kind === "approval") {
25002
+ title = operation?.attention?.title || operation?.label || artifact.label;
25003
+ description = operation?.attention?.detail || operation?.description || null;
25004
+ statusLabel = "Awaiting approval";
25005
+ if (context.canApprove) {
25006
+ primaryAction = action(
25007
+ "approve",
25008
+ "Approve",
25009
+ "primary",
25010
+ actionDisabled,
25011
+ disabledReason
25012
+ );
25013
+ secondaryActions = [
25014
+ action(
25015
+ "decline",
25016
+ "Decline",
25017
+ "secondary",
25018
+ actionDisabled,
25019
+ disabledReason
25020
+ )
25021
+ ];
25022
+ }
25023
+ } else if (kind === "failure" || kind === "blocked") {
25024
+ const attention = current?.attention || operation?.attention;
25025
+ title = attention?.title || artifact.label;
25026
+ description = context.directError || attention?.detail || operation?.description || null;
25027
+ statusLabel = kind === "failure" ? "Failed" : "Needs attention";
25028
+ if (attention?.retryable) {
25029
+ primaryAction = action(
25030
+ "retry",
25031
+ "Retry",
25032
+ "primary",
25033
+ actionDisabled,
25034
+ disabledReason
25035
+ );
25036
+ } else if (context.canEdit) {
25037
+ primaryAction = action(
25038
+ "edit",
25039
+ "Edit details",
25040
+ "primary",
25041
+ actionDisabled,
25042
+ disabledReason
25043
+ );
25044
+ }
25045
+ } else if (kind === "partial") {
25046
+ title = operation?.attention?.title || artifact.label;
25047
+ description = operation?.attention?.detail || operation?.description || null;
25048
+ statusLabel = "Partially completed";
25049
+ primaryAction = action(
25050
+ "retry",
25051
+ "Retry unfinished work",
25052
+ "primary",
25053
+ actionDisabled,
25054
+ disabledReason
25055
+ );
25056
+ } else if (kind === "stale") {
25057
+ title = operation?.attention?.title || artifact.label;
25058
+ description = operation?.attention?.detail || operation?.description || null;
25059
+ statusLabel = "Details changed";
25060
+ primaryAction = action(
25061
+ "refresh",
25062
+ "Review latest details",
25063
+ "primary",
25064
+ actionDisabled,
25065
+ disabledReason
25066
+ );
25067
+ } else if (kind === "result") {
25068
+ title = artifact.label;
25069
+ description = operation?.recap?.summary || artifact.description || null;
25070
+ statusLabel = "Completed";
25071
+ primaryAction = action("close", "Done", "primary", false);
25072
+ } else {
25073
+ title = artifact.label;
25074
+ description = operation?.attention?.detail || "Nothing else will run.";
25075
+ statusLabel = "Canceled";
25076
+ primaryAction = action("close", "Done", "primary", false);
25077
+ }
25078
+ const activeNodeId = kind === "input" ? activeInput?.id || workflow.currentNodeId : workflow.currentNodeId;
25079
+ const announcement = [statusLabel, title, description].filter((value) => Boolean(value)).join(". ");
25080
+ return {
25081
+ artifactId: artifact.artifactId,
25082
+ revision: workflow.revision,
25083
+ updatedAt: artifact.updatedAt,
25084
+ kind,
25085
+ title,
25086
+ description,
25087
+ statusLabel,
25088
+ announcement,
25089
+ workflow,
25090
+ activeNodeId,
25091
+ operationNodeId: operation?.id || null,
25092
+ showNavigation: navigationIsUseful(workflow),
25093
+ primaryAction,
25094
+ secondaryActions,
25095
+ transportStatus
25096
+ };
25097
+ }
25098
+
25099
+ export { Environment, EnvironmentSession, GRANULAR_FEED_DIAGNOSTIC_EVENT, Granular, OPENAI_MODEL_PRICING_USD_PER_MILLION, OntologyHandle, Session, SessionFeedController, WSClient, assertArtifactWorkflow, buildContinuationInstruction, buildContinuationInstructionFromTemplate, buildGranularAgentCheckpointBlock, buildGranularAgentDomainBlock, buildGranularAgentFileBlock, buildGranularAgentHeapBlock, buildGranularAgentLoopBlock, buildGranularAgentManualActionBlock, buildGranularAgentManualActionMemorySummary, buildGranularAgentReferentBlock, buildGranularAgentRuntimeImportsBlock, buildGranularAgentSessionBlock, buildGranularAgentSystemPrompt, buildGranularAgentSystemPromptFromTemplate, buildGranularAgentToolBlock, buildGranularAgentWorkflowBlock, buildOpenAISpendEventId, buildSessionTranscript, buildSessionTranscriptFromFeedItems, calculateOpenAITokenSpend, canInspectArtifactWorkflowNode, canonicalizeCommitValue, changedRecordSnapshots, consumeGranularReasoningOnlyChunk, consumeGranularReasoningTraceChunk, createFeedPublisher, createHarnessVerifierSnapshot, defineEffect, defineProjection, defineStateMachine, emitFeedDiagnostic, emitFeedDiagnosticToDefaultSink, emptyFeedSnapshot, environmentSetupProgressFromError, environmentSetupProgressFromResponse, evaluateContinuation, evaluateValidationRule, extractPromptTokens, getArtifactWorkflowNavigationLeaves, getArtifactWorkflowNavigationTarget, getArtifactWorkflowNodes, getCurrentArtifactWorkflowNode, getCurrentClosureId, getDefaultHarnessTemplateId, getExclusivePromptTarget, getOpenAIModelPricing, hasCanonicalSessionFeedActivation, hasOpenPrompt, hashHarnessTemplateValue, invokeRegisteredEffect, isArtifactWorkflowNodeNavigableLeaf, isArtifactWorkflowNodeTerminal, isCanonicalSessionFeedDocument, isGraphQLMutationDocument, isLocalApiUrl, listHarnessTemplates, mergeFeedItemsBySequence, normalizeEffectBehaviors, normalizeFeedDiagnostic, normalizeFeedDiagnosticKind, normalizeFeedPage, normalizeOpenAIUsage, normalizePrompt, normalizePromptChoiceOption, normalizePromptText, normalizePromptType, orderTransientFeedItems, pollEnvironmentSetup, projectArtifactWorkflow, projectConversationReferentFocus, projectConversationReferentSummary, projectHeapSummary, projectLoopSummary, projectRecordSnapshotMutation, projectSessionFileSummary, projectWorkflowFocus, projectWorkflowSummary, readSessionFeedSnapshot, recordOpenAIUsageSpend, renderContinuationInstructionFromTemplate, renderGranularAgentSystemPromptFromTemplate, requireArtifactWorkflow, requireArtifactWorkflowResult, resolveApiUrl, resolveAuthTokenForApiUrl, resolveHarnessTemplate, resolveJobPresentation, resolvePromptAnswer, reviewGeneratedJobCode, scorePromptChoiceMatch, selectArtifactSurface, stripGranularReasoningTrace, toGranularHttpBase, validateHarnessTemplateManifest, validateProjectionResult, validationRuleFailureMessage };
23948
25100
  //# sourceMappingURL=index.mjs.map
23949
25101
  //# sourceMappingURL=index.mjs.map