@namzu/sdk 0.4.2 → 0.4.3

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 (207) hide show
  1. package/CHANGELOG.md +10 -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.test.d.ts +28 -0
  43. package/dist/bridge/tools/connector/adapter.test.d.ts.map +1 -0
  44. package/dist/bridge/tools/connector/adapter.test.js +182 -0
  45. package/dist/bridge/tools/connector/adapter.test.js.map +1 -0
  46. package/dist/bridge/tools/connector/definitions.test.d.ts +23 -0
  47. package/dist/bridge/tools/connector/definitions.test.d.ts.map +1 -0
  48. package/dist/bridge/tools/connector/definitions.test.js +158 -0
  49. package/dist/bridge/tools/connector/definitions.test.js.map +1 -0
  50. package/dist/bridge/tools/connector/router.test.d.ts +21 -0
  51. package/dist/bridge/tools/connector/router.test.d.ts.map +1 -0
  52. package/dist/bridge/tools/connector/router.test.js +139 -0
  53. package/dist/bridge/tools/connector/router.test.js.map +1 -0
  54. package/dist/bus/breaker.test.d.ts +41 -0
  55. package/dist/bus/breaker.test.d.ts.map +1 -0
  56. package/dist/bus/breaker.test.js +242 -0
  57. package/dist/bus/breaker.test.js.map +1 -0
  58. package/dist/bus/index.test.d.ts +25 -0
  59. package/dist/bus/index.test.d.ts.map +1 -0
  60. package/dist/bus/index.test.js +151 -0
  61. package/dist/bus/index.test.js.map +1 -0
  62. package/dist/bus/lock.test.d.ts +44 -0
  63. package/dist/bus/lock.test.d.ts.map +1 -0
  64. package/dist/bus/lock.test.js +226 -0
  65. package/dist/bus/lock.test.js.map +1 -0
  66. package/dist/bus/ownership.test.d.ts +26 -0
  67. package/dist/bus/ownership.test.d.ts.map +1 -0
  68. package/dist/bus/ownership.test.js +205 -0
  69. package/dist/bus/ownership.test.js.map +1 -0
  70. package/dist/connector/BaseConnector.test.d.ts +21 -0
  71. package/dist/connector/BaseConnector.test.d.ts.map +1 -0
  72. package/dist/connector/BaseConnector.test.js +108 -0
  73. package/dist/connector/BaseConnector.test.js.map +1 -0
  74. package/dist/connector/builtins/http.test.d.ts +30 -0
  75. package/dist/connector/builtins/http.test.d.ts.map +1 -0
  76. package/dist/connector/builtins/http.test.js +232 -0
  77. package/dist/connector/builtins/http.test.js.map +1 -0
  78. package/dist/connector/builtins/webhook.test.d.ts +20 -0
  79. package/dist/connector/builtins/webhook.test.d.ts.map +1 -0
  80. package/dist/connector/builtins/webhook.test.js +113 -0
  81. package/dist/connector/builtins/webhook.test.js.map +1 -0
  82. package/dist/connector/execution/factory.test.d.ts +16 -0
  83. package/dist/connector/execution/factory.test.d.ts.map +1 -0
  84. package/dist/connector/execution/factory.test.js +64 -0
  85. package/dist/connector/execution/factory.test.js.map +1 -0
  86. package/dist/connector/execution/remote.test.d.ts +16 -0
  87. package/dist/connector/execution/remote.test.d.ts.map +1 -0
  88. package/dist/connector/execution/remote.test.js +53 -0
  89. package/dist/connector/execution/remote.test.js.map +1 -0
  90. package/dist/connector/mcp/adapter.test.d.ts +34 -0
  91. package/dist/connector/mcp/adapter.test.d.ts.map +1 -0
  92. package/dist/connector/mcp/adapter.test.js +199 -0
  93. package/dist/connector/mcp/adapter.test.js.map +1 -0
  94. package/dist/rag/chunking.test.d.ts +20 -0
  95. package/dist/rag/chunking.test.d.ts.map +1 -0
  96. package/dist/rag/chunking.test.js +92 -0
  97. package/dist/rag/chunking.test.js.map +1 -0
  98. package/dist/rag/context-assembler.test.d.ts +19 -0
  99. package/dist/rag/context-assembler.test.d.ts.map +1 -0
  100. package/dist/rag/context-assembler.test.js +98 -0
  101. package/dist/rag/context-assembler.test.js.map +1 -0
  102. package/dist/rag/embedding.test.d.ts +19 -0
  103. package/dist/rag/embedding.test.d.ts.map +1 -0
  104. package/dist/rag/embedding.test.js +115 -0
  105. package/dist/rag/embedding.test.js.map +1 -0
  106. package/dist/rag/ingestion.test.d.ts +22 -0
  107. package/dist/rag/ingestion.test.d.ts.map +1 -0
  108. package/dist/rag/ingestion.test.js +99 -0
  109. package/dist/rag/ingestion.test.js.map +1 -0
  110. package/dist/rag/knowledge-base.test.d.ts +17 -0
  111. package/dist/rag/knowledge-base.test.d.ts.map +1 -0
  112. package/dist/rag/knowledge-base.test.js +77 -0
  113. package/dist/rag/knowledge-base.test.js.map +1 -0
  114. package/dist/rag/rag-tool.test.d.ts +21 -0
  115. package/dist/rag/rag-tool.test.d.ts.map +1 -0
  116. package/dist/rag/rag-tool.test.js +149 -0
  117. package/dist/rag/rag-tool.test.js.map +1 -0
  118. package/dist/rag/retriever.test.d.ts +26 -0
  119. package/dist/rag/retriever.test.d.ts.map +1 -0
  120. package/dist/rag/retriever.test.js +180 -0
  121. package/dist/rag/retriever.test.js.map +1 -0
  122. package/dist/rag/vector-store.test.d.ts +38 -0
  123. package/dist/rag/vector-store.test.d.ts.map +1 -0
  124. package/dist/rag/vector-store.test.js +175 -0
  125. package/dist/rag/vector-store.test.js.map +1 -0
  126. package/dist/registry/ManagedRegistry.test.d.ts +21 -0
  127. package/dist/registry/ManagedRegistry.test.d.ts.map +1 -0
  128. package/dist/registry/ManagedRegistry.test.js +98 -0
  129. package/dist/registry/ManagedRegistry.test.js.map +1 -0
  130. package/dist/registry/Registry.test.d.ts +18 -0
  131. package/dist/registry/Registry.test.d.ts.map +1 -0
  132. package/dist/registry/Registry.test.js +79 -0
  133. package/dist/registry/Registry.test.js.map +1 -0
  134. package/dist/registry/agent/definitions.test.d.ts +15 -0
  135. package/dist/registry/agent/definitions.test.d.ts.map +1 -0
  136. package/dist/registry/agent/definitions.test.js +84 -0
  137. package/dist/registry/agent/definitions.test.js.map +1 -0
  138. package/dist/registry/connector/definitions.test.d.ts +13 -0
  139. package/dist/registry/connector/definitions.test.d.ts.map +1 -0
  140. package/dist/registry/connector/definitions.test.js +41 -0
  141. package/dist/registry/connector/definitions.test.js.map +1 -0
  142. package/dist/registry/connector/scoped.test.d.ts +21 -0
  143. package/dist/registry/connector/scoped.test.d.ts.map +1 -0
  144. package/dist/registry/connector/scoped.test.js +115 -0
  145. package/dist/registry/connector/scoped.test.js.map +1 -0
  146. package/dist/registry/plugin/index.test.d.ts +12 -0
  147. package/dist/registry/plugin/index.test.d.ts.map +1 -0
  148. package/dist/registry/plugin/index.test.js +69 -0
  149. package/dist/registry/plugin/index.test.js.map +1 -0
  150. package/dist/registry/tool/execute.test.d.ts +42 -0
  151. package/dist/registry/tool/execute.test.d.ts.map +1 -0
  152. package/dist/registry/tool/execute.test.js +281 -0
  153. package/dist/registry/tool/execute.test.js.map +1 -0
  154. package/dist/runtime/query/iteration/phases/advisory.test.d.ts +42 -0
  155. package/dist/runtime/query/iteration/phases/advisory.test.d.ts.map +1 -0
  156. package/dist/runtime/query/iteration/phases/advisory.test.js +334 -0
  157. package/dist/runtime/query/iteration/phases/advisory.test.js.map +1 -0
  158. package/dist/test-setup.d.ts +22 -0
  159. package/dist/test-setup.d.ts.map +1 -0
  160. package/dist/test-setup.js +23 -0
  161. package/dist/test-setup.js.map +1 -0
  162. package/dist/utils/logger.d.ts +1 -1
  163. package/dist/utils/logger.d.ts.map +1 -1
  164. package/dist/utils/logger.js +5 -0
  165. package/dist/utils/logger.js.map +1 -1
  166. package/package.json +4 -1
  167. package/src/advisory/context.test.ts +109 -0
  168. package/src/advisory/evaluator.test.ts +192 -0
  169. package/src/advisory/executor.test.ts +272 -0
  170. package/src/advisory/registry.test.ts +75 -0
  171. package/src/bridge/a2a/agent-card.test.ts +140 -0
  172. package/src/bridge/a2a/mapper.test.ts +293 -0
  173. package/src/bridge/a2a/message.test.ts +138 -0
  174. package/src/bridge/a2a/task.test.ts +235 -0
  175. package/src/bridge/mcp/connector/adapter.test.ts +230 -0
  176. package/src/bridge/sse/mapper.test.ts +422 -0
  177. package/src/bridge/tools/connector/adapter.test.ts +224 -0
  178. package/src/bridge/tools/connector/definitions.test.ts +183 -0
  179. package/src/bridge/tools/connector/router.test.ts +159 -0
  180. package/src/bus/breaker.test.ts +274 -0
  181. package/src/bus/index.test.ts +183 -0
  182. package/src/bus/lock.test.ts +265 -0
  183. package/src/bus/ownership.test.ts +243 -0
  184. package/src/connector/BaseConnector.test.ts +130 -0
  185. package/src/connector/builtins/http.test.ts +290 -0
  186. package/src/connector/builtins/webhook.test.ts +138 -0
  187. package/src/connector/execution/factory.test.ts +75 -0
  188. package/src/connector/execution/remote.test.ts +63 -0
  189. package/src/connector/mcp/adapter.test.ts +249 -0
  190. package/src/rag/chunking.test.ts +107 -0
  191. package/src/rag/context-assembler.test.ts +114 -0
  192. package/src/rag/embedding.test.ts +130 -0
  193. package/src/rag/ingestion.test.ts +114 -0
  194. package/src/rag/knowledge-base.test.ts +106 -0
  195. package/src/rag/rag-tool.test.ts +167 -0
  196. package/src/rag/retriever.test.ts +210 -0
  197. package/src/rag/vector-store.test.ts +196 -0
  198. package/src/registry/ManagedRegistry.test.ts +118 -0
  199. package/src/registry/Registry.test.ts +91 -0
  200. package/src/registry/agent/definitions.test.ts +100 -0
  201. package/src/registry/connector/definitions.test.ts +51 -0
  202. package/src/registry/connector/scoped.test.ts +129 -0
  203. package/src/registry/plugin/index.test.ts +85 -0
  204. package/src/registry/tool/execute.test.ts +330 -0
  205. package/src/runtime/query/iteration/phases/advisory.test.ts +412 -0
  206. package/src/test-setup.ts +24 -0
  207. package/src/utils/logger.ts +6 -1
