@borgee/agents-host 0.2.97 → 0.2.110

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 (104) hide show
  1. package/README.md +77 -5
  2. package/dist/agents-host.d.ts +16 -0
  3. package/dist/agents-host.js +608 -82
  4. package/dist/catalog-worker.js +21 -0
  5. package/dist/catalog-worker.js.map +7 -0
  6. package/dist/chat/chat-control-plane.d.ts +19 -4
  7. package/dist/chat/normalized-input.d.ts +9 -0
  8. package/dist/chat/normalized-input.js +24 -0
  9. package/dist/chat/sdk-chat-control-plane.d.ts +46 -6
  10. package/dist/chat/sdk-chat-control-plane.js +169 -6
  11. package/dist/cli-args.d.ts +1 -1
  12. package/dist/cli-args.js +13 -4
  13. package/dist/config.d.ts +3 -1
  14. package/dist/config.js +25 -0
  15. package/dist/context/injection.d.ts +16 -0
  16. package/dist/context/injection.js +37 -21
  17. package/dist/context/prompt.d.ts +12 -0
  18. package/dist/context/prompt.js +25 -0
  19. package/dist/context/skill-manual.js +1 -1
  20. package/dist/context/turn-preparation.d.ts +2 -1
  21. package/dist/context/turn-preparation.js +15 -0
  22. package/dist/execution-telemetry.d.ts +96 -0
  23. package/dist/execution-telemetry.js +583 -0
  24. package/dist/gateway/channel-file-workspace.d.ts +15 -0
  25. package/dist/gateway/channel-file-workspace.js +130 -0
  26. package/dist/gateway/localhost-gateway.js +167 -2
  27. package/dist/hosted-turn-content.js +3 -2
  28. package/dist/local-config.js +34 -1
  29. package/dist/managed-daemon.js +15 -0
  30. package/dist/native-environment.d.ts +2 -0
  31. package/dist/native-environment.js +9 -0
  32. package/dist/native-protocol-types/codec.d.ts +49 -0
  33. package/dist/native-protocol-types/commands.d.ts +71 -0
  34. package/dist/native-protocol-types/compatibility.d.ts +5 -0
  35. package/dist/native-protocol-types/cursor.d.ts +7 -0
  36. package/dist/native-protocol-types/envelope.d.ts +1105 -0
  37. package/dist/native-protocol-types/file-changes.d.ts +20 -0
  38. package/dist/native-protocol-types/history.d.ts +487 -0
  39. package/dist/native-protocol-types/index.d.ts +18 -0
  40. package/dist/native-protocol-types/interactions.d.ts +619 -0
  41. package/dist/native-protocol-types/messages.d.ts +2482 -0
  42. package/dist/native-protocol-types/public-validation.d.ts +10 -0
  43. package/dist/native-protocol-types/remote-host-uplink.d.ts +67 -0
  44. package/dist/native-protocol-types/resources.d.ts +126 -0
  45. package/dist/native-protocol-types/session-settings.d.ts +22 -0
  46. package/dist/native-protocol-types/snapshot.d.ts +575 -0
  47. package/dist/native-protocol-types/timeline.d.ts +1503 -0
  48. package/dist/native-protocol-types/tool-result.d.ts +26 -0
  49. package/dist/native-protocol-types/uplink.d.ts +56 -0
  50. package/dist/native-protocol-types/version.d.ts +5 -0
  51. package/dist/native-provider-types/commands.d.ts +51 -0
  52. package/dist/native-provider-types/control.d.ts +192 -0
  53. package/dist/native-provider-types/file-changes.d.ts +9 -0
  54. package/dist/native-provider-types/index.d.ts +8 -0
  55. package/dist/native-provider-types/interactions.d.ts +7 -0
  56. package/dist/native-provider-types/observation.d.ts +209 -0
  57. package/dist/native-provider-types/provider.d.ts +124 -0
  58. package/dist/native-provider-types/session-settings.d.ts +17 -0
  59. package/dist/native-provider-types/testing.d.ts +27 -0
  60. package/dist/native-provider-types/tool-result.d.ts +21 -0
  61. package/dist/native-providers.d.ts +21 -0
  62. package/dist/native-providers.js +18402 -0
  63. package/dist/native-providers.js.map +7 -0
  64. package/dist/plugin-sdk.js +10713 -229
  65. package/dist/plugin-sdk.js.map +4 -4
  66. package/dist/policy/copilot-permission.js +1 -8
  67. package/dist/policy/gateway-authorization.d.ts +1 -1
  68. package/dist/policy/gateway-authorization.js +22 -2
  69. package/dist/providers/claude/adapter.js +1 -0
  70. package/dist/providers/claude/cli-client.js +4 -0
  71. package/dist/providers/codex/adapter.js +1 -0
  72. package/dist/providers/codex/cli-client.js +5 -0
  73. package/dist/providers/copilot/adapter.js +4 -0
  74. package/dist/providers/copilot/cli-client.js +11 -1
  75. package/dist/providers/copilot/sdk-session.d.ts +12 -3
  76. package/dist/providers/copilot/sdk-session.js +230 -6
  77. package/dist/providers/create-provider.js +24 -0
  78. package/dist/providers/normalized/adapter.d.ts +107 -0
  79. package/dist/providers/normalized/adapter.js +1650 -0
  80. package/dist/providers/normalized/control-policy.d.ts +5 -0
  81. package/dist/providers/normalized/control-policy.js +12 -0
  82. package/dist/providers/normalized/interactions.d.ts +3 -0
  83. package/dist/providers/normalized/interactions.js +39 -0
  84. package/dist/providers/normalized/legacy-input-ownership.d.ts +34 -0
  85. package/dist/providers/normalized/legacy-input-ownership.js +123 -0
  86. package/dist/providers/normalized/session-management.d.ts +67 -0
  87. package/dist/providers/normalized/session-management.js +334 -0
  88. package/dist/providers/normalized/session-ownership.d.ts +3 -0
  89. package/dist/providers/normalized/session-ownership.js +90 -0
  90. package/dist/providers/prompt-usage.d.ts +8 -0
  91. package/dist/providers/prompt-usage.js +52 -0
  92. package/dist/providers/provider-adapter.d.ts +43 -1
  93. package/dist/providers/provider-adapter.js +8 -0
  94. package/dist/state-paths.d.ts +2 -0
  95. package/dist/state-paths.js +6 -0
  96. package/dist/types.d.ts +66 -0
  97. package/dist/typing-lease.d.ts +14 -0
  98. package/dist/typing-lease.js +31 -0
  99. package/dist/vendor/agent-provider-codex/LICENSE +211 -0
  100. package/dist/vendor/agent-provider-codex/NOTICE +13 -0
  101. package/package.json +22 -13
  102. package/skills/borgee-agent/SKILL.md +12 -4
  103. package/skills/borgee-agent/scripts/borgee-agent.mjs +110 -0
  104. package/skills/borgee-agent/scripts/borgee-agent.py +116 -0
