@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.
- package/LICENSE +7 -0
- package/README.md +238 -0
- package/dist/bin/codex-orchestrator.js +507 -0
- package/dist/orchestrator/src/agents/builder.js +16 -0
- package/dist/orchestrator/src/agents/index.js +4 -0
- package/dist/orchestrator/src/agents/planner.js +17 -0
- package/dist/orchestrator/src/agents/reviewer.js +13 -0
- package/dist/orchestrator/src/agents/tester.js +13 -0
- package/dist/orchestrator/src/cli/adapters/CommandBuilder.js +20 -0
- package/dist/orchestrator/src/cli/adapters/CommandPlanner.js +164 -0
- package/dist/orchestrator/src/cli/adapters/CommandReviewer.js +32 -0
- package/dist/orchestrator/src/cli/adapters/CommandTester.js +33 -0
- package/dist/orchestrator/src/cli/adapters/index.js +4 -0
- package/dist/orchestrator/src/cli/config/userConfig.js +28 -0
- package/dist/orchestrator/src/cli/doctor.js +48 -0
- package/dist/orchestrator/src/cli/events/runEvents.js +84 -0
- package/dist/orchestrator/src/cli/exec/command.js +56 -0
- package/dist/orchestrator/src/cli/exec/context.js +108 -0
- package/dist/orchestrator/src/cli/exec/experience.js +77 -0
- package/dist/orchestrator/src/cli/exec/finalization.js +140 -0
- package/dist/orchestrator/src/cli/exec/learning.js +62 -0
- package/dist/orchestrator/src/cli/exec/stageRunner.js +71 -0
- package/dist/orchestrator/src/cli/exec/summary.js +109 -0
- package/dist/orchestrator/src/cli/exec/telemetry.js +18 -0
- package/dist/orchestrator/src/cli/exec/tfgrpo.js +200 -0
- package/dist/orchestrator/src/cli/exec/tfgrpoArtifacts.js +19 -0
- package/dist/orchestrator/src/cli/exec/types.js +1 -0
- package/dist/orchestrator/src/cli/init.js +64 -0
- package/dist/orchestrator/src/cli/mcp.js +124 -0
- package/dist/orchestrator/src/cli/metrics/metricsAggregator.js +404 -0
- package/dist/orchestrator/src/cli/metrics/metricsRecorder.js +138 -0
- package/dist/orchestrator/src/cli/orchestrator.js +554 -0
- package/dist/orchestrator/src/cli/pipelines/defaultDiagnostics.js +32 -0
- package/dist/orchestrator/src/cli/pipelines/designReference.js +72 -0
- package/dist/orchestrator/src/cli/pipelines/hiFiDesignToolkit.js +71 -0
- package/dist/orchestrator/src/cli/pipelines/index.js +34 -0
- package/dist/orchestrator/src/cli/run/environment.js +24 -0
- package/dist/orchestrator/src/cli/run/manifest.js +367 -0
- package/dist/orchestrator/src/cli/run/manifestPersister.js +88 -0
- package/dist/orchestrator/src/cli/run/runPaths.js +30 -0
- package/dist/orchestrator/src/cli/selfCheck.js +12 -0
- package/dist/orchestrator/src/cli/services/commandRunner.js +420 -0
- package/dist/orchestrator/src/cli/services/controlPlaneService.js +107 -0
- package/dist/orchestrator/src/cli/services/execRuntime.js +69 -0
- package/dist/orchestrator/src/cli/services/pipelineResolver.js +47 -0
- package/dist/orchestrator/src/cli/services/runPreparation.js +82 -0
- package/dist/orchestrator/src/cli/services/runSummaryWriter.js +35 -0
- package/dist/orchestrator/src/cli/services/schedulerService.js +42 -0
- package/dist/orchestrator/src/cli/tasks/taskMetadata.js +19 -0
- package/dist/orchestrator/src/cli/telemetry/schema.js +8 -0
- package/dist/orchestrator/src/cli/types.js +1 -0
- package/dist/orchestrator/src/cli/ui/HudApp.js +112 -0
- package/dist/orchestrator/src/cli/ui/controller.js +26 -0
- package/dist/orchestrator/src/cli/ui/store.js +240 -0
- package/dist/orchestrator/src/cli/utils/enforcementMode.js +12 -0
- package/dist/orchestrator/src/cli/utils/fs.js +8 -0
- package/dist/orchestrator/src/cli/utils/interactive.js +25 -0
- package/dist/orchestrator/src/cli/utils/jsonlWriter.js +10 -0
- package/dist/orchestrator/src/cli/utils/optionalDeps.js +30 -0
- package/dist/orchestrator/src/cli/utils/packageInfo.js +25 -0
- package/dist/orchestrator/src/cli/utils/planFormatter.js +49 -0
- package/dist/orchestrator/src/cli/utils/runId.js +7 -0
- package/dist/orchestrator/src/cli/utils/specGuardRunner.js +26 -0
- package/dist/orchestrator/src/cli/utils/strings.js +8 -0
- package/dist/orchestrator/src/cli/utils/time.js +6 -0
- package/dist/orchestrator/src/control-plane/drift-reporter.js +109 -0
- package/dist/orchestrator/src/control-plane/index.js +3 -0
- package/dist/orchestrator/src/control-plane/request-builder.js +217 -0
- package/dist/orchestrator/src/control-plane/types.js +1 -0
- package/dist/orchestrator/src/control-plane/validator.js +50 -0
- package/dist/orchestrator/src/credentials/CredentialBroker.js +1 -0
- package/dist/orchestrator/src/events/EventBus.js +25 -0
- package/dist/orchestrator/src/learning/crystalizer.js +108 -0
- package/dist/orchestrator/src/learning/harvester.js +146 -0
- package/dist/orchestrator/src/learning/manifest.js +56 -0
- package/dist/orchestrator/src/learning/runner.js +177 -0
- package/dist/orchestrator/src/learning/validator.js +164 -0
- package/dist/orchestrator/src/logger.js +20 -0
- package/dist/orchestrator/src/manager.js +388 -0
- package/dist/orchestrator/src/persistence/ArtifactStager.js +95 -0
- package/dist/orchestrator/src/persistence/ExperienceStore.js +210 -0
- package/dist/orchestrator/src/persistence/PersistenceCoordinator.js +65 -0
- package/dist/orchestrator/src/persistence/RunManifestWriter.js +23 -0
- package/dist/orchestrator/src/persistence/TaskStateStore.js +172 -0
- package/dist/orchestrator/src/persistence/identifierGuards.js +1 -0
- package/dist/orchestrator/src/persistence/lockFile.js +26 -0
- package/dist/orchestrator/src/persistence/sanitizeIdentifier.js +26 -0
- package/dist/orchestrator/src/persistence/sanitizeRunId.js +8 -0
- package/dist/orchestrator/src/persistence/sanitizeTaskId.js +8 -0
- package/dist/orchestrator/src/persistence/writeAtomicFile.js +4 -0
- package/dist/orchestrator/src/privacy/guard.js +111 -0
- package/dist/orchestrator/src/scheduler/index.js +1 -0
- package/dist/orchestrator/src/scheduler/plan.js +171 -0
- package/dist/orchestrator/src/scheduler/types.js +1 -0
- package/dist/orchestrator/src/sync/CloudRunsClient.js +1 -0
- package/dist/orchestrator/src/sync/CloudRunsHttpClient.js +82 -0
- package/dist/orchestrator/src/sync/CloudSyncWorker.js +206 -0
- package/dist/orchestrator/src/sync/createCloudSyncWorker.js +15 -0
- package/dist/orchestrator/src/types.js +1 -0
- package/dist/orchestrator/src/utils/atomicWrite.js +15 -0
- package/dist/orchestrator/src/utils/errorMessage.js +14 -0
- package/dist/orchestrator/src/utils/executionMode.js +69 -0
- package/dist/packages/control-plane-schemas/src/index.js +1 -0
- package/dist/packages/control-plane-schemas/src/run-request.js +548 -0
- package/dist/packages/orchestrator/src/exec/handle-service.js +203 -0
- package/dist/packages/orchestrator/src/exec/session-manager.js +147 -0
- package/dist/packages/orchestrator/src/exec/unified-exec.js +432 -0
- package/dist/packages/orchestrator/src/index.js +3 -0
- package/dist/packages/orchestrator/src/instructions/loader.js +101 -0
- package/dist/packages/orchestrator/src/instructions/promptPacks.js +151 -0
- package/dist/packages/orchestrator/src/notifications/index.js +74 -0
- package/dist/packages/orchestrator/src/telemetry/otel-exporter.js +142 -0
- package/dist/packages/orchestrator/src/tool-orchestrator.js +161 -0
- package/dist/packages/sdk-node/src/orchestrator.js +195 -0
- package/dist/packages/shared/config/designConfig.js +495 -0
- package/dist/packages/shared/config/env.js +37 -0
- package/dist/packages/shared/config/index.js +2 -0
- package/dist/packages/shared/design-artifacts/writer.js +221 -0
- package/dist/packages/shared/events/serializer.js +84 -0
- package/dist/packages/shared/events/types.js +1 -0
- package/dist/packages/shared/manifest/artifactUtils.js +36 -0
- package/dist/packages/shared/manifest/designArtifacts.js +665 -0
- package/dist/packages/shared/manifest/fileIO.js +29 -0
- package/dist/packages/shared/manifest/toolRuns.js +78 -0
- package/dist/packages/shared/manifest/toolkitArtifacts.js +223 -0
- package/dist/packages/shared/manifest/types.js +5 -0
- package/dist/packages/shared/manifest/validator.js +73 -0
- package/dist/packages/shared/manifest/writer.js +2 -0
- package/dist/packages/shared/streams/stdio.js +112 -0
- package/dist/scripts/design/pipeline/advanced-assets.js +466 -0
- package/dist/scripts/design/pipeline/componentize.js +74 -0
- package/dist/scripts/design/pipeline/context.js +34 -0
- package/dist/scripts/design/pipeline/extract.js +249 -0
- package/dist/scripts/design/pipeline/optionalDeps.js +107 -0
- package/dist/scripts/design/pipeline/prepare.js +46 -0
- package/dist/scripts/design/pipeline/reference.js +94 -0
- package/dist/scripts/design/pipeline/state.js +206 -0
- package/dist/scripts/design/pipeline/toolkit/common.js +94 -0
- package/dist/scripts/design/pipeline/toolkit/extract.js +258 -0
- package/dist/scripts/design/pipeline/toolkit/publish.js +202 -0
- package/dist/scripts/design/pipeline/toolkit/publishActions.js +12 -0
- package/dist/scripts/design/pipeline/toolkit/reference.js +846 -0
- package/dist/scripts/design/pipeline/toolkit/snapshot.js +882 -0
- package/dist/scripts/design/pipeline/toolkit/tokens.js +456 -0
- package/dist/scripts/design/pipeline/visual-regression.js +137 -0
- package/dist/scripts/design/pipeline/write-artifacts.js +61 -0
- package/package.json +97 -0
- package/schemas/manifest.json +1064 -0
- package/templates/README.md +12 -0
- package/templates/codex/mcp-client.json +8 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { PLANNER_EXECUTION_MODE_PARSER, resolveRequiresCloudPolicy } from '../../utils/executionMode.js';
|
|
2
|
+
export class CommandPlanner {
|
|
3
|
+
pipeline;
|
|
4
|
+
options;
|
|
5
|
+
cachedPlan = null;
|
|
6
|
+
constructor(pipeline, options = {}) {
|
|
7
|
+
this.pipeline = pipeline;
|
|
8
|
+
this.options = options;
|
|
9
|
+
}
|
|
10
|
+
async plan(task) {
|
|
11
|
+
void task;
|
|
12
|
+
if (!this.cachedPlan) {
|
|
13
|
+
const items = this.pipeline.stages.map((stage, index) => this.buildPlanItem(stage, index));
|
|
14
|
+
const targetId = this.resolveTargetId(items);
|
|
15
|
+
const normalizedItems = items.map((item) => ({
|
|
16
|
+
...item,
|
|
17
|
+
selected: item.id === targetId
|
|
18
|
+
}));
|
|
19
|
+
this.cachedPlan = {
|
|
20
|
+
items: normalizedItems,
|
|
21
|
+
notes: this.pipeline.description,
|
|
22
|
+
targetId: targetId ?? null
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return clonePlanResult(this.cachedPlan);
|
|
26
|
+
}
|
|
27
|
+
buildPlanItem(stage, index) {
|
|
28
|
+
const stagePlanHints = extractStagePlanHints(stage);
|
|
29
|
+
const requiresCloud = resolveStageRequiresCloud(stage, stagePlanHints);
|
|
30
|
+
const runnable = resolveStageRunnable(stagePlanHints);
|
|
31
|
+
const metadata = {
|
|
32
|
+
pipelineId: this.pipeline.id,
|
|
33
|
+
stageId: stage.id,
|
|
34
|
+
stageKind: stage.kind,
|
|
35
|
+
index
|
|
36
|
+
};
|
|
37
|
+
if (stagePlanHints.aliases.length > 0) {
|
|
38
|
+
metadata.aliases = stagePlanHints.aliases;
|
|
39
|
+
}
|
|
40
|
+
if (stagePlanHints.defaultTarget) {
|
|
41
|
+
metadata.defaultTarget = true;
|
|
42
|
+
}
|
|
43
|
+
if (stagePlanHints.executionMode) {
|
|
44
|
+
metadata.executionMode = stagePlanHints.executionMode;
|
|
45
|
+
}
|
|
46
|
+
metadata.requiresCloud = requiresCloud;
|
|
47
|
+
return {
|
|
48
|
+
id: `${this.pipeline.id}:${stage.id}`,
|
|
49
|
+
description: stage.title,
|
|
50
|
+
requires_cloud: requiresCloud,
|
|
51
|
+
requiresCloud,
|
|
52
|
+
runnable,
|
|
53
|
+
metadata
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
resolveTargetId(items) {
|
|
57
|
+
const explicit = this.normalizeTargetId(this.options.targetStageId ?? null, items);
|
|
58
|
+
if (explicit) {
|
|
59
|
+
return explicit;
|
|
60
|
+
}
|
|
61
|
+
const flagged = items.find((item) => item.metadata?.defaultTarget === true);
|
|
62
|
+
if (flagged) {
|
|
63
|
+
return flagged.id;
|
|
64
|
+
}
|
|
65
|
+
const runnableItems = items.filter((item) => item.runnable !== false);
|
|
66
|
+
if (runnableItems.length > 0) {
|
|
67
|
+
return runnableItems[0].id;
|
|
68
|
+
}
|
|
69
|
+
return items[0]?.id ?? null;
|
|
70
|
+
}
|
|
71
|
+
normalizeTargetId(candidate, items) {
|
|
72
|
+
if (!candidate) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const exact = items.find((item) => item.id === candidate);
|
|
76
|
+
if (exact) {
|
|
77
|
+
return exact.id;
|
|
78
|
+
}
|
|
79
|
+
const normalized = candidate.includes(':') ? candidate.split(':').pop() ?? candidate : candidate;
|
|
80
|
+
const lowerNormalized = normalized.toLowerCase();
|
|
81
|
+
for (const item of items) {
|
|
82
|
+
const stageId = item.metadata?.stageId?.toLowerCase();
|
|
83
|
+
if (stageId && stageId === lowerNormalized) {
|
|
84
|
+
return item.id;
|
|
85
|
+
}
|
|
86
|
+
const aliases = Array.isArray(item.metadata?.aliases)
|
|
87
|
+
? item.metadata?.aliases
|
|
88
|
+
: [];
|
|
89
|
+
if (aliases.some((alias) => alias.toLowerCase() === lowerNormalized)) {
|
|
90
|
+
return item.id;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function extractStagePlanHints(stage) {
|
|
97
|
+
const stageRecord = stage;
|
|
98
|
+
const planConfig = stageRecord.plan ?? {};
|
|
99
|
+
const aliases = Array.isArray(planConfig.aliases)
|
|
100
|
+
? planConfig.aliases.map((alias) => String(alias))
|
|
101
|
+
: [];
|
|
102
|
+
const defaultTarget = Boolean(planConfig.defaultTarget ?? planConfig.default ?? planConfig.primary);
|
|
103
|
+
const requiresCloud = typeof planConfig.requiresCloud === 'boolean'
|
|
104
|
+
? planConfig.requiresCloud
|
|
105
|
+
: typeof planConfig.requires_cloud === 'boolean'
|
|
106
|
+
? planConfig.requires_cloud
|
|
107
|
+
: undefined;
|
|
108
|
+
const rawExecutionMode = typeof planConfig.executionMode === 'string'
|
|
109
|
+
? planConfig.executionMode
|
|
110
|
+
: typeof stageRecord.executionMode === 'string'
|
|
111
|
+
? stageRecord.executionMode
|
|
112
|
+
: typeof stageRecord.execution_mode === 'string'
|
|
113
|
+
? stageRecord.execution_mode
|
|
114
|
+
: typeof stageRecord.mode === 'string'
|
|
115
|
+
? stageRecord.mode
|
|
116
|
+
: undefined;
|
|
117
|
+
const executionMode = typeof rawExecutionMode === 'string'
|
|
118
|
+
? rawExecutionMode.trim().toLowerCase() || null
|
|
119
|
+
: null;
|
|
120
|
+
return {
|
|
121
|
+
runnable: planConfig.runnable,
|
|
122
|
+
defaultTarget,
|
|
123
|
+
aliases,
|
|
124
|
+
requiresCloud,
|
|
125
|
+
executionMode
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function resolveStageRequiresCloud(stage, hints) {
|
|
129
|
+
const stageRecord = stage;
|
|
130
|
+
const requiresCloud = resolveRequiresCloudPolicy({
|
|
131
|
+
boolFlags: [
|
|
132
|
+
hints.requiresCloud,
|
|
133
|
+
typeof stageRecord.requires_cloud === 'boolean'
|
|
134
|
+
? stageRecord.requires_cloud
|
|
135
|
+
: undefined,
|
|
136
|
+
typeof stageRecord.requiresCloud === 'boolean'
|
|
137
|
+
? stageRecord.requiresCloud
|
|
138
|
+
: undefined
|
|
139
|
+
],
|
|
140
|
+
metadata: {
|
|
141
|
+
executionMode: hints.executionMode ?? null,
|
|
142
|
+
mode: null
|
|
143
|
+
},
|
|
144
|
+
metadataOrder: ['executionMode'],
|
|
145
|
+
parseMode: PLANNER_EXECUTION_MODE_PARSER
|
|
146
|
+
});
|
|
147
|
+
return requiresCloud ?? false;
|
|
148
|
+
}
|
|
149
|
+
function resolveStageRunnable(hints) {
|
|
150
|
+
if (typeof hints.runnable === 'boolean') {
|
|
151
|
+
return hints.runnable;
|
|
152
|
+
}
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
function clonePlanResult(plan) {
|
|
156
|
+
return {
|
|
157
|
+
items: plan.items.map((item) => ({
|
|
158
|
+
...item,
|
|
159
|
+
metadata: item.metadata ? { ...item.metadata } : undefined
|
|
160
|
+
})),
|
|
161
|
+
notes: plan.notes,
|
|
162
|
+
targetId: plan.targetId ?? null
|
|
163
|
+
};
|
|
164
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export class CommandReviewer {
|
|
2
|
+
getResult;
|
|
3
|
+
constructor(getResult) {
|
|
4
|
+
this.getResult = getResult;
|
|
5
|
+
}
|
|
6
|
+
async review(input) {
|
|
7
|
+
void input;
|
|
8
|
+
const result = this.requireResult();
|
|
9
|
+
const summaryLines = [
|
|
10
|
+
result.success
|
|
11
|
+
? 'Diagnostics pipeline succeeded.'
|
|
12
|
+
: 'Diagnostics pipeline failed — inspect manifest for details.',
|
|
13
|
+
`Manifest: ${result.manifestPath}`,
|
|
14
|
+
`Runner log: ${result.logPath}`
|
|
15
|
+
];
|
|
16
|
+
const summary = summaryLines.join('\n');
|
|
17
|
+
return {
|
|
18
|
+
summary,
|
|
19
|
+
decision: {
|
|
20
|
+
approved: result.success,
|
|
21
|
+
feedback: result.notes.join('\n') || undefined
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
requireResult() {
|
|
26
|
+
const result = this.getResult();
|
|
27
|
+
if (!result) {
|
|
28
|
+
throw new Error('Pipeline result unavailable during reviewer stage.');
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ensureGuardrailStatus } from '../run/manifest.js';
|
|
2
|
+
export class CommandTester {
|
|
3
|
+
getResult;
|
|
4
|
+
constructor(getResult) {
|
|
5
|
+
this.getResult = getResult;
|
|
6
|
+
}
|
|
7
|
+
async test(input) {
|
|
8
|
+
const result = this.requireResult();
|
|
9
|
+
const guardrailStatus = ensureGuardrailStatus(result.manifest);
|
|
10
|
+
const reports = [
|
|
11
|
+
{
|
|
12
|
+
name: 'guardrails',
|
|
13
|
+
status: guardrailStatus.present ? 'passed' : 'failed',
|
|
14
|
+
details: guardrailStatus.present
|
|
15
|
+
? guardrailStatus.summary
|
|
16
|
+
: guardrailStatus.recommendation ?? guardrailStatus.summary
|
|
17
|
+
}
|
|
18
|
+
];
|
|
19
|
+
return {
|
|
20
|
+
subtaskId: input.build.subtaskId,
|
|
21
|
+
success: guardrailStatus.present && result.success,
|
|
22
|
+
reports,
|
|
23
|
+
runId: input.runId
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
requireResult() {
|
|
27
|
+
const result = this.getResult();
|
|
28
|
+
if (!result) {
|
|
29
|
+
throw new Error('Pipeline result unavailable during tester stage.');
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { logger } from '../../logger.js';
|
|
4
|
+
export async function loadUserConfig(env) {
|
|
5
|
+
const configPath = join(env.repoRoot, 'codex.orchestrator.json');
|
|
6
|
+
try {
|
|
7
|
+
const raw = await readFile(configPath, 'utf8');
|
|
8
|
+
const parsed = JSON.parse(raw);
|
|
9
|
+
logger.info(`[codex-config] Loaded user config from ${configPath}`);
|
|
10
|
+
if (parsed && Array.isArray(parsed.pipelines)) {
|
|
11
|
+
return parsed;
|
|
12
|
+
}
|
|
13
|
+
return parsed ?? null;
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
if (error.code === 'ENOENT') {
|
|
17
|
+
logger.warn(`[codex-config] Missing codex.orchestrator.json at ${configPath}`);
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function findPipeline(config, id) {
|
|
24
|
+
if (!config?.pipelines) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return config.pipelines.find((pipeline) => pipeline.id === id) ?? null;
|
|
28
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import { resolveOptionalDependency } from './utils/optionalDeps.js';
|
|
3
|
+
const OPTIONAL_DEPENDENCIES = [
|
|
4
|
+
{
|
|
5
|
+
name: 'playwright',
|
|
6
|
+
install: 'npm install --save-dev playwright && npx playwright install'
|
|
7
|
+
},
|
|
8
|
+
{ name: 'pngjs', install: 'npm install --save-dev pngjs' },
|
|
9
|
+
{ name: 'pixelmatch', install: 'npm install --save-dev pixelmatch' },
|
|
10
|
+
{ name: 'cheerio', install: 'npm install --save-dev cheerio' }
|
|
11
|
+
];
|
|
12
|
+
export function runDoctor(cwd = process.cwd()) {
|
|
13
|
+
const dependencies = OPTIONAL_DEPENDENCIES.map((entry) => {
|
|
14
|
+
const resolved = resolveOptionalDependency(entry.name, cwd);
|
|
15
|
+
if (resolved.path) {
|
|
16
|
+
return { name: entry.name, status: 'ok', source: resolved.source };
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
name: entry.name,
|
|
20
|
+
status: 'missing',
|
|
21
|
+
source: null,
|
|
22
|
+
install: entry.install
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
const missing = dependencies.filter((dep) => dep.status === 'missing').map((dep) => dep.name);
|
|
26
|
+
return {
|
|
27
|
+
status: missing.length === 0 ? 'ok' : 'warning',
|
|
28
|
+
missing,
|
|
29
|
+
dependencies
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function formatDoctorSummary(result) {
|
|
33
|
+
const lines = [];
|
|
34
|
+
lines.push(`Status: ${result.status}`);
|
|
35
|
+
for (const dep of result.dependencies) {
|
|
36
|
+
if (dep.status === 'ok') {
|
|
37
|
+
const source = dep.source ? ` (${dep.source})` : '';
|
|
38
|
+
lines.push(` - ${dep.name}: ok${source}`);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
lines.push(` - ${dep.name}: missing`);
|
|
42
|
+
if (dep.install) {
|
|
43
|
+
lines.push(` install: ${dep.install}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return lines;
|
|
48
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { isoTimestamp } from '../utils/time.js';
|
|
3
|
+
export class RunEventEmitter {
|
|
4
|
+
emitter = new EventEmitter({ captureRejections: false });
|
|
5
|
+
emit(event) {
|
|
6
|
+
this.emitter.emit(event.type, event);
|
|
7
|
+
this.emitter.emit('*', event);
|
|
8
|
+
}
|
|
9
|
+
on(type, listener) {
|
|
10
|
+
const wrapped = (payload) => listener(payload);
|
|
11
|
+
this.emitter.on(type, wrapped);
|
|
12
|
+
return () => {
|
|
13
|
+
this.emitter.off(type, wrapped);
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
dispose() {
|
|
17
|
+
this.emitter.removeAllListeners();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export class RunEventPublisher {
|
|
21
|
+
emitter;
|
|
22
|
+
context;
|
|
23
|
+
constructor(emitter, context) {
|
|
24
|
+
this.emitter = emitter;
|
|
25
|
+
this.context = context;
|
|
26
|
+
}
|
|
27
|
+
runStarted(stages, status) {
|
|
28
|
+
this.emit('run:started', {
|
|
29
|
+
pipelineId: this.context.pipelineId,
|
|
30
|
+
pipelineTitle: this.context.pipelineTitle,
|
|
31
|
+
manifestPath: this.context.manifestPath,
|
|
32
|
+
logPath: this.context.logPath,
|
|
33
|
+
status,
|
|
34
|
+
stages
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
stageStarted(payload) {
|
|
38
|
+
this.emit('stage:started', payload);
|
|
39
|
+
}
|
|
40
|
+
stageCompleted(payload) {
|
|
41
|
+
this.emit('stage:completed', payload);
|
|
42
|
+
}
|
|
43
|
+
runCompleted(payload) {
|
|
44
|
+
this.emit('run:completed', payload);
|
|
45
|
+
}
|
|
46
|
+
runError(payload) {
|
|
47
|
+
this.emit('run:error', payload);
|
|
48
|
+
}
|
|
49
|
+
log(payload) {
|
|
50
|
+
this.emit('log', payload);
|
|
51
|
+
}
|
|
52
|
+
toolCall(payload) {
|
|
53
|
+
this.emit('tool:call', payload);
|
|
54
|
+
}
|
|
55
|
+
emit(type, payload) {
|
|
56
|
+
if (!this.emitter) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const base = {
|
|
60
|
+
type: type,
|
|
61
|
+
timestamp: isoTimestamp(),
|
|
62
|
+
taskId: this.context.taskId,
|
|
63
|
+
runId: this.context.runId
|
|
64
|
+
};
|
|
65
|
+
const event = { ...base, ...payload };
|
|
66
|
+
this.emitter.emit(event);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export function snapshotStages(manifest, pipeline) {
|
|
70
|
+
return manifest.commands.map((command) => {
|
|
71
|
+
const stage = pipeline.stages[command.index - 1];
|
|
72
|
+
return {
|
|
73
|
+
index: command.index,
|
|
74
|
+
id: command.id,
|
|
75
|
+
title: command.title,
|
|
76
|
+
kind: stage?.kind === 'subpipeline' ? 'subpipeline' : 'command',
|
|
77
|
+
status: command.status,
|
|
78
|
+
summary: command.summary,
|
|
79
|
+
exitCode: command.exit_code,
|
|
80
|
+
logPath: command.log_path,
|
|
81
|
+
subRunId: command.sub_run_id
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { serializeRunSummaryEvent } from '../../../../packages/shared/events/serializer.js';
|
|
2
|
+
import { appendMetricsEntry } from '../metrics/metricsRecorder.js';
|
|
3
|
+
import { resolveTfgrpoContext } from './tfgrpo.js';
|
|
4
|
+
import { bootstrapExecContext } from './context.js';
|
|
5
|
+
import { runExecStage } from './stageRunner.js';
|
|
6
|
+
import { finalizeCommandLifecycle } from './finalization.js';
|
|
7
|
+
import { handleTfgrpoArtifacts } from './tfgrpoArtifacts.js';
|
|
8
|
+
import { buildExecRunSummary, createRunSummaryPayload, emitCommandError, persistRunOutputs, renderRunOutput } from './summary.js';
|
|
9
|
+
import { deliverNotifications, flushTelemetry, recordSummaryTelemetry, shutdownSinks } from './telemetry.js';
|
|
10
|
+
import { maybeTriggerLearning } from './learning.js';
|
|
11
|
+
export async function executeExecCommand(context, invocation) {
|
|
12
|
+
if (!invocation.command) {
|
|
13
|
+
throw new Error('exec command requires a command to run.');
|
|
14
|
+
}
|
|
15
|
+
const runContext = await bootstrapExecContext(context, invocation);
|
|
16
|
+
const stageResult = await runExecStage(runContext);
|
|
17
|
+
const finalization = await finalizeCommandLifecycle(runContext, stageResult);
|
|
18
|
+
const tfgrpoContext = resolveTfgrpoContext();
|
|
19
|
+
const runMetricSummary = await handleTfgrpoArtifacts(runContext, finalization, tfgrpoContext);
|
|
20
|
+
const summaryPayload = createRunSummaryPayload({
|
|
21
|
+
env: runContext.env,
|
|
22
|
+
paths: runContext.paths,
|
|
23
|
+
manifest: runContext.manifest,
|
|
24
|
+
runStatus: finalization.runStatus,
|
|
25
|
+
shellCommand: runContext.shellCommand,
|
|
26
|
+
argv: runContext.argv,
|
|
27
|
+
resultSummary: finalization.summarySnapshot,
|
|
28
|
+
toolRecord: finalization.toolRecord,
|
|
29
|
+
execEvents: runContext.execEvents,
|
|
30
|
+
exitCode: finalization.exitCode,
|
|
31
|
+
signal: finalization.signal,
|
|
32
|
+
notificationTargets: runContext.notificationSink.targets,
|
|
33
|
+
cwd: runContext.stage.cwd ?? null,
|
|
34
|
+
metrics: runMetricSummary
|
|
35
|
+
});
|
|
36
|
+
const summaryEvent = serializeRunSummaryEvent(summaryPayload);
|
|
37
|
+
await deliverNotifications(runContext, summaryPayload, summaryEvent);
|
|
38
|
+
recordSummaryTelemetry(runContext, summaryEvent);
|
|
39
|
+
renderRunOutput(runContext, summaryPayload, summaryEvent);
|
|
40
|
+
await flushTelemetry(runContext);
|
|
41
|
+
await persistRunOutputs(runContext, summaryEvent);
|
|
42
|
+
await appendMetricsEntry(runContext.env, runContext.paths, runContext.manifest, runContext.persister);
|
|
43
|
+
await maybeTriggerLearning(runContext, finalization.runStatus);
|
|
44
|
+
await runContext.persister.flush();
|
|
45
|
+
await shutdownSinks(runContext);
|
|
46
|
+
emitCommandError(runContext, finalization.commandError);
|
|
47
|
+
return buildExecRunSummary({
|
|
48
|
+
manifest: runContext.manifest,
|
|
49
|
+
summaryPayload,
|
|
50
|
+
summaryEvent,
|
|
51
|
+
shellCommand: runContext.shellCommand,
|
|
52
|
+
argv: runContext.argv,
|
|
53
|
+
events: runContext.execEvents,
|
|
54
|
+
toolRecord: finalization.toolRecord
|
|
55
|
+
});
|
|
56
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import { bootstrapManifest } from '../run/manifest.js';
|
|
3
|
+
import { generateRunId } from '../utils/runId.js';
|
|
4
|
+
import { JsonlWriter } from '../utils/jsonlWriter.js';
|
|
5
|
+
import { ExperienceStore } from '../../persistence/ExperienceStore.js';
|
|
6
|
+
import { createTelemetrySink } from '../../../../packages/orchestrator/src/telemetry/otel-exporter.js';
|
|
7
|
+
import { createNotificationSink } from '../../../../packages/orchestrator/src/notifications/index.js';
|
|
8
|
+
import { ManifestPersister, persistManifest } from '../run/manifestPersister.js';
|
|
9
|
+
export async function bootstrapExecContext(context, invocation) {
|
|
10
|
+
const argv = [invocation.command, ...(invocation.args ?? [])];
|
|
11
|
+
const shellCommand = buildShellCommand(argv);
|
|
12
|
+
const stdout = context.stdout ?? process.stdout;
|
|
13
|
+
const stderr = context.stderr ?? process.stderr;
|
|
14
|
+
const outputMode = invocation.outputMode;
|
|
15
|
+
if (outputMode === 'interactive') {
|
|
16
|
+
stdout.write(`$ ${shellCommand}\n`);
|
|
17
|
+
}
|
|
18
|
+
const runIdFactory = context.runIdFactory ?? generateRunId;
|
|
19
|
+
const env = context.env;
|
|
20
|
+
const experienceStore = new ExperienceStore({
|
|
21
|
+
outDir: env.outRoot,
|
|
22
|
+
runsDir: env.runsRoot
|
|
23
|
+
});
|
|
24
|
+
const runId = runIdFactory();
|
|
25
|
+
const pipeline = createPipeline(shellCommand, invocation, env);
|
|
26
|
+
const stage = pipeline.stages[0];
|
|
27
|
+
const { manifest, paths } = await bootstrapManifest(runId, {
|
|
28
|
+
env,
|
|
29
|
+
pipeline,
|
|
30
|
+
taskSlug: null,
|
|
31
|
+
approvalPolicy: null
|
|
32
|
+
});
|
|
33
|
+
const persister = new ManifestPersister({
|
|
34
|
+
manifest,
|
|
35
|
+
paths,
|
|
36
|
+
persistIntervalMs: Math.max(1000, manifest.heartbeat_interval_seconds * 1000)
|
|
37
|
+
});
|
|
38
|
+
manifest.status = 'in_progress';
|
|
39
|
+
await persistManifest(paths, manifest, persister, { force: true });
|
|
40
|
+
const telemetrySink = context.telemetrySink ?? createTelemetrySink({
|
|
41
|
+
endpoint: invocation.otelEndpoint,
|
|
42
|
+
enabled: Boolean(invocation.otelEndpoint)
|
|
43
|
+
});
|
|
44
|
+
const envNotifications = parseNotificationEnv(process.env.CODEX_ORCHESTRATOR_NOTIFY);
|
|
45
|
+
const notificationSink = context.notificationSink ??
|
|
46
|
+
createNotificationSink({
|
|
47
|
+
targets: invocation.notifyTargets,
|
|
48
|
+
envTargets: envNotifications
|
|
49
|
+
});
|
|
50
|
+
const jsonlWriter = outputMode === 'jsonl' ? new JsonlWriter(stdout) : null;
|
|
51
|
+
return {
|
|
52
|
+
env,
|
|
53
|
+
invocation,
|
|
54
|
+
argv,
|
|
55
|
+
shellCommand,
|
|
56
|
+
outputMode,
|
|
57
|
+
stdout,
|
|
58
|
+
stderr,
|
|
59
|
+
runId,
|
|
60
|
+
pipeline,
|
|
61
|
+
stage,
|
|
62
|
+
manifest,
|
|
63
|
+
paths,
|
|
64
|
+
persister,
|
|
65
|
+
telemetrySink,
|
|
66
|
+
notificationSink,
|
|
67
|
+
jsonlWriter,
|
|
68
|
+
experienceStore,
|
|
69
|
+
execEvents: [],
|
|
70
|
+
telemetryTasks: []
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export function createPipeline(shellCommand, invocation, env) {
|
|
74
|
+
const stage = {
|
|
75
|
+
kind: 'command',
|
|
76
|
+
id: 'exec',
|
|
77
|
+
title: invocation.description ?? `Execute ${shellCommand}`,
|
|
78
|
+
command: shellCommand,
|
|
79
|
+
cwd: invocation.cwd ?? env.repoRoot,
|
|
80
|
+
env: invocation.env,
|
|
81
|
+
session: invocation.session
|
|
82
|
+
};
|
|
83
|
+
return {
|
|
84
|
+
id: 'exec',
|
|
85
|
+
title: 'CLI Exec Command',
|
|
86
|
+
stages: [stage],
|
|
87
|
+
guardrailsRequired: false
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
export function buildShellCommand(argv) {
|
|
91
|
+
return argv.map(shellEscape).join(' ');
|
|
92
|
+
}
|
|
93
|
+
export function parseNotificationEnv(value) {
|
|
94
|
+
if (!value) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
const tokens = value.split(',').map((entry) => entry.trim()).filter(Boolean);
|
|
98
|
+
return tokens.length ? tokens : null;
|
|
99
|
+
}
|
|
100
|
+
function shellEscape(value) {
|
|
101
|
+
if (value === '') {
|
|
102
|
+
return "''";
|
|
103
|
+
}
|
|
104
|
+
if (/^[A-Za-z0-9_/.:=-]+$/.test(value)) {
|
|
105
|
+
return value;
|
|
106
|
+
}
|
|
107
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
108
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const DEFAULT_POLICY = {
|
|
2
|
+
maxSummaryWords: 32,
|
|
3
|
+
rewardFloor: 0
|
|
4
|
+
};
|
|
5
|
+
export function summarizeTrajectory(request) {
|
|
6
|
+
const reward = request.reward ?? { gtScore: 0, relativeRank: 0 };
|
|
7
|
+
const summary = buildTrajectorySummary(request.frames, request.baseSummary);
|
|
8
|
+
const toolStats = request.frames.map(toToolStat);
|
|
9
|
+
return {
|
|
10
|
+
runId: request.runId,
|
|
11
|
+
taskId: request.taskId,
|
|
12
|
+
epoch: request.epoch,
|
|
13
|
+
groupId: request.groupId,
|
|
14
|
+
summary,
|
|
15
|
+
reward,
|
|
16
|
+
toolStats,
|
|
17
|
+
stampSignature: request.stampSignature,
|
|
18
|
+
domain: request.domain
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function optimizeExperience(input, policy = DEFAULT_POLICY) {
|
|
22
|
+
const limit = Math.max(1, policy.maxSummaryWords);
|
|
23
|
+
return {
|
|
24
|
+
...input,
|
|
25
|
+
summary: truncateSummary(input.summary, limit),
|
|
26
|
+
reward: {
|
|
27
|
+
gtScore: Math.max(input.reward.gtScore, policy.rewardFloor ?? 0),
|
|
28
|
+
relativeRank: input.reward.relativeRank
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function framesFromToolMetrics(metrics, representativeEvent) {
|
|
33
|
+
return metrics.map((metric) => ({
|
|
34
|
+
event: representativeEvent,
|
|
35
|
+
tool: metric.tool,
|
|
36
|
+
tokens: metric.tokens,
|
|
37
|
+
costUsd: metric.costUsd,
|
|
38
|
+
latencyMs: metric.latencyMs
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
export function formatExperienceInjections(experiences, slots) {
|
|
42
|
+
if (slots <= 0) {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
return experiences.slice(0, slots).map((experience) => {
|
|
46
|
+
const rewardScore = (experience.reward.gtScore + experience.reward.relativeRank).toFixed(2);
|
|
47
|
+
const statText = experience.toolStats
|
|
48
|
+
.map((stat) => `${stat.tool}: ${stat.tokens}t/${stat.costUsd.toFixed(3)}usd/${Math.round(stat.latencyMs)}ms`)
|
|
49
|
+
.join('; ');
|
|
50
|
+
return `[exp ${experience.id} | epoch ${experience.epoch ?? 'n/a'} | reward ${rewardScore}] ${experience.summary32} (stats: ${statText})`;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
function buildTrajectorySummary(frames, fallback) {
|
|
54
|
+
const terminal = frames[frames.length - 1];
|
|
55
|
+
if (terminal?.event.type === 'exec:end') {
|
|
56
|
+
const stdout = terminal.event.payload.stdout?.trim();
|
|
57
|
+
if (stdout) {
|
|
58
|
+
return stdout.split('\n').slice(0, 2).join(' ');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return fallback ?? 'TF-GRPO trajectory summary unavailable.';
|
|
62
|
+
}
|
|
63
|
+
function toToolStat(frame) {
|
|
64
|
+
return {
|
|
65
|
+
tool: frame.tool,
|
|
66
|
+
tokens: frame.tokens,
|
|
67
|
+
costUsd: frame.costUsd,
|
|
68
|
+
latencyMs: frame.latencyMs
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function truncateSummary(value, maxWords) {
|
|
72
|
+
const tokens = value.trim().split(/\s+/u).filter(Boolean);
|
|
73
|
+
if (tokens.length <= maxWords) {
|
|
74
|
+
return tokens.join(' ');
|
|
75
|
+
}
|
|
76
|
+
return tokens.slice(0, maxWords).join(' ');
|
|
77
|
+
}
|