@gajae-code/coding-agent 0.12.6 → 0.12.8

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 (150) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/dist/types/async/job-manager.d.ts +36 -1
  3. package/dist/types/commands/notify.d.ts +4 -0
  4. package/dist/types/config/file-lock.d.ts +3 -0
  5. package/dist/types/config/model-discovery-manager.d.ts +6 -1
  6. package/dist/types/config/model-registry.d.ts +2 -0
  7. package/dist/types/config/resolve-config-value.d.ts +2 -1
  8. package/dist/types/config/settings-schema.d.ts +17 -1
  9. package/dist/types/eval/backend.d.ts +2 -2
  10. package/dist/types/exec/bash-executor.d.ts +8 -10
  11. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +0 -36
  12. package/dist/types/internal-urls/registry-helpers.d.ts +0 -18
  13. package/dist/types/main.d.ts +1 -0
  14. package/dist/types/modes/components/hook-selector.d.ts +20 -0
  15. package/dist/types/modes/components/notifications-settings-editor.d.ts +53 -4
  16. package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
  17. package/dist/types/modes/controllers/selector-controller.d.ts +4 -2
  18. package/dist/types/sdk/broker/broker.d.ts +5 -1
  19. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +1 -0
  20. package/dist/types/sdk/bus/chat-daemon-control.d.ts +11 -3
  21. package/dist/types/sdk/bus/config.d.ts +75 -113
  22. package/dist/types/sdk/bus/discord-live-provider.d.ts +8 -2
  23. package/dist/types/sdk/bus/discord-provider.d.ts +19 -0
  24. package/dist/types/sdk/bus/index.d.ts +1 -1
  25. package/dist/types/sdk/bus/notification-orchestration.d.ts +127 -18
  26. package/dist/types/sdk/bus/notification-service.d.ts +31 -4
  27. package/dist/types/sdk/bus/session-control.d.ts +8 -3
  28. package/dist/types/sdk/bus/slack-live-provider.d.ts +9 -2
  29. package/dist/types/sdk/bus/slack-provider.d.ts +22 -0
  30. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -4
  31. package/dist/types/sdk/client/discovery.d.ts +2 -1
  32. package/dist/types/sdk/host/host.d.ts +2 -23
  33. package/dist/types/sdk/host/query/handlers.d.ts +2 -0
  34. package/dist/types/sdk/host/reverse-leases.d.ts +4 -7
  35. package/dist/types/sdk/index.d.ts +1 -0
  36. package/dist/types/sdk/providers.d.ts +22 -0
  37. package/dist/types/sdk/session-directory.d.ts +1 -1
  38. package/dist/types/session/artifacts.d.ts +5 -8
  39. package/dist/types/session/client-bridge.d.ts +0 -14
  40. package/dist/types/session/internal/managed-session-scope.d.ts +17 -1
  41. package/dist/types/session/internal/managed-session-storage.d.ts +11 -4
  42. package/dist/types/session/internal/native-publish-outcome.d.ts +1 -0
  43. package/dist/types/session/internal/session-open-errors.d.ts +5 -0
  44. package/dist/types/session/messages.d.ts +1 -4
  45. package/dist/types/session/session-manager.d.ts +7 -6
  46. package/dist/types/session/session-storage.d.ts +2 -0
  47. package/dist/types/session/streaming-output.d.ts +0 -32
  48. package/dist/types/task/receipt.d.ts +1 -2
  49. package/dist/types/task/types.d.ts +43 -178
  50. package/dist/types/tools/bash.d.ts +1 -1
  51. package/dist/types/tools/fetch.d.ts +0 -3
  52. package/dist/types/tools/output-meta.d.ts +5 -37
  53. package/dist/types/tools/subagent.d.ts +15 -0
  54. package/dist/types/tools/todo-write.d.ts +3 -0
  55. package/dist/types/tools/tool-result.d.ts +0 -1
  56. package/package.json +10 -8
  57. package/scripts/build-sdk-package-smoke.ts +51 -0
  58. package/scripts/capture-notifications-settings-showcase.ts +1 -1
  59. package/src/async/job-manager.ts +430 -46
  60. package/src/cli/notify-cli.ts +204 -94
  61. package/src/commands/notify.ts +19 -8
  62. package/src/config/file-lock.ts +6 -0
  63. package/src/config/model-discovery-manager.ts +50 -8
  64. package/src/config/model-registry.ts +862 -75
  65. package/src/config/resolve-config-value.ts +10 -8
  66. package/src/config/settings-schema.ts +25 -15
  67. package/src/config/settings.ts +15 -15
  68. package/src/cursor.ts +13 -20
  69. package/src/defaults/gjc/skills/ultragoal/SKILL.md +2 -3
  70. package/src/eval/backend.ts +2 -2
  71. package/src/exec/bash-executor.ts +151 -425
  72. package/src/gjc-runtime/session-state-sidecar.ts +12 -2
  73. package/src/gjc-runtime/ultragoal-runtime.ts +3 -280
  74. package/src/gjc-runtime/workflow-manifest.generated.json +1 -40
  75. package/src/gjc-runtime/workflow-manifest.ts +0 -12
  76. package/src/internal-urls/agent-protocol.ts +38 -98
  77. package/src/internal-urls/artifact-protocol.ts +17 -28
  78. package/src/internal-urls/docs-index.generated.ts +7 -7
  79. package/src/internal-urls/registry-helpers.ts +0 -62
  80. package/src/main.ts +43 -7
  81. package/src/modes/DESIGN.md +11 -11
  82. package/src/modes/acp/acp-agent.ts +0 -24
  83. package/src/modes/acp/acp-client-bridge.ts +11 -154
  84. package/src/modes/components/hook-selector.ts +155 -44
  85. package/src/modes/components/model-selector.ts +242 -49
  86. package/src/modes/components/notifications-settings-editor.ts +631 -85
  87. package/src/modes/controllers/extension-ui-controller.ts +114 -15
  88. package/src/modes/controllers/selector-controller.ts +464 -50
  89. package/src/prompts/tools/bash.md +2 -2
  90. package/src/sdk/acp/adapter.ts +9 -200
  91. package/src/sdk/broker/broker.ts +54 -10
  92. package/src/sdk/broker/lifecycle-ledger.ts +34 -1
  93. package/src/sdk/broker/lifecycle.ts +47 -50
  94. package/src/sdk/bus/chat-daemon-cli.ts +12 -5
  95. package/src/sdk/bus/chat-daemon-control.ts +35 -8
  96. package/src/sdk/bus/chat-daemon-runtime.ts +16 -2
  97. package/src/sdk/bus/config.ts +534 -286
  98. package/src/sdk/bus/discord-live-provider.ts +57 -2
  99. package/src/sdk/bus/discord-provider.ts +21 -0
  100. package/src/sdk/bus/index.ts +105 -359
  101. package/src/sdk/bus/notification-orchestration.ts +371 -68
  102. package/src/sdk/bus/notification-service.ts +431 -130
  103. package/src/sdk/bus/session-control.ts +119 -44
  104. package/src/sdk/bus/slack-live-provider.ts +78 -2
  105. package/src/sdk/bus/slack-provider.ts +24 -0
  106. package/src/sdk/bus/telegram-daemon-cli.ts +9 -2
  107. package/src/sdk/bus/telegram-daemon-contract.ts +9 -4
  108. package/src/sdk/bus/telegram-daemon-control.ts +3 -3
  109. package/src/sdk/bus/telegram-daemon.ts +23 -15
  110. package/src/sdk/client/discovery.ts +10 -4
  111. package/src/sdk/host/host.ts +9 -187
  112. package/src/sdk/host/query/handlers.ts +20 -1
  113. package/src/sdk/host/reverse-leases.ts +42 -249
  114. package/src/sdk/index.ts +1 -0
  115. package/src/sdk/protocol/operation-inventory.generated.json +18 -0
  116. package/src/sdk/protocol/operation-registry.ts +5 -2
  117. package/src/sdk/providers.ts +51 -0
  118. package/src/sdk/session-directory.ts +1 -0
  119. package/src/sdk/session.ts +10 -7
  120. package/src/session/agent-session.ts +59 -202
  121. package/src/session/artifacts.ts +74 -211
  122. package/src/session/client-bridge.ts +0 -15
  123. package/src/session/internal/managed-session-scope.ts +981 -450
  124. package/src/session/internal/managed-session-storage.ts +362 -124
  125. package/src/session/internal/native-publish-outcome.ts +3 -1
  126. package/src/session/internal/session-open-errors.ts +11 -0
  127. package/src/session/messages.ts +7 -32
  128. package/src/session/session-manager.ts +48 -15
  129. package/src/session/session-storage.ts +72 -5
  130. package/src/session/streaming-output.ts +40 -198
  131. package/src/slash-commands/builtin-registry.ts +82 -4
  132. package/src/task/index.ts +237 -65
  133. package/src/task/receipt.ts +2 -4
  134. package/src/task/render.ts +0 -30
  135. package/src/task/types.ts +10 -34
  136. package/src/tools/ask.ts +1 -1
  137. package/src/tools/bash.ts +239 -818
  138. package/src/tools/eval.ts +4 -99
  139. package/src/tools/fetch.ts +15 -77
  140. package/src/tools/find.ts +3 -7
  141. package/src/tools/output-meta.ts +114 -696
  142. package/src/tools/search.ts +4 -9
  143. package/src/tools/subagent.ts +97 -75
  144. package/src/tools/telegram-send.ts +4 -2
  145. package/src/tools/todo-write.ts +47 -0
  146. package/src/tools/tool-result.ts +0 -1
  147. package/dist/types/gjc-runtime/ultragoal-review-source.d.ts +0 -83
  148. package/dist/types/utils/artifact-id.d.ts +0 -2
  149. package/src/gjc-runtime/ultragoal-review-source.ts +0 -378
  150. package/src/utils/artifact-id.ts +0 -6
