@zq-silk/yui 0.6.0 → 0.6.2

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.
Files changed (150) hide show
  1. package/README.md +5 -5
  2. package/dist/agent/managedRuntimeEnvironment.js +2 -1
  3. package/dist/cli/commandCatalog.js +251 -13
  4. package/dist/cli/updateOrchestrator.js +8 -0
  5. package/dist/cli/updatePorts.js +76 -22
  6. package/dist/cli.js +264 -20
  7. package/dist/commands/configCommands.js +83 -9
  8. package/dist/commands/controllerCommands.js +103 -0
  9. package/dist/commands/deliveryGuardPreflight.js +30 -0
  10. package/dist/commands/durableJobCommands.js +231 -0
  11. package/dist/commands/executionAuditCommands.js +193 -0
  12. package/dist/commands/grantCommands.js +374 -0
  13. package/dist/commands/projectCommands.js +119 -81
  14. package/dist/commands/releaseCommands.js +444 -0
  15. package/dist/commands/resourcesCommands.js +274 -0
  16. package/dist/commands/sessionCommands.js +104 -0
  17. package/dist/commands/taskActor.js +117 -0
  18. package/dist/commands/taskChangeSetCommands.js +60 -0
  19. package/dist/commands/taskCommands.js +618 -202
  20. package/dist/commands/taskCompletionGate.js +78 -1
  21. package/dist/commands/taskContextCommand.js +33 -6
  22. package/dist/commands/taskInputCommands.js +1 -1
  23. package/dist/commands/taskIntegrationCommands.js +136 -33
  24. package/dist/commands/taskIntegrationQueueCommands.js +228 -0
  25. package/dist/commands/taskNextActionCommand.js +100 -0
  26. package/dist/commands/taskOverlapCommands.js +120 -0
  27. package/dist/commands/taskOverviewCommand.js +36 -8
  28. package/dist/commands/telemetryCommands.js +330 -0
  29. package/dist/commands/workflowCommands.js +415 -0
  30. package/dist/config/yuiConfig.js +62 -0
  31. package/dist/controller/clientRuntime.js +42 -1
  32. package/dist/controller/controller.js +402 -56
  33. package/dist/controller/controllerMain.js +25 -2
  34. package/dist/controller/domainIdentity.js +16 -8
  35. package/dist/controller/fileSchedulerStoreAdapter.js +423 -31
  36. package/dist/controller/handoverCandidate.js +168 -0
  37. package/dist/controller/jobClient.js +102 -0
  38. package/dist/controller/jobControl.js +613 -0
  39. package/dist/controller/jobSupervisor.js +498 -0
  40. package/dist/controller/providerHookRunFence.js +34 -5
  41. package/dist/controller/resourceCleanupLinux.js +18 -9
  42. package/dist/controller/resourceInventoryLinux.js +90 -39
  43. package/dist/controller/runtime.js +165 -15
  44. package/dist/controller/runtimeEventInbox.js +234 -57
  45. package/dist/controller/runtimeEventProcessor.js +297 -58
  46. package/dist/controller/sessionOwnerReconciliation.js +321 -0
  47. package/dist/core/controllerServer.js +416 -27
  48. package/dist/core/controllerTelemetry.js +167 -0
  49. package/dist/doctor/doctor.js +113 -16
  50. package/dist/domain/validation.js +9 -0
  51. package/dist/execution/executionGroup.js +40 -3
  52. package/dist/executor/agentExecutor.js +6 -3
  53. package/dist/executor/effectiveLaunch.js +52 -0
  54. package/dist/executor/executorRegistry.js +50 -0
  55. package/dist/executor/fileRoleLaunchPlanner.js +61 -6
  56. package/dist/grant/capabilityGrant.js +282 -0
  57. package/dist/integration/changeSet.js +16 -3
  58. package/dist/integration/changeSetManifest.js +46 -0
  59. package/dist/integration/gitIntegrationService.js +528 -147
  60. package/dist/integration/integrationAttempt.js +54 -5
  61. package/dist/integration/integrationQueueEntry.js +221 -0
  62. package/dist/integration/integrationQueueService.js +955 -0
  63. package/dist/integration/manifestTags.js +99 -0
  64. package/dist/integration/overlapDiagnostics.js +211 -0
  65. package/dist/job/durableJob.js +449 -0
  66. package/dist/job/jobRunner.js +350 -0
  67. package/dist/lifecycle/exactRunTerminalization.js +24 -2
  68. package/dist/lifecycle/providerErrorClass.js +126 -0
  69. package/dist/message/message.js +16 -3
  70. package/dist/observability/executionAudit.js +545 -0
  71. package/dist/observability/faultClassification.js +160 -0
  72. package/dist/observability/runtimeIdentity.js +367 -0
  73. package/dist/release/fakeReleasePorts.js +55 -0
  74. package/dist/release/releaseHandover.js +475 -0
  75. package/dist/release/releaseIdempotencyStore.js +165 -0
  76. package/dist/release/releaseWorkflow.js +459 -0
  77. package/dist/release/releaseWorkflowEngine.js +688 -0
  78. package/dist/release/releaseWorkflowPorts.js +1720 -0
  79. package/dist/release/runtimeRelease.js +495 -0
  80. package/dist/release/workflowFileLock.js +218 -0
  81. package/dist/repository/gitWorkspace.js +177 -1
  82. package/dist/repository/projectMaintenanceLock.js +315 -0
  83. package/dist/repository/taskWorkspaceCoordinator.js +87 -17
  84. package/dist/repository/taskWorkspacePreparer.js +1091 -517
  85. package/dist/resources/autoResourceGc.js +116 -0
  86. package/dist/resources/liveReferences.js +574 -0
  87. package/dist/resources/resourceDiscovery.js +477 -0
  88. package/dist/resources/resourceGc.js +645 -0
  89. package/dist/resources/resourceRegistrar.js +256 -0
  90. package/dist/resources/resourceRegistry.js +150 -0
  91. package/dist/resources/resourceRegistryStore.js +41 -0
  92. package/dist/resources/resourceTypes.js +42 -0
  93. package/dist/resources/sqliteResourceRegistry.js +111 -0
  94. package/dist/review/reviewConfig.js +10 -0
  95. package/dist/review/reviewFinding.js +240 -0
  96. package/dist/review/reviewFindingLedger.js +545 -0
  97. package/dist/review/reviewOutcomeClassifier.js +61 -0
  98. package/dist/review/reviewRound.js +56 -4
  99. package/dist/run/agentRun.js +80 -4
  100. package/dist/run/providerRetry.js +84 -0
  101. package/dist/run/providerRetryConfig.js +63 -0
  102. package/dist/run/yieldReceipt.js +65 -0
  103. package/dist/runtime/exactControlPlane.js +79 -2
  104. package/dist/runtime/index.js +4 -0
  105. package/dist/runtime/sessionOwnerIdentity.js +269 -0
  106. package/dist/runtime/sessionOwnerRegistry.js +132 -0
  107. package/dist/runtime/sessionReconciliation.js +93 -0
  108. package/dist/runtime/sessionTerminationGuard.js +211 -0
  109. package/dist/runtime/taskRuntimeIsolation.js +13 -0
  110. package/dist/runtime/tmuxAdapters.js +34 -1
  111. package/dist/scheduler/actionability.js +155 -0
  112. package/dist/scheduler/activeRoleRunDelivery.js +14 -5
  113. package/dist/scheduler/activeTaskProgress.js +60 -0
  114. package/dist/scheduler/leaderWakeupProcessor.js +22 -11
  115. package/dist/scheduler/roleRunStall.js +135 -29
  116. package/dist/scheduler/taskExecutionProjection.js +11 -0
  117. package/dist/storage/compatibleTaskStore.js +112 -5
  118. package/dist/storage/migration/productionRegistry.js +736 -1
  119. package/dist/storage/sqliteSchema.js +264 -3
  120. package/dist/storage/sqliteStore.js +487 -13
  121. package/dist/storage/storeRpc.js +21 -0
  122. package/dist/storage/taskStore.js +974 -21
  123. package/dist/storage/upgrade/homeClassification.js +120 -2
  124. package/dist/storage/upgrade/migrationReceipt.js +67 -0
  125. package/dist/storage/upgrade/pseudoLayoutRepair.js +241 -0
  126. package/dist/storage/upgrade/recordVersions.js +10 -1
  127. package/dist/storage/upgrade/sqliteMigrationTarget.js +58 -6
  128. package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +290 -0
  129. package/dist/storage/upgrade/sqliteStateMigration.js +258 -2
  130. package/dist/storage/upgrade/upgradeOrchestrator.js +482 -16
  131. package/dist/task/deliveryGuard.js +226 -0
  132. package/dist/task/nextAction.js +738 -0
  133. package/dist/task/repairWave.js +137 -0
  134. package/dist/task/taskRecordReference.js +6 -1
  135. package/dist/telemetry/sqliteTelemetryStore.js +387 -0
  136. package/dist/telemetry/telemetryCompaction.js +251 -0
  137. package/dist/telemetry/telemetryConfig.js +64 -0
  138. package/dist/telemetry/telemetryRouter.js +32 -0
  139. package/dist/telemetry/telemetryStore.js +19 -0
  140. package/dist/telemetry/telemetryWiring.js +33 -0
  141. package/dist/tmux/tmuxManager.js +20 -1
  142. package/dist/tmux/tmuxSocketEndpoint.js +20 -0
  143. package/dist/verification/gateArtifact.js +216 -0
  144. package/dist/verification/gateArtifactStore.js +87 -0
  145. package/dist/verification/verificationGateService.js +414 -0
  146. package/dist/verification/verificationPlan.js +308 -0
  147. package/dist/workspace/gitChangeSetCapture.js +12 -2
  148. package/dist/workspace/workItemChangeSetManager.js +60 -3
  149. package/package.json +1 -1
  150. package/skills/yui-leader/SKILL.md +8 -0
