agentic-orchestrator 0.1.26 → 0.1.28

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 (172) hide show
  1. package/AGENTS.md +2 -2
  2. package/CLAUDE.md +2 -2
  3. package/README.md +47 -14
  4. package/agentic/orchestrator/agents.yaml +13 -0
  5. package/agentic/orchestrator/policy.yaml +3 -0
  6. package/agentic/orchestrator/schemas/agents.schema.json +76 -0
  7. package/agentic/orchestrator/schemas/policy.schema.json +16 -0
  8. package/agentic/orchestrator/schemas/policy.user.schema.json +16 -0
  9. package/agentic/orchestrator/schemas/state.schema.json +53 -0
  10. package/apps/control-plane/src/application/configuration-service.ts +181 -0
  11. package/apps/control-plane/src/application/kernel-tool-wiring.ts +292 -0
  12. package/apps/control-plane/src/application/services/checkpoint-service.ts +523 -0
  13. package/apps/control-plane/src/application/services/feature-send-message-service.ts +132 -0
  14. package/apps/control-plane/src/application/services/patch-service.ts +29 -5
  15. package/apps/control-plane/src/application/services/repo-operations-service.ts +276 -0
  16. package/apps/control-plane/src/application/services/worktree-watchdog-service.ts +156 -0
  17. package/apps/control-plane/src/cli/cli-argument-parser.ts +12 -0
  18. package/apps/control-plane/src/cli/help-command-handler.ts +17 -0
  19. package/apps/control-plane/src/cli/init-command-handler.ts +31 -0
  20. package/apps/control-plane/src/cli/resume-command-handler.ts +31 -4
  21. package/apps/control-plane/src/cli/rollback-command-handler.ts +217 -0
  22. package/apps/control-plane/src/cli/run-command-handler.ts +8 -0
  23. package/apps/control-plane/src/cli/types.ts +3 -0
  24. package/apps/control-plane/src/core/kernel-types.ts +55 -0
  25. package/apps/control-plane/src/core/kernel.ts +61 -878
  26. package/apps/control-plane/src/core/tool-caller.ts +10 -0
  27. package/apps/control-plane/src/core/utils/field-readers.ts +38 -0
  28. package/apps/control-plane/src/core/utils/index-normalizer.ts +119 -0
  29. package/apps/control-plane/src/core/utils/path-normalizers.ts +22 -0
  30. package/apps/control-plane/src/interfaces/cli/bootstrap.ts +15 -0
  31. package/apps/control-plane/src/providers/api-worker-provider.ts +14 -12
  32. package/apps/control-plane/src/providers/cli-worker-provider.ts +82 -12
  33. package/apps/control-plane/src/providers/providers.ts +45 -24
  34. package/apps/control-plane/src/providers/worker-provider-factory.ts +36 -1
  35. package/apps/control-plane/src/supervisor/run-coordinator.ts +91 -36
  36. package/apps/control-plane/src/supervisor/runtime.ts +107 -1
  37. package/apps/control-plane/src/supervisor/types.ts +9 -0
  38. package/apps/control-plane/src/supervisor/worker-decision-loop.ts +253 -14
  39. package/apps/control-plane/test/checkpoint-service.spec.ts +537 -0
  40. package/apps/control-plane/test/cli-helpers.spec.ts +28 -0
  41. package/apps/control-plane/test/cli.unit.spec.ts +52 -0
  42. package/apps/control-plane/test/configuration-service.spec.ts +466 -0
  43. package/apps/control-plane/test/dashboard-api.integration.spec.ts +537 -0
  44. package/apps/control-plane/test/dashboard-client.spec.ts +233 -0
  45. package/apps/control-plane/test/feature-send-message-service.spec.ts +314 -0
  46. package/apps/control-plane/test/init-wizard.spec.ts +35 -0
  47. package/apps/control-plane/test/path-normalizers.spec.ts +41 -0
  48. package/apps/control-plane/test/repo-operations-service.spec.ts +339 -0
  49. package/apps/control-plane/test/resume-command.spec.ts +33 -0
  50. package/apps/control-plane/test/review-workspace-logic.spec.ts +130 -0
  51. package/apps/control-plane/test/rollback-command.spec.ts +208 -0
  52. package/apps/control-plane/test/run-coordinator.spec.ts +119 -0
  53. package/apps/control-plane/test/worker-decision-loop.spec.ts +209 -0
  54. package/apps/control-plane/test/worker-provider-adapters.spec.ts +102 -0
  55. package/apps/control-plane/test/worker-provider-factory.spec.ts +14 -0
  56. package/apps/control-plane/test/worktree-watchdog-service.spec.ts +147 -0
  57. package/config/agentic/orchestrator/agents.yaml +13 -0
  58. package/dist/apps/control-plane/application/configuration-service.d.ts +19 -0
  59. package/dist/apps/control-plane/application/configuration-service.js +123 -0
  60. package/dist/apps/control-plane/application/configuration-service.js.map +1 -0
  61. package/dist/apps/control-plane/application/kernel-tool-wiring.d.ts +39 -0
  62. package/dist/apps/control-plane/application/kernel-tool-wiring.js +38 -0
  63. package/dist/apps/control-plane/application/kernel-tool-wiring.js.map +1 -0
  64. package/dist/apps/control-plane/application/services/checkpoint-service.d.ts +84 -0
  65. package/dist/apps/control-plane/application/services/checkpoint-service.js +367 -0
  66. package/dist/apps/control-plane/application/services/checkpoint-service.js.map +1 -0
  67. package/dist/apps/control-plane/application/services/feature-send-message-service.d.ts +25 -0
  68. package/dist/apps/control-plane/application/services/feature-send-message-service.js +105 -0
  69. package/dist/apps/control-plane/application/services/feature-send-message-service.js.map +1 -0
  70. package/dist/apps/control-plane/application/services/patch-service.d.ts +6 -0
  71. package/dist/apps/control-plane/application/services/patch-service.js +11 -2
  72. package/dist/apps/control-plane/application/services/patch-service.js.map +1 -1
  73. package/dist/apps/control-plane/application/services/repo-operations-service.d.ts +70 -0
  74. package/dist/apps/control-plane/application/services/repo-operations-service.js +213 -0
  75. package/dist/apps/control-plane/application/services/repo-operations-service.js.map +1 -0
  76. package/dist/apps/control-plane/application/services/worktree-watchdog-service.d.ts +23 -0
  77. package/dist/apps/control-plane/application/services/worktree-watchdog-service.js +119 -0
  78. package/dist/apps/control-plane/application/services/worktree-watchdog-service.js.map +1 -0
  79. package/dist/apps/control-plane/cli/cli-argument-parser.js +12 -0
  80. package/dist/apps/control-plane/cli/cli-argument-parser.js.map +1 -1
  81. package/dist/apps/control-plane/cli/help-command-handler.js +17 -0
  82. package/dist/apps/control-plane/cli/help-command-handler.js.map +1 -1
  83. package/dist/apps/control-plane/cli/init-command-handler.js +23 -0
  84. package/dist/apps/control-plane/cli/init-command-handler.js.map +1 -1
  85. package/dist/apps/control-plane/cli/resume-command-handler.js +25 -5
  86. package/dist/apps/control-plane/cli/resume-command-handler.js.map +1 -1
  87. package/dist/apps/control-plane/cli/rollback-command-handler.d.ts +6 -0
  88. package/dist/apps/control-plane/cli/rollback-command-handler.js +177 -0
  89. package/dist/apps/control-plane/cli/rollback-command-handler.js.map +1 -0
  90. package/dist/apps/control-plane/cli/run-command-handler.js +7 -1
  91. package/dist/apps/control-plane/cli/run-command-handler.js.map +1 -1
  92. package/dist/apps/control-plane/cli/types.d.ts +3 -0
  93. package/dist/apps/control-plane/cli/types.js +1 -0
  94. package/dist/apps/control-plane/cli/types.js.map +1 -1
  95. package/dist/apps/control-plane/core/configuration-service.d.ts +25 -0
  96. package/dist/apps/control-plane/core/configuration-service.js +130 -0
  97. package/dist/apps/control-plane/core/configuration-service.js.map +1 -0
  98. package/dist/apps/control-plane/core/kernel-tool-wiring.d.ts +50 -0
  99. package/dist/apps/control-plane/core/kernel-tool-wiring.js +44 -0
  100. package/dist/apps/control-plane/core/kernel-tool-wiring.js.map +1 -0
  101. package/dist/apps/control-plane/core/kernel-types.d.ts +48 -0
  102. package/dist/apps/control-plane/core/kernel-types.js +2 -0
  103. package/dist/apps/control-plane/core/kernel-types.js.map +1 -0
  104. package/dist/apps/control-plane/core/kernel.d.ts +17 -48
  105. package/dist/apps/control-plane/core/kernel.js +44 -539
  106. package/dist/apps/control-plane/core/kernel.js.map +1 -1
  107. package/dist/apps/control-plane/core/tool-caller.d.ts +10 -0
  108. package/dist/apps/control-plane/core/utils/error-normalizer.d.ts +2 -0
  109. package/dist/apps/control-plane/core/utils/error-normalizer.js +51 -0
  110. package/dist/apps/control-plane/core/utils/error-normalizer.js.map +1 -0
  111. package/dist/apps/control-plane/core/utils/field-readers.d.ts +9 -0
  112. package/dist/apps/control-plane/core/utils/field-readers.js +30 -0
  113. package/dist/apps/control-plane/core/utils/field-readers.js.map +1 -0
  114. package/dist/apps/control-plane/core/utils/index-normalizer.d.ts +7 -0
  115. package/dist/apps/control-plane/core/utils/index-normalizer.js +92 -0
  116. package/dist/apps/control-plane/core/utils/index-normalizer.js.map +1 -0
  117. package/dist/apps/control-plane/core/utils/path-normalizers.d.ts +2 -0
  118. package/dist/apps/control-plane/core/utils/path-normalizers.js +17 -0
  119. package/dist/apps/control-plane/core/utils/path-normalizers.js.map +1 -0
  120. package/dist/apps/control-plane/interfaces/cli/bootstrap.js +13 -1
  121. package/dist/apps/control-plane/interfaces/cli/bootstrap.js.map +1 -1
  122. package/dist/apps/control-plane/providers/api-worker-provider.d.ts +4 -13
  123. package/dist/apps/control-plane/providers/api-worker-provider.js +10 -0
  124. package/dist/apps/control-plane/providers/api-worker-provider.js.map +1 -1
  125. package/dist/apps/control-plane/providers/cli-worker-provider.d.ts +11 -13
  126. package/dist/apps/control-plane/providers/cli-worker-provider.js +64 -0
  127. package/dist/apps/control-plane/providers/cli-worker-provider.js.map +1 -1
  128. package/dist/apps/control-plane/providers/providers.d.ts +31 -24
  129. package/dist/apps/control-plane/providers/providers.js +10 -0
  130. package/dist/apps/control-plane/providers/providers.js.map +1 -1
  131. package/dist/apps/control-plane/providers/worker-provider-factory.d.ts +11 -0
  132. package/dist/apps/control-plane/providers/worker-provider-factory.js +20 -1
  133. package/dist/apps/control-plane/providers/worker-provider-factory.js.map +1 -1
  134. package/dist/apps/control-plane/supervisor/run-coordinator.d.ts +3 -0
  135. package/dist/apps/control-plane/supervisor/run-coordinator.js +81 -33
  136. package/dist/apps/control-plane/supervisor/run-coordinator.js.map +1 -1
  137. package/dist/apps/control-plane/supervisor/runtime.d.ts +8 -1
  138. package/dist/apps/control-plane/supervisor/runtime.js +90 -0
  139. package/dist/apps/control-plane/supervisor/runtime.js.map +1 -1
  140. package/dist/apps/control-plane/supervisor/types.d.ts +11 -0
  141. package/dist/apps/control-plane/supervisor/types.js.map +1 -1
  142. package/dist/apps/control-plane/supervisor/worker-decision-loop.d.ts +21 -1
  143. package/dist/apps/control-plane/supervisor/worker-decision-loop.js +207 -13
  144. package/dist/apps/control-plane/supervisor/worker-decision-loop.js.map +1 -1
  145. package/package.json +1 -1
  146. package/packages/web-dashboard/package.json +2 -0
  147. package/packages/web-dashboard/src/app/analytics/page.tsx +83 -2
  148. package/packages/web-dashboard/src/app/api/actions/route.ts +92 -1
  149. package/packages/web-dashboard/src/app/api/analytics/route.ts +5 -2
  150. package/packages/web-dashboard/src/app/api/features/[id]/checkpoints/[checkpointId]/diff/route.ts +43 -0
  151. package/packages/web-dashboard/src/app/api/features/[id]/checkpoints/compare/route.ts +45 -0
  152. package/packages/web-dashboard/src/app/api/features/[id]/checkpoints/stream/route.ts +170 -0
  153. package/packages/web-dashboard/src/app/api/features/[id]/file-diff/route.ts +144 -0
  154. package/packages/web-dashboard/src/app/api/features/[id]/log-stream/route.ts +167 -0
  155. package/packages/web-dashboard/src/app/api/features/[id]/raw-logs/[filename]/route.ts +65 -0
  156. package/packages/web-dashboard/src/app/api/features/[id]/raw-logs/route.ts +63 -0
  157. package/packages/web-dashboard/src/app/api/features/[id]/timeline/route.ts +60 -0
  158. package/packages/web-dashboard/src/app/feature/[id]/page.tsx +32 -11
  159. package/packages/web-dashboard/src/app/globals.css +2 -0
  160. package/packages/web-dashboard/src/components/detail-panel.tsx +483 -0
  161. package/packages/web-dashboard/src/components/review-workspace.tsx +1162 -0
  162. package/packages/web-dashboard/src/lib/aop-client.ts +725 -0
  163. package/packages/web-dashboard/src/lib/review-contracts.ts +182 -0
  164. package/packages/web-dashboard/src/lib/review-workspace-logic.ts +64 -0
  165. package/packages/web-dashboard/src/lib/types.ts +131 -0
  166. package/packages/web-dashboard/src/styles/dashboard.module.css +333 -0
  167. package/spec-files/completed/agentic_orchestrator_execution_mode_spec.md +1905 -0
  168. package/spec-files/outstanding/agentic_orchestrator_runtime_inspection_spec.md +940 -0
  169. package/spec-files/outstanding/execution_mode_critical_review.md +355 -0
  170. package/spec-files/outstanding/shadow_workspace_implementation_spec.md +1271 -0
  171. package/spec-files/outstanding/shadow_workspace_spec_summary.md +222 -0
  172. package/spec-files/progress.md +269 -1
