@everfir/az8-cli 0.3.0-preview.2 → 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
@@ -14177,7 +14177,28 @@ var CANVAS_OPERATION_REGISTRY = [
14177
14177
  }),
14178
14178
  write2("insertPromptReference", "Bind one visible image, text, or video View Node and place its platform At token in the Prompt", "silent", {
14179
14179
  properties: {
14180
- placement: { enum: ["start", "end"], type: "string" },
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
+ },
14181
14202
  referenceViewNodeId: stringSchema(),
14182
14203
  viewNodeId: stringSchema()
14183
14204
  },
@@ -15068,7 +15089,7 @@ function insertPromptReferenceIntoDraft(draft, definition, reference, sourceData
15068
15089
  const prompt = promptInput.content;
15069
15090
  const token = `{{@${binding.value.sourceDataType}:${binding.slotName}:${binding.index}:${reference.id}|view-node|${encodeURIComponent(reference.viewNodeId)}}}`;
15070
15091
  const content = placement === "start" ? prompt.trim() ? `${token}
15071
- ${prompt}` : token : prompt.trim() ? `${prompt}${prompt.endsWith("\n") ? "" : "\n"}${token}` : token;
15092
+ ${prompt}` : token : placement === "end" ? prompt.trim() ? `${prompt}${prompt.endsWith("\n") ? "" : "\n"}${token}` : token : insertAtPromptAnchor(prompt, token, placement);
15072
15093
  return {
15073
15094
  ...structuredClone(withoutExistingToken),
15074
15095
  inputs: {
@@ -15129,21 +15150,57 @@ function appendPromptReferenceToken(draft, slotName, referenceId, viewNodeId, no
15129
15150
  };
15130
15151
  }
15131
15152
  function removePromptReferenceToken(draft, referenceId) {
15132
- const pattern = new RegExp(PROMPT_REFERENCE_TOKEN_PATTERN, "g");
15133
15153
  const slots = Object.fromEntries(Object.entries(draft.inputs.slots).map(([slotName, values]) => [
15134
15154
  slotName,
15135
15155
  values.map((value) => {
15136
15156
  if (value.kind !== "literal" || value.assetType !== "text")
15137
15157
  return value;
15138
- const lines = value.content.split("\n").flatMap((line) => {
15139
- const content = line.replace(pattern, (token, _type, _slot, _index, bindingId) => bindingId === referenceId ? "" : token);
15140
- return content.trim() ? [content] : [];
15141
- });
15142
- return { ...value, content: lines.join("\n") };
15158
+ return { ...value, content: removeReferenceTokenFromContent(value.content, referenceId) };
15143
15159
  })
15144
15160
  ]));
15145
15161
  return { ...draft, inputs: { slots } };
15146
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
+ }
15147
15204
  function createDefaultSlots(definition) {
15148
15205
  return Object.fromEntries(definition.inputSchema.map((slot) => {
15149
15206
  if (slot.defaultValue === void 0)
@@ -16105,6 +16162,18 @@ var CoreNodeCreativeLoop = class {
16105
16162
  for (const id2 of ids)
16106
16163
  await this.recoverWorkflowRun(id2, timeoutMs);
16107
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
+ }
16108
16177
  close() {
16109
16178
  this.closed = true;
16110
16179
  for (const subscription of this.subscriptions.values())
@@ -16269,9 +16338,7 @@ var CoreNodeCreativeLoop = class {
16269
16338
  const sourceDataType = await this.resolveReferenceDataType(referenceViewNodeId);
16270
16339
  const existing = Object.values(draft.inputs.slots).flat().find((reference) => reference.kind === "view-node" && reference.viewNodeId === referenceViewNodeId);
16271
16340
  const referenceId = existing?.id ?? `input_ref_${crypto.randomUUID().replaceAll("-", "")}`;
16272
- const placement = input.placement === void 0 ? "end" : input.placement === "start" || input.placement === "end" ? input.placement : null;
16273
- if (!placement)
16274
- throw new Error("placement must be start or end.");
16341
+ const placement = parsePromptReferencePlacement(input.placement);
16275
16342
  const nextDraft = insertPromptReferenceIntoDraft(draft, definition, { id: referenceId, kind: "view-node", viewNodeId: referenceViewNodeId }, sourceDataType, placement, this.context.now());
16276
16343
  await this.replaceDraft(target, nextDraft, "Insert Prompt Reference", timeoutMs);
16277
16344
  return attention(target.id, { placement, referenceId, viewNodeId: target.id });
@@ -16921,6 +16988,38 @@ function requireString3(value, field) {
16921
16988
  throw new Error(`${field} must be non-empty.`);
16922
16989
  return value.trim();
16923
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
+ }
16924
17023
  function requirePlainString2(value, field) {
16925
17024
  if (typeof value !== "string")
16926
17025
  throw new Error(`${field} must be a string.`);
@@ -17384,9 +17483,12 @@ var MediaUploader = class {
17384
17483
  this.systems = systems;
17385
17484
  this.projection = systems.projectContentProjection;
17386
17485
  }
17387
- async execute(input, timeoutMs) {
17486
+ async execute(input, timeoutMs, onProgress) {
17388
17487
  const request = normalizeUploadRequest(input);
17389
- 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
+ });
17390
17492
  try {
17391
17493
  const descriptor2 = requireValidDescriptor(prepared.descriptor);
17392
17494
  const intent = await createIntent(request, descriptor2);
@@ -17420,9 +17522,10 @@ var MediaUploader = class {
17420
17522
  let uploaded = false;
17421
17523
  let coreNodeId;
17422
17524
  try {
17423
- const object2 = await prepared.upload({ timeoutMs });
17525
+ const object2 = await prepared.upload({ onProgress: safeProgress, timeoutMs });
17424
17526
  uploaded = true;
17425
17527
  const fileUrl = requireUploadedMediaUrl(object2.url);
17528
+ await safeProgress({ operation: "uploadMedia", stage: "creating-content" });
17426
17529
  const completion = await this.systems.projectContent.completeMediaUploads(this.context.projectId, [
17427
17530
  {
17428
17531
  asset: {
@@ -17456,6 +17559,7 @@ var MediaUploader = class {
17456
17559
  await this.updateBindingStatus(viewNode, "failed", errorMessage2(error51), timeoutMs);
17457
17560
  throw error51;
17458
17561
  }
17562
+ await safeProgress({ operation: "uploadMedia", stage: "updating-canvas" });
17459
17563
  await this.updateBindingStatus(viewNode, "ready", null, timeoutMs);
17460
17564
  return result2(viewNode.id, coreNodeId, intent, true, uploaded);
17461
17565
  } finally {
@@ -17559,6 +17663,14 @@ var MediaUploader = class {
17559
17663
  return after;
17560
17664
  }
17561
17665
  };
17666
+ function safeProgressListener(listener) {
17667
+ return async (progress) => {
17668
+ try {
17669
+ await listener?.(progress);
17670
+ } catch {
17671
+ }
17672
+ };
17673
+ }
17562
17674
  var MediaUploadIndeterminateError = class extends Error {
17563
17675
  indeterminate = true;
17564
17676
  timedOut = false;
@@ -18328,6 +18440,8 @@ var SemanticCanvasRuntime = class {
18328
18440
  this.unsubscribeRemote = options.document.subscribeRemoteChange(() => {
18329
18441
  const previous = this.snapshot;
18330
18442
  this.snapshot = options.document.getSnapshot();
18443
+ const previousPending = new Set(previous.pendingWorkflowInstanceIds);
18444
+ const addedPendingWorkflowInstanceIds = this.snapshot.pendingWorkflowInstanceIds.filter((id2) => !previousPending.has(id2));
18331
18445
  const viewNodeChanges = diffSemanticViewNodes(previous.viewNodes, this.snapshot.viewNodes);
18332
18446
  this.projectionRevision += 1;
18333
18447
  this.emit({
@@ -18339,6 +18453,9 @@ var SemanticCanvasRuntime = class {
18339
18453
  revision: this.projectionRevision
18340
18454
  });
18341
18455
  this.scheduleProjectContentRefresh();
18456
+ if (addedPendingWorkflowInstanceIds.length > 0) {
18457
+ this.creativeLoop?.observePendingWorkflowRuns(addedPendingWorkflowInstanceIds, 15e3);
18458
+ }
18342
18459
  });
18343
18460
  }
18344
18461
  get revision() {
@@ -18421,7 +18538,7 @@ var SemanticCanvasRuntime = class {
18421
18538
  this.listeners.add(listener);
18422
18539
  return () => this.listeners.delete(listener);
18423
18540
  }
18424
- async execute(name, input, timeoutMs) {
18541
+ async execute(name, input, timeoutMs, options = {}) {
18425
18542
  this.requireCapability(name);
18426
18543
  if (name === "importExternalMedia") {
18427
18544
  if (!this.externalMediaImporter)
@@ -18432,7 +18549,7 @@ var SemanticCanvasRuntime = class {
18432
18549
  if (name === "uploadMedia") {
18433
18550
  if (!this.mediaUploader)
18434
18551
  throw new Error("Media upload system is unavailable.");
18435
- const result3 = await this.mediaUploader.execute(input, timeoutMs);
18552
+ const result3 = await this.mediaUploader.execute(input, timeoutMs, options.onProgress);
18436
18553
  return { ...result3, revision: this.projectionRevision };
18437
18554
  }
18438
18555
  if (isCreativeLoopOperation(name)) {
@@ -23510,10 +23627,12 @@ var HASH_BUFFER_BYTES = 256 * 1024;
23510
23627
  function createNodeCanvasMediaUploadSystem(config2, cwd = process.cwd()) {
23511
23628
  const backend = new Az8BackendClient(config2);
23512
23629
  return {
23513
- async prepare(sourceReference) {
23630
+ async prepare(sourceReference, options) {
23631
+ const report = createProgressReporter(options?.onProgress);
23632
+ await report({ bytesCompleted: 0, operation: "uploadMedia", stage: "inspecting" });
23514
23633
  const source = await openUploadSource(sourceReference, cwd);
23515
23634
  try {
23516
- const descriptor2 = await inspectUploadSource(source.handle, source.absolutePath);
23635
+ const descriptor2 = await inspectUploadSource(source.handle, source.absolutePath, report);
23517
23636
  return createPreparedUpload(config2, backend, source.handle, source.absolutePath, descriptor2);
23518
23637
  } catch (error51) {
23519
23638
  await source.handle.close().catch(() => void 0);
@@ -23547,7 +23666,7 @@ async function openUploadSource(sourceReference, cwd) {
23547
23666
  }
23548
23667
  return { absolutePath, handle };
23549
23668
  }
23550
- async function inspectUploadSource(handle, absolutePath) {
23669
+ async function inspectUploadSource(handle, absolutePath, report) {
23551
23670
  const before = await handle.stat();
23552
23671
  if (!Number.isSafeInteger(before.size) || before.size < 1) {
23553
23672
  throw new Error("Media upload source must contain at least one byte.");
@@ -23566,6 +23685,7 @@ async function inspectUploadSource(handle, absolutePath) {
23566
23685
  chunk2.copy(header, bytes, 0, Math.min(chunk2.byteLength, header.byteLength - bytes));
23567
23686
  }
23568
23687
  bytes += bytesRead;
23688
+ await report(uploadProgress("inspecting", bytes, before.size));
23569
23689
  }
23570
23690
  const after = await handle.stat();
23571
23691
  if (after.size !== before.size || after.mtimeMs !== before.mtimeMs) {
@@ -23602,6 +23722,10 @@ function createPreparedUpload(config2, backend, handle, absolutePath, descriptor
23602
23722
  if (closed) throw new Error("Prepared media upload is already closed.");
23603
23723
  if (attempted) throw new Error("Prepared media upload can only be attempted once.");
23604
23724
  attempted = true;
23725
+ await reportProgress(options?.onProgress, {
23726
+ operation: "uploadMedia",
23727
+ stage: "requesting-upload"
23728
+ });
23605
23729
  const presign = await backend.request("core_nodes/upload/presign", {
23606
23730
  json: { format: descriptor2.extension, type: descriptor2.mediaType },
23607
23731
  method: "POST",
@@ -23610,12 +23734,20 @@ function createPreparedUpload(config2, backend, handle, absolutePath, descriptor
23610
23734
  });
23611
23735
  const uploadUrl = requireHttpUrl(presign.upload_url, "upload");
23612
23736
  const fileUrl = requireHttpUrl(presign.file_url, "file");
23613
- 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
+ );
23614
23746
  return { url: fileUrl };
23615
23747
  }
23616
23748
  };
23617
23749
  }
23618
- async function putPreparedFile(config2, handle, absolutePath, descriptor2, uploadUrl, timeoutMs) {
23750
+ async function putPreparedFile(config2, handle, absolutePath, descriptor2, uploadUrl, timeoutMs, report) {
23619
23751
  const originalStat = await handle.stat();
23620
23752
  const uploadHandle = await open(absolutePath, constants.O_RDONLY | constants.O_NOFOLLOW);
23621
23753
  let uploadStat;
@@ -23636,11 +23768,15 @@ async function putPreparedFile(config2, handle, absolutePath, descriptor2, uploa
23636
23768
  );
23637
23769
  const hash2 = createHash("sha256");
23638
23770
  let bytes = 0;
23771
+ await report(uploadProgress("uploading", 0, descriptor2.bytes));
23639
23772
  const meter = new Transform({
23640
23773
  transform(chunk2, _encoding, callback) {
23641
23774
  hash2.update(chunk2);
23642
23775
  bytes += chunk2.byteLength;
23643
- callback(null, chunk2);
23776
+ Promise.resolve(report(uploadProgress("uploading", bytes, descriptor2.bytes))).then(
23777
+ () => callback(null, chunk2),
23778
+ callback
23779
+ );
23644
23780
  }
23645
23781
  });
23646
23782
  const source = uploadHandle.createReadStream({
@@ -23676,6 +23812,35 @@ async function putPreparedFile(config2, handle, absolutePath, descriptor2, uploa
23676
23812
  throw new Error("Media upload source changed before the upload completed.");
23677
23813
  }
23678
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
+ }
23679
23844
  function requireHttpUrl(value, kind) {
23680
23845
  if (typeof value !== "string" || !value.trim()) {
23681
23846
  throw new Error(`Media upload presign returned no ${kind} URL.`);
@@ -23744,8 +23909,19 @@ var COLLABORATION_SESSION_STATES = [
23744
23909
  ];
23745
23910
 
23746
23911
  // src/canvas/watch/interest-registry.ts
23912
+ var SYSTEM_WORKFLOW_TERMINAL_INTEREST_KEY = "system.workflow-terminal";
23747
23913
  var CollaborationInterestRegistry = class {
23748
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
+ }
23749
23925
  patch(input) {
23750
23926
  const upserts = input.upsert ?? [];
23751
23927
  const removals = input.remove ?? [];
@@ -23755,6 +23931,9 @@ var CollaborationInterestRegistry = class {
23755
23931
  for (const entry of upserts) {
23756
23932
  const key = requireKey(entry.key);
23757
23933
  const current = next.get(key);
23934
+ if (current?.system) {
23935
+ throw interestError("system-interest-readonly", `System Interest ${key} cannot be changed.`);
23936
+ }
23758
23937
  if (entry.patch.kind !== void 0 && !isInterestKind(entry.patch.kind)) {
23759
23938
  throw interestError(
23760
23939
  "interest-kind-invalid",
@@ -23775,6 +23954,9 @@ var CollaborationInterestRegistry = class {
23775
23954
  }
23776
23955
  for (const rawKey of removals) {
23777
23956
  const key = requireKey(rawKey);
23957
+ if (next.get(key)?.system) {
23958
+ throw interestError("system-interest-readonly", `System Interest ${key} cannot be removed.`);
23959
+ }
23778
23960
  if (next.delete(key)) changedKeys.push(key);
23779
23961
  }
23780
23962
  this.interests.clear();
@@ -23980,6 +24162,7 @@ function projectWorkflowRun(run, observedAt) {
23980
24162
  status: run.status,
23981
24163
  summary: {
23982
24164
  definitionId: run.definitionId,
24165
+ workflowRunId: run.id,
23983
24166
  ...run.error ? {
23984
24167
  error: {
23985
24168
  ...run.error.code ? { code: run.error.code } : {},
@@ -23989,6 +24172,7 @@ function projectWorkflowRun(run, observedAt) {
23989
24172
  ...run.progress === void 0 ? {} : { progress: run.progress },
23990
24173
  ...run.outputs.length > 0 ? {
23991
24174
  outputs: run.outputs.map((output) => ({
24175
+ coreNodeId: output.coreNodeId,
23992
24176
  role: output.role,
23993
24177
  slotName: output.slotName,
23994
24178
  ...output.viewNodeId ? { viewNodeId: output.viewNodeId } : {}
@@ -24043,7 +24227,7 @@ function safeWorkflowMessage(value) {
24043
24227
 
24044
24228
  // src/canvas/watch/watch.ts
24045
24229
  var AgentCollaborationWatch = class {
24046
- interests = new CollaborationInterestRegistry();
24230
+ interests;
24047
24231
  maxChangesPerDelivery;
24048
24232
  maxPendingChanges;
24049
24233
  maxRecentResults;
@@ -24054,6 +24238,7 @@ var AgentCollaborationWatch = class {
24054
24238
  activeWait = null;
24055
24239
  continuityLost = false;
24056
24240
  constructor(options = {}) {
24241
+ this.interests = new CollaborationInterestRegistry(options.includeSystemWorkflowTerminal);
24057
24242
  this.maxChangesPerDelivery = positive(
24058
24243
  options.maxChangesPerDelivery ?? 64,
24059
24244
  "maxChangesPerDelivery"
@@ -24289,7 +24474,7 @@ var CanvasSession = class {
24289
24474
  this.projectId = projectId;
24290
24475
  this.dependencies = dependencies;
24291
24476
  this.clientInstanceId = clientInstanceId;
24292
- this.watch = dependencies.watch ?? new AgentCollaborationWatch();
24477
+ this.watch = dependencies.watch ?? new AgentCollaborationWatch({ includeSystemWorkflowTerminal: true });
24293
24478
  this.health = {
24294
24479
  connectionAttempts: 0,
24295
24480
  connectionId: null,
@@ -24382,6 +24567,9 @@ var CanvasSession = class {
24382
24567
  this.projectionRevision = runtime.revision;
24383
24568
  this.unsubscribeRuntime = runtime.subscribe((observation) => {
24384
24569
  this.projectionRevision = observation.revision;
24570
+ if (observation.workflowRun) {
24571
+ this.knownWorkflowRunIds.add(observation.workflowRun.id);
24572
+ }
24385
24573
  this.watch.recordMany(projectCanvasObservation(observation, Date.now()));
24386
24574
  const legacyObservation = {
24387
24575
  ...observation.changedViewNodeIds ? { changedViewNodeIds: observation.changedViewNodeIds } : {},
@@ -24400,12 +24588,12 @@ var CanvasSession = class {
24400
24588
  throw error51;
24401
24589
  }
24402
24590
  }
24403
- async execute(name, input, timeoutMs) {
24591
+ async execute(name, input, timeoutMs, options = {}) {
24404
24592
  const runtime = this.requireRuntime();
24405
24593
  if (typeof input.workflowRunId === "string" && input.workflowRunId) {
24406
24594
  this.knownWorkflowRunIds.add(input.workflowRunId);
24407
24595
  }
24408
- const result3 = await runtime.execute(name, input, timeoutMs);
24596
+ const result3 = await runtime.execute(name, input, timeoutMs, options);
24409
24597
  if (typeof result3.result.workflowRunId === "string" && result3.result.workflowRunId) {
24410
24598
  this.knownWorkflowRunIds.add(result3.result.workflowRunId);
24411
24599
  }
@@ -24654,7 +24842,9 @@ var CanvasClient = class {
24654
24842
  };
24655
24843
  this.receipts.push(receipt);
24656
24844
  try {
24657
- 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
+ });
24658
24848
  Object.assign(receipt, {
24659
24849
  completedAt: (/* @__PURE__ */ new Date()).toISOString(),
24660
24850
  projectionRevisionAfter: operation.revision,
@@ -24827,7 +25017,7 @@ import { chmod, unlink } from "node:fs/promises";
24827
25017
  import net from "node:net";
24828
25018
  var MAX_RPC_REQUEST_BYTES = 1024 * 1024;
24829
25019
  var MAX_RPC_RESPONSE_BYTES = 16 * 1024 * 1024;
24830
- async function requestAgentSession(socketPath, request, timeoutMs = 5e3, signal) {
25020
+ async function requestAgentSession(socketPath, request, timeoutMs = 5e3, signal, onProgress) {
24831
25021
  return new Promise((resolve, reject) => {
24832
25022
  const socket = net.createConnection(socketPath);
24833
25023
  let source = "";
@@ -24857,14 +25047,23 @@ async function requestAgentSession(socketPath, request, timeoutMs = 5e3, signal)
24857
25047
  finish(rpcError("session-rpc-too-large", "Agent Session RPC response is too large."));
24858
25048
  return;
24859
25049
  }
24860
- const newline = source.indexOf("\n");
24861
- if (newline < 0) return;
24862
- try {
24863
- const response = JSON.parse(source.slice(0, newline));
24864
- if (!response.ok) finish(rpcError(response.error.code, response.error.message));
24865
- else finish(void 0, response.value);
24866
- } catch {
24867
- 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
+ }
24868
25067
  }
24869
25068
  });
24870
25069
  socket.once("error", (error51) => finish(error51));
@@ -24903,7 +25102,10 @@ async function listenAgentSessionRpc(socketPath, handler) {
24903
25102
  handled = true;
24904
25103
  try {
24905
25104
  const request = JSON.parse(source.slice(0, newline));
24906
- 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
+ });
24907
25109
  writeResponse(socket, { ok: true, value });
24908
25110
  } catch (error51) {
24909
25111
  writeResponse(
@@ -24935,6 +25137,13 @@ async function listenAgentSessionRpc(socketPath, handler) {
24935
25137
  }
24936
25138
  };
24937
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
+ }
24938
25147
  function writeResponse(socket, response) {
24939
25148
  if (socket.destroyed) return;
24940
25149
  socket.end(`${JSON.stringify(response)}
@@ -25339,6 +25548,7 @@ async function startAgentSessionDaemon(spec, dependencies = {}) {
25339
25548
  return await client.executeOperation({
25340
25549
  input: request.input,
25341
25550
  name: request.name,
25551
+ onProgress: context.reportProgress,
25342
25552
  requestId: request.requestId,
25343
25553
  ...request.timeoutMs ? { timeoutMs: request.timeoutMs } : {}
25344
25554
  });
@@ -25347,6 +25557,7 @@ async function startAgentSessionDaemon(spec, dependencies = {}) {
25347
25557
  () => client.executeOperation({
25348
25558
  input: request.input,
25349
25559
  name: request.name,
25560
+ onProgress: context.reportProgress,
25350
25561
  requestId: request.requestId,
25351
25562
  ...request.timeoutMs ? { timeoutMs: request.timeoutMs } : {}
25352
25563
  })
@@ -25795,6 +26006,11 @@ var CanvasProtocolServer = class {
25795
26006
  const outcome = await this.client.executeOperation({
25796
26007
  input: request.payload.input,
25797
26008
  name: request.payload.name,
26009
+ onProgress: (progress) => this.output.emitMandatory({
26010
+ payload: progress,
26011
+ requestId: request.requestId,
26012
+ type: "operationProgress"
26013
+ }),
25798
26014
  requestId: request.requestId,
25799
26015
  ...request.payload.timeoutMs ? { timeoutMs: request.payload.timeoutMs } : {}
25800
26016
  });
@@ -29434,6 +29650,9 @@ async function runCanvasOneShot(config2, command, dependencies = {}) {
29434
29650
  input: command.input,
29435
29651
  name: command.name,
29436
29652
  requestId: command.requestId,
29653
+ ...dependencies.onProgress ? {
29654
+ onProgress: (progress) => dependencies.onProgress?.({ ...progress, requestId: command.requestId })
29655
+ } : {},
29437
29656
  ...command.timeoutMs ? { timeoutMs: command.timeoutMs } : {}
29438
29657
  });
29439
29658
  return operationResult(command, outcome);
@@ -29619,18 +29838,21 @@ function resolveDownloadableCoreNodeMedia(coreNode) {
29619
29838
 
29620
29839
  // src/media/download.ts
29621
29840
  var DEFAULT_RESOLUTION_TIMEOUT_MS = 15e3;
29841
+ var DEFAULT_DOWNLOAD_ATTEMPTS = 3;
29622
29842
  var FILE_TYPE_HEADER_BYTES2 = 64 * 1024;
29623
29843
  var MediaDownloadError = class extends Error {
29624
- constructor(code, message, exitCode, retryable) {
29844
+ constructor(code, message, exitCode, retryable, details = {}) {
29625
29845
  super(message);
29626
29846
  this.code = code;
29627
29847
  this.exitCode = exitCode;
29628
29848
  this.retryable = retryable;
29849
+ this.details = details;
29629
29850
  this.name = "MediaDownloadError";
29630
29851
  }
29631
29852
  code;
29632
29853
  exitCode;
29633
29854
  retryable;
29855
+ details;
29634
29856
  };
29635
29857
  async function runCanvasMediaDownload(config2, command, dependencies = {}) {
29636
29858
  const requestedAbsolutePath = path6.resolve(dependencies.cwd ?? process.cwd(), command.outputPath);
@@ -29645,42 +29867,80 @@ async function runCanvasMediaDownload(config2, command, dependencies = {}) {
29645
29867
  } catch (error51) {
29646
29868
  return failureResult2(command, error51, requestedAbsolutePath);
29647
29869
  }
29648
- let client;
29649
- let media;
29650
- try {
29651
- client = dependencies.mediaDownloadClientFactory?.(config2, command.projectId) ?? new CanvasClient(config2, command.projectId);
29652
- try {
29653
- await client.connect(normalizeCanvasClientName(command.clientName));
29654
- } catch {
29655
- throw new MediaDownloadError(
29656
- "canvas-connection-failed",
29657
- "Unable to establish a ready Project Canvas context.",
29658
- 2,
29659
- true
29660
- );
29661
- }
29662
- throwIfCancelled(dependencies.signal);
29663
- media = await resolveMediaSource(client, command, dependencies.randomUUID);
29664
- } catch (error51) {
29665
- return failureResult2(command, error51, outputTarget.absolutePath);
29666
- } finally {
29667
- 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
+ );
29668
29885
  }
29669
- try {
29670
- const file2 = await transferMedia(config2, media, outputTarget, command, dependencies);
29671
- return {
29672
- exitCode: 0,
29673
- output: {
29674
- file: file2,
29675
- format: "az8.canvas.media-download.v1",
29676
- media: { coreNodeId: media.coreNodeId, mediaType: media.mediaType },
29677
- projectId: command.projectId,
29678
- 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);
29679
29914
  }
29680
- };
29681
- } catch (error51) {
29682
- 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
+ }
29683
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)));
29684
29944
  }
29685
29945
  function formatCanvasMediaDownloadResult(result3) {
29686
29946
  return `${JSON.stringify(result3.output, null, 2)}
@@ -29696,7 +29956,8 @@ async function resolveMediaSource(client, command, randomUUID4) {
29696
29956
  "view-node-media-unavailable",
29697
29957
  safeCanvasMessage(error51),
29698
29958
  1,
29699
- false
29959
+ false,
29960
+ { cause: sanitizeDownloadCause(error51), phase: "source-resolution" }
29700
29961
  );
29701
29962
  }
29702
29963
  } else {
@@ -29714,7 +29975,8 @@ async function resolveMediaSource(client, command, randomUUID4) {
29714
29975
  "core-node-resolution-failed",
29715
29976
  outcome.receipt.error?.message ?? `Core Node ${coreNodeId} could not be resolved.`,
29716
29977
  indeterminate ? 2 : 1,
29717
- indeterminate
29978
+ indeterminate,
29979
+ { phase: "core-node-resolution" }
29718
29980
  );
29719
29981
  }
29720
29982
  const coreNode = parseCoreNode(outcome.receipt.result?.coreNode, coreNodeId);
@@ -29733,7 +29995,8 @@ function parseCoreNode(value, expectedId) {
29733
29995
  "core-node-response-invalid",
29734
29996
  `Core Node ${expectedId} returned an invalid record.`,
29735
29997
  2,
29736
- true
29998
+ true,
29999
+ { phase: "core-node-resolution" }
29737
30000
  );
29738
30001
  }
29739
30002
  const record2 = value;
@@ -29742,7 +30005,8 @@ function parseCoreNode(value, expectedId) {
29742
30005
  "core-node-response-invalid",
29743
30006
  `Core Node ${expectedId} returned an invalid record.`,
29744
30007
  2,
29745
- true
30008
+ true,
30009
+ { phase: "core-node-resolution" }
29746
30010
  );
29747
30011
  }
29748
30012
  return value;
@@ -29753,7 +30017,8 @@ async function prepareOutputTarget(requestedPath, overwrite, cwd) {
29753
30017
  "invalid-output-path",
29754
30018
  "Download output path must not be blank.",
29755
30019
  1,
29756
- false
30020
+ false,
30021
+ { phase: "output-setup" }
29757
30022
  );
29758
30023
  }
29759
30024
  const absolutePath = path6.resolve(cwd, requestedPath);
@@ -29766,7 +30031,8 @@ async function prepareOutputTarget(requestedPath, overwrite, cwd) {
29766
30031
  "output-parent-unavailable",
29767
30032
  "Download output parent directory does not exist or cannot be accessed.",
29768
30033
  1,
29769
- false
30034
+ false,
30035
+ { phase: "output-setup" }
29770
30036
  );
29771
30037
  }
29772
30038
  if (!parent.isDirectory()) {
@@ -29774,7 +30040,8 @@ async function prepareOutputTarget(requestedPath, overwrite, cwd) {
29774
30040
  "output-parent-not-directory",
29775
30041
  "Download output parent is not a directory.",
29776
30042
  1,
29777
- false
30043
+ false,
30044
+ { phase: "output-setup" }
29778
30045
  );
29779
30046
  }
29780
30047
  let existing;
@@ -29786,7 +30053,8 @@ async function prepareOutputTarget(requestedPath, overwrite, cwd) {
29786
30053
  "output-target-unavailable",
29787
30054
  "Download output target cannot be inspected.",
29788
30055
  1,
29789
- false
30056
+ false,
30057
+ { phase: "output-setup" }
29790
30058
  );
29791
30059
  }
29792
30060
  }
@@ -29795,7 +30063,8 @@ async function prepareOutputTarget(requestedPath, overwrite, cwd) {
29795
30063
  "output-target-exists",
29796
30064
  "Download output target already exists; pass --overwrite to replace a regular file.",
29797
30065
  1,
29798
- false
30066
+ false,
30067
+ { phase: "output-setup" }
29799
30068
  );
29800
30069
  }
29801
30070
  if (existing && (!existing.isFile() || existing.isSymbolicLink())) {
@@ -29803,7 +30072,8 @@ async function prepareOutputTarget(requestedPath, overwrite, cwd) {
29803
30072
  "output-target-not-regular-file",
29804
30073
  "Download output target must be a regular file and must not be a symbolic link.",
29805
30074
  1,
29806
- false
30075
+ false,
30076
+ { phase: "output-setup" }
29807
30077
  );
29808
30078
  }
29809
30079
  return { absolutePath, existed: Boolean(existing), parentPath };
@@ -29814,9 +30084,9 @@ async function transferMedia(config2, media, outputTarget, command, dependencies
29814
30084
  let response;
29815
30085
  try {
29816
30086
  response = await config2.fetch(media.url, { redirect: "follow", signal: deadline.signal });
29817
- } catch {
30087
+ } catch (error51) {
29818
30088
  deadline.cleanup();
29819
- throw transferFailure(deadline);
30089
+ throw transferFailure(deadline, error51);
29820
30090
  }
29821
30091
  if (!response.ok) {
29822
30092
  deadline.cleanup();
@@ -29825,7 +30095,8 @@ async function transferMedia(config2, media, outputTarget, command, dependencies
29825
30095
  "media-http-error",
29826
30096
  `Media server returned HTTP ${response.status}.`,
29827
30097
  retryable ? 2 : 1,
29828
- retryable
30098
+ retryable,
30099
+ { phase: "media-transfer" }
29829
30100
  );
29830
30101
  }
29831
30102
  const contentType = normalizeContentType(response.headers.get("content-type"));
@@ -29835,8 +30106,9 @@ async function transferMedia(config2, media, outputTarget, command, dependencies
29835
30106
  throw new MediaDownloadError(
29836
30107
  "media-content-type-mismatch",
29837
30108
  `Media response is ${headerFamily}, but Core Node ${media.coreNodeId} is ${media.mediaType}.`,
29838
- 2,
29839
- true
30109
+ 1,
30110
+ false,
30111
+ { phase: "media-verification" }
29840
30112
  );
29841
30113
  }
29842
30114
  if (contentType === "text/html" || contentType === "application/json") {
@@ -29844,8 +30116,9 @@ async function transferMedia(config2, media, outputTarget, command, dependencies
29844
30116
  throw new MediaDownloadError(
29845
30117
  "media-content-type-mismatch",
29846
30118
  "Media response returned a non-media document.",
29847
- 2,
29848
- true
30119
+ 1,
30120
+ false,
30121
+ { phase: "media-verification" }
29849
30122
  );
29850
30123
  }
29851
30124
  const remoteContentLength = readContentLength(response);
@@ -29878,7 +30151,8 @@ async function transferMedia(config2, media, outputTarget, command, dependencies
29878
30151
  "output-finalize-failed",
29879
30152
  "Temporary download file could not be finalized.",
29880
30153
  1,
29881
- false
30154
+ false,
30155
+ { phase: "output-finalize" }
29882
30156
  );
29883
30157
  }
29884
30158
  let detected;
@@ -29889,7 +30163,8 @@ async function transferMedia(config2, media, outputTarget, command, dependencies
29889
30163
  "media-verification-failed",
29890
30164
  "Downloaded media could not be inspected safely.",
29891
30165
  1,
29892
- false
30166
+ false,
30167
+ { phase: "media-verification" }
29893
30168
  );
29894
30169
  }
29895
30170
  validateDownloadedMedia(media, contentType, detected);
@@ -29911,12 +30186,13 @@ async function transferMedia(config2, media, outputTarget, command, dependencies
29911
30186
  } catch (error51) {
29912
30187
  await temporary.handle.close().catch(() => void 0);
29913
30188
  if (error51 instanceof MediaDownloadError) throw error51;
29914
- if (deadline.signal.aborted) throw transferFailure(deadline);
30189
+ if (deadline.signal.aborted) throw transferFailure(deadline, error51);
29915
30190
  throw new MediaDownloadError(
29916
30191
  "media-download-internal-failed",
29917
30192
  "Media download could not complete its local processing.",
29918
30193
  2,
29919
- true
30194
+ true,
30195
+ { cause: sanitizeDownloadCause(error51), phase: "media-transfer" }
29920
30196
  );
29921
30197
  } finally {
29922
30198
  deadline.cleanup();
@@ -29967,7 +30243,8 @@ async function streamResponseToFile(response, handle, deadline, expectedBytes) {
29967
30243
  "media-response-body-missing",
29968
30244
  "Media response has no readable body.",
29969
30245
  2,
29970
- true
30246
+ true,
30247
+ { phase: "media-transfer" }
29971
30248
  );
29972
30249
  }
29973
30250
  const reader = response.body.getReader();
@@ -29985,16 +30262,17 @@ async function streamResponseToFile(response, handle, deadline, expectedBytes) {
29985
30262
  }
29986
30263
  } catch (error51) {
29987
30264
  if (error51 instanceof MediaDownloadError) throw error51;
29988
- if (deadline.signal.aborted) throw transferFailure(deadline);
30265
+ if (deadline.signal.aborted) throw transferFailure(deadline, error51);
29989
30266
  if (readFsCode2(error51)) {
29990
30267
  throw new MediaDownloadError(
29991
30268
  "output-write-failed",
29992
30269
  "Temporary download file could not be written completely.",
29993
30270
  1,
29994
- false
30271
+ false,
30272
+ { cause: sanitizeDownloadCause(error51), phase: "output-write" }
29995
30273
  );
29996
30274
  }
29997
- throw transferFailure(deadline);
30275
+ throw transferFailure(deadline, error51);
29998
30276
  } finally {
29999
30277
  reader.releaseLock();
30000
30278
  }
@@ -30003,7 +30281,8 @@ async function streamResponseToFile(response, handle, deadline, expectedBytes) {
30003
30281
  "media-content-length-mismatch",
30004
30282
  `Media response declared ${expectedBytes} bytes but delivered ${bytes}.`,
30005
30283
  2,
30006
- true
30284
+ true,
30285
+ { phase: "media-transfer" }
30007
30286
  );
30008
30287
  }
30009
30288
  return { bytes, sha256: hash2.digest("hex") };
@@ -30017,7 +30296,8 @@ async function writeAll(handle, value) {
30017
30296
  "output-write-stalled",
30018
30297
  "Temporary download file stopped accepting data.",
30019
30298
  1,
30020
- false
30299
+ false,
30300
+ { phase: "output-write" }
30021
30301
  );
30022
30302
  }
30023
30303
  offset += bytesWritten;
@@ -30029,8 +30309,9 @@ function validateDownloadedMedia(media, contentType, detected) {
30029
30309
  throw new MediaDownloadError(
30030
30310
  "media-content-mismatch",
30031
30311
  `Downloaded file is ${detectedFamily}, but Core Node ${media.coreNodeId} is ${media.mediaType}.`,
30032
- 2,
30033
- true
30312
+ 1,
30313
+ false,
30314
+ { phase: "media-verification" }
30034
30315
  );
30035
30316
  }
30036
30317
  const headerMatches = readMediaFamily(contentType) === media.mediaType;
@@ -30039,8 +30320,9 @@ function validateDownloadedMedia(media, contentType, detected) {
30039
30320
  throw new MediaDownloadError(
30040
30321
  "media-content-unverified",
30041
30322
  `Downloaded file could not be verified as ${media.mediaType} media.`,
30042
- 2,
30043
- true
30323
+ 1,
30324
+ false,
30325
+ { phase: "media-verification" }
30044
30326
  );
30045
30327
  }
30046
30328
  }
@@ -30058,14 +30340,16 @@ async function commitTemporaryFile(temporaryPath, outputTarget, overwrite) {
30058
30340
  "output-target-exists",
30059
30341
  "Download output target was created before the download could be committed.",
30060
30342
  1,
30061
- false
30343
+ false,
30344
+ { phase: "output-commit" }
30062
30345
  );
30063
30346
  }
30064
30347
  throw new MediaDownloadError(
30065
30348
  "output-commit-failed",
30066
30349
  "Verified media could not be committed to the output path.",
30067
30350
  1,
30068
- false
30351
+ false,
30352
+ { cause: sanitizeDownloadCause(error51), phase: "output-commit" }
30069
30353
  );
30070
30354
  }
30071
30355
  }
@@ -30089,18 +30373,29 @@ function createTransferSignal(external, timeoutMs) {
30089
30373
  signal: controller.signal
30090
30374
  };
30091
30375
  }
30092
- function transferFailure(deadline) {
30376
+ function transferFailure(deadline, cause) {
30093
30377
  if (deadline.external?.aborted) {
30094
- 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
+ );
30095
30387
  }
30096
30388
  if (deadline.isTimedOut()) {
30097
- 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
+ });
30098
30392
  }
30099
30393
  return new MediaDownloadError(
30100
30394
  "media-network-failed",
30101
30395
  "Media response could not be downloaded completely.",
30102
30396
  2,
30103
- true
30397
+ true,
30398
+ { cause: sanitizeDownloadCause(cause), phase: "media-transfer" }
30104
30399
  );
30105
30400
  }
30106
30401
  function throwIfCancelled(signal) {
@@ -30129,23 +30424,111 @@ function readMediaFamily(value) {
30129
30424
  if (value?.startsWith("video/")) return "video";
30130
30425
  return null;
30131
30426
  }
30132
- function failureResult2(command, error51, absolutePath) {
30133
- 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);
30134
30429
  return {
30135
30430
  exitCode: failure.exitCode,
30136
30431
  output: {
30137
30432
  error: {
30138
30433
  code: failure.code,
30139
30434
  message: safeCanvasMessage(failure),
30140
- retryable: failure.retryable
30435
+ ...failure.details.phase ? { phase: failure.details.phase } : {},
30436
+ retryable: failure.retryable,
30437
+ ...failure.details.cause ? { cause: failure.details.cause } : {}
30141
30438
  },
30142
30439
  format: "az8.canvas.media-download.v1",
30440
+ localState: {
30441
+ destinationCommitted: false,
30442
+ partialFileRetained: false
30443
+ },
30143
30444
  outputPath: absolutePath ?? path6.resolve(command.outputPath),
30144
30445
  projectId: command.projectId,
30446
+ attempts: { completed: attemptsCompleted, maximum: maximumAttempts },
30447
+ remoteState: "unverified",
30145
30448
  source: command.source
30146
30449
  }
30147
30450
  };
30148
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
+ }
30149
30532
  function readFsCode2(error51) {
30150
30533
  return typeof error51 === "object" && error51 !== null && "code" in error51 ? String(error51.code) : void 0;
30151
30534
  }
@@ -45064,7 +45447,7 @@ function registerCanvasCommands(program2, dependencies, setResult) {
45064
45447
  // package.json
45065
45448
  var package_default = {
45066
45449
  name: "@everfir/az8-cli",
45067
- version: "0.3.0-preview.2",
45450
+ version: "0.3.0-preview.3",
45068
45451
  description: "Semantic Project Canvas client for AZ8 Studio agents.",
45069
45452
  license: "UNLICENSED",
45070
45453
  type: "module",
@@ -45427,7 +45810,8 @@ var AgentSessionManager = class {
45427
45810
  descriptor2.socketPath,
45428
45811
  request,
45429
45812
  options.timeoutMs ?? 5e3,
45430
- options.signal
45813
+ options.signal,
45814
+ options.onProgress
45431
45815
  ).catch((error51) => {
45432
45816
  if (isRemoteFailure(error51)) throw error51;
45433
45817
  throw managerError(
@@ -45795,7 +46179,11 @@ function registerSessionCommands(program2, dependencies, setResult) {
45795
46179
  requestId,
45796
46180
  ...options.timeoutMs ? { timeoutMs: options.timeoutMs } : {}
45797
46181
  },
45798
- { 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
+ }
45799
46187
  );
45800
46188
  const receipt = readOperationReceipt(value);
45801
46189
  const exitCode = receipt.status === "succeeded" ? 0 : receipt.error?.outcome === "indeterminate" ? 2 : 1;
@@ -45999,6 +46387,12 @@ process.once("SIGINT", abortOnSignal);
45999
46387
  process.once("SIGTERM", abortOnSignal);
46000
46388
  try {
46001
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
+ },
46002
46396
  persistentCanvas: runPersistentCanvas,
46003
46397
  signal: abortController.signal
46004
46398
  });