@@ -66,7 +66,7 @@ export class FileRuntimeEventInbox {
66
66
  }
67
67
  enqueueProviderProgress(input) {
68
68
  const normalized = normalizeProviderProgressInput(input);
69
- return this.publish(Object.freeze({
69
+ return this.publishProgress(Object.freeze({
70
70
  schemaVersion: 1,
71
71
  id: runtimeEventId("native-turn-progress", normalized),
72
72
  type: "native-turn-progress",
@@ -74,6 +74,16 @@ export class FileRuntimeEventInbox {
74
74
  ...normalized
75
75
  }));
76
76
  }
77
+ enqueueDurableJobTerminal(input) {
78
+ const normalized = normalizeDurableJobTerminalInput(input);
79
+ return this.publish(Object.freeze({
80
+ schemaVersion: 1,
81
+ id: runtimeEventId("durable-job-terminal", normalized),
82
+ type: "durable-job-terminal",
83
+ receivedAt: this.now().toISOString(),
84
+ ...normalized
85
+ }));
86
+ }
77
87
  list() {
78
88
  if (!existsSync(this.directory))
79
89
  return [];
@@ -99,8 +109,7 @@ export class FileRuntimeEventInbox {
99
109
  }
100
110
  }
101
111
  }
102
- return events.sort((left, right) => (left.receivedAt.localeCompare(right.receivedAt)
103
- || left.id.localeCompare(right.id)));
112
+ return events.sort(compareRuntimeEvents);
104
113
  }
105
114
  read(id) {
106
115
  assertEventId(id);
@@ -145,6 +154,44 @@ export class FileRuntimeEventInbox {
145
154
  throw error;
146
155
  }
147
156
  }
157
+ acknowledgeMany(ids) {
158
+ if (ids.length === 0)
159
+ return [];
160
+ const acknowledged = [];
161
+ try {
162
+ for (const id of ids) {
163
+ assertEventId(id);
164
+ try {
165
+ unlinkSync(this.eventPath(id));
166
+ acknowledged.push(id);
167
+ }
168
+ catch (error) {
169
+ if (!isNodeError(error, "ENOENT"))
170
+ throw error;
171
+ }
172
+ }
173
+ }
174
+ finally {
175
+ // Preserve the old per-event durability guarantee even when a later
176
+ // unlink in the batch fails after earlier entries were removed.
177
+ if (acknowledged.length > 0)
178
+ fsyncDirectory(this.directory);
179
+ }
180
+ return acknowledged;
181
+ }
182
+ publishProgress(event) {
183
+ return withUpgradeCoordinationLock(this.home, () => {
184
+ this.hooks.afterAdmission?.();
185
+ const result = this.publishUnlocked(event);
186
+ if (!result.created)
187
+ return result;
188
+ const superseded = this.supersededProgressIds(event);
189
+ if (superseded.length === 0)
190
+ return result;
191
+ this.acknowledgeMany(superseded);
192
+ return { ...result, coalescedEventCount: superseded.length };
193
+ });
194
+ }
148
195
  publish(event) {
149
196
  // The fence check and the complete durable write share one sibling
150
197
  // coordination lock with upgrade's final inbox scan/copy/two-step switch.
@@ -153,45 +200,77 @@ export class FileRuntimeEventInbox {
153
200
  // UpgradeFenceError after the cutover holder releases the lock.
154
201
  return withUpgradeCoordinationLock(this.home, () => {
155
202
  this.hooks.afterAdmission?.();
156
- const content = `${JSON.stringify(event)}\n`;
157
- if (Buffer.byteLength(content, "utf8") > MAX_RUNTIME_EVENT_FILE_BYTES) {
158
- throw new RuntimeEventInboxError("RUNTIME_EVENT_TOO_LARGE", "Runtime event exceeds the durable inbox limit.");
159
- }
160
- ensureInboxDirectory(this.directory);
161
- const target = this.eventPath(event.id);
162
- const temporary = join(this.directory, `.${event.id}.tmp-${process.pid}-${randomUUID()}`);
163
- let descriptor = null;
203
+ return this.publishUnlocked(event);
204
+ });
205
+ }
206
+ publishUnlocked(event) {
207
+ const content = `${JSON.stringify(event)}\n`;
208
+ if (Buffer.byteLength(content, "utf8") > MAX_RUNTIME_EVENT_FILE_BYTES) {
209
+ throw new RuntimeEventInboxError("RUNTIME_EVENT_TOO_LARGE", "Runtime event exceeds the durable inbox limit.");
210
+ }
211
+ ensureInboxDirectory(this.directory);
212
+ const target = this.eventPath(event.id);
213
+ const temporary = join(this.directory, `.${event.id}.tmp-${process.pid}-${randomUUID()}`);
214
+ let descriptor = null;
215
+ try {
216
+ descriptor = openSync(temporary, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 0o600);
217
+ writeFileSync(descriptor, content, "utf8");
218
+ fchmodSync(descriptor, 0o600);
219
+ fsyncSync(descriptor);
220
+ closeSync(descriptor);
221
+ descriptor = null;
164
222
  try {
165
- descriptor = openSync(temporary, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, 0o600);
166
- writeFileSync(descriptor, content, "utf8");
167
- fchmodSync(descriptor, 0o600);
168
- fsyncSync(descriptor);
169
- closeSync(descriptor);
170
- descriptor = null;
171
- try {
172
- linkSync(temporary, target);
173
- unlinkSync(temporary);
174
- fsyncDirectory(this.directory);
175
- return { event, created: true };
176
- }
177
- catch (error) {
178
- if (!isNodeError(error, "EEXIST"))
179
- throw error;
180
- const existing = this.read(event.id);
181
- if (existing === null)
182
- return { event, created: false };
183
- if (!hasSameIdentity(existing, event)) {
184
- throw new RuntimeEventInboxError("RUNTIME_EVENT_CONFLICT", `Runtime event id conflicts with an existing file: ${event.id}`);
185
- }
186
- return { event: existing, created: false };
223
+ linkSync(temporary, target);
224
+ unlinkSync(temporary);
225
+ fsyncDirectory(this.directory);
226
+ return { event, created: true };
227
+ }
228
+ catch (error) {
229
+ if (!isNodeError(error, "EEXIST"))
230
+ throw error;
231
+ const existing = this.read(event.id);
232
+ if (existing === null)
233
+ return { event, created: false };
234
+ if (!hasSameIdentity(existing, event)) {
235
+ throw new RuntimeEventInboxError("RUNTIME_EVENT_CONFLICT", `Runtime event id conflicts with an existing file: ${event.id}`);
187
236
  }
237
+ return { event: existing, created: false };
188
238
  }
189
- finally {
190
- if (descriptor !== null)
191
- closeSync(descriptor);
192
- rmSync(temporary, { force: true });
239
+ }
240
+ finally {
241
+ if (descriptor !== null)
242
+ closeSync(descriptor);
243
+ rmSync(temporary, { force: true });
244
+ }
245
+ }
246
+ supersededProgressIds(event) {
247
+ const events = this.list();
248
+ const segment = semanticSegment(events, event);
249
+ const candidates = events.filter((candidate) => (candidate.type === "native-turn-progress"
250
+ && sameProgressStream(candidate, event)
251
+ && compareRuntimeEvents(candidate, segment.before) > 0
252
+ && (segment.after === undefined || compareRuntimeEvents(candidate, segment.after) < 0)));
253
+ let latestReceivedAt = "";
254
+ let greatestSequence;
255
+ for (const candidate of candidates) {
256
+ const receivedAtOrder = candidate.receivedAt.localeCompare(latestReceivedAt);
257
+ if (receivedAtOrder > 0) {
258
+ latestReceivedAt = candidate.receivedAt;
259
+ greatestSequence = candidate.sequence;
193
260
  }
194
- });
261
+ else if (receivedAtOrder === 0
262
+ && candidate.sequence !== undefined
263
+ && (greatestSequence === undefined || candidate.sequence > greatestSequence)) {
264
+ greatestSequence = candidate.sequence;
265
+ }
266
+ }
267
+ return candidates.flatMap((candidate) => (candidate.receivedAt.localeCompare(latestReceivedAt) < 0
268
+ || (candidate.receivedAt === latestReceivedAt
269
+ && candidate.sequence !== undefined
270
+ && greatestSequence !== undefined
271
+ && candidate.sequence < greatestSequence)
272
+ ? [candidate.id]
273
+ : []));
195
274
  }
196
275
  eventPath(id) {
197
276
  return join(this.directory, `${id}.json`);
@@ -221,22 +300,37 @@ export class RuntimeEventInboxError extends Error {
221
300
  }
222
301
  }
223
302
  function runtimeEventId(type, input) {
303
+ if (type === "durable-job-terminal") {
304
+ const job = input;
305
+ return `turn-${createHash("sha256").update(JSON.stringify([
306
+ 1,
307
+ type,
308
+ job.scope,
309
+ job.taskId,
310
+ job.jobId,
311
+ job.status,
312
+ job.outcome
313
+ ])).digest("hex")}`;
314
+ }
315
+ // The durable-job-terminal branch returned above; narrow the union so
316
+ // provider-only identity fields are accessible without `in` guards.
317
+ const provider = input;
224
318
  const common = [
225
319
  1,
226
320
  type,
227
- input.scope,
228
- input.taskId ?? null,
229
- input.roleName,
230
- input.agentId,
231
- input.adapterId,
232
- input.launchId ?? null,
233
- input.nativeSessionId,
234
- "turnId" in input ? input.turnId : null,
235
- input.runId ?? null,
236
- "progressId" in input ? input.progressId : null,
237
- "sequence" in input ? input.sequence ?? null : null,
238
- "sessionSource" in input ? input.sessionSource ?? null : null,
239
- "receiptId" in input ? input.receiptId ?? null : null
321
+ provider.scope,
322
+ provider.taskId ?? null,
323
+ provider.roleName,
324
+ provider.agentId,
325
+ provider.adapterId,
326
+ provider.launchId ?? null,
327
+ provider.nativeSessionId,
328
+ "turnId" in provider ? provider.turnId : null,
329
+ provider.runId ?? null,
330
+ "progressId" in provider ? provider.progressId : null,
331
+ "sequence" in provider ? provider.sequence ?? null : null,
332
+ "sessionSource" in provider ? provider.sessionSource ?? null : null,
333
+ "receiptId" in provider ? provider.receiptId ?? null : null
240
334
  ];
241
335
  return `turn-${createHash("sha256").update(JSON.stringify(common)).digest("hex")}`;
242
336
  }
@@ -357,6 +451,23 @@ function normalizeProviderProgressInput(input) {
357
451
  ...(input.sequence === undefined ? {} : { sequence: input.sequence })
358
452
  };
359
453
  }
454
+ function normalizeDurableJobTerminalInput(input) {
455
+ if (input.scope !== "task")
456
+ throw invalidEvent();
457
+ const terminalStatuses = [
458
+ "succeeded", "failed", "timed-out", "cancelled", "unknown-needs-attention"
459
+ ];
460
+ if (!terminalStatuses.includes(input.status)) {
461
+ throw invalidEvent();
462
+ }
463
+ return {
464
+ scope: "task",
465
+ taskId: requireIdentityText(input.taskId, "Task id"),
466
+ jobId: requireIdentityText(input.jobId, "Job id"),
467
+ status: input.status,
468
+ outcome: requireIdentityText(input.outcome, "Job outcome")
469
+ };
470
+ }
360
471
  function parseRuntimeEvent(value) {
361
472
  if (!isObject(value))
362
473
  throw invalidEvent();
@@ -366,6 +477,7 @@ function parseRuntimeEvent(value) {
366
477
  case "native-session-lifecycle": return parseSessionLifecycleEvent(value);
367
478
  case "native-prompt-accepted": return parsePromptAcceptedEvent(value);
368
479
  case "native-turn-progress": return parseProviderProgressEvent(value);
480
+ case "durable-job-terminal": return parseDurableJobTerminalEvent(value);
369
481
  default: throw invalidEvent();
370
482
  }
371
483
  }
@@ -420,6 +532,22 @@ function parseProviderProgressEvent(value) {
420
532
  ...normalized
421
533
  });
422
534
  }
