@everfir/az8-cli 0.3.0-preview.1 → 0.3.0-preview.3

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/main.js CHANGED
@@ -14175,11 +14175,47 @@ var CANVAS_OPERATION_REGISTRY = [
14175
14175
  },
14176
14176
  required: ["referenceViewNodeId", "viewNodeId"]
14177
14177
  }),
14178
+ write2("insertPromptReference", "Bind one visible image, text, or video View Node and place its platform At token in the Prompt", "silent", {
14179
+ properties: {
14180
+ placement: {
14181
+ anyOf: [
14182
+ { enum: ["start", "end"], type: "string" },
14183
+ {
14184
+ additionalProperties: false,
14185
+ properties: {
14186
+ anchor: {
14187
+ additionalProperties: false,
14188
+ properties: {
14189
+ occurrence: { minimum: 1, type: "integer" },
14190
+ text: stringSchema()
14191
+ },
14192
+ required: ["text"],
14193
+ type: "object"
14194
+ },
14195
+ kind: { enum: ["before", "after"], type: "string" }
14196
+ },
14197
+ required: ["anchor", "kind"],
14198
+ type: "object"
14199
+ }
14200
+ ]
14201
+ },
14202
+ referenceViewNodeId: stringSchema(),
14203
+ viewNodeId: stringSchema()
14204
+ },
14205
+ required: ["referenceViewNodeId", "viewNodeId"]
14206
+ }),
14178
14207
  write2("removeVisualReference", "Remove one Canvas View Node reference from a generation Draft", "silent", {
14179
14208
  properties: { referenceId: stringSchema(), viewNodeId: stringSchema() },
14180
14209
  required: ["referenceId", "viewNodeId"]
14181
14210
  }),
14182
14211
  write2("startGeneration", "Estimate current Draft inputs, start a Workflow Run, and acknowledge immediate Canvas writes", "barrier", { properties: { viewNodeId: stringSchema() }, required: ["viewNodeId"] }, "workflow-accepted"),
