@quantiya/codevibe-core 2.0.73 → 2.0.74

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.
@@ -10084,9 +10084,7 @@ function createPromptRetentionJournal(store) {
10084
10084
  await fence(), receipt2.status === "RERAISE_REQUIRED" && (row3.entries.push({ ...receipt2, reRaiseToken: (0, import_node_crypto12.randomUUID)() }), await save(), await settle(promptId2));
10085
10085
  }
10086
10086
  });
10087
- }, remove: () => store.runExclusive(async (fence) => {
10088
- await fence(), await store.remove(), await fence();
10089
- }) });
10087
+ }, remove: () => store.remove() });
10090
10088
  }
10091
10089
 
10092
10090
  // src/appsync/sync-prompt-publication.ts
@@ -10534,19 +10532,10 @@ var sourceCursorId = (scope, stream) => stableUuid("codevibe-sync-source-cursor-
10534
10532
  let scope = this.sessionScope;
10535
10533
  if (!scope) return invalid7();
10536
10534
  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
- }
10535
+ return this.removeBookkeepingRecord(id5, ".desktop", [`registration-${id5}.lockdir`], async (key) => {
10536
+ let row3 = await this.readValue(id5, key, ".desktop", (value) => decodeDesktopSyncRegistration(value, invalid7));
10537
+ return row3 ? row3.operationId !== id5 ? invalid7() : (this.requireScope(row3), !0) : !1;
10538
+ });
10550
10539
  }
10551
10540
  /** Provider queues share the existing encrypted/account-fenced capacity.
10552
10541
  * Native identity and generation are immutable: no automatic re-homing. */
@@ -10839,13 +10828,12 @@ var sourceCursorId = (scope, stream) => stableUuid("codevibe-sync-source-cursor-
10839
10828
  let sealed = this.seal(row3, key);
10840
10829
  await this.checkCapacity(Buffer.byteLength(sealed), id5 + ".retention"), await this.write(id5, sealed, ".retention");
10841
10830
  }),
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
- })
10831
+ remove: async () => {
10832
+ await this.removeBookkeepingRecord(id5, ".retention", ["prompt-retention-" + id5 + ".lockdir"], async (key) => {
10833
+ let row3 = await this.readValue(id5, key, ".retention", decodePromptRetention);
10834
+ return row3 ? row3.operationId !== id5 || !this.matchesScope(row3) ? invalid7() : !0 : !1;
10835
+ });
10836
+ }
10849
10837
  });
10850
10838
  }
