@gajae-code/coding-agent 0.12.21 → 0.13.0

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 (222) hide show
  1. package/CHANGELOG.md +67 -2
  2. package/dist/types/cli/args.d.ts +5 -2
  3. package/dist/types/cli/root-flags.d.ts +246 -0
  4. package/dist/types/cli.d.ts +11 -19
  5. package/dist/types/commands/daemon.d.ts +0 -30
  6. package/dist/types/commands/launch.d.ts +11 -19
  7. package/dist/types/commands/quick-lane.d.ts +32 -0
  8. package/dist/types/commands/sdk.d.ts +21 -15
  9. package/dist/types/config/file-lock-gc.d.ts +2 -0
  10. package/dist/types/config/model-equivalence.d.ts +20 -0
  11. package/dist/types/config/model-profile-activation.d.ts +62 -17
  12. package/dist/types/config/model-profiles.d.ts +10 -0
  13. package/dist/types/config/model-registry.d.ts +68 -1
  14. package/dist/types/config/model-resolver.d.ts +15 -4
  15. package/dist/types/config/models-config-schema.d.ts +7 -2
  16. package/dist/types/config/provider-selection-policy.d.ts +62 -0
  17. package/dist/types/config/settings-schema.d.ts +36 -78
  18. package/dist/types/config/settings.d.ts +2 -0
  19. package/dist/types/edit/renderer.d.ts +5 -2
  20. package/dist/types/edit/streaming.d.ts +13 -0
  21. package/dist/types/extensibility/custom-tools/types.d.ts +2 -0
  22. package/dist/types/extensibility/extensions/compact-handler.d.ts +2 -1
  23. package/dist/types/extensibility/extensions/types.d.ts +3 -0
  24. package/dist/types/gjc-runtime/gc-runtime.d.ts +19 -5
  25. package/dist/types/internal-urls/local-protocol.d.ts +1 -0
  26. package/dist/types/modes/components/model-selector.d.ts +0 -2
  27. package/dist/types/modes/components/provider-order-context.d.ts +45 -0
  28. package/dist/types/modes/components/provider-order-editor.d.ts +24 -0
  29. package/dist/types/modes/components/settings-defs.d.ts +5 -1
  30. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  31. package/dist/types/quick-lane/classify.d.ts +40 -0
  32. package/dist/types/runtime/stt-settings-setup.d.ts +18 -0
  33. package/dist/types/sdk/broker/broker.d.ts +56 -2
  34. package/dist/types/sdk/broker/operation-receipt-ledger.d.ts +25 -0
  35. package/dist/types/sdk/broker/session-index.d.ts +83 -2
  36. package/dist/types/sdk/broker/state-version.d.ts +1 -1
  37. package/dist/types/sdk/bus/notification-service.d.ts +6 -0
  38. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +8 -1
  39. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +4 -1
  40. package/dist/types/sdk/bus/telegram-daemon.d.ts +26 -0
  41. package/dist/types/sdk/cli/rows.d.ts +98 -0
  42. package/dist/types/sdk/cli/session-cli.d.ts +35 -3
  43. package/dist/types/sdk/elevation/allowlist.d.ts +20 -0
  44. package/dist/types/sdk/elevation/audit.d.ts +93 -0
  45. package/dist/types/sdk/elevation/capability.d.ts +3 -0
  46. package/dist/types/sdk/elevation/digest.d.ts +72 -0
  47. package/dist/types/sdk/elevation/dispatch-receipt.d.ts +85 -0
  48. package/dist/types/sdk/elevation/grant-ledger.d.ts +163 -0
  49. package/dist/types/sdk/elevation/owner.d.ts +48 -0
  50. package/dist/types/sdk/guides/cache.d.ts +98 -0
  51. package/dist/types/sdk/guides/catalog.d.ts +156 -0
  52. package/dist/types/sdk/guides/cli.d.ts +39 -0
  53. package/dist/types/sdk/guides/manifest.d.ts +66 -0
  54. package/dist/types/sdk/guides/verify.d.ts +66 -0
  55. package/dist/types/sdk/host/control/dispatch.d.ts +8 -0
  56. package/dist/types/sdk/host/control/index.d.ts +1 -1
  57. package/dist/types/sdk/host/control/operations.d.ts +3 -2
  58. package/dist/types/sdk/host/query/cursor.d.ts +10 -0
  59. package/dist/types/sdk/host/query/handlers.d.ts +21 -0
  60. package/dist/types/sdk/host/session-runtime.d.ts +9 -0
  61. package/dist/types/sdk/mcp/server.d.ts +2 -2
  62. package/dist/types/sdk/prompt-status.d.ts +11 -0
  63. package/dist/types/sdk/session.d.ts +4 -0
  64. package/dist/types/sdk/transport/index.d.ts +1 -0
  65. package/dist/types/sdk/transport/relay.d.ts +1 -0
  66. package/dist/types/sdk/transport/serve-cli.d.ts +20 -1
  67. package/dist/types/session/agent-session.d.ts +32 -18
  68. package/dist/types/session/blob-store.d.ts +13 -7
  69. package/dist/types/session/fallback-chain-controller.d.ts +15 -0
  70. package/dist/types/session/internal/managed-session-storage.d.ts +25 -1
  71. package/dist/types/session/internal/session-memory-sidecar.d.ts +5 -5
  72. package/dist/types/session/session-manager.d.ts +139 -10
  73. package/dist/types/session/session-storage.d.ts +48 -0
  74. package/dist/types/setup/provider-onboarding.d.ts +7 -1
  75. package/dist/types/slash-commands/builtin-registry.d.ts +14 -0
  76. package/dist/types/stt/setup.d.ts +2 -0
  77. package/dist/types/task/executor.d.ts +10 -2
  78. package/dist/types/tools/browser/runtime-diagnostics.d.ts +65 -0
  79. package/dist/types/tools/browser/tab-protocol.d.ts +4 -0
  80. package/dist/types/tools/browser/tab-supervisor.d.ts +2 -0
  81. package/dist/types/tools/browser/tab-worker.d.ts +2 -0
  82. package/dist/types/tools/browser.d.ts +2 -0
  83. package/dist/types/tools/image-gen.d.ts +14 -18
  84. package/dist/types/tools/index.d.ts +2 -0
  85. package/dist/types/tools/resource-gc.d.ts +2 -0
  86. package/dist/types/utils/pasted-image-path.d.ts +6 -0
  87. package/package.json +9 -9
  88. package/scripts/generate-sdk-operation-inventory.ts +8 -0
  89. package/scripts/run-test-manifest.ts +61 -21
  90. package/scripts/verify-gjc-sdk-canonicalization.ts +9 -4
  91. package/src/cli/args.ts +62 -9
  92. package/src/cli/completion-cli.ts +1 -0
  93. package/src/cli/fast-help.ts +1 -0
  94. package/src/cli/root-flags.ts +100 -0
  95. package/src/cli/setup-cli.ts +6 -2
  96. package/src/cli.ts +42 -65
  97. package/src/commands/acp.ts +1 -1
  98. package/src/commands/daemon.ts +2 -31
  99. package/src/commands/gc.ts +1 -1
  100. package/src/commands/launch.ts +8 -129
  101. package/src/commands/quick-lane.ts +51 -0
  102. package/src/commands/sdk.ts +200 -26
  103. package/src/commands/setup.ts +4 -2
  104. package/src/config/file-lock-gc.ts +4 -1
  105. package/src/config/model-equivalence.ts +76 -7
  106. package/src/config/model-profile-activation.ts +1115 -265
  107. package/src/config/model-profiles.ts +134 -2
  108. package/src/config/model-registry.ts +352 -50
  109. package/src/config/model-resolver.ts +289 -38
  110. package/src/config/models-config-schema.ts +11 -4
  111. package/src/config/provider-selection-policy.ts +146 -0
  112. package/src/config/settings-schema.ts +50 -77
  113. package/src/config/settings.ts +40 -0
  114. package/src/coordinator-mcp/server.ts +32 -2
  115. package/src/edit/renderer.ts +175 -223
  116. package/src/edit/streaming.ts +108 -1
  117. package/src/export/html/index.ts +57 -7
  118. package/src/extensibility/custom-tools/types.ts +2 -0
  119. package/src/extensibility/extensions/compact-handler.ts +3 -2
  120. package/src/extensibility/extensions/runner.ts +6 -0
  121. package/src/extensibility/extensions/types.ts +3 -0
  122. package/src/gjc-runtime/gc-runtime.ts +379 -20
  123. package/src/gjc-runtime/launch-worktree.ts +5 -1
  124. package/src/gjc-runtime/team-runtime.ts +59 -2
  125. package/src/gjc-runtime/tmux-owner-isolation.ts +19 -0
  126. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +16 -1
  127. package/src/internal-urls/docs-index.generated.ts +17 -13
  128. package/src/internal-urls/local-protocol.ts +1 -0
  129. package/src/main.ts +40 -3
  130. package/src/memories/index.ts +4 -2
  131. package/src/modes/acp/acp-agent.ts +235 -91
  132. package/src/modes/components/model-selector.ts +206 -42
  133. package/src/modes/components/provider-order-context.ts +197 -0
  134. package/src/modes/components/provider-order-editor.ts +441 -0
  135. package/src/modes/components/settings-defs.ts +21 -1
  136. package/src/modes/components/settings-selector.ts +45 -2
  137. package/src/modes/components/tool-execution.ts +47 -15
  138. package/src/modes/controllers/command-controller.ts +6 -3
  139. package/src/modes/controllers/extension-ui-controller.ts +2 -0
  140. package/src/modes/controllers/input-controller.ts +73 -2
  141. package/src/modes/controllers/selector-controller.ts +260 -153
  142. package/src/modes/prompt-suggestion-controller.ts +1 -1
  143. package/src/modes/runtime-init.ts +1 -0
  144. package/src/modes/utils/ui-helpers.ts +18 -0
  145. package/src/prompts/tools/browser.md +1 -0
  146. package/src/quick-lane/classify.ts +257 -0
  147. package/src/rlm/index.ts +1 -1
  148. package/src/runtime/stt-settings-setup.ts +57 -0
  149. package/src/sdk/acp/adapter.ts +2 -1
  150. package/src/sdk/broker/broker.ts +780 -12
  151. package/src/sdk/broker/lifecycle.ts +70 -29
  152. package/src/sdk/broker/operation-receipt-ledger.ts +144 -0
  153. package/src/sdk/broker/session-index.ts +463 -48
  154. package/src/sdk/broker/state-version.ts +13 -9
  155. package/src/sdk/broker/transport.ts +14 -1
  156. package/src/sdk/bus/index.ts +34 -13
  157. package/src/sdk/bus/kind-aware-reconciliation.ts +21 -2
  158. package/src/sdk/bus/notification-service.ts +47 -1
  159. package/src/sdk/bus/prompt-reconciliation.ts +22 -3
  160. package/src/sdk/bus/telegram-daemon-contract.ts +4 -1
  161. package/src/sdk/bus/telegram-daemon.ts +54 -3
  162. package/src/sdk/cli/rows.ts +247 -0
  163. package/src/sdk/cli/session-cli.ts +930 -79
  164. package/src/sdk/elevation/allowlist.ts +54 -0
  165. package/src/sdk/elevation/audit.ts +208 -0
  166. package/src/sdk/elevation/capability.ts +41 -0
  167. package/src/sdk/elevation/digest.ts +129 -0
  168. package/src/sdk/elevation/dispatch-receipt.ts +184 -0
  169. package/src/sdk/elevation/grant-ledger.ts +1142 -0
  170. package/src/sdk/elevation/owner.ts +109 -0
  171. package/src/sdk/guides/cache.ts +363 -0
  172. package/src/sdk/guides/catalog.ts +554 -0
  173. package/src/sdk/guides/cli.ts +235 -0
  174. package/src/sdk/guides/manifest.ts +152 -0
  175. package/src/sdk/guides/verify.ts +178 -0
  176. package/src/sdk/host/control/dispatch.ts +49 -1
  177. package/src/sdk/host/control/index.ts +1 -0
  178. package/src/sdk/host/control/operations.ts +8 -1
  179. package/src/sdk/host/query/cursor.ts +47 -3
  180. package/src/sdk/host/query/handlers.ts +155 -2
  181. package/src/sdk/host/sdk-surface-parity.test.ts +1 -0
  182. package/src/sdk/host/session-runtime.test.ts +391 -1
  183. package/src/sdk/host/session-runtime.ts +308 -73
  184. package/src/sdk/host/surface-policy.ts +3 -0
  185. package/src/sdk/host/websocket-transport.ts +7 -1
  186. package/src/sdk/mcp/server.ts +88 -15
  187. package/src/sdk/prompt-status.ts +8 -0
  188. package/src/sdk/protocol/operation-inventory.generated.json +62 -0
  189. package/src/sdk/protocol/operation-registry.ts +3 -2
  190. package/src/sdk/session.ts +87 -39
  191. package/src/sdk/transport/index.ts +1 -0
  192. package/src/sdk/transport/relay.ts +3 -0
  193. package/src/sdk/transport/serve-cli.ts +111 -24
  194. package/src/secrets/obfuscator.ts +92 -12
  195. package/src/session/agent-session.ts +322 -93
  196. package/src/session/blob-store.ts +60 -8
  197. package/src/session/fallback-chain-controller.ts +35 -0
  198. package/src/session/internal/managed-session-scope.ts +25 -3
  199. package/src/session/internal/managed-session-storage.ts +574 -73
  200. package/src/session/internal/session-memory-sidecar.ts +19 -29
  201. package/src/session/session-manager.ts +2166 -743
  202. package/src/session/session-storage.ts +715 -48
  203. package/src/setup/provider-onboarding.ts +19 -3
  204. package/src/setup/provider-presets.json +3 -1
  205. package/src/slash-commands/builtin-registry.ts +85 -3
  206. package/src/stt/downloader.ts +2 -3
  207. package/src/stt/setup.ts +29 -4
  208. package/src/task/executor.ts +22 -18
  209. package/src/task/index.ts +24 -1
  210. package/src/tools/archive-reader.ts +11 -2
  211. package/src/tools/browser/runtime-diagnostics.ts +249 -0
  212. package/src/tools/browser/tab-protocol.ts +4 -0
  213. package/src/tools/browser/tab-supervisor.ts +5 -0
  214. package/src/tools/browser/tab-worker.ts +36 -1
  215. package/src/tools/browser.ts +7 -0
  216. package/src/tools/image-gen.ts +110 -166
  217. package/src/tools/index.ts +2 -0
  218. package/src/tools/read.ts +1 -1
  219. package/src/tools/resource-gc.ts +6 -1
  220. package/src/tools/tool-catalog.generated.ts +5 -1
  221. package/src/utils/pasted-image-path.ts +93 -0
  222. package/src/web/search/index.ts +3 -3
