agentic-orchestrator 0.1.25 → 0.1.26
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/agentic/orchestrator/defaults/policy.defaults.yaml +1 -0
- package/agentic/orchestrator/policy.yaml +1 -0
- package/agentic/orchestrator/schemas/policy.schema.json +4 -0
- package/agentic/orchestrator/schemas/policy.user.schema.json +4 -0
- package/apps/control-plane/src/application/services/activity-monitor-service.ts +42 -0
- package/apps/control-plane/src/application/services/collision-queue-service.ts +48 -0
- package/apps/control-plane/src/application/services/cost-tracking-service.ts +80 -0
- package/apps/control-plane/src/application/services/feature-deletion-service.ts +57 -0
- package/apps/control-plane/src/application/services/feature-lifecycle-service.ts +65 -0
- package/apps/control-plane/src/application/services/feature-state-service.ts +87 -0
- package/apps/control-plane/src/application/services/gate-selection-service.ts +28 -0
- package/apps/control-plane/src/application/services/gate-service.ts +148 -0
- package/apps/control-plane/src/application/services/issue-tracker-service.ts +28 -0
- package/apps/control-plane/src/application/services/lock-service.ts +88 -0
- package/apps/control-plane/src/application/services/merge-service.ts +63 -0
- package/apps/control-plane/src/application/services/notifier-service.ts +70 -0
- package/apps/control-plane/src/application/services/patch-service.ts +95 -0
- package/apps/control-plane/src/application/services/performance-analytics-service.ts +90 -0
- package/apps/control-plane/src/application/services/plan-service.ts +184 -0
- package/apps/control-plane/src/application/services/pr-monitor-service.ts +58 -0
- package/apps/control-plane/src/application/services/qa-index-service.ts +68 -0
- package/apps/control-plane/src/application/services/reactions-service.ts +77 -0
- package/apps/control-plane/src/application/services/reporting-service.ts +79 -0
- package/apps/control-plane/src/application/services/run-lease-service.ts +79 -0
- package/apps/control-plane/src/application/tools/tool-router.ts +76 -0
- package/apps/control-plane/src/mcp/tool-runtime.ts +4 -0
- package/apps/control-plane/src/providers/providers.ts +96 -0
- package/apps/control-plane/src/providers/worker-provider-factory.ts +4 -0
- package/apps/control-plane/src/supervisor/build-wave-executor.ts +4 -0
- package/apps/control-plane/src/supervisor/planning-wave-executor.ts +4 -0
- package/apps/control-plane/src/supervisor/qa-wave-executor.ts +4 -0
- package/apps/control-plane/src/supervisor/run-coordinator.ts +4 -0
- package/apps/control-plane/src/supervisor/runtime.ts +4 -0
- package/apps/control-plane/src/supervisor/session-orchestrator.ts +4 -0
- package/apps/control-plane/test/dashboard-api.integration.spec.ts +310 -1
- package/apps/control-plane/test/dashboard-client.spec.ts +136 -1
- package/apps/control-plane/test/dashboard-live-feed.spec.ts +153 -0
- package/apps/control-plane/test/dashboard-ui-utils.spec.ts +132 -0
- package/apps/control-plane/test/mcp.spec.ts +96 -0
- package/apps/control-plane/test/tool-runtime.spec.ts +68 -0
- package/config/agentic/orchestrator/policy.yaml +1 -0
- package/dist/apps/control-plane/application/services/activity-monitor-service.d.ts +42 -0
- package/dist/apps/control-plane/application/services/activity-monitor-service.js +42 -0
- package/dist/apps/control-plane/application/services/activity-monitor-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/collision-queue-service.d.ts +48 -0
- package/dist/apps/control-plane/application/services/collision-queue-service.js +48 -0
- package/dist/apps/control-plane/application/services/collision-queue-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/cost-tracking-service.d.ts +79 -0
- package/dist/apps/control-plane/application/services/cost-tracking-service.js +76 -0
- package/dist/apps/control-plane/application/services/cost-tracking-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/feature-deletion-service.d.ts +57 -0
- package/dist/apps/control-plane/application/services/feature-deletion-service.js +57 -0
- package/dist/apps/control-plane/application/services/feature-deletion-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/feature-lifecycle-service.d.ts +64 -0
- package/dist/apps/control-plane/application/services/feature-lifecycle-service.js +61 -0
- package/dist/apps/control-plane/application/services/feature-lifecycle-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/feature-state-service.d.ts +86 -0
- package/dist/apps/control-plane/application/services/feature-state-service.js +83 -0
- package/dist/apps/control-plane/application/services/feature-state-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/gate-selection-service.d.ts +28 -0
- package/dist/apps/control-plane/application/services/gate-selection-service.js +28 -0
- package/dist/apps/control-plane/application/services/gate-selection-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/gate-service.d.ts +148 -0
- package/dist/apps/control-plane/application/services/gate-service.js +120 -0
- package/dist/apps/control-plane/application/services/gate-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/issue-tracker-service.d.ts +28 -0
- package/dist/apps/control-plane/application/services/issue-tracker-service.js +28 -0
- package/dist/apps/control-plane/application/services/issue-tracker-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/lock-service.d.ts +88 -0
- package/dist/apps/control-plane/application/services/lock-service.js +64 -0
- package/dist/apps/control-plane/application/services/lock-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/merge-service.d.ts +62 -0
- package/dist/apps/control-plane/application/services/merge-service.js +59 -0
- package/dist/apps/control-plane/application/services/merge-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/notifier-service.d.ts +70 -0
- package/dist/apps/control-plane/application/services/notifier-service.js +70 -0
- package/dist/apps/control-plane/application/services/notifier-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/patch-service.d.ts +94 -0
- package/dist/apps/control-plane/application/services/patch-service.js +91 -0
- package/dist/apps/control-plane/application/services/patch-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/performance-analytics-service.d.ts +89 -0
- package/dist/apps/control-plane/application/services/performance-analytics-service.js +86 -0
- package/dist/apps/control-plane/application/services/performance-analytics-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/plan-service.d.ts +287 -0
- package/dist/apps/control-plane/application/services/plan-service.js +207 -0
- package/dist/apps/control-plane/application/services/plan-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/pr-monitor-service.d.ts +58 -0
- package/dist/apps/control-plane/application/services/pr-monitor-service.js +58 -0
- package/dist/apps/control-plane/application/services/pr-monitor-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/qa-index-service.d.ts +67 -0
- package/dist/apps/control-plane/application/services/qa-index-service.js +64 -0
- package/dist/apps/control-plane/application/services/qa-index-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/reactions-service.d.ts +76 -0
- package/dist/apps/control-plane/application/services/reactions-service.js +73 -0
- package/dist/apps/control-plane/application/services/reactions-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/reporting-service.d.ts +78 -0
- package/dist/apps/control-plane/application/services/reporting-service.js +75 -0
- package/dist/apps/control-plane/application/services/reporting-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/run-lease-service.d.ts +79 -0
- package/dist/apps/control-plane/application/services/run-lease-service.js +79 -0
- package/dist/apps/control-plane/application/services/run-lease-service.js.map +1 -1
- package/dist/apps/control-plane/application/tools/tool-router.d.ts +76 -0
- package/dist/apps/control-plane/application/tools/tool-router.js +62 -0
- package/dist/apps/control-plane/application/tools/tool-router.js.map +1 -1
- package/dist/apps/control-plane/mcp/tool-runtime.d.ts +3 -0
- package/dist/apps/control-plane/mcp/tool-runtime.js +3 -0
- package/dist/apps/control-plane/mcp/tool-runtime.js.map +1 -1
- package/dist/apps/control-plane/providers/providers.d.ts +88 -0
- package/dist/apps/control-plane/providers/providers.js.map +1 -1
- package/dist/apps/control-plane/providers/worker-provider-factory.d.ts +3 -0
- package/dist/apps/control-plane/providers/worker-provider-factory.js.map +1 -1
- package/dist/apps/control-plane/supervisor/build-wave-executor.d.ts +3 -0
- package/dist/apps/control-plane/supervisor/build-wave-executor.js +3 -0
- package/dist/apps/control-plane/supervisor/build-wave-executor.js.map +1 -1
- package/dist/apps/control-plane/supervisor/planning-wave-executor.d.ts +3 -0
- package/dist/apps/control-plane/supervisor/planning-wave-executor.js +3 -0
- package/dist/apps/control-plane/supervisor/planning-wave-executor.js.map +1 -1
- package/dist/apps/control-plane/supervisor/qa-wave-executor.d.ts +3 -0
- package/dist/apps/control-plane/supervisor/qa-wave-executor.js +3 -0
- package/dist/apps/control-plane/supervisor/qa-wave-executor.js.map +1 -1
- package/dist/apps/control-plane/supervisor/run-coordinator.d.ts +3 -0
- package/dist/apps/control-plane/supervisor/run-coordinator.js +3 -0
- package/dist/apps/control-plane/supervisor/run-coordinator.js.map +1 -1
- package/dist/apps/control-plane/supervisor/runtime.d.ts +3 -0
- package/dist/apps/control-plane/supervisor/runtime.js +3 -0
- package/dist/apps/control-plane/supervisor/runtime.js.map +1 -1
- package/dist/apps/control-plane/supervisor/session-orchestrator.d.ts +3 -0
- package/dist/apps/control-plane/supervisor/session-orchestrator.js +3 -0
- package/dist/apps/control-plane/supervisor/session-orchestrator.js.map +1 -1
- package/package.json +1 -1
- package/packages/web-dashboard/src/app/analytics/page.tsx +451 -0
- package/packages/web-dashboard/src/app/api/analytics/route.ts +62 -0
- package/packages/web-dashboard/src/app/api/collisions/route.ts +63 -0
- package/packages/web-dashboard/src/app/api/features/[id]/cost/route.ts +29 -0
- package/packages/web-dashboard/src/app/api/features/[id]/review-brief/route.ts +31 -0
- package/packages/web-dashboard/src/app/api/features/[id]/route.ts +5 -8
- package/packages/web-dashboard/src/app/api/features/[id]/test-index/route.ts +31 -0
- package/packages/web-dashboard/src/app/api/flaky/route.ts +20 -0
- package/packages/web-dashboard/src/app/api/policy/budget/route.ts +31 -0
- package/packages/web-dashboard/src/app/api/run/route.ts +162 -0
- package/packages/web-dashboard/src/app/api/status/route.ts +2 -5
- package/packages/web-dashboard/src/app/feature/[id]/page.tsx +112 -0
- package/packages/web-dashboard/src/app/page.tsx +379 -77
- package/packages/web-dashboard/src/components/agent-pipeline-stepper.tsx +100 -0
- package/packages/web-dashboard/src/components/dependency-chains.tsx +58 -0
- package/packages/web-dashboard/src/components/detail-panel.tsx +215 -5
- package/packages/web-dashboard/src/components/feature-card.tsx +5 -0
- package/packages/web-dashboard/src/components/feature-cost-panel.tsx +66 -0
- package/packages/web-dashboard/src/components/feature-list-view.tsx +336 -0
- package/packages/web-dashboard/src/components/gate-step-drilldown.tsx +92 -0
- package/packages/web-dashboard/src/components/kanban-board.tsx +3 -0
- package/packages/web-dashboard/src/components/live-event-feed.tsx +60 -0
- package/packages/web-dashboard/src/components/lock-resource-map.tsx +73 -0
- package/packages/web-dashboard/src/components/plan-revision-timeline.tsx +55 -0
- package/packages/web-dashboard/src/components/plan-risk-annotations.tsx +63 -0
- package/packages/web-dashboard/src/components/plan-scope-tree.tsx +285 -0
- package/packages/web-dashboard/src/components/qa-coverage-map.tsx +174 -0
- package/packages/web-dashboard/src/components/quick-launch-panel.tsx +70 -0
- package/packages/web-dashboard/src/components/review-brief-panel.tsx +75 -0
- package/packages/web-dashboard/src/components/run-health-panel.tsx +85 -0
- package/packages/web-dashboard/src/components/summary-bar.tsx +59 -2
- package/packages/web-dashboard/src/components/verification-signals.tsx +167 -0
- package/packages/web-dashboard/src/lib/analytics-utils.ts +3 -0
- package/packages/web-dashboard/src/lib/aop-client.ts +510 -11
- package/packages/web-dashboard/src/lib/api-envelope.ts +48 -0
- package/packages/web-dashboard/src/lib/authz-adapter.ts +33 -0
- package/packages/web-dashboard/src/lib/dashboard-utils.ts +304 -0
- package/packages/web-dashboard/src/lib/live-feed.ts +218 -0
- package/packages/web-dashboard/src/lib/policy-reader.ts +22 -0
- package/packages/web-dashboard/src/lib/types.ts +114 -0
- package/packages/web-dashboard/src/styles/dashboard.module.css +341 -1
- package/spec-files/{outstanding → completed}/agentic_orchestrator_dashboard_advanced_ux_spec.md +1 -1
- package/spec-files/outstanding/agentic_orchestrator_evidence_integrity_doctor_spec.md +561 -0
- package/spec-files/outstanding/agentic_orchestrator_kernel_simplification_spec.md +834 -0
- package/spec-files/outstanding/agentic_orchestrator_observability_integrity_diagnostics_spec.md +12 -5
- package/spec-files/outstanding/agentic_orchestrator_observability_replay_spec.md +442 -0
- package/spec-files/progress.md +174 -1
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { AgentPipelineStatus, RoleStatus } from '@/lib/types.js';
|
|
2
|
+
import styles from '@/styles/dashboard.module.css';
|
|
3
|
+
|
|
4
|
+
interface AgentPipelineStepperProps {
|
|
5
|
+
status?: AgentPipelineStatus;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function normalizeRoleStatus(value: RoleStatus | undefined): RoleStatus {
|
|
9
|
+
return value ?? 'unknown';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function roleLabel(role: 'planner' | 'builder' | 'qa'): string {
|
|
13
|
+
if (role === 'planner') {
|
|
14
|
+
return 'Planner';
|
|
15
|
+
}
|
|
16
|
+
if (role === 'builder') {
|
|
17
|
+
return 'Builder';
|
|
18
|
+
}
|
|
19
|
+
return 'QA';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function statusLabel(status: RoleStatus): string {
|
|
23
|
+
if (status === 'ready') {
|
|
24
|
+
return 'Ready';
|
|
25
|
+
}
|
|
26
|
+
if (status === 'running') {
|
|
27
|
+
return 'Running';
|
|
28
|
+
}
|
|
29
|
+
if (status === 'blocked') {
|
|
30
|
+
return 'Blocked';
|
|
31
|
+
}
|
|
32
|
+
if (status === 'done') {
|
|
33
|
+
return 'Done';
|
|
34
|
+
}
|
|
35
|
+
return 'Unknown';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function statusClass(status: RoleStatus): string {
|
|
39
|
+
if (status === 'done') {
|
|
40
|
+
return styles.stepDone;
|
|
41
|
+
}
|
|
42
|
+
if (status === 'running') {
|
|
43
|
+
return styles.stepRunning;
|
|
44
|
+
}
|
|
45
|
+
if (status === 'blocked') {
|
|
46
|
+
return styles.stepBlocked;
|
|
47
|
+
}
|
|
48
|
+
if (status === 'ready') {
|
|
49
|
+
return styles.stepReady;
|
|
50
|
+
}
|
|
51
|
+
return styles.stepUnknown;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function statusIcon(status: RoleStatus): string {
|
|
55
|
+
if (status === 'done') {
|
|
56
|
+
return '✓';
|
|
57
|
+
}
|
|
58
|
+
if (status === 'running') {
|
|
59
|
+
return '…';
|
|
60
|
+
}
|
|
61
|
+
if (status === 'blocked') {
|
|
62
|
+
return '!';
|
|
63
|
+
}
|
|
64
|
+
if (status === 'ready') {
|
|
65
|
+
return '○';
|
|
66
|
+
}
|
|
67
|
+
return '?';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function AgentPipelineStepper({ status }: AgentPipelineStepperProps) {
|
|
71
|
+
const normalized: AgentPipelineStatus = {
|
|
72
|
+
planner: normalizeRoleStatus(status?.planner),
|
|
73
|
+
builder: normalizeRoleStatus(status?.builder),
|
|
74
|
+
qa: normalizeRoleStatus(status?.qa),
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<section className={styles.section}>
|
|
79
|
+
<h3 className={styles.sectionTitle}>Agent Pipeline</h3>
|
|
80
|
+
<ol className={styles.pipelineStepper} aria-label="Agent pipeline stepper">
|
|
81
|
+
{(['planner', 'builder', 'qa'] as const).map((role) => {
|
|
82
|
+
const current = normalized[role];
|
|
83
|
+
return (
|
|
84
|
+
<li key={role} className={`${styles.pipelineStep} ${statusClass(current)}`}>
|
|
85
|
+
<span
|
|
86
|
+
className={`${styles.pipelineIcon} ${current === 'running' ? styles.pipelineIconRunning : ''}`}
|
|
87
|
+
aria-hidden="true"
|
|
88
|
+
>
|
|
89
|
+
{statusIcon(current)}
|
|
90
|
+
</span>
|
|
91
|
+
<span>
|
|
92
|
+
<strong>{roleLabel(role)}</strong> <span>{statusLabel(current)}</span>
|
|
93
|
+
</span>
|
|
94
|
+
</li>
|
|
95
|
+
);
|
|
96
|
+
})}
|
|
97
|
+
</ol>
|
|
98
|
+
</section>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Fragment } from 'react';
|
|
2
|
+
import styles from '@/styles/dashboard.module.css';
|
|
3
|
+
|
|
4
|
+
interface DependencyEntry {
|
|
5
|
+
feature_id: string;
|
|
6
|
+
depends_on_unresolved: string[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface DependencyChainsProps {
|
|
10
|
+
chains: DependencyEntry[];
|
|
11
|
+
onSelectFeature: (featureId: string) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function DependencyChains({ chains, onSelectFeature }: DependencyChainsProps) {
|
|
15
|
+
return (
|
|
16
|
+
<section className={styles.section}>
|
|
17
|
+
<h2 className={styles.sectionTitle}>Dependency Chains</h2>
|
|
18
|
+
{chains.length === 0 ? (
|
|
19
|
+
<p className={styles.metaText}>No unresolved dependency chains.</p>
|
|
20
|
+
) : (
|
|
21
|
+
<ul className={styles.planList}>
|
|
22
|
+
{chains.map((entry) => (
|
|
23
|
+
<li key={entry.feature_id} className={styles.planListItem}>
|
|
24
|
+
<button
|
|
25
|
+
type="button"
|
|
26
|
+
className={styles.listHeaderButton}
|
|
27
|
+
onClick={() => onSelectFeature(entry.feature_id)}
|
|
28
|
+
>
|
|
29
|
+
{entry.feature_id}
|
|
30
|
+
</button>
|
|
31
|
+
<span className={styles.metaText}> {' -> depends on -> '} </span>
|
|
32
|
+
{entry.depends_on_unresolved.length === 0 ? (
|
|
33
|
+
<span className={styles.metaText}>unknown</span>
|
|
34
|
+
) : (
|
|
35
|
+
<span className={styles.inlineActions}>
|
|
36
|
+
{entry.depends_on_unresolved.map((dependency, index) => (
|
|
37
|
+
<Fragment key={dependency}>
|
|
38
|
+
<button
|
|
39
|
+
type="button"
|
|
40
|
+
className={styles.listHeaderButton}
|
|
41
|
+
onClick={() => onSelectFeature(dependency)}
|
|
42
|
+
>
|
|
43
|
+
{dependency}
|
|
44
|
+
</button>
|
|
45
|
+
{index < entry.depends_on_unresolved.length - 1 ? (
|
|
46
|
+
<span className={styles.metaText}>, </span>
|
|
47
|
+
) : null}
|
|
48
|
+
</Fragment>
|
|
49
|
+
))}
|
|
50
|
+
</span>
|
|
51
|
+
)}
|
|
52
|
+
</li>
|
|
53
|
+
))}
|
|
54
|
+
</ul>
|
|
55
|
+
)}
|
|
56
|
+
</section>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
@@ -1,14 +1,34 @@
|
|
|
1
|
-
import { useEffect, useMemo, useState } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
buildDashboardFeatureHref,
|
|
4
|
+
formatElapsedTime,
|
|
5
|
+
projectQuery,
|
|
6
|
+
} from '@/lib/dashboard-utils.js';
|
|
7
|
+
import type {
|
|
8
|
+
CostSummary,
|
|
9
|
+
FeatureDetail,
|
|
10
|
+
FeatureLog,
|
|
11
|
+
FeatureLogEntry,
|
|
12
|
+
QaTestIndex,
|
|
13
|
+
ReviewBrief,
|
|
14
|
+
} from '@/lib/types.js';
|
|
4
15
|
import styles from '@/styles/dashboard.module.css';
|
|
16
|
+
import { AgentPipelineStepper } from '@/components/agent-pipeline-stepper';
|
|
5
17
|
import { ConfirmationModal } from '@/components/confirmation-modal';
|
|
6
18
|
import { DiffViewer } from '@/components/diff-viewer';
|
|
7
19
|
import { EvidenceViewer } from '@/components/evidence-viewer';
|
|
20
|
+
import { FeatureCostPanel } from '@/components/feature-cost-panel';
|
|
8
21
|
import { GateResults } from '@/components/gate-results';
|
|
22
|
+
import { GateStepDrilldown } from '@/components/gate-step-drilldown';
|
|
9
23
|
import { HumanInputPanel } from '@/components/human-input-panel';
|
|
10
24
|
import { PlanViewer } from '@/components/plan-viewer';
|
|
25
|
+
import { PlanRevisionTimeline } from '@/components/plan-revision-timeline';
|
|
26
|
+
import { PlanRiskAnnotations } from '@/components/plan-risk-annotations';
|
|
27
|
+
import { PlanScopeTree } from '@/components/plan-scope-tree';
|
|
11
28
|
import { PrStatusCard } from '@/components/pr-status-card';
|
|
29
|
+
import { QaCoverageMap } from '@/components/qa-coverage-map';
|
|
30
|
+
import { ReviewBriefPanel } from '@/components/review-brief-panel';
|
|
31
|
+
import { VerificationSignals } from '@/components/verification-signals';
|
|
12
32
|
|
|
13
33
|
type ReviewAction =
|
|
14
34
|
| 'review.approve'
|
|
@@ -25,6 +45,9 @@ interface ActionResponse {
|
|
|
25
45
|
interface DetailPanelProps {
|
|
26
46
|
detail: FeatureDetail | null;
|
|
27
47
|
project: string;
|
|
48
|
+
unresolvedDependencies?: string[];
|
|
49
|
+
flakySuspects?: string[];
|
|
50
|
+
isFocusView?: boolean;
|
|
28
51
|
onClose: () => void;
|
|
29
52
|
refreshDetail: (featureId: string) => Promise<void>;
|
|
30
53
|
refreshStatus: () => Promise<void>;
|
|
@@ -51,6 +74,12 @@ const DEFAULT_HISTORY: HistoryState = {
|
|
|
51
74
|
total: 0,
|
|
52
75
|
};
|
|
53
76
|
|
|
77
|
+
interface ApiEnvelope<TData> {
|
|
78
|
+
ok: boolean;
|
|
79
|
+
data?: TData;
|
|
80
|
+
error?: { code?: string; message?: string; retryable?: boolean };
|
|
81
|
+
}
|
|
82
|
+
|
|
54
83
|
async function callAction(
|
|
55
84
|
action: ReviewAction,
|
|
56
85
|
featureId: string,
|
|
@@ -68,6 +97,9 @@ async function callAction(
|
|
|
68
97
|
export function DetailPanel({
|
|
69
98
|
detail,
|
|
70
99
|
project,
|
|
100
|
+
unresolvedDependencies = [],
|
|
101
|
+
flakySuspects = [],
|
|
102
|
+
isFocusView = false,
|
|
71
103
|
onClose,
|
|
72
104
|
refreshDetail,
|
|
73
105
|
refreshStatus,
|
|
@@ -82,11 +114,18 @@ export function DetailPanel({
|
|
|
82
114
|
const [history, setHistory] = useState<HistoryState>(DEFAULT_HISTORY);
|
|
83
115
|
const [nowMs, setNowMs] = useState(Date.now());
|
|
84
116
|
const [submitting, setSubmitting] = useState(false);
|
|
117
|
+
const [cost, setCost] = useState<CostSummary | null>(detail?.cost ?? null);
|
|
118
|
+
const [qaIndex, setQaIndex] = useState<QaTestIndex | null>(detail?.qa_test_index ?? null);
|
|
119
|
+
const [reviewBrief, setReviewBrief] = useState<ReviewBrief | null>(detail?.review_brief ?? null);
|
|
120
|
+
const [budgetLimitUsd, setBudgetLimitUsd] = useState<number | null>(null);
|
|
121
|
+
const costCacheRef = useRef<Map<string, { expiresAt: number; value: CostSummary | null }>>(
|
|
122
|
+
new Map(),
|
|
123
|
+
);
|
|
85
124
|
|
|
86
125
|
useEffect(() => {
|
|
87
126
|
const interval = setInterval(() => {
|
|
88
127
|
setNowMs(Date.now());
|
|
89
|
-
},
|
|
128
|
+
}, 5000);
|
|
90
129
|
return () => clearInterval(interval);
|
|
91
130
|
}, []);
|
|
92
131
|
|
|
@@ -142,6 +181,126 @@ export function DetailPanel({
|
|
|
142
181
|
};
|
|
143
182
|
}, [detail, project]);
|
|
144
183
|
|
|
184
|
+
useEffect(() => {
|
|
185
|
+
let cancelled = false;
|
|
186
|
+
void (async () => {
|
|
187
|
+
try {
|
|
188
|
+
const response = await fetch(`/api/policy/budget${projectQuery(project)}`);
|
|
189
|
+
const body = (await response.json()) as ApiEnvelope<{
|
|
190
|
+
per_feature_limit_usd?: number | null;
|
|
191
|
+
}>;
|
|
192
|
+
if (cancelled) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
setBudgetLimitUsd(
|
|
196
|
+
body.ok && typeof body.data?.per_feature_limit_usd === 'number'
|
|
197
|
+
? body.data.per_feature_limit_usd
|
|
198
|
+
: null,
|
|
199
|
+
);
|
|
200
|
+
} catch {
|
|
201
|
+
if (!cancelled) {
|
|
202
|
+
setBudgetLimitUsd(null);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
})();
|
|
206
|
+
|
|
207
|
+
return () => {
|
|
208
|
+
cancelled = true;
|
|
209
|
+
};
|
|
210
|
+
}, [project]);
|
|
211
|
+
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
if (!detail) {
|
|
214
|
+
setCost(null);
|
|
215
|
+
setQaIndex(null);
|
|
216
|
+
setReviewBrief(null);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const featureId = detail.feature.feature_id;
|
|
221
|
+
setCost(detail.cost ?? null);
|
|
222
|
+
setQaIndex(detail.qa_test_index ?? null);
|
|
223
|
+
setReviewBrief(detail.review_brief ?? null);
|
|
224
|
+
|
|
225
|
+
const controller = new AbortController();
|
|
226
|
+
let cancelled = false;
|
|
227
|
+
|
|
228
|
+
const fetchCost = async () => {
|
|
229
|
+
const cacheKey = `${project}:${featureId}`;
|
|
230
|
+
const cached = costCacheRef.current.get(cacheKey);
|
|
231
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
232
|
+
setCost(cached.value);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
const response = await fetch(`/api/features/${featureId}/cost${projectQuery(project)}`, {
|
|
238
|
+
signal: controller.signal,
|
|
239
|
+
});
|
|
240
|
+
const body = (await response.json()) as ApiEnvelope<CostSummary>;
|
|
241
|
+
if (cancelled) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (body.ok && body.data) {
|
|
245
|
+
setCost(body.data);
|
|
246
|
+
costCacheRef.current.set(cacheKey, {
|
|
247
|
+
expiresAt: Date.now() + 60_000,
|
|
248
|
+
value: body.data,
|
|
249
|
+
});
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
setCost(null);
|
|
253
|
+
} catch {
|
|
254
|
+
if (!cancelled) {
|
|
255
|
+
setCost(null);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const fetchQaIndex = async () => {
|
|
261
|
+
try {
|
|
262
|
+
const response = await fetch(
|
|
263
|
+
`/api/features/${featureId}/test-index${projectQuery(project)}`,
|
|
264
|
+
{ signal: controller.signal },
|
|
265
|
+
);
|
|
266
|
+
const body = (await response.json()) as ApiEnvelope<QaTestIndex>;
|
|
267
|
+
if (!cancelled) {
|
|
268
|
+
setQaIndex(body.ok && body.data ? body.data : null);
|
|
269
|
+
}
|
|
270
|
+
} catch {
|
|
271
|
+
if (!cancelled) {
|
|
272
|
+
setQaIndex(null);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const fetchReviewBrief = async () => {
|
|
278
|
+
try {
|
|
279
|
+
const response = await fetch(
|
|
280
|
+
`/api/features/${featureId}/review-brief${projectQuery(project)}`,
|
|
281
|
+
{ signal: controller.signal },
|
|
282
|
+
);
|
|
283
|
+
const body = (await response.json()) as ApiEnvelope<ReviewBrief>;
|
|
284
|
+
if (!cancelled) {
|
|
285
|
+
setReviewBrief(body.ok && body.data ? body.data : null);
|
|
286
|
+
}
|
|
287
|
+
} catch {
|
|
288
|
+
if (!cancelled) {
|
|
289
|
+
setReviewBrief(null);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
void fetchCost();
|
|
295
|
+
void fetchQaIndex();
|
|
296
|
+
void fetchReviewBrief();
|
|
297
|
+
|
|
298
|
+
return () => {
|
|
299
|
+
cancelled = true;
|
|
300
|
+
controller.abort();
|
|
301
|
+
};
|
|
302
|
+
}, [detail, project]);
|
|
303
|
+
|
|
145
304
|
const phase = detail?.feature.phase ?? 'unknown';
|
|
146
305
|
|
|
147
306
|
const shouldShowPlan = phase === 'planning' || phase === 'building';
|
|
@@ -235,6 +394,9 @@ export function DetailPanel({
|
|
|
235
394
|
<h2 className={styles.panelTitle}>{detail.feature.feature_id}</h2>
|
|
236
395
|
<p className={styles.metaText}>Phase: {phase}</p>
|
|
237
396
|
<p className={styles.metaText}>Status: {detail.feature.status}</p>
|
|
397
|
+
{unresolvedDependencies.length > 0 ? (
|
|
398
|
+
<p className={styles.metaText}>Substate: Awaiting Dependencies</p>
|
|
399
|
+
) : null}
|
|
238
400
|
<p className={styles.metaText}>Branch: {detail.feature.branch ?? '-'}</p>
|
|
239
401
|
<p className={styles.metaText}>Time in phase: {timeInPhase}</p>
|
|
240
402
|
{detail.feature.activity_state === 'active' ? (
|
|
@@ -246,6 +408,50 @@ export function DetailPanel({
|
|
|
246
408
|
</button>
|
|
247
409
|
</header>
|
|
248
410
|
|
|
411
|
+
<div className={styles.inlineActions}>
|
|
412
|
+
<a
|
|
413
|
+
href={`/feature/${detail.feature.feature_id}${projectQuery(project)}`}
|
|
414
|
+
className={styles.linkButton}
|
|
415
|
+
>
|
|
416
|
+
Open Focus View
|
|
417
|
+
</a>
|
|
418
|
+
</div>
|
|
419
|
+
|
|
420
|
+
<AgentPipelineStepper status={detail.feature.role_status} />
|
|
421
|
+
<FeatureCostPanel cost={cost} budgetLimitUsd={budgetLimitUsd} />
|
|
422
|
+
{detail.feature.status === 'paused_budget' ? (
|
|
423
|
+
<div className={styles.banner}>Budget exhausted</div>
|
|
424
|
+
) : null}
|
|
425
|
+
<VerificationSignals feature={detail.feature} plan={detail.plan} />
|
|
426
|
+
<PlanRevisionTimeline
|
|
427
|
+
plan={detail.plan}
|
|
428
|
+
featurePlanVersion={detail.feature.plan_version}
|
|
429
|
+
featureRevisionOf={detail.feature.revision_of}
|
|
430
|
+
featureRevisionReason={detail.feature.revision_reason}
|
|
431
|
+
/>
|
|
432
|
+
<PlanRiskAnnotations plan={detail.plan} expandedByDefault={phase === 'ready_to_merge'} />
|
|
433
|
+
<PlanScopeTree plan={detail.plan} />
|
|
434
|
+
{unresolvedDependencies.length > 0 ? (
|
|
435
|
+
<div className={styles.section}>
|
|
436
|
+
<h3 className={styles.sectionTitle}>Unresolved Dependencies</h3>
|
|
437
|
+
<ul className={styles.planList}>
|
|
438
|
+
{unresolvedDependencies.map((dependency) => (
|
|
439
|
+
<li key={dependency} className={styles.planListItem}>
|
|
440
|
+
<a
|
|
441
|
+
className={styles.linkButton}
|
|
442
|
+
href={buildDashboardFeatureHref(dependency, project)}
|
|
443
|
+
>
|
|
444
|
+
{dependency}
|
|
445
|
+
</a>
|
|
446
|
+
</li>
|
|
447
|
+
))}
|
|
448
|
+
</ul>
|
|
449
|
+
</div>
|
|
450
|
+
) : null}
|
|
451
|
+
{phase === 'ready_to_merge' || phase === 'qa' ? (
|
|
452
|
+
<ReviewBriefPanel brief={reviewBrief} />
|
|
453
|
+
) : null}
|
|
454
|
+
|
|
249
455
|
{detail.feature.activity_state === 'waiting_input' ? (
|
|
250
456
|
<HumanInputPanel
|
|
251
457
|
featureId={detail.feature.feature_id}
|
|
@@ -262,6 +468,10 @@ export function DetailPanel({
|
|
|
262
468
|
|
|
263
469
|
{shouldShowPlan ? <PlanViewer plan={detail.plan} /> : null}
|
|
264
470
|
{shouldShowGates ? <GateResults feature={detail.feature} /> : null}
|
|
471
|
+
{shouldShowGates ? <GateStepDrilldown evidence={detail.gate_evidence} /> : null}
|
|
472
|
+
{phase === 'qa' || phase === 'ready_to_merge' ? (
|
|
473
|
+
<QaCoverageMap qaIndex={qaIndex} flakySuspects={flakySuspects} />
|
|
474
|
+
) : null}
|
|
265
475
|
{shouldShowDiff ? <DiffViewer diff={detail.diff} /> : null}
|
|
266
476
|
{shouldShowEvidence ? (
|
|
267
477
|
<EvidenceViewer
|
|
@@ -299,7 +509,7 @@ export function DetailPanel({
|
|
|
299
509
|
) : null}
|
|
300
510
|
|
|
301
511
|
{phase === 'ready_to_merge' ? (
|
|
302
|
-
<div className={styles.section}>
|
|
512
|
+
<div className={`${styles.section} ${isFocusView ? styles.stickyReviewFooter : ''}`}>
|
|
303
513
|
<h3 className={styles.sectionTitle}>Review Actions</h3>
|
|
304
514
|
<label className={styles.label} htmlFor="review-message">
|
|
305
515
|
Review message
|
|
@@ -12,6 +12,7 @@ interface FeatureCardProps {
|
|
|
12
12
|
feature: FeatureSummary;
|
|
13
13
|
isSelected: boolean;
|
|
14
14
|
isFocused: boolean;
|
|
15
|
+
awaitingDependencies?: boolean;
|
|
15
16
|
onSelect: (featureId: string) => void;
|
|
16
17
|
onFocus: (featureId: string) => void;
|
|
17
18
|
}
|
|
@@ -86,6 +87,7 @@ export function FeatureCard({
|
|
|
86
87
|
feature,
|
|
87
88
|
isSelected,
|
|
88
89
|
isFocused,
|
|
90
|
+
awaitingDependencies = false,
|
|
89
91
|
onSelect,
|
|
90
92
|
onFocus,
|
|
91
93
|
}: FeatureCardProps) {
|
|
@@ -139,6 +141,9 @@ export function FeatureCard({
|
|
|
139
141
|
<span className={`${styles.badge} ${styles.badgeNeutral}`}>
|
|
140
142
|
activity {feature.activity_state ?? 'unknown'}
|
|
141
143
|
</span>
|
|
144
|
+
{awaitingDependencies ? (
|
|
145
|
+
<span className={`${styles.badge} ${styles.badgePending}`}>Awaiting Dependencies</span>
|
|
146
|
+
) : null}
|
|
142
147
|
</div>
|
|
143
148
|
</button>
|
|
144
149
|
);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { CostSummary } from '@/lib/types.js';
|
|
2
|
+
import styles from '@/styles/dashboard.module.css';
|
|
3
|
+
|
|
4
|
+
interface FeatureCostPanelProps {
|
|
5
|
+
cost: CostSummary | null | undefined;
|
|
6
|
+
budgetLimitUsd?: number | null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function budgetClass(ratio: number): string {
|
|
10
|
+
if (ratio >= 1) {
|
|
11
|
+
return styles.badgeFail;
|
|
12
|
+
}
|
|
13
|
+
if (ratio > 0.85) {
|
|
14
|
+
return styles.badgeFail;
|
|
15
|
+
}
|
|
16
|
+
if (ratio > 0.6) {
|
|
17
|
+
return styles.badgePending;
|
|
18
|
+
}
|
|
19
|
+
return styles.badgePass;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function FeatureCostPanel({ cost, budgetLimitUsd = null }: FeatureCostPanelProps) {
|
|
23
|
+
if (!cost) {
|
|
24
|
+
return (
|
|
25
|
+
<div className={styles.section}>
|
|
26
|
+
<h3 className={styles.sectionTitle}>Feature Budget Meter</h3>
|
|
27
|
+
<p className={styles.metaText}>No cost data recorded yet.</p>
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const hasLimit = typeof budgetLimitUsd === 'number' && budgetLimitUsd > 0;
|
|
33
|
+
const ratio = hasLimit ? cost.estimated_cost_usd / budgetLimitUsd : 0;
|
|
34
|
+
const stateLabel = hasLimit
|
|
35
|
+
? ratio >= 1
|
|
36
|
+
? 'Exceeded'
|
|
37
|
+
: ratio > 0.85
|
|
38
|
+
? 'Critical'
|
|
39
|
+
: ratio > 0.6
|
|
40
|
+
? 'Warning'
|
|
41
|
+
: 'Normal'
|
|
42
|
+
: 'No Limit';
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<details className={styles.section}>
|
|
46
|
+
<summary className={styles.sectionSummary}>Feature Budget Meter</summary>
|
|
47
|
+
<p className={styles.metaText}>Tokens: {cost.tokens_used.toLocaleString('en-US')}</p>
|
|
48
|
+
<p className={styles.metaText}>Estimated Cost: ${cost.estimated_cost_usd.toFixed(2)}</p>
|
|
49
|
+
<p className={styles.metaText}>Last Recorded: {cost.recorded_at ?? '-'}</p>
|
|
50
|
+
<div className={styles.badgeRow}>
|
|
51
|
+
<span className={`${styles.badge} ${hasLimit ? budgetClass(ratio) : styles.badgeNeutral}`}>
|
|
52
|
+
{stateLabel}
|
|
53
|
+
</span>
|
|
54
|
+
{hasLimit ? (
|
|
55
|
+
<span className={`${styles.badge} ${styles.badgeNeutral}`}>
|
|
56
|
+
{Math.min(999, Math.max(0, Math.round(ratio * 100)))}% of ${budgetLimitUsd.toFixed(2)}
|
|
57
|
+
</span>
|
|
58
|
+
) : (
|
|
59
|
+
<span className={`${styles.badge} ${styles.badgeNeutral}`}>
|
|
60
|
+
No per-feature budget limit configured
|
|
61
|
+
</span>
|
|
62
|
+
)}
|
|
63
|
+
</div>
|
|
64
|
+
</details>
|
|
65
|
+
);
|
|
66
|
+
}
|