14212
+ write2("reconcileGenerationStart", "Reconcile one known Workflow Run with its target without starting another Run", "barrier", {
14213
+ properties: {
14214
+ viewNodeId: stringSchema(),
14215
+ workflowRunId: stringSchema()
14216
+ },
14217
+ required: ["viewNodeId", "workflowRunId"]
14218
+ }),
14183
14219
  read("getWorkflowRun", "Read one Workflow Run", {
14184
14220
  properties: { workflowRunId: stringSchema() },
14185
14221
  required: ["workflowRunId"]
@@ -14246,10 +14282,25 @@ function sizeSchema() {
14246
14282
  }
14247
14283
 
14248
14284
  // ../../packages/project-canvas-runtime/dist/operation-usage.js
14285
+ function attachCanvasOperationIndeterminateResult(value, result3) {
14286
+ const cause = value instanceof Error ? value : new Error("Unexpected Canvas Operation failure.");
14287
+ const error51 = new Error(cause.message, { cause });
14288
+ return Object.assign(error51, {
14289
+ indeterminate: true,
14290
+ operationResult: structuredClone(result3),
14291
+ ..."timedOut" in cause && cause.timedOut === true ? { timedOut: true } : {}
14292
+ });
14293
+ }
14249
14294
  function attachCanvasOperationUsage(value, usage) {
14250
14295
  const error51 = value instanceof Error ? value : new Error("Unexpected Canvas Operation failure.");
14251
14296
  return Object.assign(error51, { operationUsage: usage });
14252
14297
  }
14298
+ function readCanvasOperationIndeterminateResult(value) {
14299
+ if (typeof value !== "object" || value === null || !("operationResult" in value))
14300
+ return void 0;
14301
+ const result3 = value.operationResult;
14302
+ return typeof result3 === "object" && result3 !== null && !Array.isArray(result3) ? result3 : void 0;
14303
+ }
14253
14304
  function readCanvasOperationUsage(value) {
14254
14305
  if (typeof value !== "object" || value === null || !("operationUsage" in value))
14255
14306
  return void 0;
@@ -15022,6 +15073,37 @@ function addVisualReferenceToDraft(draft, definition, reference, sourceDataType,
15022
15073
  }
15023
15074
  return appendPromptReferenceToken(nextDraft, slot.name, reference.id, reference.viewNodeId, now);
15024
15075
  }
15076
+ function insertPromptReferenceIntoDraft(draft, definition, reference, sourceDataType, placement, now) {
15077
+ const withReference = addVisualReferenceToDraft(draft, definition, reference, sourceDataType, now);
15078
+ const binding = Object.entries(withReference.inputs.slots).flatMap(([slotName, values]) => values.flatMap((value, index) => value.kind === "view-node" && value.id === reference.id ? [{ index, slotName, value }] : []))[0];
15079
+ if (!binding)
15080
+ throw new Error(`Generation Reference ${reference.id} does not exist in the Draft.`);
15081
+ if (binding.value.sourceDataType !== "image" && binding.value.sourceDataType !== "text" && binding.value.sourceDataType !== "video") {
15082
+ throw new Error("Prompt References must resolve to image, text, or video content.");
15083
+ }
15084
+ const promptSlot = requirePromptSlot(definition);
15085
+ const withoutExistingToken = removePromptReferenceToken(withReference, reference.id);
15086
+ const promptValues = withoutExistingToken.inputs.slots[promptSlot.name] ?? [];
15087
+ const existingLiteral = promptValues.find((value) => value.kind === "literal" && value.assetType === "text");
15088
+ const promptInput = existingLiteral ?? literal(withoutExistingToken, promptSlot.name, "text", "");
15089
+ const prompt = promptInput.content;
15090
+ const token = `{{@${binding.value.sourceDataType}:${binding.slotName}:${binding.index}:${reference.id}|view-node|${encodeURIComponent(reference.viewNodeId)}}}`;
15091
+ const content = placement === "start" ? prompt.trim() ? `${token}
15092
+ ${prompt}` : token : placement === "end" ? prompt.trim() ? `${prompt}${prompt.endsWith("\n") ? "" : "\n"}${token}` : token : insertAtPromptAnchor(prompt, token, placement);
15093
+ return {
15094
+ ...structuredClone(withoutExistingToken),
15095
+ inputs: {
15096
+ slots: {
15097
+ ...structuredClone(withoutExistingToken.inputs.slots),
15098
+ [promptSlot.name]: [
15099
+ { ...promptInput, content },
15100
+ ...promptValues.filter((value) => value.id !== promptInput.id)
15101
+ ]
15102
+ }
15103
+ },
15104
+ updatedAt: now
15105
+ };
15106
+ }
15025
15107
  function removeVisualReferenceFromDraft(draft, referenceId, now) {
15026
15108
  let removed = false;
15027
15109
  let removedTextReference = false;
@@ -15068,21 +15150,57 @@ function appendPromptReferenceToken(draft, slotName, referenceId, viewNodeId, no
15068
15150
  };
15069
15151
  }
15070
15152
  function removePromptReferenceToken(draft, referenceId) {
15071
- const pattern = new RegExp(PROMPT_REFERENCE_TOKEN_PATTERN, "g");
15072
15153
  const slots = Object.fromEntries(Object.entries(draft.inputs.slots).map(([slotName, values]) => [
15073
15154
  slotName,
15074
15155
  values.map((value) => {
15075
15156
  if (value.kind !== "literal" || value.assetType !== "text")
15076
15157
  return value;
15077
- const lines = value.content.split("\n").flatMap((line) => {
15078
- const content = line.replace(pattern, (token, _type, _slot, _index, bindingId) => bindingId === referenceId ? "" : token);
15079
- return content.trim() ? [content] : [];
15080
- });
15081
- return { ...value, content: lines.join("\n") };
15158
+ return { ...value, content: removeReferenceTokenFromContent(value.content, referenceId) };
15082
15159
  })
15083
15160
  ]));
15084
15161
  return { ...draft, inputs: { slots } };
15085
15162
  }
15163
+ function insertAtPromptAnchor(prompt, token, placement) {
15164
+ const anchor = placement.anchor.text;
15165
+ if (!anchor.trim())
15166
+ throw new Error("placement.anchor.text must be non-empty.");
15167
+ const indexes = [];
15168
+ let from2 = 0;
15169
+ while (from2 <= prompt.length - anchor.length) {
15170
+ const index2 = prompt.indexOf(anchor, from2);
15171
+ if (index2 < 0)
15172
+ break;
15173
+ indexes.push(index2);
15174
+ from2 = index2 + Math.max(anchor.length, 1);
15175
+ }
15176
+ if (indexes.length === 0) {
15177
+ throw new Error(`Prompt anchor ${JSON.stringify(anchor)} does not exist.`);
15178
+ }
15179
+ const occurrence = placement.anchor.occurrence;
15180
+ if (occurrence === void 0 && indexes.length !== 1) {
15181
+ throw new Error(`Prompt anchor ${JSON.stringify(anchor)} is ambiguous; placement.anchor.occurrence is required.`);
15182
+ }
15183
+ const index = indexes[(occurrence ?? 1) - 1];
15184
+ if (index === void 0) {
15185
+ throw new Error(`Prompt anchor ${JSON.stringify(anchor)} does not have occurrence ${String(occurrence)}.`);
15186
+ }
15187
+ const insertionIndex = placement.kind === "before" ? index : index + anchor.length;
15188
+ return `${prompt.slice(0, insertionIndex)}${token}${prompt.slice(insertionIndex)}`;
15189
+ }
15190
+ function removeReferenceTokenFromContent(content, referenceId) {
15191
+ const matches2 = [...content.matchAll(new RegExp(PROMPT_REFERENCE_TOKEN_PATTERN, "g"))].filter((match) => match[4] === referenceId);
15192
+ let next = content;
15193
+ for (const match of matches2.reverse()) {
15194
+ let start = match.index;
15195
+ let end = start + match[0].length;
15196
+ if (start === 0 && next[end] === "\n")
15197
+ end += 1;
15198
+ else if (end === next.length && next[start - 1] === "\n")
15199
+ start -= 1;
15200
+ next = `${next.slice(0, start)}${next.slice(end)}`;
15201
+ }
15202
+ return next;
15203
+ }
15086
15204
  function createDefaultSlots(definition) {
15087
15205
  return Object.fromEntries(definition.inputSchema.map((slot) => {
15088
15206
  if (slot.defaultValue === void 0)
@@ -15322,7 +15440,8 @@ function createGenerationExecutionOperations(input) {
15322
15440
  if (definitionChanges || Object.keys(editInput).length > 2) {
15323
15441
  operations.push({ input: editInput, name: "editGenerationDraft" });
15324
15442
  }
15325
- const currentReferences = definitionChanges ? [] : readGenerationDraftReferences(input.currentDraft);
15443
+ const promptSlotName = resolvePromptSlot(input.definition)?.name;
15444
+ const currentReferences = (definitionChanges ? [] : readGenerationDraftReferences(input.currentDraft)).filter((reference) => editInput.prompt === void 0 || reference.sourceSlotName !== promptSlotName);
15326
15445
  const desiredKeys = new Set(input.assignments.map((item) => referenceKey(item.reference)));
15327
15446
  for (const current of currentReferences) {
15328
15447
  if (!current.draftInputId || desiredKeys.has(referenceKey(current)))
@@ -15823,7 +15942,14 @@ async function resolveEffectiveTextInputContent(input) {
15823
15942
  if (!literal3)
15824
15943
  return null;
15825
15944
  const fragmentsByBindingId = /* @__PURE__ */ new Map();
15826
- for (const reference of input.references) {
15945
+ const bindingsById = /* @__PURE__ */ new Map();
15946
+ for (const [slotName, references] of Object.entries(input.allReferencesBySlot)) {
15947
+ for (const reference of references) {
15948
+ if (reference.kind !== "literal")
15949
+ bindingsById.set(reference.id, { reference, slotName });
15950
+ }
15951
+ }
15952
+ for (const reference of Object.values(input.allReferencesBySlot).flat()) {
15827
15953
  if (reference.kind !== "view-node" || reference.sourceDataType !== "text")
15828
15954
  continue;
15829
15955
  const content = await input.resolveViewNodeTextContent(reference.viewNodeId);
@@ -15831,8 +15957,17 @@ async function resolveEffectiveTextInputContent(input) {
15831
15957
  fragmentsByBindingId.set(reference.id, content.trim());
15832
15958
  }
15833
15959
  const positionedBindingIds = /* @__PURE__ */ new Set();
15834
- const positionedContent = literal3.content.replace(new RegExp(PROMPT_REFERENCE_TOKEN_PATTERN2, "g"), (token, _contentType, _slotName, _index, bindingId) => {
15960
+ const positionedContent = literal3.content.replace(new RegExp(PROMPT_REFERENCE_TOKEN_PATTERN2, "g"), (token, contentType, slotName, _rawIndex, bindingId, encodedViewNodeId) => {
15835
15961
  if (!bindingId)
15962
+ throw new Error("Prompt contains an At token without a binding identity.");
15963
+ const binding = bindingsById.get(bindingId);
15964
+ if (!binding || binding.slotName !== slotName || binding.reference.sourceDataType !== contentType) {
15965
+ throw new Error(`Prompt At token ${bindingId} does not match its Generation binding.`);
15966
+ }
15967
+ if (encodedViewNodeId && (binding.reference.kind !== "view-node" || binding.reference.viewNodeId !== decodePromptViewNodeId(encodedViewNodeId))) {
15968
+ throw new Error(`Prompt At token ${bindingId} does not match its View Node.`);
15969
+ }
15970
+ if (contentType !== "text")
15836
15971
  return token;
15837
15972
  const content = fragmentsByBindingId.get(bindingId);
15838
15973
  if (content === void 0)
@@ -15841,11 +15976,21 @@ async function resolveEffectiveTextInputContent(input) {
15841
15976
  return content;
15842
15977
  });
15843
15978
  const trailingFragments = [...fragmentsByBindingId.entries()].flatMap(([bindingId, content]) => positionedBindingIds.has(bindingId) ? [] : [content]);
15844
- if (new RegExp(PROMPT_REFERENCE_TOKEN_PATTERN2).test(positionedContent)) {
15979
+ const unresolvedTextToken = [
15980
+ ...positionedContent.matchAll(new RegExp(PROMPT_REFERENCE_TOKEN_PATTERN2, "g"))
15981
+ ].some((match) => match[1] === "text");
15982
+ if (unresolvedTextToken) {
15845
15983
  throw new Error("Prompt contains an unresolved Text View Node reference.");
15846
15984
  }
15847
15985
  return appendUniqueTextFragments(positionedContent, trailingFragments);
15848
15986
  }
15987
+ function decodePromptViewNodeId(value) {
15988
+ try {
15989
+ return decodeURIComponent(value);
15990
+ } catch {
15991
+ throw new Error("Prompt At token contains an invalid View Node identity.");
15992
+ }
15993
+ }
15849
15994
  function appendUniqueTextFragments(base, fragments) {
15850
15995
  let result3 = base;
15851
15996
  for (const fragment of fragments) {
@@ -15865,6 +16010,7 @@ async function prepareGenerationInput(input) {
15865
16010
  for (const slot of input.definition.inputSchema) {
15866
16011
  const references = input.draft.inputs.slots[slot.name] ?? [];
15867
16012
  const effectiveText = slot.name === promptSlot?.name ? await resolveEffectiveTextInputContent({
16013
+ allReferencesBySlot: input.draft.inputs.slots,
15868
16014
  references,
15869
16015
  resolveViewNodeTextContent: input.resolveViewNodeTextContent
15870
16016
  }) : null;
@@ -15981,10 +16127,15 @@ var CoreNodeCreativeLoop = class {
15981
16127
  return this.editDraft(input, timeoutMs);
15982
16128
  if (name === "addVisualReference")
15983
16129
  return this.addVisualReference(input, timeoutMs);
16130
+ if (name === "insertPromptReference")
16131
+ return this.insertPromptReference(input, timeoutMs);
15984
16132
  if (name === "removeVisualReference")
15985
16133
  return this.removeVisualReference(input, timeoutMs);
15986
16134
  if (name === "startGeneration")
15987
16135
  return this.startGeneration(input, timeoutMs);
16136
+ if (name === "reconcileGenerationStart") {
16137
+ return this.reconcileGenerationStart(input, timeoutMs);
16138
+ }
15988
16139
  if (name === "getWorkflowRun")
15989
16140
  return this.getWorkflowRun(input);
15990
16141
  if (name === "waitWorkflowRun")
@@ -16011,6 +16162,18 @@ var CoreNodeCreativeLoop = class {
16011
16162
  for (const id2 of ids)
16012
16163
  await this.recoverWorkflowRun(id2, timeoutMs);
16013
16164
  }
16165
+ observePendingWorkflowRuns(ids, timeoutMs) {
16166
+ for (const id2 of new Set(ids)) {
16167
+ if (!this.isPending(id2))
16168
+ continue;
16169
+ void this.recoverWorkflowRun(id2, timeoutMs).catch((error51) => {
16170
+ if (isSettlementWriteAttempt(error51))
16171
+ this.context.reportBackgroundError(error51);
16172
+ else
16173
+ this.scheduleWorkflowRunRecovery(id2, timeoutMs, error51);
16174
+ });
16175
+ }
16176
+ }
16014
16177
  close() {
16015
16178
  this.closed = true;
16016
16179
  for (const subscription of this.subscriptions.values())
@@ -16160,23 +16323,26 @@ var CoreNodeCreativeLoop = class {
16160
16323
  const draft = requireDraft(target);
16161
16324
  const definition = await this.requireDefinition(draft.definitionId);
16162
16325
  const referenceViewNodeId = requireString3(input.referenceViewNodeId, "referenceViewNodeId");
16163
- const referenceViewNode = this.requireViewNode(referenceViewNodeId);
16164
- if (referenceViewNode.type !== "resource") {
16165
- throw new Error("Generation References require a resource View Node.");
16166
- }
16167
- let sourceDataType = referenceViewNode.contentType ?? "";
16168
- const referenceCoreNodeId = this.resolveViewNodeCoreNodeId(referenceViewNode);
16169
- if (sourceDataType !== "text" || asRecord3(referenceViewNode.data).mode === "content") {
16170
- if (!referenceCoreNodeId)
16171
- throw new Error("Referenced View Node has no current Core Node.");
16172
- const coreNode = await this.requireCoreNode(referenceCoreNodeId);
16173
- sourceDataType = readCoreNodeDataType2(coreNode);
16174
- }
16326
+ const sourceDataType = await this.resolveReferenceDataType(referenceViewNodeId);
16175
16327
  const referenceId = `input_ref_${crypto.randomUUID().replaceAll("-", "")}`;
16176
16328
  const nextDraft = addVisualReferenceToDraft(draft, definition, { id: referenceId, kind: "view-node", viewNodeId: referenceViewNodeId }, sourceDataType, this.context.now());
16177
16329
  await this.replaceDraft(target, nextDraft, "Add Visual Reference", timeoutMs);
16178
16330
  return attention(target.id, { referenceId, viewNodeId: target.id });
16179
16331
  }
16332
+ async insertPromptReference(input, timeoutMs) {
16333
+ assertOnlyKeys3(input, ["placement", "referenceViewNodeId", "viewNodeId"]);
16334
+ const target = this.requireTarget(requireString3(input.viewNodeId, "viewNodeId"));
16335
+ const draft = requireDraft(target);
16336
+ const definition = await this.requireDefinition(draft.definitionId);
16337
+ const referenceViewNodeId = requireString3(input.referenceViewNodeId, "referenceViewNodeId");
16338
+ const sourceDataType = await this.resolveReferenceDataType(referenceViewNodeId);
16339
+ const existing = Object.values(draft.inputs.slots).flat().find((reference) => reference.kind === "view-node" && reference.viewNodeId === referenceViewNodeId);
16340
+ const referenceId = existing?.id ?? `input_ref_${crypto.randomUUID().replaceAll("-", "")}`;
16341
+ const placement = parsePromptReferencePlacement(input.placement);
16342
+ const nextDraft = insertPromptReferenceIntoDraft(draft, definition, { id: referenceId, kind: "view-node", viewNodeId: referenceViewNodeId }, sourceDataType, placement, this.context.now());
16343
+ await this.replaceDraft(target, nextDraft, "Insert Prompt Reference", timeoutMs);
16344
+ return attention(target.id, { placement, referenceId, viewNodeId: target.id });
16345
+ }
16180
16346
  async removeVisualReference(input, timeoutMs) {
16181
16347
  assertOnlyKeys3(input, ["referenceId", "viewNodeId"]);
16182
16348
  const target = this.requireTarget(requireString3(input.viewNodeId, "viewNodeId"));
@@ -16187,7 +16353,8 @@ var CoreNodeCreativeLoop = class {
16187
16353
  }
16188
16354
  async startGeneration(input, timeoutMs) {
16189
16355
  let estimate;
16190
- let runAccepted = false;
16356
+ let acceptedRun;
16357
+ let acceptedTargetId;
16191
16358
  try {
16192
16359
  assertOnlyKeys3(input, ["viewNodeId"]);
16193
16360
  const target = this.requireTarget(requireString3(input.viewNodeId, "viewNodeId"));
@@ -16200,6 +16367,10 @@ var CoreNodeCreativeLoop = class {
16200
16367
  resolveViewNodeCoreNodeId: (id2) => this.resolveViewNodeCoreNodeId(this.requireViewNode(id2)),
16201
16368
  resolveViewNodeTextContent: (id2) => this.resolveViewNodeTextContent(this.requireViewNode(id2))
16202
16369
  });
16370
+ const targetSnapshot = structuredClone(target);
16371
+ const targetData = normalizeOptionalRecord(target.data, "View Node data");
16372
+ const targetHistory = readGenerationHistory(target.history);
16373
+ const effectivePrompt = readPrompt(prepared.effectiveDraft);
16203
16374
  estimate = await this.systems.workflowRuntime.estimateRun({ definitionId: definition.id, inputs: prepared.estimateInputs }, { timeoutMs });
16204
16375
  if (!estimate.canAfford)
16205
16376
  throw new Error("Insufficient credits to start this generation.");
@@ -16218,7 +16389,8 @@ var CoreNodeCreativeLoop = class {
16218
16389
  { slotName: output.slotName, targetId: target.id, targetSlot: draft.id }
16219
16390
  ]
16220
16391
  }, { timeoutMs });
16221
- runAccepted = true;
16392
+ acceptedRun = run;
16393
+ acceptedTargetId = target.id;
16222
16394
  this.runs.set(run.id, run);
16223
16395
  const timestamp = this.context.now();
16224
16396
  const historyEntry = {
@@ -16234,15 +16406,15 @@ var CoreNodeCreativeLoop = class {
16234
16406
  },
16235
16407
  updatedAt: timestamp
16236
16408
  };
16237
- const { creationIntent: _creationIntent, ...data } = asRecord3(target.data);
16409
+ const { creationIntent: _creationIntent, ...data } = targetData;
16238
16410
  const after = {
16239
- ...structuredClone(target),
16411
+ ...targetSnapshot,
16240
16412
  data: {
16241
16413
  ...data,
16242
- ...readPrompt(prepared.effectiveDraft) === null ? {} : { prompt: readPrompt(prepared.effectiveDraft) }
16414
+ ...effectivePrompt === null ? {} : { prompt: effectivePrompt }
16243
16415
  },
16244
16416
  draft: null,
16245
- history: toDocumentJson([...readGenerationHistory(target.history), historyEntry]),
16417
+ history: toDocumentJson([...targetHistory, historyEntry]),
16246
16418
  updatedAt: timestamp
16247
16419
  };
16248
16420
  await this.context.commit({
@@ -16264,8 +16436,15 @@ var CoreNodeCreativeLoop = class {
16264
16436
  usage: estimatedUsage(estimate)
16265
16437
  };
16266
16438
  } catch (error51) {
16267
- const usage = runAccepted && estimate ? estimatedUsage(estimate) : isIndeterminateWrite(error51) ? unknownUsage(estimate) : notConsumedUsage(estimate);
16268
- throw attachCanvasOperationUsage(error51, usage);
16439
+ const failure = acceptedRun && acceptedTargetId ? attachCanvasOperationIndeterminateResult(error51, {
16440
+ canvasAcknowledged: false,
16441
+ reconciliationRequired: true,
16442
+ status: acceptedRun.status,
16443
+ viewNodeId: acceptedTargetId,
16444
+ workflowRunId: acceptedRun.id
16445
+ }) : error51;
16446
+ const usage = acceptedRun && estimate ? estimatedUsage(estimate) : isIndeterminateWrite(error51) ? unknownUsage(estimate) : notConsumedUsage(estimate);
16447
+ throw attachCanvasOperationUsage(failure, usage);
16269
16448
  }
16270
16449
  }
16271
16450
  async getWorkflowRun(input) {
@@ -16273,6 +16452,133 @@ var CoreNodeCreativeLoop = class {
16273
16452
  const run = await this.refreshRun(requireString3(input.workflowRunId, "workflowRunId"), 15e3);
16274
16453
  return { result: { workflowRun: run } };
16275
16454
  }
16455
+ async reconcileGenerationStart(input, timeoutMs) {
16456
+ assertOnlyKeys3(input, ["viewNodeId", "workflowRunId"]);
16457
+ const target = this.requireTarget(requireString3(input.viewNodeId, "viewNodeId"));
16458
+ const workflowRunId = requireString3(input.workflowRunId, "workflowRunId");
16459
+ const run = await this.systems.workflowRuntime.getRun(workflowRunId);
16460
+ if (!run)
16461
+ throw new Error(`Workflow Run ${workflowRunId} does not exist.`);
16462
+ this.recordRun(run);
16463
+ const sync = await this.systems.projectContent.syncProject(this.context.projectId, 0, {
16464
+ timeoutMs
16465
+ });
16466
+ this.projection.merge(sync.contents);
16467
+ const history = readGenerationHistory(target.history);
16468
+ const existing = history.find((entry) => entry.runtime.instanceIds.includes(run.id));
16469
+ if (existing) {
16470
+ if (existing.definitionId !== run.definitionId) {
16471
+ return reconciliationResult(target.id, run.id, "conflict", {
16472
+ reason: "The existing History entry and Workflow Run use different Definitions."
16473
+ });
16474
+ }
16475
+ const repairedPendingState = !isTerminalWorkflowRunStatus(run.status) && !this.isPending(run.id);
16476
+ if (isTerminalWorkflowRunStatus(run.status))
16477
+ await this.settleRun(run, timeoutMs);
16478
+ else if (repairedPendingState) {
16479
+ await this.context.commit({
16480
+ addPendingWorkflowInstanceIds: [run.id],
16481
+ after: [],
16482
+ before: [],
16483
+ history: "barrier",
16484
+ label: "Reconcile generation start",
16485
+ timeoutMs
16486
+ });
16487
+ this.watchRun(run.id, timeoutMs);
16488
+ }
16489
+ return reconciliationResult(target.id, run.id, "consistent", {
16490
+ repairedPendingState,
16491
+ workflowStatus: run.status
16492
+ });
16493
+ }
16494
+ const draft = readGenerationDraft(target.draft);
16495
+ if (!draft) {
16496
+ return reconciliationResult(target.id, run.id, "conflict", {
16497
+ reason: "The target has neither a matching History entry nor its original Draft.",
16498
+ workflowStatus: run.status
16499
+ });
16500
+ }
16501
+ if (draft.definitionId !== run.definitionId) {
16502
+ return reconciliationResult(target.id, run.id, "conflict", {
16503
+ reason: "The target Draft and Workflow Run use different Definitions.",
16504
+ workflowStatus: run.status
16505
+ });
16506
+ }
16507
+ if (!isTerminalWorkflowRunStatus(run.status)) {
16508
+ return reconciliationResult(target.id, run.id, "running-insufficient", {
16509
+ reason: "The running Workflow does not yet expose authoritative target output evidence. Wait for a terminal state; do not start another Run.",
16510
+ workflowStatus: run.status
16511
+ });
16512
+ }
16513
+ if (run.status !== "completed") {
16514
+ return reconciliationResult(target.id, run.id, "terminal", {
16515
+ reason: "The Workflow ended without a completed output, so no History entry was invented.",
16516
+ workflowStatus: run.status
16517
+ });
16518
+ }
16519
+ const submittedAt = parseWorkflowTimestamp(run.createdAt);
16520
+ if (submittedAt === null) {
16521
+ return reconciliationResult(target.id, run.id, "insufficient", {
16522
+ reason: "The completed Workflow Run has no trustworthy submission timestamp.",
16523
+ workflowStatus: run.status
16524
+ });
16525
+ }
16526
+ if ((draft.updatedAt ?? draft.createdAt) > submittedAt) {
16527
+ return reconciliationResult(target.id, run.id, "conflict", {
16528
+ reason: "The target Draft changed after the Workflow Run was submitted.",
16529
+ workflowStatus: run.status
16530
+ });
16531
+ }
16532
+ const definition = await this.requireDefinition(run.definitionId);
16533
+ const primaryOutput = requirePrimaryOutput(definition, target.contentType);
16534
+ const targetOutputs = sync.contents.filter((content) => content.sourceId === run.id || content.workflowInstanceId === run.id).flatMap((content) => content.outputs).filter((output) => output.targetId === target.id && output.targetSlot === draft.id && output.slotName === primaryOutput.slotName);
16535
+ if (targetOutputs.length === 0) {
16536
+ return reconciliationResult(target.id, run.id, "insufficient", {
16537
+ reason: "The completed Workflow Run has no Project Content output bound to this target and Draft.",
16538
+ workflowStatus: run.status
16539
+ });
16540
+ }
16541
+ const outputCoreNodeIds = new Set(run.outputs.map((output) => output.coreNodeId));
16542
+ if (outputCoreNodeIds.size > 0 && targetOutputs.every((output) => !outputCoreNodeIds.has(output.coreNodeId))) {
16543
+ return reconciliationResult(target.id, run.id, "conflict", {
16544
+ reason: "Workflow and Project Content outputs disagree.",
16545
+ workflowStatus: run.status
16546
+ });
16547
+ }
16548
+ const timestamp = this.context.now();
16549
+ const historyEntry = {
16550
+ ...draft,
16551
+ runtime: {
16552
+ ...run.definitionVersion === void 0 ? {} : { expectedDefinitionVersion: run.definitionVersion },
16553
+ instanceIds: [run.id],
16554
+ materializedInputs: inferReconciledMaterializedInputs(draft, run),
16555
+ originClientId: "unknown",
16556
+ submittedAt
16557
+ },
16558
+ updatedAt: timestamp
16559
+ };
16560
+ const { creationIntent: _creationIntent, ...data } = normalizeOptionalRecord(target.data, "View Node data");
16561
+ const prompt = readPrompt(draft);
16562
+ const after = {
16563
+ ...structuredClone(target),
16564
+ data: { ...data, ...prompt === null ? {} : { prompt } },
16565
+ draft: null,
16566
+ history: toDocumentJson([...history, historyEntry]),
16567
+ updatedAt: timestamp
16568
+ };
16569
+ await this.context.commit({
16570
+ after: [after],
16571
+ before: [target],
16572
+ history: "barrier",
16573
+ label: "Reconcile generation start",
16574
+ ...this.isPending(run.id) ? { removePendingWorkflowInstanceIds: [run.id] } : {},
16575
+ timeoutMs
16576
+ });
16577
+ return reconciliationResult(target.id, run.id, "repaired", {
16578
+ outputCoreNodeIds: [...new Set(targetOutputs.map((output) => output.coreNodeId))],
16579
+ workflowStatus: run.status
16580
+ });
16581
+ }
16276
16582
  async waitWorkflowRun(input, timeoutMs) {
16277
16583
  assertOnlyKeys3(input, ["workflowRunId"]);
16278
16584
  const id2 = requireString3(input.workflowRunId, "workflowRunId");
@@ -16479,6 +16785,21 @@ var CoreNodeCreativeLoop = class {
16479
16785
  this.definitions.set(id2, definition);
16480
16786
  return definition;
16481
16787
  }
16788
+ async resolveReferenceDataType(referenceViewNodeId) {
16789
+ const referenceViewNode = this.requireViewNode(referenceViewNodeId);
16790
+ if (referenceViewNode.type !== "resource") {
16791
+ throw new Error("Generation References require a resource View Node.");
16792
+ }
16793
+ let sourceDataType = referenceViewNode.contentType ?? "";
16794
+ const referenceCoreNodeId = this.resolveViewNodeCoreNodeId(referenceViewNode);
16795
+ if (sourceDataType !== "text" || asRecord3(referenceViewNode.data).mode === "content") {
16796
+ if (!referenceCoreNodeId)
16797
+ throw new Error("Referenced View Node has no current Core Node.");
16798
+ const coreNode = await this.requireCoreNode(referenceCoreNodeId);
16799
+ sourceDataType = readCoreNodeDataType2(coreNode);
16800
+ }
16801
+ return sourceDataType;
16802
+ }
16482
16803
  requireTarget(id2) {
16483
16804
  const viewNode = this.requireViewNode(id2);
16484
16805
  requireSupportedContentType(viewNode.contentType);
@@ -16524,8 +16845,6 @@ var CoreNodeCreativeLoop = class {
16524
16845
  data: {
16525
16846
  ...contentType === "text" ? { mode: "content" } : {},
16526
16847
  ...data,
16527
- creator_id: this.context.actor.id,
16528
- creator_name: this.context.actor.displayName,
16529
16848
  ...name ? { label: name } : {}
16530
16849
  },
16531
16850
  draft: null,
@@ -16563,6 +16882,14 @@ function createEstimatedCompletionTimes(estimate, submittedAt) {
16563
16882
  function attention(viewNodeId, result3) {
16564
16883
  return { attention: { kind: "view-nodes", viewNodeIds: [viewNodeId] }, result: result3 };
16565
16884
  }
16885
+ function reconciliationResult(viewNodeId, workflowRunId, reconciliationStatus, detail) {
16886
+ return attention(viewNodeId, {
16887
+ ...detail,
16888
+ reconciliationStatus,
16889
+ viewNodeId,
16890
+ workflowRunId
16891
+ });
16892
+ }
16566
16893
  function requireDraft(viewNode) {
16567
16894
  const draft = readGenerationDraft(viewNode.draft);
16568
16895
  if (!draft)
@@ -16588,6 +16915,38 @@ function readPrompt(draft) {
16588
16915
  }
16589
16916
  return null;
16590
16917
  }
16918
+ function inferReconciledMaterializedInputs(draft, run) {
16919
+ const materialized = {};
16920
+ for (const [slotName, bindings] of Object.entries(draft.inputs.slots)) {
16921
+ const references = bindings.filter((binding) => binding.kind !== "literal");
16922
+ if (references.length === 0)
16923
+ continue;
16924
+ const input = run.inputs[slotName];
16925
+ if (typeof input !== "object" || input === null || Array.isArray(input) || !("coreNodeIds" in input) || !Array.isArray(input.coreNodeIds)) {
16926
+ continue;
16927
+ }
16928
+ const coreNodeIds = input.coreNodeIds.filter((coreNodeId) => typeof coreNodeId === "string" && Boolean(coreNodeId));
16929
+ if (references.length === 1) {
16930
+ const reference = references[0];
16931
+ if (reference)
16932
+ materialized[slotName] = [{ coreNodeIds, inputRefId: reference.id }];
16933
+ continue;
16934
+ }
16935
+ if (references.length !== coreNodeIds.length)
16936
+ continue;
16937
+ materialized[slotName] = references.map((reference, index) => ({
16938
+ coreNodeIds: [coreNodeIds[index]],
16939
+ inputRefId: reference.id
16940
+ }));
16941
+ }
16942
+ return materialized;
16943
+ }
16944
+ function parseWorkflowTimestamp(value) {
16945
+ if (!value)
16946
+ return null;
16947
+ const timestamp = Date.parse(value);
16948
+ return Number.isFinite(timestamp) ? timestamp : null;
16949
+ }
16591
16950
  function parseConfig2(value) {
16592
16951
  const record2 = asRecord3(value);
16593
16952
  for (const item of Object.values(record2)) {
@@ -16616,11 +16975,51 @@ function asRecord3(value) {
16616
16975
  }
16617
16976
  return value;
16618
16977
  }
16978
+ function normalizeOptionalRecord(value, field) {
16979
+ if (value === void 0 || value === null)
16980
+ return {};
16981
+ if (typeof value !== "object" || Array.isArray(value)) {
16982
+ throw new Error(`${field} must be an object when present.`);
16983
+ }
16984
+ return structuredClone(value);
16985
+ }
16619
16986
  function requireString3(value, field) {
16620
16987
  if (typeof value !== "string" || !value.trim())
16621
16988
  throw new Error(`${field} must be non-empty.`);
16622
16989
  return value.trim();
16623
16990
  }
16991
+ function parsePromptReferencePlacement(value) {
16992
+ if (value === void 0 || value === "end")
16993
+ return "end";
16994
+ if (value === "start")
16995
+ return "start";
16996
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
16997
+ throw new Error("placement must be start, end, or an anchor placement object.");
16998
+ }
16999
+ const placement = value;
17000
+ assertOnlyKeys3(placement, ["anchor", "kind"]);
17001
+ if (placement.kind !== "before" && placement.kind !== "after") {
17002
+ throw new Error("placement.kind must be before or after.");
17003
+ }
17004
+ if (typeof placement.anchor !== "object" || placement.anchor === null || Array.isArray(placement.anchor)) {
17005
+ throw new Error("placement.anchor must be an object.");
17006
+ }
17007
+ const anchor = placement.anchor;
17008
+ assertOnlyKeys3(anchor, ["occurrence", "text"]);
17009
+ const text = requirePlainString2(anchor.text, "placement.anchor.text");
17010
+ if (!text.trim())
17011
+ throw new Error("placement.anchor.text must be non-empty.");
17012
+ if (anchor.occurrence !== void 0 && (!Number.isInteger(anchor.occurrence) || anchor.occurrence < 1)) {
17013
+ throw new Error("placement.anchor.occurrence must be a positive integer.");
17014
+ }
17015
+ return {
17016
+ anchor: {
17017
+ ...anchor.occurrence === void 0 ? {} : { occurrence: anchor.occurrence },
17018
+ text
17019
+ },
17020
+ kind: placement.kind
17021
+ };
17022
+ }
16624
17023
  function requirePlainString2(value, field) {
16625
17024
  if (typeof value !== "string")
16626
17025
  throw new Error(`${field} must be a string.`);
@@ -16775,8 +17174,6 @@ var ExternalMediaImporter = class {
16775
17174
  data: {
16776
17175
  contentBindingError: null,
16777
17176
  contentBindingStatus: "pending",
16778
- creator_id: this.context.actor.id,
16779
- creator_name: this.context.actor.displayName,
16780
17177
  [IMPORT_INTENT_DATA_KEY]: structuredClone(intent),
16781
17178
  label: intent.name
16782
17179
  },
@@ -17086,9 +17483,12 @@ var MediaUploader = class {
17086
17483
  this.systems = systems;
17087
17484
  this.projection = systems.projectContentProjection;
17088
17485
  }
17089
- async execute(input, timeoutMs) {
17486
+ async execute(input, timeoutMs, onProgress) {
17090
17487
  const request = normalizeUploadRequest(input);
17091
- const prepared = await this.systems.mediaUpload.prepare(request.source);
17488
+ const safeProgress = safeProgressListener(onProgress);
17489
+ const prepared = await this.systems.mediaUpload.prepare(request.source, {
17490
+ onProgress: safeProgress
17491
+ });
17092
17492
  try {
17093
17493
  const descriptor2 = requireValidDescriptor(prepared.descriptor);
17094
17494
  const intent = await createIntent(request, descriptor2);
@@ -17122,9 +17522,10 @@ var MediaUploader = class {
17122
17522
  let uploaded = false;
17123
17523
  let coreNodeId;
17124
17524
  try {
17125
- const object2 = await prepared.upload({ timeoutMs });
17525
+ const object2 = await prepared.upload({ onProgress: safeProgress, timeoutMs });
17126
17526
  uploaded = true;
17127
17527
  const fileUrl = requireUploadedMediaUrl(object2.url);
17528
+ await safeProgress({ operation: "uploadMedia", stage: "creating-content" });
17128
17529
  const completion = await this.systems.projectContent.completeMediaUploads(this.context.projectId, [
17129
17530
  {
17130
17531
  asset: {
@@ -17158,6 +17559,7 @@ var MediaUploader = class {
17158
17559
  await this.updateBindingStatus(viewNode, "failed", errorMessage2(error51), timeoutMs);
17159
17560
  throw error51;
17160
17561
  }
17562
+ await safeProgress({ operation: "uploadMedia", stage: "updating-canvas" });
17161
17563
  await this.updateBindingStatus(viewNode, "ready", null, timeoutMs);
17162
17564
  return result2(viewNode.id, coreNodeId, intent, true, uploaded);
17163
17565
  } finally {
@@ -17201,8 +17603,6 @@ var MediaUploader = class {
17201
17603
  data: {
17202
17604
  contentBindingError: null,
17203
17605
  contentBindingStatus: "pending",
17204
- creator_id: this.context.actor.id,
17205
- creator_name: this.context.actor.displayName,
17206
17606
  label: intent.name,
17207
17607
  [UPLOAD_INTENT_DATA_KEY]: structuredClone(intent)
17208
17608
  },
@@ -17263,6 +17663,14 @@ var MediaUploader = class {
17263
17663
  return after;
17264
17664
  }
17265
17665
  };
17666
+ function safeProgressListener(listener) {
17667
+ return async (progress) => {
17668
+ try {
17669
+ await listener?.(progress);
17670
+ } catch {
17671
+ }
17672
+ };
17673
+ }
17266
17674
  var MediaUploadIndeterminateError = class extends Error {
17267
17675
  indeterminate = true;
17268
17676
  timedOut = false;
@@ -17991,6 +18399,7 @@ var SemanticCanvasRuntime = class {
17991
18399
  unsubscribeRemote;
17992
18400
  commands;
17993
18401
  contentProjection;
18402
+ creationProvenanceSuffix;
17994
18403
  creativeLoop;
17995
18404
  externalMediaImporter;
17996
18405
  mediaUploader;
@@ -18000,12 +18409,12 @@ var SemanticCanvasRuntime = class {
18000
18409
  constructor(options) {
18001
18410
  this.options = options;
18002
18411
  this.now = options.now ?? Date.now;
18412
+ this.creationProvenanceSuffix = normalizeCreationProvenanceSuffix(options.clientName);
18003
18413
  this.commands = createProjectCanvasCommandExecutor(options.document, this.now);
18004
18414
  this.contentProjection = new ProjectContentProjection(options.projectContents);
18005
18415
  this.projectionRevision = options.initialRevision ?? 1;
18006
18416
  this.snapshot = options.document.getSnapshot();
18007
18417
  const operationContext = {
18008
- actor: options.actor,
18009
18418
  canWrite: options.role !== "viewer",
18010
18419
  clientInstanceId: options.clientInstanceId ?? "unknown",
18011
18420
  commit: async (input) => {
@@ -18031,6 +18440,8 @@ var SemanticCanvasRuntime = class {
18031
18440
  this.unsubscribeRemote = options.document.subscribeRemoteChange(() => {
18032
18441
  const previous = this.snapshot;
18033
18442
  this.snapshot = options.document.getSnapshot();
18443
+ const previousPending = new Set(previous.pendingWorkflowInstanceIds);
18444
+ const addedPendingWorkflowInstanceIds = this.snapshot.pendingWorkflowInstanceIds.filter((id2) => !previousPending.has(id2));
18034
18445
  const viewNodeChanges = diffSemanticViewNodes(previous.viewNodes, this.snapshot.viewNodes);
18035
18446
  this.projectionRevision += 1;
18036
18447
  this.emit({
@@ -18042,6 +18453,9 @@ var SemanticCanvasRuntime = class {
18042
18453
  revision: this.projectionRevision
18043
18454
  });
18044
18455
  this.scheduleProjectContentRefresh();
18456
+ if (addedPendingWorkflowInstanceIds.length > 0) {
18457
+ this.creativeLoop?.observePendingWorkflowRuns(addedPendingWorkflowInstanceIds, 15e3);
18458
+ }
18045
18459
  });
18046
18460
  }
18047
18461
  get revision() {
@@ -18124,7 +18538,7 @@ var SemanticCanvasRuntime = class {
18124
18538
  this.listeners.add(listener);
18125
18539
  return () => this.listeners.delete(listener);
18126
18540
  }
18127
- async execute(name, input, timeoutMs) {
18541
+ async execute(name, input, timeoutMs, options = {}) {
18128
18542
  this.requireCapability(name);
18129
18543
  if (name === "importExternalMedia") {
18130
18544
  if (!this.externalMediaImporter)
@@ -18135,7 +18549,7 @@ var SemanticCanvasRuntime = class {
18135
18549
  if (name === "uploadMedia") {
18136
18550
  if (!this.mediaUploader)
18137
18551
  throw new Error("Media upload system is unavailable.");
18138
- const result3 = await this.mediaUploader.execute(input, timeoutMs);
18552
+ const result3 = await this.mediaUploader.execute(input, timeoutMs, options.onProgress);
18139
18553
  return { ...result3, revision: this.projectionRevision };
18140
18554
  }
18141
18555
  if (isCreativeLoopOperation(name)) {
@@ -18342,15 +18756,18 @@ var SemanticCanvasRuntime = class {
18342
18756
  };
18343
18757
  }
18344
18758
  async commit(entry, timeoutMs) {
18345
- await this.applyHistoryState(entry.after, entry.viewNodeIds, entry.label, timeoutMs);
18346
- this.undoStack.push({ ...entry, kind: "reversible" });
18759
+ const after = this.stampCreatedViewNodes(entry.after, entry.before);
18760
+ await this.applyHistoryState(after, entry.viewNodeIds, entry.label, timeoutMs);
18761
+ this.undoStack.push({ ...entry, after, kind: "reversible" });
18347
18762
  this.redoStack.length = 0;
18348
18763
  }
18349
18764
  async applyOperationPlan(plan, timeoutMs) {
18350
18765
  if (plan.changes.length > 0) {
18766
+ const before = plan.changes.map((change) => change.before);
18767
+ const after = this.stampCreatedViewNodes(plan.changes.map((change) => change.after), before);
18351
18768
  const entry = {
18352
- after: plan.changes.map((change) => change.after),
18353
- before: plan.changes.map((change) => change.before),
18769
+ after,
18770
+ before,
18354
18771
  kind: "reversible",
18355
18772
  label: plan.label,
18356
18773
  viewNodeIds: plan.changes.map((change) => change.viewNodeId)
@@ -18387,16 +18804,23 @@ var SemanticCanvasRuntime = class {
18387
18804
  });
18388
18805
  }
18389
18806
  async applySemanticChanges(changes, label, timeoutMs, history, addPendingWorkflowInstanceIds, removePendingWorkflowInstanceIds) {
18807
+ const preparedChanges = changes.map((change) => ({
18808
+ ...change,
18809
+ after: change.before === null ? this.stampCreatedViewNode(change.after) : structuredClone(change.after)
18810
+ }));
18390
18811
  const previous = this.snapshot;
18391
18812
  this.snapshot = await this.commands.changeViewNodes({
18392
18813
  ...addPendingWorkflowInstanceIds ? { addPendingWorkflowInstanceIds } : {},
18393
- changes: changes.map((change) => ({ state: change.after, viewNodeId: change.viewNodeId })),
18814
+ changes: preparedChanges.map((change) => ({
18815
+ state: change.after,
18816
+ viewNodeId: change.viewNodeId
18817
+ })),
18394
18818
  operationName: label,
18395
18819
  ...removePendingWorkflowInstanceIds ? { removePendingWorkflowInstanceIds } : {},
18396
18820
  timeoutMs
18397
18821
  });
18398
18822
  this.projectionRevision += 1;
18399
- const viewNodeIds = changes.map((change) => change.viewNodeId);
18823
+ const viewNodeIds = preparedChanges.map((change) => change.viewNodeId);
18400
18824
  const viewNodeChanges = diffSemanticViewNodes(previous.viewNodes, this.snapshot.viewNodes, viewNodeIds);
18401
18825
  this.emit({
18402
18826
  changedViewNodeIds: viewNodeIds,
@@ -18406,8 +18830,8 @@ var SemanticCanvasRuntime = class {
18406
18830
  });
18407
18831
  if (history === "undoable") {
18408
18832
  this.undoStack.push({
18409
- after: changes.map((change) => change.after),
18410
- before: changes.map((change) => change.before),
18833
+ after: preparedChanges.map((change) => change.after),
18834
+ before: preparedChanges.map((change) => change.before),
18411
18835
  kind: "reversible",
18412
18836
  label,
18413
18837
  viewNodeIds
@@ -18418,6 +18842,22 @@ var SemanticCanvasRuntime = class {
18418
18842
  this.redoStack.length = 0;
18419
18843
  }
18420
18844
  }
18845
+ stampCreatedViewNodes(after, before) {
18846
+ return after.map((viewNode, index) => viewNode !== null && before[index] === null ? this.stampCreatedViewNode(viewNode) : structuredClone(viewNode));
18847
+ }
18848
+ stampCreatedViewNode(viewNode) {
18849
+ if (!this.creationProvenanceSuffix)
18850
+ return structuredClone(viewNode);
18851
+ const data = typeof viewNode.data === "object" && viewNode.data !== null && !Array.isArray(viewNode.data) ? structuredClone(viewNode.data) : {};
18852
+ delete data.creator_id;
18853
+ delete data.creator_name;
18854
+ data.createdByCli = this.creationProvenanceSuffix;
18855
+ if (viewNode.type === "note") {
18856
+ data.creator_id = this.options.actor.id;
18857
+ data.creator_name = `${this.options.actor.displayName} (${this.creationProvenanceSuffix})`;
18858
+ }
18859
+ return { ...structuredClone(viewNode), data };
18860
+ }
18421
18861
  nextNotePosition() {
18422
18862
  const count = this.snapshot.viewNodes.length;
18423
18863
  return { x: 160 + count % 4 * 348, y: 160 + Math.floor(count / 4) * 348 };
@@ -18472,7 +18912,7 @@ function duplicateInputContainsResource(snapshot, input) {
18472
18912
  return snapshot.viewNodes.some((viewNode) => ids.has(viewNode.id) && viewNode.type === "resource");
18473
18913
  }
18474
18914
  function isCreativeLoopOperation(name) {
18475
- return name === "getCoreNode" || name === "placeCoreNode" || name === "createGenerationTarget" || name === "createMediaTarget" || name === "planGeneration" || name === "editGenerationDraft" || name === "addVisualReference" || name === "removeVisualReference" || name === "startGeneration" || name === "getWorkflowRun" || name === "waitWorkflowRun";
18915
+ return name === "getCoreNode" || name === "placeCoreNode" || name === "createGenerationTarget" || name === "createMediaTarget" || name === "planGeneration" || name === "editGenerationDraft" || name === "addVisualReference" || name === "insertPromptReference" || name === "removeVisualReference" || name === "startGeneration" || name === "reconcileGenerationStart" || name === "getWorkflowRun" || name === "waitWorkflowRun";
18476
18916
  }
18477
18917
  function createViewNodeId() {
18478
18918
  return `view_node_${crypto.randomUUID().replaceAll("-", "")}`;
@@ -18510,6 +18950,17 @@ function requireString6(value, field) {
18510
18950
  throw new Error(`${field} must be a string.`);
18511
18951
  return value;
18512
18952
  }
18953
+ function normalizeCreationProvenanceSuffix(value) {
18954
+ if (value === void 0)
18955
+ return null;
18956
+ const normalized = value.trim();
18957
+ if (!normalized)
18958
+ throw new Error("clientName must be non-empty when provided.");
18959
+ if (normalized.includes("(") || normalized.includes(")")) {
18960
+ throw new Error("clientName must not contain parentheses.");
18961
+ }
18962
+ return normalized;
18963
+ }
18513
18964
 
18514
18965
  // src/integrations/backend.ts
18515
18966
  var BackendRequestError = class extends Error {
@@ -23176,10 +23627,12 @@ var HASH_BUFFER_BYTES = 256 * 1024;
23176
23627
  function createNodeCanvasMediaUploadSystem(config2, cwd = process.cwd()) {
23177
23628
  const backend = new Az8BackendClient(config2);
23178
23629
  return {
23179
- async prepare(sourceReference) {
23630
+ async prepare(sourceReference, options) {
23631
+ const report = createProgressReporter(options?.onProgress);
23632
+ await report({ bytesCompleted: 0, operation: "uploadMedia", stage: "inspecting" });
23180
23633
  const source = await openUploadSource(sourceReference, cwd);
23181
23634
  try {
23182
- const descriptor2 = await inspectUploadSource(source.handle, source.absolutePath);
23635
+ const descriptor2 = await inspectUploadSource(source.handle, source.absolutePath, report);
23183
23636
  return createPreparedUpload(config2, backend, source.handle, source.absolutePath, descriptor2);
23184
23637
  } catch (error51) {
23185
23638
  await source.handle.close().catch(() => void 0);
@@ -23213,7 +23666,7 @@ async function openUploadSource(sourceReference, cwd) {
23213
23666
  }
23214
23667
  return { absolutePath, handle };
23215
23668
  }
23216
- async function inspectUploadSource(handle, absolutePath) {
23669
+ async function inspectUploadSource(handle, absolutePath, report) {
23217
23670
  const before = await handle.stat();
23218
23671
  if (!Number.isSafeInteger(before.size) || before.size < 1) {
23219
23672
  throw new Error("Media upload source must contain at least one byte.");
@@ -23232,6 +23685,7 @@ async function inspectUploadSource(handle, absolutePath) {
23232
23685
  chunk2.copy(header, bytes, 0, Math.min(chunk2.byteLength, header.byteLength - bytes));
23233
23686
  }
23234
23687
  bytes += bytesRead;
23688
+ await report(uploadProgress("inspecting", bytes, before.size));
23235
23689
  }
23236
23690
  const after = await handle.stat();
23237
23691
  if (after.size !== before.size || after.mtimeMs !== before.mtimeMs) {
@@ -23268,6 +23722,10 @@ function createPreparedUpload(config2, backend, handle, absolutePath, descriptor
23268
23722
  if (closed) throw new Error("Prepared media upload is already closed.");
23269
23723
  if (attempted) throw new Error("Prepared media upload can only be attempted once.");
23270
23724
  attempted = true;
23725
+ await reportProgress(options?.onProgress, {
23726
+ operation: "uploadMedia",
23727
+ stage: "requesting-upload"
23728
+ });
23271
23729
  const presign = await backend.request("core_nodes/upload/presign", {
23272
23730
  json: { format: descriptor2.extension, type: descriptor2.mediaType },
23273
23731
  method: "POST",
@@ -23276,12 +23734,20 @@ function createPreparedUpload(config2, backend, handle, absolutePath, descriptor
23276
23734
  });
23277
23735
  const uploadUrl = requireHttpUrl(presign.upload_url, "upload");
23278
23736
  const fileUrl = requireHttpUrl(presign.file_url, "file");
23279
- await putPreparedFile(config2, handle, absolutePath, descriptor2, uploadUrl, options?.timeoutMs);
23737
+ await putPreparedFile(
23738
+ config2,
23739
+ handle,
23740
+ absolutePath,
23741
+ descriptor2,
23742
+ uploadUrl,
23743
+ options?.timeoutMs,
23744
+ createProgressReporter(options?.onProgress)
23745
+ );
23280
23746
  return { url: fileUrl };
23281
23747
  }
23282
23748
  };
23283
23749
  }
23284
- async function putPreparedFile(config2, handle, absolutePath, descriptor2, uploadUrl, timeoutMs) {
23750
+ async function putPreparedFile(config2, handle, absolutePath, descriptor2, uploadUrl, timeoutMs, report) {
23285
23751
  const originalStat = await handle.stat();
23286
23752
  const uploadHandle = await open(absolutePath, constants.O_RDONLY | constants.O_NOFOLLOW);
23287
23753
  let uploadStat;
@@ -23302,11 +23768,15 @@ async function putPreparedFile(config2, handle, absolutePath, descriptor2, uploa
23302
23768
  );
23303
23769
  const hash2 = createHash("sha256");
23304
23770
  let bytes = 0;
23771
+ await report(uploadProgress("uploading", 0, descriptor2.bytes));
23305
23772
  const meter = new Transform({
23306
23773
  transform(chunk2, _encoding, callback) {
23307
23774
  hash2.update(chunk2);
23308
23775
  bytes += chunk2.byteLength;
23309
- callback(null, chunk2);
23776
+ Promise.resolve(report(uploadProgress("uploading", bytes, descriptor2.bytes))).then(
23777
+ () => callback(null, chunk2),
23778
+ callback
23779
+ );
23310
23780
  }
23311
23781
  });
23312
23782
  const source = uploadHandle.createReadStream({
@@ -23342,6 +23812,35 @@ async function putPreparedFile(config2, handle, absolutePath, descriptor2, uploa
23342
23812
  throw new Error("Media upload source changed before the upload completed.");
23343
23813
  }
23344
23814
  }
23815
+ function createProgressReporter(listener) {
23816
+ let lastStage = "";
23817
+ let lastReportedAt = 0;
23818
+ return async (progress) => {
23819
+ if (!listener) return;
23820
+ const now = Date.now();
23821
+ const complete = progress.bytesTotal !== void 0 && progress.bytesCompleted === progress.bytesTotal;
23822
+ const stageChanged = progress.stage !== lastStage;
23823
+ if (!stageChanged && !complete && now - lastReportedAt < 500) return;
23824
+ lastStage = progress.stage;
23825
+ lastReportedAt = now;
23826
+ await reportProgress(listener, progress);
23827
+ };
23828
+ }
23829
+ async function reportProgress(listener, progress) {
23830
+ try {
23831
+ await listener?.(progress);
23832
+ } catch {
23833
+ }
23834
+ }
23835
+ function uploadProgress(stage, bytesCompleted, bytesTotal) {
23836
+ return {
23837
+ bytesCompleted,
23838
+ bytesTotal,
23839
+ operation: "uploadMedia",
23840
+ percent: Math.min(100, Math.round(bytesCompleted / bytesTotal * 1e3) / 10),
23841
+ stage
23842
+ };
23843
+ }
23345
23844
  function requireHttpUrl(value, kind) {
23346
23845
  if (typeof value !== "string" || !value.trim()) {
23347
23846
  throw new Error(`Media upload presign returned no ${kind} URL.`);
@@ -23410,8 +23909,19 @@ var COLLABORATION_SESSION_STATES = [
23410
23909
  ];
23411
23910
 
23412
23911
  // src/canvas/watch/interest-registry.ts
23912
+ var SYSTEM_WORKFLOW_TERMINAL_INTEREST_KEY = "system.workflow-terminal";
23413
23913
  var CollaborationInterestRegistry = class {
23414
23914
  interests = /* @__PURE__ */ new Map();
23915
+ constructor(includeSystemWorkflowTerminal = false) {
23916
+ if (includeSystemWorkflowTerminal) {
23917
+ this.interests.set(SYSTEM_WORKFLOW_TERMINAL_INTEREST_KEY, {
23918
+ filter: { terminalOnly: true },
23919
+ key: SYSTEM_WORKFLOW_TERMINAL_INTEREST_KEY,
23920
+ kind: "workflow",
23921
+ system: true
23922
+ });
23923
+ }
23924
+ }
23415
23925
  patch(input) {
23416
23926
  const upserts = input.upsert ?? [];
23417
23927
  const removals = input.remove ?? [];
@@ -23421,6 +23931,9 @@ var CollaborationInterestRegistry = class {
23421
23931
  for (const entry of upserts) {
23422
23932
  const key = requireKey(entry.key);
23423
23933
  const current = next.get(key);
23934
+ if (current?.system) {
23935
+ throw interestError("system-interest-readonly", `System Interest ${key} cannot be changed.`);
23936
+ }
23424
23937
  if (entry.patch.kind !== void 0 && !isInterestKind(entry.patch.kind)) {
23425
23938
  throw interestError(
23426
23939
  "interest-kind-invalid",
@@ -23441,6 +23954,9 @@ var CollaborationInterestRegistry = class {
23441
23954
  }
23442
23955
  for (const rawKey of removals) {
23443
23956
  const key = requireKey(rawKey);
23957
+ if (next.get(key)?.system) {
23958
+ throw interestError("system-interest-readonly", `System Interest ${key} cannot be removed.`);
23959
+ }
23444
23960
  if (next.delete(key)) changedKeys.push(key);
23445
23961
  }
23446
23962
  this.interests.clear();
@@ -23646,6 +24162,7 @@ function projectWorkflowRun(run, observedAt) {
23646
24162
  status: run.status,
23647
24163
  summary: {
23648
24164
  definitionId: run.definitionId,
24165
+ workflowRunId: run.id,
23649
24166
  ...run.error ? {
23650
24167
  error: {
23651
24168
  ...run.error.code ? { code: run.error.code } : {},
@@ -23655,6 +24172,7 @@ function projectWorkflowRun(run, observedAt) {
23655
24172
  ...run.progress === void 0 ? {} : { progress: run.progress },
23656
24173
  ...run.outputs.length > 0 ? {
23657
24174
  outputs: run.outputs.map((output) => ({
24175
+ coreNodeId: output.coreNodeId,
23658
24176
  role: output.role,
23659
24177
  slotName: output.slotName,
23660
24178
  ...output.viewNodeId ? { viewNodeId: output.viewNodeId } : {}
@@ -23709,16 +24227,18 @@ function safeWorkflowMessage(value) {
23709
24227
 
23710
24228
  // src/canvas/watch/watch.ts
23711
24229
  var AgentCollaborationWatch = class {
23712
- interests = new CollaborationInterestRegistry();
24230
+ interests;
23713
24231
  maxChangesPerDelivery;
23714
24232
  maxPendingChanges;
23715
24233
  maxRecentResults;
23716
24234
  now;
23717
24235
  pending = [];
23718
24236
  recent = [];
24237
+ workflowWatermarks = /* @__PURE__ */ new Map();
23719
24238
  activeWait = null;
23720
24239
  continuityLost = false;
23721
24240
  constructor(options = {}) {
24241
+ this.interests = new CollaborationInterestRegistry(options.includeSystemWorkflowTerminal);
23722
24242
  this.maxChangesPerDelivery = positive(
23723
24243
  options.maxChangesPerDelivery ?? 64,
23724
24244
  "maxChangesPerDelivery"
@@ -23728,7 +24248,13 @@ var AgentCollaborationWatch = class {
23728
24248
  this.now = options.now ?? Date.now;
23729
24249
  }
23730
24250
  patchInterests(input) {
23731
- return this.interests.patch(input);
24251
+ const result3 = this.interests.patch(input);
24252
+ for (const key of result3.changedKeys) {
24253
+ for (const watermarkKey of this.workflowWatermarks.keys()) {
24254
+ if (watermarkKey.startsWith(`${key}\0`)) this.workflowWatermarks.delete(watermarkKey);
24255
+ }
24256
+ }
24257
+ return result3;
23732
24258
  }
23733
24259
  getInterests() {
23734
24260
  return this.interests.getAll();
@@ -23752,10 +24278,24 @@ var AgentCollaborationWatch = class {
23752
24278
  recordMany(facts) {
23753
24279
  if (this.continuityLost) return;
23754
24280
  for (const fact of facts) {
23755
- for (const interest of this.interests.matching(fact)) this.enqueue(interest.key, fact);
24281
+ for (const interest of this.interests.matching(fact)) {
24282
+ this.recordWorkflowWatermark(interest.key, fact);
24283
+ this.enqueue(interest.key, fact);
24284
+ }
23756
24285
  }
23757
24286
  if (this.activeWait && (this.pending.length > 0 || this.continuityLost)) this.deliverActive();
23758
24287
  }
24288
+ recordAuthoritativeWorkflow(fact) {
24289
+ if (fact.domain !== "workflow" || this.continuityLost) return;
24290
+ for (const interest of this.interests.matching(fact)) {
24291
+ const key = workflowWatermarkKey(interest.key, fact.ref);
24292
+ const fingerprint = workflowFingerprint(fact);
24293
+ if (this.workflowWatermarks.get(key) === fingerprint) continue;
24294
+ this.workflowWatermarks.set(key, fingerprint);
24295
+ this.enqueue(interest.key, fact);
24296
+ }
24297
+ if (this.activeWait && this.pending.length > 0) this.deliverActive();
24298
+ }
23759
24299
  wait(options) {
23760
24300
  const timeoutMs = positive(options.timeoutMs, "timeoutMs");
23761
24301
  if (this.activeWait)
@@ -23789,6 +24329,11 @@ var AgentCollaborationWatch = class {
23789
24329
  resynchronize() {
23790
24330
  this.continuityLost = false;
23791
24331
  this.pending.length = 0;
24332
+ this.workflowWatermarks.clear();
24333
+ }
24334
+ recordWorkflowWatermark(interest, fact) {
24335
+ if (fact.domain !== "workflow") return;
24336
+ this.workflowWatermarks.set(workflowWatermarkKey(interest, fact.ref), workflowFingerprint(fact));
23792
24337
  }
23793
24338
  enqueue(interest, fact) {
23794
24339
  const terminalPending = this.pending.find(
@@ -23832,6 +24377,7 @@ var AgentCollaborationWatch = class {
23832
24377
  merged: entry.merged,
23833
24378
  quietForMs: Math.max(0, now - entry.lastObservedAt),
23834
24379
  ref: entry.fact.ref,
24380
+ ...entry.fact.domain === "canvas" ? { source: entry.fact.source } : {},
23835
24381
  summary: structuredClone(entry.fact.summary)
23836
24382
  }));
23837
24383
  const result3 = {
@@ -23908,6 +24454,18 @@ function positive(value, field) {
23908
24454
  function watchError(code, message) {
23909
24455
  return Object.assign(new Error(message), { code });
23910
24456
  }
24457
+ function workflowWatermarkKey(interest, runId) {
24458
+ return `${interest}\0${runId}`;
24459
+ }
24460
+ function workflowFingerprint(fact) {
24461
+ return JSON.stringify({
24462
+ changeKind: fact.changeKind,
24463
+ kind: fact.kind,
24464
+ status: fact.status,
24465
+ summary: fact.summary,
24466
+ terminal: fact.terminal
24467
+ });
24468
+ }
23911
24469
 
23912
24470
  // src/canvas/session.ts
23913
24471
  var CanvasSession = class {
@@ -23916,7 +24474,7 @@ var CanvasSession = class {
23916
24474
  this.projectId = projectId;
23917
24475
  this.dependencies = dependencies;
23918
24476
  this.clientInstanceId = clientInstanceId;
23919
- this.watch = dependencies.watch ?? new AgentCollaborationWatch();
24477
+ this.watch = dependencies.watch ?? new AgentCollaborationWatch({ includeSystemWorkflowTerminal: true });
23920
24478
  this.health = {
23921
24479
  connectionAttempts: 0,
23922
24480
  connectionId: null,
@@ -23934,6 +24492,7 @@ var CanvasSession = class {
23934
24492
  runtime = null;
23935
24493
  unsubscribeRuntime = null;
23936
24494
  observationListeners = /* @__PURE__ */ new Set();
24495
+ knownWorkflowRunIds = /* @__PURE__ */ new Set();
23937
24496
  projectionRevision = 0;
23938
24497
  actorId = null;
23939
24498
  collaborationContext = null;
@@ -23983,6 +24542,7 @@ var CanvasSession = class {
23983
24542
  const connectionId = this.health.connectionId;
23984
24543
  const runtime = new SemanticCanvasRuntime({
23985
24544
  actor: { displayName: actor.displayName, id: actor.accountId },
24545
+ clientName: normalizedClientName,
23986
24546
  clientInstanceId: this.clientInstanceId,
23987
24547
  document: document2,
23988
24548
  foundationData,
@@ -24001,9 +24561,15 @@ var CanvasSession = class {
24001
24561
  workflowRuntime
24002
24562
  });
24003
24563
  this.runtime = runtime;
24564
+ for (const workflowRunId of runtime.getSnapshot().pendingWorkflowInstanceIds) {
24565
+ this.knownWorkflowRunIds.add(workflowRunId);
24566
+ }
24004
24567
  this.projectionRevision = runtime.revision;
24005
24568
  this.unsubscribeRuntime = runtime.subscribe((observation) => {
24006
24569
  this.projectionRevision = observation.revision;
24570
+ if (observation.workflowRun) {
24571
+ this.knownWorkflowRunIds.add(observation.workflowRun.id);
24572
+ }
24007
24573
  this.watch.recordMany(projectCanvasObservation(observation, Date.now()));
24008
24574
  const legacyObservation = {
24009
24575
  ...observation.changedViewNodeIds ? { changedViewNodeIds: observation.changedViewNodeIds } : {},
@@ -24022,13 +24588,60 @@ var CanvasSession = class {
24022
24588
  throw error51;
24023
24589
  }
24024
24590
  }
24025
- async execute(name, input, timeoutMs) {
24591
+ async execute(name, input, timeoutMs, options = {}) {
24026
24592
  const runtime = this.requireRuntime();
24027
- const result3 = await runtime.execute(name, input, timeoutMs);
24593
+ if (typeof input.workflowRunId === "string" && input.workflowRunId) {
24594
+ this.knownWorkflowRunIds.add(input.workflowRunId);
24595
+ }
24596
+ const result3 = await runtime.execute(name, input, timeoutMs, options);
24597
+ if (typeof result3.result.workflowRunId === "string" && result3.result.workflowRunId) {
24598
+ this.knownWorkflowRunIds.add(result3.result.workflowRunId);
24599
+ }
24028
24600
  this.projectionRevision = result3.revision;
24029
24601
  if (result3.attention) this.publishAttention(result3.attention);
24030
24602
  return result3;
24031
24603
  }
24604
+ async catchUpWorkflowInterests(timeoutMs) {
24605
+ const runtime = this.requireRuntime();
24606
+ const deadline = Date.now() + timeoutMs;
24607
+ const knownWorkflowRunIds = [...this.knownWorkflowRunIds];
24608
+ const runIds = [
24609
+ ...new Set(
24610
+ this.watch.getInterests().filter((interest) => interest.kind === "workflow").flatMap((interest) => interest.filter.runIds ?? knownWorkflowRunIds)
24611
+ )
24612
+ ];
24613
+ for (const workflowRunId of runIds) {
24614
+ const remainingMs = deadline - Date.now();
24615
+ if (remainingMs < 1) break;
24616
+ try {
24617
+ const result3 = await runtime.execute("getWorkflowRun", { workflowRunId }, remainingMs);
24618
+ const workflowRun = result3.result.workflowRun;
24619
+ if (!isWorkflowRun(workflowRun)) continue;
24620
+ for (const fact of projectCanvasObservation(
24621
+ { kind: "workflow", revision: result3.revision, workflowRun },
24622
+ Date.now()
24623
+ )) {
24624
+ this.watch.recordAuthoritativeWorkflow(fact);
24625
+ }
24626
+ } catch {
24627
+ }
24628
+ }
24629
+ }
24630
+ acknowledgeWatchDelivery(result3) {
24631
+ if (result3.status !== "ready") return;
24632
+ for (const change of result3.changes) {
24633
+ if (change.source !== "remote" || !change.kind.startsWith("note.") && !change.kind.startsWith("view-node.")) {
24634
+ continue;
24635
+ }
24636
+ const bounds = this.requireRuntime().resolveAttentionBounds({
24637
+ kind: "view-nodes",
24638
+ viewNodeIds: [change.ref]
24639
+ });
24640
+ if (!bounds) continue;
24641
+ this.publishAttentionBounds(bounds);
24642
+ return;
24643
+ }
24644
+ }
24032
24645
  getCapabilities(detail) {
24033
24646
  return this.requireRuntime().capabilities.map(
24034
24647
  (descriptor2) => detail ? descriptor2 : {
@@ -24142,6 +24755,9 @@ function isIndeterminateWrite4(value) {
24142
24755
  function isWorkflowRecoveryExhausted(value) {
24143
24756
  return typeof value === "object" && value !== null && "workflowRecoveryExhausted" in value && value.workflowRecoveryExhausted === true;
24144
24757
  }
24758
+ function isWorkflowRun(value) {
24759
+ return typeof value === "object" && value !== null && "id" in value && typeof value.id === "string" && "status" in value && typeof value.status === "string" && "definitionId" in value && typeof value.definitionId === "string" && "outputs" in value && Array.isArray(value.outputs);
24760
+ }
24145
24761
 
24146
24762
  // src/canvas/client.ts
24147
24763
  var CANVAS_QUERY_TARGETS = [
@@ -24226,7 +24842,9 @@ var CanvasClient = class {
24226
24842
  };
24227
24843
  this.receipts.push(receipt);
24228
24844
  try {
24229
- const operation = await this.session.execute(name, input.input, input.timeoutMs ?? 15e3);
24845
+ const operation = await this.session.execute(name, input.input, input.timeoutMs ?? 15e3, {
24846
+ onProgress: input.onProgress
24847
+ });
24230
24848
  Object.assign(receipt, {
24231
24849
  completedAt: (/* @__PURE__ */ new Date()).toISOString(),
24232
24850
  projectionRevisionAfter: operation.revision,
@@ -24236,6 +24854,7 @@ var CanvasClient = class {
24236
24854
  });
24237
24855
  return { receipt };
24238
24856
  } catch (error51) {
24857
+ const operationResult2 = readCanvasOperationIndeterminateResult(error51);
24239
24858
  const operationUsage = readCanvasOperationUsage(error51);
24240
24859
  const indeterminate = isIndeterminateError(error51);
24241
24860
  const timedOut = indeterminate && (error51 instanceof IndeterminateBackendWriteError && error51.timedOut || readTimedOut(error51) || error51 instanceof Error && /timed out/i.test(error51.message));
@@ -24247,6 +24866,7 @@ var CanvasClient = class {
24247
24866
  message: safeCanvasMessage(error51),
24248
24867
  outcome: indeterminate ? "indeterminate" : "failed"
24249
24868
  },
24869
+ ...operationResult2 ? { result: redactCanvasRecord(operationResult2) } : {},
24250
24870
  status: timedOut ? "timed-out" : "failed",
24251
24871
  ...operationUsage ? { usage: operationUsage } : {}
24252
24872
  });
@@ -24397,7 +25017,7 @@ import { chmod, unlink } from "node:fs/promises";
24397
25017
  import net from "node:net";
24398
25018
  var MAX_RPC_REQUEST_BYTES = 1024 * 1024;
24399
25019
  var MAX_RPC_RESPONSE_BYTES = 16 * 1024 * 1024;
24400
- async function requestAgentSession(socketPath, request, timeoutMs = 5e3, signal) {
25020
+ async function requestAgentSession(socketPath, request, timeoutMs = 5e3, signal, onProgress) {
24401
25021
  return new Promise((resolve, reject) => {
24402
25022
  const socket = net.createConnection(socketPath);
24403
25023
  let source = "";
@@ -24427,14 +25047,23 @@ async function requestAgentSession(socketPath, request, timeoutMs = 5e3, signal)
24427
25047
  finish(rpcError("session-rpc-too-large", "Agent Session RPC response is too large."));
24428
25048
  return;
24429
25049
  }
24430
- const newline = source.indexOf("\n");
24431
- if (newline < 0) return;
24432
- try {
24433
- const response = JSON.parse(source.slice(0, newline));
24434
- if (!response.ok) finish(rpcError(response.error.code, response.error.message));
24435
- else finish(void 0, response.value);
24436
- } catch {
24437
- finish(rpcError("invalid-session-rpc", "Agent Session RPC response is invalid."));
25050
+ while (!settled) {
25051
+ const newline = source.indexOf("\n");
25052
+ if (newline < 0) return;
25053
+ const line = source.slice(0, newline);
25054
+ source = source.slice(newline + 1);
25055
+ try {
25056
+ const message = JSON.parse(line);
25057
+ if ("type" in message && message.type === "progress") {
25058
+ void Promise.resolve(onProgress?.(message.progress)).catch(() => void 0);
25059
+ continue;
25060
+ }
25061
+ const response = message;
25062
+ if (!response.ok) finish(rpcError(response.error.code, response.error.message));
25063
+ else finish(void 0, response.value);
25064
+ } catch {
25065
+ finish(rpcError("invalid-session-rpc", "Agent Session RPC response is invalid."));
25066
+ }
24438
25067
  }
24439
25068
  });
24440
25069
  socket.once("error", (error51) => finish(error51));
@@ -24473,7 +25102,10 @@ async function listenAgentSessionRpc(socketPath, handler) {
24473
25102
  handled = true;
24474
25103
  try {
24475
25104
  const request = JSON.parse(source.slice(0, newline));
24476
- const value = await handler(request, { signal: abortController2.signal });
25105
+ const value = await handler(request, {
25106
+ reportProgress: (progress) => writeProgress(socket, progress),
25107
+ signal: abortController2.signal
25108
+ });
24477
25109
  writeResponse(socket, { ok: true, value });
24478
25110
  } catch (error51) {
24479
25111
  writeResponse(
@@ -24505,6 +25137,13 @@ async function listenAgentSessionRpc(socketPath, handler) {
24505
25137
  }
24506
25138
  };
24507
25139
  }
25140
+ function writeProgress(socket, progress) {
25141
+ if (socket.destroyed || !socket.writable) return Promise.resolve();
25142
+ return new Promise((resolve) => {
25143
+ socket.write(`${JSON.stringify({ progress, type: "progress" })}
25144
+ `, () => resolve());
25145
+ });
25146
+ }
24508
25147
  function writeResponse(socket, response) {
24509
25148
  if (socket.destroyed) return;
24510
25149
  socket.end(`${JSON.stringify(response)}
@@ -24856,7 +25495,16 @@ async function startAgentSessionDaemon(spec, dependencies = {}) {
24856
25495
  }
24857
25496
  if (request.kind === "delivery.ack") {
24858
25497
  const acknowledged = outstandingDelivery?.deliveryId === request.deliveryId;
24859
- if (acknowledged) outstandingDelivery = void 0;
25498
+ if (acknowledged) {
25499
+ const result3 = outstandingDelivery?.result;
25500
+ outstandingDelivery = void 0;
25501
+ if (result3) {
25502
+ try {
25503
+ client.session.acknowledgeWatchDelivery?.(result3);
25504
+ } catch {
25505
+ }
25506
+ }
25507
+ }
24860
25508
  return { acknowledged };
24861
25509
  }
24862
25510
  if (request.kind === "interests.get") return watch.getInterests();
@@ -24882,7 +25530,10 @@ async function startAgentSessionDaemon(spec, dependencies = {}) {
24882
25530
  }
24883
25531
  if (request.kind === "watch.wait") {
24884
25532
  if (outstandingDelivery) return outstandingDelivery;
24885
- const result3 = await watch.wait({ signal: context.signal, timeoutMs: request.timeoutMs });
25533
+ const startedAt = Date.now();
25534
+ await client.session.catchUpWorkflowInterests?.(Math.min(request.timeoutMs, 15e3));
25535
+ const remainingMs = Math.max(1, request.timeoutMs - (Date.now() - startedAt));
25536
+ const result3 = await watch.wait({ signal: context.signal, timeoutMs: remainingMs });
24886
25537
  if (result3.status !== "ready") return { result: result3 };
24887
25538
  outstandingDelivery = { deliveryId: crypto.randomUUID(), result: result3 };
24888
25539
  return outstandingDelivery;
@@ -24897,6 +25548,7 @@ async function startAgentSessionDaemon(spec, dependencies = {}) {
24897
25548
  return await client.executeOperation({
24898
25549
  input: request.input,
24899
25550
  name: request.name,
25551
+ onProgress: context.reportProgress,
24900
25552
  requestId: request.requestId,
24901
25553
  ...request.timeoutMs ? { timeoutMs: request.timeoutMs } : {}
24902
25554
  });
@@ -24905,6 +25557,7 @@ async function startAgentSessionDaemon(spec, dependencies = {}) {
24905
25557
  () => client.executeOperation({
24906
25558
  input: request.input,
24907
25559
  name: request.name,
25560
+ onProgress: context.reportProgress,
24908
25561
  requestId: request.requestId,
24909
25562
  ...request.timeoutMs ? { timeoutMs: request.timeoutMs } : {}
24910
25563
  })
@@ -24949,6 +25602,8 @@ async function createCanvasClient(spec, dependencies) {
24949
25602
  executeOperation: (input) => client.executeOperation(input),
24950
25603
  query: (input) => client.query(input),
24951
25604
  session: {
25605
+ acknowledgeWatchDelivery: (result3) => session.acknowledgeWatchDelivery(result3),
25606
+ catchUpWorkflowInterests: (timeoutMs) => session.catchUpWorkflowInterests(timeoutMs),
24952
25607
  getContext: () => session.getCollaborationContext(),
24953
25608
  getHealth: () => session.getHealth(),
24954
25609
  getIdentity: () => session.getIdentity(),
@@ -25351,6 +26006,11 @@ var CanvasProtocolServer = class {
25351
26006
  const outcome = await this.client.executeOperation({
25352
26007
  input: request.payload.input,
25353
26008
  name: request.payload.name,
26009
+ onProgress: (progress) => this.output.emitMandatory({
26010
+ payload: progress,
26011
+ requestId: request.requestId,
26012
+ type: "operationProgress"
26013
+ }),
25354
26014
  requestId: request.requestId,
25355
26015
  ...request.payload.timeoutMs ? { timeoutMs: request.payload.timeoutMs } : {}
25356
26016
  });
@@ -28990,6 +29650,9 @@ async function runCanvasOneShot(config2, command, dependencies = {}) {
28990
29650
  input: command.input,
28991
29651
  name: command.name,
28992
29652
  requestId: command.requestId,
29653
+ ...dependencies.onProgress ? {
29654
+ onProgress: (progress) => dependencies.onProgress?.({ ...progress, requestId: command.requestId })
29655
+ } : {},
28993
29656
  ...command.timeoutMs ? { timeoutMs: command.timeoutMs } : {}
28994
29657
  });
28995
29658
  return operationResult(command, outcome);
@@ -29175,18 +29838,21 @@ function resolveDownloadableCoreNodeMedia(coreNode) {
29175
29838
 
29176
29839
  // src/media/download.ts
29177
29840
  var DEFAULT_RESOLUTION_TIMEOUT_MS = 15e3;
29841
+ var DEFAULT_DOWNLOAD_ATTEMPTS = 3;
29178
29842
  var FILE_TYPE_HEADER_BYTES2 = 64 * 1024;
29179
29843
  var MediaDownloadError = class extends Error {
29180
- constructor(code, message, exitCode, retryable) {
29844
+ constructor(code, message, exitCode, retryable, details = {}) {
29181
29845
  super(message);
29182
29846
  this.code = code;
29183
29847
  this.exitCode = exitCode;
29184
29848
  this.retryable = retryable;
29849
+ this.details = details;
29185
29850
  this.name = "MediaDownloadError";
29186
29851
  }
29187
29852
  code;
29188
29853
  exitCode;
29189
29854
  retryable;
29855
+ details;
29190
29856
  };
29191
29857
  async function runCanvasMediaDownload(config2, command, dependencies = {}) {
29192
29858
  const requestedAbsolutePath = path6.resolve(dependencies.cwd ?? process.cwd(), command.outputPath);
@@ -29201,42 +29867,80 @@ async function runCanvasMediaDownload(config2, command, dependencies = {}) {
29201
29867
  } catch (error51) {
29202
29868
  return failureResult2(command, error51, requestedAbsolutePath);
29203
29869
  }
29204
- let client;
29205
- let media;
29206
- try {
29207
- client = dependencies.mediaDownloadClientFactory?.(config2, command.projectId) ?? new CanvasClient(config2, command.projectId);
29208
- try {
29209
- await client.connect(normalizeCanvasClientName(command.clientName));
29210
- } catch {
29211
- throw new MediaDownloadError(
29212
- "canvas-connection-failed",
29213
- "Unable to establish a ready Project Canvas context.",
29214
- 2,
29215
- true
29216
- );
29217
- }
29218
- throwIfCancelled(dependencies.signal);
29219
- media = await resolveMediaSource(client, command, dependencies.randomUUID);
29220
- } catch (error51) {
29221
- return failureResult2(command, error51, outputTarget.absolutePath);
29222
- } finally {
29223
- client?.close();
29870
+ const maximumAttempts = dependencies.maxAttempts ?? DEFAULT_DOWNLOAD_ATTEMPTS;
29871
+ if (!Number.isInteger(maximumAttempts) || maximumAttempts < 1 || maximumAttempts > 3) {
29872
+ return failureResult2(
29873
+ command,
29874
+ new MediaDownloadError(
29875
+ "invalid-download-attempts",
29876
+ "Download attempt limit must be between 1 and 3.",
29877
+ 1,
29878
+ false,
29879
+ { phase: "setup" }
29880
+ ),
29881
+ outputTarget.absolutePath,
29882
+ 0,
29883
+ 0
29884
+ );
29224
29885
  }
29225
- try {
29226
- const file2 = await transferMedia(config2, media, outputTarget, command, dependencies);
29227
- return {
29228
- exitCode: 0,
29229
- output: {
29230
- file: file2,
29231
- format: "az8.canvas.media-download.v1",
29232
- media: { coreNodeId: media.coreNodeId, mediaType: media.mediaType },
29233
- projectId: command.projectId,
29234
- source: command.source
29886
+ const deadlineAt = command.timeoutMs ? Date.now() + command.timeoutMs : null;
29887
+ let lastError;
29888
+ for (let attempt = 1; attempt <= maximumAttempts; attempt += 1) {
29889
+ try {
29890
+ throwIfCancelled(dependencies.signal);
29891
+ const remainingMs = remainingDownloadMs(deadlineAt);
29892
+ const attemptCommand = {
29893
+ ...command,
29894
+ ...remainingMs === void 0 ? {} : { timeoutMs: remainingMs }
29895
+ };
29896
+ const media = await resolveMediaForAttempt(config2, attemptCommand, dependencies);
29897
+ const file2 = await transferMedia(config2, media, outputTarget, attemptCommand, dependencies);
29898
+ return {
29899
+ exitCode: 0,
29900
+ output: {
29901
+ attempts: { completed: attempt, maximum: maximumAttempts },
29902
+ file: file2,
29903
+ format: "az8.canvas.media-download.v1",
29904
+ media: { coreNodeId: media.coreNodeId, mediaType: media.mediaType },
29905
+ projectId: command.projectId,
29906
+ source: command.source
29907
+ }
29908
+ };
29909
+ } catch (error51) {
29910
+ lastError = error51;
29911
+ const failure = normalizeDownloadError(error51);
29912
+ if (!failure.retryable || failure.code === "download-cancelled" || failure.code === "download-timed-out" || attempt === maximumAttempts) {
29913
+ return failureResult2(command, failure, outputTarget.absolutePath, attempt, maximumAttempts);
29235
29914
  }
29236
- };
29237
- } catch (error51) {
29238
- return failureResult2(command, error51, outputTarget.absolutePath);
29915
+ try {
29916
+ await waitForDownloadRetry(
29917
+ downloadRetryDelay(attempt, dependencies.random?.() ?? Math.random()),
29918
+ deadlineAt,
29919
+ dependencies
29920
+ );
29921
+ } catch (retryError) {
29922
+ return failureResult2(
29923
+ command,
29924
+ retryError,
29925
+ outputTarget.absolutePath,
29926
+ attempt,
29927
+ maximumAttempts
29928
+ );
29929
+ }
29930
+ }
29239
29931
  }
29932
+ return failureResult2(
29933
+ command,
29934
+ lastError,
29935
+ outputTarget.absolutePath,
29936
+ maximumAttempts,
29937
+ maximumAttempts
29938
+ );
29939
+ }
29940
+ function downloadRetryDelay(attempt, random) {
29941
+ const boundedRandom = Number.isFinite(random) ? Math.max(0, Math.min(1, random)) : 0.5;
29942
+ const base = Math.min(250 * 2 ** (attempt - 1), 1e3);
29943
+ return Math.min(1e3, Math.round(base * (0.75 + boundedRandom * 0.5)));
29240
29944
  }
29241
29945
  function formatCanvasMediaDownloadResult(result3) {
29242
29946
  return `${JSON.stringify(result3.output, null, 2)}
@@ -29252,7 +29956,8 @@ async function resolveMediaSource(client, command, randomUUID4) {
29252
29956
  "view-node-media-unavailable",
29253
29957
  safeCanvasMessage(error51),
29254
29958
  1,
29255
- false
29959
+ false,
29960
+ { cause: sanitizeDownloadCause(error51), phase: "source-resolution" }
29256
29961
  );
29257
29962
  }
29258
29963
  } else {
@@ -29270,7 +29975,8 @@ async function resolveMediaSource(client, command, randomUUID4) {
29270
29975
  "core-node-resolution-failed",
29271
29976
  outcome.receipt.error?.message ?? `Core Node ${coreNodeId} could not be resolved.`,
29272
29977
  indeterminate ? 2 : 1,
29273
- indeterminate
29978
+ indeterminate,
29979
+ { phase: "core-node-resolution" }
29274
29980
  );
29275
29981
  }
29276
29982
  const coreNode = parseCoreNode(outcome.receipt.result?.coreNode, coreNodeId);
@@ -29289,7 +29995,8 @@ function parseCoreNode(value, expectedId) {
29289
29995
  "core-node-response-invalid",
29290
29996
  `Core Node ${expectedId} returned an invalid record.`,
29291
29997
  2,
29292
- true
29998
+ true,
29999
+ { phase: "core-node-resolution" }
29293
30000
  );
29294
30001
  }
29295
30002
  const record2 = value;
@@ -29298,7 +30005,8 @@ function parseCoreNode(value, expectedId) {
29298
30005
  "core-node-response-invalid",
29299
30006
  `Core Node ${expectedId} returned an invalid record.`,
29300
30007
  2,
29301
- true
30008
+ true,
30009
+ { phase: "core-node-resolution" }
29302
30010
  );
29303
30011
  }
29304
30012
  return value;
@@ -29309,7 +30017,8 @@ async function prepareOutputTarget(requestedPath, overwrite, cwd) {
29309
30017
  "invalid-output-path",
29310
30018
  "Download output path must not be blank.",
29311
30019
  1,
29312
- false
30020
+ false,
30021
+ { phase: "output-setup" }
29313
30022
  );
29314
30023
  }
29315
30024
  const absolutePath = path6.resolve(cwd, requestedPath);
@@ -29322,7 +30031,8 @@ async function prepareOutputTarget(requestedPath, overwrite, cwd) {
29322
30031
  "output-parent-unavailable",
29323
30032
  "Download output parent directory does not exist or cannot be accessed.",
29324
30033
  1,
29325
- false
30034
+ false,
30035
+ { phase: "output-setup" }
29326
30036
  );
29327
30037
  }
29328
30038
  if (!parent.isDirectory()) {
@@ -29330,7 +30040,8 @@ async function prepareOutputTarget(requestedPath, overwrite, cwd) {
29330
30040
  "output-parent-not-directory",
29331
30041
  "Download output parent is not a directory.",
29332
30042
  1,
29333
- false
30043
+ false,
30044
+ { phase: "output-setup" }
29334
30045
  );
29335
30046
  }
29336
30047
  let existing;
@@ -29342,7 +30053,8 @@ async function prepareOutputTarget(requestedPath, overwrite, cwd) {
29342
30053
  "output-target-unavailable",
29343
30054
  "Download output target cannot be inspected.",
29344
30055
  1,
29345
- false
30056
+ false,
30057
+ { phase: "output-setup" }
29346
30058
  );
29347
30059
  }
29348
30060
  }
@@ -29351,7 +30063,8 @@ async function prepareOutputTarget(requestedPath, overwrite, cwd) {
29351
30063
  "output-target-exists",
29352
30064
  "Download output target already exists; pass --overwrite to replace a regular file.",
29353
30065
  1,
29354
- false
30066
+ false,
30067
+ { phase: "output-setup" }
29355
30068
  );
29356
30069
  }
29357
30070
  if (existing && (!existing.isFile() || existing.isSymbolicLink())) {
@@ -29359,7 +30072,8 @@ async function prepareOutputTarget(requestedPath, overwrite, cwd) {
29359
30072
  "output-target-not-regular-file",
29360
30073
  "Download output target must be a regular file and must not be a symbolic link.",
29361
30074
  1,
29362
- false
30075
+ false,
30076
+ { phase: "output-setup" }
29363
30077
  );
29364
30078
  }
29365
30079
  return { absolutePath, existed: Boolean(existing), parentPath };
@@ -29370,9 +30084,9 @@ async function transferMedia(config2, media, outputTarget, command, dependencies
29370
30084
  let response;
29371
30085
  try {
29372
30086
  response = await config2.fetch(media.url, { redirect: "follow", signal: deadline.signal });
29373
- } catch {
30087
+ } catch (error51) {
29374
30088
  deadline.cleanup();
29375
- throw transferFailure(deadline);
30089
+ throw transferFailure(deadline, error51);
29376
30090
  }
29377
30091
  if (!response.ok) {
29378
30092
  deadline.cleanup();
@@ -29381,7 +30095,8 @@ async function transferMedia(config2, media, outputTarget, command, dependencies
29381
30095
  "media-http-error",
29382
30096
  `Media server returned HTTP ${response.status}.`,
29383
30097
  retryable ? 2 : 1,
29384
- retryable
30098
+ retryable,
30099
+ { phase: "media-transfer" }
29385
30100
  );
29386
30101
  }
29387
30102
  const contentType = normalizeContentType(response.headers.get("content-type"));
@@ -29391,8 +30106,9 @@ async function transferMedia(config2, media, outputTarget, command, dependencies
29391
30106
  throw new MediaDownloadError(
29392
30107
  "media-content-type-mismatch",
29393
30108
  `Media response is ${headerFamily}, but Core Node ${media.coreNodeId} is ${media.mediaType}.`,
29394
- 2,
29395
- true
30109
+ 1,
30110
+ false,
30111
+ { phase: "media-verification" }
29396
30112
  );
29397
30113
  }
29398
30114
  if (contentType === "text/html" || contentType === "application/json") {
@@ -29400,8 +30116,9 @@ async function transferMedia(config2, media, outputTarget, command, dependencies
29400
30116
  throw new MediaDownloadError(
29401
30117
  "media-content-type-mismatch",
29402
30118
  "Media response returned a non-media document.",
29403
- 2,
29404
- true
30119
+ 1,
30120
+ false,
30121
+ { phase: "media-verification" }
29405
30122
  );
29406
30123
  }
29407
30124
  const remoteContentLength = readContentLength(response);
@@ -29434,7 +30151,8 @@ async function transferMedia(config2, media, outputTarget, command, dependencies
29434
30151
  "output-finalize-failed",
29435
30152
  "Temporary download file could not be finalized.",
29436
30153
  1,
29437
- false
30154
+ false,
30155
+ { phase: "output-finalize" }
29438
30156
  );
29439
30157
  }
29440
30158
  let detected;
@@ -29445,7 +30163,8 @@ async function transferMedia(config2, media, outputTarget, command, dependencies
29445
30163
  "media-verification-failed",
29446
30164
  "Downloaded media could not be inspected safely.",
29447
30165
  1,
29448
- false
30166
+ false,
30167
+ { phase: "media-verification" }
29449
30168
  );
29450
30169
  }
29451
30170
  validateDownloadedMedia(media, contentType, detected);
@@ -29467,12 +30186,13 @@ async function transferMedia(config2, media, outputTarget, command, dependencies
29467
30186
  } catch (error51) {
29468
30187
  await temporary.handle.close().catch(() => void 0);
29469
30188
  if (error51 instanceof MediaDownloadError) throw error51;
29470
- if (deadline.signal.aborted) throw transferFailure(deadline);
30189
+ if (deadline.signal.aborted) throw transferFailure(deadline, error51);
29471
30190
  throw new MediaDownloadError(
29472
30191
  "media-download-internal-failed",
29473
30192
  "Media download could not complete its local processing.",
29474
30193
  2,
29475
- true
30194
+ true,
30195
+ { cause: sanitizeDownloadCause(error51), phase: "media-transfer" }
29476
30196
  );
29477
30197
  } finally {
29478
30198
  deadline.cleanup();
@@ -29523,7 +30243,8 @@ async function streamResponseToFile(response, handle, deadline, expectedBytes) {
29523
30243
  "media-response-body-missing",
29524
30244
  "Media response has no readable body.",
29525
30245
  2,
29526
- true
30246
+ true,
30247
+ { phase: "media-transfer" }
29527
30248
  );
29528
30249
  }
29529
30250
  const reader = response.body.getReader();
@@ -29541,16 +30262,17 @@ async function streamResponseToFile(response, handle, deadline, expectedBytes) {
29541
30262
  }
29542
30263
  } catch (error51) {
29543
30264
  if (error51 instanceof MediaDownloadError) throw error51;
29544
- if (deadline.signal.aborted) throw transferFailure(deadline);
30265
+ if (deadline.signal.aborted) throw transferFailure(deadline, error51);
29545
30266
  if (readFsCode2(error51)) {
29546
30267
  throw new MediaDownloadError(
29547
30268
  "output-write-failed",
29548
30269
  "Temporary download file could not be written completely.",
29549
30270
  1,
29550
- false
30271
+ false,
30272
+ { cause: sanitizeDownloadCause(error51), phase: "output-write" }
29551
30273
  );
29552
30274
  }
29553
- throw transferFailure(deadline);
30275
+ throw transferFailure(deadline, error51);
29554
30276
  } finally {
29555
30277
  reader.releaseLock();
29556
30278
  }
@@ -29559,7 +30281,8 @@ async function streamResponseToFile(response, handle, deadline, expectedBytes) {
29559
30281
  "media-content-length-mismatch",
29560
30282
  `Media response declared ${expectedBytes} bytes but delivered ${bytes}.`,
29561
30283
  2,
29562
- true
30284
+ true,
30285
+ { phase: "media-transfer" }
29563
30286
  );
29564
30287
  }
29565
30288
  return { bytes, sha256: hash2.digest("hex") };
@@ -29573,7 +30296,8 @@ async function writeAll(handle, value) {
29573
30296
  "output-write-stalled",
29574
30297
  "Temporary download file stopped accepting data.",
29575
30298
  1,
29576
- false
30299
+ false,
30300
+ { phase: "output-write" }
29577
30301
  );
29578
30302
  }
29579
30303
  offset += bytesWritten;
@@ -29585,8 +30309,9 @@ function validateDownloadedMedia(media, contentType, detected) {
29585
30309
  throw new MediaDownloadError(
29586
30310
  "media-content-mismatch",
29587
30311
  `Downloaded file is ${detectedFamily}, but Core Node ${media.coreNodeId} is ${media.mediaType}.`,
29588
- 2,
29589
- true
30312
+ 1,
30313
+ false,
30314
+ { phase: "media-verification" }
29590
30315
  );
29591
30316
  }
29592
30317
  const headerMatches = readMediaFamily(contentType) === media.mediaType;
@@ -29595,8 +30320,9 @@ function validateDownloadedMedia(media, contentType, detected) {
29595
30320
  throw new MediaDownloadError(
29596
30321
  "media-content-unverified",
29597
30322
  `Downloaded file could not be verified as ${media.mediaType} media.`,
29598
- 2,
29599
- true
30323
+ 1,
30324
+ false,
30325
+ { phase: "media-verification" }
29600
30326
  );
29601
30327
  }
29602
30328
  }
@@ -29614,14 +30340,16 @@ async function commitTemporaryFile(temporaryPath, outputTarget, overwrite) {
29614
30340
  "output-target-exists",
29615
30341
  "Download output target was created before the download could be committed.",
29616
30342
  1,
29617
- false
30343
+ false,
30344
+ { phase: "output-commit" }
29618
30345
  );
29619
30346
  }
29620
30347
  throw new MediaDownloadError(
29621
30348
  "output-commit-failed",
29622
30349
  "Verified media could not be committed to the output path.",
29623
30350
  1,
29624
- false
30351
+ false,
30352
+ { cause: sanitizeDownloadCause(error51), phase: "output-commit" }
29625
30353
  );
29626
30354
  }
29627
30355
  }
@@ -29645,18 +30373,29 @@ function createTransferSignal(external, timeoutMs) {
29645
30373
  signal: controller.signal
29646
30374
  };
29647
30375
  }
29648
- function transferFailure(deadline) {
30376
+ function transferFailure(deadline, cause) {
29649
30377
  if (deadline.external?.aborted) {
29650
- return new MediaDownloadError("download-cancelled", "Media download was cancelled.", 130, true);
30378
+ return new MediaDownloadError(
30379
+ "download-cancelled",
30380
+ "Media download was cancelled.",
30381
+ 130,
30382
+ true,
30383
+ {
30384
+ phase: "media-transfer"
30385
+ }
30386
+ );
29651
30387
  }
29652
30388
  if (deadline.isTimedOut()) {
29653
- return new MediaDownloadError("download-timed-out", "Media download timed out.", 2, true);
30389
+ return new MediaDownloadError("download-timed-out", "Media download timed out.", 2, true, {
30390
+ phase: "media-transfer"
30391
+ });
29654
30392
  }
29655
30393
  return new MediaDownloadError(
29656
30394
  "media-network-failed",
29657
30395
  "Media response could not be downloaded completely.",
29658
30396
  2,
29659
- true
30397
+ true,
30398
+ { cause: sanitizeDownloadCause(cause), phase: "media-transfer" }
29660
30399
  );
29661
30400
  }
29662
30401
  function throwIfCancelled(signal) {
@@ -29685,23 +30424,111 @@ function readMediaFamily(value) {
29685
30424
  if (value?.startsWith("video/")) return "video";
29686
30425
  return null;
29687
30426
  }
29688
- function failureResult2(command, error51, absolutePath) {
29689
- const failure = error51 instanceof MediaDownloadError ? error51 : new MediaDownloadError("media-download-failed", safeCanvasMessage(error51), 2, true);
30427
+ function failureResult2(command, error51, absolutePath, attemptsCompleted = 1, maximumAttempts = 1) {
30428
+ const failure = normalizeDownloadError(error51);
29690
30429
  return {
29691
30430
  exitCode: failure.exitCode,
29692
30431
  output: {
29693
30432
  error: {
29694
30433
  code: failure.code,
29695
30434
  message: safeCanvasMessage(failure),
29696
- retryable: failure.retryable
30435
+ ...failure.details.phase ? { phase: failure.details.phase } : {},
30436
+ retryable: failure.retryable,
30437
+ ...failure.details.cause ? { cause: failure.details.cause } : {}
29697
30438
  },
29698
30439
  format: "az8.canvas.media-download.v1",
30440
+ localState: {
30441
+ destinationCommitted: false,
30442
+ partialFileRetained: false
30443
+ },
29699
30444
  outputPath: absolutePath ?? path6.resolve(command.outputPath),
29700
30445
  projectId: command.projectId,
30446
+ attempts: { completed: attemptsCompleted, maximum: maximumAttempts },
30447
+ remoteState: "unverified",
29701
30448
  source: command.source
29702
30449
  }
29703
30450
  };
29704
30451
  }
30452
+ async function resolveMediaForAttempt(config2, command, dependencies) {
30453
+ const client = dependencies.mediaDownloadClientFactory?.(config2, command.projectId) ?? new CanvasClient(config2, command.projectId);
30454
+ try {
30455
+ try {
30456
+ await client.connect(normalizeCanvasClientName(command.clientName));
30457
+ } catch (error51) {
30458
+ throw new MediaDownloadError(
30459
+ "canvas-connection-failed",
30460
+ "Unable to establish a ready Project Canvas context.",
30461
+ 2,
30462
+ true,
30463
+ { cause: sanitizeDownloadCause(error51), phase: "canvas-connect" }
30464
+ );
30465
+ }
30466
+ throwIfCancelled(dependencies.signal);
30467
+ return await resolveMediaSource(client, command, dependencies.randomUUID);
30468
+ } finally {
30469
+ client.close();
30470
+ }
30471
+ }
30472
+ function normalizeDownloadError(error51) {
30473
+ return error51 instanceof MediaDownloadError ? error51 : new MediaDownloadError("media-download-failed", safeCanvasMessage(error51), 2, true, {
30474
+ cause: sanitizeDownloadCause(error51),
30475
+ phase: "unknown"
30476
+ });
30477
+ }
30478
+ function remainingDownloadMs(deadlineAt) {
30479
+ if (deadlineAt === null) return void 0;
30480
+ const remaining = deadlineAt - Date.now();
30481
+ if (remaining < 1) {
30482
+ throw new MediaDownloadError("download-timed-out", "Media download timed out.", 2, true, {
30483
+ phase: "retry-wait"
30484
+ });
30485
+ }
30486
+ return remaining;
30487
+ }
30488
+ async function waitForDownloadRetry(delayMs, deadlineAt, dependencies) {
30489
+ const remaining = remainingDownloadMs(deadlineAt);
30490
+ if (remaining !== void 0 && remaining <= delayMs) {
30491
+ throw new MediaDownloadError("download-timed-out", "Media download timed out.", 2, true, {
30492
+ phase: "retry-wait"
30493
+ });
30494
+ }
30495
+ await (dependencies.sleep ?? abortableSleep)(delayMs, dependencies.signal);
30496
+ throwIfCancelled(dependencies.signal);
30497
+ }
30498
+ async function abortableSleep(delayMs, signal) {
30499
+ if (signal?.aborted) return;
30500
+ await new Promise((resolve) => {
30501
+ const timeout = setTimeout(done, delayMs);
30502
+ const onAbort = () => done();
30503
+ function done() {
30504
+ clearTimeout(timeout);
30505
+ signal?.removeEventListener("abort", onAbort);
30506
+ resolve();
30507
+ }
30508
+ signal?.addEventListener("abort", onAbort, { once: true });
30509
+ });
30510
+ }
30511
+ function sanitizeDownloadCause(error51) {
30512
+ if (!error51 || typeof error51 !== "object") return void 0;
30513
+ const record2 = error51;
30514
+ const cause = record2.cause && typeof record2.cause === "object" ? record2.cause : record2;
30515
+ const message = cause instanceof Error ? cause.message : typeof cause.message === "string" ? cause.message : error51 instanceof Error ? error51.message : void 0;
30516
+ const code = typeof cause.code === "string" || typeof cause.code === "number" ? String(cause.code) : void 0;
30517
+ const closeCode = typeof cause.closeCode === "number" ? cause.closeCode : typeof cause.websocketCloseCode === "number" ? cause.websocketCloseCode : void 0;
30518
+ const closeReason = typeof cause.closeReason === "string" ? cause.closeReason : typeof cause.websocketCloseReason === "string" ? cause.websocketCloseReason : void 0;
30519
+ const sanitizedMessage = message ? sanitizeTransportDetail(message) : void 0;
30520
+ const sanitizedReason = closeReason ? sanitizeTransportDetail(closeReason) : void 0;
30521
+ if (!code && !closeCode && !sanitizedMessage && !sanitizedReason) return void 0;
30522
+ return {
30523
+ ...code ? { code } : {},
30524
+ ...sanitizedMessage ? { message: sanitizedMessage } : {},
30525
+ ...closeCode ? { websocketCloseCode: closeCode } : {},
30526
+ ...sanitizedReason ? { websocketCloseReason: sanitizedReason } : {}
30527
+ };
30528
+ }
30529
+ function sanitizeTransportDetail(value) {
30530
+ return value.replace(/token=[^\s;&]+/gi, "token=[REDACTED]").replace(/eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g, "[REDACTED]").replace(/(?:https?|wss?):\/\/[^\s]+/gi, "[REDACTED_URL]");
30531
+ }
29705
30532
  function readFsCode2(error51) {
29706
30533
  return typeof error51 === "object" && error51 !== null && "code" in error51 ? String(error51.code) : void 0;
29707
30534
  }
@@ -44620,7 +45447,7 @@ function registerCanvasCommands(program2, dependencies, setResult) {
44620
45447
  // package.json
44621
45448
  var package_default = {
44622
45449
  name: "@everfir/az8-cli",
44623
- version: "0.3.0-preview.1",
45450
+ version: "0.3.0-preview.3",
44624
45451
  description: "Semantic Project Canvas client for AZ8 Studio agents.",
44625
45452
  license: "UNLICENSED",
44626
45453
  type: "module",
@@ -44983,7 +45810,8 @@ var AgentSessionManager = class {
44983
45810
  descriptor2.socketPath,
44984
45811
  request,
44985
45812
  options.timeoutMs ?? 5e3,
44986
- options.signal
45813
+ options.signal,
45814
+ options.onProgress
44987
45815
  ).catch((error51) => {
44988
45816
  if (isRemoteFailure(error51)) throw error51;
44989
45817
  throw managerError(
@@ -45351,7 +46179,11 @@ function registerSessionCommands(program2, dependencies, setResult) {
45351
46179
  requestId,
45352
46180
  ...options.timeoutMs ? { timeoutMs: options.timeoutMs } : {}
45353
46181
  },
45354
- { sessionId: options.session, timeoutMs: (options.timeoutMs ?? 15e3) + 5e3 }
46182
+ {
46183
+ onProgress: (progress) => dependencies.onProgress?.({ ...progress, requestId }),
46184
+ sessionId: options.session,
46185
+ timeoutMs: (options.timeoutMs ?? 15e3) + 5e3
46186
+ }
45355
46187
  );
45356
46188
  const receipt = readOperationReceipt(value);
45357
46189
  const exitCode = receipt.status === "succeeded" ? 0 : receipt.error?.outcome === "indeterminate" ? 2 : 1;
@@ -45555,6 +46387,12 @@ process.once("SIGINT", abortOnSignal);
45555
46387
  process.once("SIGTERM", abortOnSignal);
45556
46388
  try {
45557
46389
  const result3 = await runAz8Cli(process.argv.slice(2), {
46390
+ onProgress(progress) {
46391
+ process.stderr.write(
46392
+ `${JSON.stringify({ format: "az8.operation-progress.v1", ...progress })}
46393
+ `
46394
+ );
46395
+ },
45558
46396
  persistentCanvas: runPersistentCanvas,
45559
46397
  signal: abortController.signal
45560
46398
  });