@google/gemini-cli-core 0.42.0 → 0.43.0-preview.1

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 (342) hide show
  1. package/dist/docs/changelogs/index.md +14 -0
  2. package/dist/docs/changelogs/latest.md +108 -166
  3. package/dist/docs/changelogs/preview.md +227 -103
  4. package/dist/docs/cli/auto-memory.md +60 -38
  5. package/dist/docs/cli/settings.md +1 -1
  6. package/dist/docs/cli/tutorials/memory-management.md +1 -1
  7. package/dist/docs/extensions/releasing.md +58 -24
  8. package/dist/docs/reference/configuration.md +14 -1
  9. package/dist/docs/reference/keyboard-shortcuts.md +23 -0
  10. package/dist/google-gemini-cli-core-0.43.0-preview.0.tgz +0 -0
  11. package/dist/src/agent/content-utils.js +2 -0
  12. package/dist/src/agent/content-utils.js.map +1 -1
  13. package/dist/src/agent/content-utils.test.js +5 -1
  14. package/dist/src/agent/content-utils.test.js.map +1 -1
  15. package/dist/src/agent/event-translator.js +7 -6
  16. package/dist/src/agent/event-translator.js.map +1 -1
  17. package/dist/src/agent/legacy-agent-session.js +4 -0
  18. package/dist/src/agent/legacy-agent-session.js.map +1 -1
  19. package/dist/src/agent/legacy-agent-session.test.js +9 -1
  20. package/dist/src/agent/legacy-agent-session.test.js.map +1 -1
  21. package/dist/src/agent/tool-display-utils.d.ts +3 -2
  22. package/dist/src/agent/tool-display-utils.js +3 -2
  23. package/dist/src/agent/tool-display-utils.js.map +1 -1
  24. package/dist/src/agent/types.d.ts +33 -3
  25. package/dist/src/agents/a2aUtils.d.ts +1 -1
  26. package/dist/src/agents/a2aUtils.js +4 -3
  27. package/dist/src/agents/a2aUtils.js.map +1 -1
  28. package/dist/src/agents/agentLoader.d.ts +2 -2
  29. package/dist/src/agents/browser/browserAgentInvocation.js +24 -19
  30. package/dist/src/agents/browser/browserAgentInvocation.js.map +1 -1
  31. package/dist/src/agents/local-executor.d.ts +1 -0
  32. package/dist/src/agents/local-executor.js +46 -32
  33. package/dist/src/agents/local-executor.js.map +1 -1
  34. package/dist/src/agents/local-executor.test.js +127 -0
  35. package/dist/src/agents/local-executor.test.js.map +1 -1
  36. package/dist/src/agents/local-invocation.js +24 -20
  37. package/dist/src/agents/local-invocation.js.map +1 -1
  38. package/dist/src/agents/local-invocation.test.js +9 -9
  39. package/dist/src/agents/local-invocation.test.js.map +1 -1
  40. package/dist/src/agents/local-subagent-protocol.d.ts +18 -0
  41. package/dist/src/agents/local-subagent-protocol.js +357 -0
  42. package/dist/src/agents/local-subagent-protocol.js.map +1 -0
  43. package/dist/src/agents/local-subagent-protocol.test.d.ts +6 -0
  44. package/dist/src/agents/local-subagent-protocol.test.js +676 -0
  45. package/dist/src/agents/local-subagent-protocol.test.js.map +1 -0
  46. package/dist/src/agents/remote-invocation.js +6 -6
  47. package/dist/src/agents/remote-invocation.js.map +1 -1
  48. package/dist/src/agents/remote-invocation.test.js +23 -12
  49. package/dist/src/agents/remote-invocation.test.js.map +1 -1
  50. package/dist/src/agents/remote-subagent-protocol.d.ts +31 -0
  51. package/dist/src/agents/remote-subagent-protocol.js +330 -0
  52. package/dist/src/agents/remote-subagent-protocol.js.map +1 -0
  53. package/dist/src/agents/remote-subagent-protocol.test.d.ts +6 -0
  54. package/dist/src/agents/remote-subagent-protocol.test.js +652 -0
  55. package/dist/src/agents/remote-subagent-protocol.test.js.map +1 -0
  56. package/dist/src/agents/skill-extraction-agent.js +1 -0
  57. package/dist/src/agents/skill-extraction-agent.js.map +1 -1
  58. package/dist/src/agents/skill-extraction-agent.test.js +1 -0
  59. package/dist/src/agents/skill-extraction-agent.test.js.map +1 -1
  60. package/dist/src/agents/types.d.ts +13 -2
  61. package/dist/src/agents/types.js +7 -0
  62. package/dist/src/agents/types.js.map +1 -1
  63. package/dist/src/availability/modelAvailabilityService.d.ts +6 -6
  64. package/dist/src/availability/modelAvailabilityService.js +14 -7
  65. package/dist/src/availability/modelAvailabilityService.js.map +1 -1
  66. package/dist/src/availability/modelAvailabilityService.test.js +34 -0
  67. package/dist/src/availability/modelAvailabilityService.test.js.map +1 -1
  68. package/dist/src/availability/policyHelpers.js +24 -12
  69. package/dist/src/availability/policyHelpers.js.map +1 -1
  70. package/dist/src/availability/policyHelpers.test.js +3 -2
  71. package/dist/src/availability/policyHelpers.test.js.map +1 -1
  72. package/dist/src/code_assist/oauth2.js +3 -0
  73. package/dist/src/code_assist/oauth2.js.map +1 -1
  74. package/dist/src/code_assist/setup.d.ts +3 -0
  75. package/dist/src/code_assist/setup.js +9 -0
  76. package/dist/src/code_assist/setup.js.map +1 -1
  77. package/dist/src/code_assist/setup.test.js +9 -1
  78. package/dist/src/code_assist/setup.test.js.map +1 -1
  79. package/dist/src/commands/memory.d.ts +5 -14
  80. package/dist/src/commands/memory.js +19 -141
  81. package/dist/src/commands/memory.js.map +1 -1
  82. package/dist/src/commands/memory.test.js +62 -0
  83. package/dist/src/commands/memory.test.js.map +1 -1
  84. package/dist/src/config/config.d.ts +6 -2
  85. package/dist/src/config/config.js +63 -8
  86. package/dist/src/config/config.js.map +1 -1
  87. package/dist/src/config/config.test.js +48 -0
  88. package/dist/src/config/config.test.js.map +1 -1
  89. package/dist/src/config/defaultModelConfigs.js +13 -0
  90. package/dist/src/config/defaultModelConfigs.js.map +1 -1
  91. package/dist/src/config/models.js +1 -1
  92. package/dist/src/config/models.js.map +1 -1
  93. package/dist/src/config/projectRegistry.d.ts +1 -0
  94. package/dist/src/config/projectRegistry.js +13 -2
  95. package/dist/src/config/projectRegistry.js.map +1 -1
  96. package/dist/src/config/projectRegistry.test.js +43 -0
  97. package/dist/src/config/projectRegistry.test.js.map +1 -1
  98. package/dist/src/context/config/profiles.js +4 -0
  99. package/dist/src/context/config/profiles.js.map +1 -1
  100. package/dist/src/context/contextManager.barrier.test.js +4 -3
  101. package/dist/src/context/contextManager.barrier.test.js.map +1 -1
  102. package/dist/src/context/contextManager.d.ts +9 -2
  103. package/dist/src/context/contextManager.hotstart.test.d.ts +6 -0
  104. package/dist/src/context/contextManager.hotstart.test.js +61 -0
  105. package/dist/src/context/contextManager.hotstart.test.js.map +1 -0
  106. package/dist/src/context/contextManager.js +96 -21
  107. package/dist/src/context/contextManager.js.map +1 -1
  108. package/dist/src/context/eventBus.d.ts +6 -0
  109. package/dist/src/context/eventBus.js +6 -0
  110. package/dist/src/context/eventBus.js.map +1 -1
  111. package/dist/src/context/graph/render.d.ts +3 -1
  112. package/dist/src/context/graph/render.js +33 -9
  113. package/dist/src/context/graph/render.js.map +1 -1
  114. package/dist/src/context/graph/render.test.d.ts +6 -0
  115. package/dist/src/context/graph/render.test.js +203 -0
  116. package/dist/src/context/graph/render.test.js.map +1 -0
  117. package/dist/src/context/graph/toGraph.js +3 -3
  118. package/dist/src/context/graph/toGraph.js.map +1 -1
  119. package/dist/src/context/graph/toGraph.test.d.ts +6 -0
  120. package/dist/src/context/graph/toGraph.test.js +35 -0
  121. package/dist/src/context/graph/toGraph.test.js.map +1 -0
  122. package/dist/src/context/initializer.js +11 -2
  123. package/dist/src/context/initializer.js.map +1 -1
  124. package/dist/src/context/pipeline/contextWorkingBuffer.d.ts +5 -7
  125. package/dist/src/context/pipeline/contextWorkingBuffer.js +105 -29
  126. package/dist/src/context/pipeline/contextWorkingBuffer.js.map +1 -1
  127. package/dist/src/context/pipeline/contextWorkingBuffer.test.js +67 -0
  128. package/dist/src/context/pipeline/contextWorkingBuffer.test.js.map +1 -1
  129. package/dist/src/context/pipeline/environment.d.ts +4 -0
  130. package/dist/src/context/pipeline/environmentImpl.d.ts +6 -5
  131. package/dist/src/context/pipeline/environmentImpl.js +6 -8
  132. package/dist/src/context/pipeline/environmentImpl.js.map +1 -1
  133. package/dist/src/context/pipeline/environmentImpl.test.js +5 -1
  134. package/dist/src/context/pipeline/environmentImpl.test.js.map +1 -1
  135. package/dist/src/context/pipeline/orchestrator.d.ts +1 -0
  136. package/dist/src/context/pipeline/orchestrator.js +59 -24
  137. package/dist/src/context/pipeline/orchestrator.js.map +1 -1
  138. package/dist/src/context/processors/blobDegradationProcessor.test.js +2 -2
  139. package/dist/src/context/processors/rollingSummaryProcessor.js +2 -15
  140. package/dist/src/context/processors/rollingSummaryProcessor.js.map +1 -1
  141. package/dist/src/context/processors/stateSnapshotAsyncProcessor.d.ts +7 -0
  142. package/dist/src/context/processors/stateSnapshotAsyncProcessor.js +33 -21
  143. package/dist/src/context/processors/stateSnapshotAsyncProcessor.js.map +1 -1
  144. package/dist/src/context/processors/stateSnapshotAsyncProcessor.test.js +31 -7
  145. package/dist/src/context/processors/stateSnapshotAsyncProcessor.test.js.map +1 -1
  146. package/dist/src/context/processors/stateSnapshotProcessor.d.ts +2 -0
  147. package/dist/src/context/processors/stateSnapshotProcessor.js +28 -4
  148. package/dist/src/context/processors/stateSnapshotProcessor.js.map +1 -1
  149. package/dist/src/context/processors/stateSnapshotProcessor.test.js +40 -1
  150. package/dist/src/context/processors/stateSnapshotProcessor.test.js.map +1 -1
  151. package/dist/src/context/system-tests/hysteresis.test.d.ts +6 -0
  152. package/dist/src/context/system-tests/hysteresis.test.js +98 -0
  153. package/dist/src/context/system-tests/hysteresis.test.js.map +1 -0
  154. package/dist/src/context/system-tests/lifecycle.golden.test.js +90 -69
  155. package/dist/src/context/system-tests/lifecycle.golden.test.js.map +1 -1
  156. package/dist/src/context/system-tests/simulationHarness.d.ts +1 -4
  157. package/dist/src/context/system-tests/simulationHarness.js +16 -30
  158. package/dist/src/context/system-tests/simulationHarness.js.map +1 -1
  159. package/dist/src/context/testing/contextTestUtils.d.ts +1 -0
  160. package/dist/src/context/testing/contextTestUtils.js +48 -25
  161. package/dist/src/context/testing/contextTestUtils.js.map +1 -1
  162. package/dist/src/context/utils/adaptiveTokenCalculator.d.ts +61 -0
  163. package/dist/src/context/utils/adaptiveTokenCalculator.js +116 -0
  164. package/dist/src/context/utils/adaptiveTokenCalculator.js.map +1 -0
  165. package/dist/src/context/utils/adaptiveTokenCalculator.test.d.ts +6 -0
  166. package/dist/src/context/utils/adaptiveTokenCalculator.test.js +85 -0
  167. package/dist/src/context/utils/adaptiveTokenCalculator.test.js.map +1 -0
  168. package/dist/src/context/utils/contextTokenCalculator.d.ts +47 -1
  169. package/dist/src/context/utils/contextTokenCalculator.js +20 -3
  170. package/dist/src/context/utils/contextTokenCalculator.js.map +1 -1
  171. package/dist/src/context/utils/contextTokenCalculator.test.js +8 -8
  172. package/dist/src/context/utils/contextTokenCalculator.test.js.map +1 -1
  173. package/dist/src/context/utils/formatNodesForLlm.d.ts +21 -0
  174. package/dist/src/context/utils/formatNodesForLlm.js +69 -0
  175. package/dist/src/context/utils/formatNodesForLlm.js.map +1 -0
  176. package/dist/src/context/utils/formatNodesForLlm.test.d.ts +6 -0
  177. package/dist/src/context/utils/formatNodesForLlm.test.js +110 -0
  178. package/dist/src/context/utils/formatNodesForLlm.test.js.map +1 -0
  179. package/dist/src/context/utils/snapshotGenerator.d.ts +23 -1
  180. package/dist/src/context/utils/snapshotGenerator.js +249 -31
  181. package/dist/src/context/utils/snapshotGenerator.js.map +1 -1
  182. package/dist/src/context/utils/snapshotGenerator.test.d.ts +6 -0
  183. package/dist/src/context/utils/snapshotGenerator.test.js +255 -0
  184. package/dist/src/context/utils/snapshotGenerator.test.js.map +1 -0
  185. package/dist/src/context/utils/tokenCalibration.d.ts +9 -0
  186. package/dist/src/context/utils/tokenCalibration.js +30 -0
  187. package/dist/src/context/utils/tokenCalibration.js.map +1 -0
  188. package/dist/src/core/baseLlmClient.d.ts +8 -0
  189. package/dist/src/core/baseLlmClient.js +14 -0
  190. package/dist/src/core/baseLlmClient.js.map +1 -1
  191. package/dist/src/core/client.js +12 -1
  192. package/dist/src/core/client.js.map +1 -1
  193. package/dist/src/core/client.test.js +4 -4
  194. package/dist/src/core/contentGenerator.js +12 -10
  195. package/dist/src/core/contentGenerator.js.map +1 -1
  196. package/dist/src/core/geminiChat.d.ts +2 -0
  197. package/dist/src/core/geminiChat.js +60 -5
  198. package/dist/src/core/geminiChat.js.map +1 -1
  199. package/dist/src/core/geminiChat.test.js +195 -2
  200. package/dist/src/core/geminiChat.test.js.map +1 -1
  201. package/dist/src/core/turn.js +30 -2
  202. package/dist/src/core/turn.js.map +1 -1
  203. package/dist/src/core/turn.test.js +13 -8
  204. package/dist/src/core/turn.test.js.map +1 -1
  205. package/dist/src/generated/git-commit.d.ts +2 -2
  206. package/dist/src/generated/git-commit.js +2 -2
  207. package/dist/src/generated/git-commit.js.map +1 -1
  208. package/dist/src/hooks/hookEventHandler.js +3 -2
  209. package/dist/src/hooks/hookEventHandler.js.map +1 -1
  210. package/dist/src/hooks/hookEventHandler.test.js +80 -0
  211. package/dist/src/hooks/hookEventHandler.test.js.map +1 -1
  212. package/dist/src/index.d.ts +2 -1
  213. package/dist/src/index.js +2 -2
  214. package/dist/src/index.js.map +1 -1
  215. package/dist/src/prompts/snippets.js +1 -1
  216. package/dist/src/prompts/snippets.js.map +1 -1
  217. package/dist/src/routing/strategies/approvalModeStrategy.js +5 -3
  218. package/dist/src/routing/strategies/approvalModeStrategy.js.map +1 -1
  219. package/dist/src/routing/strategies/approvalModeStrategy.test.js +2 -0
  220. package/dist/src/routing/strategies/approvalModeStrategy.test.js.map +1 -1
  221. package/dist/src/routing/strategies/classifierStrategy.js +8 -1
  222. package/dist/src/routing/strategies/classifierStrategy.js.map +1 -1
  223. package/dist/src/routing/strategies/classifierStrategy.test.js +4 -0
  224. package/dist/src/routing/strategies/classifierStrategy.test.js.map +1 -1
  225. package/dist/src/routing/strategies/gemmaClassifierStrategy.js +7 -1
  226. package/dist/src/routing/strategies/gemmaClassifierStrategy.js.map +1 -1
  227. package/dist/src/routing/strategies/gemmaClassifierStrategy.test.js +4 -0
  228. package/dist/src/routing/strategies/gemmaClassifierStrategy.test.js.map +1 -1
  229. package/dist/src/routing/strategies/numericalClassifierStrategy.d.ts +1 -0
  230. package/dist/src/routing/strategies/numericalClassifierStrategy.js +22 -3
  231. package/dist/src/routing/strategies/numericalClassifierStrategy.js.map +1 -1
  232. package/dist/src/routing/strategies/numericalClassifierStrategy.test.js +168 -23
  233. package/dist/src/routing/strategies/numericalClassifierStrategy.test.js.map +1 -1
  234. package/dist/src/scheduler/scheduler.js +11 -0
  235. package/dist/src/scheduler/scheduler.js.map +1 -1
  236. package/dist/src/scheduler/scheduler.test.js +1 -1
  237. package/dist/src/scheduler/scheduler.test.js.map +1 -1
  238. package/dist/src/scheduler/state-manager.js +5 -1
  239. package/dist/src/scheduler/state-manager.js.map +1 -1
  240. package/dist/src/scheduler/tool-executor.js +7 -4
  241. package/dist/src/scheduler/tool-executor.js.map +1 -1
  242. package/dist/src/scheduler/types.d.ts +5 -1
  243. package/dist/src/scheduler/types.js.map +1 -1
  244. package/dist/src/services/fileDiscoveryService.js +2 -1
  245. package/dist/src/services/fileDiscoveryService.js.map +1 -1
  246. package/dist/src/services/fileDiscoveryService.test.js +36 -0
  247. package/dist/src/services/fileDiscoveryService.test.js.map +1 -1
  248. package/dist/src/services/gitService.js +8 -1
  249. package/dist/src/services/gitService.js.map +1 -1
  250. package/dist/src/services/gitService.test.js +104 -0
  251. package/dist/src/services/gitService.test.js.map +1 -1
  252. package/dist/src/services/keychainService.js +14 -5
  253. package/dist/src/services/keychainService.js.map +1 -1
  254. package/dist/src/services/memoryPatchUtils.d.ts +66 -4
  255. package/dist/src/services/memoryPatchUtils.js +267 -5
  256. package/dist/src/services/memoryPatchUtils.js.map +1 -1
  257. package/dist/src/services/memoryService.js +25 -1
  258. package/dist/src/services/memoryService.js.map +1 -1
  259. package/dist/src/services/memoryService.test.js +61 -1
  260. package/dist/src/services/memoryService.test.js.map +1 -1
  261. package/dist/src/services/test-data/resolved-aliases-retry.golden.json +11 -0
  262. package/dist/src/services/test-data/resolved-aliases.golden.json +11 -0
  263. package/dist/src/telemetry/gcp-exporters.d.ts +2 -0
  264. package/dist/src/telemetry/gcp-exporters.js +69 -5
  265. package/dist/src/telemetry/gcp-exporters.js.map +1 -1
  266. package/dist/src/telemetry/gcp-exporters.test.js +52 -0
  267. package/dist/src/telemetry/gcp-exporters.test.js.map +1 -1
  268. package/dist/src/telemetry/metrics.js +13 -2
  269. package/dist/src/telemetry/metrics.js.map +1 -1
  270. package/dist/src/telemetry/metrics.test.js +61 -1
  271. package/dist/src/telemetry/metrics.test.js.map +1 -1
  272. package/dist/src/tools/definitions/model-family-sets/default-legacy.js +5 -2
  273. package/dist/src/tools/definitions/model-family-sets/default-legacy.js.map +1 -1
  274. package/dist/src/tools/definitions/model-family-sets/gemini-3.js +7 -4
  275. package/dist/src/tools/definitions/model-family-sets/gemini-3.js.map +1 -1
  276. package/dist/src/tools/edit.js +19 -0
  277. package/dist/src/tools/edit.js.map +1 -1
  278. package/dist/src/tools/edit.test.js +9 -0
  279. package/dist/src/tools/edit.test.js.map +1 -1
  280. package/dist/src/tools/grep.js +13 -1
  281. package/dist/src/tools/grep.js.map +1 -1
  282. package/dist/src/tools/ls.js +5 -0
  283. package/dist/src/tools/ls.js.map +1 -1
  284. package/dist/src/tools/mcp-client.js +17 -3
  285. package/dist/src/tools/mcp-client.js.map +1 -1
  286. package/dist/src/tools/mcp-client.test.js +24 -0
  287. package/dist/src/tools/mcp-client.test.js.map +1 -1
  288. package/dist/src/tools/read-file.js +11 -6
  289. package/dist/src/tools/read-file.js.map +1 -1
  290. package/dist/src/tools/read-file.test.js +20 -8
  291. package/dist/src/tools/read-file.test.js.map +1 -1
  292. package/dist/src/tools/ripGrep.js +13 -1
  293. package/dist/src/tools/ripGrep.js.map +1 -1
  294. package/dist/src/tools/shell.js +14 -0
  295. package/dist/src/tools/shell.js.map +1 -1
  296. package/dist/src/tools/shell.test.js +5 -0
  297. package/dist/src/tools/shell.test.js.map +1 -1
  298. package/dist/src/tools/tools.d.ts +6 -0
  299. package/dist/src/tools/tools.js.map +1 -1
  300. package/dist/src/tools/topicTool.js +5 -0
  301. package/dist/src/tools/topicTool.js.map +1 -1
  302. package/dist/src/tools/write-file.js +13 -0
  303. package/dist/src/tools/write-file.js.map +1 -1
  304. package/dist/src/tools/write-file.test.js +8 -0
  305. package/dist/src/tools/write-file.test.js.map +1 -1
  306. package/dist/src/utils/errors.js +3 -8
  307. package/dist/src/utils/errors.js.map +1 -1
  308. package/dist/src/utils/filesearch/ignore.js +4 -1
  309. package/dist/src/utils/filesearch/ignore.js.map +1 -1
  310. package/dist/src/utils/ignoreFileParser.js +1 -1
  311. package/dist/src/utils/ignoreFileParser.js.map +1 -1
  312. package/dist/src/utils/ignorePatterns.js +2 -0
  313. package/dist/src/utils/ignorePatterns.js.map +1 -1
  314. package/dist/src/utils/ignorePatterns.test.js +1 -0
  315. package/dist/src/utils/ignorePatterns.test.js.map +1 -1
  316. package/dist/src/utils/memoryDiscovery.js +55 -40
  317. package/dist/src/utils/memoryDiscovery.js.map +1 -1
  318. package/dist/src/utils/memoryDiscovery.test.js +77 -9
  319. package/dist/src/utils/memoryDiscovery.test.js.map +1 -1
  320. package/dist/src/utils/modelUtils.d.ts +14 -0
  321. package/dist/src/utils/modelUtils.js +17 -0
  322. package/dist/src/utils/modelUtils.js.map +1 -0
  323. package/dist/src/utils/modelUtils.test.d.ts +6 -0
  324. package/dist/src/utils/modelUtils.test.js +23 -0
  325. package/dist/src/utils/modelUtils.test.js.map +1 -0
  326. package/dist/src/utils/paths.d.ts +15 -1
  327. package/dist/src/utils/paths.js +22 -7
  328. package/dist/src/utils/paths.js.map +1 -1
  329. package/dist/src/utils/paths.test.js +25 -1
  330. package/dist/src/utils/paths.test.js.map +1 -1
  331. package/dist/src/utils/quotaErrorDetection.js +23 -12
  332. package/dist/src/utils/quotaErrorDetection.js.map +1 -1
  333. package/dist/src/utils/shell-utils.js +9 -1
  334. package/dist/src/utils/shell-utils.js.map +1 -1
  335. package/dist/src/utils/shell-utils.test.js +7 -3
  336. package/dist/src/utils/shell-utils.test.js.map +1 -1
  337. package/dist/src/utils/tokenCalculation.js +2 -1
  338. package/dist/src/utils/tokenCalculation.js.map +1 -1
  339. package/dist/src/utils/tokenCalculation.test.js +15 -0
  340. package/dist/src/utils/tokenCalculation.test.js.map +1 -1
  341. package/dist/tsconfig.tsbuildinfo +1 -1
  342. package/package.json +1 -1
