@walwal-harness/cli 7.1.40 → 7.1.42
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/metrics/route.ts +17 -0
- package/apps/harness-dashboard/app/api/wake/route.ts +41 -0
- package/apps/harness-dashboard/app/brick.css +482 -0
- package/apps/harness-dashboard/app/layout.tsx +13 -0
- package/apps/harness-dashboard/app/page.tsx +2 -2
- package/apps/harness-dashboard/components/Scene.tsx +690 -36
- package/apps/harness-dashboard/components/brick/BrickDashboard.tsx +149 -0
- package/apps/harness-dashboard/components/brick/reports.tsx +291 -0
- package/apps/harness-dashboard/components/brick/tweaks.tsx +738 -0
- package/apps/harness-dashboard/components/brick/ui.tsx +463 -0
- package/apps/harness-dashboard/components/brick/views-graph.tsx +305 -0
- package/apps/harness-dashboard/components/brick/views-grid.tsx +543 -0
- package/apps/harness-dashboard/components/brick/views-timeline.tsx +238 -0
- 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/brick/adapter.ts +423 -0
- package/apps/harness-dashboard/lib/brick/contract.ts +247 -0
- package/apps/harness-dashboard/lib/harness-state.ts +107 -796
- package/apps/harness-dashboard/lib/metrics/sampler.ts +545 -0
- 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/scripts/harness-worker-dispatch.sh +31 -7
- 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
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/* =============================================================
|
|
4
|
+
walwal-harness · BRICK OFFICE — App shell
|
|
5
|
+
Ported from design_handoff_harness_dashboard/Harness Dashboard.html
|
|
6
|
+
App() (lines ~395-437) + the view switch.
|
|
7
|
+
|
|
8
|
+
The mockup drove its state from a `useHarness(motion)` simulation;
|
|
9
|
+
here we drive it from the REAL SSE feed (useHarnessStream) mapped
|
|
10
|
+
through toContractState(snapshot, connected, nowMs). The shell ticks
|
|
11
|
+
a `nowMs` clock ~1/s so "ago"/heartbeat/stale read live.
|
|
12
|
+
|
|
13
|
+
The mockup set data-tone / data-density on <body>; in the Next app the
|
|
14
|
+
shell is a component, so those attributes (and the .brick scope class)
|
|
15
|
+
live on the shell root <div>.
|
|
16
|
+
|
|
17
|
+
brick.css is imported at app/layout level (see app/layout.tsx), matching
|
|
18
|
+
how Scene's CSS is loaded via globals.css.
|
|
19
|
+
============================================================= */
|
|
20
|
+
|
|
21
|
+
import React, { useEffect, useState } from "react";
|
|
22
|
+
|
|
23
|
+
import type { HarnessSnapshot } from "../../lib/types";
|
|
24
|
+
import { useHarnessStream } from "../../hooks/useHarnessStream";
|
|
25
|
+
import { toContractState } from "../../lib/brick/adapter";
|
|
26
|
+
import type { AgentRole, DocTarget } from "../../lib/brick/contract";
|
|
27
|
+
import type { MetricsSample } from "../../lib/metrics/sampler";
|
|
28
|
+
|
|
29
|
+
import { Header, Sidebar, type BrickView } from "./ui";
|
|
30
|
+
import { GridView } from "./views-grid";
|
|
31
|
+
import { GraphView } from "./views-graph";
|
|
32
|
+
import { TimelineView } from "./views-timeline";
|
|
33
|
+
import { DocViewer } from "./reports";
|
|
34
|
+
import { BrickTweaks, useTweaks } from "./tweaks";
|
|
35
|
+
|
|
36
|
+
export interface BrickDashboardProps {
|
|
37
|
+
/** Server-rendered initial snapshot, hydrated then kept fresh over SSE. */
|
|
38
|
+
initialSnapshot: HarnessSnapshot;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function BrickDashboard({
|
|
42
|
+
initialSnapshot,
|
|
43
|
+
}: BrickDashboardProps): React.JSX.Element {
|
|
44
|
+
const { snapshot, connectionState } = useHarnessStream(initialSnapshot);
|
|
45
|
+
|
|
46
|
+
// tweaks (agent layout / density / live motion / surface) — persisted.
|
|
47
|
+
const [t, setTweak] = useTweaks();
|
|
48
|
+
|
|
49
|
+
// top-level view + chrome state (mirrors the mockup App() useState block).
|
|
50
|
+
const [view, setView] = useState<BrickView>("grid");
|
|
51
|
+
const [collapsed, setCollapsed] = useState(false);
|
|
52
|
+
const [sel, setSel] = useState<AgentRole>("cto");
|
|
53
|
+
const [doc, setDoc] = useState<DocTarget | null>(null);
|
|
54
|
+
|
|
55
|
+
// ~1/s clock so "ago"/heartbeat/stale derive against a live `now`.
|
|
56
|
+
const [nowMs, setNowMs] = useState<number>(() => Date.now());
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
const id = window.setInterval(() => setNowMs(Date.now()), 1000);
|
|
59
|
+
return () => window.clearInterval(id);
|
|
60
|
+
}, []);
|
|
61
|
+
|
|
62
|
+
// REAL telemetry poll (read-only): /api/metrics every ~5s. On any failure we
|
|
63
|
+
// keep the last good sample (or null), and the honest "—" placeholders stand.
|
|
64
|
+
const [metrics, setMetrics] = useState<MetricsSample | null>(null);
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
let cancelled = false;
|
|
67
|
+
const poll = async (): Promise<void> => {
|
|
68
|
+
try {
|
|
69
|
+
const res = await fetch("/api/metrics", { cache: "no-store" });
|
|
70
|
+
if (!res.ok) return;
|
|
71
|
+
const sample = (await res.json()) as MetricsSample;
|
|
72
|
+
if (!cancelled && sample && typeof sample === "object") {
|
|
73
|
+
setMetrics(sample);
|
|
74
|
+
}
|
|
75
|
+
} catch {
|
|
76
|
+
/* network/parse error — keep last sample; never fabricate */
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
void poll();
|
|
80
|
+
const id = window.setInterval(() => void poll(), 5000);
|
|
81
|
+
return () => {
|
|
82
|
+
cancelled = true;
|
|
83
|
+
window.clearInterval(id);
|
|
84
|
+
};
|
|
85
|
+
}, []);
|
|
86
|
+
|
|
87
|
+
// build the single Contract state object every view renders from.
|
|
88
|
+
const connected = connectionState === "open";
|
|
89
|
+
const s = toContractState(snapshot, connected, nowMs);
|
|
90
|
+
|
|
91
|
+
// Override the adapter's honest nulls with REAL sampled telemetry where it
|
|
92
|
+
// exists. Anything still null stays null -> the views render "—".
|
|
93
|
+
if (metrics) {
|
|
94
|
+
s.metrics.tokensPerMin = metrics.tokensPerMin;
|
|
95
|
+
s.metrics.costToday = metrics.costToday;
|
|
96
|
+
s.metrics.cpu = metrics.cpu;
|
|
97
|
+
s.metrics.contextPct = metrics.contextPct;
|
|
98
|
+
s.metrics.costEstimated = metrics.costEstimated;
|
|
99
|
+
s.mtrend = metrics.mtrend ?? [];
|
|
100
|
+
s.ctrend = metrics.ctrend ?? [];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const openDoc = (target: DocTarget): void => setDoc(target);
|
|
104
|
+
const closeDoc = (): void => setDoc(null);
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<div className="brick" data-tone={t.surface} data-density={t.density}>
|
|
108
|
+
<div className="app">
|
|
109
|
+
<Header metrics={s.metrics} view={view} setView={setView} alerts={s.alerts} />
|
|
110
|
+
<div className="app-body">
|
|
111
|
+
<Sidebar
|
|
112
|
+
collapsed={collapsed}
|
|
113
|
+
onToggle={() => setCollapsed((c) => !c)}
|
|
114
|
+
missions={snapshot.missions}
|
|
115
|
+
ownerHistory={snapshot.ownerHistory}
|
|
116
|
+
/>
|
|
117
|
+
<div className="app-main">
|
|
118
|
+
{view === "grid" && (
|
|
119
|
+
<div className="viewscroll">
|
|
120
|
+
<GridView s={s} layout={t.agentLayout} openDoc={openDoc} />
|
|
121
|
+
</div>
|
|
122
|
+
)}
|
|
123
|
+
{view === "graph" && (
|
|
124
|
+
<div className="viewscroll viewscroll-graph">
|
|
125
|
+
<GraphView s={s} sel={sel} setSel={setSel} openDoc={openDoc} />
|
|
126
|
+
</div>
|
|
127
|
+
)}
|
|
128
|
+
{view === "timeline" && (
|
|
129
|
+
<div className="viewscroll">
|
|
130
|
+
<TimelineView s={s} openDoc={openDoc} />
|
|
131
|
+
</div>
|
|
132
|
+
)}
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<DocViewer
|
|
137
|
+
target={doc}
|
|
138
|
+
state={s}
|
|
139
|
+
onClose={closeDoc}
|
|
140
|
+
onRetarget={openDoc}
|
|
141
|
+
/>
|
|
142
|
+
|
|
143
|
+
<BrickTweaks values={t} setTweak={setTweak} />
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export default BrickDashboard;
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/* =============================================================
|
|
4
|
+
walwal-harness · BRICK OFFICE — markdown renderer + DocViewer
|
|
5
|
+
Ported from design_handoff_harness_dashboard/harness/reports.jsx.
|
|
6
|
+
|
|
7
|
+
The mockup hard-coded its report bodies in a REPORTS table; here
|
|
8
|
+
buildReport returns REAL markdown supplied by the adapter
|
|
9
|
+
(agent.report / worker.report), with leading YAML frontmatter
|
|
10
|
+
stripped before rendering. Everything else (classNames, CSS,
|
|
11
|
+
markdown grammar, DocViewer layout) is preserved verbatim.
|
|
12
|
+
|
|
13
|
+
Exports: MD, DocViewer, buildReport
|
|
14
|
+
============================================================= */
|
|
15
|
+
|
|
16
|
+
import React, { useEffect, type CSSProperties, type ReactNode } from "react";
|
|
17
|
+
import {
|
|
18
|
+
fmt,
|
|
19
|
+
statusColor,
|
|
20
|
+
type ContractState,
|
|
21
|
+
type ContractAgent,
|
|
22
|
+
type ContractWorker,
|
|
23
|
+
type DocTarget,
|
|
24
|
+
} from "../../lib/brick/contract";
|
|
25
|
+
import { stripFrontmatter } from "../../lib/brick/adapter";
|
|
26
|
+
import { StatusDot } from "./ui";
|
|
27
|
+
|
|
28
|
+
/* ---- tiny markdown renderer ------------------------------------------- */
|
|
29
|
+
|
|
30
|
+
/** Inline pass: splits on `code` and **bold**, returns React children. */
|
|
31
|
+
function inline(text: string, key: string | number): ReactNode[] {
|
|
32
|
+
const out: ReactNode[] = [];
|
|
33
|
+
let rest = text;
|
|
34
|
+
let i = 0;
|
|
35
|
+
const re = /(`[^`]+`|\*\*[^*]+\*\*)/;
|
|
36
|
+
while (rest.length) {
|
|
37
|
+
const m = rest.match(re);
|
|
38
|
+
if (!m || m.index === undefined) {
|
|
39
|
+
out.push(rest);
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
if (m.index > 0) out.push(rest.slice(0, m.index));
|
|
43
|
+
const tok = m[0];
|
|
44
|
+
if (tok.startsWith("`")) {
|
|
45
|
+
out.push(<code key={key + "-" + i}>{tok.slice(1, -1)}</code>);
|
|
46
|
+
} else {
|
|
47
|
+
out.push(<b key={key + "-" + i}>{tok.slice(2, -2)}</b>);
|
|
48
|
+
}
|
|
49
|
+
rest = rest.slice(m.index + tok.length);
|
|
50
|
+
i++;
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface ListProps {
|
|
56
|
+
ordered: boolean;
|
|
57
|
+
items: string[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function List({ ordered, items }: ListProps): React.JSX.Element {
|
|
61
|
+
const Tag = ordered ? "ol" : "ul";
|
|
62
|
+
return (
|
|
63
|
+
<Tag>
|
|
64
|
+
{items.map((it, i) => (
|
|
65
|
+
<li key={i}>{inline(it, "li" + i)}</li>
|
|
66
|
+
))}
|
|
67
|
+
</Tag>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface MDProps {
|
|
72
|
+
src: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Block-level markdown renderer. Supports #/##/###, -/1. lists, `code`,
|
|
77
|
+
* **bold**, > blockquote and --- rules (same grammar as the mockup).
|
|
78
|
+
*/
|
|
79
|
+
export function MD({ src }: MDProps): React.JSX.Element {
|
|
80
|
+
const lines = src.split("\n");
|
|
81
|
+
const blocks: ReactNode[] = [];
|
|
82
|
+
// accumulate list items so consecutive bullets/numbers merge into one list
|
|
83
|
+
let listOrdered: boolean | null = null;
|
|
84
|
+
let listItems: string[] = [];
|
|
85
|
+
let listKey = 0;
|
|
86
|
+
const flush = () => {
|
|
87
|
+
if (listOrdered !== null) {
|
|
88
|
+
blocks.push(
|
|
89
|
+
<List key={"list-" + listKey} ordered={listOrdered} items={listItems} />
|
|
90
|
+
);
|
|
91
|
+
listOrdered = null;
|
|
92
|
+
listItems = [];
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
lines.forEach((ln, idx) => {
|
|
97
|
+
const t = ln.trimEnd();
|
|
98
|
+
if (!t.trim()) {
|
|
99
|
+
flush();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (t.startsWith("### ")) {
|
|
103
|
+
flush();
|
|
104
|
+
blocks.push(<h4 key={idx}>{inline(t.slice(4), idx)}</h4>);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (t.startsWith("## ")) {
|
|
108
|
+
flush();
|
|
109
|
+
blocks.push(<h3 key={idx}>{inline(t.slice(3), idx)}</h3>);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (t.startsWith("# ")) {
|
|
113
|
+
flush();
|
|
114
|
+
blocks.push(<h2 key={idx}>{inline(t.slice(2), idx)}</h2>);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (t === "---") {
|
|
118
|
+
flush();
|
|
119
|
+
blocks.push(<hr key={idx} />);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (t.startsWith("> ")) {
|
|
123
|
+
flush();
|
|
124
|
+
blocks.push(<blockquote key={idx}>{inline(t.slice(2), idx)}</blockquote>);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const oli = t.match(/^(\d+)\.\s+(.*)/);
|
|
128
|
+
const uli = t.match(/^[-*]\s+(.*)/);
|
|
129
|
+
if (oli || uli) {
|
|
130
|
+
const ordered = !!oli;
|
|
131
|
+
if (listOrdered === null || listOrdered !== ordered) {
|
|
132
|
+
flush();
|
|
133
|
+
listOrdered = ordered;
|
|
134
|
+
listKey = idx;
|
|
135
|
+
}
|
|
136
|
+
listItems.push(oli ? oli[2] : uli![1]);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
flush();
|
|
140
|
+
blocks.push(<p key={idx}>{inline(t, idx)}</p>);
|
|
141
|
+
});
|
|
142
|
+
flush();
|
|
143
|
+
return <div className="md">{blocks}</div>;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* ---- report assembly (REAL markdown, not the mockup table) ------------ */
|
|
147
|
+
|
|
148
|
+
export interface BuiltReport {
|
|
149
|
+
/** Title shown in the meta block (agent work string / worker name). */
|
|
150
|
+
title: string;
|
|
151
|
+
/** The owning agent of this document. */
|
|
152
|
+
owner: ContractAgent;
|
|
153
|
+
/** Short document-kind label. */
|
|
154
|
+
kind: string;
|
|
155
|
+
/** Status string for the StatusDot + color. */
|
|
156
|
+
status: string;
|
|
157
|
+
/** Markdown body, frontmatter already stripped. */
|
|
158
|
+
body: string;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const NO_SOURCE = "_no report source — this agent has not produced a document yet._";
|
|
162
|
+
const NO_WORKER_SOURCE = "_no report source — this worker has not produced a brief yet._";
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Build the document to render for a DocTarget, pulling REAL markdown from
|
|
166
|
+
* the adapter-provided report strings. Agent reports are stripped of leading
|
|
167
|
+
* YAML frontmatter at render time (per spec); worker reports are already
|
|
168
|
+
* stripped by the adapter but stripping again is harmless and keeps the
|
|
169
|
+
* render path uniform.
|
|
170
|
+
*/
|
|
171
|
+
export function buildReport(target: DocTarget, state: ContractState): BuiltReport {
|
|
172
|
+
const agent =
|
|
173
|
+
state.agents.find((a) => a.id === target.agent) ?? state.agents[0];
|
|
174
|
+
|
|
175
|
+
if (target.type === "worker") {
|
|
176
|
+
const worker = target.worker;
|
|
177
|
+
const body = stripFrontmatter(worker.report) || NO_WORKER_SOURCE;
|
|
178
|
+
return {
|
|
179
|
+
title: worker.name,
|
|
180
|
+
owner: agent,
|
|
181
|
+
kind: "Worker Brief",
|
|
182
|
+
status: worker.status,
|
|
183
|
+
body,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const body = stripFrontmatter(agent.report) || NO_SOURCE;
|
|
188
|
+
return {
|
|
189
|
+
title: agent.work,
|
|
190
|
+
owner: agent,
|
|
191
|
+
kind: "CXX Document",
|
|
192
|
+
status: agent.status,
|
|
193
|
+
body,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/* ---- DocViewer drawer -------------------------------------------------- */
|
|
198
|
+
|
|
199
|
+
export interface DocViewerProps {
|
|
200
|
+
/** The document to show, or null to render nothing. */
|
|
201
|
+
target: DocTarget | null;
|
|
202
|
+
/** The live contract state. */
|
|
203
|
+
state: ContractState;
|
|
204
|
+
/** Close the drawer. */
|
|
205
|
+
onClose: () => void;
|
|
206
|
+
/** Re-point the drawer at another document (e.g. a spawned worker). */
|
|
207
|
+
onRetarget: (target: DocTarget) => void;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Right-hand document drawer with the agent meta block + markdown body. */
|
|
211
|
+
export function DocViewer({
|
|
212
|
+
target,
|
|
213
|
+
state,
|
|
214
|
+
onClose,
|
|
215
|
+
onRetarget,
|
|
216
|
+
}: DocViewerProps): React.JSX.Element | null {
|
|
217
|
+
useEffect(() => {
|
|
218
|
+
const h = (e: KeyboardEvent) => {
|
|
219
|
+
if (e.key === "Escape") onClose();
|
|
220
|
+
};
|
|
221
|
+
window.addEventListener("keydown", h);
|
|
222
|
+
return () => window.removeEventListener("keydown", h);
|
|
223
|
+
}, [onClose]);
|
|
224
|
+
|
|
225
|
+
if (!target) return null;
|
|
226
|
+
|
|
227
|
+
const rep = buildReport(target, state);
|
|
228
|
+
const a = rep.owner;
|
|
229
|
+
const mine = state.workers.filter((w) => w.agent === a.id);
|
|
230
|
+
const hueVar = { "--hue": a.hue } as CSSProperties;
|
|
231
|
+
|
|
232
|
+
return (
|
|
233
|
+
<div className="docroot">
|
|
234
|
+
<div className="docback" onClick={onClose} />
|
|
235
|
+
<aside className="docview" style={hueVar}>
|
|
236
|
+
<div className="docview-bar">
|
|
237
|
+
<span className="docview-crumb">
|
|
238
|
+
DOCUMENT VIEWER <span className="dv-sep">·</span>
|
|
239
|
+
<span style={{ color: a.hue }}>{a.name}</span>
|
|
240
|
+
{target.type === "worker" && (
|
|
241
|
+
<>
|
|
242
|
+
<span className="dv-sep">·</span> {target.worker.name}
|
|
243
|
+
</>
|
|
244
|
+
)}
|
|
245
|
+
</span>
|
|
246
|
+
<button className="docview-x" onClick={onClose}>
|
|
247
|
+
esc ✕
|
|
248
|
+
</button>
|
|
249
|
+
</div>
|
|
250
|
+
<div className="docview-scroll">
|
|
251
|
+
<div className="docview-meta">
|
|
252
|
+
<span
|
|
253
|
+
className="glyph glyph-lg"
|
|
254
|
+
style={{ color: a.hue, borderColor: a.hue + "66" }}
|
|
255
|
+
>
|
|
256
|
+
{a.name}
|
|
257
|
+
</span>
|
|
258
|
+
<div className="dvm-id">
|
|
259
|
+
<div className="dvm-kind">{rep.kind}</div>
|
|
260
|
+
<div className="dvm-status" style={{ color: statusColor(rep.status) }}>
|
|
261
|
+
<StatusDot status={rep.status} size={7} />
|
|
262
|
+
{rep.status}
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
265
|
+
<div className="dvm-time">{fmt.time(new Date(state.now))}</div>
|
|
266
|
+
</div>
|
|
267
|
+
|
|
268
|
+
{target.type === "agent" && mine.length > 0 && (
|
|
269
|
+
<div className="dvm-workers">
|
|
270
|
+
<span className="dvm-wlabel">spawned workers</span>
|
|
271
|
+
{mine.map((w: ContractWorker) => (
|
|
272
|
+
<button
|
|
273
|
+
key={w.id}
|
|
274
|
+
className="dvm-wchip"
|
|
275
|
+
onClick={() =>
|
|
276
|
+
onRetarget({ type: "worker", agent: a.id, worker: w })
|
|
277
|
+
}
|
|
278
|
+
>
|
|
279
|
+
<StatusDot status={w.status} size={5} pulse={false} />
|
|
280
|
+
{w.name} <em>{w.progress == null ? "—" : Math.round(w.progress) + "%"}</em>
|
|
281
|
+
</button>
|
|
282
|
+
))}
|
|
283
|
+
</div>
|
|
284
|
+
)}
|
|
285
|
+
|
|
286
|
+
<MD src={rep.body} />
|
|
287
|
+
</div>
|
|
288
|
+
</aside>
|
|
289
|
+
</div>
|
|
290
|
+
);
|
|
291
|
+
}
|