@quantiya/codevibe-core 2.0.73 → 2.0.75

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.
@@ -8605,15 +8605,9 @@ function parseCompanionSyncState(value, environment, now = Date.now()) {
8605
8605
  let date = new Date(value.observedAtEpochMs), resetAt = Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 1);
8606
8606
  (value.periodId !== date.toISOString().slice(0, 7) || value.resetAtEpochMs !== resetAt || value.validUntilEpochMs > resetAt) && invalid2(), count(value.usage.usageRevision);
8607
8607
  let gap2 = count(value.usage.gapStartedAtEpochMs), periodStart = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1);
8608
- gap2 !== 0n && (gap2 < BigInt(periodStart) || gap2 > BigInt(value.observedAtEpochMs)) && invalid2();
8609
- let state = "ACTIVE";
8610
- for (let meter of METERS) {
8611
- let committed = count(value.usage[`${meter}Committed`]);
8612
- count(value.usage[`${meter}Reserved`]);
8613
- let limit = count(value.usage[`${meter}Limit`]), pausedAtLimit = count(value.usage[`${meter}PauseLimit`]);
8614
- pausedAtLimit > 0n && gap2 === 0n && invalid2(), value.usage[`${meter}Limit`] !== value.limits[meter] && invalid2(), meter === "syncUnits" && (committed >= limit || pausedAtLimit >= limit) ? state = "PAUSED" : committed * 5n >= limit * 4n && state !== "PAUSED" && (state = "WARNING");
8615
- }
8616
- value.usage.syncState !== state && invalid2();
8608
+ gap2 !== 0n && (gap2 < BigInt(periodStart) || gap2 > BigInt(value.observedAtEpochMs)) && invalid2(), ["ACTIVE", "WARNING", "PAUSED"].includes(value.usage.syncState) || invalid2();
8609
+ for (let meter of METERS)
8610
+ count(value.usage[`${meter}Committed`]), count(value.usage[`${meter}Reserved`]), count(value.usage[`${meter}Limit`]), count(value.usage[`${meter}PauseLimit`]) > 0n && gap2 === 0n && invalid2(), value.usage[`${meter}Limit`] !== value.limits[meter] && invalid2();
8617
8611
  } else (value.protocol === "SYNC_V2" && value.mode !== "SHADOW" || value.mode === "ROLLBACK_FREEZE" && value.protocol !== "FROZEN" || value.usage !== null || value.periodId !== null || value.resetAtEpochMs !== null) && invalid2();
8618
8612
  if (value.accountingGeneration === null && (value.mode !== "LEGACY" || value.controlGeneration !== 0) && invalid2(), value.accountingGeneration !== null && value.controlGeneration < 1 && invalid2(), value.usage?.syncState === "PAUSED") {
8619
8613
  let notice2 = value.quotaNotice;
@@ -10084,9 +10078,7 @@ function createPromptRetentionJournal(store) {
10084
10078
  await fence(), receipt2.status === "RERAISE_REQUIRED" && (row3.entries.push({ ...receipt2, reRaiseToken: (0, import_node_crypto12.randomUUID)() }), await save(), await settle(promptId2));
10085
10079
  }
10086
10080
  });
10087
- }, remove: () => store.runExclusive(async (fence) => {
10088
- await fence(), await store.remove(), await fence();
10089
- }) });
10081
+ }, remove: () => store.remove() });
10090
10082
  }
10091
10083
 
10092
10084
  // src/appsync/sync-prompt-publication.ts
