@gajae-code/coding-agent 0.15.4 → 0.15.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.
Files changed (174) hide show
  1. package/CHANGELOG.md +46 -2
  2. package/dist/types/async/job-manager.d.ts +83 -0
  3. package/dist/types/cli/auth-gateway-cli.d.ts +17 -0
  4. package/dist/types/cli/model-presets-cli.d.ts +9 -0
  5. package/dist/types/cli/update-cli.d.ts +27 -5
  6. package/dist/types/commands/auth-gateway.d.ts +3 -0
  7. package/dist/types/commands/gc.d.ts +13 -0
  8. package/dist/types/commands/model-presets.d.ts +21 -0
  9. package/dist/types/config/autorouting-tier-map.d.ts +99 -0
  10. package/dist/types/config/file-lock.d.ts +49 -2
  11. package/dist/types/config/internal/model-preset-registry-test-state.d.ts +4 -0
  12. package/dist/types/config/internal/model-preset-registry-test-support.d.ts +3 -0
  13. package/dist/types/config/keybindings.d.ts +3 -2
  14. package/dist/types/config/model-preset-registry.d.ts +368 -0
  15. package/dist/types/config/model-profile-activation.d.ts +39 -0
  16. package/dist/types/config/model-profiles.d.ts +5 -3
  17. package/dist/types/config/model-registry.d.ts +11 -5
  18. package/dist/types/config/settings-schema.d.ts +9 -0
  19. package/dist/types/coordinator-mcp/model-preset.d.ts +7 -5
  20. package/dist/types/coordinator-mcp/projection-scan.d.ts +46 -0
  21. package/dist/types/extensibility/extensions/types.d.ts +4 -4
  22. package/dist/types/extensibility/shared-events.d.ts +2 -0
  23. package/dist/types/gjc-runtime/empty-delete-gc.d.ts +58 -0
  24. package/dist/types/gjc-runtime/gc-runtime.d.ts +2 -0
  25. package/dist/types/gjc-runtime/session-state-lock.d.ts +35 -6
  26. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +6 -0
  27. package/dist/types/index.d.ts +1 -0
  28. package/dist/types/main.d.ts +3 -1
  29. package/dist/types/modes/components/jobs-overlay-model.d.ts +4 -2
  30. package/dist/types/modes/jobs-observer.d.ts +36 -0
  31. package/dist/types/sdk/broker/broker.d.ts +3 -0
  32. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +3 -0
  33. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +4 -0
  34. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +8 -1
  35. package/dist/types/sdk/cli/session-cli.d.ts +42 -1
  36. package/dist/types/sdk/client/discovery.d.ts +1 -0
  37. package/dist/types/sdk/host/host.d.ts +2 -0
  38. package/dist/types/sdk/host/model-pin.d.ts +7 -2
  39. package/dist/types/sdk/host/sdk-run-capability.d.ts +1 -0
  40. package/dist/types/sdk/host/session-runtime.d.ts +19 -1
  41. package/dist/types/sdk/prompt-deadline-manager.d.ts +11 -1
  42. package/dist/types/sdk/protocol/adapter-validation.d.ts +2 -0
  43. package/dist/types/sdk/router/session-router.d.ts +9 -0
  44. package/dist/types/session/account-inventory.d.ts +1 -0
  45. package/dist/types/session/agent-session.d.ts +19 -5
  46. package/dist/types/session/fold-coordinator.d.ts +201 -0
  47. package/dist/types/session/internal/managed-session-storage.d.ts +4 -3
  48. package/dist/types/session/sdk-run-capability-internal.d.ts +7 -0
  49. package/dist/types/session/sdk-run-capability.d.ts +2 -0
  50. package/dist/types/telemetry/control.d.ts +11 -0
  51. package/dist/types/telemetry/events.d.ts +20 -0
  52. package/dist/types/telemetry/index.d.ts +3 -0
  53. package/dist/types/telemetry/transport.d.ts +22 -0
  54. package/dist/types/tools/bash-interactive.d.ts +23 -0
  55. package/dist/types/tools/index.d.ts +6 -5
  56. package/dist/types/tools/path-utils.d.ts +5 -6
  57. package/dist/types/tools/tmux-self-injection-guard.d.ts +15 -0
  58. package/dist/types/web/search/providers/base.d.ts +9 -4
  59. package/dist/types/web/search/providers/xai.d.ts +2 -1
  60. package/dist/types/web/search/types.d.ts +14 -0
  61. package/package.json +10 -7
  62. package/scripts/generate-hotkeys-docs.ts +4 -0
  63. package/scripts/generate-sdk-operation-inventory.ts +3 -3
  64. package/src/async/job-manager.ts +671 -39
  65. package/src/cli/auth-broker-cli.ts +40 -12
  66. package/src/cli/auth-gateway-cli.ts +273 -97
  67. package/src/cli/config-cli.ts +2 -2
  68. package/src/cli/fast-help.ts +3 -0
  69. package/src/cli/model-presets-cli.ts +100 -0
  70. package/src/cli/update-cli.ts +142 -6
  71. package/src/cli.ts +6 -2
  72. package/src/commands/auth-gateway.ts +12 -8
  73. package/src/commands/gc.ts +19 -0
  74. package/src/commands/model-presets.ts +24 -0
  75. package/src/commands/sdk.ts +5 -0
  76. package/src/config/autorouting-tier-map.ts +33 -0
  77. package/src/config/file-lock-gc.ts +9 -4
  78. package/src/config/file-lock.ts +775 -59
  79. package/src/config/internal/model-preset-registry-test-state.ts +28 -0
  80. package/src/config/internal/model-preset-registry-test-support.ts +11 -0
  81. package/src/config/keybindings.ts +6 -2
  82. package/src/config/model-preset-registry.ts +2799 -0
  83. package/src/config/model-profile-activation.ts +115 -28
  84. package/src/config/model-profile-contract.ts +4 -1
  85. package/src/config/model-profiles.ts +56 -14
  86. package/src/config/model-registry.ts +1028 -123
  87. package/src/config/settings-schema.ts +11 -0
  88. package/src/config/settings.ts +6 -1
  89. package/src/coordinator-mcp/model-preset.ts +32 -17
  90. package/src/coordinator-mcp/policy.ts +1 -1
  91. package/src/coordinator-mcp/projection-scan.ts +571 -0
  92. package/src/coordinator-mcp/server.ts +19 -24
  93. package/src/coordinator-mcp/session-reaper.ts +9 -3
  94. package/src/extensibility/extensions/loader.ts +0 -1
  95. package/src/extensibility/extensions/types.ts +4 -2
  96. package/src/extensibility/gjc-plugins/runtime-adapters.ts +1 -0
  97. package/src/extensibility/shared-events.ts +2 -0
  98. package/src/gjc-runtime/empty-delete-gc.ts +301 -0
  99. package/src/gjc-runtime/gc-render.ts +29 -0
  100. package/src/gjc-runtime/gc-runtime.ts +174 -11
  101. package/src/gjc-runtime/launch-worktree.ts +44 -7
  102. package/src/gjc-runtime/ralplan-runtime.ts +14 -1
  103. package/src/gjc-runtime/session-state-lock.ts +1242 -141
  104. package/src/gjc-runtime/session-state-sidecar.ts +76 -3
  105. package/src/gjc-runtime/tmux-sessions.ts +145 -48
  106. package/src/index.ts +1 -0
  107. package/src/internal-urls/docs-index.generated.ts +14 -13
  108. package/src/lsp/config.ts +4 -3
  109. package/src/lsp/path-trust.ts +12 -6
  110. package/src/main.ts +9 -2
  111. package/src/modes/acp/acp-agent.ts +11 -5
  112. package/src/modes/components/jobs-overlay-model.ts +99 -16
  113. package/src/modes/components/model-selector.ts +158 -18
  114. package/src/modes/components/status-line/segments.ts +6 -1
  115. package/src/modes/controllers/extension-ui-controller.ts +1 -1
  116. package/src/modes/controllers/input-controller.ts +23 -6
  117. package/src/modes/interactive-mode.ts +14 -1
  118. package/src/modes/jobs-observer.ts +149 -16
  119. package/src/modes/prompt-action-autocomplete.ts +26 -47
  120. package/src/modes/runtime-init.ts +1 -1
  121. package/src/modes/utils/injected-user-submission.ts +15 -1
  122. package/src/prompts/tools/bash.md +2 -2
  123. package/src/prompts/tools/fold-receipt.md +3 -0
  124. package/src/prompts/tools/job.md +1 -1
  125. package/src/sdk/acp/adapter.ts +17 -2
  126. package/src/sdk/broker/broker.ts +48 -21
  127. package/src/sdk/broker/lifecycle.ts +32 -6
  128. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  129. package/src/sdk/bus/chat-daemon-runtime.ts +11 -1
  130. package/src/sdk/bus/kind-aware-reconciliation.ts +46 -8
  131. package/src/sdk/bus/prompt-reconciliation.ts +33 -6
  132. package/src/sdk/bus/slack-conversation.ts +5 -1
  133. package/src/sdk/bus/telegram-daemon-contract.ts +8 -1
  134. package/src/sdk/cli/session-cli.ts +161 -5
  135. package/src/sdk/client/discovery.ts +3 -1
  136. package/src/sdk/host/control/dispatch.ts +3 -0
  137. package/src/sdk/host/host.ts +7 -0
  138. package/src/sdk/host/model-pin.ts +185 -25
  139. package/src/sdk/host/sdk-run-capability.ts +1 -0
  140. package/src/sdk/host/session-runtime.test.ts +1240 -107
  141. package/src/sdk/host/session-runtime.ts +1029 -115
  142. package/src/sdk/prompt-deadline-manager.ts +16 -1
  143. package/src/sdk/protocol/adapter-validation.ts +27 -0
  144. package/src/sdk/protocol/operation-inventory.generated.json +2 -2
  145. package/src/sdk/protocol/operation-registry.ts +4 -1
  146. package/src/sdk/router/session-router.ts +297 -111
  147. package/src/sdk/session.ts +83 -12
  148. package/src/session/account-inventory.ts +27 -11
  149. package/src/session/agent-session.ts +887 -95
  150. package/src/session/fold-coordinator.ts +433 -0
  151. package/src/session/internal/managed-session-storage.ts +64 -26
  152. package/src/session/sdk-run-capability-internal.ts +15 -0
  153. package/src/session/sdk-run-capability.ts +2 -0
  154. package/src/session/session-manager.ts +29 -10
  155. package/src/setup/provider-presets.json +1 -1
  156. package/src/slash-commands/helpers/credential-switch.ts +6 -1
  157. package/src/task/executor.ts +19 -3
  158. package/src/telemetry/control.ts +26 -0
  159. package/src/telemetry/events.ts +142 -0
  160. package/src/telemetry/index.ts +3 -0
  161. package/src/telemetry/transport.ts +87 -0
  162. package/src/tools/bash-interactive.ts +182 -85
  163. package/src/tools/bash.ts +786 -67
  164. package/src/tools/index.ts +7 -5
  165. package/src/tools/path-utils.ts +77 -29
  166. package/src/tools/tmux-self-injection-guard.ts +359 -0
  167. package/src/tools/tool-catalog.generated.ts +1 -1
  168. package/src/web/search/provider.ts +120 -11
  169. package/src/web/search/providers/anthropic.ts +89 -17
  170. package/src/web/search/providers/base.ts +9 -4
  171. package/src/web/search/providers/gemini.ts +87 -15
  172. package/src/web/search/providers/openai-compatible.ts +23 -8
  173. package/src/web/search/providers/xai.ts +98 -12
  174. package/src/web/search/types.ts +16 -0
