@namzu/sdk 0.4.2 → 0.4.4

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 (310) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/advisory/context.test.d.ts +16 -0
  3. package/dist/advisory/context.test.d.ts.map +1 -0
  4. package/dist/advisory/context.test.js +92 -0
  5. package/dist/advisory/context.test.js.map +1 -0
  6. package/dist/advisory/evaluator.test.d.ts +34 -0
  7. package/dist/advisory/evaluator.test.d.ts.map +1 -0
  8. package/dist/advisory/evaluator.test.js +172 -0
  9. package/dist/advisory/evaluator.test.js.map +1 -0
  10. package/dist/advisory/executor.test.d.ts +35 -0
  11. package/dist/advisory/executor.test.d.ts.map +1 -0
  12. package/dist/advisory/executor.test.js +233 -0
  13. package/dist/advisory/executor.test.js.map +1 -0
  14. package/dist/advisory/registry.test.d.ts +16 -0
  15. package/dist/advisory/registry.test.d.ts.map +1 -0
  16. package/dist/advisory/registry.test.js +62 -0
  17. package/dist/advisory/registry.test.js.map +1 -0
  18. package/dist/bridge/a2a/agent-card.test.d.ts +24 -0
  19. package/dist/bridge/a2a/agent-card.test.d.ts.map +1 -0
  20. package/dist/bridge/a2a/agent-card.test.js +118 -0
  21. package/dist/bridge/a2a/agent-card.test.js.map +1 -0
  22. package/dist/bridge/a2a/mapper.test.d.ts +29 -0
  23. package/dist/bridge/a2a/mapper.test.d.ts.map +1 -0
  24. package/dist/bridge/a2a/mapper.test.js +265 -0
  25. package/dist/bridge/a2a/mapper.test.js.map +1 -0
  26. package/dist/bridge/a2a/message.test.d.ts +20 -0
  27. package/dist/bridge/a2a/message.test.d.ts.map +1 -0
  28. package/dist/bridge/a2a/message.test.js +116 -0
  29. package/dist/bridge/a2a/message.test.js.map +1 -0
  30. package/dist/bridge/a2a/task.test.d.ts +29 -0
  31. package/dist/bridge/a2a/task.test.d.ts.map +1 -0
  32. package/dist/bridge/a2a/task.test.js +198 -0
  33. package/dist/bridge/a2a/task.test.js.map +1 -0
  34. package/dist/bridge/mcp/connector/adapter.test.d.ts +27 -0
  35. package/dist/bridge/mcp/connector/adapter.test.d.ts.map +1 -0
  36. package/dist/bridge/mcp/connector/adapter.test.js +203 -0
  37. package/dist/bridge/mcp/connector/adapter.test.js.map +1 -0
  38. package/dist/bridge/sse/mapper.test.d.ts +27 -0
  39. package/dist/bridge/sse/mapper.test.d.ts.map +1 -0
  40. package/dist/bridge/sse/mapper.test.js +271 -0
  41. package/dist/bridge/sse/mapper.test.js.map +1 -0
  42. package/dist/bridge/tools/connector/adapter.d.ts +2 -2
  43. package/dist/bridge/tools/connector/adapter.test.d.ts +28 -0
  44. package/dist/bridge/tools/connector/adapter.test.d.ts.map +1 -0
  45. package/dist/bridge/tools/connector/adapter.test.js +182 -0
  46. package/dist/bridge/tools/connector/adapter.test.js.map +1 -0
  47. package/dist/bridge/tools/connector/definitions.test.d.ts +23 -0
  48. package/dist/bridge/tools/connector/definitions.test.d.ts.map +1 -0
  49. package/dist/bridge/tools/connector/definitions.test.js +158 -0
  50. package/dist/bridge/tools/connector/definitions.test.js.map +1 -0
  51. package/dist/bridge/tools/connector/router.test.d.ts +21 -0
  52. package/dist/bridge/tools/connector/router.test.d.ts.map +1 -0
  53. package/dist/bridge/tools/connector/router.test.js +139 -0
  54. package/dist/bridge/tools/connector/router.test.js.map +1 -0
  55. package/dist/bus/breaker.test.d.ts +41 -0
  56. package/dist/bus/breaker.test.d.ts.map +1 -0
  57. package/dist/bus/breaker.test.js +242 -0
  58. package/dist/bus/breaker.test.js.map +1 -0
  59. package/dist/bus/index.d.ts +3 -1
  60. package/dist/bus/index.d.ts.map +1 -1
  61. package/dist/bus/index.js +18 -11
  62. package/dist/bus/index.js.map +1 -1
  63. package/dist/bus/index.test.d.ts +25 -0
  64. package/dist/bus/index.test.d.ts.map +1 -0
  65. package/dist/bus/index.test.js +151 -0
  66. package/dist/bus/index.test.js.map +1 -0
  67. package/dist/bus/lock.test.d.ts +44 -0
  68. package/dist/bus/lock.test.d.ts.map +1 -0
  69. package/dist/bus/lock.test.js +226 -0
  70. package/dist/bus/lock.test.js.map +1 -0
  71. package/dist/bus/ownership.test.d.ts +26 -0
  72. package/dist/bus/ownership.test.d.ts.map +1 -0
  73. package/dist/bus/ownership.test.js +205 -0
  74. package/dist/bus/ownership.test.js.map +1 -0
  75. package/dist/config/runtime.d.ts +28 -28
  76. package/dist/connector/BaseConnector.test.d.ts +21 -0
  77. package/dist/connector/BaseConnector.test.d.ts.map +1 -0
  78. package/dist/connector/BaseConnector.test.js +108 -0
  79. package/dist/connector/BaseConnector.test.js.map +1 -0
  80. package/dist/connector/builtins/http.test.d.ts +30 -0
  81. package/dist/connector/builtins/http.test.d.ts.map +1 -0
  82. package/dist/connector/builtins/http.test.js +232 -0
  83. package/dist/connector/builtins/http.test.js.map +1 -0
  84. package/dist/connector/builtins/webhook.test.d.ts +20 -0
  85. package/dist/connector/builtins/webhook.test.d.ts.map +1 -0
  86. package/dist/connector/builtins/webhook.test.js +113 -0
  87. package/dist/connector/builtins/webhook.test.js.map +1 -0
  88. package/dist/connector/execution/factory.test.d.ts +16 -0
  89. package/dist/connector/execution/factory.test.d.ts.map +1 -0
  90. package/dist/connector/execution/factory.test.js +64 -0
  91. package/dist/connector/execution/factory.test.js.map +1 -0
  92. package/dist/connector/execution/remote.test.d.ts +16 -0
  93. package/dist/connector/execution/remote.test.d.ts.map +1 -0
  94. package/dist/connector/execution/remote.test.js +53 -0
  95. package/dist/connector/execution/remote.test.js.map +1 -0
  96. package/dist/connector/mcp/adapter.test.d.ts +34 -0
  97. package/dist/connector/mcp/adapter.test.d.ts.map +1 -0
  98. package/dist/connector/mcp/adapter.test.js +199 -0
  99. package/dist/connector/mcp/adapter.test.js.map +1 -0
  100. package/dist/probe/context.d.ts +8 -0
  101. package/dist/probe/context.d.ts.map +1 -0
  102. package/dist/probe/context.js +7 -0
  103. package/dist/probe/context.js.map +1 -0
  104. package/dist/probe/errors.d.ts +12 -0
  105. package/dist/probe/errors.d.ts.map +1 -0
  106. package/dist/probe/errors.js +21 -0
  107. package/dist/probe/errors.js.map +1 -0
  108. package/dist/probe/index.d.ts +5 -0
  109. package/dist/probe/index.d.ts.map +1 -0
  110. package/dist/probe/index.js +4 -0
  111. package/dist/probe/index.js.map +1 -0
  112. package/dist/probe/registry.d.ts +24 -0
  113. package/dist/probe/registry.d.ts.map +1 -0
  114. package/dist/probe/registry.js +228 -0
  115. package/dist/probe/registry.js.map +1 -0
  116. package/dist/probe/registry.test.d.ts +7 -0
  117. package/dist/probe/registry.test.d.ts.map +1 -0
  118. package/dist/probe/registry.test.js +310 -0
  119. package/dist/probe/registry.test.js.map +1 -0
  120. package/dist/provider/instrumentation.d.ts +9 -0
  121. package/dist/provider/instrumentation.d.ts.map +1 -0
  122. package/dist/provider/instrumentation.js +104 -0
  123. package/dist/provider/instrumentation.js.map +1 -0
  124. package/dist/provider/instrumentation.test.d.ts +2 -0
  125. package/dist/provider/instrumentation.test.d.ts.map +1 -0
  126. package/dist/provider/instrumentation.test.js +152 -0
  127. package/dist/provider/instrumentation.test.js.map +1 -0
  128. package/dist/public-runtime.d.ts +5 -0
  129. package/dist/public-runtime.d.ts.map +1 -1
  130. package/dist/public-runtime.js +4 -0
  131. package/dist/public-runtime.js.map +1 -1
  132. package/dist/public-types.d.ts +3 -0
  133. package/dist/public-types.d.ts.map +1 -1
  134. package/dist/rag/chunking.test.d.ts +20 -0
  135. package/dist/rag/chunking.test.d.ts.map +1 -0
  136. package/dist/rag/chunking.test.js +92 -0
  137. package/dist/rag/chunking.test.js.map +1 -0
  138. package/dist/rag/context-assembler.test.d.ts +19 -0
  139. package/dist/rag/context-assembler.test.d.ts.map +1 -0
  140. package/dist/rag/context-assembler.test.js +98 -0
  141. package/dist/rag/context-assembler.test.js.map +1 -0
  142. package/dist/rag/embedding.test.d.ts +19 -0
  143. package/dist/rag/embedding.test.d.ts.map +1 -0
  144. package/dist/rag/embedding.test.js +115 -0
  145. package/dist/rag/embedding.test.js.map +1 -0
  146. package/dist/rag/ingestion.test.d.ts +22 -0
  147. package/dist/rag/ingestion.test.d.ts.map +1 -0
  148. package/dist/rag/ingestion.test.js +99 -0
  149. package/dist/rag/ingestion.test.js.map +1 -0
  150. package/dist/rag/knowledge-base.test.d.ts +17 -0
  151. package/dist/rag/knowledge-base.test.d.ts.map +1 -0
  152. package/dist/rag/knowledge-base.test.js +77 -0
  153. package/dist/rag/knowledge-base.test.js.map +1 -0
  154. package/dist/rag/rag-tool.test.d.ts +21 -0
  155. package/dist/rag/rag-tool.test.d.ts.map +1 -0
  156. package/dist/rag/rag-tool.test.js +149 -0
  157. package/dist/rag/rag-tool.test.js.map +1 -0
  158. package/dist/rag/retriever.test.d.ts +26 -0
  159. package/dist/rag/retriever.test.d.ts.map +1 -0
  160. package/dist/rag/retriever.test.js +180 -0
  161. package/dist/rag/retriever.test.js.map +1 -0
  162. package/dist/rag/vector-store.test.d.ts +38 -0
  163. package/dist/rag/vector-store.test.d.ts.map +1 -0
  164. package/dist/rag/vector-store.test.js +175 -0
  165. package/dist/rag/vector-store.test.js.map +1 -0
  166. package/dist/registry/ManagedRegistry.test.d.ts +21 -0
  167. package/dist/registry/ManagedRegistry.test.d.ts.map +1 -0
  168. package/dist/registry/ManagedRegistry.test.js +98 -0
  169. package/dist/registry/ManagedRegistry.test.js.map +1 -0
  170. package/dist/registry/Registry.test.d.ts +18 -0
  171. package/dist/registry/Registry.test.d.ts.map +1 -0
  172. package/dist/registry/Registry.test.js +79 -0
  173. package/dist/registry/Registry.test.js.map +1 -0
  174. package/dist/registry/agent/definitions.test.d.ts +15 -0
  175. package/dist/registry/agent/definitions.test.d.ts.map +1 -0
  176. package/dist/registry/agent/definitions.test.js +84 -0
  177. package/dist/registry/agent/definitions.test.js.map +1 -0
  178. package/dist/registry/connector/definitions.test.d.ts +13 -0
  179. package/dist/registry/connector/definitions.test.d.ts.map +1 -0
  180. package/dist/registry/connector/definitions.test.js +41 -0
  181. package/dist/registry/connector/definitions.test.js.map +1 -0
  182. package/dist/registry/connector/scoped.test.d.ts +21 -0
  183. package/dist/registry/connector/scoped.test.d.ts.map +1 -0
  184. package/dist/registry/connector/scoped.test.js +115 -0
  185. package/dist/registry/connector/scoped.test.js.map +1 -0
  186. package/dist/registry/plugin/index.test.d.ts +12 -0
  187. package/dist/registry/plugin/index.test.d.ts.map +1 -0
  188. package/dist/registry/plugin/index.test.js +69 -0
  189. package/dist/registry/plugin/index.test.js.map +1 -0
  190. package/dist/registry/tool/execute.test.d.ts +42 -0
  191. package/dist/registry/tool/execute.test.d.ts.map +1 -0
  192. package/dist/registry/tool/execute.test.js +281 -0
  193. package/dist/registry/tool/execute.test.js.map +1 -0
  194. package/dist/runtime/query/events.d.ts +3 -1
  195. package/dist/runtime/query/events.d.ts.map +1 -1
  196. package/dist/runtime/query/events.js +6 -1
  197. package/dist/runtime/query/events.js.map +1 -1
  198. package/dist/runtime/query/executor.d.ts +3 -1
  199. package/dist/runtime/query/executor.d.ts.map +1 -1
  200. package/dist/runtime/query/executor.js +30 -1
  201. package/dist/runtime/query/executor.js.map +1 -1
  202. package/dist/runtime/query/iteration/phases/advisory.test.d.ts +42 -0
  203. package/dist/runtime/query/iteration/phases/advisory.test.d.ts.map +1 -0
  204. package/dist/runtime/query/iteration/phases/advisory.test.js +334 -0
  205. package/dist/runtime/query/iteration/phases/advisory.test.js.map +1 -0
  206. package/dist/test-setup.d.ts +22 -0
  207. package/dist/test-setup.d.ts.map +1 -0
  208. package/dist/test-setup.js +23 -0
  209. package/dist/test-setup.js.map +1 -0
  210. package/dist/types/bus/index.d.ts +46 -2
  211. package/dist/types/bus/index.d.ts.map +1 -1
  212. package/dist/types/doctor/check.d.ts +41 -0
  213. package/dist/types/doctor/check.d.ts.map +1 -0
  214. package/dist/types/doctor/check.js +2 -0
  215. package/dist/types/doctor/check.js.map +1 -0
  216. package/dist/types/doctor/index.d.ts +2 -0
  217. package/dist/types/doctor/index.d.ts.map +1 -0
  218. package/dist/types/doctor/index.js +2 -0
  219. package/dist/types/doctor/index.js.map +1 -0
  220. package/dist/types/probe/event-kind.d.ts +6 -0
  221. package/dist/types/probe/event-kind.d.ts.map +1 -0
  222. package/dist/types/probe/event-kind.js +2 -0
  223. package/dist/types/probe/event-kind.js.map +1 -0
  224. package/dist/types/probe/event-of.d.ts +5 -0
  225. package/dist/types/probe/event-of.d.ts.map +1 -0
  226. package/dist/types/probe/event-of.js +2 -0
  227. package/dist/types/probe/event-of.js.map +1 -0
  228. package/dist/types/probe/index.d.ts +4 -0
  229. package/dist/types/probe/index.d.ts.map +1 -0
  230. package/dist/types/probe/index.js +2 -0
  231. package/dist/types/probe/index.js.map +1 -0
  232. package/dist/types/probe/registry.d.ts +27 -0
  233. package/dist/types/probe/registry.d.ts.map +1 -0
  234. package/dist/types/probe/registry.js +2 -0
  235. package/dist/types/probe/registry.js.map +1 -0
  236. package/dist/utils/logger.d.ts +1 -1
  237. package/dist/utils/logger.d.ts.map +1 -1
  238. package/dist/utils/logger.js +5 -0
  239. package/dist/utils/logger.js.map +1 -1
  240. package/dist/vault/instrumentation.d.ts +11 -0
  241. package/dist/vault/instrumentation.d.ts.map +1 -0
  242. package/dist/vault/instrumentation.js +32 -0
  243. package/dist/vault/instrumentation.js.map +1 -0
  244. package/dist/vault/instrumentation.test.d.ts +2 -0
  245. package/dist/vault/instrumentation.test.d.ts.map +1 -0
  246. package/dist/vault/instrumentation.test.js +80 -0
  247. package/dist/vault/instrumentation.test.js.map +1 -0
  248. package/package.json +4 -1
  249. package/src/advisory/context.test.ts +109 -0
  250. package/src/advisory/evaluator.test.ts +192 -0
  251. package/src/advisory/executor.test.ts +272 -0
  252. package/src/advisory/registry.test.ts +75 -0
  253. package/src/bridge/a2a/agent-card.test.ts +140 -0
  254. package/src/bridge/a2a/mapper.test.ts +293 -0
  255. package/src/bridge/a2a/message.test.ts +138 -0
  256. package/src/bridge/a2a/task.test.ts +235 -0
  257. package/src/bridge/mcp/connector/adapter.test.ts +230 -0
  258. package/src/bridge/sse/mapper.test.ts +422 -0
  259. package/src/bridge/tools/connector/adapter.test.ts +224 -0
  260. package/src/bridge/tools/connector/definitions.test.ts +183 -0
  261. package/src/bridge/tools/connector/router.test.ts +159 -0
  262. package/src/bus/breaker.test.ts +274 -0
  263. package/src/bus/index.test.ts +183 -0
  264. package/src/bus/index.ts +21 -10
  265. package/src/bus/lock.test.ts +265 -0
  266. package/src/bus/ownership.test.ts +243 -0
  267. package/src/connector/BaseConnector.test.ts +130 -0
  268. package/src/connector/builtins/http.test.ts +290 -0
  269. package/src/connector/builtins/webhook.test.ts +138 -0
  270. package/src/connector/execution/factory.test.ts +75 -0
  271. package/src/connector/execution/remote.test.ts +63 -0
  272. package/src/connector/mcp/adapter.test.ts +249 -0
  273. package/src/probe/context.ts +14 -0
  274. package/src/probe/errors.ts +27 -0
  275. package/src/probe/index.ts +4 -0
  276. package/src/probe/registry.test.ts +480 -0
  277. package/src/probe/registry.ts +276 -0
  278. package/src/provider/instrumentation.test.ts +192 -0
  279. package/src/provider/instrumentation.ts +139 -0
  280. package/src/public-runtime.ts +17 -0
  281. package/src/public-types.ts +3 -0
  282. package/src/rag/chunking.test.ts +107 -0
  283. package/src/rag/context-assembler.test.ts +114 -0
  284. package/src/rag/embedding.test.ts +130 -0
  285. package/src/rag/ingestion.test.ts +114 -0
  286. package/src/rag/knowledge-base.test.ts +106 -0
  287. package/src/rag/rag-tool.test.ts +167 -0
  288. package/src/rag/retriever.test.ts +210 -0
  289. package/src/rag/vector-store.test.ts +196 -0
  290. package/src/registry/ManagedRegistry.test.ts +118 -0
  291. package/src/registry/Registry.test.ts +91 -0
  292. package/src/registry/agent/definitions.test.ts +100 -0
  293. package/src/registry/connector/definitions.test.ts +51 -0
  294. package/src/registry/connector/scoped.test.ts +129 -0
  295. package/src/registry/plugin/index.test.ts +85 -0
  296. package/src/registry/tool/execute.test.ts +330 -0
  297. package/src/runtime/query/events.ts +6 -1
  298. package/src/runtime/query/executor.ts +34 -0
  299. package/src/runtime/query/iteration/phases/advisory.test.ts +412 -0
  300. package/src/test-setup.ts +24 -0
  301. package/src/types/bus/index.ts +54 -2
  302. package/src/types/doctor/check.ts +53 -0
  303. package/src/types/doctor/index.ts +9 -0
  304. package/src/types/probe/event-kind.ts +8 -0
  305. package/src/types/probe/event-of.ts +3 -0
  306. package/src/types/probe/index.ts +11 -0
  307. package/src/types/probe/registry.ts +36 -0
  308. package/src/utils/logger.ts +6 -1
  309. package/src/vault/instrumentation.test.ts +98 -0
  310. package/src/vault/instrumentation.ts +56 -0
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Current-code invariants asserted (2026-04-21, ses_006 Phase 2):
3
+ *
4
+ * - `createConnectorExecuteTool(config)` returns a `defineTool`-wrapped
5
+ * tool named `connector_execute` that:
6
+ * - Returns `{success: false, error: '... not found'}` when instance
7
+ * is missing.
8
+ * - Returns `{success: false, error: '... not connected'}` when the
9
+ * instance exists but `status !== 'connected'`.
10
+ * - Returns `{success: false, error: ...}` when `manager.execute`
11
+ * itself reports failure; falls back to 'Connector execution
12
+ * failed' when the execute error is undefined.
13
+ * - On success, stringifies non-string outputs to JSON; strings pass
14
+ * through as-is.
15
+ * - Attaches `data: { durationMs, metadata }` on success.
16
+ * - `createConnectorListTool(config)` returns a tool named
17
+ * `connector_list` that:
18
+ * - Emits "No connector instances configured." when none exist.
19
+ * - Lists each instance with `- <id> (<connectorId>): <name> [<status>]`.
20
+ * - `createConnectorTools(config)` returns the pair of tools above.
21
+ */
22
+ import { describe, expect, it, vi } from 'vitest';
23
+ import { createConnectorExecuteTool, createConnectorListTool, createConnectorTools, } from './definitions.js';
24
+ const CID = 'conn_http';
25
+ const IID = 'ci_abc123';
26
+ function makeInstance(overrides = {}) {
27
+ return {
28
+ id: IID,
29
+ connectorId: CID,
30
+ config: { connectorId: CID, name: 'default' },
31
+ status: 'connected',
32
+ createdAt: Date.now(),
33
+ ...overrides,
34
+ };
35
+ }
36
+ function makeManager(overrides = {}) {
37
+ return {
38
+ getInstance: vi.fn(() => makeInstance()),
39
+ listInstances: vi.fn(() => [makeInstance()]),
40
+ execute: vi.fn(),
41
+ ...overrides,
42
+ };
43
+ }
44
+ const ctx = {};
45
+ describe('createConnectorExecuteTool', () => {
46
+ it('is named connector_execute', () => {
47
+ const tool = createConnectorExecuteTool({ manager: makeManager() });
48
+ expect(tool.name).toBe('connector_execute');
49
+ });
50
+ it('returns error when instance is missing', async () => {
51
+ const manager = makeManager({
52
+ getInstance: vi.fn(() => undefined),
53
+ });
54
+ const tool = createConnectorExecuteTool({ manager });
55
+ const result = await tool.execute({ instance_id: IID, method: 'request', input: {} }, ctx);
56
+ expect(result.success).toBe(false);
57
+ expect(result.error).toMatch(/not found/);
58
+ });
59
+ it('returns error when instance status is not "connected"', async () => {
60
+ const manager = makeManager({
61
+ getInstance: vi.fn(() => makeInstance({ status: 'disconnected' })),
62
+ });
63
+ const tool = createConnectorExecuteTool({ manager });
64
+ const result = await tool.execute({ instance_id: IID, method: 'request', input: {} }, ctx);
65
+ expect(result.success).toBe(false);
66
+ expect(result.error).toMatch(/not connected/);
67
+ expect(result.error).toMatch(/disconnected/);
68
+ });
69
+ it('wraps manager.execute failure; falls back to "Connector execution failed" on undefined error', async () => {
70
+ const manager = makeManager();
71
+ vi.mocked(manager.execute).mockResolvedValueOnce({
72
+ success: false,
73
+ output: undefined,
74
+ durationMs: 1,
75
+ });
76
+ const tool = createConnectorExecuteTool({ manager });
77
+ const result = await tool.execute({ instance_id: IID, method: 'request', input: {} }, ctx);
78
+ expect(result.success).toBe(false);
79
+ expect(result.error).toBe('Connector execution failed');
80
+ });
81
+ it('carries the explicit error message when provided by manager.execute', async () => {
82
+ const manager = makeManager();
83
+ vi.mocked(manager.execute).mockResolvedValueOnce({
84
+ success: false,
85
+ output: undefined,
86
+ durationMs: 1,
87
+ error: 'timeout',
88
+ });
89
+ const tool = createConnectorExecuteTool({ manager });
90
+ const result = await tool.execute({ instance_id: IID, method: 'request', input: {} }, ctx);
91
+ expect(result.error).toBe('timeout');
92
+ });
93
+ it('passes through string outputs as-is', async () => {
94
+ const manager = makeManager();
95
+ vi.mocked(manager.execute).mockResolvedValueOnce({
96
+ success: true,
97
+ output: 'hi there',
98
+ durationMs: 10,
99
+ });
100
+ const tool = createConnectorExecuteTool({ manager });
101
+ const result = await tool.execute({ instance_id: IID, method: 'request', input: {} }, ctx);
102
+ expect(result.success).toBe(true);
103
+ expect(result.output).toBe('hi there');
104
+ });
105
+ it('stringifies non-string outputs + attaches durationMs + metadata to data', async () => {
106
+ const manager = makeManager();
107
+ vi.mocked(manager.execute).mockResolvedValueOnce({
108
+ success: true,
109
+ output: { answer: 42 },
110
+ durationMs: 15,
111
+ metadata: { region: 'us-east-1' },
112
+ });
113
+ const tool = createConnectorExecuteTool({ manager });
114
+ const result = await tool.execute({ instance_id: IID, method: 'request', input: {} }, ctx);
115
+ expect(result.output).toBe(JSON.stringify({ answer: 42 }, null, 2));
116
+ expect(result.data).toEqual({ durationMs: 15, metadata: { region: 'us-east-1' } });
117
+ });
118
+ });
119
+ describe('createConnectorListTool', () => {
120
+ it('is named connector_list + read-only', () => {
121
+ const tool = createConnectorListTool({ manager: makeManager() });
122
+ expect(tool.name).toBe('connector_list');
123
+ expect(tool.isReadOnly?.({})).toBe(true);
124
+ });
125
+ it('emits "No connector instances configured." when none', async () => {
126
+ const manager = makeManager({
127
+ listInstances: vi.fn(() => []),
128
+ });
129
+ const tool = createConnectorListTool({ manager });
130
+ const result = await tool.execute({}, ctx);
131
+ expect(result.success).toBe(true);
132
+ expect(result.output).toBe('No connector instances configured.');
133
+ });
134
+ it('lists every instance with id / connectorId / name / status', async () => {
135
+ const manager = makeManager({
136
+ listInstances: vi.fn(() => [
137
+ makeInstance({ id: IID }),
138
+ makeInstance({
139
+ id: 'ci_def',
140
+ status: 'disconnected',
141
+ }),
142
+ ]),
143
+ });
144
+ const tool = createConnectorListTool({ manager });
145
+ const result = await tool.execute({}, ctx);
146
+ expect(result.success).toBe(true);
147
+ expect(result.output).toContain(`- ${IID} (${CID}): default [connected]`);
148
+ expect(result.output).toContain(`- ci_def (${CID}): default [disconnected]`);
149
+ expect(result.data).toBeDefined();
150
+ });
151
+ });
152
+ describe('createConnectorTools', () => {
153
+ it('returns both execute + list tools', () => {
154
+ const tools = createConnectorTools({ manager: makeManager() });
155
+ expect(tools.map((t) => t.name)).toEqual(['connector_execute', 'connector_list']);
156
+ });
157
+ });
158
+ //# sourceMappingURL=definitions.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"definitions.test.js","sourceRoot":"","sources":["../../../../src/bridge/tools/connector/definitions.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Current-code invariants asserted (2026-04-21, ses_006 Phase 2):
3
+ *
4
+ * - `new ConnectorToolRouter({ manager })` defaults strategy to
5
+ * `'per-method'`.
6
+ * - `getTools()` with strategy `'router'`:
7
+ * - Returns `[]` when there are no connected instances.
8
+ * - Returns a single `connector_execute` routing tool otherwise.
9
+ * - `getTools()` with strategy `'per-method'`:
10
+ * - Emits one tool per method per connected instance.
11
+ * - Catches errors per-instance (logs + skips) — a broken instance
12
+ * does not poison the entire tool list.
13
+ * - `registerTools(registry)` delegates to `registry.register` for
14
+ * every tool and returns the list of names.
15
+ * - `unregisterTools(registry, names)` calls `registry.unregister`
16
+ * for each name.
17
+ * - `refreshTools(registry, previous)` is unregister-then-register in
18
+ * one call; returns the new names list.
19
+ */
20
+ export {};
21
+ //# sourceMappingURL=router.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.test.d.ts","sourceRoot":"","sources":["../../../../src/bridge/tools/connector/router.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG"}
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Current-code invariants asserted (2026-04-21, ses_006 Phase 2):
3
+ *
4
+ * - `new ConnectorToolRouter({ manager })` defaults strategy to
5
+ * `'per-method'`.
6
+ * - `getTools()` with strategy `'router'`:
7
+ * - Returns `[]` when there are no connected instances.
8
+ * - Returns a single `connector_execute` routing tool otherwise.
9
+ * - `getTools()` with strategy `'per-method'`:
10
+ * - Emits one tool per method per connected instance.
11
+ * - Catches errors per-instance (logs + skips) — a broken instance
12
+ * does not poison the entire tool list.
13
+ * - `registerTools(registry)` delegates to `registry.register` for
14
+ * every tool and returns the list of names.
15
+ * - `unregisterTools(registry, names)` calls `registry.unregister`
16
+ * for each name.
17
+ * - `refreshTools(registry, previous)` is unregister-then-register in
18
+ * one call; returns the new names list.
19
+ */
20
+ import { describe, expect, it, vi } from 'vitest';
21
+ import { z } from 'zod';
22
+ import { ConnectorToolRouter } from './router.js';
23
+ const CID = 'conn_http';
24
+ const IID1 = 'ci_a';
25
+ const IID2 = 'ci_b';
26
+ function makeDefinition() {
27
+ return {
28
+ id: CID,
29
+ name: 'HTTP',
30
+ description: 'x',
31
+ connectionType: 'http',
32
+ configSchema: z.object({}),
33
+ methods: [
34
+ { name: 'request', description: 'd', inputSchema: z.object({}) },
35
+ { name: 'send', description: 'd', inputSchema: z.object({}) },
36
+ ],
37
+ };
38
+ }
39
+ function makeInstance(id) {
40
+ return {
41
+ id,
42
+ connectorId: CID,
43
+ config: { connectorId: CID, name: 'x' },
44
+ status: 'connected',
45
+ createdAt: Date.now(),
46
+ };
47
+ }
48
+ function makeManager(instances) {
49
+ const def = makeDefinition();
50
+ const registry = {
51
+ getOrThrow: vi.fn(() => def),
52
+ get: vi.fn(() => def),
53
+ };
54
+ return {
55
+ getInstance: vi.fn((id) => instances.find((i) => i.id === id)),
56
+ getRegistry: vi.fn(() => registry),
57
+ listConnectedInstances: vi.fn(() => instances),
58
+ listInstances: vi.fn(() => instances),
59
+ execute: vi.fn(),
60
+ };
61
+ }
62
+ function makeToolRegistry() {
63
+ return {
64
+ register: vi.fn(),
65
+ unregister: vi.fn(() => true),
66
+ clear: vi.fn(),
67
+ };
68
+ }
69
+ describe('ConnectorToolRouter', () => {
70
+ it('defaults strategy to per-method', () => {
71
+ const router = new ConnectorToolRouter({ manager: makeManager([makeInstance(IID1)]) });
72
+ const tools = router.getTools();
73
+ expect(tools.map((t) => t.name)).toEqual([`${CID}_request`, `${CID}_send`]);
74
+ });
75
+ it('router strategy with connected instances emits one connector_execute tool', () => {
76
+ const router = new ConnectorToolRouter({
77
+ manager: makeManager([makeInstance(IID1)]),
78
+ strategy: 'router',
79
+ });
80
+ const tools = router.getTools();
81
+ expect(tools).toHaveLength(1);
82
+ expect(tools[0]?.name).toBe('connector_execute');
83
+ });
84
+ it('router strategy with no connected instances returns empty array', () => {
85
+ const router = new ConnectorToolRouter({
86
+ manager: makeManager([]),
87
+ strategy: 'router',
88
+ });
89
+ expect(router.getTools()).toEqual([]);
90
+ });
91
+ it('per-method strategy with multiple instances emits methods per-instance', () => {
92
+ const router = new ConnectorToolRouter({
93
+ manager: makeManager([makeInstance(IID1), makeInstance(IID2)]),
94
+ });
95
+ const tools = router.getTools();
96
+ expect(tools).toHaveLength(4); // 2 methods * 2 instances
97
+ });
98
+ it('per-method strategy skips a broken instance + continues with others', () => {
99
+ const good = makeInstance(IID1);
100
+ const bad = makeInstance(IID2);
101
+ const manager = makeManager([good, bad]);
102
+ // make instance IID2 "not found" by overriding getInstance
103
+ vi.mocked(manager.getInstance).mockImplementation((id) => (id === IID1 ? good : undefined));
104
+ const router = new ConnectorToolRouter({ manager });
105
+ const tools = router.getTools();
106
+ // 2 from IID1; IID2 threw + got caught
107
+ expect(tools.map((t) => t.name)).toEqual([`${CID}_request`, `${CID}_send`]);
108
+ });
109
+ });
110
+ describe('ConnectorToolRouter.registerTools', () => {
111
+ it('registers every tool and returns the names', () => {
112
+ const router = new ConnectorToolRouter({ manager: makeManager([makeInstance(IID1)]) });
113
+ const reg = makeToolRegistry();
114
+ const names = router.registerTools(reg);
115
+ expect(names).toEqual([`${CID}_request`, `${CID}_send`]);
116
+ expect(reg.register).toHaveBeenCalledTimes(2);
117
+ });
118
+ });
119
+ describe('ConnectorToolRouter.unregisterTools', () => {
120
+ it('unregisters each named tool', () => {
121
+ const router = new ConnectorToolRouter({ manager: makeManager([]) });
122
+ const reg = makeToolRegistry();
123
+ router.unregisterTools(reg, ['a', 'b']);
124
+ expect(reg.unregister).toHaveBeenCalledWith('a');
125
+ expect(reg.unregister).toHaveBeenCalledWith('b');
126
+ expect(reg.unregister).toHaveBeenCalledTimes(2);
127
+ });
128
+ });
129
+ describe('ConnectorToolRouter.refreshTools', () => {
130
+ it('unregisters previous names then registers new ones', () => {
131
+ const router = new ConnectorToolRouter({ manager: makeManager([makeInstance(IID1)]) });
132
+ const reg = makeToolRegistry();
133
+ const newNames = router.refreshTools(reg, [`${CID}_old_method`]);
134
+ expect(reg.unregister).toHaveBeenCalledWith(`${CID}_old_method`);
135
+ expect(newNames).toEqual([`${CID}_request`, `${CID}_send`]);
136
+ expect(reg.register).toHaveBeenCalledTimes(2);
137
+ });
138
+ });
139
+ //# sourceMappingURL=router.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.test.js","sourceRoot":"","sources":["../../../../src/bridge/tools/connector/router.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Current-code invariants asserted (2026-04-21, ses_006 Phase 1):
3
+ *
4
+ * - `canExecute(runId)` on an unknown runId returns true (no breaker
5
+ * means no constraint).
6
+ * - `recordFailure(runId)` lazily creates a breaker entry in `closed`
7
+ * state for unknown runIds; `recordSuccess(runId)` is a no-op when
8
+ * no entry exists.
9
+ * - Consecutive failures: after `failureThreshold` calls in a row
10
+ * without intervening success, `state` → `open`, `trippedAt` set,
11
+ * single `breaker_tripped` event.
12
+ * - In `open` state, `canExecute` returns false until `resetTimeoutMs`
13
+ * has elapsed since `trippedAt`; at that point it transitions to
14
+ * `half_open`, emits `breaker_half_open`, and returns true.
15
+ * - In `half_open`, `canExecute` keeps returning true (the probe is
16
+ * allowed more than once) until the next `recordSuccess` closes the
17
+ * breaker or the next `recordFailure` re-trips it.
18
+ * - `recordSuccess` in `half_open` → `closed`; emits
19
+ * `breaker_probe_success` then `breaker_reset` (two events, in
20
+ * that order).
21
+ * - `recordFailure` in `half_open` → `open`; emits
22
+ * `breaker_probe_failure` then `breaker_tripped` (two events).
23
+ * The `consecutiveFailures` count carries over (not reset by the
24
+ * half-open probe cycle).
25
+ * - `recordSuccess` in `closed` resets `consecutiveFailures` to 0 and
26
+ * updates `lastSuccessAt`; emits nothing.
27
+ * - `recordSuccess` while `open`: logs a warning, does NOT change
28
+ * state (behaviour is "discarded"); emits nothing.
29
+ * - `recordFailure` while `open`: no additional events; state stays
30
+ * open (consecutive counter does NOT advance from `recordFailure`
31
+ * while open in the current implementation either — see test).
32
+ * - `reset(runId)` forces the breaker to `closed` regardless of prior
33
+ * state; emits `breaker_reset`; clears `consecutiveFailures` and
34
+ * `trippedAt` but preserves `lastFailureAt` / `lastSuccessAt`.
35
+ * - `listTripped()` returns snapshots for every breaker in `open` or
36
+ * `half_open`; closed breakers are excluded.
37
+ * - Breaker entries are keyed per-`RunId`; state does not leak across
38
+ * runs. No per-tenant dimension (design.md §2.1 aspirational).
39
+ */
40
+ export {};
41
+ //# sourceMappingURL=breaker.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"breaker.test.d.ts","sourceRoot":"","sources":["../../src/bus/breaker.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG"}
@@ -0,0 +1,242 @@
1
+ /**
2
+ * Current-code invariants asserted (2026-04-21, ses_006 Phase 1):
3
+ *
4
+ * - `canExecute(runId)` on an unknown runId returns true (no breaker
5
+ * means no constraint).
6
+ * - `recordFailure(runId)` lazily creates a breaker entry in `closed`
7
+ * state for unknown runIds; `recordSuccess(runId)` is a no-op when
8
+ * no entry exists.
9
+ * - Consecutive failures: after `failureThreshold` calls in a row
10
+ * without intervening success, `state` → `open`, `trippedAt` set,
11
+ * single `breaker_tripped` event.
12
+ * - In `open` state, `canExecute` returns false until `resetTimeoutMs`
13
+ * has elapsed since `trippedAt`; at that point it transitions to
14
+ * `half_open`, emits `breaker_half_open`, and returns true.
15
+ * - In `half_open`, `canExecute` keeps returning true (the probe is
16
+ * allowed more than once) until the next `recordSuccess` closes the
17
+ * breaker or the next `recordFailure` re-trips it.
18
+ * - `recordSuccess` in `half_open` → `closed`; emits
19
+ * `breaker_probe_success` then `breaker_reset` (two events, in
20
+ * that order).
21
+ * - `recordFailure` in `half_open` → `open`; emits
22
+ * `breaker_probe_failure` then `breaker_tripped` (two events).
23
+ * The `consecutiveFailures` count carries over (not reset by the
24
+ * half-open probe cycle).
25
+ * - `recordSuccess` in `closed` resets `consecutiveFailures` to 0 and
26
+ * updates `lastSuccessAt`; emits nothing.
27
+ * - `recordSuccess` while `open`: logs a warning, does NOT change
28
+ * state (behaviour is "discarded"); emits nothing.
29
+ * - `recordFailure` while `open`: no additional events; state stays
30
+ * open (consecutive counter does NOT advance from `recordFailure`
31
+ * while open in the current implementation either — see test).
32
+ * - `reset(runId)` forces the breaker to `closed` regardless of prior
33
+ * state; emits `breaker_reset`; clears `consecutiveFailures` and
34
+ * `trippedAt` but preserves `lastFailureAt` / `lastSuccessAt`.
35
+ * - `listTripped()` returns snapshots for every breaker in `open` or
36
+ * `half_open`; closed breakers are excluded.
37
+ * - Breaker entries are keyed per-`RunId`; state does not leak across
38
+ * runs. No per-tenant dimension (design.md §2.1 aspirational).
39
+ */
40
+ import fc from 'fast-check';
41
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
42
+ import { CircuitBreaker } from './breaker.js';
43
+ function makeLogger() {
44
+ const stub = { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() };
45
+ return { ...stub, child: vi.fn(() => ({ ...stub, child: vi.fn() })) };
46
+ }
47
+ function runId(n) {
48
+ return `run_${n}`;
49
+ }
50
+ const THRESHOLD = 5;
51
+ const RESET_MS = 30_000;
52
+ describe('CircuitBreaker', () => {
53
+ let events;
54
+ let breaker;
55
+ beforeEach(() => {
56
+ events = [];
57
+ breaker = new CircuitBreaker(makeLogger(), (e) => events.push(e), THRESHOLD, RESET_MS);
58
+ });
59
+ afterEach(() => {
60
+ vi.useRealTimers();
61
+ });
62
+ describe('canExecute', () => {
63
+ it('returns true for an unknown runId (no breaker entry yet)', () => {
64
+ expect(breaker.canExecute(runId(1))).toBe(true);
65
+ });
66
+ });
67
+ describe('recordFailure', () => {
68
+ it('lazily creates a closed breaker entry for unknown runIds', () => {
69
+ breaker.recordFailure(runId(1));
70
+ const snap = breaker.getSnapshot(runId(1));
71
+ expect(snap?.state).toBe('closed');
72
+ expect(snap?.consecutiveFailures).toBe(1);
73
+ });
74
+ it('trips after exactly `failureThreshold` consecutive failures', () => {
75
+ for (let i = 0; i < THRESHOLD - 1; i++) {
76
+ breaker.recordFailure(runId(1));
77
+ }
78
+ expect(breaker.getSnapshot(runId(1))?.state).toBe('closed');
79
+ expect(events.filter((e) => e.type === 'breaker_tripped')).toHaveLength(0);
80
+ breaker.recordFailure(runId(1));
81
+ expect(breaker.getSnapshot(runId(1))?.state).toBe('open');
82
+ const trippedEvents = events.filter((e) => e.type === 'breaker_tripped');
83
+ expect(trippedEvents).toHaveLength(1);
84
+ if (trippedEvents[0]?.type === 'breaker_tripped') {
85
+ expect(trippedEvents[0].consecutiveFailures).toBe(THRESHOLD);
86
+ }
87
+ });
88
+ it('trips exactly once — further failures in `open` emit no new breaker_tripped', () => {
89
+ for (let i = 0; i < THRESHOLD; i++)
90
+ breaker.recordFailure(runId(1));
91
+ events.length = 0;
92
+ breaker.recordFailure(runId(1));
93
+ breaker.recordFailure(runId(1));
94
+ expect(events).toEqual([]);
95
+ });
96
+ it('property: any N ≥ threshold consecutive failures trips exactly once', () => {
97
+ fc.assert(fc.property(fc.integer({ min: THRESHOLD, max: THRESHOLD * 4 }), (n) => {
98
+ const local = new CircuitBreaker(makeLogger(), () => { }, THRESHOLD, RESET_MS);
99
+ for (let i = 0; i < n; i++)
100
+ local.recordFailure(runId(999));
101
+ expect(local.getSnapshot(runId(999))?.state).toBe('open');
102
+ }), { numRuns: 25 });
103
+ });
104
+ it('property: any N < threshold keeps the breaker closed', () => {
105
+ fc.assert(fc.property(fc.integer({ min: 0, max: THRESHOLD - 1 }), (n) => {
106
+ const local = new CircuitBreaker(makeLogger(), () => { }, THRESHOLD, RESET_MS);
107
+ for (let i = 0; i < n; i++)
108
+ local.recordFailure(runId(888));
109
+ const snap = local.getSnapshot(runId(888));
110
+ if (n === 0) {
111
+ expect(snap).toBeUndefined();
112
+ }
113
+ else {
114
+ expect(snap?.state).toBe('closed');
115
+ }
116
+ }), { numRuns: 25 });
117
+ });
118
+ });
119
+ describe('recordSuccess', () => {
120
+ it('is a no-op on an unknown runId — no breaker entry created', () => {
121
+ breaker.recordSuccess(runId(42));
122
+ expect(breaker.getSnapshot(runId(42))).toBeUndefined();
123
+ expect(events).toEqual([]);
124
+ });
125
+ it('resets consecutiveFailures to 0 without changing closed state', () => {
126
+ breaker.recordFailure(runId(1));
127
+ breaker.recordFailure(runId(1));
128
+ breaker.recordSuccess(runId(1));
129
+ const snap = breaker.getSnapshot(runId(1));
130
+ expect(snap?.state).toBe('closed');
131
+ expect(snap?.consecutiveFailures).toBe(0);
132
+ expect(snap?.lastSuccessAt).toBeDefined();
133
+ });
134
+ it('is discarded (warned, no state change) when called while breaker is open', () => {
135
+ for (let i = 0; i < THRESHOLD; i++)
136
+ breaker.recordFailure(runId(1));
137
+ events.length = 0;
138
+ breaker.recordSuccess(runId(1));
139
+ expect(breaker.getSnapshot(runId(1))?.state).toBe('open');
140
+ expect(events).toEqual([]);
141
+ });
142
+ });
143
+ describe('open → half_open transition', () => {
144
+ it('transitions to half_open after resetTimeoutMs elapsed; emits breaker_half_open', () => {
145
+ vi.useFakeTimers();
146
+ const trip = new CircuitBreaker(makeLogger(), (e) => events.push(e), THRESHOLD, RESET_MS);
147
+ for (let i = 0; i < THRESHOLD; i++)
148
+ trip.recordFailure(runId(1));
149
+ events.length = 0;
150
+ expect(trip.canExecute(runId(1))).toBe(false);
151
+ vi.advanceTimersByTime(RESET_MS - 1);
152
+ expect(trip.canExecute(runId(1))).toBe(false);
153
+ vi.advanceTimersByTime(1);
154
+ expect(trip.canExecute(runId(1))).toBe(true);
155
+ expect(trip.getSnapshot(runId(1))?.state).toBe('half_open');
156
+ expect(events).toEqual([{ type: 'breaker_half_open', agentRunId: runId(1) }]);
157
+ });
158
+ it('canExecute in half_open keeps returning true until success or failure resolves', () => {
159
+ vi.useFakeTimers();
160
+ const trip = new CircuitBreaker(makeLogger(), () => { }, THRESHOLD, RESET_MS);
161
+ for (let i = 0; i < THRESHOLD; i++)
162
+ trip.recordFailure(runId(1));
163
+ vi.advanceTimersByTime(RESET_MS);
164
+ trip.canExecute(runId(1)); // flip to half_open
165
+ expect(trip.canExecute(runId(1))).toBe(true);
166
+ expect(trip.canExecute(runId(1))).toBe(true);
167
+ });
168
+ });
169
+ describe('half_open → closed / open resolution', () => {
170
+ function setupHalfOpen() {
171
+ vi.useFakeTimers();
172
+ const b = new CircuitBreaker(makeLogger(), (e) => events.push(e), THRESHOLD, RESET_MS);
173
+ for (let i = 0; i < THRESHOLD; i++)
174
+ b.recordFailure(runId(1));
175
+ vi.advanceTimersByTime(RESET_MS);
176
+ b.canExecute(runId(1)); // flip
177
+ events.length = 0;
178
+ return b;
179
+ }
180
+ it('recordSuccess in half_open closes the breaker + emits probe_success then reset', () => {
181
+ const b = setupHalfOpen();
182
+ b.recordSuccess(runId(1));
183
+ expect(b.getSnapshot(runId(1))?.state).toBe('closed');
184
+ expect(b.getSnapshot(runId(1))?.consecutiveFailures).toBe(0);
185
+ expect(events).toEqual([
186
+ { type: 'breaker_probe_success', agentRunId: runId(1) },
187
+ { type: 'breaker_reset', agentRunId: runId(1) },
188
+ ]);
189
+ });
190
+ it('recordFailure in half_open re-trips + emits probe_failure then tripped', () => {
191
+ const b = setupHalfOpen();
192
+ b.recordFailure(runId(1));
193
+ expect(b.getSnapshot(runId(1))?.state).toBe('open');
194
+ expect(events).toEqual([
195
+ { type: 'breaker_probe_failure', agentRunId: runId(1) },
196
+ {
197
+ type: 'breaker_tripped',
198
+ agentRunId: runId(1),
199
+ consecutiveFailures: THRESHOLD + 1,
200
+ },
201
+ ]);
202
+ });
203
+ });
204
+ describe('reset', () => {
205
+ it('forces a tripped breaker back to closed + emits breaker_reset', () => {
206
+ for (let i = 0; i < THRESHOLD; i++)
207
+ breaker.recordFailure(runId(1));
208
+ events.length = 0;
209
+ breaker.reset(runId(1));
210
+ const snap = breaker.getSnapshot(runId(1));
211
+ expect(snap?.state).toBe('closed');
212
+ expect(snap?.consecutiveFailures).toBe(0);
213
+ expect(snap?.trippedAt).toBeUndefined();
214
+ expect(events).toEqual([{ type: 'breaker_reset', agentRunId: runId(1) }]);
215
+ });
216
+ it('is a no-op on an unknown runId (no event)', () => {
217
+ breaker.reset(runId(999));
218
+ expect(events).toEqual([]);
219
+ });
220
+ });
221
+ describe('per-runId isolation', () => {
222
+ it('tripping one runId does not affect another', () => {
223
+ for (let i = 0; i < THRESHOLD; i++)
224
+ breaker.recordFailure(runId(1));
225
+ expect(breaker.getSnapshot(runId(1))?.state).toBe('open');
226
+ expect(breaker.canExecute(runId(2))).toBe(true);
227
+ expect(breaker.getSnapshot(runId(2))).toBeUndefined();
228
+ });
229
+ });
230
+ describe('listTripped', () => {
231
+ it('returns only open + half_open breakers; excludes closed', () => {
232
+ for (let i = 0; i < THRESHOLD; i++)
233
+ breaker.recordFailure(runId(1));
234
+ breaker.recordFailure(runId(2));
235
+ const tripped = breaker.listTripped();
236
+ expect(tripped).toHaveLength(1);
237
+ expect(tripped[0]?.agentRunId).toBe(runId(1));
238
+ expect(tripped[0]?.state).toBe('open');
239
+ });
240
+ });
241
+ });
242
+ //# sourceMappingURL=breaker.test.js.map