@@ -10534,19 +10526,10 @@ var sourceCursorId = (scope, stream) => stableUuid("codevibe-sync-source-cursor-
10534
10526
  let scope = this.sessionScope;
10535
10527
  if (!scope) return invalid7();
10536
10528
  let id5 = stableUuid("codevibe-sync-desktop-registration-v1\0", [scope.sessionId, scope.sessionGenerationId]);
10537
- this.options.assertAccount(), await this.ensureDirectory();
10538
- let owner = await this.lock(`registration-${id5}.lockdir`).acquire();
10539
- try {
10540
- return await this.withStore(async (key) => {
10541
- let row3 = await this.readValue(id5, key, ".desktop", (value) => decodeDesktopSyncRegistration(value, invalid7));
10542
- if (!row3) return !1;
10543
- if (row3.operationId !== id5) return invalid7();
10544
- if (this.requireScope(row3), this.options.assertAccount(), !await owner.reassertOwnership()) throw new SyncJournalError("SYNC_JOURNAL_LOCK_LOST");
10545
- return await import_node_fs5.promises.unlink(this.file(id5, ".desktop")), await this.syncDirectory(this.directory), !0;
10546
- });
10547
- } finally {
10548
- await owner.release();
10549
- }
10529
+ return this.removeBookkeepingRecord(id5, ".desktop", [`registration-${id5}.lockdir`], async (key) => {
10530
+ let row3 = await this.readValue(id5, key, ".desktop", (value) => decodeDesktopSyncRegistration(value, invalid7));
10531
+ return row3 ? row3.operationId !== id5 ? invalid7() : (this.requireScope(row3), !0) : !1;
10532
+ });
10550
10533
  }
10551
10534
  /** Provider queues share the existing encrypted/account-fenced capacity.
10552
10535
  * Native identity and generation are immutable: no automatic re-homing. */
@@ -10839,13 +10822,12 @@ var sourceCursorId = (scope, stream) => stableUuid("codevibe-sync-source-cursor-
10839
10822
  let sealed = this.seal(row3, key);
10840
10823
  await this.checkCapacity(Buffer.byteLength(sealed), id5 + ".retention"), await this.write(id5, sealed, ".retention");
10841
10824
  }),
10842
- remove: () => this.withStore(async (key) => {
10843
- let row3 = await this.readValue(id5, key, ".retention", decodePromptRetention);
10844
- if (row3) {
10845
- if (row3.operationId !== id5 || !this.matchesScope(row3)) return invalid7();
10846
- this.options.assertAccount(), await import_node_fs5.promises.unlink(this.file(id5, ".retention")), await this.syncDirectory(this.directory);
10847
- }
10848
- })
10825
+ remove: async () => {
10826
+ await this.removeBookkeepingRecord(id5, ".retention", ["prompt-retention-" + id5 + ".lockdir"], async (key) => {
10827
+ let row3 = await this.readValue(id5, key, ".retention", decodePromptRetention);
10828
+ return row3 ? row3.operationId !== id5 || !this.matchesScope(row3) ? invalid7() : !0 : !1;
10829
+ });
10830
+ }
10849
10831
  });
10850
10832
  }
