@pasko70/pibo 3.0.1 → 3.1.0

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 (122) hide show
  1. package/README.md +20 -6
  2. package/compute-image/Dockerfile +47 -0
  3. package/compute-image/Dockerfile.dockerignore +5 -0
  4. package/dist/agent-runtime/context-build.js +26 -2
  5. package/dist/agent-runtime/contract.js +78 -28
  6. package/dist/agent-runtime/portable-history.js +19 -9
  7. package/dist/agent-runtime/registry.js +8 -2
  8. package/dist/agent-runtime/routed-session.js +120 -15
  9. package/dist/agent-runtimes/codex-native/adapter.js +12 -0
  10. package/dist/agent-runtimes/codex-native/resource-delivery.js +35 -7
  11. package/dist/agent-runtimes/omp/adapter.js +3 -7
  12. package/dist/agent-runtimes/pi/adapter.js +5 -3
  13. package/dist/agent-runtimes/pi/routed-session.js +59 -12
  14. package/dist/agent-runtimes/pi/runtime.js +6 -0
  15. package/dist/apps/chat/agent-profiles.js +31 -2
  16. package/dist/apps/chat/agent-store.js +98 -6
  17. package/dist/apps/chat/chat-request-normalizers.js +31 -0
  18. package/dist/apps/chat/data/chat-data-mappers.js +61 -11
  19. package/dist/apps/chat/data/project-service.js +119 -15
  20. package/dist/apps/chat/data/room-service.js +21 -0
  21. package/dist/apps/chat/data/session-query-service.js +33 -10
  22. package/dist/apps/chat/data/timeline-query-service.js +60 -35
  23. package/dist/apps/chat/loop-api.js +10 -2
  24. package/dist/apps/chat/output-compactor.js +144 -21
  25. package/dist/apps/chat/output-event-policy.js +179 -2
  26. package/dist/apps/chat/stream.js +16 -7
  27. package/dist/apps/chat/trace-v2.js +1 -0
  28. package/dist/apps/chat/web-app.js +317 -86
  29. package/dist/apps/chat-ui/assets/{dist-BDIATCQt.js → dist-CUQJqggN.js} +1 -1
  30. package/dist/apps/chat-ui/assets/{dist-mqCviI-c.js → dist-ClUlQWYN.js} +1 -1
  31. package/dist/apps/chat-ui/assets/{dist-JnW4v8UE.js → dist-Djul9BmZ.js} +1 -1
  32. package/dist/apps/chat-ui/assets/{dist-lRiLP9rr.js → dist-GD0JGdCi.js} +1 -1
  33. package/dist/apps/chat-ui/assets/{dist-BBDMeU5-.js → dist-W5HOqHym.js} +1 -1
  34. package/dist/apps/chat-ui/assets/index-BcjkX-iP.js +228 -0
  35. package/dist/apps/chat-ui/assets/index-CmqRSbBU.css +1 -0
  36. package/dist/apps/chat-ui/index.html +2 -2
  37. package/dist/apps/chat-vscode-web/assets/index-JvrPUGvI.js +43 -0
  38. package/dist/apps/chat-vscode-web/index.html +1 -1
  39. package/dist/apps/cli-ui/InkSessionApp.js +111 -12
  40. package/dist/apps/cli-ui/cliSessionsCommand.js +34 -8
  41. package/dist/cli-session/localSessionSource.js +153 -161
  42. package/dist/cli.js +7 -11
  43. package/dist/compute/cli.js +35 -16
  44. package/dist/compute/docker.js +341 -94
  45. package/dist/config/config.js +3 -0
  46. package/dist/core/context-build.js +7 -16
  47. package/dist/core/output-persistence-retry.js +484 -0
  48. package/dist/core/output-render-sequence.js +331 -0
  49. package/dist/core/profiles.js +24 -0
  50. package/dist/core/provider-recovery.js +7 -1
  51. package/dist/core/session-model.js +37 -1
  52. package/dist/core/session-router.js +78 -110
  53. package/dist/data/ingest-service.js +91 -9
  54. package/dist/data/navigation-store.js +5 -5
  55. package/dist/data/pibo-store.js +8 -1
  56. package/dist/data/schema.js +243 -3
  57. package/dist/data/session-store.js +3 -3
  58. package/dist/debug/agents.js +91 -89
  59. package/dist/debug/index.js +46 -2
  60. package/dist/debug/pty.js +127 -48
  61. package/dist/gateway/cli.js +4 -0
  62. package/dist/gateway/web.js +8 -8
  63. package/dist/local/client.js +3 -2
  64. package/dist/loops/channel.js +3 -1
  65. package/dist/loops/cli.js +2 -1
  66. package/dist/loops/service.js +90 -11
  67. package/dist/loops/store.js +100 -74
  68. package/dist/mcp/agent-context.js +13 -26
  69. package/dist/mcp/commands/info.js +3 -1
  70. package/dist/mcp/config-command.js +46 -2
  71. package/dist/mcp/config.js +18 -4
  72. package/dist/plugins/context-files-store.js +36 -6
  73. package/dist/plugins/context-files.js +52 -1
  74. package/dist/plugins/registry.js +2 -0
  75. package/dist/plugins/user-profile-resources.js +22 -0
  76. package/dist/previews/base-url.js +34 -0
  77. package/dist/previews/config.js +2 -32
  78. package/dist/ralph/store.js +5 -0
  79. package/dist/reliability/store.js +378 -106
  80. package/dist/session-ui/terminalRows.js +8 -4
  81. package/dist/sessions/pibo-data-store.js +145 -0
  82. package/dist/sessions/store.js +42 -0
  83. package/dist/setup/cli.js +422 -6
  84. package/dist/setup/installation-profiles.js +464 -0
  85. package/dist/shared/deterministic-digest.js +94 -0
  86. package/dist/shared/trace-engine.js +54 -0
  87. package/dist/shared/trace-event-projection.js +124 -67
  88. package/dist/shared/trace-history.js +47 -12
  89. package/dist/shared/trace-live-reducer.js +23 -4
  90. package/dist/shared/trace-nodes.js +17 -28
  91. package/dist/shared/trace-order.js +41 -5
  92. package/dist/shared/trace-page-merge.js +44 -14
  93. package/dist/shared/trace-patch-nodes.js +2 -0
  94. package/dist/shared/trace-subagent-links.js +4 -1
  95. package/dist/shared/trace-tool-identity.js +15 -8
  96. package/dist/signals/projector.js +28 -6
  97. package/dist/subagents/observation-query.js +121 -0
  98. package/dist/subagents/tool.js +7 -3
  99. package/dist/tools/agent-browser-leases.js +58 -23
  100. package/dist/tools/agent-browser-wrapper.js +1 -1
  101. package/dist/tools/browser-use-leases.js +129 -16
  102. package/dist/tools/browser-use-wrapper.js +1 -1
  103. package/dist/tools/index.js +27 -11
  104. package/dist/tools/python-runtime.js +10 -3
  105. package/dist/tools/registry.js +1 -1
  106. package/dist/tools/runtime/node-backend.js +16 -5
  107. package/dist/tools/runtime/node-worker-source.js +112 -33
  108. package/dist/tools/runtime/python-backend.js +16 -5
  109. package/dist/tools/runtime/python-worker-source.js +167 -16
  110. package/dist/tools/runtime/registry.js +23 -10
  111. package/dist/user-skills/store.js +0 -1
  112. package/docs/ops/vscode-extension-release.md +9 -1
  113. package/npm-shrinkwrap.json +9 -2
  114. package/package.json +9 -2
  115. package/scripts/docker-entrypoint.sh +46 -0
  116. package/scripts/prepare-agent-browser-wrapper.sh +70 -0
  117. package/scripts/prepare-browser-use-wrapper.sh +255 -0
  118. package/dist/apps/chat-ui/assets/index-0XOOVxLP.js +0 -228
  119. package/dist/apps/chat-ui/assets/index-DV7_CgsC.css +0 -1
  120. package/dist/apps/chat-vscode-web/assets/index-Dtw2Z7jz.js +0 -43
  121. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  122. package/dist/apps/vscode-artifacts/pibo-vscode-ext-3.0.1.vsix +0 -0