@@ -1,5 +1,6 @@
1
1
  import * as path from "node:path";
2
2
  import { ThinkingLevel } from "@gajae-code/agent-core";
3
+ import type { Api, Model } from "@gajae-code/ai/core";
3
4
  import { getOAuthProviders } from "@gajae-code/ai/utils/oauth";
4
5
  import type { OAuthProvider } from "@gajae-code/ai/utils/oauth/types";
5
6
  import type { Component, OverlayHandle, SlashCommand } from "@gajae-code/tui";
@@ -20,6 +21,7 @@ import { formatModelSelectorValue } from "../../config/model-resolver";
20
21
  import { selectorHead } from "../../config/model-selector-value";
21
22
  import type { ModelProfileConfig } from "../../config/models-config-schema";
22
23
  import { type Settings, type SettingsAtomicReceipt, settings } from "../../config/settings";
24
+ import type { SettingValue } from "../../config/settings-schema";
23
25
  import { DebugSelectorComponent } from "../../debug";
24
26
  import { disableProvider, enableProvider } from "../../discovery";
25
27
  import { clearPluginRootsAndCaches, resolveActiveProjectRegistryPath } from "../../discovery/helpers";
@@ -50,6 +52,7 @@ import {
50
52
  stopInteractiveActivityIndicator,
51
53
  suspendInteractiveActivityIndicator,
52
54
  } from "../../modes/types";
