@google/gemini-cli-core 0.34.0-preview.3 → 0.35.0-nightly.20260314.3038fdce2

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 (355) hide show
  1. package/dist/docs/changelogs/index.md +19 -0
  2. package/dist/docs/changelogs/latest.md +217 -191
  3. package/dist/docs/changelogs/preview.md +447 -184
  4. package/dist/docs/cli/model-routing.md +18 -1
  5. package/dist/docs/cli/model-steering.md +79 -0
  6. package/dist/docs/cli/plan-mode.md +99 -14
  7. package/dist/docs/cli/settings.md +14 -12
  8. package/dist/docs/cli/telemetry.md +45 -0
  9. package/dist/docs/cli/tutorials/plan-mode-steering.md +89 -0
  10. package/dist/docs/core/index.md +2 -0
  11. package/dist/docs/core/local-model-routing.md +193 -0
  12. package/dist/docs/reference/configuration.md +27 -3
  13. package/dist/docs/reference/keyboard-shortcuts.md +168 -90
  14. package/dist/docs/reference/policy-engine.md +30 -6
  15. package/dist/docs/resources/troubleshooting.md +15 -0
  16. package/dist/docs/sidebar.json +10 -0
  17. package/dist/docs/tools/shell.md +8 -0
  18. package/dist/google-gemini-cli-core-0.35.0-nightly.20260311.657f19c1f.tgz +0 -0
  19. package/dist/src/agents/a2a-client-manager.d.ts +11 -4
  20. package/dist/src/agents/a2a-client-manager.js +61 -34
  21. package/dist/src/agents/a2a-client-manager.js.map +1 -1
  22. package/dist/src/agents/a2a-client-manager.test.js +178 -115
  23. package/dist/src/agents/a2a-client-manager.test.js.map +1 -1
  24. package/dist/src/agents/a2aUtils.d.ts +4 -28
  25. package/dist/src/agents/a2aUtils.js +20 -181
  26. package/dist/src/agents/a2aUtils.js.map +1 -1
  27. package/dist/src/agents/a2aUtils.test.js +19 -106
  28. package/dist/src/agents/a2aUtils.test.js.map +1 -1
  29. package/dist/src/agents/agent-scheduler.d.ts +2 -0
  30. package/dist/src/agents/agent-scheduler.js +3 -2
  31. package/dist/src/agents/agent-scheduler.js.map +1 -1
  32. package/dist/src/agents/agent-scheduler.test.js +12 -3
  33. package/dist/src/agents/agent-scheduler.test.js.map +1 -1
  34. package/dist/src/agents/agentLoader.d.ts +2 -2
  35. package/dist/src/agents/agentLoader.js +15 -0
  36. package/dist/src/agents/agentLoader.js.map +1 -1
  37. package/dist/src/agents/auth-provider/factory.d.ts +2 -0
  38. package/dist/src/agents/auth-provider/factory.js +6 -3
  39. package/dist/src/agents/auth-provider/factory.js.map +1 -1
  40. package/dist/src/agents/auth-provider/google-credentials-provider.d.ts +26 -0
  41. package/dist/src/agents/auth-provider/google-credentials-provider.js +117 -0
  42. package/dist/src/agents/auth-provider/google-credentials-provider.js.map +1 -0
  43. package/dist/src/agents/auth-provider/google-credentials-provider.test.d.ts +6 -0
  44. package/dist/src/agents/auth-provider/google-credentials-provider.test.js +126 -0
  45. package/dist/src/agents/auth-provider/google-credentials-provider.test.js.map +1 -0
  46. package/dist/src/agents/browser/browserAgentFactory.js +11 -2
  47. package/dist/src/agents/browser/browserAgentFactory.js.map +1 -1
  48. package/dist/src/agents/browser/browserAgentInvocation.d.ts +4 -3
  49. package/dist/src/agents/browser/browserAgentInvocation.js +10 -4
  50. package/dist/src/agents/browser/browserAgentInvocation.js.map +1 -1
  51. package/dist/src/agents/browser/browserAgentInvocation.test.js +1 -0
  52. package/dist/src/agents/browser/browserAgentInvocation.test.js.map +1 -1
  53. package/dist/src/agents/browser/browserManager.d.ts +10 -0
  54. package/dist/src/agents/browser/browserManager.js +53 -8
  55. package/dist/src/agents/browser/browserManager.js.map +1 -1
  56. package/dist/src/agents/browser/inputBlocker.d.ts +51 -0
  57. package/dist/src/agents/browser/inputBlocker.js +234 -0
  58. package/dist/src/agents/browser/inputBlocker.js.map +1 -0
  59. package/dist/src/agents/browser/inputBlocker.test.d.ts +6 -0
  60. package/dist/src/agents/browser/inputBlocker.test.js +82 -0
  61. package/dist/src/agents/browser/inputBlocker.test.js.map +1 -0
  62. package/dist/src/agents/browser/mcpToolWrapper.d.ts +3 -2
  63. package/dist/src/agents/browser/mcpToolWrapper.js +49 -8
  64. package/dist/src/agents/browser/mcpToolWrapper.js.map +1 -1
  65. package/dist/src/agents/browser/mcpToolWrapper.test.js +51 -0
  66. package/dist/src/agents/browser/mcpToolWrapper.test.js.map +1 -1
  67. package/dist/src/agents/local-executor.d.ts +5 -4
  68. package/dist/src/agents/local-executor.js +30 -25
  69. package/dist/src/agents/local-executor.js.map +1 -1
  70. package/dist/src/agents/local-executor.test.js +269 -3
  71. package/dist/src/agents/local-executor.test.js.map +1 -1
  72. package/dist/src/agents/local-invocation.d.ts +4 -4
  73. package/dist/src/agents/local-invocation.js +12 -9
  74. package/dist/src/agents/local-invocation.js.map +1 -1
  75. package/dist/src/agents/local-invocation.test.js +19 -3
  76. package/dist/src/agents/local-invocation.test.js.map +1 -1
  77. package/dist/src/agents/registry.js +3 -2
  78. package/dist/src/agents/registry.js.map +1 -1
  79. package/dist/src/agents/registry.test.js +1 -0
  80. package/dist/src/agents/registry.test.js.map +1 -1
  81. package/dist/src/agents/remote-invocation.d.ts +0 -9
  82. package/dist/src/agents/remote-invocation.js +1 -28
  83. package/dist/src/agents/remote-invocation.js.map +1 -1
  84. package/dist/src/agents/remote-invocation.test.js +1 -0
  85. package/dist/src/agents/remote-invocation.test.js.map +1 -1
  86. package/dist/src/agents/subagent-tool-wrapper.d.ts +5 -4
  87. package/dist/src/agents/subagent-tool-wrapper.js +9 -5
  88. package/dist/src/agents/subagent-tool-wrapper.js.map +1 -1
  89. package/dist/src/agents/subagent-tool-wrapper.test.js +15 -1
  90. package/dist/src/agents/subagent-tool-wrapper.test.js.map +1 -1
  91. package/dist/src/agents/subagent-tool.d.ts +3 -3
  92. package/dist/src/agents/subagent-tool.js +16 -12
  93. package/dist/src/agents/subagent-tool.js.map +1 -1
  94. package/dist/src/agents/subagent-tool.test.js +13 -3
  95. package/dist/src/agents/subagent-tool.test.js.map +1 -1
  96. package/dist/src/code_assist/oauth2.js +0 -1
  97. package/dist/src/code_assist/oauth2.js.map +1 -1
  98. package/dist/src/code_assist/server.js +2 -2
  99. package/dist/src/code_assist/server.js.map +1 -1
  100. package/dist/src/code_assist/server.test.js +5 -1
  101. package/dist/src/code_assist/server.test.js.map +1 -1
  102. package/dist/src/code_assist/telemetry.d.ts +2 -2
  103. package/dist/src/code_assist/telemetry.js +5 -3
  104. package/dist/src/code_assist/telemetry.js.map +1 -1
  105. package/dist/src/code_assist/telemetry.test.js +14 -12
  106. package/dist/src/code_assist/telemetry.test.js.map +1 -1
  107. package/dist/src/code_assist/types.d.ts +14 -12
  108. package/dist/src/code_assist/types.js.map +1 -1
  109. package/dist/src/config/agent-loop-context.d.ts +3 -0
  110. package/dist/src/config/config.d.ts +75 -2
  111. package/dist/src/config/config.js +78 -11
  112. package/dist/src/config/config.js.map +1 -1
  113. package/dist/src/config/config.test.js +170 -10
  114. package/dist/src/config/config.test.js.map +1 -1
  115. package/dist/src/config/models.d.ts +14 -0
  116. package/dist/src/config/models.js +37 -0
  117. package/dist/src/config/models.js.map +1 -1
  118. package/dist/src/config/models.test.js +51 -1
  119. package/dist/src/config/models.test.js.map +1 -1
  120. package/dist/src/config/storage.d.ts +1 -0
  121. package/dist/src/config/storage.js +3 -0
  122. package/dist/src/config/storage.js.map +1 -1
  123. package/dist/src/core/baseLlmClient.js +19 -3
  124. package/dist/src/core/baseLlmClient.js.map +1 -1
  125. package/dist/src/core/baseLlmClient.test.js +2 -0
  126. package/dist/src/core/baseLlmClient.test.js.map +1 -1
  127. package/dist/src/core/client.d.ts +4 -3
  128. package/dist/src/core/client.js +42 -17
  129. package/dist/src/core/client.js.map +1 -1
  130. package/dist/src/core/client.test.js +8 -0
  131. package/dist/src/core/client.test.js.map +1 -1
  132. package/dist/src/core/contentGenerator.d.ts +1 -0
  133. package/dist/src/core/contentGenerator.js +35 -3
  134. package/dist/src/core/contentGenerator.js.map +1 -1
  135. package/dist/src/core/contentGenerator.test.js +137 -2
  136. package/dist/src/core/contentGenerator.test.js.map +1 -1
  137. package/dist/src/core/coreToolHookTriggers.d.ts +2 -3
  138. package/dist/src/core/coreToolHookTriggers.js +5 -11
  139. package/dist/src/core/coreToolHookTriggers.js.map +1 -1
  140. package/dist/src/core/coreToolHookTriggers.test.js +24 -0
  141. package/dist/src/core/coreToolHookTriggers.test.js.map +1 -1
  142. package/dist/src/core/coreToolScheduler.js +1 -1
  143. package/dist/src/core/coreToolScheduler.js.map +1 -1
  144. package/dist/src/core/geminiChat.js +31 -21
  145. package/dist/src/core/geminiChat.js.map +1 -1
  146. package/dist/src/core/geminiChat.test.js +6 -4
  147. package/dist/src/core/geminiChat.test.js.map +1 -1
  148. package/dist/src/core/geminiChat_network_retry.test.js +21 -11
  149. package/dist/src/core/geminiChat_network_retry.test.js.map +1 -1
  150. package/dist/src/fallback/handler.js +0 -4
  151. package/dist/src/fallback/handler.js.map +1 -1
  152. package/dist/src/fallback/handler.test.js +0 -6
  153. package/dist/src/fallback/handler.test.js.map +1 -1
  154. package/dist/src/generated/git-commit.d.ts +2 -2
  155. package/dist/src/generated/git-commit.js +2 -2
  156. package/dist/src/generated/git-commit.js.map +1 -1
  157. package/dist/src/ide/detect-ide.d.ts +8 -0
  158. package/dist/src/ide/detect-ide.js +12 -1
  159. package/dist/src/ide/detect-ide.js.map +1 -1
  160. package/dist/src/ide/detect-ide.test.js +12 -0
  161. package/dist/src/ide/detect-ide.test.js.map +1 -1
  162. package/dist/src/ide/ide-installer.js +20 -8
  163. package/dist/src/ide/ide-installer.js.map +1 -1
  164. package/dist/src/ide/ide-installer.test.js +60 -4
  165. package/dist/src/ide/ide-installer.test.js.map +1 -1
  166. package/dist/src/mcp/oauth-provider.js +0 -2
  167. package/dist/src/mcp/oauth-provider.js.map +1 -1
  168. package/dist/src/mcp/oauth-token-storage.test.js +6 -10
  169. package/dist/src/mcp/oauth-token-storage.test.js.map +1 -1
  170. package/dist/src/mcp/oauth-utils.js +0 -2
  171. package/dist/src/mcp/oauth-utils.js.map +1 -1
  172. package/dist/src/mcp/token-storage/file-token-storage.d.ts +24 -0
  173. package/dist/src/mcp/token-storage/file-token-storage.js +152 -0
  174. package/dist/src/mcp/token-storage/file-token-storage.js.map +1 -0
  175. package/dist/src/mcp/token-storage/file-token-storage.test.d.ts +6 -0
  176. package/dist/src/mcp/token-storage/file-token-storage.test.js +276 -0
  177. package/dist/src/mcp/token-storage/file-token-storage.test.js.map +1 -0
  178. package/dist/src/mcp/token-storage/hybrid-token-storage.js +21 -9
  179. package/dist/src/mcp/token-storage/hybrid-token-storage.js.map +1 -1
  180. package/dist/src/mcp/token-storage/hybrid-token-storage.test.js +59 -10
  181. package/dist/src/mcp/token-storage/hybrid-token-storage.test.js.map +1 -1
  182. package/dist/src/mcp/token-storage/index.d.ts +1 -1
  183. package/dist/src/mcp/token-storage/index.js +1 -1
  184. package/dist/src/mcp/token-storage/index.js.map +1 -1
  185. package/dist/src/mcp/token-storage/keychain-token-storage.d.ts +0 -1
  186. package/dist/src/mcp/token-storage/keychain-token-storage.js +0 -3
  187. package/dist/src/mcp/token-storage/keychain-token-storage.js.map +1 -1
  188. package/dist/src/policy/config.d.ts +14 -2
  189. package/dist/src/policy/config.js +94 -49
  190. package/dist/src/policy/config.js.map +1 -1
  191. package/dist/src/policy/config.test.js +208 -488
  192. package/dist/src/policy/config.test.js.map +1 -1
  193. package/dist/src/policy/policies/plan.toml +1 -1
  194. package/dist/src/policy/stable-stringify.js +15 -5
  195. package/dist/src/policy/stable-stringify.js.map +1 -1
  196. package/dist/src/policy/types.d.ts +1 -0
  197. package/dist/src/policy/types.js.map +1 -1
  198. package/dist/src/policy/utils.d.ts +17 -0
  199. package/dist/src/policy/utils.js +27 -7
  200. package/dist/src/policy/utils.js.map +1 -1
  201. package/dist/src/prompts/snippets.js +2 -1
  202. package/dist/src/prompts/snippets.js.map +1 -1
  203. package/dist/src/prompts/utils.test.d.ts +6 -0
  204. package/dist/src/prompts/utils.test.js +225 -0
  205. package/dist/src/prompts/utils.test.js.map +1 -0
  206. package/dist/src/routing/modelRouterService.js +2 -2
  207. package/dist/src/routing/modelRouterService.js.map +1 -1
  208. package/dist/src/routing/modelRouterService.test.js +4 -3
  209. package/dist/src/routing/modelRouterService.test.js.map +1 -1
  210. package/dist/src/routing/strategies/numericalClassifierStrategy.js +6 -40
  211. package/dist/src/routing/strategies/numericalClassifierStrategy.js.map +1 -1
  212. package/dist/src/routing/strategies/numericalClassifierStrategy.test.js +29 -64
  213. package/dist/src/routing/strategies/numericalClassifierStrategy.test.js.map +1 -1
  214. package/dist/src/scheduler/policy.d.ts +3 -7
  215. package/dist/src/scheduler/policy.js +4 -3
  216. package/dist/src/scheduler/policy.js.map +1 -1
  217. package/dist/src/scheduler/policy.test.js +51 -31
  218. package/dist/src/scheduler/policy.test.js.map +1 -1
  219. package/dist/src/scheduler/scheduler.d.ts +4 -2
  220. package/dist/src/scheduler/scheduler.js +19 -16
  221. package/dist/src/scheduler/scheduler.js.map +1 -1
  222. package/dist/src/scheduler/scheduler.test.js +33 -10
  223. package/dist/src/scheduler/scheduler.test.js.map +1 -1
  224. package/dist/src/scheduler/scheduler_parallel.test.js +40 -2
  225. package/dist/src/scheduler/scheduler_parallel.test.js.map +1 -1
  226. package/dist/src/scheduler/scheduler_waiting_callback.test.js +1 -1
  227. package/dist/src/scheduler/scheduler_waiting_callback.test.js.map +1 -1
  228. package/dist/src/scheduler/tool-executor.d.ts +3 -3
  229. package/dist/src/scheduler/tool-executor.js +15 -21
  230. package/dist/src/scheduler/tool-executor.js.map +1 -1
  231. package/dist/src/scheduler/tool-executor.test.js +43 -8
  232. package/dist/src/scheduler/tool-executor.test.js.map +1 -1
  233. package/dist/src/services/chatRecordingService.js +2 -1
  234. package/dist/src/services/chatRecordingService.js.map +1 -1
  235. package/dist/src/services/chatRecordingService.test.js +21 -0
  236. package/dist/src/services/chatRecordingService.test.js.map +1 -1
  237. package/dist/src/services/executionLifecycleService.d.ts +83 -0
  238. package/dist/src/services/executionLifecycleService.js +271 -0
  239. package/dist/src/services/executionLifecycleService.js.map +1 -0
  240. package/dist/src/services/executionLifecycleService.test.d.ts +6 -0
  241. package/dist/src/services/executionLifecycleService.test.js +227 -0
  242. package/dist/src/services/executionLifecycleService.test.js.map +1 -0
  243. package/dist/src/services/keychainService.d.ts +0 -5
  244. package/dist/src/services/keychainService.js +13 -30
  245. package/dist/src/services/keychainService.js.map +1 -1
  246. package/dist/src/services/keychainService.test.js +22 -52
  247. package/dist/src/services/keychainService.test.js.map +1 -1
  248. package/dist/src/services/sandboxManager.d.ts +54 -0
  249. package/dist/src/services/sandboxManager.js +30 -0
  250. package/dist/src/services/sandboxManager.js.map +1 -0
  251. package/dist/src/services/sandboxManager.test.d.ts +6 -0
  252. package/dist/src/services/sandboxManager.test.js +95 -0
  253. package/dist/src/services/sandboxManager.test.js.map +1 -0
  254. package/dist/src/services/shellExecutionService.d.ts +4 -52
  255. package/dist/src/services/shellExecutionService.js +437 -498
  256. package/dist/src/services/shellExecutionService.js.map +1 -1
  257. package/dist/src/services/shellExecutionService.test.js +10 -21
  258. package/dist/src/services/shellExecutionService.test.js.map +1 -1
  259. package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +13 -2
  260. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +91 -1
  261. package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
  262. package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js +77 -0
  263. package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +1 -1
  264. package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +10 -1
  265. package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +25 -1
  266. package/dist/src/telemetry/clearcut-logger/event-metadata-key.js.map +1 -1
  267. package/dist/src/telemetry/index.d.ts +3 -3
  268. package/dist/src/telemetry/index.js +3 -3
  269. package/dist/src/telemetry/index.js.map +1 -1
  270. package/dist/src/telemetry/loggers.d.ts +2 -1
  271. package/dist/src/telemetry/loggers.js +32 -1
  272. package/dist/src/telemetry/loggers.js.map +1 -1
  273. package/dist/src/telemetry/loggers.test.js +37 -2
  274. package/dist/src/telemetry/loggers.test.js.map +1 -1
  275. package/dist/src/telemetry/metrics.d.ts +13 -0
  276. package/dist/src/telemetry/metrics.js +17 -0
  277. package/dist/src/telemetry/metrics.js.map +1 -1
  278. package/dist/src/telemetry/startupProfiler.js +4 -4
  279. package/dist/src/telemetry/startupProfiler.js.map +1 -1
  280. package/dist/src/telemetry/startupProfiler.test.js +19 -0
  281. package/dist/src/telemetry/startupProfiler.test.js.map +1 -1
  282. package/dist/src/telemetry/telemetry-utils.test.js +3 -3
  283. package/dist/src/telemetry/types.d.ts +16 -2
  284. package/dist/src/telemetry/types.js +34 -0
  285. package/dist/src/telemetry/types.js.map +1 -1
  286. package/dist/src/telemetry/uiTelemetry.d.ts +7 -0
  287. package/dist/src/telemetry/uiTelemetry.js +78 -0
  288. package/dist/src/telemetry/uiTelemetry.js.map +1 -1
  289. package/dist/src/telemetry/uiTelemetry.test.js +101 -0
  290. package/dist/src/telemetry/uiTelemetry.test.js.map +1 -1
  291. package/dist/src/tools/exit-plan-mode.js +10 -2
  292. package/dist/src/tools/exit-plan-mode.js.map +1 -1
  293. package/dist/src/tools/exit-plan-mode.test.js +15 -0
  294. package/dist/src/tools/exit-plan-mode.test.js.map +1 -1
  295. package/dist/src/tools/ls.js +2 -2
  296. package/dist/src/tools/ls.js.map +1 -1
  297. package/dist/src/tools/mcp-client.js +0 -1
  298. package/dist/src/tools/mcp-client.js.map +1 -1
  299. package/dist/src/tools/mcp-client.test.js +4 -0
  300. package/dist/src/tools/mcp-client.test.js.map +1 -1
  301. package/dist/src/tools/mcp-tool.test.js +10 -1
  302. package/dist/src/tools/mcp-tool.test.js.map +1 -1
  303. package/dist/src/tools/read-many-files.js +2 -4
  304. package/dist/src/tools/read-many-files.js.map +1 -1
  305. package/dist/src/tools/shell.d.ts +1 -1
  306. package/dist/src/tools/shell.js +3 -3
  307. package/dist/src/tools/shell.js.map +1 -1
  308. package/dist/src/tools/tool-registry.test.js +4 -0
  309. package/dist/src/tools/tool-registry.test.js.map +1 -1
  310. package/dist/src/tools/tools.d.ts +34 -1
  311. package/dist/src/tools/tools.js +47 -1
  312. package/dist/src/tools/tools.js.map +1 -1
  313. package/dist/src/tools/web-fetch.d.ts +5 -0
  314. package/dist/src/tools/web-fetch.js +177 -100
  315. package/dist/src/tools/web-fetch.js.map +1 -1
  316. package/dist/src/tools/web-fetch.test.js +130 -26
  317. package/dist/src/tools/web-fetch.test.js.map +1 -1
  318. package/dist/src/tools/web-search.test.js +2 -0
  319. package/dist/src/tools/web-search.test.js.map +1 -1
  320. package/dist/src/utils/fetch.d.ts +0 -29
  321. package/dist/src/utils/fetch.js +10 -123
  322. package/dist/src/utils/fetch.js.map +1 -1
  323. package/dist/src/utils/fetch.test.js +1 -103
  324. package/dist/src/utils/fetch.test.js.map +1 -1
  325. package/dist/src/utils/language-detection.js +96 -89
  326. package/dist/src/utils/language-detection.js.map +1 -1
  327. package/dist/src/utils/language-detection.test.d.ts +6 -0
  328. package/dist/src/utils/language-detection.test.js +39 -0
  329. package/dist/src/utils/language-detection.test.js.map +1 -0
  330. package/dist/src/utils/oauth-flow.js +0 -2
  331. package/dist/src/utils/oauth-flow.js.map +1 -1
  332. package/dist/src/utils/paths.js +5 -2
  333. package/dist/src/utils/paths.js.map +1 -1
  334. package/dist/src/utils/paths.test.js +11 -0
  335. package/dist/src/utils/paths.test.js.map +1 -1
  336. package/dist/src/utils/process-utils.js +18 -4
  337. package/dist/src/utils/process-utils.js.map +1 -1
  338. package/dist/src/utils/process-utils.test.js +32 -10
  339. package/dist/src/utils/process-utils.test.js.map +1 -1
  340. package/dist/src/utils/retry.d.ts +7 -0
  341. package/dist/src/utils/retry.js +40 -3
  342. package/dist/src/utils/retry.js.map +1 -1
  343. package/dist/src/utils/retry.test.js +13 -0
  344. package/dist/src/utils/retry.test.js.map +1 -1
  345. package/dist/src/utils/summarizer.test.js +5 -0
  346. package/dist/src/utils/summarizer.test.js.map +1 -1
  347. package/dist/src/utils/surface.d.ts +18 -0
  348. package/dist/src/utils/surface.js +46 -0
  349. package/dist/src/utils/surface.js.map +1 -0
  350. package/dist/tsconfig.tsbuildinfo +1 -1
  351. package/package.json +1 -1
  352. package/dist/google-gemini-cli-core-0.34.0-preview.2.tgz +0 -0
  353. package/dist/src/services/fileKeychain.d.ts +0 -24
  354. package/dist/src/services/fileKeychain.js +0 -123
  355. package/dist/src/services/fileKeychain.js.map +0 -1
