@walwal-harness/cli 7.1.39 → 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.
Files changed (74) hide show
  1. package/HR-Resource/cdo/SKILL.md +9 -1
  2. package/HR-Resource/ceo/SKILL.md +33 -2
  3. package/HR-Resource/coo/SKILL.md +9 -1
  4. package/HR-Resource/cqo/SKILL.md +9 -1
  5. package/HR-Resource/cto/SKILL.md +10 -2
  6. package/HR-Resource/ops/SKILL.md +20 -2
  7. package/HR-Resource/ops-prompt-inspector/SKILL.md +105 -0
  8. package/HR-Resource/ops-prompt-inspector/agents/installation-agent.json +7 -0
  9. package/HR-Resource/ops-prompt-inspector/assets/PromptInspector.tsx +599 -0
  10. package/HR-Resource/ops-prompt-inspector/assets/prompt-inspector-static.js +180 -0
  11. package/HR-Resource/ops-prompt-inspector/references/handler-templates.md +255 -0
  12. package/HR-Resource/ops-prompt-inspector/scripts/discover_apis.py +509 -0
  13. package/HR-Resource/ops-prompt-inspector/scripts/install.sh +32 -0
  14. package/HR-Resource/ops-prompt-inspector/scripts/setup.py +381 -0
  15. package/apps/harness-dashboard/app/api/wake/route.ts +41 -0
  16. package/apps/harness-dashboard/components/Scene.tsx +568 -35
  17. package/apps/harness-dashboard/lib/__tests__/harness-state.test.ts +1 -202
  18. package/apps/harness-dashboard/lib/__tests__/sandbox-e2e.test.ts +490 -0
  19. package/apps/harness-dashboard/lib/harness-state.ts +107 -796
  20. package/apps/harness-dashboard/lib/types.ts +22 -222
  21. package/apps/harness-dashboard/playwright.config.ts +26 -7
  22. package/assets/templates/AGENTS-ko.md.template +1 -1
  23. package/assets/templates/AGENTS.md.template +12 -2
  24. package/assets/templates/HARNESS.md +2 -0
  25. package/assets/templates/company-pipeline-manifest.json +5 -14
  26. package/assets/templates/worker-report.md.template +38 -0
  27. package/bin/init.js +4 -3
  28. package/commands/goal.md +4 -2
  29. package/commands/hot-fix.md +1 -1
  30. package/commands/submission.md +1 -1
  31. package/package.json +1 -1
  32. package/scripts/conductor-tick.sh +12 -0
  33. package/scripts/harness-agenda.sh +105 -0
  34. package/scripts/harness-company-block.sh +89 -0
  35. package/scripts/harness-company-complete.sh +17 -8
  36. package/scripts/harness-company-cycle.sh +54 -0
  37. package/scripts/harness-progress-set.sh +8 -2
  38. package/scripts/harness-stop.sh +76 -0
  39. package/scripts/harness-wake-control.sh +151 -0
  40. package/scripts/harness-wake.sh +5 -0
  41. package/apps/harness-dashboard/components/Drawer.tsx +0 -146
  42. package/apps/harness-dashboard/components/Header.tsx +0 -40
  43. package/apps/harness-dashboard/components/MissionTimeline.tsx +0 -214
  44. package/apps/harness-dashboard/components/OrgTree.tsx +0 -459
  45. package/apps/harness-dashboard/components/drawer/AgentLogTab.tsx +0 -56
  46. package/apps/harness-dashboard/components/drawer/DesignPreviewTab.tsx +0 -160
  47. package/apps/harness-dashboard/components/drawer/GotchasTab.tsx +0 -89
  48. package/apps/harness-dashboard/components/drawer/HypothesisTab.tsx +0 -42
  49. package/apps/harness-dashboard/components/drawer/IncidentsTab.tsx +0 -50
  50. package/apps/harness-dashboard/components/drawer/MissionDocTab.tsx +0 -96
  51. package/apps/harness-dashboard/components/drawer/MissionFlowTab.tsx +0 -576
  52. package/apps/harness-dashboard/components/drawer/OwnerHistoryTab.tsx +0 -160
  53. package/apps/harness-dashboard/components/drawer/RoomMetricsTab.tsx +0 -90
  54. package/apps/harness-dashboard/components/drawer/TracksTab.tsx +0 -47
  55. package/apps/harness-dashboard/components/three/ArchiveBoxes.tsx +0 -95
  56. package/apps/harness-dashboard/components/three/DeptBoard.tsx +0 -175
  57. package/apps/harness-dashboard/components/three/Floor3D.tsx +0 -404
  58. package/apps/harness-dashboard/components/three/GoalCard3D.tsx +0 -123
  59. package/apps/harness-dashboard/components/three/HypothesisCards.tsx +0 -71
  60. package/apps/harness-dashboard/components/three/MeetingWhiteboard.tsx +0 -98
  61. package/apps/harness-dashboard/components/three/Minifig3D.tsx +0 -391
  62. package/apps/harness-dashboard/components/three/OpsSiren.tsx +0 -99
  63. package/apps/harness-dashboard/components/three/Stage3D.tsx +0 -104
  64. package/apps/harness-dashboard/components/three/TrackRibbons.tsx +0 -81
  65. package/apps/harness-dashboard/lib/__tests__/i18n.test.ts +0 -16
  66. package/apps/harness-dashboard/lib/__tests__/iso.test.ts +0 -42
  67. package/apps/harness-dashboard/lib/__tests__/state-mapping.test.ts +0 -183
  68. package/apps/harness-dashboard/lib/agent-roster.ts +0 -42
  69. package/apps/harness-dashboard/lib/furniture.ts +0 -165
  70. package/apps/harness-dashboard/lib/i18n.ts +0 -26
  71. package/apps/harness-dashboard/lib/iso.ts +0 -150
  72. package/apps/harness-dashboard/lib/markdown.tsx +0 -306
  73. package/apps/harness-dashboard/lib/path-planning.ts +0 -72
  74. 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("&", "&amp;")
11
- .replaceAll("<", "&lt;")
12
- .replaceAll(">", "&gt;")
13
- .replaceAll('"', "&quot;");
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
- }