@kbediako/codex-orchestrator 0.1.2 → 0.1.4

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 (73) hide show
  1. package/README.md +15 -8
  2. package/dist/bin/codex-orchestrator.js +252 -121
  3. package/dist/orchestrator/src/cli/config/delegationConfig.js +485 -0
  4. package/dist/orchestrator/src/cli/config/userConfig.js +86 -12
  5. package/dist/orchestrator/src/cli/control/confirmations.js +262 -0
  6. package/dist/orchestrator/src/cli/control/controlServer.js +1476 -0
  7. package/dist/orchestrator/src/cli/control/controlState.js +46 -0
  8. package/dist/orchestrator/src/cli/control/controlWatcher.js +222 -0
  9. package/dist/orchestrator/src/cli/control/delegationTokens.js +62 -0
  10. package/dist/orchestrator/src/cli/control/questions.js +106 -0
  11. package/dist/orchestrator/src/cli/delegationServer.js +1368 -0
  12. package/dist/orchestrator/src/cli/events/runEventStream.js +246 -0
  13. package/dist/orchestrator/src/cli/exec/context.js +9 -3
  14. package/dist/orchestrator/src/cli/exec/learning.js +5 -3
  15. package/dist/orchestrator/src/cli/exec/stageRunner.js +30 -5
  16. package/dist/orchestrator/src/cli/exec/summary.js +1 -1
  17. package/dist/orchestrator/src/cli/metrics/metricsAggregator.js +377 -147
  18. package/dist/orchestrator/src/cli/metrics/metricsRecorder.js +3 -5
  19. package/dist/orchestrator/src/cli/orchestrator.js +233 -47
  20. package/dist/orchestrator/src/cli/pipelines/index.js +13 -24
  21. package/dist/orchestrator/src/cli/rlm/prompt.js +31 -0
  22. package/dist/orchestrator/src/cli/rlm/runner.js +177 -0
  23. package/dist/orchestrator/src/cli/rlm/types.js +1 -0
  24. package/dist/orchestrator/src/cli/rlm/validator.js +159 -0
  25. package/dist/orchestrator/src/cli/rlmRunner.js +440 -0
  26. package/dist/orchestrator/src/cli/run/environment.js +4 -11
  27. package/dist/orchestrator/src/cli/run/manifest.js +7 -1
  28. package/dist/orchestrator/src/cli/run/manifestPersister.js +33 -3
  29. package/dist/orchestrator/src/cli/run/runPaths.js +14 -0
  30. package/dist/orchestrator/src/cli/services/commandRunner.js +2 -2
  31. package/dist/orchestrator/src/cli/services/controlPlaneService.js +3 -1
  32. package/dist/orchestrator/src/cli/services/execRuntime.js +1 -2
  33. package/dist/orchestrator/src/cli/services/pipelineResolver.js +33 -2
  34. package/dist/orchestrator/src/cli/services/runPreparation.js +7 -1
  35. package/dist/orchestrator/src/cli/services/schedulerService.js +1 -1
  36. package/dist/orchestrator/src/cli/utils/devtools.js +33 -2
  37. package/dist/orchestrator/src/cli/utils/specGuardRunner.js +3 -1
  38. package/dist/orchestrator/src/cli/utils/strings.js +8 -6
  39. package/dist/orchestrator/src/persistence/ExperienceStore.js +115 -58
  40. package/dist/orchestrator/src/persistence/PersistenceCoordinator.js +8 -8
  41. package/dist/orchestrator/src/persistence/TaskStateStore.js +3 -2
  42. package/dist/orchestrator/src/persistence/lockFile.js +26 -1
  43. package/dist/orchestrator/src/persistence/sanitizeIdentifier.js +1 -1
  44. package/dist/orchestrator/src/sync/CloudSyncWorker.js +17 -4
  45. package/dist/packages/orchestrator/src/exec/stdio.js +112 -0
  46. package/dist/packages/orchestrator/src/exec/unified-exec.js +1 -1
  47. package/dist/packages/orchestrator/src/index.js +1 -0
  48. package/dist/packages/orchestrator/src/telemetry/otel-exporter.js +21 -0
  49. package/dist/packages/shared/design-artifacts/writer.js +4 -14
  50. package/dist/packages/shared/streams/stdio.js +2 -112
  51. package/dist/packages/shared/utils/strings.js +17 -0
  52. package/dist/scripts/design/pipeline/advanced-assets.js +1 -1
  53. package/dist/scripts/design/pipeline/context.js +5 -5
  54. package/dist/scripts/design/pipeline/extract.js +9 -6
  55. package/dist/scripts/design/pipeline/{optionalDeps.js → optional-deps.js} +49 -38
  56. package/dist/scripts/design/pipeline/permit.js +59 -0
  57. package/dist/scripts/design/pipeline/toolkit/common.js +18 -32
  58. package/dist/scripts/design/pipeline/toolkit/reference.js +1 -1
  59. package/dist/scripts/design/pipeline/toolkit/snapshot.js +1 -1
  60. package/dist/scripts/design/pipeline/visual-regression.js +2 -11
  61. package/dist/scripts/lib/cli-args.js +53 -0
  62. package/dist/scripts/lib/docs-helpers.js +111 -0
  63. package/dist/scripts/lib/npm-pack.js +20 -0
  64. package/dist/scripts/lib/run-manifests.js +160 -0
  65. package/package.json +7 -2
  66. package/dist/orchestrator/src/cli/pipelines/defaultDiagnostics.js +0 -32
  67. package/dist/orchestrator/src/cli/pipelines/designReference.js +0 -72
  68. package/dist/orchestrator/src/cli/pipelines/hiFiDesignToolkit.js +0 -71
  69. package/dist/orchestrator/src/cli/utils/jsonlWriter.js +0 -10
  70. package/dist/orchestrator/src/control-plane/index.js +0 -3
  71. package/dist/orchestrator/src/persistence/identifierGuards.js +0 -1
  72. package/dist/orchestrator/src/persistence/writeAtomicFile.js +0 -4
  73. package/dist/orchestrator/src/scheduler/index.js +0 -1