@@ -0,0 +1,466 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+
3
+ const pathExistsMock = vi.hoisted(() => vi.fn(async () => false));
4
+ const ensureAopRuntimeLayoutMock = vi.hoisted(() => vi.fn(async () => {}));
5
+ const loadAndValidateYamlMock = vi.hoisted(() => vi.fn());
6
+ const loadComposedPolicyMock = vi.hoisted(() => vi.fn());
7
+ const toolRegistryLoadMock = vi.hoisted(() => vi.fn(async () => null));
8
+ const adapterRegistryResolveMock = vi.hoisted(() => vi.fn());
9
+
10
+ vi.mock('../src/core/fs.js', () => ({
11
+ pathExists: pathExistsMock,
12
+ ensureDir: vi.fn(async () => {}),
13
+ }));
14
+
15
+ vi.mock('../src/core/path-layout.js', async (importOriginal) => {
16
+ const actual = await importOriginal();
17
+ return {
18
+ ...(actual as object),
19
+ ensureAopRuntimeLayout: ensureAopRuntimeLayoutMock,
20
+ };
21
+ });
22
+
23
+ vi.mock('../src/core/schemas.js', () => ({
24
+ SchemaRegistry: vi.fn().mockImplementation(() => ({})),
25
+ loadAndValidateYaml: loadAndValidateYamlMock,
26
+ }));
27
+
28
+ vi.mock('../src/application/services/policy-loader-service.js', () => ({
29
+ loadComposedPolicy: loadComposedPolicyMock,
30
+ }));
31
+
32
+ vi.mock('../src/mcp/tool-registry-loader.js', () => ({
33
+ ToolRegistryLoader: vi.fn(function (this: Record<string, unknown>) {
34
+ this.load = toolRegistryLoadMock;
35
+ }),
36
+ }));
37
+
38
+ vi.mock('../src/application/adapters/adapter-registry.js', async (importOriginal) => {
39
+ const actual = await importOriginal();
40
+ return {
41
+ ...(actual as object),
42
+ globalAdapterRegistry: {
43
+ resolve: adapterRegistryResolveMock,
44
+ },
45
+ };
46
+ });
47
+
48
+ import { ConfigurationService } from '../src/application/configuration-service.js';
49
+ import { AopPathLayout } from '../src/core/path-layout.js';
50
+
51
+ function makePathLayout(repoRoot = '/repo') {
52
+ return new AopPathLayout(repoRoot);
53
+ }
54
+
55
+ function makeSchemaRegistry() {
56
+ return {} as any; // SchemaRegistry is fully mocked; plain object suffices
57
+ }
58
+
59
+ function makeConfigOverrides(overrides: Record<string, unknown> = {}) {
60
+ return {
61
+ gatesPath: '/config/gates.yaml',
62
+ policyPath: '/config/policy.yaml',
63
+ agentsPath: '/config/agents.yaml',
64
+ adaptersPath: '/config/adapters.yaml',
65
+ ...overrides,
66
+ };
67
+ }
68
+
69
+ function makeValidGatesResult() {
70
+ return {
71
+ parsed: { profiles: { fast: {}, full: {}, merge: {} } },
72
+ validation: { valid: true, errors: [] },
73
+ };
74
+ }
75
+
76
+ function makeValidAgentsResult() {
77
+ return {
78
+ parsed: {
79
+ version: 1,
80
+ roles: {},
81
+ runtime: {},
82
+ },
83
+ validation: { valid: true, errors: [] },
84
+ };
85
+ }
86
+
87
+ function makeValidAdaptersResult(parsed: Record<string, unknown> = {}) {
88
+ return {
89
+ parsed,
90
+ validation: { valid: true, errors: [] },
91
+ };
92
+ }
93
+
94
+ function makeValidPolicy() {
95
+ return {
96
+ mergedPolicy: {
97
+ implementation: { workspace: 'nx' },
98
+ testing: { framework: 'vitest' },
99
+ },
100
+ };
101
+ }
102
+
103
+ describe('ConfigurationService.loadAll', () => {
104
+ beforeEach(() => {
105
+ vi.resetAllMocks();
106
+ // Re-establish default implementations after reset
107
+ pathExistsMock.mockResolvedValue(false);
108
+ ensureAopRuntimeLayoutMock.mockResolvedValue(undefined);
109
+ toolRegistryLoadMock.mockResolvedValue(null);
110
+ });
111
+
112
+ it('GIVEN_invalid_gates_yaml_WHEN_loadAll_THEN_throws_invalid_gates_yaml', async () => {
113
+ loadAndValidateYamlMock.mockResolvedValue({
114
+ parsed: {},
115
+ validation: { valid: false, errors: ['gates error'] },
116
+ });
117
+
118
+ const service = new ConfigurationService(
119
+ '/repo',
120
+ makePathLayout(),
121
+ makeSchemaRegistry(),
122
+ makeConfigOverrides(),
123
+ );
124
+ await expect(service.loadAll()).rejects.toThrow('invalid_gates_yaml');
125
+ });
126
+
127
+ it('GIVEN_policy_workspace_not_nx_WHEN_loadAll_THEN_throws_invalid_workspace', async () => {
128
+ loadAndValidateYamlMock.mockResolvedValue(makeValidGatesResult());
129
+ loadComposedPolicyMock.mockResolvedValue({
130
+ mergedPolicy: {
131
+ implementation: { workspace: 'other' },
132
+ testing: { framework: 'vitest' },
133
+ },
134
+ });
135
+
136
+ const service = new ConfigurationService(
137
+ '/repo',
138
+ makePathLayout(),
139
+ makeSchemaRegistry(),
140
+ makeConfigOverrides(),
141
+ );
142
+ await expect(service.loadAll()).rejects.toThrow('invalid_workspace_implementation');
143
+ });
144
+
145
+ it('GIVEN_policy_testing_framework_not_vitest_WHEN_loadAll_THEN_throws_invalid_workspace', async () => {
146
+ loadAndValidateYamlMock.mockResolvedValue(makeValidGatesResult());
147
+ loadComposedPolicyMock.mockResolvedValue({
148
+ mergedPolicy: {
149
+ implementation: { workspace: 'nx' },
150
+ testing: { framework: 'jest' },
151
+ },
152
+ });
153
+
154
+ const service = new ConfigurationService(
155
+ '/repo',
156
+ makePathLayout(),
157
+ makeSchemaRegistry(),
158
+ makeConfigOverrides(),
159
+ );
160
+ await expect(service.loadAll()).rejects.toThrow('invalid_workspace_implementation');
161
+ });
162
+
163
+ it('GIVEN_agents_file_not_exists_WHEN_loadAll_THEN_uses_default_agents', async () => {
164
+ pathExistsMock.mockResolvedValue(false);
165
+ loadAndValidateYamlMock.mockResolvedValue(makeValidGatesResult());
166
+ loadComposedPolicyMock.mockResolvedValue(makeValidPolicy());
167
+
168
+ const service = new ConfigurationService(
169
+ '/repo',
170
+ makePathLayout(),
171
+ makeSchemaRegistry(),
172
+ makeConfigOverrides(),
173
+ );
174
+ const result = await service.loadAll();
175
+ // agents.schema.json should not have been called for agents validation
176
+ expect(loadAndValidateYamlMock).toHaveBeenCalledTimes(1); // only gates
177
+ expect(result.agentsConfig).toMatchObject({ version: 1, roles: {} });
178
+ });
179
+
180
+ it('GIVEN_agents_file_exists_and_valid_WHEN_loadAll_THEN_loads_agents', async () => {
181
+ pathExistsMock
182
+ .mockResolvedValueOnce(true) // agentsExists
183
+ .mockResolvedValueOnce(false); // adaptersExists
184
+
185
+ loadAndValidateYamlMock
186
+ .mockResolvedValueOnce(makeValidGatesResult())
187
+ .mockResolvedValueOnce(makeValidAgentsResult());
188
+
189
+ loadComposedPolicyMock.mockResolvedValue(makeValidPolicy());
190
+
191
+ const service = new ConfigurationService(
192
+ '/repo',
193
+ makePathLayout(),
194
+ makeSchemaRegistry(),
195
+ makeConfigOverrides(),
196
+ );
197
+ const result = await service.loadAll();
198
+ expect(loadAndValidateYamlMock).toHaveBeenCalledTimes(2);
199
+ expect(result.agentsConfig).toMatchObject({ version: 1 });
200
+ });
201
+
202
+ it('GIVEN_agents_file_invalid_WHEN_loadAll_THEN_throws_invalid_agents_yaml', async () => {
203
+ pathExistsMock.mockResolvedValueOnce(true);
204
+
205
+ loadAndValidateYamlMock.mockResolvedValueOnce(makeValidGatesResult()).mockResolvedValueOnce({
206
+ parsed: {},
207
+ validation: { valid: false, errors: ['agents error'] },
208
+ });
209
+
210
+ loadComposedPolicyMock.mockResolvedValue(makeValidPolicy());
211
+
212
+ const service = new ConfigurationService(
213
+ '/repo',
214
+ makePathLayout(),
215
+ makeSchemaRegistry(),
216
+ makeConfigOverrides(),
217
+ );
218
+ await expect(service.loadAll()).rejects.toThrow('invalid_agents_yaml');
219
+ });
220
+
221
+ it('GIVEN_agents_timeout_relationship_invalid_WHEN_loadAll_THEN_throws', async () => {
222
+ pathExistsMock
223
+ .mockResolvedValueOnce(true) // agentsExists
224
+ .mockResolvedValueOnce(false); // adaptersExists
225
+
226
+ loadAndValidateYamlMock.mockResolvedValueOnce(makeValidGatesResult()).mockResolvedValueOnce({
227
+ parsed: {
228
+ version: 1,
229
+ roles: {},
230
+ runtime: {
231
+ worker_response_timeout_ms: 5000,
232
+ worker_spawn_timeout_ms: 10000, // spawn >= response → invalid
233
+ },
234
+ },
235
+ validation: { valid: true, errors: [] },
236
+ });
237
+
238
+ loadComposedPolicyMock.mockResolvedValue(makeValidPolicy());
239
+
240
+ const service = new ConfigurationService(
241
+ '/repo',
242
+ makePathLayout(),
243
+ makeSchemaRegistry(),
244
+ makeConfigOverrides(),
245
+ );
246
+ await expect(service.loadAll()).rejects.toThrow('invalid_agents_yaml');
247
+ });
248
+
249
+ it('GIVEN_agents_idle_timeout_exceeds_response_timeout_WHEN_loadAll_THEN_throws', async () => {
250
+ pathExistsMock.mockResolvedValueOnce(true).mockResolvedValueOnce(false);
251
+
252
+ loadAndValidateYamlMock.mockResolvedValueOnce(makeValidGatesResult()).mockResolvedValueOnce({
253
+ parsed: {
254
+ version: 1,
255
+ roles: {},
256
+ runtime: {
257
+ worker_response_timeout_ms: 5000,
258
+ worker_spawn_timeout_ms: 1000,
259
+ worker_idle_timeout_ms: 6000, // idle > response → invalid
260
+ },
261
+ },
262
+ validation: { valid: true, errors: [] },
263
+ });
264
+
265
+ loadComposedPolicyMock.mockResolvedValue(makeValidPolicy());
266
+
267
+ const service = new ConfigurationService(
268
+ '/repo',
269
+ makePathLayout(),
270
+ makeSchemaRegistry(),
271
+ makeConfigOverrides(),
272
+ );
273
+ await expect(service.loadAll()).rejects.toThrow('invalid_agents_yaml');
274
+ });
275
+
276
+ it('GIVEN_adapters_file_not_exists_WHEN_loadAll_THEN_skips_adapters', async () => {
277
+ pathExistsMock.mockResolvedValue(false); // agents not found, adapters not found
278
+
279
+ loadAndValidateYamlMock.mockResolvedValue(makeValidGatesResult());
280
+ loadComposedPolicyMock.mockResolvedValue(makeValidPolicy());
281
+
282
+ const service = new ConfigurationService(
283
+ '/repo',
284
+ makePathLayout(),
285
+ makeSchemaRegistry(),
286
+ makeConfigOverrides(),
287
+ );
288
+ const result = await service.loadAll();
289
+ expect(result.adaptersConfig).toEqual({});
290
+ expect(adapterRegistryResolveMock).not.toHaveBeenCalled();
291
+ });
292
+
293
+ it('GIVEN_adapters_file_exists_and_valid_no_slots_WHEN_loadAll_THEN_loads_adapters', async () => {
294
+ pathExistsMock
295
+ .mockResolvedValueOnce(false) // agentsExists
296
+ .mockResolvedValueOnce(true); // adaptersExists
297
+
298
+ loadAndValidateYamlMock
299
+ .mockResolvedValueOnce(makeValidGatesResult())
300
+ .mockResolvedValueOnce(makeValidAdaptersResult({}));
301
+
302
+ loadComposedPolicyMock.mockResolvedValue(makeValidPolicy());
303
+
304
+ const service = new ConfigurationService(
305
+ '/repo',
306
+ makePathLayout(),
307
+ makeSchemaRegistry(),
308
+ makeConfigOverrides(),
309
+ );
310
+ const result = await service.loadAll();
311
+ expect(result.adaptersConfig).toEqual({});
312
+ expect(adapterRegistryResolveMock).not.toHaveBeenCalled();
313
+ });
314
+
315
+ it('GIVEN_adapters_file_invalid_WHEN_loadAll_THEN_throws_invalid_adapters_yaml', async () => {
316
+ pathExistsMock.mockResolvedValueOnce(false).mockResolvedValueOnce(true);
317
+
318
+ loadAndValidateYamlMock.mockResolvedValueOnce(makeValidGatesResult()).mockResolvedValueOnce({
319
+ parsed: {},
320
+ validation: { valid: false, errors: ['adapters error'] },
321
+ });
322
+
323
+ loadComposedPolicyMock.mockResolvedValue(makeValidPolicy());
324
+
325
+ const service = new ConfigurationService(
326
+ '/repo',
327
+ makePathLayout(),
328
+ makeSchemaRegistry(),
329
+ makeConfigOverrides(),
330
+ );
331
+ await expect(service.loadAll()).rejects.toThrow('invalid_adapters_yaml');
332
+ });
333
+
334
+ it('GIVEN_notification_channel_not_registered_WHEN_loadAll_THEN_throws_adapter_not_found', async () => {
335
+ pathExistsMock.mockResolvedValueOnce(false).mockResolvedValueOnce(true);
336
+
337
+ loadAndValidateYamlMock
338
+ .mockResolvedValueOnce(makeValidGatesResult())
339
+ .mockResolvedValueOnce(makeValidAdaptersResult({ 'notification-channel': 'slack' }));
340
+
341
+ loadComposedPolicyMock.mockResolvedValue(makeValidPolicy());
342
+ adapterRegistryResolveMock.mockImplementation(() => {
343
+ throw new Error('not found');
344
+ });
345
+
346
+ const service = new ConfigurationService(
347
+ '/repo',
348
+ makePathLayout(),
349
+ makeSchemaRegistry(),
350
+ makeConfigOverrides(),
351
+ );
352
+ await expect(service.loadAll()).rejects.toThrow('adapter_not_found:notification-channel:slack');
353
+ });
354
+
355
+ it('GIVEN_activity_detector_not_registered_WHEN_loadAll_THEN_throws_adapter_not_found', async () => {
356
+ pathExistsMock.mockResolvedValueOnce(false).mockResolvedValueOnce(true);
357
+
358
+ loadAndValidateYamlMock
359
+ .mockResolvedValueOnce(makeValidGatesResult())
360
+ .mockResolvedValueOnce(makeValidAdaptersResult({ 'activity-detector': 'custom-detector' }));
361
+
362
+ loadComposedPolicyMock.mockResolvedValue(makeValidPolicy());
363
+ adapterRegistryResolveMock.mockImplementation(() => {
364
+ throw new Error('not found');
365
+ });
366
+
367
+ const service = new ConfigurationService(
368
+ '/repo',
369
+ makePathLayout(),
370
+ makeSchemaRegistry(),
371
+ makeConfigOverrides(),
372
+ );
373
+ await expect(service.loadAll()).rejects.toThrow(
374
+ 'adapter_not_found:activity-detector:custom-detector',
375
+ );
376
+ });
377
+
378
+ it('GIVEN_scm_provider_not_registered_WHEN_loadAll_THEN_throws_adapter_not_found', async () => {
379
+ pathExistsMock.mockResolvedValueOnce(false).mockResolvedValueOnce(true);
380
+
381
+ loadAndValidateYamlMock
382
+ .mockResolvedValueOnce(makeValidGatesResult())
383
+ .mockResolvedValueOnce(makeValidAdaptersResult({ 'scm-provider': 'github' }));
384
+
385
+ loadComposedPolicyMock.mockResolvedValue(makeValidPolicy());
386
+ adapterRegistryResolveMock.mockImplementation(() => {
387
+ throw new Error('not found');
388
+ });
389
+
390
+ const service = new ConfigurationService(
391
+ '/repo',
392
+ makePathLayout(),
393
+ makeSchemaRegistry(),
394
+ makeConfigOverrides(),
395
+ );
396
+ await expect(service.loadAll()).rejects.toThrow('adapter_not_found:scm-provider:github');
397
+ });
398
+
399
+ it('GIVEN_all_valid_config_WHEN_loadAll_THEN_returns_loaded_configuration', async () => {
400
+ pathExistsMock.mockResolvedValue(false); // agents and adapters not found
401
+
402
+ loadAndValidateYamlMock.mockResolvedValue(makeValidGatesResult());
403
+ loadComposedPolicyMock.mockResolvedValue(makeValidPolicy());
404
+ toolRegistryLoadMock.mockResolvedValue({ tools: [] });
405
+
406
+ const service = new ConfigurationService(
407
+ '/repo',
408
+ makePathLayout(),
409
+ makeSchemaRegistry(),
410
+ makeConfigOverrides(),
411
+ );
412
+ const result = await service.loadAll();
413
+ expect(result.gatesConfig).toBeDefined();
414
+ expect(result.policy).toBeDefined();
415
+ expect(result.agentsConfig).toBeDefined();
416
+ expect(result.toolRegistry).toEqual({ tools: [] });
417
+ });
418
+
419
+ it('GIVEN_config_overrides_absent_primary_path_exists_WHEN_loadAll_THEN_uses_primary_path', async () => {
420
+ // No overrides → resolveDefaultConfigPath is used, primary path exists
421
+ pathExistsMock.mockResolvedValue(true);
422
+
423
+ loadAndValidateYamlMock.mockResolvedValue(makeValidGatesResult());
424
+ loadComposedPolicyMock.mockResolvedValue({
425
+ mergedPolicy: {
426
+ implementation: { workspace: 'nx' },
427
+ testing: { framework: 'vitest' },
428
+ },
429
+ });
430
+
431
+ const service = new ConfigurationService(
432
+ '/repo',
433
+ makePathLayout(),
434
+ makeSchemaRegistry(),
435
+ // no config overrides → uses resolveDefaultConfigPath
436
+ {},
437
+ );
438
+ // Should not throw — just verifying it takes the primary path branch
439
+ // We don't care about the full result since the agents/adapters path mocks will cause issues
440
+ // Just check the gates path was resolved
441
+ try {
442
+ await service.loadAll();
443
+ } catch {
444
+ // may throw due to agents validation with pathExists always returning true
445
+ }
446
+ expect(loadAndValidateYamlMock).toHaveBeenCalled();
447
+ });
448
+
449
+ it('GIVEN_config_overrides_absent_primary_not_exists_legacy_exists_WHEN_loadAll_THEN_uses_legacy_path', async () => {
450
+ // Primary doesn't exist, legacy does — for gates only; everything else defaults to false
451
+ pathExistsMock
452
+ .mockResolvedValueOnce(false) // gates primary not found
453
+ .mockResolvedValueOnce(true); // gates legacy found
454
+
455
+ loadAndValidateYamlMock.mockResolvedValue(makeValidGatesResult());
456
+ loadComposedPolicyMock.mockResolvedValue(makeValidPolicy());
457
+
458
+ const layout = makePathLayout();
459
+ const service = new ConfigurationService('/repo', layout, makeSchemaRegistry(), {});
460
+ await service.loadAll();
461
+
462
+ // Verify the legacy path was used for gates (legacy path contains 'agentic/orchestrator')
463
+ const callArg = loadAndValidateYamlMock.mock.calls[0][2] as string;
464
+ expect(callArg).toContain('agentic/orchestrator');
465
+ });
466
+ });