@@ -70,6 +70,7 @@ import { Settings, type SkillsSettings } from "../config/settings";
70
70
  import { resolveEagerTaskDelegation } from "../config/task-delegation";
71
71
  import { CursorExecHandlers } from "../cursor";
72
72
  import { EditTool } from "../edit";
73
+ import { describeFoldReceipt } from "../session/fold-coordinator";
73
74
  import type { BashRestrictionProfile } from "../tools/bash-allowed-prefixes";
74
75
  import { SearchTool } from "../tools/search";
75
76
  import "../discovery";
@@ -1345,14 +1346,19 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1345
1346
  const startupAuthConfig = hasInjectedAuth
1346
1347
  ? undefined
1347
1348
  : (options.startupAuthConfig ?? (await resolveStartupAuthConfig(agentDir)));
1349
+ const ownsModelRegistry = options.modelRegistry === undefined;
1348
1350
  const ownsAuthStorage = options.modelRegistry === undefined && options.authStorage === undefined;
1349
1351
  const modelRegistry =
1350
1352
  options.modelRegistry ??
1351
1353
  new ModelRegistry(
1352
1354
  options.authStorage ??
1353
1355
  (await logger.time("discoverModels", () => discoverAuthStorage(agentDir, startupAuthConfig))),
1356
+ path.join(agentDir, "models.yml"),
1357
+ undefined,
1358
+ { agentDir },
1354
1359
  );
