@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,140 @@
1
+ import { appendCommandError, finalizeStatus, updateCommandStatus } from '../run/manifest.js';
2
+ import { isoTimestamp } from '../utils/time.js';
3
+ import { normalizeErrorMessage } from '../../utils/errorMessage.js';
4
+ export { normalizeErrorMessage };
5
+ export async function finalizeCommandLifecycle(context, stageResult) {
6
+ const commandIndex = 0;
7
+ let commandEntry = context.manifest.commands[commandIndex];
8
+ const summarySnapshot = stageResult.summary;
9
+ let resultExitCode = extractExecMetadataField(stageResult.toolRecord, 'exitCode') ?? null;
10
+ if (summarySnapshot && summarySnapshot.exitCode !== undefined) {
11
+ resultExitCode = summarySnapshot.exitCode;
12
+ }
13
+ let resultSignal = extractExecMetadataField(stageResult.toolRecord, 'signal') ?? null;
14
+ if (summarySnapshot && summarySnapshot.signal !== undefined) {
15
+ resultSignal = summarySnapshot.signal;
16
+ }
17
+ if (stageResult.commandError && resultExitCode === null) {
18
+ resultExitCode = 1;
19
+ }
20
+ if (stageResult.commandError && commandEntry) {
21
+ commandEntry = await finalizeFailedCommandEntry({
22
+ env: context.env,
23
+ paths: context.paths,
24
+ manifest: context.manifest,
25
+ commandEntry,
26
+ commandIndex,
27
+ error: stageResult.commandError,
28
+ exitCode: resultExitCode,
29
+ signal: resultSignal,
30
+ toolRecord: stageResult.toolRecord,
31
+ summarySnapshot,
32
+ shellCommand: context.shellCommand
33
+ });
34
+ }
35
+ const runStatus = determineRunStatus(commandEntry);
36
+ context.manifest.summary = commandEntry?.summary ?? context.manifest.summary;
37
+ finalizeStatus(context.manifest, runStatus, commandEntry?.status === 'failed' ? 'exec-failed' : null);
38
+ return {
39
+ commandEntry,
40
+ runStatus,
41
+ exitCode: resultExitCode,
42
+ signal: resultSignal,
43
+ summarySnapshot,
44
+ toolRecord: stageResult.toolRecord,
45
+ commandError: stageResult.commandError
46
+ };
47
+ }
48
+ export async function finalizeFailedCommandEntry(params) {
49
+ const { env, paths, manifest, commandEntry, commandIndex, error, exitCode, signal, toolRecord, summarySnapshot, shellCommand } = params;
50
+ const failureSummary = buildFailureSummary(error, exitCode, signal);
51
+ const updatedEntry = updateCommandStatus(manifest, commandIndex, {
52
+ status: 'failed',
53
+ completed_at: commandEntry.completed_at ?? isoTimestamp(),
54
+ exit_code: exitCode,
55
+ summary: failureSummary
56
+ });
57
+ const errorDetails = buildCommandErrorDetails({
58
+ error,
59
+ exitCode,
60
+ signal,
61
+ toolRecord,
62
+ summarySnapshot,
63
+ shellCommand
64
+ });
65
+ const errorFile = await appendCommandError(env, paths, manifest, updatedEntry, 'command-execution-error', errorDetails);
66
+ return updateCommandStatus(manifest, commandIndex, {
67
+ error_file: errorFile
68
+ });
69
+ }
70
+ export function buildFailureSummary(error, exitCode, signal) {
71
+ const message = normalizeErrorMessage(error);
72
+ if (signal) {
73
+ return `Command failed with signal ${signal}${message ? `: ${message}` : ''}`;
74
+ }
75
+ if (exitCode !== null && exitCode !== undefined) {
76
+ return `Command failed with code ${exitCode}${message ? `: ${message}` : ''}`;
77
+ }
78
+ return message ? `Command failed: ${message}` : 'Command failed.';
79
+ }
80
+ export function buildCommandErrorDetails(params) {
81
+ const { error, exitCode, signal, toolRecord, summarySnapshot, shellCommand } = params;
82
+ const execMetadata = readExecMetadata(toolRecord);
83
+ const details = {
84
+ message: normalizeErrorMessage(error),
85
+ command: shellCommand
86
+ };
87
+ if (exitCode !== null && exitCode !== undefined) {
88
+ details.exit_code = exitCode;
89
+ }
90
+ if (signal) {
91
+ details.signal = signal;
92
+ }
93
+ const sandboxState = summarySnapshot?.sandboxState ?? toolRecord?.sandboxState ?? execMetadata?.sandboxState;
94
+ if (sandboxState) {
95
+ details.sandbox_state = sandboxState;
96
+ }
97
+ const correlationId = execMetadata?.correlationId ?? null;
98
+ if (correlationId) {
99
+ details.correlation_id = correlationId;
100
+ }
101
+ if (execMetadata?.sessionId) {
102
+ details.session_id = execMetadata.sessionId;
103
+ }
104
+ if (typeof execMetadata?.persisted === 'boolean') {
105
+ details.persisted = execMetadata.persisted;
106
+ }
107
+ if (toolRecord?.id) {
108
+ details.tool_run_id = toolRecord.id;
109
+ }
110
+ if (toolRecord?.status) {
111
+ details.status = toolRecord.status;
112
+ }
113
+ if (typeof toolRecord?.attemptCount === 'number') {
114
+ details.attempts = toolRecord.attemptCount;
115
+ }
116
+ else if (typeof toolRecord?.retryCount === 'number') {
117
+ details.attempts = toolRecord.retryCount + 1;
118
+ }
119
+ return details;
120
+ }
121
+ export function determineRunStatus(entry) {
122
+ if (!entry) {
123
+ return 'failed';
124
+ }
125
+ return entry.status === 'succeeded' ? 'succeeded' : 'failed';
126
+ }
127
+ export function readExecMetadata(record) {
128
+ if (!record?.metadata || typeof record.metadata !== 'object') {
129
+ return null;
130
+ }
131
+ const candidate = record.metadata.exec;
132
+ if (!candidate || typeof candidate !== 'object') {
133
+ return null;
134
+ }
135
+ return candidate;
136
+ }
137
+ export function extractExecMetadataField(record, field) {
138
+ const metadata = readExecMetadata(record);
139
+ return metadata ? metadata[field] : undefined;
140
+ }
@@ -0,0 +1,62 @@
1
+ import process from 'node:process';
2
+ import { persistManifest } from '../run/manifestPersister.js';
3
+ import { isoTimestamp } from '../utils/time.js';
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
+ export async function maybeTriggerLearning(runContext, runStatus) {
9
+ const enabled = process.env.LEARNING_PIPELINE_ENABLED === '1';
10
+ if (!enabled) {
11
+ return;
12
+ }
13
+ const storageDir = process.env.LEARNING_SNAPSHOT_DIR;
14
+ if (runStatus !== 'succeeded') {
15
+ return;
16
+ }
17
+ try {
18
+ const harvester = await runLearningHarvester(runContext.manifest, {
19
+ repoRoot: runContext.env.repoRoot,
20
+ runsRoot: runContext.env.runsRoot,
21
+ manifestPath: runContext.paths.manifestPath,
22
+ taskId: runContext.env.taskId,
23
+ runId: runContext.manifest.run_id,
24
+ diffPath: null,
25
+ promptPath: null,
26
+ executionHistoryPath: runContext.paths.logPath,
27
+ storageDir,
28
+ alertTargets: { slack: '#learning-alerts', pagerduty: 'learning-pipeline' }
29
+ });
30
+ const scenario = await synthesizeScenario({
31
+ manifest: harvester.manifest,
32
+ taskId: runContext.env.taskId,
33
+ runId: runContext.manifest.run_id,
34
+ runsRoot: runContext.env.runsRoot,
35
+ prompt: null,
36
+ diff: null,
37
+ executionHistory: [
38
+ {
39
+ command: runContext.shellCommand,
40
+ exitCode: 0,
41
+ cwd: runContext.stage.cwd ?? runContext.env.repoRoot,
42
+ timestamp: isoTimestamp()
43
+ }
44
+ ],
45
+ alertTargets: { slack: '#learning-alerts', pagerduty: 'learning-pipeline' },
46
+ pagerDutySeverity: 'none'
47
+ });
48
+ const validation = await runScenarioValidation({
49
+ manifest: scenario.manifest,
50
+ repoRoot: runContext.env.repoRoot,
51
+ runsRoot: runContext.env.runsRoot,
52
+ taskId: runContext.env.taskId,
53
+ runId: runContext.manifest.run_id,
54
+ paths: runContext.paths,
55
+ scenarioPath: scenario.scenarioPath
56
+ });
57
+ await persistManifest(runContext.paths, validation.manifest, runContext.persister, { force: true });
58
+ }
59
+ catch (error) {
60
+ logger.warn(`[learning] auto-trigger failed: ${error instanceof Error ? error.message : String(error)}`);
61
+ }
62
+ }
@@ -0,0 +1,71 @@
1
+ import { runCommandStage } from '../services/commandRunner.js';
2
+ import { serializeExecEvent } from '../../../../packages/shared/events/serializer.js';
3
+ import { sanitizeToolRunRecord } from '../../../../packages/shared/manifest/writer.js';
4
+ export async function runExecStage(context) {
5
+ let runResultSummary = null;
6
+ let toolRecord = null;
7
+ const hooks = {
8
+ onEvent: (event) => {
9
+ context.execEvents.push(event);
10
+ const serialized = serializeExecEvent(event);
11
+ context.telemetryTasks.push(Promise.resolve(context.telemetrySink.record(serialized)).then(() => undefined));
12
+ if (context.outputMode === 'jsonl' && context.jsonlWriter) {
13
+ context.jsonlWriter.write(serialized);
14
+ }
15
+ else if (context.outputMode === 'interactive') {
16
+ streamInteractive(context.stdout, context.stderr, event);
17
+ }
18
+ },
19
+ onResult: (result) => {
20
+ runResultSummary = {
21
+ correlationId: result.correlationId,
22
+ stdout: result.stdout,
23
+ stderr: result.stderr,
24
+ exitCode: result.exitCode,
25
+ signal: result.signal,
26
+ durationMs: result.durationMs,
27
+ status: result.status,
28
+ sandboxState: result.sandboxState
29
+ };
30
+ toolRecord = sanitizeToolRunRecord(JSON.parse(JSON.stringify(result.record)));
31
+ },
32
+ onError: (error) => {
33
+ toolRecord = sanitizeToolRunRecord(JSON.parse(JSON.stringify(error.record)));
34
+ }
35
+ };
36
+ let commandError = null;
37
+ try {
38
+ await runCommandStage({
39
+ env: context.env,
40
+ paths: context.paths,
41
+ manifest: context.manifest,
42
+ stage: context.stage,
43
+ index: 1
44
+ }, hooks);
45
+ }
46
+ catch (error) {
47
+ commandError = error;
48
+ }
49
+ return {
50
+ summary: runResultSummary,
51
+ toolRecord,
52
+ commandError
53
+ };
54
+ }
55
+ export function streamInteractive(stdout, stderr, event) {
56
+ switch (event.type) {
57
+ case 'exec:chunk':
58
+ if (event.payload.stream === 'stdout') {
59
+ stdout.write(event.payload.data);
60
+ }
61
+ else {
62
+ stderr.write(event.payload.data);
63
+ }
64
+ break;
65
+ case 'exec:retry':
66
+ stderr.write(`[retry] attempt ${event.attempt} in ${event.payload.delayMs}ms — ${event.payload.errorMessage}\n`);
67
+ break;
68
+ default:
69
+ break;
70
+ }
71
+ }
@@ -0,0 +1,109 @@
1
+ import { join } from 'node:path';
2
+ import { relativeToRepo } from '../run/runPaths.js';
3
+ import { writeJsonAtomic } from '../utils/fs.js';
4
+ import { persistManifest } from '../run/manifestPersister.js';
5
+ import { readExecMetadata } from './finalization.js';
6
+ export function createRunSummaryPayload(params) {
7
+ const { env, paths, manifest, runStatus, shellCommand, argv, resultSummary, toolRecord, execEvents, exitCode, signal, notificationTargets, cwd, metrics } = params;
8
+ const stdout = resultSummary?.stdout ?? '';
9
+ const stderr = resultSummary?.stderr ?? '';
10
+ const correlationId = resultSummary?.correlationId ?? execEvents[0]?.correlationId ?? manifest.run_id;
11
+ const attempts = typeof toolRecord?.attemptCount === 'number'
12
+ ? toolRecord.attemptCount
13
+ : (toolRecord?.retryCount ?? 0) + 1;
14
+ const execMetadata = readExecMetadata(toolRecord);
15
+ const sessionId = typeof execMetadata?.sessionId === 'string' ? execMetadata.sessionId : 'default';
16
+ const persisted = Boolean(execMetadata?.persisted);
17
+ const metadataSandbox = execMetadata?.sandboxState;
18
+ const sandboxState = resultSummary?.sandboxState ?? toolRecord?.sandboxState ?? metadataSandbox ?? 'sandboxed';
19
+ return {
20
+ status: runStatus === 'succeeded' ? 'succeeded' : 'failed',
21
+ run: {
22
+ id: manifest.run_id,
23
+ taskId: manifest.task_id,
24
+ pipelineId: manifest.pipeline_id,
25
+ manifest: relativeToRepo(env, paths.manifestPath),
26
+ artifactRoot: manifest.artifact_root,
27
+ summary: manifest.summary
28
+ },
29
+ result: {
30
+ exitCode,
31
+ signal,
32
+ durationMs: resultSummary?.durationMs ?? 0,
33
+ status: resultSummary?.status ?? (toolRecord?.status ?? 'failed'),
34
+ sandboxState,
35
+ correlationId,
36
+ attempts
37
+ },
38
+ command: {
39
+ argv,
40
+ shell: shellCommand,
41
+ cwd,
42
+ sessionId,
43
+ persisted
44
+ },
45
+ outputs: {
46
+ stdout,
47
+ stderr
48
+ },
49
+ logs: {
50
+ runner: relativeToRepo(env, paths.logPath),
51
+ command: manifest.commands[0]?.log_path ?? null
52
+ },
53
+ toolRun: toolRecord,
54
+ metrics: metrics ?? undefined,
55
+ notifications: {
56
+ targets: notificationTargets,
57
+ delivered: [],
58
+ failures: []
59
+ }
60
+ };
61
+ }
62
+ export function renderRunOutput(context, summaryPayload, summaryEvent) {
63
+ if (context.outputMode === 'jsonl' && context.jsonlWriter) {
64
+ context.jsonlWriter.write(summaryEvent);
65
+ return;
66
+ }
67
+ if (context.outputMode === 'json') {
68
+ const spacing = context.invocation.jsonPretty ? 2 : 0;
69
+ context.stdout.write(`${JSON.stringify(summaryEvent, null, spacing)}\n`);
70
+ return;
71
+ }
72
+ if (context.outputMode === 'interactive') {
73
+ context.stdout.write(`\n${summaryPayload.run.id} ${summaryPayload.status.toUpperCase()}\n`);
74
+ context.stdout.write(`Manifest: ${summaryPayload.run.manifest}\n`);
75
+ context.stdout.write(`Log: ${summaryPayload.logs.runner}\n`);
76
+ }
77
+ }
78
+ export async function persistRunOutputs(context, summaryEvent) {
79
+ const runSummaryPath = join(context.paths.runDir, 'run-summary.json');
80
+ await writeJsonAtomic(runSummaryPath, summaryEvent);
81
+ context.manifest.run_summary_path = relativeToRepo(context.env, runSummaryPath);
82
+ await persistManifest(context.paths, context.manifest, context.persister, { force: true });
83
+ }
84
+ export function emitCommandError(context, commandError) {
85
+ if (!commandError) {
86
+ return;
87
+ }
88
+ if (context.outputMode === 'jsonl' || context.outputMode === 'json') {
89
+ return;
90
+ }
91
+ context.stderr.write(`Command execution failed: ${commandError?.message ?? String(commandError)}\n`);
92
+ }
93
+ export function buildExecRunSummary(params) {
94
+ return {
95
+ runId: params.manifest.run_id,
96
+ manifestPath: params.summaryPayload.run.manifest,
97
+ logPath: params.summaryPayload.logs.runner,
98
+ status: params.summaryPayload.status,
99
+ exitCode: params.summaryPayload.result.exitCode,
100
+ signal: params.summaryPayload.result.signal,
101
+ stdout: params.summaryPayload.outputs.stdout,
102
+ stderr: params.summaryPayload.outputs.stderr,
103
+ shellCommand: params.shellCommand,
104
+ argv: params.argv,
105
+ events: params.events,
106
+ summaryEvent: params.summaryEvent,
107
+ toolRun: params.toolRecord ?? null
108
+ };
109
+ }
@@ -0,0 +1,18 @@
1
+ export async function deliverNotifications(context, summaryPayload, summaryEvent) {
2
+ const notificationOutcome = await context.notificationSink.notify(summaryEvent);
3
+ if (summaryPayload.notifications) {
4
+ summaryPayload.notifications.delivered = notificationOutcome.delivered;
5
+ summaryPayload.notifications.failures = notificationOutcome.failures;
6
+ }
7
+ }
8
+ export function recordSummaryTelemetry(context, summaryEvent) {
9
+ context.telemetryTasks.push(Promise.resolve(context.telemetrySink.recordSummary(summaryEvent)).then(() => undefined));
10
+ }
11
+ export async function flushTelemetry(context) {
12
+ await Promise.allSettled(context.telemetryTasks);
13
+ await context.telemetrySink.flush();
14
+ }
15
+ export async function shutdownSinks(context) {
16
+ await context.telemetrySink.shutdown();
17
+ await context.notificationSink.shutdown();
18
+ }
@@ -0,0 +1,200 @@
1
+ import { relativeToRepo } from '../run/runPaths.js';
2
+ import { logger } from '../../logger.js';
3
+ import { summarizeTrajectory, optimizeExperience, framesFromToolMetrics } from './experience.js';
4
+ const COST_PER_TOKEN_USD = 0.000002;
5
+ const DEFAULT_EXPERIENCE_WORD_LIMIT = 32;
6
+ export function resolveTfgrpoContext(env = process.env) {
7
+ const epoch = parsePositiveInteger(env.TFGRPO_EPOCH);
8
+ const groupSize = parsePositiveInteger(env.TFGRPO_GROUP_SIZE);
9
+ const groupId = env.TFGRPO_GROUP_ID?.trim() || null;
10
+ return {
11
+ epoch,
12
+ groupId,
13
+ groupSize,
14
+ active: epoch !== null || groupSize !== null || groupId !== null
15
+ };
16
+ }
17
+ export function resolveExperiencePolicy(env = process.env) {
18
+ const configured = env.TFGRPO_EXPERIENCE_MAX_WORDS;
19
+ const parsed = configured ? Number.parseInt(configured, 10) : NaN;
20
+ const maxSummaryWords = Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_EXPERIENCE_WORD_LIMIT;
21
+ return {
22
+ maxSummaryWords,
23
+ rewardFloor: 0
24
+ };
25
+ }
26
+ export function createToolMetricSnapshot(toolRecord, summary) {
27
+ if (!toolRecord && !summary) {
28
+ return null;
29
+ }
30
+ const stdout = summary?.stdout ?? '';
31
+ const tokens = estimateTokenCount(stdout);
32
+ const latencyMs = summary?.durationMs ?? deriveDurationFromEvents(toolRecord);
33
+ const costUsd = roundCurrency(tokens * COST_PER_TOKEN_USD);
34
+ const attempts = toolRecord?.attemptCount ?? 1;
35
+ const status = toolRecord?.status ?? summary?.status ?? 'failed';
36
+ const sandboxState = summary?.sandboxState ?? toolRecord?.sandboxState ?? 'sandboxed';
37
+ const toolName = toolRecord?.tool ?? 'cli:command';
38
+ return {
39
+ tool: toolName,
40
+ tokens,
41
+ costUsd,
42
+ latencyMs,
43
+ attempts,
44
+ status,
45
+ sandboxState
46
+ };
47
+ }
48
+ export function createRunMetricSummary(metrics, context) {
49
+ const toolCalls = metrics.length;
50
+ if (toolCalls === 0 && !context.active) {
51
+ return null;
52
+ }
53
+ const tokenTotal = metrics.reduce((sum, metric) => sum + metric.tokens, 0);
54
+ const costUsd = roundCurrency(metrics.reduce((sum, metric) => sum + metric.costUsd, 0));
55
+ const latencyMs = metrics.reduce((sum, metric) => sum + metric.latencyMs, 0);
56
+ return {
57
+ toolCalls,
58
+ tokenTotal,
59
+ costUsd,
60
+ latencyMs,
61
+ perTool: metrics,
62
+ tfgrpo: context.active
63
+ ? {
64
+ epoch: context.epoch,
65
+ groupSize: context.groupSize,
66
+ groupId: context.groupId
67
+ }
68
+ : undefined
69
+ };
70
+ }
71
+ export function mergeTfgrpoManifest(existing, metrics, context) {
72
+ if (!metrics && !existing && !context.active) {
73
+ return null;
74
+ }
75
+ const manifestMetrics = metrics
76
+ ? {
77
+ tool_calls: metrics.toolCalls,
78
+ token_total: metrics.tokenTotal,
79
+ cost_usd: metrics.costUsd,
80
+ latency_ms: metrics.latencyMs,
81
+ per_tool: metrics.perTool.map((entry) => ({
82
+ tool: entry.tool,
83
+ tokens: entry.tokens,
84
+ cost_usd: entry.costUsd,
85
+ latency_ms: entry.latencyMs,
86
+ attempts: entry.attempts,
87
+ status: entry.status,
88
+ sandbox_state: entry.sandboxState
89
+ }))
90
+ }
91
+ : existing?.tool_metrics;
92
+ return {
93
+ epoch: context.epoch ?? existing?.epoch ?? null,
94
+ group_id: context.groupId ?? existing?.group_id ?? null,
95
+ group_size: context.groupSize ?? existing?.group_size ?? null,
96
+ tool_metrics: manifestMetrics ?? existing?.tool_metrics,
97
+ experiences: existing?.experiences
98
+ };
99
+ }
100
+ export async function persistExperienceRecords(params) {
101
+ const { runMetrics } = params;
102
+ if (!runMetrics || runMetrics.perTool.length === 0) {
103
+ return null;
104
+ }
105
+ const promptPack = params.manifest.prompt_packs?.[0];
106
+ if (!promptPack?.domain || !promptPack.stamp) {
107
+ return null;
108
+ }
109
+ const terminalEvent = findTerminalEvent(params.execEvents);
110
+ if (!terminalEvent) {
111
+ return null;
112
+ }
113
+ try {
114
+ const frames = framesFromToolMetrics(runMetrics.perTool, terminalEvent);
115
+ const trajectory = summarizeTrajectory({
116
+ runId: params.manifest.run_id,
117
+ taskId: params.manifest.task_id,
118
+ epoch: params.tfgrpoContext.epoch,
119
+ groupId: params.tfgrpoContext.groupId,
120
+ domain: promptPack.domain,
121
+ stampSignature: promptPack.stamp,
122
+ frames,
123
+ baseSummary: params.manifest.summary ?? undefined
124
+ });
125
+ const optimized = optimizeExperience(trajectory, params.policy);
126
+ const manifestPath = relativeToRepo(params.env, params.paths.manifestPath);
127
+ const written = await params.store.recordBatch([optimized], manifestPath);
128
+ if (written.length > 0) {
129
+ const existing = params.manifest.tfgrpo?.experiences ?? {
130
+ ids: [],
131
+ written: 0,
132
+ manifest_path: manifestPath
133
+ };
134
+ const summary = {
135
+ ids: [...existing.ids, ...written.map((record) => record.id)],
136
+ written: existing.written + written.length,
137
+ manifest_path: manifestPath
138
+ };
139
+ params.manifest.tfgrpo = {
140
+ ...(params.manifest.tfgrpo ?? {
141
+ epoch: params.tfgrpoContext.epoch,
142
+ group_id: params.tfgrpoContext.groupId,
143
+ group_size: params.tfgrpoContext.groupSize
144
+ }),
145
+ experiences: summary
146
+ };
147
+ }
148
+ return written;
149
+ }
150
+ catch (error) {
151
+ logger.warn(`Failed to persist TF-GRPO experience: ${String(error)}`);
152
+ return null;
153
+ }
154
+ }
155
+ function parsePositiveInteger(value) {
156
+ if (!value) {
157
+ return null;
158
+ }
159
+ const parsed = Number.parseInt(value, 10);
160
+ if (Number.isNaN(parsed) || parsed <= 0) {
161
+ return null;
162
+ }
163
+ return parsed;
164
+ }
165
+ function deriveDurationFromEvents(record) {
166
+ const events = record?.events;
167
+ if (!Array.isArray(events) || events.length === 0) {
168
+ return 0;
169
+ }
170
+ const first = events.find((event) => event.type === 'exec:begin');
171
+ const last = [...events].reverse().find((event) => event.type === 'exec:end');
172
+ if (!first || !last) {
173
+ return 0;
174
+ }
175
+ const startedAt = Date.parse(first.timestamp);
176
+ const completedAt = Date.parse(last.timestamp);
177
+ if (Number.isNaN(startedAt) || Number.isNaN(completedAt) || completedAt < startedAt) {
178
+ return 0;
179
+ }
180
+ return completedAt - startedAt;
181
+ }
182
+ function estimateTokenCount(output) {
183
+ const trimmed = output.trim();
184
+ if (!trimmed) {
185
+ return 0;
186
+ }
187
+ return trimmed.split(/\s+/u).filter(Boolean).length;
188
+ }
189
+ function roundCurrency(value) {
190
+ return Math.round(value * 1_000_000) / 1_000_000;
191
+ }
192
+ function findTerminalEvent(events) {
193
+ for (let index = events.length - 1; index >= 0; index -= 1) {
194
+ const candidate = events[index];
195
+ if (candidate?.type === 'exec:end') {
196
+ return candidate;
197
+ }
198
+ }
199
+ return events.length > 0 ? events[events.length - 1] : null;
200
+ }
@@ -0,0 +1,19 @@
1
+ import { persistManifest } from '../run/manifestPersister.js';
2
+ import { createRunMetricSummary, createToolMetricSnapshot, mergeTfgrpoManifest, persistExperienceRecords, resolveExperiencePolicy } from './tfgrpo.js';
3
+ export async function handleTfgrpoArtifacts(context, finalization, tfgrpoContext) {
4
+ const toolMetric = createToolMetricSnapshot(finalization.toolRecord, finalization.summarySnapshot);
5
+ const runMetricSummary = createRunMetricSummary(toolMetric ? [toolMetric] : [], tfgrpoContext);
6
+ context.manifest.tfgrpo = mergeTfgrpoManifest(context.manifest.tfgrpo, runMetricSummary, tfgrpoContext);
7
+ await persistExperienceRecords({
8
+ store: context.experienceStore,
9
+ manifest: context.manifest,
10
+ env: context.env,
11
+ paths: context.paths,
12
+ tfgrpoContext,
13
+ runMetrics: runMetricSummary,
14
+ execEvents: context.execEvents,
15
+ policy: resolveExperiencePolicy()
16
+ });
17
+ await persistManifest(context.paths, context.manifest, context.persister, { force: true });
18
+ return runMetricSummary;
19
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,64 @@
1
+ import { copyFile, mkdir, readdir, stat } from 'node:fs/promises';
2
+ import { existsSync } from 'node:fs';
3
+ import { dirname, join, relative } from 'node:path';
4
+ import { findPackageRoot } from './utils/packageInfo.js';
5
+ export async function initCodexTemplates(options) {
6
+ const root = findPackageRoot();
7
+ const templateRoot = join(root, 'templates', options.template);
8
+ const written = [];
9
+ const skipped = [];
10
+ await assertDirectory(templateRoot);
11
+ await copyTemplateDir(templateRoot, options.cwd, {
12
+ force: options.force,
13
+ written,
14
+ skipped
15
+ });
16
+ return { written, skipped, templateRoot };
17
+ }
18
+ async function assertDirectory(path) {
19
+ const info = await stat(path).catch(() => null);
20
+ if (!info || !info.isDirectory()) {
21
+ throw new Error(`Template directory not found: ${path}`);
22
+ }
23
+ }
24
+ async function copyTemplateDir(sourceDir, targetDir, options) {
25
+ await mkdir(targetDir, { recursive: true });
26
+ const entries = await readdir(sourceDir, { withFileTypes: true });
27
+ for (const entry of entries) {
28
+ const sourcePath = join(sourceDir, entry.name);
29
+ const targetPath = join(targetDir, entry.name);
30
+ if (entry.isDirectory()) {
31
+ await copyTemplateDir(sourcePath, targetPath, options);
32
+ continue;
33
+ }
34
+ if (!entry.isFile()) {
35
+ continue;
36
+ }
37
+ if (existsSync(targetPath) && !options.force) {
38
+ options.skipped.push(targetPath);
39
+ continue;
40
+ }
41
+ await mkdir(dirname(targetPath), { recursive: true });
42
+ await copyFile(sourcePath, targetPath);
43
+ options.written.push(targetPath);
44
+ }
45
+ }
46
+ export function formatInitSummary(result, cwd) {
47
+ const lines = [];
48
+ if (result.written.length > 0) {
49
+ lines.push('Written:');
50
+ for (const filePath of result.written) {
51
+ lines.push(` - ${relative(cwd, filePath)}`);
52
+ }
53
+ }
54
+ if (result.skipped.length > 0) {
55
+ lines.push('Skipped (already exists):');
56
+ for (const filePath of result.skipped) {
57
+ lines.push(` - ${relative(cwd, filePath)}`);
58
+ }
59
+ }
60
+ if (lines.length === 0) {
61
+ lines.push('No files written.');
62
+ }
63
+ return lines;
64
+ }