@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,466 @@
|
|
|
1
|
+
import { mkdir, readFile, rm } from 'node:fs/promises';
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
|
+
import { isAbsolute, join, relative } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
import { pathToFileURL } from 'node:url';
|
|
7
|
+
import { loadDesignContext } from './context.js';
|
|
8
|
+
import { appendApprovals, appendArtifacts, ensureToolkitState, loadDesignRunState, saveDesignRunState, upsertStage } from './state.js';
|
|
9
|
+
import { stageArtifacts } from '../../../orchestrator/src/persistence/ArtifactStager.js';
|
|
10
|
+
import { runDefaultInteractions } from './toolkit/snapshot.js';
|
|
11
|
+
import { loadPlaywright } from './optionalDeps.js';
|
|
12
|
+
const execFileAsync = promisify(execFile);
|
|
13
|
+
const MOTION_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
|
14
|
+
const DEFAULT_CAPTURE_SECONDS = 12;
|
|
15
|
+
const MIN_CAPTURE_SECONDS = 4;
|
|
16
|
+
async function main() {
|
|
17
|
+
const context = await loadDesignContext();
|
|
18
|
+
const state = await loadDesignRunState(context.statePath);
|
|
19
|
+
const stageId = 'design-advanced-assets';
|
|
20
|
+
const advanced = context.config.config.advanced;
|
|
21
|
+
if (!advanced.framerMotion.enabled && !advanced.ffmpeg.enabled) {
|
|
22
|
+
upsertStage(state, {
|
|
23
|
+
id: stageId,
|
|
24
|
+
title: 'Advanced asset generation',
|
|
25
|
+
status: 'skipped',
|
|
26
|
+
notes: ['Advanced assets disabled in design.config.yaml']
|
|
27
|
+
});
|
|
28
|
+
await saveDesignRunState(context.statePath, state);
|
|
29
|
+
console.log('[design-advanced-assets] skipped — advanced features disabled');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const toolkit = ensureToolkitState(state);
|
|
33
|
+
const contexts = toolkit.contexts;
|
|
34
|
+
if (contexts.length === 0) {
|
|
35
|
+
upsertStage(state, {
|
|
36
|
+
id: stageId,
|
|
37
|
+
title: 'Advanced asset generation',
|
|
38
|
+
status: 'skipped',
|
|
39
|
+
notes: ['No toolkit contexts available. Run extract + reference stages first.']
|
|
40
|
+
});
|
|
41
|
+
await saveDesignRunState(context.statePath, state);
|
|
42
|
+
console.log('[design-advanced-assets] skipped — no toolkit contexts to capture');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
await ensureFfmpegAvailable();
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
50
|
+
upsertStage(state, {
|
|
51
|
+
id: stageId,
|
|
52
|
+
title: 'Advanced asset generation',
|
|
53
|
+
status: 'failed',
|
|
54
|
+
notes: [message]
|
|
55
|
+
});
|
|
56
|
+
await saveDesignRunState(context.statePath, state);
|
|
57
|
+
throw new Error(message);
|
|
58
|
+
}
|
|
59
|
+
const tmpRoot = join(tmpdir(), `design-motion-${Date.now()}`);
|
|
60
|
+
await mkdir(tmpRoot, { recursive: true });
|
|
61
|
+
const motionEnabled = advanced.framerMotion.enabled;
|
|
62
|
+
const videoEnabled = advanced.ffmpeg.enabled;
|
|
63
|
+
let remainingMotionSeconds = motionEnabled ? Math.max(0, advanced.framerMotion.quotaSeconds) : 0;
|
|
64
|
+
let remainingVideoSeconds = videoEnabled ? Math.max(0, advanced.ffmpeg.quotaSeconds) : 0;
|
|
65
|
+
if ((motionEnabled && remainingMotionSeconds <= 0) && (videoEnabled && remainingVideoSeconds <= 0)) {
|
|
66
|
+
upsertStage(state, {
|
|
67
|
+
id: stageId,
|
|
68
|
+
title: 'Advanced asset generation',
|
|
69
|
+
status: 'skipped',
|
|
70
|
+
notes: ['Motion/FFmpeg quotas exhausted before capture']
|
|
71
|
+
});
|
|
72
|
+
await saveDesignRunState(context.statePath, state);
|
|
73
|
+
console.log('[design-advanced-assets] skipped — quotaSeconds=0 for enabled advanced features');
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const pipelineConfig = context.config.config.pipelines.hiFiDesignToolkit;
|
|
77
|
+
const designArtifacts = [];
|
|
78
|
+
const approvals = [];
|
|
79
|
+
const stageArtifactsSummaries = [];
|
|
80
|
+
const failures = [];
|
|
81
|
+
let captureCount = 0;
|
|
82
|
+
let motionSeconds = 0;
|
|
83
|
+
let videoSeconds = 0;
|
|
84
|
+
let quotaStopped = false;
|
|
85
|
+
for (const entry of contexts) {
|
|
86
|
+
const captureDuration = computeCaptureDuration({
|
|
87
|
+
motionEnabled,
|
|
88
|
+
videoEnabled,
|
|
89
|
+
remainingMotionSeconds,
|
|
90
|
+
remainingVideoSeconds,
|
|
91
|
+
maxDurationSeconds: videoEnabled && advanced.ffmpeg.maxDurationSeconds
|
|
92
|
+
? advanced.ffmpeg.maxDurationSeconds
|
|
93
|
+
: DEFAULT_CAPTURE_SECONDS
|
|
94
|
+
});
|
|
95
|
+
if (captureDuration < MIN_CAPTURE_SECONDS) {
|
|
96
|
+
quotaStopped = true;
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const viewport = resolveViewport(entry, pipelineConfig);
|
|
101
|
+
const capture = await recordInteractionVideo(entry, viewport, captureDuration, tmpRoot, context.repoRoot);
|
|
102
|
+
const outputs = await transcodeMotionOutputs({
|
|
103
|
+
slug: entry.slug,
|
|
104
|
+
rawVideoPath: capture.rawVideoPath,
|
|
105
|
+
durationSeconds: captureDuration,
|
|
106
|
+
tmpRoot,
|
|
107
|
+
motionEnabled,
|
|
108
|
+
videoEnabled
|
|
109
|
+
});
|
|
110
|
+
const artifactInputs = buildArtifactInputs(outputs, entry.slug);
|
|
111
|
+
if (artifactInputs.length === 0) {
|
|
112
|
+
failures.push(`${entry.slug}: no motion outputs produced`);
|
|
113
|
+
await safeRemove(capture.rawVideoPath);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const staged = await stageArtifacts({
|
|
117
|
+
taskId: context.taskId,
|
|
118
|
+
runId: context.runId,
|
|
119
|
+
artifacts: artifactInputs.map((artifact) => ({
|
|
120
|
+
path: relative(process.cwd(), artifact.path),
|
|
121
|
+
description: artifact.description
|
|
122
|
+
})),
|
|
123
|
+
options: {
|
|
124
|
+
relativeDir: join('design-toolkit', 'diffs', entry.slug, 'motion'),
|
|
125
|
+
overwrite: true
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
const slugApprovals = buildApprovals(entry.slug, advanced, context);
|
|
129
|
+
approvals.push(...slugApprovals);
|
|
130
|
+
let index = 0;
|
|
131
|
+
if (outputs.motionPath) {
|
|
132
|
+
const stagedMotion = staged[index++];
|
|
133
|
+
motionSeconds += captureDuration;
|
|
134
|
+
remainingMotionSeconds = Math.max(0, remainingMotionSeconds - captureDuration);
|
|
135
|
+
const approval = slugApprovals.find((record) => record.id === `motion-${entry.slug}`);
|
|
136
|
+
designArtifacts.push(buildDesignArtifact({
|
|
137
|
+
stage: 'motion',
|
|
138
|
+
slug: entry.slug,
|
|
139
|
+
path: stagedMotion.path,
|
|
140
|
+
durationSeconds: captureDuration,
|
|
141
|
+
viewport,
|
|
142
|
+
url: capture.url,
|
|
143
|
+
approval,
|
|
144
|
+
quotaLimit: advanced.framerMotion.quotaSeconds,
|
|
145
|
+
format: 'webm'
|
|
146
|
+
}));
|
|
147
|
+
stageArtifactsSummaries.push({
|
|
148
|
+
relative_path: stagedMotion.path,
|
|
149
|
+
stage: 'motion',
|
|
150
|
+
status: 'succeeded',
|
|
151
|
+
description: `${entry.slug} motion loop (WebM)`
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
if (outputs.videoPath) {
|
|
155
|
+
const stagedVideo = staged[index++];
|
|
156
|
+
videoSeconds += captureDuration;
|
|
157
|
+
remainingVideoSeconds = Math.max(0, remainingVideoSeconds - captureDuration);
|
|
158
|
+
const approval = slugApprovals.find((record) => record.id === `ffmpeg-motion-${entry.slug}`);
|
|
159
|
+
designArtifacts.push(buildDesignArtifact({
|
|
160
|
+
stage: 'video',
|
|
161
|
+
slug: entry.slug,
|
|
162
|
+
path: stagedVideo.path,
|
|
163
|
+
durationSeconds: captureDuration,
|
|
164
|
+
viewport,
|
|
165
|
+
url: capture.url,
|
|
166
|
+
approval,
|
|
167
|
+
quotaLimit: advanced.ffmpeg.quotaSeconds,
|
|
168
|
+
format: 'mp4'
|
|
169
|
+
}));
|
|
170
|
+
stageArtifactsSummaries.push({
|
|
171
|
+
relative_path: stagedVideo.path,
|
|
172
|
+
stage: 'video',
|
|
173
|
+
status: 'succeeded',
|
|
174
|
+
description: `${entry.slug} motion loop (MP4)`
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
captureCount += 1;
|
|
178
|
+
await safeRemove(capture.rawVideoPath);
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
182
|
+
failures.push(`${entry.slug}: ${message}`);
|
|
183
|
+
console.error(`[design-advanced-assets] failed for ${entry.slug}: ${message}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (designArtifacts.length > 0) {
|
|
187
|
+
appendArtifacts(state, designArtifacts);
|
|
188
|
+
}
|
|
189
|
+
if (approvals.length > 0) {
|
|
190
|
+
appendApprovals(state, approvals);
|
|
191
|
+
}
|
|
192
|
+
let status = 'succeeded';
|
|
193
|
+
if (captureCount === 0) {
|
|
194
|
+
status = failures.length > 0 ? 'failed' : 'skipped';
|
|
195
|
+
}
|
|
196
|
+
const notes = [...failures];
|
|
197
|
+
if (quotaStopped) {
|
|
198
|
+
notes.push('Stopped early after exhausting capture quota.');
|
|
199
|
+
}
|
|
200
|
+
upsertStage(state, {
|
|
201
|
+
id: stageId,
|
|
202
|
+
title: 'Advanced asset generation',
|
|
203
|
+
status,
|
|
204
|
+
notes: notes.length > 0 ? notes : undefined,
|
|
205
|
+
metrics: captureCount > 0
|
|
206
|
+
? {
|
|
207
|
+
capture_count: captureCount,
|
|
208
|
+
motion_seconds: Number(motionSeconds.toFixed(2)),
|
|
209
|
+
video_seconds: Number(videoSeconds.toFixed(2)),
|
|
210
|
+
motion_quota_remaining: Number(remainingMotionSeconds.toFixed(2)),
|
|
211
|
+
video_quota_remaining: Number(remainingVideoSeconds.toFixed(2))
|
|
212
|
+
}
|
|
213
|
+
: undefined,
|
|
214
|
+
artifacts: stageArtifactsSummaries.length > 0 ? stageArtifactsSummaries : undefined
|
|
215
|
+
});
|
|
216
|
+
await saveDesignRunState(context.statePath, state);
|
|
217
|
+
if (status === 'failed') {
|
|
218
|
+
throw new Error('Advanced asset generation failed.');
|
|
219
|
+
}
|
|
220
|
+
console.log(`[design-advanced-assets] captured ${captureCount} motion loop${captureCount === 1 ? '' : 's'}`);
|
|
221
|
+
}
|
|
222
|
+
function buildDesignArtifact(options) {
|
|
223
|
+
return {
|
|
224
|
+
stage: options.stage,
|
|
225
|
+
status: 'succeeded',
|
|
226
|
+
relative_path: options.path,
|
|
227
|
+
description: `${options.slug} motion capture (${options.format.toUpperCase()})`,
|
|
228
|
+
approvals: options.approval ? [options.approval] : undefined,
|
|
229
|
+
quota: {
|
|
230
|
+
type: 'runtime',
|
|
231
|
+
unit: 'seconds',
|
|
232
|
+
limit: options.quotaLimit,
|
|
233
|
+
consumed: Number(options.durationSeconds.toFixed(2))
|
|
234
|
+
},
|
|
235
|
+
metadata: {
|
|
236
|
+
slug: options.slug,
|
|
237
|
+
url: options.url,
|
|
238
|
+
duration_seconds: Number(options.durationSeconds.toFixed(2)),
|
|
239
|
+
viewport: `${options.viewport.width}x${options.viewport.height}`,
|
|
240
|
+
format: options.format
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function buildApprovals(slug, advanced, context) {
|
|
245
|
+
const approvals = [];
|
|
246
|
+
const defaultActor = context.config.config.metadata.design.privacy.approver ?? 'design-reviewer';
|
|
247
|
+
if (advanced.framerMotion.enabled) {
|
|
248
|
+
approvals.push({
|
|
249
|
+
id: `motion-${slug}`,
|
|
250
|
+
actor: advanced.framerMotion.approver ?? defaultActor,
|
|
251
|
+
reason: `Framer Motion capture approved for ${slug}`,
|
|
252
|
+
timestamp: new Date().toISOString()
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
if (advanced.ffmpeg.enabled) {
|
|
256
|
+
approvals.push({
|
|
257
|
+
id: `ffmpeg-motion-${slug}`,
|
|
258
|
+
actor: advanced.ffmpeg.approver ?? defaultActor,
|
|
259
|
+
reason: `FFmpeg capture approved for ${slug}`,
|
|
260
|
+
timestamp: new Date().toISOString()
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
return approvals;
|
|
264
|
+
}
|
|
265
|
+
function buildArtifactInputs(outputs, slug) {
|
|
266
|
+
const artifacts = [];
|
|
267
|
+
if (outputs.motionPath) {
|
|
268
|
+
artifacts.push({ path: outputs.motionPath, description: `${slug} motion loop (WebM)` });
|
|
269
|
+
}
|
|
270
|
+
if (outputs.videoPath) {
|
|
271
|
+
artifacts.push({ path: outputs.videoPath, description: `${slug} motion loop (MP4)` });
|
|
272
|
+
}
|
|
273
|
+
return artifacts;
|
|
274
|
+
}
|
|
275
|
+
async function ensureFfmpegAvailable() {
|
|
276
|
+
try {
|
|
277
|
+
await execFileAsync('ffmpeg', ['-version']);
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
throw new Error('FFmpeg is required for advanced motion capture. Install it or run `npm run setup:design-tools` before rerunning.');
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function resolveViewport(entry, pipeline) {
|
|
284
|
+
for (const breakpointId of entry.breakpoints) {
|
|
285
|
+
const match = pipeline.breakpoints.find((bp) => bp.id === breakpointId);
|
|
286
|
+
if (match) {
|
|
287
|
+
return { width: match.width, height: match.height };
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const fallback = pipeline.breakpoints[0];
|
|
291
|
+
if (fallback) {
|
|
292
|
+
return { width: fallback.width, height: fallback.height };
|
|
293
|
+
}
|
|
294
|
+
return { width: 1440, height: 900 };
|
|
295
|
+
}
|
|
296
|
+
async function recordInteractionVideo(entry, viewport, durationSeconds, tmpRoot, repoRoot) {
|
|
297
|
+
const captureDir = join(tmpRoot, entry.slug, `${Date.now()}`);
|
|
298
|
+
await mkdir(captureDir, { recursive: true });
|
|
299
|
+
const playwright = await loadPlaywright();
|
|
300
|
+
const browser = await playwright.chromium.launch({ headless: true });
|
|
301
|
+
const targetUrl = resolveMotionTarget(entry, repoRoot);
|
|
302
|
+
const macro = await loadMotionMacro(entry, repoRoot);
|
|
303
|
+
let context = null;
|
|
304
|
+
try {
|
|
305
|
+
const activeContext = await browser.newContext({
|
|
306
|
+
viewport,
|
|
307
|
+
userAgent: MOTION_USER_AGENT,
|
|
308
|
+
recordVideo: {
|
|
309
|
+
dir: captureDir,
|
|
310
|
+
size: viewport
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
if (targetUrl.startsWith('file:')) {
|
|
314
|
+
await activeContext.route('**/*', (route) => {
|
|
315
|
+
const url = route.request().url();
|
|
316
|
+
if (url.startsWith('file:') || url.startsWith('data:')) {
|
|
317
|
+
return route.continue();
|
|
318
|
+
}
|
|
319
|
+
return route.abort();
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
context = activeContext;
|
|
323
|
+
if (macro?.contextScript) {
|
|
324
|
+
await activeContext.addInitScript({ content: macro.contextScript });
|
|
325
|
+
}
|
|
326
|
+
const activePage = await activeContext.newPage();
|
|
327
|
+
const start = Date.now();
|
|
328
|
+
await activePage.goto(targetUrl, { waitUntil: 'networkidle', timeout: 120_000 });
|
|
329
|
+
if (macro?.script) {
|
|
330
|
+
await activePage.addScriptTag({ content: macro.script }).catch(() => { });
|
|
331
|
+
}
|
|
332
|
+
await activePage.waitForTimeout(entry.interactionWaitMs ?? 800);
|
|
333
|
+
await runDefaultInteractions(activePage);
|
|
334
|
+
const elapsed = Date.now() - start;
|
|
335
|
+
const remaining = durationSeconds * 1000 - elapsed;
|
|
336
|
+
if (remaining > 0) {
|
|
337
|
+
await activePage.waitForTimeout(remaining);
|
|
338
|
+
}
|
|
339
|
+
const video = await activePage.video();
|
|
340
|
+
await activePage.close();
|
|
341
|
+
const rawPath = video ? await video.path() : null;
|
|
342
|
+
if (!rawPath) {
|
|
343
|
+
throw new Error('Playwright finished without producing a video artifact');
|
|
344
|
+
}
|
|
345
|
+
return { rawVideoPath: rawPath, url: targetUrl };
|
|
346
|
+
}
|
|
347
|
+
finally {
|
|
348
|
+
if (context) {
|
|
349
|
+
await context.close().catch(() => { });
|
|
350
|
+
}
|
|
351
|
+
await browser.close();
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
function resolveMotionTarget(entry, repoRoot) {
|
|
355
|
+
const referenceCandidate = entry.referencePath ?? entry.snapshotHtmlPath ?? null;
|
|
356
|
+
if (referenceCandidate) {
|
|
357
|
+
const absolute = isAbsolute(referenceCandidate) ? referenceCandidate : join(repoRoot, referenceCandidate);
|
|
358
|
+
return pathToFileURL(absolute).toString();
|
|
359
|
+
}
|
|
360
|
+
return entry.referenceUrl ?? entry.url;
|
|
361
|
+
}
|
|
362
|
+
async function loadMotionMacro(entry, repoRoot) {
|
|
363
|
+
if (!entry.interactionScriptPath) {
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
try {
|
|
367
|
+
const absolute = isAbsolute(entry.interactionScriptPath)
|
|
368
|
+
? entry.interactionScriptPath
|
|
369
|
+
: join(repoRoot, entry.interactionScriptPath);
|
|
370
|
+
const script = await readFile(absolute, 'utf8');
|
|
371
|
+
const context = {
|
|
372
|
+
slug: entry.slug,
|
|
373
|
+
url: entry.url,
|
|
374
|
+
waitMs: entry.interactionWaitMs ?? null,
|
|
375
|
+
runtimeCanvasColors: entry.runtimeCanvasColors ?? [],
|
|
376
|
+
resolvedFonts: entry.resolvedFonts ?? []
|
|
377
|
+
};
|
|
378
|
+
const contextScript = `(function(){window.macroContext=Object.assign({},window.macroContext||{},${JSON.stringify(context)});})();`;
|
|
379
|
+
return { script: script.trim(), contextScript };
|
|
380
|
+
}
|
|
381
|
+
catch (error) {
|
|
382
|
+
console.warn(`[design-advanced-assets] Failed to load interaction macro for ${entry.slug}:`, error);
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
async function transcodeMotionOutputs(options) {
|
|
387
|
+
const { slug, rawVideoPath, durationSeconds, tmpRoot, motionEnabled, videoEnabled } = options;
|
|
388
|
+
const outputDir = join(tmpRoot, slug, 'transcodes');
|
|
389
|
+
await mkdir(outputDir, { recursive: true });
|
|
390
|
+
const outputs = {};
|
|
391
|
+
const durationArg = formatDuration(durationSeconds);
|
|
392
|
+
if (motionEnabled) {
|
|
393
|
+
const motionPath = join(outputDir, `${slug}-motion.webm`);
|
|
394
|
+
await execFileAsync('ffmpeg', [
|
|
395
|
+
'-y',
|
|
396
|
+
'-i',
|
|
397
|
+
rawVideoPath,
|
|
398
|
+
'-t',
|
|
399
|
+
durationArg,
|
|
400
|
+
'-c:v',
|
|
401
|
+
'libvpx-vp9',
|
|
402
|
+
'-auto-alt-ref',
|
|
403
|
+
'1',
|
|
404
|
+
'-pix_fmt',
|
|
405
|
+
'yuv420p',
|
|
406
|
+
'-an',
|
|
407
|
+
motionPath
|
|
408
|
+
]);
|
|
409
|
+
outputs.motionPath = motionPath;
|
|
410
|
+
}
|
|
411
|
+
if (videoEnabled) {
|
|
412
|
+
const videoPath = join(outputDir, `${slug}-motion.mp4`);
|
|
413
|
+
await execFileAsync('ffmpeg', [
|
|
414
|
+
'-y',
|
|
415
|
+
'-i',
|
|
416
|
+
rawVideoPath,
|
|
417
|
+
'-t',
|
|
418
|
+
durationArg,
|
|
419
|
+
'-c:v',
|
|
420
|
+
'libx264',
|
|
421
|
+
'-preset',
|
|
422
|
+
'veryfast',
|
|
423
|
+
'-pix_fmt',
|
|
424
|
+
'yuv420p',
|
|
425
|
+
'-movflags',
|
|
426
|
+
'+faststart',
|
|
427
|
+
'-an',
|
|
428
|
+
videoPath
|
|
429
|
+
]);
|
|
430
|
+
outputs.videoPath = videoPath;
|
|
431
|
+
}
|
|
432
|
+
return outputs;
|
|
433
|
+
}
|
|
434
|
+
function computeCaptureDuration(options) {
|
|
435
|
+
const budgets = [];
|
|
436
|
+
if (options.motionEnabled) {
|
|
437
|
+
budgets.push(Math.max(0, options.remainingMotionSeconds));
|
|
438
|
+
}
|
|
439
|
+
if (options.videoEnabled) {
|
|
440
|
+
budgets.push(Math.max(0, options.remainingVideoSeconds));
|
|
441
|
+
}
|
|
442
|
+
const limitingBudget = budgets.length > 0 ? Math.min(...budgets) : DEFAULT_CAPTURE_SECONDS;
|
|
443
|
+
if (limitingBudget <= 0) {
|
|
444
|
+
return 0;
|
|
445
|
+
}
|
|
446
|
+
const candidate = Math.min(DEFAULT_CAPTURE_SECONDS, options.maxDurationSeconds, limitingBudget);
|
|
447
|
+
return candidate;
|
|
448
|
+
}
|
|
449
|
+
function formatDuration(value) {
|
|
450
|
+
return value.toFixed(2);
|
|
451
|
+
}
|
|
452
|
+
async function safeRemove(path) {
|
|
453
|
+
try {
|
|
454
|
+
await rm(path, { force: true });
|
|
455
|
+
}
|
|
456
|
+
catch (error) {
|
|
457
|
+
if (error.code !== 'ENOENT') {
|
|
458
|
+
console.warn('[design-advanced-assets] Failed to cleanup temp file', path, error);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
main().catch((error) => {
|
|
463
|
+
console.error('[design-advanced-assets] failed to process advanced assets');
|
|
464
|
+
console.error(error instanceof Error ? error.stack ?? error.message : error);
|
|
465
|
+
process.exitCode = 1;
|
|
466
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { writeFile, mkdir } from 'node:fs/promises';
|
|
2
|
+
import { join, relative } from 'node:path';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { loadDesignContext } from './context.js';
|
|
5
|
+
import { appendArtifacts, loadDesignRunState, saveDesignRunState, upsertStage } from './state.js';
|
|
6
|
+
import { stageArtifacts } from '../../../orchestrator/src/persistence/ArtifactStager.js';
|
|
7
|
+
async function main() {
|
|
8
|
+
const context = await loadDesignContext();
|
|
9
|
+
const state = await loadDesignRunState(context.statePath);
|
|
10
|
+
const stageId = 'design-componentize';
|
|
11
|
+
const components = buildComponentManifest(context.config.config.metadata.design.captureUrls);
|
|
12
|
+
const tmpRoot = join(tmpdir(), `design-components-${Date.now()}`);
|
|
13
|
+
await mkdir(tmpRoot, { recursive: true });
|
|
14
|
+
const tempFile = join(tmpRoot, 'components.json');
|
|
15
|
+
await writeFile(tempFile, JSON.stringify(components, null, 2), 'utf8');
|
|
16
|
+
const [staged] = await stageArtifacts({
|
|
17
|
+
taskId: context.taskId,
|
|
18
|
+
runId: context.runId,
|
|
19
|
+
artifacts: [
|
|
20
|
+
{
|
|
21
|
+
path: relative(process.cwd(), tempFile),
|
|
22
|
+
description: 'Componentization manifest'
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
options: {
|
|
26
|
+
relativeDir: 'design/components',
|
|
27
|
+
overwrite: true
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
const artifact = {
|
|
31
|
+
stage: 'components',
|
|
32
|
+
status: 'succeeded',
|
|
33
|
+
relative_path: staged.path,
|
|
34
|
+
type: 'storybook-manifest',
|
|
35
|
+
description: 'Componentization summary',
|
|
36
|
+
metadata: {
|
|
37
|
+
stories: components.stories.length
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
appendArtifacts(state, [artifact]);
|
|
41
|
+
upsertStage(state, {
|
|
42
|
+
id: stageId,
|
|
43
|
+
title: 'Componentize design artifacts',
|
|
44
|
+
status: 'succeeded',
|
|
45
|
+
metrics: {
|
|
46
|
+
story_count: components.stories.length
|
|
47
|
+
},
|
|
48
|
+
artifacts: [
|
|
49
|
+
{
|
|
50
|
+
relative_path: staged.path,
|
|
51
|
+
stage: 'components',
|
|
52
|
+
status: 'succeeded',
|
|
53
|
+
description: 'components.json'
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
});
|
|
57
|
+
await saveDesignRunState(context.statePath, state);
|
|
58
|
+
console.log(`[design-componentize] staged components summary: ${staged.path}`);
|
|
59
|
+
}
|
|
60
|
+
function buildComponentManifest(urls) {
|
|
61
|
+
const generatedAt = new Date().toISOString();
|
|
62
|
+
return {
|
|
63
|
+
generatedAt,
|
|
64
|
+
stories: urls.map((url, index) => ({
|
|
65
|
+
id: `story-${index + 1}`,
|
|
66
|
+
source: url
|
|
67
|
+
}))
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
main().catch((error) => {
|
|
71
|
+
console.error('[design-componentize] failed to build components');
|
|
72
|
+
console.error(error instanceof Error ? error.stack ?? error.message : error);
|
|
73
|
+
process.exitCode = 1;
|
|
74
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { mkdir } from 'node:fs/promises';
|
|
3
|
+
import { loadDesignConfig, designPipelineId } from '../../../packages/shared/config/index.js';
|
|
4
|
+
import { sanitizeTaskId } from '../../../orchestrator/src/persistence/sanitizeTaskId.js';
|
|
5
|
+
import { sanitizeRunId } from '../../../orchestrator/src/persistence/sanitizeRunId.js';
|
|
6
|
+
export async function loadDesignContext() {
|
|
7
|
+
const repoRoot = process.env.CODEX_ORCHESTRATOR_REPO_ROOT ?? process.cwd();
|
|
8
|
+
const runsRoot = process.env.CODEX_ORCHESTRATOR_RUNS_DIR ?? join(repoRoot, '.runs');
|
|
9
|
+
const outRoot = process.env.CODEX_ORCHESTRATOR_OUT_DIR ?? join(repoRoot, 'out');
|
|
10
|
+
const taskId = sanitizeTaskId(process.env.CODEX_ORCHESTRATOR_TASK_ID ?? process.env.MCP_RUNNER_TASK_ID ?? 'unknown-task');
|
|
11
|
+
const runId = process.env.CODEX_ORCHESTRATOR_RUN_ID ?? 'run-local';
|
|
12
|
+
const runDir = process.env.CODEX_ORCHESTRATOR_RUN_DIR ?? join(runsRoot, taskId, sanitizeRunId(runId));
|
|
13
|
+
const manifestPath = process.env.CODEX_ORCHESTRATOR_MANIFEST_PATH ?? join(runDir, 'manifest.json');
|
|
14
|
+
const designConfigPath = process.env.DESIGN_CONFIG_PATH ?? join(repoRoot, 'design.config.yaml');
|
|
15
|
+
const config = await loadDesignConfig({ rootDir: repoRoot, filePath: designConfigPath });
|
|
16
|
+
const stateDir = join(runDir, 'design');
|
|
17
|
+
await mkdir(stateDir, { recursive: true });
|
|
18
|
+
const statePath = join(stateDir, 'state.json');
|
|
19
|
+
return {
|
|
20
|
+
taskId,
|
|
21
|
+
runId,
|
|
22
|
+
repoRoot,
|
|
23
|
+
runsRoot,
|
|
24
|
+
outRoot,
|
|
25
|
+
runDir,
|
|
26
|
+
manifestPath,
|
|
27
|
+
statePath,
|
|
28
|
+
designConfigPath,
|
|
29
|
+
config
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function resolveDesignPipelineId(result) {
|
|
33
|
+
return designPipelineId(result);
|
|
34
|
+
}
|