@walwal-harness/cli 7.1.40 → 7.1.41
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/HR-Resource/cdo/SKILL.md +9 -1
- package/HR-Resource/ceo/SKILL.md +33 -2
- package/HR-Resource/coo/SKILL.md +9 -1
- package/HR-Resource/cqo/SKILL.md +9 -1
- package/HR-Resource/cto/SKILL.md +10 -2
- package/HR-Resource/ops/SKILL.md +12 -4
- package/HR-Resource/ops-prompt-inspector/SKILL.md +11 -6
- package/HR-Resource/ops-prompt-inspector/assets/prompt-inspector-static.js +180 -0
- package/HR-Resource/ops-prompt-inspector/scripts/setup.py +66 -3
- package/apps/harness-dashboard/app/api/wake/route.ts +41 -0
- package/apps/harness-dashboard/components/Scene.tsx +568 -35
- package/apps/harness-dashboard/lib/__tests__/harness-state.test.ts +1 -202
- package/apps/harness-dashboard/lib/__tests__/sandbox-e2e.test.ts +490 -0
- package/apps/harness-dashboard/lib/harness-state.ts +107 -796
- package/apps/harness-dashboard/lib/types.ts +22 -222
- package/apps/harness-dashboard/playwright.config.ts +26 -7
- package/assets/templates/AGENTS-ko.md.template +1 -1
- package/assets/templates/AGENTS.md.template +12 -2
- package/assets/templates/HARNESS.md +2 -0
- package/assets/templates/company-pipeline-manifest.json +5 -14
- package/assets/templates/worker-report.md.template +38 -0
- package/bin/init.js +2 -1
- package/commands/goal.md +4 -2
- package/commands/hot-fix.md +1 -1
- package/commands/submission.md +1 -1
- package/package.json +1 -1
- package/scripts/conductor-tick.sh +12 -0
- package/scripts/harness-agenda.sh +105 -0
- package/scripts/harness-company-block.sh +89 -0
- package/scripts/harness-company-complete.sh +17 -8
- package/scripts/harness-company-cycle.sh +54 -0
- package/scripts/harness-progress-set.sh +8 -2
- package/scripts/harness-stop.sh +76 -0
- package/scripts/harness-wake-control.sh +151 -0
- package/scripts/harness-wake.sh +5 -0
- package/apps/harness-dashboard/components/Drawer.tsx +0 -146
- package/apps/harness-dashboard/components/Header.tsx +0 -40
- package/apps/harness-dashboard/components/MissionTimeline.tsx +0 -214
- package/apps/harness-dashboard/components/OrgTree.tsx +0 -459
- package/apps/harness-dashboard/components/drawer/AgentLogTab.tsx +0 -56
- package/apps/harness-dashboard/components/drawer/DesignPreviewTab.tsx +0 -160
- package/apps/harness-dashboard/components/drawer/GotchasTab.tsx +0 -89
- package/apps/harness-dashboard/components/drawer/HypothesisTab.tsx +0 -42
- package/apps/harness-dashboard/components/drawer/IncidentsTab.tsx +0 -50
- package/apps/harness-dashboard/components/drawer/MissionDocTab.tsx +0 -96
- package/apps/harness-dashboard/components/drawer/MissionFlowTab.tsx +0 -576
- package/apps/harness-dashboard/components/drawer/OwnerHistoryTab.tsx +0 -160
- package/apps/harness-dashboard/components/drawer/RoomMetricsTab.tsx +0 -90
- package/apps/harness-dashboard/components/drawer/TracksTab.tsx +0 -47
- package/apps/harness-dashboard/components/three/ArchiveBoxes.tsx +0 -95
- package/apps/harness-dashboard/components/three/DeptBoard.tsx +0 -175
- package/apps/harness-dashboard/components/three/Floor3D.tsx +0 -404
- package/apps/harness-dashboard/components/three/GoalCard3D.tsx +0 -123
- package/apps/harness-dashboard/components/three/HypothesisCards.tsx +0 -71
- package/apps/harness-dashboard/components/three/MeetingWhiteboard.tsx +0 -98
- package/apps/harness-dashboard/components/three/Minifig3D.tsx +0 -391
- package/apps/harness-dashboard/components/three/OpsSiren.tsx +0 -99
- package/apps/harness-dashboard/components/three/Stage3D.tsx +0 -104
- package/apps/harness-dashboard/components/three/TrackRibbons.tsx +0 -81
- package/apps/harness-dashboard/lib/__tests__/i18n.test.ts +0 -16
- package/apps/harness-dashboard/lib/__tests__/iso.test.ts +0 -42
- package/apps/harness-dashboard/lib/__tests__/state-mapping.test.ts +0 -183
- package/apps/harness-dashboard/lib/agent-roster.ts +0 -42
- package/apps/harness-dashboard/lib/furniture.ts +0 -165
- package/apps/harness-dashboard/lib/i18n.ts +0 -26
- package/apps/harness-dashboard/lib/iso.ts +0 -150
- package/apps/harness-dashboard/lib/markdown.tsx +0 -306
- package/apps/harness-dashboard/lib/path-planning.ts +0 -72
- package/apps/harness-dashboard/lib/state-mapping.ts +0 -146
|
@@ -1,459 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { useCallback, useMemo, useRef, useState } from "react";
|
|
3
|
-
import type { AgentId, HarnessSnapshot, MinifigState, OrgNodeDef } from "@/lib/types";
|
|
4
|
-
|
|
5
|
-
interface OrgTreeProps {
|
|
6
|
-
snapshot: HarnessSnapshot;
|
|
7
|
-
activeNodeId: string | null;
|
|
8
|
-
onNodeClick: (node: OrgNodeDef) => void;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function StatusDot({ status }: { status: MinifigState }) {
|
|
12
|
-
const cls = {
|
|
13
|
-
"red-alert": "bg-rose-500 animate-ping",
|
|
14
|
-
typing: "bg-cyan-400 animate-pulse",
|
|
15
|
-
talking: "bg-emerald-400",
|
|
16
|
-
queued: "bg-amber-400",
|
|
17
|
-
idle: "bg-gray-600",
|
|
18
|
-
}[status] ?? "bg-gray-600";
|
|
19
|
-
return <span className={`absolute top-2 right-2 h-2 w-2 rounded-full ${cls}`} />;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function OrgCard({
|
|
23
|
-
node,
|
|
24
|
-
size = "md",
|
|
25
|
-
active,
|
|
26
|
-
onClick,
|
|
27
|
-
}: {
|
|
28
|
-
node: OrgNodeDef;
|
|
29
|
-
size?: "lg" | "md" | "sm";
|
|
30
|
-
active: boolean;
|
|
31
|
-
onClick: (n: OrgNodeDef) => void;
|
|
32
|
-
}) {
|
|
33
|
-
const roleColor = {
|
|
34
|
-
owner: "border-amber-500/50 bg-amber-500/5",
|
|
35
|
-
ceo: "border-cyan-400/50 bg-cyan-400/5",
|
|
36
|
-
coo: "border-emerald-400/40 bg-emerald-500/5",
|
|
37
|
-
cdo: "border-purple-400/40 bg-purple-500/5",
|
|
38
|
-
cto: "border-blue-400/40 bg-blue-500/5",
|
|
39
|
-
cqo: "border-amber-400/40 bg-amber-500/5",
|
|
40
|
-
ops: "border-rose-400/40 bg-rose-500/5",
|
|
41
|
-
}[node.role] ?? "border-gray-600/40 bg-gray-800/30";
|
|
42
|
-
|
|
43
|
-
const widths = {
|
|
44
|
-
lg: "min-w-[220px] max-w-[260px]",
|
|
45
|
-
md: "min-w-[150px] max-w-[190px]",
|
|
46
|
-
sm: "min-w-[130px] max-w-[160px]",
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
return (
|
|
50
|
-
<button
|
|
51
|
-
type="button"
|
|
52
|
-
onClick={() => onClick(node)}
|
|
53
|
-
className={`brutal-tile relative rounded-md p-3 text-left transition-all cursor-pointer ${roleColor} ${widths[size]} ${active ? "ring-2 ring-cyan-300 border-cyan-300/80" : "hover:border-white/35"}`}
|
|
54
|
-
>
|
|
55
|
-
<StatusDot status={node.status} />
|
|
56
|
-
<div className="font-mono text-[9px] uppercase tracking-[0.2em] text-gray-500 mb-0.5">{node.role}</div>
|
|
57
|
-
<div className="text-sm font-semibold text-gray-100 leading-tight">{node.label}</div>
|
|
58
|
-
{node.sublabel && (
|
|
59
|
-
<div className="text-[10px] text-gray-500 mt-0.5">{node.sublabel}</div>
|
|
60
|
-
)}
|
|
61
|
-
{node.activity && (
|
|
62
|
-
<div className="mt-2 text-[10px] text-gray-400 line-clamp-2 leading-relaxed">
|
|
63
|
-
{node.activity}
|
|
64
|
-
</div>
|
|
65
|
-
)}
|
|
66
|
-
</button>
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function OrgTree({ snapshot, activeNodeId, onNodeClick }: OrgTreeProps) {
|
|
71
|
-
const defaultZoom = 0.74;
|
|
72
|
-
const [zoom, setZoom] = useState(defaultZoom);
|
|
73
|
-
const scrollRef = useRef<HTMLDivElement | null>(null);
|
|
74
|
-
const dragRef = useRef<{ x: number; y: number; left: number; top: number } | null>(null);
|
|
75
|
-
|
|
76
|
-
const agentMap = new Map(snapshot.agents.map((a) => [a.id, a]));
|
|
77
|
-
|
|
78
|
-
const getStatus = (ids: AgentId[]): MinifigState => {
|
|
79
|
-
const states = ids.map((id) => agentMap.get(id)?.minifigState ?? "idle");
|
|
80
|
-
if (states.includes("red-alert")) return "red-alert";
|
|
81
|
-
if (states.includes("typing")) return "typing";
|
|
82
|
-
if (states.includes("talking")) return "talking";
|
|
83
|
-
if (states.includes("queued")) return "queued";
|
|
84
|
-
return "idle";
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const currentMission = snapshot.missions?.[0] ?? null;
|
|
88
|
-
|
|
89
|
-
// Parse CEO activity from mission doc
|
|
90
|
-
const ceoActivity = (() => {
|
|
91
|
-
if (!currentMission?.ceo) return null;
|
|
92
|
-
const match = currentMission.ceo.match(/##\s*Owner 요청 요약\s*\n([\s\S]*?)(?=\n##)/);
|
|
93
|
-
return (
|
|
94
|
-
match?.[1]
|
|
95
|
-
?.split("\n")
|
|
96
|
-
.find((l) => l.trim().length > 0)
|
|
97
|
-
?.replace(/^[-*\d.]\s*/, "")
|
|
98
|
-
.trim()
|
|
99
|
-
.slice(0, 100) ?? null
|
|
100
|
-
);
|
|
101
|
-
})();
|
|
102
|
-
|
|
103
|
-
const ownerNode: OrgNodeDef = {
|
|
104
|
-
id: "owner",
|
|
105
|
-
role: "owner",
|
|
106
|
-
label: "Owner",
|
|
107
|
-
sublabel: "/goal · /submission · /hot-fix",
|
|
108
|
-
status: "idle",
|
|
109
|
-
activity: snapshot.ownerHistory?.[0]?.content.slice(0, 80) ?? null,
|
|
110
|
-
agentIds: [],
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
const ceoNode: OrgNodeDef = {
|
|
114
|
-
id: "ceo",
|
|
115
|
-
role: "ceo",
|
|
116
|
-
label: "harness-ceo",
|
|
117
|
-
sublabel: "Dispatcher · Brainstormer",
|
|
118
|
-
status: getStatus(["dispatcher", "brainstormer"] as AgentId[]),
|
|
119
|
-
activity: ceoActivity,
|
|
120
|
-
agentIds: ["dispatcher", "brainstormer"] as AgentId[],
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
const cxxNodes: OrgNodeDef[] = [
|
|
124
|
-
{
|
|
125
|
-
id: "coo",
|
|
126
|
-
role: "coo",
|
|
127
|
-
label: "harness-coo",
|
|
128
|
-
sublabel: "Planner · Research",
|
|
129
|
-
status: getStatus(["planner", "coo-developer", "documentationer"] as AgentId[]),
|
|
130
|
-
activity: null,
|
|
131
|
-
agentIds: ["planner", "coo-developer", "documentationer"] as AgentId[],
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
id: "cdo",
|
|
135
|
-
role: "cdo",
|
|
136
|
-
label: "harness-cdo",
|
|
137
|
-
sublabel: "Design · Branding",
|
|
138
|
-
status: "idle",
|
|
139
|
-
activity: null,
|
|
140
|
-
agentIds: [],
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
id: "cto",
|
|
144
|
-
role: "cto",
|
|
145
|
-
label: "harness-cto",
|
|
146
|
-
sublabel: "Conductor · Generators",
|
|
147
|
-
status: getStatus([
|
|
148
|
-
"cto",
|
|
149
|
-
"conductor",
|
|
150
|
-
"generator-backend",
|
|
151
|
-
"generator-frontend",
|
|
152
|
-
"generator-designer",
|
|
153
|
-
"generator-devops",
|
|
154
|
-
] as AgentId[]),
|
|
155
|
-
activity: currentMission?.cto ? "Architecture plan active" : null,
|
|
156
|
-
agentIds: [
|
|
157
|
-
"cto",
|
|
158
|
-
"conductor",
|
|
159
|
-
"generator-backend",
|
|
160
|
-
"generator-frontend",
|
|
161
|
-
"generator-designer",
|
|
162
|
-
"generator-devops",
|
|
163
|
-
] as AgentId[],
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
id: "cqo",
|
|
167
|
-
role: "cqo",
|
|
168
|
-
label: "harness-cqo",
|
|
169
|
-
sublabel: "Evaluators",
|
|
170
|
-
status: getStatus([
|
|
171
|
-
"cqo",
|
|
172
|
-
"evaluator-functional",
|
|
173
|
-
"evaluator-visual",
|
|
174
|
-
"evaluator-code-quality",
|
|
175
|
-
"evaluator-architecture",
|
|
176
|
-
"evaluator-security",
|
|
177
|
-
] as AgentId[]),
|
|
178
|
-
activity: currentMission?.cqo?.includes("ACCEPTED")
|
|
179
|
-
? "ACCEPTED"
|
|
180
|
-
: currentMission?.cqo
|
|
181
|
-
? "Report available"
|
|
182
|
-
: null,
|
|
183
|
-
agentIds: [
|
|
184
|
-
"cqo",
|
|
185
|
-
"evaluator-functional",
|
|
186
|
-
"evaluator-visual",
|
|
187
|
-
"evaluator-code-quality",
|
|
188
|
-
"evaluator-architecture",
|
|
189
|
-
"evaluator-security",
|
|
190
|
-
] as AgentId[],
|
|
191
|
-
},
|
|
192
|
-
{
|
|
193
|
-
id: "ops",
|
|
194
|
-
role: "ops",
|
|
195
|
-
label: "harness-ops",
|
|
196
|
-
sublabel: "Service Monitor",
|
|
197
|
-
status: getStatus(["service-ops"] as AgentId[]),
|
|
198
|
-
activity: null,
|
|
199
|
-
agentIds: ["service-ops"] as AgentId[],
|
|
200
|
-
},
|
|
201
|
-
];
|
|
202
|
-
|
|
203
|
-
// Only hired HR-Resource workers are positioned in the org chart. Legacy
|
|
204
|
-
// report-only workers remain visible in mission documents, but not as seats.
|
|
205
|
-
const workers = (currentMission?.workers ?? []).filter((w) => w.hired);
|
|
206
|
-
|
|
207
|
-
const setBoundedZoom = useCallback((next: number) => {
|
|
208
|
-
setZoom(Math.min(1.35, Math.max(0.62, Number(next.toFixed(2)))));
|
|
209
|
-
}, []);
|
|
210
|
-
|
|
211
|
-
const handleWheel = useCallback(
|
|
212
|
-
(event: React.WheelEvent<HTMLDivElement>) => {
|
|
213
|
-
if (!event.ctrlKey && !event.metaKey) return;
|
|
214
|
-
event.preventDefault();
|
|
215
|
-
setBoundedZoom(zoom + (event.deltaY > 0 ? -0.06 : 0.06));
|
|
216
|
-
},
|
|
217
|
-
[setBoundedZoom, zoom],
|
|
218
|
-
);
|
|
219
|
-
|
|
220
|
-
const handlePointerDown = useCallback((event: React.PointerEvent<HTMLDivElement>) => {
|
|
221
|
-
if (event.button !== 0 || (event.target as HTMLElement).closest("button")) return;
|
|
222
|
-
const node = scrollRef.current;
|
|
223
|
-
if (!node) return;
|
|
224
|
-
dragRef.current = {
|
|
225
|
-
x: event.clientX,
|
|
226
|
-
y: event.clientY,
|
|
227
|
-
left: node.scrollLeft,
|
|
228
|
-
top: node.scrollTop,
|
|
229
|
-
};
|
|
230
|
-
node.setPointerCapture(event.pointerId);
|
|
231
|
-
}, []);
|
|
232
|
-
|
|
233
|
-
const handlePointerMove = useCallback((event: React.PointerEvent<HTMLDivElement>) => {
|
|
234
|
-
const start = dragRef.current;
|
|
235
|
-
const node = scrollRef.current;
|
|
236
|
-
if (!start || !node) return;
|
|
237
|
-
node.scrollLeft = start.left - (event.clientX - start.x);
|
|
238
|
-
node.scrollTop = start.top - (event.clientY - start.y);
|
|
239
|
-
}, []);
|
|
240
|
-
|
|
241
|
-
const endDrag = useCallback((event: React.PointerEvent<HTMLDivElement>) => {
|
|
242
|
-
scrollRef.current?.releasePointerCapture(event.pointerId);
|
|
243
|
-
dragRef.current = null;
|
|
244
|
-
}, []);
|
|
245
|
-
|
|
246
|
-
const canvas = { width: 1280, height: 500 };
|
|
247
|
-
const ownerPos = { x: 70, y: 196 };
|
|
248
|
-
const ceoPos = { x: 320, y: 196 };
|
|
249
|
-
const cxxX = 610;
|
|
250
|
-
const workerX = 890;
|
|
251
|
-
|
|
252
|
-
const layout = useMemo(() => {
|
|
253
|
-
const startY = 50;
|
|
254
|
-
const rowGap = 86;
|
|
255
|
-
|
|
256
|
-
return cxxNodes.map((node, index) => {
|
|
257
|
-
const nodeWorkers = workers.filter((w) => w.owner === node.role);
|
|
258
|
-
const y = startY + index * rowGap;
|
|
259
|
-
const workerHeight = Math.max(58, nodeWorkers.length * 54 + Math.max(0, nodeWorkers.length - 1) * 6);
|
|
260
|
-
return { node, x: cxxX, y, workerX, workerY: y - workerHeight / 2 + 30, nodeWorkers };
|
|
261
|
-
});
|
|
262
|
-
}, [cxxNodes, workers]);
|
|
263
|
-
|
|
264
|
-
return (
|
|
265
|
-
<div className="select-none">
|
|
266
|
-
<div className="flex items-center justify-between border-b border-white/10 bg-white/5 px-3 py-2 backdrop-blur">
|
|
267
|
-
<div className="min-w-0">
|
|
268
|
-
{currentMission && (
|
|
269
|
-
<div className="truncate font-mono text-[10px] text-cyan-300/70">
|
|
270
|
-
current mission · {currentMission.missionId}
|
|
271
|
-
</div>
|
|
272
|
-
)}
|
|
273
|
-
</div>
|
|
274
|
-
<div className="flex items-center gap-1">
|
|
275
|
-
<button
|
|
276
|
-
type="button"
|
|
277
|
-
onClick={() => setBoundedZoom(zoom - 0.08)}
|
|
278
|
-
className="h-7 w-7 rounded-md border border-white/15 bg-black/35 font-mono text-xs text-gray-200 hover:border-cyan-300/60"
|
|
279
|
-
title="Zoom out"
|
|
280
|
-
>
|
|
281
|
-
-
|
|
282
|
-
</button>
|
|
283
|
-
<button
|
|
284
|
-
type="button"
|
|
285
|
-
onClick={() => setBoundedZoom(defaultZoom)}
|
|
286
|
-
className="h-7 min-w-12 rounded-md border border-white/15 bg-black/35 px-2 font-mono text-[10px] text-gray-300 hover:border-cyan-300/60"
|
|
287
|
-
title="Reset zoom"
|
|
288
|
-
>
|
|
289
|
-
{Math.round(zoom * 100)}%
|
|
290
|
-
</button>
|
|
291
|
-
<button
|
|
292
|
-
type="button"
|
|
293
|
-
onClick={() => setBoundedZoom(zoom + 0.08)}
|
|
294
|
-
className="h-7 w-7 rounded-md border border-white/15 bg-black/35 font-mono text-xs text-gray-200 hover:border-cyan-300/60"
|
|
295
|
-
title="Zoom in"
|
|
296
|
-
>
|
|
297
|
-
+
|
|
298
|
-
</button>
|
|
299
|
-
</div>
|
|
300
|
-
</div>
|
|
301
|
-
|
|
302
|
-
<div
|
|
303
|
-
ref={scrollRef}
|
|
304
|
-
onWheel={handleWheel}
|
|
305
|
-
onPointerDown={handlePointerDown}
|
|
306
|
-
onPointerMove={handlePointerMove}
|
|
307
|
-
onPointerUp={endDrag}
|
|
308
|
-
onPointerCancel={endDrag}
|
|
309
|
-
className="scrollbar-hidden isometric-stage h-[390px] cursor-grab overflow-auto active:cursor-grabbing"
|
|
310
|
-
>
|
|
311
|
-
<div
|
|
312
|
-
className="relative"
|
|
313
|
-
style={{
|
|
314
|
-
width: canvas.width * zoom,
|
|
315
|
-
height: canvas.height * zoom,
|
|
316
|
-
}}
|
|
317
|
-
>
|
|
318
|
-
<div
|
|
319
|
-
className="absolute left-0 top-0 origin-top-left"
|
|
320
|
-
style={{
|
|
321
|
-
width: canvas.width,
|
|
322
|
-
height: canvas.height,
|
|
323
|
-
transform: `scale(${zoom})`,
|
|
324
|
-
}}
|
|
325
|
-
>
|
|
326
|
-
<svg className="pointer-events-none absolute inset-0" width={canvas.width} height={canvas.height}>
|
|
327
|
-
<path
|
|
328
|
-
d={`M ${ownerPos.x + 235} ${ownerPos.y + 56} H ${ceoPos.x}`}
|
|
329
|
-
stroke="#374151"
|
|
330
|
-
strokeWidth="1.5"
|
|
331
|
-
fill="none"
|
|
332
|
-
/>
|
|
333
|
-
<path
|
|
334
|
-
d={`M ${ceoPos.x + 235} ${ceoPos.y + 56} H ${cxxX - 54}`}
|
|
335
|
-
stroke="#374151"
|
|
336
|
-
strokeWidth="1.5"
|
|
337
|
-
fill="none"
|
|
338
|
-
/>
|
|
339
|
-
<path
|
|
340
|
-
d={`M ${cxxX - 54} ${layout[0]?.y ?? 80} V ${layout[layout.length - 1]?.y ?? 420}`}
|
|
341
|
-
stroke="#374151"
|
|
342
|
-
strokeWidth="1.5"
|
|
343
|
-
fill="none"
|
|
344
|
-
/>
|
|
345
|
-
{layout.map(({ node, x, y, nodeWorkers }) => (
|
|
346
|
-
<g key={node.id}>
|
|
347
|
-
<path
|
|
348
|
-
d={`M ${x - 54} ${y} H ${x - 10}`}
|
|
349
|
-
stroke="#374151"
|
|
350
|
-
strokeDasharray="4 5"
|
|
351
|
-
strokeWidth="1.5"
|
|
352
|
-
fill="none"
|
|
353
|
-
/>
|
|
354
|
-
{nodeWorkers.length > 0 && (
|
|
355
|
-
<path
|
|
356
|
-
d={`M ${x + 166} ${y} H ${workerX - 16}`}
|
|
357
|
-
stroke="#374151"
|
|
358
|
-
strokeWidth="1.5"
|
|
359
|
-
fill="none"
|
|
360
|
-
/>
|
|
361
|
-
)}
|
|
362
|
-
</g>
|
|
363
|
-
))}
|
|
364
|
-
</svg>
|
|
365
|
-
|
|
366
|
-
<div className="absolute" style={{ left: ownerPos.x, top: ownerPos.y }}>
|
|
367
|
-
<OrgCard node={ownerNode} size="lg" active={activeNodeId === "owner"} onClick={onNodeClick} />
|
|
368
|
-
</div>
|
|
369
|
-
|
|
370
|
-
<div className="absolute" style={{ left: ceoPos.x, top: ceoPos.y }}>
|
|
371
|
-
<OrgCard
|
|
372
|
-
node={ceoNode}
|
|
373
|
-
size="lg"
|
|
374
|
-
active={activeNodeId === "ceo"}
|
|
375
|
-
onClick={onNodeClick}
|
|
376
|
-
/>
|
|
377
|
-
</div>
|
|
378
|
-
|
|
379
|
-
{layout.map(({ node, x, y, workerX, workerY, nodeWorkers }) => {
|
|
380
|
-
const hasActiveWorker = nodeWorkers.some((w) => w.active);
|
|
381
|
-
const displayNode = hasActiveWorker
|
|
382
|
-
? { ...node, status: "typing" as const, activity: "Worker active" }
|
|
383
|
-
: node;
|
|
384
|
-
|
|
385
|
-
return (
|
|
386
|
-
<div
|
|
387
|
-
key={node.id}
|
|
388
|
-
className="absolute"
|
|
389
|
-
style={{ left: x, top: y - 38 }}
|
|
390
|
-
>
|
|
391
|
-
<OrgCard
|
|
392
|
-
node={displayNode}
|
|
393
|
-
size="md"
|
|
394
|
-
active={activeNodeId === node.id}
|
|
395
|
-
onClick={onNodeClick}
|
|
396
|
-
/>
|
|
397
|
-
{nodeWorkers.length > 0 && (
|
|
398
|
-
<div
|
|
399
|
-
className="absolute flex w-[170px] flex-col gap-1.5"
|
|
400
|
-
style={{ left: workerX - x, top: workerY - y }}
|
|
401
|
-
>
|
|
402
|
-
{nodeWorkers.map((w) => {
|
|
403
|
-
const workerNodeId = `worker-${node.role}-${w.name}`;
|
|
404
|
-
const workerActive = activeNodeId === workerNodeId || w.active;
|
|
405
|
-
return (
|
|
406
|
-
<button
|
|
407
|
-
key={`${node.role}-${w.name}`}
|
|
408
|
-
type="button"
|
|
409
|
-
onClick={() =>
|
|
410
|
-
onNodeClick({
|
|
411
|
-
id: workerNodeId,
|
|
412
|
-
role: node.role,
|
|
413
|
-
label: w.displayName,
|
|
414
|
-
sublabel: w.active ? `running · ${w.name}` : `${w.status} · ${w.name}`,
|
|
415
|
-
status: w.active ? "typing" : w.status === "COMPLETE" ? "idle" : "queued",
|
|
416
|
-
activity: w.sourcePath ?? null,
|
|
417
|
-
agentIds: [],
|
|
418
|
-
})
|
|
419
|
-
}
|
|
420
|
-
className={`brutal-tile relative rounded-md px-2 py-1.5 text-left min-w-[130px] max-w-[160px] transition-all hover:border-white/25 ${
|
|
421
|
-
workerActive
|
|
422
|
-
? "border-cyan-300/70 bg-cyan-400/10 shadow-[0_0_18px_rgba(34,211,238,0.18)]"
|
|
423
|
-
: "border-gray-700/40 bg-gray-800/30"
|
|
424
|
-
} ${activeNodeId === workerNodeId ? "ring-1 ring-cyan-300" : ""}`}
|
|
425
|
-
>
|
|
426
|
-
{w.active && (
|
|
427
|
-
<span className="absolute right-2 top-2 h-1.5 w-1.5 rounded-full bg-cyan-300 animate-pulse" />
|
|
428
|
-
)}
|
|
429
|
-
<div className={`font-mono text-[9px] ${w.active ? "text-cyan-300/80" : "text-gray-500"}`}>
|
|
430
|
-
hired worker
|
|
431
|
-
</div>
|
|
432
|
-
<div className="text-[11px] font-medium text-gray-200 truncate pr-3">
|
|
433
|
-
{w.displayName}
|
|
434
|
-
</div>
|
|
435
|
-
<div
|
|
436
|
-
className={`text-[9px] font-mono mt-0.5 ${
|
|
437
|
-
w.active
|
|
438
|
-
? "text-cyan-300"
|
|
439
|
-
: w.status === "COMPLETE"
|
|
440
|
-
? "text-emerald-400"
|
|
441
|
-
: "text-amber-400"
|
|
442
|
-
}`}
|
|
443
|
-
>
|
|
444
|
-
{w.active ? "RUNNING" : w.status}
|
|
445
|
-
</div>
|
|
446
|
-
</button>
|
|
447
|
-
);
|
|
448
|
-
})}
|
|
449
|
-
</div>
|
|
450
|
-
)}
|
|
451
|
-
</div>
|
|
452
|
-
);
|
|
453
|
-
})}
|
|
454
|
-
</div>
|
|
455
|
-
</div>
|
|
456
|
-
</div>
|
|
457
|
-
</div>
|
|
458
|
-
);
|
|
459
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { useEffect, useRef, useState } from "react";
|
|
3
|
-
|
|
4
|
-
interface AgentLogTabProps {
|
|
5
|
-
agentId: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function AgentLogTab({ agentId }: AgentLogTabProps) {
|
|
9
|
-
const [lines, setLines] = useState<string[] | null>(null);
|
|
10
|
-
const [error, setError] = useState<string | null>(null);
|
|
11
|
-
const scrollRef = useRef<HTMLDivElement>(null);
|
|
12
|
-
|
|
13
|
-
useEffect(() => {
|
|
14
|
-
let cancelled = false;
|
|
15
|
-
setLines(null);
|
|
16
|
-
setError(null);
|
|
17
|
-
fetch(`/api/log?agent=${encodeURIComponent(agentId)}&limit=50`)
|
|
18
|
-
.then((r) => r.json())
|
|
19
|
-
.then((d) => {
|
|
20
|
-
if (cancelled) return;
|
|
21
|
-
setLines(d.lines ?? []);
|
|
22
|
-
})
|
|
23
|
-
.catch((e) => {
|
|
24
|
-
if (cancelled) return;
|
|
25
|
-
setError(String(e));
|
|
26
|
-
});
|
|
27
|
-
return () => {
|
|
28
|
-
cancelled = true;
|
|
29
|
-
};
|
|
30
|
-
}, [agentId]);
|
|
31
|
-
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
if (lines && scrollRef.current) {
|
|
34
|
-
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
|
35
|
-
}
|
|
36
|
-
}, [lines]);
|
|
37
|
-
|
|
38
|
-
if (error) {
|
|
39
|
-
return <div className="text-aura-alert">Error loading log: {error}</div>;
|
|
40
|
-
}
|
|
41
|
-
if (lines === null) {
|
|
42
|
-
return <div className="text-gray-500">Loading…</div>;
|
|
43
|
-
}
|
|
44
|
-
if (lines.length === 0) {
|
|
45
|
-
return <div className="text-gray-500">No log lines for {agentId}.</div>;
|
|
46
|
-
}
|
|
47
|
-
return (
|
|
48
|
-
<div ref={scrollRef} className="space-y-1 max-h-full">
|
|
49
|
-
{lines.map((line, i) => (
|
|
50
|
-
<div key={i} className="whitespace-pre-wrap break-words text-[11px] leading-relaxed">
|
|
51
|
-
{line}
|
|
52
|
-
</div>
|
|
53
|
-
))}
|
|
54
|
-
</div>
|
|
55
|
-
);
|
|
56
|
-
}
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import type { MissionDoc } from "@/lib/types";
|
|
3
|
-
|
|
4
|
-
interface DesignPreviewTabProps {
|
|
5
|
-
mission: MissionDoc | null;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
function escapeHtml(value: string) {
|
|
9
|
-
return value
|
|
10
|
-
.replaceAll("&", "&")
|
|
11
|
-
.replaceAll("<", "<")
|
|
12
|
-
.replaceAll(">", ">")
|
|
13
|
-
.replaceAll('"', """);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function extractFirstBullet(markdown: string | null | undefined) {
|
|
17
|
-
if (!markdown) return null;
|
|
18
|
-
return (
|
|
19
|
-
markdown
|
|
20
|
-
.split("\n")
|
|
21
|
-
.map((line) => line.trim())
|
|
22
|
-
.find((line) => /^[-*]\s+\S/.test(line))
|
|
23
|
-
?.replace(/^[-*]\s+/, "")
|
|
24
|
-
.slice(0, 140) ?? null
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function buildPreviewDoc(mission: MissionDoc | null) {
|
|
29
|
-
if (mission?.cdoPreview?.trim()) {
|
|
30
|
-
return mission.cdoPreview;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const missionId = mission?.missionId ?? "No active mission";
|
|
34
|
-
const brief =
|
|
35
|
-
extractFirstBullet(mission?.cdo) ??
|
|
36
|
-
extractFirstBullet(mission?.ceo) ??
|
|
37
|
-
"Design direction will appear here after harness-cdo writes cdo.md.";
|
|
38
|
-
|
|
39
|
-
return `<!doctype html>
|
|
40
|
-
<html lang="en">
|
|
41
|
-
<head>
|
|
42
|
-
<meta charset="utf-8" />
|
|
43
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
44
|
-
<style>
|
|
45
|
-
:root {
|
|
46
|
-
color-scheme: dark;
|
|
47
|
-
--bg: #101216;
|
|
48
|
-
--panel: #171b22;
|
|
49
|
-
--line: #2d3543;
|
|
50
|
-
--text: #eef2f7;
|
|
51
|
-
--muted: #8a94a6;
|
|
52
|
-
--cyan: #22d3ee;
|
|
53
|
-
--amber: #fbbf24;
|
|
54
|
-
--rose: #fb7185;
|
|
55
|
-
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
56
|
-
}
|
|
57
|
-
* { box-sizing: border-box; }
|
|
58
|
-
body {
|
|
59
|
-
margin: 0;
|
|
60
|
-
min-height: 100vh;
|
|
61
|
-
background: radial-gradient(circle at 18% 0%, rgba(34, 211, 238, .12), transparent 26rem), var(--bg);
|
|
62
|
-
color: var(--text);
|
|
63
|
-
}
|
|
64
|
-
.shell { min-height: 100vh; display: grid; grid-template-rows: auto 1fr; }
|
|
65
|
-
header {
|
|
66
|
-
display: flex; align-items: center; justify-content: space-between; gap: 16px;
|
|
67
|
-
padding: 18px 22px; border-bottom: 1px solid var(--line); background: rgba(10, 12, 16, .72);
|
|
68
|
-
}
|
|
69
|
-
.brand { font-size: 12px; letter-spacing: .22em; text-transform: uppercase; color: var(--cyan); }
|
|
70
|
-
.mission { max-width: 54ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); font-size: 12px; }
|
|
71
|
-
main { display: grid; grid-template-columns: 280px 1fr; min-height: 0; }
|
|
72
|
-
aside { border-right: 1px solid var(--line); padding: 18px; background: rgba(255,255,255,.02); }
|
|
73
|
-
.nav { display: grid; gap: 8px; }
|
|
74
|
-
.nav div { padding: 10px 12px; border: 1px solid var(--line); border-radius: 8px; color: var(--muted); font-size: 12px; }
|
|
75
|
-
.nav div:first-child { color: var(--text); border-color: rgba(34,211,238,.55); background: rgba(34,211,238,.08); }
|
|
76
|
-
section { padding: 22px; min-width: 0; }
|
|
77
|
-
.hero {
|
|
78
|
-
border: 1px solid var(--line); border-radius: 10px; overflow: hidden; background: var(--panel);
|
|
79
|
-
display: grid; grid-template-columns: minmax(260px, 1fr) 280px;
|
|
80
|
-
}
|
|
81
|
-
.hero-copy { padding: 28px; }
|
|
82
|
-
h1 { margin: 0; font-size: 34px; line-height: 1.08; letter-spacing: 0; }
|
|
83
|
-
p { color: var(--muted); line-height: 1.55; }
|
|
84
|
-
.actions { display: flex; gap: 10px; margin-top: 22px; }
|
|
85
|
-
button { border: 0; border-radius: 8px; padding: 10px 14px; font-weight: 700; }
|
|
86
|
-
.primary { background: var(--cyan); color: #061014; }
|
|
87
|
-
.secondary { background: #242a35; color: var(--text); }
|
|
88
|
-
.visual { padding: 20px; background: linear-gradient(135deg, rgba(34,211,238,.18), rgba(251,191,36,.12)); display: grid; align-content: center; gap: 12px; }
|
|
89
|
-
.card { border: 1px solid rgba(255,255,255,.16); border-radius: 8px; padding: 14px; background: rgba(16,18,22,.72); box-shadow: 0 18px 40px rgba(0,0,0,.22); }
|
|
90
|
-
.metric { font-size: 26px; font-weight: 800; }
|
|
91
|
-
.grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; margin-top: 16px; }
|
|
92
|
-
.tile { border: 1px solid var(--line); border-radius: 8px; padding: 14px; background: rgba(255,255,255,.03); min-height: 112px; }
|
|
93
|
-
.tile b { display: block; margin-bottom: 8px; }
|
|
94
|
-
@media (max-width: 760px) {
|
|
95
|
-
main, .hero { grid-template-columns: 1fr; }
|
|
96
|
-
aside { display: none; }
|
|
97
|
-
h1 { font-size: 28px; }
|
|
98
|
-
.grid { grid-template-columns: 1fr; }
|
|
99
|
-
}
|
|
100
|
-
</style>
|
|
101
|
-
</head>
|
|
102
|
-
<body>
|
|
103
|
-
<div class="shell">
|
|
104
|
-
<header>
|
|
105
|
-
<div class="brand">CDO Visual Sample</div>
|
|
106
|
-
<div class="mission">${escapeHtml(missionId)}</div>
|
|
107
|
-
</header>
|
|
108
|
-
<main>
|
|
109
|
-
<aside>
|
|
110
|
-
<div class="nav">
|
|
111
|
-
<div>Overview</div>
|
|
112
|
-
<div>Interaction</div>
|
|
113
|
-
<div>Accessibility</div>
|
|
114
|
-
<div>Review</div>
|
|
115
|
-
</div>
|
|
116
|
-
</aside>
|
|
117
|
-
<section>
|
|
118
|
-
<div class="hero">
|
|
119
|
-
<div class="hero-copy">
|
|
120
|
-
<h1>${escapeHtml(brief)}</h1>
|
|
121
|
-
<p>Structured sample generated from the active CDO/CEO mission context. Replace this with a richer cdo.md mockup artifact when available.</p>
|
|
122
|
-
<div class="actions">
|
|
123
|
-
<button class="primary">Primary action</button>
|
|
124
|
-
<button class="secondary">Secondary</button>
|
|
125
|
-
</div>
|
|
126
|
-
</div>
|
|
127
|
-
<div class="visual">
|
|
128
|
-
<div class="card"><div class="metric">92%</div><p>Readiness score</p></div>
|
|
129
|
-
<div class="card"><b>State</b><p>Focused, scannable, worker-backed.</p></div>
|
|
130
|
-
</div>
|
|
131
|
-
</div>
|
|
132
|
-
<div class="grid">
|
|
133
|
-
<div class="tile"><b>Layout</b><p>Quiet hierarchy, constrained content, no nested cards.</p></div>
|
|
134
|
-
<div class="tile"><b>Flow</b><p>Primary workflow remains visible without explanation copy.</p></div>
|
|
135
|
-
<div class="tile"><b>Quality</b><p>Designed for CDO review before CQO evidence collection.</p></div>
|
|
136
|
-
</div>
|
|
137
|
-
</section>
|
|
138
|
-
</main>
|
|
139
|
-
</div>
|
|
140
|
-
</body>
|
|
141
|
-
</html>`;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export function DesignPreviewTab({ mission }: DesignPreviewTabProps) {
|
|
145
|
-
const srcDoc = buildPreviewDoc(mission);
|
|
146
|
-
|
|
147
|
-
return (
|
|
148
|
-
<div className="flex h-full min-h-[620px] flex-col gap-3">
|
|
149
|
-
<div className="rounded border border-purple-400/30 bg-purple-500/10 px-3 py-2 text-[11px] text-purple-200">
|
|
150
|
-
harness-cdo visual preview
|
|
151
|
-
</div>
|
|
152
|
-
<iframe
|
|
153
|
-
title="CDO visual sample"
|
|
154
|
-
sandbox=""
|
|
155
|
-
srcDoc={srcDoc}
|
|
156
|
-
className="min-h-0 flex-1 rounded-md border border-gray-700 bg-[#101216]"
|
|
157
|
-
/>
|
|
158
|
-
</div>
|
|
159
|
-
);
|
|
160
|
-
}
|