@gajae-code/coding-agent 0.18.12 → 0.19.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 (305) hide show
  1. package/CHANGELOG.md +467 -0
  2. package/dist/types/capability/rule.d.ts +7 -2
  3. package/dist/types/cli/append-system-prompt.d.ts +6 -0
  4. package/dist/types/cli/args.d.ts +7 -2
  5. package/dist/types/cli/customize-doctor-precedence.d.ts +16 -0
  6. package/dist/types/cli/customize-doctor.d.ts +4 -3
  7. package/dist/types/cli/public-command-errors.d.ts +1 -0
  8. package/dist/types/cli/root-flags.d.ts +0 -12
  9. package/dist/types/cli/update-cli.d.ts +7 -2
  10. package/dist/types/cli-main.d.ts +0 -6
  11. package/dist/types/commands/launch.d.ts +0 -6
  12. package/dist/types/commands/migrate.d.ts +1 -1
  13. package/dist/types/commands/sdk.d.ts +8 -0
  14. package/dist/types/commit/map-reduce/index.d.ts +4 -4
  15. package/dist/types/config/autorouting-tier-map.d.ts +23 -1
  16. package/dist/types/config/config-hot-reload.d.ts +5 -0
  17. package/dist/types/config/file-lock.d.ts +44 -2
  18. package/dist/types/config/model-profile-activation.d.ts +19 -5
  19. package/dist/types/config/model-registry.d.ts +16 -3
  20. package/dist/types/config/model-resolver.d.ts +5 -4
  21. package/dist/types/config/settings-schema.d.ts +11 -1
  22. package/dist/types/coordinator-mcp/server.d.ts +4 -0
  23. package/dist/types/customization/import.d.ts +2 -0
  24. package/dist/types/customization/types.d.ts +1 -1
  25. package/dist/types/dap/session.d.ts +11 -2
  26. package/dist/types/discovery/helpers.d.ts +7 -2
  27. package/dist/types/edit/index.d.ts +5 -0
  28. package/dist/types/edit/modes/patch.d.ts +9 -0
  29. package/dist/types/eval/py/runtime.d.ts +9 -0
  30. package/dist/types/export/ttsr.d.ts +19 -0
  31. package/dist/types/extensibility/extensions/types.d.ts +5 -1
  32. package/dist/types/extensibility/slash-commands.d.ts +1 -0
  33. package/dist/types/gjc-runtime/ralplan-current-final.d.ts +54 -0
  34. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +5 -0
  35. package/dist/types/gjc-runtime/tmux-sessions.d.ts +24 -0
  36. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +6 -0
  37. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -0
  38. package/dist/types/harness-control-plane/receipt-spool.d.ts +4 -0
  39. package/dist/types/hindsight/content.d.ts +5 -6
  40. package/dist/types/internal-urls/types.d.ts +2 -0
  41. package/dist/types/lsp/clients/swiftlint-client.d.ts +0 -4
  42. package/dist/types/main.d.ts +23 -18
  43. package/dist/types/migrate/action-planner.d.ts +10 -1
  44. package/dist/types/migrate/adapters/codex.d.ts +1 -1
  45. package/dist/types/migrate/adapters/index.d.ts +35 -3
  46. package/dist/types/migrate/hook-module.d.ts +23 -0
  47. package/dist/types/migrate/report.d.ts +2 -1
  48. package/dist/types/migrate/types.d.ts +69 -2
  49. package/dist/types/modes/components/agent-dashboard.d.ts +1 -1
  50. package/dist/types/modes/components/model-selector.d.ts +43 -0
  51. package/dist/types/modes/prompt-suggestion-controller.d.ts +1 -1
  52. package/dist/types/modes/utils/host-status.d.ts +15 -11
  53. package/dist/types/runtime-mcp/project-host-values.d.ts +11 -5
  54. package/dist/types/sdk/broker/broker-exit.d.ts +1 -1
  55. package/dist/types/sdk/broker/broker.d.ts +22 -1
  56. package/dist/types/sdk/broker/ensure.d.ts +5 -2
  57. package/dist/types/sdk/broker/hop.d.ts +9 -0
  58. package/dist/types/sdk/broker/lifecycle.d.ts +22 -0
  59. package/dist/types/sdk/broker/session-index.d.ts +2 -0
  60. package/dist/types/sdk/broker/transport.d.ts +2 -0
  61. package/dist/types/sdk/bus/index.d.ts +21 -0
  62. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +7 -1
  63. package/dist/types/sdk/bus/telegram-daemon.d.ts +2 -0
  64. package/dist/types/sdk/bus/threaded-render.d.ts +2 -1
  65. package/dist/types/sdk/host/control/operations.d.ts +5 -1
  66. package/dist/types/sdk/host/events.d.ts +4 -0
  67. package/dist/types/sdk/host/host.d.ts +2 -1
  68. package/dist/types/sdk/host/prompt-image-upload.d.ts +43 -0
  69. package/dist/types/sdk/host/session-runtime.d.ts +8 -8
  70. package/dist/types/sdk/host/surface-policy.d.ts +2 -1
  71. package/dist/types/sdk/lifecycle/service.d.ts +12 -0
  72. package/dist/types/sdk/prompt-status.d.ts +11 -4
  73. package/dist/types/session/agent-session.d.ts +50 -8
  74. package/dist/types/session/session-manager.d.ts +16 -0
  75. package/dist/types/skill-state/workflow-mutation-guard.d.ts +13 -1
  76. package/dist/types/slash-commands/helpers/mcp.d.ts +5 -0
  77. package/dist/types/ssh/ssh-executor.d.ts +8 -1
  78. package/dist/types/system-prompt.d.ts +3 -2
  79. package/dist/types/task/role-body.d.ts +11 -0
  80. package/dist/types/tools/awaiting-input.d.ts +28 -0
  81. package/dist/types/tools/bash-allowed-prefixes.d.ts +6 -0
  82. package/dist/types/tools/bash-skill-urls.d.ts +4 -0
  83. package/dist/types/tools/bash.d.ts +7 -0
  84. package/dist/types/tools/descriptors.d.ts +13 -2
  85. package/dist/types/tools/fetch.d.ts +6 -0
  86. package/dist/types/tools/irc.d.ts +5 -3
  87. package/dist/types/tools/tmux-self-injection-guard.d.ts +2 -0
  88. package/dist/types/tools/ultragoal-ask-guard.d.ts +1 -1
  89. package/dist/types/tools/write.d.ts +2 -0
  90. package/dist/types/utils/file-mentions.d.ts +5 -0
  91. package/dist/types/utils/image-limits.d.ts +7 -0
  92. package/dist/types/utils/image-loading.d.ts +0 -1
  93. package/dist/types/utils/open.d.ts +0 -1
  94. package/dist/types/utils/pasted-image-loading.d.ts +1 -5
  95. package/dist/types/utils/prompt-suggestion.d.ts +2 -2
  96. package/dist/types/utils/shell-snapshot.d.ts +2 -0
  97. package/dist/types/utils/title-generator.d.ts +13 -3
  98. package/dist/types/web/search/providers/codex.d.ts +6 -7
  99. package/package.json +9 -8
  100. package/scripts/acp-conformance-agent.ts +12 -2
  101. package/scripts/generate-sdk-operation-inventory.ts +16 -0
  102. package/src/capability/fs.ts +31 -3
  103. package/src/capability/rule.ts +11 -2
  104. package/src/cli/append-system-prompt.ts +12 -0
  105. package/src/cli/args.ts +11 -7
  106. package/src/cli/customize-doctor-precedence.ts +31 -0
  107. package/src/cli/customize-doctor.ts +49 -39
  108. package/src/cli/fast-help.ts +0 -2
  109. package/src/cli/migrate-cli.ts +14 -3
  110. package/src/cli/public-command-errors.ts +2 -0
  111. package/src/cli/root-flags.ts +0 -2
  112. package/src/cli/update-cli.ts +247 -6
  113. package/src/commands/harness.ts +25 -15
  114. package/src/commands/migrate.ts +3 -3
  115. package/src/commands/sdk.ts +81 -11
  116. package/src/commit/agentic/agent.ts +6 -6
  117. package/src/commit/analysis/conventional.ts +14 -5
  118. package/src/commit/map-reduce/index.ts +7 -7
  119. package/src/commit/pipeline.ts +12 -12
  120. package/src/config/autorouting-tier-map.ts +20 -1
  121. package/src/config/config-file.ts +106 -3
  122. package/src/config/config-hot-reload.ts +8 -0
  123. package/src/config/file-lock.ts +1431 -304
  124. package/src/config/keybindings.ts +154 -7
  125. package/src/config/model-profile-activation.ts +177 -39
  126. package/src/config/model-registry.ts +380 -55
  127. package/src/config/model-resolver.ts +49 -21
  128. package/src/config/settings-schema.ts +13 -1
  129. package/src/config/settings.ts +2 -0
  130. package/src/coordinator-mcp/server.ts +37 -2
  131. package/src/crash/sanitize.ts +3 -1
  132. package/src/customization/import.ts +276 -7
  133. package/src/customization/inventory.ts +24 -8
  134. package/src/customization/types.ts +1 -0
  135. package/src/dap/client.ts +3 -2
  136. package/src/dap/session.ts +161 -36
  137. package/src/debug/crash-diagnostics.ts +87 -5
  138. package/src/debug/report-bundle.ts +71 -2
  139. package/src/discovery/builtin.ts +4 -3
  140. package/src/discovery/helpers.ts +24 -14
  141. package/src/edit/index.ts +191 -5
  142. package/src/edit/modes/patch.ts +27 -0
  143. package/src/eval/py/runtime.ts +44 -4
  144. package/src/exec/non-interactive-env.ts +0 -1
  145. package/src/export/html/index.ts +60 -20
  146. package/src/export/ttsr.ts +176 -1
  147. package/src/extensibility/extensions/loader.ts +2 -0
  148. package/src/extensibility/extensions/ouroboros-ooo-bridge.ts +63 -1
  149. package/src/extensibility/extensions/types.ts +5 -1
  150. package/src/extensibility/hooks/loader.ts +10 -2
  151. package/src/extensibility/plugins/marketplace/manager.ts +36 -10
  152. package/src/extensibility/slash-commands.ts +5 -0
  153. package/src/gjc-runtime/gc-runtime.ts +4 -1
  154. package/src/gjc-runtime/launch-tmux.ts +23 -13
  155. package/src/gjc-runtime/ralplan-current-final.ts +294 -0
  156. package/src/gjc-runtime/ralplan-runtime.ts +95 -1
  157. package/src/gjc-runtime/state-writer.ts +109 -5
  158. package/src/gjc-runtime/tmux-sessions.ts +93 -13
  159. package/src/gjc-runtime/ultragoal-change-set.ts +90 -11
  160. package/src/gjc-runtime/ultragoal-guard.ts +9 -1
  161. package/src/gjc-runtime/ultragoal-runtime.ts +15 -5
  162. package/src/harness-control-plane/finalize.ts +11 -0
  163. package/src/harness-control-plane/receipt-spool.ts +105 -28
  164. package/src/harness-control-plane/storage.ts +124 -6
  165. package/src/hindsight/content.ts +116 -16
  166. package/src/hindsight/mental-models.ts +22 -10
  167. package/src/hindsight/state.ts +7 -4
  168. package/src/hooks/native-skill-hook.ts +3 -1
  169. package/src/internal-urls/artifact-protocol.ts +3 -1
  170. package/src/internal-urls/docs-index.generated.ts +14 -14
  171. package/src/internal-urls/local-protocol.ts +28 -3
  172. package/src/internal-urls/types.ts +2 -0
  173. package/src/lsp/client.ts +2 -1
  174. package/src/lsp/clients/biome-client.ts +2 -0
  175. package/src/lsp/clients/swiftlint-client.ts +2 -0
  176. package/src/main.ts +330 -92
  177. package/src/memories/index.ts +25 -2
  178. package/src/migrate/action-planner.ts +517 -2
  179. package/src/migrate/adapters/claude-code.ts +141 -3
  180. package/src/migrate/adapters/codex.ts +57 -5
  181. package/src/migrate/adapters/index.ts +240 -5
  182. package/src/migrate/adapters/opencode.ts +4 -0
  183. package/src/migrate/executor.ts +141 -3
  184. package/src/migrate/hook-module.ts +193 -0
  185. package/src/migrate/report.ts +45 -1
  186. package/src/migrate/types.ts +73 -3
  187. package/src/modes/acp/acp-agent.ts +10 -1
  188. package/src/modes/components/agent-dashboard.ts +20 -4
  189. package/src/modes/components/customization/customization-dashboard.ts +7 -0
  190. package/src/modes/components/customization/import-wizard.ts +29 -3
  191. package/src/modes/components/model-selector.ts +157 -103
  192. package/src/modes/components/welcome.ts +3 -1
  193. package/src/modes/controllers/command-controller.ts +271 -16
  194. package/src/modes/controllers/event-controller.ts +1 -1
  195. package/src/modes/controllers/extension-ui-controller.ts +31 -1
  196. package/src/modes/controllers/goal-mode-controller.ts +79 -10
  197. package/src/modes/controllers/input-controller.ts +2 -0
  198. package/src/modes/controllers/plan-mode-controller.ts +49 -4
  199. package/src/modes/controllers/runtime-mcp-command-controller.ts +7 -6
  200. package/src/modes/controllers/selector-controller.ts +14 -1
  201. package/src/modes/interactive-mode.ts +55 -4
  202. package/src/modes/prompt-suggestion-controller.ts +1 -1
  203. package/src/modes/runtime-init.ts +5 -1
  204. package/src/modes/utils/host-status.ts +21 -10
  205. package/src/prompts/agent-fragments/browser-aside.md +2 -2
  206. package/src/prompts/system/irc-incoming.md +3 -1
  207. package/src/prompts/tools/irc.md +1 -1
  208. package/src/runtime-mcp/manager.ts +8 -3
  209. package/src/runtime-mcp/project-host-values.ts +12 -8
  210. package/src/runtime-mcp/tool-bridge.ts +68 -4
  211. package/src/runtime-mcp/transports/stdio.ts +2 -1
  212. package/src/sdk/broker/broker-exit.ts +2 -0
  213. package/src/sdk/broker/broker.ts +143 -4
  214. package/src/sdk/broker/ensure.ts +74 -6
  215. package/src/sdk/broker/hop.ts +13 -1
  216. package/src/sdk/broker/lifecycle-ledger.ts +3 -0
  217. package/src/sdk/broker/lifecycle.ts +102 -6
  218. package/src/sdk/broker/session-index.ts +29 -4
  219. package/src/sdk/broker/transport.ts +17 -1
  220. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  221. package/src/sdk/bus/index.ts +408 -14
  222. package/src/sdk/bus/session-control.ts +2 -0
  223. package/src/sdk/bus/slack-daemon.ts +105 -9
  224. package/src/sdk/bus/telegram-daemon-contract.ts +7 -1
  225. package/src/sdk/bus/telegram-daemon.ts +10 -2
  226. package/src/sdk/bus/threaded-render.ts +20 -1
  227. package/src/sdk/cli/session-cli.ts +2 -0
  228. package/src/sdk/host/control/dispatch.ts +13 -1
  229. package/src/sdk/host/control/operations.ts +10 -1
  230. package/src/sdk/host/events.ts +67 -4
  231. package/src/sdk/host/host.ts +8 -2
  232. package/src/sdk/host/prompt-image-upload.ts +438 -0
  233. package/src/sdk/host/sdk-surface-parity.test.ts +15 -0
  234. package/src/sdk/host/session-runtime.test.ts +1537 -24
  235. package/src/sdk/host/session-runtime.ts +332 -36
  236. package/src/sdk/host/surface-policy.ts +4 -0
  237. package/src/sdk/host/websocket-transport.lifecycle.test.ts +90 -2
  238. package/src/sdk/host/websocket-transport.ts +19 -7
  239. package/src/sdk/lifecycle/service.ts +14 -1
  240. package/src/sdk/prompt-status.ts +6 -4
  241. package/src/sdk/protocol/adapter-validation.ts +28 -1
  242. package/src/sdk/protocol/operation-inventory.generated.json +160 -0
  243. package/src/sdk/protocol/operation-registry.ts +33 -3
  244. package/src/sdk/session.ts +123 -36
  245. package/src/session/agent-session.ts +1614 -329
  246. package/src/session/history-storage.ts +182 -12
  247. package/src/session/internal/managed-session-scope.ts +47 -18
  248. package/src/session/internal/managed-session-storage.ts +71 -1
  249. package/src/session/session-manager.ts +90 -5
  250. package/src/session-import/redact.ts +53 -2
  251. package/src/skill-state/workflow-mutation-guard.ts +1498 -18
  252. package/src/slash-commands/builtin-registry.ts +6 -3
  253. package/src/slash-commands/helpers/mcp.ts +11 -3
  254. package/src/ssh/ssh-executor.ts +9 -2
  255. package/src/stt/recorder.ts +26 -9
  256. package/src/system-prompt.ts +61 -16
  257. package/src/task/executor.ts +12 -8
  258. package/src/task/role-body.ts +84 -0
  259. package/src/task/token-log-mode.test.ts +162 -0
  260. package/src/task/token-log.ts +91 -2
  261. package/src/tools/archive-reader.ts +650 -37
  262. package/src/tools/ask.ts +1 -0
  263. package/src/tools/ast-grep.ts +7 -2
  264. package/src/tools/auto-generated-guard.ts +12 -3
  265. package/src/tools/awaiting-input.ts +114 -0
  266. package/src/tools/bash-allowed-prefixes.ts +61 -1
  267. package/src/tools/bash-skill-urls.ts +58 -5
  268. package/src/tools/bash.ts +99 -26
  269. package/src/tools/browser/tab-worker.ts +39 -13
  270. package/src/tools/browser.ts +17 -7
  271. package/src/tools/cron.ts +10 -1
  272. package/src/tools/debug.ts +9 -2
  273. package/src/tools/descriptors.test.ts +4 -4
  274. package/src/tools/descriptors.ts +16 -4
  275. package/src/tools/eval.ts +15 -0
  276. package/src/tools/fetch.ts +90 -2
  277. package/src/tools/gh.ts +27 -12
  278. package/src/tools/image-gen.ts +20 -6
  279. package/src/tools/irc.ts +5 -3
  280. package/src/tools/monitor.ts +9 -1
  281. package/src/tools/output-meta.ts +51 -3
  282. package/src/tools/read.ts +9 -2
  283. package/src/tools/recipe/runner.ts +23 -12
  284. package/src/tools/render-utils.ts +2 -2
  285. package/src/tools/search.ts +3 -2
  286. package/src/tools/ssh.ts +274 -8
  287. package/src/tools/subagent.ts +10 -4
  288. package/src/tools/tmux-self-injection-guard.ts +3301 -122
  289. package/src/tools/tool-catalog.generated.ts +1 -1
  290. package/src/tools/ultragoal-ask-guard.ts +13 -1
  291. package/src/tools/write.ts +7 -0
  292. package/src/tui/tree-list.ts +15 -1
  293. package/src/utils/commit-message-generator.ts +3 -2
  294. package/src/utils/external-editor.ts +21 -7
  295. package/src/utils/file-mentions.ts +46 -6
  296. package/src/utils/git.ts +38 -7
  297. package/src/utils/image-limits.ts +8 -0
  298. package/src/utils/image-loading.ts +1 -1
  299. package/src/utils/open.ts +73 -4
  300. package/src/utils/pasted-image-loading.ts +9 -8
  301. package/src/utils/prompt-suggestion.ts +5 -4
  302. package/src/utils/shell-snapshot.ts +9 -1
  303. package/src/utils/title-generator.ts +65 -20
  304. package/src/web/search/index.ts +2 -1
  305. package/src/web/search/providers/codex.ts +29 -32
