@gajae-code/coding-agent 0.11.6 → 0.11.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/dist/types/capability/index.d.ts +10 -20
  3. package/dist/types/capability/types.d.ts +3 -0
  4. package/dist/types/cli/args.d.ts +0 -2
  5. package/dist/types/commands/daemon.d.ts +2 -2
  6. package/dist/types/commands/sdk.d.ts +20 -2
  7. package/dist/types/config/keybindings.d.ts +34 -4
  8. package/dist/types/config/model-profile-activation.d.ts +4 -3
  9. package/dist/types/config/model-profile-contract.d.ts +38 -0
  10. package/dist/types/config/settings-schema.d.ts +70 -7
  11. package/dist/types/edit/modes/patch.d.ts +13 -0
  12. package/dist/types/edit/path-mutation-lock.d.ts +15 -0
  13. package/dist/types/eval/py/executor.d.ts +1 -1
  14. package/dist/types/eval/py/runner-artifact.d.ts +8 -0
  15. package/dist/types/eval/py/tool-bridge.d.ts +1 -2
  16. package/dist/types/gjc-runtime/repository-binding.d.ts +63 -0
  17. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +6 -5
  18. package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +20 -0
  19. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +43 -5
  20. package/dist/types/main.d.ts +32 -1
  21. package/dist/types/modes/components/btw-panel.d.ts +1 -0
  22. package/dist/types/modes/components/model-selector.d.ts +2 -0
  23. package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +2 -1
  24. package/dist/types/modes/components/tool-status-header.d.ts +4 -2
  25. package/dist/types/modes/components/welcome.d.ts +2 -0
  26. package/dist/types/modes/controllers/btw-controller.d.ts +5 -0
  27. package/dist/types/modes/controllers/command-controller.d.ts +2 -0
  28. package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
  29. package/dist/types/modes/interactive-mode.d.ts +7 -2
  30. package/dist/types/modes/prompt-action-autocomplete.d.ts +6 -1
  31. package/dist/types/modes/prompt-suggestion-controller.d.ts +35 -0
  32. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +2 -2
  33. package/dist/types/modes/types.d.ts +4 -0
  34. package/dist/types/modes/utils/hotkeys-markdown.d.ts +2 -1
  35. package/dist/types/runtime-mcp/content-limits.d.ts +7 -0
  36. package/dist/types/runtime-mcp/manager.d.ts +1 -1
  37. package/dist/types/runtime-mcp/oauth-flow.d.ts +0 -5
  38. package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +14 -0
  39. package/dist/types/sdk/broker/broker.d.ts +2 -0
  40. package/dist/types/sdk/broker/lifecycle.d.ts +6 -0
  41. package/dist/types/sdk/bus/chat-daemon-control.d.ts +6 -1
  42. package/dist/types/sdk/bus/config-commands.d.ts +2 -2
  43. package/dist/types/sdk/bus/config.d.ts +2 -0
  44. package/dist/types/sdk/bus/conversation-store.d.ts +1 -0
  45. package/dist/types/sdk/bus/index.d.ts +26 -1
  46. package/dist/types/sdk/bus/lifecycle-commands.d.ts +5 -4
  47. package/dist/types/sdk/bus/notification-orchestration.d.ts +1 -0
  48. package/dist/types/sdk/bus/notification-service.d.ts +34 -0
  49. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +105 -0
  50. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +18 -3
  51. package/dist/types/sdk/bus/telegram-daemon-control.d.ts +5 -1
  52. package/dist/types/sdk/bus/telegram-daemon.d.ts +189 -4
  53. package/dist/types/sdk/bus/telegram-reference.d.ts +2 -0
  54. package/dist/types/sdk/bus/topic-registry.d.ts +88 -2
  55. package/dist/types/sdk/client/discovery.d.ts +2 -0
  56. package/dist/types/sdk/client/liveness.d.ts +7 -0
  57. package/dist/types/sdk/host/control/operations.d.ts +1 -1
  58. package/dist/types/sdk/host/host.d.ts +1 -1
  59. package/dist/types/sdk/host/query/handlers.d.ts +9 -0
  60. package/dist/types/sdk/index.d.ts +2 -0
  61. package/dist/types/sdk/prompt-status.d.ts +88 -0
  62. package/dist/types/sdk/session-directory.d.ts +1 -1
  63. package/dist/types/sdk/startup-capability.d.ts +3 -0
  64. package/dist/types/sdk/transport/auth-preface.d.ts +9 -0
  65. package/dist/types/sdk/transport/index.d.ts +12 -0
  66. package/dist/types/sdk/transport/relay.d.ts +43 -0
  67. package/dist/types/sdk/transport/serve-cli.d.ts +4 -0
  68. package/dist/types/sdk/transport/socket.d.ts +7 -0
  69. package/dist/types/sdk/transport/stdio.d.ts +3 -0
  70. package/dist/types/session/agent-session.d.ts +30 -5
  71. package/dist/types/session/btw-contract.d.ts +16 -0
  72. package/dist/types/session/internal/managed-session-scope.d.ts +32 -9
  73. package/dist/types/session/internal/managed-session-storage.d.ts +27 -10
  74. package/dist/types/session/internal/native-publish-outcome.d.ts +48 -0
  75. package/dist/types/session/session-manager.d.ts +25 -4
  76. package/dist/types/session/session-storage.d.ts +18 -0
  77. package/dist/types/setup/credential-import.d.ts +5 -1
  78. package/dist/types/setup/provider-onboarding.d.ts +2 -0
  79. package/dist/types/ssh/utils.d.ts +1 -0
  80. package/dist/types/task/discovery.d.ts +3 -1
  81. package/dist/types/task/index.d.ts +3 -1
  82. package/dist/types/task/receipt.d.ts +2 -0
  83. package/dist/types/task/types.d.ts +94 -0
  84. package/dist/types/tools/ask.d.ts +454 -174
  85. package/dist/types/tools/fetch.d.ts +5 -8
  86. package/dist/types/tools/image-gen.d.ts +63 -1
  87. package/dist/types/tools/index.d.ts +4 -0
  88. package/dist/types/tools/skill-discovery.d.ts +7 -0
  89. package/dist/types/tools/sqlite-reader.d.ts +7 -0
  90. package/dist/types/utils/prompt-suggestion.d.ts +33 -0
  91. package/dist/types/utils/shell-snapshot.d.ts +12 -0
  92. package/dist/types/web/insane/bridge.d.ts +1 -0
  93. package/dist/types/web/insane/url-guard.d.ts +17 -0
  94. package/package.json +8 -8
  95. package/scripts/capture-platform-shortcut-labels-showcase.ts +342 -0
  96. package/scripts/dogfood-repository-binding.ts +209 -0
  97. package/scripts/generate-sdk-operation-inventory.ts +4 -0
  98. package/scripts/run-test-manifest.ts +4 -4
  99. package/src/capability/index.ts +50 -42
  100. package/src/capability/types.ts +3 -0
  101. package/src/cli/args.ts +2 -92
  102. package/src/cli/notify-cli.ts +5 -38
  103. package/src/cli/setup-cli.ts +10 -4
  104. package/src/cli.ts +52 -9
  105. package/src/commands/daemon.ts +22 -5
  106. package/src/commands/sdk.ts +44 -11
  107. package/src/config/keybindings.ts +158 -25
  108. package/src/config/model-profile-activation.ts +7 -18
  109. package/src/config/model-profile-contract.ts +161 -0
  110. package/src/config/model-profiles.ts +18 -0
  111. package/src/config/model-registry.ts +11 -1
  112. package/src/config/settings-schema.ts +73 -7
  113. package/src/coordinator-mcp/model-preset.ts +21 -74
  114. package/src/defaults/gjc/skills/deep-interview/SKILL.md +2 -2
  115. package/src/defaults/gjc/skills/ralplan/SKILL.md +3 -1
  116. package/src/defaults/gjc/skills/ultragoal/SKILL.md +42 -1
  117. package/src/discovery/ssh.ts +12 -1
  118. package/src/edit/index.ts +3 -6
  119. package/src/edit/modes/patch.ts +38 -1
  120. package/src/edit/modes/replace.ts +42 -0
  121. package/src/edit/path-mutation-lock.ts +112 -0
  122. package/src/eval/py/executor.ts +30 -11
  123. package/src/eval/py/kernel.ts +1 -21
  124. package/src/eval/py/prelude.py +2 -2
  125. package/src/eval/py/runner-artifact.ts +84 -0
  126. package/src/eval/py/tool-bridge.ts +34 -18
  127. package/src/extensibility/gjc-plugins/runtime-adapters.ts +2 -1
  128. package/src/extensibility/plugins/marketplace/fetcher.ts +191 -7
  129. package/src/gjc-runtime/launch-tmux.ts +1 -1
  130. package/src/gjc-runtime/managed-owner-supervisor.ts +83 -4
  131. package/src/gjc-runtime/ralplan-runtime.ts +75 -3
  132. package/src/gjc-runtime/repository-binding.ts +267 -0
  133. package/src/gjc-runtime/session-state-sidecar.ts +25 -9
  134. package/src/gjc-runtime/tmux-owner-isolation.ts +2 -1
  135. package/src/gjc-runtime/tmux-sessions.ts +45 -6
  136. package/src/gjc-runtime/ultragoal-guard.ts +93 -15
  137. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +103 -0
  138. package/src/gjc-runtime/ultragoal-runtime.ts +456 -31
  139. package/src/gjc-runtime/workflow-manifest.generated.json +58 -0
  140. package/src/gjc-runtime/workflow-manifest.ts +23 -0
  141. package/src/internal-urls/docs-index.generated.ts +12 -10
  142. package/src/internal-urls/local-protocol.ts +64 -21
  143. package/src/internal-urls/mcp-protocol.ts +73 -11
  144. package/src/main.ts +111 -37
  145. package/src/modes/DESIGN.md +5 -0
  146. package/src/modes/acp/acp-agent.ts +50 -16
  147. package/src/modes/action-registry.ts +1 -1
  148. package/src/modes/components/btw-panel.ts +82 -16
  149. package/src/modes/components/model-selector.ts +73 -11
  150. package/src/modes/components/plan-preview-overlay.ts +1 -1
  151. package/src/modes/components/runtime-mcp-add-wizard.ts +5 -0
  152. package/src/modes/components/tool-status-header.ts +25 -4
  153. package/src/modes/components/welcome.ts +22 -18
  154. package/src/modes/controllers/btw-controller.ts +119 -36
  155. package/src/modes/controllers/command-controller.ts +97 -54
  156. package/src/modes/controllers/event-controller.ts +10 -0
  157. package/src/modes/controllers/input-controller.ts +38 -2
  158. package/src/modes/controllers/runtime-mcp-command-controller.ts +29 -6
  159. package/src/modes/controllers/selector-controller.ts +149 -63
  160. package/src/modes/interactive-mode.ts +57 -34
  161. package/src/modes/prompt-action-autocomplete.ts +32 -13
  162. package/src/modes/prompt-suggestion-controller.ts +96 -0
  163. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +2 -2
  164. package/src/modes/types.ts +4 -0
  165. package/src/modes/utils/hotkeys-markdown.ts +64 -36
  166. package/src/modes/utils/ui-helpers.ts +5 -3
  167. package/src/prompts/agents/architect.md +0 -1
  168. package/src/prompts/agents/critic.md +0 -1
  169. package/src/prompts/system/btw-user.md +3 -8
  170. package/src/prompts/system/prompt-suggestion-system.md +32 -0
  171. package/src/prompts/system/system-prompt.md +6 -4
  172. package/src/prompts/tools/cron.md +3 -1
  173. package/src/prompts/tools/skill-discovery.md +1 -0
  174. package/src/runtime-mcp/client.ts +41 -55
  175. package/src/runtime-mcp/content-limits.ts +71 -0
  176. package/src/runtime-mcp/json-rpc.ts +23 -9
  177. package/src/runtime-mcp/manager.ts +51 -53
  178. package/src/runtime-mcp/oauth-flow.ts +0 -72
  179. package/src/runtime-mcp/plugin-network-boundary.ts +101 -0
  180. package/src/runtime-mcp/transports/http.ts +63 -22
  181. package/src/sdk/broker/broker.ts +34 -20
  182. package/src/sdk/broker/lifecycle.ts +185 -36
  183. package/src/sdk/bus/chat-daemon-control.ts +19 -5
  184. package/src/sdk/bus/config-commands.ts +2 -2
  185. package/src/sdk/bus/config.ts +9 -1
  186. package/src/sdk/bus/conversation-store.ts +40 -6
  187. package/src/sdk/bus/index.ts +244 -70
  188. package/src/sdk/bus/lifecycle-commands.ts +63 -10
  189. package/src/sdk/bus/lifecycle-control-runtime.ts +11 -2
  190. package/src/sdk/bus/lifecycle-orchestrator.ts +32 -1
  191. package/src/sdk/bus/notification-orchestration.ts +12 -0
  192. package/src/sdk/bus/notification-service.ts +68 -20
  193. package/src/sdk/bus/prompt-reconciliation.ts +239 -0
  194. package/src/sdk/bus/recent-activity.ts +90 -26
  195. package/src/sdk/bus/telegram-daemon-contract.ts +18 -3
  196. package/src/sdk/bus/telegram-daemon-control.ts +147 -35
  197. package/src/sdk/bus/telegram-daemon.ts +2463 -450
  198. package/src/sdk/bus/telegram-reference.ts +20 -2
  199. package/src/sdk/bus/topic-registry.ts +451 -12
  200. package/src/sdk/cli/session-cli.ts +15 -2
  201. package/src/sdk/client/discovery.ts +19 -3
  202. package/src/sdk/client/liveness.ts +49 -0
  203. package/src/sdk/host/control/dispatch.ts +1 -1
  204. package/src/sdk/host/control/operations.ts +1 -1
  205. package/src/sdk/host/host.ts +2 -6
  206. package/src/sdk/host/query/handlers.ts +70 -5
  207. package/src/sdk/index.ts +12 -0
  208. package/src/sdk/prompt-status.ts +97 -0
  209. package/src/sdk/protocol/operation-inventory.generated.json +58 -0
  210. package/src/sdk/protocol/operation-registry.ts +18 -4
  211. package/src/sdk/session-directory.ts +5 -1
  212. package/src/sdk/session.ts +31 -5
  213. package/src/sdk/startup-capability.ts +9 -1
  214. package/src/sdk/transport/auth-preface.ts +82 -0
  215. package/src/sdk/transport/index.ts +18 -0
  216. package/src/sdk/transport/relay.ts +252 -0
  217. package/src/sdk/transport/serve-cli.ts +105 -0
  218. package/src/sdk/transport/socket.ts +197 -0
  219. package/src/sdk/transport/stdio.ts +17 -0
  220. package/src/session/agent-session.ts +292 -154
  221. package/src/session/blob-store.ts +25 -7
  222. package/src/session/btw-contract.ts +59 -0
  223. package/src/session/internal/managed-session-scope.ts +1163 -137
  224. package/src/session/internal/managed-session-storage.ts +397 -234
  225. package/src/session/internal/native-publish-outcome.ts +266 -0
  226. package/src/session/session-manager.ts +620 -403
  227. package/src/session/session-storage.ts +128 -11
  228. package/src/setup/credential-import.ts +28 -3
  229. package/src/setup/provider-onboarding.ts +30 -1
  230. package/src/setup/provider-presets.json +2 -1
  231. package/src/slash-commands/builtin-registry.ts +1 -1
  232. package/src/ssh/utils.ts +14 -0
  233. package/src/task/discovery.ts +8 -2
  234. package/src/task/gjc-command.ts +2 -2
  235. package/src/task/index.ts +121 -15
  236. package/src/task/receipt.ts +3 -0
  237. package/src/task/types.ts +34 -3
  238. package/src/tools/ask.ts +199 -89
  239. package/src/tools/ast-edit.ts +2 -2
  240. package/src/tools/bash-pty-selection.ts +2 -2
  241. package/src/tools/browser/screenshot-format.ts +6 -1
  242. package/src/tools/cron.ts +1 -1
  243. package/src/tools/fetch.ts +13 -131
  244. package/src/tools/image-gen.ts +476 -23
  245. package/src/tools/index.ts +4 -0
  246. package/src/tools/read.ts +2 -0
  247. package/src/tools/render-utils.ts +7 -1
  248. package/src/tools/skill-discovery.ts +39 -3
  249. package/src/tools/sqlite-reader.ts +63 -5
  250. package/src/utils/edit-mode.ts +2 -2
  251. package/src/utils/pasted-image-path.ts +15 -6
  252. package/src/utils/prompt-suggestion.ts +265 -0
  253. package/src/utils/shell-snapshot.ts +173 -63
  254. package/src/utils/sixel.ts +3 -3
  255. package/src/web/insane/bridge.ts +5 -1
  256. package/src/web/insane/url-guard.ts +140 -27
  257. package/src/web/scrapers/docs-rs.ts +30 -5
  258. package/src/web/scrapers/types.ts +19 -49
  259. package/src/web/scrapers/utils.ts +12 -26