10851
10833
  /** Canonical INACTIVE proof and joined publication owners are prerequisites.
@@ -10909,23 +10891,11 @@ var sourceCursorId = (scope, stream) => stableUuid("codevibe-sync-source-cursor-
10909
10891
  }
10910
10892
  return ids2.sort();
10911
10893
  }), removed = 0;
10912
- for (let id5 of ids) {
10913
- let owner = await this.lock("deferred-dispatch-" + id5 + ".lockdir").acquire();
10914
- try {
10915
- await this.withStore(async (key) => {
10916
- let row3 = await read(id5, key);
10917
- if (row3) {
10918
- if (!deferredScopeMatches(row3, scope)) return invalid7();
10919
- if (!row3.entries.some((entry5) => entry5.notice)) {
10920
- if (this.options.assertAccount(), !await owner.reassertOwnership()) throw new SyncJournalError("SYNC_JOURNAL_LOCK_LOST");
10921
- await import_node_fs5.promises.unlink(this.file(id5, ".deferred")), await this.syncDirectory(this.directory), removed++;
10922
- }
10923
- }
10924
- });
10925
- } finally {
10926
- await owner.release();
10927
- }
10928
- }
10894
+ for (let id5 of ids)
10895
+ await this.removeBookkeepingRecord(id5, ".deferred", ["deferred-dispatch-" + id5 + ".lockdir"], async (key) => {
10896
+ let row3 = await read(id5, key);
10897
+ return row3 ? deferredScopeMatches(row3, scope) ? !row3.entries.some((entry5) => entry5.notice) : invalid7() : !1;
10898
+ }) && removed++;
10929
10899
  return removed;
10930
10900
  }
10931
10901
  /** One small encrypted row for unresolved inbound inputs. Completed cloud
@@ -10975,23 +10945,30 @@ var sourceCursorId = (scope, stream) => stableUuid("codevibe-sync-source-cursor-
10975
10945
  let scope = this.sessionScope;
10976
10946
  if (!scope) return invalid7();
10977
10947
  let id5 = stableUuid("codevibe-sync-inbound-v1\0", [scope.sessionId, scope.sessionGenerationId]);
10948
+ return this.removeBookkeepingRecord(id5, ".inbox", [`inbound-dispatch-${id5}.lockdir`, `inbound-record-${id5}.lockdir`], async (key) => {
10949
+ let row3 = await this.readValue(id5, key, ".inbox", decodeDesktopInbound);
10950
+ return row3 ? row3.operationId !== id5 ? invalid7() : (this.requireScope(row3), !0) : !1;
10951
+ });
10952
+ }
10953
+ /** The encrypted record is also the recovery index for these outer locks.
10954
+ * Release them strictly BEFORE unlinking it, while the store stays fenced.
10955
+ * A failed release preserves the record and the caller's retirement intent. */
10956
+ async removeBookkeepingRecord(id5, suffix, names, removable) {
10978
10957
  this.options.assertAccount(), await this.ensureDirectory();
10979
- let dispatch = await this.lock(`inbound-dispatch-${id5}.lockdir`).acquire();
10958
+ let owners = [], release = async () => {
10959
+ let failed = (await Promise.allSettled(owners.splice(0).reverse().map((owner) => owner.release()))).find((result) => result.status === "rejected");
10960
+ if (failed?.status === "rejected") throw failed.reason;
10961
+ };
10980
10962
  try {
10981
- let record2 = await this.lock(`inbound-record-${id5}.lockdir`).acquire();
10982
- try {
10983
- return await this.withStore(async (key) => {
10984
- let row3 = await this.readValue(id5, key, ".inbox", decodeDesktopInbound);
10985
- if (!row3) return !1;
10986
- if (row3.operationId !== id5) return invalid7();
10987
- if (this.requireScope(row3), this.options.assertAccount(), !await dispatch.reassertOwnership() || !await record2.reassertOwnership()) throw new SyncJournalError("SYNC_JOURNAL_LOCK_LOST");
10988
- return await import_node_fs5.promises.unlink(this.file(id5, ".inbox")), await this.syncDirectory(this.directory), !0;
10989
- });
10990
- } finally {
10991
- await record2.release();
10992
- }
10963
+ for (let name of names) owners.push(await this.lock(name, !0).acquire());
10964
+ return await this.withStore(async (key, storeFence) => {
10965
+ let remove = await removable(key);
10966
+ this.options.assertAccount();
10967
+ for (let owner of owners) if (!await owner.reassertOwnership()) throw new SyncJournalError("SYNC_JOURNAL_LOCK_LOST");
10968
+ return await release(), await storeFence(), remove ? (await import_node_fs5.promises.unlink(this.file(id5, suffix)), await this.syncDirectory(this.directory), await storeFence(), !0) : !1;
10969
+ });
10993
10970
  } finally {
10994
- await dispatch.release();
10971
+ await release();
10995
10972
  }
10996
10973
  }