10851
10839
  /** Canonical INACTIVE proof and joined publication owners are prerequisites.
@@ -10909,23 +10897,11 @@ var sourceCursorId = (scope, stream) => stableUuid("codevibe-sync-source-cursor-
10909
10897
  }
10910
10898
  return ids2.sort();
10911
10899
  }), 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
- }
10900
+ for (let id5 of ids)
10901
+ await this.removeBookkeepingRecord(id5, ".deferred", ["deferred-dispatch-" + id5 + ".lockdir"], async (key) => {
10902
+ let row3 = await read(id5, key);
10903
+ return row3 ? deferredScopeMatches(row3, scope) ? !row3.entries.some((entry5) => entry5.notice) : invalid7() : !1;
10904
+ }) && removed++;
10929
10905
  return removed;
10930
10906
  }
10931
10907
  /** One small encrypted row for unresolved inbound inputs. Completed cloud
@@ -10975,23 +10951,30 @@ var sourceCursorId = (scope, stream) => stableUuid("codevibe-sync-source-cursor-
10975
10951
  let scope = this.sessionScope;
10976
10952
  if (!scope) return invalid7();
10977
10953
  let id5 = stableUuid("codevibe-sync-inbound-v1\0", [scope.sessionId, scope.sessionGenerationId]);
10954
+ return this.removeBookkeepingRecord(id5, ".inbox", [`inbound-dispatch-${id5}.lockdir`, `inbound-record-${id5}.lockdir`], async (key) => {
10955
+ let row3 = await this.readValue(id5, key, ".inbox", decodeDesktopInbound);
10956
+ return row3 ? row3.operationId !== id5 ? invalid7() : (this.requireScope(row3), !0) : !1;
10957
+ });
10958
+ }
10959
+ /** The encrypted record is also the recovery index for these outer locks.
10960
+ * Release them strictly BEFORE unlinking it, while the store stays fenced.
10961
+ * A failed release preserves the record and the caller's retirement intent. */
10962
+ async removeBookkeepingRecord(id5, suffix, names, removable) {
10978
10963
  this.options.assertAccount(), await this.ensureDirectory();
10979
- let dispatch = await this.lock(`inbound-dispatch-${id5}.lockdir`).acquire();
10964
+ let owners = [], release = async () => {
10965
+ let failed = (await Promise.allSettled(owners.splice(0).reverse().map((owner) => owner.release()))).find((result) => result.status === "rejected");
10966
+ if (failed?.status === "rejected") throw failed.reason;
10967
+ };
10980
10968
  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
- }
10969
+ for (let name of names) owners.push(await this.lock(name, !0).acquire());
10970
+ return await this.withStore(async (key, storeFence) => {
10971
+ let remove = await removable(key);
10972
+ this.options.assertAccount();
10973
+ for (let owner of owners) if (!await owner.reassertOwnership()) throw new SyncJournalError("SYNC_JOURNAL_LOCK_LOST");
10974
+ 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;
10975
+ });
10993
10976
  } finally {
10994
- await dispatch.release();
10977
+ await release();
10995
10978
  }
10996
10979
  }
10997
10980
  /** Exact empty-source GC only after the owner has joined all its emitters
@@ -11050,7 +11033,34 @@ var sourceCursorId = (scope, stream) => stableUuid("codevibe-sync-source-cursor-
11050
11033
  if (errorCode(error) !== "ENOENT") throw error;
11051
11034
  return this.options.assertAccount(), !1;
11052
11035
  }
11053
- return this.withStore(async (key) => (await this.retiredSourcePlan(key, expectedSessionKeyGen)).sources.length > 0);
11036
+ return await this.withStore(async (key) => (await this.retiredSourcePlan(key, expectedSessionKeyGen)).sources.length > 0) ? !0 : this.hasSessionRegistrationState();
11037
+ }
11038
+ /** Presence is not deletion authority. Callers still need their joined
11039
+ * terminal owner and fresh retirement confirmation before cleanup. */
11040
+ async hasSessionRegistrationState() {
11041
+ if (!this.sessionScope) return invalid7();
11042
+ this.options.assertAccount();
11043
+ try {
11044
+ await import_node_fs5.promises.lstat(this.directory);
11045
+ } catch (error) {
11046
+ if (errorCode(error) !== "ENOENT") throw error;
11047
+ return this.options.assertAccount(), !1;
11048
+ }
11049
+ return this.withStore(async (key) => {
11050
+ let records = [
11051
+ [".desktop", (value) => decodeDesktopSyncRegistration(value, invalid7)],
11052
+ [".inbox", decodeDesktopInbound],
11053
+ [".retention", decodePromptRetention],
11054
+ [".echo", decodeMobileEcho],
11055
+ [".deferred", decodeDeferredInputs]
11056
+ ];
11057
+ for (let [suffix, decode] of records) for (let name of await this.recordNames(suffix)) {
11058
+ let row3 = await this.readValue(name.slice(0, -suffix.length), key, suffix, decode);
11059
+ if (!row3) return invalid7();
11060
+ if (this.matchesScope(row3)) return !0;
11061
+ }
11062
+ return !1;
11063
+ });
11054
11064
  }
11055
11065
  /** Durable stop/join intent -> confirmed source cleanup -> control ACKs ->
11056
11066
  * intent removal. Network I/O never holds the account-store lock. */
@@ -11976,12 +11986,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
11986
  throw new SyncEventClientError("INVALID_SYNC_INPUT");