1355
1360
  const authStorage = modelRegistry.authStorage;
1361
+ const authStorageOwner = modelRegistry.getAuthStorageOwner();
1356
1362
  if (options.authStorage && options.authStorage !== authStorage) {
1357
1363
  throw new Error(
1358
1364
  "options.authStorage and options.modelRegistry.authStorage must be the same instance when both are provided",
@@ -1390,7 +1396,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1390
1396
  let credentialScopeId: string | undefined;
1391
1397
  let credentialScopeLeased = false;
1392
1398
  let closeOwnedSettings: () => Promise<void> = async () => {};
1393
- const closeOwnedAuthStorage = (): void => {
1399
+ const closeOwnedAuthStorage = async (): Promise<void> => {
1400
+ if (ownsModelRegistry) await modelRegistry.dispose();
1394
1401
  if (!hasSession && credentialScopeLeased && credentialScopeId) {
1395
1402
  authStorage.releaseCredentialScope(credentialScopeId);
1396
1403
  credentialScopeLeased = false;
@@ -1422,10 +1429,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1422
1429
  }
1423
1430
  });
1424
1431
  const applyCredentialSelector = (scopeId: string, provider: string, selector: AuthCredentialSelector): void => {
1425
- authStorage.setSessionCredentialSelector(scopeId, provider, selector);
1432
+ authStorage.setSessionCredentialSelector(scopeId, provider, selector, authStorageOwner);
1426
1433
  };
1427
1434
  const ownsScopedSettings = options.settings === undefined;
1428
1435
  const settings = options.settings ?? (await logger.time("settings", Settings.loadForScope, { cwd, agentDir }));
1436
+ if (ownsModelRegistry) modelRegistry.setScopedSettings(settings);
1429
1437
  const autoroutingInactive =
1430
1438
  settings.get("task.autorouting.enabled") === true && !settings.getEffectiveAutorouting().active;
1431
1439
  closeOwnedSettings = async (): Promise<void> => {
@@ -1695,10 +1703,15 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1695
1703
  ) {
1696
1704
  throw new Error("Durable numeric credential pin authority changed");
1697
1705
  }
1698
- authStorage.setSessionCredentialSelector(credentialSessionId, record.provider, {
1699
- kind: pin.kind,
1700
- value: pin.value,
1701
- });
1706
+ authStorage.setSessionCredentialSelector(
1707
+ credentialSessionId,
1708
+ record.provider,
1709
+ {
1710
+ kind: pin.kind,
1711
+ value: pin.value,
1712
+ },
1713
+ authStorageOwner,
1714
+ );
1702
1715
  staleDurableCredentialPins.delete(resolveOAuthStorageProvider(record.provider));
1703
1716
  }
1704
1717
  } catch {
@@ -2066,13 +2079,49 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2066
2079
  if (asyncJobManager!.isDeliverySuppressed(jobId, job?.generation)) return;
2067
2080
  const deniedOwnedDelivery =
2068
2081
  ownedCompletion !== undefined && !isOwnedCompletionEnvelopeAllowed(ownedCompletion);
2082
+ // Fold disposition is decided by the coordinator's durable slot state.
2083
+ // `parked` means the fold transaction has not finished capturing its
2084
+ // receipt yet; it replays this completion itself, so enqueuing here
2085
+ // would double-deliver.
2086
+ const foldDisposition = job
2087
+ ? session.foldCoordinator.onDelivery(job, result)
2088
+ : ({ kind: "ordinary" } as const);
2089
+ if (foldDisposition.kind === "parked") {
2090
+ if (job) asyncJobManager?.retainParkedDelivery(job, result);
2091
+ return;
2092
+ }
2069
2093
  const formattedResult = await formatAsyncResultForFollowUp(result, !deniedOwnedDelivery);
2094
+ if (
2095
+ foldDisposition.kind === "receipt" &&
2096
+ job &&
2097
+ !session.foldCoordinator.claimCompletionDelivery(job)
2098
+ )
2099
+ return;
2100
+ // A folded job's result must arrive with its receipt so the wake turn
2101
+ // completes the original task rather than merely reporting output.
2102
+ const deliveredResult =
2103
+ foldDisposition.kind === "receipt"
2104
+ ? `${formattedResult}\n\n${describeFoldReceipt(foldDisposition.receipt)}`
2105
+ : formattedResult;
2106
+ // Exactly one transcript notice per completion: a retried delivery
2107
+ // reuses the same job object, so the claim guards against repeats.
2108
+ if (
2109
+ foldDisposition.kind === "receipt" &&
2110
+ job &&
2111
+ session.foldCoordinator.claimCompletionNotice(job)
2112
+ ) {
2113
+ session.emitNotice(
2114
+ "info",
2115
+ `Folded job ${foldDisposition.receipt.jobId} (${foldDisposition.receipt.label}) finished.`,
2116
+ "fold",
2117
+ );
2118
+ }
2070
2119
 
2071
2120
  const durationMs = job ? jobElapsedMs(job) : undefined;
2072
2121
  session.yieldQueue.enqueue<AsyncResultEntry>("async-result", {
2073
2122
  jobId,
2074
2123
  generation: job?.generation ?? "",
2075
- result: formattedResult,
2124
+ result: deliveredResult,
2076
2125
  job,
2077
2126
  durationMs,
2078
2127
  ...(ownedCompletion
@@ -2085,6 +2134,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2085
2134
  }
2086
2135
  : {}),
2087
2136
  });