@@ -0,0 +1,484 @@
1
+ import { randomUUID } from "node:crypto";
2
+ const DEFAULT_MAX_PENDING = 4_096;
3
+ const DEFAULT_MAX_ATTEMPTS = 5;
4
+ const DEFAULT_BASE_DELAY_MS = 25;
5
+ const DEFAULT_MAX_DELAY_MS = 1_000;
6
+ const DEFAULT_MAX_DEAD_LETTERS = 1_024;
7
+ const DEFAULT_QUEUE_NAME = "output-persistence";
8
+ const DEFAULT_RECOVERY_BATCH_SIZE = 100;
9
+ class LostOutputPersistenceClaimError extends Error {
10
+ constructor() {
11
+ super("Lost durable output persistence claim");
12
+ this.name = "LostOutputPersistenceClaimError";
13
+ }
14
+ }
15
+ /**
16
+ * Bounded retry with an optional durable Pibo reliability-job backing. The
17
+ * durable envelope stores only correlation metadata plus the caller-owned
18
+ * delivery phase. A recovered process supplies the executable handler again.
19
+ */
20
+ export class OutputPersistenceRetryQueue {
21
+ pending = new Map();
22
+ deadLetters = [];
23
+ maxPending;
24
+ maxAttempts;
25
+ baseDelayMs;
26
+ maxDelayMs;
27
+ maxDeadLetters;
28
+ durableStore;
29
+ queueName;
30
+ workerId;
31
+ visibilityTimeoutMs;
32
+ heartbeatIntervalMs;
33
+ recoveryBatchSize;
34
+ blockedDurableJobs = new Map();
35
+ changeWaiters = new Set();
36
+ recoveryFactory;
37
+ recoveryTimer;
38
+ changeVersion = 0;
39
+ draining = false;
40
+ disposed = false;
41
+ constructor(options = {}) {
42
+ this.maxPending = positiveInteger(options.maxPending, DEFAULT_MAX_PENDING);
43
+ this.maxAttempts = positiveInteger(options.maxAttempts, DEFAULT_MAX_ATTEMPTS);
44
+ this.baseDelayMs = positiveInteger(options.baseDelayMs, DEFAULT_BASE_DELAY_MS);
45
+ this.maxDelayMs = positiveInteger(options.maxDelayMs, DEFAULT_MAX_DELAY_MS);
46
+ this.maxDeadLetters = positiveInteger(options.maxDeadLetters, DEFAULT_MAX_DEAD_LETTERS);
47
+ this.durableStore = options.durableStore;
48
+ this.queueName = options.queueName ?? DEFAULT_QUEUE_NAME;
49
+ this.workerId = options.workerId ?? `output-persistence:${process.pid}:${randomUUID()}`;
50
+ this.visibilityTimeoutMs = positiveInteger(options.visibilityTimeoutMs, 30_000);
51
+ this.heartbeatIntervalMs = Math.min(this.visibilityTimeoutMs, positiveInteger(options.heartbeatIntervalMs, Math.max(1, Math.floor(this.visibilityTimeoutMs / 3))));
52
+ this.recoveryBatchSize = positiveInteger(options.recoveryBatchSize, DEFAULT_RECOVERY_BATCH_SIZE);
53
+ }
54
+ enqueue(job) {
55
+ if (this.disposed)
56
+ throw new Error("Output persistence retry queue is disposed");
57
+ const existing = this.pending.get(job.key);
58
+ if (existing) {
59
+ const retryWasScheduled = existing.timer !== undefined;
60
+ if (existing.timer)
61
+ clearTimeout(existing.timer);
62
+ existing.timer = undefined;
63
+ // A retry timer is published only after run() has failed. Its Promise
64
+ // bookkeeping can still be waiting for the next microtask, so clear that
65
+ // settled execution before honoring an immediate producer replay.
66
+ if (retryWasScheduled)
67
+ existing.execution = undefined;
68
+ this.startAttempt(existing, true);
69
+ return;
70
+ }
71
+ if (this.pending.size >= this.maxPending) {
72
+ this.recordDeadLetter(job, new Error("output persistence retry queue capacity exceeded"), 0, "capacity_exceeded", true);
73
+ return;
74
+ }
75
+ const payload = job.payload ?? null;
76
+ let durableJob;
77
+ if (this.durableStore) {
78
+ durableJob = this.durableStore.enqueue({
79
+ queue: this.queueName,
80
+ payload: durableEnvelope(job, payload),
81
+ maxAttempts: this.maxAttempts,
82
+ idempotencyKey: job.key,
83
+ });
84
+ }
85
+ const pending = {
86
+ ...job,
87
+ payload,
88
+ attempts: durableJob?.attempts ?? 0,
89
+ maxAttempts: durableJob?.maxAttempts ?? this.maxAttempts,
90
+ ...(durableJob ? { durableJobId: durableJob.jobId } : {}),
91
+ };
92
+ this.pending.set(job.key, pending);
93
+ this.notifyChange();
94
+ this.startAttempt(pending, false);
95
+ }
96
+ quarantine(reasonCode) {
97
+ if (this.disposed)
98
+ return;
99
+ this.recordDeadLetter({ key: `quarantine:${randomUUID()}`, payload: null, run() { } }, new Error(reasonCode), 0, reasonCode, true);
100
+ }
101
+ recover(factory) {
102
+ if (!this.durableStore)
103
+ return;
104
+ this.recoveryFactory = factory;
105
+ this.pumpRecovery();
106
+ }
107
+ flushNow() {
108
+ for (const job of [...this.pending.values()]) {
109
+ if (job.timer)
110
+ clearTimeout(job.timer);
111
+ job.timer = undefined;
112
+ this.startAttempt(job, true);
113
+ }
114
+ }
115
+ async drain() {
116
+ if (this.disposed)
117
+ return;
118
+ this.draining = true;
119
+ try {
120
+ for (;;) {
121
+ const observedVersion = this.changeVersion;
122
+ this.flushNow();
123
+ this.pumpRecovery();
124
+ const executions = [...this.pending.values()]
125
+ .map((job) => job.execution)
126
+ .filter((execution) => execution !== undefined);
127
+ if (executions.length > 0) {
128
+ await Promise.allSettled(executions);
129
+ continue;
130
+ }
131
+ const durableWorkRemains = !!this.recoveryFactory && !!this.durableStore?.hasRecoverableJobs(this.queueName, true);
132
+ if (this.pending.size === 0 && !durableWorkRemains)
133
+ return;
134
+ this.scheduleRecovery();
135
+ await this.waitForChange(observedVersion);
136
+ }
137
+ }
138
+ finally {
139
+ this.draining = false;
140
+ }
141
+ }
142
+ dispose() {
143
+ if (this.disposed)
144
+ return;
145
+ this.disposed = true;
146
+ if (this.recoveryTimer)
147
+ clearTimeout(this.recoveryTimer);
148
+ this.recoveryTimer = undefined;
149
+ for (const job of this.pending.values()) {
150
+ if (job.timer)
151
+ clearTimeout(job.timer);
152
+ this.clearHeartbeat(job);
153
+ job.abortController?.abort();
154
+ if (job.durableJobId && job.durableClaimed) {
155
+ this.durableStore?.releaseJob(job.durableJobId, this.workerId, 0, job.durableClaimToken);
156
+ }
157
+ }
158
+ this.pending.clear();
159
+ this.blockedDurableJobs.clear();
160
+ this.notifyChange();
161
+ }
162
+ debugState() {
163
+ return {
164
+ pending: this.pending.size,
165
+ activeHeartbeats: [...this.pending.values()].filter((job) => job.heartbeatTimer !== undefined).length,
166
+ deadLetters: this.deadLetters.map((entry) => ({ ...entry })),
167
+ };
168
+ }
169
+ startAttempt(job, forcePending) {
170
+ if (!this.pending.has(job.key) || this.disposed)
171
+ return;
172
+ if (job.execution) {
173
+ if (forcePending)
174
+ job.forcePendingAttempt = true;
175
+ return;
176
+ }
177
+ job.forcePendingAttempt = false;
178
+ const execution = this.attempt(job, forcePending);
179
+ job.execution = execution;
180
+ void execution.finally(() => {
181
+ if (job.execution === execution)
182
+ job.execution = undefined;
183
+ this.notifyChange();
184
+ if (job.forcePendingAttempt && this.pending.has(job.key)) {
185
+ job.forcePendingAttempt = false;
186
+ if (job.timer)
187
+ clearTimeout(job.timer);
188
+ job.timer = undefined;
189
+ this.startAttempt(job, true);
190
+ }
191
+ this.pumpRecovery();
192
+ });
193
+ }
194
+ async attempt(job, forcePending) {
195
+ if (!this.pending.has(job.key) || this.disposed)
196
+ return;
197
+ let attempt = job.attempts + 1;
198
+ if (job.durableJobId) {
199
+ const claimed = this.durableStore?.claimRecoverableJob(job.durableJobId, this.workerId, this.visibilityTimeoutMs, forcePending);
200
+ if (!claimed) {
201
+ this.reconcileClaimMiss(job);
202
+ return;
203
+ }
204
+ job.durableClaimed = true;
205
+ job.durableClaimToken = claimed.claimToken;
206
+ job.attempts = claimed.attempts;
207
+ job.maxAttempts = claimed.maxAttempts;
208
+ attempt = claimed.attempts;
209
+ const parsed = parseDurableEnvelope(claimed.payload);
210
+ if (!parsed.envelope) {
211
+ this.durableStore?.quarantineJob(claimed.jobId, parsed.reason, correlationFields(job));
212
+ this.pending.delete(job.key);
213
+ return;
214
+ }
215
+ job.payload = parsed.envelope.state;
216
+ }
217
+ else {
218
+ job.attempts = attempt;
219
+ }
220
+ const abortController = new AbortController();
221
+ job.abortController = abortController;
222
+ this.startHeartbeat(job, abortController);
223
+ try {
224
+ await job.run({
225
+ payload: job.payload,
226
+ attempt,
227
+ signal: abortController.signal,
228
+ updatePayload: (payload) => {
229
+ job.payload = payload;
230
+ if (job.durableJobId) {
231
+ const updated = this.durableStore?.updateJobPayload(job.durableJobId, this.workerId, durableEnvelope(job, payload), job.durableClaimToken);
232
+ if (!updated)
233
+ throw new LostOutputPersistenceClaimError();
234
+ }
235
+ },
236
+ });
237
+ if (abortController.signal.aborted)
238
+ throw new LostOutputPersistenceClaimError();
239
+ if (job.durableJobId && !this.durableStore?.ack(job.durableJobId, this.workerId, job.durableClaimToken)) {
240
+ if (this.durableStore?.hasLiveJob(job.durableJobId))
241
+ throw new LostOutputPersistenceClaimError();
242
+ }
243
+ this.pending.delete(job.key);
244
+ job.durableClaimed = false;
245
+ try {
246
+ job.onSuccess?.();
247
+ }
248
+ catch (error) {
249
+ console.error("[pibo] output persistence success callback failed", { ...correlation(job, attempt, "callback_failed"), queue: this.queueName }, error);
250
+ }
251
+ }
252
+ catch (error) {
253
+ if (this.disposed)
254
+ return;
255
+ job.durableClaimed = false;
256
+ if (job.durableJobId) {
257
+ const retried = error instanceof LostOutputPersistenceClaimError ? false : this.durableStore?.retry(job.durableJobId, this.workerId, {
258
+ error: errorMessage(error),
259
+ delayMs: retryDelay(job.attempts, this.baseDelayMs, this.maxDelayMs),
260
+ claimToken: job.durableClaimToken,
261
+ });
262
+ if (!retried) {
263
+ this.reconcileClaimMiss(job);
264
+ return;
265
+ }
266
+ }
267
+ if (job.attempts >= job.maxAttempts) {
268
+ this.pending.delete(job.key);
269
+ this.recordDeadLetter(job, error, job.attempts, "max_attempts", false);
270
+ return;
271
+ }
272
+ const delayMs = retryDelay(job.attempts, this.baseDelayMs, this.maxDelayMs);
273
+ console.warn("[pibo] output persistence retry scheduled", { ...correlation(job, job.attempts, "pending"), queue: this.queueName });
274
+ this.schedule(job, delayMs);
275
+ }
276
+ finally {
277
+ this.clearHeartbeat(job);
278
+ job.abortController = undefined;
279
+ job.durableClaimToken = undefined;
280
+ }
281
+ }
282
+ schedule(job, delayMs) {
283
+ if (job.timer)
284
+ clearTimeout(job.timer);
285
+ job.timer = setTimeout(() => {
286
+ job.timer = undefined;
287
+ this.notifyChange();
288
+ this.startAttempt(job, false);
289
+ }, delayMs);
290
+ job.timer.unref?.();
291
+ this.notifyChange();
292
+ }
293
+ recordDeadLetter(job, error, attempts, reason, persistDurable) {
294
+ const entry = {
295
+ key: job.key,
296
+ piboSessionId: job.piboSessionId,
297
+ eventId: job.eventId,
298
+ attempts,
299
+ error: errorMessage(error),
300
+ };
301
+ this.deadLetters.push(entry);
302
+ if (this.deadLetters.length > this.maxDeadLetters)
303
+ this.deadLetters.splice(0, this.deadLetters.length - this.maxDeadLetters);
304
+ if (persistDurable && this.durableStore) {
305
+ this.durableStore.deadLetter({
306
+ queue: this.queueName,
307
+ payload: sanitizedQuarantineEnvelope(job, reason),
308
+ attempts,
309
+ maxAttempts: this.maxAttempts,
310
+ idempotencyKey: job.key,
311
+ reason,
312
+ error: errorMessage(error),
313
+ });
314
+ }
315
+ console.error("[pibo] output persistence dead letter", { ...entry, queue: this.queueName, status: "dead" });
316
+ try {
317
+ job.onDeadLetter?.(error, attempts);
318
+ }
319
+ catch (callbackError) {
320
+ console.error("[pibo] output persistence dead-letter callback failed", { ...correlation(job, attempts, "callback_failed"), queue: this.queueName }, callbackError);
321
+ }
322
+ }
323
+ pumpRecovery() {
324
+ if (this.disposed || !this.recoveryFactory || !this.durableStore)
325
+ return;
326
+ const timestamp = Date.now();
327
+ for (const [jobId, retryAt] of this.blockedDurableJobs) {
328
+ if (retryAt <= timestamp)
329
+ this.blockedDurableJobs.delete(jobId);
330
+ }
331
+ while (this.pending.size < this.maxPending) {
332
+ const excludedJobIds = [
333
+ ...this.blockedDurableJobs.keys(),
334
+ ...[...this.pending.values()].flatMap((job) => job.durableJobId ? [job.durableJobId] : []),
335
+ ];
336
+ const jobs = this.durableStore.listRecoverableJobs({
337
+ queue: this.queueName,
338
+ limit: Math.min(this.recoveryBatchSize, this.maxPending - this.pending.size),
339
+ excludeJobIds: excludedJobIds,
340
+ includeDeferredPending: this.draining,
341
+ });
342
+ if (jobs.length === 0)
343
+ break;
344
+ let admitted = 0;
345
+ for (const durableJob of jobs) {
346
+ const parsed = parseDurableEnvelope(durableJob.payload);
347
+ if (!parsed.envelope) {
348
+ this.durableStore.quarantineJob(durableJob.jobId, parsed.reason);
349
+ continue;
350
+ }
351
+ const envelope = parsed.envelope;
352
+ if (this.pending.has(envelope.key))
353
+ continue;
354
+ let recovered;
355
+ try {
356
+ recovered = this.recoveryFactory({
357
+ key: envelope.key,
358
+ piboSessionId: envelope.piboSessionId,
359
+ eventId: envelope.eventId,
360
+ payload: envelope.state,
361
+ attempts: durableJob.attempts,
362
+ maxAttempts: durableJob.maxAttempts,
363
+ });
364
+ }
365
+ catch {
366
+ this.durableStore.quarantineJob(durableJob.jobId, "payload_invalid");
367
+ continue;
368
+ }
369
+ const pending = {
370
+ ...recovered,
371
+ key: envelope.key,
372
+ piboSessionId: envelope.piboSessionId,
373
+ eventId: envelope.eventId,
374
+ payload: envelope.state,
375
+ attempts: durableJob.attempts,
376
+ maxAttempts: durableJob.maxAttempts,
377
+ durableJobId: durableJob.jobId,
378
+ };
379
+ this.pending.set(pending.key, pending);
380
+ admitted += 1;
381
+ this.startAttempt(pending, this.draining);
382
+ }
383
+ this.notifyChange();
384
+ if (admitted === 0 && jobs.every((job) => this.durableStore?.hasLiveJob(job.jobId)))
385
+ break;
386
+ }
387
+ this.scheduleRecovery();
388
+ }
389
+ reconcileClaimMiss(job) {
390
+ this.pending.delete(job.key);
391
+ job.durableClaimed = false;
392
+ if (job.durableJobId && this.durableStore?.hasLiveJob(job.durableJobId)) {
393
+ this.blockedDurableJobs.set(job.durableJobId, Date.now() + this.baseDelayMs);
394
+ }
395
+ this.notifyChange();
396
+ this.scheduleRecovery();
397
+ }
398
+ startHeartbeat(job, abortController) {
399
+ if (!job.durableJobId || job.durableClaimToken === undefined)
400
+ return;
401
+ job.heartbeatTimer = setInterval(() => {
402
+ const renewed = this.durableStore?.heartbeat(job.durableJobId, this.workerId, this.visibilityTimeoutMs, job.durableClaimToken);
403
+ if (!renewed)
404
+ abortController.abort();
405
+ }, this.heartbeatIntervalMs);
406
+ job.heartbeatTimer.unref?.();
407
+ }
408
+ clearHeartbeat(job) {
409
+ if (job.heartbeatTimer)
410
+ clearInterval(job.heartbeatTimer);
411
+ job.heartbeatTimer = undefined;
412
+ }
413
+ scheduleRecovery() {
414
+ if (this.disposed || !this.recoveryFactory || !this.durableStore || this.recoveryTimer)
415
+ return;
416
+ if (!this.durableStore.hasJobs(this.queueName))
417
+ return;
418
+ const nextBlockedAt = Math.min(...this.blockedDurableJobs.values(), Date.now() + this.baseDelayMs);
419
+ const delayMs = Math.max(1, nextBlockedAt - Date.now());
420
+ this.recoveryTimer = setTimeout(() => {
421
+ this.recoveryTimer = undefined;
422
+ this.notifyChange();
423
+ this.pumpRecovery();
424
+ }, delayMs);
425
+ this.recoveryTimer.unref?.();
426
+ }
427
+ notifyChange() {
428
+ this.changeVersion += 1;
429
+ for (const resolve of this.changeWaiters)
430
+ resolve();
431
+ this.changeWaiters.clear();
432
+ }
433
+ waitForChange(observedVersion) {
434
+ if (this.changeVersion !== observedVersion)
435
+ return Promise.resolve();
436
+ return new Promise((resolve) => this.changeWaiters.add(resolve));
437
+ }
438
+ }
439
+ function durableEnvelope(job, state) {
440
+ return {
441
+ version: 1,
442
+ key: job.key,
443
+ ...(job.piboSessionId ? { piboSessionId: job.piboSessionId } : {}),
444
+ ...(job.eventId ? { eventId: job.eventId } : {}),
445
+ state,
446
+ };
447
+ }
448
+ function parseDurableEnvelope(value) {
449
+ if (!value || typeof value !== "object" || Array.isArray(value))
450
+ return { reason: "payload_malformed" };
451
+ const candidate = value;
452
+ if (candidate.version !== 1)
453
+ return { reason: "payload_version_unsupported" };
454
+ if (typeof candidate.key !== "string" || !("state" in candidate))
455
+ return { reason: "payload_malformed" };
456
+ return {
457
+ reason: "",
458
+ envelope: {
459
+ version: 1,
460
+ key: candidate.key,
461
+ ...(typeof candidate.piboSessionId === "string" ? { piboSessionId: candidate.piboSessionId } : {}),
462
+ ...(typeof candidate.eventId === "string" ? { eventId: candidate.eventId } : {}),
463
+ state: candidate.state ?? null,
464
+ },
465
+ };
466
+ }
467
+ function sanitizedQuarantineEnvelope(job, reasonCode) {
468
+ return durableEnvelope(job, { phase: "quarantined", reasonCode });
469
+ }
470
+ function correlationFields(job) {
471
+ return { key: job.key, ...(job.piboSessionId ? { piboSessionId: job.piboSessionId } : {}), ...(job.eventId ? { eventId: job.eventId } : {}) };
472
+ }
473
+ function retryDelay(attempts, baseDelayMs, maxDelayMs) {
474
+ return Math.min(maxDelayMs, baseDelayMs * (2 ** Math.max(0, attempts - 1)));
475
+ }
476
+ function correlation(job, attempt, status) {
477
+ return { queue: DEFAULT_QUEUE_NAME, key: job.key, piboSessionId: job.piboSessionId, eventId: job.eventId, attempt, status };
478
+ }
479
+ function errorMessage(error) {
480
+ return error instanceof Error ? error.message : String(error);
481
+ }
482
+ function positiveInteger(value, fallback) {
483
+ return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : fallback;
484
+ }