@@ -5,7 +5,7 @@ import { CompactionCancelledError, type CompactionOutcome } from "@gajae-code/ag
5
5
  import { getEnvApiKey, type ToolCall, type UsageLimit, type UsageReport } from "@gajae-code/ai/core";
6
6
  import type { ProviderDetails } from "@gajae-code/ai/provider-details";
7
7
  import { type Keybinding, Loader, Markdown, padding, Spacer, Text, visibleWidth } from "@gajae-code/tui";
8
- import { formatBytes, formatDuration, Snowflake, setProjectDir } from "@gajae-code/utils";
8
+ import { formatBytes, formatDuration, setProjectDir } from "@gajae-code/utils";
9
9
  import { canonicalEnvKey, projectEnvSnapshot } from "@gajae-code/utils/env-file";
10
10
  import { resolveAppendOnlyMode } from "../../append-only-mode";
11
11
  import { jobElapsedMs } from "../../async";
@@ -48,6 +48,7 @@ import {
48
48
  buildConversationTitleInput,
49
49
  generateSessionTitle,
50
50
  setSessionTerminalTitle,
51
+ titleInputObfuscator,
51
52
  } from "../../utils/title-generator";
52
53
  import { addChatChild, prepareTranscriptRebuild, syncPendingExecutionComponents } from "../utils/ui-helpers";