@@ -0,0 +1,676 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
7
+ import { LocalSubagentSession } from './local-subagent-protocol.js';
8
+ import { LocalAgentExecutor } from './local-executor.js';
9
+ import { AgentTerminateMode, } from './types.js';
10
+ import { makeFakeConfig } from '../test-utils/config.js';
11
+ import { createMockMessageBus } from '../test-utils/mock-message-bus.js';
12
+ vi.mock('./local-executor.js');
13
+ const MockLocalAgentExecutor = vi.mocked(LocalAgentExecutor);
14
+ // Captures the onActivity callback passed to LocalAgentExecutor.create().
15
+ // Set via create.mockImplementation in beforeEach to avoid mock.calls index fragility.
16
+ let capturedOnActivity;
17
+ const testDefinition = {
18
+ kind: 'local',
19
+ name: 'TestProtocolAgent',
20
+ description: 'A test agent for protocol tests.',
21
+ inputConfig: {
22
+ inputSchema: {
23
+ type: 'object',
24
+ properties: {
25
+ task: { type: 'string' },
26
+ priority: { type: 'number' },
27
+ },
28
+ },
29
+ },
30
+ modelConfig: { model: 'test', generateContentConfig: {} },
31
+ runConfig: { maxTimeMinutes: 1 },
32
+ promptConfig: { systemPrompt: 'test' },
33
+ };
34
+ const GOAL_OUTPUT = {
35
+ result: 'Analysis complete.',
36
+ terminate_reason: AgentTerminateMode.GOAL,
37
+ };
38
+ describe('LocalSubagentSession (protocol)', () => {
39
+ let mockContext;
40
+ let mockMessageBus;
41
+ let mockExecutorInstance;
42
+ beforeEach(() => {
43
+ vi.clearAllMocks();
44
+ capturedOnActivity = undefined;
45
+ mockContext = makeFakeConfig();
46
+ mockMessageBus = createMockMessageBus();
47
+ mockExecutorInstance = {
48
+ run: vi.fn().mockResolvedValue(GOAL_OUTPUT),
49
+ definition: testDefinition,
50
+ };
51
+ // Use mockImplementation (not mockResolvedValue) so we can capture onActivity.
52
+ MockLocalAgentExecutor.create.mockImplementation(
53
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
54
+ async (_def, _ctx, onActivity) => {
55
+ capturedOnActivity = onActivity;
56
+ return mockExecutorInstance;
57
+ });
58
+ });
59
+ afterEach(() => {
60
+ vi.restoreAllMocks();
61
+ });
62
+ // ---------------------------------------------------------------------------
63
+ // Lifecycle events
64
+ // ---------------------------------------------------------------------------
65
+ describe('lifecycle events', () => {
66
+ it('emits agent_start then agent_end(completed) for a GOAL run', async () => {
67
+ const session = new LocalSubagentSession(testDefinition, mockContext, mockMessageBus);
68
+ const events = [];
69
+ session.subscribe((e) => events.push(e));
70
+ await session.send({
71
+ message: { content: [{ type: 'text', text: 'query' }] },
72
+ });
73
+ await session.getResult();
74
+ expect(events[0].type).toBe('agent_start');
75
+ expect(events[events.length - 1].type).toBe('agent_end');
76
+ const endEvent = events[events.length - 1];
77
+ if (endEvent.type === 'agent_end') {
78
+ expect(endEvent.reason).toBe('completed');
79
+ }
80
+ });
81
+ it('emits agent_start exactly once even if ensureAgentStart called twice internally', async () => {
82
+ const session = new LocalSubagentSession(testDefinition, mockContext, mockMessageBus);
83
+ const events = [];
84
+ session.subscribe((e) => events.push(e));
85
+ await session.send({
86
+ message: { content: [{ type: 'text', text: 'query' }] },
87
+ });
88
+ await session.getResult();
89
+ const startEvents = events.filter((e) => e.type === 'agent_start');
90
+ expect(startEvents).toHaveLength(1);
91
+ });
92
+ it('emits agent_end exactly once on error path', async () => {
93
+ const session = new LocalSubagentSession(testDefinition, mockContext, mockMessageBus);
94
+ mockExecutorInstance.run.mockRejectedValue(new Error('executor failed'));
95
+ const events = [];
96
+ session.subscribe((e) => events.push(e));
97
+ await session.send({
98
+ message: { content: [{ type: 'text', text: 'query' }] },
99
+ });
100
+ await expect(session.getResult()).rejects.toThrow('executor failed');
101
+ const endEvents = events.filter((e) => e.type === 'agent_end');
102
+ expect(endEvents).toHaveLength(1);
103
+ });
104
+ it('all events share the same streamId', async () => {
105
+ const session = new LocalSubagentSession(testDefinition, mockContext, mockMessageBus);
106
+ const events = [];
107
+ session.subscribe((e) => events.push(e));
108
+ await session.send({
109
+ message: { content: [{ type: 'text', text: 'query' }] },
110
+ });
111
+ await session.getResult();
112
+ const streamIds = new Set(events.map((e) => e.streamId));
113
+ expect(streamIds.size).toBe(1);
114
+ });
115
+ });
116
+ // ---------------------------------------------------------------------------
117
+ // Config buffering (update + message pattern)
118
+ // ---------------------------------------------------------------------------
119
+ describe('config buffering', () => {
120
+ it('merges buffered config with message query', async () => {
121
+ const session = new LocalSubagentSession(testDefinition, mockContext, mockMessageBus);
122
+ await session.send({
123
+ update: { config: { task: 'analyze', priority: 5 } },
124
+ });
125
+ await session.send({
126
+ message: { content: [{ type: 'text', text: 'my query' }] },
127
+ });
128
+ await session.getResult();
129
+ expect(mockExecutorInstance.run).toHaveBeenCalledWith({ task: 'analyze', priority: 5, query: 'my query' }, expect.any(AbortSignal));
130
+ });
131
+ it('omits query key when message text is empty', async () => {
132
+ const session = new LocalSubagentSession(testDefinition, mockContext, mockMessageBus);
133
+ await session.send({ update: { config: { task: 'no-query-task' } } });
134
+ await session.send({
135
+ message: { content: [{ type: 'text', text: '' }] },
136
+ });
137
+ await session.getResult();
138
+ const callArgs = mockExecutorInstance.run.mock.calls[0][0];
139
+ expect(callArgs).not.toHaveProperty('query');
140
+ expect(callArgs).toEqual({ task: 'no-query-task' });
141
+ });
142
+ it('sends only query when no prior update', async () => {
143
+ const session = new LocalSubagentSession(testDefinition, mockContext, mockMessageBus);
144
+ await session.send({
145
+ message: { content: [{ type: 'text', text: 'just a query' }] },
146
+ });
147
+ await session.getResult();
148
+ expect(mockExecutorInstance.run).toHaveBeenCalledWith({ query: 'just a query' }, expect.any(AbortSignal));
149
+ });
150
+ it('multiple update calls are merged', async () => {
151
+ const session = new LocalSubagentSession(testDefinition, mockContext, mockMessageBus);
152
+ await session.send({ update: { config: { field1: 'a' } } });
153
+ await session.send({ update: { config: { field2: 'b' } } });
154
+ await session.send({
155
+ message: { content: [{ type: 'text', text: 'q' }] },
156
+ });
157
+ await session.getResult();
158
+ expect(mockExecutorInstance.run).toHaveBeenCalledWith({ field1: 'a', field2: 'b', query: 'q' }, expect.any(AbortSignal));
159
+ });
160
+ it('update returns streamId: null; message returns a streamId', async () => {
161
+ const session = new LocalSubagentSession(testDefinition, mockContext, mockMessageBus);
162
+ const updateResult = await session.send({ update: { config: {} } });
163
+ expect(updateResult.streamId).toBeNull();
164
+ const messageResult = await session.send({
165
+ message: { content: [{ type: 'text', text: 'q' }] },
166
+ });
167
+ expect(messageResult.streamId).not.toBeNull();
168
+ expect(typeof messageResult.streamId).toBe('string');
169
+ // Await completion to prevent dangling execution affecting subsequent tests
170
+ await session.getResult();
171
+ });
172
+ });
173
+ // ---------------------------------------------------------------------------
174
+ // Activity translation
175
+ // ---------------------------------------------------------------------------
176
+ describe('activity translation', () => {
177
+ function makeSession() {
178
+ const activityEvents = [];
179
+ const session = new LocalSubagentSession(testDefinition, mockContext, mockMessageBus);
180
+ return { session, activityEvents };
181
+ }
182
+ async function runWithActivities(session, activities) {
183
+ mockExecutorInstance.run.mockImplementation(async () => {
184
+ // capturedOnActivity is set by the create.mockImplementation in beforeEach
185
+ // and updated whenever create() is called. By the time run() is called,
186
+ // capturedOnActivity holds the onActivity closure for the most-recently
187
+ // created executor — which is the one associated with this session.
188
+ for (const act of activities) {
189
+ capturedOnActivity?.(act);
190
+ }
191
+ return GOAL_OUTPUT;
192
+ });
193
+ const events = [];
194
+ session.subscribe((e) => events.push(e));
195
+ await session.send({
196
+ message: { content: [{ type: 'text', text: 'q' }] },
197
+ });
198
+ await session.getResult();
199
+ return events;
200
+ }
201
+ it('THOUGHT_CHUNK → message event with thought content', async () => {
202
+ const { session } = makeSession();
203
+ const events = await runWithActivities(session, [
204
+ {
205
+ isSubagentActivityEvent: true,
206
+ agentName: 'TestProtocolAgent',
207
+ type: 'THOUGHT_CHUNK',
208
+ data: { text: 'I am thinking...' },
209
+ },
210
+ ]);
211
+ const msgEvent = events.find((e) => e.type === 'message');
212
+ expect(msgEvent).toBeDefined();
213
+ if (msgEvent?.type === 'message') {
214
+ expect(msgEvent.role).toBe('agent');
215
+ expect(msgEvent.content).toContainEqual({
216
+ type: 'thought',
217
+ thought: 'I am thinking...',
218
+ });
219
+ }
220
+ });
221
+ it('TOOL_CALL_START → tool_request event', async () => {
222
+ const { session } = makeSession();
223
+ const events = await runWithActivities(session, [
224
+ {
225
+ isSubagentActivityEvent: true,
226
+ agentName: 'TestProtocolAgent',
227
+ type: 'TOOL_CALL_START',
228
+ data: { callId: 'call-123', name: 'read_file', args: { path: '/a' } },
229
+ },
230
+ ]);
231
+ const reqEvent = events.find((e) => e.type === 'tool_request');
232
+ expect(reqEvent).toBeDefined();
233
+ if (reqEvent?.type === 'tool_request') {
234
+ expect(reqEvent.requestId).toBe('call-123');
235
+ expect(reqEvent.name).toBe('read_file');
236
+ expect(reqEvent.args).toEqual({ path: '/a' });
237
+ }
238
+ });
239
+ it('TOOL_CALL_END → tool_response event', async () => {
240
+ const { session } = makeSession();
241
+ const events = await runWithActivities(session, [
242
+ {
243
+ isSubagentActivityEvent: true,
244
+ agentName: 'TestProtocolAgent',
245
+ type: 'TOOL_CALL_END',
246
+ data: { id: 'call-123', name: 'read_file', output: 'file contents' },
247
+ },
248
+ ]);
249
+ const respEvent = events.find((e) => e.type === 'tool_response');
250
+ expect(respEvent).toBeDefined();
251
+ if (respEvent?.type === 'tool_response') {
252
+ expect(respEvent.requestId).toBe('call-123');
253
+ expect(respEvent.name).toBe('read_file');
254
+ expect(respEvent.content).toContainEqual({
255
+ type: 'text',
256
+ text: 'file contents',
257
+ });
258
+ }
259
+ });
260
+ it('ERROR activity → error event with INTERNAL status, fatal: false', async () => {
261
+ const { session } = makeSession();
262
+ const events = await runWithActivities(session, [
263
+ {
264
+ isSubagentActivityEvent: true,
265
+ agentName: 'TestProtocolAgent',
266
+ type: 'ERROR',
267
+ data: { error: 'something went wrong' },
268
+ },
269
+ ]);
270
+ const errEvent = events.find((e) => e.type === 'error');
271
+ expect(errEvent).toBeDefined();
272
+ if (errEvent?.type === 'error') {
273
+ expect(errEvent.status).toBe('INTERNAL');
274
+ expect(errEvent.message).toBe('something went wrong');
275
+ expect(errEvent.fatal).toBe(false);
276
+ }
277
+ });
278
+ it('unknown activity type → no events emitted', async () => {
279
+ const { session } = makeSession();
280
+ const events = await runWithActivities(session, [
281
+ {
282
+ isSubagentActivityEvent: true,
283
+ agentName: 'TestProtocolAgent',
284
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
285
+ type: 'UNKNOWN_TYPE',
286
+ data: {},
287
+ },
288
+ ]);
289
+ // Only agent_start and agent_end should be present
290
+ const nonLifecycle = events.filter((e) => e.type !== 'agent_start' && e.type !== 'agent_end');
291
+ expect(nonLifecycle).toHaveLength(0);
292
+ });
293
+ it('TOOL_CALL_START with non-object args defaults to {}', async () => {
294
+ const { session } = makeSession();
295
+ const events = await runWithActivities(session, [
296
+ {
297
+ isSubagentActivityEvent: true,
298
+ agentName: 'TestProtocolAgent',
299
+ type: 'TOOL_CALL_START',
300
+ data: { callId: 'x', name: 'tool', args: null },
301
+ },
302
+ ]);
303
+ const reqEvent = events.find((e) => e.type === 'tool_request');
304
+ if (reqEvent?.type === 'tool_request') {
305
+ expect(reqEvent.args).toEqual({});
306
+ }
307
+ });
308
+ });
309
+ // ---------------------------------------------------------------------------
310
+ // getResult() promise
311
+ // ---------------------------------------------------------------------------
312
+ describe('getResult()', () => {
313
+ it('resolves with OutputObject on GOAL termination', async () => {
314
+ const session = new LocalSubagentSession(testDefinition, mockContext, mockMessageBus);
315
+ await session.send({
316
+ message: { content: [{ type: 'text', text: 'q' }] },
317
+ });
318
+ const output = await session.getResult();
319
+ expect(output.result).toBe('Analysis complete.');
320
+ expect(output.terminate_reason).toBe(AgentTerminateMode.GOAL);
321
+ });
322
+ it('rejects when executor throws', async () => {
323
+ const session = new LocalSubagentSession(testDefinition, mockContext, mockMessageBus);
324
+ mockExecutorInstance.run.mockRejectedValue(new Error('executor error'));
325
+ await session.send({
326
+ message: { content: [{ type: 'text', text: 'q' }] },
327
+ });
328
+ await expect(session.getResult()).rejects.toThrow('executor error');
329
+ });
330
+ });
331
+ // ---------------------------------------------------------------------------
332
+ // rawActivityCallback
333
+ // ---------------------------------------------------------------------------
334
+ describe('rawActivityCallback', () => {
335
+ it('receives raw SubagentActivityEvent before AgentEvent translation', async () => {
336
+ const rawActivities = [];
337
+ const session = new LocalSubagentSession(testDefinition, mockContext, mockMessageBus, (activity) => rawActivities.push(activity));
338
+ const thoughtActivity = {
339
+ isSubagentActivityEvent: true,
340
+ agentName: 'TestProtocolAgent',
341
+ type: 'THOUGHT_CHUNK',
342
+ data: { text: 'raw thought' },
343
+ };
344
+ mockExecutorInstance.run.mockImplementation(async () => {
345
+ const onActivity = MockLocalAgentExecutor.create.mock.calls[0]?.[2];
346
+ onActivity?.(thoughtActivity);
347
+ return GOAL_OUTPUT;
348
+ });
349
+ await session.send({
350
+ message: { content: [{ type: 'text', text: 'q' }] },
351
+ });
352
+ await session.getResult();
353
+ expect(rawActivities).toHaveLength(1);
354
+ expect(rawActivities[0]).toBe(thoughtActivity);
355
+ });
356
+ it('is called before AgentEvent translation (raw arrives first)', async () => {
357
+ const callOrder = [];
358
+ const session = new LocalSubagentSession(testDefinition, mockContext, mockMessageBus, () => callOrder.push('raw'));
359
+ session.subscribe((e) => {
360
+ if (e.type === 'message')
361
+ callOrder.push('translated');
362
+ });
363
+ mockExecutorInstance.run.mockImplementation(async () => {
364
+ const onActivity = MockLocalAgentExecutor.create.mock.calls[0]?.[2];
365
+ onActivity?.({
366
+ isSubagentActivityEvent: true,
367
+ agentName: 'TestProtocolAgent',
368
+ type: 'THOUGHT_CHUNK',
369
+ data: { text: 'thought' },
370
+ });
371
+ return GOAL_OUTPUT;
372
+ });
373
+ await session.send({
374
+ message: { content: [{ type: 'text', text: 'q' }] },
375
+ });
376
+ await session.getResult();
377
+ expect(callOrder).toEqual(['raw', 'translated']);
378
+ });
379
+ it('is optional — no callback causes no error', async () => {
380
+ const session = new LocalSubagentSession(testDefinition, mockContext, mockMessageBus);
381
+ await session.send({
382
+ message: { content: [{ type: 'text', text: 'q' }] },
383
+ });
384
+ await expect(session.getResult()).resolves.toBeDefined();
385
+ });
386
+ });
387
+ // ---------------------------------------------------------------------------
388
+ // Subscription
389
+ // ---------------------------------------------------------------------------
390
+ describe('subscription', () => {
391
+ it('unsubscribe stops event delivery', async () => {
392
+ const session = new LocalSubagentSession(testDefinition, mockContext, mockMessageBus);
393
+ const received = [];
394
+ const unsub = session.subscribe((e) => received.push(e));
395
+ unsub();
396
+ await session.send({
397
+ message: { content: [{ type: 'text', text: 'q' }] },
398
+ });
399
+ await session.getResult();
400
+ expect(received).toHaveLength(0);
401
+ });
402
+ it('multiple subscribers all receive events', async () => {
403
+ const session = new LocalSubagentSession(testDefinition, mockContext, mockMessageBus);
404
+ const received1 = [];
405
+ const received2 = [];
406
+ session.subscribe((e) => received1.push(e));
407
+ session.subscribe((e) => received2.push(e));
408
+ await session.send({
409
+ message: { content: [{ type: 'text', text: 'q' }] },
410
+ });
411
+ await session.getResult();
412
+ expect(received1.length).toBeGreaterThan(0);
413
+ expect(received1).toEqual(received2);
414
+ });
415
+ it('events array accumulates all emitted events', async () => {
416
+ const session = new LocalSubagentSession(testDefinition, mockContext, mockMessageBus);
417
+ await session.send({
418
+ message: { content: [{ type: 'text', text: 'q' }] },
419
+ });
420
+ await session.getResult();
421
+ expect(session.events.length).toBeGreaterThanOrEqual(2); // at least agent_start + agent_end
422
+ expect(session.events[0].type).toBe('agent_start');
423
+ });
424
+ });
425
+ // ---------------------------------------------------------------------------
426
+ // Terminate mode mapping
427
+ // ---------------------------------------------------------------------------
428
+ describe('terminate mode → StreamEndReason mapping', () => {
429
+ const cases = [
430
+ [AgentTerminateMode.GOAL, 'completed'],
431
+ [AgentTerminateMode.TIMEOUT, 'max_time'],
432
+ [AgentTerminateMode.MAX_TURNS, 'max_turns'],
433
+ [AgentTerminateMode.ABORTED, 'aborted'],
434
+ [AgentTerminateMode.ERROR, 'failed'],
435
+ [AgentTerminateMode.ERROR_NO_COMPLETE_TASK_CALL, 'failed'],
436
+ ];
437
+ for (const [terminateMode, expectedReason] of cases) {
438
+ it(`${terminateMode} → agent_end(reason:'${expectedReason}')`, async () => {
439
+ mockExecutorInstance.run.mockResolvedValue({
440
+ result: 'done',
441
+ terminate_reason: terminateMode,
442
+ });
443
+ const session = new LocalSubagentSession(testDefinition, mockContext, mockMessageBus);
444
+ const events = [];
445
+ session.subscribe((e) => events.push(e));
446
+ await session.send({
447
+ message: { content: [{ type: 'text', text: 'q' }] },
448
+ });
449
+ await session.getResult().catch(() => {
450
+ // ABORTED results in rejection — catch to let test complete
451
+ });
452
+ const endEvent = events.find((e) => e.type === 'agent_end');
453
+ expect(endEvent).toBeDefined();
454
+ if (endEvent?.type === 'agent_end') {
455
+ expect(endEvent.reason).toBe(expectedReason);
456
+ }
457
+ });
458
+ }
459
+ });
460
+ // ---------------------------------------------------------------------------
461
+ // Abort
462
+ // ---------------------------------------------------------------------------
463
+ describe('abort()', () => {
464
+ it('abort() causes agent_end(reason:aborted)', async () => {
465
+ // Make run() wait until aborted
466
+ let abortSignal;
467
+ mockExecutorInstance.run.mockImplementation((_params, signal) => {
468
+ abortSignal = signal;
469
+ return new Promise((_resolve, reject) => {
470
+ signal.addEventListener('abort', () => {
471
+ const err = new Error('AbortError');
472
+ err.name = 'AbortError';
473
+ reject(err);
474
+ });
475
+ });
476
+ });
477
+ const session = new LocalSubagentSession(testDefinition, mockContext, mockMessageBus);
478
+ const events = [];
479
+ session.subscribe((e) => events.push(e));
480
+ void session.send({
481
+ message: { content: [{ type: 'text', text: 'q' }] },
482
+ });
483
+ // Wait for executor to be created and run started
484
+ await vi.waitFor(() => {
485
+ expect(abortSignal).toBeDefined();
486
+ });
487
+ await session.abort();
488
+ const result = await session.getResult();
489
+ expect(result.result).toBe('');
490
+ expect(result.terminate_reason).toBe('ABORTED');
491
+ const endEvent = events.find((e) => e.type === 'agent_end');
492
+ expect(endEvent).toBeDefined();
493
+ if (endEvent?.type === 'agent_end') {
494
+ expect(endEvent.reason).toBe('aborted');
495
+ }
496
+ });
497
+ });
498
+ // ---------------------------------------------------------------------------
499
+ // Full event sequence
500
+ // ---------------------------------------------------------------------------
501
+ describe('full event sequence', () => {
502
+ it('emits agent_start → message(thought) → tool_request → tool_response → agent_end in order', async () => {
503
+ const session = new LocalSubagentSession(testDefinition, mockContext, mockMessageBus);
504
+ mockExecutorInstance.run.mockImplementation(async () => {
505
+ const onActivity = MockLocalAgentExecutor.create.mock.calls[0]?.[2];
506
+ onActivity?.({
507
+ isSubagentActivityEvent: true,
508
+ agentName: 'TestProtocolAgent',
509
+ type: 'THOUGHT_CHUNK',
510
+ data: { text: 'thinking' },
511
+ });
512
+ onActivity?.({
513
+ isSubagentActivityEvent: true,
514
+ agentName: 'TestProtocolAgent',
515
+ type: 'TOOL_CALL_START',
516
+ data: { callId: 'c1', name: 'tool', args: {} },
517
+ });
518
+ onActivity?.({
519
+ isSubagentActivityEvent: true,
520
+ agentName: 'TestProtocolAgent',
521
+ type: 'TOOL_CALL_END',
522
+ data: { id: 'c1', name: 'tool', output: 'result' },
523
+ });
524
+ return GOAL_OUTPUT;
525
+ });
526
+ const events = [];
527
+ session.subscribe((e) => events.push(e));
528
+ await session.send({
529
+ message: { content: [{ type: 'text', text: 'go' }] },
530
+ });
531
+ await session.getResult();
532
+ const types = events.map((e) => e.type);
533
+ expect(types).toEqual([
534
+ 'agent_start',
535
+ 'message',
536
+ 'tool_request',
537
+ 'tool_response',
538
+ 'agent_end',
539
+ ]);
540
+ });
541
+ });
542
+ // ---------------------------------------------------------------------------
543
+ // Concurrent send() guard
544
+ // ---------------------------------------------------------------------------
545
+ describe('concurrent send() guard', () => {
546
+ it('calling send() while a stream is active throws', async () => {
547
+ let abortSignal;
548
+ mockExecutorInstance.run.mockImplementation((_params, signal) => {
549
+ abortSignal = signal;
550
+ return new Promise((_resolve, reject) => {
551
+ // Reject when aborted so getResult() can settle during cleanup
552
+ signal.addEventListener('abort', () => {
553
+ const err = new Error('AbortError');
554
+ err.name = 'AbortError';
555
+ reject(err);
556
+ });
557
+ });
558
+ });
559
+ const session = new LocalSubagentSession(testDefinition, mockContext, mockMessageBus);
560
+ void session.send({
561
+ message: { content: [{ type: 'text', text: 'first' }] },
562
+ });
563
+ // Wait for execution to start
564
+ await vi.waitFor(() => {
565
+ expect(abortSignal).toBeDefined();
566
+ });
567
+ // Second send() while first stream is active must throw
568
+ await expect(session.send({
569
+ message: { content: [{ type: 'text', text: 'second' }] },
570
+ })).rejects.toThrow('cannot be called while a stream is active');
571
+ // Clean up: abort to unblock the hanging executor
572
+ await session.abort();
573
+ await session.getResult().catch(() => { });
574
+ });
575
+ });
576
+ // ---------------------------------------------------------------------------
577
+ // Multi-send support
578
+ // ---------------------------------------------------------------------------
579
+ describe('multi-send', () => {
580
+ it('supports sequential sends after stream completion', async () => {
581
+ const session = new LocalSubagentSession(testDefinition, mockContext, mockMessageBus);
582
+ // First send
583
+ const result1 = await session.send({
584
+ message: { content: [{ type: 'text', text: 'first' }] },
585
+ });
586
+ expect(result1.streamId).not.toBeNull();
587
+ const output1 = await session.getResult();
588
+ expect(output1.result).toBe('Analysis complete.');
589
+ // Second send — should work, not throw
590
+ const secondOutput = {
591
+ result: 'Second analysis.',
592
+ terminate_reason: AgentTerminateMode.GOAL,
593
+ };
594
+ mockExecutorInstance.run.mockResolvedValue(secondOutput);
595
+ const result2 = await session.send({
596
+ message: { content: [{ type: 'text', text: 'second' }] },
597
+ });
598
+ expect(result2.streamId).not.toBeNull();
599
+ expect(result2.streamId).not.toBe(result1.streamId);
600
+ const output2 = await session.getResult();
601
+ expect(output2.result).toBe('Second analysis.');
602
+ });
603
+ it('getResult() returns the latest stream result', async () => {
604
+ const session = new LocalSubagentSession(testDefinition, mockContext, mockMessageBus);
605
+ // First send
606
+ await session.send({
607
+ message: { content: [{ type: 'text', text: 'first' }] },
608
+ });
609
+ const result1 = await session.getResult();
610
+ // Second send with different output
611
+ const secondOutput = {
612
+ result: 'Different result.',
613
+ terminate_reason: AgentTerminateMode.GOAL,
614
+ };
615
+ mockExecutorInstance.run.mockResolvedValue(secondOutput);
616
+ await session.send({
617
+ message: { content: [{ type: 'text', text: 'second' }] },
618
+ });
619
+ const result2 = await session.getResult();
620
+ expect(result1.result).toBe('Analysis complete.');
621
+ expect(result2.result).toBe('Different result.');
622
+ });
623
+ it('buffered config does not bleed across sends', async () => {
624
+ const session = new LocalSubagentSession(testDefinition, mockContext, mockMessageBus);
625
+ // Buffer config, then send first message
626
+ await session.send({ update: { config: { temperature: 0.5 } } });
627
+ await session.send({
628
+ message: { content: [{ type: 'text', text: 'first' }] },
629
+ });
630
+ await session.getResult();
631
+ // The executor.run params include the buffered config
632
+ const firstRunParams = mockExecutorInstance.run.mock.calls[0]?.[0];
633
+ expect(firstRunParams).toHaveProperty('temperature', 0.5);
634
+ expect(firstRunParams).toHaveProperty('query', 'first');
635
+ // Second send without buffered config — temperature should be gone
636
+ mockExecutorInstance.run.mockResolvedValue(GOAL_OUTPUT);
637
+ await session.send({
638
+ message: { content: [{ type: 'text', text: 'second' }] },
639
+ });
640
+ await session.getResult();
641
+ const secondRunParams = mockExecutorInstance.run.mock.calls[1]?.[0];
642
+ expect(secondRunParams).not.toHaveProperty('temperature');
643
+ expect(secondRunParams).toHaveProperty('query', 'second');
644
+ });
645
+ it('getResult() rejects when called before any send', async () => {
646
+ const session = new LocalSubagentSession(testDefinition, mockContext, mockMessageBus);
647
+ await expect(session.getResult()).rejects.toThrow('No active or completed stream');
648
+ });
649
+ it('emits fresh agent_start/agent_end per stream', async () => {
650
+ const session = new LocalSubagentSession(testDefinition, mockContext, mockMessageBus);
651
+ const events = [];
652
+ session.subscribe((e) => events.push(e));
653
+ // First send
654
+ await session.send({
655
+ message: { content: [{ type: 'text', text: 'first' }] },
656
+ });
657
+ await session.getResult();
658
+ const firstStreamEvents = events.length;
659
+ expect(events[0]?.type).toBe('agent_start');
660
+ expect(events[firstStreamEvents - 1]?.type).toBe('agent_end');
661
+ // Second send
662
+ mockExecutorInstance.run.mockResolvedValue(GOAL_OUTPUT);
663
+ await session.send({
664
+ message: { content: [{ type: 'text', text: 'second' }] },
665
+ });
666
+ await session.getResult();
667
+ // Should have a second agent_start/agent_end pair
668
+ const secondStreamStart = events[firstStreamEvents];
669
+ const lastEvent = events[events.length - 1];
670
+ expect(secondStreamStart?.type).toBe('agent_start');
671
+ expect(lastEvent?.type).toBe('agent_end');
672
+ expect(secondStreamStart?.streamId).not.toBe(events[0]?.streamId);
673
+ });
674
+ });
675
+ });
676
+ //# sourceMappingURL=local-subagent-protocol.test.js.map