535
+ function parseDurableJobTerminalEvent(value) {
536
+ const expected = [
537
+ "schemaVersion", "id", "type", "receivedAt", "scope", "taskId",
538
+ "jobId", "status", "outcome"
539
+ ];
540
+ if (value.schemaVersion !== 1 || !hasExactKeys(value, expected))
541
+ throw invalidEvent();
542
+ const normalized = normalizeDurableJobTerminalInput(value);
543
+ return Object.freeze({
544
+ schemaVersion: 1,
545
+ id: requireIdentityText(value.id, "Event id"),
546
+ type: "durable-job-terminal",
547
+ receivedAt: requireTimestamp(value.receivedAt),
548
+ ...normalized
549
+ });
550
+ }
423
551
  function parseCodexEvent(value) {
424
552
  const scope = value.scope;
425
553
  const expected = scope === "task"
@@ -535,17 +663,66 @@ function hasSameIdentity(left, right) {
535
663
  && left.type === right.type
536
664
  && left.scope === right.scope
537
665
  && left.taskId === right.taskId
666
+ && (!("roleName" in left) || !("roleName" in right) || left.roleName === right.roleName)
667
+ && (!("agentId" in left) || !("agentId" in right) || left.agentId === right.agentId)
668
+ && (!("adapterId" in left) || !("adapterId" in right) || left.adapterId === right.adapterId)
669
+ && (!("launchId" in left) || !("launchId" in right) || left.launchId === right.launchId)
670
+ && (!("nativeSessionId" in left)
671
+ || !("nativeSessionId" in right)
672
+ || left.nativeSessionId === right.nativeSessionId)
673
+ && (!("runId" in left) || !("runId" in right) || left.runId === right.runId)
674
+ && (!("turnId" in left) || !("turnId" in right) || left.turnId === right.turnId)
675
+ && (!("progressId" in left)
676
+ || !("progressId" in right)
677
+ || left.progressId === right.progressId)
678
+ && (!("sequence" in left) || !("sequence" in right) || left.sequence === right.sequence)
679
+ && (!("jobId" in left) || !("jobId" in right) || left.jobId === right.jobId);
680
+ }
681
+ function compareRuntimeEvents(left, right) {
682
+ return left.receivedAt.localeCompare(right.receivedAt)
683
+ || left.id.localeCompare(right.id);
684
+ }
685
+ /** Chooses one latest fact inside an already-isolated exact progress stream. */
686
+ export function compareRuntimeProgressRecency(left, right) {
687
+ const receivedAt = left.receivedAt.localeCompare(right.receivedAt);
688
+ if (receivedAt !== 0)
689
+ return receivedAt;
690
+ if (left.sequence !== undefined
691
+ && right.sequence !== undefined
692
+ && left.sequence !== right.sequence) {
693
+ return left.sequence - right.sequence;
694
+ }
695
+ // A content-derived event id is identity, not arrival evidence. Without two
696
+ // comparable provider sequences, same-timestamp facts remain incomparable.
697
+ return 0;
698
+ }
699
+ function semanticSegment(events, current) {
700
+ let before = {
701
+ receivedAt: "",
702
+ id: ""
703
+ };
704
+ let after;
705
+ for (const event of events) {
706
+ if (event.type === "native-turn-progress")
707
+ continue;
708
+ const order = compareRuntimeEvents(event, current);
709
+ if (order < 0 && compareRuntimeEvents(event, before) > 0) {
710
+ before = event;
711
+ }
712
+ else if (order > 0 && (after === undefined || compareRuntimeEvents(event, after) < 0)) {
713
+ after = event;
714
+ }
715
+ }
716
+ return { before, ...(after === undefined ? {} : { after }) };
717
+ }
718
+ function sameProgressStream(left, right) {
719
+ return left.taskId === right.taskId
538
720
  && left.roleName === right.roleName
539
721
  && left.agentId === right.agentId
540
722
  && left.adapterId === right.adapterId
541
723
  && left.launchId === right.launchId
542
724
  && left.nativeSessionId === right.nativeSessionId
543
- && left.runId === right.runId
544
- && (!("turnId" in left) || !("turnId" in right) || left.turnId === right.turnId)
545
- && (!("progressId" in left)
546
- || !("progressId" in right)
547
- || left.progressId === right.progressId)
548
- && (!("sequence" in left) || !("sequence" in right) || left.sequence === right.sequence);
725
+ && left.runId === right.runId;
549
726
  }
550
727
  function hasExactKeys(value, expected) {
551
728
  const actual = Object.keys(value).sort();