53
54
 
@@ -108,7 +109,117 @@ export function buildHelpMarkdown(keybindings: Pick<KeybindingsManager, "getAcce
108
109
  ].join("\n");
109
110
  }
110
111
 
111
- const GH_SHARE_DESTINATION_KEYS = ["GH_HOST", "GH_CONFIG_DIR"] as const;
112
+ const GH_SHARE_TRUST_KEYS = [
113
+ "GH_HOST",
114
+ "GH_CONFIG_DIR",
115
+ "GH_TOKEN",
116
+ "GITHUB_TOKEN",
117
+ "GH_ENTERPRISE_TOKEN",
118
+ "GITHUB_ENTERPRISE_TOKEN",
119
+ "XDG_CONFIG_HOME",
120
+ "HOME",
121
+ "USERPROFILE",
122
+ "APPDATA",
123
+ "BASH_ENV",
124
+ "BUN_OPTIONS",
125
+ "ENV",
126
+ "ZDOTDIR",
127
+ "NODE_OPTIONS",
128
+ "NODE_PATH",
129
+ "PYTHONHOME",
130
+ "PYTHONPATH",
131
+ "PYTHONUSERBASE",
132
+ "PYTHONSTARTUP",
133
+ "JULIA_DEPOT_PATH",
134
+ "JULIA_LOAD_PATH",
135
+ "JULIA_PROJECT",
136
+ "TCL_LIBRARY",
137
+ "TCLLIBPATH",
138
+ "LUA_INIT",
139
+ "LUA_INIT_5_1",
140
+ "LUA_INIT_5_2",
141
+ "LUA_INIT_5_3",
142
+ "LUA_INIT_5_4",
143
+ "LUA_INIT_5_5",
144
+ "LUA_PATH",
145
+ "LUA_CPATH",
146
+ "LUA_PATH_5_1",
147
+ "LUA_PATH_5_2",
148
+ "LUA_PATH_5_3",
149
+ "LUA_PATH_5_4",
150
+ "LUA_PATH_5_5",
151
+ "LUA_CPATH_5_1",
152
+ "LUA_CPATH_5_2",
153
+ "LUA_CPATH_5_3",
154
+ "LUA_CPATH_5_4",
155
+ "LUA_CPATH_5_5",
156
+ "R_ENVIRON",
157
+ "R_ENVIRON_USER",
158
+ "R_PROFILE",
159
+ "R_PROFILE_USER",
160
+ "ELIXIR_ERL_OPTIONS",
161
+ "ERL_AFLAGS",
162
+ "ERL_FLAGS",
163
+ "ERL_ZFLAGS",
164
+ "BUNDLER_SETUP",
165
+ "BUNDLE_GEMFILE",
166
+ "GEM_HOME",
167
+ "GEM_PATH",
168
+ "RUBYGEMS_GEMDEPS",
169
+ "RUBYOPT",
170
+ "RUBYLIB",
171
+ "PERL5OPT",
172
+ "PERL5LIB",
173
+ "PERLLIB",
174
+ "JAVA_HOME",
175
+ "JAVA_TOOL_OPTIONS",
176
+ "JDK_JAVA_OPTIONS",
177
+ "_JAVA_OPTIONS",
178
+ "CLASSPATH",
179
+ "PHPRC",
180
+ "PHP_INI_SCAN_DIR",
181
+ "DOTNET_STARTUP_HOOKS",
182
+ "DOTNET_ROOT",
183
+ "DOTNET_ROOT_X86",
184
+ "DOTNET_ROOT_X64",
185
+ "DOTNET_ROOT_ARM",
186
+ "DOTNET_ROOT_ARM64",
187
+ "CORECLR_ENABLE_PROFILING",
188
+ "DOTNET_ENABLE_PROFILING",
189
+ "CORECLR_PROFILER",
190
+ "DOTNET_PROFILER",
191
+ "CORECLR_PROFILER_PATH",
192
+ "CORECLR_PROFILER_PATH_32",
193
+ "CORECLR_PROFILER_PATH_64",
194
+ "CORECLR_PROFILER_PATH_ARM32",
195
+ "CORECLR_PROFILER_PATH_ARM64",
196
+ "DOTNET_PROFILER_PATH",
197
+ "DOTNET_PROFILER_PATH_32",
198
+ "DOTNET_PROFILER_PATH_64",
199
+ "DOTNET_PROFILER_PATH_ARM32",
200
+ "DOTNET_PROFILER_PATH_ARM64",
201
+ "PATH",
202
+ "HTTP_PROXY",
203
+ "HTTPS_PROXY",
204
+ "ALL_PROXY",
205
+ "NO_PROXY",
206
+ "OPENSSL_CONF",
207
+ "OPENSSL_CONF_INCLUDE",
208
+ "OPENSSL_MODULES",
209
+ "OPENSSL_ENGINES",
210
+ "SSL_CERT_FILE",
211
+ "SSL_CERT_DIR",
212
+ "LD_PRELOAD",
213
+ "LD_LIBRARY_PATH",
214
+ "LD_AUDIT",
215
+ "DYLD_INSERT_LIBRARIES",
216
+ "DYLD_LIBRARY_PATH",
217
+ "DYLD_FRAMEWORK_PATH",
218
+ "DYLD_FALLBACK_LIBRARY_PATH",
219
+ "DYLD_FALLBACK_FRAMEWORK_PATH",
220
+ "DYLD_ROOT_PATH",
221
+ ] as const;
222
+ const GH_SHARE_TRUST_KEY_SET = new Set(GH_SHARE_TRUST_KEYS.map(name => name.toUpperCase()));
112
223
 
