@gajae-code/coding-agent 0.18.11 → 0.18.12

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 (270) hide show
  1. package/CHANGELOG.md +293 -0
  2. package/dist/types/async/job-manager.d.ts +7 -3
  3. package/dist/types/async/result-formatting.d.ts +2 -0
  4. package/dist/types/autoresearch/git.d.ts +1 -1
  5. package/dist/types/capability/fs.d.ts +2 -2
  6. package/dist/types/cli/accounts-cli.d.ts +2 -2
  7. package/dist/types/cli/doctor/install-repairs.d.ts +4 -1
  8. package/dist/types/cli/install-activation.d.ts +10 -4
  9. package/dist/types/cli/public-command-errors.d.ts +1 -0
  10. package/dist/types/cli/update-cli.d.ts +14 -1
  11. package/dist/types/cli-ordinary.d.ts +2 -2
  12. package/dist/types/commands/accounts.d.ts +2 -1
  13. package/dist/types/commands/migrate.d.ts +1 -1
  14. package/dist/types/commands/sdk.d.ts +5 -0
  15. package/dist/types/commit/changelog/index.d.ts +1 -0
  16. package/dist/types/config/autorouting-tier-map.d.ts +30 -0
  17. package/dist/types/config/config-hot-reload-trust.d.ts +12 -0
  18. package/dist/types/config/config-hot-reload.d.ts +39 -0
  19. package/dist/types/config/file-lock.d.ts +8 -1
  20. package/dist/types/config/model-discovery-manager.d.ts +2 -0
  21. package/dist/types/config/model-profile-activation.d.ts +62 -6
  22. package/dist/types/config/model-registry.d.ts +56 -5
  23. package/dist/types/config/model-resolver.d.ts +2 -0
  24. package/dist/types/config/models-config-schema.d.ts +10 -0
  25. package/dist/types/config/settings-schema.d.ts +46 -3
  26. package/dist/types/config/settings.d.ts +33 -1
  27. package/dist/types/dap/config.d.ts +8 -4
  28. package/dist/types/debug/system-info.d.ts +6 -1
  29. package/dist/types/discovery/mcp-compat.d.ts +3 -1
  30. package/dist/types/edit/modes/patch.d.ts +5 -0
  31. package/dist/types/edit/modes/replace.d.ts +3 -0
  32. package/dist/types/exec/bash-executor.d.ts +12 -0
  33. package/dist/types/gjc-runtime/managed-owner-admission.d.ts +3 -0
  34. package/dist/types/gjc-runtime/session-state-lock.d.ts +2 -0
  35. package/dist/types/gjc-runtime/state-runtime.d.ts +0 -2
  36. package/dist/types/migrate/adapters/codex.d.ts +2 -1
  37. package/dist/types/migrate/adapters/index.d.ts +10 -1
  38. package/dist/types/migrate/executor.d.ts +3 -1
  39. package/dist/types/migrate/types.d.ts +15 -3
  40. package/dist/types/modes/components/settings-selector.d.ts +6 -0
  41. package/dist/types/modes/controllers/selector-controller.d.ts +2 -0
  42. package/dist/types/modes/theme/theme.d.ts +1 -0
  43. package/dist/types/runtime-mcp/content-limits.d.ts +4 -0
  44. package/dist/types/runtime-mcp/manager.d.ts +7 -0
  45. package/dist/types/runtime-mcp/plugin-mcp-name-filter.d.ts +22 -0
  46. package/dist/types/runtime-mcp/tool-bridge.d.ts +4 -0
  47. package/dist/types/sdk/broker/broker-exit.d.ts +2 -2
  48. package/dist/types/sdk/broker/broker.d.ts +7 -1
  49. package/dist/types/sdk/broker/daemon-entry.d.ts +11 -1
  50. package/dist/types/sdk/broker/doctor-restart.d.ts +1 -1
  51. package/dist/types/sdk/broker/ensure.d.ts +136 -7
  52. package/dist/types/sdk/broker/hop.d.ts +36 -0
  53. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +18 -10
  54. package/dist/types/sdk/broker/lock-host-identity.d.ts +5 -0
  55. package/dist/types/sdk/broker/session-index.d.ts +4 -1
  56. package/dist/types/sdk/bus/chat-command-policy.d.ts +2 -1
  57. package/dist/types/sdk/bus/index.d.ts +23 -0
  58. package/dist/types/sdk/bus/reconciliation-store.d.ts +10 -0
  59. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +3 -1
  60. package/dist/types/sdk/cli/session-cli.d.ts +0 -1
  61. package/dist/types/sdk/host/session-runtime.d.ts +32 -0
  62. package/dist/types/sdk/prompt-deadline-flush.d.ts +42 -4
  63. package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -0
  64. package/dist/types/sdk/protocol/adapter-validation.d.ts +10 -4
  65. package/dist/types/sdk/reconciliation-extensions.d.ts +1 -1
  66. package/dist/types/sdk/session-list.d.ts +1 -1
  67. package/dist/types/session/agent-session.d.ts +58 -2
  68. package/dist/types/session/internal/managed-session-scope.d.ts +2 -2
  69. package/dist/types/session/internal/managed-session-storage.d.ts +1 -1
  70. package/dist/types/session/internal/native-publish-outcome.d.ts +1 -1
  71. package/dist/types/session/messages.d.ts +5 -1
  72. package/dist/types/session/session-manager.d.ts +13 -3
  73. package/dist/types/session/startup-auth-config.d.ts +24 -2
  74. package/dist/types/session/streaming-output.d.ts +11 -0
  75. package/dist/types/session/terminal-abort.d.ts +3 -1
  76. package/dist/types/session-import/service.d.ts +1 -1
  77. package/dist/types/skill-state/workflow-mutation-guard.d.ts +7 -3
  78. package/dist/types/slash-commands/helpers/provider-auth-status.d.ts +8 -0
  79. package/dist/types/task/executor.d.ts +19 -2
  80. package/dist/types/tools/ast-edit-write-path.d.ts +10 -0
  81. package/dist/types/tools/atomic-file-write.d.ts +9 -7
  82. package/dist/types/tools/bash-allowed-prefixes.d.ts +11 -0
  83. package/dist/types/tools/descriptors.d.ts +2 -0
  84. package/dist/types/tools/fetch.d.ts +1 -1
  85. package/dist/types/tools/index.d.ts +2 -0
  86. package/dist/types/tools/python.d.ts +1 -1
  87. package/dist/types/tools/report-issue.d.ts +41 -0
  88. package/dist/types/tools/subagent.d.ts +23 -0
  89. package/dist/types/web/scrapers/types.d.ts +8 -1
  90. package/dist/types/web/search/providers/insane.d.ts +7 -1
  91. package/dist/types/web/search/providers/searxng.d.ts +5 -0
  92. package/package.json +8 -8
  93. package/scripts/build-binary.ts +2 -1
  94. package/scripts/capture-subagent-compact-panel-showcase.ts +105 -0
  95. package/scripts/generate-docs-index.ts +8 -1
  96. package/scripts/generate-sdk-operation-inventory.ts +22 -4
  97. package/src/async/job-manager.ts +356 -56
  98. package/src/async/result-formatting.ts +40 -0
  99. package/src/autoresearch/git.ts +9 -2
  100. package/src/autoresearch/prompts/prompt-setup.md +1 -1
  101. package/src/capability/fs.ts +27 -19
  102. package/src/capability/index.ts +2 -2
  103. package/src/cli/accounts-cli.ts +176 -8
  104. package/src/cli/auth-broker-cli.ts +21 -11
  105. package/src/cli/doctor/install-repairs.ts +35 -5
  106. package/src/cli/fast-help.ts +1 -1
  107. package/src/cli/install-activation.ts +47 -4
  108. package/src/cli/migrate-cli.ts +7 -5
  109. package/src/cli/public-command-errors.ts +4 -0
  110. package/src/cli/root-flags.ts +2 -2
  111. package/src/cli/stats-cli.ts +9 -5
  112. package/src/cli/update-cli.ts +224 -7
  113. package/src/cli-ordinary.ts +7 -2
  114. package/src/commands/accounts.ts +10 -5
  115. package/src/commands/migrate.ts +3 -3
  116. package/src/commands/sdk.ts +199 -42
  117. package/src/commands/ultragoal.ts +16 -8
  118. package/src/commit/changelog/index.ts +39 -1
  119. package/src/commit/message.ts +10 -3
  120. package/src/config/autorouting-tier-map.ts +30 -0
  121. package/src/config/config-hot-reload-trust.ts +148 -0
  122. package/src/config/config-hot-reload.ts +734 -0
  123. package/src/config/file-lock.ts +216 -56
  124. package/src/config/model-bindings-applier.ts +85 -47
  125. package/src/config/model-discovery-manager.ts +10 -0
  126. package/src/config/model-profile-activation.ts +716 -113
  127. package/src/config/model-registry.ts +1334 -158
  128. package/src/config/model-resolver.ts +3 -1
  129. package/src/config/models-config-schema.ts +6 -0
  130. package/src/config/settings-schema.ts +60 -3
  131. package/src/config/settings.ts +458 -24
  132. package/src/coordinator-mcp/policy.ts +26 -1
  133. package/src/crash/sanitize.ts +4 -0
  134. package/src/customization/import.ts +3 -1
  135. package/src/dap/config.ts +31 -11
  136. package/src/dap/defaults.json +1 -1
  137. package/src/debug/crash-diagnostics.ts +2 -1
  138. package/src/debug/report-bundle.ts +19 -0
  139. package/src/debug/system-info.ts +8 -3
  140. package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.ts +21 -7
  141. package/src/discovery/agents-md.ts +1 -1
  142. package/src/discovery/helpers.ts +1 -2
  143. package/src/discovery/mcp-compat.ts +120 -17
  144. package/src/edit/apply-patch/index.ts +12 -1
  145. package/src/edit/index.ts +13 -17
  146. package/src/edit/modes/patch.ts +187 -8
  147. package/src/edit/modes/replace.ts +136 -0
  148. package/src/eval/js/context-manager.ts +29 -3
  149. package/src/eval/js/worker-entry.ts +51 -18
  150. package/src/eval/py/executor.ts +14 -4
  151. package/src/eval/py/runner.py +144 -5
  152. package/src/exec/bash-executor.ts +81 -4
  153. package/src/exec/bash-shell-guardian.ts +4 -1
  154. package/src/exec/bash-shell-supervisor.ts +8 -4
  155. package/src/exec/isolated-shell.ts +5 -1
  156. package/src/export/html/index.ts +46 -6
  157. package/src/extensibility/custom-tools/wrapper.ts +3 -1
  158. package/src/extensibility/extensions/loader.ts +8 -1
  159. package/src/extensibility/plugins/marketplace/fetcher.ts +2 -2
  160. package/src/gjc-runtime/gc-runtime.ts +33 -6
  161. package/src/gjc-runtime/launch-tmux.ts +7 -2
  162. package/src/gjc-runtime/launch-worktree.test.ts +148 -0
  163. package/src/gjc-runtime/launch-worktree.ts +101 -14
  164. package/src/gjc-runtime/managed-owner-admission.ts +46 -2
  165. package/src/gjc-runtime/session-resolution.ts +38 -6
  166. package/src/gjc-runtime/session-state-lock.ts +59 -14
  167. package/src/gjc-runtime/state-runtime.ts +23 -19
  168. package/src/hooks/native-skill-hook.ts +14 -11
  169. package/src/internal-urls/docs-index.generated.ts +14 -13
  170. package/src/internal-urls/local-protocol.ts +142 -8
  171. package/src/lsp/index.ts +29 -0
  172. package/src/lsp/workspace-path.ts +23 -0
  173. package/src/migrate/action-planner.ts +57 -0
  174. package/src/migrate/adapters/claude-code.ts +23 -4
  175. package/src/migrate/adapters/codex.ts +22 -4
  176. package/src/migrate/adapters/index.ts +22 -4
  177. package/src/migrate/adapters/opencode.ts +6 -1
  178. package/src/migrate/executor.ts +84 -7
  179. package/src/migrate/report.ts +19 -2
  180. package/src/migrate/types.ts +14 -4
  181. package/src/modes/DESIGN.md +51 -0
  182. package/src/modes/acp/permission-mode.ts +18 -1
  183. package/src/modes/components/assistant-message.ts +55 -1
  184. package/src/modes/components/settings-selector.ts +103 -37
  185. package/src/modes/controllers/command-controller.ts +38 -2
  186. package/src/modes/controllers/plan-mode-controller.ts +17 -7
  187. package/src/modes/controllers/selector-controller.ts +62 -6
  188. package/src/modes/interactive-mode.ts +68 -0
  189. package/src/modes/theme/theme.ts +15 -0
  190. package/src/prompts/tools/debug.md +1 -1
  191. package/src/prompts/tools/read.md +1 -1
  192. package/src/prompts/tools/report-issue.md +13 -0
  193. package/src/prompts/tools/task.md +1 -1
  194. package/src/runtime-credential-selector.ts +2 -2
  195. package/src/runtime-mcp/content-limits.ts +7 -0
  196. package/src/runtime-mcp/manager-pool.test.ts +5 -4
  197. package/src/runtime-mcp/manager.ts +32 -0
  198. package/src/runtime-mcp/oauth-flow.ts +32 -6
  199. package/src/runtime-mcp/plugin-mcp-name-filter.ts +62 -0
  200. package/src/runtime-mcp/render.ts +16 -1
  201. package/src/runtime-mcp/tool-bridge.ts +126 -17
  202. package/src/sdk/broker/broker-exit.ts +8 -5
  203. package/src/sdk/broker/broker.ts +342 -82
  204. package/src/sdk/broker/daemon-entry.ts +338 -116
  205. package/src/sdk/broker/doctor-restart.ts +2 -1
  206. package/src/sdk/broker/ensure.ts +1143 -265
  207. package/src/sdk/broker/hop.ts +274 -0
  208. package/src/sdk/broker/lifecycle-ledger.ts +35 -11
  209. package/src/sdk/broker/lock-host-identity.ts +29 -0
  210. package/src/sdk/broker/session-index.ts +62 -12
  211. package/src/sdk/bus/chat-command-policy.ts +16 -3
  212. package/src/sdk/bus/index.ts +52 -21
  213. package/src/sdk/bus/reconciliation-store.ts +201 -8
  214. package/src/sdk/bus/telegram-daemon-contract.ts +3 -1
  215. package/src/sdk/cli/session-cli.ts +21 -6
  216. package/src/sdk/host/session-runtime.test.ts +118 -23
  217. package/src/sdk/host/session-runtime.ts +352 -47
  218. package/src/sdk/lifecycle/service.ts +1 -0
  219. package/src/sdk/prompt-deadline-flush.ts +78 -5
  220. package/src/sdk/prompt-deadline-manager.ts +68 -14
  221. package/src/sdk/protocol/adapter-validation.ts +53 -11
  222. package/src/sdk/protocol/operation-inventory.generated.json +101 -2
  223. package/src/sdk/reconciliation-extensions.ts +1 -0
  224. package/src/sdk/session-list.ts +1 -0
  225. package/src/sdk/session.ts +114 -61
  226. package/src/session/agent-session.ts +1043 -81
  227. package/src/session/internal/managed-session-scope.ts +126 -39
  228. package/src/session/internal/managed-session-storage.ts +14 -11
  229. package/src/session/internal/native-publish-outcome.ts +5 -1
  230. package/src/session/messages.ts +47 -6
  231. package/src/session/session-manager.ts +236 -14
  232. package/src/session/startup-auth-config.ts +116 -8
  233. package/src/session/streaming-output.ts +262 -4
  234. package/src/session/terminal-abort.ts +14 -2
  235. package/src/session-import/service.ts +1 -0
  236. package/src/skill-state/workflow-mutation-guard.ts +491 -32
  237. package/src/slash-commands/builtin-registry.ts +15 -3
  238. package/src/slash-commands/helpers/credential-switch.ts +10 -5
  239. package/src/slash-commands/helpers/provider-auth-status.ts +456 -0
  240. package/src/task/executor.ts +103 -12
  241. package/src/task/index.ts +389 -175
  242. package/src/tools/ast-edit-write-path.ts +64 -0
  243. package/src/tools/ast-edit.ts +18 -1
  244. package/src/tools/atomic-file-write.ts +46 -15
  245. package/src/tools/auto-generated-guard.ts +3 -0
  246. package/src/tools/bash-allowed-prefixes.ts +153 -5
  247. package/src/tools/bash-interactive.ts +14 -2
  248. package/src/tools/bash.ts +14 -0
  249. package/src/tools/debug.ts +7 -6
  250. package/src/tools/descriptors.test.ts +44 -0
  251. package/src/tools/descriptors.ts +34 -2
  252. package/src/tools/fetch.ts +3 -2
  253. package/src/tools/index.ts +2 -0
  254. package/src/tools/output-meta.ts +23 -2
  255. package/src/tools/python.ts +1 -1
  256. package/src/tools/read.ts +17 -35
  257. package/src/tools/report-issue.ts +184 -0
  258. package/src/tools/subagent-render.ts +114 -12
  259. package/src/tools/subagent.ts +108 -1
  260. package/src/tools/todo-write.ts +41 -3
  261. package/src/tools/tool-catalog.generated.ts +31 -3
  262. package/src/tools/write.ts +35 -3
  263. package/src/utils/commit-message-generator.ts +9 -2
  264. package/src/utils/file-mentions.ts +29 -1
  265. package/src/utils/title-generator.ts +7 -2
  266. package/src/web/scrapers/index.ts +3 -1
  267. package/src/web/scrapers/types.ts +8 -0
  268. package/src/web/scrapers/youtube.ts +3 -2
  269. package/src/web/search/providers/insane.ts +91 -28
  270. package/src/web/search/providers/searxng.ts +62 -10