11977
11987
  };
11978
11988
  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();
11989
+ 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;
11990
+ (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
11991
  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
11992
  bytes[6] = bytes[6] & 15 | 128, bytes[8] = bytes[8] & 63 | 128;
11983
11993
  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 };
11994
+ return { envelope, operationId: operationId2, timestamp: timestamp5 };
11985
11995
  }
11986
11996
  async function retireToolActivityWindows(options) {
11987
11997
  let scope = { ...options.sessionScope };
@@ -12004,7 +12014,7 @@ function createToolActivitySyncTransport(options) {
12004
12014
  protocol: "SYNC_V2",
12005
12015
  sessionGenerationId: scope.sessionGenerationId,
12006
12016
  send: async (id5, value) => {
12007
- let { envelope, operationId: operationId2 } = window(id5, value);
12017
+ let { envelope, operationId: operationId2, timestamp: timestamp5 } = window(id5, value);
12008
12018
  try {
12009
12019
  return await writer.retry(operationId2);
12010
12020
  } catch (error) {
@@ -12017,7 +12027,7 @@ function createToolActivitySyncTransport(options) {
12017
12027
  ...scope,
12018
12028
  clientOperationId: operationId2,
12019
12029
  type: "TOOL_ACTIVITY",
12020
- timestamp: envelope.windowStart,
12030
+ timestamp: timestamp5,
12021
12031
  expectedSessionKeyGen: context.sessionKeyGen ?? SESSION_KEY_GEN_ABSENT_SENTINEL,
12022
12032
  isEncrypted: !0,
12023
12033
  encryptionVersion: 1,
@@ -14503,6 +14513,13 @@ var AppSyncClient = class _AppSyncClient {
14503
14513
  async retireDesktopSyncRegistration(options, retired) {
14504
14514
  let userId = this.getCurrentUserId();
14505
14515
  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");
14516
+ return this.removeDesktopSyncRegistrationState(options);
14517
+ }
14518
+ /** Both callers have terminal authority and have joined their read owners:
14519
+ * the returned INACTIVE session above, or a freshly confirmed durable intent. */
14520
+ async removeDesktopSyncRegistrationState(options) {
14521
+ let userId = this.getCurrentUserId();
14522
+ this.assertSyncAccount(userId);
14506
14523
  let writer = this.desktopSyncWriter({ root: options.root, sessionScope: options }), result = await writer.removeDesktopRegistration();
14507
14524
  return await writer.removeInbound(), await writer.removeDeferredInputs(), await writer.removeMobileEchoes(), await writer.promptRetention().remove(), this.assertSyncAccount(userId), result;
14508
14525
  }
@@ -14937,7 +14954,7 @@ var AppSyncClient = class _AppSyncClient {
14937
14954
  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
14955
  }
14939
14956
  /** 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
14957
+ * account store's file cap; empty inventory does no cloud I/O. Reuses
14941
14958
  * original ownership/generation, not current Session or encryption state.
14942
14959
  * No failure/age/quota condition deletes an outstanding intent. */
14943
14960
  recoverPromptResolutions(options, signal) {
@@ -15108,12 +15125,12 @@ var AppSyncClient = class _AppSyncClient {
15108
15125
  await work, this.assertSyncAccount(userId), _AppSyncClient.throwIfAborted(signal);
15109
15126
  }
15110
15127
  async finishRecordedSessionRetirement(record2, options, fence, signal) {
15111
- let { input } = record2, { root } = options, userId = this.getCurrentUserId(), writer = this.desktopSyncWriter({ root, sessionScope: input });
15128
+ let { input } = record2, { root } = options, userId = this.getCurrentUserId(), writer = this.desktopSyncWriter({ root, sessionScope: input }), confirmed = !1, confirm = async (request3) => {
15129
+ let receipt2 = await this.confirmDesktopSyncRetirement(request3, signal);
15130
+ return confirmed = !0, receipt2;
15131
+ };
15112
15132
  await fence(), _AppSyncClient.throwIfAborted(signal);
15113
- let result = await writer.removeRetiredSources(
15114
- input.expectedSessionKeyGen,
15115
- (request3) => this.confirmDesktopSyncRetirement(request3, signal)
15116
- ), cleanupDeferred = !1;
15133
+ let result = await writer.removeRetiredSources(input.expectedSessionKeyGen, confirm), cleanupDeferred = !1;
15117
15134
  if (record2.toolOutbox) {
15118
15135
  await fence();
15119
15136
  let tools = await retireToolActivityWindows({
@@ -15121,14 +15138,14 @@ var AppSyncClient = class _AppSyncClient {
15121
15138
  writer,
15122
15139
  outbox: new ToolActivityOutbox({ ...record2.toolOutbox, logger }),
15123
15140
  expectedSessionKeyGen: input.expectedSessionKeyGen,
15124
- confirm: (input2) => this.confirmDesktopSyncRetirement(input2, signal),
15141
+ confirm,
15125
15142
  deferIfPaged: options.deferOversizedTools
15126
15143
  });
15127
15144
  result.sourcesRemoved += tools.windowsRemoved, result.operationsRemoved += tools.operationsRemoved, cleanupDeferred = tools.hasMore === !0;
15128
15145
  }
15129
15146
  for (let input2 of await writer.promptResolutions())
15130
15147
  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 } };
15148
+ 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
15149
  }
15133
15150
  /** Wire-only PROMPT_OWNERSHIP transition. It cannot create an Event. Plugin
15134
15151
  * preimage authority does not require the retired Session's encryption key;
@@ -69151,6 +69168,14 @@ ${section}`);
69151
69168
  sessionId: this.deps.session.sessionId,
69152
69169
  summary: summaryModel
69153
69170
  }
69171
+ }).then((result) => {
69172
+ if (result.emitted) return;
69173
+ let code = result.error?.code;
69174
+ logger.warn("[QuorumLoop] Companion audit summary was not acknowledged (non-fatal)", {
69175
+ taskId,
69176
+ reason: result.reason,
69177
+ ...typeof code == "string" && /^[A-Z][A-Z0-9_]{0,79}$/.test(code) ? { errorCode: code } : {}
69178
+ });
69154
69179
  }).catch((wErr) => {
69155
69180
  logger.warn("[QuorumLoop] Companion audit summary emission failed (non-fatal)", {
69156
69181
  taskId,
@@ -81268,9 +81293,9 @@ function createTaskProgressRelay(deps) {
81268
81293
  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
81294
  pendingTimeout && (clearTimeout(pendingTimeout), pendingTimeout = null), pendingFlush = null;
81270
81295
  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 });
81296
+ lastRelayedAt = Date.now(), lastMessage = message2, lastPhase = event.phase, lastTaskId = taskId ?? null;
81272
81297
  try {
81273
- return await deps.emitShellEvent({
81298
+ let result = await deps.emitShellEvent({
81274
81299
  sessionId: deps.sessionId,
81275
81300
  type: "NOTIFICATION",
81276
81301
  source: "DESKTOP",
@@ -81282,7 +81307,16 @@ function createTaskProgressRelay(deps) {
81282
81307
  message: message2,
81283
81308
  ...taskId ? { taskId } : {}
81284
81309
  }
81285
- }), !0;
81310
+ });
81311
+ if (!result.emitted) {
81312
+ let code = result.error?.code;
81313
+ return logger.warn("[task-progress-relay] progress notification was not acknowledged", {
81314
+ phase: event.phase,
81315
+ reason: result.reason,
81316
+ ...typeof code == "string" && /^[A-Z][A-Z0-9_]{0,79}$/.test(code) ? { errorCode: code } : {}
81317
+ }), !1;
81318
+ }
81319
+ 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
81320
  } catch (err) {
81287
81321
  return logger.debug("[task-progress-relay] failed to emit progress notification (non-fatal)", {
81288
81322
  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.74",
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",