@@ -0,0 +1,583 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';
3
+ import { dirname } from 'node:path';
4
+ export class ExecutionDeliveryJournal {
5
+ path;
6
+ maxCompletedEntries;
7
+ now;
8
+ entries = new Map();
9
+ loaded = false;
10
+ writeQueue = Promise.resolve();
11
+ constructor(path, maxCompletedEntries = 2_048, now = Date.now) {
12
+ this.path = path;
13
+ this.maxCompletedEntries = maxCompletedEntries;
14
+ this.now = now;
15
+ }
16
+ async load() {
17
+ if (this.loaded)
18
+ return;
19
+ if (!this.path) {
20
+ this.loaded = true;
21
+ return;
22
+ }
23
+ let content;
24
+ try {
25
+ content = await readFile(this.path, 'utf8');
26
+ }
27
+ catch (error) {
28
+ if (error.code === 'ENOENT') {
29
+ this.loaded = true;
30
+ return;
31
+ }
32
+ throw error;
33
+ }
34
+ const parsed = JSON.parse(content);
35
+ if (!Array.isArray(parsed) ||
36
+ parsed.some((entry) => !this.isStoredExecutionDelivery(entry)) ||
37
+ new Set(parsed.map((entry) => entry.executionId)).size !== parsed.length) {
38
+ throw new Error('Invalid execution delivery journal');
39
+ }
40
+ this.entries = new Map(parsed.map((entry) => [entry.executionId, entry]));
41
+ this.loaded = true;
42
+ }
43
+ async claim(executionId) {
44
+ await this.load();
45
+ const existing = this.entries.get(executionId);
46
+ if (existing?.state === 'completed')
47
+ return 'completed';
48
+ if (existing?.state === 'running')
49
+ return 'recovering';
50
+ await this.mutate((next) => {
51
+ next.set(executionId, {
52
+ executionId,
53
+ state: 'running',
54
+ updatedAt: this.now(),
55
+ });
56
+ });
57
+ return 'new';
58
+ }
59
+ async complete(executionId) {
60
+ await this.load();
61
+ await this.mutate((next) => {
62
+ next.set(executionId, {
63
+ executionId,
64
+ state: 'completed',
65
+ updatedAt: this.now(),
66
+ });
67
+ const completed = [...next.values()]
68
+ .filter((entry) => entry.state === 'completed')
69
+ .sort((left, right) => left.updatedAt - right.updatedAt);
70
+ for (const entry of completed.slice(0, Math.max(0, completed.length - this.maxCompletedEntries))) {
71
+ next.delete(entry.executionId);
72
+ }
73
+ });
74
+ }
75
+ async recordUsage(executionId, usage) {
76
+ await this.load();
77
+ await this.mutate((next) => {
78
+ const entry = next.get(executionId);
79
+ if (!entry || entry.state !== 'running') {
80
+ throw new Error('Cannot record usage for an execution that is not running');
81
+ }
82
+ next.set(executionId, {
83
+ ...entry,
84
+ updatedAt: this.now(),
85
+ usage: { ...usage },
86
+ });
87
+ });
88
+ }
89
+ async readUsage(executionId) {
90
+ await this.load();
91
+ const usage = this.entries.get(executionId)?.usage;
92
+ return usage ? { ...usage } : undefined;
93
+ }
94
+ async mutate(update) {
95
+ const operation = this.writeQueue.then(async () => {
96
+ const next = new Map(this.entries);
97
+ update(next);
98
+ if (this.path) {
99
+ await mkdir(dirname(this.path), { recursive: true, mode: 0o700 });
100
+ const temporaryPath = `${this.path}.${process.pid}.${randomUUID()}.tmp`;
101
+ try {
102
+ await writeFile(temporaryPath, JSON.stringify([...next.values()]), { encoding: 'utf8', mode: 0o600 });
103
+ await rename(temporaryPath, this.path);
104
+ }
105
+ catch (error) {
106
+ await unlink(temporaryPath).catch(() => { });
107
+ throw error;
108
+ }
109
+ }
110
+ this.entries = next;
111
+ });
112
+ this.writeQueue = operation.catch(() => { });
113
+ await operation;
114
+ }
115
+ isStoredExecutionDelivery(value) {
116
+ if (!value || typeof value !== 'object')
117
+ return false;
118
+ const entry = value;
119
+ return (typeof entry.executionId === 'string' &&
120
+ entry.executionId.length > 0 &&
121
+ (entry.state === 'running' || entry.state === 'completed') &&
122
+ typeof entry.updatedAt === 'number' &&
123
+ Number.isSafeInteger(entry.updatedAt) &&
124
+ (entry.usage === undefined || this.isStoredExecutionUsage(entry.usage)));
125
+ }
126
+ isStoredExecutionUsage(value) {
127
+ if (!value || typeof value !== 'object')
128
+ return false;
129
+ const usage = value;
130
+ return ((usage.provider === 'claude' ||
131
+ usage.provider === 'codex' ||
132
+ usage.provider === 'copilot') &&
133
+ [usage.inputTokens, usage.outputTokens, usage.cacheReadTokens, usage.cacheWriteTokens]
134
+ .every((token) => (typeof token === 'number' &&
135
+ Number.isSafeInteger(token) &&
136
+ token >= 0)));
137
+ }
138
+ }
139
+ function stableEventId(executionId, kind) {
140
+ return createHash('sha256')
141
+ .update(`agents-host-execution-telemetry\0${executionId}\0${kind}`)
142
+ .digest('hex');
143
+ }
144
+ export class ExecutionTelemetryReporter {
145
+ grant;
146
+ sink;
147
+ now;
148
+ persistUsage;
149
+ started = false;
150
+ finished = false;
151
+ attemptCount = 0;
152
+ attemptsSucceeded = true;
153
+ provider;
154
+ pendingUsage;
155
+ usageFrameDurable = false;
156
+ usagePersistence = Promise.resolve();
157
+ continuations = [];
158
+ finishing;
159
+ get startFrameDurable() {
160
+ return this.started;
161
+ }
162
+ get terminalFrameDurable() {
163
+ return this.finished;
164
+ }
165
+ get allRequiredFramesDurable() {
166
+ return (this.started &&
167
+ this.finished &&
168
+ (this.pendingUsage === undefined || this.usageFrameDurable));
169
+ }
170
+ constructor(grant, sink, now = Date.now, persistUsage) {
171
+ this.grant = grant;
172
+ this.sink = sink;
173
+ this.now = now;
174
+ this.persistUsage = persistUsage;
175
+ }
176
+ async start() {
177
+ if (this.started || this.finished)
178
+ return;
179
+ await this.sink.reportExecutionTelemetry({
180
+ type: 'execution_started',
181
+ eventId: stableEventId(this.grant.executionId, 'started'),
182
+ executionId: this.grant.executionId,
183
+ telemetryContext: this.grant.telemetryContext,
184
+ startedAt: this.now(),
185
+ });
186
+ this.started = true;
187
+ }
188
+ async usage(provider, usage) {
189
+ if (!this.started || this.finished)
190
+ return;
191
+ if (usage.inputTokens === 0 &&
192
+ usage.outputTokens === 0 &&
193
+ usage.cacheReadTokens === 0 &&
194
+ usage.cacheWriteTokens === 0) {
195
+ return;
196
+ }
197
+ this.provider ??= provider;
198
+ this.pendingUsage = {
199
+ inputTokens: (this.pendingUsage?.inputTokens ?? 0) + usage.inputTokens,
200
+ outputTokens: (this.pendingUsage?.outputTokens ?? 0) + usage.outputTokens,
201
+ cacheReadTokens: (this.pendingUsage?.cacheReadTokens ?? 0) + usage.cacheReadTokens,
202
+ cacheWriteTokens: (this.pendingUsage?.cacheWriteTokens ?? 0) + usage.cacheWriteTokens,
203
+ };
204
+ this.usageFrameDurable = false;
205
+ if (this.persistUsage) {
206
+ const snapshot = { ...this.pendingUsage };
207
+ this.usagePersistence = this.usagePersistence
208
+ .then(() => this.persistUsage(this.provider, snapshot))
209
+ .catch(() => undefined);
210
+ await this.usagePersistence;
211
+ }
212
+ }
213
+ noteAttempt(succeeded) {
214
+ if (!this.started || this.finished)
215
+ return;
216
+ this.attemptCount += 1;
217
+ this.attemptsSucceeded &&= succeeded;
218
+ }
219
+ noteFailure() {
220
+ if (!this.started || this.finished)
221
+ return;
222
+ this.attemptsSucceeded = false;
223
+ }
224
+ hold(completion) {
225
+ if (this.finished || this.finishing)
226
+ return;
227
+ this.continuations.push(completion);
228
+ }
229
+ finish(succeeded) {
230
+ if (this.finishing)
231
+ return this.finishing;
232
+ const attempt = this.finalize(succeeded);
233
+ this.finishing = attempt;
234
+ void attempt.catch(() => {
235
+ if (this.finishing === attempt) {
236
+ this.finishing = undefined;
237
+ }
238
+ });
239
+ return this.finishing;
240
+ }
241
+ async finalize(succeeded) {
242
+ if (!this.started || this.finished)
243
+ return;
244
+ const continuationResults = await Promise.allSettled(this.continuations);
245
+ if (continuationResults.some((result) => result.status === 'rejected' || result.value === false)) {
246
+ this.attemptsSucceeded = false;
247
+ }
248
+ const finalSucceeded = succeeded ?? (this.attemptCount > 0 && this.attemptsSucceeded);
249
+ if (this.pendingUsage && !this.usageFrameDurable) {
250
+ const errors = [];
251
+ try {
252
+ await this.usagePersistence;
253
+ await this.persistUsage?.(this.provider, this.pendingUsage);
254
+ await this.sink.reportExecutionTelemetry({
255
+ type: 'execution_usage',
256
+ eventId: stableEventId(this.grant.executionId, 'usage'),
257
+ executionId: this.grant.executionId,
258
+ telemetryContext: this.grant.telemetryContext,
259
+ usageGranularity: 'execution_aggregate',
260
+ provider: this.provider,
261
+ model: 'unknown',
262
+ inputTokens: this.pendingUsage.inputTokens,
263
+ outputTokens: this.pendingUsage.outputTokens,
264
+ cacheReadTokens: this.pendingUsage.cacheReadTokens,
265
+ cacheWriteTokens: this.pendingUsage.cacheWriteTokens,
266
+ observedAt: this.now(),
267
+ });
268
+ this.usageFrameDurable = true;
269
+ }
270
+ catch (error) {
271
+ errors.push(error);
272
+ }
273
+ if (errors.length > 0) {
274
+ throw new AggregateError(errors, 'Execution usage telemetry could not be persisted');
275
+ }
276
+ }
277
+ try {
278
+ await this.sink.reportExecutionTelemetry({
279
+ type: 'execution_finished',
280
+ eventId: stableEventId(this.grant.executionId, 'finished'),
281
+ executionId: this.grant.executionId,
282
+ telemetryContext: this.grant.telemetryContext,
283
+ succeeded: finalSucceeded,
284
+ endedAt: this.now(),
285
+ });
286
+ this.finished = true;
287
+ }
288
+ catch (error) {
289
+ throw new AggregateError([error], 'Execution telemetry could not be finalized');
290
+ }
291
+ }
292
+ }
293
+ function isStoredFrame(value) {
294
+ if (!value || typeof value !== 'object')
295
+ return false;
296
+ const frame = value;
297
+ const hasIdentity = typeof frame.eventId === 'string' &&
298
+ frame.eventId.length > 0 &&
299
+ typeof frame.executionId === 'string' &&
300
+ frame.executionId.length > 0 &&
301
+ typeof frame.telemetryContext === 'string' &&
302
+ frame.telemetryContext.length > 0;
303
+ if (!hasIdentity)
304
+ return false;
305
+ if (frame.type === 'execution_started') {
306
+ return typeof frame.startedAt === 'number' && Number.isSafeInteger(frame.startedAt);
307
+ }
308
+ if (frame.type === 'execution_finished') {
309
+ return (typeof frame.succeeded === 'boolean' &&
310
+ typeof frame.endedAt === 'number' &&
311
+ Number.isSafeInteger(frame.endedAt));
312
+ }
313
+ if (frame.type !== 'execution_usage')
314
+ return false;
315
+ return ((frame.usageGranularity === 'llm_call' ||
316
+ frame.usageGranularity === 'execution_aggregate') &&
317
+ typeof frame.provider === 'string' &&
318
+ frame.provider.length > 0 &&
319
+ typeof frame.model === 'string' &&
320
+ frame.model.length > 0 &&
321
+ [frame.inputTokens, frame.outputTokens, frame.cacheReadTokens, frame.cacheWriteTokens]
322
+ .every((token) => typeof token === 'number' && Number.isSafeInteger(token) && token >= 0) &&
323
+ typeof frame.observedAt === 'number' &&
324
+ Number.isSafeInteger(frame.observedAt));
325
+ }
326
+ // The wire has ACK but no terminal NACK. Requiring both age and attempts keeps
327
+ // a long offline interval from looking like rejection, while maxEntries applies
328
+ // backpressure instead of deleting recent unacknowledged facts.
329
+ const DEFAULT_OUTBOX_POLICY = {
330
+ maxEntries: 2_048,
331
+ maxAttempts: 64,
332
+ retentionMs: 7 * 24 * 60 * 60 * 1_000,
333
+ retryIntervalMs: 30_000,
334
+ };
335
+ export class ExecutionTelemetryOutboxCapacityError extends Error {
336
+ constructor() {
337
+ super('Execution telemetry outbox capacity reached');
338
+ this.name = 'ExecutionTelemetryOutboxCapacityError';
339
+ }
340
+ }
341
+ export class ExecutionTelemetryOutbox {
342
+ path;
343
+ frames = new Map();
344
+ loaded = false;
345
+ writeQueue = Promise.resolve();
346
+ flushInFlight;
347
+ now;
348
+ onRetired;
349
+ policy;
350
+ capacityWaiters = new Set();
351
+ constructor(path, options = {}) {
352
+ this.path = path;
353
+ this.now = options.now ?? Date.now;
354
+ this.onRetired = options.onRetired;
355
+ this.policy = { ...DEFAULT_OUTBOX_POLICY, ...options.policy };
356
+ }
357
+ async load() {
358
+ if (this.loaded)
359
+ return;
360
+ if (!this.path) {
361
+ this.loaded = true;
362
+ return;
363
+ }
364
+ let content;
365
+ try {
366
+ content = await readFile(this.path, 'utf8');
367
+ }
368
+ catch (error) {
369
+ if (error.code === 'ENOENT') {
370
+ this.loaded = true;
371
+ return;
372
+ }
373
+ throw error;
374
+ }
375
+ const parsed = JSON.parse(content);
376
+ if (!Array.isArray(parsed)) {
377
+ throw new Error('Invalid execution telemetry outbox');
378
+ }
379
+ const restored = [];
380
+ for (const value of parsed) {
381
+ if (isStoredFrame(value)) {
382
+ restored.push({
383
+ frame: value,
384
+ enqueuedAt: this.frameTimestamp(value),
385
+ attempts: 0,
386
+ });
387
+ continue;
388
+ }
389
+ if (!this.isStoredEntry(value)) {
390
+ throw new Error('Invalid execution telemetry outbox');
391
+ }
392
+ restored.push(value);
393
+ }
394
+ if (new Set(restored.map(({ frame }) => frame.eventId)).size !== restored.length) {
395
+ throw new Error('Invalid execution telemetry outbox');
396
+ }
397
+ this.frames = new Map(restored.map((entry) => [entry.frame.eventId, entry]));
398
+ this.loaded = true;
399
+ }
400
+ async enqueue(frame) {
401
+ await this.load();
402
+ await this.retireExpired();
403
+ await this.mutate((next) => {
404
+ if (next.has(frame.eventId))
405
+ return false;
406
+ if (next.size >= this.policy.maxEntries) {
407
+ throw new ExecutionTelemetryOutboxCapacityError();
408
+ }
409
+ next.set(frame.eventId, {
410
+ frame,
411
+ enqueuedAt: this.now(),
412
+ attempts: 0,
413
+ });
414
+ return true;
415
+ });
416
+ }
417
+ async acknowledge(eventId) {
418
+ await this.load();
419
+ if (!this.frames.has(eventId))
420
+ return;
421
+ await this.mutate((next) => {
422
+ next.delete(eventId);
423
+ return true;
424
+ });
425
+ this.notifyCapacityAvailable();
426
+ }
427
+ async flush(send, force = false) {
428
+ if (this.flushInFlight)
429
+ return this.flushInFlight;
430
+ const operation = this.flushOnce(send, force);
431
+ const tracked = operation.finally(() => {
432
+ if (this.flushInFlight === tracked) {
433
+ this.flushInFlight = undefined;
434
+ }
435
+ });
436
+ this.flushInFlight = tracked;
437
+ return tracked;
438
+ }
439
+ async flushOnce(send, force) {
440
+ await this.load();
441
+ await this.retireExpired();
442
+ const now = this.now();
443
+ let pending = [];
444
+ await this.mutate((next) => {
445
+ pending = [...next.values()].filter((entry) => entry.attempts < this.policy.maxAttempts &&
446
+ (entry.attempts === 0 ||
447
+ force ||
448
+ entry.lastAttemptAt === undefined ||
449
+ now - entry.lastAttemptAt >= this.policy.retryIntervalMs));
450
+ if (pending.length === 0)
451
+ return false;
452
+ const pendingIds = new Set(pending.map(({ frame }) => frame.eventId));
453
+ for (const [eventId, entry] of next) {
454
+ if (!pendingIds.has(eventId))
455
+ continue;
456
+ next.set(eventId, {
457
+ ...entry,
458
+ attempts: entry.attempts + 1,
459
+ lastAttemptAt: now,
460
+ });
461
+ }
462
+ return true;
463
+ });
464
+ const errors = [];
465
+ for (const { frame } of pending) {
466
+ try {
467
+ send(frame);
468
+ }
469
+ catch (error) {
470
+ errors.push(error);
471
+ }
472
+ }
473
+ if (errors.length > 0) {
474
+ throw new AggregateError(errors, 'Execution telemetry delivery failed');
475
+ }
476
+ }
477
+ async settle() {
478
+ await this.writeQueue;
479
+ }
480
+ pendingCount() {
481
+ return this.frames.size;
482
+ }
483
+ nextRetryDelayMs() {
484
+ if (this.frames.size === 0)
485
+ return undefined;
486
+ const now = this.now();
487
+ return Math.max(0, Math.min(...[...this.frames.values()].map((entry) => entry.attempts >= this.policy.maxAttempts
488
+ ? entry.enqueuedAt + this.policy.retentionMs - now
489
+ : entry.attempts === 0 || entry.lastAttemptAt === undefined
490
+ ? 0
491
+ : entry.lastAttemptAt + this.policy.retryIntervalMs - now)));
492
+ }
493
+ hasCapacity() {
494
+ return this.frames.size < this.policy.maxEntries;
495
+ }
496
+ waitForCapacity(timeoutMs) {
497
+ if (this.hasCapacity())
498
+ return Promise.resolve(true);
499
+ return new Promise((resolve) => {
500
+ const settle = (available) => {
501
+ clearTimeout(timeout);
502
+ this.capacityWaiters.delete(settle);
503
+ resolve(available);
504
+ };
505
+ const timeout = setTimeout(() => settle(false), timeoutMs);
506
+ this.capacityWaiters.add(settle);
507
+ });
508
+ }
509
+ async retireExpired() {
510
+ const now = this.now();
511
+ let retired = 0;
512
+ await this.mutate((next) => {
513
+ for (const [eventId, entry] of next) {
514
+ if (entry.attempts >= this.policy.maxAttempts &&
515
+ now - entry.enqueuedAt >= this.policy.retentionMs) {
516
+ next.delete(eventId);
517
+ retired += 1;
518
+ }
519
+ }
520
+ return retired > 0;
521
+ });
522
+ if (retired > 0) {
523
+ this.onRetired?.(retired);
524
+ this.notifyCapacityAvailable();
525
+ }
526
+ }
527
+ async mutate(change) {
528
+ const operation = this.writeQueue.then(async () => {
529
+ const next = new Map(this.frames);
530
+ if (!change(next))
531
+ return;
532
+ await this.persist(next);
533
+ this.frames = next;
534
+ });
535
+ this.writeQueue = operation.catch(() => { });
536
+ await operation;
537
+ }
538
+ async persist(frames) {
539
+ if (!this.path)
540
+ return;
541
+ await mkdir(dirname(this.path), { recursive: true, mode: 0o700 });
542
+ const temporary = `${this.path}.${process.pid}.${randomUUID()}.tmp`;
543
+ try {
544
+ await writeFile(temporary, JSON.stringify([...frames.values()]), {
545
+ encoding: 'utf8',
546
+ mode: 0o600,
547
+ flag: 'wx',
548
+ });
549
+ await rename(temporary, this.path);
550
+ }
551
+ catch (error) {
552
+ await unlink(temporary).catch(() => { });
553
+ throw error;
554
+ }
555
+ }
556
+ frameTimestamp(frame) {
557
+ if (frame.type === 'execution_started')
558
+ return frame.startedAt;
559
+ if (frame.type === 'execution_usage')
560
+ return frame.observedAt;
561
+ return frame.endedAt;
562
+ }
563
+ isStoredEntry(value) {
564
+ if (!value || typeof value !== 'object')
565
+ return false;
566
+ const entry = value;
567
+ return (isStoredFrame(entry.frame) &&
568
+ typeof entry.enqueuedAt === 'number' &&
569
+ Number.isSafeInteger(entry.enqueuedAt) &&
570
+ typeof entry.attempts === 'number' &&
571
+ Number.isSafeInteger(entry.attempts) &&
572
+ entry.attempts >= 0 &&
573
+ (entry.lastAttemptAt === undefined ||
574
+ (typeof entry.lastAttemptAt === 'number' &&
575
+ Number.isSafeInteger(entry.lastAttemptAt))));
576
+ }
577
+ notifyCapacityAvailable() {
578
+ if (!this.hasCapacity())
579
+ return;
580
+ for (const waiter of [...this.capacityWaiters])
581
+ waiter(true);
582
+ }
583
+ }
@@ -0,0 +1,15 @@
1
+ export declare const CHANNEL_FILE_WORKING_COPY_DIRECTORY = ".borgee/channel-files";
2
+ export declare const CHANNEL_FILE_TRANSFER_MAX_BYTES: number;
3
+ export declare class ChannelFileWorkspaceError extends Error {
4
+ readonly code: 'execution_local_directory_required' | 'invalid_file_path' | 'symlink_not_allowed' | 'file_too_large' | 'file_exists' | 'file_not_found';
5
+ constructor(code: 'execution_local_directory_required' | 'invalid_file_path' | 'symlink_not_allowed' | 'file_too_large' | 'file_exists' | 'file_not_found', message: string);
6
+ }
7
+ export declare function validatePortableRelativePath(path: string): string;
8
+ export declare function requireExecutionRoot(payload: {
9
+ resolvedWorkspace?: {
10
+ authority: string;
11
+ rootPath: string;
12
+ };
13
+ }): string;
14
+ export declare function readWorkspaceFile(rootPath: string, sourcePath: string): Promise<Buffer>;
15
+ export declare function writeWorkingCopy(rootPath: string, channelPath: string, content: Buffer, overwrite: boolean): Promise<string>;