@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.
- package/CHANGELOG.md +3 -1
- package/dist/types/async/job-manager.d.ts +36 -1
- package/dist/types/eval/backend.d.ts +2 -2
- package/dist/types/exec/bash-executor.d.ts +8 -10
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +0 -36
- package/dist/types/internal-urls/registry-helpers.d.ts +0 -18
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +1 -3
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +2 -4
- package/dist/types/sdk/host/host.d.ts +2 -23
- package/dist/types/sdk/host/reverse-leases.d.ts +4 -7
- package/dist/types/session/artifacts.d.ts +5 -8
- package/dist/types/session/client-bridge.d.ts +0 -14
- package/dist/types/session/internal/managed-session-storage.d.ts +2 -5
- package/dist/types/session/messages.d.ts +1 -4
- package/dist/types/session/session-manager.d.ts +4 -5
- package/dist/types/session/streaming-output.d.ts +0 -32
- package/dist/types/task/receipt.d.ts +1 -2
- package/dist/types/task/types.d.ts +43 -178
- package/dist/types/tools/bash.d.ts +1 -1
- package/dist/types/tools/fetch.d.ts +0 -3
- package/dist/types/tools/output-meta.d.ts +5 -37
- package/dist/types/tools/subagent.d.ts +15 -0
- package/dist/types/tools/todo-write.d.ts +3 -0
- package/dist/types/tools/tool-result.d.ts +0 -1
- package/package.json +8 -8
- package/src/async/job-manager.ts +430 -46
- package/src/cursor.ts +13 -20
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +2 -3
- package/src/eval/backend.ts +2 -2
- package/src/exec/bash-executor.ts +151 -425
- package/src/gjc-runtime/ultragoal-runtime.ts +3 -280
- package/src/gjc-runtime/workflow-manifest.generated.json +1 -40
- package/src/gjc-runtime/workflow-manifest.ts +0 -12
- package/src/internal-urls/agent-protocol.ts +38 -98
- package/src/internal-urls/artifact-protocol.ts +17 -28
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/internal-urls/registry-helpers.ts +0 -62
- package/src/modes/acp/acp-agent.ts +0 -24
- package/src/modes/acp/acp-client-bridge.ts +11 -154
- package/src/prompts/tools/bash.md +2 -2
- package/src/sdk/acp/adapter.ts +9 -200
- package/src/sdk/bus/chat-daemon-control.ts +3 -5
- package/src/sdk/bus/index.ts +8 -324
- package/src/sdk/bus/telegram-daemon-contract.ts +2 -4
- package/src/sdk/host/host.ts +9 -187
- package/src/sdk/host/reverse-leases.ts +42 -249
- package/src/session/agent-session.ts +15 -178
- package/src/session/artifacts.ts +74 -211
- package/src/session/client-bridge.ts +0 -15
- package/src/session/internal/managed-session-storage.ts +5 -104
- package/src/session/messages.ts +7 -32
- package/src/session/session-manager.ts +21 -12
- package/src/session/streaming-output.ts +40 -198
- package/src/task/index.ts +230 -64
- package/src/task/receipt.ts +2 -4
- package/src/task/render.ts +0 -30
- package/src/task/types.ts +10 -34
- package/src/tools/bash.ts +239 -818
- package/src/tools/eval.ts +4 -99
- package/src/tools/fetch.ts +15 -77
- package/src/tools/find.ts +3 -7
- package/src/tools/output-meta.ts +114 -696
- package/src/tools/search.ts +4 -9
- package/src/tools/subagent.ts +97 -75
- package/src/tools/todo-write.ts +47 -0
- package/src/tools/tool-result.ts +0 -1
- package/dist/types/gjc-runtime/ultragoal-review-source.d.ts +0 -83
- package/dist/types/utils/artifact-id.d.ts +0 -2
- package/src/gjc-runtime/ultragoal-review-source.ts +0 -378
- 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
|
|
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.#
|
|
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.#
|
|
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.#
|
|
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
|
-
|
|
205
|
-
|
|
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
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
441
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
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
|
-
#
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
request.reject(failure);
|
|
349
|
+
#installDefinitionsFor(capability: string, definitions: unknown): void {
|
|
350
|
+
this.#installDefinitions?.(capability, definitions);
|
|
351
|
+
this.#installedCapabilities.add(capability);
|
|
530
352
|
}
|
|
531
|
-
#
|
|
532
|
-
capability
|
|
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) >
|
|
359
|
+
if (encoded !== undefined && Buffer.byteLength(encoded) > MAX_REVERSE_PAYLOAD_BYTES)
|
|
567
360
|
throw new ReverseLeaseError("payload_too_large");
|
|
568
361
|
}
|
|
569
362
|
}
|