2137
+ if (job) asyncJobManager?.retainDeliveryClaim(job);
2088
2138
  },
2089
2139
  })
2090
2140
  : options.inheritedAsyncJobManager;
@@ -2540,6 +2590,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2540
2590
  // (review thread P1). For a top-level session this equals the
2541
2591
  // session id.
2542
2592
  getSessionId: () => AsyncJobManager.endpointIdOf(asyncJobManager) ?? asyncJobEndpointId,
2593
+ registerForegroundFoldParticipant: adapter =>
2594
+ session?.registerForegroundFoldParticipant(adapter) ?? (() => {}),
2595
+ hasForegroundBashBackgroundRequestHandler: () => session?.hasForegroundBashBackgroundRequestHandler() ?? false,
2596
+ requestForegroundBashBackground: () => Promise.resolve(session?.requestForegroundBashBackground() ?? false),
2597
+
2543
2598
  getCredentialSessionId: () => session?.credentialSessionId ?? credentialSessionId,
2544
2599
  getMcpManager: () => mcpManager ?? options.inheritedMcpManager,
2545
2600
  isManagedSessionDestination: () => sessionManager.isManagedDestination(),
@@ -4086,18 +4141,24 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
4086
4141
  const requestStartedAt = performance.now();
4087
4142
  let stream: Awaited<ReturnType<typeof streamSimple>>;
