@kbediako/codex-orchestrator 0.1.0

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 (150) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +238 -0
  3. package/dist/bin/codex-orchestrator.js +507 -0
  4. package/dist/orchestrator/src/agents/builder.js +16 -0
  5. package/dist/orchestrator/src/agents/index.js +4 -0
  6. package/dist/orchestrator/src/agents/planner.js +17 -0
  7. package/dist/orchestrator/src/agents/reviewer.js +13 -0
  8. package/dist/orchestrator/src/agents/tester.js +13 -0
  9. package/dist/orchestrator/src/cli/adapters/CommandBuilder.js +20 -0
  10. package/dist/orchestrator/src/cli/adapters/CommandPlanner.js +164 -0
  11. package/dist/orchestrator/src/cli/adapters/CommandReviewer.js +32 -0
  12. package/dist/orchestrator/src/cli/adapters/CommandTester.js +33 -0
  13. package/dist/orchestrator/src/cli/adapters/index.js +4 -0
  14. package/dist/orchestrator/src/cli/config/userConfig.js +28 -0
  15. package/dist/orchestrator/src/cli/doctor.js +48 -0
  16. package/dist/orchestrator/src/cli/events/runEvents.js +84 -0
  17. package/dist/orchestrator/src/cli/exec/command.js +56 -0
  18. package/dist/orchestrator/src/cli/exec/context.js +108 -0
  19. package/dist/orchestrator/src/cli/exec/experience.js +77 -0
  20. package/dist/orchestrator/src/cli/exec/finalization.js +140 -0
  21. package/dist/orchestrator/src/cli/exec/learning.js +62 -0
  22. package/dist/orchestrator/src/cli/exec/stageRunner.js +71 -0
  23. package/dist/orchestrator/src/cli/exec/summary.js +109 -0
  24. package/dist/orchestrator/src/cli/exec/telemetry.js +18 -0
  25. package/dist/orchestrator/src/cli/exec/tfgrpo.js +200 -0
  26. package/dist/orchestrator/src/cli/exec/tfgrpoArtifacts.js +19 -0
  27. package/dist/orchestrator/src/cli/exec/types.js +1 -0
  28. package/dist/orchestrator/src/cli/init.js +64 -0
  29. package/dist/orchestrator/src/cli/mcp.js +124 -0
  30. package/dist/orchestrator/src/cli/metrics/metricsAggregator.js +404 -0
  31. package/dist/orchestrator/src/cli/metrics/metricsRecorder.js +138 -0
  32. package/dist/orchestrator/src/cli/orchestrator.js +554 -0
  33. package/dist/orchestrator/src/cli/pipelines/defaultDiagnostics.js +32 -0
  34. package/dist/orchestrator/src/cli/pipelines/designReference.js +72 -0
  35. package/dist/orchestrator/src/cli/pipelines/hiFiDesignToolkit.js +71 -0
  36. package/dist/orchestrator/src/cli/pipelines/index.js +34 -0
  37. package/dist/orchestrator/src/cli/run/environment.js +24 -0
  38. package/dist/orchestrator/src/cli/run/manifest.js +367 -0
  39. package/dist/orchestrator/src/cli/run/manifestPersister.js +88 -0
  40. package/dist/orchestrator/src/cli/run/runPaths.js +30 -0
  41. package/dist/orchestrator/src/cli/selfCheck.js +12 -0
  42. package/dist/orchestrator/src/cli/services/commandRunner.js +420 -0
  43. package/dist/orchestrator/src/cli/services/controlPlaneService.js +107 -0
  44. package/dist/orchestrator/src/cli/services/execRuntime.js +69 -0
  45. package/dist/orchestrator/src/cli/services/pipelineResolver.js +47 -0
  46. package/dist/orchestrator/src/cli/services/runPreparation.js +82 -0
  47. package/dist/orchestrator/src/cli/services/runSummaryWriter.js +35 -0
  48. package/dist/orchestrator/src/cli/services/schedulerService.js +42 -0
  49. package/dist/orchestrator/src/cli/tasks/taskMetadata.js +19 -0
  50. package/dist/orchestrator/src/cli/telemetry/schema.js +8 -0
  51. package/dist/orchestrator/src/cli/types.js +1 -0
  52. package/dist/orchestrator/src/cli/ui/HudApp.js +112 -0
  53. package/dist/orchestrator/src/cli/ui/controller.js +26 -0
  54. package/dist/orchestrator/src/cli/ui/store.js +240 -0
  55. package/dist/orchestrator/src/cli/utils/enforcementMode.js +12 -0
  56. package/dist/orchestrator/src/cli/utils/fs.js +8 -0
  57. package/dist/orchestrator/src/cli/utils/interactive.js +25 -0
  58. package/dist/orchestrator/src/cli/utils/jsonlWriter.js +10 -0
  59. package/dist/orchestrator/src/cli/utils/optionalDeps.js +30 -0
  60. package/dist/orchestrator/src/cli/utils/packageInfo.js +25 -0
  61. package/dist/orchestrator/src/cli/utils/planFormatter.js +49 -0
  62. package/dist/orchestrator/src/cli/utils/runId.js +7 -0
  63. package/dist/orchestrator/src/cli/utils/specGuardRunner.js +26 -0
  64. package/dist/orchestrator/src/cli/utils/strings.js +8 -0
  65. package/dist/orchestrator/src/cli/utils/time.js +6 -0
  66. package/dist/orchestrator/src/control-plane/drift-reporter.js +109 -0
  67. package/dist/orchestrator/src/control-plane/index.js +3 -0
  68. package/dist/orchestrator/src/control-plane/request-builder.js +217 -0
  69. package/dist/orchestrator/src/control-plane/types.js +1 -0
  70. package/dist/orchestrator/src/control-plane/validator.js +50 -0
  71. package/dist/orchestrator/src/credentials/CredentialBroker.js +1 -0
  72. package/dist/orchestrator/src/events/EventBus.js +25 -0
  73. package/dist/orchestrator/src/learning/crystalizer.js +108 -0
  74. package/dist/orchestrator/src/learning/harvester.js +146 -0
  75. package/dist/orchestrator/src/learning/manifest.js +56 -0
  76. package/dist/orchestrator/src/learning/runner.js +177 -0
  77. package/dist/orchestrator/src/learning/validator.js +164 -0
  78. package/dist/orchestrator/src/logger.js +20 -0
  79. package/dist/orchestrator/src/manager.js +388 -0
  80. package/dist/orchestrator/src/persistence/ArtifactStager.js +95 -0
  81. package/dist/orchestrator/src/persistence/ExperienceStore.js +210 -0
  82. package/dist/orchestrator/src/persistence/PersistenceCoordinator.js +65 -0
  83. package/dist/orchestrator/src/persistence/RunManifestWriter.js +23 -0
  84. package/dist/orchestrator/src/persistence/TaskStateStore.js +172 -0
  85. package/dist/orchestrator/src/persistence/identifierGuards.js +1 -0
  86. package/dist/orchestrator/src/persistence/lockFile.js +26 -0
  87. package/dist/orchestrator/src/persistence/sanitizeIdentifier.js +26 -0
  88. package/dist/orchestrator/src/persistence/sanitizeRunId.js +8 -0
  89. package/dist/orchestrator/src/persistence/sanitizeTaskId.js +8 -0
  90. package/dist/orchestrator/src/persistence/writeAtomicFile.js +4 -0
  91. package/dist/orchestrator/src/privacy/guard.js +111 -0
  92. package/dist/orchestrator/src/scheduler/index.js +1 -0
  93. package/dist/orchestrator/src/scheduler/plan.js +171 -0
  94. package/dist/orchestrator/src/scheduler/types.js +1 -0
  95. package/dist/orchestrator/src/sync/CloudRunsClient.js +1 -0
  96. package/dist/orchestrator/src/sync/CloudRunsHttpClient.js +82 -0
  97. package/dist/orchestrator/src/sync/CloudSyncWorker.js +206 -0
  98. package/dist/orchestrator/src/sync/createCloudSyncWorker.js +15 -0
  99. package/dist/orchestrator/src/types.js +1 -0
  100. package/dist/orchestrator/src/utils/atomicWrite.js +15 -0
  101. package/dist/orchestrator/src/utils/errorMessage.js +14 -0
  102. package/dist/orchestrator/src/utils/executionMode.js +69 -0
  103. package/dist/packages/control-plane-schemas/src/index.js +1 -0
  104. package/dist/packages/control-plane-schemas/src/run-request.js +548 -0
  105. package/dist/packages/orchestrator/src/exec/handle-service.js +203 -0
  106. package/dist/packages/orchestrator/src/exec/session-manager.js +147 -0
  107. package/dist/packages/orchestrator/src/exec/unified-exec.js +432 -0
  108. package/dist/packages/orchestrator/src/index.js +3 -0
  109. package/dist/packages/orchestrator/src/instructions/loader.js +101 -0
  110. package/dist/packages/orchestrator/src/instructions/promptPacks.js +151 -0
  111. package/dist/packages/orchestrator/src/notifications/index.js +74 -0
  112. package/dist/packages/orchestrator/src/telemetry/otel-exporter.js +142 -0
  113. package/dist/packages/orchestrator/src/tool-orchestrator.js +161 -0
  114. package/dist/packages/sdk-node/src/orchestrator.js +195 -0
  115. package/dist/packages/shared/config/designConfig.js +495 -0
  116. package/dist/packages/shared/config/env.js +37 -0
  117. package/dist/packages/shared/config/index.js +2 -0
  118. package/dist/packages/shared/design-artifacts/writer.js +221 -0
  119. package/dist/packages/shared/events/serializer.js +84 -0
  120. package/dist/packages/shared/events/types.js +1 -0
  121. package/dist/packages/shared/manifest/artifactUtils.js +36 -0
  122. package/dist/packages/shared/manifest/designArtifacts.js +665 -0
  123. package/dist/packages/shared/manifest/fileIO.js +29 -0
  124. package/dist/packages/shared/manifest/toolRuns.js +78 -0
  125. package/dist/packages/shared/manifest/toolkitArtifacts.js +223 -0
  126. package/dist/packages/shared/manifest/types.js +5 -0
  127. package/dist/packages/shared/manifest/validator.js +73 -0
  128. package/dist/packages/shared/manifest/writer.js +2 -0
  129. package/dist/packages/shared/streams/stdio.js +112 -0
  130. package/dist/scripts/design/pipeline/advanced-assets.js +466 -0
  131. package/dist/scripts/design/pipeline/componentize.js +74 -0
  132. package/dist/scripts/design/pipeline/context.js +34 -0
  133. package/dist/scripts/design/pipeline/extract.js +249 -0
  134. package/dist/scripts/design/pipeline/optionalDeps.js +107 -0
  135. package/dist/scripts/design/pipeline/prepare.js +46 -0
  136. package/dist/scripts/design/pipeline/reference.js +94 -0
  137. package/dist/scripts/design/pipeline/state.js +206 -0
  138. package/dist/scripts/design/pipeline/toolkit/common.js +94 -0
  139. package/dist/scripts/design/pipeline/toolkit/extract.js +258 -0
  140. package/dist/scripts/design/pipeline/toolkit/publish.js +202 -0
  141. package/dist/scripts/design/pipeline/toolkit/publishActions.js +12 -0
  142. package/dist/scripts/design/pipeline/toolkit/reference.js +846 -0
  143. package/dist/scripts/design/pipeline/toolkit/snapshot.js +882 -0
  144. package/dist/scripts/design/pipeline/toolkit/tokens.js +456 -0
  145. package/dist/scripts/design/pipeline/visual-regression.js +137 -0
  146. package/dist/scripts/design/pipeline/write-artifacts.js +61 -0
  147. package/package.json +97 -0
  148. package/schemas/manifest.json +1064 -0
  149. package/templates/README.md +12 -0
  150. package/templates/codex/mcp-client.json +8 -0