@@ -18,7 +18,9 @@ import { debugLogger } from '../utils/debugLogger.js';
18
18
  import { Storage } from '../config/storage.js';
19
19
  import { serializeTerminalToObject, } from '../utils/terminalSerializer.js';
20
20
  import { sanitizeEnvironment, } from './environmentSanitization.js';
21
+ import { NoopSandboxManager } from './sandboxManager.js';
21
22
  import { killProcessGroup } from '../utils/process-utils.js';
23
+ import { ExecutionLifecycleService, } from './executionLifecycleService.js';
22
24
  const { Terminal } = pkg;
23
25
  const MAX_CHILD_PROCESS_BUFFER_SIZE = 16 * 1024 * 1024; // 16MB
24
26
  /**
@@ -135,7 +137,6 @@ export class ShellExecutionService {
135
137
  static activeChildProcesses = new Map();
136
138
  static backgroundLogPids = new Set();
137
139
  static backgroundLogStreams = new Map();
138
- static exitedPtyInfo = new Map();
139
140
  static getLogDir() {
140
141
  return path.join(Storage.getGlobalTempDir(), 'background-processes');
141
142
  }
@@ -161,8 +162,6 @@ export class ShellExecutionService {
161
162
  }
162
163
  this.backgroundLogPids.delete(pid);
163
164
  }
164
- static activeResolvers = new Map();
165
- static activeListeners = new Map();
166
165
  /**
167
166
  * Executes a shell command using `node-pty`, capturing all output and lifecycle events.
168
167
  *
@@ -174,11 +173,19 @@ export class ShellExecutionService {
174
173
  * resolves with the complete execution result.
175
174
  */
