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
@@ -1,9 +1,15 @@
1
1
  import { TOOLS } from '../core/constants.js';
2
2
  import { ERROR_CODES } from '../core/error-codes.js';
3
- import type { WorkerProvider } from '../providers/providers.js';
3
+ import type { ExecutionMode, WorkerProvider, WorkerRunInput } from '../providers/providers.js';
4
4
  import type { RuntimeRole, SupervisorToolCaller } from './types.js';
5
5
  import type { ActivityMonitorService } from '../application/services/activity-monitor-service.js';
6
6
  import { appendWorkerRuntimeEvent } from './worker-event-journal.js';
7
+ import type {
8
+ CheckpointRecord,
9
+ CheckpointService,
10
+ InteractiveExecutionConfig,
11
+ } from '../application/services/checkpoint-service.js';
12
+ import type { WorktreeWatchdogService } from '../application/services/worktree-watchdog-service.js';
7
13
 
8
14
  type AnyRecord = Record<string, unknown>;
9
15
  const PLAN_TOP_LEVEL_KEYS = new Set([
@@ -125,6 +131,19 @@ interface WorkerDecisionLoopDependencies {
125
131
  activityMonitor?: ActivityMonitorService;
126
132
  repoRoot?: string;
127
133
  runId?: string | (() => string);
134
+ resolveExecutionMode?: (featureId: string, role: RuntimeRole) => Promise<ExecutionMode>;
135
+ resolveInteractiveConfig?: () => InteractiveExecutionConfig;
136
+ watchdog?: WorktreeWatchdogService;
137
+ checkpointService?: CheckpointService;
138
+ resolveWorktreePath?: (featureId: string) => string;
139
+ resolveRoleSessionId?: (role: RuntimeRole, featureId: string) => string | null;
140
+ }
141
+
142
+ interface InteractiveRunSummary {
143
+ workerOutput: Record<string, unknown>;
144
+ finalCheckpoint: CheckpointRecord;
145
+ changedFiles: string[];
146
+ checkpointInvalid: boolean;
128
147
  }
129
148
 
130
149
  export class WorkerDecisionLoop implements WorkerDecisionRunner {
@@ -133,6 +152,16 @@ export class WorkerDecisionLoop implements WorkerDecisionRunner {
133
152
  private readonly activityMonitor: ActivityMonitorService | undefined;
134
153
  private readonly repoRoot: string | null;
135
154
  private readonly runId: string | (() => string) | null;
155
+ private readonly resolveExecutionModeOverride:
156
+ | ((featureId: string, role: RuntimeRole) => Promise<ExecutionMode>)
157
+ | undefined;
158
+ private readonly resolveInteractiveConfigOverride: (() => InteractiveExecutionConfig) | undefined;
159
+ private readonly watchdog: WorktreeWatchdogService | undefined;
160
+ private readonly checkpointService: CheckpointService | undefined;
161
+ private readonly resolveWorktreePath: ((featureId: string) => string) | undefined;
162
+ private readonly resolveRoleSessionId:
163
+ | ((role: RuntimeRole, featureId: string) => string | null)
164
+ | undefined;
136
165
 
137
166
  constructor(dependencies: WorkerDecisionLoopDependencies) {
138
167
  this.provider = dependencies.provider;
@@ -140,6 +169,12 @@ export class WorkerDecisionLoop implements WorkerDecisionRunner {
140
169
  this.activityMonitor = dependencies.activityMonitor;
141
170
  this.repoRoot = dependencies.repoRoot ?? null;
142
171
  this.runId = dependencies.runId ?? null;
172
+ this.resolveExecutionModeOverride = dependencies.resolveExecutionMode;
173
+ this.resolveInteractiveConfigOverride = dependencies.resolveInteractiveConfig;
174
+ this.watchdog = dependencies.watchdog;
175
+ this.checkpointService = dependencies.checkpointService;
176
+ this.resolveWorktreePath = dependencies.resolveWorktreePath;
177
+ this.resolveRoleSessionId = dependencies.resolveRoleSessionId;
143
178
  }
144
179
 
145
180
  async execute(input: WorkerDecisionInput): Promise<WorkerDecisionResult> {
@@ -147,6 +182,27 @@ export class WorkerDecisionLoop implements WorkerDecisionRunner {
147
182
  if (typeof this.provider.runWorker !== 'function') {
148
183
  return result;
149
184
  }
185
+ const requestedExecutionMode = await this.resolveExecutionMode(input.featureId, input.role);
186
+ const interactiveEligible = this.isInteractiveEligible(requestedExecutionMode, input.role);
187
+ const executionMode: ExecutionMode =
188
+ requestedExecutionMode === 'interactive' && interactiveEligible
189
+ ? 'interactive'
190
+ : 'deterministic';
191
+ if (requestedExecutionMode === 'interactive' && !interactiveEligible) {
192
+ await this.appendWorkerEvent({
193
+ eventType: 'interactive_fallback',
194
+ featureId: input.featureId,
195
+ role: input.role,
196
+ outputTypes: [],
197
+ patchCount: 0,
198
+ planSubmissionCount: 0,
199
+ requestCount: 0,
200
+ noteCount: 0,
201
+ valid: true,
202
+ errorCode: null,
203
+ executionMode,
204
+ });
205
+ }
150
206
  await this.appendWorkerEvent({
151
207
  eventType: 'worker_started',
152
208
  featureId: input.featureId,
@@ -158,22 +214,20 @@ export class WorkerDecisionLoop implements WorkerDecisionRunner {
158
214
  noteCount: 0,
159
215
  valid: true,
160
216
  errorCode: null,
217
+ executionMode,
161
218
  });
162
219
 
163
220
  let workerOutput: Record<string, unknown>;
221
+ let interactiveSummary: InteractiveRunSummary | null = null;
164
222
  try {
165
- workerOutput = await this.provider.runWorker({
166
- role: input.role,
167
- feature_id: input.featureId,
168
- context_bundle: input.contextBundle,
169
- instructions: input.instructions,
170
- last_tool_results: input.lastToolResults ?? [],
171
- runtime_selection: {
172
- provider: this.provider.selection.provider,
173
- model: this.provider.selection.model,
174
- provider_config_ref: this.provider.selection.provider_config_ref,
175
- },
176
- });
223
+ if (interactiveEligible) {
224
+ interactiveSummary = await this.runInteractiveWorker(input);
225
+ workerOutput = interactiveSummary.workerOutput;
226
+ } else {
227
+ workerOutput = await this.provider.runWorker(
228
+ this.buildRunInput(input, executionMode, undefined),
229
+ );
230
+ }
177
231
  } catch (error) {
178
232
  const typed = error as { code?: string; details?: Record<string, unknown> };
179
233
  const errorCode =
@@ -216,6 +270,7 @@ export class WorkerDecisionLoop implements WorkerDecisionRunner {
216
270
  signal: typeof typed.details?.signal === 'string' ? typed.details.signal : null,
217
271
  exitCode:
218
272
  typeof typed.details?.exit_code === 'number' ? typed.details.exit_code : undefined,
273
+ executionMode,
219
274
  });
220
275
  throw error;
221
276
  }
@@ -246,7 +301,14 @@ export class WorkerDecisionLoop implements WorkerDecisionRunner {
246
301
  }
247
302
  if (outputType === 'PATCH') {
248
303
  patchCount += 1;
249
- await this.routePatch(input, output, result);
304
+ if (interactiveSummary) {
305
+ const hasPatchPayload = Boolean(asNonEmptyString(output.unified_diff ?? output.diff));
306
+ if (hasPatchPayload) {
307
+ result.patchApplied = true;
308
+ }
309
+ } else {
310
+ await this.routePatch(input, output, result);
311
+ }
250
312
  continue;
251
313
  }
252
314
  if (outputType === 'NOTE') {
@@ -260,6 +322,18 @@ export class WorkerDecisionLoop implements WorkerDecisionRunner {
260
322
  }
261
323
  }
262
324
 
325
+ if (interactiveSummary) {
326
+ result.toolResults.push({
327
+ checkpoint: structuredClone(interactiveSummary.finalCheckpoint),
328
+ });
329
+ if (interactiveSummary.changedFiles.length > 0) {
330
+ result.patchApplied = true;
331
+ }
332
+ if (interactiveSummary.checkpointInvalid) {
333
+ result.invalidOutput = true;
334
+ }
335
+ }
336
+
263
337
  const hasProgress = result.planSubmission || result.patchApplied || result.requestHandled;
264
338
  result.noProgress = !hasProgress;
265
339
 
@@ -274,6 +348,7 @@ export class WorkerDecisionLoop implements WorkerDecisionRunner {
274
348
  noteCount,
275
349
  valid: !result.invalidOutput,
276
350
  errorCode: result.invalidOutput ? ERROR_CODES.PROVIDER_OUTPUT_INVALID : null,
351
+ executionMode,
277
352
  });
278
353
 
279
354
  if (this.activityMonitor) {
@@ -283,6 +358,168 @@ export class WorkerDecisionLoop implements WorkerDecisionRunner {
283
358
  return result;
284
359
  }
285
360
 
361
+ private async resolveExecutionMode(featureId: string, role: RuntimeRole): Promise<ExecutionMode> {
362
+ if (this.resolveExecutionModeOverride) {
363
+ return await this.resolveExecutionModeOverride(featureId, role);
364
+ }
365
+ return 'deterministic';
366
+ }
367
+
368
+ private isInteractiveEligible(executionMode: ExecutionMode, role: RuntimeRole): boolean {
369
+ if (executionMode !== 'interactive') {
370
+ return false;
371
+ }
372
+ if (role !== 'builder' && role !== 'qa') {
373
+ return false;
374
+ }
375
+ if (!this.repoRoot || !this.watchdog || !this.checkpointService || !this.resolveWorktreePath) {
376
+ return false;
377
+ }
378
+ const capabilities = this.provider.getCapabilities?.();
379
+ if (!capabilities) {
380
+ return true;
381
+ }
382
+ return capabilities.supportsInteractiveMode && capabilities.supportsWorkingDirectory;
383
+ }
384
+
385
+ private resolveInteractiveConfig(): InteractiveExecutionConfig {
386
+ if (this.resolveInteractiveConfigOverride) {
387
+ return this.resolveInteractiveConfigOverride();
388
+ }
389
+ return {
390
+ checkpointIntervalMs: 30_000,
391
+ watchdogPollIntervalMs: 2_000,
392
+ maxUncommittedChanges: 50,
393
+ validationOnCheckpoint: true,
394
+ revertOnViolation: false,
395
+ violationSeverity: 'warning',
396
+ };
397
+ }
398
+
399
+ private buildRunInput(
400
+ input: WorkerDecisionInput,
401
+ executionMode: ExecutionMode,
402
+ workingDirectory: string | undefined,
403
+ ): WorkerRunInput {
404
+ return {
405
+ role: input.role,
406
+ feature_id: input.featureId,
407
+ context_bundle: input.contextBundle,
408
+ instructions: input.instructions,
409
+ last_tool_results: input.lastToolResults ?? [],
410
+ runtime_selection: {
411
+ provider: this.provider.selection.provider,
412
+ model: this.provider.selection.model,
413
+ provider_config_ref: this.provider.selection.provider_config_ref,
414
+ },
415
+ execution_mode: executionMode,
416
+ working_directory: workingDirectory,
417
+ pause_resume_protocol: 'none',
418
+ };
419
+ }
420
+
421
+ private async runInteractiveWorker(input: WorkerDecisionInput): Promise<InteractiveRunSummary> {
422
+ if (!this.repoRoot || !this.watchdog || !this.checkpointService || !this.resolveWorktreePath) {
423
+ const fallbackOutput = await this.provider.runWorker(
424
+ this.buildRunInput(input, 'deterministic', undefined),
425
+ );
426
+ return {
427
+ workerOutput: fallbackOutput,
428
+ finalCheckpoint: this.createSyntheticCheckpoint(input.featureId),
429
+ changedFiles: [],
430
+ checkpointInvalid: false,
431
+ };
432
+ }
433
+
434
+ const interactiveConfig = this.resolveInteractiveConfig();
435
+ const worktreePath = this.resolveWorktreePath(input.featureId);
436
+ const sessionId = this.resolveRoleSessionId?.(input.role, input.featureId) ?? null;
437
+ await this.watchdog.startWatching({
438
+ featureId: input.featureId,
439
+ repoRoot: this.repoRoot,
440
+ worktreePath,
441
+ pollIntervalMs: interactiveConfig.watchdogPollIntervalMs,
442
+ maxUncommittedChanges: interactiveConfig.maxUncommittedChanges,
443
+ });
444
+
445
+ let latestCheckpoint: CheckpointRecord | null = null;
446
+ let checkpointInvalid = false;
447
+ let lastCheckpointAt = 0;
448
+ const checkpointDebounceMs = 5_000;
449
+ let checkpointQueue = Promise.resolve();
450
+
451
+ const enqueueCheckpoint = (
452
+ trigger: 'interval' | 'change_threshold' | 'final',
453
+ ): Promise<void> => {
454
+ checkpointQueue = checkpointQueue.then(async () => {
455
+ const now = Date.now();
456
+ if (trigger !== 'final' && now - lastCheckpointAt < checkpointDebounceMs) {
457
+ return;
458
+ }
459
+ try {
460
+ const checkpoint = await this.checkpointService?.createCheckpoint({
461
+ featureId: input.featureId,
462
+ sessionId,
463
+ trigger,
464
+ });
465
+ if (checkpoint) {
466
+ latestCheckpoint = checkpoint.checkpoint;
467
+ checkpointInvalid = checkpointInvalid || !checkpoint.valid || checkpoint.blockMerge;
468
+ }
469
+ } catch {
470
+ checkpointInvalid = true;
471
+ } finally {
472
+ lastCheckpointAt = Date.now();
473
+ }
474
+ });
475
+ checkpointQueue = checkpointQueue.catch(() => undefined);
476
+ return checkpointQueue;
477
+ };
478
+
479
+ const thresholdListener = (featureId: string): void => {
480
+ if (featureId !== input.featureId) {
481
+ return;
482
+ }
483
+ void enqueueCheckpoint('change_threshold');
484
+ };
485
+
486
+ this.watchdog.on('changeThreshold', thresholdListener);
487
+ const timer = setInterval(() => {
488
+ void enqueueCheckpoint('interval');
489
+ }, interactiveConfig.checkpointIntervalMs);
490
+ timer.unref?.();
491
+
492
+ try {
493
+ const workerOutput = await this.provider.runWorker(
494
+ this.buildRunInput(input, 'interactive', worktreePath),
495
+ );
496
+ await enqueueCheckpoint('final');
497
+ const changedFiles = latestCheckpoint?.files_changed ?? [];
498
+ return {
499
+ workerOutput,
500
+ finalCheckpoint: latestCheckpoint ?? this.createSyntheticCheckpoint(input.featureId),
501
+ changedFiles,
502
+ checkpointInvalid,
503
+ };
504
+ } finally {
505
+ clearInterval(timer);
506
+ this.watchdog.off('changeThreshold', thresholdListener);
507
+ await checkpointQueue.catch(() => undefined);
508
+ await this.watchdog.stopWatching(input.featureId);
509
+ }
510
+ }
511
+
512
+ private createSyntheticCheckpoint(featureId: string): CheckpointRecord {
513
+ return {
514
+ checkpoint_id: `checkpoint-synthetic-${Date.now()}`,
515
+ timestamp: new Date().toISOString(),
516
+ files_changed: [],
517
+ validation_status: 'skipped',
518
+ violations: [],
519
+ diff_snapshot: `.aop/features/${featureId}/checkpoints/synthetic.diff`,
520
+ };
521
+ }
522
+
286
523
  private async routePlanSubmission(
287
524
  input: WorkerDecisionInput,
288
525
  output: AnyRecord,
@@ -464,6 +701,7 @@ export class WorkerDecisionLoop implements WorkerDecisionRunner {
464
701
  eventType: string;
465
702
  featureId: string;
466
703
  role: RuntimeRole;
704
+ executionMode?: ExecutionMode;
467
705
  outputTypes: string[];
468
706
  patchCount: number;
469
707
  planSubmissionCount: number;
@@ -492,6 +730,7 @@ export class WorkerDecisionLoop implements WorkerDecisionRunner {
492
730
  role: event.role,
493
731
  phase: 'execution',
494
732
  event_type: event.eventType,
733
+ execution_mode: event.executionMode ?? 'deterministic',
495
734
  output_types: event.outputTypes,
496
735
  patch_count: event.patchCount,
497
736
  plan_submission_count: event.planSubmissionCount,