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
@@ -11,12 +11,33 @@ const readDashboardStatusMock = vi.hoisted(() =>
11
11
  })),
12
12
  );
13
13
  const readFeatureStateMock = vi.hoisted(() => vi.fn(async () => null));
14
+ const readFeatureDiffMock = vi.hoisted(() => vi.fn(async () => ''));
15
+ const readFeatureCheckpointsMock = vi.hoisted(() =>
16
+ vi.fn(async () => ({
17
+ execution_mode: 'interactive',
18
+ checkpoints: [],
19
+ })),
20
+ );
21
+ const readFeatureCheckpointDiffMock = vi.hoisted(() => vi.fn(async () => null));
22
+ const compareFeatureCheckpointsMock = vi.hoisted(() => vi.fn(async () => null));
14
23
  const readFeatureLogMock = vi.hoisted(() =>
15
24
  vi.fn(async () => ({
16
25
  entries: [{ timestamp: '2026-03-05T00:00:00Z', message: 'log message', type: 'log' }],
17
26
  total: 1,
18
27
  })),
19
28
  );
29
+ const readFeaturesIndexMock = vi.hoisted(() =>
30
+ vi.fn(async () => ({
31
+ active: ['feature_checkout'],
32
+ blocked: [],
33
+ merged: [],
34
+ blocked_queue: [],
35
+ runtime_sessions: { run_id: 'run:1' },
36
+ })),
37
+ );
38
+ const listRawLogFilesMock = vi.hoisted(() => vi.fn(async () => []));
39
+ const readRawLogFileMock = vi.hoisted(() => vi.fn(async () => null));
40
+ const readWorkerTimelineEntriesMock = vi.hoisted(() => vi.fn(async () => []));
20
41
  const readFeatureCostMock = vi.hoisted(() =>
21
42
  vi.fn(async () => ({
22
43
  feature_id: 'feature_checkout',
@@ -49,6 +70,31 @@ const readFeatureReviewBriefMock = vi.hoisted(() =>
49
70
  generated_at: '2026-03-05T00:00:00Z',
50
71
  })),
51
72
  );
73
+ const readInteractivePerformanceMetricsMock = vi.hoisted(() =>
74
+ vi.fn(async () => ({
75
+ updated_at: '2026-03-06T00:00:00.000Z',
76
+ checkpoint_count: 0,
77
+ valid_count: 0,
78
+ invalid_count: 0,
79
+ skipped_count: 0,
80
+ avg_files_changed: null,
81
+ avg_checkpoint_latency_ms: null,
82
+ p95_checkpoint_latency_ms: null,
83
+ avg_validation_latency_ms: null,
84
+ p95_validation_latency_ms: null,
85
+ avg_diff_capture_latency_ms: null,
86
+ latest: null,
87
+ execution_modes: {
88
+ interactive_iterations: 0,
89
+ deterministic_iterations: 0,
90
+ interactive_avg_elapsed_ms: null,
91
+ deterministic_avg_elapsed_ms: null,
92
+ interactive_avg_request_count: null,
93
+ deterministic_avg_request_count: null,
94
+ context_request_reduction_ratio: null,
95
+ },
96
+ })),
97
+ );
52
98
  const getAopRootMock = vi.hoisted(() => vi.fn(() => process.cwd()));
53
99
  const approveFeatureReviewMock = vi.hoisted(() =>
54
100
  vi.fn(async () => ({ ok: true, data: { merged: true } })),
@@ -74,10 +120,19 @@ vi.mock('../../../packages/web-dashboard/src/lib/aop-client.js', () => ({
74
120
  resolveProjectRoot: resolveProjectRootMock,
75
121
  readDashboardStatus: readDashboardStatusMock,
76
122
  readFeatureState: readFeatureStateMock,
123
+ readFeatureDiff: readFeatureDiffMock,
124
+ readFeatureCheckpoints: readFeatureCheckpointsMock,
125
+ readFeatureCheckpointDiff: readFeatureCheckpointDiffMock,
126
+ compareFeatureCheckpoints: compareFeatureCheckpointsMock,
77
127
  readFeatureLog: readFeatureLogMock,
128
+ readFeaturesIndex: readFeaturesIndexMock,
78
129
  readFeatureCost: readFeatureCostMock,
79
130
  readFeatureQaTestIndex: readFeatureQaTestIndexMock,
80
131
  readFeatureReviewBrief: readFeatureReviewBriefMock,
132
+ readInteractivePerformanceMetrics: readInteractivePerformanceMetricsMock,
133
+ listRawLogFiles: listRawLogFilesMock,
134
+ readRawLogFile: readRawLogFileMock,
135
+ readWorkerTimelineEntries: readWorkerTimelineEntriesMock,
81
136
  getAopRoot: getAopRootMock,
82
137
  }));
83
138
 
@@ -109,9 +164,16 @@ import { POST as checkoutPost } from '../../../packages/web-dashboard/src/app/ap
109
164
  import { GET as collisionsGet } from '../../../packages/web-dashboard/src/app/api/collisions/route.js';
110
165
  import { GET as costGet } from '../../../packages/web-dashboard/src/app/api/features/[id]/cost/route.js';
111
166
  import { GET as logGet } from '../../../packages/web-dashboard/src/app/api/features/[id]/log/route.js';
167
+ import { GET as fileDiffGet } from '../../../packages/web-dashboard/src/app/api/features/[id]/file-diff/route.js';
168
+ import { GET as checkpointDiffGet } from '../../../packages/web-dashboard/src/app/api/features/[id]/checkpoints/[checkpointId]/diff/route.js';
169
+ import { GET as checkpointCompareGet } from '../../../packages/web-dashboard/src/app/api/features/[id]/checkpoints/compare/route.js';
170
+ import { GET as checkpointStreamGet } from '../../../packages/web-dashboard/src/app/api/features/[id]/checkpoints/stream/route.js';
171
+ import { GET as rawLogsGet } from '../../../packages/web-dashboard/src/app/api/features/[id]/raw-logs/route.js';
172
+ import { GET as rawLogFileGet } from '../../../packages/web-dashboard/src/app/api/features/[id]/raw-logs/[filename]/route.js';
112
173
  import { GET as reviewBriefGet } from '../../../packages/web-dashboard/src/app/api/features/[id]/review-brief/route.js';
113
174
  import { GET as statusGet } from '../../../packages/web-dashboard/src/app/api/status/route.js';
114
175
  import { GET as testIndexGet } from '../../../packages/web-dashboard/src/app/api/features/[id]/test-index/route.js';
176
+ import { GET as timelineGet } from '../../../packages/web-dashboard/src/app/api/features/[id]/timeline/route.js';
115
177
  import { GET as projectsGet } from '../../../packages/web-dashboard/src/app/api/projects/route.js';
116
178
  import {
117
179
  GET as runGet,
@@ -130,10 +192,19 @@ describe('dashboard api integration', () => {
130
192
  resolveProjectRootMock.mockReset();
131
193
  readDashboardStatusMock.mockReset();
132
194
  readFeatureStateMock.mockReset();
195
+ readFeatureDiffMock.mockReset();
196
+ readFeatureCheckpointsMock.mockReset();
197
+ readFeatureCheckpointDiffMock.mockReset();
198
+ compareFeatureCheckpointsMock.mockReset();
133
199
  readFeatureLogMock.mockReset();
200
+ readFeaturesIndexMock.mockReset();
134
201
  readFeatureCostMock.mockReset();
135
202
  readFeatureQaTestIndexMock.mockReset();
136
203
  readFeatureReviewBriefMock.mockReset();
204
+ readInteractivePerformanceMetricsMock.mockReset();
205
+ listRawLogFilesMock.mockReset();
206
+ readRawLogFileMock.mockReset();
207
+ readWorkerTimelineEntriesMock.mockReset();
137
208
  getAopRootMock.mockReset();
138
209
  approveFeatureReviewMock.mockReset();
139
210
  denyFeatureReviewMock.mockReset();
@@ -145,6 +216,21 @@ describe('dashboard api integration', () => {
145
216
 
146
217
  resolveProjectRootMock.mockResolvedValue(repoRoot);
147
218
  getAopRootMock.mockReturnValue(repoRoot);
219
+ readFeatureStateMock.mockResolvedValue({
220
+ id: 'feature_checkout',
221
+ feature_id: 'feature_checkout',
222
+ status: 'ready_to_merge',
223
+ phase: 'ready_to_merge',
224
+ branch: 'feature/feature_checkout',
225
+ worktree_path: path.join(repoRoot, '.worktrees', 'feature_checkout'),
226
+ cluster: {
227
+ orchestrator_session_id: 'orch-session',
228
+ planner_session_id: 'planner-session',
229
+ builder_session_id: 'builder-session',
230
+ qa_session_id: 'qa-session',
231
+ },
232
+ pr: null,
233
+ });
148
234
  readDashboardStatusMock.mockResolvedValue({
149
235
  index: { active: ['feature_checkout'], blocked: [], merged: [], blocked_queue: [] },
150
236
  features: [
@@ -163,6 +249,24 @@ describe('dashboard api integration', () => {
163
249
  entries: [{ timestamp: '2026-03-05T00:00:00Z', message: 'log message', type: 'log' }],
164
250
  total: 1,
165
251
  });
252
+ readFeatureDiffMock.mockResolvedValue(
253
+ [
254
+ 'diff --git a/src/a.ts b/src/a.ts',
255
+ 'index 111..222 100644',
256
+ '--- a/src/a.ts',
257
+ '+++ b/src/a.ts',
258
+ '@@ -1 +1 @@',
259
+ '-const oldValue = 1;',
260
+ '+const newValue = 2;',
261
+ ].join('\n'),
262
+ );
263
+ readFeaturesIndexMock.mockResolvedValue({
264
+ active: ['feature_checkout'],
265
+ blocked: [],
266
+ merged: [],
267
+ blocked_queue: [],
268
+ runtime_sessions: { run_id: 'run:1' },
269
+ });
166
270
  readFeatureCostMock.mockResolvedValue({
167
271
  feature_id: 'feature_checkout',
168
272
  tokens_used: 120,
@@ -183,6 +287,38 @@ describe('dashboard api integration', () => {
183
287
  evidence_refs: [],
184
288
  generated_at: '2026-03-05T00:00:00Z',
185
289
  });
290
+ listRawLogFilesMock.mockResolvedValue([]);
291
+ readRawLogFileMock.mockResolvedValue(null);
292
+ readWorkerTimelineEntriesMock.mockResolvedValue([]);
293
+ readFeatureCheckpointsMock.mockResolvedValue({
294
+ execution_mode: 'interactive',
295
+ checkpoints: [],
296
+ });
297
+ readFeatureCheckpointDiffMock.mockResolvedValue(null);
298
+ compareFeatureCheckpointsMock.mockResolvedValue(null);
299
+ readInteractivePerformanceMetricsMock.mockResolvedValue({
300
+ updated_at: '2026-03-06T00:00:00.000Z',
301
+ checkpoint_count: 0,
302
+ valid_count: 0,
303
+ invalid_count: 0,
304
+ skipped_count: 0,
305
+ avg_files_changed: null,
306
+ avg_checkpoint_latency_ms: null,
307
+ p95_checkpoint_latency_ms: null,
308
+ avg_validation_latency_ms: null,
309
+ p95_validation_latency_ms: null,
310
+ avg_diff_capture_latency_ms: null,
311
+ latest: null,
312
+ execution_modes: {
313
+ interactive_iterations: 0,
314
+ deterministic_iterations: 0,
315
+ interactive_avg_elapsed_ms: null,
316
+ deterministic_avg_elapsed_ms: null,
317
+ interactive_avg_request_count: null,
318
+ deterministic_avg_request_count: null,
319
+ context_request_reduction_ratio: null,
320
+ },
321
+ });
186
322
  callOrchestratorToolMock.mockResolvedValue({
187
323
  ok: true,
188
324
  data: { feature_id: 'feature_checkout' } as Record<string, unknown>,
@@ -290,6 +426,118 @@ describe('dashboard api integration', () => {
290
426
  );
291
427
  });
292
428
 
429
+ it('GIVEN_feature_send_message_for_terminal_feature_WHEN_posted_THEN_returns_session_inactive', async () => {
430
+ readFeatureStateMock.mockResolvedValue({
431
+ id: 'feature_checkout',
432
+ feature_id: 'feature_checkout',
433
+ status: 'merged',
434
+ phase: 'merged',
435
+ cluster: {
436
+ orchestrator_session_id: 'orch-session',
437
+ planner_session_id: 'planner-session',
438
+ builder_session_id: 'builder-session',
439
+ qa_session_id: 'qa-session',
440
+ },
441
+ });
442
+
443
+ const response = await actionsPost(
444
+ new Request('http://localhost/api/actions?project=alpha', {
445
+ method: 'POST',
446
+ body: JSON.stringify({
447
+ action: 'feature.send_message',
448
+ feature_id: 'feature_checkout',
449
+ message: 'Need clarification',
450
+ }),
451
+ }),
452
+ );
453
+ const body = (await response.json()) as { ok: boolean; error: { code: string } };
454
+
455
+ expect(response.status).toBe(409);
456
+ expect(body.ok).toBe(false);
457
+ expect(body.error.code).toBe('session_inactive');
458
+ expect(sendFeatureMessageMock).not.toHaveBeenCalled();
459
+ });
460
+
461
+ it('GIVEN_feature_send_message_without_active_runtime_WHEN_posted_THEN_returns_session_inactive', async () => {
462
+ readFeatureStateMock.mockResolvedValue({
463
+ id: 'feature_checkout',
464
+ feature_id: 'feature_checkout',
465
+ status: 'qa',
466
+ phase: 'qa',
467
+ cluster: {
468
+ orchestrator_session_id: 'orch-session',
469
+ planner_session_id: 'planner-session',
470
+ builder_session_id: 'builder-session',
471
+ qa_session_id: 'qa-session',
472
+ },
473
+ });
474
+ readFeaturesIndexMock.mockResolvedValue({
475
+ active: ['feature_checkout'],
476
+ blocked: [],
477
+ merged: [],
478
+ blocked_queue: [],
479
+ runtime_sessions: { run_id: 'unknown' },
480
+ });
481
+
482
+ const response = await actionsPost(
483
+ new Request('http://localhost/api/actions?project=alpha', {
484
+ method: 'POST',
485
+ body: JSON.stringify({
486
+ action: 'feature.send_message',
487
+ feature_id: 'feature_checkout',
488
+ message: 'Need clarification',
489
+ }),
490
+ }),
491
+ );
492
+ const body = (await response.json()) as { ok: boolean; error: { code: string } };
493
+
494
+ expect(response.status).toBe(409);
495
+ expect(body.ok).toBe(false);
496
+ expect(body.error.code).toBe('session_inactive');
497
+ expect(sendFeatureMessageMock).not.toHaveBeenCalled();
498
+ });
499
+
500
+ it('GIVEN_feature_send_message_called_twice_quickly_WHEN_posted_THEN_second_call_is_rate_limited', async () => {
501
+ readFeatureStateMock.mockResolvedValue({
502
+ id: 'feature_rate',
503
+ feature_id: 'feature_rate',
504
+ status: 'qa',
505
+ phase: 'qa',
506
+ cluster: {
507
+ orchestrator_session_id: 'orch-session',
508
+ planner_session_id: 'planner-session',
509
+ builder_session_id: 'builder-session',
510
+ qa_session_id: 'qa-session',
511
+ },
512
+ });
513
+ const first = await actionsPost(
514
+ new Request('http://localhost/api/actions?project=alpha', {
515
+ method: 'POST',
516
+ body: JSON.stringify({
517
+ action: 'feature.send_message',
518
+ feature_id: 'feature_rate',
519
+ message: 'Need clarification',
520
+ }),
521
+ }),
522
+ );
523
+ const second = await actionsPost(
524
+ new Request('http://localhost/api/actions?project=alpha', {
525
+ method: 'POST',
526
+ body: JSON.stringify({
527
+ action: 'feature.send_message',
528
+ feature_id: 'feature_rate',
529
+ message: 'Second message',
530
+ }),
531
+ }),
532
+ );
533
+ const body = (await second.json()) as { ok: boolean; error: { code: string } };
534
+
535
+ expect(first.status).toBe(200);
536
+ expect(second.status).toBe(429);
537
+ expect(body.ok).toBe(false);
538
+ expect(body.error.code).toBe('invalid_input');
539
+ });
540
+
293
541
  it('GIVEN_feature_retry_WHEN_posted_THEN_routes_to_retry_tool', async () => {
294
542
  const response = await actionsPost(
295
543
  new Request('http://localhost/api/actions', {
@@ -482,6 +730,295 @@ describe('dashboard api integration', () => {
482
730
  expect(body.meta?.source).toBe('artifact');
483
731
  });
484
732
 
733
+ it('GIVEN_file_diff_route_WHEN_path_is_in_scope_THEN_returns_monaco_payload_envelope', async () => {
734
+ execFileMock.mockResolvedValue({
735
+ stdout: 'const value = 1;\n',
736
+ stderr: '',
737
+ });
738
+
739
+ const response = await fileDiffGet(
740
+ new Request('http://localhost/api/features/feature_checkout/file-diff?path=src%2Fa.ts'),
741
+ { params: Promise.resolve({ id: 'feature_checkout' }) },
742
+ );
743
+ const body = (await response.json()) as {
744
+ ok: boolean;
745
+ data?: { path: string; language: string; change_type: string };
746
+ };
747
+
748
+ expect(response.status).toBe(200);
749
+ expect(body.ok).toBe(true);
750
+ expect(body.data).toMatchObject({
751
+ path: 'src/a.ts',
752
+ language: 'typescript',
753
+ change_type: 'modified',
754
+ });
755
+ });
756
+
757
+ it('GIVEN_raw_logs_route_with_policy_disabled_WHEN_requested_THEN_returns_disabled_state', async () => {
758
+ const response = await rawLogsGet(
759
+ new Request('http://localhost/api/features/feature_checkout/raw-logs'),
760
+ { params: Promise.resolve({ id: 'feature_checkout' }) },
761
+ );
762
+ const body = (await response.json()) as {
763
+ ok: boolean;
764
+ data?: { enabled: boolean; files: unknown[] };
765
+ };
766
+
767
+ expect(response.status).toBe(200);
768
+ expect(body.ok).toBe(true);
769
+ expect(body.data).toEqual({ enabled: false, files: [] });
770
+ });
771
+
772
+ it('GIVEN_raw_logs_route_with_policy_enabled_WHEN_requested_THEN_returns_filtered_files', async () => {
773
+ const policyPath = path.join(repoRoot, 'config', 'agentic', 'orchestrator');
774
+ await fs.mkdir(policyPath, { recursive: true });
775
+ await fs.writeFile(
776
+ path.join(policyPath, 'policy.yaml'),
777
+ [
778
+ 'observability:',
779
+ ' raw_agent_logs_enabled: true',
780
+ ' raw_agent_logs_retention_days: 60',
781
+ ].join('\n'),
782
+ 'utf8',
783
+ );
784
+ listRawLogFilesMock.mockResolvedValue([
785
+ {
786
+ filename: 'builder-1760000000000.txt',
787
+ role: 'builder',
788
+ timestamp: '2026-03-05T00:00:00.000Z',
789
+ size_bytes: 128,
790
+ },
791
+ ]);
792
+ readRawLogFileMock.mockResolvedValue('raw output');
793
+
794
+ const listResponse = await rawLogsGet(
795
+ new Request('http://localhost/api/features/feature_checkout/raw-logs?role=builder'),
796
+ { params: Promise.resolve({ id: 'feature_checkout' }) },
797
+ );
798
+ const listBody = (await listResponse.json()) as {
799
+ ok: boolean;
800
+ data?: { enabled: boolean; files: Array<{ filename: string }> };
801
+ };
802
+
803
+ const fileResponse = await rawLogFileGet(
804
+ new Request(
805
+ 'http://localhost/api/features/feature_checkout/raw-logs/builder-1760000000000.txt',
806
+ ),
807
+ {
808
+ params: Promise.resolve({ id: 'feature_checkout', filename: 'builder-1760000000000.txt' }),
809
+ },
810
+ );
811
+ const fileBody = (await fileResponse.json()) as {
812
+ ok: boolean;
813
+ data?: { filename: string; content: string };
814
+ };
815
+
816
+ expect(listResponse.status).toBe(200);
817
+ expect(listBody.ok).toBe(true);
818
+ expect(listBody.data?.enabled).toBe(true);
819
+ expect(listBody.data?.files[0].filename).toBe('builder-1760000000000.txt');
820
+
821
+ expect(fileResponse.status).toBe(200);
822
+ expect(fileBody.ok).toBe(true);
823
+ expect(fileBody.data?.content).toBe('raw output');
824
+ });
825
+
826
+ it('GIVEN_raw_log_filename_with_traversal_WHEN_requested_THEN_returns_invalid_input', async () => {
827
+ const policyPath = path.join(repoRoot, 'config', 'agentic', 'orchestrator');
828
+ await fs.mkdir(policyPath, { recursive: true });
829
+ await fs.writeFile(
830
+ path.join(policyPath, 'policy.yaml'),
831
+ ['observability:', ' raw_agent_logs_enabled: true'].join('\n'),
832
+ 'utf8',
833
+ );
834
+
835
+ const response = await rawLogFileGet(
836
+ new Request('http://localhost/api/features/feature_checkout/raw-logs/..%2Fsecrets.txt'),
837
+ { params: Promise.resolve({ id: 'feature_checkout', filename: '../secrets.txt' }) },
838
+ );
839
+ const body = (await response.json()) as { ok: boolean; error: { code: string } };
840
+
841
+ expect(response.status).toBe(400);
842
+ expect(body.ok).toBe(false);
843
+ expect(body.error.code).toBe('invalid_input');
844
+ expect(readRawLogFileMock).not.toHaveBeenCalled();
845
+ });
846
+
847
+ it('GIVEN_raw_log_panel_failure_WHEN_timeline_requested_THEN_other_review_panels_remain_available', async () => {
848
+ const policyPath = path.join(repoRoot, 'config', 'agentic', 'orchestrator');
849
+ await fs.mkdir(policyPath, { recursive: true });
850
+ await fs.writeFile(
851
+ path.join(policyPath, 'policy.yaml'),
852
+ ['observability:', ' raw_agent_logs_enabled: true'].join('\n'),
853
+ 'utf8',
854
+ );
855
+ readRawLogFileMock.mockResolvedValue(null);
856
+ readWorkerTimelineEntriesMock.mockResolvedValue([
857
+ {
858
+ ts: '2026-03-05T00:00:00.000Z',
859
+ role: 'builder',
860
+ output_types: ['PATCH'],
861
+ patch_count: 1,
862
+ plan_submission_count: 0,
863
+ request_count: 0,
864
+ note_count: 1,
865
+ valid: true,
866
+ error_code: null,
867
+ provider: 'codex',
868
+ model: 'gpt-5',
869
+ },
870
+ ]);
871
+
872
+ const rawLogResponse = await rawLogFileGet(
873
+ new Request(
874
+ 'http://localhost/api/features/feature_checkout/raw-logs/builder-1760000000000.txt',
875
+ ),
876
+ {
877
+ params: Promise.resolve({ id: 'feature_checkout', filename: 'builder-1760000000000.txt' }),
878
+ },
879
+ );
880
+ const rawLogBody = (await rawLogResponse.json()) as { ok: boolean; error: { code: string } };
881
+
882
+ const timelineResponse = await timelineGet(
883
+ new Request('http://localhost/api/features/feature_checkout/timeline?role=builder'),
884
+ { params: Promise.resolve({ id: 'feature_checkout' }) },
885
+ );
886
+ const timelineBody = (await timelineResponse.json()) as {
887
+ ok: boolean;
888
+ data?: { entries: unknown[] };
889
+ };
890
+
891
+ expect(rawLogResponse.status).toBe(404);
892
+ expect(rawLogBody.ok).toBe(false);
893
+ expect(rawLogBody.error.code).toBe('artifact_missing');
894
+
895
+ expect(timelineResponse.status).toBe(200);
896
+ expect(timelineBody.ok).toBe(true);
897
+ expect(timelineBody.data?.entries).toHaveLength(1);
898
+ });
899
+
900
+ it('GIVEN_timeline_route_WHEN_requested_with_filters_THEN_returns_entries_and_summary', async () => {
901
+ readWorkerTimelineEntriesMock.mockResolvedValue([
902
+ {
903
+ ts: '2026-03-05T00:00:00.000Z',
904
+ role: 'builder',
905
+ output_types: ['PATCH'],
906
+ patch_count: 1,
907
+ plan_submission_count: 0,
908
+ request_count: 0,
909
+ note_count: 1,
910
+ valid: true,
911
+ error_code: null,
912
+ provider: 'codex',
913
+ model: 'gpt-5',
914
+ },
915
+ ]);
916
+
917
+ const response = await timelineGet(
918
+ new Request(
919
+ 'http://localhost/api/features/feature_checkout/timeline?role=builder&valid=valid',
920
+ ),
921
+ { params: Promise.resolve({ id: 'feature_checkout' }) },
922
+ );
923
+ const body = (await response.json()) as {
924
+ ok: boolean;
925
+ data?: { entries: unknown[]; summary: { total: number; valid: number } };
926
+ };
927
+
928
+ expect(response.status).toBe(200);
929
+ expect(body.ok).toBe(true);
930
+ expect(body.data?.entries).toHaveLength(1);
931
+ expect(body.data?.summary.total).toBe(1);
932
+ expect(body.data?.summary.valid).toBe(1);
933
+ });
934
+
935
+ it('GIVEN_checkpoint_diff_route_WHEN_checkpoint_exists_THEN_returns_snapshot_diff_payload', async () => {
936
+ readFeatureCheckpointDiffMock.mockResolvedValue({
937
+ checkpoint: {
938
+ checkpoint_id: 'checkpoint-001',
939
+ timestamp: '2026-03-06T00:00:00.000Z',
940
+ files_changed: ['src/a.ts'],
941
+ validation_status: 'valid',
942
+ violations: [],
943
+ diff_snapshot: '.aop/features/feature_checkout/checkpoints/checkpoint-001.diff',
944
+ },
945
+ diff: 'diff --git a/src/a.ts b/src/a.ts\\n',
946
+ });
947
+
948
+ const response = await checkpointDiffGet(
949
+ new Request('http://localhost/api/features/feature_checkout/checkpoints/checkpoint-001/diff'),
950
+ { params: Promise.resolve({ id: 'feature_checkout', checkpointId: 'checkpoint-001' }) },
951
+ );
952
+ const body = (await response.json()) as {
953
+ ok: boolean;
954
+ data?: { checkpoint: { checkpoint_id: string }; diff: string };
955
+ };
956
+
957
+ expect(response.status).toBe(200);
958
+ expect(body.ok).toBe(true);
959
+ expect(body.data?.checkpoint.checkpoint_id).toBe('checkpoint-001');
960
+ expect(body.data?.diff).toContain('diff --git');
961
+ });
962
+
963
+ it('GIVEN_checkpoint_compare_route_without_required_params_WHEN_called_THEN_returns_invalid_input', async () => {
964
+ const response = await checkpointCompareGet(
965
+ new Request('http://localhost/api/features/feature_checkout/checkpoints/compare'),
966
+ { params: Promise.resolve({ id: 'feature_checkout' }) },
967
+ );
968
+ const body = (await response.json()) as { ok: boolean; error: { code: string } };
969
+
970
+ expect(response.status).toBe(400);
971
+ expect(body.ok).toBe(false);
972
+ expect(body.error.code).toBe('invalid_input');
973
+ });
974
+
975
+ it('GIVEN_checkpoint_compare_route_WHEN_checkpoints_exist_THEN_returns_comparison_payload', async () => {
976
+ compareFeatureCheckpointsMock.mockResolvedValue({
977
+ from_checkpoint_id: 'checkpoint-001',
978
+ to_checkpoint_id: 'checkpoint-002',
979
+ from_timestamp: '2026-03-06T00:00:00.000Z',
980
+ to_timestamp: '2026-03-06T00:01:00.000Z',
981
+ summary: { added: 1, removed: 0, changed: 1, unchanged: 0 },
982
+ files: [
983
+ { path: 'src/a.ts', status: 'changed' },
984
+ { path: 'src/b.ts', status: 'added' },
985
+ ],
986
+ from_diff: 'diff --git a/src/a.ts b/src/a.ts\\n',
987
+ to_diff: 'diff --git a/src/a.ts b/src/a.ts\\n',
988
+ });
989
+
990
+ const response = await checkpointCompareGet(
991
+ new Request(
992
+ 'http://localhost/api/features/feature_checkout/checkpoints/compare?from=checkpoint-001&to=checkpoint-002',
993
+ ),
994
+ { params: Promise.resolve({ id: 'feature_checkout' }) },
995
+ );
996
+ const body = (await response.json()) as {
997
+ ok: boolean;
998
+ data?: { summary: { changed: number; added: number }; files: Array<{ path: string }> };
999
+ };
1000
+
1001
+ expect(response.status).toBe(200);
1002
+ expect(body.ok).toBe(true);
1003
+ expect(body.data?.summary.changed).toBe(1);
1004
+ expect(body.data?.summary.added).toBe(1);
1005
+ expect(body.data?.files[0].path).toBe('src/a.ts');
1006
+ });
1007
+
1008
+ it('GIVEN_checkpoint_stream_route_WHEN_feature_missing_THEN_returns_feature_not_found', async () => {
1009
+ readFeatureStateMock.mockResolvedValueOnce(null);
1010
+
1011
+ const response = await checkpointStreamGet(
1012
+ new Request('http://localhost/api/features/feature_checkout/checkpoints/stream'),
1013
+ { params: Promise.resolve({ id: 'feature_checkout' }) },
1014
+ );
1015
+ const body = (await response.json()) as { ok: boolean; error: { code: string } };
1016
+
1017
+ expect(response.status).toBe(404);
1018
+ expect(body.ok).toBe(false);
1019
+ expect(body.error.code).toBe('feature_not_found');
1020
+ });
1021
+
485
1022
  it('GIVEN_multi_project_config_WHEN_projects_endpoint_called_THEN_returns_switchable_projects', async () => {
486
1023
  const projectAlpha = path.join(repoRoot, 'project-alpha');
487
1024
  const projectBeta = path.join(repoRoot, 'project-beta');