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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (341) 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/src/agent/content-utils.js +2 -0
  11. package/dist/src/agent/content-utils.js.map +1 -1
  12. package/dist/src/agent/content-utils.test.js +5 -1
  13. package/dist/src/agent/content-utils.test.js.map +1 -1
  14. package/dist/src/agent/event-translator.js +7 -6
  15. package/dist/src/agent/event-translator.js.map +1 -1
  16. package/dist/src/agent/legacy-agent-session.js +4 -0
  17. package/dist/src/agent/legacy-agent-session.js.map +1 -1
  18. package/dist/src/agent/legacy-agent-session.test.js +9 -1
  19. package/dist/src/agent/legacy-agent-session.test.js.map +1 -1
  20. package/dist/src/agent/tool-display-utils.d.ts +3 -2
  21. package/dist/src/agent/tool-display-utils.js +3 -2
  22. package/dist/src/agent/tool-display-utils.js.map +1 -1
  23. package/dist/src/agent/types.d.ts +33 -3
  24. package/dist/src/agents/a2aUtils.d.ts +1 -1
  25. package/dist/src/agents/a2aUtils.js +4 -3
  26. package/dist/src/agents/a2aUtils.js.map +1 -1
  27. package/dist/src/agents/agentLoader.d.ts +2 -2
  28. package/dist/src/agents/browser/browserAgentInvocation.js +24 -19
  29. package/dist/src/agents/browser/browserAgentInvocation.js.map +1 -1
  30. package/dist/src/agents/local-executor.d.ts +1 -0
  31. package/dist/src/agents/local-executor.js +46 -32
  32. package/dist/src/agents/local-executor.js.map +1 -1
  33. package/dist/src/agents/local-executor.test.js +127 -0
  34. package/dist/src/agents/local-executor.test.js.map +1 -1
  35. package/dist/src/agents/local-invocation.js +24 -20
  36. package/dist/src/agents/local-invocation.js.map +1 -1
  37. package/dist/src/agents/local-invocation.test.js +9 -9
  38. package/dist/src/agents/local-invocation.test.js.map +1 -1
  39. package/dist/src/agents/local-subagent-protocol.d.ts +18 -0
  40. package/dist/src/agents/local-subagent-protocol.js +357 -0
  41. package/dist/src/agents/local-subagent-protocol.js.map +1 -0
  42. package/dist/src/agents/local-subagent-protocol.test.d.ts +6 -0
  43. package/dist/src/agents/local-subagent-protocol.test.js +676 -0
  44. package/dist/src/agents/local-subagent-protocol.test.js.map +1 -0
  45. package/dist/src/agents/remote-invocation.js +6 -6
  46. package/dist/src/agents/remote-invocation.js.map +1 -1
  47. package/dist/src/agents/remote-invocation.test.js +23 -12
  48. package/dist/src/agents/remote-invocation.test.js.map +1 -1
  49. package/dist/src/agents/remote-subagent-protocol.d.ts +31 -0
  50. package/dist/src/agents/remote-subagent-protocol.js +330 -0
  51. package/dist/src/agents/remote-subagent-protocol.js.map +1 -0
  52. package/dist/src/agents/remote-subagent-protocol.test.d.ts +6 -0
  53. package/dist/src/agents/remote-subagent-protocol.test.js +652 -0
  54. package/dist/src/agents/remote-subagent-protocol.test.js.map +1 -0
  55. package/dist/src/agents/skill-extraction-agent.js +1 -0
  56. package/dist/src/agents/skill-extraction-agent.js.map +1 -1
  57. package/dist/src/agents/skill-extraction-agent.test.js +1 -0
  58. package/dist/src/agents/skill-extraction-agent.test.js.map +1 -1
  59. package/dist/src/agents/types.d.ts +13 -2
  60. package/dist/src/agents/types.js +7 -0
  61. package/dist/src/agents/types.js.map +1 -1
  62. package/dist/src/availability/modelAvailabilityService.d.ts +6 -6
  63. package/dist/src/availability/modelAvailabilityService.js +14 -7
  64. package/dist/src/availability/modelAvailabilityService.js.map +1 -1
  65. package/dist/src/availability/modelAvailabilityService.test.js +34 -0
  66. package/dist/src/availability/modelAvailabilityService.test.js.map +1 -1
  67. package/dist/src/availability/policyHelpers.js +24 -12
  68. package/dist/src/availability/policyHelpers.js.map +1 -1
  69. package/dist/src/availability/policyHelpers.test.js +3 -2
  70. package/dist/src/availability/policyHelpers.test.js.map +1 -1
  71. package/dist/src/code_assist/oauth2.js +3 -0
  72. package/dist/src/code_assist/oauth2.js.map +1 -1
  73. package/dist/src/code_assist/setup.d.ts +3 -0
  74. package/dist/src/code_assist/setup.js +9 -0
  75. package/dist/src/code_assist/setup.js.map +1 -1
  76. package/dist/src/code_assist/setup.test.js +9 -1
  77. package/dist/src/code_assist/setup.test.js.map +1 -1
  78. package/dist/src/commands/memory.d.ts +5 -14
  79. package/dist/src/commands/memory.js +19 -141
  80. package/dist/src/commands/memory.js.map +1 -1
  81. package/dist/src/commands/memory.test.js +62 -0
  82. package/dist/src/commands/memory.test.js.map +1 -1
  83. package/dist/src/config/config.d.ts +6 -2
  84. package/dist/src/config/config.js +63 -8
  85. package/dist/src/config/config.js.map +1 -1
  86. package/dist/src/config/config.test.js +48 -0
  87. package/dist/src/config/config.test.js.map +1 -1
  88. package/dist/src/config/defaultModelConfigs.js +13 -0
  89. package/dist/src/config/defaultModelConfigs.js.map +1 -1
  90. package/dist/src/config/models.js +1 -1
  91. package/dist/src/config/models.js.map +1 -1
  92. package/dist/src/config/projectRegistry.d.ts +1 -0
  93. package/dist/src/config/projectRegistry.js +13 -2
  94. package/dist/src/config/projectRegistry.js.map +1 -1
  95. package/dist/src/config/projectRegistry.test.js +43 -0
  96. package/dist/src/config/projectRegistry.test.js.map +1 -1
  97. package/dist/src/context/config/profiles.js +4 -0
  98. package/dist/src/context/config/profiles.js.map +1 -1
  99. package/dist/src/context/contextManager.barrier.test.js +4 -3
  100. package/dist/src/context/contextManager.barrier.test.js.map +1 -1
  101. package/dist/src/context/contextManager.d.ts +9 -2
  102. package/dist/src/context/contextManager.hotstart.test.d.ts +6 -0
  103. package/dist/src/context/contextManager.hotstart.test.js +61 -0
  104. package/dist/src/context/contextManager.hotstart.test.js.map +1 -0
  105. package/dist/src/context/contextManager.js +96 -21
  106. package/dist/src/context/contextManager.js.map +1 -1
  107. package/dist/src/context/eventBus.d.ts +6 -0
  108. package/dist/src/context/eventBus.js +6 -0
  109. package/dist/src/context/eventBus.js.map +1 -1
  110. package/dist/src/context/graph/render.d.ts +3 -1
  111. package/dist/src/context/graph/render.js +33 -9
  112. package/dist/src/context/graph/render.js.map +1 -1
  113. package/dist/src/context/graph/render.test.d.ts +6 -0
  114. package/dist/src/context/graph/render.test.js +203 -0
  115. package/dist/src/context/graph/render.test.js.map +1 -0
  116. package/dist/src/context/graph/toGraph.js +3 -3
  117. package/dist/src/context/graph/toGraph.js.map +1 -1
  118. package/dist/src/context/graph/toGraph.test.d.ts +6 -0
  119. package/dist/src/context/graph/toGraph.test.js +35 -0
  120. package/dist/src/context/graph/toGraph.test.js.map +1 -0
  121. package/dist/src/context/initializer.js +11 -2
  122. package/dist/src/context/initializer.js.map +1 -1
  123. package/dist/src/context/pipeline/contextWorkingBuffer.d.ts +5 -7
  124. package/dist/src/context/pipeline/contextWorkingBuffer.js +105 -29
  125. package/dist/src/context/pipeline/contextWorkingBuffer.js.map +1 -1
  126. package/dist/src/context/pipeline/contextWorkingBuffer.test.js +67 -0
  127. package/dist/src/context/pipeline/contextWorkingBuffer.test.js.map +1 -1
  128. package/dist/src/context/pipeline/environment.d.ts +4 -0
  129. package/dist/src/context/pipeline/environmentImpl.d.ts +6 -5
  130. package/dist/src/context/pipeline/environmentImpl.js +6 -8
  131. package/dist/src/context/pipeline/environmentImpl.js.map +1 -1
  132. package/dist/src/context/pipeline/environmentImpl.test.js +5 -1
  133. package/dist/src/context/pipeline/environmentImpl.test.js.map +1 -1
  134. package/dist/src/context/pipeline/orchestrator.d.ts +1 -0
  135. package/dist/src/context/pipeline/orchestrator.js +59 -24
  136. package/dist/src/context/pipeline/orchestrator.js.map +1 -1
  137. package/dist/src/context/processors/blobDegradationProcessor.test.js +2 -2
  138. package/dist/src/context/processors/rollingSummaryProcessor.js +2 -15
  139. package/dist/src/context/processors/rollingSummaryProcessor.js.map +1 -1
  140. package/dist/src/context/processors/stateSnapshotAsyncProcessor.d.ts +7 -0
  141. package/dist/src/context/processors/stateSnapshotAsyncProcessor.js +33 -21
  142. package/dist/src/context/processors/stateSnapshotAsyncProcessor.js.map +1 -1
  143. package/dist/src/context/processors/stateSnapshotAsyncProcessor.test.js +31 -7
  144. package/dist/src/context/processors/stateSnapshotAsyncProcessor.test.js.map +1 -1
  145. package/dist/src/context/processors/stateSnapshotProcessor.d.ts +2 -0
  146. package/dist/src/context/processors/stateSnapshotProcessor.js +28 -4
  147. package/dist/src/context/processors/stateSnapshotProcessor.js.map +1 -1
  148. package/dist/src/context/processors/stateSnapshotProcessor.test.js +40 -1
  149. package/dist/src/context/processors/stateSnapshotProcessor.test.js.map +1 -1
  150. package/dist/src/context/system-tests/hysteresis.test.d.ts +6 -0
  151. package/dist/src/context/system-tests/hysteresis.test.js +98 -0
  152. package/dist/src/context/system-tests/hysteresis.test.js.map +1 -0
  153. package/dist/src/context/system-tests/lifecycle.golden.test.js +90 -69
  154. package/dist/src/context/system-tests/lifecycle.golden.test.js.map +1 -1
  155. package/dist/src/context/system-tests/simulationHarness.d.ts +1 -4
  156. package/dist/src/context/system-tests/simulationHarness.js +16 -30
  157. package/dist/src/context/system-tests/simulationHarness.js.map +1 -1
  158. package/dist/src/context/testing/contextTestUtils.d.ts +1 -0
  159. package/dist/src/context/testing/contextTestUtils.js +48 -25
  160. package/dist/src/context/testing/contextTestUtils.js.map +1 -1
  161. package/dist/src/context/utils/adaptiveTokenCalculator.d.ts +61 -0
  162. package/dist/src/context/utils/adaptiveTokenCalculator.js +116 -0
  163. package/dist/src/context/utils/adaptiveTokenCalculator.js.map +1 -0
  164. package/dist/src/context/utils/adaptiveTokenCalculator.test.d.ts +6 -0
  165. package/dist/src/context/utils/adaptiveTokenCalculator.test.js +85 -0
  166. package/dist/src/context/utils/adaptiveTokenCalculator.test.js.map +1 -0
  167. package/dist/src/context/utils/contextTokenCalculator.d.ts +47 -1
  168. package/dist/src/context/utils/contextTokenCalculator.js +20 -3
  169. package/dist/src/context/utils/contextTokenCalculator.js.map +1 -1
  170. package/dist/src/context/utils/contextTokenCalculator.test.js +8 -8
  171. package/dist/src/context/utils/contextTokenCalculator.test.js.map +1 -1
  172. package/dist/src/context/utils/formatNodesForLlm.d.ts +21 -0
  173. package/dist/src/context/utils/formatNodesForLlm.js +69 -0
  174. package/dist/src/context/utils/formatNodesForLlm.js.map +1 -0
  175. package/dist/src/context/utils/formatNodesForLlm.test.d.ts +6 -0
  176. package/dist/src/context/utils/formatNodesForLlm.test.js +110 -0
  177. package/dist/src/context/utils/formatNodesForLlm.test.js.map +1 -0
  178. package/dist/src/context/utils/snapshotGenerator.d.ts +23 -1
  179. package/dist/src/context/utils/snapshotGenerator.js +249 -31
  180. package/dist/src/context/utils/snapshotGenerator.js.map +1 -1
  181. package/dist/src/context/utils/snapshotGenerator.test.d.ts +6 -0
  182. package/dist/src/context/utils/snapshotGenerator.test.js +255 -0
  183. package/dist/src/context/utils/snapshotGenerator.test.js.map +1 -0
  184. package/dist/src/context/utils/tokenCalibration.d.ts +9 -0
  185. package/dist/src/context/utils/tokenCalibration.js +30 -0
  186. package/dist/src/context/utils/tokenCalibration.js.map +1 -0
  187. package/dist/src/core/baseLlmClient.d.ts +8 -0
  188. package/dist/src/core/baseLlmClient.js +14 -0
  189. package/dist/src/core/baseLlmClient.js.map +1 -1
  190. package/dist/src/core/client.js +12 -1
  191. package/dist/src/core/client.js.map +1 -1
  192. package/dist/src/core/client.test.js +4 -4
  193. package/dist/src/core/contentGenerator.js +12 -10
  194. package/dist/src/core/contentGenerator.js.map +1 -1
  195. package/dist/src/core/geminiChat.d.ts +2 -0
  196. package/dist/src/core/geminiChat.js +60 -5
  197. package/dist/src/core/geminiChat.js.map +1 -1
  198. package/dist/src/core/geminiChat.test.js +195 -2
  199. package/dist/src/core/geminiChat.test.js.map +1 -1
  200. package/dist/src/core/turn.js +30 -2
  201. package/dist/src/core/turn.js.map +1 -1
  202. package/dist/src/core/turn.test.js +13 -8
  203. package/dist/src/core/turn.test.js.map +1 -1
  204. package/dist/src/generated/git-commit.d.ts +2 -2
  205. package/dist/src/generated/git-commit.js +2 -2
  206. package/dist/src/hooks/hookEventHandler.js +3 -2
  207. package/dist/src/hooks/hookEventHandler.js.map +1 -1
  208. package/dist/src/hooks/hookEventHandler.test.js +80 -0
  209. package/dist/src/hooks/hookEventHandler.test.js.map +1 -1
  210. package/dist/src/index.d.ts +2 -1
  211. package/dist/src/index.js +2 -2
  212. package/dist/src/index.js.map +1 -1
  213. package/dist/src/prompts/snippets.js +1 -1
  214. package/dist/src/prompts/snippets.js.map +1 -1
  215. package/dist/src/routing/strategies/approvalModeStrategy.js +5 -3
  216. package/dist/src/routing/strategies/approvalModeStrategy.js.map +1 -1
  217. package/dist/src/routing/strategies/approvalModeStrategy.test.js +2 -0
  218. package/dist/src/routing/strategies/approvalModeStrategy.test.js.map +1 -1
  219. package/dist/src/routing/strategies/classifierStrategy.js +8 -1
  220. package/dist/src/routing/strategies/classifierStrategy.js.map +1 -1
  221. package/dist/src/routing/strategies/classifierStrategy.test.js +4 -0
  222. package/dist/src/routing/strategies/classifierStrategy.test.js.map +1 -1
  223. package/dist/src/routing/strategies/gemmaClassifierStrategy.js +7 -1
  224. package/dist/src/routing/strategies/gemmaClassifierStrategy.js.map +1 -1
  225. package/dist/src/routing/strategies/gemmaClassifierStrategy.test.js +4 -0
  226. package/dist/src/routing/strategies/gemmaClassifierStrategy.test.js.map +1 -1
  227. package/dist/src/routing/strategies/numericalClassifierStrategy.d.ts +1 -0
  228. package/dist/src/routing/strategies/numericalClassifierStrategy.js +22 -3
  229. package/dist/src/routing/strategies/numericalClassifierStrategy.js.map +1 -1
  230. package/dist/src/routing/strategies/numericalClassifierStrategy.test.js +168 -23
  231. package/dist/src/routing/strategies/numericalClassifierStrategy.test.js.map +1 -1
  232. package/dist/src/scheduler/scheduler.js +11 -0
  233. package/dist/src/scheduler/scheduler.js.map +1 -1
  234. package/dist/src/scheduler/scheduler.test.js +1 -1
  235. package/dist/src/scheduler/scheduler.test.js.map +1 -1
  236. package/dist/src/scheduler/state-manager.js +5 -1
  237. package/dist/src/scheduler/state-manager.js.map +1 -1
  238. package/dist/src/scheduler/tool-executor.js +7 -4
  239. package/dist/src/scheduler/tool-executor.js.map +1 -1
  240. package/dist/src/scheduler/types.d.ts +5 -1
  241. package/dist/src/scheduler/types.js.map +1 -1
  242. package/dist/src/services/fileDiscoveryService.js +2 -1
  243. package/dist/src/services/fileDiscoveryService.js.map +1 -1
  244. package/dist/src/services/fileDiscoveryService.test.js +36 -0
  245. package/dist/src/services/fileDiscoveryService.test.js.map +1 -1
  246. package/dist/src/services/gitService.js +8 -1
  247. package/dist/src/services/gitService.js.map +1 -1
  248. package/dist/src/services/gitService.test.js +104 -0
  249. package/dist/src/services/gitService.test.js.map +1 -1
  250. package/dist/src/services/keychainService.js +14 -5
  251. package/dist/src/services/keychainService.js.map +1 -1
  252. package/dist/src/services/memoryPatchUtils.d.ts +66 -4
  253. package/dist/src/services/memoryPatchUtils.js +267 -5
  254. package/dist/src/services/memoryPatchUtils.js.map +1 -1
  255. package/dist/src/services/memoryService.js +25 -1
  256. package/dist/src/services/memoryService.js.map +1 -1
  257. package/dist/src/services/memoryService.test.js +61 -1
  258. package/dist/src/services/memoryService.test.js.map +1 -1
  259. package/dist/src/services/test-data/resolved-aliases-retry.golden.json +11 -0
  260. package/dist/src/services/test-data/resolved-aliases.golden.json +11 -0
  261. package/dist/src/telemetry/gcp-exporters.d.ts +2 -0
  262. package/dist/src/telemetry/gcp-exporters.js +69 -5
  263. package/dist/src/telemetry/gcp-exporters.js.map +1 -1
  264. package/dist/src/telemetry/gcp-exporters.test.js +52 -0
  265. package/dist/src/telemetry/gcp-exporters.test.js.map +1 -1
  266. package/dist/src/telemetry/metrics.js +13 -2
  267. package/dist/src/telemetry/metrics.js.map +1 -1
  268. package/dist/src/telemetry/metrics.test.js +61 -1
  269. package/dist/src/telemetry/metrics.test.js.map +1 -1
  270. package/dist/src/tools/definitions/model-family-sets/default-legacy.js +5 -2
  271. package/dist/src/tools/definitions/model-family-sets/default-legacy.js.map +1 -1
  272. package/dist/src/tools/definitions/model-family-sets/gemini-3.js +7 -4
  273. package/dist/src/tools/definitions/model-family-sets/gemini-3.js.map +1 -1
  274. package/dist/src/tools/edit.js +19 -0
  275. package/dist/src/tools/edit.js.map +1 -1
  276. package/dist/src/tools/edit.test.js +9 -0
  277. package/dist/src/tools/edit.test.js.map +1 -1
  278. package/dist/src/tools/grep.js +13 -1
  279. package/dist/src/tools/grep.js.map +1 -1
  280. package/dist/src/tools/ls.js +5 -0
  281. package/dist/src/tools/ls.js.map +1 -1
  282. package/dist/src/tools/mcp-client.js +17 -3
  283. package/dist/src/tools/mcp-client.js.map +1 -1
  284. package/dist/src/tools/mcp-client.test.js +24 -0
  285. package/dist/src/tools/mcp-client.test.js.map +1 -1
  286. package/dist/src/tools/read-file.js +11 -6
  287. package/dist/src/tools/read-file.js.map +1 -1
  288. package/dist/src/tools/read-file.test.js +20 -8
  289. package/dist/src/tools/read-file.test.js.map +1 -1
  290. package/dist/src/tools/ripGrep.js +13 -1
  291. package/dist/src/tools/ripGrep.js.map +1 -1
  292. package/dist/src/tools/shell.js +14 -0
  293. package/dist/src/tools/shell.js.map +1 -1
  294. package/dist/src/tools/shell.test.js +5 -0
  295. package/dist/src/tools/shell.test.js.map +1 -1
  296. package/dist/src/tools/tools.d.ts +6 -0
  297. package/dist/src/tools/tools.js.map +1 -1
  298. package/dist/src/tools/topicTool.js +5 -0
  299. package/dist/src/tools/topicTool.js.map +1 -1
  300. package/dist/src/tools/write-file.js +13 -0
  301. package/dist/src/tools/write-file.js.map +1 -1
  302. package/dist/src/tools/write-file.test.js +8 -0
  303. package/dist/src/tools/write-file.test.js.map +1 -1
  304. package/dist/src/utils/errors.js +3 -8
  305. package/dist/src/utils/errors.js.map +1 -1
  306. package/dist/src/utils/filesearch/ignore.js +4 -1
  307. package/dist/src/utils/filesearch/ignore.js.map +1 -1
  308. package/dist/src/utils/ignoreFileParser.js +1 -1
  309. package/dist/src/utils/ignoreFileParser.js.map +1 -1
  310. package/dist/src/utils/ignorePatterns.js +2 -0
  311. package/dist/src/utils/ignorePatterns.js.map +1 -1
  312. package/dist/src/utils/ignorePatterns.test.js +1 -0
  313. package/dist/src/utils/ignorePatterns.test.js.map +1 -1
  314. package/dist/src/utils/memoryDiscovery.js +55 -40
  315. package/dist/src/utils/memoryDiscovery.js.map +1 -1
  316. package/dist/src/utils/memoryDiscovery.test.js +77 -9
  317. package/dist/src/utils/memoryDiscovery.test.js.map +1 -1
  318. package/dist/src/utils/modelUtils.d.ts +14 -0
  319. package/dist/src/utils/modelUtils.js +17 -0
  320. package/dist/src/utils/modelUtils.js.map +1 -0
  321. package/dist/src/utils/modelUtils.test.d.ts +6 -0
  322. package/dist/src/utils/modelUtils.test.js +23 -0
  323. package/dist/src/utils/modelUtils.test.js.map +1 -0
  324. package/dist/src/utils/paths.d.ts +15 -1
  325. package/dist/src/utils/paths.js +22 -7
  326. package/dist/src/utils/paths.js.map +1 -1
  327. package/dist/src/utils/paths.test.js +25 -1
  328. package/dist/src/utils/paths.test.js.map +1 -1
  329. package/dist/src/utils/quotaErrorDetection.js +23 -12
  330. package/dist/src/utils/quotaErrorDetection.js.map +1 -1
  331. package/dist/src/utils/shell-utils.js +9 -1
  332. package/dist/src/utils/shell-utils.js.map +1 -1
  333. package/dist/src/utils/shell-utils.test.js +7 -3
  334. package/dist/src/utils/shell-utils.test.js.map +1 -1
  335. package/dist/src/utils/tokenCalculation.js +2 -1
  336. package/dist/src/utils/tokenCalculation.js.map +1 -1
  337. package/dist/src/utils/tokenCalculation.test.js +15 -0
  338. package/dist/src/utils/tokenCalculation.test.js.map +1 -1
  339. package/dist/tsconfig.tsbuildinfo +1 -1
  340. package/package.json +1 -1
  341. package/dist/google-gemini-cli-core-0.42.0-preview.0.tgz +0 -0