113
224
  /**
114
225
  * Project dotenv at import, before `/move` changes cwd. Those declarations are
@@ -116,22 +227,101 @@ const GH_SHARE_DESTINATION_KEYS = ["GH_HOST", "GH_CONFIG_DIR"] as const;
116
227
  * remove them.
117
228
  */
118
229
  const ghShareProjectSnapshot = projectEnvSnapshot();
230
+ const ghShareOperatorPath = process.env.PATH;
231
+ const ghShareLaunchRoot = path.resolve(process.cwd());
232
+
233
+ function isInsideDirectory(root: string, candidate: string): boolean {
234
+ const relative = path.relative(root, candidate);
235
+ return relative === "" || (relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
236
+ }
237
+
238
+ async function findCheckoutRoots(directory: string): Promise<string[] | undefined> {
239
+ const resolvedDirectory = await fs.realpath(directory).catch(() => undefined);
240
+ if (!resolvedDirectory) return undefined;
241
+ const roots: string[] = [];
242
+ let ancestor = resolvedDirectory;
243
+ while (true) {
244
+ try {
245
+ await fs.lstat(path.join(ancestor, ".git"));
246
+ roots.push(ancestor);
247
+ } catch (error) {
248
+ if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") return undefined;
249
+ }
250
+ const parent = path.dirname(ancestor);
251
+ if (parent === ancestor) break;
252
+ ancestor = parent;
253
+ }
254
+ return roots;
255
+ }
256
+
257
+ function isInsideAnyDirectory(roots: readonly string[], candidate: string): boolean {
258
+ return roots.some(root => isInsideDirectory(root, candidate));
259
+ }
260
+
261
+ async function resolveGhShareExecutable(): Promise<{ executable: string; searchPath: string } | undefined> {
262
+ const operatorPath = ghShareOperatorPath;
263
+ const key = canonicalEnvKey("PATH");
264
+ const declared = ghShareProjectSnapshot.values[key];
265
+ if (
266
+ !operatorPath ||
267
+ (declared !== undefined && (ghShareProjectSnapshot.dynamic.has(key) || declared.trim() === operatorPath.trim()))
268
+ )
269
+ return undefined;
270
+ const checkoutRoots = new Set<string>();
271
+ for (const directory of new Set([ghShareLaunchRoot, path.resolve(process.cwd())])) {
272
+ const roots = await findCheckoutRoots(directory);
273
+ if (!roots) return undefined;
274
+ for (const root of roots) checkoutRoots.add(root);
275
+ }
276
+ const untrustedDirectories = [...checkoutRoots];
277
+ const trustedDirectories: string[] = [];
278
+ const seenDirectories = new Set<string>();
279
+ let executable: string | undefined;
280
+ for (const directory of operatorPath.split(path.delimiter)) {
281
+ if (!path.isAbsolute(directory)) continue;
282
+ const absoluteDirectory = path.resolve(directory);
283
+ if (isInsideAnyDirectory(untrustedDirectories, absoluteDirectory)) continue;
284
+ const realDirectory = await fs.realpath(absoluteDirectory).catch(() => undefined);
285
+ if (
286
+ !realDirectory ||
287
+ isInsideAnyDirectory(untrustedDirectories, realDirectory) ||
288
+ seenDirectories.has(realDirectory)
289
+ )
290
+ continue;
291
+ const candidate = Bun.which("gh", { PATH: realDirectory });
292
+ if (candidate) {
293
+ const absoluteCandidate = path.resolve(candidate);
294
+ if (isInsideAnyDirectory(untrustedDirectories, absoluteCandidate)) continue;
295
+ const realExecutable = await fs.realpath(absoluteCandidate).catch(() => undefined);
296
+ if (!realExecutable || isInsideAnyDirectory(untrustedDirectories, realExecutable)) continue;
297
+ executable ??= realExecutable;
298
+ }
299
+ seenDirectories.add(realDirectory);
300
+ trustedDirectories.push(realDirectory);
301
+ }
302
+ return executable ? { executable, searchPath: trustedDirectories.join(path.delimiter) } : undefined;
303
+ }
119
304
 
120
305
  /**
121
- * Environment for the default `/share` `gh` children. A `GH_HOST` or
122
- * `GH_CONFIG_DIR` that matches the project dotenv is omitted so the repository
123
- * cannot retarget gist upload or point `gh` at a config directory it shipped.
124
- * An operator value the project does not declare is kept.
306
+ * Environment for the default `/share` `gh` children. GitHub CLI routing,
307
+ * config, authentication, proxy, certificate, shell/runtime startup, executable-search,
308
+ * and dynamic loader overrides that match the project dotenv are omitted so a repository
309
+ * cannot redirect the upload, substitute the `gh` binary, authenticate with a
310
+ * project-provided token, or intercept its HTTPS request. The executable itself
311
+ * and its child search path use absolute operator PATH directories whose real
312
+ * paths are outside actual launch and active checkout roots. Operator values the project
313
+ * does not declare are kept.
125
314
  */
126
- function ghShareChildEnv(env: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {
315
+ function ghShareChildEnv(trustedPath: string, env: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {
127
316
  const child: NodeJS.ProcessEnv = { ...env };
128
- for (const name of GH_SHARE_DESTINATION_KEYS) {
317
+ for (const name of Object.keys(child)) {
318
+ if (!GH_SHARE_TRUST_KEY_SET.has(name.toUpperCase())) continue;
129
319
  const raw = child[name];
130
320
  if (typeof raw !== "string") continue;
131
321
  const key = canonicalEnvKey(name);
132
322
  const declared = ghShareProjectSnapshot.values[key];
133
323
  if (declared === undefined) continue;
134
- if (!(ghShareProjectSnapshot.dynamic.has(key) || declared === raw || declared === raw.trim())) continue;
324
+ if (!(ghShareProjectSnapshot.dynamic.has(key) || declared.trim() === raw.trim())) continue;
135
325
  delete child[name];
136
326
  if (process.platform === "win32") {
137
327
  for (const present of Object.keys(child)) {
@@ -139,6 +329,43 @@ function ghShareChildEnv(env: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEn
139
329
  }
140
330
  }
141
331
  }
332
+ const ghConfigDirKey = canonicalEnvKey("GH_CONFIG_DIR");
333
+ const xdgConfigHomeKey = canonicalEnvKey("XDG_CONFIG_HOME");
334
+ for (const name of Object.keys(child)) {
335
+ const key = canonicalEnvKey(name);
336
+ if (key !== ghConfigDirKey && key !== xdgConfigHomeKey) continue;
337
+ const value = child[name];
338
+ if (typeof value !== "string" || value.length === 0 || path.isAbsolute(value)) continue;
339
+ // `gh` children use the filesystem root as cwd to avoid project Bun
340
+ // preloads, so retain the launch-directory meaning of relative config paths.
341
+ child[name] = path.resolve(ghShareLaunchRoot, value);
342
+ }
343
+ const certFileKey = canonicalEnvKey("SSL_CERT_FILE");
344
+ const certDirKey = canonicalEnvKey("SSL_CERT_DIR");
345
+ for (const name of Object.keys(child)) {
346
+ const key = canonicalEnvKey(name);
347
+ const value = child[name];
348
+ if (typeof value !== "string" || value.length === 0) continue;
349
+ if (key === certFileKey && !path.isAbsolute(value)) {
350
+ // Certificate overrides retain their launch-directory meaning even
351
+ // though `gh` children run from the filesystem root.
352
+ child[name] = path.resolve(ghShareLaunchRoot, value);
353
+ } else if (key === certDirKey) {
354
+ child[name] = value
355
+ .split(path.delimiter)
356
+ .map(directory =>
357
+ directory.length > 0 && !path.isAbsolute(directory)
358
+ ? path.resolve(ghShareLaunchRoot, directory)
359
+ : directory,
360
+ )
361
+ .join(path.delimiter);
362
+ }
363
+ }
364
+ const pathKey = canonicalEnvKey("PATH");
365
+ for (const name of Object.keys(child)) {
366
+ if (canonicalEnvKey(name) === pathKey) delete child[name];
367
+ }
368
+ child.PATH = trustedPath;
142
369
  return child;
143
370
  }
144
371
 
@@ -182,6 +409,7 @@ export class CommandController {
182
409
  }
183
410
 
184
411
  async handleDebugTranscriptCommand(): Promise<void> {
412
+ let tempDir: string | undefined;
185
413
  try {
186
414
  const width = Math.max(1, this.ctx.ui.terminal.columns);
187
415
  const renderedLines = this.ctx.chatContainer.render(width).map(line => replaceTabs(Bun.stripANSI(line)));
@@ -190,10 +418,22 @@ export class CommandController {
190
418
  this.ctx.showError("No messages to dump yet.");
191
419
  return;
192
420
  }
193
- const tmpPath = path.join(os.tmpdir(), `${Snowflake.next()}-tmp.txt`);
194
- await Bun.write(tmpPath, `${rendered}\n`);
421
+ // Random directory plus an exclusive create: a preexisting symlink under the
422
+ // shared temp directory is not opened, and the transcript is not left world-readable.
423
+ tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "gjc-debug-transcript-"));
424
+ if (process.platform !== "win32") await fs.chmod(tempDir, 0o700);
425
+ const tmpPath = path.join(tempDir, "transcript.txt");
426
+ const handle = await fs.open(tmpPath, "wx", 0o600);
427
+ try {
428
+ await handle.writeFile(`${rendered}\n`);
429
+ } finally {
430
+ await handle.close();
431
+ }
432
+ if (process.platform !== "win32") await fs.chmod(tmpPath, 0o600);
195
433
  this.ctx.showStatus(`Debug transcript written to:\n${tmpPath}`);
434
+ tempDir = undefined;
196
435
  } catch (error: unknown) {
436
+ if (tempDir) await fs.rm(tempDir, { recursive: true, force: true }).catch(() => {});
197
437
  this.ctx.showError(
198
438
  `Failed to write debug transcript: ${error instanceof Error ? error.message : "Unknown error"}`,
199
439
  );
@@ -277,9 +517,20 @@ export class CommandController {
277
517
  return;
278
518
  }
279
519
 
280
- const ghEnv = ghShareChildEnv();
520
+ const ghShare = await resolveGhShareExecutable();
521
+ if (!ghShare) {
522
+ this.ctx.showError(
523
+ "GitHub CLI (gh) is not installed on the trusted operator PATH. Install it from https://cli.github.com/",
524
+ );
525
+ return;
526
+ }
527
+ const ghShareExecutable = ghShare.executable;
528
+ // Bun wrappers load project bunfig.toml preloads from their working directory.
529
+ const ghShareCwd = path.parse(ghShareLaunchRoot).root;
530
+ const ghEnv = ghShareChildEnv(ghShare.searchPath);
281
531
  try {
282
- const authProcess = Bun.spawn(["gh", "auth", "status"], {
532
+ const authProcess = Bun.spawn([ghShareExecutable, "auth", "status"], {
533
+ cwd: ghShareCwd,
283
534
  env: ghEnv,
284
535
  stdin: "ignore",
285
536
  stdout: "ignore",
@@ -313,7 +564,8 @@ export class CommandController {
313
564
 
314
565
  let cancellationRequested = false;
315
566
  try {
316
- const gistProcess = Bun.spawn(["gh", "gist", "create", "--public=false", tmpFile], {
567
+ const gistProcess = Bun.spawn([ghShareExecutable, "gist", "create", "--public=false", tmpFile], {
568
+ cwd: ghShareCwd,
317
569
  env: ghEnv,
318
570
  stdin: "ignore",
319
571
  stdout: "pipe",
@@ -1212,7 +1464,9 @@ export class CommandController {
1212
1464
  try {
1213
1465
  if (!title?.trim()) {
1214
1466
  const generation = beginSessionTitleGeneration(this.ctx.sessionManager);
1215
- const input = buildConversationTitleInput(this.ctx.session.messages);
1467
+ const activeCwd = this.ctx.sessionManager.getCwd();
1468
+ const obfuscateText = await titleInputObfuscator(this.ctx.settings, activeCwd);
1469
+ const input = buildConversationTitleInput(this.ctx.session.messages, obfuscateText);
1216
1470
  if (!input) {
1217
1471
  this.ctx.showError("Nothing to summarize yet — pass a title: /rename <title>");
1218
1472
  return;
@@ -1225,10 +1479,11 @@ export class CommandController {
1225
1479
  this.ctx.session.credentialSessionId,
1226
1480
  this.ctx.session.model,
1227
1481
  provider => this.ctx.session.agent.metadataForProvider(provider),
1482
+ activeCwd,
1228
1483
  );
1229
1484
  if (
1230
1485
  !isSessionTitleGenerationCurrent(this.ctx.sessionManager, generation) ||
1231
- buildConversationTitleInput(this.ctx.session.messages) !== input
1486
+ buildConversationTitleInput(this.ctx.session.messages, obfuscateText) !== input
1232
1487
  )
1233
1488
  return;
1234
1489
  if (!generated) {
@@ -716,7 +716,7 @@ export class EventController {
716
716
  ): Promise<void> {
717
717
  this.ctx.showStatus(`Fallback model: ${event.from} → ${event.to}`);
718
718
  this.ctx.statusLine.invalidate();
719
- this.ctx.ui.requestRender();
719
+ this.ctx.updateEditorBorderColor();
720
720
  }
721
721
 
722
722
  async #handleNotice(event: Extract<AgentSessionEvent, { type: "notice" }>): Promise<void> {
@@ -84,6 +84,7 @@ const EXTENSION_CONTEXT_MUTATIONS: ReadonlySet<PropertyKey> = new Set([
84
84
  "clearContext",
85
85
  "cycleModel",
86
86
  "setModelProfile",
87
+ "setDefaultModelProfile",
87
88
  "cycleThinkingLevel",
88
89
  "setQueueMode",
89
90
  "invokeSkill",
@@ -93,6 +94,14 @@ const EXTENSION_CONTEXT_MUTATIONS: ReadonlySet<PropertyKey> = new Set([
93
94
  "setSdkClientBridge",
94
95
  "sdkControl",
95
96
  ]);
97
+ const MODEL_CHANGE_MUTATIONS: ReadonlySet<PropertyKey> = new Set([
98
+ "setModel",
99
+ "setModelTemporaryForControl",
100
+ "cycleModel",
101
+ "setModelProfile",
102
+ "setDefaultModelProfile",
103
+ "sdkControl",
104
+ ]);
96
105
  const EXTENSION_COMMAND_MUTATIONS: ReadonlySet<PropertyKey> = new Set([
97
106
  "reload",
98
107
  "newSession",
@@ -250,7 +259,11 @@ export class ExtensionUiController {
250
259
  const slashIndex = selector.indexOf("/");
251
260
  const model =
252
261
  slashIndex > 0
253
- ? session.modelRegistry.find(selector.slice(0, slashIndex), selector.slice(slashIndex + 1))
262
+ ? session.modelRegistry.find(
263
+ selector.slice(0, slashIndex),
264
+ selector.slice(slashIndex + 1),
265
+ session.credentialSessionId,
266
+ )
254
267
  : undefined;
255
268
  if (!model || !thinkingLevel || thinkingLevel === ThinkingLevel.Inherit)
256
269
  throw Object.assign(new Error("model.set requires a valid model id and concrete thinkingLevel."), {
@@ -442,6 +455,14 @@ export class ExtensionUiController {
442
455
  return this.ctx.isStopped?.() === true;
443
456
  }
444
457
 
458
+ async #withModelChangeRefresh<T>(changeModel: () => Promise<T>): Promise<T> {
459
+ try {
460
+ return await changeModel();
461
+ } finally {
462
+ if (!this.#isStopped()) this.ctx.updateEditorChrome();
463
+ }
464
+ }
465
+
445
466
  #assertActive(): void {
446
467
  if (this.#isStopped()) throw Object.assign(new Error("Interactive mode stopped"), { code: "cancelled" });
447
468
  }
@@ -453,6 +474,9 @@ export class ExtensionUiController {
453
474
  if (!mutationNames.has(property) || typeof member !== "function") return member;
454
475
  return (...args: unknown[]) => {
455
476
  this.#assertActive();
477
+ if (MODEL_CHANGE_MUTATIONS.has(property)) {
478
+ return this.#withModelChangeRefresh(async () => Reflect.apply(member, current, args));
479
+ }
456
480
  return Reflect.apply(member, current, args);
457
481
  };
458
482
  },
@@ -646,6 +670,7 @@ export class ExtensionUiController {
646
670
  branch: async () => prohibitBrokerLifecycleOperation("session.branch"),
647
671
  navigateTree: async (targetId, navOptions) => {
648
672
  const result = await this.ctx.session.navigateTree(targetId, { summarize: navOptions?.summarize });
673
+ if (!result.cancelled) await this.ctx.session.notifySessionIdentityChanged();
649
674
  return { cancelled: result.cancelled };
650
675
  },
651
676
  switchSession: async () => prohibitBrokerLifecycleOperation("session.switch"),
@@ -653,6 +678,8 @@ export class ExtensionUiController {
653
678
  const previousSessionId = this.ctx.sessionManager.getSessionId();
654
679
  await this.ctx.session.reload();
655
680
  if (this.#isStopped()) return;
681
+ await this.ctx.session.notifySessionIdentityChanged();
682
+ if (this.#isStopped()) return;
656
683
  const sessionIdentityChanged = previousSessionId !== this.ctx.sessionManager.getSessionId();
657
684
  if (sessionIdentityChanged) this.ctx.resetIrcSidebarSession();
658
685
  this.ctx.rebuildInitialMessages(sessionIdentityChanged ? "replace-identity" : "reconcile-same-transcript");
@@ -876,6 +903,7 @@ export class ExtensionUiController {
876
903
  branch: async () => prohibitBrokerLifecycleOperation("session.branch"),
877
904
  navigateTree: async (targetId, navOptions) => {
878
905
  const result = await this.ctx.session.navigateTree(targetId, { summarize: navOptions?.summarize });
906
+ if (!result.cancelled) await this.ctx.session.notifySessionIdentityChanged();
879
907
  return { cancelled: result.cancelled };
880
908
  },
881
909
  switchSession: async () => prohibitBrokerLifecycleOperation("session.switch"),
@@ -886,6 +914,8 @@ export class ExtensionUiController {
886
914
  const previousSessionId = this.ctx.sessionManager.getSessionId();
887
915
  await this.ctx.session.reload();
888
916
  if (this.#isStopped()) return;
917
+ await this.ctx.session.notifySessionIdentityChanged();
918
+ if (this.#isStopped()) return;
889
919
  const sessionIdentityChanged = previousSessionId !== this.ctx.sessionManager.getSessionId();
890
920
  if (sessionIdentityChanged) this.ctx.resetIrcSidebarSession();
891
921
  this.ctx.rebuildInitialMessages(sessionIdentityChanged ? "replace-identity" : "reconcile-same-transcript");
@@ -54,6 +54,7 @@ export class GoalModeController {
54
54
  #enabled = false;
55
55
  #paused = false;
56
56
  #previousTools: string[] | undefined;
57
+ #previousMCPToolNames: string[] | undefined;
57
58
  #continuationTimer: NodeJS.Timeout | undefined;
58
59
  #turnHadToolCalls = false;
59
60
  #continuationTurnInFlight = false;
@@ -264,8 +265,13 @@ export class GoalModeController {
264
265
  if (this.#enabled || this.#paused) this.ctx.modeGate.enter("goal");
265
266
  if (restored?.goal) {
266
267
  this.#previousTools = this.ctx.session.getActiveToolNames();
268
+ this.#previousMCPToolNames = this.ctx.session.getActiveMCPToolNames();
267
269
  if (!this.ctx.session.isExplicitEmptyToolSelection()) {
268
- await this.ctx.session.setActiveToolsByName([...new Set([...this.#previousTools, "goal"])]);
270
+ await this.ctx.session.setActiveToolsByName([...new Set([...this.#previousTools, "goal"])], {
271
+ explicit: false,
272
+ persistMCPSelection: false,
273
+ retryOnMcpCatalogChange: true,
274
+ });
269
275
  }
270
276
  }
271
277
  this.#updateStatus();
@@ -316,16 +322,63 @@ export class GoalModeController {
316
322
  }): Promise<void> {
317
323
  if (this.#enabled) return;
318
324
  if (!this.ctx.modeGate.enter("goal")) return this.ctx.showWarning("Exit plan mode first.");
325
+ const previousGoalState = this.ctx.session.getGoalModeState();
319
326
  this.#previousTools = this.ctx.session.getActiveToolNames();
327
+ this.#previousMCPToolNames = this.ctx.session.getActiveMCPToolNames();
320
328
  this.#paused = false;
321
- const state = options.resume
322
- ? await this.ctx.session.goalRuntime.resumeGoal()
323
- : await this.ctx.session.goalRuntime.createGoal({
324
- objective: options.objective ?? "",
325
- provenance: options.provenance,
326
- });
327
- if (!this.ctx.session.isExplicitEmptyToolSelection()) {
328
- await this.ctx.session.setActiveToolsByName([...new Set([...this.#previousTools, "goal"])]);
329
+ let state: GoalModeState;
330
+ try {
331
+ state = options.resume
332
+ ? await this.ctx.session.goalRuntime.resumeGoal()
333
+ : await this.ctx.session.goalRuntime.createGoal({
334
+ objective: options.objective ?? "",
335
+ provenance: options.provenance,
336
+ });
337
+ if (!this.ctx.session.isExplicitEmptyToolSelection()) {
338
+ await this.ctx.session.setActiveToolsByName(
339
+ [...new Set([...this.#restoreToolsWithCurrentMCPSelection(this.#previousTools), "goal"])],
340
+ {
341
+ explicit: false,
342
+ persistMCPSelection: false,
343
+ retryOnMcpCatalogChange: true,
344
+ },
345
+ );
346
+ }
347
+ } catch (error) {
348
+ const currentGoalState = this.ctx.session.getGoalModeState();
349
+ const goalWasActivated =
350
+ currentGoalState?.enabled === true &&
351
+ currentGoalState.goal.status === "active" &&
352
+ (options.resume
353
+ ? previousGoalState?.goal.status === "paused" && currentGoalState.goal.id === previousGoalState.goal.id
354
+ : currentGoalState.goal.id !== previousGoalState?.goal.id);
355
+ let rollbackError: unknown;
356
+ if (goalWasActivated) {
357
+ try {
358
+ if (options.resume) await this.ctx.session.goalRuntime.pauseGoal();
359
+ else await this.ctx.session.goalRuntime.dropGoal();
360
+ } catch (goalRollbackError) {
361
+ rollbackError = goalRollbackError;
362
+ }
363
+ }
364
+ const rollbackState = this.ctx.session.getGoalModeState();
365
+ this.#enabled = rollbackState?.enabled === true;
366
+ this.#paused = rollbackState?.enabled !== true && rollbackState?.goal.status === "paused";
367
+ this.#previousTools = undefined;
368
+ this.#previousMCPToolNames = undefined;
369
+ this.#continuationTurnInFlight = false;
370
+ this.#resetContinuationSuppression();
371
+ this.cancelContinuation();
372
+ if (this.#enabled || this.#paused) this.ctx.modeGate.enter("goal");
373
+ else this.ctx.modeGate.exit("goal");
374
+ this.#updateStatus();
375
+ if (rollbackError !== undefined) {
376
+ throw new AggregateError(
377
+ [error, rollbackError],
378
+ "Goal-mode entry failed and goal state rollback also failed",
379
+ );
380
+ }
381
+ throw error;
329
382
  }
330
383
  this.ctx.session.setGoalModeState(state);
331
384
  this.#enabled = true;
@@ -344,7 +397,12 @@ export class GoalModeController {
344
397
  this.#previousTools &&
345
398
  options?.reason !== "dropped" &&
346
399
  (this.#enabled || options?.reason === "completed" || options?.paused);
347
- if (shouldRestoreTools && this.#previousTools) await this.ctx.session.setActiveToolsByName(this.#previousTools);
400
+ if (shouldRestoreTools && this.#previousTools)
401
+ await this.ctx.session.setActiveToolsByName(this.#restoreToolsWithCurrentMCPSelection(this.#previousTools), {
402
+ explicit: false,
403
+ persistMCPSelection: false,
404
+ retryOnMcpCatalogChange: true,
405
+ });
348
406
  const currentState = this.ctx.session.getGoalModeState();
349
407
  if (options?.reason === "completed") {
350
408
  this.ctx.session.setGoalModeState(undefined);
@@ -358,6 +416,7 @@ export class GoalModeController {
358
416
  this.#enabled = false;
359
417
  this.#paused = options?.paused ?? false;
360
418
  this.#previousTools = undefined;
419
+ this.#previousMCPToolNames = undefined;
361
420
  this.#continuationTurnInFlight = false;
362
421
  this.#resetContinuationSuppression();
363
422
  this.cancelContinuation();
@@ -375,6 +434,16 @@ export class GoalModeController {
375
434
  );
376
435
  }
377
436
 
437
+ #restoreToolsWithCurrentMCPSelection(previousTools: readonly string[]): string[] {
438
+ const previousMCPToolNames = new Set(this.#previousMCPToolNames ?? []);
439
+ return [
440
+ ...new Set([
441
+ ...previousTools.filter(name => !previousMCPToolNames.has(name)),
442
+ ...this.ctx.session.getActiveMCPToolNames(),
443
+ ]),
444
+ ];
445
+ }
446
+
378
447
  #getPausedState(): GoalModeState | undefined {
379
448
  const state = this.ctx.session.getGoalModeState();
380
449
  return state?.goal && !state.enabled && state.goal.status === "paused" ? state : undefined;
@@ -1642,6 +1642,7 @@ export class InputController {
1642
1642
  // Keep locally intercepted command arguments away from the title model.
1643
1643
  if (this.ctx.session.isLocallyHandledSlashCommand(text)) return;
1644
1644
  this.#autoTitleInFlightSessionId = sessionId;
1645
+ const activeCwd = this.ctx.sessionManager.getCwd();
1645
1646
  generateSessionTitle(
1646
1647
  text,
1647
1648
  this.ctx.session.modelRegistry,
@@ -1649,6 +1650,7 @@ export class InputController {
1649
1650
  this.ctx.session.credentialSessionId,
1650
1651
  this.ctx.session.model,
1651
1652
  provider => this.ctx.session.agent.metadataForProvider(provider),
1653
+ activeCwd,
1652
1654
  )
1653
1655
  .then(async title => {
1654
1656
  if (!title || this.ctx.sessionManager.getSessionId() !== sessionId) return;