@namzu/sdk 0.1.4 → 0.1.5-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (324) hide show
  1. package/dist/advisory/executor.d.ts +2 -2
  2. package/dist/advisory/executor.d.ts.map +1 -1
  3. package/dist/advisory/executor.js.map +1 -1
  4. package/dist/agents/AbstractAgent.d.ts +20 -2
  5. package/dist/agents/AbstractAgent.d.ts.map +1 -1
  6. package/dist/agents/AbstractAgent.js +23 -1
  7. package/dist/agents/AbstractAgent.js.map +1 -1
  8. package/dist/agents/PipelineAgent.d.ts.map +1 -1
  9. package/dist/agents/PipelineAgent.js +1 -1
  10. package/dist/agents/PipelineAgent.js.map +1 -1
  11. package/dist/agents/ReactiveAgent.d.ts.map +1 -1
  12. package/dist/agents/ReactiveAgent.js +1 -0
  13. package/dist/agents/ReactiveAgent.js.map +1 -1
  14. package/dist/agents/RouterAgent.d.ts.map +1 -1
  15. package/dist/agents/RouterAgent.js +4 -2
  16. package/dist/agents/RouterAgent.js.map +1 -1
  17. package/dist/agents/SupervisorAgent.d.ts.map +1 -1
  18. package/dist/agents/SupervisorAgent.js +4 -1
  19. package/dist/agents/SupervisorAgent.js.map +1 -1
  20. package/dist/agents/__tests__/lock.test.d.ts +2 -0
  21. package/dist/agents/__tests__/lock.test.d.ts.map +1 -0
  22. package/dist/agents/__tests__/lock.test.js +131 -0
  23. package/dist/agents/__tests__/lock.test.js.map +1 -0
  24. package/dist/agents/index.d.ts +2 -0
  25. package/dist/agents/index.d.ts.map +1 -1
  26. package/dist/agents/index.js +1 -0
  27. package/dist/agents/index.js.map +1 -1
  28. package/dist/agents/lock.d.ts +42 -0
  29. package/dist/agents/lock.d.ts.map +1 -0
  30. package/dist/agents/lock.js +54 -0
  31. package/dist/agents/lock.js.map +1 -0
  32. package/dist/bridge/a2a/message.d.ts.map +1 -1
  33. package/dist/bridge/a2a/message.js.map +1 -1
  34. package/dist/bridge/tools/connector/router.d.ts +4 -5
  35. package/dist/bridge/tools/connector/router.d.ts.map +1 -1
  36. package/dist/bridge/tools/connector/router.js.map +1 -1
  37. package/dist/compaction/__tests__/SlidingWindowManager.test.d.ts +2 -0
  38. package/dist/compaction/__tests__/SlidingWindowManager.test.d.ts.map +1 -0
  39. package/dist/compaction/__tests__/SlidingWindowManager.test.js +113 -0
  40. package/dist/compaction/__tests__/SlidingWindowManager.test.js.map +1 -0
  41. package/dist/compaction/__tests__/dangling.test.d.ts +2 -0
  42. package/dist/compaction/__tests__/dangling.test.d.ts.map +1 -0
  43. package/dist/compaction/__tests__/dangling.test.js +356 -0
  44. package/dist/compaction/__tests__/dangling.test.js.map +1 -0
  45. package/dist/compaction/__tests__/factory.test.d.ts +2 -0
  46. package/dist/compaction/__tests__/factory.test.d.ts.map +1 -0
  47. package/dist/compaction/__tests__/factory.test.js +43 -0
  48. package/dist/compaction/__tests__/factory.test.js.map +1 -0
  49. package/dist/compaction/dangling.d.ts +96 -0
  50. package/dist/compaction/dangling.d.ts.map +1 -0
  51. package/dist/compaction/dangling.js +274 -0
  52. package/dist/compaction/dangling.js.map +1 -0
  53. package/dist/compaction/factory.d.ts +20 -0
  54. package/dist/compaction/factory.d.ts.map +1 -0
  55. package/dist/compaction/factory.js +35 -0
  56. package/dist/compaction/factory.js.map +1 -0
  57. package/dist/compaction/index.d.ts +5 -0
  58. package/dist/compaction/index.d.ts.map +1 -1
  59. package/dist/compaction/index.js +3 -0
  60. package/dist/compaction/index.js.map +1 -1
  61. package/dist/compaction/interface.d.ts +33 -0
  62. package/dist/compaction/interface.d.ts.map +1 -0
  63. package/dist/compaction/interface.js +2 -0
  64. package/dist/compaction/interface.js.map +1 -0
  65. package/dist/compaction/managers/index.d.ts +4 -0
  66. package/dist/compaction/managers/index.d.ts.map +1 -0
  67. package/dist/compaction/managers/index.js +4 -0
  68. package/dist/compaction/managers/index.js.map +1 -0
  69. package/dist/compaction/managers/null.d.ts +12 -0
  70. package/dist/compaction/managers/null.d.ts.map +1 -0
  71. package/dist/compaction/managers/null.js +15 -0
  72. package/dist/compaction/managers/null.js.map +1 -0
  73. package/dist/compaction/managers/slidingWindow.d.ts +27 -0
  74. package/dist/compaction/managers/slidingWindow.d.ts.map +1 -0
  75. package/dist/compaction/managers/slidingWindow.js +41 -0
  76. package/dist/compaction/managers/slidingWindow.js.map +1 -0
  77. package/dist/compaction/managers/structured.d.ts +23 -0
  78. package/dist/compaction/managers/structured.d.ts.map +1 -0
  79. package/dist/compaction/managers/structured.js +144 -0
  80. package/dist/compaction/managers/structured.js.map +1 -0
  81. package/dist/compaction/types.d.ts +1 -1
  82. package/dist/compaction/types.d.ts.map +1 -1
  83. package/dist/config/runtime.d.ts +16 -16
  84. package/dist/config/runtime.js +1 -1
  85. package/dist/config/runtime.js.map +1 -1
  86. package/dist/constants/agent/index.d.ts +1 -1
  87. package/dist/constants/agent/index.d.ts.map +1 -1
  88. package/dist/gateway/local.d.ts +2 -2
  89. package/dist/gateway/local.d.ts.map +1 -1
  90. package/dist/gateway/local.js +10 -1
  91. package/dist/gateway/local.js.map +1 -1
  92. package/dist/index.d.ts +18 -4
  93. package/dist/index.d.ts.map +1 -1
  94. package/dist/index.js +12 -2
  95. package/dist/index.js.map +1 -1
  96. package/dist/manager/agent/lifecycle.d.ts.map +1 -1
  97. package/dist/manager/agent/lifecycle.js +3 -2
  98. package/dist/manager/agent/lifecycle.js.map +1 -1
  99. package/dist/manager/run/persistence.d.ts +1 -2
  100. package/dist/manager/run/persistence.d.ts.map +1 -1
  101. package/dist/manager/run/persistence.js +2 -1
  102. package/dist/manager/run/persistence.js.map +1 -1
  103. package/dist/plugin/__tests__/lifecycle.test.d.ts +2 -0
  104. package/dist/plugin/__tests__/lifecycle.test.d.ts.map +1 -0
  105. package/dist/plugin/__tests__/lifecycle.test.js +332 -0
  106. package/dist/plugin/__tests__/lifecycle.test.js.map +1 -0
  107. package/dist/plugin/lifecycle.d.ts +2 -2
  108. package/dist/plugin/lifecycle.d.ts.map +1 -1
  109. package/dist/plugin/lifecycle.js +28 -2
  110. package/dist/plugin/lifecycle.js.map +1 -1
  111. package/dist/plugin/resolver.d.ts +2 -2
  112. package/dist/plugin/resolver.d.ts.map +1 -1
  113. package/dist/plugin/resolver.js.map +1 -1
  114. package/dist/registry/agent/definitions.d.ts +3 -2
  115. package/dist/registry/agent/definitions.d.ts.map +1 -1
  116. package/dist/registry/agent/definitions.js.map +1 -1
  117. package/dist/registry/tool/execute.d.ts +2 -5
  118. package/dist/registry/tool/execute.d.ts.map +1 -1
  119. package/dist/registry/tool/execute.js.map +1 -1
  120. package/dist/runtime/decision/parser.d.ts.map +1 -1
  121. package/dist/runtime/decision/parser.js +15 -40
  122. package/dist/runtime/decision/parser.js.map +1 -1
  123. package/dist/runtime/query/context-cache.d.ts +3 -3
  124. package/dist/runtime/query/context-cache.d.ts.map +1 -1
  125. package/dist/runtime/query/context-cache.js.map +1 -1
  126. package/dist/runtime/query/context.d.ts +1 -1
  127. package/dist/runtime/query/context.d.ts.map +1 -1
  128. package/dist/runtime/query/context.js.map +1 -1
  129. package/dist/runtime/query/events.js +11 -11
  130. package/dist/runtime/query/events.js.map +1 -1
  131. package/dist/runtime/query/executor.d.ts +4 -2
  132. package/dist/runtime/query/executor.d.ts.map +1 -1
  133. package/dist/runtime/query/executor.js +1 -0
  134. package/dist/runtime/query/executor.js.map +1 -1
  135. package/dist/runtime/query/index.d.ts +5 -3
  136. package/dist/runtime/query/index.d.ts.map +1 -1
  137. package/dist/runtime/query/index.js +2 -1
  138. package/dist/runtime/query/index.js.map +1 -1
  139. package/dist/runtime/query/iteration/index.d.ts +2 -2
  140. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  141. package/dist/runtime/query/iteration/index.js.map +1 -1
  142. package/dist/runtime/query/iteration/phases/advisory.d.ts.map +1 -1
  143. package/dist/runtime/query/iteration/phases/advisory.js.map +1 -1
  144. package/dist/runtime/query/iteration/phases/checkpoint.d.ts +1 -1
  145. package/dist/runtime/query/iteration/phases/checkpoint.d.ts.map +1 -1
  146. package/dist/runtime/query/iteration/phases/checkpoint.js.map +1 -1
  147. package/dist/runtime/query/iteration/phases/context.d.ts +2 -2
  148. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  149. package/dist/runtime/query/iteration/phases/plan.d.ts +1 -1
  150. package/dist/runtime/query/iteration/phases/plan.d.ts.map +1 -1
  151. package/dist/runtime/query/iteration/phases/plan.js.map +1 -1
  152. package/dist/runtime/query/prompt.d.ts +2 -2
  153. package/dist/runtime/query/prompt.d.ts.map +1 -1
  154. package/dist/runtime/query/prompt.js.map +1 -1
  155. package/dist/runtime/query/result.d.ts +1 -1
  156. package/dist/runtime/query/result.d.ts.map +1 -1
  157. package/dist/runtime/query/result.js.map +1 -1
  158. package/dist/runtime/query/tooling.d.ts +4 -2
  159. package/dist/runtime/query/tooling.d.ts.map +1 -1
  160. package/dist/runtime/query/tooling.js +1 -0
  161. package/dist/runtime/query/tooling.js.map +1 -1
  162. package/dist/store/conversation/memory.d.ts +1 -1
  163. package/dist/store/conversation/memory.d.ts.map +1 -1
  164. package/dist/store/conversation/memory.js +15 -3
  165. package/dist/store/conversation/memory.js.map +1 -1
  166. package/dist/store/run/disk.d.ts +1 -2
  167. package/dist/store/run/disk.d.ts.map +1 -1
  168. package/dist/store/run/disk.js +21 -13
  169. package/dist/store/run/disk.js.map +1 -1
  170. package/dist/tools/builtins/__tests__/structuredOutput.example.d.ts +140 -0
  171. package/dist/tools/builtins/__tests__/structuredOutput.example.d.ts.map +1 -0
  172. package/dist/tools/builtins/__tests__/structuredOutput.example.js +183 -0
  173. package/dist/tools/builtins/__tests__/structuredOutput.example.js.map +1 -0
  174. package/dist/tools/builtins/__tests__/structuredOutput.test.d.ts +2 -0
  175. package/dist/tools/builtins/__tests__/structuredOutput.test.d.ts.map +1 -0
  176. package/dist/tools/builtins/__tests__/structuredOutput.test.js +224 -0
  177. package/dist/tools/builtins/__tests__/structuredOutput.test.js.map +1 -0
  178. package/dist/tools/builtins/grep.d.ts.map +1 -1
  179. package/dist/tools/builtins/grep.js +1 -2
  180. package/dist/tools/builtins/grep.js.map +1 -1
  181. package/dist/tools/builtins/index.d.ts +1 -0
  182. package/dist/tools/builtins/index.d.ts.map +1 -1
  183. package/dist/tools/builtins/index.js +3 -0
  184. package/dist/tools/builtins/index.js.map +1 -1
  185. package/dist/tools/builtins/ls.d.ts +1 -1
  186. package/dist/tools/builtins/structuredOutput.d.ts +27 -0
  187. package/dist/tools/builtins/structuredOutput.d.ts.map +1 -0
  188. package/dist/tools/builtins/structuredOutput.js +46 -0
  189. package/dist/tools/builtins/structuredOutput.js.map +1 -0
  190. package/dist/tools/task/list.d.ts +1 -1
  191. package/dist/tools/task/list.d.ts.map +1 -1
  192. package/dist/tools/task/list.js.map +1 -1
  193. package/dist/types/agent/base.d.ts +4 -1
  194. package/dist/types/agent/base.d.ts.map +1 -1
  195. package/dist/types/agent/index.d.ts +1 -0
  196. package/dist/types/agent/index.d.ts.map +1 -1
  197. package/dist/types/agent/index.js +1 -0
  198. package/dist/types/agent/index.js.map +1 -1
  199. package/dist/types/agent/manager.d.ts +27 -0
  200. package/dist/types/agent/manager.d.ts.map +1 -0
  201. package/dist/types/agent/manager.js +2 -0
  202. package/dist/types/agent/manager.js.map +1 -0
  203. package/dist/types/agent/reactive.d.ts +2 -2
  204. package/dist/types/agent/reactive.d.ts.map +1 -1
  205. package/dist/types/agent/supervisor.d.ts +2 -2
  206. package/dist/types/agent/supervisor.d.ts.map +1 -1
  207. package/dist/types/agent/task.d.ts +0 -2
  208. package/dist/types/agent/task.d.ts.map +1 -1
  209. package/dist/types/agent/task.js +0 -2
  210. package/dist/types/agent/task.js.map +1 -1
  211. package/dist/types/common/index.d.ts +0 -1
  212. package/dist/types/common/index.d.ts.map +1 -1
  213. package/dist/types/common/index.js +0 -1
  214. package/dist/types/common/index.js.map +1 -1
  215. package/dist/types/hitl/index.d.ts +1 -2
  216. package/dist/types/hitl/index.d.ts.map +1 -1
  217. package/dist/types/hitl/index.js.map +1 -1
  218. package/dist/types/invocation/__tests__/state.test.d.ts +2 -0
  219. package/dist/types/invocation/__tests__/state.test.d.ts.map +1 -0
  220. package/dist/types/invocation/__tests__/state.test.js +167 -0
  221. package/dist/types/invocation/__tests__/state.test.js.map +1 -0
  222. package/dist/types/invocation/index.d.ts +37 -0
  223. package/dist/types/invocation/index.d.ts.map +1 -0
  224. package/dist/types/invocation/index.js +23 -0
  225. package/dist/types/invocation/index.js.map +1 -0
  226. package/dist/types/plugin/index.d.ts +6 -0
  227. package/dist/types/plugin/index.d.ts.map +1 -1
  228. package/dist/types/plugin/index.js +16 -0
  229. package/dist/types/plugin/index.js.map +1 -1
  230. package/dist/types/run/events.d.ts +1 -1
  231. package/dist/types/run/events.d.ts.map +1 -1
  232. package/dist/types/run/index.d.ts +1 -0
  233. package/dist/types/run/index.d.ts.map +1 -1
  234. package/dist/types/run/index.js +1 -0
  235. package/dist/types/run/index.js.map +1 -1
  236. package/dist/types/run/metadata.d.ts +1 -1
  237. package/dist/types/run/metadata.d.ts.map +1 -1
  238. package/dist/types/run/state.d.ts +1 -1
  239. package/dist/types/run/state.d.ts.map +1 -1
  240. package/dist/types/run/stop-reason.d.ts +2 -0
  241. package/dist/types/run/stop-reason.d.ts.map +1 -0
  242. package/dist/types/run/stop-reason.js +2 -0
  243. package/dist/types/run/stop-reason.js.map +1 -0
  244. package/dist/types/structured-output/index.d.ts +51 -0
  245. package/dist/types/structured-output/index.d.ts.map +1 -0
  246. package/dist/types/structured-output/index.js +2 -0
  247. package/dist/types/structured-output/index.js.map +1 -0
  248. package/dist/types/tool/index.d.ts +36 -0
  249. package/dist/types/tool/index.d.ts.map +1 -1
  250. package/package.json +1 -1
  251. package/src/advisory/executor.ts +2 -4
  252. package/src/agents/AbstractAgent.ts +26 -3
  253. package/src/agents/PipelineAgent.ts +1 -1
  254. package/src/agents/ReactiveAgent.ts +1 -0
  255. package/src/agents/RouterAgent.ts +8 -2
  256. package/src/agents/SupervisorAgent.ts +5 -1
  257. package/src/agents/__tests__/lock.test.ts +158 -0
  258. package/src/agents/index.ts +2 -0
  259. package/src/agents/lock.ts +66 -0
  260. package/src/bridge/a2a/message.ts +1 -2
  261. package/src/bridge/tools/connector/router.ts +4 -5
  262. package/src/compaction/__tests__/SlidingWindowManager.test.ts +139 -0
  263. package/src/compaction/__tests__/dangling.test.ts +447 -0
  264. package/src/compaction/__tests__/factory.test.ts +53 -0
  265. package/src/compaction/dangling.ts +321 -0
  266. package/src/compaction/factory.ts +41 -0
  267. package/src/compaction/index.ts +14 -0
  268. package/src/compaction/interface.ts +35 -0
  269. package/src/compaction/managers/index.ts +3 -0
  270. package/src/compaction/managers/null.ts +19 -0
  271. package/src/compaction/managers/slidingWindow.ts +57 -0
  272. package/src/compaction/managers/structured.ts +169 -0
  273. package/src/compaction/types.ts +1 -1
  274. package/src/config/runtime.ts +1 -1
  275. package/src/constants/agent/index.ts +1 -1
  276. package/src/gateway/local.ts +13 -4
  277. package/src/index.ts +38 -1
  278. package/src/manager/agent/lifecycle.ts +3 -2
  279. package/src/manager/run/persistence.ts +3 -8
  280. package/src/plugin/__tests__/lifecycle.test.ts +430 -0
  281. package/src/plugin/lifecycle.ts +32 -6
  282. package/src/plugin/resolver.ts +3 -3
  283. package/src/registry/agent/definitions.ts +3 -2
  284. package/src/registry/tool/execute.ts +2 -5
  285. package/src/runtime/decision/parser.ts +15 -40
  286. package/src/runtime/query/context-cache.ts +3 -4
  287. package/src/runtime/query/context.ts +1 -2
  288. package/src/runtime/query/events.ts +11 -11
  289. package/src/runtime/query/executor.ts +5 -3
  290. package/src/runtime/query/index.ts +11 -4
  291. package/src/runtime/query/iteration/index.ts +2 -2
  292. package/src/runtime/query/iteration/phases/advisory.ts +1 -2
  293. package/src/runtime/query/iteration/phases/checkpoint.ts +1 -2
  294. package/src/runtime/query/iteration/phases/context.ts +2 -2
  295. package/src/runtime/query/iteration/phases/plan.ts +1 -2
  296. package/src/runtime/query/prompt.ts +3 -3
  297. package/src/runtime/query/result.ts +1 -2
  298. package/src/runtime/query/tooling.ts +5 -2
  299. package/src/store/conversation/memory.ts +21 -5
  300. package/src/store/run/disk.ts +18 -16
  301. package/src/tools/builtins/__tests__/structuredOutput.example.ts +221 -0
  302. package/src/tools/builtins/__tests__/structuredOutput.test.ts +275 -0
  303. package/src/tools/builtins/grep.ts +1 -2
  304. package/src/tools/builtins/index.ts +3 -0
  305. package/src/tools/builtins/structuredOutput.ts +55 -0
  306. package/src/tools/task/list.ts +1 -2
  307. package/src/types/agent/base.ts +5 -1
  308. package/src/types/agent/index.ts +1 -0
  309. package/src/types/agent/manager.ts +36 -0
  310. package/src/types/agent/reactive.ts +2 -2
  311. package/src/types/agent/supervisor.ts +2 -2
  312. package/src/types/agent/task.ts +0 -4
  313. package/src/types/common/index.ts +0 -2
  314. package/src/types/hitl/index.ts +1 -2
  315. package/src/types/invocation/__tests__/state.test.ts +210 -0
  316. package/src/types/invocation/index.ts +55 -0
  317. package/src/types/plugin/index.ts +19 -0
  318. package/src/types/run/events.ts +1 -10
  319. package/src/types/run/index.ts +1 -0
  320. package/src/types/run/metadata.ts +1 -1
  321. package/src/types/run/state.ts +1 -1
  322. package/src/types/run/stop-reason.ts +10 -0
  323. package/src/types/structured-output/index.ts +56 -0
  324. package/src/types/tool/index.ts +45 -0
