@gajae-code/coding-agent 0.12.6 → 0.12.7

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 (70) hide show
  1. package/CHANGELOG.md +3 -1
  2. package/dist/types/async/job-manager.d.ts +36 -1
  3. package/dist/types/eval/backend.d.ts +2 -2
  4. package/dist/types/exec/bash-executor.d.ts +8 -10
  5. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +0 -36
  6. package/dist/types/internal-urls/registry-helpers.d.ts +0 -18
  7. package/dist/types/sdk/bus/chat-daemon-control.d.ts +1 -3
  8. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +2 -4
  9. package/dist/types/sdk/host/host.d.ts +2 -23
  10. package/dist/types/sdk/host/reverse-leases.d.ts +4 -7
  11. package/dist/types/session/artifacts.d.ts +5 -8
  12. package/dist/types/session/client-bridge.d.ts +0 -14
  13. package/dist/types/session/internal/managed-session-storage.d.ts +2 -5
  14. package/dist/types/session/messages.d.ts +1 -4
  15. package/dist/types/session/session-manager.d.ts +4 -5
  16. package/dist/types/session/streaming-output.d.ts +0 -32
  17. package/dist/types/task/receipt.d.ts +1 -2
  18. package/dist/types/task/types.d.ts +43 -178
  19. package/dist/types/tools/bash.d.ts +1 -1
  20. package/dist/types/tools/fetch.d.ts +0 -3
  21. package/dist/types/tools/output-meta.d.ts +5 -37
  22. package/dist/types/tools/subagent.d.ts +15 -0
  23. package/dist/types/tools/todo-write.d.ts +3 -0
  24. package/dist/types/tools/tool-result.d.ts +0 -1
  25. package/package.json +8 -8
  26. package/src/async/job-manager.ts +430 -46
  27. package/src/cursor.ts +13 -20
  28. package/src/defaults/gjc/skills/ultragoal/SKILL.md +2 -3
  29. package/src/eval/backend.ts +2 -2
  30. package/src/exec/bash-executor.ts +151 -425
  31. package/src/gjc-runtime/ultragoal-runtime.ts +3 -280
  32. package/src/gjc-runtime/workflow-manifest.generated.json +1 -40
  33. package/src/gjc-runtime/workflow-manifest.ts +0 -12
  34. package/src/internal-urls/agent-protocol.ts +38 -98
  35. package/src/internal-urls/artifact-protocol.ts +17 -28
  36. package/src/internal-urls/docs-index.generated.ts +1 -1
  37. package/src/internal-urls/registry-helpers.ts +0 -62
  38. package/src/modes/acp/acp-agent.ts +0 -24
  39. package/src/modes/acp/acp-client-bridge.ts +11 -154
  40. package/src/prompts/tools/bash.md +2 -2
  41. package/src/sdk/acp/adapter.ts +9 -200
  42. package/src/sdk/bus/chat-daemon-control.ts +3 -5
  43. package/src/sdk/bus/index.ts +8 -324
  44. package/src/sdk/bus/telegram-daemon-contract.ts +2 -4
  45. package/src/sdk/host/host.ts +9 -187
  46. package/src/sdk/host/reverse-leases.ts +42 -249
  47. package/src/session/agent-session.ts +15 -178
  48. package/src/session/artifacts.ts +74 -211
  49. package/src/session/client-bridge.ts +0 -15
  50. package/src/session/internal/managed-session-storage.ts +5 -104
  51. package/src/session/messages.ts +7 -32
  52. package/src/session/session-manager.ts +21 -12
  53. package/src/session/streaming-output.ts +40 -198
  54. package/src/task/index.ts +230 -64
  55. package/src/task/receipt.ts +2 -4
  56. package/src/task/render.ts +0 -30
  57. package/src/task/types.ts +10 -34
  58. package/src/tools/bash.ts +239 -818
  59. package/src/tools/eval.ts +4 -99
  60. package/src/tools/fetch.ts +15 -77
  61. package/src/tools/find.ts +3 -7
  62. package/src/tools/output-meta.ts +114 -696
  63. package/src/tools/search.ts +4 -9
  64. package/src/tools/subagent.ts +97 -75
  65. package/src/tools/todo-write.ts +47 -0
  66. package/src/tools/tool-result.ts +0 -1
  67. package/dist/types/gjc-runtime/ultragoal-review-source.d.ts +0 -83
  68. package/dist/types/utils/artifact-id.d.ts +0 -2
  69. package/src/gjc-runtime/ultragoal-review-source.ts +0 -378
  70. package/src/utils/artifact-id.ts +0 -6