@@ -4,6 +4,7 @@
4
4
  * Handles /mcp subcommands for managing MCP servers.
5
5
  */
6
6
  import * as path from "node:path";
7
+ import { resolveMCPOAuthResourceOrigin, resolveMCPOAuthTokenEndpoint } from "@gajae-code/ai";
7
8
  import { Spacer, Text } from "@gajae-code/tui";
8
9
  import { getMCPConfigPath, getProjectDir } from "@gajae-code/utils";
9
10
  import type { SourceMeta } from "../../capability/types";
@@ -61,6 +62,7 @@ function withTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string)
61
62
  */
62
63
  interface OAuthFlowResult {
63
64
  credentialId: string;
65
+ tokenEndpoint: string;
64
66
  clientId?: string;
65
67
  clientSecret?: string;
66
68
  }
@@ -423,6 +425,7 @@ export class MCPCommandController {
423
425
  try {
424
426
  const oauthClientSecret = finalConfig.oauth?.clientSecret ?? "";
425
427
  const oauthResult = await this.#handleOAuthFlow(
428
+ finalConfig.url,
426
429
  oauth.authorizationUrl,
427
430
  oauth.tokenUrl,
428
431
  oauth.clientId ?? finalConfig.oauth?.clientId ?? "",
@@ -439,7 +442,7 @@ export class MCPCommandController {
439
442
  auth: {
440
443
  type: "oauth",
441
444
  credentialId: oauthResult.credentialId,
442
- tokenUrl: oauth.tokenUrl,
445
+ tokenUrl: oauthResult.tokenEndpoint,
443
446
  clientId: persistedClientId,
444
447
  clientSecret: persistedClientSecret,
445
448
  },
@@ -480,8 +483,15 @@ export class MCPCommandController {
480
483
  done();
481
484
  this.#handleWizardCancel();
482
485
  },
483
- async (authUrl: string, tokenUrl: string, clientId: string, clientSecret: string, scopes: string) => {
484
- return await this.#handleOAuthFlow(authUrl, tokenUrl, clientId, clientSecret, scopes);
486
+ async (
487
+ endpointUrl: string,
488
+ authUrl: string,
489
+ tokenUrl: string,
490
+ clientId: string,
491
+ clientSecret: string,
492
+ scopes: string,
493
+ ) => {
494
+ return await this.#handleOAuthFlow(endpointUrl, authUrl, tokenUrl, clientId, clientSecret, scopes);
485
495
  },
486
496
  async (config: MCPServerConfig) => {
487
497
  return await this.#handleTestConnection(config);
@@ -503,6 +513,7 @@ export class MCPCommandController {
503
513
  * Handle OAuth authentication flow for MCP server
504
514
  */
505
515
  async #handleOAuthFlow(
516
+ endpointUrl: string,
506
517
  authUrl: string,
507
518
  tokenUrl: string,
508
519
  clientId: string,
@@ -514,11 +525,13 @@ export class MCPCommandController {
514
525
  ): Promise<OAuthFlowResult> {
515
526
  const authStorage = this.ctx.session.modelRegistry.authStorage;
516
527
  let parsedAuthUrl: URL;
528
+ const resourceOrigin = resolveMCPOAuthResourceOrigin(endpointUrl);
529
+ const tokenEndpoint = resolveMCPOAuthTokenEndpoint(tokenUrl);
530
+ if (!resourceOrigin || !tokenEndpoint) throw new Error("Invalid MCP OAuth configuration.");
517
531
 
518
532
  // Validate OAuth URLs
519
533
  try {
520
534
  parsedAuthUrl = new URL(authUrl);
521
- new URL(tokenUrl);
522
535
  } catch (_error) {
523
536
  throw new Error(
524
537
  `Invalid OAuth URLs. Please check:\n Authorization URL: ${authUrl}\n Token URL: ${tokenUrl}`,
@@ -533,7 +546,7 @@ export class MCPCommandController {
533
546
  const flow = new MCPOAuthFlow(
534
547
  {
535
548
  authorizationUrl: authUrl,
536
- tokenUrl: tokenUrl,
549
+ tokenUrl: tokenEndpoint,
537
550
  clientId: resolvedClientId,
538
551
  clientSecret: resolvedClientSecret,
539
552
  scopes: scopes || undefined,
@@ -618,6 +631,10 @@ export class MCPCommandController {
618
631
  const oauthCredential: OAuthCredential = {
619
632
  type: "oauth",
620
633
  ...credentials,
634
+ mcpBinding: {
635
+ resourceOrigin,
636
+ tokenEndpoint,
637
+ },
621
638
  };
622
639
 
623
640
  // Store under a synthetic provider name
@@ -625,6 +642,7 @@ export class MCPCommandController {
625
642
 
626
643
  return {
627
644
  credentialId,
645
+ tokenEndpoint,
628
646
  clientId: flow.resolvedClientId,
629
647
  clientSecret: flow.registeredClientSecret,
630
648
  };
@@ -1328,6 +1346,10 @@ export class MCPCommandController {
1328
1346
  this.ctx.showError(`Server "${name}" is disabled. Run /mcp enable ${name} first.`);
1329
1347
  return;
1330
1348
  }
1349
+ if (found.config.type !== "http" && found.config.type !== "sse") {
1350
+ this.ctx.showError(`Server "${name}" does not use a remote MCP transport.`);
1351
+ return;
1352
+ }
1331
1353
 
1332
1354
  const currentAuth = (found.config as MCPServerConfig & { auth?: MCPAuthConfig }).auth;
1333
1355
  if (currentAuth?.type === "oauth") {
@@ -1341,6 +1363,7 @@ export class MCPCommandController {
1341
1363
  this.#showMessage(["", theme.fg("muted", `Reauthorizing "${name}"...`), ""].join("\n"));
1342
1364
 
1343
1365
  const oauthResult = await this.#handleOAuthFlow(
1366
+ found.config.url,
1344
1367
  oauth.authorizationUrl,
1345
1368
  oauth.tokenUrl,
1346
1369
  oauth.clientId ?? found.config.oauth?.clientId ?? "",
@@ -1359,7 +1382,7 @@ export class MCPCommandController {
1359
1382
  auth: {
1360
1383
  type: "oauth",
1361
1384
  credentialId: oauthResult.credentialId,
1362
- tokenUrl: oauth.tokenUrl,
1385
+ tokenUrl: oauthResult.tokenEndpoint,
1363
1386
  clientId: persistedClientId,
1364
1387
  clientSecret: persistedClientSecret,
1365
1388
  },
@@ -5,7 +5,7 @@ import type { OAuthProvider } from "@gajae-code/ai/utils/oauth/types";
5
5
  import type { Component, OverlayHandle, SlashCommand } from "@gajae-code/tui";
6
6
  import { Input, isPetMode, Loader, Spacer, Text } from "@gajae-code/tui";
7
7
  import { getAgentDbPath, getProjectDir, logger, VERSION } from "@gajae-code/utils";
8
- import { type AppKeybinding, formatKeyHints } from "../../config/keybindings";
8
+ import type { AppKeybinding } from "../../config/keybindings";
9
9
  import {
10
10
  activateModelProfile,
11
11
  type MaterializeModelProfileForDeletionResult,
@@ -64,7 +64,7 @@ import {
64
64
  import type { NotificationSessionStatus } from "../../sdk/bus/session-control";
65
65
  import {
66
66
  ensureTelegramDaemonRunningDetailed,
67
- readDaemonState,
67
+ resolveTelegramSetupPreflight,
68
68
  unregisterNotificationRoot,
69
69
  } from "../../sdk/bus/telegram-daemon";
70
70
  import { TelegramDaemonController } from "../../sdk/bus/telegram-daemon-control";
@@ -101,8 +101,10 @@ import {
101
101
  } from "../../setup/model-onboarding-guidance";
102
102
  import { addApiCompatibleProvider, formatProviderSetupResult } from "../../setup/provider-onboarding";
103
103
  import {
104
+ IMAGE_PROVIDER_DEFAULTS,
104
105
  isConfigurableSearchProviderId,
105
106
  isSearchProviderPreference,
107
+ setConfiguredImageModel,
106
108
  setPreferredImageProvider,
107
109
  setPreferredSearchProvider,
108
110
  setSearchFallbackProviders,
@@ -190,6 +192,7 @@ export function buildStatusLineSettings(settingsInstance: Settings): StatusLineS
190
192
  rightSegments: settingsInstance.get("statusLine.rightSegments"),
191
193
  separator: settingsInstance.get("statusLine.separator"),
192
194
  showHookStatus: settingsInstance.get("statusLine.showHookStatus"),
195
+ showActionHints: settingsInstance.get("statusLine.showActionHints"),
193
196
  sessionAccent: settingsInstance.get("statusLine.sessionAccent"),
194
197
  maxRows: settingsInstance.get("statusLine.maxRows"),
195
198
  segmentOptions: settingsInstance.get("statusLine.segmentOptions"),
@@ -223,6 +226,7 @@ export interface NotificationsEditorOperationDependencies {
223
226
  sanitizeDiagnostic: typeof sanitizeDiagnostic;
224
227
  ensureTelegramDaemonRunningDetailed: typeof ensureTelegramDaemonRunningDetailed;
225
228
  runTelegramSetup: typeof runTelegramSetup;
229
+ resolveTelegramSetupPreflight: typeof resolveTelegramSetupPreflight;
226
230
  proposedTelegramIdentity: typeof proposedTelegramIdentity;
227
231
  reconcileCommittedTelegramConfiguration: typeof reconcileCommittedTelegramConfiguration;
228
232
  saveTelegramInactive: typeof saveTelegramInactive;
@@ -247,6 +251,7 @@ const notificationEditorOperationDependencies: NotificationsEditorOperationDepen
247
251
  sanitizeDiagnostic,
248
252
  ensureTelegramDaemonRunningDetailed,
249
253
  runTelegramSetup,
254
+ resolveTelegramSetupPreflight,
250
255
  proposedTelegramIdentity,
251
256
  reconcileCommittedTelegramConfiguration,
252
257
  saveTelegramInactive,
@@ -303,33 +308,8 @@ export function createNotificationsEditorOperations(
303
308
  cwd: ctx.sessionManager.getCwd(),
304
309
  sessionId: ctx.sessionManager.getSessionId(),
305
310
  });
306
- const telegramSetupPreflight = async (): Promise<TelegramSetupPreflight> => {
307
- const storedChatId = services.getNotificationConfig(ctx.settings).chatId;
308
- try {
309
- const state = await readDaemonState(ctx.settings);
310
- const validPid = Number.isSafeInteger(state?.pid) && (state?.pid ?? 0) > 0;
311
- if (!state || !validPid) return { storedChatId };
312
- let live = false;
313
- try {
314
- process.kill(state.pid, 0);
315
- live = true;
316
- } catch (error) {
317
- live = (error as NodeJS.ErrnoException).code === "EPERM";
318
- }
319
- return live
320
- ? {
321
- storedChatId,
322
- daemon: {
323
- live,
324
- tokenFingerprint: typeof state.tokenFingerprint === "string" ? state.tokenFingerprint : undefined,
325
- chatId: typeof state.chatId === "string" && state.chatId.trim() ? state.chatId.trim() : undefined,
326
- },
327
- }
328
- : { storedChatId };
329
- } catch {
330
- return { storedChatId };
331
- }
332
- };
311
+ const telegramSetupPreflight = async (): Promise<TelegramSetupPreflight> =>
312
+ await services.resolveTelegramSetupPreflight(ctx.settings);
333
313
 
334
314
  return {
335
315
  loadState: async () => {
@@ -835,7 +815,7 @@ export class SelectorController {
835
815
  id: `action:${action.id}`,
836
816
  label: action.label,
837
817
  description: action.id,
838
- keybinding: formatKeyHints(this.ctx.keybindings.getKeys(action.id as AppKeybinding)) || undefined,
818
+ keybinding: this.ctx.keybindings.getDisplayString(action.id as AppKeybinding) || undefined,
839
819
  searchText: action.id,
840
820
  handler: action.handler,
841
821
  })),
@@ -1103,6 +1083,75 @@ export class SelectorController {
1103
1083
  }
1104
1084
  }
1105
1085
 
1086
+ async #handleImageGenerationConfig(): Promise<void> {
1087
+ const provider = await this.ctx.showHookInput(
1088
+ "Image Generation provider (auto, openai, gemini, openrouter, antigravity, alibaba, custom)",
1089
+ "auto",
1090
+ );
1091
+ if (provider === undefined) return;
1092
+ const normalized = provider.trim().toLowerCase();
1093
+ const validProviders = ["auto", "openai", "gemini", "openrouter", "antigravity", "alibaba", "custom"];
1094
+ if (!validProviders.includes(normalized)) {
1095
+ this.ctx.showStatus(`Invalid image provider: ${normalized}. Valid: ${validProviders.join(", ")}`);
1096
+ return;
1097
+ }
1098
+ let model: string | undefined;
1099
+ if (normalized !== "auto" && normalized !== "custom") {
1100
+ const defaultModel = IMAGE_PROVIDER_DEFAULTS[normalized];
1101
+ model = await this.ctx.showHookInput(`Image model for ${normalized} (default: ${defaultModel})`, defaultModel);
1102
+ if (model === undefined) return;
1103
+ model = model.trim() || defaultModel;
1104
+ }
1105
+ let customUrl: string | undefined;
1106
+ let customKey: string | undefined;
1107
+ if (normalized === "custom") {
1108
+ customUrl = await this.ctx.showHookInput("Custom image endpoint base URL");
1109
+ if (!customUrl?.trim()) {
1110
+ this.ctx.showStatus("Custom image endpoint requires a base URL");
1111
+ return;
1112
+ }
1113
+ model = await this.ctx.showHookInput("Custom image model", IMAGE_PROVIDER_DEFAULTS.openai);
1114
+ if (model === undefined) return;
1115
+ model = model.trim() || IMAGE_PROVIDER_DEFAULTS.openai;
1116
+ customKey = await this.ctx.showHookInput("Custom image endpoint API key");
1117
+ }
1118
+ const scope = await this.ctx.showHookInput(
1119
+ "Scope: 'session' (this session only) or 'default' (persist)",
1120
+ "session",
1121
+ );
1122
+ if (scope === undefined) return;
1123
+ const persistDefault = scope.trim().toLowerCase() === "default";
1124
+
1125
+ const imageProvider = normalized as
1126
+ | "auto"
1127
+ | "openai"
1128
+ | "gemini"
1129
+ | "openrouter"
1130
+ | "antigravity"
1131
+ | "alibaba"
1132
+ | "custom";
1133
+ setPreferredImageProvider(imageProvider === "custom" ? "auto" : imageProvider);
1134
+ setConfiguredImageModel({
1135
+ provider: imageProvider,
1136
+ model: model ?? null,
1137
+ customUrl: customUrl?.trim(),
1138
+ customKey: customKey?.trim(),
1139
+ });
1140
+
1141
+ if (persistDefault) {
1142
+ this.ctx.settings.set("providers.image", imageProvider);
1143
+ if (model) this.ctx.settings.set("providers.imageModel", model);
1144
+ if (customUrl?.trim()) this.ctx.settings.set("providers.imageCustomUrl", customUrl.trim());
1145
+ if (customKey?.trim()) this.ctx.settings.set("providers.imageCustomKey", customKey.trim());
1146
+ }
1147
+
1148
+ const displayModel =
1149
+ model ?? (normalized !== "auto" && normalized !== "custom" ? IMAGE_PROVIDER_DEFAULTS[normalized] : undefined);
1150
+ const label = normalized === "auto" ? "Auto" : `${normalized}${displayModel ? ` (${displayModel})` : ""}`;
1151
+ this.ctx.showStatus(`Image Generation: ${label}${persistDefault ? " (default)" : " (session)"}`);
1152
+ this.ctx.ui.requestRender();
1153
+ }
1154
+
1106
1155
  showCustomProviderWizard(): void {
1107
1156
  this.showSelector(done => {
1108
1157
  let wizard: CustomProviderWizardComponent;
@@ -1516,6 +1565,7 @@ export class SelectorController {
1516
1565
  case "statusLine.separator":
1517
1566
  case "statusLineShowHooks":
1518
1567
  case "statusLine.showHookStatus":
1568
+ case "statusLine.showActionHints":
1519
1569
  case "statusLine.sessionAccent":
1520
1570
  case "statusLine.maxRows":
1521
1571
  case "statusLine.leftSegments":
@@ -1563,16 +1613,35 @@ export class SelectorController {
1563
1613
  }
1564
1614
  break;
1565
1615
  case "providers.image":
1616
+ case "providers.imageModel":
1617
+ case "providers.imageCustomUrl":
1618
+ case "providers.imageCustomKey":
1619
+ case "providers.imageCustomKeyEnv": {
1620
+ const imgProvider = this.ctx.settings.get("providers.image");
1621
+ const imgModel = this.ctx.settings.get("providers.imageModel");
1622
+ const imgCustomUrl = this.ctx.settings.get("providers.imageCustomUrl");
1623
+ const imgCustomKey = this.ctx.settings.get("providers.imageCustomKey");
1624
+ const imgCustomKeyEnv = this.ctx.settings.get("providers.imageCustomKeyEnv");
1566
1625
  if (
1567
- value === "auto" ||
1568
- value === "openai" ||
1569
- value === "gemini" ||
1570
- value === "openrouter" ||
1571
- value === "antigravity"
1626
+ imgProvider === "auto" ||
1627
+ imgProvider === "openai" ||
1628
+ imgProvider === "gemini" ||
1629
+ imgProvider === "openrouter" ||
1630
+ imgProvider === "antigravity" ||
1631
+ imgProvider === "alibaba" ||
1632
+ imgProvider === "custom"
1572
1633
  ) {
1573
- setPreferredImageProvider(value);
1634
+ setPreferredImageProvider(imgProvider === "custom" ? "auto" : imgProvider);
1635
+ setConfiguredImageModel({
1636
+ provider: imgProvider,
1637
+ model: imgModel ?? null,
1638
+ customUrl: imgCustomUrl,
1639
+ customKey: imgCustomKey,
1640
+ customKeyEnv: imgCustomKeyEnv,
1641
+ });
1574
1642
  }
1575
1643
  break;
1644
+ }
1576
1645
 
1577
1646
  // MCP update injection - live subscribe/unsubscribe
1578
1647
  case "mcp.notifications":
@@ -1610,6 +1679,14 @@ export class SelectorController {
1610
1679
  showModelSelector(options?: { temporaryOnly?: boolean }): void {
1611
1680
  this.showSelector(done => {
1612
1681
  let modelSelector: ModelSelectorComponent;
1682
+ const refreshRoleAssignments = () => {
1683
+ modelSelector.refreshRoleAssignments({
1684
+ currentModel: this.ctx.session.model,
1685
+ currentThinkingLevel: this.ctx.session.thinkingLevel,
1686
+ activeModelProfile:
1687
+ this.ctx.session.getActiveModelProfile?.() ?? this.ctx.settings.get("modelProfile.default"),
1688
+ });
1689
+ };
1613
1690
  modelSelector = new ModelSelectorComponent(
1614
1691
  this.ctx.ui,
1615
1692
  this.ctx.session.model,
@@ -1617,6 +1694,8 @@ export class SelectorController {
1617
1694
  this.ctx.session.modelRegistry,
1618
1695
  this.ctx.session.scopedModels,
1619
1696
  async selection => {
1697
+ const isTrackedSingleAssignment =
1698
+ selection.kind === "assignment" && selection.role !== null && selection.roles === undefined;
1620
1699
  try {
1621
1700
  if (selection.kind === "createProfile") {
1622
1701
  done();
@@ -1631,6 +1710,11 @@ export class SelectorController {
1631
1710
  await this.#deleteCustomModelPreset(selection.profileName, modelSelector);
1632
1711
  return;
1633
1712
  }
1713
+ if (selection.kind === "imageGeneration") {
1714
+ done();
1715
+ await this.#handleImageGenerationConfig();
1716
+ return;
1717
+ }
1634
1718
  if (selection.kind === "profile") {
1635
1719
  await this.#applyModelProfile(selection.profileName, selection.setDefault);
1636
1720
  done();
@@ -1652,7 +1736,7 @@ export class SelectorController {
1652
1736
  this.ctx.showStatus(`Temporary model: ${selectedSelector ?? model.id}`);
1653
1737
  done();
1654
1738
  this.ctx.ui.requestRender();
1655
- } else if (selection.roles) {
1739
+ } else if (selection.roles !== undefined) {
1656
1740
  const targetRoles: readonly GjcModelAssignmentTargetId[] = selection.roles;
1657
1741
  const includesDefault = targetRoles.includes("default");
1658
1742
  const includesRoleAgent = targetRoles.some(targetRole => targetRole !== "default");
@@ -1730,25 +1814,23 @@ export class SelectorController {
1730
1814
  selectedSelector ?? `${model.provider}/${model.id}`,
1731
1815
  thinkingLevel,
1732
1816
  );
1733
- materializeActiveModelProfileAssignment({
1734
- session: this.ctx.session,
1735
- settings: this.ctx.settings,
1736
- role,
1737
- selector: value,
1738
- });
1817
+ if (
1818
+ !materializeActiveModelProfileAssignment({
1819
+ session: this.ctx.session,
1820
+ settings: this.ctx.settings,
1821
+ role,
1822
+ selector: value,
1823
+ })
1824
+ ) {
1825
+ this.ctx.settings.setModelRole(role, value);
1826
+ }
1739
1827
  if (thinkingLevel && thinkingLevel !== ThinkingLevel.Inherit) {
1740
1828
  this.ctx.session.setThinkingLevel(thinkingLevel);
1741
1829
  }
1742
- modelSelector.refreshRoleAssignments({
1743
- currentModel: this.ctx.session.model,
1744
- currentThinkingLevel: this.ctx.session.thinkingLevel,
1745
- activeModelProfile:
1746
- this.ctx.session.getActiveModelProfile?.() ?? this.ctx.settings.get("modelProfile.default"),
1747
- });
1830
+ refreshRoleAssignments();
1748
1831
  this.ctx.statusLine.invalidate();
1749
1832
  this.ctx.updateEditorBorderColor();
1750
1833
  this.ctx.showStatus(`Default model: ${selectedSelector ?? model.id}`);
1751
- done();
1752
1834
  this.ctx.ui.requestRender();
1753
1835
  } else {
1754
1836
  const apiKey = await this.ctx.session.modelRegistry.getApiKey(model, this.ctx.session.sessionId);
@@ -1771,22 +1853,21 @@ export class SelectorController {
1771
1853
  this.ctx.settings.setAgentModelOverride(role, value);
1772
1854
  }
1773
1855
  }
1774
- modelSelector.refreshRoleAssignments({
1775
- currentModel: this.ctx.session.model,
1776
- currentThinkingLevel: this.ctx.session.thinkingLevel,
1777
- activeModelProfile:
1778
- this.ctx.session.getActiveModelProfile?.() ?? this.ctx.settings.get("modelProfile.default"),
1779
- });
1856
+ refreshRoleAssignments();
1780
1857
  this.ctx.settings.getStorage()?.recordModelUsage(`${model.provider}/${model.id}`);
1781
1858
  this.ctx.statusLine.invalidate();
1782
1859
  this.ctx.updateEditorBorderColor();
1783
1860
  await this.ctx.notifyConfigChanged?.();
1784
1861
  this.ctx.showStatus(`${role} agent model: ${value}`);
1785
- done();
1786
1862
  this.ctx.ui.requestRender();
1787
1863
  }
1788
1864
  } catch (error) {
1789
1865
  this.ctx.showError(error instanceof Error ? error.message : String(error));
1866
+ if (isTrackedSingleAssignment) {
1867
+ refreshRoleAssignments();
1868
+ this.ctx.ui.requestRender();
1869
+ throw error;
1870
+ }
1790
1871
  }
1791
1872
  },
1792
1873
  () => {
@@ -2057,10 +2138,7 @@ export class SelectorController {
2057
2138
  }
2058
2139
 
2059
2140
  async showSessionSelector(): Promise<void> {
2060
- const sessions = await SessionManager.listForResumePickerReadOnly(
2061
- this.ctx.sessionManager.getCwd(),
2062
- this.ctx.sessionManager.getSessionDir(),
2063
- );
2141
+ const sessions = await this.ctx.sessionManager.listForResumePickerReadOnly();
2064
2142
  this.showSelector(done => {
2065
2143
  const selector = new SessionSelectorComponent(
2066
2144
  sessions,
@@ -2156,8 +2234,16 @@ export class SelectorController {
2156
2234
  this.#clearTransientSessionUi();
2157
2235
  const migrationPolicy =
2158
2236
  this.ctx.settings?.get("session.directoryMigration") === "disabled" ? "disabled" : "copy-retain";
2159
- const writableSessionPath = await SessionManager.prepareManagedCandidateForWrite(sessionPath, migrationPolicy);
2160
-
2237
+ let writableSessionPath = sessionPath;
2238
+ if (this.ctx.sessionManager.isManagedDestination()) {
2239
+ const inspection = await SessionManager.inspectSessionTailReadOnly(sessionPath);
2240
+ if (inspection.kind === "error") throw new Error(`Could not inspect selected session: ${inspection.reason}`);
2241
+ writableSessionPath = await this.ctx.sessionManager.prepareManagedCandidateForStrictAdoption(
2242
+ sessionPath,
2243
+ migrationPolicy,
2244
+ inspection.identity,
2245
+ );
2246
+ }
2161
2247
  // Switch session via AgentSession (emits hook and tool session events)
2162
2248
  if (!(await this.ctx.session.switchSession(writableSessionPath))) return;
2163
2249
  const switchingToDifferentSession = previousSessionId !== this.ctx.sessionManager.getSessionId();