@@ -0,0 +1,447 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import type {
3
+ AssistantMessage,
4
+ Message,
5
+ ToolMessage,
6
+ UserMessage,
7
+ } from '../../types/message/index.js'
8
+ import { findDanglingMessages, findSafeTrimIndex, removeDanglingMessages } from '../dangling.js'
9
+
10
+ /**
11
+ * Test helpers to create messages with proper typing.
12
+ */
13
+ function createUserMessage(content: string): UserMessage {
14
+ return { role: 'user', content, timestamp: Date.now() }
15
+ }
16
+
17
+ function createAssistantMessage(content: string | null, toolCallIds?: string[]): AssistantMessage {
18
+ const msg: AssistantMessage = {
19
+ role: 'assistant',
20
+ content,
21
+ timestamp: Date.now(),
22
+ }
23
+
24
+ if (toolCallIds && toolCallIds.length > 0) {
25
+ msg.toolCalls = toolCallIds.map((id) => ({
26
+ id,
27
+ type: 'function',
28
+ function: { name: 'test_tool', arguments: '{}' },
29
+ }))
30
+ }
31
+
32
+ return msg
33
+ }
34
+
35
+ function createToolMessage(content: string, toolCallId: string): ToolMessage {
36
+ return { role: 'tool', content, toolCallId, timestamp: Date.now() }
37
+ }
38
+
39
+ describe('findDanglingMessages', () => {
40
+ it('should find no dangling messages in a valid sequence', () => {
41
+ const messages: Message[] = [
42
+ createUserMessage('test'),
43
+ createAssistantMessage('response', ['call-1']),
44
+ createToolMessage('result', 'call-1'),
45
+ createUserMessage('next'),
46
+ ]
47
+
48
+ const result = findDanglingMessages(messages)
49
+
50
+ expect(result.isValid).toBe(true)
51
+ expect(result.assistantsWithUnmatchedCalls).toHaveLength(0)
52
+ expect(result.orphanedToolMessages).toHaveLength(0)
53
+ })
54
+
55
+ it('should find assistant messages with unmatched tool calls', () => {
56
+ const messages: Message[] = [
57
+ createUserMessage('test'),
58
+ createAssistantMessage('response', ['call-1']),
59
+ // Missing tool message for call-1
60
+ createUserMessage('next'),
61
+ ]
62
+
63
+ const result = findDanglingMessages(messages)
64
+
65
+ expect(result.isValid).toBe(false)
66
+ expect(result.assistantsWithUnmatchedCalls).toContain(1)
67
+ expect(result.orphanedToolMessages).toHaveLength(0)
68
+ })
69
+
70
+ it('should find orphaned tool messages with no matching assistant call', () => {
71
+ const messages: Message[] = [
72
+ createUserMessage('test'),
73
+ createAssistantMessage('response', ['call-1']),
74
+ createToolMessage('result', 'call-1'),
75
+ createToolMessage('orphan', 'call-2'), // No assistant call for call-2
76
+ ]
77
+
78
+ const result = findDanglingMessages(messages)
79
+
80
+ expect(result.isValid).toBe(false)
81
+ expect(result.orphanedToolMessages).toContain(3)
82
+ expect(result.assistantsWithUnmatchedCalls).toHaveLength(0)
83
+ })
84
+
85
+ it('should handle multiple tool calls in a single assistant message', () => {
86
+ const messages: Message[] = [
87
+ createUserMessage('test'),
88
+ createAssistantMessage('response', ['call-1', 'call-2']),
89
+ createToolMessage('result-1', 'call-1'),
90
+ createToolMessage('result-2', 'call-2'),
91
+ createUserMessage('next'),
92
+ ]
93
+
94
+ const result = findDanglingMessages(messages)
95
+
96
+ expect(result.isValid).toBe(true)
97
+ })
98
+
99
+ it('should find partially satisfied multiple tool calls', () => {
100
+ const messages: Message[] = [
101
+ createUserMessage('test'),
102
+ createAssistantMessage('response', ['call-1', 'call-2']),
103
+ createToolMessage('result-1', 'call-1'),
104
+ // Missing result for call-2
105
+ ]
106
+
107
+ const result = findDanglingMessages(messages)
108
+
109
+ expect(result.isValid).toBe(false)
110
+ expect(result.assistantsWithUnmatchedCalls).toContain(1)
111
+ })
112
+
113
+ it('should find no dangling messages in an assistant-only sequence', () => {
114
+ const messages: Message[] = [
115
+ createUserMessage('test'),
116
+ createAssistantMessage('response without tools'),
117
+ createUserMessage('next'),
118
+ ]
119
+
120
+ const result = findDanglingMessages(messages)
121
+
122
+ expect(result.isValid).toBe(true)
123
+ })
124
+
125
+ it('should handle empty message array', () => {
126
+ const messages: Message[] = []
127
+
128
+ const result = findDanglingMessages(messages)
129
+
130
+ expect(result.isValid).toBe(true)
131
+ expect(result.assistantsWithUnmatchedCalls).toHaveLength(0)
132
+ expect(result.orphanedToolMessages).toHaveLength(0)
133
+ })
134
+
135
+ it('should find multiple dangling issues in complex sequence', () => {
136
+ const messages: Message[] = [
137
+ createUserMessage('test'),
138
+ createAssistantMessage('response', ['call-1']), // Unmatched
139
+ createToolMessage('orphan-1', 'call-999'), // Orphaned
140
+ createAssistantMessage('response2', ['call-2']),
141
+ createToolMessage('result-2', 'call-2'),
142
+ createToolMessage('orphan-2', 'call-888'), // Orphaned
143
+ ]
144
+
145
+ const result = findDanglingMessages(messages)
146
+
147
+ expect(result.isValid).toBe(false)
148
+ expect(result.assistantsWithUnmatchedCalls).toContain(1)
149
+ expect(result.orphanedToolMessages).toContain(2)
150
+ expect(result.orphanedToolMessages).toContain(5)
151
+ })
152
+ })
153
+
154
+ describe('removeDanglingMessages', () => {
155
+ it('should return a copy when messages are already valid', () => {
156
+ const messages: Message[] = [
157
+ createUserMessage('test'),
158
+ createAssistantMessage('response', ['call-1']),
159
+ createToolMessage('result', 'call-1'),
160
+ ]
161
+
162
+ const cleaned = removeDanglingMessages(messages)
163
+
164
+ expect(cleaned).toEqual(messages)
165
+ expect(cleaned).not.toBe(messages) // Different object
166
+ })
167
+
168
+ it('should remove assistant messages with unmatched tool calls', () => {
169
+ const messages: Message[] = [
170
+ createUserMessage('test'),
171
+ createAssistantMessage('response', ['call-1']), // Will be removed
172
+ createUserMessage('next'),
173
+ ]
174
+
175
+ const cleaned = removeDanglingMessages(messages)
176
+
177
+ expect(cleaned).toHaveLength(2)
178
+ expect(cleaned[0]?.role).toBe('user')
179
+ expect(cleaned[1]?.role).toBe('user')
180
+ })
181
+
182
+ it('should remove orphaned tool messages', () => {
183
+ const messages: Message[] = [
184
+ createUserMessage('test'),
185
+ createAssistantMessage('response', ['call-1']),
186
+ createToolMessage('result', 'call-1'),
187
+ createToolMessage('orphan', 'call-999'), // Will be removed
188
+ ]
189
+
190
+ const cleaned = removeDanglingMessages(messages)
191
+
192
+ expect(cleaned).toHaveLength(3)
193
+ expect(cleaned[3]).toBeUndefined()
194
+ })
195
+
196
+ it('should remove assistant and following orphaned tool messages together', () => {
197
+ const messages: Message[] = [
198
+ createUserMessage('test'),
199
+ createAssistantMessage('response', ['call-1', 'call-2']), // call-2 unmatched → assistant flagged
200
+ createToolMessage('attempt', 'call-1'), // Matches assistant's call list → removed with assistant
201
+ createUserMessage('next'),
202
+ ]
203
+
204
+ const cleaned = removeDanglingMessages(messages)
205
+
206
+ expect(cleaned).toHaveLength(2)
207
+ expect(cleaned[0]?.role).toBe('user')
208
+ expect(cleaned[0]?.content).toBe('test')
209
+ expect(cleaned[1]?.role).toBe('user')
210
+ expect(cleaned[1]?.content).toBe('next')
211
+ })
212
+
213
+ it('should preserve message order while removing dangling messages', () => {
214
+ const messages: Message[] = [
215
+ createUserMessage('1'),
216
+ createAssistantMessage('response', ['call-1']),
217
+ createToolMessage('result', 'call-1'),
218
+ createUserMessage('2'),
219
+ createAssistantMessage('response2', ['call-2']), // Unmatched
220
+ createUserMessage('3'),
221
+ ]
222
+
223
+ const cleaned = removeDanglingMessages(messages)
224
+
225
+ // Only the unmatched assistant is removed; valid pair and users are kept in order
226
+ expect(cleaned).toHaveLength(5)
227
+ expect(cleaned[0]?.content).toBe('1')
228
+ expect(cleaned[1]?.content).toBe('response')
229
+ expect(cleaned[2]?.content).toBe('result')
230
+ expect(cleaned[3]?.content).toBe('2')
231
+ expect(cleaned[4]?.content).toBe('3')
232
+ })
233
+
234
+ it('should handle complex cleanup with multiple dangling issues', () => {
235
+ const messages: Message[] = [
236
+ createUserMessage('start'),
237
+ createAssistantMessage('response1', ['call-1']), // Unmatched
238
+ createToolMessage('orphan-1', 'call-999'), // Orphaned
239
+ createAssistantMessage('response2', ['call-2']),
240
+ createToolMessage('result-2', 'call-2'),
241
+ createToolMessage('orphan-2', 'call-888'), // Orphaned
242
+ createUserMessage('end'),
243
+ ]
244
+
245
+ const cleaned = removeDanglingMessages(messages)
246
+
247
+ // Removed: assistant('response1') (unmatched), orphan-1, orphan-2
248
+ // Kept: start, response2, result-2 (valid pair), end
249
+ expect(cleaned).toHaveLength(4)
250
+ expect(cleaned[0]?.content).toBe('start')
251
+ expect(cleaned[1]?.content).toBe('response2')
252
+ expect(cleaned[2]?.content).toBe('result-2')
253
+ expect(cleaned[3]?.content).toBe('end')
254
+ })
255
+
256
+ it('should remove dangling assistant messages when they have follow-up tool attempts', () => {
257
+ const messages: Message[] = [
258
+ createUserMessage('test'),
259
+ createAssistantMessage('response', ['call-1', 'call-2']), // call-2 unmatched → assistant flagged
260
+ createToolMessage('wrong-result', 'call-1'), // Matches assistant's call list → removed with assistant
261
+ createUserMessage('next'),
262
+ ]
263
+
264
+ const cleaned = removeDanglingMessages(messages)
265
+
266
+ expect(cleaned).toHaveLength(2)
267
+ expect(cleaned[0]?.content).toBe('test')
268
+ expect(cleaned[1]?.content).toBe('next')
269
+ })
270
+ })
271
+
272
+ describe('findSafeTrimIndex', () => {
273
+ it('should not trim when target is 0', () => {
274
+ const messages: Message[] = [
275
+ createUserMessage('test'),
276
+ createAssistantMessage('response', ['call-1']),
277
+ createToolMessage('result', 'call-1'),
278
+ ]
279
+
280
+ const safeIdx = findSafeTrimIndex(messages, 0)
281
+
282
+ expect(safeIdx).toBe(0)
283
+ })
284
+
285
+ it('should not trim when target is at end', () => {
286
+ const messages: Message[] = [
287
+ createUserMessage('test'),
288
+ createAssistantMessage('response', ['call-1']),
289
+ createToolMessage('result', 'call-1'),
290
+ ]
291
+
292
+ const safeIdx = findSafeTrimIndex(messages, messages.length)
293
+
294
+ expect(safeIdx).toBe(messages.length)
295
+ })
296
+
297
+ it('should clamp negative target index to 0', () => {
298
+ const messages: Message[] = [createUserMessage('test')]
299
+
300
+ const safeIdx = findSafeTrimIndex(messages, -5)
301
+
302
+ expect(safeIdx).toBeGreaterThanOrEqual(0)
303
+ })
304
+
305
+ it('should clamp target beyond array length', () => {
306
+ const messages: Message[] = [createUserMessage('test')]
307
+
308
+ const safeIdx = findSafeTrimIndex(messages, 1000)
309
+
310
+ expect(safeIdx).toBeLessThanOrEqual(messages.length)
311
+ })
312
+
313
+ it('should skip orphaned tool messages at trim point', () => {
314
+ const messages: Message[] = [
315
+ createUserMessage('test'),
316
+ createAssistantMessage('response', ['call-1']),
317
+ createToolMessage('result', 'call-1'),
318
+ createToolMessage('orphan', 'call-999'), // Orphaned at trim point
319
+ createUserMessage('next'),
320
+ ]
321
+
322
+ const safeIdx = findSafeTrimIndex(messages, 3)
323
+
324
+ // Should skip the orphaned tool message at index 3
325
+ expect(safeIdx).toBeGreaterThan(3)
326
+ })
327
+
328
+ it('should preserve complete tool call/result pairs', () => {
329
+ const messages: Message[] = [
330
+ createUserMessage('test'),
331
+ createAssistantMessage('response', ['call-1']),
332
+ createToolMessage('result', 'call-1'),
333
+ createUserMessage('next'),
334
+ ]
335
+
336
+ // Try to trim in middle of pair
337
+ const safeIdx = findSafeTrimIndex(messages, 2)
338
+
339
+ // Algorithm advances forward past the pair — safeIdx=3 keeps the pair intact in slice(0, safeIdx)
340
+ expect(safeIdx).toBe(3)
341
+ })
342
+
343
+ it('should not start with a tool message after trim', () => {
344
+ const messages: Message[] = [
345
+ createUserMessage('test'),
346
+ createAssistantMessage('response', ['call-1']),
347
+ createToolMessage('result', 'call-1'),
348
+ createToolMessage('orphan', 'call-999'),
349
+ createUserMessage('next'),
350
+ ]
351
+
352
+ const safeIdx = findSafeTrimIndex(messages, 3)
353
+
354
+ if (safeIdx < messages.length) {
355
+ const firstAfterTrim = messages[safeIdx]
356
+ if (firstAfterTrim) {
357
+ expect(firstAfterTrim.role).not.toBe('tool')
358
+ }
359
+ }
360
+ })
361
+
362
+ it('should handle complex scenario with multiple pairs', () => {
363
+ const messages: Message[] = [
364
+ createUserMessage('1'),
365
+ createAssistantMessage('response1', ['call-1']),
366
+ createToolMessage('result-1', 'call-1'),
367
+ createUserMessage('2'),
368
+ createAssistantMessage('response2', ['call-2']),
369
+ createToolMessage('result-2', 'call-2'),
370
+ createUserMessage('3'),
371
+ ]
372
+
373
+ // Try to trim between the two pairs
374
+ const safeIdx = findSafeTrimIndex(messages, 4)
375
+
376
+ // Should either include or exclude the second pair completely
377
+ const kept = messages.slice(0, safeIdx)
378
+ const danglingResult = findDanglingMessages(kept)
379
+ expect(danglingResult.isValid).toBe(true)
380
+ })
381
+
382
+ it('should produce valid message sequence after trim', () => {
383
+ const messages: Message[] = [
384
+ createUserMessage('1'),
385
+ createAssistantMessage('response1', ['call-1']),
386
+ createToolMessage('result-1', 'call-1'),
387
+ createUserMessage('2'),
388
+ createAssistantMessage('response2', ['call-2']),
389
+ createToolMessage('result-2', 'call-2'),
390
+ createUserMessage('3'),
391
+ createAssistantMessage('unmatched', ['call-3']), // Unmatched
392
+ ]
393
+
394
+ // Try various trim points — target within bounds (excludes edge case target=messages.length
395
+ // where trailing unmatched assistant cannot be trimmed forward)
396
+ for (let target = 0; target < messages.length; target++) {
397
+ const safeIdx = findSafeTrimIndex(messages, target)
398
+ const keptMessages = messages.slice(0, safeIdx)
399
+
400
+ const result = findDanglingMessages(keptMessages)
401
+ expect(result.isValid).toBe(true)
402
+ }
403
+ })
404
+
405
+ it('should not trim unnecessarily if sequence is already valid', () => {
406
+ const messages: Message[] = [
407
+ createUserMessage('test'),
408
+ createAssistantMessage('response', ['call-1']),
409
+ createToolMessage('result', 'call-1'),
410
+ createUserMessage('next'),
411
+ ]
412
+
413
+ const safeIdx = findSafeTrimIndex(messages, messages.length)
414
+
415
+ expect(safeIdx).toBe(messages.length)
416
+ })
417
+
418
+ it('should advance past unsatisfied assistant message and its following tool attempt', () => {
419
+ const messages: Message[] = [
420
+ createUserMessage('test'),
421
+ createAssistantMessage('response', ['call-1']), // Unmatched at index 1
422
+ createToolMessage('attempt', 'call-1'), // Following attempt at index 2
423
+ createUserMessage('next'),
424
+ ]
425
+
426
+ const safeIdx = findSafeTrimIndex(messages, 2)
427
+
428
+ // Should skip past the unmatched call and its following tool message
429
+ expect(safeIdx).toBeGreaterThan(2)
430
+ const kept = messages.slice(0, safeIdx)
431
+ expect(findDanglingMessages(kept).isValid).toBe(true)
432
+ })
433
+
434
+ it('should handle all-tool-messages scenario', () => {
435
+ const messages: Message[] = [
436
+ createUserMessage('test'),
437
+ createToolMessage('orphan-1', 'call-999'),
438
+ createToolMessage('orphan-2', 'call-888'),
439
+ ]
440
+
441
+ const safeIdx = findSafeTrimIndex(messages, 1)
442
+
443
+ // Algorithm advances past both orphan tools — kept portion (slice from safeIdx) is empty and valid
444
+ const kept = messages.slice(safeIdx)
445
+ expect(findDanglingMessages(kept).isValid).toBe(true)
446
+ })
447
+ })
@@ -0,0 +1,53 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { RUNTIME_DEFAULTS } from '../../config/runtime.js'
3
+ import { createConversationManager } from '../factory.js'
4
+ import {
5
+ NullManager,
6
+ SlidingWindowManager,
7
+ StructuredCompactionManager,
8
+ } from '../managers/index.js'
9
+
10
+ describe('createConversationManager', () => {
11
+ const config = RUNTIME_DEFAULTS.compaction
12
+
13
+ it('should create NullManager for disabled strategy', () => {
14
+ const manager = createConversationManager('disabled', config)
15
+ expect(manager).toBeInstanceOf(NullManager)
16
+ expect(manager.name).toBe('null')
17
+ })
18
+
19
+ it('should create SlidingWindowManager for sliding-window strategy', () => {
20
+ const manager = createConversationManager('sliding-window', config)
21
+ expect(manager).toBeInstanceOf(SlidingWindowManager)
22
+ expect(manager.name).toBe('sliding-window')
23
+ })
24
+
25
+ it('should create StructuredCompactionManager for structured strategy', () => {
26
+ const manager = createConversationManager('structured', config)
27
+ expect(manager).toBeInstanceOf(StructuredCompactionManager)
28
+ expect(manager.name).toBe('structured')
29
+ })
30
+
31
+ it('should throw on unknown strategy', () => {
32
+ // @ts-expect-error - Testing invalid input
33
+ expect(() => createConversationManager('unknown', config)).toThrow()
34
+ })
35
+
36
+ it('should pass keepRecentMessages to SlidingWindowManager', () => {
37
+ const customConfig = {
38
+ ...config,
39
+ keepRecentMessages: 8,
40
+ }
41
+ const manager = createConversationManager('sliding-window', customConfig)
42
+ expect(manager).toBeInstanceOf(SlidingWindowManager)
43
+ })
44
+
45
+ it('should pass config to StructuredCompactionManager', () => {
46
+ const customConfig = {
47
+ ...config,
48
+ convoTextBudget: 5000,
49
+ }
50
+ const manager = createConversationManager('structured', customConfig)
51
+ expect(manager).toBeInstanceOf(StructuredCompactionManager)
52
+ })
53
+ })