@@ -0,0 +1,652 @@
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 { RemoteSubagentSession } from './remote-subagent-protocol.js';
8
+ import { A2AAuthProviderFactory } from './auth-provider/factory.js';
9
+ import { createMockMessageBus } from '../test-utils/mock-message-bus.js';
10
+ // Mock A2AClientManager at module level
11
+ vi.mock('./a2a-client-manager.js', () => ({
12
+ A2AClientManager: vi.fn().mockImplementation(() => ({
13
+ getClient: vi.fn(),
14
+ loadAgent: vi.fn(),
15
+ sendMessageStream: vi.fn(),
16
+ })),
17
+ }));
18
+ // Mock A2AAuthProviderFactory
19
+ vi.mock('./auth-provider/factory.js', () => ({
20
+ A2AAuthProviderFactory: {
21
+ create: vi.fn(),
22
+ },
23
+ }));
24
+ const mockDefinition = {
25
+ name: 'test-remote-agent',
26
+ kind: 'remote',
27
+ agentCardUrl: 'http://test-agent/card',
28
+ displayName: 'Test Remote Agent',
29
+ description: 'A test remote agent',
30
+ inputConfig: {
31
+ inputSchema: { type: 'object' },
32
+ },
33
+ };
34
+ function makeChunk(text) {
35
+ return {
36
+ kind: 'message',
37
+ messageId: `msg-${Math.random()}`,
38
+ role: 'agent',
39
+ parts: [{ kind: 'text', text }],
40
+ };
41
+ }
42
+ describe('RemoteSubagentSession (protocol)', () => {
43
+ let mockClientManager;
44
+ let mockContext;
45
+ let mockMessageBus;
46
+ beforeEach(() => {
47
+ vi.clearAllMocks();
48
+ // Each test creates fresh session instances. contextId/taskId persist as
49
+ // instance fields within a session, not via static state.
50
+ mockClientManager = {
51
+ getClient: vi.fn().mockReturnValue(undefined), // client not yet loaded
52
+ loadAgent: vi.fn().mockResolvedValue(undefined),
53
+ sendMessageStream: vi.fn(),
54
+ };
55
+ const mockConfig = {
56
+ getA2AClientManager: vi.fn().mockReturnValue(mockClientManager),
57
+ injectionService: {
58
+ getLatestInjectionIndex: vi.fn().mockReturnValue(0),
59
+ },
60
+ };
61
+ mockContext = { config: mockConfig };
62
+ mockMessageBus = createMockMessageBus();
63
+ // Default: sendMessageStream yields one chunk with "Hello"
64
+ mockClientManager.sendMessageStream.mockImplementation(async function* () {
65
+ yield makeChunk('Hello');
66
+ });
67
+ });
68
+ afterEach(() => {
69
+ vi.restoreAllMocks();
70
+ });
71
+ // Helper: run a session with the default or custom stream and collect events
72
+ async function runSession(definition = mockDefinition, query = 'test query') {
73
+ const session = new RemoteSubagentSession(definition, mockContext, mockMessageBus);
74
+ const events = [];
75
+ session.subscribe((e) => events.push(e));
76
+ await session.send({
77
+ message: { content: [{ type: 'text', text: query }] },
78
+ });
79
+ const result = await session.getResult();
80
+ return { session, events, result };
81
+ }
82
+ // ---------------------------------------------------------------------------
83
+ // Lifecycle events
84
+ // ---------------------------------------------------------------------------
85
+ describe('lifecycle events', () => {
86
+ it('emits agent_start then agent_end(completed) on success', async () => {
87
+ const { events } = await runSession();
88
+ const types = events.map((e) => e.type);
89
+ expect(types[0]).toBe('agent_start');
90
+ expect(types[types.length - 1]).toBe('agent_end');
91
+ const end = events[events.length - 1];
92
+ if (end.type === 'agent_end') {
93
+ expect(end.reason).toBe('completed');
94
+ }
95
+ });
96
+ it('emits agent_start exactly once', async () => {
97
+ const { events } = await runSession();
98
+ expect(events.filter((e) => e.type === 'agent_start')).toHaveLength(1);
99
+ });
100
+ it('emits agent_end exactly once on error path', async () => {
101
+ mockClientManager.sendMessageStream.mockReturnValue({
102
+ [Symbol.asyncIterator]() {
103
+ return {
104
+ async next() {
105
+ throw new Error('stream error');
106
+ },
107
+ };
108
+ },
109
+ });
110
+ const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
111
+ const events = [];
112
+ session.subscribe((e) => events.push(e));
113
+ await session.send({
114
+ message: { content: [{ type: 'text', text: 'q' }] },
115
+ });
116
+ await expect(session.getResult()).rejects.toThrow('stream error');
117
+ expect(events.filter((e) => e.type === 'agent_end')).toHaveLength(1);
118
+ });
119
+ it('all events share the same streamId', async () => {
120
+ const { events } = await runSession();
121
+ const streamIds = new Set(events.map((e) => e.streamId));
122
+ expect(streamIds.size).toBe(1);
123
+ });
124
+ it('message returns a non-null streamId; unsupported payload returns null', async () => {
125
+ const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
126
+ const updateResult = await session.send({
127
+ update: { config: { key: 'val' } },
128
+ });
129
+ expect(updateResult.streamId).toBeNull();
130
+ const messageResult = await session.send({
131
+ message: { content: [{ type: 'text', text: 'q' }] },
132
+ });
133
+ expect(messageResult.streamId).not.toBeNull();
134
+ // complete the session to avoid dangling execution
135
+ await session.getResult();
136
+ });
137
+ });
138
+ // ---------------------------------------------------------------------------
139
+ // Chunk → AgentEvent translation
140
+ // ---------------------------------------------------------------------------
141
+ describe('chunk → AgentEvent translation', () => {
142
+ it('each A2A chunk produces a message event with incremental delta text', async () => {
143
+ mockClientManager.sendMessageStream.mockImplementation(async function* () {
144
+ yield makeChunk('Hello');
145
+ yield makeChunk(' world');
146
+ });
147
+ const { events } = await runSession();
148
+ const msgEvents = events.filter((e) => e.type === 'message');
149
+ expect(msgEvents).toHaveLength(2);
150
+ // Each message event contains only the delta, not accumulated text
151
+ if (msgEvents[0]?.type === 'message') {
152
+ const text = msgEvents[0].content.find((c) => c.type === 'text');
153
+ expect(text?.type === 'text' && text.text).toBe('Hello');
154
+ }
155
+ if (msgEvents[1]?.type === 'message') {
156
+ const text = msgEvents[1].content.find((c) => c.type === 'text');
157
+ expect(text?.type === 'text' && text.text).toBe(' world');
158
+ }
159
+ });
160
+ it('getLatestProgress() is updated per chunk with state running', async () => {
161
+ let capturedProgress;
162
+ mockClientManager.sendMessageStream.mockImplementation(async function* () {
163
+ yield makeChunk('Partial');
164
+ });
165
+ const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
166
+ session.subscribe((e) => {
167
+ if (e.type === 'message') {
168
+ capturedProgress = session.getLatestProgress();
169
+ }
170
+ });
171
+ await session.send({
172
+ message: { content: [{ type: 'text', text: 'q' }] },
173
+ });
174
+ await session.getResult();
175
+ // During streaming, progress should be 'running'
176
+ expect(capturedProgress).toBeDefined();
177
+ // Note: by the time we check, progress may be 'completed'.
178
+ // During the message event, it was 'running'.
179
+ expect(capturedProgress?.isSubagentProgress).toBe(true);
180
+ expect(capturedProgress?.agentName).toBe('Test Remote Agent');
181
+ });
182
+ it('getLatestProgress() state is completed after getResult() resolves', async () => {
183
+ const { session } = await runSession();
184
+ const progress = session.getLatestProgress();
185
+ expect(progress?.state).toBe('completed');
186
+ expect(progress?.result).toBe('Hello');
187
+ });
188
+ });
189
+ // ---------------------------------------------------------------------------
190
+ // getResult() promise
191
+ // ---------------------------------------------------------------------------
192
+ describe('getResult()', () => {
193
+ it('resolves with ToolResult containing llmContent and SubagentProgress returnDisplay', async () => {
194
+ mockClientManager.sendMessageStream.mockImplementation(async function* () {
195
+ yield makeChunk('Result text');
196
+ });
197
+ const { result } = await runSession();
198
+ expect(result.llmContent).toEqual([{ text: 'Result text' }]);
199
+ const display = result.returnDisplay;
200
+ expect(display.isSubagentProgress).toBe(true);
201
+ expect(display.state).toBe('completed');
202
+ expect(display.result).toBe('Result text');
203
+ expect(display.agentName).toBe('Test Remote Agent');
204
+ });
205
+ it('rejects when stream throws a non-A2A error', async () => {
206
+ mockClientManager.sendMessageStream.mockReturnValue({
207
+ [Symbol.asyncIterator]() {
208
+ return {
209
+ async next() {
210
+ throw new Error('network failure');
211
+ },
212
+ };
213
+ },
214
+ });
215
+ const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
216
+ await session.send({
217
+ message: { content: [{ type: 'text', text: 'q' }] },
218
+ });
219
+ await expect(session.getResult()).rejects.toThrow();
220
+ });
221
+ it('resolves even with empty stream (empty final output)', async () => {
222
+ mockClientManager.sendMessageStream.mockImplementation(async function* () {
223
+ // yield nothing
224
+ });
225
+ const { result } = await runSession();
226
+ expect(result.llmContent).toEqual([{ text: '' }]);
227
+ });
228
+ });
229
+ // ---------------------------------------------------------------------------
230
+ // Session state persistence
231
+ // ---------------------------------------------------------------------------
232
+ describe('session state persistence', () => {
233
+ it('second send reuses contextId captured from first send', async () => {
234
+ let callCount = 0;
235
+ mockClientManager.sendMessageStream.mockImplementation(async function* (_name, _query, opts) {
236
+ callCount++;
237
+ if (callCount === 1) {
238
+ yield {
239
+ kind: 'message',
240
+ messageId: 'msg-1',
241
+ role: 'agent',
242
+ contextId: 'ctx-from-server',
243
+ parts: [{ kind: 'text', text: 'First response' }],
244
+ };
245
+ }
246
+ else {
247
+ // Second send on same session should pass the contextId
248
+ expect(opts.contextId).toBe('ctx-from-server');
249
+ yield makeChunk('Second response');
250
+ }
251
+ });
252
+ const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
253
+ // First send — establishes contextId
254
+ await session.send({
255
+ message: { content: [{ type: 'text', text: 'first' }] },
256
+ });
257
+ await session.getResult();
258
+ // Second send on same session — should reuse contextId
259
+ await session.send({
260
+ message: { content: [{ type: 'text', text: 'second' }] },
261
+ });
262
+ await session.getResult();
263
+ expect(callCount).toBe(2);
264
+ });
265
+ it('separate session instances have independent state', async () => {
266
+ const capturedContextIds = [];
267
+ mockClientManager.sendMessageStream.mockImplementation(async function* (_name, _query, opts) {
268
+ capturedContextIds.push(opts.contextId);
269
+ yield {
270
+ kind: 'message',
271
+ messageId: 'msg-1',
272
+ role: 'agent',
273
+ contextId: 'ctx-from-server',
274
+ parts: [{ kind: 'text', text: 'ok' }],
275
+ };
276
+ });
277
+ // Two separate sessions for the same agent — state is NOT shared
278
+ const session1 = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
279
+ await session1.send({
280
+ message: { content: [{ type: 'text', text: 'q' }] },
281
+ });
282
+ await session1.getResult();
283
+ const session2 = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
284
+ await session2.send({
285
+ message: { content: [{ type: 'text', text: 'q' }] },
286
+ });
287
+ await session2.getResult();
288
+ // Both start with no contextId — separate instances, no shared state
289
+ expect(capturedContextIds[0]).toBeUndefined();
290
+ expect(capturedContextIds[1]).toBeUndefined();
291
+ });
292
+ it('taskId is cleared when a terminal-state task chunk is received', async () => {
293
+ let callCount = 0;
294
+ const capturedTaskIds = [];
295
+ mockClientManager.sendMessageStream.mockImplementation(async function* (_n, _q, opts) {
296
+ callCount++;
297
+ capturedTaskIds.push(opts.taskId);
298
+ if (callCount === 1) {
299
+ yield {
300
+ kind: 'task',
301
+ id: 'task-123',
302
+ contextId: 'ctx-1',
303
+ status: { state: 'completed' },
304
+ };
305
+ }
306
+ else {
307
+ yield makeChunk('done');
308
+ }
309
+ });
310
+ // Use same session for multi-send
311
+ const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
312
+ await session.send({
313
+ message: { content: [{ type: 'text', text: 'first' }] },
314
+ });
315
+ await session.getResult();
316
+ await session.send({
317
+ message: { content: [{ type: 'text', text: 'second' }] },
318
+ });
319
+ await session.getResult();
320
+ expect(callCount).toBe(2);
321
+ // First call starts with no taskId
322
+ expect(capturedTaskIds[0]).toBeUndefined();
323
+ // Second call: taskId was cleared because terminal-state task chunk was received
324
+ expect(capturedTaskIds[1]).toBeUndefined();
325
+ });
326
+ });
327
+ // ---------------------------------------------------------------------------
328
+ // Auth setup
329
+ // ---------------------------------------------------------------------------
330
+ describe('auth setup', () => {
331
+ it('no auth → loadAgent called without auth handler', async () => {
332
+ await runSession();
333
+ expect(mockClientManager.loadAgent).toHaveBeenCalledWith('test-remote-agent', { type: 'url', url: 'http://test-agent/card' }, undefined);
334
+ });
335
+ it('definition.auth present → A2AAuthProviderFactory.create called', async () => {
336
+ const authDef = {
337
+ ...mockDefinition,
338
+ name: 'auth-agent',
339
+ auth: {
340
+ type: 'http',
341
+ scheme: 'Bearer',
342
+ token: 'secret',
343
+ },
344
+ };
345
+ const mockProvider = {
346
+ type: 'http',
347
+ headers: vi.fn().mockResolvedValue({ Authorization: 'Bearer secret' }),
348
+ shouldRetryWithHeaders: vi.fn(),
349
+ };
350
+ A2AAuthProviderFactory.create.mockResolvedValue(mockProvider);
351
+ await runSession(authDef, 'q');
352
+ expect(A2AAuthProviderFactory.create).toHaveBeenCalledWith(expect.objectContaining({
353
+ agentName: 'auth-agent',
354
+ agentCardUrl: 'http://test-agent/card',
355
+ }));
356
+ expect(mockClientManager.loadAgent).toHaveBeenCalledWith('auth-agent', expect.any(Object), mockProvider);
357
+ });
358
+ it('auth factory returns undefined → throws error that rejects getResult()', async () => {
359
+ const authDef = {
360
+ ...mockDefinition,
361
+ name: 'failing-auth-agent',
362
+ auth: {
363
+ type: 'http',
364
+ scheme: 'Bearer',
365
+ token: 'secret',
366
+ },
367
+ };
368
+ A2AAuthProviderFactory.create.mockResolvedValue(undefined);
369
+ const session = new RemoteSubagentSession(authDef, mockContext, mockMessageBus);
370
+ await session.send({
371
+ message: { content: [{ type: 'text', text: 'q' }] },
372
+ });
373
+ await expect(session.getResult()).rejects.toThrow("Failed to create auth provider for agent 'failing-auth-agent'");
374
+ });
375
+ it('agent already loaded → loadAgent not called again', async () => {
376
+ // Return a client object (truthy) so getClient returns defined
377
+ mockClientManager.getClient.mockReturnValue({});
378
+ await runSession();
379
+ expect(mockClientManager.loadAgent).not.toHaveBeenCalled();
380
+ });
381
+ });
382
+ // ---------------------------------------------------------------------------
383
+ // Error handling
384
+ // ---------------------------------------------------------------------------
385
+ describe('error handling', () => {
386
+ it('stream error → error event + agent_end(failed)', async () => {
387
+ mockClientManager.sendMessageStream.mockReturnValue({
388
+ [Symbol.asyncIterator]() {
389
+ return {
390
+ async next() {
391
+ throw new Error('network error');
392
+ },
393
+ };
394
+ },
395
+ });
396
+ const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
397
+ const events = [];
398
+ session.subscribe((e) => events.push(e));
399
+ await session.send({
400
+ message: { content: [{ type: 'text', text: 'q' }] },
401
+ });
402
+ await expect(session.getResult()).rejects.toThrow();
403
+ const errEvent = events.find((e) => e.type === 'error');
404
+ expect(errEvent).toBeDefined();
405
+ const endEvent = events.find((e) => e.type === 'agent_end');
406
+ expect(endEvent).toBeDefined();
407
+ if (endEvent?.type === 'agent_end') {
408
+ expect(endEvent.reason).toBe('failed');
409
+ }
410
+ });
411
+ it('missing A2AClientManager → rejects getResult()', async () => {
412
+ const mockConfig = {
413
+ getA2AClientManager: vi.fn().mockReturnValue(undefined),
414
+ injectionService: {
415
+ getLatestInjectionIndex: vi.fn().mockReturnValue(0),
416
+ },
417
+ };
418
+ const noClientContext = {
419
+ config: mockConfig,
420
+ };
421
+ const session = new RemoteSubagentSession(mockDefinition, noClientContext, mockMessageBus);
422
+ await session.send({
423
+ message: { content: [{ type: 'text', text: 'q' }] },
424
+ });
425
+ await expect(session.getResult()).rejects.toThrow('A2AClientManager not available');
426
+ });
427
+ });
428
+ // ---------------------------------------------------------------------------
429
+ // Subscription
430
+ // ---------------------------------------------------------------------------
431
+ describe('subscription', () => {
432
+ it('unsubscribe stops event delivery', async () => {
433
+ const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
434
+ const received = [];
435
+ const unsub = session.subscribe((e) => received.push(e));
436
+ unsub();
437
+ await session.send({
438
+ message: { content: [{ type: 'text', text: 'q' }] },
439
+ });
440
+ await session.getResult();
441
+ expect(received).toHaveLength(0);
442
+ });
443
+ it('multiple subscribers all receive events', async () => {
444
+ const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
445
+ const events1 = [];
446
+ const events2 = [];
447
+ session.subscribe((e) => events1.push(e));
448
+ session.subscribe((e) => events2.push(e));
449
+ await session.send({
450
+ message: { content: [{ type: 'text', text: 'q' }] },
451
+ });
452
+ await session.getResult();
453
+ expect(events1.length).toBeGreaterThan(0);
454
+ expect(events1).toEqual(events2);
455
+ });
456
+ });
457
+ // ---------------------------------------------------------------------------
458
+ // Abort
459
+ // ---------------------------------------------------------------------------
460
+ describe('abort()', () => {
461
+ it('abort() causes agent_end(reason:aborted)', async () => {
462
+ let rejectWithAbort;
463
+ // Stream that blocks until aborted, then throws AbortError
464
+ mockClientManager.sendMessageStream.mockImplementation(
465
+ // eslint-disable-next-line require-yield
466
+ async function* () {
467
+ await new Promise((_resolve, reject) => {
468
+ rejectWithAbort = reject;
469
+ });
470
+ });
471
+ const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
472
+ const events = [];
473
+ session.subscribe((e) => events.push(e));
474
+ void session.send({
475
+ message: { content: [{ type: 'text', text: 'q' }] },
476
+ });
477
+ // Wait for agent_start to be emitted before aborting
478
+ await vi.waitFor(() => {
479
+ expect(events.some((e) => e.type === 'agent_start')).toBe(true);
480
+ });
481
+ await session.abort();
482
+ // Simulate the transport throwing AbortError when signal fires
483
+ const abortErr = new Error('AbortError');
484
+ abortErr.name = 'AbortError';
485
+ rejectWithAbort?.(abortErr);
486
+ const result = await session.getResult();
487
+ expect(result.llmContent).toEqual([{ text: '' }]);
488
+ expect(result.returnDisplay).toBe('');
489
+ const endEvent = events.find((e) => e.type === 'agent_end');
490
+ expect(endEvent).toBeDefined();
491
+ if (endEvent?.type === 'agent_end') {
492
+ expect(endEvent.reason).toBe('aborted');
493
+ }
494
+ });
495
+ });
496
+ // ---------------------------------------------------------------------------
497
+ // sendMessageStream call args
498
+ // ---------------------------------------------------------------------------
499
+ describe('sendMessageStream call arguments', () => {
500
+ it('passes the query string from the message payload', async () => {
501
+ await runSession(mockDefinition, 'my specific query');
502
+ expect(mockClientManager.sendMessageStream).toHaveBeenCalledWith('test-remote-agent', 'my specific query', expect.objectContaining({ signal: expect.any(Object) }));
503
+ });
504
+ it('uses DEFAULT_QUERY_STRING when message text is empty', async () => {
505
+ const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
506
+ await session.send({
507
+ message: { content: [{ type: 'text', text: '' }] },
508
+ });
509
+ await session.getResult();
510
+ // DEFAULT_QUERY_STRING = 'Get Started!'
511
+ expect(mockClientManager.sendMessageStream).toHaveBeenCalledWith('test-remote-agent', 'Get Started!', expect.objectContaining({ signal: expect.any(Object) }));
512
+ });
513
+ });
514
+ // ---------------------------------------------------------------------------
515
+ // Concurrent send() guard
516
+ // ---------------------------------------------------------------------------
517
+ describe('concurrent send() guard', () => {
518
+ it('calling send() while a stream is active throws', async () => {
519
+ let resolveChunk;
520
+ mockClientManager.sendMessageStream.mockImplementation(async function* () {
521
+ // Block until test releases the chunk
522
+ await new Promise((resolve) => {
523
+ resolveChunk = resolve;
524
+ });
525
+ yield makeChunk('late');
526
+ });
527
+ const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
528
+ void session.send({
529
+ message: { content: [{ type: 'text', text: 'first' }] },
530
+ });
531
+ // Wait for the stream to actually start (agent_start emitted)
532
+ const events = [];
533
+ session.subscribe((e) => events.push(e));
534
+ await vi.waitFor(() => {
535
+ expect(events.some((e) => e.type === 'agent_start')).toBe(true);
536
+ });
537
+ // Second send() while first stream is active must throw
538
+ await expect(session.send({
539
+ message: { content: [{ type: 'text', text: 'second' }] },
540
+ })).rejects.toThrow('cannot be called while a stream is active');
541
+ // Clean up: release the blocked generator so getResult() can settle
542
+ resolveChunk();
543
+ await session.getResult().catch(() => { });
544
+ });
545
+ });
546
+ // ---------------------------------------------------------------------------
547
+ // Multi-send support
548
+ // ---------------------------------------------------------------------------
549
+ describe('multi-send', () => {
550
+ it('supports sequential sends after stream completion', async () => {
551
+ let callCount = 0;
552
+ mockClientManager.sendMessageStream.mockImplementation(async function* () {
553
+ callCount++;
554
+ yield makeChunk(`Response ${callCount}`);
555
+ });
556
+ const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
557
+ // First send
558
+ const result1 = await session.send({
559
+ message: { content: [{ type: 'text', text: 'first' }] },
560
+ });
561
+ expect(result1.streamId).not.toBeNull();
562
+ const output1 = await session.getResult();
563
+ expect(output1.llmContent).toEqual([{ text: 'Response 1' }]);
564
+ // Second send — should work, not throw
565
+ const result2 = await session.send({
566
+ message: { content: [{ type: 'text', text: 'second' }] },
567
+ });
568
+ expect(result2.streamId).not.toBeNull();
569
+ expect(result2.streamId).not.toBe(result1.streamId);
570
+ const output2 = await session.getResult();
571
+ expect(output2.llmContent).toEqual([{ text: 'Response 2' }]);
572
+ });
573
+ it('getResult() returns the latest stream result', async () => {
574
+ let callCount = 0;
575
+ mockClientManager.sendMessageStream.mockImplementation(async function* () {
576
+ callCount++;
577
+ yield makeChunk(`Result ${callCount}`);
578
+ });
579
+ const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
580
+ await session.send({
581
+ message: { content: [{ type: 'text', text: 'first' }] },
582
+ });
583
+ const result1 = await session.getResult();
584
+ await session.send({
585
+ message: { content: [{ type: 'text', text: 'second' }] },
586
+ });
587
+ const result2 = await session.getResult();
588
+ expect(result1.llmContent).toEqual([{ text: 'Result 1' }]);
589
+ expect(result2.llmContent).toEqual([{ text: 'Result 2' }]);
590
+ });
591
+ it('contextId/taskId persist across sends within the same session', async () => {
592
+ let sendCallCount = 0;
593
+ mockClientManager.sendMessageStream.mockImplementation(async function* (_name, _query, _opts) {
594
+ sendCallCount++;
595
+ // First call returns ids; second call should receive them
596
+ yield {
597
+ kind: 'message',
598
+ messageId: `msg-${sendCallCount}`,
599
+ contextId: `ctx-${sendCallCount}`,
600
+ taskId: `task-${sendCallCount}`,
601
+ role: 'agent',
602
+ parts: [{ kind: 'text', text: `Response ${sendCallCount}` }],
603
+ };
604
+ });
605
+ const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
606
+ // First send — establishes contextId/taskId
607
+ await session.send({
608
+ message: { content: [{ type: 'text', text: 'first' }] },
609
+ });
610
+ await session.getResult();
611
+ // Second send — should pass the persisted contextId/taskId
612
+ await session.send({
613
+ message: { content: [{ type: 'text', text: 'second' }] },
614
+ });
615
+ await session.getResult();
616
+ // Verify the second call received the contextId/taskId from first call
617
+ expect(mockClientManager.sendMessageStream).toHaveBeenCalledTimes(2);
618
+ const secondCallOpts = mockClientManager.sendMessageStream.mock.calls[1]?.[2];
619
+ expect(secondCallOpts).toHaveProperty('contextId', 'ctx-1');
620
+ expect(secondCallOpts).toHaveProperty('taskId', 'task-1');
621
+ });
622
+ it('getResult() rejects when called before any send', async () => {
623
+ const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
624
+ await expect(session.getResult()).rejects.toThrow('No active or completed stream');
625
+ });
626
+ it('emits fresh agent_start/agent_end per stream', async () => {
627
+ const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
628
+ const events = [];
629
+ session.subscribe((e) => events.push(e));
630
+ // First send
631
+ await session.send({
632
+ message: { content: [{ type: 'text', text: 'first' }] },
633
+ });
634
+ await session.getResult();
635
+ const firstStreamEvents = events.length;
636
+ expect(events[0]?.type).toBe('agent_start');
637
+ expect(events[firstStreamEvents - 1]?.type).toBe('agent_end');
638
+ // Second send
639
+ await session.send({
640
+ message: { content: [{ type: 'text', text: 'second' }] },
641
+ });
642
+ await session.getResult();
643
+ // Should have a second agent_start/agent_end pair
644
+ const secondStreamStart = events[firstStreamEvents];
645
+ const lastEvent = events[events.length - 1];
646
+ expect(secondStreamStart?.type).toBe('agent_start');
647
+ expect(lastEvent?.type).toBe('agent_end');
648
+ expect(secondStreamStart?.streamId).not.toBe(events[0]?.streamId);
649
+ });
650
+ });
651
+ });
652
+ //# sourceMappingURL=remote-subagent-protocol.test.js.map