@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,82 @@
1
+ import process from 'node:process';
2
+ import { CommandPlanner } from '../adapters/index.js';
3
+ import { PipelineResolver } from './pipelineResolver.js';
4
+ import { sanitizeTaskId } from '../run/environment.js';
5
+ import { loadTaskMetadata } from '../tasks/taskMetadata.js';
6
+ import { resolvePipeline } from '../pipelines/index.js';
7
+ import { findPipeline } from '../config/userConfig.js';
8
+ import { logger } from '../../logger.js';
9
+ export function overrideTaskEnvironment(baseEnv, taskId) {
10
+ if (!taskId) {
11
+ return { ...baseEnv };
12
+ }
13
+ const sanitized = sanitizeTaskId(taskId);
14
+ return { ...baseEnv, taskId: sanitized };
15
+ }
16
+ export function resolveTargetStageId(explicit, fallback, envTarget = process.env.CODEX_ORCHESTRATOR_TARGET_STAGE) {
17
+ const normalizedExplicit = explicit?.trim();
18
+ if (normalizedExplicit) {
19
+ return normalizedExplicit;
20
+ }
21
+ const normalizedFallback = fallback?.trim();
22
+ if (normalizedFallback) {
23
+ return normalizedFallback;
24
+ }
25
+ if (typeof envTarget === 'string' && envTarget.trim().length > 0) {
26
+ return envTarget.trim();
27
+ }
28
+ return null;
29
+ }
30
+ export async function prepareRun(options) {
31
+ logger.info(`prepareRun start for pipeline ${options.pipelineId ?? options.pipeline?.id ?? '<default>'}`);
32
+ const env = overrideTaskEnvironment(options.baseEnv, options.taskIdOverride);
33
+ const resolver = options.resolver ?? new PipelineResolver();
34
+ logger.info(`prepareRun resolving pipeline ${options.pipelineId ?? '<default>'}`);
35
+ const resolvedPipeline = options.pipeline
36
+ ? {
37
+ pipeline: options.pipeline,
38
+ source: options.pipelineSource ?? null,
39
+ envOverrides: options.envOverrides ?? {}
40
+ }
41
+ : await resolver.resolve(env, { pipelineId: options.pipelineId });
42
+ logger.info(`prepareRun resolved pipeline ${resolvedPipeline.pipeline.id}`);
43
+ const metadata = await loadTaskMetadata(env);
44
+ logger.info(`prepareRun loaded metadata for task ${metadata.id}`);
45
+ const taskContext = createTaskContext(metadata);
46
+ const targetId = resolveTargetStageId(options.targetStageId, options.planTargetFallback ?? null);
47
+ const planner = options.planner ?? new CommandPlanner(resolvedPipeline.pipeline, { targetStageId: targetId });
48
+ logger.info(`prepareRun running planner for pipeline ${resolvedPipeline.pipeline.id}`);
49
+ const planPreview = await planner.plan(taskContext);
50
+ logger.info(`prepareRun planner completed for pipeline ${resolvedPipeline.pipeline.id}`);
51
+ logger.info(`prepareRun complete for pipeline ${resolvedPipeline.pipeline.id}`);
52
+ return {
53
+ env,
54
+ pipeline: resolvedPipeline.pipeline,
55
+ pipelineSource: resolvedPipeline.source ?? null,
56
+ envOverrides: resolvedPipeline.envOverrides ?? {},
57
+ planner,
58
+ plannerTargetId: planPreview?.targetId ?? targetId,
59
+ taskContext,
60
+ metadata,
61
+ resolver,
62
+ planPreview
63
+ };
64
+ }
65
+ export function resolvePipelineForResume(env, manifest, config) {
66
+ const existing = findPipeline(config ?? null, manifest.pipeline_id);
67
+ if (existing) {
68
+ return existing;
69
+ }
70
+ const { pipeline } = resolvePipeline(env, { pipelineId: manifest.pipeline_id, config });
71
+ return pipeline;
72
+ }
73
+ export function createTaskContext(metadata) {
74
+ return {
75
+ id: metadata.id,
76
+ title: metadata.title,
77
+ description: undefined,
78
+ metadata: {
79
+ slug: metadata.slug
80
+ }
81
+ };
82
+ }
@@ -0,0 +1,35 @@
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
+ export function applyHandlesToRunSummary(runSummary, manifest) {
6
+ if (!manifest.handles || manifest.handles.length === 0) {
7
+ return;
8
+ }
9
+ runSummary.handles = manifest.handles.map((handle) => ({
10
+ handleId: handle.handle_id,
11
+ correlationId: handle.correlation_id,
12
+ stageId: handle.stage_id,
13
+ status: handle.status,
14
+ frameCount: handle.frame_count,
15
+ latestSequence: handle.latest_sequence
16
+ }));
17
+ }
18
+ export function applyPrivacyToRunSummary(runSummary, manifest) {
19
+ if (!manifest.privacy) {
20
+ return;
21
+ }
22
+ runSummary.privacy = {
23
+ mode: manifest.privacy.mode,
24
+ totalFrames: manifest.privacy.totals.total_frames,
25
+ redactedFrames: manifest.privacy.totals.redacted_frames,
26
+ blockedFrames: manifest.privacy.totals.blocked_frames,
27
+ allowedFrames: manifest.privacy.totals.allowed_frames
28
+ };
29
+ }
30
+ export async function persistRunSummary(env, paths, manifest, runSummary, persister) {
31
+ const summaryPath = join(paths.runDir, 'run-summary.json');
32
+ await writeJsonAtomic(summaryPath, runSummary);
33
+ manifest.run_summary_path = relativeToRepo(env, summaryPath);
34
+ await persistManifest(paths, manifest, persister, { force: true });
35
+ }
@@ -0,0 +1,42 @@
1
+ import { sanitizeTaskId } from '../run/environment.js';
2
+ import { persistManifest } from '../run/manifestPersister.js';
3
+ import { buildSchedulerRunSummary, createSchedulerPlan, finalizeSchedulerPlan, serializeSchedulerPlan } from '../../scheduler/index.js';
4
+ import { isoTimestamp } from '../utils/time.js';
5
+ export class SchedulerService {
6
+ now;
7
+ constructor(now = () => new Date()) {
8
+ this.now = now;
9
+ }
10
+ async createPlanForRun(options) {
11
+ const plan = createSchedulerPlan(options.controlPlaneResult.request, {
12
+ now: this.now,
13
+ instancePrefix: sanitizeTaskId(options.env.taskId)
14
+ });
15
+ this.attachSchedulerPlanToManifest(options.manifest, plan);
16
+ await persistManifest(options.paths, options.manifest, options.persister, { force: true });
17
+ return plan;
18
+ }
19
+ async finalizePlan(options) {
20
+ const finalStatus = this.resolveSchedulerFinalStatus(options.manifest.status);
21
+ const finalizedAt = options.manifest.completed_at ?? isoTimestamp();
22
+ finalizeSchedulerPlan(options.plan, finalStatus, finalizedAt);
23
+ this.attachSchedulerPlanToManifest(options.manifest, options.plan);
24
+ await persistManifest(options.paths, options.manifest, options.persister, { force: true });
25
+ }
26
+ applySchedulerToRunSummary(runSummary, plan) {
27
+ runSummary.scheduler = buildSchedulerRunSummary(plan);
28
+ }
29
+ resolveSchedulerFinalStatus(status) {
30
+ switch (status) {
31
+ case 'succeeded':
32
+ return 'succeeded';
33
+ case 'in_progress':
34
+ return 'running';
35
+ default:
36
+ return 'failed';
37
+ }
38
+ }
39
+ attachSchedulerPlanToManifest(manifest, plan) {
40
+ manifest.scheduler = serializeSchedulerPlan(plan);
41
+ }
42
+ }
@@ -0,0 +1,19 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ export async function loadTaskMetadata(env) {
4
+ const tasksPath = join(env.repoRoot, 'tasks', 'index.json');
5
+ try {
6
+ const raw = await readFile(tasksPath, 'utf8');
7
+ const data = JSON.parse(raw);
8
+ const match = data.items.find((item) => item.id === env.taskId);
9
+ if (match) {
10
+ return { id: match.id, slug: match.slug, title: match.title };
11
+ }
12
+ }
13
+ catch (error) {
14
+ if (error.code !== 'ENOENT') {
15
+ throw error;
16
+ }
17
+ }
18
+ return { id: env.taskId, slug: env.taskId, title: `Task ${env.taskId}` };
19
+ }
@@ -0,0 +1,8 @@
1
+ import { getManifestSchema, validateManifest } from '../../../../packages/shared/manifest/validator.js';
2
+ export const CLI_MANIFEST_SCHEMA = getManifestSchema();
3
+ export function getTelemetrySchemas() {
4
+ return { manifest: CLI_MANIFEST_SCHEMA };
5
+ }
6
+ export function validateCliManifest(candidate) {
7
+ return validateManifest(candidate);
8
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,112 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useState } from 'react';
3
+ import { Box, Text } from 'ink';
4
+ import { createInitialHudState } from './store.js';
5
+ export function HudApp({ store, footerNote }) {
6
+ const [state, setState] = useState(store.getState());
7
+ const [tick, setTick] = useState(() => Date.now());
8
+ useEffect(() => {
9
+ const unsubscribe = store.subscribe(setState);
10
+ return () => unsubscribe();
11
+ }, [store]);
12
+ useEffect(() => {
13
+ const timer = setInterval(() => setTick(Date.now()), 1000);
14
+ return () => clearInterval(timer);
15
+ }, []);
16
+ const elapsed = useMemo(() => formatElapsed(state.startedAt, state.completedAt, tick), [state.startedAt, state.completedAt, tick]);
17
+ if (state.status === 'idle' && !state.runId) {
18
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "cyan", children: "codex-orchestrator HUD" }), _jsx(Text, { color: "gray", children: "Awaiting run events..." })] }));
19
+ }
20
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Header, { state: state, elapsed: elapsed }), _jsx(Text, { children: " " }), _jsx(StageList, { stages: state.stages }), _jsx(Text, { children: " " }), _jsx(LogPanel, { logs: state.logs }), _jsx(Text, { children: " " }), _jsx(Footer, { manifestPath: state.manifestPath, metricsPath: state.metricsPath, runSummaryPath: state.runSummaryPath, note: footerNote })] }));
21
+ }
22
+ function Header({ state, elapsed }) {
23
+ const statusColor = colorForStatus(state.status);
24
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: ["HUD | TASK ", state.taskId ?? '-', " | RUN ", state.runId ?? '-', " | STATUS", ' ', _jsxs(Text, { color: statusColor, children: ["[", state.status.toUpperCase(), "]"] }), " | ELAPSED ", elapsed] }), _jsxs(Text, { color: "gray", children: [state.pipelineTitle ?? 'pipeline', " | manifest: ", state.manifestPath ?? 'pending', " | log: ", state.logPath ?? 'pending'] })] }));
25
+ }
26
+ function StageList({ stages }) {
27
+ if (!stages || stages.length === 0) {
28
+ return (_jsx(Box, { children: _jsx(Text, { color: "gray", children: "No stage metadata available yet." }) }));
29
+ }
30
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "cyan", children: "Stages" }), stages.map((stage) => (_jsx(StageRow, { stage: stage }, stage.id)))] }));
31
+ }
32
+ function StageRow({ stage }) {
33
+ const statusColor = colorForStatus(stage.status);
34
+ const label = stage.title || stage.id;
35
+ const summary = stage.summary ?? stage.logPath ?? '';
36
+ return (_jsxs(Text, { children: ["[", String(stage.index).padStart(2, '0'), "] ", _jsx(Text, { color: statusColor, children: stage.status.toUpperCase() }), " ", label, summary ? ` - ${summary}` : ''] }));
37
+ }
38
+ function LogPanel({ logs }) {
39
+ const tail = logs.slice(-8);
40
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "cyan", children: ["Log Tail (latest ", tail.length, ")"] }), tail.length === 0 ? (_jsx(Text, { color: "gray", children: "No logs yet." })) : (tail.map((log) => (_jsxs(Text, { color: colorForLevel(log.level), children: [formatClock(log.timestamp), " ", log.stageId ? `[${log.stageId}] ` : '', log.message.trim()] }, log.id))))] }));
41
+ }
42
+ function Footer({ manifestPath, metricsPath, runSummaryPath, note }) {
43
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "gray", children: ["read-only HUD | manifest: ", manifestPath ?? 'pending', " | metrics: ", metricsPath ?? 'pending', " | summary:", ' ', runSummaryPath ?? 'pending'] }), note ? _jsx(Text, { color: "gray", children: note }) : null] }));
44
+ }
45
+ function colorForStatus(status) {
46
+ switch (status) {
47
+ case 'running':
48
+ case 'in_progress':
49
+ return 'cyan';
50
+ case 'succeeded':
51
+ return 'green';
52
+ case 'skipped':
53
+ return 'yellow';
54
+ case 'failed':
55
+ case 'cancelled':
56
+ return 'red';
57
+ case 'pending':
58
+ return 'gray';
59
+ default:
60
+ return undefined;
61
+ }
62
+ }
63
+ function colorForLevel(level) {
64
+ switch (level) {
65
+ case 'warn':
66
+ return 'yellow';
67
+ case 'error':
68
+ return 'red';
69
+ case 'debug':
70
+ return 'gray';
71
+ default:
72
+ return undefined;
73
+ }
74
+ }
75
+ function formatElapsed(start, end, nowMs) {
76
+ if (!start) {
77
+ return '--:--';
78
+ }
79
+ const startMs = Date.parse(start);
80
+ const endMs = end ? Date.parse(end) : nowMs;
81
+ if (Number.isNaN(startMs) || Number.isNaN(endMs)) {
82
+ return '--:--';
83
+ }
84
+ const totalSeconds = Math.max(0, Math.floor((endMs - startMs) / 1000));
85
+ const minutes = Math.floor(totalSeconds / 60);
86
+ const seconds = totalSeconds % 60;
87
+ const hours = Math.floor(minutes / 60);
88
+ const minsRemaining = minutes % 60;
89
+ if (hours > 0) {
90
+ return `${String(hours).padStart(2, '0')}:${String(minsRemaining).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
91
+ }
92
+ return `${String(minsRemaining).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
93
+ }
94
+ function formatClock(timestamp) {
95
+ const parsed = Date.parse(timestamp);
96
+ if (Number.isNaN(parsed)) {
97
+ return '--:--:--';
98
+ }
99
+ const date = new Date(parsed);
100
+ const hours = String(date.getHours()).padStart(2, '0');
101
+ const minutes = String(date.getMinutes()).padStart(2, '0');
102
+ const seconds = String(date.getSeconds()).padStart(2, '0');
103
+ return `${hours}:${minutes}:${seconds}`;
104
+ }
105
+ // Exported for tests
106
+ export const __private = {
107
+ formatElapsed,
108
+ formatClock,
109
+ colorForStatus,
110
+ colorForLevel,
111
+ createInitialHudState
112
+ };
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { render } from 'ink';
3
+ import { HudApp } from './HudApp.js';
4
+ import { HudStore } from './store.js';
5
+ export function startHud(options) {
6
+ const store = new HudStore({
7
+ logLimit: options.logLimit,
8
+ batchIntervalMs: options.batchIntervalMs
9
+ });
10
+ const unsubscribe = options.emitter.on('*', (event) => {
11
+ store.enqueue(event);
12
+ });
13
+ const ink = render(_jsx(HudApp, { store: store, footerNote: options.footerNote }));
14
+ let stopped = false;
15
+ return {
16
+ store,
17
+ stop() {
18
+ if (stopped)
19
+ return;
20
+ stopped = true;
21
+ unsubscribe();
22
+ store.dispose();
23
+ ink.unmount();
24
+ }
25
+ };
26
+ }
@@ -0,0 +1,240 @@
1
+ const DEFAULT_LOG_LIMIT = 200;
2
+ const DEFAULT_BATCH_INTERVAL_MS = 120;
3
+ export function createInitialHudState() {
4
+ return {
5
+ runId: null,
6
+ taskId: null,
7
+ pipelineTitle: null,
8
+ status: 'idle',
9
+ manifestPath: null,
10
+ runSummaryPath: null,
11
+ metricsPath: null,
12
+ logPath: null,
13
+ stages: [],
14
+ logs: [],
15
+ startedAt: null,
16
+ completedAt: null,
17
+ lastUpdated: null
18
+ };
19
+ }
20
+ export function reduceHudState(state, event, logLimit = DEFAULT_LOG_LIMIT) {
21
+ switch (event.type) {
22
+ case 'run:started':
23
+ return applyRunStarted(state, event);
24
+ case 'stage:started':
25
+ return applyStageStarted(state, event);
26
+ case 'stage:completed':
27
+ return applyStageCompleted(state, event);
28
+ case 'run:completed':
29
+ return {
30
+ ...state,
31
+ status: event.status,
32
+ manifestPath: event.manifestPath,
33
+ runSummaryPath: event.runSummaryPath,
34
+ metricsPath: event.metricsPath,
35
+ completedAt: event.timestamp,
36
+ lastUpdated: event.timestamp
37
+ };
38
+ case 'run:error': {
39
+ const status = state.status === 'succeeded' ? state.status : 'failed';
40
+ return appendLogEntry({
41
+ ...state,
42
+ status,
43
+ lastUpdated: event.timestamp
44
+ }, {
45
+ id: buildLogId(state),
46
+ message: event.message,
47
+ level: 'error',
48
+ stageId: event.stageId ?? null,
49
+ timestamp: event.timestamp
50
+ }, logLimit);
51
+ }
52
+ case 'log':
53
+ return appendLogEntry(state, {
54
+ id: buildLogId(state),
55
+ message: event.message,
56
+ level: event.level,
57
+ stageId: event.stageId,
58
+ stageIndex: event.stageIndex ?? null,
59
+ timestamp: event.timestamp
60
+ }, logLimit);
61
+ case 'tool:call':
62
+ return appendLogEntry(state, {
63
+ id: buildLogId(state),
64
+ message: formatToolCall(event),
65
+ level: 'info',
66
+ stageId: event.stageId,
67
+ stageIndex: event.stageIndex ?? null,
68
+ timestamp: event.timestamp
69
+ }, logLimit);
70
+ default:
71
+ return state;
72
+ }
73
+ }
74
+ export class HudStore {
75
+ state = createInitialHudState();
76
+ logLimit;
77
+ batchIntervalMs;
78
+ listeners = new Set();
79
+ pendingEvents = [];
80
+ batchTimer = null;
81
+ constructor(options = {}) {
82
+ this.logLimit = options.logLimit ?? DEFAULT_LOG_LIMIT;
83
+ this.batchIntervalMs = options.batchIntervalMs ?? DEFAULT_BATCH_INTERVAL_MS;
84
+ }
85
+ getState() {
86
+ return this.state;
87
+ }
88
+ enqueue(event) {
89
+ this.pendingEvents.push(event);
90
+ this.scheduleFlush();
91
+ }
92
+ subscribe(listener) {
93
+ this.listeners.add(listener);
94
+ listener(this.state);
95
+ return () => {
96
+ this.listeners.delete(listener);
97
+ };
98
+ }
99
+ dispose() {
100
+ if (this.batchTimer) {
101
+ clearTimeout(this.batchTimer);
102
+ this.batchTimer = null;
103
+ }
104
+ this.listeners.clear();
105
+ this.pendingEvents = [];
106
+ }
107
+ scheduleFlush() {
108
+ if (this.batchTimer) {
109
+ return;
110
+ }
111
+ this.batchTimer = setTimeout(() => {
112
+ this.batchTimer = null;
113
+ this.flush();
114
+ }, this.batchIntervalMs);
115
+ }
116
+ flush() {
117
+ if (this.pendingEvents.length === 0) {
118
+ return;
119
+ }
120
+ const events = this.pendingEvents;
121
+ this.pendingEvents = [];
122
+ let nextState = this.state;
123
+ for (const event of events) {
124
+ nextState = reduceHudState(nextState, event, this.logLimit);
125
+ }
126
+ this.state = nextState;
127
+ for (const listener of this.listeners) {
128
+ listener(this.state);
129
+ }
130
+ }
131
+ }
132
+ function applyRunStarted(state, event) {
133
+ const stages = event.stages
134
+ .slice()
135
+ .sort((a, b) => a.index - b.index)
136
+ .map((stage) => ({
137
+ id: stage.id,
138
+ index: stage.index,
139
+ title: stage.title,
140
+ kind: stage.kind,
141
+ status: stage.status,
142
+ summary: stage.summary,
143
+ exitCode: stage.exitCode,
144
+ logPath: stage.logPath,
145
+ subRunId: stage.subRunId,
146
+ startedAt: stage.status === 'running' ? event.timestamp : null,
147
+ completedAt: ['succeeded', 'failed', 'skipped'].includes(stage.status) ? event.timestamp : null
148
+ }));
149
+ return {
150
+ ...state,
151
+ runId: event.runId,
152
+ taskId: event.taskId,
153
+ pipelineTitle: event.pipelineTitle,
154
+ status: event.status,
155
+ manifestPath: event.manifestPath,
156
+ logPath: event.logPath,
157
+ stages,
158
+ startedAt: event.timestamp,
159
+ completedAt: null,
160
+ lastUpdated: event.timestamp
161
+ };
162
+ }
163
+ function applyStageStarted(state, event) {
164
+ const stages = updateStages(state.stages, {
165
+ id: event.stageId,
166
+ index: event.stageIndex,
167
+ title: event.title,
168
+ kind: event.kind,
169
+ status: 'running',
170
+ summary: null,
171
+ exitCode: null,
172
+ logPath: event.logPath ?? null,
173
+ startedAt: event.timestamp
174
+ });
175
+ return {
176
+ ...state,
177
+ stages,
178
+ status: state.status === 'idle' ? 'in_progress' : state.status,
179
+ lastUpdated: event.timestamp
180
+ };
181
+ }
182
+ function applyStageCompleted(state, event) {
183
+ const stages = updateStages(state.stages, {
184
+ id: event.stageId,
185
+ index: event.stageIndex,
186
+ title: event.title,
187
+ kind: event.kind,
188
+ status: event.status,
189
+ summary: event.summary,
190
+ exitCode: event.exitCode,
191
+ logPath: event.logPath ?? null,
192
+ completedAt: event.timestamp,
193
+ subRunId: event.subRunId
194
+ });
195
+ return {
196
+ ...state,
197
+ stages,
198
+ lastUpdated: event.timestamp
199
+ };
200
+ }
201
+ function updateStages(stages, update) {
202
+ const existingIndex = stages.findIndex((stage) => stage.id === update.id);
203
+ if (existingIndex >= 0) {
204
+ const next = stages.slice();
205
+ next[existingIndex] = {
206
+ ...next[existingIndex],
207
+ ...update
208
+ };
209
+ return next.sort((a, b) => a.index - b.index);
210
+ }
211
+ const summary = 'summary' in update ? update.summary ?? null : null;
212
+ const exitCode = 'exitCode' in update ? update.exitCode ?? null : null;
213
+ const logPath = 'logPath' in update ? update.logPath ?? null : null;
214
+ return [
215
+ ...stages,
216
+ {
217
+ ...update,
218
+ status: update.status ?? 'pending',
219
+ summary,
220
+ exitCode,
221
+ logPath
222
+ }
223
+ ].sort((a, b) => a.index - b.index);
224
+ }
225
+ function appendLogEntry(state, entry, limit) {
226
+ const nextLogs = [...state.logs, entry];
227
+ if (nextLogs.length > limit) {
228
+ nextLogs.splice(0, nextLogs.length - limit);
229
+ }
230
+ return { ...state, logs: nextLogs, lastUpdated: entry.timestamp };
231
+ }
232
+ function buildLogId(state) {
233
+ return `${state.logs.length + 1}`;
234
+ }
235
+ function formatToolCall(event) {
236
+ const stageFragment = event.stageId ? ` [${event.stageId}]` : '';
237
+ const attempt = typeof event.attempt === 'number' ? ` attempt ${event.attempt}` : '';
238
+ const message = event.message ? ` - ${event.message}` : '';
239
+ return `${event.toolName}${stageFragment}: ${event.status}${attempt}${message}`;
240
+ }
@@ -0,0 +1,12 @@
1
+ const TRUTHY_ENFORCE_VALUES = ['1', 'true', 'enforce', 'on', 'yes'];
2
+ export function resolveEnforcementMode(explicit, enforce) {
3
+ const candidate = (explicit ?? null) ?? (enforce ?? null);
4
+ if (!candidate) {
5
+ return 'shadow';
6
+ }
7
+ const normalized = candidate.trim().toLowerCase();
8
+ if (TRUTHY_ENFORCE_VALUES.includes(normalized)) {
9
+ return 'enforce';
10
+ }
11
+ return 'shadow';
12
+ }
@@ -0,0 +1,8 @@
1
+ import { writeAtomicFile as writeAtomicFileInternal } from '../../utils/atomicWrite.js';
2
+ export async function writeJsonAtomic(targetPath, data) {
3
+ const payload = `${JSON.stringify(data, null, 2)}\n`;
4
+ await writeFileAtomic(targetPath, payload);
5
+ }
6
+ export async function writeFileAtomic(targetPath, contents) {
7
+ await writeAtomicFileInternal(targetPath, contents, { ensureDir: true, encoding: 'utf8' });
8
+ }
@@ -0,0 +1,25 @@
1
+ import process from 'node:process';
2
+ import { EnvUtils } from '../../../../packages/shared/config/index.js';
3
+ export function evaluateInteractiveGate(options) {
4
+ const { requested, disabled = false, format, stdoutIsTTY = process.stdout.isTTY === true, stderrIsTTY = process.stderr.isTTY === true, term = process.env.TERM ?? null, env = process.env } = options;
5
+ if (!requested) {
6
+ return { enabled: false, reason: 'not requested' };
7
+ }
8
+ if (disabled) {
9
+ return { enabled: false, reason: 'flagged off' };
10
+ }
11
+ if (format === 'json') {
12
+ return { enabled: false, reason: 'json format requested' };
13
+ }
14
+ if (!stdoutIsTTY || !stderrIsTTY) {
15
+ return { enabled: false, reason: 'non-tty output streams' };
16
+ }
17
+ if ((term ?? '').toLowerCase() === 'dumb') {
18
+ return { enabled: false, reason: 'TERM=dumb' };
19
+ }
20
+ const ci = env.CI ?? '';
21
+ if (ci && EnvUtils.isTrue(ci)) {
22
+ return { enabled: false, reason: 'CI detected' };
23
+ }
24
+ return { enabled: true, reason: null };
25
+ }
@@ -0,0 +1,10 @@
1
+ export class JsonlWriter {
2
+ stream;
3
+ constructor(stream = process.stdout) {
4
+ this.stream = stream;
5
+ }
6
+ write(event) {
7
+ const payload = `${JSON.stringify(event)}\n`;
8
+ this.stream.write(payload);
9
+ }
10
+ }
@@ -0,0 +1,30 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { createRequire } from 'node:module';
3
+ import { join } from 'node:path';
4
+ import { pathToFileURL } from 'node:url';
5
+ export function resolveOptionalDependency(specifier, cwd = process.cwd()) {
6
+ const cwdPackage = join(cwd, 'package.json');
7
+ if (existsSync(cwdPackage)) {
8
+ try {
9
+ const cwdRequire = createRequire(cwdPackage);
10
+ return { path: cwdRequire.resolve(specifier), source: 'cwd' };
11
+ }
12
+ catch {
13
+ // ignore and fall back
14
+ }
15
+ }
16
+ try {
17
+ const selfRequire = createRequire(import.meta.url);
18
+ return { path: selfRequire.resolve(specifier), source: 'package' };
19
+ }
20
+ catch {
21
+ return { path: null, source: null };
22
+ }
23
+ }
24
+ export async function importOptionalDependency(specifier, cwd) {
25
+ const resolved = resolveOptionalDependency(specifier, cwd);
26
+ if (!resolved.path) {
27
+ throw new Error(`Missing optional dependency: ${specifier}`);
28
+ }
29
+ return (await import(pathToFileURL(resolved.path).href));
30
+ }