176
175
  static async execute(commandToExecute, cwd, onOutputEvent, abortSignal, shouldUseNodePty, shellExecutionConfig) {
176
+ const sandboxManager = new NoopSandboxManager();
177
+ const { env: sanitizedEnv } = await sandboxManager.prepareCommand({
178
+ command: commandToExecute,
179
+ args: [],
180
+ env: process.env,
181
+ cwd,
182
+ config: shellExecutionConfig,
183
+ });
177
184
  if (shouldUseNodePty) {
178
185
  const ptyInfo = await getPty();
179
186
  if (ptyInfo) {
180
187
  try {
181
- return await this.executeWithPty(commandToExecute, cwd, onOutputEvent, abortSignal, shellExecutionConfig, ptyInfo);
188
+ return await this.executeWithPty(commandToExecute, cwd, onOutputEvent, abortSignal, shellExecutionConfig, ptyInfo, sanitizedEnv);
182
189
  }
183
190
  catch (_e) {
184
191
  // Fallback to child_process
@@ -209,12 +216,6 @@ export class ShellExecutionService {
209
216
  const truncatedBuffer = currentBuffer.substring(charsToTrim);
210
217
  return { newBuffer: truncatedBuffer + chunk, truncated: true };
211
218
  }
212
- static emitEvent(pid, event) {
213
- const listeners = this.activeListeners.get(pid);
214
- if (listeners) {
215
- listeners.forEach((listener) => listener(event));
216
- }
217
- }
218
219
  static childProcessFallback(commandToExecute, cwd, onOutputEvent, abortSignal, sanitizationConfig, isInteractive) {
219
220
  try {
220
221
  const isWindows = os.platform() === 'win32';
@@ -276,169 +277,202 @@ export class ShellExecutionService {
276
277
  state,
277
278
  });
278
279
  }
279
- const result = new Promise((resolve) => {
280
- if (child.pid) {
281
- this.activeResolvers.set(child.pid, resolve);
282
- }
283
- let stdoutDecoder = null;
284
- let stderrDecoder = null;
285
- let error = null;
286
- let exited = false;
287
- let isStreamingRawContent = true;
288
- const MAX_SNIFF_SIZE = 4096;
289
- let sniffedBytes = 0;
290
- const handleOutput = (data, stream) => {
291
- if (!stdoutDecoder || !stderrDecoder) {
292
- const encoding = getCachedEncodingForBuffer(data);
280
+ const lifecycleHandle = child.pid
281
+ ? ExecutionLifecycleService.attachExecution(child.pid, {
282
+ executionMethod: 'child_process',
283
+ getBackgroundOutput: () => state.output,
284
+ getSubscriptionSnapshot: () => state.output || undefined,
285
+ writeInput: (input) => {
286
+ const stdin = child.stdin;
287
+ if (stdin) {
288
+ stdin.write(input);
289
+ }
290
+ },
291
+ kill: () => {
292
+ if (child.pid) {
293
+ killProcessGroup({ pid: child.pid }).catch(() => { });
294
+ this.activeChildProcesses.delete(child.pid);
295
+ }
296
+ },
297
+ isActive: () => {
298
+ if (!child.pid) {
299
+ return false;
300
+ }
293
301
  try {
294
- stdoutDecoder = new TextDecoder(encoding);
295
- stderrDecoder = new TextDecoder(encoding);
302
+ return process.kill(child.pid, 0);
296
303
  }
297
304
  catch {
298
- stdoutDecoder = new TextDecoder('utf-8');
299
- stderrDecoder = new TextDecoder('utf-8');
305
+ return false;
300
306
  }
307
+ },
308
+ })
309
+ : undefined;
310
+ let resolveWithoutPid;
311
+ const result = lifecycleHandle?.result ??
312
+ new Promise((resolve) => {
313
+ resolveWithoutPid = resolve;
314
+ });
315
+ let stdoutDecoder = null;
316
+ let stderrDecoder = null;
317
+ let error = null;
318
+ let exited = false;
319
+ let isStreamingRawContent = true;
320
+ const MAX_SNIFF_SIZE = 4096;
321
+ let sniffedBytes = 0;
322
+ const handleOutput = (data, stream) => {
323
+ if (!stdoutDecoder || !stderrDecoder) {
324
+ const encoding = getCachedEncodingForBuffer(data);
325
+ try {
326
+ stdoutDecoder = new TextDecoder(encoding);
327
+ stderrDecoder = new TextDecoder(encoding);
301
328
  }
302
- state.outputChunks.push(data);
303
- if (isStreamingRawContent && sniffedBytes < MAX_SNIFF_SIZE) {
304
- const sniffBuffer = Buffer.concat(state.outputChunks.slice(0, 20));
305
- sniffedBytes = sniffBuffer.length;
306
- if (isBinary(sniffBuffer)) {
307
- isStreamingRawContent = false;
308
- const event = { type: 'binary_detected' };
309
- onOutputEvent(event);
310
- if (child.pid)
311
- ShellExecutionService.emitEvent(child.pid, event);
312
- }
329
+ catch {
330
+ stdoutDecoder = new TextDecoder('utf-8');
331
+ stderrDecoder = new TextDecoder('utf-8');
313
332
  }
314
- if (isStreamingRawContent) {
315
- const decoder = stream === 'stdout' ? stdoutDecoder : stderrDecoder;
316
- const decodedChunk = decoder.decode(data, { stream: true });
317
- const { newBuffer, truncated } = this.appendAndTruncate(state.output, decodedChunk, MAX_CHILD_PROCESS_BUFFER_SIZE);
318
- state.output = newBuffer;
319
- if (truncated) {
320
- state.truncated = true;
321
- }
322
- if (decodedChunk) {
323
- const event = {
324
- type: 'data',
325
- chunk: decodedChunk,
326
- };
327
- onOutputEvent(event);
328
- if (child.pid) {
329
- ShellExecutionService.emitEvent(child.pid, event);
330
- if (ShellExecutionService.backgroundLogPids.has(child.pid)) {
331
- ShellExecutionService.syncBackgroundLog(child.pid, decodedChunk);
332
- }
333
- }
333
+ }
334
+ state.outputChunks.push(data);
335
+ if (isStreamingRawContent && sniffedBytes < MAX_SNIFF_SIZE) {
336
+ const sniffBuffer = Buffer.concat(state.outputChunks.slice(0, 20));
337
+ sniffedBytes = sniffBuffer.length;
338
+ if (isBinary(sniffBuffer)) {
339
+ isStreamingRawContent = false;
340
+ const event = { type: 'binary_detected' };
341
+ onOutputEvent(event);
342
+ if (child.pid) {
343
+ ExecutionLifecycleService.emitEvent(child.pid, event);
334
344
  }
335
345
  }
336
- else {
337
- const totalBytes = state.outputChunks.reduce((sum, chunk) => sum + chunk.length, 0);
346
+ }
347
+ if (isStreamingRawContent) {
348
+ const decoder = stream === 'stdout' ? stdoutDecoder : stderrDecoder;
349
+ const decodedChunk = decoder.decode(data, { stream: true });
350
+ const { newBuffer, truncated } = this.appendAndTruncate(state.output, decodedChunk, MAX_CHILD_PROCESS_BUFFER_SIZE);
351
+ state.output = newBuffer;
352
+ if (truncated) {
353
+ state.truncated = true;
354
+ }
355
+ if (decodedChunk) {
338
356
  const event = {
339
- type: 'binary_progress',
340
- bytesReceived: totalBytes,
357
+ type: 'data',
358
+ chunk: decodedChunk,
341
359
  };
342
360
  onOutputEvent(event);
343
- if (child.pid)
344
- ShellExecutionService.emitEvent(child.pid, event);
345
- }
346
- };
347
- const handleExit = (code, signal) => {
348
- const { finalBuffer } = cleanup();
349
- let combinedOutput = state.output;
350
- if (state.truncated) {
351
- const truncationMessage = `\n[GEMINI_CLI_WARNING: Output truncated. The buffer is limited to ${MAX_CHILD_PROCESS_BUFFER_SIZE / (1024 * 1024)}MB.]`;
352
- combinedOutput += truncationMessage;
361
+ if (child.pid) {
362
+ ExecutionLifecycleService.emitEvent(child.pid, event);
363
+ if (ShellExecutionService.backgroundLogPids.has(child.pid)) {
364
+ ShellExecutionService.syncBackgroundLog(child.pid, decodedChunk);
365
+ }
366
+ }
353
367
  }
354
- const finalStrippedOutput = stripAnsi(combinedOutput).trim();
355
- const exitCode = code;
356
- const exitSignal = signal ? os.constants.signals[signal] : null;
368
+ }
369
+ else {
370
+ const totalBytes = state.outputChunks.reduce((sum, chunk) => sum + chunk.length, 0);
371
+ const event = {
372
+ type: 'binary_progress',
373
+ bytesReceived: totalBytes,
374
+ };
375
+ onOutputEvent(event);
357
376
  if (child.pid) {
358
- const pid = child.pid;
359
- const event = {
360
- type: 'exit',
361
- exitCode,
362
- signal: exitSignal,
363
- };
364
- onOutputEvent(event);
365
- ShellExecutionService.emitEvent(pid, event);
366
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
367
- ShellExecutionService.cleanupLogStream(pid).then(() => {
368
- this.activeChildProcesses.delete(pid);
369
- this.activeResolvers.delete(pid);
370
- this.activeListeners.delete(pid);
371
- });
377
+ ExecutionLifecycleService.emitEvent(child.pid, event);
372
378
  }
373
- resolve({
374
- rawOutput: finalBuffer,
375
- output: finalStrippedOutput,
379
+ }
380
+ };
381
+ const handleExit = (code, signal) => {
382
+ const { finalBuffer } = cleanup();
383
+ let combinedOutput = state.output;
384
+ if (state.truncated) {
385
+ const truncationMessage = `\n[GEMINI_CLI_WARNING: Output truncated. The buffer is limited to ${MAX_CHILD_PROCESS_BUFFER_SIZE / (1024 * 1024)}MB.]`;
386
+ combinedOutput += truncationMessage;
387
+ }
388
+ const finalStrippedOutput = stripAnsi(combinedOutput).trim();
389
+ const exitCode = code;
390
+ const exitSignal = signal ? os.constants.signals[signal] : null;
391
+ const resultPayload = {
392
+ rawOutput: finalBuffer,
393
+ output: finalStrippedOutput,
394
+ exitCode,
395
+ signal: exitSignal,
396
+ error,
397
+ aborted: abortSignal.aborted,
398
+ pid: child.pid,
399
+ executionMethod: 'child_process',
400
+ };
401
+ if (child.pid) {
402
+ const pid = child.pid;
403
+ const event = {
404
+ type: 'exit',
376
405
  exitCode,
377
406
  signal: exitSignal,
378
- error,
379
- aborted: abortSignal.aborted,
407
+ };
408
+ onOutputEvent(event);
409
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
410
+ ShellExecutionService.cleanupLogStream(pid).then(() => {
411
+ ShellExecutionService.activeChildProcesses.delete(pid);
412
+ });
413
+ ExecutionLifecycleService.completeWithResult(pid, resultPayload);
414
+ }
415
+ else {
416
+ resolveWithoutPid?.(resultPayload);
417
+ }
418
+ };
419
+ child.stdout.on('data', (data) => handleOutput(data, 'stdout'));
420
+ child.stderr.on('data', (data) => handleOutput(data, 'stderr'));
421
+ child.on('error', (err) => {
422
+ error = err;
423
+ handleExit(1, null);
424
+ });
425
+ const abortHandler = async () => {
426
+ if (child.pid && !exited) {
427
+ await killProcessGroup({
380
428
  pid: child.pid,
381
- executionMethod: 'child_process',
429
+ escalate: true,
430
+ isExited: () => exited,
382
431
  });
383
- };
384
- child.stdout.on('data', (data) => handleOutput(data, 'stdout'));
385
- child.stderr.on('data', (data) => handleOutput(data, 'stderr'));
386
- child.on('error', (err) => {
387
- error = err;
388
- handleExit(1, null);
389
- });
390
- const abortHandler = async () => {
391
- if (child.pid && !exited) {
392
- await killProcessGroup({
393
- pid: child.pid,
394
- escalate: true,
395
- isExited: () => exited,
396
- });
397
- }
398
- };
399
- abortSignal.addEventListener('abort', abortHandler, { once: true });
400
- child.on('exit', (code, signal) => {
401
- handleExit(code, signal);
402
- });
403
- function cleanup() {
404
- exited = true;
405
- abortSignal.removeEventListener('abort', abortHandler);
406
- if (stdoutDecoder) {
407
- const remaining = stdoutDecoder.decode();
408
- if (remaining) {
409
- state.output += remaining;
410
- // If there's remaining output, we should technically emit it too,
411
- // but it's rare to have partial utf8 chars at the very end of stream.
412
- if (isStreamingRawContent && remaining) {
413
- const event = {
414
- type: 'data',
415
- chunk: remaining,
416
- };
417
- onOutputEvent(event);
418
- if (child.pid)
419
- ShellExecutionService.emitEvent(child.pid, event);
432
+ }
433
+ };
434
+ abortSignal.addEventListener('abort', abortHandler, { once: true });
435
+ child.on('exit', (code, signal) => {
436
+ handleExit(code, signal);
437
+ });
438
+ function cleanup() {
439
+ exited = true;
440
+ abortSignal.removeEventListener('abort', abortHandler);
441
+ if (stdoutDecoder) {
442
+ const remaining = stdoutDecoder.decode();
443
+ if (remaining) {
444
+ state.output += remaining;
445
+ if (isStreamingRawContent) {
446
+ const event = {
447
+ type: 'data',
448
+ chunk: remaining,
449
+ };
450
+ onOutputEvent(event);
451
+ if (child.pid) {
452
+ ExecutionLifecycleService.emitEvent(child.pid, event);
420
453
  }
421
454
  }
422
455
  }
423
- if (stderrDecoder) {
424
- const remaining = stderrDecoder.decode();
425
- if (remaining) {
426
- state.output += remaining;
427
- if (isStreamingRawContent && remaining) {
428
- const event = {
429
- type: 'data',
430
- chunk: remaining,
431
- };
432
- onOutputEvent(event);
433
- if (child.pid)
434
- ShellExecutionService.emitEvent(child.pid, event);
456
+ }
457
+ if (stderrDecoder) {
458
+ const remaining = stderrDecoder.decode();
459
+ if (remaining) {
460
+ state.output += remaining;
461
+ if (isStreamingRawContent) {
462
+ const event = {
463
+ type: 'data',
464
+ chunk: remaining,
465
+ };
466
+ onOutputEvent(event);
467
+ if (child.pid) {
468
+ ExecutionLifecycleService.emitEvent(child.pid, event);
435
469
  }
436
470
  }
437
471
  }
438
- const finalBuffer = Buffer.concat(state.outputChunks);
439
- return { finalBuffer };
440
472
  }
441
- });
473
+ const finalBuffer = Buffer.concat(state.outputChunks);
474
+ return { finalBuffer };
475
+ }
442
476
  return { pid: child.pid, result };
443
477
  }
444
478
  catch (e) {
@@ -459,7 +493,7 @@ export class ShellExecutionService {
459
493
  };
460
494
  }
461
495
  }
462
- static async executeWithPty(commandToExecute, cwd, onOutputEvent, abortSignal, shellExecutionConfig, ptyInfo) {
496
+ static async executeWithPty(commandToExecute, cwd, onOutputEvent, abortSignal, shellExecutionConfig, ptyInfo, sanitizedEnv) {
463
497
  if (!ptyInfo) {
464
498
  // This should not happen, but as a safeguard...
465
499
  throw new Error('PTY implementation not found');
@@ -482,7 +516,7 @@ export class ShellExecutionService {
482
516
  cols,
483
517
  rows,
484
518
  env: {
485
- ...sanitizeEnvironment(process.env, shellExecutionConfig.sanitizationConfig),
519
+ ...sanitizedEnv,
486
520
  GEMINI_CLI: '1',
487
521
  TERM: 'xterm-256color',
488
522
  PAGER: shellExecutionConfig.pager ?? 'cat',
@@ -492,246 +526,271 @@ export class ShellExecutionService {
492
526
  });
493
527
  // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
494
528
  spawnedPty = ptyProcess;
495
- const result = new Promise((resolve) => {
496
- this.activeResolvers.set(ptyProcess.pid, resolve);
497
- const headlessTerminal = new Terminal({
498
- allowProposedApi: true,
499
- cols,
500
- rows,
501
- scrollback: shellExecutionConfig.scrollback ?? SCROLLBACK_LIMIT,
502
- });
503
- headlessTerminal.scrollToTop();
504
- this.activePtys.set(ptyProcess.pid, {
505
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
506
- ptyProcess,
507
- headlessTerminal,
508
- maxSerializedLines: shellExecutionConfig.maxSerializedLines,
509
- });
510
- let processingChain = Promise.resolve();
511
- let decoder = null;
512
- let output = null;
513
- const outputChunks = [];
514
- const error = null;
515
- let exited = false;
516
- let isStreamingRawContent = true;
517
- const MAX_SNIFF_SIZE = 4096;
518
- let sniffedBytes = 0;
519
- let isWriting = false;
520
- let hasStartedOutput = false;
521
- let renderTimeout = null;
522
- const renderFn = () => {
523
- renderTimeout = null;
524
- if (!isStreamingRawContent) {
529
+ const ptyPid = Number(ptyProcess.pid);
530
+ const headlessTerminal = new Terminal({
531
+ allowProposedApi: true,
532
+ cols,
533
+ rows,
534
+ scrollback: shellExecutionConfig.scrollback ?? SCROLLBACK_LIMIT,
535
+ });
536
+ headlessTerminal.scrollToTop();
537
+ this.activePtys.set(ptyPid, {
538
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
539
+ ptyProcess,
540
+ headlessTerminal,
541
+ maxSerializedLines: shellExecutionConfig.maxSerializedLines,
542
+ });
543
+ const result = ExecutionLifecycleService.attachExecution(ptyPid, {
544
+ executionMethod: ptyInfo?.name ?? 'node-pty',
545
+ writeInput: (input) => {
546
+ if (!ExecutionLifecycleService.isActive(ptyPid)) {
525
547
  return;
526
548
  }
527
- if (!shellExecutionConfig.disableDynamicLineTrimming) {
528
- if (!hasStartedOutput) {
529
- const bufferText = getFullBufferText(headlessTerminal);
530
- if (bufferText.trim().length === 0) {
531
- return;
532
- }
533
- hasStartedOutput = true;
534
- }
535
- }
536
- const buffer = headlessTerminal.buffer.active;
537
- const endLine = buffer.length;
538
- const startLine = Math.max(0, endLine - (shellExecutionConfig.maxSerializedLines ?? 2000));
539
- let newOutput;
540
- if (shellExecutionConfig.showColor) {
541
- newOutput = serializeTerminalToObject(headlessTerminal, startLine, endLine);
542
- }
543
- else {
544
- newOutput = (serializeTerminalToObject(headlessTerminal, startLine, endLine) ||
545
- []).map((line) => line.map((token) => {
546
- token.fg = '';
547
- token.bg = '';
548
- return token;
549
- }));
549
+ ptyProcess.write(input);
550
+ },
551
+ kill: () => {
552
+ killProcessGroup({
553
+ pid: ptyPid,
554
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
555
+ pty: ptyProcess,
556
+ }).catch(() => { });
557
+ try {
558
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
559
+ ptyProcess.destroy?.();
550
560
  }
551
- let lastNonEmptyLine = -1;
552
- for (let i = newOutput.length - 1; i >= 0; i--) {
553
- const line = newOutput[i];
554
- if (line
555
- .map((segment) => segment.text)
556
- .join('')
557
- .trim().length > 0) {
558
- lastNonEmptyLine = i;
559
- break;
560
- }
561
+ catch {
562
+ // Ignore errors during cleanup
561
563
  }
562
- const absoluteCursorY = buffer.baseY + buffer.cursorY;
563
- const cursorRelativeIndex = absoluteCursorY - startLine;
564
- if (cursorRelativeIndex > lastNonEmptyLine) {
565
- lastNonEmptyLine = cursorRelativeIndex;
564
+ this.activePtys.delete(ptyPid);
565
+ },
566
+ isActive: () => {
567
+ try {
568
+ return process.kill(ptyPid, 0);
566
569
  }
567
- const trimmedOutput = newOutput.slice(0, lastNonEmptyLine + 1);
568
- const finalOutput = shellExecutionConfig.disableDynamicLineTrimming
569
- ? newOutput
570
- : trimmedOutput;
571
- if (output !== finalOutput) {
572
- output = finalOutput;
573
- const event = {
574
- type: 'data',
575
- chunk: finalOutput,
576
- };
577
- onOutputEvent(event);
578
- ShellExecutionService.emitEvent(ptyProcess.pid, event);
570
+ catch {
571
+ return false;
579
572
  }
580
- };
581
- const render = (finalRender = false) => {
582
- if (finalRender) {
583
- if (renderTimeout) {
584
- clearTimeout(renderTimeout);
573
+ },
574
+ getBackgroundOutput: () => getFullBufferText(headlessTerminal),
575
+ getSubscriptionSnapshot: () => {
576
+ const endLine = headlessTerminal.buffer.active.length;
577
+ const startLine = Math.max(0, endLine - (shellExecutionConfig.maxSerializedLines ?? 2000));
578
+ const bufferData = serializeTerminalToObject(headlessTerminal, startLine, endLine);
579
+ return bufferData.length > 0 ? bufferData : undefined;
580
+ },
581
+ }).result;
582
+ let processingChain = Promise.resolve();
583
+ let decoder = null;
584
+ let output = null;
585
+ const outputChunks = [];
586
+ const error = null;
587
+ let exited = false;
588
+ let isStreamingRawContent = true;
589
+ const MAX_SNIFF_SIZE = 4096;
590
+ let sniffedBytes = 0;
591
+ let isWriting = false;
592
+ let hasStartedOutput = false;
593
+ let renderTimeout = null;
594
+ const renderFn = () => {
595
+ renderTimeout = null;
596
+ if (!isStreamingRawContent) {
597
+ return;
598
+ }
599
+ if (!shellExecutionConfig.disableDynamicLineTrimming) {
600
+ if (!hasStartedOutput) {
601
+ const bufferText = getFullBufferText(headlessTerminal);
602
+ if (bufferText.trim().length === 0) {
603
+ return;
585
604
  }
586
- renderFn();
587
- return;
605
+ hasStartedOutput = true;
588
606
  }
589
- if (renderTimeout) {
590
- return;
607
+ }
608
+ const buffer = headlessTerminal.buffer.active;
609
+ const endLine = buffer.length;
610
+ const startLine = Math.max(0, endLine - (shellExecutionConfig.maxSerializedLines ?? 2000));
611
+ let newOutput;
612
+ if (shellExecutionConfig.showColor) {
613
+ newOutput = serializeTerminalToObject(headlessTerminal, startLine, endLine);
614
+ }
615
+ else {
616
+ newOutput = (serializeTerminalToObject(headlessTerminal, startLine, endLine) ||
617
+ []).map((line) => line.map((token) => {
618
+ token.fg = '';
619
+ token.bg = '';
620
+ return token;
621
+ }));
622
+ }
623
+ let lastNonEmptyLine = -1;
624
+ for (let i = newOutput.length - 1; i >= 0; i--) {
625
+ const line = newOutput[i];
626
+ if (line
627
+ .map((segment) => segment.text)
628
+ .join('')
629
+ .trim().length > 0) {
630
+ lastNonEmptyLine = i;
631
+ break;
591
632
  }
592
- renderTimeout = setTimeout(() => {
593
- renderFn();
594
- renderTimeout = null;
595
- }, 68);
596
- };
597
- headlessTerminal.onScroll(() => {
598
- if (!isWriting) {
599
- render();
633
+ }
634
+ const absoluteCursorY = buffer.baseY + buffer.cursorY;
635
+ const cursorRelativeIndex = absoluteCursorY - startLine;
636
+ if (cursorRelativeIndex > lastNonEmptyLine) {
637
+ lastNonEmptyLine = cursorRelativeIndex;
638
+ }
639
+ const trimmedOutput = newOutput.slice(0, lastNonEmptyLine + 1);
640
+ const finalOutput = shellExecutionConfig.disableDynamicLineTrimming
641
+ ? newOutput
642
+ : trimmedOutput;
643
+ if (output !== finalOutput) {
644
+ output = finalOutput;
645
+ const event = {
646
+ type: 'data',
647
+ chunk: finalOutput,
648
+ };
649
+ onOutputEvent(event);
650
+ ExecutionLifecycleService.emitEvent(ptyPid, event);
651
+ }
652
+ };
653
+ const render = (finalRender = false) => {
654
+ if (finalRender) {
655
+ if (renderTimeout) {
656
+ clearTimeout(renderTimeout);
600
657
  }
601
- });
602
- const handleOutput = (data) => {
603
- processingChain = processingChain.then(() => new Promise((resolve) => {
604
- if (!decoder) {
605
- const encoding = getCachedEncodingForBuffer(data);
606
- try {
607
- decoder = new TextDecoder(encoding);
608
- }
609
- catch {
610
- decoder = new TextDecoder('utf-8');
611
- }
612
- }
613
- outputChunks.push(data);
614
- if (isStreamingRawContent && sniffedBytes < MAX_SNIFF_SIZE) {
615
- const sniffBuffer = Buffer.concat(outputChunks.slice(0, 20));
616
- sniffedBytes = sniffBuffer.length;
617
- if (isBinary(sniffBuffer)) {
618
- isStreamingRawContent = false;
619
- const event = { type: 'binary_detected' };
620
- onOutputEvent(event);
621
- ShellExecutionService.emitEvent(ptyProcess.pid, event);
622
- }
658
+ renderFn();
659
+ return;
660
+ }
661
+ if (renderTimeout) {
662
+ return;
663
+ }
664
+ renderTimeout = setTimeout(() => {
665
+ renderFn();
666
+ renderTimeout = null;
667
+ }, 68);
668
+ };
669
+ headlessTerminal.onScroll(() => {
670
+ if (!isWriting) {
671
+ render();
672
+ }
673
+ });
674
+ const handleOutput = (data) => {
675
+ processingChain = processingChain.then(() => new Promise((resolveChunk) => {
676
+ if (!decoder) {
677
+ const encoding = getCachedEncodingForBuffer(data);
678
+ try {
679
+ decoder = new TextDecoder(encoding);
623
680
  }
624
- if (isStreamingRawContent) {
625
- const decodedChunk = decoder.decode(data, { stream: true });
626
- if (decodedChunk.length === 0) {
627
- resolve();
628
- return;
629
- }
630
- if (ShellExecutionService.backgroundLogPids.has(ptyProcess.pid)) {
631
- ShellExecutionService.syncBackgroundLog(ptyProcess.pid, decodedChunk);
632
- }
633
- isWriting = true;
634
- headlessTerminal.write(decodedChunk, () => {
635
- render();
636
- isWriting = false;
637
- resolve();
638
- });
681
+ catch {
682
+ decoder = new TextDecoder('utf-8');
639
683
  }
640
- else {
641
- const totalBytes = outputChunks.reduce((sum, chunk) => sum + chunk.length, 0);
642
- const event = {
643
- type: 'binary_progress',
644
- bytesReceived: totalBytes,
645
- };
684
+ }
685
+ outputChunks.push(data);
686
+ if (isStreamingRawContent && sniffedBytes < MAX_SNIFF_SIZE) {
687
+ const sniffBuffer = Buffer.concat(outputChunks.slice(0, 20));
688
+ sniffedBytes = sniffBuffer.length;
689
+ if (isBinary(sniffBuffer)) {
690
+ isStreamingRawContent = false;
691
+ const event = { type: 'binary_detected' };
646
692
  onOutputEvent(event);
647
- ShellExecutionService.emitEvent(ptyProcess.pid, event);
648
- resolve();
693
+ ExecutionLifecycleService.emitEvent(ptyPid, event);
649
694
  }
650
- }));
651
- };
652
- ptyProcess.onData((data) => {
653
- const bufferData = Buffer.from(data, 'utf-8');
654
- handleOutput(bufferData);
655
- });
656
- ptyProcess.onExit(({ exitCode, signal }) => {
657
- exited = true;
658
- abortSignal.removeEventListener('abort', abortHandler);
659
- // Attempt to destroy the PTY to ensure FD is closed
660
- try {
661
- // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
662
- ptyProcess.destroy?.();
663
695
  }
664
- catch {
665
- // Ignore errors during cleanup
666
- }
667
- const finalize = () => {
668
- render(true);
669
- // Store exit info for late subscribers (e.g. backgrounding race condition)
670
- this.exitedPtyInfo.set(ptyProcess.pid, { exitCode, signal });
671
- setTimeout(() => {
672
- this.exitedPtyInfo.delete(ptyProcess.pid);
673
- }, 5 * 60 * 1000).unref();
674
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
675
- ShellExecutionService.cleanupLogStream(ptyProcess.pid).then(() => {
676
- this.activePtys.delete(ptyProcess.pid);
677
- this.activeResolvers.delete(ptyProcess.pid);
678
- const event = {
679
- type: 'exit',
680
- exitCode,
681
- signal: signal ?? null,
682
- };
683
- onOutputEvent(event);
684
- ShellExecutionService.emitEvent(ptyProcess.pid, event);
685
- this.activeListeners.delete(ptyProcess.pid);
686
- const finalBuffer = Buffer.concat(outputChunks);
687
- resolve({
688
- rawOutput: finalBuffer,
689
- output: getFullBufferText(headlessTerminal),
690
- exitCode,
691
- signal: signal ?? null,
692
- error,
693
- aborted: abortSignal.aborted,
694
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
695
- pid: ptyProcess.pid,
696
- executionMethod: ptyInfo?.name ?? 'node-pty',
697
- });
696
+ if (isStreamingRawContent) {
697
+ const decodedChunk = decoder.decode(data, { stream: true });
698
+ if (decodedChunk.length === 0) {
699
+ resolveChunk();
700
+ return;
701
+ }
702
+ if (ShellExecutionService.backgroundLogPids.has(ptyPid)) {
703
+ ShellExecutionService.syncBackgroundLog(ptyPid, decodedChunk);
704
+ }
705
+ isWriting = true;
706
+ headlessTerminal.write(decodedChunk, () => {
707
+ render();
708
+ isWriting = false;
709
+ resolveChunk();
698
710
  });
711
+ }
712
+ else {
713
+ const totalBytes = outputChunks.reduce((sum, chunk) => sum + chunk.length, 0);
714
+ const event = {
715
+ type: 'binary_progress',
716
+ bytesReceived: totalBytes,
717
+ };
718
+ onOutputEvent(event);
719
+ ExecutionLifecycleService.emitEvent(ptyPid, event);
720
+ resolveChunk();
721
+ }
722
+ }));
723
+ };
724
+ ptyProcess.onData((data) => {
725
+ const bufferData = Buffer.from(data, 'utf-8');
726
+ handleOutput(bufferData);
727
+ });
728
+ ptyProcess.onExit(({ exitCode, signal }) => {
729
+ exited = true;
730
+ abortSignal.removeEventListener('abort', abortHandler);
731
+ // Attempt to destroy the PTY to ensure FD is closed
732
+ try {
733
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
734
+ ptyProcess.destroy?.();
735
+ }
736
+ catch {
737
+ // Ignore errors during cleanup
738
+ }
739
+ const finalize = () => {
740
+ render(true);
741
+ const event = {
742
+ type: 'exit',
743
+ exitCode,
744
+ signal: signal ?? null,
699
745
  };
746
+ onOutputEvent(event);
747
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
748
+ ShellExecutionService.cleanupLogStream(ptyPid).then(() => {
749
+ ShellExecutionService.activePtys.delete(ptyPid);
750
+ });
751
+ ExecutionLifecycleService.completeWithResult(ptyPid, {
752
+ rawOutput: Buffer.concat(outputChunks),
753
+ output: getFullBufferText(headlessTerminal),
754
+ exitCode,
755
+ signal: signal ?? null,
756
+ error,
757
+ aborted: abortSignal.aborted,
758
+ pid: ptyPid,
759
+ executionMethod: ptyInfo?.name ?? 'node-pty',
760
+ });
761
+ };
762
+ if (abortSignal.aborted) {
763
+ finalize();
764
+ return;
765
+ }
766
+ const processingComplete = processingChain.then(() => 'processed');
767
+ const abortFired = new Promise((res) => {
700
768
  if (abortSignal.aborted) {
701
- finalize();
769
+ res('aborted');
702
770
  return;
703
771
  }
704
- const processingComplete = processingChain.then(() => 'processed');
705
- const abortFired = new Promise((res) => {
706
- if (abortSignal.aborted) {
707
- res('aborted');
708
- return;
709
- }
710
- abortSignal.addEventListener('abort', () => res('aborted'), {
711
- once: true,
712
- });
713
- });
714
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
715
- Promise.race([processingComplete, abortFired]).then(() => {
716
- finalize();
772
+ abortSignal.addEventListener('abort', () => res('aborted'), {
773
+ once: true,
717
774
  });
718
775
  });
719
- const abortHandler = async () => {
720
- if (ptyProcess.pid && !exited) {
721
- await killProcessGroup({
722
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
723
- pid: ptyProcess.pid,
724
- escalate: true,
725
- isExited: () => exited,
726
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
727
- pty: ptyProcess,
728
- });
729
- }
730
- };
731
- abortSignal.addEventListener('abort', abortHandler, { once: true });
776
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
777
+ Promise.race([processingComplete, abortFired]).then(() => {
778
+ finalize();
779
+ });
732
780
  });
733
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
734
- return { pid: ptyProcess.pid, result };
781
+ const abortHandler = async () => {
782
+ if (ptyProcess.pid && !exited) {
783
+ await killProcessGroup({
784
+ pid: ptyPid,
785
+ escalate: true,
786
+ isExited: () => exited,
787
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
788
+ pty: ptyProcess,
789
+ });
790
+ }
791
+ };
792
+ abortSignal.addEventListener('abort', abortHandler, { once: true });
793
+ return { pid: ptyPid, result };
735
794
  }
736
795
  catch (e) {
737
796
  // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
@@ -775,38 +834,10 @@ export class ShellExecutionService {
775
834
  * @param input The string to write to the terminal.
776
835
  */
777
836
  static writeToPty(pid, input) {
778
- if (this.activeChildProcesses.has(pid)) {
779
- const activeChild = this.activeChildProcesses.get(pid);
780
- if (activeChild) {
781
- activeChild.process.stdin?.write(input);
782
- }
783
- return;
784
- }
785
- if (!this.isPtyActive(pid)) {
786
- return;
787
- }
788
- const activePty = this.activePtys.get(pid);
789
- if (activePty) {
790
- activePty.ptyProcess.write(input);
791
- }
837
+ ExecutionLifecycleService.writeInput(pid, input);
792
838
  }
793
839
  static isPtyActive(pid) {
794
- if (this.activeChildProcesses.has(pid)) {
795
- try {
796
- return process.kill(pid, 0);
797
- }
798
- catch {
799
- return false;
800
- }
801
- }
802
- try {
803
- // process.kill with signal 0 is a way to check for the existence of a process.
804
- // It doesn't actually send a signal.
805
- return process.kill(pid, 0);
806
- }
807
- catch (_) {
808
- return false;
809
- }
840
+ return ExecutionLifecycleService.isActive(pid);
810
841
  }
811
842
  /**
812
843
  * Registers a callback to be invoked when the process with the given PID exits.
@@ -817,36 +848,7 @@ export class ShellExecutionService {
817
848
  * @returns An unsubscribe function.
818
849
  */
819
850
  static onExit(pid, callback) {
820
- const activePty = this.activePtys.get(pid);
821
- if (activePty) {
822
- const disposable = activePty.ptyProcess.onExit(({ exitCode, signal }) => {
823
- callback(exitCode, signal);
824
- disposable.dispose();
825
- });
826
- return () => disposable.dispose();
827
- }
828
- else if (this.activeChildProcesses.has(pid)) {
829
- const activeChild = this.activeChildProcesses.get(pid);
830
- const listener = (code, signal) => {
831
- let signalNumber;
832
- if (signal) {
833
- signalNumber = os.constants.signals[signal];
834
- }
835
- callback(code ?? 0, signalNumber);
836
- };
837
- activeChild?.process.on('exit', listener);
838
- return () => {
839
- activeChild?.process.removeListener('exit', listener);
840
- };
841
- }
842
- else {
843
- // Check if it already exited recently
844
- const exitedInfo = this.exitedPtyInfo.get(pid);
845
- if (exitedInfo) {
846
- callback(exitedInfo.exitCode, exitedInfo.signal);
847
- }
848
- return () => { };
849
- }
851
+ return ExecutionLifecycleService.onExit(pid, callback);
850
852
  }
851
853
  /**
852
854
  * Kills a process by its PID.
@@ -854,25 +856,10 @@ export class ShellExecutionService {
854
856
  * @param pid The process ID to kill.
855
857
  */
856
858
  static async kill(pid) {
857
- const activePty = this.activePtys.get(pid);
858
- const activeChild = this.activeChildProcesses.get(pid);
859
859
  await this.cleanupLogStream(pid);
860
- if (activeChild) {
861
- killProcessGroup({ pid }).catch(() => { });
862
- this.activeChildProcesses.delete(pid);
863
- }
864
- else if (activePty) {
865
- killProcessGroup({ pid, pty: activePty.ptyProcess }).catch(() => { });
866
- try {
867
- activePty.ptyProcess.destroy?.();
868
- }
869
- catch {
870
- // Ignore errors during cleanup
871
- }
872
- this.activePtys.delete(pid);
873
- }
874
- this.activeResolvers.delete(pid);
875
- this.activeListeners.delete(pid);
860
+ this.activePtys.delete(pid);
861
+ this.activeChildProcesses.delete(pid);
862
+ ExecutionLifecycleService.kill(pid);
876
863
  }
877
864
  /**
878
865
  * Moves a running shell command to the background.
@@ -881,17 +868,9 @@ export class ShellExecutionService {
881
868
  * @param pid The process ID of the target PTY.
882
869
  */
883
870
  static background(pid) {
884
- const resolve = this.activeResolvers.get(pid);
885
- if (!resolve)
886
- return;
887
871
  const activePty = this.activePtys.get(pid);
888
872
  const activeChild = this.activeChildProcesses.get(pid);
889
- if (!activePty && !activeChild)
890
- return;
891
- const output = activePty
892
- ? getFullBufferText(activePty.headlessTerminal)
893
- : (activeChild?.state.output ?? '');
894
- const executionMethod = activePty ? 'node-pty' : 'child_process';
873
+ // Set up background logging
895
874
  const logPath = this.getLogFilePath(pid);
896
875
  const logDir = this.getLogDir();
897
876
  try {
@@ -905,6 +884,7 @@ export class ShellExecutionService {
905
884
  writeBufferToLogStream(activePty.headlessTerminal, stream, 0);
906
885
  }
907
886
  else if (activeChild) {
887
+ const output = activeChild.state.output;
908
888
  if (output) {
909
889
  stream.write(stripAnsi(output) + '\n');
910
890
  }
@@ -914,48 +894,10 @@ export class ShellExecutionService {
914
894
  debugLogger.warn('Failed to setup background logging:', e);
915
895
  }
916
896
  this.backgroundLogPids.add(pid);
917
- resolve({
918
- rawOutput: Buffer.from(''),
919
- output,
920
- exitCode: null,
921
- signal: null,
922
- error: null,
923
- aborted: false,
924
- pid,
925
- executionMethod,
926
- backgrounded: true,
927
- });
928
- this.activeResolvers.delete(pid);
897
+ ExecutionLifecycleService.background(pid);
929
898
  }
930
899
  static subscribe(pid, listener) {
931
- if (!this.activeListeners.has(pid)) {
932
- this.activeListeners.set(pid, new Set());
933
- }
934
- this.activeListeners.get(pid)?.add(listener);
935
- // Send current buffer content immediately
936
- const activePty = this.activePtys.get(pid);
937
- const activeChild = this.activeChildProcesses.get(pid);
938
- if (activePty) {
939
- // Use serializeTerminalToObject to preserve colors and structure
940
- const endLine = activePty.headlessTerminal.buffer.active.length;
941
- const startLine = Math.max(0, endLine - (activePty.maxSerializedLines ?? 2000));
942
- const bufferData = serializeTerminalToObject(activePty.headlessTerminal, startLine, endLine);
943
- if (bufferData && bufferData.length > 0) {
944
- listener({ type: 'data', chunk: bufferData });
945
- }
946
- }
947
- else if (activeChild) {
948
- const output = activeChild.state.output;
949
- if (output) {
950
- listener({ type: 'data', chunk: output });
951
- }
952
- }
953
- return () => {
954
- this.activeListeners.get(pid)?.delete(listener);
955
- if (this.activeListeners.get(pid)?.size === 0) {
956
- this.activeListeners.delete(pid);
957
- }
958
- };
900
+ return ExecutionLifecycleService.subscribe(pid, listener);
959
901
  }
960
902
  /**
961
903
  * Resizes the pseudo-terminal (PTY) of a running process.
@@ -997,10 +939,7 @@ export class ShellExecutionService {
997
939
  const startLine = Math.max(0, endLine - (activePty.maxSerializedLines ?? 2000));
998
940
  const bufferData = serializeTerminalToObject(activePty.headlessTerminal, startLine, endLine);
999
941
  const event = { type: 'data', chunk: bufferData };
1000
- const listeners = ShellExecutionService.activeListeners.get(pid);
1001
- if (listeners) {
1002
- listeners.forEach((listener) => listener(event));
1003
- }
942
+ ExecutionLifecycleService.emitEvent(pid, event);
1004
943
  }
1005
944
  }
1006
945
  /**