@@ -5,12 +5,7 @@ export const REVERSE_HEARTBEAT_MS = 5_000;
5
5
  export const REVERSE_LEASE_TTL_MS = 15_000;
6
6
  export const REVERSE_RECLAIM_GRACE_MS = 15_000;
7
7
  export const MAX_REVERSE_OUTSTANDING = 64;
8
- export const MAX_REVERSE_CLEANUP_OUTSTANDING = 2;
9
8
  export const MAX_REVERSE_PAYLOAD_BYTES = 256 * 1024;
10
- export const MAX_REVERSE_TERMINAL_OUTPUT_BYTES = 10 * 1024 * 1024;
11
- export const MAX_REVERSE_TERMINAL_OUTPUT_PAYLOAD_BYTES = 64 * 1024 * 1024;
12
- const MAX_REVERSE_IDEMPOTENCY_ENTRIES = 256;
13
- const MAX_REVERSE_IDEMPOTENCY_KEY_BYTES = 512;
14
9
 
15
10
  export class ReverseLeaseError extends Error {
16
11
  constructor(
@@ -44,7 +39,6 @@ interface Outstanding {
44
39
  connectionId: string;
45
40
  capability: string;
46
41
  leaseId: string;
47
- method: string;
48
42
  resolve: (value: unknown) => void;
49
43
  reject: (reason: Error) => void;
50
44
  signal?: AbortSignal;
@@ -55,13 +49,7 @@ export interface ReverseLeaseOptions {
55
49
  now?: () => number;
56
50
  leaseTtlMs?: number;
57
51
  sendFrame: (connectionId: string, frame: SdkFrame) => void | Promise<void>;
58
- installDefinitions?: (
59
- capability: string,
60
- definitions: unknown,
61
- leaseId: string,
62
- connectionId: string,
63
- connectionGeneration: number,
64
- ) => void;
52
+ installDefinitions?: (capability: string, definitions: unknown) => void;
65
53
  onCancel?: (requestId: string, reason: "provider_disconnected" | "lease_released") => void;
66
54
  onDefinitionsRemoved?: (capability: string) => void;
67
55
  }
@@ -93,10 +81,8 @@ export class ReverseLeaseRuntime {
93
81
  readonly #onCancel?: ReverseLeaseOptions["onCancel"];
94
82
  readonly #leases = new Map<string, ProviderLease>();
95
83
  readonly #idempotency = new Map<string, { fingerprint: string; lease: ProviderLease }>();
96
- readonly #idempotencyOrder: string[] = [];
97
-
98
84
  readonly #outstanding = new Map<string, Outstanding>();
99
- readonly #installedCapabilities = new Map<string, string>();
85
+ readonly #installedCapabilities = new Set<string>();
100
86
  readonly #sweepTimer: ReturnType<typeof setInterval>;
101
87
  #disposing = false;
102
88
 
@@ -117,15 +103,11 @@ export class ReverseLeaseRuntime {
117
103
  definitions: unknown,
118
104
  expectedLeaseId?: string,
119
105
  idempotencyKey?: string,
120
- connectionGeneration = 0,
121
106
  ): ProviderLease {
122
107
  if (this.#disposing) throw new Error("reverse runtime is disposing");
123
- if (idempotencyKey !== undefined && Buffer.byteLength(idempotencyKey, "utf8") > MAX_REVERSE_IDEMPOTENCY_KEY_BYTES)
124
- throw new ReverseLeaseError("payload_too_large");
125
108
  const key = `${connectionId}\u0000${idempotencyKey ?? ""}`;
126
109
  const fingerprint = registrationFingerprint(capability, definitions, expectedLeaseId);
127
110
  const replay = idempotencyKey ? this.#idempotency.get(key) : undefined;
128
-
129
111
  if (replay) {
130
112
  if (replay.fingerprint !== fingerprint) throw new ReverseLeaseError("idempotency_conflict");
131
113
  const current = this.#leases.get(capability);
@@ -133,19 +115,12 @@ export class ReverseLeaseRuntime {
133
115
  }
134
116
  const now = this.#now();
135
117
  const existing = this.#leases.get(capability);
136
- if (existing && existing.expiresAt <= now) {
137
- this.#cancelForLease(existing.capability, existing.leaseId);
138
- this.#removeDefinitions(existing.capability, existing.leaseId);
139
- this.#evictIdempotencyForLease(existing.leaseId);
140
- }
141
- const reclaimReserved = existing?.graceUntil !== undefined && existing.graceUntil > now;
142
- if (reclaimReserved && existing!.leaseId !== expectedLeaseId) {
143
- throw new ReverseLeaseError("provider_lease_conflict");
144
- }
118
+ if (existing && existing.expiresAt <= now) this.#removeDefinitions(existing.capability);
145
119
  const pendingHandoff = existing?.active === false && existing.expiresAt > now;
146
120
  if (pendingHandoff) {
147
121
  if (existing!.connectionId !== connectionId || existing!.leaseId !== expectedLeaseId)
148
122
  throw new ReverseLeaseError("provider_lease_conflict");
123
+ this.#installDefinitionsFor(capability, definitions);
149
124
  const lease: ProviderLease = {
150
125
  leaseId: existing!.leaseId,
151
126
  connectionId,
@@ -154,22 +129,17 @@ export class ReverseLeaseRuntime {
154
129
  expiresAt: now + this.#leaseTtlMs,
155
130
  active: true,
156
131
  };
157
- this.#installDefinitionsFor(capability, definitions, lease.leaseId, lease.connectionId, connectionGeneration);
158
132
  this.#leases.set(capability, lease);
159
- if (idempotencyKey) this.#rememberIdempotency(key, fingerprint, lease);
160
-
133
+ if (idempotencyKey) this.#idempotency.set(key, { fingerprint, lease });
161
134
  return { ...lease };
162
135
  }
163
136
  const reclaiming =
164
137
  existing?.leaseId === expectedLeaseId && existing?.graceUntil !== undefined && now <= existing.graceUntil;
165
138
  const refreshing =
166
- existing?.active !== false &&
167
- existing?.connectionId === connectionId &&
168
- existing.expiresAt > now &&
169
- (expectedLeaseId === undefined || existing.leaseId === expectedLeaseId);
170
-
139
+ existing?.active !== false && existing?.connectionId === connectionId && existing.expiresAt > now;
171
140
  if (existing && !reclaiming && !refreshing && existing.connectionId !== connectionId && existing.expiresAt > now)
172
141
  throw new ReverseLeaseError("provider_lease_conflict");
142
+ this.#installDefinitionsFor(capability, definitions);
173
143
  const lease: ProviderLease = {
174
144
  leaseId: reclaiming || refreshing ? existing!.leaseId : randomUUID(),
175
145
  connectionId,
@@ -178,22 +148,17 @@ export class ReverseLeaseRuntime {
178
148
  expiresAt: now + this.#leaseTtlMs,
179
149
  active: true,
180
150
  };
181
- this.#installDefinitionsFor(capability, definitions, lease.leaseId, lease.connectionId, connectionGeneration);
182
151
  this.#leases.set(capability, lease);
183
- if (idempotencyKey) this.#rememberIdempotency(key, fingerprint, lease);
184
-
152
+ if (idempotencyKey) this.#idempotency.set(key, { fingerprint, lease });
185
153
  return { ...lease };
186
154
  }
187
155
 
188
156
  heartbeat(connectionId: string, leaseId: string): ProviderLease {
189
157
  const lease = this.#owner(connectionId, leaseId);
190
158
  if (lease.expiresAt <= this.#now()) {
191
- this.#cancelForLease(lease.capability, lease.leaseId);
192
- this.#removeDefinitions(lease.capability, lease.leaseId);
193
- this.#evictIdempotencyForLease(lease.leaseId);
159
+ this.#removeDefinitions(lease.capability);
194
160
  throw new ReverseLeaseError("lease_expired");
195
161
  }
196
-
197
162
  lease.expiresAt = this.#now() + this.#leaseTtlMs;
198
163
  lease.graceUntil = undefined;
199
164
  return { ...lease };
@@ -201,14 +166,8 @@ export class ReverseLeaseRuntime {
201
166
 
202
167
  release(connectionId: string, leaseId: string, handoffTo?: string): ProviderLease {
203
168
  const lease = this.#owner(connectionId, leaseId);
204
- if (lease.expiresAt <= this.#now()) {
205
- this.#cancelForLease(lease.capability, lease.leaseId);
206
- this.#removeDefinitions(lease.capability, lease.leaseId);
207
- this.#evictIdempotencyForLease(lease.leaseId);
208
- throw new ReverseLeaseError("lease_expired");
209
- }
210
- this.#cancelForLease(lease.capability, lease.leaseId);
211
- this.#removeDefinitions(lease.capability, lease.leaseId);
169
+ this.#cancelForConnection(connectionId, "lease_released");
170
+ this.#removeDefinitions(lease.capability);
212
171
  if (handoffTo) {
213
172
  lease.connectionId = handoffTo;
214
173
  lease.expiresAt = this.#now() + REVERSE_RECLAIM_GRACE_MS;
@@ -216,32 +175,22 @@ export class ReverseLeaseRuntime {
216
175
  lease.active = false;
217
176
  return { ...lease };
218
177
  }
219
- this.#evictIdempotencyForLease(lease.leaseId);
220
178
  this.#leases.delete(lease.capability);
221
179
  return { ...lease };
222
180
  }
223
181
 
224
182
  disconnect(connectionId: string): void {
225
183
  const now = this.#now();
226
- for (const lease of this.#leases.values()) {
227
- if (lease.connectionId !== connectionId) continue;
228
- lease.expiresAt = now;
229
- lease.graceUntil = now + REVERSE_RECLAIM_GRACE_MS;
230
- this.#removeDefinitions(lease.capability, lease.leaseId);
231
- this.#evictIdempotencyForLease(lease.leaseId);
232
- }
184
+ for (const lease of this.#leases.values())
185
+ if (lease.connectionId === connectionId) {
186
+ lease.expiresAt = now;
187
+ lease.graceUntil = now + REVERSE_RECLAIM_GRACE_MS;
188
+ this.#removeDefinitions(lease.capability);
189
+ }
233
190
  this.#cancelForConnection(connectionId, "provider_disconnected");
234
191
  }
235
192
 
236
- request(
237
- capability: string,
238
- method: string,
239
- payload: unknown,
240
- signal?: AbortSignal,
241
- expectedLeaseId?: string,
242
- cleanup = false,
243
- expectedConnectionId?: string,
244
- ): Promise<unknown> {
193
+ request(capability: string, method: string, payload: unknown, signal?: AbortSignal): Promise<unknown> {
245
194
  if (this.#disposing) throw new Error("reverse runtime is disposing");
246
195
  this.#assertPayload(payload);
247
196
  const lease = this.#liveLease(capability);
@@ -251,24 +200,14 @@ export class ReverseLeaseRuntime {
251
200
  throw new ReverseLeaseError("lease_unavailable");
252
201
  throw new ReverseLeaseError("provider_required");
253
202
  }
254
- if (expectedLeaseId !== undefined && lease.leaseId !== expectedLeaseId) {
255
- throw new ReverseLeaseError("lease_unavailable");
256
- }
257
- if (expectedConnectionId !== undefined && lease.connectionId !== expectedConnectionId) {
258
- throw new ReverseLeaseError("lease_unavailable");
259
- }
260
203
  if (signal?.aborted)
261
204
  return Promise.reject(Object.assign(new Error("request_cancelled"), { name: "request_cancelled" }));
262
- const outstandingLimit = cleanup
263
- ? MAX_REVERSE_OUTSTANDING + MAX_REVERSE_CLEANUP_OUTSTANDING
264
- : MAX_REVERSE_OUTSTANDING;
265
- if (this.#outstanding.size >= outstandingLimit) throw new ReverseLeaseError("too_many_outstanding");
205
+ if (this.#outstanding.size >= MAX_REVERSE_OUTSTANDING) throw new ReverseLeaseError("too_many_outstanding");
266
206
  const id = randomUUID();
267
207
  return new Promise((resolve, reject) => {
268
208
  const outstanding: Outstanding = {
269
209
  connectionId: lease.connectionId,
270
210
  capability,
271
- method,
272
211
  leaseId: lease.leaseId,
273
212
  resolve,
274
213
  reject,
@@ -325,58 +264,13 @@ export class ReverseLeaseRuntime {
325
264
  leaseId: string,
326
265
  result: unknown,
327
266
  error?: { code: string; message: string },
328
- wireEnvelope?: unknown,
329
267
  ): void {
268
+ this.#assertPayload(result);
330
269
  const request = this.#outstanding.get(id);
331
270
  if (!request) throw new ReverseLeaseError("unknown_request");
332
271
  if (request.connectionId !== connectionId || request.leaseId !== leaseId)
333
272
  throw new ReverseLeaseError("not_lease_owner");
334
- const lease = this.#leases.get(request.capability);
335
- if (
336
- !lease?.active ||
337
- lease.leaseId !== request.leaseId ||
338
- lease.connectionId !== request.connectionId ||
339
- lease.expiresAt <= this.#now()
340
- ) {
341
- const failure = new ReverseLeaseError("lease_expired");
342
- this.#settleOutstandingWithFailure(id, request, failure, true);
343
- throw failure;
344
- }
345
- const envelope =
346
- wireEnvelope ??
347
- ({
348
- type: "reverse_response",
349
- id,
350
- connectionId,
351
- leaseId,
352
- ok: error === undefined,
353
- ...(error ? { error } : { result }),
354
- } satisfies Record<string, unknown>);
355
- try {
356
- this.#assertPayload(envelope, MAX_REVERSE_TERMINAL_OUTPUT_PAYLOAD_BYTES);
357
- const terminalOutputResponse =
358
- error === undefined &&
359
- request.capability === "terminal" &&
360
- request.method === "terminal.output" &&
361
- this.#isTerminalOutputEnvelope(envelope, id, connectionId, leaseId);
362
- const maxBytes = terminalOutputResponse
363
- ? MAX_REVERSE_TERMINAL_OUTPUT_PAYLOAD_BYTES
364
- : MAX_REVERSE_PAYLOAD_BYTES;
365
- if (
366
- terminalOutputResponse &&
367
- result &&
368
- typeof result === "object" &&
369
- typeof (result as { output?: unknown }).output === "string" &&
370
- Buffer.byteLength((result as { output: string }).output, "utf-8") > MAX_REVERSE_TERMINAL_OUTPUT_BYTES
371
- )
372
- throw new ReverseLeaseError("payload_too_large");
373
- this.#assertPayload(envelope, maxBytes);
374
- } catch (failure) {
375
- const responseError = failure instanceof Error ? failure : new Error(String(failure));
376
- this.#settleOutstandingWithFailure(id, request, responseError);
377
- throw responseError;
378
- }
379
- if (this.#takeOutstanding(id) !== request) return;
273
+ this.#takeOutstanding(id);
380
274
  if (error) {
381
275
  const rejection = new Error(error.message);
382
276
  rejection.name = error.code;
@@ -384,44 +278,6 @@ export class ReverseLeaseRuntime {
384
278
  } else request.resolve(result);
385
279
  }
386
280
 
387
- #isTerminalOutputEnvelope(value: unknown, id: string, connectionId: string, leaseId: string): boolean {
388
- if (!value || typeof value !== "object" || Array.isArray(value)) return false;
389
- const frame = value as Record<string, unknown>;
390
- if (!Object.keys(frame).every(key => ["type", "id", "connectionId", "leaseId", "ok", "result"].includes(key)))
391
- return false;
392
- if (
393
- frame.type !== "reverse_response" ||
394
- frame.id !== id ||
395
- frame.connectionId !== connectionId ||
396
- frame.leaseId !== leaseId ||
397
- frame.ok !== true
398
- )
399
- return false;
400
- const response = frame.result;
401
- if (!response || typeof response !== "object" || Array.isArray(response)) return false;
402
- const resultRecord = response as Record<string, unknown>;
403
- if (
404
- !Object.keys(resultRecord).every(key => ["output", "truncated", "exitStatus"].includes(key)) ||
405
- typeof resultRecord.output !== "string" ||
406
- typeof resultRecord.truncated !== "boolean"
407
- )
408
- return false;
409
- const exitStatus = resultRecord.exitStatus;
410
- if (exitStatus === undefined || exitStatus === null) return true;
411
- if (typeof exitStatus !== "object" || Array.isArray(exitStatus)) return false;
412
- const status = exitStatus as Record<string, unknown>;
413
- if (!Object.keys(status).every(key => ["exitCode", "signal"].includes(key))) return false;
414
- const exitCodeValid =
415
- status.exitCode === undefined ||
416
- status.exitCode === null ||
417
- (typeof status.exitCode === "number" && Number.isSafeInteger(status.exitCode));
418
- const signalValid =
419
- status.signal === undefined ||
420
- status.signal === null ||
421
- (typeof status.signal === "string" && Buffer.byteLength(status.signal, "utf-8") <= 128);
422
- return exitCodeValid && signalValid;
423
- }
424
-
425
281
  getLease(capability: string): ProviderLease | undefined {
426
282
  const lease = this.#liveLease(capability);
427
283
  return lease && { ...lease };
@@ -437,20 +293,20 @@ export class ReverseLeaseRuntime {
437
293
  this.#disposing = true;
438
294
  clearInterval(this.#sweepTimer);
439
295
  const outstanding = [...this.#outstanding.entries()];
440
- const installedCapabilities = [...this.#installedCapabilities.entries()];
441
- for (const [id] of outstanding) this.#takeOutstanding(id);
296
+ const installedCapabilities = [...this.#installedCapabilities];
297
+ this.#outstanding.clear();
442
298
  this.#installedCapabilities.clear();
443
299
  this.#leases.clear();
444
300
  this.#idempotency.clear();
445
- this.#idempotencyOrder.length = 0;
301
+ for (const request of outstanding.map(([, request]) => request))
302
+ if (request.signal && request.onAbort) request.signal.removeEventListener("abort", request.onAbort);
446
303
  for (const [id, request] of outstanding) {
447
- this.#sendCancellation(id, request);
448
304
  request.reject(new Error("request_cancelled"));
449
305
  try {
450
306
  this.#onCancel?.(id, "lease_released");
451
307
  } catch {}
452
308
  }
453
- for (const [capability] of installedCapabilities) {
309
+ for (const capability of installedCapabilities) {
454
310
  try {
455
311
  this.#onDefinitionsRemoved?.(capability);
456
312
  } catch {}
@@ -466,55 +322,22 @@ export class ReverseLeaseRuntime {
466
322
  #liveLease(capability: string): ProviderLease | undefined {
467
323
  const lease = this.#leases.get(capability);
468
324
  if (!lease?.active || lease.expiresAt <= this.#now()) {
469
- if (lease?.expiresAt !== undefined && lease.expiresAt <= this.#now()) {
470
- this.#removeDefinitions(capability, lease.leaseId);
471
- this.#cancelForLease(lease.capability, lease.leaseId);
472
- this.#evictIdempotencyForLease(lease.leaseId);
473
- }
325
+ if (lease?.expiresAt !== undefined && lease.expiresAt <= this.#now()) this.#removeDefinitions(capability);
474
326
  return undefined;
475
327
  }
476
-
477
328
  return lease;
478
329
  }
479
330
  #expireStaleLeases(): void {
480
- for (const lease of this.#leases.values()) {
481
- if (lease.expiresAt > this.#now()) continue;
482
- this.#removeDefinitions(lease.capability, lease.leaseId);
483
- this.#cancelForLease(lease.capability, lease.leaseId);
484
- this.#evictIdempotencyForLease(lease.leaseId);
485
- }
486
- }
487
- #sendCancellation(id: string, request: Outstanding): void {
488
- queueMicrotask(() => {
489
- try {
490
- const cancellation = this.#sendFrame(request.connectionId, {
491
- type: "reverse_cancel",
492
- id,
493
- connectionId: request.connectionId,
494
- leaseId: request.leaseId,
495
- });
496
- void Promise.resolve(cancellation).catch(() => {});
497
- } catch {
498
- // Cancellation is best effort; local retirement remains authoritative.
499
- }
500
- });
501
- }
502
- #cancelForLease(capability: string, leaseId: string): void {
503
- for (const [id, request] of this.#outstanding) {
504
- if (request.capability !== capability || request.leaseId !== leaseId) continue;
505
- if (this.#takeOutstanding(id) !== request) continue;
506
- this.#sendCancellation(id, request);
507
- request.reject(new Error("request_cancelled"));
508
- }
331
+ for (const lease of this.#leases.values())
332
+ if (lease.expiresAt <= this.#now()) this.#removeDefinitions(lease.capability);
509
333
  }
510
334
  #cancelForConnection(connectionId: string, reason: "provider_disconnected" | "lease_released"): void {
511
- for (const [id, request] of this.#outstanding) {
512
- if (request.connectionId !== connectionId) continue;
513
- if (this.#takeOutstanding(id) !== request) continue;
514
- this.#sendCancellation(id, request);
515
- request.reject(new Error("request_cancelled"));
516
- this.#onCancel?.(id, reason);
517
- }
335
+ for (const [id, request] of this.#outstanding)
336
+ if (request.connectionId === connectionId) {
337
+ this.#takeOutstanding(id);
338
+ request.reject(new Error("request_cancelled"));
339
+ this.#onCancel?.(id, reason);
340
+ }
518
341
  }
519
342
  #takeOutstanding(id: string): Outstanding | undefined {
520
343
  const request = this.#outstanding.get(id);
@@ -523,47 +346,17 @@ export class ReverseLeaseRuntime {
523
346
  if (request.signal && request.onAbort) request.signal.removeEventListener("abort", request.onAbort);
524
347
  return request;
525
348
  }
526
- #settleOutstandingWithFailure(id: string, request: Outstanding, failure: Error, sendCancellation = false): void {
527
- if (this.#takeOutstanding(id) !== request) return;
528
- if (sendCancellation) this.#sendCancellation(id, request);
529
- request.reject(failure);
349
+ #installDefinitionsFor(capability: string, definitions: unknown): void {
350
+ this.#installDefinitions?.(capability, definitions);
351
+ this.#installedCapabilities.add(capability);
530
352
  }
531
- #installDefinitionsFor(
532
- capability: string,
533
- definitions: unknown,
534
- leaseId: string,
535
- connectionId: string,
536
- connectionGeneration: number,
537
- ): void {
538
- this.#installDefinitions?.(capability, definitions, leaseId, connectionId, connectionGeneration);
539
- this.#installedCapabilities.set(capability, leaseId);
540
- }
541
- #removeDefinitions(capability: string, leaseId?: string): void {
542
- const installedLeaseId = this.#installedCapabilities.get(capability);
543
- if (installedLeaseId === undefined) return;
544
- if (leaseId !== undefined && installedLeaseId !== leaseId) return;
545
- this.#installedCapabilities.delete(capability);
353
+ #removeDefinitions(capability: string): void {
354
+ if (!this.#installedCapabilities.delete(capability)) return;
546
355
  this.#onDefinitionsRemoved?.(capability);
547
356
  }
548
-
549
- #rememberIdempotency(key: string, fingerprint: string, lease: ProviderLease): void {
550
- this.#idempotency.set(key, { fingerprint, lease });
551
- this.#idempotencyOrder.push(key);
552
- while (this.#idempotencyOrder.length > MAX_REVERSE_IDEMPOTENCY_ENTRIES) {
553
- const evicted = this.#idempotencyOrder.shift();
554
- if (evicted !== undefined) this.#idempotency.delete(evicted);
555
- }
556
- }
557
- #evictIdempotencyForLease(leaseId: string): void {
558
- for (const key of this.#idempotencyOrder) {
559
- if (this.#idempotency.get(key)?.lease.leaseId === leaseId) this.#idempotency.delete(key);
560
- }
561
- while (this.#idempotencyOrder.length > 0 && !this.#idempotency.has(this.#idempotencyOrder[0]!))
562
- this.#idempotencyOrder.shift();
563
- }
564
- #assertPayload(payload: unknown, maxBytes = MAX_REVERSE_PAYLOAD_BYTES): void {
357
+ #assertPayload(payload: unknown): void {
565
358
  const encoded = JSON.stringify(payload);
566
- if (encoded !== undefined && Buffer.byteLength(encoded) > maxBytes)
359
+ if (encoded !== undefined && Buffer.byteLength(encoded) > MAX_REVERSE_PAYLOAD_BYTES)
567
360
  throw new ReverseLeaseError("payload_too_large");
568
361
  }
569
362
  }