@gajae-code/coding-agent 0.12.5 → 0.12.6
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 +32 -0
- package/README.md +4 -1
- package/dist/types/eval/backend.d.ts +2 -2
- package/dist/types/exec/bash-executor.d.ts +10 -8
- package/dist/types/extensibility/extensions/runner.d.ts +9 -7
- package/dist/types/gjc-runtime/launch-tmux.d.ts +22 -3
- package/dist/types/gjc-runtime/psmux-detect.d.ts +3 -0
- package/dist/types/gjc-runtime/team-launch.d.ts +3 -1
- package/dist/types/gjc-runtime/team-runtime.d.ts +14 -2
- package/dist/types/gjc-runtime/team-store.d.ts +0 -4
- package/dist/types/gjc-runtime/team-workers.d.ts +5 -0
- package/dist/types/gjc-runtime/tmux-common.d.ts +1 -0
- package/dist/types/gjc-runtime/tmux-provider-context.d.ts +57 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +8 -1
- package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +3 -0
- package/dist/types/gjc-runtime/ultragoal-review-source.d.ts +83 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +57 -4
- package/dist/types/internal-urls/registry-helpers.d.ts +18 -0
- package/dist/types/modes/components/gjc-bundle-settings.d.ts +1 -0
- package/dist/types/modes/components/plugin-settings.d.ts +2 -0
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/sdk/broker/broker.d.ts +18 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +13 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +1 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +4 -3
- package/dist/types/sdk/bus/index.d.ts +4 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +4 -2
- package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -3
- package/dist/types/sdk/bus/topic-registry.d.ts +63 -3
- package/dist/types/sdk/host/host.d.ts +23 -2
- package/dist/types/sdk/host/reverse-leases.d.ts +7 -4
- package/dist/types/session/agent-session.d.ts +5 -4
- package/dist/types/session/artifacts.d.ts +8 -5
- package/dist/types/session/attempt-record-store.d.ts +19 -0
- package/dist/types/session/client-bridge.d.ts +14 -0
- package/dist/types/session/internal/managed-session-storage.d.ts +5 -2
- package/dist/types/session/messages.d.ts +4 -1
- package/dist/types/session/session-manager.d.ts +6 -4
- package/dist/types/session/session-storage.d.ts +8 -0
- package/dist/types/session/streaming-output.d.ts +32 -0
- package/dist/types/task/receipt.d.ts +2 -0
- package/dist/types/task/types.d.ts +178 -0
- package/dist/types/tools/bash.d.ts +1 -1
- package/dist/types/tools/fetch.d.ts +3 -0
- package/dist/types/tools/output-meta.d.ts +37 -5
- package/dist/types/tools/tool-result.d.ts +1 -0
- package/dist/types/utils/artifact-id.d.ts +2 -0
- package/package.json +8 -8
- package/scripts/capture-sticky-viewport-showcase.ts +131 -5
- package/scripts/verify-gjc-sdk-canonicalization.ts +128 -183
- package/scripts/verify-sticky-viewport-showcase.ts +620 -93
- package/src/config/model-profiles.ts +8 -0
- package/src/config/settings-schema.ts +17 -3
- package/src/cursor.ts +20 -13
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +5 -2
- package/src/eval/backend.ts +2 -2
- package/src/exec/bash-executor.ts +425 -151
- package/src/extensibility/extensions/runner.ts +80 -6
- package/src/extensibility/extensions/wrapper.ts +22 -15
- package/src/extensibility/runtime-skill-discovery.ts +4 -6
- package/src/gjc-runtime/launch-tmux.ts +309 -293
- package/src/gjc-runtime/managed-owner-supervisor.ts +6 -2
- package/src/gjc-runtime/psmux-detect.ts +52 -6
- package/src/gjc-runtime/team-launch.ts +50 -2
- package/src/gjc-runtime/team-runtime.ts +716 -251
- package/src/gjc-runtime/team-store.ts +25 -7
- package/src/gjc-runtime/team-workers.ts +161 -1
- package/src/gjc-runtime/tmux-common.ts +16 -4
- package/src/gjc-runtime/tmux-gc.ts +37 -5
- package/src/gjc-runtime/tmux-owner-isolation.ts +2 -0
- package/src/gjc-runtime/tmux-provider-context.ts +451 -0
- package/src/gjc-runtime/tmux-sessions.ts +409 -156
- package/src/gjc-runtime/ultragoal-change-set.ts +90 -44
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +7 -0
- package/src/gjc-runtime/ultragoal-review-source.ts +378 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +640 -159
- package/src/gjc-runtime/workflow-manifest.generated.json +40 -1
- package/src/gjc-runtime/workflow-manifest.ts +12 -0
- package/src/internal-urls/agent-protocol.ts +98 -38
- package/src/internal-urls/artifact-protocol.ts +28 -17
- package/src/internal-urls/docs-index.generated.ts +5 -5
- package/src/internal-urls/local-protocol.ts +39 -18
- package/src/internal-urls/registry-helpers.ts +62 -0
- package/src/modes/acp/acp-agent.ts +51 -13
- package/src/modes/acp/acp-client-bridge.ts +154 -11
- package/src/modes/components/gjc-bundle-settings.ts +2 -0
- package/src/modes/components/plugin-settings.ts +5 -0
- package/src/modes/components/settings-selector.ts +4 -0
- package/src/modes/controllers/selector-controller.ts +1 -0
- package/src/prompts/tools/bash.md +2 -2
- package/src/prompts/tools/monitor.md +4 -4
- package/src/sdk/acp/adapter.ts +200 -9
- package/src/sdk/broker/broker.ts +93 -10
- package/src/sdk/broker/lifecycle-ledger.ts +227 -11
- package/src/sdk/broker/lifecycle.ts +691 -92
- package/src/sdk/bus/chat-daemon-control.ts +32 -6
- package/src/sdk/bus/index.ts +347 -14
- package/src/sdk/bus/telegram-daemon-contract.ts +4 -2
- package/src/sdk/bus/telegram-daemon.ts +283 -44
- package/src/sdk/bus/topic-registry.ts +133 -10
- package/src/sdk/host/host.ts +187 -9
- package/src/sdk/host/reverse-leases.ts +249 -42
- package/src/sdk/session.ts +7 -6
- package/src/session/agent-session.ts +930 -354
- package/src/session/artifacts.ts +211 -74
- package/src/session/attempt-record-store.ts +106 -0
- package/src/session/client-bridge.ts +15 -0
- package/src/session/internal/managed-session-scope.ts +252 -67
- package/src/session/internal/managed-session-storage.ts +105 -5
- package/src/session/messages.ts +32 -7
- package/src/session/session-manager.ts +19 -21
- package/src/session/session-storage.ts +146 -24
- package/src/session/streaming-output.ts +198 -40
- package/src/task/index.ts +45 -0
- package/src/task/provider-retry-status.ts +4 -1
- package/src/task/receipt.ts +4 -0
- package/src/task/render.ts +30 -0
- package/src/task/types.ts +34 -0
- package/src/tools/bash.ts +818 -239
- package/src/tools/eval.ts +99 -4
- package/src/tools/fetch.ts +77 -15
- package/src/tools/find.ts +7 -3
- package/src/tools/monitor.ts +7 -1
- package/src/tools/output-meta.ts +706 -116
- package/src/tools/search.ts +9 -4
- package/src/tools/tool-result.ts +1 -0
- package/src/utils/artifact-id.ts +6 -0
|
@@ -5,7 +5,12 @@ 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;
|
|
8
9
|
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;
|
|
9
14
|
|
|
10
15
|
export class ReverseLeaseError extends Error {
|
|
11
16
|
constructor(
|
|
@@ -39,6 +44,7 @@ interface Outstanding {
|
|
|
39
44
|
connectionId: string;
|
|
40
45
|
capability: string;
|
|
41
46
|
leaseId: string;
|
|
47
|
+
method: string;
|
|
42
48
|
resolve: (value: unknown) => void;
|
|
43
49
|
reject: (reason: Error) => void;
|
|
44
50
|
signal?: AbortSignal;
|
|
@@ -49,7 +55,13 @@ export interface ReverseLeaseOptions {
|
|
|
49
55
|
now?: () => number;
|
|
50
56
|
leaseTtlMs?: number;
|
|
51
57
|
sendFrame: (connectionId: string, frame: SdkFrame) => void | Promise<void>;
|
|
52
|
-
installDefinitions?: (
|
|
58
|
+
installDefinitions?: (
|
|
59
|
+
capability: string,
|
|
60
|
+
definitions: unknown,
|
|
61
|
+
leaseId: string,
|
|
62
|
+
connectionId: string,
|
|
63
|
+
connectionGeneration: number,
|
|
64
|
+
) => void;
|
|
53
65
|
onCancel?: (requestId: string, reason: "provider_disconnected" | "lease_released") => void;
|
|
54
66
|
onDefinitionsRemoved?: (capability: string) => void;
|
|
55
67
|
}
|
|
@@ -81,8 +93,10 @@ export class ReverseLeaseRuntime {
|
|
|
81
93
|
readonly #onCancel?: ReverseLeaseOptions["onCancel"];
|
|
82
94
|
readonly #leases = new Map<string, ProviderLease>();
|
|
83
95
|
readonly #idempotency = new Map<string, { fingerprint: string; lease: ProviderLease }>();
|
|
96
|
+
readonly #idempotencyOrder: string[] = [];
|
|
97
|
+
|
|
84
98
|
readonly #outstanding = new Map<string, Outstanding>();
|
|
85
|
-
readonly #installedCapabilities = new
|
|
99
|
+
readonly #installedCapabilities = new Map<string, string>();
|
|
86
100
|
readonly #sweepTimer: ReturnType<typeof setInterval>;
|
|
87
101
|
#disposing = false;
|
|
88
102
|
|
|
@@ -103,11 +117,15 @@ export class ReverseLeaseRuntime {
|
|
|
103
117
|
definitions: unknown,
|
|
104
118
|
expectedLeaseId?: string,
|
|
105
119
|
idempotencyKey?: string,
|
|
120
|
+
connectionGeneration = 0,
|
|
106
121
|
): ProviderLease {
|
|
107
122
|
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");
|
|
108
125
|
const key = `${connectionId}\u0000${idempotencyKey ?? ""}`;
|
|
109
126
|
const fingerprint = registrationFingerprint(capability, definitions, expectedLeaseId);
|
|
110
127
|
const replay = idempotencyKey ? this.#idempotency.get(key) : undefined;
|
|
128
|
+
|
|
111
129
|
if (replay) {
|
|
112
130
|
if (replay.fingerprint !== fingerprint) throw new ReverseLeaseError("idempotency_conflict");
|
|
113
131
|
const current = this.#leases.get(capability);
|
|
@@ -115,12 +133,19 @@ export class ReverseLeaseRuntime {
|
|
|
115
133
|
}
|
|
116
134
|
const now = this.#now();
|
|
117
135
|
const existing = this.#leases.get(capability);
|
|
118
|
-
if (existing && existing.expiresAt <= now)
|
|
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
|
+
}
|
|
119
145
|
const pendingHandoff = existing?.active === false && existing.expiresAt > now;
|
|
120
146
|
if (pendingHandoff) {
|
|
121
147
|
if (existing!.connectionId !== connectionId || existing!.leaseId !== expectedLeaseId)
|
|
122
148
|
throw new ReverseLeaseError("provider_lease_conflict");
|
|
123
|
-
this.#installDefinitionsFor(capability, definitions);
|
|
124
149
|
const lease: ProviderLease = {
|
|
125
150
|
leaseId: existing!.leaseId,
|
|
126
151
|
connectionId,
|
|
@@ -129,17 +154,22 @@ export class ReverseLeaseRuntime {
|
|
|
129
154
|
expiresAt: now + this.#leaseTtlMs,
|
|
130
155
|
active: true,
|
|
131
156
|
};
|
|
157
|
+
this.#installDefinitionsFor(capability, definitions, lease.leaseId, lease.connectionId, connectionGeneration);
|
|
132
158
|
this.#leases.set(capability, lease);
|
|
133
|
-
if (idempotencyKey) this.#
|
|
159
|
+
if (idempotencyKey) this.#rememberIdempotency(key, fingerprint, lease);
|
|
160
|
+
|
|
134
161
|
return { ...lease };
|
|
135
162
|
}
|
|
136
163
|
const reclaiming =
|
|
137
164
|
existing?.leaseId === expectedLeaseId && existing?.graceUntil !== undefined && now <= existing.graceUntil;
|
|
138
165
|
const refreshing =
|
|
139
|
-
existing?.active !== false &&
|
|
166
|
+
existing?.active !== false &&
|
|
167
|
+
existing?.connectionId === connectionId &&
|
|
168
|
+
existing.expiresAt > now &&
|
|
169
|
+
(expectedLeaseId === undefined || existing.leaseId === expectedLeaseId);
|
|
170
|
+
|
|
140
171
|
if (existing && !reclaiming && !refreshing && existing.connectionId !== connectionId && existing.expiresAt > now)
|
|
141
172
|
throw new ReverseLeaseError("provider_lease_conflict");
|
|
142
|
-
this.#installDefinitionsFor(capability, definitions);
|
|
143
173
|
const lease: ProviderLease = {
|
|
144
174
|
leaseId: reclaiming || refreshing ? existing!.leaseId : randomUUID(),
|
|
145
175
|
connectionId,
|
|
@@ -148,17 +178,22 @@ export class ReverseLeaseRuntime {
|
|
|
148
178
|
expiresAt: now + this.#leaseTtlMs,
|
|
149
179
|
active: true,
|
|
150
180
|
};
|
|
181
|
+
this.#installDefinitionsFor(capability, definitions, lease.leaseId, lease.connectionId, connectionGeneration);
|
|
151
182
|
this.#leases.set(capability, lease);
|
|
152
|
-
if (idempotencyKey) this.#
|
|
183
|
+
if (idempotencyKey) this.#rememberIdempotency(key, fingerprint, lease);
|
|
184
|
+
|
|
153
185
|
return { ...lease };
|
|
154
186
|
}
|
|
155
187
|
|
|
156
188
|
heartbeat(connectionId: string, leaseId: string): ProviderLease {
|
|
157
189
|
const lease = this.#owner(connectionId, leaseId);
|
|
158
190
|
if (lease.expiresAt <= this.#now()) {
|
|
159
|
-
this.#
|
|
191
|
+
this.#cancelForLease(lease.capability, lease.leaseId);
|
|
192
|
+
this.#removeDefinitions(lease.capability, lease.leaseId);
|
|
193
|
+
this.#evictIdempotencyForLease(lease.leaseId);
|
|
160
194
|
throw new ReverseLeaseError("lease_expired");
|
|
161
195
|
}
|
|
196
|
+
|
|
162
197
|
lease.expiresAt = this.#now() + this.#leaseTtlMs;
|
|
163
198
|
lease.graceUntil = undefined;
|
|
164
199
|
return { ...lease };
|
|
@@ -166,8 +201,14 @@ export class ReverseLeaseRuntime {
|
|
|
166
201
|
|
|
167
202
|
release(connectionId: string, leaseId: string, handoffTo?: string): ProviderLease {
|
|
168
203
|
const lease = this.#owner(connectionId, leaseId);
|
|
169
|
-
this.#
|
|
170
|
-
|
|
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);
|
|
171
212
|
if (handoffTo) {
|
|
172
213
|
lease.connectionId = handoffTo;
|
|
173
214
|
lease.expiresAt = this.#now() + REVERSE_RECLAIM_GRACE_MS;
|
|
@@ -175,22 +216,32 @@ export class ReverseLeaseRuntime {
|
|
|
175
216
|
lease.active = false;
|
|
176
217
|
return { ...lease };
|
|
177
218
|
}
|
|
219
|
+
this.#evictIdempotencyForLease(lease.leaseId);
|
|
178
220
|
this.#leases.delete(lease.capability);
|
|
179
221
|
return { ...lease };
|
|
180
222
|
}
|
|
181
223
|
|
|
182
224
|
disconnect(connectionId: string): void {
|
|
183
225
|
const now = this.#now();
|
|
184
|
-
for (const lease of this.#leases.values())
|
|
185
|
-
if (lease.connectionId
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
+
}
|
|
190
233
|
this.#cancelForConnection(connectionId, "provider_disconnected");
|
|
191
234
|
}
|
|
192
235
|
|
|
193
|
-
request(
|
|
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> {
|
|
194
245
|
if (this.#disposing) throw new Error("reverse runtime is disposing");
|
|
195
246
|
this.#assertPayload(payload);
|
|
196
247
|
const lease = this.#liveLease(capability);
|
|
@@ -200,14 +251,24 @@ export class ReverseLeaseRuntime {
|
|
|
200
251
|
throw new ReverseLeaseError("lease_unavailable");
|
|
201
252
|
throw new ReverseLeaseError("provider_required");
|
|
202
253
|
}
|
|
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
|
+
}
|
|
203
260
|
if (signal?.aborted)
|
|
204
261
|
return Promise.reject(Object.assign(new Error("request_cancelled"), { name: "request_cancelled" }));
|
|
205
|
-
|
|
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");
|
|
206
266
|
const id = randomUUID();
|
|
207
267
|
return new Promise((resolve, reject) => {
|
|
208
268
|
const outstanding: Outstanding = {
|
|
209
269
|
connectionId: lease.connectionId,
|
|
210
270
|
capability,
|
|
271
|
+
method,
|
|
211
272
|
leaseId: lease.leaseId,
|
|
212
273
|
resolve,
|
|
213
274
|
reject,
|
|
@@ -264,13 +325,58 @@ export class ReverseLeaseRuntime {
|
|
|
264
325
|
leaseId: string,
|
|
265
326
|
result: unknown,
|
|
266
327
|
error?: { code: string; message: string },
|
|
328
|
+
wireEnvelope?: unknown,
|
|
267
329
|
): void {
|
|
268
|
-
this.#assertPayload(result);
|
|
269
330
|
const request = this.#outstanding.get(id);
|
|
270
331
|
if (!request) throw new ReverseLeaseError("unknown_request");
|
|
271
332
|
if (request.connectionId !== connectionId || request.leaseId !== leaseId)
|
|
272
333
|
throw new ReverseLeaseError("not_lease_owner");
|
|
273
|
-
this.#
|
|
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;
|
|
274
380
|
if (error) {
|
|
275
381
|
const rejection = new Error(error.message);
|
|
276
382
|
rejection.name = error.code;
|
|
@@ -278,6 +384,44 @@ export class ReverseLeaseRuntime {
|
|
|
278
384
|
} else request.resolve(result);
|
|
279
385
|
}
|
|
280
386
|
|
|
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
|
+
|
|
281
425
|
getLease(capability: string): ProviderLease | undefined {
|
|
282
426
|
const lease = this.#liveLease(capability);
|
|
283
427
|
return lease && { ...lease };
|
|
@@ -293,20 +437,20 @@ export class ReverseLeaseRuntime {
|
|
|
293
437
|
this.#disposing = true;
|
|
294
438
|
clearInterval(this.#sweepTimer);
|
|
295
439
|
const outstanding = [...this.#outstanding.entries()];
|
|
296
|
-
const installedCapabilities = [...this.#installedCapabilities];
|
|
297
|
-
this.#
|
|
440
|
+
const installedCapabilities = [...this.#installedCapabilities.entries()];
|
|
441
|
+
for (const [id] of outstanding) this.#takeOutstanding(id);
|
|
298
442
|
this.#installedCapabilities.clear();
|
|
299
443
|
this.#leases.clear();
|
|
300
444
|
this.#idempotency.clear();
|
|
301
|
-
|
|
302
|
-
if (request.signal && request.onAbort) request.signal.removeEventListener("abort", request.onAbort);
|
|
445
|
+
this.#idempotencyOrder.length = 0;
|
|
303
446
|
for (const [id, request] of outstanding) {
|
|
447
|
+
this.#sendCancellation(id, request);
|
|
304
448
|
request.reject(new Error("request_cancelled"));
|
|
305
449
|
try {
|
|
306
450
|
this.#onCancel?.(id, "lease_released");
|
|
307
451
|
} catch {}
|
|
308
452
|
}
|
|
309
|
-
for (const capability of installedCapabilities) {
|
|
453
|
+
for (const [capability] of installedCapabilities) {
|
|
310
454
|
try {
|
|
311
455
|
this.#onDefinitionsRemoved?.(capability);
|
|
312
456
|
} catch {}
|
|
@@ -322,22 +466,55 @@ export class ReverseLeaseRuntime {
|
|
|
322
466
|
#liveLease(capability: string): ProviderLease | undefined {
|
|
323
467
|
const lease = this.#leases.get(capability);
|
|
324
468
|
if (!lease?.active || lease.expiresAt <= this.#now()) {
|
|
325
|
-
if (lease?.expiresAt !== undefined && 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
|
+
}
|
|
326
474
|
return undefined;
|
|
327
475
|
}
|
|
476
|
+
|
|
328
477
|
return lease;
|
|
329
478
|
}
|
|
330
479
|
#expireStaleLeases(): void {
|
|
331
|
-
for (const lease of this.#leases.values())
|
|
332
|
-
if (lease.expiresAt
|
|
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
|
+
}
|
|
333
486
|
}
|
|
334
|
-
#
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
this.#
|
|
338
|
-
|
|
339
|
-
|
|
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.
|
|
340
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
|
+
}
|
|
509
|
+
}
|
|
510
|
+
#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
|
+
}
|
|
341
518
|
}
|
|
342
519
|
#takeOutstanding(id: string): Outstanding | undefined {
|
|
343
520
|
const request = this.#outstanding.get(id);
|
|
@@ -346,17 +523,47 @@ export class ReverseLeaseRuntime {
|
|
|
346
523
|
if (request.signal && request.onAbort) request.signal.removeEventListener("abort", request.onAbort);
|
|
347
524
|
return request;
|
|
348
525
|
}
|
|
349
|
-
#
|
|
350
|
-
this.#
|
|
351
|
-
this.#
|
|
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);
|
|
352
530
|
}
|
|
353
|
-
#
|
|
354
|
-
|
|
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);
|
|
355
546
|
this.#onDefinitionsRemoved?.(capability);
|
|
356
547
|
}
|
|
357
|
-
|
|
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 {
|
|
358
565
|
const encoded = JSON.stringify(payload);
|
|
359
|
-
if (encoded !== undefined && Buffer.byteLength(encoded) >
|
|
566
|
+
if (encoded !== undefined && Buffer.byteLength(encoded) > maxBytes)
|
|
360
567
|
throw new ReverseLeaseError("payload_too_large");
|
|
361
568
|
}
|
|
362
569
|
}
|
package/src/sdk/session.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from "@gajae-code/agent-core";
|
|
12
12
|
import {
|
|
13
13
|
type AssistantMessage,
|
|
14
|
+
type AttemptScopeRef,
|
|
14
15
|
type AuthCredentialSelector,
|
|
15
16
|
type CredentialDisabledEvent,
|
|
16
17
|
type Message,
|
|
@@ -2538,21 +2539,21 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2538
2539
|
if (!obfuscator?.hasSecrets()) return converted;
|
|
2539
2540
|
return obfuscateMessages(obfuscator, converted);
|
|
2540
2541
|
};
|
|
2541
|
-
const transformContext = async (messages: AgentMessage[], _signal?: AbortSignal) => {
|
|
2542
|
+
const transformContext = async (messages: AgentMessage[], _signal?: AbortSignal, scope?: AttemptScopeRef) => {
|
|
2542
2543
|
// External Agent events dispatch listeners without awaiting them. The
|
|
2543
2544
|
// session-owned barrier makes any pre-admission artifact transformation
|
|
2544
2545
|
// visible before this provider context is normalized.
|
|
2545
2546
|
await session?.awaitPendingContextTransformations();
|
|
2546
|
-
return extensionRunner ? await extensionRunner.emitContext(messages) : messages;
|
|
2547
|
+
return extensionRunner ? await extensionRunner.emitContext(messages, scope) : messages;
|
|
2547
2548
|
};
|
|
2548
2549
|
const onPayload = extensionRunner
|
|
2549
|
-
? async (payload: unknown, _model?: Model) => {
|
|
2550
|
-
return await extensionRunner.emitBeforeProviderRequest(payload);
|
|
2550
|
+
? async (payload: unknown, _model?: Model, scope?: AttemptScopeRef) => {
|
|
2551
|
+
return await extensionRunner.emitBeforeProviderRequest(payload, scope);
|
|
2551
2552
|
}
|
|
2552
2553
|
: undefined;
|
|
2553
2554
|
const onResponse: SimpleStreamOptions["onResponse"] | undefined = extensionRunner
|
|
2554
|
-
? async (response, model) => {
|
|
2555
|
-
await extensionRunner.emitAfterProviderResponse(response, model);
|
|
2555
|
+
? async (response, model, scope) => {
|
|
2556
|
+
await extensionRunner.emitAfterProviderResponse(response, model, scope);
|
|
2556
2557
|
}
|
|
2557
2558
|
: undefined;
|
|
2558
2559
|
|