4088
4143
  try {
4144
+ const effectiveApiKey = await modelRegistry.getApiKey(streamModel, credentialSessionId);
4089
4145
  stream = await streamSimple(streamModel, context, {
4090
4146
  ...streamOptions,
4147
+ apiKey: effectiveApiKey,
4148
+ authCredentialType: modelRegistry.getSessionCredentialType(streamModel.provider, credentialSessionId),
4091
4149
  onAuthError: async (provider, oldKey, error) => {
4092
4150
  await modelRegistry.authStorage.invalidateCredentialMatching(provider, oldKey, {
4093
4151
  signal: streamOptions?.signal,
4094
4152
  sessionId: credentialSessionId,
4153
+ owner: modelRegistry.getAuthStorageOwner(),
4095
4154
  });
4096
4155
  logger.debug("Retrying provider request after credential invalidation", {
4097
4156
  provider,
4098
4157
  error: error instanceof Error ? error.message : String(error),
4099
4158
  });
4100
- return modelRegistry.getApiKeyForProvider(provider, credentialSessionId);
4159
+ return modelRegistry.getApiKey(streamModel, credentialSessionId, {
4160
+ signal: streamOptions?.signal,
4161
+ });
4101
4162
  },
4102
4163
  });
4103
4164
  } catch (error) {
@@ -4285,7 +4346,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
4285
4346
  const sessionAsyncJobManager = asyncJobManager;
4286
4347
  if (sessionAsyncJobManager) {
4287
4348
  session.yieldQueue.register<AsyncResultEntry>("async-result", {
4288
- isStale: entry => sessionAsyncJobManager.isDeliverySuppressed(entry.jobId, entry.generation),
4349
+ isStale: entry => {
4350
+ const stale = sessionAsyncJobManager.isDeliverySuppressed(entry.jobId, entry.generation);
4351
+ if (stale) sessionAsyncJobManager.releaseDeliveryClaim(entry.generation);
4352
+ return stale;
4353
+ },
4289
4354
  // Build one message per ownership origin so an owned-scope drop of
4290
4355
  // one turn's message never suppresses other turns'/ordinary
4291
4356
  // completions batched in the same flush (review thread P2).
@@ -4293,7 +4358,13 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
4293
4358
  entry.ownedCompletion
4294
4359
  ? `${entry.ownedCompletion.lineageIdHash}\u0000${entry.ownedCompletion.promptAttemptEpoch}`
4295
4360
  : "ordinary",
4296
- build: buildAsyncResultBatchMessage,
4361
+ build: entries => {
4362
+ try {
4363
+ return buildAsyncResultBatchMessage(entries);
4364
+ } finally {
4365
+ for (const entry of entries) sessionAsyncJobManager.releaseDeliveryClaim(entry.generation);
4366
+ }
4367
+ },
4297
4368
  });
4298
4369
  }
4299
4370
  session.yieldQueue.register<McpNotificationEntry>("mcp-notification", {
@@ -4329,7 +4400,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
4329
4400
  );
4330
4401
  AsyncJobManager.unregisterManager(asyncJobManager);
4331
4402
  }
4332
- closeOwnedAuthStorage();
4403
+ await closeOwnedAuthStorage();
4333
4404
  await closeOwnedSettings();
4334
4405
  }
