@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.js CHANGED
@@ -4300,8 +4300,8 @@ function normalizeFeedItems(values) {
4300
4300
  return { items: Object.freeze(items), error };
4301
4301
  }
4302
4302
  function normalizeTransients(values) {
4303
- const record = asRecord(values);
4304
- if (!record) {
4303
+ const record2 = asRecord(values);
4304
+ if (!record2) {
4305
4305
  return {
4306
4306
  items: [],
4307
4307
  error: new Error("Canonical feed transientById is not an object.")
@@ -4310,7 +4310,7 @@ function normalizeTransients(values) {
4310
4310
  let error = null;
4311
4311
  const byId = /* @__PURE__ */ new Map();
4312
4312
  const idByOrdinal = /* @__PURE__ */ new Map();
4313
- for (const [key, value] of Object.entries(record)) {
4313
+ for (const [key, value] of Object.entries(record2)) {
4314
4314
  const item = normalizeTransientFeedItem(value);
4315
4315
  if (!item || item.id !== key) {
4316
4316
  error ||= new Error("Canonical feed contains an invalid transient item.");
@@ -5183,11 +5183,11 @@ function operationId(prefix) {
5183
5183
  return randomUuid ? `${prefix}_${randomUuid}` : `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2)}`;
5184
5184
  }
5185
5185
  function unwrapPublishedItem(value) {
5186
- const record = asRecord(value);
5187
- const directItems = Array.isArray(record?.items) ? record.items : [];
5188
- const durable = asRecord(record?.durable);
5186
+ const record2 = asRecord(value);
5187
+ const directItems = Array.isArray(record2?.items) ? record2.items : [];
5188
+ const durable = asRecord(record2?.durable);
5189
5189
  const durableItems = Array.isArray(durable?.items) ? durable.items : [];
5190
- return record?.item || directItems[0] || durableItems[0] || value;
5190
+ return record2?.item || directItems[0] || durableItems[0] || value;
5191
5191
  }
5192
5192
  function requireFeedbackItem(value) {
5193
5193
  const item = normalizeFeedItem(unwrapPublishedItem(value));
@@ -5227,14 +5227,14 @@ function createFeedPublisher(publish) {
5227
5227
  get revision() {
5228
5228
  return current.revision;
5229
5229
  },
5230
- update(text, options = {}) {
5230
+ update(text2, options = {}) {
5231
5231
  const reservedOperationId = options.operationId || operationId("feed_transient_update");
5232
5232
  const reservedOptions = { ...options };
5233
5233
  return enqueueMutation(async () => {
5234
5234
  const response = await publish("feed.transient.update", {
5235
5235
  transientId: current.id,
5236
5236
  expectedRevision: current.revision,
5237
- text,
5237
+ text: text2,
5238
5238
  ...reservedOptions,
5239
5239
  operationId: reservedOperationId
5240
5240
  });
@@ -5242,14 +5242,14 @@ function createFeedPublisher(publish) {
5242
5242
  return handle;
5243
5243
  });
5244
5244
  },
5245
- settle(text, options = {}) {
5245
+ settle(text2, options = {}) {
5246
5246
  const reservedOperationId = options.operationId || operationId("feed_transient_settle");
5247
5247
  const reservedOptions = { ...options };
5248
5248
  return enqueueMutation(async () => {
5249
5249
  const response = await publish("feed.transient.settle", {
5250
5250
  transientId: current.id,
5251
5251
  expectedRevision: current.revision,
5252
- ...text === void 0 ? {} : { text },
5252
+ ...text2 === void 0 ? {} : { text: text2 },
5253
5253
  ...reservedOptions,
5254
5254
  operationId: reservedOperationId
5255
5255
  });
@@ -5267,19 +5267,19 @@ function createFeedPublisher(publish) {
5267
5267
  return handle;
5268
5268
  };
5269
5269
  return {
5270
- async feedback(text, options = {}) {
5270
+ async feedback(text2, options = {}) {
5271
5271
  return requireFeedbackItem(
5272
5272
  await publish("feed.feedback", {
5273
- text,
5273
+ text: text2,
5274
5274
  ...options,
5275
5275
  operationId: options.operationId || operationId("feed_feedback")
5276
5276
  })
5277
5277
  );
5278
5278
  },
5279
- async transientFeedback(text, options = {}) {
5279
+ async transientFeedback(text2, options = {}) {
5280
5280
  const item = requireTransientFeedbackItem(
5281
5281
  await publish("feed.transient.create", {
5282
- text,
5282
+ text: text2,
5283
5283
  ...options,
5284
5284
  operationId: options.operationId || operationId("feed_transient_create")
5285
5285
  })
@@ -5309,9 +5309,9 @@ var DEFAULT_MAX_RECONNECT_ATTEMPTS = 5;
5309
5309
  var DOCUMENT_RESYNC_CLOSE_CODE = 4e3;
5310
5310
  var DOCUMENT_RESYNC_CLOSE_REASON = "Document resync required";
5311
5311
  function documentVersion(document) {
5312
- const record = document;
5313
- const epochValue = record.documentEpoch;
5314
- const revisionValue = record.documentRevision;
5312
+ const record2 = document;
5313
+ const epochValue = record2.documentEpoch;
5314
+ const revisionValue = record2.documentRevision;
5315
5315
  const epoch = epochValue === void 0 ? 0 : epochValue;
5316
5316
  const revision = revisionValue === void 0 ? 0 : revisionValue;
5317
5317
  if (!Number.isSafeInteger(epoch) || Number(epoch) < 0 || !Number.isSafeInteger(revision) || Number(revision) < 0) {
@@ -5333,11 +5333,43 @@ function stableDocumentFingerprint(value) {
5333
5333
  if (Array.isArray(value)) {
5334
5334
  return `[${value.map(stableDocumentFingerprint).join(",")}]`;
5335
5335
  }
5336
- const record = value;
5337
- return `{${Object.keys(record).sort().map(
5338
- (key) => `${JSON.stringify(key)}:${stableDocumentFingerprint(record[key])}`
5336
+ const record2 = value;
5337
+ return `{${Object.keys(record2).sort().map(
5338
+ (key) => `${JSON.stringify(key)}:${stableDocumentFingerprint(record2[key])}`
5339
5339
  ).join(",")}}`;
5340
5340
  }
5341
+ function canonicalDocumentFingerprint(value) {
5342
+ const stripPresentation = (input, path) => {
5343
+ if (input === null || typeof input !== "object") return input;
5344
+ if (input instanceof Date || input instanceof Uint8Array) return input;
5345
+ if (Array.isArray(input)) {
5346
+ return input.map((entry) => stripPresentation(entry, path));
5347
+ }
5348
+ const record2 = input;
5349
+ const output = {};
5350
+ for (const [key, entry] of Object.entries(record2)) {
5351
+ if (path.length === 1 && path[0] === "feed" && [
5352
+ "transientById",
5353
+ "feedbackUsageByJob",
5354
+ "revision",
5355
+ "lastTransientOrdinal"
5356
+ ].includes(key)) {
5357
+ continue;
5358
+ }
5359
+ if (path.length === 3 && path[0] === "jobs" && path[1] === "byId" && [
5360
+ "actionTrace",
5361
+ "actionSummary",
5362
+ "actionSummaryText",
5363
+ "lastActionAt"
5364
+ ].includes(key)) {
5365
+ continue;
5366
+ }
5367
+ output[key] = stripPresentation(entry, [...path, key]);
5368
+ }
5369
+ return output;
5370
+ };
5371
+ return stableDocumentFingerprint(stripPresentation(value, []));
5372
+ }
5341
5373
  function debugWs(...args) {
5342
5374
  if (DEBUG_WS) {
5343
5375
  console.log(...args);
@@ -5655,6 +5687,7 @@ var WSClient = class {
5655
5687
  }
5656
5688
  this.lastInboundActivityAt = Date.now();
5657
5689
  this.reconnectAttempts = 0;
5690
+ this.connectPromise = null;
5658
5691
  this.emit("open", {});
5659
5692
  finish();
5660
5693
  };
@@ -5662,8 +5695,8 @@ var WSClient = class {
5662
5695
  if (!isCurrent()) return;
5663
5696
  this.lastInboundActivityAt = Date.now();
5664
5697
  try {
5665
- const text = typeof data === "string" ? data : data && typeof data === "object" && "toString" in data ? String(data.toString()) : "";
5666
- this.handleMessage(JSON.parse(text));
5698
+ const text2 = typeof data === "string" ? data : data && typeof data === "object" && "toString" in data ? String(data.toString()) : "";
5699
+ this.handleMessage(JSON.parse(text2));
5667
5700
  } catch (error) {
5668
5701
  console.error("[Granular] Failed to parse message:", error);
5669
5702
  }
@@ -5721,8 +5754,8 @@ var WSClient = class {
5721
5754
  if (typeof reason === "string") return reason;
5722
5755
  if (typeof reason === "object" && reason && "toString" in reason) {
5723
5756
  try {
5724
- const text = String(reason.toString());
5725
- return text || void 0;
5757
+ const text2 = String(reason.toString());
5758
+ return text2 || void 0;
5726
5759
  } catch {
5727
5760
  return void 0;
5728
5761
  }
@@ -5817,7 +5850,7 @@ var WSClient = class {
5817
5850
  this.canonicalDocumentActivated = true;
5818
5851
  }
5819
5852
  }
5820
- canAcceptDocumentReplacement(replacement, strictlyNewer) {
5853
+ canAcceptDocumentReplacement(replacement, strictlyNewer, options = {}) {
5821
5854
  const replacementCanonical = isCanonicalSessionFeedDocument(replacement);
5822
5855
  const currentCanonical = isCanonicalSessionFeedDocument(this.doc);
5823
5856
  if (replacementCanonical) {
@@ -5866,7 +5899,7 @@ var WSClient = class {
5866
5899
  debugWs(`[Granular DEBUG] Rejected session snapshot: ${canonicalError}`);
5867
5900
  return false;
5868
5901
  }
5869
- if (this.canonicalDocumentActivated && incoming.epoch === current.epoch && incoming.revision === current.revision && !readSessionFeedSnapshot(this.doc).error && stableDocumentFingerprint(Automerge__namespace.toJS(replacement)) !== stableDocumentFingerprint(Automerge__namespace.toJS(this.doc))) {
5902
+ if (this.canonicalDocumentActivated && incoming.epoch === current.epoch && incoming.revision === current.revision && !readSessionFeedSnapshot(this.doc).error && (options.allowEqualRevisionPresentation ? canonicalDocumentFingerprint(Automerge__namespace.toJS(replacement)) : stableDocumentFingerprint(Automerge__namespace.toJS(replacement))) !== (options.allowEqualRevisionPresentation ? canonicalDocumentFingerprint(Automerge__namespace.toJS(this.doc)) : stableDocumentFingerprint(Automerge__namespace.toJS(this.doc)))) {
5870
5903
  debugWs(
5871
5904
  "[Granular DEBUG] Rejected divergent canonical snapshot at the accepted document version."
5872
5905
  );
@@ -5988,6 +6021,7 @@ var WSClient = class {
5988
6021
  reconnectScheduled: false
5989
6022
  };
5990
6023
  this.ws = null;
6024
+ this.connectPromise = null;
5991
6025
  this.emit("close", info);
5992
6026
  if (this.reconnectTimer) {
5993
6027
  clearTimeout(this.reconnectTimer);
@@ -6025,13 +6059,89 @@ var WSClient = class {
6025
6059
  );
6026
6060
  return;
6027
6061
  }
6062
+ if ("type" in message && message.type === "job_action_trace") {
6063
+ const update = message;
6064
+ 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(
6065
+ (event) => event && typeof event === "object" && typeof event.id === "string" && typeof event.timestamp === "number" && Number.isFinite(event.timestamp)
6066
+ ) || !Array.isArray(update.actionSummary) || update.actionSummary.length > 16 || !update.actionSummary.every((line) => typeof line === "string") || typeof update.actionSummaryText !== "string" || update.actionSummaryText.length > 8e3) {
6067
+ console.warn("[Granular] Ignoring invalid job_action_trace message");
6068
+ return;
6069
+ }
6070
+ const currentVersion = documentVersion(this.doc);
6071
+ if (update.documentEpoch < currentVersion.epoch || update.documentEpoch === currentVersion.epoch && update.documentRevision < currentVersion.revision) {
6072
+ return;
6073
+ }
6074
+ if (update.documentEpoch > currentVersion.epoch || update.documentEpoch === currentVersion.epoch && update.documentRevision > currentVersion.revision) {
6075
+ return;
6076
+ }
6077
+ const current = Automerge__namespace.toJS(this.doc);
6078
+ const job = current.jobs?.byId?.[update.jobId];
6079
+ if (job && typeof job === "object" && !Array.isArray(job)) {
6080
+ job.actionTrace = update.actionTrace;
6081
+ job.actionSummary = update.actionSummary;
6082
+ job.actionSummaryText = update.actionSummaryText;
6083
+ const lastEvent = update.actionTrace[update.actionTrace.length - 1];
6084
+ if (lastEvent && typeof lastEvent.timestamp === "number") {
6085
+ job.lastActionAt = lastEvent.timestamp;
6086
+ }
6087
+ this.doc = Automerge__namespace.from(current);
6088
+ this.emit("job_action_trace", update);
6089
+ this.emit("sync", this.doc);
6090
+ }
6091
+ return;
6092
+ }
6093
+ if ("type" in message && message.type === "feed_transient_patch") {
6094
+ const update = message;
6095
+ 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) => {
6096
+ if (!change || typeof change !== "object" || typeof change.transientId !== "string" || change.transientId.length === 0) {
6097
+ return false;
6098
+ }
6099
+ if (change.removed === true) return change.item === void 0;
6100
+ const item = change.item;
6101
+ return Boolean(
6102
+ item && typeof item === "object" && !Array.isArray(item) && item.id === change.transientId
6103
+ );
6104
+ })) {
6105
+ console.warn("[Granular] Ignoring invalid feed_transient_patch");
6106
+ return;
6107
+ }
6108
+ const currentVersion = documentVersion(this.doc);
6109
+ 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) {
6110
+ return;
6111
+ }
6112
+ const current = Automerge__namespace.toJS(this.doc);
6113
+ const feed = current.feed;
6114
+ if (!feed || typeof feed !== "object" || Array.isArray(feed)) return;
6115
+ if (Number.isSafeInteger(feed.revision) && feed.revision > update.feedRevision) {
6116
+ return;
6117
+ }
6118
+ if (Number.isSafeInteger(feed.lastTransientOrdinal) && feed.lastTransientOrdinal > update.lastTransientOrdinal) {
6119
+ return;
6120
+ }
6121
+ if (!feed.transientById || typeof feed.transientById !== "object") {
6122
+ feed.transientById = {};
6123
+ }
6124
+ for (const change of update.changes) {
6125
+ if (change.removed === true) {
6126
+ delete feed.transientById[change.transientId];
6127
+ } else {
6128
+ feed.transientById[change.transientId] = change.item;
6129
+ }
6130
+ }
6131
+ feed.revision = update.feedRevision;
6132
+ feed.lastTransientOrdinal = update.lastTransientOrdinal;
6133
+ this.doc = Automerge__namespace.from(current);
6134
+ this.emit("feed_transient_patch", update);
6135
+ this.emit("sync", this.doc);
6136
+ return;
6137
+ }
6028
6138
  if ("type" in message && message.type === "snapshot_reset") {
6029
6139
  const resetMessage = message;
6030
6140
  try {
6031
- if (!Number.isSafeInteger(resetMessage.documentEpoch) || resetMessage.documentEpoch <= 0 || !Number.isSafeInteger(resetMessage.documentRevision) || resetMessage.documentRevision < 0 || !Array.isArray(resetMessage.data)) {
6141
+ 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))) {
6032
6142
  throw new Error("snapshot_reset metadata is invalid");
6033
6143
  }
6034
- const replacement = Automerge__namespace.load(
6144
+ const replacement = resetMessage.document ? Automerge__namespace.from(resetMessage.document) : Automerge__namespace.load(
6035
6145
  this.decodeDocumentBytes(
6036
6146
  resetMessage.data,
6037
6147
  "Automerge snapshot_reset"
@@ -6045,7 +6155,10 @@ var WSClient = class {
6045
6155
  "snapshot_reset metadata does not match the saved document"
6046
6156
  );
6047
6157
  }
6048
- if (!this.canAcceptDocumentReplacement(replacement, true)) {
6158
+ const isJsonReplacement = Boolean(resetMessage.document);
6159
+ if (!this.canAcceptDocumentReplacement(replacement, !isJsonReplacement, {
6160
+ allowEqualRevisionPresentation: isJsonReplacement
6161
+ })) {
6049
6162
  if (this.documentRepairRequired) {
6050
6163
  this.requireDocumentResync(
6051
6164
  "Stale Automerge snapshot_reset during document repair",
@@ -6338,30 +6451,30 @@ function parseJsonPromptChoiceOption(option) {
6338
6451
  }
6339
6452
  function normalizePromptChoiceOption(option) {
6340
6453
  if (typeof option === "string") {
6341
- const record2 = parseJsonPromptChoiceOption(option);
6342
- if (!record2) {
6454
+ const record3 = parseJsonPromptChoiceOption(option);
6455
+ if (!record3) {
6343
6456
  return { value: option, label: option };
6344
6457
  }
6345
- const value2 = typeof record2.value === "string" ? record2.value : typeof record2.id === "string" ? record2.id : typeof record2.label === "string" ? record2.label : JSON.stringify(record2);
6458
+ const value2 = typeof record3.value === "string" ? record3.value : typeof record3.id === "string" ? record3.id : typeof record3.label === "string" ? record3.label : JSON.stringify(record3);
6346
6459
  return {
6347
6460
  value: value2,
6348
- label: typeof record2.label === "string" ? record2.label : value2,
6349
- description: typeof record2.description === "string" ? record2.description : void 0
6461
+ label: typeof record3.label === "string" ? record3.label : value2,
6462
+ description: typeof record3.description === "string" ? record3.description : void 0
6350
6463
  };
6351
6464
  }
6352
- const record = option;
6353
- if (!record) {
6465
+ const record2 = option;
6466
+ if (!record2) {
6354
6467
  return { value: "", label: "" };
6355
6468
  }
6356
- const nestedJson = (typeof record.value === "string" ? parseJsonPromptChoiceOption(record.value) : null) || (typeof record.label === "string" ? parseJsonPromptChoiceOption(record.label) : null);
6469
+ const nestedJson = (typeof record2.value === "string" ? parseJsonPromptChoiceOption(record2.value) : null) || (typeof record2.label === "string" ? parseJsonPromptChoiceOption(record2.label) : null);
6357
6470
  if (nestedJson) {
6358
6471
  return normalizePromptChoiceOption(nestedJson);
6359
6472
  }
6360
- const value = typeof record.value === "string" ? record.value : typeof record.label === "string" ? record.label : JSON.stringify(record);
6473
+ const value = typeof record2.value === "string" ? record2.value : typeof record2.label === "string" ? record2.label : JSON.stringify(record2);
6361
6474
  return {
6362
6475
  value,
6363
- label: typeof record.label === "string" ? record.label : value,
6364
- description: typeof record.description === "string" ? record.description : void 0
6476
+ label: typeof record2.label === "string" ? record2.label : value,
6477
+ description: typeof record2.description === "string" ? record2.description : void 0
6365
6478
  };
6366
6479
  }
6367
6480
  function scorePromptChoiceMatch(answer, answerTokens, option) {
@@ -6653,18 +6766,18 @@ var Session = class {
6653
6766
  get revision() {
6654
6767
  return current.revision;
6655
6768
  },
6656
- async update(text, options = {}) {
6769
+ async update(text2, options = {}) {
6657
6770
  current = await track(
6658
- current.update(text, {
6771
+ current.update(text2, {
6659
6772
  ...options,
6660
6773
  operationId: options.operationId || nextOperationId("transient-update")
6661
6774
  })
6662
6775
  );
6663
6776
  return wrapped;
6664
6777
  },
6665
- settle(text, options = {}) {
6778
+ settle(text2, options = {}) {
6666
6779
  return track(
6667
- current.settle(text, {
6780
+ current.settle(text2, {
6668
6781
  ...options,
6669
6782
  operationId: options.operationId || nextOperationId("transient-settle")
6670
6783
  })
@@ -6674,14 +6787,14 @@ var Session = class {
6674
6787
  return wrapped;
6675
6788
  };
6676
6789
  return {
6677
- feedback: (text, options = {}) => track(
6678
- publisher.feedback(text, {
6790
+ feedback: (text2, options = {}) => track(
6791
+ publisher.feedback(text2, {
6679
6792
  ...options,
6680
6793
  operationId: options.operationId || nextOperationId("feedback")
6681
6794
  })
6682
6795
  ),
6683
- transientFeedback: (text, options = {}) => track(
6684
- publisher.transientFeedback(text, {
6796
+ transientFeedback: (text2, options = {}) => track(
6797
+ publisher.transientFeedback(text2, {
6685
6798
  ...options,
6686
6799
  operationId: options.operationId || nextOperationId("transient-create")
6687
6800
  }).then(wrapTransientHandle)
@@ -7869,8 +7982,8 @@ function artifactRecordsById(liveDoc) {
7869
7982
  const byId = asRecord3(artifacts?.byId) || {};
7870
7983
  return Object.fromEntries(
7871
7984
  Object.entries(byId).map(([artifactId, value]) => {
7872
- const record = asRecord3(value);
7873
- return record ? [artifactId, record] : null;
7985
+ const record2 = asRecord3(value);
7986
+ return record2 ? [artifactId, record2] : null;
7874
7987
  }).filter((entry) => Boolean(entry))
7875
7988
  );
7876
7989
  }
@@ -7903,8 +8016,8 @@ function latestInputEditSummary(metadata) {
7903
8016
  ].filter(Boolean);
7904
8017
  return `lastEdit=${source}${actor ? ` by ${actor}` : ""}${changed.length ? ` (${changed.join("; ")})` : ""}`;
7905
8018
  }
7906
- function artifactIssueSummary(record) {
7907
- const validation = asRecord3(record.validation);
8019
+ function artifactIssueSummary(record2) {
8020
+ const validation = asRecord3(record2.validation);
7908
8021
  if (!validation) return null;
7909
8022
  const issues = Array.isArray(validation.issues) ? validation.issues.map((issue) => asRecord3(issue)).filter((issue) => Boolean(issue)).slice(0, 3) : [];
7910
8023
  if (issues.length > 0) {
@@ -7951,18 +8064,18 @@ function artifactStatePathSummary(metadata) {
7951
8064
  ].filter(Boolean);
7952
8065
  return pieces.length ? pieces.join("; ") : null;
7953
8066
  }
7954
- function artifactSummaryLine(artifactId, record) {
7955
- if (!record) return `- ${artifactId}: unavailable in session artifact store`;
7956
- const label = trimString(record.label) || artifactId;
7957
- const kind = asString(record.kind) || "artifact";
7958
- const status = asString(record.status) || "unknown";
7959
- const createdByJobId = asString(record.createdByJobId);
7960
- const target = asRecord3(record.target);
7961
- const metadata = asRecord3(record.metadata) || {};
7962
- const subArtifactIds = Array.isArray(record.subArtifactIds) ? record.subArtifactIds.filter(
8067
+ function artifactSummaryLine(artifactId, record2) {
8068
+ if (!record2) return `- ${artifactId}: unavailable in session artifact store`;
8069
+ const label = trimString(record2.label) || artifactId;
8070
+ const kind = asString(record2.kind) || "artifact";
8071
+ const status = asString(record2.status) || "unknown";
8072
+ const createdByJobId = asString(record2.createdByJobId);
8073
+ const target = asRecord3(record2.target);
8074
+ const metadata = asRecord3(record2.metadata) || {};
8075
+ const subArtifactIds = Array.isArray(record2.subArtifactIds) ? record2.subArtifactIds.filter(
7963
8076
  (id) => typeof id === "string" && id.trim().length > 0
7964
8077
  ).slice(0, 8) : [];
7965
- const relationships = compactJson(record.relationships, 220);
8078
+ const relationships = compactJson(record2.relationships, 220);
7966
8079
  const pieces = [
7967
8080
  `kind=${kind}`,
7968
8081
  `status=${status}`,
@@ -7970,7 +8083,7 @@ function artifactSummaryLine(artifactId, record) {
7970
8083
  target ? `target=${asString(target.className) || "record"}:${asString(target.id) || "unknown"}${asString(target.label) ? ` (${asString(target.label)})` : ""}` : null,
7971
8084
  artifactStatePathSummary(metadata),
7972
8085
  artifactExecutionSummary(metadata),
7973
- artifactIssueSummary(record),
8086
+ artifactIssueSummary(record2),
7974
8087
  latestInputEditSummary(metadata),
7975
8088
  subArtifactIds.length ? `subArtifacts=${subArtifactIds.join(",")}` : null,
7976
8089
  relationships ? `relationships=${relationships}` : null
@@ -12390,6 +12503,7 @@ var StateMachineTransitionSchema = external_exports.object({
12390
12503
  to: external_exports.string().min(1),
12391
12504
  label: external_exports.string().optional(),
12392
12505
  description: external_exports.string().optional(),
12506
+ userVisible: external_exports.boolean().optional(),
12393
12507
  action: StateTransitionActionSchema.optional(),
12394
12508
  assignee: StateTransitionAssigneeSchema.optional(),
12395
12509
  requirements: StateTransitionRequirementsSchema.optional(),
@@ -12474,6 +12588,7 @@ var CreatesSchema = external_exports.union([
12474
12588
  idPath: external_exports.string().min(1).optional(),
12475
12589
  pathPath: external_exports.string().min(1).optional(),
12476
12590
  statePath: external_exports.string().min(1).optional(),
12591
+ completionState: external_exports.string().min(1).optional(),
12477
12592
  classStateHandle: external_exports.boolean().optional()
12478
12593
  }).strict()
12479
12594
  ]);
@@ -12823,6 +12938,7 @@ function normalizeCreationSummary(metamodels) {
12823
12938
  ...typeof raw.idPath === "string" && raw.idPath.trim() ? { idPath: raw.idPath.trim() } : {},
12824
12939
  ...typeof raw.pathPath === "string" && raw.pathPath.trim() ? { pathPath: raw.pathPath.trim() } : {},
12825
12940
  ...typeof raw.statePath === "string" && raw.statePath.trim() ? { statePath: raw.statePath.trim() } : {},
12941
+ ...typeof raw.completionState === "string" && raw.completionState.trim() ? { completionState: raw.completionState.trim() } : {},
12826
12942
  ...typeof raw.classStateHandle === "boolean" ? { classStateHandle: raw.classStateHandle } : {}
12827
12943
  };
12828
12944
  }
@@ -12948,7 +13064,7 @@ var effectBehaviorsMetamodelPackage = defineMetamodelPackage({
12948
13064
  },
12949
13065
  {
12950
13066
  key: "creates",
12951
- description: 'Marks a static method as an allowed creator for a class. Use `creates: "class_name"` or `{ className, idPath, pathPath, statePath, classStateHandle }`.'
13067
+ description: 'Marks a static method as an allowed creator for a class. Use `creates: "class_name"` or `{ className, idPath, pathPath, statePath, completionState, classStateHandle }`.'
12952
13068
  }
12953
13069
  ]
12954
13070
  },
@@ -13171,18 +13287,18 @@ function validateScalarRecord(value, path) {
13171
13287
  }
13172
13288
  function validateProjectedRecord(value, path) {
13173
13289
  validateObjectReference(value, path);
13174
- const record = value;
13175
- if (record.label !== void 0) {
13176
- if (typeof record.label !== "string") {
13290
+ const record2 = value;
13291
+ if (record2.label !== void 0) {
13292
+ if (typeof record2.label !== "string") {
13177
13293
  throw new Error(`${path}.label must be a string`);
13178
13294
  }
13179
- if (record.label.length > MAX_SAFE_TEXT_LENGTH) {
13295
+ if (record2.label.length > MAX_SAFE_TEXT_LENGTH) {
13180
13296
  throw new Error(
13181
13297
  `${path}.label exceeds ${MAX_SAFE_TEXT_LENGTH} characters`
13182
13298
  );
13183
13299
  }
13184
13300
  }
13185
- validateScalarRecord(record.fields, `${path}.fields`);
13301
+ validateScalarRecord(record2.fields, `${path}.fields`);
13186
13302
  }
13187
13303
  function validateBoundedJson(value, path, depth = 0) {
13188
13304
  if (depth > 12) throw new Error(`${path} is nested too deeply`);
@@ -13232,6 +13348,11 @@ function validateProjectionResult(declaration, projection) {
13232
13348
  "projection.source.version"
13233
13349
  );
13234
13350
  }
13351
+ if (projection.source.versionScope !== void 0 && projection.source.versionScope !== "global" && projection.source.versionScope !== "target") {
13352
+ throw new Error(
13353
+ "projection.source.versionScope must be 'global' or 'target'"
13354
+ );
13355
+ }
13235
13356
  if (!Array.isArray(projection.changes)) {
13236
13357
  throw new Error("projection.changes must be an array");
13237
13358
  }
@@ -13595,18 +13716,18 @@ function createInvocationFeedbackContext(bridge) {
13595
13716
  get revision() {
13596
13717
  return current.revision;
13597
13718
  },
13598
- async update(text, options = {}) {
13719
+ async update(text2, options = {}) {
13599
13720
  current = await track(
13600
- current.update(text, {
13721
+ current.update(text2, {
13601
13722
  ...options,
13602
13723
  operationId: options.operationId || nextOperationId("transient-update")
13603
13724
  })
13604
13725
  );
13605
13726
  return wrapped;
13606
13727
  },
13607
- settle(text, options = {}) {
13728
+ settle(text2, options = {}) {
13608
13729
  return track(
13609
- current.settle(text, {
13730
+ current.settle(text2, {
13610
13731
  ...options,
13611
13732
  operationId: options.operationId || nextOperationId("transient-settle")
13612
13733
  })
@@ -13616,17 +13737,17 @@ function createInvocationFeedbackContext(bridge) {
13616
13737
  return wrapped;
13617
13738
  };
13618
13739
  return {
13619
- feedback(text, options = {}) {
13740
+ feedback(text2, options = {}) {
13620
13741
  return track(
13621
- publisher.feedback(text, {
13742
+ publisher.feedback(text2, {
13622
13743
  ...options,
13623
13744
  operationId: options.operationId || nextOperationId("feedback")
13624
13745
  })
13625
13746
  );
13626
13747
  },
13627
- transientFeedback(text, options = {}) {
13748
+ transientFeedback(text2, options = {}) {
13628
13749
  return track(
13629
- publisher.transientFeedback(text, {
13750
+ publisher.transientFeedback(text2, {
13630
13751
  ...options,
13631
13752
  operationId: options.operationId || nextOperationId("transient-create")
13632
13753
  }).then(wrapTransientHandle)
@@ -14474,9 +14595,9 @@ var noteMetamodelPackage = defineMetamodelPackage({
14474
14595
  notes: async (ant) => await run(ant.notes())
14475
14596
  },
14476
14597
  ModelMutation: {
14477
- add_note: async (ant, { text }) => {
14478
- const model = await run(ant.add_note(text));
14479
- return { model, text };
14598
+ add_note: async (ant, { text: text2 }) => {
14599
+ const model = await run(ant.add_note(text2));
14600
+ return { model, text: text2 };
14480
14601
  }
14481
14602
  }
14482
14603
  };
@@ -14889,6 +15010,7 @@ function normalizeStateMachines(values) {
14889
15010
  to: String(transition?.to?.name || ""),
14890
15011
  label: typeof transition?.label === "string" ? transition.label : null,
14891
15012
  description: typeof transition?.description === "string" ? transition.description : null,
15013
+ userVisible: typeof transition?.user_visible === "boolean" ? transition.user_visible : typeof transition?.userVisible === "boolean" ? transition.userVisible : null,
14892
15014
  action: parseJsonRecord(transition?.action) || parseJsonRecord(transition?.action_json),
14893
15015
  assignee: parseJsonRecord(transition?.assignee) || parseJsonRecord(transition?.assignee_json),
14894
15016
  requirements: parseJsonRecord(transition?.requirements) || parseJsonRecord(transition?.requirements_json),
@@ -14960,6 +15082,9 @@ function transitionMetadataGraphqlArgs(transition) {
14960
15082
  if (typeof transition.description === "string") {
14961
15083
  args.push(`description: ${JSON.stringify(transition.description)}`);
14962
15084
  }
15085
+ if (typeof transition.userVisible === "boolean") {
15086
+ args.push(`user_visible: ${transition.userVisible}`);
15087
+ }
14963
15088
  if (transition.action) {
14964
15089
  args.push(
14965
15090
  `action_json: ${JSON.stringify(JSON.stringify(transition.action))}`
@@ -15271,7 +15396,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15271
15396
  name: String!
15272
15397
  state_machine: StateMachine!
15273
15398
  add_state(name: String!, is_final: Boolean, label: String, description: String): StateMachineMutation!
15274
- 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!
15399
+ 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!
15275
15400
  activate_transition(name: String!): StateMachineMutation!
15276
15401
  }
15277
15402
 
@@ -15329,6 +15454,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15329
15454
  to: StateMachineState!
15330
15455
  label: String
15331
15456
  description: String
15457
+ user_visible: Boolean
15332
15458
  action_json: String
15333
15459
  assignee_json: String
15334
15460
  requirements_json: String
@@ -15410,6 +15536,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15410
15536
  to,
15411
15537
  label,
15412
15538
  description,
15539
+ user_visible,
15413
15540
  action_json,
15414
15541
  assignee_json,
15415
15542
  requirements_json,
@@ -15427,6 +15554,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15427
15554
  {
15428
15555
  label,
15429
15556
  description,
15557
+ userVisible: user_visible,
15430
15558
  actionJson: action_json,
15431
15559
  assigneeJson: assignee_json,
15432
15560
  requirementsJson: requirements_json,
@@ -15491,6 +15619,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15491
15619
  to: (value) => value.to_state || { name: value.to, is_final: false },
15492
15620
  label: (value) => value.label || null,
15493
15621
  description: (value) => value.description || null,
15622
+ user_visible: (value) => value.user_visible ?? null,
15494
15623
  action_json: (value) => value.action_json || null,
15495
15624
  assignee_json: (value) => value.assignee_json || null,
15496
15625
  requirements_json: (value) => value.requirements_json || null,
@@ -15577,6 +15706,7 @@ var stateMachineMetamodelPackage = defineMetamodelPackage({
15577
15706
  to { name }
15578
15707
  label
15579
15708
  description
15709
+ user_visible
15580
15710
  action_json
15581
15711
  assignee_json
15582
15712
  requirements_json
@@ -15984,6 +16114,786 @@ function buildEffectMetamodelMutations(toolPath, spec) {
15984
16114
  );
15985
16115
  }
15986
16116
 
16117
+ // src/artifact-workflow.ts
16118
+ var TERMINAL_ARTIFACT_STATUSES = /* @__PURE__ */ new Set([
16119
+ "completed",
16120
+ "partially_completed",
16121
+ "failed",
16122
+ "canceled"
16123
+ ]);
16124
+ function record(value) {
16125
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
16126
+ }
16127
+ function text(value) {
16128
+ return typeof value === "string" && value.trim() ? value.trim() : null;
16129
+ }
16130
+ function humanize(value) {
16131
+ const words = value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
16132
+ return words ? words[0].toUpperCase() + words.slice(1) : "Details";
16133
+ }
16134
+ function stableToken(value) {
16135
+ return encodeURIComponent(value.trim().toLowerCase());
16136
+ }
16137
+ function isMissing(value) {
16138
+ return value === null || value === void 0 || typeof value === "string" && !value.trim() || Array.isArray(value) && value.length === 0;
16139
+ }
16140
+ function displayValue(value) {
16141
+ if (typeof value === "string") return value;
16142
+ if (typeof value === "number" || typeof value === "boolean") {
16143
+ return String(value);
16144
+ }
16145
+ if (Array.isArray(value)) {
16146
+ return value.map(displayValue).filter(Boolean).join(", ");
16147
+ }
16148
+ return "Provided";
16149
+ }
16150
+ function artifactFields(artifact) {
16151
+ return Array.isArray(artifact.inputSchema?.fields) ? artifact.inputSchema.fields.filter(
16152
+ (field) => Boolean(record(field))
16153
+ ) : [];
16154
+ }
16155
+ function declaredSections(artifact) {
16156
+ const fields = artifactFields(artifact);
16157
+ if (!fields.length) return [];
16158
+ const knownNames = new Set(
16159
+ fields.map((field) => text(field.name)).filter((name) => Boolean(name))
16160
+ );
16161
+ const ui = record(artifact.inputSchema?.ui);
16162
+ const workflow = record(ui?.workflow);
16163
+ const rawSections = Array.isArray(workflow?.steps) ? workflow.steps : Array.isArray(ui?.sections) ? ui.sections : [];
16164
+ const used = /* @__PURE__ */ new Set();
16165
+ const sections = rawSections.flatMap((value) => {
16166
+ const section = record(value);
16167
+ const id = text(section?.id);
16168
+ const label = text(section?.label);
16169
+ const fieldNames = Array.isArray(section?.fieldNames) ? section.fieldNames.map(text).filter(
16170
+ (name) => name !== null && knownNames.has(name) && !used.has(name)
16171
+ ) : [];
16172
+ if (!id || !label || !fieldNames.length) return [];
16173
+ fieldNames.forEach((name) => used.add(name));
16174
+ return [{ id, label, description: text(section?.description), fieldNames }];
16175
+ });
16176
+ const remaining = [...knownNames].filter((name) => !used.has(name));
16177
+ const chunkSize = 5;
16178
+ const chunkCount = Math.ceil(remaining.length / chunkSize);
16179
+ const hasDeclaredSections = sections.length > 0;
16180
+ for (let index = 0; index < chunkCount; index += 1) {
16181
+ const baseLabel = hasDeclaredSections ? "Other details" : "Details";
16182
+ sections.push({
16183
+ id: chunkCount === 1 ? hasDeclaredSections ? "other-details" : "details" : `${hasDeclaredSections ? "other-details" : "details"}-${index + 1}`,
16184
+ label: chunkCount === 1 ? baseLabel : `${baseLabel} ${index + 1}`,
16185
+ description: null,
16186
+ fieldNames: remaining.slice(index * chunkSize, (index + 1) * chunkSize)
16187
+ });
16188
+ }
16189
+ return sections;
16190
+ }
16191
+ function fieldValue(artifact, fieldName) {
16192
+ const field = artifactFields(artifact).find(
16193
+ (candidate) => text(candidate.name) === fieldName
16194
+ );
16195
+ const kind = text(field?.kind);
16196
+ return kind === "relationshipOne" || kind === "relationshipMany" ? artifact.relationships?.[fieldName] : artifact.inputValues?.[fieldName];
16197
+ }
16198
+ function conditionallyRequired(artifact, field) {
16199
+ const raw = field.requiredWhen ?? field.required_when;
16200
+ const conditions = Array.isArray(raw) ? raw : raw ? [raw] : [];
16201
+ return conditions.length > 0 && conditions.every((value) => {
16202
+ const condition = record(value);
16203
+ const dependency = text(condition?.field);
16204
+ return Boolean(dependency) && Object.prototype.hasOwnProperty.call(condition || {}, "equals") && Object.is(fieldValue(artifact, dependency), condition.equals);
16205
+ });
16206
+ }
16207
+ function fieldValidationRules(field) {
16208
+ const raw = Array.isArray(field.validationRules) ? field.validationRules : Array.isArray(field.validation_rules) ? field.validation_rules : Array.isArray(field.validate) ? field.validate : [];
16209
+ return raw.filter(
16210
+ (rule) => Boolean(record(rule)) && typeof record(rule)?.operator === "string"
16211
+ );
16212
+ }
16213
+ function fieldProvenanceNeedsReview(artifact, field) {
16214
+ const fieldName = text(field.name);
16215
+ if (!fieldName) return false;
16216
+ const namespace = text(field.kind)?.startsWith("relationship") ? "relationships" : "inputValues";
16217
+ const provenance = artifact.fieldProvenance?.[`${namespace}.${fieldName}`];
16218
+ if (!provenance) return false;
16219
+ const confirmedAt = typeof provenance.confirmedAt === "number" ? provenance.confirmedAt : null;
16220
+ const updatedAt = typeof provenance.updatedAt === "number" ? provenance.updatedAt : null;
16221
+ const invalidatedAt = typeof provenance.invalidatedAt === "number" ? provenance.invalidatedAt : null;
16222
+ const explicitlyConfirmed = confirmedAt !== null && (updatedAt === null || confirmedAt >= updatedAt) && (invalidatedAt === null || confirmedAt >= invalidatedAt);
16223
+ if (explicitlyConfirmed) return false;
16224
+ const invalidated = invalidatedAt !== null && (confirmedAt === null || confirmedAt < invalidatedAt);
16225
+ const assistantSource = [
16226
+ "user_statement",
16227
+ "agent_extraction",
16228
+ "agent_inference",
16229
+ "provider_lookup",
16230
+ "ontology_default"
16231
+ ].includes(provenance.source);
16232
+ return invalidated || assistantSource && provenance.confidence === "ambiguous";
16233
+ }
16234
+ function sectionComplete(artifact, fieldNames) {
16235
+ const schema = record(artifact.inputSchema);
16236
+ const declaredRequiredNames = new Set(
16237
+ [
16238
+ ...Array.isArray(schema?.required) ? schema.required : [],
16239
+ ...Array.isArray(schema?.["x-granular-artifact-required"]) ? schema["x-granular-artifact-required"] : []
16240
+ ].map(text).filter((name) => Boolean(name))
16241
+ );
16242
+ const validation = record(artifact.validation);
16243
+ const invalidFieldNames = new Set(
16244
+ (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)\./, ""))
16245
+ );
16246
+ const fieldsByName = new Map(
16247
+ artifactFields(artifact).map((field) => [text(field.name), field])
16248
+ );
16249
+ return fieldNames.every((fieldName) => {
16250
+ if (invalidFieldNames.has(fieldName)) return false;
16251
+ const field = fieldsByName.get(fieldName);
16252
+ if (!field) return true;
16253
+ if (fieldProvenanceNeedsReview(artifact, field)) return false;
16254
+ const required = declaredRequiredNames.has(fieldName) || field.required === true || conditionallyRequired(artifact, field);
16255
+ const value = fieldValue(artifact, fieldName);
16256
+ const provenance = artifact.fieldProvenance?.[`${text(field.kind)?.startsWith("relationship") ? "relationships" : "inputValues"}.${fieldName}`];
16257
+ if (provenance?.invalidatedAt) return false;
16258
+ if (text(field.kind)?.startsWith("relationship")) {
16259
+ const resolvers = record(
16260
+ record(artifact.metadata)?.relationshipResolvers
16261
+ );
16262
+ const resolverStatus = text(record(resolvers?.[fieldName])?.status);
16263
+ if (resolverStatus === "missing") return false;
16264
+ if (resolverStatus === "choice" && isMissing(value)) return false;
16265
+ }
16266
+ if (required && isMissing(value)) return false;
16267
+ if (isMissing(value)) return true;
16268
+ return fieldValidationRules(field).every(
16269
+ (rule) => evaluateValidationRule(value, rule).passed
16270
+ );
16271
+ });
16272
+ }
16273
+ function sectionRecap(artifact, fieldNames) {
16274
+ const fields = artifactFields(artifact);
16275
+ const fieldsByName = new Map(
16276
+ fields.map((field) => [text(field.name), field])
16277
+ );
16278
+ const labels = new Map(
16279
+ fields.map((field) => [
16280
+ text(field.name),
16281
+ text(field.label) || humanize(text(field.name) || "detail")
16282
+ ])
16283
+ );
16284
+ const items = fieldNames.flatMap((fieldName) => {
16285
+ const value = fieldValue(artifact, fieldName);
16286
+ if (isMissing(value)) return [];
16287
+ const field = fieldsByName.get(fieldName);
16288
+ const relationshipLabels = record(
16289
+ record(artifact.metadata)?.relationshipLabels
16290
+ );
16291
+ const fieldLabels = record(relationshipLabels?.[fieldName]);
16292
+ const resolvers = record(record(artifact.metadata)?.relationshipResolvers);
16293
+ const candidates = Array.isArray(record(resolvers?.[fieldName])?.candidates) ? record(resolvers?.[fieldName])?.candidates : [];
16294
+ const candidateLabel = candidates.map(record).find((candidate) => text(candidate?.id) === value)?.label;
16295
+ const rawOptions = Array.isArray(field?.enumOptions) ? field.enumOptions : Array.isArray(field?.options) ? field.options : [];
16296
+ const enumLabel = rawOptions.map(
16297
+ (option) => typeof option === "string" ? { value: option, label: humanize(option) } : record(option)
16298
+ ).find((option) => text(option?.value) === value)?.label;
16299
+ 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);
16300
+ return [
16301
+ { label: labels.get(fieldName) || humanize(fieldName), value: rendered }
16302
+ ];
16303
+ });
16304
+ return {
16305
+ summary: items.length ? items.map((item) => `${item.label}: ${item.value}`).join(" \xB7 ") : "Completed",
16306
+ items
16307
+ };
16308
+ }
16309
+ function operationPhase(artifact) {
16310
+ const execution = record(record(artifact.metadata)?.execution);
16311
+ const phase = text(execution?.phase);
16312
+ if (artifact.status === "completed") return "completed";
16313
+ if (artifact.status === "failed") return "failed";
16314
+ if (artifact.status === "awaiting_human") return "waiting_for_approval";
16315
+ if (phase === "preparing") return "reviewing";
16316
+ if (phase === "syncing_record") return "synchronizing";
16317
+ if (phase === "verifying_target") return "verifying";
16318
+ if (artifact.status === "running") return "sending";
16319
+ return null;
16320
+ }
16321
+ function operationLabel(artifact) {
16322
+ const ui = record(artifact.inputSchema?.ui);
16323
+ const workflow = record(ui?.workflow);
16324
+ const effect = record(record(artifact.metadata)?.effect);
16325
+ return text(workflow?.operationLabel) || text(ui?.submitLabel) || text(effect?.activityLabel) || text(effect?.label) || artifact.label;
16326
+ }
16327
+ function relationshipValueContainsRecordId(value, recordId) {
16328
+ return Array.isArray(value) ? value.some((item) => item === recordId) : value === recordId;
16329
+ }
16330
+ function relationshipChildLinkPending(parent, fieldName, child) {
16331
+ if (child.status !== "completed") return false;
16332
+ const parentMetadata = record(parent.metadata);
16333
+ const linkMarker = record(
16334
+ record(parentMetadata?.relationshipLinkProvenance)?.[fieldName]
16335
+ );
16336
+ const relationshipValue = parent.relationships?.[fieldName];
16337
+ const childExecution = record(record(child.metadata)?.execution);
16338
+ const childResult = record(childExecution?.result);
16339
+ const childCommit = record(childResult?.commit);
16340
+ const primaryTarget2 = record(childCommit?.primaryTarget);
16341
+ const committedRecordId = text(primaryTarget2?.id);
16342
+ const markerRecordId = text(linkMarker?.recordId);
16343
+ const exactRecordId = markerRecordId || committedRecordId;
16344
+ if (exactRecordId) {
16345
+ return !relationshipValueContainsRecordId(relationshipValue, exactRecordId);
16346
+ }
16347
+ return isMissing(relationshipValue);
16348
+ }
16349
+ function stateTransitionSteps(artifact) {
16350
+ if (artifact.kind !== "state_path") return [];
16351
+ const statePlan = record(record(artifact.metadata)?.statePlan);
16352
+ const plan = record(statePlan?.plan);
16353
+ const transitionMetadata = record(statePlan?.transitionMetadata);
16354
+ const rawSteps = Array.isArray(statePlan?.steps) ? statePlan.steps : Array.isArray(plan?.steps) ? plan.steps : [];
16355
+ return Array.from(rawSteps).flatMap((value, fallbackIndex) => {
16356
+ const step = record(value);
16357
+ const transition = text(step?.transition);
16358
+ if (!step || !transition) return [];
16359
+ const transitionDescription = record(transitionMetadata?.[transition]);
16360
+ const userVisible = typeof step.userVisible === "boolean" ? step.userVisible : typeof transitionDescription?.userVisible === "boolean" ? transitionDescription.userVisible : true;
16361
+ const blockers = Array.isArray(step.blockers) ? step.blockers.filter(
16362
+ (blocker) => Boolean(record(blocker))
16363
+ ) : [];
16364
+ return [
16365
+ {
16366
+ index: typeof step.index === "number" ? step.index : fallbackIndex,
16367
+ transition,
16368
+ label: text(step.label) || humanize(transition),
16369
+ description: text(step.description) || text(record(step.expectedOutcome)?.summary) || null,
16370
+ status: text(step.status) || "pending",
16371
+ // State plans may contain bookkeeping transitions that are necessary
16372
+ // for execution/audit but are already represented by an actionable
16373
+ // form or by the single final operation. Producers must opt those
16374
+ // transitions out explicitly; the client never infers this from a
16375
+ // label or transition name.
16376
+ userVisible,
16377
+ blockers
16378
+ }
16379
+ ];
16380
+ });
16381
+ }
16382
+ function stateTransitionGroupLabel(artifact) {
16383
+ if (artifact.kind !== "state_path") return null;
16384
+ const statePlan = record(record(artifact.metadata)?.statePlan);
16385
+ const classLabel = text(statePlan?.classLabel);
16386
+ return classLabel ? `${classLabel} workflow` : null;
16387
+ }
16388
+ function workflowGroupStatus(nodes) {
16389
+ const active = nodes.find(
16390
+ (node) => ["current", "needs_input", "working", "blocked", "failed"].includes(
16391
+ node.status
16392
+ )
16393
+ );
16394
+ if (active) return active.status;
16395
+ if (nodes.some((node) => node.status === "upcoming")) return "upcoming";
16396
+ const lastTerminal = [...nodes].reverse().find((node) => isTerminalWorkflowStepStatus(node.status));
16397
+ return lastTerminal?.status || nodes[0]?.status || "upcoming";
16398
+ }
16399
+ function transitionNodeStatus(artifact, step) {
16400
+ const statePlan = record(record(artifact.metadata)?.statePlan);
16401
+ const currentStep = record(statePlan?.currentStep);
16402
+ const execution = record(record(artifact.metadata)?.execution);
16403
+ const currentTransition = text(execution?.currentTransition);
16404
+ const completedTransitions = new Set(
16405
+ Array.isArray(execution?.completedTransitions) ? execution.completedTransitions.map(text).filter((value) => Boolean(value)) : []
16406
+ );
16407
+ const isCurrent = currentTransition === step.transition || currentStep?.index === step.index;
16408
+ if (completedTransitions.has(step.transition) || step.status === "completed") {
16409
+ return "completed";
16410
+ }
16411
+ if (isCurrent && artifact.status === "failed") return "failed";
16412
+ if (isCurrent && artifact.status === "canceled") return "canceled";
16413
+ if (isCurrent && artifact.status === "running") return "working";
16414
+ if (step.status === "failed") return "failed";
16415
+ if (artifact.status === "canceled") return "canceled";
16416
+ if (step.status === "blocked") return "blocked";
16417
+ if (step.status === "needs_other_user" || step.status === "needs_input") {
16418
+ return "needs_input";
16419
+ }
16420
+ if (isCurrent || step.status === "ready" || step.status === "current") {
16421
+ return "current";
16422
+ }
16423
+ if (step.status === "skipped") return "skipped";
16424
+ if (step.status === "canceled") return "canceled";
16425
+ return "upcoming";
16426
+ }
16427
+ var TERMINAL_WORKFLOW_STEP_STATUSES = /* @__PURE__ */ new Set([
16428
+ "completed",
16429
+ "skipped",
16430
+ "canceled"
16431
+ ]);
16432
+ function isArtifactWorkflowNodeTerminal(nodeOrStatus) {
16433
+ const status = typeof nodeOrStatus === "string" ? nodeOrStatus : nodeOrStatus.status;
16434
+ return TERMINAL_WORKFLOW_STEP_STATUSES.has(status);
16435
+ }
16436
+ function isTerminalWorkflowStepStatus(status) {
16437
+ return isArtifactWorkflowNodeTerminal(status);
16438
+ }
16439
+ function firstTransitionStatus(artifact, status) {
16440
+ if (status !== "upcoming") return status;
16441
+ if (artifact.status === "canceled") return "canceled";
16442
+ if (artifact.status === "failed") return "failed";
16443
+ if (artifact.status === "blocked" || artifact.status === "stale" || artifact.status === "partially_completed") {
16444
+ return "blocked";
16445
+ }
16446
+ if (artifact.status === "running") return "working";
16447
+ if (artifact.status === "awaiting_human") return "needs_input";
16448
+ return "current";
16449
+ }
16450
+ function operationStatus(artifact, transitionStatuses = []) {
16451
+ if (artifact.status === "completed") {
16452
+ if (transitionStatuses.some(
16453
+ (status) => !TERMINAL_WORKFLOW_STEP_STATUSES.has(status)
16454
+ )) {
16455
+ if (transitionStatuses.includes("failed")) return "failed";
16456
+ if (transitionStatuses.includes("needs_input")) return "needs_input";
16457
+ if (transitionStatuses.includes("working")) return "working";
16458
+ if (transitionStatuses.includes("current")) return "current";
16459
+ return "blocked";
16460
+ }
16461
+ return "completed";
16462
+ }
16463
+ if (artifact.status === "failed") return "failed";
16464
+ if (artifact.status === "canceled") return "canceled";
16465
+ if (artifact.status === "partially_completed") return "blocked";
16466
+ if (artifact.status === "blocked" || artifact.status === "stale")
16467
+ return "blocked";
16468
+ if (artifact.status === "running") return "working";
16469
+ if (artifact.status === "awaiting_human") return "needs_input";
16470
+ if (artifact.status === "ready") return "current";
16471
+ return "upcoming";
16472
+ }
16473
+ function semanticWorkflow(workflow) {
16474
+ const { revision: _revision, updatedAt: _updatedAt, ...semantic } = workflow;
16475
+ return semantic;
16476
+ }
16477
+ function isNavigationOnlyOperation(workflow, node) {
16478
+ const hasExplicitTransitionLeaf = getArtifactWorkflowNodes(workflow).some(
16479
+ (candidate) => candidate.childIds.length === 0 && candidate.kind === "transition"
16480
+ );
16481
+ return hasExplicitTransitionLeaf && node.childIds.length === 0 && node.kind === "operation" && // A terminal operation is redundant once authored state-path leaves are
16482
+ // available. While it is still delivering/synchronizing, however, it is
16483
+ // the only honest representation of the work still in progress.
16484
+ isArtifactWorkflowNodeTerminal(node);
16485
+ }
16486
+ function getArtifactWorkflowNavigationLeaves(workflow) {
16487
+ return getArtifactWorkflowNodes(workflow).filter(
16488
+ (node) => isArtifactWorkflowNodeNavigableLeaf(workflow, node)
16489
+ );
16490
+ }
16491
+ function isArtifactWorkflowNodeNavigableLeaf(workflow, node) {
16492
+ return node.childIds.length === 0 && !isNavigationOnlyOperation(workflow, node);
16493
+ }
16494
+ function isArtifactWorkflowNodeActionable(node) {
16495
+ return node.availability === "available" && ["current", "needs_input", "working", "blocked", "failed"].includes(
16496
+ node.status
16497
+ );
16498
+ }
16499
+ function getArtifactWorkflowNavigationTarget(workflow) {
16500
+ const leaves = getArtifactWorkflowNavigationLeaves(workflow);
16501
+ return leaves.find(
16502
+ (node) => !isArtifactWorkflowNodeTerminal(node) && isArtifactWorkflowNodeActionable(node)
16503
+ ) || leaves.at(-1) || null;
16504
+ }
16505
+ function projectArtifactWorkflow(artifact, allArtifacts = [artifact], now = artifact.updatedAt) {
16506
+ const nodesById = {};
16507
+ const rootNodeIds = [];
16508
+ const sections = declaredSections(artifact);
16509
+ const children = new Map(
16510
+ allArtifacts.filter((candidate2) => candidate2.parentArtifactId === artifact.artifactId).map((candidate2) => [candidate2.artifactId, candidate2])
16511
+ );
16512
+ const relationshipChildren = record(
16513
+ record(artifact.metadata)?.relationshipChildren
16514
+ );
16515
+ const operationId2 = `${artifact.artifactId}:operation`;
16516
+ const validationId = `${artifact.artifactId}:validation`;
16517
+ const sectionNodeIds = [];
16518
+ let currentNodeId = null;
16519
+ let foundIncompleteSection = false;
16520
+ let foundActiveLeaf = false;
16521
+ let transitionHasActiveStep = false;
16522
+ let hasVisibleTransition = false;
16523
+ const projectedTransitionStatuses = [];
16524
+ const transitionNodeIds = [];
16525
+ const transitionGroupLabel = stateTransitionGroupLabel(artifact);
16526
+ const transitionGroupId = `${artifact.artifactId}:transition-group`;
16527
+ for (const section of sections) {
16528
+ const id = `${artifact.artifactId}:section:${stableToken(section.id)}`;
16529
+ const complete = sectionComplete(artifact, section.fieldNames);
16530
+ const blockedByEarlierSection = foundIncompleteSection;
16531
+ const blockedByEarlierAction = foundIncompleteSection || foundActiveLeaf;
16532
+ const relationshipFields = artifactFields(artifact).filter(
16533
+ (field) => section.fieldNames.includes(text(field.name) || "") && ["relationshipOne", "relationshipMany"].includes(
16534
+ text(field.kind) || ""
16535
+ )
16536
+ );
16537
+ const relationshipField = relationshipFields[0];
16538
+ const relationshipChildrenInSection = relationshipFields.flatMap(
16539
+ (field) => {
16540
+ const relationshipName = text(field.name);
16541
+ const childArtifactId = relationshipName ? text(relationshipChildren?.[relationshipName]) : null;
16542
+ const child = childArtifactId ? children.get(childArtifactId) : null;
16543
+ if (!relationshipName || !child) return [];
16544
+ return [
16545
+ {
16546
+ relationshipName,
16547
+ child,
16548
+ childLinkPending: relationshipChildLinkPending(
16549
+ artifact,
16550
+ relationshipName,
16551
+ child
16552
+ ),
16553
+ childId: `${artifact.artifactId}:child:${stableToken(child.artifactId)}`
16554
+ }
16555
+ ];
16556
+ }
16557
+ );
16558
+ const relationshipChildFieldNames = new Set(
16559
+ relationshipChildrenInSection.map(
16560
+ ({ relationshipName }) => relationshipName
16561
+ )
16562
+ );
16563
+ const residualFieldNames = section.fieldNames.filter(
16564
+ (fieldName) => !relationshipChildFieldNames.has(fieldName)
16565
+ );
16566
+ const residualComplete = sectionComplete(artifact, residualFieldNames);
16567
+ const residualId = `${id}:details`;
16568
+ const childActive = relationshipChildrenInSection.some(
16569
+ ({ child, childLinkPending }) => childLinkPending || !TERMINAL_ARTIFACT_STATUSES.has(child.status)
16570
+ );
16571
+ const childIsWorking = relationshipChildrenInSection.some(
16572
+ ({ child, childLinkPending }) => childLinkPending || child.status === "running"
16573
+ );
16574
+ const sectionStatus = complete && !blockedByEarlierSection ? "completed" : blockedByEarlierAction ? "upcoming" : childActive ? childIsWorking ? "working" : "needs_input" : !TERMINAL_ARTIFACT_STATUSES.has(artifact.status) ? "needs_input" : "upcoming";
16575
+ if (!complete) foundIncompleteSection = true;
16576
+ nodesById[id] = {
16577
+ id,
16578
+ artifactId: artifact.artifactId,
16579
+ parentId: operationId2,
16580
+ childIds: [
16581
+ ...relationshipChildrenInSection.map(({ childId }) => childId),
16582
+ ...relationshipChildrenInSection.length > 0 && residualFieldNames.length > 0 && !residualComplete ? [residualId] : []
16583
+ ],
16584
+ kind: relationshipChildrenInSection.length > 0 ? "group" : relationshipField ? "relationship" : "input",
16585
+ label: section.label,
16586
+ description: section.description,
16587
+ status: sectionStatus,
16588
+ availability: sectionStatus === "completed" ? "readonly" : sectionStatus === "upcoming" ? "unavailable" : "available",
16589
+ fieldNames: relationshipChildrenInSection.length > 0 ? void 0 : section.fieldNames,
16590
+ recap: sectionStatus === "completed" ? sectionRecap(artifact, section.fieldNames) : null,
16591
+ completedAt: sectionStatus === "completed" ? artifact.updatedAt : null
16592
+ };
16593
+ sectionNodeIds.push(id);
16594
+ const childStatuses = [];
16595
+ for (const {
16596
+ child,
16597
+ childId,
16598
+ childLinkPending
16599
+ } of relationshipChildrenInSection) {
16600
+ 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";
16601
+ const childStatus = blockedByEarlierSection ? "upcoming" : foundActiveLeaf && !isTerminalWorkflowStepStatus(authoritativeChildStatus) ? "upcoming" : authoritativeChildStatus;
16602
+ childStatuses.push(childStatus);
16603
+ nodesById[childId] = {
16604
+ id: childId,
16605
+ artifactId: artifact.artifactId,
16606
+ parentId: id,
16607
+ childIds: [],
16608
+ kind: "child_artifact",
16609
+ label: child.label,
16610
+ description: child.description,
16611
+ status: childStatus,
16612
+ availability: childStatus === "completed" ? "readonly" : childStatus === "upcoming" ? "unavailable" : "available",
16613
+ childArtifactId: child.artifactId,
16614
+ recap: childStatus === "completed" ? { summary: `${child.label} completed`, items: [] } : null,
16615
+ attention: childStatus === "failed" ? { title: `${child.label} failed`, retryable: true } : null,
16616
+ startedAt: child.executedAt || null,
16617
+ completedAt: childLinkPending ? null : child.completedAt || null
16618
+ };
16619
+ if (!blockedByEarlierSection && !isArtifactWorkflowNodeTerminal(childStatus)) {
16620
+ if (!foundActiveLeaf) {
16621
+ currentNodeId = childId;
16622
+ foundActiveLeaf = true;
16623
+ }
16624
+ }
16625
+ }
16626
+ if (relationshipChildrenInSection.length > 0 && residualFieldNames.length > 0 && !residualComplete) {
16627
+ const residualStatus = residualComplete ? "completed" : blockedByEarlierSection || foundActiveLeaf ? "upcoming" : !TERMINAL_ARTIFACT_STATUSES.has(artifact.status) ? "needs_input" : "upcoming";
16628
+ const residualFields = artifactFields(artifact).filter(
16629
+ (field) => residualFieldNames.includes(text(field.name) || "")
16630
+ );
16631
+ const residualLabel = residualFields.length === 1 ? text(residualFields[0]?.label) || section.label : "Details";
16632
+ childStatuses.push(residualStatus);
16633
+ nodesById[residualId] = {
16634
+ id: residualId,
16635
+ artifactId: artifact.artifactId,
16636
+ parentId: id,
16637
+ childIds: [],
16638
+ kind: residualFields.some(
16639
+ (field) => ["relationshipOne", "relationshipMany"].includes(
16640
+ text(field.kind) || ""
16641
+ )
16642
+ ) ? "relationship" : "input",
16643
+ label: residualLabel,
16644
+ description: section.description,
16645
+ status: residualStatus,
16646
+ availability: residualStatus === "completed" ? "readonly" : residualStatus === "upcoming" ? "unavailable" : "available",
16647
+ fieldNames: residualFieldNames,
16648
+ recap: residualComplete ? sectionRecap(artifact, residualFieldNames) : null,
16649
+ completedAt: residualComplete ? artifact.updatedAt : null
16650
+ };
16651
+ if (!foundActiveLeaf && ["needs_input", "working", "blocked", "failed"].includes(residualStatus)) {
16652
+ currentNodeId = residualId;
16653
+ foundActiveLeaf = true;
16654
+ }
16655
+ }
16656
+ if (childStatuses.length > 0) {
16657
+ const representativeChildStatus = childStatuses.find(
16658
+ (status) => !isArtifactWorkflowNodeTerminal(status)
16659
+ ) ?? (complete ? childStatuses.at(-1) : null);
16660
+ if (representativeChildStatus) {
16661
+ nodesById[id].status = representativeChildStatus;
16662
+ }
16663
+ nodesById[id].availability = "readonly";
16664
+ }
16665
+ if (!foundActiveLeaf && relationshipChildrenInSection.length === 0 && ["needs_input", "working", "blocked", "failed"].includes(sectionStatus)) {
16666
+ currentNodeId = id;
16667
+ foundActiveLeaf = true;
16668
+ }
16669
+ }
16670
+ const transitionsAreGatedByForm = (foundIncompleteSection || foundActiveLeaf) && !TERMINAL_ARTIFACT_STATUSES.has(artifact.status);
16671
+ for (const step of stateTransitionSteps(artifact)) {
16672
+ if (!step.userVisible) continue;
16673
+ hasVisibleTransition = true;
16674
+ const id = `${artifact.artifactId}:transition:${stableToken(step.transition)}`;
16675
+ const rawStatus = transitionNodeStatus(artifact, step);
16676
+ const status = isTerminalWorkflowStepStatus(rawStatus) ? rawStatus : transitionsAreGatedByForm || transitionHasActiveStep ? "upcoming" : firstTransitionStatus(artifact, rawStatus);
16677
+ projectedTransitionStatuses.push(status);
16678
+ if (!isTerminalWorkflowStepStatus(status)) {
16679
+ transitionHasActiveStep = true;
16680
+ }
16681
+ const blocker = step.blockers[0];
16682
+ nodesById[id] = {
16683
+ id,
16684
+ artifactId: artifact.artifactId,
16685
+ parentId: transitionGroupLabel ? transitionGroupId : operationId2,
16686
+ childIds: [],
16687
+ kind: "transition",
16688
+ label: step.label,
16689
+ description: step.description,
16690
+ status,
16691
+ availability: status === "completed" ? "readonly" : status === "upcoming" ? "unavailable" : "available",
16692
+ operationPhase: step.status === "needs_other_user" ? "waiting_for_approval" : status === "working" ? operationPhase(artifact) : null,
16693
+ operationDetail: text(record(record(artifact.metadata)?.execution)?.message) || null,
16694
+ recap: status === "completed" ? { summary: `${step.label} completed`, items: [] } : null,
16695
+ attention: status === "failed" || status === "blocked" ? {
16696
+ title: text(blocker?.label) || `${step.label} needs attention`,
16697
+ detail: text(blocker?.detail) || text(blocker?.description) || null,
16698
+ retryable: status === "failed"
16699
+ } : null,
16700
+ startedAt: status === "working" ? artifact.executedAt || artifact.updatedAt : null,
16701
+ completedAt: status === "completed" ? artifact.updatedAt : null
16702
+ };
16703
+ transitionNodeIds.push(id);
16704
+ if (!currentNodeId && !["completed", "upcoming", "skipped", "canceled"].includes(status)) {
16705
+ currentNodeId = id;
16706
+ }
16707
+ }
16708
+ if (transitionNodeIds.length) {
16709
+ const transitionNodes = transitionNodeIds.map((id) => nodesById[id]).filter((node) => Boolean(node));
16710
+ if (transitionGroupLabel) {
16711
+ const groupStatus = workflowGroupStatus(transitionNodes);
16712
+ const activeTransition = transitionNodes.find(
16713
+ (node) => ["current", "needs_input", "working", "blocked", "failed"].includes(
16714
+ node.status
16715
+ )
16716
+ );
16717
+ nodesById[transitionGroupId] = {
16718
+ id: transitionGroupId,
16719
+ artifactId: artifact.artifactId,
16720
+ parentId: operationId2,
16721
+ childIds: transitionNodeIds,
16722
+ kind: "group",
16723
+ label: transitionGroupLabel,
16724
+ description: null,
16725
+ status: groupStatus,
16726
+ availability: groupStatus === "upcoming" ? "unavailable" : groupStatus === "completed" ? "readonly" : "available",
16727
+ operationPhase: activeTransition?.operationPhase || null,
16728
+ startedAt: activeTransition?.startedAt || null,
16729
+ completedAt: groupStatus === "completed" ? transitionNodes.at(-1)?.completedAt || artifact.updatedAt : null
16730
+ };
16731
+ sectionNodeIds.push(transitionGroupId);
16732
+ } else {
16733
+ sectionNodeIds.push(...transitionNodeIds);
16734
+ }
16735
+ }
16736
+ const projectedOperationStatus = operationStatus(
16737
+ artifact,
16738
+ projectedTransitionStatuses
16739
+ );
16740
+ if (projectedOperationStatus === "completed") {
16741
+ for (const transitionNodeId of transitionNodeIds) {
16742
+ const transition = nodesById[transitionNodeId];
16743
+ if (transition?.kind === "transition" && transition.status === "completed") {
16744
+ transition.completionState = "graph_propagated";
16745
+ }
16746
+ }
16747
+ }
16748
+ const phase = operationPhase(artifact);
16749
+ const activeDescendantStatus = currentNodeId ? nodesById[currentNodeId]?.status : null;
16750
+ const finalStatus = activeDescendantStatus && !isTerminalWorkflowStepStatus(activeDescendantStatus) ? activeDescendantStatus : projectedOperationStatus;
16751
+ const validationStatus = currentNodeId ? "upcoming" : projectedOperationStatus === "upcoming" ? "current" : projectedOperationStatus;
16752
+ nodesById[validationId] = {
16753
+ id: validationId,
16754
+ artifactId: artifact.artifactId,
16755
+ parentId: operationId2,
16756
+ childIds: [],
16757
+ kind: "operation",
16758
+ label: operationLabel(artifact),
16759
+ description: artifact.description || null,
16760
+ status: validationStatus,
16761
+ availability: validationStatus === "upcoming" ? "unavailable" : validationStatus === "completed" ? "readonly" : "available",
16762
+ operationPhase: phase,
16763
+ completionState: projectedOperationStatus === "completed" ? "graph_propagated" : phase === "synchronizing" || phase === "verifying" ? "effect_applied" : null,
16764
+ operationDetail: text(
16765
+ record(record(artifact.metadata)?.execution)?.message
16766
+ ),
16767
+ recap: validationStatus === "completed" ? { summary: `${artifact.label} completed`, items: [] } : null,
16768
+ attention: validationStatus === "failed" ? { title: `${artifact.label} failed`, retryable: true } : validationStatus === "blocked" ? {
16769
+ title: `${artifact.label} needs attention`,
16770
+ retryable: artifact.status === "stale"
16771
+ } : null,
16772
+ startedAt: artifact.executedAt || null,
16773
+ completedAt: artifact.completedAt || null
16774
+ };
16775
+ sectionNodeIds.push(validationId);
16776
+ if (!currentNodeId && !hasVisibleTransition && !["completed", "canceled"].includes(finalStatus)) {
16777
+ currentNodeId = validationId;
16778
+ }
16779
+ const rootStatus = currentNodeId === validationId ? nodesById[validationId].status : finalStatus;
16780
+ nodesById[operationId2] = {
16781
+ id: operationId2,
16782
+ artifactId: artifact.artifactId,
16783
+ parentId: null,
16784
+ childIds: sectionNodeIds,
16785
+ kind: "group",
16786
+ label: artifact.label,
16787
+ description: artifact.description || null,
16788
+ status: rootStatus,
16789
+ availability: rootStatus === "upcoming" ? "unavailable" : rootStatus === "completed" ? "readonly" : "available",
16790
+ operationPhase: null,
16791
+ startedAt: artifact.executedAt || null,
16792
+ completedAt: artifact.completedAt || null
16793
+ };
16794
+ rootNodeIds.push(operationId2);
16795
+ const candidateBeforeTarget = {
16796
+ schemaVersion: 1,
16797
+ revision: artifact.workflow?.revision || 1,
16798
+ artifactId: artifact.artifactId,
16799
+ rootNodeIds,
16800
+ nodesById,
16801
+ currentNodeId: null,
16802
+ updatedAt: now
16803
+ };
16804
+ currentNodeId = getArtifactWorkflowNavigationTarget(candidateBeforeTarget)?.id || null;
16805
+ const automaticTarget = currentNodeId ? nodesById[currentNodeId] || null : null;
16806
+ const canonicalRootStatus = automaticTarget && !isArtifactWorkflowNodeTerminal(automaticTarget) ? automaticTarget.status : projectedOperationStatus;
16807
+ nodesById[operationId2].status = canonicalRootStatus;
16808
+ nodesById[operationId2].availability = canonicalRootStatus === "upcoming" ? "unavailable" : canonicalRootStatus === "completed" ? "readonly" : "available";
16809
+ const candidate = {
16810
+ ...candidateBeforeTarget,
16811
+ currentNodeId
16812
+ };
16813
+ if (artifact.workflow && JSON.stringify(semanticWorkflow(artifact.workflow)) !== JSON.stringify(semanticWorkflow(candidate))) {
16814
+ candidate.revision = artifact.workflow.revision + 1;
16815
+ } else if (artifact.workflow) {
16816
+ candidate.updatedAt = artifact.workflow.updatedAt;
16817
+ }
16818
+ return candidate;
16819
+ }
16820
+ function getCurrentArtifactWorkflowNode(workflow) {
16821
+ return workflow.currentNodeId ? workflow.nodesById[workflow.currentNodeId] || null : null;
16822
+ }
16823
+ function getArtifactWorkflowNodes(workflow) {
16824
+ const visit = (id) => {
16825
+ const node = workflow.nodesById[id];
16826
+ return node ? [node, ...node.childIds.flatMap(visit)] : [];
16827
+ };
16828
+ return workflow.rootNodeIds.flatMap(visit);
16829
+ }
16830
+ function canInspectArtifactWorkflowNode(node) {
16831
+ return node.childIds.length === 0 && node.availability !== "unavailable";
16832
+ }
16833
+ function assertArtifactWorkflow(workflow) {
16834
+ if (workflow.schemaVersion !== 1)
16835
+ throw new Error("Unsupported artifact workflow schema");
16836
+ if (!Number.isSafeInteger(workflow.revision) || workflow.revision < 1) {
16837
+ throw new Error("Artifact workflow revision must be a positive integer");
16838
+ }
16839
+ const visited = /* @__PURE__ */ new Set();
16840
+ const visiting = /* @__PURE__ */ new Set();
16841
+ const nodes = [];
16842
+ const visit = (id, expectedParentId) => {
16843
+ const node = workflow.nodesById[id];
16844
+ if (!node) throw new Error(`Artifact workflow node ${id} does not exist`);
16845
+ if (visiting.has(id)) throw new Error("Artifact workflow contains a cycle");
16846
+ if (visited.has(id))
16847
+ throw new Error("Artifact workflow contains a duplicate node reference");
16848
+ if (node.parentId !== expectedParentId) {
16849
+ throw new Error(`Artifact workflow node ${id} has an invalid parent`);
16850
+ }
16851
+ visiting.add(id);
16852
+ nodes.push(node);
16853
+ node.childIds.forEach((childId) => visit(childId, id));
16854
+ visiting.delete(id);
16855
+ visited.add(id);
16856
+ };
16857
+ workflow.rootNodeIds.forEach((id) => visit(id, null));
16858
+ if (visited.size !== Object.keys(workflow.nodesById).length) {
16859
+ throw new Error("Artifact workflow contains unreachable nodes");
16860
+ }
16861
+ if (workflow.currentNodeId && !workflow.nodesById[workflow.currentNodeId]) {
16862
+ throw new Error("Artifact workflow current node does not exist");
16863
+ }
16864
+ const currentNode = workflow.currentNodeId ? workflow.nodesById[workflow.currentNodeId] : null;
16865
+ if (currentNode && currentNode.childIds.length > 0) {
16866
+ throw new Error("Artifact workflow current node must be actionable leaf");
16867
+ }
16868
+ const actionable = nodes.filter(
16869
+ (node) => isArtifactWorkflowNodeNavigableLeaf(workflow, node) && isArtifactWorkflowNodeActionable(node)
16870
+ );
16871
+ if (actionable.length > 1)
16872
+ throw new Error("Artifact workflow has multiple current nodes");
16873
+ if (actionable.length === 1 && actionable[0].id !== workflow.currentNodeId) {
16874
+ throw new Error("Artifact workflow current node does not own its action");
16875
+ }
16876
+ if (workflow.currentNodeId && !actionable.length && !isArtifactWorkflowNodeTerminal(currentNode)) {
16877
+ throw new Error("Artifact workflow current node is not actionable");
16878
+ }
16879
+ }
16880
+ function requireArtifactWorkflow(artifact) {
16881
+ if (!artifact.workflow) {
16882
+ throw new Error(
16883
+ `Artifact ${artifact.artifactId} has no canonical workflow`
16884
+ );
16885
+ }
16886
+ assertArtifactWorkflow(artifact.workflow);
16887
+ if (artifact.workflow.artifactId !== artifact.artifactId) {
16888
+ throw new Error("Artifact workflow belongs to a different artifact");
16889
+ }
16890
+ return artifact;
16891
+ }
16892
+ function requireArtifactWorkflowResult(result) {
16893
+ requireArtifactWorkflow(result.artifact);
16894
+ return result;
16895
+ }
16896
+
15987
16897
  // src/client.ts
15988
16898
  var DEFAULT_CONVERSATION_SESSION_LIST_LIMIT = 100;
15989
16899
  var MAX_CONVERSATION_SESSION_LIST_LIMIT = 500;
@@ -16137,17 +17047,17 @@ function planRecordObjectsChunks(records, batchSize) {
16137
17047
  }
16138
17048
  return plans;
16139
17049
  }
16140
- function preserveRecordObjectRealId(record) {
16141
- const realId = record.id.trim();
17050
+ function preserveRecordObjectRealId(record2) {
17051
+ const realId = record2.id.trim();
16142
17052
  if (!realId) {
16143
- return record;
17053
+ return record2;
16144
17054
  }
16145
- const fields = record.fields || {};
17055
+ const fields = record2.fields || {};
16146
17056
  if (typeof fields.real_id === "string" && fields.real_id.trim()) {
16147
- return record;
17057
+ return record2;
16148
17058
  }
16149
17059
  return {
16150
- ...record,
17060
+ ...record2,
16151
17061
  fields: {
16152
17062
  ...fields,
16153
17063
  real_id: realId
@@ -16479,7 +17389,7 @@ var Environment = class _Environment {
16479
17389
  }
16480
17390
  get data() {
16481
17391
  return {
16482
- record: async (record) => this.recordObject(record),
17392
+ record: async (record2) => this.recordObject(record2),
16483
17393
  recordMany: async (records, options) => this.recordObjects(records, options),
16484
17394
  import: async (records, options) => this.enqueueRecordImport(records, options),
16485
17395
  listImports: async (status) => this.listRecordImports(status),
@@ -17827,6 +18737,7 @@ var EnvironmentSession = class extends Session {
17827
18737
  sessionDataHeaders;
17828
18738
  beforeMutationSend;
17829
18739
  heartbeatTimer = null;
18740
+ heartbeatProbeInFlight = false;
17830
18741
  /** The last known graph container status, updated by checkReadiness(). */
17831
18742
  graphContainerStatus = null;
17832
18743
  constructor(client, environment, clientId, options = {}) {
@@ -17846,16 +18757,34 @@ var EnvironmentSession = class extends Session {
17846
18757
  * an application RPC may be delayed by Durable Object work even while the
17847
18758
  * underlying WebSocket is healthy.
17848
18759
  */
17849
- startHeartbeat() {
18760
+ startHeartbeat(options = {}) {
17850
18761
  if (this.heartbeatTimer) return;
17851
- this.heartbeatTimer = setInterval(() => {
17852
- if (!this.client.isTransportOpen()) return;
17853
- if (this.client.hasRecentInboundActivity(3e4)) return;
17854
- if (this.client.hasRecentlyPendingRpcPeriod(6e4)) return;
17855
- this.client.reportTransportFailure(
17856
- new Error("No server activity received for 30 seconds")
17857
- );
17858
- }, 5e3);
18762
+ const probeServer = options.probeServer === true;
18763
+ this.heartbeatTimer = setInterval(
18764
+ () => {
18765
+ if (!this.client.isTransportOpen()) return;
18766
+ if (probeServer) {
18767
+ if (this.heartbeatProbeInFlight) return;
18768
+ if (this.client.hasPendingRpcRequests()) return;
18769
+ if (this.client.hasRecentInboundActivity(1e4)) return;
18770
+ this.heartbeatProbeInFlight = true;
18771
+ void this.client.call("client.heartbeat", {}).catch((error) => {
18772
+ if (this.client.isTransportOpen()) {
18773
+ this.client.reportTransportFailure(error);
18774
+ }
18775
+ }).finally(() => {
18776
+ this.heartbeatProbeInFlight = false;
18777
+ });
18778
+ return;
18779
+ }
18780
+ if (this.client.hasRecentInboundActivity(3e4)) return;
18781
+ if (this.client.hasRecentlyPendingRpcPeriod(6e4)) return;
18782
+ this.client.reportTransportFailure(
18783
+ new Error("No server activity received for 30 seconds")
18784
+ );
18785
+ },
18786
+ probeServer ? 1e4 : 5e3
18787
+ );
17859
18788
  this.heartbeatTimer.unref?.();
17860
18789
  }
17861
18790
  stopHeartbeat() {
@@ -17863,10 +18792,15 @@ var EnvironmentSession = class extends Session {
17863
18792
  clearInterval(this.heartbeatTimer);
17864
18793
  this.heartbeatTimer = null;
17865
18794
  }
18795
+ this.heartbeatProbeInFlight = false;
17866
18796
  }
17867
18797
  async hello() {
17868
18798
  const result = await super.hello();
17869
- this.startHeartbeat();
18799
+ if (result.features?.serverHeartbeat === false) {
18800
+ this.startHeartbeat({ probeServer: true });
18801
+ } else {
18802
+ this.startHeartbeat();
18803
+ }
17870
18804
  return result;
17871
18805
  }
17872
18806
  get environmentId() {
@@ -18001,13 +18935,13 @@ var EnvironmentSession = class extends Session {
18001
18935
  input.operationId,
18002
18936
  "operationId"
18003
18937
  );
18004
- const text = requiredAssistantReplyString(input.text, "text");
18938
+ const text2 = requiredAssistantReplyString(input.text, "text");
18005
18939
  return this.sessionDataRequest(
18006
18940
  "/assistant-replies",
18007
18941
  void 0,
18008
18942
  {
18009
18943
  method: "POST",
18010
- body: { id, operationId: operationId2, text }
18944
+ body: { id, operationId: operationId2, text: text2 }
18011
18945
  }
18012
18946
  );
18013
18947
  }
@@ -18052,7 +18986,10 @@ var EnvironmentSession = class extends Session {
18052
18986
  queryOptions.targetId = options.target.id;
18053
18987
  delete queryOptions.target;
18054
18988
  }
18055
- return this.sessionDataRequest("/artifacts", queryOptions);
18989
+ return this.sessionDataRequest("/artifacts", queryOptions).then((page) => ({
18990
+ ...page,
18991
+ items: page.items.map(requireArtifactWorkflow)
18992
+ }));
18056
18993
  },
18057
18994
  listForLatestJob: (options = {}) => this.artifacts.list({
18058
18995
  ...options,
@@ -18065,7 +19002,7 @@ var EnvironmentSession = class extends Session {
18065
19002
  // `cache: no-store`; this also bypasses intermediaries that ignore
18066
19003
  // the Fetch cache directive for an otherwise identical GET URL.
18067
19004
  { _granularLiveRead: Date.now() }
18068
- ),
19005
+ ).then(requireArtifactWorkflow),
18069
19006
  create: (artifact) => this.sessionDataRequest(
18070
19007
  "/artifacts",
18071
19008
  void 0,
@@ -18073,7 +19010,7 @@ var EnvironmentSession = class extends Session {
18073
19010
  method: "POST",
18074
19011
  body: artifact
18075
19012
  }
18076
- ),
19013
+ ).then(requireArtifactWorkflow),
18077
19014
  acceptSuggestion: (sequence) => this.sessionDataRequest(
18078
19015
  "/artifacts/suggestions/accept",
18079
19016
  void 0,
@@ -18081,7 +19018,7 @@ var EnvironmentSession = class extends Session {
18081
19018
  method: "POST",
18082
19019
  body: { sequence }
18083
19020
  }
18084
- ),
19021
+ ).then(requireArtifactWorkflow),
18085
19022
  updateInputs: (artifactId, patch) => this.sessionDataRequest(
18086
19023
  `/artifacts/${encodeURIComponent(artifactId)}`,
18087
19024
  void 0,
@@ -18089,7 +19026,7 @@ var EnvironmentSession = class extends Session {
18089
19026
  method: "PATCH",
18090
19027
  body: patch
18091
19028
  }
18092
- ),
19029
+ ).then(requireArtifactWorkflow),
18093
19030
  relationshipOptions: (artifactId, input) => this.sessionDataRequest(
18094
19031
  `/artifacts/${encodeURIComponent(artifactId)}/relationship-options`,
18095
19032
  void 0,
@@ -18099,27 +19036,27 @@ var EnvironmentSession = class extends Session {
18099
19036
  `/artifacts/${encodeURIComponent(artifactId)}/relationship-create`,
18100
19037
  void 0,
18101
19038
  { method: "POST", body: input }
18102
- ),
19039
+ ).then(requireArtifactWorkflow),
18103
19040
  validate: (artifactId) => this.sessionDataRequest(
18104
19041
  `/artifacts/${encodeURIComponent(artifactId)}/validate`,
18105
19042
  void 0,
18106
19043
  { method: "POST" }
18107
- ),
19044
+ ).then(requireArtifactWorkflowResult),
18108
19045
  execute: (artifactId, options) => this.sessionDataRequest(
18109
19046
  `/artifacts/${encodeURIComponent(artifactId)}/execute`,
18110
19047
  void 0,
18111
19048
  { method: "POST", body: options }
18112
- ),
19049
+ ).then(requireArtifactWorkflowResult),
18113
19050
  approve: (artifactId, options) => this.sessionDataRequest(
18114
19051
  `/artifacts/${encodeURIComponent(artifactId)}/approve`,
18115
19052
  void 0,
18116
19053
  { method: "POST", body: options }
18117
- ),
19054
+ ).then(requireArtifactWorkflowResult),
18118
19055
  cancel: (artifactId) => this.sessionDataRequest(
18119
19056
  `/artifacts/${encodeURIComponent(artifactId)}/cancel`,
18120
19057
  void 0,
18121
19058
  { method: "POST" }
18122
- )
19059
+ ).then(requireArtifactWorkflow)
18123
19060
  };
18124
19061
  }
18125
19062
  get manualActions() {
@@ -20107,13 +21044,13 @@ var Granular = class _Granular {
20107
21044
  * Permission Profile management for sandboxes
20108
21045
  */
20109
21046
  get permissionProfiles() {
20110
- const profileSourceFromRecord = (record) => {
20111
- const profile = record.profile || record.rules || {};
21047
+ const profileSourceFromRecord = (record2) => {
21048
+ const profile = record2.profile || record2.rules || {};
20112
21049
  return {
20113
21050
  ...profile,
20114
21051
  schemaVersion: profile.schemaVersion || 1,
20115
- name: profile.name || record.name,
20116
- description: profile.description || record.description
21052
+ name: profile.name || record2.name,
21053
+ description: profile.description || record2.description
20117
21054
  };
20118
21055
  };
20119
21056
  return {
@@ -20696,18 +21633,18 @@ function stableValue(value) {
20696
21633
  function stableJson(value) {
20697
21634
  return JSON.stringify(stableValue(value));
20698
21635
  }
20699
- function recordKey(record) {
20700
- return `${record.className}\0${record.id}`;
21636
+ function recordKey(record2) {
21637
+ return `${record2.className}\0${record2.id}`;
20701
21638
  }
20702
21639
  function recordReference(className, id) {
20703
21640
  return { className, id };
20704
21641
  }
20705
21642
  function changedRecordSnapshots(beforeRecords, afterRecords) {
20706
21643
  const before = new Map(
20707
- beforeRecords.map((record) => [recordKey(record), record])
21644
+ beforeRecords.map((record2) => [recordKey(record2), record2])
20708
21645
  );
20709
21646
  const after = new Map(
20710
- afterRecords.map((record) => [recordKey(record), record])
21647
+ afterRecords.map((record2) => [recordKey(record2), record2])
20711
21648
  );
20712
21649
  const changedKeys = /* @__PURE__ */ new Set();
20713
21650
  for (const key of /* @__PURE__ */ new Set([...before.keys(), ...after.keys()])) {
@@ -20717,10 +21654,10 @@ function changedRecordSnapshots(beforeRecords, afterRecords) {
20717
21654
  }
20718
21655
  return {
20719
21656
  beforeRecords: beforeRecords.filter(
20720
- (record) => changedKeys.has(recordKey(record))
21657
+ (record2) => changedKeys.has(recordKey(record2))
20721
21658
  ),
20722
21659
  afterRecords: afterRecords.filter(
20723
- (record) => changedKeys.has(recordKey(record))
21660
+ (record2) => changedKeys.has(recordKey(record2))
20724
21661
  )
20725
21662
  };
20726
21663
  }
@@ -20769,35 +21706,35 @@ function changedObjectProjection(before, after) {
20769
21706
  }
20770
21707
  function projectionChanges(input) {
20771
21708
  const before = new Map(
20772
- input.beforeRecords.map((record) => [recordKey(record), record])
21709
+ input.beforeRecords.map((record2) => [recordKey(record2), record2])
20773
21710
  );
20774
21711
  const after = new Map(
20775
- input.afterRecords.map((record) => [recordKey(record), record])
21712
+ input.afterRecords.map((record2) => [recordKey(record2), record2])
20776
21713
  );
20777
21714
  const targets = relationshipTargets(input.manifest);
20778
21715
  const changes = [];
20779
- for (const record of input.afterRecords) {
20780
- const previous = before.get(recordKey(record));
20781
- const objectChange = changedObjectProjection(previous, record);
21716
+ for (const record2 of input.afterRecords) {
21717
+ const previous = before.get(recordKey(record2));
21718
+ const objectChange = changedObjectProjection(previous, record2);
20782
21719
  if (objectChange) changes.push(objectChange);
20783
21720
  const relationshipNames = /* @__PURE__ */ new Set([
20784
21721
  ...Object.keys(previous?.relationships || {}),
20785
- ...Object.keys(record.relationships || {})
21722
+ ...Object.keys(record2.relationships || {})
20786
21723
  ]);
20787
21724
  for (const relationshipName of [...relationshipNames].sort()) {
20788
21725
  const target = targets.get(
20789
- `${record.className}\0${relationshipName}`
21726
+ `${record2.className}\0${relationshipName}`
20790
21727
  );
20791
21728
  if (!target) {
20792
21729
  throw new Error(
20793
- `No ontology relationship target is declared for ${record.className}.${relationshipName}`
21730
+ `No ontology relationship target is declared for ${record2.className}.${relationshipName}`
20794
21731
  );
20795
21732
  }
20796
21733
  const previousIds = new Set(
20797
21734
  asIds(previous?.relationships?.[relationshipName])
20798
21735
  );
20799
21736
  const currentIds = new Set(
20800
- asIds(record.relationships?.[relationshipName])
21737
+ asIds(record2.relationships?.[relationshipName])
20801
21738
  );
20802
21739
  for (const id of [...previousIds].sort()) {
20803
21740
  if (currentIds.has(id)) continue;
@@ -20805,7 +21742,7 @@ function projectionChanges(input) {
20805
21742
  kind: "relationship",
20806
21743
  operation: "disconnected",
20807
21744
  relationship: relationshipName,
20808
- from: recordReference(record.className, record.id),
21745
+ from: recordReference(record2.className, record2.id),
20809
21746
  to: recordReference(target.className, id)
20810
21747
  });
20811
21748
  }
@@ -20815,37 +21752,37 @@ function projectionChanges(input) {
20815
21752
  kind: "relationship",
20816
21753
  operation: "connected",
20817
21754
  relationship: relationshipName,
20818
- from: recordReference(record.className, record.id),
21755
+ from: recordReference(record2.className, record2.id),
20819
21756
  to: recordReference(target.className, id)
20820
21757
  });
20821
21758
  }
20822
21759
  }
20823
21760
  const machines = /* @__PURE__ */ new Set([
20824
21761
  ...Object.keys(previous?.states || {}),
20825
- ...Object.keys(record.states || {})
21762
+ ...Object.keys(record2.states || {})
20826
21763
  ]);
20827
21764
  for (const machine of [...machines].sort()) {
20828
- const next = record.states?.[machine];
21765
+ const next = record2.states?.[machine];
20829
21766
  if (next === void 0) continue;
20830
21767
  const prior = previous?.states?.[machine];
20831
21768
  if (prior !== void 0 && stateName(prior) === stateName(next)) continue;
20832
- if (input.transition && input.transition.className === record.className && input.transition.objectId === record.id && input.transition.machine === machine) {
21769
+ if (input.transition && input.transition.className === record2.className && input.transition.objectId === record2.id && input.transition.machine === machine) {
20833
21770
  continue;
20834
21771
  }
20835
21772
  changes.push({
20836
21773
  kind: "state_observation",
20837
- target: recordReference(record.className, record.id),
21774
+ target: recordReference(record2.className, record2.id),
20838
21775
  machine,
20839
21776
  state: stateName(next)
20840
21777
  });
20841
21778
  }
20842
21779
  }
20843
- for (const record of input.beforeRecords) {
20844
- if (after.has(recordKey(record))) continue;
21780
+ for (const record2 of input.beforeRecords) {
21781
+ if (after.has(recordKey(record2))) continue;
20845
21782
  changes.push({
20846
21783
  kind: "object",
20847
21784
  operation: "deleted",
20848
- target: recordReference(record.className, record.id)
21785
+ target: recordReference(record2.className, record2.id)
20849
21786
  });
20850
21787
  }
20851
21788
  return changes;
@@ -20890,7 +21827,7 @@ function transitionOutcome(transition, afterRecords) {
20890
21827
  throw new Error("A transition commit has no authored transition context.");
20891
21828
  }
20892
21829
  const target = afterRecords.find(
20893
- (record) => record.className === transition.className && record.id === transition.objectId
21830
+ (record2) => record2.className === transition.className && record2.id === transition.objectId
20894
21831
  );
20895
21832
  const observed = target?.states?.[transition.machine];
20896
21833
  if (!observed) {
@@ -20936,7 +21873,8 @@ function projectRecordSnapshotMutation(input) {
20936
21873
  const base = {
20937
21874
  source: {
20938
21875
  reference: input.source.reference,
20939
- ...input.source.version ? { version: input.source.version } : {}
21876
+ ...input.source.version ? { version: input.source.version } : {},
21877
+ ...input.source.versionScope ? { versionScope: input.source.versionScope } : {}
20940
21878
  },
20941
21879
  ...target ? { primaryTarget: target } : {},
20942
21880
  changes,
@@ -21107,29 +22045,29 @@ function requireRecord(value, context) {
21107
22045
  }
21108
22046
  return value;
21109
22047
  }
21110
- function requiredString(record, key, context) {
21111
- const value = record[key];
22048
+ function requiredString(record2, key, context) {
22049
+ const value = record2[key];
21112
22050
  if (typeof value !== "string" || !value.trim()) {
21113
22051
  throw new Error(`${context}.${key} must be a non-empty string.`);
21114
22052
  }
21115
22053
  return value;
21116
22054
  }
21117
- function requiredNumber(record, key, context) {
21118
- const value = record[key];
22055
+ function requiredNumber(record2, key, context) {
22056
+ const value = record2[key];
21119
22057
  if (typeof value !== "number" || !Number.isFinite(value)) {
21120
22058
  throw new Error(`${context}.${key} must be a finite number.`);
21121
22059
  }
21122
22060
  return value;
21123
22061
  }
21124
- function requiredStringArray(record, key, context) {
21125
- const value = record[key];
22062
+ function requiredStringArray(record2, key, context) {
22063
+ const value = record2[key];
21126
22064
  if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || !item.trim())) {
21127
22065
  throw new Error(`${context}.${key} must be an array of non-empty strings.`);
21128
22066
  }
21129
22067
  return [...value];
21130
22068
  }
21131
- function requiredNumberRecord(record, key, context) {
21132
- const value = requireRecord(record[key], `${context}.${key}`);
22069
+ function requiredNumberRecord(record2, key, context) {
22070
+ const value = requireRecord(record2[key], `${context}.${key}`);
21133
22071
  const output = {};
21134
22072
  for (const [entryKey, entryValue] of Object.entries(value)) {
21135
22073
  if (typeof entryValue !== "number" || !Number.isFinite(entryValue) || entryValue < 0 || entryValue > 1) {
@@ -21142,22 +22080,22 @@ function requiredNumberRecord(record, key, context) {
21142
22080
  return output;
21143
22081
  }
21144
22082
  function validateHarnessTemplateManifest(value, context = "HarnessTemplateManifest") {
21145
- const record = requireRecord(value, context);
21146
- const status = requiredString(record, "status", context);
22083
+ const record2 = requireRecord(value, context);
22084
+ const status = requiredString(record2, "status", context);
21147
22085
  if (!HARNESS_TEMPLATE_STATUSES.includes(status)) {
21148
22086
  throw new Error(
21149
22087
  `${context}.status must be one of ${HARNESS_TEMPLATE_STATUSES.join(", ")}.`
21150
22088
  );
21151
22089
  }
21152
- const createdAt = requiredString(record, "createdAt", context);
22090
+ const createdAt = requiredString(record2, "createdAt", context);
21153
22091
  if (Number.isNaN(Date.parse(createdAt))) {
21154
22092
  throw new Error(`${context}.createdAt must be an ISO timestamp.`);
21155
22093
  }
21156
22094
  const compatibility = requireRecord(
21157
- record.compatibility,
22095
+ record2.compatibility,
21158
22096
  `${context}.compatibility`
21159
22097
  );
21160
- const evalGates = requireRecord(record.evalGates, `${context}.evalGates`);
22098
+ const evalGates = requireRecord(record2.evalGates, `${context}.evalGates`);
21161
22099
  const maxRegressionPct = requiredNumber(
21162
22100
  evalGates,
21163
22101
  "maxRegressionPct",
@@ -21169,23 +22107,23 @@ function validateHarnessTemplateManifest(value, context = "HarnessTemplateManife
21169
22107
  );
21170
22108
  }
21171
22109
  return {
21172
- id: requiredString(record, "id", context),
21173
- version: requiredString(record, "version", context),
22110
+ id: requiredString(record2, "id", context),
22111
+ version: requiredString(record2, "version", context),
21174
22112
  status,
21175
- owner: requiredString(record, "owner", context),
22113
+ owner: requiredString(record2, "owner", context),
21176
22114
  createdAt,
21177
- changelog: requiredString(record, "changelog", context),
21178
- promptBuilder: requiredString(record, "promptBuilder", context),
21179
- continuationBuilder: requiredString(record, "continuationBuilder", context),
22115
+ changelog: requiredString(record2, "changelog", context),
22116
+ promptBuilder: requiredString(record2, "promptBuilder", context),
22117
+ continuationBuilder: requiredString(record2, "continuationBuilder", context),
21180
22118
  modelOutputInstruction: requiredString(
21181
- record,
22119
+ record2,
21182
22120
  "modelOutputInstruction",
21183
22121
  context
21184
22122
  ),
21185
- codeReviewPolicy: requiredString(record, "codeReviewPolicy", context),
21186
- defaultModel: requiredString(record, "defaultModel", context),
21187
- modelMatrix: requiredStringArray(record, "modelMatrix", context),
21188
- temperature: requiredNumber(record, "temperature", context),
22123
+ codeReviewPolicy: requiredString(record2, "codeReviewPolicy", context),
22124
+ defaultModel: requiredString(record2, "defaultModel", context),
22125
+ modelMatrix: requiredStringArray(record2, "modelMatrix", context),
22126
+ temperature: requiredNumber(record2, "temperature", context),
21189
22127
  compatibility: {
21190
22128
  minSdkVersion: requiredString(
21191
22129
  compatibility,
@@ -21239,27 +22177,27 @@ var DEFAULT_LOW_SIGNAL_REASONING_LINES = [
21239
22177
  function parseReasoningCommentLine(line, options = {}) {
21240
22178
  const trimmed = line.trimStart();
21241
22179
  if (!trimmed.startsWith("//")) return null;
21242
- const text = trimmed.replace(/^\/\/\s?/, "").trim();
21243
- if (!text) return { kind: "ignored" };
22180
+ const text2 = trimmed.replace(/^\/\/\s?/, "").trim();
22181
+ if (!text2) return { kind: "ignored" };
21244
22182
  const ignoredDirectives = options.ignoredCommentDirectives || DEFAULT_IGNORED_REASONING_COMMENT_DIRECTIVES;
21245
- if (ignoredDirectives.some((pattern) => pattern.test(text))) {
22183
+ if (ignoredDirectives.some((pattern) => pattern.test(text2))) {
21246
22184
  return { kind: "ignored" };
21247
22185
  }
21248
22186
  const lowSignalLines = options.lowSignalReasoningLines || DEFAULT_LOW_SIGNAL_REASONING_LINES;
21249
- if (lowSignalLines.some((pattern) => pattern.test(text))) {
22187
+ if (lowSignalLines.some((pattern) => pattern.test(text2))) {
21250
22188
  return { kind: "ignored" };
21251
22189
  }
21252
- return { kind: "reasoning", text };
22190
+ return { kind: "reasoning", text: text2 };
21253
22191
  }
21254
22192
  function consumeGranularReasoningTraceChunk(buffer, chunk, options = {}) {
21255
- let text = buffer + chunk;
22193
+ let text2 = buffer + chunk;
21256
22194
  let visibleText = "";
21257
22195
  const reasoningLines = [];
21258
22196
  while (true) {
21259
- const newlineIndex = text.indexOf("\n");
22197
+ const newlineIndex = text2.indexOf("\n");
21260
22198
  if (newlineIndex === -1) break;
21261
- const rawLine = text.slice(0, newlineIndex);
21262
- text = text.slice(newlineIndex + 1);
22199
+ const rawLine = text2.slice(0, newlineIndex);
22200
+ text2 = text2.slice(newlineIndex + 1);
21263
22201
  const comment = parseReasoningCommentLine(
21264
22202
  rawLine.replace(/\r$/, ""),
21265
22203
  options
@@ -21273,19 +22211,19 @@ function consumeGranularReasoningTraceChunk(buffer, chunk, options = {}) {
21273
22211
  `;
21274
22212
  }
21275
22213
  }
21276
- if (options.final && text.length > 0) {
21277
- const comment = parseReasoningCommentLine(text.replace(/\r$/, ""), options);
22214
+ if (options.final && text2.length > 0) {
22215
+ const comment = parseReasoningCommentLine(text2.replace(/\r$/, ""), options);
21278
22216
  if (comment?.kind === "reasoning") {
21279
22217
  reasoningLines.push(comment.text);
21280
- text = "";
22218
+ text2 = "";
21281
22219
  } else if (comment?.kind === "ignored") {
21282
- text = "";
22220
+ text2 = "";
21283
22221
  } else {
21284
- visibleText += text;
21285
- text = "";
22222
+ visibleText += text2;
22223
+ text2 = "";
21286
22224
  }
21287
22225
  }
21288
- return { buffer: text, visibleText, reasoningLines };
22226
+ return { buffer: text2, visibleText, reasoningLines };
21289
22227
  }
21290
22228
  function consumeGranularReasoningOnlyChunk(buffer, chunk, options = {}) {
21291
22229
  const result = consumeGranularReasoningTraceChunk(buffer, chunk, options);
@@ -21294,8 +22232,8 @@ function consumeGranularReasoningOnlyChunk(buffer, chunk, options = {}) {
21294
22232
  reasoningLines: result.reasoningLines
21295
22233
  };
21296
22234
  }
21297
- function stripGranularReasoningTrace(text, options = {}) {
21298
- return consumeGranularReasoningTraceChunk("", text, {
22235
+ function stripGranularReasoningTrace(text2, options = {}) {
22236
+ return consumeGranularReasoningTraceChunk("", text2, {
21299
22237
  ...options,
21300
22238
  final: true
21301
22239
  }).visibleText.trim();
@@ -21330,8 +22268,8 @@ function stableStringify(value) {
21330
22268
  if (Array.isArray(value)) {
21331
22269
  return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
21332
22270
  }
21333
- const record = value;
21334
- return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`).join(",")}}`;
22271
+ const record2 = value;
22272
+ return `{${Object.keys(record2).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(record2[key])}`).join(",")}}`;
21335
22273
  }
21336
22274
  function renderConstBlock(name, value) {
21337
22275
  return `const ${name} = ${JSON.stringify(value, null, 2)} as const;`;
@@ -21927,9 +22865,9 @@ function getLatestClosure(liveDoc) {
21927
22865
  }
21928
22866
  const closures = [];
21929
22867
  for (const [closureId, value] of Object.entries(closuresById)) {
21930
- const record = asRecord4(value);
21931
- if (!record) continue;
21932
- closures.push({ ...record, closureId });
22868
+ const record2 = asRecord4(value);
22869
+ if (!record2) continue;
22870
+ closures.push({ ...record2, closureId });
21933
22871
  }
21934
22872
  closures.sort(
21935
22873
  (left, right) => (Number(right.createdAt) || 0) - (Number(left.createdAt) || 0)
@@ -21967,9 +22905,9 @@ function getJobRecords(liveDoc) {
21967
22905
  const jobsById = asRecord4(asRecord4(liveDoc?.jobs)?.byId) || {};
21968
22906
  const jobs = [];
21969
22907
  for (const [jobId, value] of Object.entries(jobsById)) {
21970
- const record = asRecord4(value);
21971
- if (!record) continue;
21972
- jobs.push({ ...record, jobId });
22908
+ const record2 = asRecord4(value);
22909
+ if (!record2) continue;
22910
+ jobs.push({ ...record2, jobId });
21973
22911
  }
21974
22912
  jobs.sort(
21975
22913
  (left, right) => getJobTimestamp(right) - getJobTimestamp(left)
@@ -22155,8 +23093,8 @@ function hasOpenPrompt(liveDoc, pendingPrompts) {
22155
23093
  const prompts = asRecord4(asRecord4(job)?.prompts);
22156
23094
  if (!prompts) continue;
22157
23095
  for (const prompt of Object.values(prompts)) {
22158
- const record = asRecord4(prompt);
22159
- if (record?.status === "open") return true;
23096
+ const record2 = asRecord4(prompt);
23097
+ if (record2?.status === "open") return true;
22160
23098
  }
22161
23099
  }
22162
23100
  return false;
@@ -22215,13 +23153,13 @@ function projectLoopSummary(liveDoc, pendingPrompts = [], options) {
22215
23153
  title: typeof decision.title === "string" && decision.title.trim() ? decision.title.trim() : "Decision",
22216
23154
  status,
22217
23155
  candidates: status === "open" ? asArray(decision.candidates).slice(0, 5).map((candidate) => {
22218
- const record = asRecord4(candidate);
22219
- if (!record) return null;
23156
+ const record2 = asRecord4(candidate);
23157
+ if (!record2) return null;
22220
23158
  return {
22221
- id: typeof record.id === "string" ? record.id : "unknown",
22222
- label: typeof record.label === "string" && record.label.trim() ? record.label.trim() : null,
22223
- description: typeof record.description === "string" && record.description.trim() ? record.description.trim() : null,
22224
- metadata: asRecord4(record.metadata)
23159
+ id: typeof record2.id === "string" ? record2.id : "unknown",
23160
+ label: typeof record2.label === "string" && record2.label.trim() ? record2.label.trim() : null,
23161
+ description: typeof record2.description === "string" && record2.description.trim() ? record2.description.trim() : null,
23162
+ metadata: asRecord4(record2.metadata)
22225
23163
  };
22226
23164
  }).filter(Boolean) : [],
22227
23165
  selected: status === "open" ? null : {
@@ -22856,11 +23794,11 @@ function normalizeJsonSchema(value) {
22856
23794
  return asRecord4(value);
22857
23795
  }
22858
23796
  function jsonSchemaTypeName(schema) {
22859
- const record = normalizeJsonSchema(schema);
22860
- if (!record) return "unknown";
22861
- const type = record.type;
22862
- if (record.format === "date-time") return "date-time";
22863
- if (record.format === "date" || type === "date" || Array.isArray(type) && type.includes("date")) {
23797
+ const record2 = normalizeJsonSchema(schema);
23798
+ if (!record2) return "unknown";
23799
+ const type = record2.type;
23800
+ if (record2.format === "date-time") return "date-time";
23801
+ if (record2.format === "date" || type === "date" || Array.isArray(type) && type.includes("date")) {
22864
23802
  return "date";
22865
23803
  }
22866
23804
  if (typeof type === "string") {
@@ -22871,12 +23809,12 @@ function jsonSchemaTypeName(schema) {
22871
23809
  return "unknown";
22872
23810
  }
22873
23811
  function summarizeObjectSchema(schema) {
22874
- const record = normalizeJsonSchema(schema);
22875
- const properties = asRecord4(record?.properties);
23812
+ const record2 = normalizeJsonSchema(schema);
23813
+ const properties = asRecord4(record2?.properties);
22876
23814
  if (!properties || Object.keys(properties).length === 0) {
22877
- return record ? "{}" : null;
23815
+ return record2 ? "{}" : null;
22878
23816
  }
22879
- const required = new Set(asArray(record?.required));
23817
+ const required = new Set(asArray(record2?.required));
22880
23818
  const fields = Object.entries(properties).slice(0, 8).map(([name, property]) => {
22881
23819
  const marker = required.has(name) ? "*" : "?";
22882
23820
  return `${name}${marker}: ${jsonSchemaTypeName(property)}`;
@@ -23365,6 +24303,10 @@ ${actionIndex}
23365
24303
  - 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.
23366
24304
  - 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.
23367
24305
  - 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.
24306
+ - 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.
24307
+ - 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.
24308
+ - 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.
24309
+ - 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.
23368
24310
  - 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.
23369
24311
  - 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.
23370
24312
  - 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.
@@ -23618,10 +24560,10 @@ function responseTextFromFeedItems(feedItems, jobId) {
23618
24560
  if (item.kind !== "message" || item.payload.role !== "assistant" || item.source?.jobId !== jobId || item.sequence <= latestSequence) {
23619
24561
  continue;
23620
24562
  }
23621
- const text = item.payload.text.trim();
23622
- if (!text) continue;
24563
+ const text2 = item.payload.text.trim();
24564
+ if (!text2) continue;
23623
24565
  latestSequence = item.sequence;
23624
- latestText = text;
24566
+ latestText = text2;
23625
24567
  }
23626
24568
  return latestText;
23627
24569
  }
@@ -23636,39 +24578,39 @@ function pushStringArray(target, value) {
23636
24578
  pushString(target, item);
23637
24579
  }
23638
24580
  }
23639
- function collectReferencesFromRecord(record, refs) {
24581
+ function collectReferencesFromRecord(record2, refs) {
23640
24582
  for (const key of ENTRY_KEY_CANDIDATES)
23641
- pushString(refs.entryPaths, record[key]);
24583
+ pushString(refs.entryPaths, record2[key]);
23642
24584
  for (const key of ENTRY_ARRAY_KEY_CANDIDATES)
23643
- pushStringArray(refs.entryPaths, record[key]);
24585
+ pushStringArray(refs.entryPaths, record2[key]);
23644
24586
  for (const key of LIST_KEY_CANDIDATES)
23645
- pushString(refs.listNames, record[key]);
24587
+ pushString(refs.listNames, record2[key]);
23646
24588
  for (const key of LIST_ARRAY_KEY_CANDIDATES)
23647
- pushStringArray(refs.listNames, record[key]);
24589
+ pushStringArray(refs.listNames, record2[key]);
23648
24590
  for (const key of VARIABLE_KEY_CANDIDATES)
23649
- pushString(refs.variableNames, record[key]);
24591
+ pushString(refs.variableNames, record2[key]);
23650
24592
  for (const key of VARIABLE_ARRAY_KEY_CANDIDATES)
23651
- pushStringArray(refs.variableNames, record[key]);
24593
+ pushStringArray(refs.variableNames, record2[key]);
23652
24594
  }
23653
- function stringValue(record, keys) {
24595
+ function stringValue(record2, keys) {
23654
24596
  for (const key of keys) {
23655
- const value = record[key];
24597
+ const value = record2[key];
23656
24598
  if (typeof value === "string" && value.trim()) {
23657
24599
  return value.trim();
23658
24600
  }
23659
24601
  }
23660
24602
  return null;
23661
24603
  }
23662
- function findEntryPathForRecord(record, heap) {
23663
- const directPath = stringValue(record, ["entryPath", "path"]);
24604
+ function findEntryPathForRecord(record2, heap) {
24605
+ const directPath = stringValue(record2, ["entryPath", "path"]);
23664
24606
  if (directPath && heap.entriesByPath?.[directPath]) {
23665
24607
  return directPath;
23666
24608
  }
23667
- const id = stringValue(record, ["id", "_id", "recordId", "objectId"]);
24609
+ const id = stringValue(record2, ["id", "_id", "recordId", "objectId"]);
23668
24610
  if (!id) {
23669
24611
  return null;
23670
24612
  }
23671
- const className = stringValue(record, [
24613
+ const className = stringValue(record2, [
23672
24614
  "className",
23673
24615
  "_className",
23674
24616
  "__className",
@@ -23702,17 +24644,17 @@ function scanForHeapReferences(value, heap, refs, depth = 0, seen = /* @__PURE__
23702
24644
  }
23703
24645
  return;
23704
24646
  }
23705
- const record = asRecord5(value);
23706
- if (!record) return;
24647
+ const record2 = asRecord5(value);
24648
+ if (!record2) return;
23707
24649
  seen.add(value);
23708
- const entryPath = findEntryPathForRecord(record, heap);
24650
+ const entryPath = findEntryPathForRecord(record2, heap);
23709
24651
  if (entryPath) refs.entryPaths.add(entryPath);
23710
- collectReferencesFromRecord(record, refs);
24652
+ collectReferencesFromRecord(record2, refs);
23711
24653
  for (const key of UI_CONTAINER_KEYS) {
23712
- const nested = asRecord5(record[key]);
24654
+ const nested = asRecord5(record2[key]);
23713
24655
  if (nested) collectReferencesFromRecord(nested, refs);
23714
24656
  }
23715
- for (const nested of Object.values(record).slice(0, 24)) {
24657
+ for (const nested of Object.values(record2).slice(0, 24)) {
23716
24658
  scanForHeapReferences(nested, heap, refs, depth + 1, seen);
23717
24659
  }
23718
24660
  }
@@ -23861,8 +24803,8 @@ var OPENAI_MODEL_PRICING_USD_PER_MILLION = {
23861
24803
  function asRecord6(value) {
23862
24804
  return value && typeof value === "object" ? value : null;
23863
24805
  }
23864
- function numberField(record, key) {
23865
- const value = record?.[key];
24806
+ function numberField(record2, key) {
24807
+ const value = record2?.[key];
23866
24808
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
23867
24809
  }
23868
24810
  function microsPerMillion(usdPerMillion) {
@@ -23966,6 +24908,216 @@ function calculateOpenAITokenSpend(model, rawUsage) {
23966
24908
  };
23967
24909
  }
23968
24910
 
24911
+ // src/artifact-surface.ts
24912
+ function operationNode(workflow) {
24913
+ return getArtifactWorkflowNodes(workflow).find(
24914
+ (node) => node.kind === "operation"
24915
+ ) || null;
24916
+ }
24917
+ function operationStatusLabel(phase) {
24918
+ if (phase === "reviewing") return "Preparing";
24919
+ if (phase === "sending") return "In progress";
24920
+ if (phase === "synchronizing") return "Saving";
24921
+ if (phase === "verifying") return "Verifying";
24922
+ if (phase === "waiting_for_approval") return "Awaiting approval";
24923
+ if (phase === "completed") return "Completed";
24924
+ if (phase === "failed") return "Failed";
24925
+ return null;
24926
+ }
24927
+ function action(id, label, tone, disabled, disabledReason = null) {
24928
+ return { id, label, tone, disabled, disabledReason };
24929
+ }
24930
+ function navigationIsUseful(workflow) {
24931
+ const nodes = getArtifactWorkflowNodes(workflow).filter(
24932
+ (node) => node.kind !== "operation" && node.kind !== "group"
24933
+ );
24934
+ return nodes.length > 1 || nodes.some((node) => node.kind === "child_artifact");
24935
+ }
24936
+ function inputNode(workflow, activeEditorNodeId) {
24937
+ if (activeEditorNodeId) {
24938
+ const active = workflow.nodesById[activeEditorNodeId];
24939
+ if (active && (active.kind === "input" || active.kind === "relationship")) {
24940
+ return active;
24941
+ }
24942
+ }
24943
+ const current = getCurrentArtifactWorkflowNode(workflow);
24944
+ return current?.kind === "input" || current?.kind === "relationship" ? current : null;
24945
+ }
24946
+ function selectArtifactSurface(artifact, context = {}) {
24947
+ const workflow = artifact.workflow || projectArtifactWorkflow(artifact);
24948
+ const operation = operationNode(workflow);
24949
+ const workflowStatus = operation?.status || null;
24950
+ const terminal = workflowStatus === "completed" || workflowStatus === "canceled";
24951
+ const requestedEditorNodeId = terminal ? null : context.activeEditorNodeId || null;
24952
+ const current = getCurrentArtifactWorkflowNode(workflow);
24953
+ const activeInput = inputNode(workflow, requestedEditorNodeId);
24954
+ const mutationsDisabled = context.mutationsDisabled === true;
24955
+ const busy = Boolean(context.busyAction);
24956
+ const disabledReason = mutationsDisabled ? "Reconnect to continue." : busy ? "This action is already in progress." : null;
24957
+ const actionDisabled = Boolean(disabledReason);
24958
+ const transportStatus = context.transportStatus || "connected";
24959
+ let kind;
24960
+ if (artifact.status === "completed" && workflowStatus === "completed") {
24961
+ kind = "result";
24962
+ } else if (artifact.status === "completed" && workflowStatus === "failed") {
24963
+ kind = "failure";
24964
+ } else if (artifact.status === "completed" && workflowStatus === "working") {
24965
+ kind = "executing";
24966
+ } else if (artifact.status === "completed" && workflowStatus && workflowStatus !== "canceled") {
24967
+ kind = "blocked";
24968
+ } else if (artifact.status === "completed") kind = "result";
24969
+ else if (artifact.status === "canceled") kind = "canceled";
24970
+ else if (artifact.status === "stale") kind = "stale";
24971
+ else if (artifact.status === "partially_completed") kind = "partial";
24972
+ else if (artifact.status === "failed" || context.directError)
24973
+ kind = "failure";
24974
+ else if (artifact.status === "blocked") kind = "blocked";
24975
+ else if (artifact.status === "awaiting_human") kind = "approval";
24976
+ else if (artifact.status === "running" || busy) kind = "executing";
24977
+ else if (requestedEditorNodeId || artifact.status === "draft" || artifact.status === "needs_edit") {
24978
+ kind = "input";
24979
+ } else {
24980
+ kind = "review";
24981
+ }
24982
+ let title = artifact.label;
24983
+ let description = artifact.description || null;
24984
+ let statusLabel = null;
24985
+ let primaryAction = null;
24986
+ let secondaryActions = [];
24987
+ if (kind === "input") {
24988
+ title = activeInput?.label || artifact.label;
24989
+ description = activeInput?.description || artifact.description || null;
24990
+ statusLabel = activeInput?.status === "needs_input" ? "Needs input" : null;
24991
+ primaryAction = action(
24992
+ "continue",
24993
+ "Continue",
24994
+ "primary",
24995
+ actionDisabled,
24996
+ disabledReason
24997
+ );
24998
+ } else if (kind === "review") {
24999
+ title = artifact.label;
25000
+ description = artifact.description || operation?.description || null;
25001
+ primaryAction = action(
25002
+ "execute",
25003
+ operation?.label || "Continue",
25004
+ "primary",
25005
+ actionDisabled || context.canExecute === false,
25006
+ disabledReason
25007
+ );
25008
+ if (context.canEdit) {
25009
+ secondaryActions = [
25010
+ action(
25011
+ "edit",
25012
+ "Edit details",
25013
+ "secondary",
25014
+ actionDisabled,
25015
+ disabledReason
25016
+ )
25017
+ ];
25018
+ }
25019
+ } else if (kind === "executing") {
25020
+ title = operation?.operationDetail || operation?.label || artifact.label;
25021
+ description = operation?.operationDetail ? artifact.label : null;
25022
+ statusLabel = operationStatusLabel(operation?.operationPhase) || "In progress";
25023
+ } else if (kind === "approval") {
25024
+ title = operation?.attention?.title || operation?.label || artifact.label;
25025
+ description = operation?.attention?.detail || operation?.description || null;
25026
+ statusLabel = "Awaiting approval";
25027
+ if (context.canApprove) {
25028
+ primaryAction = action(
25029
+ "approve",
25030
+ "Approve",
25031
+ "primary",
25032
+ actionDisabled,
25033
+ disabledReason
25034
+ );
25035
+ secondaryActions = [
25036
+ action(
25037
+ "decline",
25038
+ "Decline",
25039
+ "secondary",
25040
+ actionDisabled,
25041
+ disabledReason
25042
+ )
25043
+ ];
25044
+ }
25045
+ } else if (kind === "failure" || kind === "blocked") {
25046
+ const attention = current?.attention || operation?.attention;
25047
+ title = attention?.title || artifact.label;
25048
+ description = context.directError || attention?.detail || operation?.description || null;
25049
+ statusLabel = kind === "failure" ? "Failed" : "Needs attention";
25050
+ if (attention?.retryable) {
25051
+ primaryAction = action(
25052
+ "retry",
25053
+ "Retry",
25054
+ "primary",
25055
+ actionDisabled,
25056
+ disabledReason
25057
+ );
25058
+ } else if (context.canEdit) {
25059
+ primaryAction = action(
25060
+ "edit",
25061
+ "Edit details",
25062
+ "primary",
25063
+ actionDisabled,
25064
+ disabledReason
25065
+ );
25066
+ }
25067
+ } else if (kind === "partial") {
25068
+ title = operation?.attention?.title || artifact.label;
25069
+ description = operation?.attention?.detail || operation?.description || null;
25070
+ statusLabel = "Partially completed";
25071
+ primaryAction = action(
25072
+ "retry",
25073
+ "Retry unfinished work",
25074
+ "primary",
25075
+ actionDisabled,
25076
+ disabledReason
25077
+ );
25078
+ } else if (kind === "stale") {
25079
+ title = operation?.attention?.title || artifact.label;
25080
+ description = operation?.attention?.detail || operation?.description || null;
25081
+ statusLabel = "Details changed";
25082
+ primaryAction = action(
25083
+ "refresh",
25084
+ "Review latest details",
25085
+ "primary",
25086
+ actionDisabled,
25087
+ disabledReason
25088
+ );
25089
+ } else if (kind === "result") {
25090
+ title = artifact.label;
25091
+ description = operation?.recap?.summary || artifact.description || null;
25092
+ statusLabel = "Completed";
25093
+ primaryAction = action("close", "Done", "primary", false);
25094
+ } else {
25095
+ title = artifact.label;
25096
+ description = operation?.attention?.detail || "Nothing else will run.";
25097
+ statusLabel = "Canceled";
25098
+ primaryAction = action("close", "Done", "primary", false);
25099
+ }
25100
+ const activeNodeId = kind === "input" ? activeInput?.id || workflow.currentNodeId : workflow.currentNodeId;
25101
+ const announcement = [statusLabel, title, description].filter((value) => Boolean(value)).join(". ");
25102
+ return {
25103
+ artifactId: artifact.artifactId,
25104
+ revision: workflow.revision,
25105
+ updatedAt: artifact.updatedAt,
25106
+ kind,
25107
+ title,
25108
+ description,
25109
+ statusLabel,
25110
+ announcement,
25111
+ workflow,
25112
+ activeNodeId,
25113
+ operationNodeId: operation?.id || null,
25114
+ showNavigation: navigationIsUseful(workflow),
25115
+ primaryAction,
25116
+ secondaryActions,
25117
+ transportStatus
25118
+ };
25119
+ }
25120
+
23969
25121
  exports.Environment = Environment;
23970
25122
  exports.EnvironmentSession = EnvironmentSession;
23971
25123
  exports.GRANULAR_FEED_DIAGNOSTIC_EVENT = GRANULAR_FEED_DIAGNOSTIC_EVENT;
@@ -23975,6 +25127,7 @@ exports.OntologyHandle = OntologyHandle;
23975
25127
  exports.Session = Session;
23976
25128
  exports.SessionFeedController = SessionFeedController;
23977
25129
  exports.WSClient = WSClient;
25130
+ exports.assertArtifactWorkflow = assertArtifactWorkflow;
23978
25131
  exports.buildContinuationInstruction = buildContinuationInstruction;
23979
25132
  exports.buildContinuationInstructionFromTemplate = buildContinuationInstructionFromTemplate;
23980
25133
  exports.buildGranularAgentCheckpointBlock = buildGranularAgentCheckpointBlock;
@@ -23995,6 +25148,7 @@ exports.buildOpenAISpendEventId = buildOpenAISpendEventId;
23995
25148
  exports.buildSessionTranscript = buildSessionTranscript;
23996
25149
  exports.buildSessionTranscriptFromFeedItems = buildSessionTranscriptFromFeedItems;
23997
25150
  exports.calculateOpenAITokenSpend = calculateOpenAITokenSpend;
25151
+ exports.canInspectArtifactWorkflowNode = canInspectArtifactWorkflowNode;
23998
25152
  exports.canonicalizeCommitValue = canonicalizeCommitValue;
23999
25153
  exports.changedRecordSnapshots = changedRecordSnapshots;
24000
25154
  exports.consumeGranularReasoningOnlyChunk = consumeGranularReasoningOnlyChunk;
@@ -24012,6 +25166,10 @@ exports.environmentSetupProgressFromResponse = environmentSetupProgressFromRespo
24012
25166
  exports.evaluateContinuation = evaluateContinuation;
24013
25167
  exports.evaluateValidationRule = evaluateValidationRule;
24014
25168
  exports.extractPromptTokens = extractPromptTokens;
25169
+ exports.getArtifactWorkflowNavigationLeaves = getArtifactWorkflowNavigationLeaves;
25170
+ exports.getArtifactWorkflowNavigationTarget = getArtifactWorkflowNavigationTarget;
25171
+ exports.getArtifactWorkflowNodes = getArtifactWorkflowNodes;
25172
+ exports.getCurrentArtifactWorkflowNode = getCurrentArtifactWorkflowNode;
24015
25173
  exports.getCurrentClosureId = getCurrentClosureId;
24016
25174
  exports.getDefaultHarnessTemplateId = getDefaultHarnessTemplateId;
24017
25175
  exports.getExclusivePromptTarget = getExclusivePromptTarget;
@@ -24020,6 +25178,8 @@ exports.hasCanonicalSessionFeedActivation = hasCanonicalSessionFeedActivation;
24020
25178
  exports.hasOpenPrompt = hasOpenPrompt;
24021
25179
  exports.hashHarnessTemplateValue = hashHarnessTemplateValue;
24022
25180
  exports.invokeRegisteredEffect = invokeRegisteredEffect;
25181
+ exports.isArtifactWorkflowNodeNavigableLeaf = isArtifactWorkflowNodeNavigableLeaf;
25182
+ exports.isArtifactWorkflowNodeTerminal = isArtifactWorkflowNodeTerminal;
24023
25183
  exports.isCanonicalSessionFeedDocument = isCanonicalSessionFeedDocument;
24024
25184
  exports.isGraphQLMutationDocument = isGraphQLMutationDocument;
24025
25185
  exports.isLocalApiUrl = isLocalApiUrl;
@@ -24036,6 +25196,7 @@ exports.normalizePromptText = normalizePromptText;
24036
25196
  exports.normalizePromptType = normalizePromptType;
24037
25197
  exports.orderTransientFeedItems = orderTransientFeedItems;
24038
25198
  exports.pollEnvironmentSetup = pollEnvironmentSetup;
25199
+ exports.projectArtifactWorkflow = projectArtifactWorkflow;
24039
25200
  exports.projectConversationReferentFocus = projectConversationReferentFocus;
24040
25201
  exports.projectConversationReferentSummary = projectConversationReferentSummary;
24041
25202
  exports.projectHeapSummary = projectHeapSummary;
@@ -24048,6 +25209,8 @@ exports.readSessionFeedSnapshot = readSessionFeedSnapshot;
24048
25209
  exports.recordOpenAIUsageSpend = recordOpenAIUsageSpend;
24049
25210
  exports.renderContinuationInstructionFromTemplate = renderContinuationInstructionFromTemplate;
24050
25211
  exports.renderGranularAgentSystemPromptFromTemplate = renderGranularAgentSystemPromptFromTemplate;
25212
+ exports.requireArtifactWorkflow = requireArtifactWorkflow;
25213
+ exports.requireArtifactWorkflowResult = requireArtifactWorkflowResult;
24051
25214
  exports.resolveApiUrl = resolveApiUrl;
24052
25215
  exports.resolveAuthTokenForApiUrl = resolveAuthTokenForApiUrl;
24053
25216
  exports.resolveHarnessTemplate = resolveHarnessTemplate;
@@ -24055,6 +25218,7 @@ exports.resolveJobPresentation = resolveJobPresentation;
24055
25218
  exports.resolvePromptAnswer = resolvePromptAnswer;
24056
25219
  exports.reviewGeneratedJobCode = reviewGeneratedJobCode;
24057
25220
  exports.scorePromptChoiceMatch = scorePromptChoiceMatch;
25221
+ exports.selectArtifactSurface = selectArtifactSurface;
24058
25222
  exports.stripGranularReasoningTrace = stripGranularReasoningTrace;
24059
25223
  exports.toGranularHttpBase = toGranularHttpBase;
24060
25224
  exports.validateHarnessTemplateManifest = validateHarnessTemplateManifest;