@@ -0,0 +1,334 @@
1
+ /**
2
+ * Current-code invariants asserted (2026-04-21, ses_006 Phase 6 follow-up):
3
+ *
4
+ * This file pins the live mutation boundary that `src/advisory/*` tests
5
+ * intentionally do NOT cover: the advisory phase IS where advisories
6
+ * inject user messages into the run via `ctx.runMgr.pushMessage(...)`
7
+ * (Codex #6). A regression that drops the `pushMessage` call — silently
8
+ * dropping all advisor output — would pass `src/advisory` tests,
9
+ * typecheck, and lint. This file is the only thing that catches it.
10
+ *
11
+ * - **Early-return paths** (no side effects):
12
+ * - No `advisoryCtx` on the iteration ctx → returns immediately.
13
+ * - `advisoryCtx.checkBudget()` denies → returns; NO pushMessage.
14
+ * - Evaluator fires no triggers → returns; NO pushMessage.
15
+ * - Trigger fires but advisor not found in registry → warn log +
16
+ * return; NO pushMessage.
17
+ * - Executor throws → warn log + return; NO pushMessage (partial
18
+ * work NOT persisted).
19
+ * - **Happy path** — trigger fires + advisor resolves + executor
20
+ * succeeds:
21
+ * - Calls `executor.consult(advisor, request, callCtx)` exactly once.
22
+ * - Calls `evaluator.recordFiring(trigger.id, iteration)`.
23
+ * - Calls `advisoryCtx.recordCall(...)` with the full call record.
24
+ * - Calls `runMgr.pushMessage(createUserMessage(wrapped))` exactly
25
+ * once.
26
+ * - The wrapped message includes `<advisory-result advisor="..."
27
+ * trigger="...">` + the advice text + closing tag.
28
+ * - When the result carries `warnings`, they appear under a
29
+ * "Warnings:" section.
30
+ * - When the result carries `decisions`, they appear under a
31
+ * "Decisions:" section AND each decision is pushed to
32
+ * `workingStateManager.addDecision` (if a workingStateManager is
33
+ * present on ctx).
34
+ * - **Trigger selection**: only the first trigger from
35
+ * `evaluator.evaluate(state)[0]` is used per iteration; other fired
36
+ * triggers are discarded this round.
37
+ * - **Question resolution**: `trigger.questionTemplate` is used when
38
+ * set; otherwise the phase uses a default
39
+ * "Iteration N: Review the current progress..." string.
40
+ */
41
+ import { describe, expect, it, vi } from 'vitest';
42
+ import { runAdvisoryPhase } from './advisory.js';
43
+ function makeLogger() {
44
+ const self = {
45
+ info: vi.fn(),
46
+ warn: vi.fn(),
47
+ error: vi.fn(),
48
+ debug: vi.fn(),
49
+ child: vi.fn(),
50
+ };
51
+ self.child = vi.fn(() => self);
52
+ return self;
53
+ }
54
+ function makeAdvisoryCtx(options = {}) {
55
+ const { budgetAllowed = true, firedTriggers = [], advisor, consultResult = { advice: 'do the thing' }, consultThrows, } = options;
56
+ const consult = vi.fn(async () => {
57
+ if (consultThrows)
58
+ throw consultThrows;
59
+ return {
60
+ result: consultResult,
61
+ usage: {
62
+ promptTokens: 0,
63
+ completionTokens: 0,
64
+ totalTokens: 0,
65
+ cachedTokens: 0,
66
+ cacheWriteTokens: 0,
67
+ },
68
+ cost: { inputCostPer1M: 0, outputCostPer1M: 0, totalCost: 0, cacheDiscount: 0 },
69
+ durationMs: 1,
70
+ };
71
+ });
72
+ const evaluate = vi.fn(() => firedTriggers);
73
+ const recordFiring = vi.fn();
74
+ const resolve = vi.fn(() => advisor);
75
+ const checkBudget = vi.fn(() => ({
76
+ allowed: budgetAllowed,
77
+ reason: budgetAllowed ? undefined : 'exhausted',
78
+ }));
79
+ const recordCall = vi.fn();
80
+ return {
81
+ ctx: {
82
+ registry: { resolve },
83
+ executor: { consult },
84
+ evaluator: { evaluate, recordFiring },
85
+ checkBudget,
86
+ recordCall,
87
+ callHistory: [],
88
+ },
89
+ mocks: { consult, evaluate, recordFiring, resolve, checkBudget, recordCall },
90
+ };
91
+ }
92
+ function makeCtx(options = {}) {
93
+ const pushMessage = vi.fn();
94
+ const addDecision = vi.fn();
95
+ const ctx = {
96
+ advisoryCtx: options.advisoryCtx,
97
+ runConfig: { tokenBudget: 100_000, costLimitUsd: undefined },
98
+ tools: {
99
+ get: vi.fn(() => undefined),
100
+ toLLMTools: vi.fn(() => []),
101
+ },
102
+ runMgr: {
103
+ id: 'run_1',
104
+ messages: [],
105
+ tokenUsage: {
106
+ promptTokens: 0,
107
+ completionTokens: 0,
108
+ totalTokens: 0,
109
+ cachedTokens: 0,
110
+ cacheWriteTokens: 0,
111
+ },
112
+ costInfo: {
113
+ inputCostPer1M: 0,
114
+ outputCostPer1M: 0,
115
+ totalCost: 0,
116
+ cacheDiscount: 0,
117
+ },
118
+ pushMessage,
119
+ },
120
+ log: makeLogger(),
121
+ workingStateManager: options.withWorkingState
122
+ ? {
123
+ getState: vi.fn(() => ({
124
+ task: '',
125
+ plan: [],
126
+ files: new Map(),
127
+ decisions: [],
128
+ failures: [],
129
+ discoveries: [],
130
+ environment: [],
131
+ toolResults: [],
132
+ userRequirements: [],
133
+ assistantNotes: [],
134
+ })),
135
+ addDecision,
136
+ }
137
+ : undefined,
138
+ };
139
+ return { ctx, pushMessage, addDecision };
140
+ }
141
+ const response = {
142
+ id: 'r',
143
+ model: 'm',
144
+ message: { role: 'assistant', content: 'text' },
145
+ usage: {
146
+ promptTokens: 0,
147
+ completionTokens: 0,
148
+ totalTokens: 0,
149
+ cachedTokens: 0,
150
+ cacheWriteTokens: 0,
151
+ },
152
+ finishReason: 'stop',
153
+ };
154
+ const advisor = {
155
+ id: 'adv',
156
+ name: 'Advisor',
157
+ provider: {},
158
+ model: 'opus',
159
+ };
160
+ const trigger = {
161
+ id: 'trig',
162
+ condition: { type: 'on_iteration', everyN: 1 },
163
+ advisorId: 'adv',
164
+ };
165
+ describe('runAdvisoryPhase — early-return paths (no pushMessage)', () => {
166
+ it('returns immediately when advisoryCtx is absent', async () => {
167
+ const { ctx, pushMessage } = makeCtx();
168
+ await runAdvisoryPhase(ctx, 1, response);
169
+ expect(pushMessage).not.toHaveBeenCalled();
170
+ });
171
+ it('returns when budget is denied', async () => {
172
+ const { ctx: advCtx, mocks } = makeAdvisoryCtx({ budgetAllowed: false });
173
+ const { ctx, pushMessage } = makeCtx({ advisoryCtx: advCtx });
174
+ await runAdvisoryPhase(ctx, 1, response);
175
+ expect(mocks.checkBudget).toHaveBeenCalled();
176
+ expect(mocks.evaluate).not.toHaveBeenCalled();
177
+ expect(pushMessage).not.toHaveBeenCalled();
178
+ });
179
+ it('returns when evaluator fires no triggers', async () => {
180
+ const { ctx: advCtx, mocks } = makeAdvisoryCtx({ firedTriggers: [] });
181
+ const { ctx, pushMessage } = makeCtx({ advisoryCtx: advCtx });
182
+ await runAdvisoryPhase(ctx, 1, response);
183
+ expect(mocks.evaluate).toHaveBeenCalled();
184
+ expect(mocks.resolve).not.toHaveBeenCalled();
185
+ expect(pushMessage).not.toHaveBeenCalled();
186
+ });
187
+ it('returns (warn) when trigger fires but advisor is not resolved', async () => {
188
+ const { ctx: advCtx, mocks } = makeAdvisoryCtx({
189
+ firedTriggers: [trigger],
190
+ advisor: undefined,
191
+ });
192
+ const { ctx, pushMessage } = makeCtx({ advisoryCtx: advCtx });
193
+ await runAdvisoryPhase(ctx, 1, response);
194
+ expect(mocks.resolve).toHaveBeenCalledWith('adv');
195
+ expect(mocks.consult).not.toHaveBeenCalled();
196
+ expect(pushMessage).not.toHaveBeenCalled();
197
+ });
198
+ it('returns when executor.consult throws — does NOT pushMessage partial work', async () => {
199
+ const { ctx: advCtx, mocks } = makeAdvisoryCtx({
200
+ firedTriggers: [trigger],
201
+ advisor,
202
+ consultThrows: new Error('provider timeout'),
203
+ });
204
+ const { ctx, pushMessage } = makeCtx({ advisoryCtx: advCtx });
205
+ await runAdvisoryPhase(ctx, 1, response);
206
+ expect(mocks.consult).toHaveBeenCalled();
207
+ expect(mocks.recordFiring).not.toHaveBeenCalled();
208
+ expect(mocks.recordCall).not.toHaveBeenCalled();
209
+ expect(pushMessage).not.toHaveBeenCalled();
210
+ });
211
+ });
212
+ describe('runAdvisoryPhase — happy path', () => {
213
+ it('calls executor + recordFiring + recordCall + pushMessage exactly once', async () => {
214
+ const { ctx: advCtx, mocks } = makeAdvisoryCtx({
215
+ firedTriggers: [trigger],
216
+ advisor,
217
+ consultResult: { advice: 'do the thing' },
218
+ });
219
+ const { ctx, pushMessage } = makeCtx({ advisoryCtx: advCtx });
220
+ await runAdvisoryPhase(ctx, 3, response);
221
+ expect(mocks.consult).toHaveBeenCalledTimes(1);
222
+ expect(mocks.recordFiring).toHaveBeenCalledWith('trig', 3);
223
+ expect(mocks.recordCall).toHaveBeenCalledTimes(1);
224
+ expect(pushMessage).toHaveBeenCalledTimes(1);
225
+ });
226
+ it('pushMessage wraps advice in <advisory-result> envelope with role user', async () => {
227
+ const { ctx: advCtx } = makeAdvisoryCtx({
228
+ firedTriggers: [trigger],
229
+ advisor,
230
+ consultResult: { advice: 'specific advice text' },
231
+ });
232
+ const { ctx, pushMessage } = makeCtx({ advisoryCtx: advCtx });
233
+ await runAdvisoryPhase(ctx, 1, response);
234
+ const pushed = pushMessage.mock.calls[0]?.[0];
235
+ expect(pushed.role).toBe('user');
236
+ expect(pushed.content).toContain('<advisory-result advisor="Advisor" trigger="trig">');
237
+ expect(pushed.content).toContain('specific advice text');
238
+ expect(pushed.content).toContain('</advisory-result>');
239
+ });
240
+ it('includes Warnings section when the result carries warnings', async () => {
241
+ const { ctx: advCtx } = makeAdvisoryCtx({
242
+ firedTriggers: [trigger],
243
+ advisor,
244
+ consultResult: {
245
+ advice: 'proceed',
246
+ warnings: ['slow response', 'retry likely'],
247
+ },
248
+ });
249
+ const { ctx, pushMessage } = makeCtx({ advisoryCtx: advCtx });
250
+ await runAdvisoryPhase(ctx, 1, response);
251
+ const pushed = pushMessage.mock.calls[0]?.[0];
252
+ expect(pushed.content).toContain('Warnings:');
253
+ expect(pushed.content).toContain('- slow response');
254
+ expect(pushed.content).toContain('- retry likely');
255
+ });
256
+ it('includes Decisions section + pushes each decision to workingStateManager', async () => {
257
+ const { ctx: advCtx } = makeAdvisoryCtx({
258
+ firedTriggers: [trigger],
259
+ advisor,
260
+ consultResult: {
261
+ advice: 'go',
262
+ decisions: ['use sqlite', 'skip migration'],
263
+ },
264
+ });
265
+ const { ctx, pushMessage, addDecision } = makeCtx({
266
+ advisoryCtx: advCtx,
267
+ withWorkingState: true,
268
+ });
269
+ await runAdvisoryPhase(ctx, 1, response);
270
+ const pushed = pushMessage.mock.calls[0]?.[0];
271
+ expect(pushed.content).toContain('Decisions:');
272
+ expect(pushed.content).toContain('- use sqlite');
273
+ expect(pushed.content).toContain('- skip migration');
274
+ expect(addDecision).toHaveBeenCalledTimes(2);
275
+ expect(addDecision).toHaveBeenCalledWith('use sqlite');
276
+ expect(addDecision).toHaveBeenCalledWith('skip migration');
277
+ });
278
+ it('does NOT attempt to addDecision when no workingStateManager is present', async () => {
279
+ const { ctx: advCtx } = makeAdvisoryCtx({
280
+ firedTriggers: [trigger],
281
+ advisor,
282
+ consultResult: {
283
+ advice: 'go',
284
+ decisions: ['a'],
285
+ },
286
+ });
287
+ const { ctx, pushMessage } = makeCtx({ advisoryCtx: advCtx, withWorkingState: false });
288
+ await runAdvisoryPhase(ctx, 1, response);
289
+ // pushMessage still carries the Decisions section in content
290
+ expect((pushMessage.mock.calls[0]?.[0]).content).toContain('- a');
291
+ });
292
+ });
293
+ describe('runAdvisoryPhase — trigger selection + question', () => {
294
+ it('uses only the first fired trigger per iteration', async () => {
295
+ const other = {
296
+ id: 'other',
297
+ condition: { type: 'on_iteration', everyN: 1 },
298
+ advisorId: 'adv',
299
+ };
300
+ const { ctx: advCtx, mocks } = makeAdvisoryCtx({
301
+ firedTriggers: [trigger, other],
302
+ advisor,
303
+ });
304
+ const { ctx } = makeCtx({ advisoryCtx: advCtx });
305
+ await runAdvisoryPhase(ctx, 1, response);
306
+ expect(mocks.recordFiring).toHaveBeenCalledTimes(1);
307
+ expect(mocks.recordFiring).toHaveBeenCalledWith('trig', 1);
308
+ });
309
+ it('uses trigger.questionTemplate when present', async () => {
310
+ const custom = {
311
+ ...trigger,
312
+ questionTemplate: 'Custom question',
313
+ };
314
+ const { ctx: advCtx, mocks } = makeAdvisoryCtx({
315
+ firedTriggers: [custom],
316
+ advisor,
317
+ });
318
+ const { ctx } = makeCtx({ advisoryCtx: advCtx });
319
+ await runAdvisoryPhase(ctx, 7, response);
320
+ const consultArgs = mocks.consult.mock.calls[0];
321
+ expect(consultArgs?.[1].question).toBe('Custom question');
322
+ });
323
+ it('falls back to the default "Iteration N: ..." question when no template', async () => {
324
+ const { ctx: advCtx, mocks } = makeAdvisoryCtx({
325
+ firedTriggers: [trigger],
326
+ advisor,
327
+ });
328
+ const { ctx } = makeCtx({ advisoryCtx: advCtx });
329
+ await runAdvisoryPhase(ctx, 7, response);
330
+ const consultArgs = mocks.consult.mock.calls[0];
331
+ expect(consultArgs?.[1].question).toContain('Iteration 7');
332
+ });
333
+ });
334
+ //# sourceMappingURL=advisory.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"advisory.test.js","sourceRoot":"","sources":["../../../../../src/runtime/query/iteration/phases/advisory.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Vitest `setupFiles` entry for `@namzu/sdk`.
3
+ *
4
+ * Silences the root logger for the entire test run. Tests that assert on
5
+ * log output use their own mocked Logger instances (constructed via the
6
+ * `makeLogger()` helpers colocated with each test) — those are not
7
+ * affected by the root-level silence.
8
+ *
9
+ * The only thing this suppresses is the stderr spam produced by
10
+ * production code paths that fall through to `getRootLogger()` — e.g.
11
+ * `ToolRegistry.execute`'s zod-validation and thrown-error branches,
12
+ * `ConnectorToolRouter.getTools`' per-instance error catch, the
13
+ * AgentBus listener-throw handler, and every connector's `connect()`
14
+ * info log. GitHub Actions annotates any `[ERROR]` stderr line as a
15
+ * workflow error; silencing the root logger during tests keeps the CI
16
+ * log clean.
17
+ *
18
+ * This is test-only configuration. It runs before every test file and
19
+ * does not affect consumers.
20
+ */
21
+ export {};
22
+ //# sourceMappingURL=test-setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-setup.d.ts","sourceRoot":"","sources":["../src/test-setup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Vitest `setupFiles` entry for `@namzu/sdk`.
3
+ *
4
+ * Silences the root logger for the entire test run. Tests that assert on
5
+ * log output use their own mocked Logger instances (constructed via the
6
+ * `makeLogger()` helpers colocated with each test) — those are not
7
+ * affected by the root-level silence.
8
+ *
9
+ * The only thing this suppresses is the stderr spam produced by
10
+ * production code paths that fall through to `getRootLogger()` — e.g.
11
+ * `ToolRegistry.execute`'s zod-validation and thrown-error branches,
12
+ * `ConnectorToolRouter.getTools`' per-instance error catch, the
13
+ * AgentBus listener-throw handler, and every connector's `connect()`
14
+ * info log. GitHub Actions annotates any `[ERROR]` stderr line as a
15
+ * workflow error; silencing the root logger during tests keeps the CI
16
+ * log clean.
17
+ *
18
+ * This is test-only configuration. It runs before every test file and
19
+ * does not affect consumers.
20
+ */
21
+ import { configureLogger } from './utils/logger.js';
22
+ configureLogger({ level: 'silent' });
23
+ //# sourceMappingURL=test-setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-setup.js","sourceRoot":"","sources":["../src/test-setup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAEnD,eAAe,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAA"}
@@ -1,4 +1,4 @@
1
- export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
1
+ export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';
2
2
  export type LogContext = Record<string, unknown>;