10997
10974
  /** Exact empty-source GC only after the owner has joined all its emitters
@@ -11050,7 +11027,34 @@ var sourceCursorId = (scope, stream) => stableUuid("codevibe-sync-source-cursor-
11050
11027
  if (errorCode(error) !== "ENOENT") throw error;
11051
11028
  return this.options.assertAccount(), !1;
11052
11029
  }
11053
- return this.withStore(async (key) => (await this.retiredSourcePlan(key, expectedSessionKeyGen)).sources.length > 0);
11030
+ return await this.withStore(async (key) => (await this.retiredSourcePlan(key, expectedSessionKeyGen)).sources.length > 0) ? !0 : this.hasSessionRegistrationState();
11031
+ }
11032
+ /** Presence is not deletion authority. Callers still need their joined
11033
+ * terminal owner and fresh retirement confirmation before cleanup. */
11034
+ async hasSessionRegistrationState() {
11035
+ if (!this.sessionScope) return invalid7();
11036
+ this.options.assertAccount();
11037
+ try {
11038
+ await import_node_fs5.promises.lstat(this.directory);
11039
+ } catch (error) {
11040
+ if (errorCode(error) !== "ENOENT") throw error;
11041
+ return this.options.assertAccount(), !1;
11042
+ }
11043
+ return this.withStore(async (key) => {
11044
+ let records = [
11045
+ [".desktop", (value) => decodeDesktopSyncRegistration(value, invalid7)],
11046
+ [".inbox", decodeDesktopInbound],
11047
+ [".retention", decodePromptRetention],
11048
+ [".echo", decodeMobileEcho],
11049
+ [".deferred", decodeDeferredInputs]
11050
+ ];
11051
+ for (let [suffix, decode] of records) for (let name of await this.recordNames(suffix)) {
11052
+ let row3 = await this.readValue(name.slice(0, -suffix.length), key, suffix, decode);
11053
+ if (!row3) return invalid7();
11054
+ if (this.matchesScope(row3)) return !0;
11055
+ }
11056
+ return !1;
11057
+ });
11054
11058
  }
11055
11059
  /** Durable stop/join intent -> confirmed source cleanup -> control ACKs ->
11056
11060
  * intent removal. Network I/O never holds the account-store lock. */
@@ -11976,12 +11980,12 @@ var UUID9 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f
11976
11980
  throw new SyncEventClientError("INVALID_SYNC_INPUT");
11977
11981
  };
11978
11982
  function toolWindow(scope, id5, value) {
11979
- let envelope = ToolActivityOutboxEnvelopeSchema.parse(value);
11980
- (id5 !== scope.sessionId || envelope.backendSessionId !== id5 || envelope.syncSessionGenerationId !== scope.sessionGenerationId || !/^[A-Za-z0-9_-]{1,128}$/.test(envelope.clientEventId) || !/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z$/.test(envelope.windowStart) || !Number.isFinite(Date.parse(envelope.windowStart)) || new Date(envelope.windowStart).toISOString() !== envelope.windowStart) && invalid8();
11983
+ let envelope = ToolActivityOutboxEnvelopeSchema.parse(value), timestamp5 = /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\dZ$/.test(envelope.windowStart) ? envelope.windowStart.slice(0, -1) + ".000Z" : envelope.windowStart;
11984
+ (id5 !== scope.sessionId || envelope.backendSessionId !== id5 || envelope.syncSessionGenerationId !== scope.sessionGenerationId || !/^[A-Za-z0-9_-]{1,128}$/.test(envelope.clientEventId) || !/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z$/.test(timestamp5) || !Number.isFinite(Date.parse(timestamp5)) || new Date(timestamp5).toISOString() !== timestamp5) && invalid8();
11981
11985
  let bytes = (0, import_node_crypto16.createHash)("sha256").update("codevibe-sync-tool-window-v1\0").update(JSON.stringify([scope.sessionId, scope.sessionGenerationId, envelope.clientEventId])).digest().subarray(0, 16);
11982
11986
  bytes[6] = bytes[6] & 15 | 128, bytes[8] = bytes[8] & 63 | 128;
11983
11987
  let hex = bytes.toString("hex"), operationId2 = [hex.slice(0, 8), hex.slice(8, 12), hex.slice(12, 16), hex.slice(16, 20), hex.slice(20)].join("-");
11984
- return { envelope, operationId: operationId2 };
11988
+ return { envelope, operationId: operationId2, timestamp: timestamp5 };
11985
11989
  }