@@ -0,0 +1,432 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { ToolInvocationFailedError } from '../tool-orchestrator.js';
4
+ import { createStdioTracker } from '../../../shared/streams/stdio.js';
5
+ export class UnifiedExecRunner {
6
+ orchestrator;
7
+ sessionManager;
8
+ executor;
9
+ maxBufferBytes;
10
+ now;
11
+ idGenerator;
12
+ listeners = new Set();
13
+ handleService;
14
+ constructor(options) {
15
+ this.orchestrator = options.orchestrator;
16
+ this.sessionManager = options.sessionManager;
17
+ this.executor = options.executor ?? defaultExecutor;
18
+ this.maxBufferBytes = options.maxBufferBytes ?? 64 * 1024;
19
+ this.now = options.now ?? (() => new Date());
20
+ this.idGenerator = options.idGenerator ?? (() => randomUUID());
21
+ this.handleService = options.handleService;
22
+ }
23
+ on(listener) {
24
+ this.listeners.add(listener);
25
+ return () => {
26
+ this.listeners.delete(listener);
27
+ };
28
+ }
29
+ async run(options) {
30
+ const args = options.args ?? [];
31
+ const invocationId = options.invocationId ?? this.idGenerator();
32
+ const correlationId = this.idGenerator();
33
+ const issuedHandle = this.handleService ? this.handleService.issueHandle(correlationId) : undefined;
34
+ const handleId = issuedHandle?.id;
35
+ let handleClosed = false;
36
+ const reuse = options.reuseSession ?? Boolean(options.sessionId);
37
+ const lease = await this.sessionManager.acquire({
38
+ id: options.sessionId,
39
+ reuse,
40
+ persist: options.persistSession,
41
+ env: options.env
42
+ });
43
+ const events = [];
44
+ const captureState = createEventCaptureState(options.eventCapture);
45
+ let chunkSequence = 0;
46
+ const sandboxOptions = options.sandbox
47
+ ? this.decorateSandboxOptions(options.sandbox, correlationId, events, handleId, captureState)
48
+ : undefined;
49
+ const metadata = {
50
+ ...options.metadata,
51
+ command: options.command,
52
+ args,
53
+ cwd: options.cwd,
54
+ sessionId: lease.id,
55
+ correlationId,
56
+ persisted: lease.persisted,
57
+ handleId
58
+ };
59
+ const toolInvocation = {
60
+ id: invocationId,
61
+ tool: options.toolId ?? 'exec',
62
+ description: options.description,
63
+ approvalRequired: options.approvalRequired,
64
+ approvalKey: options.approvalKey,
65
+ metadata,
66
+ sandbox: sandboxOptions
67
+ };
68
+ let attemptResult;
69
+ let invocationRecord;
70
+ try {
71
+ const result = await this.orchestrator.invoke({
72
+ ...toolInvocation,
73
+ run: async ({ attempt, sandboxState }) => {
74
+ const attemptStartedAt = this.now();
75
+ const tracker = createStdioTracker({
76
+ maxBufferBytes: this.maxBufferBytes,
77
+ now: this.now,
78
+ startSequence: chunkSequence
79
+ });
80
+ const beginEvent = this.createBeginEvent({
81
+ attempt,
82
+ correlationId,
83
+ command: options.command,
84
+ args,
85
+ cwd: options.cwd,
86
+ sandboxState,
87
+ sessionId: lease.id,
88
+ persisted: lease.persisted
89
+ });
90
+ await this.publishEvent(beginEvent, events, handleId, captureState);
91
+ let capturedError;
92
+ const attemptSummary = {
93
+ exitCode: null,
94
+ signal: null,
95
+ stdout: '',
96
+ stderr: '',
97
+ durationMs: 0,
98
+ status: 'failed',
99
+ sandboxState,
100
+ sessionId: lease.id
101
+ };
102
+ try {
103
+ const outcome = await this.executor({
104
+ command: options.command,
105
+ args,
106
+ cwd: options.cwd,
107
+ env: lease.envSnapshot,
108
+ attempt,
109
+ sandboxState,
110
+ session: lease,
111
+ onStdout: (chunk) => {
112
+ const sequenced = this.recordChunk(tracker, 'stdout', chunk);
113
+ chunkSequence = sequenced.sequence;
114
+ const chunkEvent = this.createChunkEvent({
115
+ attempt,
116
+ correlationId,
117
+ chunk: sequenced
118
+ });
119
+ void this.publishEvent(chunkEvent, events, handleId, captureState);
120
+ },
121
+ onStderr: (chunk) => {
122
+ const sequenced = this.recordChunk(tracker, 'stderr', chunk);
123
+ chunkSequence = sequenced.sequence;
124
+ const chunkEvent = this.createChunkEvent({
125
+ attempt,
126
+ correlationId,
127
+ chunk: sequenced
128
+ });
129
+ void this.publishEvent(chunkEvent, events, handleId, captureState);
130
+ }
131
+ });
132
+ attemptSummary.exitCode = outcome.exitCode ?? null;
133
+ attemptSummary.signal = outcome.signal ?? null;
134
+ attemptSummary.status = outcome.exitCode === 0 ? 'succeeded' : 'failed';
135
+ attemptSummary.durationMs = outcome.durationMs ?? 0;
136
+ }
137
+ catch (error) {
138
+ capturedError = error;
139
+ }
140
+ finally {
141
+ const completedAt = this.now();
142
+ const derivedDuration = Math.max(0, completedAt.getTime() - attemptStartedAt.getTime());
143
+ if (attemptSummary.durationMs <= 0) {
144
+ attemptSummary.durationMs = derivedDuration;
145
+ }
146
+ attemptSummary.stdout = tracker.getBuffered('stdout');
147
+ attemptSummary.stderr = tracker.getBuffered('stderr');
148
+ const endEvent = this.createEndEvent({
149
+ attempt,
150
+ correlationId,
151
+ summary: attemptSummary
152
+ });
153
+ await this.publishEvent(endEvent, events, handleId, captureState);
154
+ }
155
+ if (capturedError) {
156
+ throw capturedError;
157
+ }
158
+ return attemptSummary;
159
+ }
160
+ });
161
+ attemptResult = result.output;
162
+ invocationRecord = result.record;
163
+ this.attachManifestMetadata(invocationRecord, events, lease, options, correlationId, attemptResult, handleId ?? undefined);
164
+ const runOutcome = {
165
+ correlationId,
166
+ stdout: attemptResult.stdout,
167
+ stderr: attemptResult.stderr,
168
+ exitCode: attemptResult.exitCode,
169
+ signal: attemptResult.signal,
170
+ durationMs: attemptResult.durationMs,
171
+ status: attemptResult.status,
172
+ sandboxState: attemptResult.sandboxState,
173
+ record: invocationRecord,
174
+ events,
175
+ handle: undefined
176
+ };
177
+ if (handleId && this.handleService) {
178
+ this.handleService.close(handleId);
179
+ handleClosed = true;
180
+ runOutcome.handle = this.handleService.getDescriptor(handleId);
181
+ }
182
+ return runOutcome;
183
+ }
184
+ catch (error) {
185
+ if (error instanceof ToolInvocationFailedError &&
186
+ error.record) {
187
+ this.attachManifestMetadata(error.record, events, lease, options, correlationId, attemptResult, handleId ?? undefined);
188
+ }
189
+ if (handleId && this.handleService && !handleClosed) {
190
+ this.handleService.close(handleId);
191
+ handleClosed = true;
192
+ }
193
+ throw error;
194
+ }
195
+ finally {
196
+ if (handleId && this.handleService && !handleClosed) {
197
+ this.handleService.close(handleId);
198
+ }
199
+ await lease.release();
200
+ }
201
+ }
202
+ recordChunk(tracker, stream, chunk) {
203
+ return tracker.push(stream, chunk);
204
+ }
205
+ async publishEvent(event, sink, handleId, captureState) {
206
+ if (shouldCaptureEvent(event, captureState)) {
207
+ sink.push(event);
208
+ }
209
+ if (handleId && this.handleService) {
210
+ await this.handleService.append(handleId, event);
211
+ }
212
+ for (const listener of this.listeners) {
213
+ listener(event);
214
+ }
215
+ }
216
+ createBeginEvent(params) {
217
+ return {
218
+ type: 'exec:begin',
219
+ correlationId: params.correlationId,
220
+ attempt: params.attempt,
221
+ timestamp: this.now().toISOString(),
222
+ payload: {
223
+ command: params.command,
224
+ args: params.args,
225
+ cwd: params.cwd,
226
+ sandboxState: params.sandboxState,
227
+ sessionId: params.sessionId,
228
+ persisted: params.persisted
229
+ }
230
+ };
231
+ }
232
+ createChunkEvent(params) {
233
+ return {
234
+ type: 'exec:chunk',
235
+ correlationId: params.correlationId,
236
+ attempt: params.attempt,
237
+ timestamp: params.chunk.timestamp,
238
+ payload: {
239
+ stream: params.chunk.stream,
240
+ sequence: params.chunk.sequence,
241
+ bytes: params.chunk.bytes,
242
+ data: params.chunk.data
243
+ }
244
+ };
245
+ }
246
+ createEndEvent(params) {
247
+ return {
248
+ type: 'exec:end',
249
+ correlationId: params.correlationId,
250
+ attempt: params.attempt,
251
+ timestamp: this.now().toISOString(),
252
+ payload: {
253
+ exitCode: params.summary.exitCode,
254
+ signal: params.summary.signal,
255
+ durationMs: params.summary.durationMs,
256
+ stdout: params.summary.stdout,
257
+ stderr: params.summary.stderr,
258
+ sandboxState: params.summary.sandboxState,
259
+ sessionId: params.summary.sessionId,
260
+ status: params.summary.status
261
+ }
262
+ };
263
+ }
264
+ createRetryEvent(params) {
265
+ return {
266
+ type: 'exec:retry',
267
+ correlationId: params.correlationId,
268
+ attempt: params.attempt,
269
+ timestamp: this.now().toISOString(),
270
+ payload: {
271
+ delayMs: params.delayMs,
272
+ sandboxState: params.sandboxState,
273
+ errorMessage: getErrorMessage(params.error)
274
+ }
275
+ };
276
+ }
277
+ decorateSandboxOptions(input, correlationId, events, handleId, captureState) {
278
+ const { onRetry, ...rest } = input;
279
+ const decorated = {
280
+ ...rest,
281
+ onRetry: async (context) => {
282
+ const retryEvent = this.createRetryEvent({
283
+ attempt: context.attempt,
284
+ correlationId,
285
+ delayMs: context.delayMs,
286
+ sandboxState: context.sandboxState,
287
+ error: context.error
288
+ });
289
+ await this.publishEvent(retryEvent, events, handleId, captureState);
290
+ if (onRetry) {
291
+ await onRetry.call(input, context);
292
+ }
293
+ }
294
+ };
295
+ return decorated;
296
+ }
297
+ attachManifestMetadata(record, events, lease, options, correlationId, attemptResult, handleId) {
298
+ const execMetadata = {
299
+ ...(record.metadata ?? {}),
300
+ exec: {
301
+ command: options.command,
302
+ args: options.args ?? [],
303
+ cwd: options.cwd,
304
+ sessionId: lease.id,
305
+ persisted: lease.persisted,
306
+ correlationId,
307
+ handleId,
308
+ exitCode: attemptResult?.exitCode ?? null,
309
+ signal: attemptResult?.signal ?? null
310
+ }
311
+ };
312
+ record.metadata = execMetadata;
313
+ if (attemptResult) {
314
+ record.status = attemptResult.status;
315
+ record.sandboxState = attemptResult.sandboxState;
316
+ }
317
+ else if (record.status !== 'failed') {
318
+ record.status = 'failed';
319
+ }
320
+ record.events = events.map((event) => this.toManifestEvent(event));
321
+ }
322
+ toManifestEvent(event) {
323
+ switch (event.type) {
324
+ case 'exec:begin':
325
+ return {
326
+ type: 'exec:begin',
327
+ correlationId: event.correlationId,
328
+ timestamp: event.timestamp,
329
+ attempt: event.attempt,
330
+ command: event.payload.command,
331
+ args: event.payload.args,
332
+ cwd: event.payload.cwd,
333
+ sandboxState: event.payload.sandboxState,
334
+ sessionId: event.payload.sessionId,
335
+ persisted: event.payload.persisted
336
+ };
337
+ case 'exec:chunk':
338
+ return {
339
+ type: 'exec:chunk',
340
+ correlationId: event.correlationId,
341
+ timestamp: event.timestamp,
342
+ attempt: event.attempt,
343
+ stream: event.payload.stream,
344
+ sequence: event.payload.sequence,
345
+ bytes: event.payload.bytes,
346
+ data: event.payload.data
347
+ };
348
+ case 'exec:end':
349
+ return {
350
+ type: 'exec:end',
351
+ correlationId: event.correlationId,
352
+ timestamp: event.timestamp,
353
+ attempt: event.attempt,
354
+ exitCode: event.payload.exitCode,
355
+ signal: event.payload.signal,
356
+ durationMs: event.payload.durationMs,
357
+ stdout: event.payload.stdout,
358
+ stderr: event.payload.stderr,
359
+ sandboxState: event.payload.sandboxState,
360
+ sessionId: event.payload.sessionId,
361
+ status: event.payload.status
362
+ };
363
+ case 'exec:retry':
364
+ return {
365
+ type: 'exec:retry',
366
+ correlationId: event.correlationId,
367
+ timestamp: event.timestamp,
368
+ attempt: event.attempt,
369
+ delayMs: event.payload.delayMs,
370
+ sandboxState: event.payload.sandboxState,
371
+ errorMessage: event.payload.errorMessage
372
+ };
373
+ }
374
+ }
375
+ }
376
+ function createEventCaptureState(options) {
377
+ const maxChunkEvents = options?.maxChunkEvents ?? null;
378
+ if (!maxChunkEvents || maxChunkEvents <= 0) {
379
+ return null;
380
+ }
381
+ return { maxChunkEvents, capturedChunkEvents: 0 };
382
+ }
383
+ function shouldCaptureEvent(event, captureState) {
384
+ if (!captureState || captureState.maxChunkEvents === null) {
385
+ return true;
386
+ }
387
+ if (event.type !== 'exec:chunk') {
388
+ return true;
389
+ }
390
+ if (captureState.capturedChunkEvents >= captureState.maxChunkEvents) {
391
+ return false;
392
+ }
393
+ captureState.capturedChunkEvents += 1;
394
+ return true;
395
+ }
396
+ function getErrorMessage(error) {
397
+ if (error instanceof Error && error.message) {
398
+ return error.message;
399
+ }
400
+ if (typeof error === 'string') {
401
+ return error;
402
+ }
403
+ return String(error);
404
+ }
405
+ const defaultExecutor = async (request) => {
406
+ const child = spawn(request.command, request.args, {
407
+ cwd: request.cwd,
408
+ env: request.env,
409
+ stdio: ['ignore', 'pipe', 'pipe']
410
+ });
411
+ if (!child.stdout || !child.stderr) {
412
+ throw new Error('Command must provide stdout and stderr streams.');
413
+ }
414
+ child.stdout.on('data', request.onStdout);
415
+ child.stderr.on('data', request.onStderr);
416
+ return await new Promise((resolve, reject) => {
417
+ const handleExit = (exitCode, signal) => {
418
+ cleanup();
419
+ resolve({ exitCode, signal });
420
+ };
421
+ const handleError = (error) => {
422
+ cleanup();
423
+ reject(error);
424
+ };
425
+ const cleanup = () => {
426
+ child.off('exit', handleExit);
427
+ child.off('error', handleError);
428
+ };
429
+ child.once('exit', handleExit);
430
+ child.once('error', handleError);
431
+ });
432
+ };
@@ -0,0 +1,3 @@
1
+ export { ToolOrchestrator, SandboxRetryableError, ApprovalRequiredError, ApprovalDeniedError, ToolInvocationFailedError } from './tool-orchestrator.js';
2
+ export { ExecSessionManager } from './exec/session-manager.js';
3
+ export { UnifiedExecRunner } from './exec/unified-exec.js';
@@ -0,0 +1,101 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { join, relative } from 'node:path';
4
+ import { loadPromptPacks } from './promptPacks.js';
5
+ const INSTRUCTION_STAMP_PATTERN = /^(?:\uFEFF)?<!--\s*codex:instruction-stamp\s+([a-f0-9]{64})\s*-->(?:\r?\n)?/i;
6
+ const EXPERIENCE_WORD_LIMIT = 32;
7
+ export async function loadInstructionSet(repoRoot) {
8
+ const candidates = [
9
+ { path: join(repoRoot, 'AGENTS.md'), required: true },
10
+ { path: join(repoRoot, 'docs', 'AGENTS.md'), required: false },
11
+ { path: join(repoRoot, '.agent', 'AGENTS.md'), required: false }
12
+ ];
13
+ const verifier = new InstructionStampVerifier();
14
+ const sources = [];
15
+ for (const candidate of candidates) {
16
+ const source = await readStampedInstruction(candidate.path, repoRoot, verifier, candidate.required);
17
+ if (source) {
18
+ sources.push(source);
19
+ }
20
+ }
21
+ const combined = sources.map((source) => source.content).join('\n\n').trim();
22
+ const hash = combined ? createHash('sha256').update(combined, 'utf8').digest('hex') : '';
23
+ const promptPacks = await loadPromptPacks(repoRoot);
24
+ const experienceMaxWords = resolveExperienceWordLimit();
25
+ return {
26
+ hash,
27
+ sources,
28
+ combined,
29
+ promptPacks,
30
+ experienceMaxWords
31
+ };
32
+ }
33
+ class InstructionStampVerifier {
34
+ verify(raw, relativePath) {
35
+ const match = INSTRUCTION_STAMP_PATTERN.exec(raw);
36
+ if (!match) {
37
+ logInstructionGuard(`Instruction ${relativePath} is missing a codex:instruction-stamp header.`);
38
+ throw new Error(`Instruction ${relativePath} is not stamped.`);
39
+ }
40
+ const declared = match[1].toLowerCase();
41
+ const body = raw.slice(match[0].length);
42
+ const computed = createHash('sha256').update(body, 'utf8').digest('hex');
43
+ if (computed !== declared) {
44
+ logInstructionGuard(`Instruction ${relativePath} stamp mismatch. expected ${declared}, computed ${computed}.`);
45
+ throw new Error(`Instruction ${relativePath} stamp mismatch.`);
46
+ }
47
+ return { content: body, stamp: declared };
48
+ }
49
+ }
50
+ async function readStampedInstruction(absolutePath, repoRoot, verifier, required) {
51
+ try {
52
+ const raw = await readFile(absolutePath, 'utf8');
53
+ if (!raw.trim()) {
54
+ return null;
55
+ }
56
+ const relativePath = relative(repoRoot, absolutePath);
57
+ const verified = verifier.verify(raw, relativePath);
58
+ const trimmed = verified.content.trim();
59
+ if (!trimmed) {
60
+ return null;
61
+ }
62
+ return {
63
+ path: relativePath,
64
+ content: trimmed,
65
+ stamp: verified.stamp
66
+ };
67
+ }
68
+ catch (error) {
69
+ const err = error;
70
+ if (err && err.code === 'ENOENT') {
71
+ return null;
72
+ }
73
+ if (!required) {
74
+ const relativePath = relative(repoRoot, absolutePath);
75
+ logInstructionGuard(`Skipping optional instruction ${relativePath}: ${error?.message ?? String(error)}`);
76
+ return null;
77
+ }
78
+ throw error;
79
+ }
80
+ }
81
+ function resolveExperienceWordLimit(env = process.env) {
82
+ const configured = env.TFGRPO_EXPERIENCE_MAX_WORDS;
83
+ if (!configured || !configured.trim()) {
84
+ return EXPERIENCE_WORD_LIMIT;
85
+ }
86
+ const parsed = Number.parseInt(configured, 10);
87
+ if (!Number.isFinite(parsed) || parsed <= 0) {
88
+ throw new Error('TFGRPO_EXPERIENCE_MAX_WORDS must be a positive integer.');
89
+ }
90
+ if (parsed > EXPERIENCE_WORD_LIMIT) {
91
+ const message = `TFGRPO_EXPERIENCE_MAX_WORDS ${parsed} exceeds the guardrail of ${EXPERIENCE_WORD_LIMIT}.`;
92
+ logInstructionGuard(message);
93
+ throw new Error(message);
94
+ }
95
+ return parsed;
96
+ }
97
+ function logInstructionGuard(message) {
98
+ if (typeof process !== 'undefined' && typeof process.emitWarning === 'function') {
99
+ process.emitWarning(message, { code: 'instruction-guard' });
100
+ }
101
+ }
@@ -0,0 +1,151 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFile, readdir, stat } from 'node:fs/promises';
3
+ import { join, relative } from 'node:path';
4
+ const PROMPT_SECTIONS = ['system', 'inject', 'summarize', 'extract', 'optimize'];
5
+ const PROMPT_PACK_DIR = ['.agent', 'prompts', 'prompt-packs'];
6
+ export async function loadPromptPacks(repoRoot) {
7
+ const manifestPaths = await discoverPromptPackManifests(repoRoot);
8
+ const packs = [];
9
+ for (const manifestPath of manifestPaths) {
10
+ packs.push(await loadPromptPack(manifestPath, repoRoot));
11
+ }
12
+ packs.sort((a, b) => {
13
+ if (a.domain === b.domain) {
14
+ return a.id.localeCompare(b.id);
15
+ }
16
+ return a.domain.localeCompare(b.domain);
17
+ });
18
+ return packs;
19
+ }
20
+ async function discoverPromptPackManifests(repoRoot) {
21
+ const baseDir = join(repoRoot, ...PROMPT_PACK_DIR);
22
+ let entries;
23
+ try {
24
+ entries = await readdir(baseDir, { withFileTypes: true });
25
+ }
26
+ catch (error) {
27
+ if (error.code === 'ENOENT') {
28
+ return [];
29
+ }
30
+ throw error;
31
+ }
32
+ const manifests = [];
33
+ for (const entry of entries) {
34
+ if (!entry.isDirectory()) {
35
+ continue;
36
+ }
37
+ const manifestPath = join(baseDir, entry.name, 'manifest.json');
38
+ try {
39
+ await stat(manifestPath);
40
+ manifests.push(manifestPath);
41
+ }
42
+ catch (error) {
43
+ if (error.code === 'ENOENT') {
44
+ continue;
45
+ }
46
+ throw error;
47
+ }
48
+ }
49
+ return manifests;
50
+ }
51
+ async function loadPromptPack(manifestPath, repoRoot) {
52
+ let parsed;
53
+ try {
54
+ const raw = await readFile(manifestPath, 'utf8');
55
+ parsed = JSON.parse(raw);
56
+ }
57
+ catch (error) {
58
+ throw new Error(`Failed to read prompt pack manifest at ${relative(repoRoot, manifestPath)}: ${String(error)}`);
59
+ }
60
+ validateManifest(parsed, manifestPath, repoRoot);
61
+ const sections = {
62
+ system: [],
63
+ inject: [],
64
+ summarize: [],
65
+ extract: [],
66
+ optimize: []
67
+ };
68
+ sections.system = [await loadSectionSource(repoRoot, 'system', parsed.system, manifestPath)];
69
+ sections.inject = await loadSectionArray(repoRoot, 'inject', parsed.inject ?? [], manifestPath);
70
+ sections.summarize = await loadSectionArray(repoRoot, 'summarize', parsed.summarize ?? [], manifestPath);
71
+ sections.extract = await loadSectionArray(repoRoot, 'extract', parsed.extract ?? [], manifestPath);
72
+ sections.optimize = await loadSectionArray(repoRoot, 'optimize', parsed.optimize ?? [], manifestPath);
73
+ const allSources = PROMPT_SECTIONS.flatMap((section) => sections[section]);
74
+ if (allSources.length === 0) {
75
+ throw new Error(`Prompt pack ${parsed.id} ${parsed.domain} has no sources defined (${relative(repoRoot, manifestPath)})`);
76
+ }
77
+ const computedStamp = computePromptPackStamp(allSources);
78
+ if (!parsed.stamp) {
79
+ throw new Error(`Prompt pack ${parsed.id} is missing a stamp (manifest: ${relative(repoRoot, manifestPath)})`);
80
+ }
81
+ if (computedStamp !== parsed.stamp) {
82
+ throw new Error(`Prompt pack ${parsed.id} stamp mismatch. expected ${parsed.stamp}, computed ${computedStamp} (${relative(repoRoot, manifestPath)})`);
83
+ }
84
+ const experienceSlots = Number.isInteger(parsed.experienceSlots) && parsed.experienceSlots >= 0
85
+ ? parsed.experienceSlots
86
+ : 0;
87
+ return {
88
+ id: parsed.id,
89
+ domain: parsed.domain,
90
+ stamp: parsed.stamp,
91
+ experienceSlots,
92
+ sections,
93
+ sources: allSources
94
+ };
95
+ }
96
+ function validateManifest(manifest, manifestPath, repoRoot) {
97
+ const missing = [];
98
+ if (!manifest.id) {
99
+ missing.push('id');
100
+ }
101
+ if (!manifest.domain) {
102
+ missing.push('domain');
103
+ }
104
+ if (!manifest.system) {
105
+ missing.push('system');
106
+ }
107
+ if (missing.length > 0) {
108
+ throw new Error(`Prompt pack manifest ${relative(repoRoot, manifestPath)} missing required fields: ${missing.join(', ')}`);
109
+ }
110
+ }
111
+ async function loadSectionArray(repoRoot, section, paths, manifestPath) {
112
+ const sources = [];
113
+ for (const relPath of paths) {
114
+ sources.push(await loadSectionSource(repoRoot, section, relPath, manifestPath));
115
+ }
116
+ return sources;
117
+ }
118
+ async function loadSectionSource(repoRoot, section, relativePath, manifestPath) {
119
+ const absolutePath = join(repoRoot, relativePath);
120
+ let raw;
121
+ try {
122
+ raw = await readFile(absolutePath, 'utf8');
123
+ }
124
+ catch (error) {
125
+ throw new Error(`Failed to read prompt source ${relative(repoRoot, absolutePath)} referenced by ${relative(repoRoot, manifestPath)}: ${String(error)}`);
126
+ }
127
+ const content = raw.trim();
128
+ if (!content) {
129
+ throw new Error(`Prompt source ${relative(repoRoot, absolutePath)} referenced by ${relative(repoRoot, manifestPath)} is empty`);
130
+ }
131
+ return {
132
+ section,
133
+ path: relative(repoRoot, absolutePath),
134
+ content
135
+ };
136
+ }
137
+ export function computePromptPackStamp(sources) {
138
+ const hash = createHash('sha256');
139
+ const sorted = [...sources].sort((a, b) => {
140
+ if (a.section === b.section) {
141
+ return a.path.localeCompare(b.path);
142
+ }
143
+ return PROMPT_SECTIONS.indexOf(a.section) - PROMPT_SECTIONS.indexOf(b.section);
144
+ });
145
+ for (const source of sorted) {
146
+ hash.update(`${source.section}:${source.path}\n`, 'utf8');
147
+ hash.update(source.content, 'utf8');
148
+ hash.update('\n', 'utf8');
149
+ }
150
+ return hash.digest('hex');
151
+ }