@@ -4,21 +4,11 @@ import type { BrokerIndexWriter, HostEndpointAdapters, SdkFrame } from "./types"
4
4
 
5
5
  export type SdkRequestObserver = (kind: "control" | "query", connectionId: string, frame: SdkFrame) => void;
6
6
 
7
- export interface ProviderLeaseIdentity {
8
- leaseId: string;
9
- connectionId: string;
10
- connectionGeneration: number;
11
- fence: string;
12
- }
13
-
14
- export type ProviderLeaseFence = (identity: ProviderLeaseIdentity, reason?: string) => boolean;
15
-
16
7
  export interface SessionSdkHostOptions extends HostEndpointAdapters {
17
8
  control?: (connectionId: string, frame: SdkFrame) => unknown | Promise<unknown>;
18
9
  query?: (connectionId: string, frame: SdkFrame) => unknown | Promise<unknown>;
19
10
  /** Best-effort diagnostic observation of accepted control/query frames. */
20
11
  onRequest?: SdkRequestObserver;
21
- onFrameOverflow?: (connectionId: string) => void;
22
12
  /** Runs before a control response is sent; identity transitions use sendTerminal. */
23
13
  beforeControlResponse?: (
24
14
  connectionId: string,
@@ -28,25 +18,13 @@ export interface SessionSdkHostOptions extends HostEndpointAdapters {
28
18
  ) => void | Promise<void>;
29
19
  /** Runs only after a successful control response has been sent to the client. */
30
20
  afterControlResponse?: (connectionId: string, request: SdkFrame, response: SdkFrame) => void | Promise<void>;
31
- onProviderLeaseRegistered?: (lease: ProviderLease, connectionGeneration: number) => void;
32
- installProviderDefinitions?: (
33
- capability: string,
34
- definitions: unknown,
35
- leaseId: string,
36
- connectionId: string,
37
- connectionGeneration: number,
38
- ) => void;
21
+ installProviderDefinitions?: (capability: string, definitions: unknown) => void;
39
22
  onProviderDefinitionsRemoved?: (capability: string) => void;
40
23
  onReverseCancel?: (requestId: string, reason: "provider_disconnected" | "lease_released") => void;
41
24
  /** Best-effort capabilities mirrored from the native transport for out-of-band consumers. */
42
25
  connectionCapabilities?: (connectionId: string) => ReadonlySet<string> | undefined;
43
26
  }
44
27
 
45
- /** Bound asynchronous frame admission per transport connection. */
46
- const MAX_PENDING_FRAMES_PER_CONNECTION = 32;
47
- const MAX_QUARANTINED_PROVIDER_LEASES = 256;
48
- const MAX_RETAINED_CLOSED_CONNECTIONS = 256;
49
-
50
28
  const TOOL_ACTIVITY_CAPABILITY = "tool_activity_v2";
51
29
  const CAP_GATED_FRAME_KINDS = new Set(["tool_activity", "reasoning_summary"]);
52
30
  const EMPTY_CAPABILITIES: ReadonlySet<string> = new Set();
@@ -142,12 +120,6 @@ export class SessionSdkHost {
142
120
  #stopPromise?: Promise<"stopped">;
143
121
  #unsubscribe?: () => void;
144
122
  #registration?: { writer: BrokerIndexWriter; generation: number };
145
- #connectionGenerations = new Map<string, number>();
146
- #openConnections = new Set<string>();
147
- #pendingFrames = new Map<string, { generation: number; count: number }>();
148
- #leaseRecords = new Map<string, { capability: string; connectionId: string; connectionGeneration: number }>();
149
- #quarantinedLeases = new Map<string, ProviderLeaseIdentity>();
150
- #closedConnectionOrder: string[] = [];
151
123
 
152
124
  constructor(options: SessionSdkHostOptions) {
153
125
  this.#options = options;
@@ -169,120 +141,9 @@ export class SessionSdkHost {
169
141
  getProviderDefinitions(capability: string): unknown | undefined {
170
142
  return this.reverse.getInstalledDefinitions(capability);
171
143
  }
172
- /** Mark a transport connection live and return its stable generation. */
173
- handleConnect(connectionId: string): number {
174
- if (!this.#openConnections.has(connectionId)) {
175
- const generation = this.#connectionGenerations.get(connectionId) ?? 1;
176
- this.#connectionGenerations.set(connectionId, generation);
177
- this.#openConnections.add(connectionId);
178
- this.#pendingFrames.set(connectionId, { generation, count: 0 });
179
- }
180
- return this.#connectionGenerations.get(connectionId) ?? 0;
181
- }
182
-
183
- /** Current generation for a transport connection, if it has been observed. */
184
- connectionGeneration(connectionId: string): number {
185
- return this.#connectionGenerations.get(connectionId) ?? 0;
186
- }
187
-
188
- isConnectionOpen(connectionId: string, generation?: number): boolean {
189
- return (
190
- this.#openConnections.has(connectionId) &&
191
- (generation === undefined || this.#connectionGenerations.get(connectionId) === generation)
192
- );
193
- }
194
-
195
144
  /** Release reverse leases after the transport reports a WebSocket disconnect. */
196
145
  handleDisconnect(connectionId: string): void {
197
- if (!this.#openConnections.delete(connectionId)) return;
198
- this.#connectionGenerations.set(connectionId, (this.#connectionGenerations.get(connectionId) ?? 0) + 1);
199
- this.#pendingFrames.delete(connectionId);
200
- for (const [leaseId, record] of this.#leaseRecords) {
201
- if (record.connectionId === connectionId) this.#leaseRecords.delete(leaseId);
202
- }
203
146
  this.reverse.disconnect(connectionId);
204
- this.#closedConnectionOrder.push(connectionId);
205
- while (this.#closedConnectionOrder.length > MAX_RETAINED_CLOSED_CONNECTIONS) {
206
- const retired = this.#closedConnectionOrder.shift();
207
- if (retired && !this.#openConnections.has(retired)) this.#connectionGenerations.delete(retired);
208
- }
209
- }
210
-
211
- /**
212
- * Fences one exact provider lease after uncertain terminal cleanup. The fence
213
- * is idempotent for the same identity and never affects a newer generation.
214
- */
215
- quarantineProviderLease(identity: ProviderLeaseIdentity, _reason?: string): boolean {
216
- if (
217
- !identity ||
218
- typeof identity.leaseId !== "string" ||
219
- identity.leaseId.length === 0 ||
220
- typeof identity.connectionId !== "string" ||
221
- identity.connectionId.length === 0 ||
222
- !Number.isSafeInteger(identity.connectionGeneration) ||
223
- identity.connectionGeneration < 0 ||
224
- typeof identity.fence !== "string" ||
225
- identity.fence.length === 0
226
- )
227
- return false;
228
- const previous = this.#quarantinedLeases.get(identity.leaseId);
229
- if (
230
- previous &&
231
- previous.connectionId === identity.connectionId &&
232
- previous.connectionGeneration === identity.connectionGeneration &&
233
- previous.fence === identity.fence
234
- )
235
- return true;
236
- if (this.#quarantinedLeases.size >= MAX_QUARANTINED_PROVIDER_LEASES) return false;
237
- if (this.#connectionGenerations.get(identity.connectionId) !== identity.connectionGeneration) return false;
238
- const record = this.#leaseRecords.get(identity.leaseId);
239
- if (
240
- !record ||
241
- record.connectionId !== identity.connectionId ||
242
- record.connectionGeneration !== identity.connectionGeneration
243
- )
244
- return false;
245
- const lease = this.reverse.getLease(record.capability);
246
- if (!lease || lease.leaseId !== identity.leaseId || lease.connectionId !== identity.connectionId || !lease.active)
247
- return false;
248
- try {
249
- this.reverse.release(identity.connectionId, identity.leaseId);
250
- } catch {
251
- return false;
252
- }
253
- this.#quarantinedLeases.set(identity.leaseId, { ...identity });
254
- return true;
255
- }
256
-
257
- /** Dispatch a frame through bounded per-connection asynchronous admission. */
258
- dispatchFrame(connectionId: string, frame: SdkFrame): Promise<void> {
259
- const generation = this.#connectionGenerations.has(connectionId)
260
- ? (this.#connectionGenerations.get(connectionId) ?? 0)
261
- : this.handleConnect(connectionId);
262
- const pending = this.#pendingFrames.get(connectionId);
263
- const pendingCount = pending?.generation === generation ? pending.count : 0;
264
- if (pendingCount >= MAX_PENDING_FRAMES_PER_CONNECTION) {
265
- this.#options.onFrameOverflow?.(connectionId);
266
- return Promise.resolve();
267
- }
268
- this.#pendingFrames.set(connectionId, { generation, count: pendingCount + 1 });
269
- const task = Promise.resolve().then(() => {
270
- if (this.#connectionGenerations.get(connectionId) !== generation) return;
271
- if (!this.#openConnections.has(connectionId)) return;
272
- return this.#onFrame(connectionId, frame, generation);
273
- });
274
- void task.finally(() => {
275
- const current = this.#pendingFrames.get(connectionId);
276
- if (!current || current.generation !== generation) return;
277
- this.#pendingFrames.set(connectionId, {
278
- generation,
279
- count: Math.max(0, current.count - 1),
280
- });
281
- });
282
- return task.then(
283
- () => {},
284
- () => {},
285
- );
286
147
  }
287
148
 
288
149
  /** Adds an event to the resumable event ring. Transport delivery is owned by bus wiring. */
@@ -295,7 +156,7 @@ export class SessionSdkHost {
295
156
  this.events.restart();
296
157
  this.emitEvent({ name: "session_ready", sessionId: this.#options.sessionId, generation: this.events.generation });
297
158
  const disposer = this.#options.onFrame((connectionId, frame) => {
298
- void this.dispatchFrame(connectionId, frame);
159
+ void this.#onFrame(connectionId, frame);
299
160
  });
300
161
  this.#unsubscribe = typeof disposer === "function" ? disposer : undefined;
301
162
  this.#started = true;
@@ -360,30 +221,18 @@ export class SessionSdkHost {
360
221
  }
361
222
  }
362
223
 
363
- async #onFrame(connectionId: string, frame: SdkFrame, generation: number): Promise<void> {
364
- if (this.#connectionGenerations.has(connectionId) && this.#connectionGenerations.get(connectionId) !== generation)
365
- return;
224
+ async #onFrame(connectionId: string, frame: SdkFrame): Promise<void> {
366
225
  try {
367
226
  switch (frame.type) {
368
227
  case "control_request": {
369
228
  this.#observeRequest("control", connectionId, frame);
370
229
  const result = await this.#options.control?.(connectionId, frame);
371
- if (
372
- this.#connectionGenerations.has(connectionId) &&
373
- this.#connectionGenerations.get(connectionId) !== generation
374
- )
375
- return;
376
230
  if (result !== undefined) {
377
231
  const response = { type: "control_response", ...(result as SdkFrame) };
378
232
  let terminalSent = false;
379
233
  const sendTerminal = async (): Promise<void> => {
380
234
  if (terminalSent) return;
381
235
  terminalSent = true;
382
- if (
383
- this.#connectionGenerations.has(connectionId) &&
384
- this.#connectionGenerations.get(connectionId) !== generation
385
- )
386
- return;
387
236
  await this.#send(connectionId, response);
388
237
  };
389
238
  await this.#options.beforeControlResponse?.(connectionId, frame, response, sendTerminal);
@@ -422,11 +271,6 @@ export class SessionSdkHost {
422
271
  case "query_request": {
423
272
  this.#observeRequest("query", connectionId, frame);
424
273
  const result = await this.#options.query?.(connectionId, frame);
425
- if (
426
- this.#connectionGenerations.has(connectionId) &&
427
- this.#connectionGenerations.get(connectionId) !== generation
428
- )
429
- return;
430
274
  if (result !== undefined)
431
275
  await this.#send(connectionId, { type: "query_response", ...(result as SdkFrame) });
432
276
  break;
@@ -436,30 +280,13 @@ export class SessionSdkHost {
436
280
  requireConnection(connectionId, frame);
437
281
  const capability = requiredString(frame, "capability");
438
282
  if (!has(frame, "definitions")) throw invalidFrame("definitions is required.");
439
- const expectedLeaseId = optionalString(frame, "expectedLeaseId");
440
- if (expectedLeaseId && this.#quarantinedLeases.has(expectedLeaseId))
441
- throw new ReverseLeaseError("lease_unavailable");
442
- const connectionGeneration = this.#connectionGenerations.get(connectionId) ?? 0;
443
283
  const lease = this.reverse.registerProvider(
444
284
  connectionId,
445
285
  capability,
446
286
  frame.definitions,
447
- expectedLeaseId,
287
+ optionalString(frame, "expectedLeaseId"),
448
288
  optionalString(frame, "idempotencyKey"),
449
- connectionGeneration,
450
289
  );
451
- for (const [priorLeaseId, record] of this.#leaseRecords) {
452
- if (record.capability === lease.capability && priorLeaseId !== lease.leaseId)
453
- this.#leaseRecords.delete(priorLeaseId);
454
- }
455
- this.#leaseRecords.set(lease.leaseId, {
456
- capability: lease.capability,
457
- connectionId: lease.connectionId,
458
- connectionGeneration,
459
- });
460
- try {
461
- this.#options.onProviderLeaseRegistered?.(lease, connectionGeneration);
462
- } catch {}
463
290
  await this.#send(connectionId, {
464
291
  id: frame.id,
465
292
  type: "register_provider_result",
@@ -479,7 +306,6 @@ export class SessionSdkHost {
479
306
  requireConnection(connectionId, frame);
480
307
  const handoffTo = optionalString(frame, "handoffTo");
481
308
  const lease = this.reverse.release(connectionId, requiredString(frame, "leaseId"), handoffTo);
482
- this.#leaseRecords.delete(lease.leaseId);
483
309
  await this.#send(connectionId, leaseState(undefined, lease));
484
310
  break;
485
311
  }
@@ -492,7 +318,7 @@ export class SessionSdkHost {
492
318
  if (frame.ok) {
493
319
  if (!has(frame, "result") || has(frame, "error"))
494
320
  throw invalidFrame("Successful reverse responses require result and no error.");
495
- this.reverse.respond(connectionId, id, leaseId, frame.result, undefined, frame);
321
+ this.reverse.respond(connectionId, id, leaseId, frame.result);
496
322
  } else {
497
323
  if (
498
324
  has(frame, "result") ||
@@ -501,14 +327,10 @@ export class SessionSdkHost {
501
327
  typeof responseError.message !== "string"
502
328
  )
503
329
  throw invalidFrame("Failed reverse responses require a structured error and no result.");
504
- this.reverse.respond(
505
- connectionId,
506
- id,
507
- leaseId,
508
- undefined,
509
- { code: responseError.code, message: responseError.message },
510
- frame,
511
- );
330
+ this.reverse.respond(connectionId, id, leaseId, undefined, {
331
+ code: responseError.code,
332
+ message: responseError.message,
333
+ });
512
334
  }
513
335
  break;
514
336
  }
@@ -1,4 +1,6 @@
1
1
  import { PROMPT_CLIENT_REF_MAX_LENGTH } from "../../prompt-status.js";
2
+ import type { ActiveProviderDescriptor } from "../../providers.js";
3
+ import { ActiveProviderResolutionError } from "../../providers.js";
2
4
  import {
3
5
  assertCursorSelector,
4
6
  type CursorEnvelope,
@@ -22,6 +24,7 @@ export interface SessionSurface {
22
24
  getUsage(): unknown | Promise<unknown>;
23
25
  getModels(): unknown | Promise<unknown>;
24
26
  getSkillState(): unknown | Promise<unknown>;
27
+ getActiveProviders?(): ActiveProviderDescriptor[] | Promise<ActiveProviderDescriptor[]>;
25
28
  /** Q12 rows preserve workflow gate fields and include stable durable gate metadata. */
26
29
  getGates(): unknown | Promise<unknown>;
27
30
  getConfigItems(): unknown | Promise<unknown>;
@@ -102,6 +105,7 @@ const sources: Record<string, { resource: string; method: keyof SessionSurface;
102
105
  Q21: { resource: "queue", method: "getQueueMessages", mvcc: true },
103
106
  Q22: { resource: "extensions", method: "getExtensions", mvcc: true },
104
107
  Q25: { resource: "jobs", method: "getJobs", mvcc: false },
108
+ Q29: { resource: "activeProviders", method: "getActiveProviders", mvcc: false },
105
109
  Q27: { resource: "modelProfiles", method: "getModelProfiles", mvcc: true },
106
110
  };
107
111
  const names = [
@@ -133,6 +137,7 @@ const names = [
133
137
  "turn.prompt_status",
134
138
  "models.profiles.list",
135
139
  "skill.invoke_status",
140
+ "providers.list/active",
136
141
  ];
137
142
 
138
143
  export class QueryHandlers {
@@ -168,6 +173,15 @@ export class QueryHandlers {
168
173
  return this.#error(request, "invalid_request", false, "models.profiles.list does not accept input fields.");
169
174
  if (query === "Q27" && typeof this.surface.getModelProfiles !== "function")
170
175
  return this.#error(request, "unavailable", false, "models.profiles.list is unavailable for this session.");
176
+ if (query === "Q29" && request.input && Object.keys(request.input).length > 0)
177
+ return this.#error(
178
+ request,
179
+ "invalid_request",
180
+ false,
181
+ "providers.list/active does not accept input fields.",
182
+ );
183
+ if (query === "Q29" && typeof this.surface.getActiveProviders !== "function")
184
+ return this.#error(request, "unavailable", false, "providers.list/active is unavailable for this session.");
171
185
  const source = sources[query];
172
186
  if (!source) return this.#error(request, "invalid_request");
173
187
  return await this.#pageSource(request, query, source);
@@ -257,7 +271,12 @@ export class QueryHandlers {
257
271
  source.resource === "transcript" ? { highWatermark: cursor.highWatermark } : {},
258
272
  );
259
273
  } else {
260
- snapshot = await (this.surface[source.method] as () => unknown)();
274
+ try {
275
+ snapshot = await (this.surface[source.method] as () => unknown)();
276
+ } catch (error) {
277
+ if (queryId === "Q29") throw new ActiveProviderResolutionError();
278
+ throw error;
279
+ }
261
280
  revision = await this.revisions.createRevision(source.resource, resourceId, snapshot);
262
281
  }
263
282
  if (snapshot === undefined) return this.#error(request, "resource_gone");