11986
11990
  async function retireToolActivityWindows(options) {
11987
11991
  let scope = { ...options.sessionScope };
@@ -12004,7 +12008,7 @@ function createToolActivitySyncTransport(options) {
12004
12008
  protocol: "SYNC_V2",
12005
12009
  sessionGenerationId: scope.sessionGenerationId,
12006
12010
  send: async (id5, value) => {
12007
- let { envelope, operationId: operationId2 } = window(id5, value);
12011
+ let { envelope, operationId: operationId2, timestamp: timestamp5 } = window(id5, value);
12008
12012
  try {
12009
12013
  return await writer.retry(operationId2);
12010
12014
  } catch (error) {
@@ -12017,7 +12021,7 @@ function createToolActivitySyncTransport(options) {
12017
12021
  ...scope,
12018
12022
  clientOperationId: operationId2,
12019
12023
  type: "TOOL_ACTIVITY",
12020
- timestamp: envelope.windowStart,
12024
+ timestamp: timestamp5,
12021
12025
  expectedSessionKeyGen: context.sessionKeyGen ?? SESSION_KEY_GEN_ABSENT_SENTINEL,
12022
12026
  isEncrypted: !0,
12023
12027
  encryptionVersion: 1,
@@ -14503,6 +14507,13 @@ var AppSyncClient = class _AppSyncClient {
14503
14507
  async retireDesktopSyncRegistration(options, retired) {
14504
14508
  let userId = this.getCurrentUserId();
14505
14509
  if (this.assertSyncAccount(userId), !retired || retired.userId !== userId || retired.sessionId !== options.sessionId || retired.status !== "INACTIVE" /* INACTIVE */ || retired.sessionGenerationId !== options.sessionGenerationId) throw new SyncEventClientError("SYNC_SESSION_UNAVAILABLE");
14510
+ return this.removeDesktopSyncRegistrationState(options);
14511
+ }
14512
+ /** Both callers have terminal authority and have joined their read owners:
14513
+ * the returned INACTIVE session above, or a freshly confirmed durable intent. */
14514
+ async removeDesktopSyncRegistrationState(options) {
14515
+ let userId = this.getCurrentUserId();
14516
+ this.assertSyncAccount(userId);
14506
14517
  let writer = this.desktopSyncWriter({ root: options.root, sessionScope: options }), result = await writer.removeDesktopRegistration();
14507
14518
  return await writer.removeInbound(), await writer.removeDeferredInputs(), await writer.removeMobileEchoes(), await writer.promptRetention().remove(), this.assertSyncAccount(userId), result;
14508
14519
  }
@@ -14937,7 +14948,7 @@ var AppSyncClient = class _AppSyncClient {
14937
14948
  return this.resolvePromptPublication({ sessionId: input.sessionId, promptId: input.promptId }, async () => (await fence(), _AppSyncClient.throwIfAborted(signal), await this.resolveDesktopSyncPrompt(input, signal), await fence(), !0), { ...options, signal });
14938
14949
  }
14939
14950
  /** Explicit session-owned restart recovery. Inventory is bounded by the
14940
- * account store's 256-file cap; empty inventory does no cloud I/O. Reuses
14951
+ * account store's file cap; empty inventory does no cloud I/O. Reuses
14941
14952
  * original ownership/generation, not current Session or encryption state.
14942
14953
  * No failure/age/quota condition deletes an outstanding intent. */
14943
14954
  recoverPromptResolutions(options, signal) {
@@ -15108,12 +15119,12 @@ var AppSyncClient = class _AppSyncClient {
15108
15119
  await work, this.assertSyncAccount(userId), _AppSyncClient.throwIfAborted(signal);
15109
15120
  }
15110
15121
  async finishRecordedSessionRetirement(record2, options, fence, signal) {
15111
- let { input } = record2, { root } = options, userId = this.getCurrentUserId(), writer = this.desktopSyncWriter({ root, sessionScope: input });
15122
+ let { input } = record2, { root } = options, userId = this.getCurrentUserId(), writer = this.desktopSyncWriter({ root, sessionScope: input }), confirmed = !1, confirm = async (request3) => {
15123
+ let receipt2 = await this.confirmDesktopSyncRetirement(request3, signal);
15124
+ return confirmed = !0, receipt2;
15125
+ };
15112
15126
  await fence(), _AppSyncClient.throwIfAborted(signal);
15113
- let result = await writer.removeRetiredSources(
15114
- input.expectedSessionKeyGen,
15115
- (request3) => this.confirmDesktopSyncRetirement(request3, signal)
15116
- ), cleanupDeferred = !1;
15127
+ let result = await writer.removeRetiredSources(input.expectedSessionKeyGen, confirm), cleanupDeferred = !1;
15117
15128
  if (record2.toolOutbox) {
15118
15129
  await fence();
15119
15130
  let tools = await retireToolActivityWindows({
@@ -15121,14 +15132,14 @@ var AppSyncClient = class _AppSyncClient {
15121
15132
  writer,
15122
15133
  outbox: new ToolActivityOutbox({ ...record2.toolOutbox, logger }),
15123
15134
  expectedSessionKeyGen: input.expectedSessionKeyGen,
15124
- confirm: (input2) => this.confirmDesktopSyncRetirement(input2, signal),
15135
+ confirm,
15125
15136
  deferIfPaged: options.deferOversizedTools
15126
15137
  });
15127
15138
  result.sourcesRemoved += tools.windowsRemoved, result.operationsRemoved += tools.operationsRemoved, cleanupDeferred = tools.hasMore === !0;
15128
15139
  }
15129
15140
  for (let input2 of await writer.promptResolutions())
15130
15141
  await fence(), this.assertSyncAccount(userId), _AppSyncClient.throwIfAborted(signal), await writer.runPromptResolution(input2, async (saved, fence2) => (await fence2(), await this.resolveDesktopSyncPrompt(saved, signal), await fence2(), !0), { existingOnly: !0 });
15131
- return await fence(), this.assertSyncAccount(userId), _AppSyncClient.throwIfAborted(signal), { ...result, ...cleanupDeferred && { cleanupDeferred: !0 } };
15142
+ return await fence(), this.assertSyncAccount(userId), _AppSyncClient.throwIfAborted(signal), await writer.hasSessionRegistrationState() && (confirmed || await confirm(input), await fence(), _AppSyncClient.throwIfAborted(signal), await this.removeDesktopSyncRegistrationState({ ...input, root })), await fence(), this.assertSyncAccount(userId), _AppSyncClient.throwIfAborted(signal), { ...result, ...cleanupDeferred && { cleanupDeferred: !0 } };
15132
15143
  }
15133
15144
  /** Wire-only PROMPT_OWNERSHIP transition. It cannot create an Event. Plugin
15134
15145
  * preimage authority does not require the retired Session's encryption key;
@@ -69151,6 +69162,14 @@ ${section}`);
69151
69162
  sessionId: this.deps.session.sessionId,
69152
69163
  summary: summaryModel
69153
69164
  }
69165
+ }).then((result) => {
69166
+ if (result.emitted) return;
69167
+ let code = result.error?.code;
69168
+ logger.warn("[QuorumLoop] Companion audit summary was not acknowledged (non-fatal)", {
69169
+ taskId,
69170
+ reason: result.reason,
69171
+ ...typeof code == "string" && /^[A-Z][A-Z0-9_]{0,79}$/.test(code) ? { errorCode: code } : {}
69172
+ });
69154
69173
  }).catch((wErr) => {
69155
69174
  logger.warn("[QuorumLoop] Companion audit summary emission failed (non-fatal)", {
69156
69175
  taskId,
@@ -81268,9 +81287,9 @@ function createTaskProgressRelay(deps) {
81268
81287
  let minIntervalMs = deps.minIntervalMs ?? DEFAULT_MIN_RELAY_INTERVAL_MS, lastRelayedAt = 0, lastMessage = null, lastPhase = null, lastTaskId = null, pendingTimeout = null, pendingFlush = null, emit = async (event, message2) => {
81269
81288
  pendingTimeout && (clearTimeout(pendingTimeout), pendingTimeout = null), pendingFlush = null;
81270
81289
  let taskId = event.progressTaskId ?? ("taskId" in event && typeof event.taskId == "string" ? event.taskId : void 0);
81271
- lastRelayedAt = Date.now(), lastMessage = message2, lastPhase = event.phase, lastTaskId = taskId ?? null, RUNTIME_LABELLED_PHASES.has(event.phase) ? logger.info("[task-progress-relay] relayed", { phase: event.phase, message: message2 }) : logger.debug("[task-progress-relay] relayed", { phase: event.phase });
81290
+ lastRelayedAt = Date.now(), lastMessage = message2, lastPhase = event.phase, lastTaskId = taskId ?? null;
81272
81291
  try {
81273
- return await deps.emitShellEvent({
81292
+ let result = await deps.emitShellEvent({
81274
81293
  sessionId: deps.sessionId,
81275
81294
  type: "NOTIFICATION",
81276
81295
  source: "DESKTOP",
@@ -81282,7 +81301,16 @@ function createTaskProgressRelay(deps) {
81282
81301
  message: message2,
81283
81302
  ...taskId ? { taskId } : {}
81284
81303
  }
81285
- }), !0;
81304
+ });
81305
+ if (!result.emitted) {
81306
+ let code = result.error?.code;
81307
+ return logger.warn("[task-progress-relay] progress notification was not acknowledged", {
81308
+ phase: event.phase,
81309
+ reason: result.reason,
81310
+ ...typeof code == "string" && /^[A-Z][A-Z0-9_]{0,79}$/.test(code) ? { errorCode: code } : {}
81311
+ }), !1;
81312
+ }
81313
+ return RUNTIME_LABELLED_PHASES.has(event.phase) ? logger.info("[task-progress-relay] relayed", { phase: event.phase, message: message2 }) : logger.debug("[task-progress-relay] relayed", { phase: event.phase }), !0;
81286
81314
  } catch (err) {
81287
81315
  return logger.debug("[task-progress-relay] failed to emit progress notification (non-fatal)", {
81288
81316
  phase: event.phase,
@@ -1,7 +1,7 @@
1
1
  import type { TaskProgressEvent } from './task-progress';
2
- import type { ShellEventEmit } from './emit-shell-event';
2
+ import type { ShellEventEmit, ShellEventEmitResult } from './emit-shell-event';
3
3
  export interface TaskProgressRelayDeps {
4
- emitShellEvent: (args: ShellEventEmit) => Promise<unknown>;
4
+ emitShellEvent: (args: ShellEventEmit) => Promise<ShellEventEmitResult>;
5
5
  sessionId: string;
6
6
  minIntervalMs?: number;
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quantiya/codevibe-core",
3
- "version": "2.0.73",
3
+ "version": "2.0.75",
4
4
  "description": "Core library for CodeVibe plugins - shared keychain, crypto, AppSync, and auth functionality",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",