4335
4406
  }
@@ -4599,7 +4670,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
4599
4670
  }
4600
4671
  releaseLocalProtocolOverride();
4601
4672
  try {
4602
- closeOwnedAuthStorage();
4673
+ await closeOwnedAuthStorage();
4603
4674
  } catch (authCleanupError) {
4604
4675
  logger.warn("Failed to close owned auth storage after startup error", { error: authCleanupError });
4605
4676
  }
@@ -76,6 +76,7 @@ export interface AccountInventoryInput {
76
76
  modelRegistry?: {
77
77
  getAvailable?: () => Array<{ provider: string }>;
78
78
  getProviderBaseUrl?: (provider: string) => string | undefined;
79
+ getAuthStorageOwner?: () => object;
79
80
  };
80
81
  sessionId?: string;
81
82
  provider?: string;
@@ -323,8 +324,8 @@ function freshUsageCache(report: CachedUsageReport["report"]): AccountUsageCache
323
324
  };
324
325
  }
325
326
 
326
- function canPinStoredOAuth(authStorage: AuthStorage, provider: string): boolean {
327
- if (authStorage.hasRuntimeApiKey(provider) || authStorage.hasConfigApiKey(provider)) return false;
327
+ function canPinStoredOAuth(authStorage: AuthStorage, provider: string, owner?: object): boolean {
328
+ if (authStorage.hasRuntimeApiKey(provider) || authStorage.hasConfigApiKey(provider, owner)) return false;
328
329
  return !getEnvApiKey(provider);
329
330
  }
330
331
 
@@ -368,6 +369,7 @@ function addStoredRows(
368
369
  inventory: CredentialInventoryRecord[],
369
370
  sessionId: string | undefined,
370
371
  baseUrlResolver?: (provider: string) => string | undefined,
372
+ authStorageOwner?: object,
371
373
  ): void {
372
374
  const removalTargetIds = new Set(
373
375
  (typeof authStorage.listCredentialRemovalTargets === "function"
@@ -382,7 +384,9 @@ function addStoredRows(
382
384
  typeof record.provider === "string" &&
383
385
  authStorage.getSessionCredentialRowId(record.provider, sessionId) === record.id;
384
386
  const canPin =
385
- record.credentialKind === "oauth" && !record.disabled && canPinStoredOAuth(authStorage, record.provider);
387
+ record.credentialKind === "oauth" &&
388
+ !record.disabled &&
389
+ canPinStoredOAuth(authStorage, record.provider, authStorageOwner);
386
390
  const canRemove = removalTargetIds.has(record.id);
387
391
  rows.push({
388
392
  id: sourceId(record.provider, "stored", record.id),
@@ -417,12 +421,17 @@ function addSyntheticRows(
417
421
  authStorage: AuthStorage,
418
422
  providers: Set<string>,
419
423
  sessionId: string | undefined,
424
+ authStorageOwner?: object,
420
425
  ): void {
421
426
  for (const provider of [...providers].sort((a, b) => a.localeCompare(b))) {
422
427
  const runtime = authStorage.hasRuntimeApiKey(provider);
423
- const config = authStorage.hasConfigApiKey(provider);
428
+ const config = authStorage.hasConfigApiKey(provider, authStorageOwner);
424
429
  const env = Boolean(getEnvApiKey(provider));
425
- const effectiveType = authStorage.getEffectiveCredentialType(provider, sessionId);
430
+ const effectiveType = authStorage.getEffectiveCredentialType(
431
+ provider,
432
+ sessionId,
433
+ authStorageOwner ? { owner: authStorageOwner } : undefined,
434
+ );
426
435
 
427
436
  const add = (source: AccountInventorySource): void => {
428
437
  const selected =
@@ -469,10 +478,16 @@ export function buildAccountInventorySnapshot(input: AccountInventoryInput): Acc
469
478
  const nowMs = input.nowMs ?? Date.now();
470
479
  const inventory = input.authStorage.listCredentialInventory();
471
480
  const rows: AccountInventoryRow[] = [];
472
- addStoredRows(rows, input.authStorage, inventory, input.sessionId, provider =>
473
- input.modelRegistry?.getProviderBaseUrl?.(provider),
481
+ const authStorageOwner = input.modelRegistry?.getAuthStorageOwner?.();
482
+ addStoredRows(
483
+ rows,
484
+ input.authStorage,
485
+ inventory,
486
+ input.sessionId,
487
+ provider => input.modelRegistry?.getProviderBaseUrl?.(provider),
488
+ authStorageOwner,
474
489
  );
475
- addSyntheticRows(rows, input.authStorage, providerSet(input, inventory), input.sessionId);
490
+ addSyntheticRows(rows, input.authStorage, providerSet(input, inventory), input.sessionId, authStorageOwner);
476
491
  rows.sort((left, right) => left.id.localeCompare(right.id));
477
492
  return { generatedAt: nowMs, generation: input.authStorage.getGeneration(), rows };
478
493
  }
@@ -518,6 +533,7 @@ export async function checkAccountInventory(input: AccountInventoryInput): Promi
518
533
  const rows = input.provider ? fullSnapshot.rows.filter(row => row.provider === input.provider) : fullSnapshot.rows;
519
534
  const snapshot: AccountInventorySnapshot = { ...fullSnapshot, rows };
520
535
  const authStorage = input.authStorage;
536
+ const authStorageOwner = input.modelRegistry?.getAuthStorageOwner?.();
521
537
  applyStoredCheck(
522
538
  rows,
523
539
  await authStorage.checkCredentials({
@@ -530,13 +546,13 @@ export async function checkAccountInventory(input: AccountInventoryInput): Promi
530
546
  let key: string | undefined;
531
547
  if (row.source === "env") key = getEnvApiKey(row.provider);
532
548
  else if (row.source === "runtime" && authStorage.hasRuntimeApiKey(row.provider))
533
- key = await authStorage.peekApiKey(row.provider);
549
+ key = await authStorage.peekApiKey(row.provider, authStorageOwner ? { owner: authStorageOwner } : undefined);
534
550
  else if (
535
551
  row.source === "config" &&
536
- authStorage.hasConfigApiKey(row.provider) &&
552
+ authStorage.hasConfigApiKey(row.provider, authStorageOwner) &&
537
553
  !authStorage.hasRuntimeApiKey(row.provider)
538
554
  ) {
539
- key = await authStorage.peekApiKey(row.provider);
555
+ key = await authStorage.peekApiKey(row.provider, authStorageOwner ? { owner: authStorageOwner } : undefined);
540
556
  }
541
557
  const result = key
542
558
  ? await authStorage.checkApiKeyCredential(row.provider as Provider, key, {