@@ -0,0 +1,246 @@
1
+ import { createWriteStream } from 'node:fs';
2
+ import { mkdir, readFile } from 'node:fs/promises';
3
+ import { isoTimestamp } from '../utils/time.js';
4
+ const SCHEMA_VERSION = 1;
5
+ export class RunEventStream {
6
+ seq;
7
+ stream;
8
+ now;
9
+ taskId;
10
+ runId;
11
+ writeQueue = Promise.resolve();
12
+ streamError = null;
13
+ constructor(stream, options) {
14
+ this.stream = stream;
15
+ this.taskId = options.taskId;
16
+ this.runId = options.runId;
17
+ this.now = options.now;
18
+ this.seq = options.initialSeq;
19
+ this.stream.on('error', (error) => {
20
+ this.streamError = error;
21
+ });
22
+ }
23
+ static async create(options) {
24
+ await mkdir(options.paths.runDir, { recursive: true });
25
+ const initialSeq = await readLastSeq(options.paths.eventsPath);
26
+ const stream = createWriteStream(options.paths.eventsPath, { flags: 'a' });
27
+ return new RunEventStream(stream, {
28
+ taskId: options.taskId,
29
+ runId: options.runId,
30
+ now: options.now ?? isoTimestamp,
31
+ initialSeq
32
+ });
33
+ }
34
+ async append(input) {
35
+ if (this.streamError) {
36
+ throw this.streamError;
37
+ }
38
+ const entry = {
39
+ schema_version: SCHEMA_VERSION,
40
+ seq: this.nextSeq(),
41
+ timestamp: input.timestamp ?? this.now(),
42
+ task_id: this.taskId,
43
+ run_id: this.runId,
44
+ event: input.event,
45
+ actor: input.actor ?? 'runner',
46
+ payload: input.payload ?? null
47
+ };
48
+ const line = `${JSON.stringify(entry)}\n`;
49
+ this.writeQueue = this.writeQueue
50
+ .then(() => new Promise((resolve, reject) => {
51
+ this.stream.write(line, (error) => {
52
+ if (error) {
53
+ reject(error);
54
+ return;
55
+ }
56
+ resolve();
57
+ });
58
+ }))
59
+ .catch((error) => {
60
+ this.streamError = error;
61
+ throw error;
62
+ });
63
+ await this.writeQueue;
64
+ if (this.streamError) {
65
+ throw this.streamError;
66
+ }
67
+ return entry;
68
+ }
69
+ async close() {
70
+ try {
71
+ await this.writeQueue;
72
+ }
73
+ catch {
74
+ // Allow close to proceed even if a prior write failed.
75
+ }
76
+ if (this.streamError) {
77
+ this.stream.destroy();
78
+ return;
79
+ }
80
+ await new Promise((resolve, reject) => {
81
+ const onFinish = () => {
82
+ cleanup();
83
+ resolve();
84
+ };
85
+ const onError = (error) => {
86
+ cleanup();
87
+ this.stream.destroy();
88
+ reject(error);
89
+ };
90
+ const cleanup = () => {
91
+ this.stream.off('finish', onFinish);
92
+ this.stream.off('error', onError);
93
+ };
94
+ this.stream.once('finish', onFinish);
95
+ this.stream.once('error', onError);
96
+ this.stream.end();
97
+ });
98
+ }
99
+ nextSeq() {
100
+ this.seq += 1;
101
+ return this.seq;
102
+ }
103
+ }
104
+ export function attachRunEventAdapter(emitter, stream, onEntry, onError) {
105
+ return emitter.on('*', (event) => {
106
+ const mapped = mapRunEvent(event);
107
+ if (!mapped) {
108
+ return;
109
+ }
110
+ void stream
111
+ .append(mapped)
112
+ .then((entry) => {
113
+ onEntry?.(entry);
114
+ })
115
+ .catch((error) => {
116
+ onError?.(error, mapped);
117
+ });
118
+ });
119
+ }
120
+ function mapRunEvent(event) {
121
+ // The public event stream uses "step_*" naming for pipeline stages to match UI terminology.
122
+ switch (event.type) {
123
+ case 'run:started':
124
+ return {
125
+ event: 'run_started',
126
+ actor: 'runner',
127
+ payload: {
128
+ pipeline_id: event.pipelineId,
129
+ pipeline_title: event.pipelineTitle,
130
+ status: event.status,
131
+ manifest_path: event.manifestPath,
132
+ log_path: event.logPath
133
+ }
134
+ };
135
+ case 'stage:started':
136
+ return {
137
+ event: 'step_started',
138
+ actor: 'runner',
139
+ payload: {
140
+ stage_id: event.stageId,
141
+ stage_index: event.stageIndex,
142
+ title: event.title,
143
+ kind: event.kind,
144
+ status: event.status,
145
+ log_path: event.logPath
146
+ }
147
+ };
148
+ case 'stage:completed':
149
+ return {
150
+ event: event.status === 'failed' ? 'step_failed' : 'step_completed',
151
+ actor: 'runner',
152
+ payload: {
153
+ stage_id: event.stageId,
154
+ stage_index: event.stageIndex,
155
+ title: event.title,
156
+ kind: event.kind,
157
+ status: event.status,
158
+ exit_code: event.exitCode,
159
+ summary: event.summary,
160
+ log_path: event.logPath,
161
+ sub_run_id: event.subRunId ?? null
162
+ }
163
+ };
164
+ case 'run:completed':
165
+ return {
166
+ event: 'run_completed',
167
+ actor: 'runner',
168
+ payload: {
169
+ pipeline_id: event.pipelineId,
170
+ status: event.status,
171
+ manifest_path: event.manifestPath,
172
+ run_summary_path: event.runSummaryPath,
173
+ metrics_path: event.metricsPath,
174
+ summary: event.summary
175
+ }
176
+ };
177
+ case 'run:error':
178
+ return {
179
+ event: 'run_failed',
180
+ actor: 'runner',
181
+ payload: {
182
+ pipeline_id: event.pipelineId,
183
+ message: event.message,
184
+ stage_id: event.stageId ?? null
185
+ }
186
+ };
187
+ case 'log':
188
+ return {
189
+ event: 'agent_message',
190
+ actor: 'runner',
191
+ payload: {
192
+ message: event.message,
193
+ level: event.level,
194
+ stage_id: event.stageId ?? null,
195
+ stage_index: event.stageIndex ?? null,
196
+ source: event.source ?? null
197
+ }
198
+ };
199
+ case 'tool:call':
200
+ return {
201
+ event: 'tool_called',
202
+ actor: 'runner',
203
+ payload: {
204
+ tool_name: event.toolName,
205
+ status: event.status,
206
+ message: event.message ?? null,
207
+ attempt: event.attempt ?? null,
208
+ stage_id: event.stageId ?? null,
209
+ stage_index: event.stageIndex ?? null
210
+ }
211
+ };
212
+ default:
213
+ return null;
214
+ }
215
+ }
216
+ async function readLastSeq(pathname) {
217
+ try {
218
+ const raw = await readFile(pathname, 'utf8');
219
+ if (!raw.trim()) {
220
+ return 0;
221
+ }
222
+ const lines = raw.split('\n');
223
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
224
+ const line = lines[index]?.trim();
225
+ if (!line) {
226
+ continue;
227
+ }
228
+ try {
229
+ const parsed = JSON.parse(line);
230
+ if (typeof parsed.seq === 'number' && Number.isFinite(parsed.seq)) {
231
+ return parsed.seq;
232
+ }
233
+ }
234
+ catch {
235
+ continue;
236
+ }
237
+ }
238
+ return 0;
239
+ }
240
+ catch (error) {
241
+ if (error.code === 'ENOENT') {
242
+ return 0;
243
+ }
244
+ throw error;
245
+ }
246
+ }
@@ -1,11 +1,12 @@
1
1
  import process from 'node:process';