55
+ import { configureSttFromSettings } from "../../runtime/stt-settings-setup";
53
56
  import { ChatDaemonController } from "../../sdk/bus/chat-daemon-control";
54
57
  import {
55
58
  getCurrentTelegramActivationMarker,
@@ -91,6 +94,7 @@ import {
91
94
  } from "../../sdk/bus/telegram-daemon";
92
95
  import { TelegramDaemonController } from "../../sdk/bus/telegram-daemon-control";
93
96
  import { runTelegramSetup, type TelegramSetupPreflight } from "../../sdk/bus/telegram-setup";
97
+ import type { DefaultFallbackRuntimeState } from "../../session/agent-session";
94
98
  import { type SessionInfo, SessionManager } from "../../session/session-manager";
95
99
  import { getTreeForInternalRead } from "../../session/session-manager-internal";
96
100
 
@@ -123,11 +127,8 @@ import {
123
127
  } from "../../setup/model-onboarding-guidance";
124
128
  import { addApiCompatibleProvider, formatProviderSetupResult } from "../../setup/provider-onboarding";
125
129
  import {
126
- IMAGE_PROVIDER_DEFAULTS,
127
130
  isConfigurableSearchProviderId,
128
131
  isSearchProviderPreference,
129
- setConfiguredImageModel,
130
- setPreferredImageProvider,
131
132
  setPreferredSearchProvider,
132
133
  setSearchFallbackProviders,
133
134
  setSearchHardTimeoutMs,
@@ -171,6 +172,8 @@ import {
171
172
  type ProviderOnboardingAction,
172
173
  ProviderOnboardingSelectorComponent,
173
174
  } from "../components/provider-onboarding-selector";
175
+ import { ProviderOrderContext } from "../components/provider-order-context";
176
+ import { ProviderOrderEditorComponent } from "../components/provider-order-editor";
174
177
  import { SessionObserverOverlayComponent } from "../components/session-observer-overlay";
175
178
  import { SessionSelectorComponent } from "../components/session-selector";
176
179
  import { dashboardSessions, SessionsDashboardComponent } from "../components/sessions-dashboard";
@@ -1195,6 +1198,21 @@ export function createNotificationsEditorOperations(
1195
1198
  };
1196
1199
  }
1197
1200
 
1201
+ interface DefaultAssignmentRollbackSnapshot {
1202
+ model: Model<Api> | undefined;
1203
+ thinkingLevel: ThinkingLevel | undefined;
1204
+ persistedModelRoles: SettingValue<"modelRoles"> | undefined;
1205
+ persistedAgentOverrides: SettingValue<"task.agentModelOverrides"> | undefined;
1206
+ persistedProfile: SettingValue<"modelProfile.default"> | undefined;
1207
+ modelRolesOverride: SettingValue<"modelRoles"> | undefined;
1208
+ agentOverridesOverride: SettingValue<"task.agentModelOverrides"> | undefined;
1209
+ profileOverride: SettingValue<"modelProfile.default"> | undefined;
1210
+ chain: { entries: readonly string[]; origin: string; identity?: string; explicitHead?: boolean } | undefined;
1211
+ activeProfile: string | undefined;
1212
+ canonicalVariant: string | undefined;
1213
+ resumeDefaultSelector: string | undefined;
1214
+ fallbackRuntimeState: DefaultFallbackRuntimeState;
1215
+ }
1198
1216
  export class SelectorController {
1199
1217
  #transcriptViewerOpen = false;
1200
1218
  #transcriptViewer?: TranscriptViewerOverlay;
@@ -1213,6 +1231,113 @@ export class SelectorController {
1213
1231
  this.#credentialAutoImportStateStore = credentialAutoImportStateStore;
1214
1232
  }
1215
1233
 
1234
+ #captureDefaultAssignmentRollback(): DefaultAssignmentRollbackSnapshot {
1235
+ return {
1236
+ model: this.ctx.session.model,
1237
+ thinkingLevel: this.ctx.session.thinkingLevel,
1238
+ persistedModelRoles: this.ctx.settings.getGlobal("modelRoles"),
1239
+ persistedAgentOverrides: this.ctx.settings.getGlobal("task.agentModelOverrides"),
1240
+ persistedProfile: this.ctx.settings.getGlobal("modelProfile.default"),
1241
+ modelRolesOverride: this.ctx.settings.getOverride("modelRoles"),
1242
+ agentOverridesOverride: this.ctx.settings.getOverride("task.agentModelOverrides"),
1243
+ profileOverride: this.ctx.settings.getOverride("modelProfile.default"),
1244
+ chain: this.ctx.session.getConfiguredModelChainState("default"),
1245
+ activeProfile: this.ctx.session.getActiveModelProfile?.(),
1246
+ canonicalVariant: this.ctx.session.modelRegistry.getSessionCanonicalVariant?.(this.ctx.session.sessionId),
1247
+ resumeDefaultSelector: this.ctx.session.sessionManager.buildSessionContext().models.default,
1248
+ fallbackRuntimeState: this.ctx.session.getDefaultFallbackRuntimeState(),
1249
+ };
1250
+ }
1251
+
1252
+ async #restoreDefaultAssignmentRollback(
1253
+ snapshot: DefaultAssignmentRollbackSnapshot,
1254
+ error: unknown,
1255
+ restoreLiveModel: boolean = true,
1256
+ restoreProfileState: boolean = true,
1257
+ ): Promise<never> {
1258
+ if (!restoreLiveModel && !restoreProfileState) throw error;
1259
+ const rollbackErrors: unknown[] = [];
1260
+ const restore = (action: () => void): void => {
1261
+ try {
1262
+ action();
1263
+ } catch (rollbackError) {
1264
+ rollbackErrors.push(rollbackError);
1265
+ }
1266
+ };
1267
+ restore(() =>
1268
+ snapshot.persistedModelRoles === undefined
1269
+ ? this.ctx.settings.unset("modelRoles")
1270
+ : this.ctx.settings.set("modelRoles", snapshot.persistedModelRoles),
1271
+ );
1272
+ restore(() =>
1273
+ snapshot.persistedAgentOverrides === undefined
1274
+ ? this.ctx.settings.unset("task.agentModelOverrides")
1275
+ : this.ctx.settings.set("task.agentModelOverrides", snapshot.persistedAgentOverrides),
1276
+ );
1277
+ restore(() =>
1278
+ snapshot.persistedProfile === undefined
1279
+ ? this.ctx.settings.unset("modelProfile.default")
1280
+ : this.ctx.settings.set("modelProfile.default", snapshot.persistedProfile),
1281
+ );
1282
+ restore(() =>
1283
+ snapshot.modelRolesOverride === undefined
1284
+ ? this.ctx.settings.clearOverride("modelRoles")
1285
+ : this.ctx.settings.override("modelRoles", snapshot.modelRolesOverride),
1286
+ );
1287
+ restore(() =>
1288
+ snapshot.agentOverridesOverride === undefined
1289
+ ? this.ctx.settings.clearOverride("task.agentModelOverrides")
1290
+ : this.ctx.settings.override("task.agentModelOverrides", snapshot.agentOverridesOverride),
1291
+ );
1292
+ restore(() =>
1293
+ snapshot.profileOverride === undefined
1294
+ ? this.ctx.settings.clearOverride("modelProfile.default")
1295
+ : this.ctx.settings.override("modelProfile.default", snapshot.profileOverride),
1296
+ );
1297
+ if (restoreProfileState) {
1298
+ restore(() =>
1299
+ this.ctx.session.setConfiguredModelChain(
1300
+ "default",
1301
+ snapshot.chain?.entries ?? [],
1302
+ snapshot.chain?.origin ?? "rollback",
1303
+ snapshot.chain?.identity,
1304
+ snapshot.chain?.explicitHead ?? true,
1305
+ ),
1306
+ );
1307
+ restore(() => this.ctx.session.restoreDefaultFallbackRuntimeState(snapshot.fallbackRuntimeState));
1308
+ restore(() => {
1309
+ if (snapshot.canonicalVariant) {
1310
+ this.ctx.session.modelRegistry.restoreSessionCanonicalVariant?.(
1311
+ this.ctx.session.sessionId,
1312
+ snapshot.canonicalVariant,
1313
+ );
1314
+ } else {
1315
+ this.ctx.session.modelRegistry.clearCanonicalVariant?.(this.ctx.session.sessionId);
1316
+ }
1317
+ });
1318
+ }
1319
+ if (restoreLiveModel) {
1320
+ restore(() => this.ctx.session.recordResumeDefaultModel(snapshot.resumeDefaultSelector));
1321
+ }
1322
+ restore(() => this.ctx.session.setActiveModelProfile?.(snapshot.activeProfile));
1323
+ try {
1324
+ await this.ctx.settings.flushOrThrow();
1325
+ } catch (rollbackError) {
1326
+ rollbackErrors.push(rollbackError);
1327
+ }
1328
+ if (restoreLiveModel) {
1329
+ try {
1330
+ await this.ctx.session.restoreModelSelectionForRollback(snapshot.model, snapshot.thinkingLevel);
1331
+ } catch (rollbackError) {
1332
+ rollbackErrors.push(rollbackError);
1333
+ }
1334
+ }
1335
+ if (rollbackErrors.length > 0) {
1336
+ throw new AggregateError([error, ...rollbackErrors], "Model assignment failed and rollback was incomplete");
1337
+ }
1338
+ throw error;
1339
+ }
1340
+
1216
1341
  isTranscriptViewerOpen(): boolean {
1217
1342
  return this.#transcriptViewerOpen;
1218
1343
  }
