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,16 +1,12 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { ensureDir, pathExists, readJson, atomicWriteJson, atomicWriteFile, withFileLock, nowIso, stableHash, } from './fs.js';
4
- import { SchemaRegistry, loadAndValidateYaml } from './schemas.js';
5
- import { normalizeRepoPath } from './path-rules.js';
3
+ import { ensureDir, pathExists, readJson, atomicWriteJson, atomicWriteFile, withFileLock, nowIso, } from './fs.js';
4
+ import { SchemaRegistry } from './schemas.js';
6
5
  import { parseFrontMatter, buildFrontMatter } from './frontmatter.js';
7
- import { runGit, runCommand } from './git.js';
8
6
  import { ERROR_CODES } from './error-codes.js';
9
7
  import { ok, fail, withSuggestedActions } from './response.js';
10
- import { ALLOWED_ACTORS, DEFAULT_CLUSTER, DEFAULT_ROLE_STATUS, GATE_RESULT, STATUS, TOOLS, } from './constants.js';
11
- import { AopPathLayout, ensureAopRuntimeLayout } from './path-layout.js';
12
- import { applyWorktreeSymlinks, formatWorkspaceHookWarning, runWorktreePostCreate, } from './workspace-hooks.js';
13
- import { ToolRegistryLoader } from '../mcp/tool-registry-loader.js';
8
+ import { ALLOWED_ACTORS, DEFAULT_CLUSTER, DEFAULT_ROLE_STATUS, GATE_RESULT, STATUS, } from './constants.js';
9
+ import { AopPathLayout } from './path-layout.js';
14
10
  import { ToolHandlerRegistry, ToolRouter, } from '../application/tools/tool-router.js';
15
11
  import { RunLeaseService, } from '../application/services/run-lease-service.js';
16
12
  import { LockService } from '../application/services/lock-service.js';
@@ -27,52 +23,13 @@ import { FeatureDeletionService, } from '../application/services/feature-deletio
27
23
  import { CostTrackingService } from '../application/services/cost-tracking-service.js';
28
24
  import { PerformanceAnalyticsService, } from '../application/services/performance-analytics-service.js';
29
25
  import { GateSelectionService } from '../application/services/gate-selection-service.js';