2
2
  import { bootstrapManifest } from '../run/manifest.js';
3
3
  import { generateRunId } from '../utils/runId.js';
4
- import { JsonlWriter } from '../utils/jsonlWriter.js';
5
4
  import { ExperienceStore } from '../../persistence/ExperienceStore.js';
5
+ import { EnvUtils } from '../../../../packages/shared/config/index.js';
6
6
  import { createTelemetrySink } from '../../../../packages/orchestrator/src/telemetry/otel-exporter.js';
7
7
  import { createNotificationSink } from '../../../../packages/orchestrator/src/notifications/index.js';
8
8
  import { ManifestPersister, persistManifest } from '../run/manifestPersister.js';
9
+ const MAX_TELEMETRY_EVENTS = EnvUtils.getInt('CODEX_ORCHESTRATOR_TELEMETRY_MAX_EVENTS', 1000);
9
10
  export async function bootstrapExecContext(context, invocation) {
10
11
  const argv = [invocation.command, ...(invocation.args ?? [])];
11
12
  const shellCommand = buildShellCommand(argv);
@@ -39,7 +40,8 @@ export async function bootstrapExecContext(context, invocation) {
39
40
  await persistManifest(paths, manifest, persister, { force: true });
40
41
  const telemetrySink = context.telemetrySink ?? createTelemetrySink({
41
42
  endpoint: invocation.otelEndpoint,
42
- enabled: Boolean(invocation.otelEndpoint)
43
+ enabled: Boolean(invocation.otelEndpoint),
44
+ maxQueueSize: MAX_TELEMETRY_EVENTS
43
45
  });
44
46
  const envNotifications = parseNotificationEnv(process.env.CODEX_ORCHESTRATOR_NOTIFY);
45
47
  const notificationSink = context.notificationSink ??
@@ -47,7 +49,11 @@ export async function bootstrapExecContext(context, invocation) {
47
49
  targets: invocation.notifyTargets,
48
50
  envTargets: envNotifications
49
51
  });
50
- const jsonlWriter = outputMode === 'jsonl' ? new JsonlWriter(stdout) : null;
52
+ const jsonlWriter = outputMode === 'jsonl'
53
+ ? (event) => {
54
+ stdout.write(`${JSON.stringify(event)}\n`);
55
+ }
56
+ : null;
51
57
  return {
52
58
  env,
53
59
  invocation,
@@ -2,9 +2,6 @@ import process from 'node:process';
2
2
  import { persistManifest } from '../run/manifestPersister.js';
3
3
  import { isoTimestamp } from '../utils/time.js';
4
4
  import { logger } from '../../logger.js';
5
- import { runLearningHarvester } from '../../learning/harvester.js';
6
- import { synthesizeScenario } from '../../learning/runner.js';
7
- import { runScenarioValidation } from '../../learning/validator.js';
8
5
  export async function maybeTriggerLearning(runContext, runStatus) {
9
6
  const enabled = process.env.LEARNING_PIPELINE_ENABLED === '1';
10
7
  if (!enabled) {
@@ -15,6 +12,11 @@ export async function maybeTriggerLearning(runContext, runStatus) {
15
12
  return;
16
13
  }
17
14
  try {
15
+ const [{ runLearningHarvester }, { synthesizeScenario }, { runScenarioValidation }] = await Promise.all([
16
+ import('../../learning/harvester.js'),
17
+ import('../../learning/runner.js'),
18
+ import('../../learning/validator.js')
19
+ ]);
18
20
  const harvester = await runLearningHarvester(runContext.manifest, {
19
21
  repoRoot: runContext.env.repoRoot,
20
22
  runsRoot: runContext.env.runsRoot,
@@ -1,20 +1,45 @@
1
- import { runCommandStage } from '../services/commandRunner.js';
1
+ import { runCommandStage, MAX_CAPTURED_CHUNK_EVENTS } from '../services/commandRunner.js';
2
2
  import { serializeExecEvent } from '../../../../packages/shared/events/serializer.js';
3
3
  import { sanitizeToolRunRecord } from '../../../../packages/shared/manifest/writer.js';
4
4
  export async function runExecStage(context) {
5
5
  let runResultSummary = null;
6
6
  let toolRecord = null;
7
+ const maxChunkEvents = MAX_CAPTURED_CHUNK_EVENTS;
8
+ let capturedChunkEvents = 0;
9
+ const shouldCaptureEvent = (event) => {
10
+ if (maxChunkEvents <= 0) {
11
+ return true;
12
+ }
13
+ if (event.type !== 'exec:chunk') {
14
+ return true;
15
+ }
16
+ if (capturedChunkEvents >= maxChunkEvents) {
17
+ return false;
18
+ }
19
+ capturedChunkEvents += 1;
20
+ return true;
21
+ };
7
22
  const hooks = {
8
23
  onEvent: (event) => {
9
- context.execEvents.push(event);
10
- const serialized = serializeExecEvent(event);
11
- context.telemetryTasks.push(Promise.resolve(context.telemetrySink.record(serialized)).then(() => undefined));
24
+ const captureEvent = shouldCaptureEvent(event);
25
+ let serialized = null;
26
+ const getSerialized = () => {
27
+ if (!serialized) {
28
+ serialized = serializeExecEvent(event);
29
+ }
30
+ return serialized;
31
+ };
12
32
  if (context.outputMode === 'jsonl' && context.jsonlWriter) {
13
- context.jsonlWriter.write(serialized);
33
+ context.jsonlWriter(getSerialized());
14
34
  }
15
35
  else if (context.outputMode === 'interactive') {
16
36
  streamInteractive(context.stdout, context.stderr, event);
17
37
  }
38
+ if (captureEvent) {
39
+ context.execEvents.push(event);
40
+ const payload = getSerialized();
41
+ context.telemetryTasks.push(Promise.resolve(context.telemetrySink.record(payload)).then(() => undefined));
42
+ }
18
43
  },
19
44
  onResult: (result) => {
20
45
  runResultSummary = {
@@ -61,7 +61,7 @@ export function createRunSummaryPayload(params) {
61
61
  }
62
62
  export function renderRunOutput(context, summaryPayload, summaryEvent) {
63
63
  if (context.outputMode === 'jsonl' && context.jsonlWriter) {
64
- context.jsonlWriter.write(summaryEvent);
64
+ context.jsonlWriter(summaryEvent);
65
65
  return;
66
66
  }
67
67
  if (context.outputMode === 'json') {