@@ -1226,7 +1351,7 @@ export class SelectorController {
1226
1351
  await Promise.all(
1227
1352
  oauthProviders.map(provider =>
1228
1353
  this.ctx.session.modelRegistry
1229
- .getApiKeyForProvider(provider.id, this.ctx.session.sessionId)
1354
+ .getApiKeyForProvider(provider.id, this.ctx.session.credentialSessionId)
1230
1355
  .catch(() => undefined),
1231
1356
  ),
1232
1357
  );
@@ -1535,81 +1660,6 @@ export class SelectorController {
1535
1660
  }
1536
1661
  }
1537
1662
 
1538
- async #handleImageGenerationConfig(): Promise<void> {
1539
- const provider = await this.ctx.showHookInput(
1540
- "Image Generation provider (auto, openai, gemini, openrouter, antigravity, alibaba, custom)",
1541
- "auto",
1542
- );
1543
- if (provider === undefined) return;
1544
- const normalized = provider.trim().toLowerCase();
1545
- const validProviders = ["auto", "openai", "gemini", "openrouter", "antigravity", "alibaba", "custom"];
1546
- if (!validProviders.includes(normalized)) {
1547
- this.ctx.showStatus(`Invalid image provider: ${normalized}. Valid: ${validProviders.join(", ")}`);
1548
- return;
1549
- }
1550
- let model: string | undefined;
1551
- if (normalized !== "auto" && normalized !== "custom") {
1552
- const defaultModel = IMAGE_PROVIDER_DEFAULTS[normalized];
1553
- model = await this.ctx.showHookInput(`Image model for ${normalized} (default: ${defaultModel})`, defaultModel);
1554
- if (model === undefined) return;
1555
- model = model.trim() || defaultModel;
1556
- }
1557
- let customUrl: string | undefined;
1558
- let customKey: string | undefined;
1559
- if (normalized === "custom") {
1560
- customUrl = await this.ctx.showHookInput("Custom image endpoint base URL");
1561
- if (!customUrl?.trim()) {
1562
- this.ctx.showStatus("Custom image endpoint requires a base URL");
1563
- return;
1564
- }
1565
- model = await this.ctx.showHookInput("Custom image model", IMAGE_PROVIDER_DEFAULTS.openai);
1566
- if (model === undefined) return;
1567
- model = model.trim() || IMAGE_PROVIDER_DEFAULTS.openai;
1568
- customKey = await this.ctx.showHookInput("Custom image endpoint API key");
1569
- }
1570
- const scope = await this.ctx.showHookInput(
1571
- "Scope: 'session' (this session only) or 'default' (persist)",
1572
- "session",
1573
- );
1574
- if (scope === undefined) return;
1575
- const persistDefault = scope.trim().toLowerCase() === "default";
1576
- if (persistDefault && !this.ctx.settings.canWriteDurableConfig()) {
1577
- this.ctx.showError(
1578
- "Cannot change settings while config.yml has invalid YAML syntax. Repair config.yml and reload settings.",
1579
- );
1580
- return;
1581
- }
1582
-
1583
- const imageProvider = normalized as
1584
- | "auto"
1585
- | "openai"
1586
- | "gemini"
1587
- | "openrouter"
1588
- | "antigravity"
1589
- | "alibaba"
1590
- | "custom";
1591
- setPreferredImageProvider(imageProvider === "custom" ? "auto" : imageProvider);
1592
- setConfiguredImageModel({
1593
- provider: imageProvider,
1594
- model: model ?? null,
1595
- customUrl: customUrl?.trim(),
1596
- customKey: customKey?.trim(),
1597
- });
1598
-
1599
- if (persistDefault) {
1600
- this.ctx.settings.set("providers.image", imageProvider);
1601
- if (model) this.ctx.settings.set("providers.imageModel", model);
1602
- if (customUrl?.trim()) this.ctx.settings.set("providers.imageCustomUrl", customUrl.trim());
1603
- if (customKey?.trim()) this.ctx.settings.set("providers.imageCustomKey", customKey.trim());
1604
- }
1605
-
1606
- const displayModel =
1607
- model ?? (normalized !== "auto" && normalized !== "custom" ? IMAGE_PROVIDER_DEFAULTS[normalized] : undefined);
1608
- const label = normalized === "auto" ? "Auto" : `${normalized}${displayModel ? ` (${displayModel})` : ""}`;
1609
- this.ctx.showStatus(`Image Generation: ${label}${persistDefault ? " (default)" : " (session)"}`);
1610
- this.ctx.ui.requestRender();
1611
- }
1612
-
1613
1663
  showCustomProviderWizard(): void {
1614
1664
  this.showSelector(done => {
1615
1665
  let wizard: CustomProviderWizardComponent;
@@ -1745,6 +1795,40 @@ export class SelectorController {
1745
1795
  this.ctx.ui.requestRender();
1746
1796
  },
1747
1797
  onRenderRequested: () => this.ctx.ui.requestRender(),
1798
+ createProviderOrderEditor: closeEditor => {
1799
+ // The editor owns the context lifecycle: dispose() on
1800
+ // close or external teardown releases the subscriptions,
1801
+ // so the controller only wires change repaints here.
1802
+ let editor: ProviderOrderEditorComponent | undefined;
1803
+ const context = new ProviderOrderContext(
1804
+ this.ctx.session.modelRegistry,
1805
+ this.ctx.settings,
1806
+ () => {
1807
+ editor?.refresh();
1808
+ this.ctx.ui.requestRender();
1809
+ },
1810
+ this.ctx.session.credentialSessionId,
1811
+ );
1812
+ try {
1813
+ editor = new ProviderOrderEditorComponent(
1814
+ context,
1815
+ () => {
1816
+ closeEditor();
1817
+ this.ctx.ui.requestRender();
1818
+ },
1819
+ message => this.ctx.showError(message),
1820
+ );
1821
+ } catch (error) {
1822
+ // The context subscribed to settings/auth in its constructor.
1823
+ // If editor construction fails the editor disposes it before
1824
+ // rethrowing (initial rebuild failure); dispose again here so
1825
+ // failures before that point (e.g. UI setup) can't leak the
1826
+ // subscriptions this closure created.
1827
+ context.dispose();
1828
+ throw error;
1829
+ }
1830
+ return editor;
1831
+ },
1748
1832
  onCancel: () => {
1749
1833
  done();
1750
1834
  // Restore status line to saved settings
@@ -1965,6 +2049,18 @@ export class SelectorController {
1965
2049
  this.ctx.editor.setAutocompleteMaxVisible(typeof value === "number" ? value : Number(value));
1966
2050
  break;
1967
2051
 
2052
+ case "stt.enabled":
2053
+ if (value === true) {
2054
+ void configureSttFromSettings({
2055
+ modelName: settings.get("stt.modelName"),
2056
+ setEnabled: enabled => settings.set("stt.enabled", enabled),
2057
+ flush: () => settings.flushOrThrow(),
2058
+ showStatus: message => this.ctx.showStatus(message),
2059
+ showError: message => this.ctx.showError(message),
2060
+ });
2061
+ }
2062
+ break;
2063
+
1968
2064
  // Settings with UI side effects
1969
2065
  case "showImages":
1970
2066
  for (const child of this.ctx.chatContainer.children) {
@@ -2101,37 +2197,6 @@ export class SelectorController {
2101
2197
  setSearchHardTimeoutMs(value * 1000);
2102
2198
  }
2103
2199
  break;
2104
- case "providers.image":
2105
- case "providers.imageModel":
2106
- case "providers.imageCustomUrl":
2107
- case "providers.imageCustomKey":
2108
- case "providers.imageCustomKeyEnv": {
2109
- const imgProvider = this.ctx.settings.get("providers.image");
2110
- const imgModel = this.ctx.settings.get("providers.imageModel");
2111
- const imgCustomUrl = this.ctx.settings.get("providers.imageCustomUrl");
2112
- const imgCustomKey = this.ctx.settings.get("providers.imageCustomKey");
2113
- const imgCustomKeyEnv = this.ctx.settings.get("providers.imageCustomKeyEnv");
2114
- if (
2115
- imgProvider === "auto" ||
2116
- imgProvider === "openai" ||
2117
- imgProvider === "gemini" ||
2118
- imgProvider === "openrouter" ||
2119
- imgProvider === "antigravity" ||
2120
- imgProvider === "alibaba" ||
2121
- imgProvider === "custom"
2122
- ) {
2123
- setPreferredImageProvider(imgProvider === "custom" ? "auto" : imgProvider);
2124
- setConfiguredImageModel({
2125
- provider: imgProvider,
2126
- model: imgModel ?? null,
2127
- customUrl: imgCustomUrl,
2128
- customKey: imgCustomKey,
2129
- customKeyEnv: imgCustomKeyEnv,
2130
- });
2131
- }
2132
- break;
2133
- }
2134
-
2135
2200
  // MCP update injection - live subscribe/unsubscribe
2136
2201
  case "mcp.notifications":
2137
2202
  this.ctx.mcpManager?.setNotificationsEnabled(value as boolean);
@@ -2199,11 +2264,6 @@ export class SelectorController {
2199
2264
  await this.#deleteCustomModelPreset(selection.profileName, modelSelector);
2200
2265
  return;
2201
2266
  }
2202
- if (selection.kind === "imageGeneration") {
2203
- done();
2204
- await this.#handleImageGenerationConfig();
2205
- return;
2206
- }
2207
2267
  if (selection.kind === "profile") {
2208
2268
  await this.#applyModelProfile(selection.profileName, selection.setDefault);
2209
2269
  done();
@@ -2232,12 +2292,19 @@ export class SelectorController {
2232
2292
  if (includesRoleAgent) {
2233
2293
  const apiKey = await this.ctx.session.modelRegistry.getApiKey(
2234
2294
  model,
2235
- this.ctx.session.sessionId,
2295
+ this.ctx.session.credentialSessionId,
2236
2296
  );
2237
2297
  if (!apiKey) {
2238
2298
  throw new Error(`No API key for ${model.provider}/${model.id}`);
2239
2299
  }
2240
2300
  }
2301
+ if (includesDefault && !includesRoleAgent) {
2302
+ const apiKey = await this.ctx.session.modelRegistry.getApiKey(
2303
+ model,
2304
+ this.ctx.session.credentialSessionId,
2305
+ );
2306
+ if (!apiKey) throw new Error(`No API key for ${model.provider}/${model.id}`);
2307
+ }
2241
2308
  const value =
2242
2309
  selectedSelector ?? formatModelSelectorValue(`${model.provider}/${model.id}`, thinkingLevel);
2243
2310
  const assignments = new Map<GjcModelAssignmentTargetId, string>();
@@ -2247,30 +2314,47 @@ export class SelectorController {
2247
2314
  ? selectedSelector.slice(0, -thinkingLevel.length - 1)
2248
2315
  : selectedSelector;
2249
2316
 
2250
- if (includesDefault) {
2251
- await this.ctx.session.setModel(model, "default", {
2252
- selector: defaultSelector,
2253
- thinkingLevel,
2254
- cause: "user-selection",
2255
- });
2256
- if (thinkingLevel && thinkingLevel !== ThinkingLevel.Inherit) {
2257
- this.ctx.session.setThinkingLevel(thinkingLevel);
2317
+ const rollbackSnapshot = this.#captureDefaultAssignmentRollback();
2318
+ let defaultMutationStarted = false;
2319
+ let assignmentMutationStarted = false;
2320
+ let materializedProfile = false;
2321
+ try {
2322
+ if (includesDefault) {
2323
+ await this.ctx.session.setModel(model, "default", {
2324
+ selector: defaultSelector,
2325
+ thinkingLevel,
2326
+ cause: "user-selection",
2327
+ onMutationStarted: () => {
2328
+ defaultMutationStarted = true;
2329
+ },
2330
+ });
2331
+ if (thinkingLevel && thinkingLevel !== ThinkingLevel.Inherit) {
2332
+ this.ctx.session.setThinkingLevel(thinkingLevel);
2333
+ }
2258
2334
  }
2259
- }
2260
- const materializedProfile = materializeActiveModelProfileAssignments({
2261
- session: this.ctx.session,
2262
- settings: this.ctx.settings,
2263
- assignments,
2264
- });
2265
- if (!materializedProfile) {
2266
- for (const targetRole of targetRoles) {
2267
- const target = GJC_MODEL_ASSIGNMENT_TARGETS[targetRole];
2268
- if (target.settingsPath === "modelRoles") {
2269
- this.ctx.settings.setModelRole(targetRole, value);
2270
- } else {
2271
- this.ctx.settings.setAgentModelOverride(targetRole, value);
2335
+ assignmentMutationStarted = true;
2336
+ materializedProfile = materializeActiveModelProfileAssignments({
2337
+ session: this.ctx.session,
2338
+ settings: this.ctx.settings,
2339
+ assignments,
2340
+ });
2341
+ if (!materializedProfile) {
2342
+ for (const targetRole of targetRoles) {
2343
+ const target = GJC_MODEL_ASSIGNMENT_TARGETS[targetRole];
2344
+ if (target.settingsPath === "modelRoles") {
2345
+ this.ctx.settings.setModelRole(targetRole, value);
2346
+ } else {
2347
+ this.ctx.settings.setAgentModelOverride(targetRole, value);
2348
+ }
2272
2349
  }
2273
2350
  }
2351
+ } catch (error) {
2352
+ await this.#restoreDefaultAssignmentRollback(
2353
+ rollbackSnapshot,
2354
+ error,
2355
+ defaultMutationStarted,
2356
+ defaultMutationStarted || assignmentMutationStarted,
2357
+ );
2274
2358
  }
2275
2359
  modelSelector.refreshRoleAssignments({
2276
2360
  currentModel: this.ctx.session.model,
@@ -2294,27 +2378,47 @@ export class SelectorController {
2294
2378
  this.ctx.ui.requestRender();
2295
2379
  } else if (role === "default") {
2296
2380
  // Default: update agent state and persist as the active default model.
2297
- await this.ctx.session.setModel(model, role, {
2298
- selector: selectedSelector,
2299
- thinkingLevel,
2300
- cause: "user-selection",
2301
- });
2381
+ const apiKey = await this.ctx.session.modelRegistry.getApiKey(
2382
+ model,
2383
+ this.ctx.session.credentialSessionId,
2384
+ );
2385
+ if (!apiKey) throw new Error(`No API key for ${model.provider}/${model.id}`);
2386
+ const rollbackSnapshot = this.#captureDefaultAssignmentRollback();
2387
+ let defaultMutationStarted = false;
2388
+ let assignmentMutationStarted = false;
2302
2389
  const value = formatModelSelectorValue(
2303
2390
  selectedSelector ?? `${model.provider}/${model.id}`,
2304
2391
  thinkingLevel,
2305
2392
  );
2306
- if (
2307
- !materializeActiveModelProfileAssignment({
2393
+ try {
2394
+ await this.ctx.session.setModel(model, role, {
2395
+ selector: selectedSelector,
2396
+ thinkingLevel,
2397
+ cause: "user-selection",
2398
+ onMutationStarted: () => {
2399
+ defaultMutationStarted = true;
2400
+ },
2401
+ });
2402
+ assignmentMutationStarted = true;
2403
+ const materializedProfile = materializeActiveModelProfileAssignment({
2308
2404
  session: this.ctx.session,
2309
2405
  settings: this.ctx.settings,
2310
2406
  role,
2311
2407
  selector: value,
2312
- })
2313
- ) {
2314
- this.ctx.settings.setModelRole(role, value);
2315
- }
2316
- if (thinkingLevel && thinkingLevel !== ThinkingLevel.Inherit) {
2317
- this.ctx.session.setThinkingLevel(thinkingLevel);
2408
+ });
2409
+ if (!materializedProfile) {
2410
+ this.ctx.settings.setModelRole(role, value);
2411
+ }
2412
+ if (thinkingLevel && thinkingLevel !== ThinkingLevel.Inherit) {
2413
+ this.ctx.session.setThinkingLevel(thinkingLevel);
2414
+ }
2415
+ } catch (error) {
2416
+ await this.#restoreDefaultAssignmentRollback(
2417
+ rollbackSnapshot,
2418
+ error,
2419
+ defaultMutationStarted,
2420
+ defaultMutationStarted || assignmentMutationStarted,
2421
+ );
2318
2422
  }
2319
2423
  refreshRoleAssignments();
2320
2424
  this.ctx.statusLine.invalidate();
@@ -2322,7 +2426,10 @@ export class SelectorController {
2322
2426
  this.ctx.showStatus(`Default model: ${selectedSelector ?? model.id}`);
2323
2427
  this.ctx.ui.requestRender();
2324
2428
  } else {
2325
- const apiKey = await this.ctx.session.modelRegistry.getApiKey(model, this.ctx.session.sessionId);
2429
+ const apiKey = await this.ctx.session.modelRegistry.getApiKey(
2430
+ model,
2431
+ this.ctx.session.credentialSessionId,
2432
+ );
2326
2433
  if (!apiKey) {
2327
2434
  throw new Error(`No API key for ${model.provider}/${model.id}`);
2328
2435
  }
@@ -2365,7 +2472,7 @@ export class SelectorController {
2365
2472
  },
2366
2473
  {
2367
2474
  ...options,
2368
- sessionId: this.ctx.session.sessionId,
2475
+ sessionId: this.ctx.session.credentialSessionId,
2369
2476
  currentThinkingLevel: this.ctx.session.thinkingLevel,
2370
2477
  activeModelProfile:
2371
2478
  this.ctx.session.getActiveModelProfile?.() ?? this.ctx.settings.get("modelProfile.default"),
@@ -3234,7 +3341,7 @@ export class SelectorController {
3234
3341
  validateAuth: async (selectedProviderId: string) => {
3235
3342
  const apiKey = await this.ctx.session.modelRegistry.getApiKeyForProvider(
3236
3343
  selectedProviderId,
3237
- this.ctx.session.sessionId,
3344
+ this.ctx.session.credentialSessionId,
3238
3345
  );
3239
3346
  return !!apiKey;
3240
3347
  },
@@ -62,7 +62,7 @@ export class PromptSuggestionController {
62
62
  this.#ctx.session.messages,
63
63
  this.#ctx.session.modelRegistry,
64
64
  this.#ctx.settings,
65
- this.#ctx.session.sessionId,
65
+ this.#ctx.session.credentialSessionId,
66
66
  this.#ctx.session.model,
67
67
  provider => this.#ctx.session.agent.metadataForProvider(provider),
68
68
  )
@@ -99,6 +99,7 @@ export async function initializeExtensions(session: AgentSession, options: Initi
99
99
  // ExtensionContextActions
100
100
  {
101
101
  getModel: () => session.model,
102
+ getCredentialSessionId: () => session.credentialSessionId,
102
103
  isIdle: () => !session.isStreaming,
103
104
  getActivePromptHandle: () => session.activePromptHandle,
104
105
  abort: () => session.abort(),
@@ -368,6 +368,7 @@ export function trimChatChildren(ctx: InteractiveModeContext): void {
368
368
  }
369
369
 
370
370
  export class UiHelpers {
371
+ #compactionFlushRetryScheduled = false;
371
372
  #renderedIrcInlineComponents = new Map<string, readonly Component[]>();
372
373
  #viewportAnchorOccurrences = new WeakMap<object, { base: string; epoch: number; id: string }>();
373
374
  #nextViewportAnchorOccurrence = new Map<string, number>();
@@ -1217,6 +1218,23 @@ export class UiHelpers {
1217
1218
  }
1218
1219
 
1219
1220
  async flushCompactionQueue(options?: { willRetry?: boolean }): Promise<void> {
1221
+ if (this.ctx.session.isCompacting || this.ctx.session.isBashRunning || this.ctx.session.isEvalRunning) {
1222
+ if (!this.#compactionFlushRetryScheduled) {
1223
+ this.#compactionFlushRetryScheduled = true;
1224
+ void (async () => {
1225
+ while (
1226
+ this.ctx.session.isCompacting ||
1227
+ this.ctx.session.isBashRunning ||
1228
+ this.ctx.session.isEvalRunning
1229
+ ) {
1230
+ await Bun.sleep(50);
1231
+ }
1232
+ this.#compactionFlushRetryScheduled = false;
1233
+ await this.flushCompactionQueue(options);
1234
+ })();
1235
+ }
1236
+ return;
1237
+ }
1220
1238
  if (this.ctx.compactionQueuedMessages.length === 0) {
1221
1239
  return;
1222
1240
  }
@@ -11,6 +11,7 @@ Drives a real Chromium tab with full puppeteer access via JS execution.
11
11
  - Browser kinds: no `app` launches headless Chromium; `app.path` reuses CDP or kills stale same-path processes before spawning — NEVER use it for a daily Chrome profile; use explicit `app.browser: "chrome"` profile mode instead. Saved-profile/CDP automation has access to that profile's cookies and authenticated accounts. Profile mode refuses a matching non-CDP Chrome instead of killing/relaunching it, and `kill: true` can terminate only a Chrome process GJC launched; `app.cdp_url` is externally owned and disconnect-only. CDP must stay on `127.0.0.1`: it grants full browser-account access.
12
12
  - Inside `run`, `tab` exposes high-level helpers (`goto`, `observe`, `id`, `click`, `type`, `fill`, `press`, `waitFor`, `screenshot`, `extract`, …); reach for `page` (raw puppeteer Page) when they don't cover it.
13
13
  - Selectors accept CSS as well as puppeteer query handlers: `aria/Sign in`, `text/Continue`, `xpath/…`, `pierce/…`.
14
+ - Runtime diagnostics are opt-in: pass `diagnostics: true` to `open` to subscribe the tab to page `Runtime.exceptionThrown` and `console.error` events. The next successful `act`/`run` response then includes at most 20 `runtimeDiagnostics` entries plus `runtimeDiagnosticsDropped`, then drains them. Entries contain only kind, time, origin-only URL, line/column, and a built-in error class from a fixed allowlist — never path segments, query strings, messages, console arguments, values, or stacks. Output is byte-bounded and marks truncation explicitly.
14
15
  - Full reference — helpers, browser kinds, CDP/security details, and more examples — read `gjc://tools/browser.md`.
15
16
  </instruction>
16
17