3
3
  export interface Logger {
4
4
  debug(message: string, data?: LogContext): void;
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;AAE1D,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAShD,MAAM,WAAW,MAAM;IACtB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,IAAI,CAAA;IAC/C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,IAAI,CAAA;IAC9C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,IAAI,CAAA;IAC9C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,IAAI,CAAA;IAC/C,KAAK,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM,CAAA;CAClC;AAsCD,wBAAgB,aAAa,IAAI,MAAM,CAKtC;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE;IAAE,KAAK,CAAC,EAAE,QAAQ,CAAA;CAAE,GAAG,IAAI,CAEnE"}
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAA;AAErE,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAchD,MAAM,WAAW,MAAM;IACtB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,IAAI,CAAA;IAC/C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,IAAI,CAAA;IAC9C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,IAAI,CAAA;IAC9C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,IAAI,CAAA;IAC/C,KAAK,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM,CAAA;CAClC;AAsCD,wBAAgB,aAAa,IAAI,MAAM,CAKtC;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE;IAAE,KAAK,CAAC,EAAE,QAAQ,CAAA;CAAE,GAAG,IAAI,CAEnE"}
@@ -3,6 +3,11 @@ const LOG_LEVELS = {
3
3
  info: 1,
4
4
  warn: 2,
5
5
  error: 3,
6
+ // `silent` sits above every emit level so the `level < minLevelNum`
7
+ // guard in `log()` always short-circuits when configured. Used by
8
+ // test harnesses to suppress unmocked `getRootLogger()` stderr
9
+ // writes; see packages/sdk/src/test-setup.ts.
10
+ silent: 4,
6
11
  };
7
12
  function createLoggerImpl(name, minLevel, parentContext) {
8
13
  const minLevelNum = LOG_LEVELS[minLevel];
@@ -1 +1 @@
1
- {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAIA,MAAM,UAAU,GAA6B;IAC5C,KAAK,EAAE,CAAC;IACR,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;CACR,CAAA;AAUD,SAAS,gBAAgB,CAAC,IAAY,EAAE,QAAkB,EAAE,aAAyB;IACpF,MAAM,WAAW,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAA;IAExC,SAAS,GAAG,CAAC,KAAe,EAAE,OAAe,EAAE,IAAiB;QAC/D,IAAI,UAAU,CAAC,KAAK,CAAC,GAAG,WAAW;YAAE,OAAM;QAE3C,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;QAC1C,MAAM,MAAM,GAAG,IAAI,SAAS,MAAM,KAAK,CAAC,WAAW,EAAE,MAAM,IAAI,GAAG,CAAA;QAClE,MAAM,MAAM,GAAG,EAAE,GAAG,aAAa,EAAE,GAAG,IAAI,EAAE,CAAA;QAC5C,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,CAAA;QAEjD,IAAI,UAAU,EAAE,CAAC;YAChB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,MAAM,IAAI,OAAO,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QACzE,CAAC;aAAM,CAAC;YACP,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,MAAM,IAAI,OAAO,IAAI,CAAC,CAAA;QAC/C,CAAC;IACF,CAAC;IAED,SAAS,KAAK,CAAC,OAAmB;QACjC,OAAO,gBAAgB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE;YACvF,GAAG,aAAa;YAChB,GAAG,OAAO;SACV,CAAC,CAAA;IACH,CAAC;IAED,OAAO;QACN,KAAK,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC;QAC7C,IAAI,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC;QAC3C,IAAI,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC;QAC3C,KAAK,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC;QAC7C,KAAK;KACL,CAAA;AACF,CAAC;AAED,IAAI,WAAW,GAAkB,IAAI,CAAA;AAErC,MAAM,UAAU,aAAa;IAC5B,IAAI,CAAC,WAAW,EAAE,CAAC;QAClB,WAAW,GAAG,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,CAAA;IACpD,CAAC;IACD,OAAO,WAAW,CAAA;AACnB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAA6B;IAC5D,WAAW,GAAG,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,IAAI,MAAM,EAAE,EAAE,CAAC,CAAA;AACrE,CAAC"}
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAIA,MAAM,UAAU,GAA6B;IAC5C,KAAK,EAAE,CAAC;IACR,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;IACR,oEAAoE;IACpE,kEAAkE;IAClE,+DAA+D;IAC/D,8CAA8C;IAC9C,MAAM,EAAE,CAAC;CACT,CAAA;AAUD,SAAS,gBAAgB,CAAC,IAAY,EAAE,QAAkB,EAAE,aAAyB;IACpF,MAAM,WAAW,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAA;IAExC,SAAS,GAAG,CAAC,KAAe,EAAE,OAAe,EAAE,IAAiB;QAC/D,IAAI,UAAU,CAAC,KAAK,CAAC,GAAG,WAAW;YAAE,OAAM;QAE3C,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;QAC1C,MAAM,MAAM,GAAG,IAAI,SAAS,MAAM,KAAK,CAAC,WAAW,EAAE,MAAM,IAAI,GAAG,CAAA;QAClE,MAAM,MAAM,GAAG,EAAE,GAAG,aAAa,EAAE,GAAG,IAAI,EAAE,CAAA;QAC5C,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,CAAA;QAEjD,IAAI,UAAU,EAAE,CAAC;YAChB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,MAAM,IAAI,OAAO,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QACzE,CAAC;aAAM,CAAC;YACP,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,MAAM,IAAI,OAAO,IAAI,CAAC,CAAA;QAC/C,CAAC;IACF,CAAC;IAED,SAAS,KAAK,CAAC,OAAmB;QACjC,OAAO,gBAAgB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE;YACvF,GAAG,aAAa;YAChB,GAAG,OAAO;SACV,CAAC,CAAA;IACH,CAAC;IAED,OAAO;QACN,KAAK,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC;QAC7C,IAAI,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC;QAC3C,IAAI,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC;QAC3C,KAAK,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC;QAC7C,KAAK;KACL,CAAA;AACF,CAAC;AAED,IAAI,WAAW,GAAkB,IAAI,CAAA;AAErC,MAAM,UAAU,aAAa;IAC5B,IAAI,CAAC,WAAW,EAAE,CAAC;QAClB,WAAW,GAAG,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,CAAA;IACpD,CAAC;IACD,OAAO,WAAW,CAAA;AACnB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAA6B;IAC5D,WAAW,GAAG,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,IAAI,MAAM,EAAE,EAAE,CAAC,CAAA;AACrE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@namzu/sdk",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "Open-source AI agent SDK with a built-in runtime. Nothing between you and your agents.",
5
5
  "license": "FSL-1.1-MIT",
6
6
  "type": "module",
@@ -43,6 +43,8 @@
43
43
  "@biomejs/biome": "^1.9.4",
44
44
  "@opentelemetry/api": "^1.9.0",
45
45
  "@types/node": "^22.19.17",
46
+ "@vitest/coverage-v8": "^2.1.9",
47
+ "fast-check": "^4.7.0",
46
48
  "knip": "^6.4.1",
47
49
  "typescript": "^5.5.0",
48
50
  "vitest": "^2.0.0",
@@ -56,6 +58,7 @@
56
58
  "lint:fix": "biome check --write src/",
57
59
  "format": "biome format --write src/",
58
60
  "test": "vitest run --passWithNoTests",
61
+ "test:coverage": "vitest run --passWithNoTests --coverage",
59
62
  "typecheck": "tsc --noEmit",
60
63
  "knip": "knip",
61
64
  "knip:production": "knip --production"
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Current-code invariants asserted (2026-04-21, ses_006 Phase 6):
3
+ *
4
+ * - `AdvisoryContext` composes a registry + executor + evaluator +
5
+ * optional budget. It is a dumb container + a call-history log.
6
+ * - `recordCall(record)` appends to `callHistory` in call order.
7
+ * - `getBudgetStatus()`:
8
+ * - `used` = `callHistory.length`.
9
+ * - `total` = `budget?.maxCallsPerRun` (undefined when no budget).
10
+ * - `remaining` = total − used when total defined; undefined else.
11
+ * - `checkBudget()`:
12
+ * - Allowed when no budget OR remaining > 0.
13
+ * - Denied with reason when remaining ≤ 0.
14
+ */
15
+
16
+ import { describe, expect, it } from 'vitest'
17
+
18
+ import type { AdvisoryCallRecord } from '../types/advisory/index.js'
19
+
20
+ import { AdvisoryContext } from './context.js'
21
+ import type { TriggerEvaluator } from './evaluator.js'
22
+ import type { AdvisoryExecutor } from './executor.js'
23
+ import type { AdvisorRegistry } from './registry.js'
24
+
25
+ function stubRegistry(): AdvisorRegistry {
26
+ return {} as unknown as AdvisorRegistry
27
+ }
28
+
29
+ function stubExecutor(): AdvisoryExecutor {
30
+ return {} as unknown as AdvisoryExecutor
31
+ }
32
+
33
+ function stubEvaluator(): TriggerEvaluator {
34
+ return {} as unknown as TriggerEvaluator
35
+ }
36
+
37
+ function callRecord(id: string): AdvisoryCallRecord {
38
+ return {
39
+ advisorId: id,
40
+ request: { question: 'q' },
41
+ result: { advice: 'a' },
42
+ usage: {
43
+ promptTokens: 0,
44
+ completionTokens: 0,
45
+ totalTokens: 0,
46
+ cachedTokens: 0,
47
+ cacheWriteTokens: 0,
48
+ },
49
+ cost: { inputCostPer1M: 0, outputCostPer1M: 0, totalCost: 0, cacheDiscount: 0 },
50
+ durationMs: 0,
51
+ iteration: 0,
52
+ timestamp: Date.now(),
53
+ }
54
+ }
55
+
56
+ describe('AdvisoryContext', () => {
57
+ it('starts with empty callHistory', () => {
58
+ const ctx = new AdvisoryContext(stubRegistry(), stubExecutor(), stubEvaluator())
59
+ expect(ctx.callHistory).toEqual([])
60
+ })
61
+
62
+ it('recordCall appends in order', () => {
63
+ const ctx = new AdvisoryContext(stubRegistry(), stubExecutor(), stubEvaluator())
64
+ ctx.recordCall(callRecord('a'))
65
+ ctx.recordCall(callRecord('b'))
66
+ expect(ctx.callHistory.map((r) => r.advisorId)).toEqual(['a', 'b'])
67
+ })
68
+
69
+ describe('getBudgetStatus', () => {
70
+ it('no budget → total + remaining undefined; used reflects history length', () => {
71
+ const ctx = new AdvisoryContext(stubRegistry(), stubExecutor(), stubEvaluator())
72
+ ctx.recordCall(callRecord('a'))
73
+ expect(ctx.getBudgetStatus()).toEqual({ used: 1, total: undefined, remaining: undefined })
74
+ })
75
+
76
+ it('with budget → total + remaining computed', () => {
77
+ const ctx = new AdvisoryContext(stubRegistry(), stubExecutor(), stubEvaluator(), {
78
+ maxCallsPerRun: 3,
79
+ })
80
+ ctx.recordCall(callRecord('a'))
81
+ expect(ctx.getBudgetStatus()).toEqual({ used: 1, total: 3, remaining: 2 })
82
+ })
83
+ })
84
+
85
+ describe('checkBudget', () => {
86
+ it('allowed when no budget', () => {
87
+ const ctx = new AdvisoryContext(stubRegistry(), stubExecutor(), stubEvaluator())
88
+ expect(ctx.checkBudget()).toEqual({ allowed: true })
89
+ })
90
+
91
+ it('allowed when remaining > 0', () => {
92
+ const ctx = new AdvisoryContext(stubRegistry(), stubExecutor(), stubEvaluator(), {
93
+ maxCallsPerRun: 2,
94
+ })
95
+ ctx.recordCall(callRecord('a'))
96
+ expect(ctx.checkBudget()).toEqual({ allowed: true })
97
+ })
98
+
99
+ it('denied when remaining <= 0', () => {
100
+ const ctx = new AdvisoryContext(stubRegistry(), stubExecutor(), stubEvaluator(), {
101
+ maxCallsPerRun: 1,
102
+ })
103
+ ctx.recordCall(callRecord('a'))
104
+ const result = ctx.checkBudget()
105
+ expect(result.allowed).toBe(false)
106
+ expect(result.reason).toMatch(/budget exhausted/)
107
+ })
108
+ })
109
+ })