30
- import { loadComposedPolicy } from '../application/services/policy-loader-service.js';
31
- import { ACTIVITY_DETECTOR_SLOT, NOTIFICATION_CHANNEL_SLOT, SCM_PROVIDER_SLOT, globalAdapterRegistry, } from '../application/adapters/adapter-registry.js';
32
- function asArray(value) {
33
- return Array.isArray(value) ? value : [];
34
- }
35
- function readStringField(record, key) {
36
- const value = record[key];
37
- return typeof value === 'string' ? value : null;
38
- }
39
- function readNumberField(record, key) {
40
- const value = record[key];
41
- return typeof value === 'number' && Number.isFinite(value) ? value : null;
42
- }
43
- function readPositiveIntegerField(record, key) {
44
- const value = record[key];
45
- if (typeof value !== 'number' || !Number.isFinite(value) || value < 1) {
46
- return null;
47
- }
48
- return Math.floor(value);
49
- }
50
- function readBooleanField(record, key) {
51
- const value = record[key];
52
- return typeof value === 'boolean' ? value : null;
53
- }
54
- function readObjectField(record, key) {
55
- const value = record[key];
56
- return value && typeof value === 'object' ? value : {};
57
- }
58
- function normalizeSet(array) {
59
- return [...new Set(array)].sort((a, b) => a.localeCompare(b));
60
- }
61
- function validateAgentRuntimeTimeoutRelationships(agentsConfig) {
62
- const runtime = readObjectField(agentsConfig, 'runtime');
63
- if (Object.keys(runtime).length === 0) {
64
- return;
65
- }
66
- const responseTimeoutMs = readPositiveIntegerField(runtime, 'worker_response_timeout_ms');
67
- const spawnTimeoutMs = readPositiveIntegerField(runtime, 'worker_spawn_timeout_ms');
68
- const idleTimeoutMs = readPositiveIntegerField(runtime, 'worker_idle_timeout_ms');
69
- if (responseTimeoutMs != null && spawnTimeoutMs != null && spawnTimeoutMs >= responseTimeoutMs) {
70
- throw new Error('invalid_agents_yaml:runtime.worker_spawn_timeout_ms must be less than runtime.worker_response_timeout_ms');
71
- }
72
- if (responseTimeoutMs != null && idleTimeoutMs != null && idleTimeoutMs > responseTimeoutMs) {
73
- throw new Error('invalid_agents_yaml:runtime.worker_idle_timeout_ms must be less than or equal to runtime.worker_response_timeout_ms');
74
- }
75
- }
26
+ import { readStringField } from './utils/field-readers.js';
27
+ import { emptyRuntimeSessions as utilEmptyRuntimeSessions, normalizeRuntimeSessions as utilNormalizeRuntimeSessions, normalizeIndexShape as utilNormalizeIndexShape, isRunLeaseFresh as utilIsRunLeaseFresh, } from './utils/index-normalizer.js';
28
+ import { normalizeRepoPathForState } from './utils/path-normalizers.js';
29
+ import { ConfigurationService } from '../application/configuration-service.js';
30
+ import { RepoOperationsService } from '../application/services/repo-operations-service.js';
31
+ import { FeatureSendMessageService } from '../application/services/feature-send-message-service.js';
32
+ import { registerKernelTools } from '../application/kernel-tool-wiring.js';
76
33
  /**
77
34
  * Deterministic orchestration kernel for multi-agent feature development.
78
35
  *
@@ -134,6 +91,8 @@ export class AopKernel {
134
91
  costTrackingService;
135
92
  performanceAnalyticsService;
136
93
  gateSelectionService;
94
+ repoOperationsService;
95
+ sendMessageService;
137
96
  pathLayout;
138
97
  instanceId;
139
98
  provider = null;
@@ -158,7 +117,7 @@ export class AopKernel {
158
117
  this.adaptersConfig = {};
159
118
  this.toolRegistry = null;
160
119
  this.toolHandlers = new ToolHandlerRegistry();
161
- this.registerToolHandlers();
120
+ registerKernelTools(this.toolHandlers, this);
162
121
  this.runLeaseService = new RunLeaseService(this);
163
122
  this.collisionQueueService = new CollisionQueueService(this);
164
123
  this.lockService = new LockService(this);
@@ -174,6 +133,12 @@ export class AopKernel {
174
133
  this.costTrackingService = new CostTrackingService(this);
175
134
  this.performanceAnalyticsService = new PerformanceAnalyticsService(this);
176
135
  this.gateSelectionService = new GateSelectionService(this);
136
+ this.repoOperationsService = new RepoOperationsService(this);
137
+ this.sendMessageService = new FeatureSendMessageService({
138
+ readState: (id) => this.readState(id),
139
+ getRuntimeSessions: () => this.getRuntimeSessions(),
140
+ getProvider: () => this.provider,
141
+ });
177
142
  this.toolRouter = new ToolRouter(this.toolHandlers, (toolName) => Promise.resolve(fail(ERROR_CODES.INVALID_ARGUMENT, `Unknown tool ${toolName}`, {
178
143
  retryable: false,
179
144
  requires_human: true,
@@ -223,180 +188,25 @@ export class AopKernel {
223
188
  return Math.floor(configured);
224
189
  }
225
190
  emptyRuntimeSessions(at = nowIso()) {
226
- return {
227
- run_id: 'none',
228
- orchestrator_session_id: 'unknown',
229
- provider: 'unknown',
230
- model: 'unknown',
231
- provider_config_ref_hash: stableHash('none'),
232
- owner_instance_id: 'none',
233
- lease_id: 'none',
234
- started_at: at,
235
- last_heartbeat_at: at,
236
- lease_expires_at: at,
237
- orchestrator_epoch: 0,
238
- feature_sessions: {},
239
- };
191
+ return utilEmptyRuntimeSessions(at);
240
192
  }
241
193
  normalizeRuntimeSessions(value, at = nowIso()) {
242
- const fallback = this.emptyRuntimeSessions(at);
243
- const source = value && typeof value === 'object' ? value : {};
244
- const featureSessionsInput = source.feature_sessions && typeof source.feature_sessions === 'object'
245
- ? source.feature_sessions
246
- : {};
247
- const featureSessions = {};
248
- for (const [featureId, raw] of Object.entries(featureSessionsInput)) {
249
- if (!featureId || typeof raw !== 'object' || !raw) {
250
- continue;
251
- }
252
- const typed = raw;
253
- featureSessions[featureId] = {
254
- planner_session_id: typeof typed.planner_session_id === 'string' ? typed.planner_session_id : 'unassigned',
255
- builder_session_id: typeof typed.builder_session_id === 'string' ? typed.builder_session_id : 'unassigned',
256
- qa_session_id: typeof typed.qa_session_id === 'string' ? typed.qa_session_id : 'unassigned',
257
- };
258
- }
259
- const epoch = typeof source.orchestrator_epoch === 'number' && Number.isFinite(source.orchestrator_epoch)
260
- ? Math.max(0, Math.floor(source.orchestrator_epoch))
261
- : 0;
262
- return {
263
- run_id: typeof source.run_id === 'string' && source.run_id ? source.run_id : fallback.run_id,
264
- orchestrator_session_id: typeof source.orchestrator_session_id === 'string' && source.orchestrator_session_id
265
- ? source.orchestrator_session_id
266
- : fallback.orchestrator_session_id,
267
- provider: typeof source.provider === 'string' && source.provider
268
- ? source.provider
269
- : fallback.provider,
270
- model: typeof source.model === 'string' && source.model ? source.model : fallback.model,
271
- provider_config_ref_hash: typeof source.provider_config_ref_hash === 'string' && source.provider_config_ref_hash
272
- ? source.provider_config_ref_hash
273
- : fallback.provider_config_ref_hash,
274
- owner_instance_id: typeof source.owner_instance_id === 'string' && source.owner_instance_id
275
- ? source.owner_instance_id
276
- : fallback.owner_instance_id,
277
- lease_id: typeof source.lease_id === 'string' && source.lease_id
278
- ? source.lease_id
279
- : fallback.lease_id,
280
- started_at: typeof source.started_at === 'string' && source.started_at
281
- ? source.started_at
282
- : fallback.started_at,
283
- last_heartbeat_at: typeof source.last_heartbeat_at === 'string' && source.last_heartbeat_at
284
- ? source.last_heartbeat_at
285
- : fallback.last_heartbeat_at,
286
- lease_expires_at: typeof source.lease_expires_at === 'string' && source.lease_expires_at
287
- ? source.lease_expires_at
288
- : fallback.lease_expires_at,
289
- orchestrator_epoch: epoch,
290
- feature_sessions: featureSessions,
291
- };
194
+ return utilNormalizeRuntimeSessions(value, at);
292
195
  }
293
196
  normalizeIndexShape(value) {
294
- const now = nowIso();
295
- const source = value && typeof value === 'object' ? value : {};
296
- return {
297
- version: typeof source.version === 'number' && Number.isFinite(source.version)
298
- ? Math.max(1, Math.floor(source.version))
299
- : 1,
300
- active: normalizeSet(asArray(source.active).filter((item) => typeof item === 'string')),
301
- blocked: normalizeSet(asArray(source.blocked).filter((item) => typeof item === 'string')),
302
- merged: normalizeSet(asArray(source.merged).filter((item) => typeof item === 'string')),
303
- locks: source.locks && typeof source.locks === 'object' ? source.locks : {},
304
- lock_leases: source.lock_leases && typeof source.lock_leases === 'object' ? source.lock_leases : {},
305
- blocked_queue: asArray(source.blocked_queue).filter((item) => item && typeof item === 'object'),
306
- dep_blocked: asArray(source.dep_blocked).filter((item) => item && typeof item === 'object'),
307
- updated_at: typeof source.updated_at === 'string' && source.updated_at ? source.updated_at : now,
308
- runtime_sessions: this.normalizeRuntimeSessions(source.runtime_sessions, now),
309
- };
197
+ return utilNormalizeIndexShape(value);
310
198
  }
311
199
  isRunLeaseFresh(runtimeSessions) {
312
- const expiry = new Date(runtimeSessions.lease_expires_at).getTime();
313
- if (!Number.isFinite(expiry)) {
314
- return false;
315
- }
316
- return expiry > Date.now();
317
- }
318
- async resolveDefaultConfigPath(fileName) {
319
- const primary = path.join(this.pathLayout.orchestratorRoot, fileName);
320
- if (await pathExists(primary)) {
321
- return primary;
322
- }
323
- const legacy = path.join(this.pathLayout.legacyOrchestratorRoot, fileName);
324
- if (await pathExists(legacy)) {
325
- return legacy;
326
- }
327
- return primary;
200
+ return utilIsRunLeaseFresh(runtimeSessions);
328
201
  }
329
202
  async load() {
330
- await ensureAopRuntimeLayout(this.pathLayout);
331
- const gatesPath = this.configOverrides.gatesPath ?? (await this.resolveDefaultConfigPath('gates.yaml'));
332
- const policyPath = this.configOverrides.policyPath ?? (await this.resolveDefaultConfigPath('policy.yaml'));
333
- const agentsPath = this.configOverrides.agentsPath ?? (await this.resolveDefaultConfigPath('agents.yaml'));
334
- const adaptersPath = this.configOverrides.adaptersPath ?? (await this.resolveDefaultConfigPath('adapters.yaml'));
335
- const gates = await loadAndValidateYaml(this.schemaRegistry, 'gates.schema.json', gatesPath);
336
- if (!gates.validation.valid) {
337
- throw new Error(`invalid_gates_yaml:${JSON.stringify(gates.validation.errors)}`);
338
- }
339
- const { mergedPolicy } = await loadComposedPolicy(this.repoRoot, policyPath, this.schemaRegistry);
340
- const parsedPolicy = mergedPolicy;
341
- const implementation = readObjectField(parsedPolicy, 'implementation');
342
- const testing = readObjectField(parsedPolicy, 'testing');
343
- if (readStringField(implementation, 'workspace') !== 'nx') {
344
- throw new Error(ERROR_CODES.INVALID_WORKSPACE_IMPLEMENTATION);
345
- }
346
- if (readStringField(testing, 'framework') !== 'vitest') {
347
- throw new Error(ERROR_CODES.INVALID_WORKSPACE_IMPLEMENTATION);
348
- }
349
- const agentsExists = await pathExists(agentsPath);
350
- let agents = { parsed: { version: 1, roles: {} }, validation: { valid: true, errors: [] } };
351
- if (agentsExists) {
352
- agents = await loadAndValidateYaml(this.schemaRegistry, 'agents.schema.json', agentsPath);
353
- if (!agents.validation.valid) {
354
- throw new Error(`invalid_agents_yaml:${JSON.stringify(agents.validation.errors)}`);
355
- }
356
- validateAgentRuntimeTimeoutRelationships(agents.parsed);
357
- }
358
- const adaptersExists = await pathExists(adaptersPath);
359
- let adapters = { parsed: {}, validation: { valid: true, errors: [] } };
360
- if (adaptersExists) {
361
- adapters = await loadAndValidateYaml(this.schemaRegistry, 'adapters.schema.json', adaptersPath);
362
- if (!adapters.validation.valid) {
363
- throw new Error(`invalid_adapters_yaml:${JSON.stringify(adapters.validation.errors)}`);
364
- }
365
- const parsedAdapters = readObjectField(adapters, 'parsed');
366
- const notificationChannel = readStringField(parsedAdapters, NOTIFICATION_CHANNEL_SLOT.name);
367
- if (notificationChannel) {
368
- try {
369
- globalAdapterRegistry.resolve(NOTIFICATION_CHANNEL_SLOT, notificationChannel, {});
370
- }
371
- catch {
372
- throw new Error(`adapter_not_found:${NOTIFICATION_CHANNEL_SLOT.name}:${notificationChannel}`);
373
- }
374
- }
375
- const activityDetector = readStringField(parsedAdapters, ACTIVITY_DETECTOR_SLOT.name);
376
- if (activityDetector) {
377
- try {
378
- globalAdapterRegistry.resolve(ACTIVITY_DETECTOR_SLOT, activityDetector, {});
379
- }
380
- catch {
381
- throw new Error(`adapter_not_found:${ACTIVITY_DETECTOR_SLOT.name}:${activityDetector}`);
382
- }
383
- }
384
- const scmProvider = readStringField(parsedAdapters, SCM_PROVIDER_SLOT.name);
385
- if (scmProvider) {
386
- try {
387
- globalAdapterRegistry.resolve(SCM_PROVIDER_SLOT, scmProvider, {});
388
- }
389
- catch {
390
- throw new Error(`adapter_not_found:${SCM_PROVIDER_SLOT.name}:${scmProvider}`);
391
- }
392
- }
393
- }
394
- this.gatesConfig = gates.parsed;
395
- this.policy = parsedPolicy;
396
- this.agentsConfig = agents.parsed;
397
- this.adaptersConfig = readObjectField(adapters, 'parsed');
398
- const registryLoader = new ToolRegistryLoader(this.repoRoot);
399
- this.toolRegistry = await registryLoader.load();
203
+ const configService = new ConfigurationService(this.repoRoot, this.pathLayout, this.schemaRegistry, this.configOverrides);
204
+ const config = await configService.loadAll();
205
+ this.gatesConfig = config.gatesConfig;
206
+ this.policy = config.policy;
207
+ this.agentsConfig = config.agentsConfig;
208
+ this.adaptersConfig = config.adaptersConfig;
209
+ this.toolRegistry = config.toolRegistry;
400
210
  this.loaded = true;
401
211
  }
402
212
  async ensureLoaded() {
@@ -480,41 +290,6 @@ export class AopKernel {
480
290
  async dispatchTool(toolName, args, context) {
481
291
  return await this.toolRouter.route(toolName, args, context);
482
292
  }
483
- registerToolHandlers() {
484
- this.toolHandlers.register(TOOLS.FEATURE_DISCOVER_SPECS, async () => await this.featureDiscoverSpecs());
485
- this.toolHandlers.register(TOOLS.FEATURE_INIT, async (args) => await this.featureInit(readStringField(args, 'feature_id')));
486
- this.toolHandlers.register(TOOLS.FEATURE_GET_CONTEXT, async (args) => await this.featureGetContext(readStringField(args, 'feature_id')));
487
- this.toolHandlers.register(TOOLS.FEATURE_STATE_GET, async (args) => await this.featureStateGet(readStringField(args, 'feature_id')));
488
- this.toolHandlers.register(TOOLS.FEATURE_STATE_PATCH, async (args) => await this.featureStatePatch(readStringField(args, 'feature_id'), readNumberField(args, 'expected_version'), args.patch));
489
- this.toolHandlers.register(TOOLS.FEATURE_LOG_APPEND, async (args, context) => await this.featureLogAppend(readStringField(args, 'feature_id'), readStringField(args, 'note'), context));
490
- this.toolHandlers.register(TOOLS.PLAN_SUBMIT, async (args) => await this.planSubmit(readStringField(args, 'feature_id'), args.plan_json, readNumberField(args, 'expected_version')));
491
- this.toolHandlers.register(TOOLS.PLAN_GET, async (args) => await this.planGet(readStringField(args, 'feature_id')));
492
- this.toolHandlers.register(TOOLS.PLAN_UPDATE, async (args) => await this.planUpdate(readStringField(args, 'feature_id'), readNumberField(args, 'expected_plan_version'), args.plan_json));
493
- this.toolHandlers.register(TOOLS.REPO_ENSURE_WORKTREE, async (args) => await this.repoEnsureWorktree(readStringField(args, 'feature_id')));
494
- this.toolHandlers.register(TOOLS.REPO_APPLY_PATCH, async (args) => await this.repoApplyPatch(readStringField(args, 'feature_id'), readStringField(args, 'unified_diff')));
495
- this.toolHandlers.register(TOOLS.REPO_STATUS, async (args) => await this.repoStatus(readStringField(args, 'feature_id')));
496
- this.toolHandlers.register(TOOLS.REPO_DIFF, async (args) => await this.repoDiff(readStringField(args, 'feature_id'), asArray(args.options)));
497
- this.toolHandlers.register(TOOLS.REPO_READ_FILE, async (args) => await this.repoReadFile(readStringField(args, 'feature_id'), readStringField(args, 'path')));
498
- this.toolHandlers.register(TOOLS.REPO_SEARCH, async (args) => await this.repoSearch(readStringField(args, 'feature_id'), readStringField(args, 'query')));
499
- this.toolHandlers.register(TOOLS.REPO_DIFF_BUNDLE, async (args) => await this.repoDiffBundle(readStringField(args, 'feature_id')));
500
- this.toolHandlers.register(TOOLS.FEATURE_READY_TO_MERGE, async (args) => await this.featureReadyToMerge(readStringField(args, 'feature_id'), readStringField(args, 'commit_message'), readStringField(args, 'merge_strategy'), readStringField(args, 'user_approval_token')));
501
- this.toolHandlers.register(TOOLS.FEATURE_DELETE, async (args) => await this.featureDelete(readStringField(args, 'feature_id'), readBooleanField(args, 'dry_run'), readBooleanField(args, 'confirm'), readBooleanField(args, 'remove_worktree'), readStringField(args, 'remove_branch')));
502
- this.toolHandlers.register(TOOLS.GATES_LIST, async (args) => await this.gatesList(readStringField(args, 'profile')));
503
- this.toolHandlers.register(TOOLS.GATES_RUN, async (args) => await this.gatesRun(readStringField(args, 'feature_id'), readStringField(args, 'profile'), readStringField(args, 'mode')));
504
- this.toolHandlers.register(TOOLS.EVIDENCE_LATEST, async (args) => await this.evidenceLatest(readStringField(args, 'feature_id')));
505
- this.toolHandlers.register(TOOLS.QA_TEST_INDEX_GET, async (args) => await this.qaTestIndexGet(readStringField(args, 'feature_id')));
506
- this.toolHandlers.register(TOOLS.QA_TEST_INDEX_UPDATE, async (args) => await this.qaTestIndexUpdate(readStringField(args, 'feature_id'), readNumberField(args, 'expected_version'), args.updates, asArray(args.evidence_refs)));
507
- this.toolHandlers.register(TOOLS.LOCKS_ACQUIRE, async (args) => await this.locksAcquire(readStringField(args, 'resource'), readStringField(args, 'feature_id'), readNumberField(args, 'wait_timeout_seconds')));
508
- this.toolHandlers.register(TOOLS.LOCKS_RELEASE, async (args) => await this.locksRelease(readStringField(args, 'resource'), readStringField(args, 'feature_id')));
509
- this.toolHandlers.register(TOOLS.COLLISIONS_SCAN, async () => await this.collisionsScan());
510
- this.toolHandlers.register(TOOLS.REPORT_DASHBOARD, async () => await this.reportDashboard());
511
- this.toolHandlers.register(TOOLS.REPORT_FEATURE_SUMMARY, async (args) => await this.reportFeatureSummary(readStringField(args, 'feature_id')));
512
- this.toolHandlers.register(TOOLS.FEATURE_SEND_MESSAGE, async (args) => await this.featureSendMessage(readStringField(args, 'feature_id'), readStringField(args, 'message')));
513
- this.toolHandlers.register(TOOLS.COST_RECORD, async (args) => await this.costRecord(readStringField(args, 'feature_id'), typeof args.tokens_used_delta === 'number' ? args.tokens_used_delta : 0, typeof args.estimated_cost_usd_delta === 'number' ? args.estimated_cost_usd_delta : 0));
514
- this.toolHandlers.register(TOOLS.COST_GET, async (args) => await this.costGet(readStringField(args, 'feature_id')));
515
- this.toolHandlers.register(TOOLS.PERFORMANCE_RECORD_OUTCOME, async (args) => await this.performanceRecordOutcome(args));
516
- this.toolHandlers.register(TOOLS.PERFORMANCE_GET_ANALYTICS, async (args) => await this.performanceGetAnalytics(readStringField(args, 'provider'), readStringField(args, 'model')));
517
- }
518
293
  featurePath(featureId) {
519
294
  return this.pathLayout.featureRoot(featureId);
520
295
  }
@@ -562,12 +337,14 @@ export class AopKernel {
562
337
  }
563
338
  }
564
339
  makeDefaultState(featureId, branch, worktreePath) {
340
+ const configuredExecutionMode = this.agentsConfig.runtime?.execution_mode === 'interactive' ? 'interactive' : 'deterministic';
565
341
  return {
566
342
  feature_id: featureId,
567
343
  version: 1,
568
344
  branch,
569
345
  worktree_path: normalizeRepoPathForState(this.repoRoot, worktreePath),
570
346
  status: STATUS.PLANNING,
347
+ execution_mode: configuredExecutionMode,
571
348
  gate_profile: 'default',
572
349
  gates: {
573
350
  plan: GATE_RESULT.NA,
@@ -585,6 +362,7 @@ export class AopKernel {
585
362
  },
586
363
  cluster: { ...DEFAULT_CLUSTER },
587
364
  role_status: { ...DEFAULT_ROLE_STATUS },
365
+ checkpoints: [],
588
366
  last_updated: nowIso(),
589
367
  };
590
368
  }
@@ -776,76 +554,7 @@ export class AopKernel {
776
554
  return await this.planService.planUpdate(featureId, expectedPlanVersion, plan);
777
555
  }
778
556
  async repoEnsureWorktree(featureId) {
779
- const worktree = this.worktreePath(featureId);
780
- const branch = featureId;
781
- await ensureDir(path.join(this.repoRoot, '.worktrees'));
782
- if (await pathExists(worktree)) {
783
- return {
784
- data: {
785
- feature_id: featureId,
786
- branch,
787
- worktree_path_abs: worktree,
788
- existed: true,
789
- },
790
- };
791
- }
792
- const baseBranch = this.policy.worktree.base_branch;
793
- const baseCheck = await runGit(this.repoRoot, ['rev-parse', '--verify', baseBranch]);
794
- const baseRef = baseCheck.code === 0 ? baseBranch : 'HEAD';
795
- const branchCheck = await runGit(this.repoRoot, ['rev-parse', '--verify', branch]);
796
- if (branchCheck.code !== 0) {
797
- const branchCreate = await runGit(this.repoRoot, ['branch', branch, baseRef]);
798
- if (branchCreate.code !== 0) {
799
- throw {
800
- normalizedResponse: fail(ERROR_CODES.GIT_FAILURE, 'Unable to create feature branch', {
801
- feature_id: featureId,
802
- stderr: branchCreate.stderr,
803
- retryable: false,
804
- requires_human: true,
805
- }, {
806
- command: ['git', 'branch', branch, baseRef],
807
- exit_code: branchCreate.code,
808
- }),
809
- };
810
- }
811
- }
812
- const addWorktree = await runGit(this.repoRoot, ['worktree', 'add', worktree, branch]);
813
- if (addWorktree.code !== 0) {
814
- throw {
815
- normalizedResponse: fail(ERROR_CODES.GIT_FAILURE, 'Unable to create git worktree', {
816
- feature_id: featureId,
817
- stderr: addWorktree.stderr,
818
- retryable: false,
819
- requires_human: true,
820
- }, {
821
- command: ['git', 'worktree', 'add', worktree, branch],
822
- exit_code: addWorktree.code,
823
- }),
824
- };
825
- }
826
- const worktreeConfig = this.policy.worktree;
827
- const hookWarnings = [];
828
- const collectHookWarning = (warning) => {
829
- hookWarnings.push(warning);
830
- };
831
- if (worktreeConfig?.symlinks?.length) {
832
- await applyWorktreeSymlinks(this.repoRoot, worktree, worktreeConfig.symlinks, collectHookWarning);
833
- }
834
- if (worktreeConfig?.post_create?.length) {
835
- await runWorktreePostCreate(worktree, worktreeConfig.post_create, collectHookWarning);
836
- }
837
- for (const warning of hookWarnings) {
838
- // Preserve non-fatal behavior while making hook failures observable.
839
- console.warn(`[aop] workspace hook warning: ${formatWorkspaceHookWarning(warning)}`);
840
- }
841
- return {
842
- data: {
843
- feature_id: featureId,
844
- branch,
845
- worktree_path_abs: worktree,
846
- existed: false,
847
- },
848
- };
557
+ return this.repoOperationsService.repoEnsureWorktree(featureId);
849
558
  }
850
559
  async loadAcceptedPlan(featureId) {
851
560
  return await this.patchService.loadAcceptedPlan(featureId);
@@ -856,123 +565,23 @@ export class AopKernel {
856
565
  async repoApplyPatch(featureId, unifiedDiff) {
857
566
  return await this.patchService.repoApplyPatch(featureId, unifiedDiff);
858
567
  }
568
+ async validatePatchDiff(featureId, parsedDiff) {
569
+ return await this.patchService.validateDiff(featureId, parsedDiff);
570
+ }
859
571
  async repoStatus(featureId) {
860
- const worktree = this.worktreePath(featureId);
861
- const status = await runGit(this.repoRoot, ['status', '--porcelain'], { cwd: worktree });
862
- const branch = await runGit(this.repoRoot, ['rev-parse', '--abbrev-ref', 'HEAD'], {
863
- cwd: worktree,
864
- });
865
- return {
866
- data: {
867
- feature_id: featureId,
868
- branch: branch.stdout.trim(),
869
- status_porcelain: status.stdout.trim().split('\n').filter(Boolean),
870
- },
871
- };
572
+ return this.repoOperationsService.repoStatus(featureId);
872
573
  }
873
574
  async repoDiff(featureId, options = []) {
874
- const safeOptions = asArray(options).filter((option) => typeof option === 'string' && option.startsWith('--'));
875
- const diff = await runGit(this.repoRoot, ['diff', ...safeOptions], {
876
- cwd: this.worktreePath(featureId),
877
- });
878
- return {
879
- data: {
880
- feature_id: featureId,
881
- diff: diff.stdout,
882
- },
883
- };
575
+ return this.repoOperationsService.repoDiff(featureId, options);
884
576
  }
885
577
  async repoReadFile(featureId, filePath) {
886
- const normalized = await normalizeRepoPath(this.repoRoot, path.join(this.worktreePath(featureId), filePath), this.policy.path_rules.allow_symlink_traversal).then((relative) => normalizeFromWorktree(this.worktreePath(featureId), this.repoRoot, relative));
887
- const absolute = path.join(this.repoRoot, normalized);
888
- const exists = await pathExists(absolute);
889
- if (!exists) {
890
- throw {
891
- normalizedResponse: fail(ERROR_CODES.FILE_NOT_FOUND, 'File not found', {
892
- path: normalized,
893
- retryable: false,
894
- requires_human: false,
895
- }),
896
- };
897
- }
898
- const content = await fs.readFile(absolute, 'utf8');
899
- return {
900
- data: {
901
- feature_id: featureId,
902
- path: normalized,
903
- content,
904
- },
905
- };
578
+ return this.repoOperationsService.repoReadFile(featureId, filePath);
906
579
  }
907
580
  async repoSearch(featureId, query) {
908
- const worktree = this.worktreePath(featureId);
909
- const rgResult = await runCommand('rg', ['-n', '--no-heading', query, '.'], {
910
- cwd: worktree,
911
- timeoutMs: 30_000,
912
- });
913
- if (rgResult.code === 127) {
914
- throw {
915
- normalizedResponse: fail(ERROR_CODES.GIT_FAILURE, 'ripgrep (rg) not found - required for search functionality', {
916
- stderr: rgResult.stderr,
917
- retryable: false,
918
- requires_human: true,
919
- }),
920
- };
921
- }
922
- if (rgResult.code !== 0 && rgResult.code !== 1) {
923
- throw {
924
- normalizedResponse: fail(ERROR_CODES.GIT_FAILURE, 'Search failed', {
925
- stderr: rgResult.stderr,
926
- retryable: true,
927
- requires_human: false,
928
- }),
929
- };
930
- }
931
- const matches = rgResult.stdout
932
- .trim()
933
- .split('\n')
934
- .filter(Boolean)
935
- .map((line) => {
936
- const firstColon = line.indexOf(':');
937
- const secondColon = line.indexOf(':', firstColon + 1);
938
- if (firstColon === -1 || secondColon === -1) {
939
- return { raw: line };
940
- }
941
- return {
942
- path: line.slice(0, firstColon),
943
- line: Number(line.slice(firstColon + 1, secondColon)),
944
- snippet: line.slice(secondColon + 1),
945
- };
946
- });
947
- return {
948
- data: {
949
- feature_id: featureId,
950
- query,
951
- matches,
952
- },
953
- };
581
+ return this.repoOperationsService.repoSearch(featureId, query);
954
582
  }
955
583
  async repoDiffBundle(featureId) {
956
- const stat = await runGit(this.repoRoot, ['diff', '--stat'], {
957
- cwd: this.worktreePath(featureId),
958
- });
959
- const full = await runGit(this.repoRoot, ['diff'], { cwd: this.worktreePath(featureId) });
960
- const names = await runGit(this.repoRoot, ['diff', '--name-only'], {
961
- cwd: this.worktreePath(featureId),
962
- });
963
- const latest = await this.evidenceLatest(featureId);
964
- return {
965
- data: {
966
- feature_id: featureId,
967
- diff_stat: stat.stdout,
968
- diff: full.stdout,
969
- touched_files: names.stdout
970
- .split('\n')
971
- .map((x) => x.trim())
972
- .filter(Boolean),
973
- last_gate_summary: latest.data?.latest ?? null,
974
- },
975
- };
584
+ return this.repoOperationsService.repoDiffBundle(featureId);
976
585
  }
977
586
  async gatesList(profileName = null) {
978
587
  return await this.gateService.gatesList(profileName);
@@ -1024,97 +633,8 @@ export class AopKernel {
1024
633
  async recoverFromState() {
1025
634
  return await this.lockService.recoverFromState();
1026
635
  }
1027
- async waitForSessionToBecomeActive(sessionId) {
1028
- if (!this.provider?.getSessionInfo) {
1029
- return;
1030
- }
1031
- const timeoutMs = 5000;
1032
- const pollIntervalMs = 250;
1033
- const deadline = Date.now() + timeoutMs;
1034
- while (Date.now() <= deadline) {
1035
- const sessionInfo = await this.provider.getSessionInfo(sessionId).catch(() => null);
1036
- if (sessionInfo?.active) {
1037
- return;
1038
- }
1039
- await new Promise((resolve) => {
1040
- setTimeout(resolve, pollIntervalMs);
1041
- });
1042
- }
1043
- }
1044
636
  async featureSendMessage(featureId, message) {
1045
- if (!featureId) {
1046
- throw {
1047
- normalizedResponse: fail(ERROR_CODES.INVALID_ARGUMENT, 'feature_id is required', {
1048
- retryable: false,
1049
- requires_human: false,
1050
- }),
1051
- };
1052
- }
1053
- if (!message) {
1054
- throw {
1055
- normalizedResponse: fail(ERROR_CODES.INVALID_ARGUMENT, 'message is required and must not be empty', { retryable: false, requires_human: false }),
1056
- };
1057
- }
1058
- const runtimeSessions = await this.getRuntimeSessions();
1059
- const featureSession = runtimeSessions.feature_sessions?.[featureId];
1060
- if (!featureSession) {
1061
- throw {
1062
- normalizedResponse: {
1063
- ok: false,
1064
- error: { code: 'session_not_found', message: 'No active session cluster for feature' },
1065
- },
1066
- };
1067
- }
1068
- const state = await this.readState(featureId);
1069
- const status = typeof state.frontMatter.status === 'string' ? state.frontMatter.status : '';
1070
- const gates = readObjectField(state.frontMatter, 'gates');
1071
- let targetRole = 'orchestrator';
1072
- let targetSessionId = runtimeSessions.orchestrator_session_id;
1073
- if (status === STATUS.PLANNING) {
1074
- targetRole = 'planner';
1075
- targetSessionId = featureSession.planner_session_id;
1076
- }
1077
- else if (status === STATUS.BUILDING) {
1078
- targetRole = 'builder';
1079
- targetSessionId = featureSession.builder_session_id;
1080
- }
1081
- else if (status === STATUS.QA || status === STATUS.READY_TO_MERGE) {
1082
- targetRole = 'qa';
1083
- targetSessionId = featureSession.qa_session_id;
1084
- }
1085
- else if (status === STATUS.BLOCKED) {
1086
- const fastGate = readStringField(gates, 'fast');
1087
- const fullGate = readStringField(gates, 'full');
1088
- if (fastGate === GATE_RESULT.FAIL && fullGate !== GATE_RESULT.FAIL) {
1089
- targetRole = 'builder';
1090
- targetSessionId = featureSession.builder_session_id;
1091
- }
1092
- else {
1093
- targetRole = 'qa';
1094
- targetSessionId = featureSession.qa_session_id;
1095
- }
1096
- }
1097
- if (!targetSessionId || targetSessionId === 'unassigned' || targetSessionId === 'unknown') {
1098
- targetRole = 'orchestrator';
1099
- targetSessionId = runtimeSessions.orchestrator_session_id;
1100
- }
1101
- if (!this.provider?.sendMessage) {
1102
- throw {
1103
- normalizedResponse: {
1104
- ok: false,
1105
- error: { code: 'provider_unsupported', message: 'Provider does not support sendMessage' },
1106
- },
1107
- };
1108
- }
1109
- await this.waitForSessionToBecomeActive(targetSessionId);
1110
- await this.provider.sendMessage(targetSessionId, message);
1111
- return {
1112
- feature_id: featureId,
1113
- session_id: targetSessionId,
1114
- target_role: targetRole,
1115
- status,
1116
- delivered: true,
1117
- };
637
+ return this.sendMessageService.featureSendMessage(featureId, message);
1118
638
  }
1119
639
  async costRecord(featureId, tokensDelta, costUsdDelta) {
1120
640
  const updated = await this.costTrackingService.recordCost(featureId, tokensDelta, costUsdDelta);
@@ -1159,19 +679,4 @@ export class AopKernel {
1159
679
  return { ok: true, data: snapshot };
1160
680
  }
1161
681
  }
1162
- function normalizeRepoPathForState(repoRoot, absolutePath) {
1163
- const relative = path.relative(repoRoot, absolutePath).replaceAll('\\\\', '/');
1164
- if (!relative || relative === '.') {
1165
- return '.';
1166
- }
1167
- return relative;
1168
- }
1169
- function normalizeFromWorktree(worktreePath, repoRoot, repoRelativeFromWorktree) {
1170
- const absolute = path.resolve(repoRoot, repoRelativeFromWorktree);
1171
- const maybeRelativeToWorktree = path.relative(worktreePath, absolute).replaceAll('\\\\', '/');
1172
- if (!maybeRelativeToWorktree.startsWith('../')) {
1173
- return maybeRelativeToWorktree;
1174
- }
1175
- return path.relative(repoRoot, absolute).replaceAll('\\\\', '/');
1176
- }
1177
682
  //# sourceMappingURL=kernel.js.map