@@ -66,6 +66,7 @@ import { buildFastStatusReport } from "./helpers/fast-status-report";
66
66
  import { formatDuration } from "./helpers/format";
67
67
  import { commandConsumed, errorMessage, parseSlashCommand, parseSubcommand, usage } from "./helpers/parse";
68
68
  import { renderProgressReportLines, renderProgressReportText } from "./helpers/progress-report";
69
+ import { buildProviderAuthStatusText } from "./helpers/provider-auth-status";
69
70
  import { handleSshAcp } from "./helpers/ssh";
70
71
  import { buildUsageReportText, collectCachedUsageReports } from "./helpers/usage-report";
71
72
  import type {
@@ -292,6 +293,7 @@ function providerSetupUsage(): string {
292
293
  "Aliases include minimax, zai, alibaba, cline, command-code, goat, and ionet.",
293
294
  `API providers: ${MODEL_ONBOARDING_API_PROVIDER_COMMAND} [--force]`,
294
295
  `Available presets:\n${formatProviderPresetList()}`,
296
+ "Credential status: /provider status",
295
297
  "OAuth/subscription providers: /provider login [provider-id] or /login [provider-id]",
296
298
  "Headless OAuth callbacks can be pasted with /login <redirect URL or code>.",
297
299
  ].join("\n");
@@ -1151,6 +1153,7 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
1151
1153
  if (!materializedProfile) {
1152
1154
  for (const [targetId, selector] of assignments) {
1153
1155
  const target = GJC_MODEL_ASSIGNMENT_TARGETS[targetId];
1156
+ runtime.session.markProfileRoleOverrideManual?.(target.settingsPath, targetId);
1154
1157
  if (target.settingsPath === "modelRoles") {
1155
1158
  runtime.settings.setModelRole(targetId, selector);
1156
1159
  } else {
@@ -1653,7 +1656,7 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
1653
1656
  aliases: ["account"],
1654
1657
  description: "Switch this session's active stored account, or list accounts",
1655
1658
  acpDescription: "Switch or list stored credentials for this session",
1656
- inlineHint: "[email:<addr>|id:<n>|account:<id>|project:<id>|provider/<selector>]",
1659
+ inlineHint: "[email:<addr>|id:<n>|account:<id>|project:<id>|org:<id|name>|provider/<selector>]",
1657
1660
  acpInputHint: "[selector]",
1658
1661
  allowArgs: true,
1659
1662
  handle: async (command, runtime) => {
@@ -1899,8 +1902,8 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
1899
1902
 
1900
1903
  {
1901
1904
  name: "provider",
1902
- description: "Set up API-compatible providers or login providers",
1903
- inlineHint: "add|login",
1905
+ description: "Show provider credential status or set up providers",
1906
+ inlineHint: "status|add|login",
1904
1907
  allowArgs: true,
1905
1908
  handle: async (command, runtime) => {
1906
1909
  const args = command.args.trim();
@@ -1908,6 +1911,10 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
1908
1911
  await runtime.output(providerSetupUsage());
1909
1912
  return commandConsumed();
1910
1913
  }
1914
+ if (args === "status") {
1915
+ await runtime.output(await buildProviderAuthStatusText(runtime.session));
1916
+ return commandConsumed();
1917
+ }
1911
1918
  if (args === "login" || args.startsWith("login ")) {
1912
1919
  const providerId = args.slice("login".length).trim();
1913
1920
  const loginCommand = providerId ? `/login ${providerId}` : "/login [provider-id]";
@@ -1976,6 +1983,11 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
1976
1983
  runtime.ctx.editor.setText("");
1977
1984
  return;
1978
1985
  }
1986
+ if (args === "status") {
1987
+ runtime.ctx.showStatus(await buildProviderAuthStatusText(runtime.ctx.session));
1988
+ runtime.ctx.editor.setText("");
1989
+ return;
1990
+ }
1979
1991
  if (args === "help") {
1980
1992
  runtime.ctx.showStatus(providerSetupUsage());
1981
1993
  runtime.ctx.editor.setText("");
@@ -1,13 +1,16 @@
1
- import type { AuthCredentialSelector, AuthCredentialSnapshotEntry } from "@gajae-code/ai/core";
1
+ import {
2
+ type AuthCredentialSelector,
3
+ type AuthCredentialSnapshotEntry,
4
+ formatOAuthIdentityLabel,
5
+ oauthCredentialMatchesOrganization,
6
+ } from "@gajae-code/ai/core";
2
7
  import { parseCliCredentialSelector } from "../../runtime-credential-selector";
3
8
  import type { SlashCommandRuntime } from "../types";
4
9
 
5
10
  /** Human-readable label for one stored OAuth credential row. */
6
11
  function describeCredentialRow(entry: AuthCredentialSnapshotEntry): string {
7
12
  if (entry.credential.type !== "oauth") return `id:${entry.id} (api key)`;
8
- const email = entry.credential.email;
9
- const accountId = entry.credential.accountId;
10
- const label = email ?? accountId ?? `row ${entry.id}`;
13
+ const label = formatOAuthIdentityLabel(entry.credential) ?? `row ${entry.id}`;
11
14
  return `id:${entry.id} — ${label}`;
12
15
  }
13
16
 
@@ -33,6 +36,8 @@ function selectorMatchesRow(selector: AuthCredentialSelector, entry: AuthCredent
33
36
  return entry.credential.accountId === selector.value;
34
37
  case "project":
35
38
  return entry.credential.projectId === selector.value;
39
+ case "org":
40
+ return oauthCredentialMatchesOrganization(entry.credential, selector.value);
36
41
  }
37
42
  }
38
43
 
@@ -116,7 +121,7 @@ export async function switchSessionCredentialCommand(runtime: SlashCommandRuntim
116
121
  try {
117
122
  parsed = parseCliCredentialSelector(trimmed);
118
123
  } catch {
119
- return `Invalid selector "${trimmed}". Use email:name@example.com, id:15, account:<id>, project:<id>, or provider/<selector>.`;
124
+ return `Invalid selector "${trimmed}". Use email:name@example.com, id:15, account:<id>, project:<id>, org:<organization id or name>, or provider/<selector>.`;
120
125
  }
121
126
 
122
127
  const authStorage = runtime.session.modelRegistry.authStorage;
@@ -0,0 +1,456 @@
1
+ import {
2
+ type AuthCredentialSnapshotEntry,
3
+ type EnvApiKeyCredentialSource,
4
+ getEnvApiKeyCredentialSources,
5
+ listProvidersWithEnvKey,
6
+ resolveOAuthStorageProvider,
7
+ } from "@gajae-code/ai/core";
8
+ import { replaceTabs, truncateToWidth, wrapTextWithAnsi } from "@gajae-code/tui";
9
+ import { $credentialEnv, sanitizeDisplayLine } from "@gajae-code/utils";
10
+ import type { ModelRegistry } from "../../config/model-registry";
11
+ import type { AgentSession } from "../../session/agent-session";
12
+
13
+ type ProviderAuthStatusSession = Pick<AgentSession, "credentialSessionId"> & {
14
+ modelRegistry: Pick<
15
+ ModelRegistry,
16
+ | "authStorage"
17
+ | "getAll"
18
+ | "getAvailable"
19
+ | "getConfiguredProviderIds"
20
+ | "getAuthStorageOwner"
21
+ | "getProviderConfigApiKeySources"
22
+ | "getProviderRuntimeApiKeyEnvName"
23
+ | "hasRuntimeProviderApiKey"
24
+ >;
25
+ };
26
+
27
+ interface CredentialSource {
28
+ kind: "api_key" | "oauth";
29
+ layer: "runtime-override" | "runtime-provider" | "config" | "stored" | "environment" | "fallback" | "pending";
30
+ label: string;
31
+ }
32
+
33
+ type ConfigApiKeySource =
34
+ | { kind: "environment"; name: string; provider: string }
35
+ | { kind: "models-yml"; provider: string };
36
+
37
+ const MAX_STATUS_VALUE_WIDTH = 120;
38
+
39
+ function displayStatusValue(value: string): string {
40
+ const safe = replaceTabs(sanitizeDisplayLine(value))
41
+ .replaceAll(/[\u2028\u2029]/gu, " ")
42
+ .trim();
43
+ return truncateToWidth(safe, MAX_STATUS_VALUE_WIDTH).trim() || "unknown";
44
+ }
45
+
46
+ function displayAccountLabel(value: string): string {
47
+ return displayStatusValue(value);
48
+ }
49
+
50
+ function displayEnvironmentName(value: string): string {
51
+ return displayStatusValue(value);
52
+ }
53
+
54
+ function oauthLabel(entry: AuthCredentialSnapshotEntry): string {
55
+ if (entry.credential.type !== "oauth") return `OAuth (stored credential id:${entry.id})`;
56
+ const account = entry.credential.accountId ?? entry.credential.email;
57
+ return account ? `OAuth (account ${displayAccountLabel(account)})` : `OAuth (stored credential id:${entry.id})`;
58
+ }
59
+
60
+ function environmentCredentialLabel(name: string): string {
61
+ const safeName = displayEnvironmentName(name);
62
+ if (name.endsWith("_API_KEY")) return `API key (env ${safeName})`;
63
+ if (name.includes("OAUTH") || name.includes("ACCESS_TOKEN")) return `OAuth token (env ${safeName})`;
64
+ return `environment credential (env ${safeName})`;
65
+ }
66
+
67
+ function environmentSourceLabel(source: EnvApiKeyCredentialSource): string {
68
+ switch (source.kind) {
69
+ case "environment":
70
+ return environmentCredentialLabel(source.name);
71
+ case "google-adc":
72
+ return source.source === "explicit-file"
73
+ ? "Google ADC (env GOOGLE_APPLICATION_CREDENTIALS)"
74
+ : source.source === "default-file"
75
+ ? "Google ADC (default credentials file)"
76
+ : "Google ADC (metadata server)";
77
+ case "aws":
78
+ return source.source === "bearer-token"
79
+ ? "AWS bearer token (env AWS_BEARER_TOKEN_BEDROCK)"
80
+ : source.source === "environment-credentials"
81
+ ? "AWS environment credentials"
82
+ : source.source === "profile"
83
+ ? "AWS profile credentials"
84
+ : "AWS instance profile credentials";
85
+ case "unknown":
86
+ return "environment credentials";
87
+ }
88
+ }
89
+
90
+ function configSourceLabel(source: ConfigApiKeySource, includeProvider = false): string {
91
+ return source.kind === "environment"
92
+ ? `API key (env ${displayEnvironmentName(source.name)})`
93
+ : includeProvider
94
+ ? `API key (models.yml ${displayStatusValue(source.provider)})`
95
+ : "API key (models.yml)";
96
+ }
97
+
98
+ function configSourcesVaryByAlias(sources: ConfigApiKeySource[]): boolean {
99
+ const byProvider = new Map<string, Set<string>>();
100
+ for (const source of sources) {
101
+ const identities = byProvider.get(source.provider) ?? new Set<string>();
102
+ identities.add(source.kind === "environment" ? `environment:${source.name}` : `models-yml:${source.provider}`);
103
+ byProvider.set(source.provider, identities);
104
+ }
105
+ const signatures = new Set([...byProvider.values()].map(identities => [...identities].sort().join("\u0000")));
106
+ return signatures.size > 1;
107
+ }
108
+
109
+ function addSource(sources: CredentialSource[], source: CredentialSource): void {
110
+ if (
111
+ !sources.some(
112
+ existing =>
113
+ existing.kind === source.kind && existing.layer === source.layer && existing.label === source.label,
114
+ )
115
+ ) {
116
+ sources.push(source);
117
+ }
118
+ }
119
+
120
+ function hasRuntimeProviderCredential(registry: ProviderAuthStatusSession["modelRegistry"], provider: string): boolean {
121
+ const envName = registry.getProviderRuntimeApiKeyEnvName(provider);
122
+ return registry.hasRuntimeProviderApiKey(provider) && (envName === undefined || Boolean($credentialEnv(envName)));
123
+ }
124
+
125
+ function storedApiKeySource(
126
+ authStorage: ProviderAuthStatusSession["modelRegistry"]["authStorage"],
127
+ provider: string,
128
+ entries: AuthCredentialSnapshotEntry[],
129
+ rowId: number | undefined,
130
+ ): Omit<CredentialSource, "kind"> | undefined {
131
+ const apiKeys = entries.filter(entry => entry.credential.type === "api_key");
132
+ const selected = rowId === undefined ? undefined : apiKeys.find(entry => entry.id === rowId);
133
+ if (selected?.credential.type === "api_key") {
134
+ const usability = authStorage.getStoredApiKeyUsability(provider, selected.id);
135
+ if (selected.credential.key.startsWith("!") && usability !== true) {
136
+ return {
137
+ layer: "pending",
138
+ label:
139
+ usability === false
140
+ ? "API key (stored credential; retry pending)"
141
+ : "API key (stored credential; usability pending)",
142
+ };
143
+ }
144
+ return { layer: "stored", label: `API key (stored credential id:${selected.id})` };
145
+ }
146
+ const unknownCommandKey = apiKeys.some(
147
+ entry =>
148
+ entry.credential.type === "api_key" &&
149
+ entry.credential.key.startsWith("!") &&
150
+ authStorage.getStoredApiKeyUsability(provider, entry.id) === undefined,
151
+ );
152
+ if (unknownCommandKey) {
153
+ return { layer: "pending", label: "API key (stored credential; usability pending)" };
154
+ }
155
+ const retryableCommandKey = apiKeys.some(
156
+ entry =>
157
+ entry.credential.type === "api_key" &&
158
+ entry.credential.key.startsWith("!") &&
159
+ authStorage.getStoredApiKeyUsability(provider, entry.id) === false,
160
+ );
161
+ if (retryableCommandKey) {
162
+ return { layer: "pending", label: "API key (stored credential; retry pending)" };
163
+ }
164
+ const usableApiKeys = apiKeys.filter(entry => authStorage.getStoredApiKeyUsability(provider, entry.id) !== false);
165
+ if (usableApiKeys.length === 1) {
166
+ return { layer: "stored", label: `API key (stored credential id:${usableApiKeys[0]!.id})` };
167
+ }
168
+ if (usableApiKeys.length > 1) return { layer: "pending", label: "API key (stored credential; selection pending)" };
169
+ return undefined;
170
+ }
171
+
172
+ function collectSources(
173
+ provider: string,
174
+ entries: AuthCredentialSnapshotEntry[],
175
+ registry: ProviderAuthStatusSession["modelRegistry"],
176
+ environmentSources: EnvApiKeyCredentialSource[],
177
+ usesBedrockResolver: boolean,
178
+ ): CredentialSource[] {
179
+ const sources: CredentialSource[] = [];
180
+ if (usesBedrockResolver) {
181
+ for (const source of environmentSources) {
182
+ addSource(sources, { kind: "api_key", layer: "environment", label: environmentSourceLabel(source) });
183
+ }
184
+ return sources;
185
+ }
186
+ const authStorage = registry.authStorage;
187
+ for (const entry of entries) {
188
+ if (entry.credential.type === "oauth") {
189
+ addSource(sources, { kind: "oauth", layer: "stored", label: oauthLabel(entry) });
190
+ } else {
191
+ if (authStorage.getStoredApiKeyUsability(provider, entry.id) === false) continue;
192
+ addSource(sources, {
193
+ kind: "api_key",
194
+ layer: "stored",
195
+ label: `API key (stored credential id:${entry.id})`,
196
+ });
197
+ }
198
+ }
199
+
200
+ if (authStorage.hasRuntimeApiKey(provider)) {
201
+ addSource(sources, {
202
+ kind: "api_key",
203
+ layer: "runtime-override",
204
+ label: "API key (runtime override)",
205
+ });
206
+ }
207
+ const runtimeEnvName = registry.getProviderRuntimeApiKeyEnvName(provider);
208
+ if (
209
+ registry.hasRuntimeProviderApiKey(provider) &&
210
+ (runtimeEnvName === undefined || $credentialEnv(runtimeEnvName) !== undefined)
211
+ ) {
212
+ addSource(sources, {
213
+ kind: "api_key",
214
+ layer: "runtime-provider",
215
+ label: runtimeEnvName
216
+ ? `API key (env ${displayEnvironmentName(runtimeEnvName)})`
217
+ : "API key (runtime provider)",
218
+ });
219
+ }
220
+ for (const configSource of registry.getProviderConfigApiKeySources(provider)) {
221
+ addSource(sources, {
222
+ kind: "api_key",
223
+ layer: "config",
224
+ label: configSourceLabel(configSource),
225
+ });
226
+ }
227
+ for (const source of environmentSources) {
228
+ addSource(sources, { kind: "api_key", layer: "environment", label: environmentSourceLabel(source) });
229
+ }
230
+ return sources;
231
+ }
232
+
233
+ function activeSource(
234
+ provider: string,
235
+ entries: AuthCredentialSnapshotEntry[],
236
+ session: ProviderAuthStatusSession,
237
+ owner: object,
238
+ activeType: "api_key" | "oauth",
239
+ environmentSources: EnvApiKeyCredentialSource[],
240
+ usesBedrockResolver: boolean,
241
+ ): CredentialSource {
242
+ if (usesBedrockResolver) {
243
+ const source = environmentSources[0];
244
+ return source
245
+ ? { kind: "api_key", layer: "environment", label: environmentSourceLabel(source) }
246
+ : { kind: "api_key", layer: "pending", label: "AWS credentials unavailable" };
247
+ }
248
+ const { authStorage } = session.modelRegistry;
249
+ const sessionId = session.credentialSessionId;
250
+ if (activeType === "oauth") {
251
+ const pinnedRowId = authStorage.getSelectedOAuthCredentialRowId(provider, sessionId, { owner });
252
+ const preferredRowId = authStorage.getPreferredOAuthCredentialRowId(provider);
253
+ const sessionRowId = authStorage.getSessionCredentialRowId(provider, sessionId);
254
+ const availableSessionRowId =
255
+ sessionRowId !== undefined && authStorage.isCredentialAvailable(provider, sessionRowId)
256
+ ? sessionRowId
257
+ : undefined;
258
+ const rowId = pinnedRowId ?? availableSessionRowId ?? preferredRowId;
259
+ const selected = rowId === undefined ? undefined : entries.find(entry => entry.id === rowId);
260
+ if (
261
+ selected?.credential.type === "oauth" &&
262
+ (pinnedRowId === rowId || authStorage.isCredentialAvailable(provider, selected.id))
263
+ ) {
264
+ return { kind: "oauth", layer: "stored", label: oauthLabel(selected) };
265
+ }
266
+ const oauthEntries = entries.filter(
267
+ entry => entry.credential.type === "oauth" && authStorage.isCredentialAvailable(provider, entry.id),
268
+ );
269
+ if (oauthEntries.length === 1) return { kind: "oauth", layer: "stored", label: oauthLabel(oauthEntries[0]!) };
270
+ return {
271
+ kind: "oauth",
272
+ layer: "pending",
273
+ label: oauthEntries.length > 1 ? "OAuth (account selection pending)" : "OAuth (account unavailable)",
274
+ };
275
+ }
276
+
277
+ if (authStorage.hasRuntimeApiKey(provider)) {
278
+ return { kind: "api_key", layer: "runtime-override", label: "API key (runtime override)" };
279
+ }
280
+ const runtimeEnvName = session.modelRegistry.getProviderRuntimeApiKeyEnvName(provider);
281
+ if (
282
+ session.modelRegistry.hasRuntimeProviderApiKey(provider) &&
283
+ (runtimeEnvName === undefined || $credentialEnv(runtimeEnvName) !== undefined)
284
+ ) {
285
+ return {
286
+ kind: "api_key",
287
+ layer: "runtime-provider",
288
+ label: runtimeEnvName
289
+ ? `API key (env ${displayEnvironmentName(runtimeEnvName)})`
290
+ : "API key (runtime provider)",
291
+ };
292
+ }
293
+ const configSources = session.modelRegistry.getProviderConfigApiKeySources(provider);
294
+ if (authStorage.hasConfigApiKey(provider, owner) && configSources.length > 0) {
295
+ const configSource = configSources[0]!;
296
+ if (!authStorage.hasLiteralConfigApiKey(provider, owner)) {
297
+ const rowId = authStorage.getProspectiveApiKeyCredentialRowId(provider, sessionId, { owner });
298
+ const storedSource = storedApiKeySource(authStorage, provider, entries, rowId);
299
+ if (storedSource) return { kind: "api_key", ...storedSource };
300
+ }
301
+ return {
302
+ kind: "api_key",
303
+ layer: "config",
304
+ label: configSourcesVaryByAlias(configSources)
305
+ ? "API key (models.yml; source varies by provider alias)"
306
+ : configSourceLabel(configSource),
307
+ };
308
+ }
309
+ const selectedRowId = authStorage.getSelectedApiKeyCredentialRowId(provider, sessionId, { owner });
310
+ if (selectedRowId !== undefined && authStorage.getStoredApiKeyUsability(provider, selectedRowId) === false) {
311
+ return { kind: "api_key", layer: "pending", label: "API key (selected credential unavailable)" };
312
+ }
313
+ const rowId = authStorage.getProspectiveApiKeyCredentialRowId(provider, sessionId, { owner });
314
+ const storedSource = storedApiKeySource(authStorage, provider, entries, rowId);
315
+ if (storedSource) return { kind: "api_key", ...storedSource };
316
+
317
+ const envSource = environmentSources[0];
318
+ if (envSource) return { kind: "api_key", layer: "environment", label: environmentSourceLabel(envSource) };
319
+ return { kind: "api_key", layer: "fallback", label: "API key (configured)" };
320
+ }
321
+
322
+ /** Format credential provenance for `/provider status` without returning credential values. */
323
+ export async function buildProviderAuthStatusText(session: ProviderAuthStatusSession): Promise<string> {
324
+ const { modelRegistry } = session;
325
+ // Status must reflect rotating env-backed keys even before another model selection runs.
326
+ modelRegistry.getAvailable();
327
+ const googleVertexSources = await getEnvApiKeyCredentialSources("google-vertex");
328
+ const bedrockCredentialSources = await getEnvApiKeyCredentialSources("amazon-bedrock");
329
+ const authStorage = modelRegistry.authStorage;
330
+ const owner = modelRegistry.getAuthStorageOwner();
331
+ const snapshot = authStorage.exportSnapshot();
332
+ const storedProviders = new Set(snapshot.credentials.map(entry => resolveOAuthStorageProvider(entry.provider)));
333
+ const models = modelRegistry.getAll();
334
+ const providerApis = new Map<string, Set<string>>();
335
+ for (const model of models) {
336
+ const provider = resolveOAuthStorageProvider(model.provider);
337
+ const apis = providerApis.get(provider) ?? new Set<string>();
338
+ apis.add(model.api);
339
+ providerApis.set(provider, apis);
340
+ }
341
+ const bedrockRouteProviders = new Set<string>();
342
+ const bedrockOnlyProviders = new Set<string>();
343
+ const googleVertexRouteProviders = new Set<string>();
344
+ for (const [provider, apis] of providerApis) {
345
+ if (apis.has("google-vertex")) googleVertexRouteProviders.add(provider);
346
+ if (!apis.has("bedrock-converse-stream")) continue;
347
+ bedrockRouteProviders.add(provider);
348
+ if (apis.size === 1) bedrockOnlyProviders.add(provider);
349
+ }
350
+ if (!providerApis.has("google-vertex")) googleVertexRouteProviders.add("google-vertex");
351
+ if (!providerApis.has("amazon-bedrock")) {
352
+ bedrockRouteProviders.add("amazon-bedrock");
353
+ bedrockOnlyProviders.add("amazon-bedrock");
354
+ }
355
+ const providerIds = new Set([
356
+ ...models.map(model => resolveOAuthStorageProvider(model.provider)),
357
+ ...modelRegistry.getConfiguredProviderIds().map(provider => resolveOAuthStorageProvider(provider)),
358
+ ...snapshot.credentials.map(entry => resolveOAuthStorageProvider(entry.provider)),
359
+ ...listProvidersWithEnvKey().map(provider => resolveOAuthStorageProvider(provider)),
360
+ ]);
361
+ const providers = [...providerIds]
362
+ .filter(
363
+ provider =>
364
+ authStorage.hasSessionCredentialUnavailable(provider, session.credentialSessionId) ||
365
+ storedProviders.has(provider) ||
366
+ (googleVertexRouteProviders.has(provider)
367
+ ? googleVertexSources.length > 0 ||
368
+ modelRegistry.getProviderConfigApiKeySources(provider).length > 0 ||
369
+ hasRuntimeProviderCredential(modelRegistry, provider) ||
370
+ authStorage.hasRuntimeApiKey(provider)
371
+ : bedrockRouteProviders.has(provider)
372
+ ? bedrockCredentialSources.length > 0 ||
373
+ authStorage.hasAuth(provider, session.credentialSessionId, { owner })
374
+ : authStorage.hasAuth(provider, session.credentialSessionId, { owner })),
375
+ )
376
+ .sort((left, right) => left.localeCompare(right));
377
+ if (providers.length === 0) return "No configured provider credentials.";
378
+
379
+ const lines = ["Provider credentials (active source per provider):"];
380
+ for (const provider of providers) {
381
+ const entries = snapshot.credentials.filter(entry => resolveOAuthStorageProvider(entry.provider) === provider);
382
+ const configSources = modelRegistry.getProviderConfigApiKeySources(provider);
383
+ const usesBedrockResolver = bedrockOnlyProviders.has(provider);
384
+ const hasBedrockRoute = bedrockRouteProviders.has(provider);
385
+ const environmentSources = googleVertexRouteProviders.has(provider)
386
+ ? googleVertexSources
387
+ : usesBedrockResolver
388
+ ? bedrockCredentialSources
389
+ : await getEnvApiKeyCredentialSources(provider);
390
+ const activeType = authStorage.getEffectiveCredentialType(provider, session.credentialSessionId, { owner });
391
+ const sources = collectSources(provider, entries, modelRegistry, environmentSources, usesBedrockResolver);
392
+ const sessionCredentialUnavailable = authStorage.hasSessionCredentialUnavailable(
393
+ provider,
394
+ session.credentialSessionId,
395
+ );
396
+ let active: { kind: "api_key" | "oauth"; layer: string; label: string } | undefined;
397
+ if (usesBedrockResolver) {
398
+ active = activeSource(provider, entries, session, owner, "api_key", environmentSources, usesBedrockResolver);
399
+ } else if (activeType === "oauth" || activeType === "api_key") {
400
+ active = activeSource(provider, entries, session, owner, activeType, environmentSources, usesBedrockResolver);
401
+ } else if (environmentSources[0]) {
402
+ active = {
403
+ kind: "api_key" as const,
404
+ layer: "environment",
405
+ label: environmentSourceLabel(environmentSources[0]),
406
+ };
407
+ } else {
408
+ active = undefined;
409
+ }
410
+ const configOverridePrecedesUnavailablePin =
411
+ authStorage.hasConfigApiKey(provider, owner) &&
412
+ modelRegistry.getProviderConfigApiKeySources(provider).length > 0;
413
+ const overridePrecedesUnavailablePin =
414
+ active?.layer === "runtime-override" ||
415
+ active?.layer === "runtime-provider" ||
416
+ configOverridePrecedesUnavailablePin;
417
+ if (!usesBedrockResolver && sessionCredentialUnavailable && !overridePrecedesUnavailablePin) {
418
+ active = { kind: "api_key", layer: "pending", label: "selected credential unavailable" };
419
+ }
420
+ lines.push(
421
+ ...wrapTextWithAnsi(
422
+ `${displayStatusValue(provider)}: ${active?.label ?? "credential source unavailable"}`,
423
+ MAX_STATUS_VALUE_WIDTH,
424
+ ),
425
+ );
426
+ const configSourceVariesByAlias = active?.layer === "config" && configSourcesVaryByAlias(configSources);
427
+ if (configSourceVariesByAlias) {
428
+ const configuredSources = [...new Set(configSources.map(source => configSourceLabel(source, true)))];
429
+ lines.push(
430
+ ...wrapTextWithAnsi(` configured sources: ${configuredSources.join(", ")}`, MAX_STATUS_VALUE_WIDTH),
431
+ );
432
+ }
433
+ if (hasBedrockRoute && !usesBedrockResolver) {
434
+ const bedrockRouteSource = bedrockCredentialSources[0];
435
+ const bedrockRouteLabel = bedrockRouteSource
436
+ ? environmentSourceLabel(bedrockRouteSource)
437
+ : "AWS credentials unavailable";
438
+ lines.push(...wrapTextWithAnsi(` Bedrock route: ${bedrockRouteLabel}`, MAX_STATUS_VALUE_WIDTH));
439
+ }
440
+ const shadowed =
441
+ active && active.layer !== "pending"
442
+ ? sources
443
+ .filter(
444
+ source =>
445
+ !(configSourceVariesByAlias && source.layer === "config") &&
446
+ source.label !== active.label &&
447
+ !(active.layer === "stored" && source.layer === "stored" && source.kind === active.kind),
448
+ )
449
+ .map(source => source.label)
450
+ : [];
451
+ if (shadowed.length > 0) {
452
+ lines.push(...wrapTextWithAnsi(` shadowed: ${[...new Set(shadowed)].join(", ")}`, MAX_STATUS_VALUE_WIDTH));
453
+ }
454
+ }
455
+ return lines.join("\n");
456
+ }