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
@@ -26,6 +26,16 @@ export interface ToolCaller {
26
26
  export interface FeatureStateFrontMatter {
27
27
  version: number;
28
28
  status: string;
29
+ execution_mode?: 'deterministic' | 'interactive';
30
+ checkpoints?: Array<{
31
+ checkpoint_id: string;
32
+ timestamp: string;
33
+ files_changed: string[];
34
+ validation_status: 'valid' | 'invalid' | 'skipped';
35
+ violations?: string[];
36
+ severity?: 'info' | 'warning' | 'error' | 'critical';
37
+ diff_snapshot?: string;
38
+ }>;
29
39
  cluster?: Record<string, string>;
30
40
  gate_retry_count?: number;
31
41
  last_retry_at?: string | null;
@@ -0,0 +1,38 @@
1
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2
+ type AnyRecord = Record<string, any>;
3
+
4
+ export function asArray<T = unknown>(value: unknown): T[] {
5
+ return Array.isArray(value) ? (value as T[]) : [];
6
+ }
7
+
8
+ export function readStringField(record: AnyRecord, key: string): string | null {
9
+ const value = record[key];
10
+ return typeof value === 'string' ? value : null;
11
+ }
12
+
13
+ export function readNumberField(record: AnyRecord, key: string): number | null {
14
+ const value = record[key];
15
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
16
+ }
17
+
18
+ export function readPositiveIntegerField(record: AnyRecord, key: string): number | null {
19
+ const value = record[key];
20
+ if (typeof value !== 'number' || !Number.isFinite(value) || value < 1) {
21
+ return null;
22
+ }
23
+ return Math.floor(value);
24
+ }
25
+
26
+ export function readBooleanField(record: AnyRecord, key: string): boolean | null {
27
+ const value = record[key];
28
+ return typeof value === 'boolean' ? value : null;
29
+ }
30
+
31
+ export function readObjectField(record: AnyRecord, key: string): AnyRecord {
32
+ const value = record[key];
33
+ return value && typeof value === 'object' ? (value as AnyRecord) : {};
34
+ }
35
+
36
+ export function normalizeSet(array: string[]): string[] {
37
+ return [...new Set(array)].sort((a, b) => a.localeCompare(b));
38
+ }
@@ -0,0 +1,119 @@
1
+ import { stableHash, nowIso } from '../fs.js';
2
+ import type { RuntimeSessionsSnapshot } from '../runtime-sessions.js';
3
+ import { asArray, normalizeSet } from './field-readers.js';
4
+
5
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6
+ type AnyRecord = Record<string, any>;
7
+
8
+ export function emptyRuntimeSessions(at = nowIso()): RuntimeSessionsSnapshot {
9
+ return {
10
+ run_id: 'none',
11
+ orchestrator_session_id: 'unknown',
12
+ provider: 'unknown',
13
+ model: 'unknown',
14
+ provider_config_ref_hash: stableHash('none'),
15
+ owner_instance_id: 'none',
16
+ lease_id: 'none',
17
+ started_at: at,
18
+ last_heartbeat_at: at,
19
+ lease_expires_at: at,
20
+ orchestrator_epoch: 0,
21
+ feature_sessions: {},
22
+ };
23
+ }
24
+
25
+ export function normalizeRuntimeSessions(value: unknown, at = nowIso()): RuntimeSessionsSnapshot {
26
+ const fallback = emptyRuntimeSessions(at);
27
+ const source = value && typeof value === 'object' ? (value as Record<string, unknown>) : {};
28
+ const featureSessionsInput =
29
+ source.feature_sessions && typeof source.feature_sessions === 'object'
30
+ ? (source.feature_sessions as Record<string, unknown>)
31
+ : {};
32
+ const featureSessions: RuntimeSessionsSnapshot['feature_sessions'] = {};
33
+
34
+ for (const [featureId, raw] of Object.entries(featureSessionsInput)) {
35
+ if (!featureId || typeof raw !== 'object' || !raw) {
36
+ continue;
37
+ }
38
+ const typed = raw as Record<string, unknown>;
39
+ featureSessions[featureId] = {
40
+ planner_session_id:
41
+ typeof typed.planner_session_id === 'string' ? typed.planner_session_id : 'unassigned',
42
+ builder_session_id:
43
+ typeof typed.builder_session_id === 'string' ? typed.builder_session_id : 'unassigned',
44
+ qa_session_id: typeof typed.qa_session_id === 'string' ? typed.qa_session_id : 'unassigned',
45
+ };
46
+ }
47
+
48
+ const epoch =
49
+ typeof source.orchestrator_epoch === 'number' && Number.isFinite(source.orchestrator_epoch)
50
+ ? Math.max(0, Math.floor(source.orchestrator_epoch))
51
+ : 0;
52
+
53
+ return {
54
+ run_id: typeof source.run_id === 'string' && source.run_id ? source.run_id : fallback.run_id,
55
+ orchestrator_session_id:
56
+ typeof source.orchestrator_session_id === 'string' && source.orchestrator_session_id
57
+ ? source.orchestrator_session_id
58
+ : fallback.orchestrator_session_id,
59
+ provider:
60
+ typeof source.provider === 'string' && source.provider ? source.provider : fallback.provider,
61
+ model: typeof source.model === 'string' && source.model ? source.model : fallback.model,
62
+ provider_config_ref_hash:
63
+ typeof source.provider_config_ref_hash === 'string' && source.provider_config_ref_hash
64
+ ? source.provider_config_ref_hash
65
+ : fallback.provider_config_ref_hash,
66
+ owner_instance_id:
67
+ typeof source.owner_instance_id === 'string' && source.owner_instance_id
68
+ ? source.owner_instance_id
69
+ : fallback.owner_instance_id,
70
+ lease_id:
71
+ typeof source.lease_id === 'string' && source.lease_id ? source.lease_id : fallback.lease_id,
72
+ started_at:
73
+ typeof source.started_at === 'string' && source.started_at
74
+ ? source.started_at
75
+ : fallback.started_at,
76
+ last_heartbeat_at:
77
+ typeof source.last_heartbeat_at === 'string' && source.last_heartbeat_at
78
+ ? source.last_heartbeat_at
79
+ : fallback.last_heartbeat_at,
80
+ lease_expires_at:
81
+ typeof source.lease_expires_at === 'string' && source.lease_expires_at
82
+ ? source.lease_expires_at
83
+ : fallback.lease_expires_at,
84
+ orchestrator_epoch: epoch,
85
+ feature_sessions: featureSessions,
86
+ };
87
+ }
88
+
89
+ export function normalizeIndexShape(value: unknown): AnyRecord {
90
+ const now = nowIso();
91
+ const source = value && typeof value === 'object' ? (value as Record<string, unknown>) : {};
92
+ return {
93
+ version:
94
+ typeof source.version === 'number' && Number.isFinite(source.version)
95
+ ? Math.max(1, Math.floor(source.version))
96
+ : 1,
97
+ active: normalizeSet(asArray<string>(source.active).filter((item) => typeof item === 'string')),
98
+ blocked: normalizeSet(
99
+ asArray<string>(source.blocked).filter((item) => typeof item === 'string'),
100
+ ),
101
+ merged: normalizeSet(asArray<string>(source.merged).filter((item) => typeof item === 'string')),
102
+ locks: source.locks && typeof source.locks === 'object' ? source.locks : {},
103
+ lock_leases:
104
+ source.lock_leases && typeof source.lock_leases === 'object' ? source.lock_leases : {},
105
+ blocked_queue: asArray(source.blocked_queue).filter((item) => item && typeof item === 'object'),
106
+ dep_blocked: asArray(source.dep_blocked).filter((item) => item && typeof item === 'object'),
107
+ updated_at:
108
+ typeof source.updated_at === 'string' && source.updated_at ? source.updated_at : now,
109
+ runtime_sessions: normalizeRuntimeSessions(source.runtime_sessions, now),
110
+ };
111
+ }
112
+
113
+ export function isRunLeaseFresh(runtimeSessions: RuntimeSessionsSnapshot): boolean {
114
+ const expiry = new Date(runtimeSessions.lease_expires_at).getTime();
115
+ if (!Number.isFinite(expiry)) {
116
+ return false;
117
+ }
118
+ return expiry > Date.now();
119
+ }
@@ -0,0 +1,22 @@
1
+ import path from 'node:path';
2
+
3
+ export function normalizeRepoPathForState(repoRoot: string, absolutePath: string): string {
4
+ const relative = path.relative(repoRoot, absolutePath).replaceAll('\\\\', '/');
5
+ if (!relative || relative === '.') {
6
+ return '.';
7
+ }
8
+ return relative;
9
+ }
10
+
11
+ export function normalizeFromWorktree(
12
+ worktreePath: string,
13
+ repoRoot: string,
14
+ repoRelativeFromWorktree: string,
15
+ ): string {
16
+ const absolute = path.resolve(repoRoot, repoRelativeFromWorktree);
17
+ const maybeRelativeToWorktree = path.relative(worktreePath, absolute).replaceAll('\\\\', '/');
18
+ if (!maybeRelativeToWorktree.startsWith('../')) {
19
+ return maybeRelativeToWorktree;
20
+ }
21
+ return path.relative(repoRoot, absolute).replaceAll('\\\\', '/');
22
+ }
@@ -17,6 +17,7 @@ import { ResumeCommandHandler } from '../../cli/resume-command-handler.js';
17
17
  import { StopCommandHandler } from '../../cli/stop-command-handler.js';
18
18
  import { DeleteCommandHandler } from '../../cli/delete-command-handler.js';
19
19
  import { CleanupCommandHandler } from '../../cli/cleanup-command-handler.js';
20
+ import { RollbackCommandHandler } from '../../cli/rollback-command-handler.js';
20
21
  import { RunCommandHandler } from '../../cli/run-command-handler.js';
21
22
  import { InitCommandHandler } from '../../cli/init-command-handler.js';
22
23
  import { DashboardCommandHandler } from '../../cli/dashboard-command-handler.js';
@@ -30,6 +31,7 @@ import { resolveProviderSelection } from '../../providers/providers.js';
30
31
  import {
31
32
  DefaultWorkerProviderFactory,
32
33
  resolveWorkerProviderMode,
34
+ resolveWorkerProviderObservability,
33
35
  resolveWorkerProviderPolicy,
34
36
  resolveWorkerProviderRuntime,
35
37
  } from '../../providers/worker-provider-factory.js';
@@ -355,6 +357,10 @@ export async function runCli(
355
357
  policySnapshot.execution && typeof policySnapshot.execution === 'object'
356
358
  ? (policySnapshot.execution as Record<string, unknown>)
357
359
  : null;
360
+ const observabilityPolicy =
361
+ policySnapshot.observability && typeof policySnapshot.observability === 'object'
362
+ ? (policySnapshot.observability as Record<string, unknown>)
363
+ : null;
358
364
  commandProvider = workerProviderFactory.create({
359
365
  selection,
360
366
  mode: resolveWorkerProviderMode(
@@ -364,7 +370,9 @@ export async function runCli(
364
370
  ),
365
371
  context: options.command === CliCommand.Send ? 'send' : 'attach',
366
372
  policy: resolveWorkerProviderPolicy(executionPolicy),
373
+ observability: resolveWorkerProviderObservability(observabilityPolicy),
367
374
  runtime: resolveWorkerProviderRuntime(kernel.getAgentsConfig().runtime),
375
+ repoRoot,
368
376
  });
369
377
  kernel.setProvider(commandProvider);
370
378
  }
@@ -416,6 +424,13 @@ export async function runCli(
416
424
  return 0;
417
425
  }
418
426
 
427
+ if (options.command === CliCommand.Rollback) {
428
+ const handler = new RollbackCommandHandler(repoRoot);
429
+ const payload = await handler.execute(options);
430
+ printPayload(payload);
431
+ return 0;
432
+ }
433
+
419
434
  if (options.command === CliCommand.Retry) {
420
435
  const handler = new RetryCommandHandler(toolClient, runId);
421
436
  const payload = await handler.execute(options);
@@ -5,7 +5,9 @@ import type {
5
5
  ProviderSelection,
6
6
  WorkerCapabilities,
7
7
  WorkerHandle,
8
+ WorkerProviderCapabilities,
8
9
  WorkerProvider,
10
+ WorkerRunInput,
9
11
  WorkerSession,
10
12
  } from './providers.js';
11
13
  import type { ProviderOutputParser } from './output-parsers/types.js';
@@ -52,6 +54,13 @@ export class ApiWorkerProvider implements WorkerProvider {
52
54
  supportsIdleDetection: false,
53
55
  supportsSpawnDetection: false,
54
56
  };
57
+ private readonly providerCapabilities: WorkerProviderCapabilities = {
58
+ supportsInteractiveMode: false,
59
+ supportsWorkingDirectory: false,
60
+ supportsPauseResume: false,
61
+ supportsMessagePassing: false,
62
+ supportsAcknowledgment: false,
63
+ };
55
64
 
56
65
  constructor(selection: ProviderSelection, options: ApiWorkerProviderOptions) {
57
66
  this.selection = selection;
@@ -95,18 +104,7 @@ export class ApiWorkerProvider implements WorkerProvider {
95
104
  return Promise.resolve({ closed: true });
96
105
  }
97
106
 
98
- runWorker(input: {
99
- role: string;
100
- feature_id: string;
101
- context_bundle?: Record<string, unknown>;
102
- instructions?: string;
103
- last_tool_results?: Array<Record<string, unknown>>;
104
- runtime_selection?: {
105
- provider: string;
106
- model: string;
107
- provider_config_ref: string | null;
108
- };
109
- }): Promise<Record<string, unknown>> {
107
+ runWorker(input: WorkerRunInput): Promise<Record<string, unknown>> {
110
108
  const workerHandle: WorkerHandle = {
111
109
  capabilities: this.workerCapabilities,
112
110
  kill: () => {
@@ -169,4 +167,8 @@ export class ApiWorkerProvider implements WorkerProvider {
169
167
  });
170
168
  }
171
169
  }
170
+
171
+ getCapabilities(): WorkerProviderCapabilities {
172
+ return this.providerCapabilities;
173
+ }
172
174
  }
@@ -1,4 +1,6 @@
1
1
  import crypto from 'node:crypto';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
2
4
  import { ERROR_CODES } from '../core/error-codes.js';
3
5
  import {
4
6
  NodeProviderCommandRunner,
@@ -6,6 +8,8 @@ import {
6
8
  type ProviderCommandResult,
7
9
  type ProviderCommandRunner,
8
10
  type ProviderSelection,
11
+ type WorkerProviderCapabilities,
12
+ type WorkerRunInput,
9
13
  type WorkerCapabilities,
10
14
  type WorkerHandle,
11
15
  type WorkerProvider,
@@ -18,6 +22,9 @@ import { WorkerWatchdog, type WatchdogOutcome } from './worker-watchdog.js';
18
22
  interface CliWorkerProviderOptions {
19
23
  outputParser: ProviderOutputParser;
20
24
  commandRunner?: ProviderCommandRunner;
25
+ repoRoot?: string;
26
+ rawAgentLogsEnabled?: boolean;
27
+ rawAgentLogsRetentionDays?: number;
21
28
  workerResponseTimeoutMs: number;
22
29
  workerSpawnTimeoutMs?: number;
23
30
  workerIdleTimeoutMs?: number;
@@ -39,6 +46,14 @@ const DEFAULT_SPAWN_TIMEOUT_MS = 15_000;
39
46
  const DEFAULT_IDLE_TIMEOUT_MS = 120_000;
40
47
  const DEFAULT_WATCHDOG_POLL_INTERVAL_MS = 1_000;
41
48
  const DEFAULT_KILL_GRACE_PERIOD_MS = 1_500;
49
+ const DEFAULT_RAW_LOG_RETENTION_DAYS = 60;
50
+ const CLI_PROVIDER_CAPABILITIES: WorkerProviderCapabilities = {
51
+ supportsInteractiveMode: true,
52
+ supportsWorkingDirectory: true,
53
+ supportsPauseResume: false,
54
+ supportsMessagePassing: true,
55
+ supportsAcknowledgment: false,
56
+ };
42
57
 
43
58
  function buildStructuredWorkerPrompt(
44
59
  payload: Record<string, unknown>,
@@ -201,6 +216,9 @@ export class CliWorkerProvider implements WorkerProvider {
201
216
  private readonly workerIdleTimeoutMs: number;
202
217
  private readonly workerWatchdogPollIntervalMs: number;
203
218
  private readonly workerKillGracePeriodMs: number;
219
+ private readonly repoRoot: string | null;
220
+ private readonly rawAgentLogsEnabled: boolean;
221
+ private readonly rawAgentLogsRetentionDays: number;
204
222
  private readonly workerCapabilities: WorkerCapabilities;
205
223
  private readonly runTemplate: ProviderCommandTemplate | undefined;
206
224
  private readonly attachTemplate: ProviderCommandTemplate | undefined;
@@ -229,6 +247,13 @@ export class CliWorkerProvider implements WorkerProvider {
229
247
  options.workerKillGracePeriodMs,
230
248
  DEFAULT_KILL_GRACE_PERIOD_MS,
231
249
  );
250
+ this.repoRoot =
251
+ typeof options.repoRoot === 'string' && options.repoRoot.length > 0 ? options.repoRoot : null;
252
+ this.rawAgentLogsEnabled = options.rawAgentLogsEnabled === true;
253
+ this.rawAgentLogsRetentionDays = asPositiveInteger(
254
+ options.rawAgentLogsRetentionDays,
255
+ DEFAULT_RAW_LOG_RETENTION_DAYS,
256
+ );
232
257
  this.workerCapabilities = resolveCliWorkerCapabilities(selection);
233
258
  this.runTemplate = resolveRunTemplate(selection);
234
259
  this.attachTemplate = resolveAttachTemplate(selection);
@@ -290,18 +315,7 @@ export class CliWorkerProvider implements WorkerProvider {
290
315
  return Promise.resolve({ closed: true });
291
316
  }
292
317
 
293
- async runWorker(input: {
294
- role: string;
295
- feature_id: string;
296
- context_bundle?: Record<string, unknown>;
297
- instructions?: string;
298
- last_tool_results?: Array<Record<string, unknown>>;
299
- runtime_selection?: {
300
- provider: string;
301
- model: string;
302
- provider_config_ref: string | null;
303
- };
304
- }): Promise<Record<string, unknown>> {
318
+ async runWorker(input: WorkerRunInput): Promise<Record<string, unknown>> {
305
319
  if (!this.runTemplate) {
306
320
  throw providerError(
307
321
  ERROR_CODES.PROVIDER_RUNTIME_UNAVAILABLE,
@@ -364,6 +378,7 @@ export class CliWorkerProvider implements WorkerProvider {
364
378
  try {
365
379
  runResult = await this.commandRunner.run(this.runTemplate.command, args, {
366
380
  stdin: prompt,
381
+ cwd: input.working_directory,
367
382
  timeoutMs: runnerBackstopTimeoutMs,
368
383
  env: buildRunCommandEnv(this.selection),
369
384
  onLifecycleEvent: (event) => {
@@ -391,6 +406,7 @@ export class CliWorkerProvider implements WorkerProvider {
391
406
  effectiveTimeoutMs,
392
407
  watchdogOutcome,
393
408
  );
409
+ await this.persistRawAgentOutput(input.feature_id, input.role, runResult.stdout ?? '');
394
410
 
395
411
  return this.outputParser.parse(runResult.stdout ?? '', {
396
412
  sessionId,
@@ -401,6 +417,10 @@ export class CliWorkerProvider implements WorkerProvider {
401
417
  });
402
418
  }
403
419
 
420
+ getCapabilities(): WorkerProviderCapabilities {
421
+ return CLI_PROVIDER_CAPABILITIES;
422
+ }
423
+
404
424
  private sessionKey(role: string, featureId: string): string {
405
425
  return `${role}::${featureId}`;
406
426
  }
@@ -520,4 +540,54 @@ export class CliWorkerProvider implements WorkerProvider {
520
540
  },
521
541
  );
522
542
  }
543
+
544
+ private async persistRawAgentOutput(
545
+ featureId: string,
546
+ role: string,
547
+ stdout: string,
548
+ ): Promise<void> {
549
+ if (!this.rawAgentLogsEnabled || !this.repoRoot) {
550
+ return;
551
+ }
552
+ if (stdout.trim().length === 0) {
553
+ return;
554
+ }
555
+ if (role !== 'planner' && role !== 'builder' && role !== 'qa') {
556
+ return;
557
+ }
558
+
559
+ const logsDir = path.join(this.repoRoot, '.aop', 'features', featureId, 'logs');
560
+ const filename = `${role}-${Date.now()}.txt`;
561
+ const filePath = path.join(logsDir, filename);
562
+
563
+ try {
564
+ await fs.mkdir(logsDir, { recursive: true });
565
+ await fs.writeFile(filePath, stdout, 'utf8');
566
+ await this.cleanupExpiredRawAgentLogs(logsDir);
567
+ } catch {
568
+ // Raw log persistence must not fail orchestration flow.
569
+ }
570
+ }
571
+
572
+ private async cleanupExpiredRawAgentLogs(logsDir: string): Promise<void> {
573
+ const cutoffMs = Date.now() - this.rawAgentLogsRetentionDays * 24 * 60 * 60 * 1000;
574
+ try {
575
+ const files = await fs.readdir(logsDir);
576
+ await Promise.all(
577
+ files.map(async (filename) => {
578
+ const match = filename.match(/^(planner|builder|qa)-([0-9]{13})\.txt$/);
579
+ if (!match) {
580
+ return;
581
+ }
582
+ const unixMs = Number.parseInt(match[2], 10);
583
+ if (Number.isNaN(unixMs) || unixMs >= cutoffMs) {
584
+ return;
585
+ }
586
+ await fs.unlink(path.join(logsDir, filename)).catch(() => undefined);
587
+ }),
588
+ );
589
+ } catch {
590
+ // Best effort cleanup.
591
+ }
592
+ }
523
593
  }
@@ -225,6 +225,32 @@ export interface WorkerSession {
225
225
  system_prompt_loaded: boolean;
226
226
  }
227
227
 
228
+ export type ExecutionMode = 'deterministic' | 'interactive';
229
+
230
+ export interface WorkerProviderCapabilities {
231
+ supportsInteractiveMode: boolean;
232
+ supportsWorkingDirectory: boolean;
233
+ supportsPauseResume: boolean;
234
+ supportsMessagePassing: boolean;
235
+ supportsAcknowledgment: boolean;
236
+ }
237
+
238
+ export interface WorkerRunInput {
239
+ role: string;
240
+ feature_id: string;
241
+ context_bundle?: Record<string, unknown>;
242
+ instructions?: string;
243
+ last_tool_results?: Array<Record<string, unknown>>;
244
+ runtime_selection?: {
245
+ provider: string;
246
+ model: string;
247
+ provider_config_ref: string | null;
248
+ };
249
+ execution_mode?: ExecutionMode;
250
+ working_directory?: string;
251
+ pause_resume_protocol?: 'signal' | 'message' | 'none';
252
+ }
253
+
228
254
  /**
229
255
  * Worker provider interface for agent session management.
230
256
  *
@@ -293,18 +319,7 @@ export interface WorkerProvider {
293
319
  * @param input - execution input with role, feature, context, instructions
294
320
  * @returns Worker output with tool calls and text
295
321
  */
296
- runWorker(input: {
297
- role: string;
298
- feature_id: string;
299
- context_bundle?: Record<string, unknown>;
300
- instructions?: string;
301
- last_tool_results?: Array<Record<string, unknown>>;
302
- runtime_selection?: {
303
- provider: string;
304
- model: string;
305
- provider_config_ref: string | null;
306
- };
307
- }): Promise<Record<string, unknown>>;
322
+ runWorker(input: WorkerRunInput): Promise<Record<string, unknown>>;
308
323
 
309
324
  /**
310
325
  * Sends a message to an active session (optional).
@@ -328,6 +343,11 @@ export interface WorkerProvider {
328
343
  * @returns Session status
329
344
  */
330
345
  getSessionInfo?(sessionId: string): Promise<{ active: boolean; provider: string }>;
346
+
347
+ /**
348
+ * Reports provider runtime capabilities (optional).
349
+ */
350
+ getCapabilities?(): WorkerProviderCapabilities;
331
351
  }
332
352
 
333
353
  /**
@@ -367,6 +387,7 @@ export interface ProviderCommandRunnerOptions {
367
387
  interactive?: boolean;
368
388
  stdin?: string;
369
389
  env?: NodeJS.ProcessEnv;
390
+ cwd?: string;
370
391
  timeoutMs?: number;
371
392
  onLifecycleEvent?: (event: WorkerLifecycleEvent) => void;
372
393
  registerKill?: (kill: (signal: 'SIGTERM' | 'SIGKILL') => void) => void;
@@ -391,6 +412,7 @@ export class NodeProviderCommandRunner implements ProviderCommandRunner {
391
412
  try {
392
413
  child = spawn(command, args, {
393
414
  env: options.env,
415
+ cwd: options.cwd,
394
416
  stdio: options.interactive ? 'inherit' : ['pipe', 'pipe', 'pipe'],
395
417
  });
396
418
  } catch {
@@ -638,18 +660,7 @@ export class NullWorkerProvider implements WorkerProvider {
638
660
  role,
639
661
  feature_id,
640
662
  context_bundle,
641
- }: {
642
- role: string;
643
- feature_id: string;
644
- context_bundle?: Record<string, unknown>;
645
- instructions?: string;
646
- last_tool_results?: Array<Record<string, unknown>>;
647
- runtime_selection?: {
648
- provider: string;
649
- model: string;
650
- provider_config_ref: string | null;
651
- };
652
- }): Promise<Record<string, unknown>> {
663
+ }: WorkerRunInput): Promise<Record<string, unknown>> {
653
664
  if (role === 'planner') {
654
665
  const state = context_bundle?.state;
655
666
  const frontMatter =
@@ -715,4 +726,14 @@ export class NullWorkerProvider implements WorkerProvider {
715
726
  }
716
727
  throw commandFailureError(this.selection.provider, kind, details, ERROR_CODES.INTERNAL_ERROR);
717
728
  }
729
+
730
+ getCapabilities(): WorkerProviderCapabilities {
731
+ return {
732
+ supportsInteractiveMode: false,
733
+ supportsWorkingDirectory: false,
734
+ supportsPauseResume: false,
735
+ supportsMessagePassing: Boolean(this.sendMessage),
736
+ supportsAcknowledgment: false,
737
+ };
738
+ }
718
739
  }
@@ -31,6 +31,11 @@ export interface WorkerProviderFactoryPolicy {
31
31
  require_live_provider_for_run: boolean;
32
32
  }
33
33
 
34
+ export interface WorkerProviderFactoryObservability {
35
+ raw_agent_logs_enabled: boolean;
36
+ raw_agent_logs_retention_days: number;
37
+ }
38
+
34
39
  export interface WorkerProviderFactoryRuntime {
35
40
  worker_response_timeout_ms: number;
36
41
  worker_spawn_timeout_ms: number;
@@ -49,7 +54,9 @@ export interface CreateWorkerProviderInput {
49
54
  mode: WorkerProviderMode;
50
55
  context: ProviderCommandContext;
51
56
  policy: WorkerProviderFactoryPolicy;
57
+ observability?: WorkerProviderFactoryObservability;
52
58
  runtime: WorkerProviderFactoryRuntime;
59
+ repoRoot?: string;
53
60
  commandRunner?: ProviderCommandRunner;
54
61
  }
55
62
 
@@ -81,6 +88,11 @@ interface PolicyConfigInput {
81
88
  no_progress_action?: unknown;
82
89
  }
83
90
 
91
+ interface ObservabilityConfigInput {
92
+ raw_agent_logs_enabled?: unknown;
93
+ raw_agent_logs_retention_days?: unknown;
94
+ }
95
+
84
96
  function asPositiveInteger(value: unknown, fallback: number): number {
85
97
  if (typeof value !== 'number' || !Number.isFinite(value) || value < 1) {
86
98
  return fallback;
@@ -175,6 +187,15 @@ export function resolveWorkerProviderPolicy(
175
187
  };
176
188
  }
177
189
 
190
+ export function resolveWorkerProviderObservability(
191
+ input: ObservabilityConfigInput | null | undefined,
192
+ ): WorkerProviderFactoryObservability {
193
+ return {
194
+ raw_agent_logs_enabled: input?.raw_agent_logs_enabled === true,
195
+ raw_agent_logs_retention_days: asPositiveInteger(input?.raw_agent_logs_retention_days, 60),
196
+ };
197
+ }
198
+
178
199
  export function resolveMalformedWorkerOutputAction(
179
200
  input: PolicyConfigInput | null | undefined,
180
201
  ): WorkerMalformedOutputAction {
@@ -219,7 +240,12 @@ export function resolvePlanConformanceStallAction(
219
240
 
220
241
  export class DefaultWorkerProviderFactory implements WorkerProviderFactory {
221
242
  create(input: CreateWorkerProviderInput): WorkerProvider {
222
- const { selection, mode, context, policy, runtime, commandRunner } = input;
243
+ const { selection, mode, context, policy, runtime, observability, repoRoot, commandRunner } =
244
+ input;
245
+ const resolvedObservability = observability ?? {
246
+ raw_agent_logs_enabled: false,
247
+ raw_agent_logs_retention_days: 60,
248
+ };
223
249
 
224
250
  if (!SUPPORTED_PROVIDERS.has(selection.provider)) {
225
251
  throw toAppError(ERROR_CODES.UNSUPPORTED_AGENT_PROVIDER, 'Unsupported worker provider', {
@@ -259,6 +285,9 @@ export class DefaultWorkerProviderFactory implements WorkerProviderFactory {
259
285
  return new CliWorkerProvider(selection, {
260
286
  outputParser: new CodexOutputParser(),
261
287
  commandRunner,
288
+ repoRoot,
289
+ rawAgentLogsEnabled: resolvedObservability.raw_agent_logs_enabled,
290
+ rawAgentLogsRetentionDays: resolvedObservability.raw_agent_logs_retention_days,
262
291
  workerResponseTimeoutMs: runtime.worker_response_timeout_ms,
263
292
  workerSpawnTimeoutMs: runtime.worker_spawn_timeout_ms,
264
293
  workerIdleTimeoutMs: runtime.worker_idle_timeout_ms,
@@ -271,6 +300,9 @@ export class DefaultWorkerProviderFactory implements WorkerProviderFactory {
271
300
  return new CliWorkerProvider(selection, {
272
301
  outputParser: new ClaudeOutputParser(),
273
302
  commandRunner,
303
+ repoRoot,
304
+ rawAgentLogsEnabled: resolvedObservability.raw_agent_logs_enabled,
305
+ rawAgentLogsRetentionDays: resolvedObservability.raw_agent_logs_retention_days,
274
306
  workerResponseTimeoutMs: runtime.worker_response_timeout_ms,
275
307
  workerSpawnTimeoutMs: runtime.worker_spawn_timeout_ms,
276
308
  workerIdleTimeoutMs: runtime.worker_idle_timeout_ms,
@@ -287,6 +319,9 @@ export class DefaultWorkerProviderFactory implements WorkerProviderFactory {
287
319
  return new CliWorkerProvider(selection, {
288
320
  outputParser: new GenericCliOutputParser(),
289
321
  commandRunner,
322
+ repoRoot,
323
+ rawAgentLogsEnabled: resolvedObservability.raw_agent_logs_enabled,
324
+ rawAgentLogsRetentionDays: resolvedObservability.raw_agent_logs_retention_days,
290
325
  workerResponseTimeoutMs: runtime.worker_response_timeout_ms,
291
326
  workerSpawnTimeoutMs: runtime.worker_spawn_timeout_ms,
292
327
  workerIdleTimeoutMs: runtime.worker_idle_timeout_ms,