@walwal-harness/cli 7.1.9 → 7.1.13
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/ceo/SKILL.md +4 -4
- package/HR-Resource/cqo/SKILL.md +3 -3
- package/HR-Resource/cto/SKILL.md +4 -4
- package/README.md +25 -7
- package/apps/harness-dashboard/app/globals.css +9 -0
- package/apps/harness-dashboard/components/Drawer.tsx +52 -5
- package/apps/harness-dashboard/components/MissionTimeline.tsx +5 -1
- package/apps/harness-dashboard/components/OrgTree.tsx +7 -4
- package/apps/harness-dashboard/components/Scene.tsx +108 -100
- package/apps/harness-dashboard/components/drawer/MissionFlowTab.tsx +4 -1
- package/apps/harness-dashboard/components/drawer/OwnerHistoryTab.tsx +4 -0
- package/apps/harness-dashboard/lib/harness-state.ts +44 -13
- package/apps/harness-dashboard/lib/types.ts +2 -2
- package/assets/templates/AGENTS-ko.md.template +19 -6
- package/assets/templates/AGENTS.md.template +20 -7
- package/assets/templates/HARNESS.md +75 -199
- package/assets/templates/config.json +2 -2
- package/bin/init.js +11 -11
- package/commands/goal.md +6 -4
- package/commands/hot-fix.md +16 -13
- package/commands/submission.md +28 -0
- package/conventions/ceo.md +3 -3
- package/conventions/shared.md +4 -3
- package/package.json +1 -1
- package/scripts/harness-session-start.sh +1 -1
- package/scripts/harness-stop.sh +1 -1
- package/scripts/harness-user-prompt-submit.sh +12 -4
- package/scripts/harness-worker-evidence-validate.sh +8 -3
- package/scripts/import-agency-agents.js +1 -1
- package/scripts/init-agents-md.sh +17 -2
- package/assets/templates/AGENTS-ko /341/204/207/341/205/251/341/206/250/341/204/211/341/205/241/341/204/207/341/205/251/341/206/253.md" +0 -114
package/HR-Resource/ceo/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: harness-ceo
|
|
3
|
-
description: "CEO/Dispatcher. Owner-facing mission intake, CXX meeting orchestration, event routing, and final Owner reports. Trigger with /goal, /hot-fix, /ceo."
|
|
3
|
+
description: "CEO/Dispatcher. Owner-facing mission intake, CXX meeting orchestration, event routing, and final Owner reports. Trigger with /goal, /submission, /hot-fix, /ceo."
|
|
4
4
|
model: sonnet
|
|
5
5
|
disable-model-invocation: false
|
|
6
6
|
---
|
|
@@ -12,7 +12,7 @@ You are the only direct conversation channel with the Owner.
|
|
|
12
12
|
## Mission Protocol
|
|
13
13
|
|
|
14
14
|
1. Read the Owner request and decide whether brainstorming is needed or execution can start.
|
|
15
|
-
2. Create or update `.harness/documents/{
|
|
15
|
+
2. Create or update `.harness/documents/{goal-or-child-mission}/ceo.md`.
|
|
16
16
|
3. Ask each CXX only mission-relevant questions:
|
|
17
17
|
- COO: planning, market/reference research, hypothesis validation, mission fit.
|
|
18
18
|
- CDO: branding, UI/UX direction, mock selection, design review.
|
|
@@ -28,8 +28,8 @@ You are the only direct conversation channel with the Owner.
|
|
|
28
28
|
- CXX agents do not execute specialist work directly. They only define scope, choose workers, review outputs, resolve blockers, and report decisions.
|
|
29
29
|
- Every mission must use hired specialist workers for research, planning, design production, implementation, QA, ops checks, or any other domain deliverable. Small scope is not an exemption.
|
|
30
30
|
- If a suitable hired worker is absent, invoke the `harness-hiring` skill before the CXX proceeds with that deliverable.
|
|
31
|
-
- CEO must reject CXX reports that contain completed specialist deliverables without matching worker records under `.harness/documents/{
|
|
32
|
-
- Every CXX starts from fresh context and records decisions in `.harness/documents/{
|
|
31
|
+
- CEO must reject CXX reports that contain completed specialist deliverables without matching worker records under `.harness/documents/{goal-or-child-mission}/{owning-cxx}/workers/`.
|
|
32
|
+
- Every CXX starts from fresh context and records decisions in `.harness/documents/{goal-or-child-mission}/{cxx}.md`.
|
|
33
33
|
- Preserve DDD boundaries: domain decisions, application wiring, infrastructure, and quality policy are separate responsibilities.
|
|
34
34
|
- Before CTO/CDO/OPS allocate runnable services, agree with the Owner on a `{xx}000` base port and write it to project `.env` as `HARNESS_BASE_PORT={xx}000`. Mentioning the value in `ceo.md` is not sufficient.
|
|
35
35
|
- After writing `.env`, verify with `grep '^HARNESS_BASE_PORT=' .env` before routing service work.
|
package/HR-Resource/cqo/SKILL.md
CHANGED
|
@@ -12,7 +12,7 @@ Own quality, recurrence prevention, and archive eligibility.
|
|
|
12
12
|
## Workflow
|
|
13
13
|
|
|
14
14
|
1. Read CEO and CTO mission context.
|
|
15
|
-
2. Record decisions in `.harness/documents/{
|
|
15
|
+
2. Record decisions in `.harness/documents/{goal-or-child-mission}/cqo.md`.
|
|
16
16
|
3. Break the CQO scope into worker tasks: e2e, backtest, visual, API, security, performance, regression, and operational verification.
|
|
17
17
|
4. Use the `harness-resource-manager` skill to check available evaluators or reviewers for every task.
|
|
18
18
|
5. Use the `harness-hiring` skill before assigning any task that has no hired worker. Do not complete that task yourself.
|
|
@@ -28,7 +28,7 @@ CQO must not directly execute QA, visual review, security review, performance te
|
|
|
28
28
|
|
|
29
29
|
**CQO does not communicate with dev workers.** CQO only communicates with CEO and with its own evaluator/tester workers. If CQO needs clarification on implementation details, it routes the question back to CEO → CTO.
|
|
30
30
|
|
|
31
|
-
Every evaluator/tester dispatched by CQO must write its report under `.harness/documents/{
|
|
31
|
+
Every evaluator/tester dispatched by CQO must write its report under `.harness/documents/{goal-or-child-mission}/cqo/workers/{worker-name}.md`.
|
|
32
32
|
|
|
33
33
|
Required output sections in `cqo.md`:
|
|
34
34
|
|
|
@@ -40,7 +40,7 @@ Required output sections in `cqo.md`:
|
|
|
40
40
|
|
|
41
41
|
## Worker Report Note Requirement
|
|
42
42
|
|
|
43
|
-
Every CQO evaluator/tester brief must require the worker to append this English block to the bottom of `.harness/documents/{
|
|
43
|
+
Every CQO evaluator/tester brief must require the worker to append this English block to the bottom of `.harness/documents/{goal-or-child-mission}/cqo/workers/{worker-name}.md`:
|
|
44
44
|
|
|
45
45
|
```
|
|
46
46
|
## Implementation Notes
|
package/HR-Resource/cto/SKILL.md
CHANGED
|
@@ -12,7 +12,7 @@ Own engineering execution for the mission.
|
|
|
12
12
|
## Workflow
|
|
13
13
|
|
|
14
14
|
1. Read CEO, COO, and CDO mission documents.
|
|
15
|
-
2. Record decisions in `.harness/documents/{
|
|
15
|
+
2. Record decisions in `.harness/documents/{goal-or-child-mission}/cto.md`. **This file must be created before any worker is dispatched.**
|
|
16
16
|
3. Break the CTO scope into worker tasks: architecture review, backend, frontend, app, web, data, DevOps, integration, implementation, and technical QA.
|
|
17
17
|
4. Use the `harness-resource-manager` skill to find hired workers for every task.
|
|
18
18
|
5. Use the `harness-hiring` skill before assigning any missing specialty. Do not complete that task yourself.
|
|
@@ -26,9 +26,9 @@ Own engineering execution for the mission.
|
|
|
26
26
|
|
|
27
27
|
CTO must not directly write code, create build scripts, choose detailed implementation content, run technical QA as the evaluator, or produce final implementation artifacts. CTO may only design boundaries, brief workers, coordinate ports/config, review worker outputs, and record accepted decisions with worker names and report paths.
|
|
28
28
|
|
|
29
|
-
**cto.md is a prerequisite gate.** No worker may be dispatched before `cto.md` exists. A mission where workers appear in `.harness/documents/{
|
|
29
|
+
**cto.md is a prerequisite gate.** No worker may be dispatched before `cto.md` exists. A mission where workers appear in `.harness/documents/{goal-or-child-mission}/cto/workers/` but no `cto.md` exists is a protocol violation — CEO bypassed CTO.
|
|
30
30
|
|
|
31
|
-
Every worker dispatched by CTO must be listed in the Worker Evidence Manifest section of `cto.md` with their report path and status. The report path must be `.harness/documents/{
|
|
31
|
+
Every worker dispatched by CTO must be listed in the Worker Evidence Manifest section of `cto.md` with their report path and status. The report path must be `.harness/documents/{goal-or-child-mission}/cto/workers/{worker-name}.md`. Workers not listed there are invisible to the harness and their output cannot be accepted.
|
|
32
32
|
|
|
33
33
|
Required output sections in `cto.md`:
|
|
34
34
|
|
|
@@ -41,7 +41,7 @@ Required output sections in `cto.md`:
|
|
|
41
41
|
|
|
42
42
|
## Worker Report Note Requirement
|
|
43
43
|
|
|
44
|
-
Every CTO worker brief must require the worker to append this English block to the bottom of `.harness/documents/{
|
|
44
|
+
Every CTO worker brief must require the worker to append this English block to the bottom of `.harness/documents/{goal-or-child-mission}/cto/workers/{worker-name}.md`:
|
|
45
45
|
|
|
46
46
|
```
|
|
47
47
|
## Implementation Notes
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ One project = one company. The Owner speaks only to the CEO. The CEO speaks only
|
|
|
10
10
|
|
|
11
11
|
```
|
|
12
12
|
Owner
|
|
13
|
-
└─ /goal · /hot-fix
|
|
13
|
+
└─ /goal · /submission · /hot-fix
|
|
14
14
|
└─ harness-ceo orchestrator — Owner's only contact
|
|
15
15
|
├─ harness-coo research, hypothesis, service direction
|
|
16
16
|
├─ harness-cdo branding, UI/UX, design review
|
|
@@ -48,6 +48,7 @@ What `init` installs:
|
|
|
48
48
|
| Path | Contents |
|
|
49
49
|
|---|---|
|
|
50
50
|
| `.claude/commands/goal.md` | `/goal` Owner command |
|
|
51
|
+
| `.claude/commands/submission.md` | `/submission` Owner command |
|
|
51
52
|
| `.claude/commands/hot-fix.md` | `/hot-fix` Owner command |
|
|
52
53
|
| `.claude/skills/harness-{ceo,coo,cdo,cto,cqo,ops}/` | CXX agent skills |
|
|
53
54
|
| `.harness/shared/HR-Resource/` | Hireable worker skill pool |
|
|
@@ -79,6 +80,16 @@ Owner /hot-fix → CEO → CTO → [dev workers] → CQO → [evaluator workers]
|
|
|
79
80
|
|
|
80
81
|
**Complete when:** `cto.md` + `cqo.md` + at least one `.harness/gotchas/` or `.harness/conventions/` entry exist.
|
|
81
82
|
|
|
83
|
+
### Submission
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
Owner /submission → CEO → [relevant CXX] → [workers] → [convention updates]
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
1. CEO locates the active `.harness/documents/goal-{index}-{name}/`.
|
|
90
|
+
2. CEO creates `.harness/documents/goal-{index}-{name}/submission-{index}-{name}/`.
|
|
91
|
+
3. Relevant CXX update `.harness/conventions/` when the new requirement changes durable project rules.
|
|
92
|
+
|
|
82
93
|
---
|
|
83
94
|
|
|
84
95
|
## Hard Rules
|
|
@@ -93,7 +104,7 @@ Owner /hot-fix → CEO → CTO → [dev workers] → CQO → [evaluator workers]
|
|
|
93
104
|
| 6 | CEO routes only to CXX — never directly to workers |
|
|
94
105
|
| 7 | No CXX self-execution — deliverables without matching worker records are rejected |
|
|
95
106
|
| 8 | No verdict without worker evidence — CQO self-inspection is not valid |
|
|
96
|
-
| 9 | Hierarchical worker ownership — worker reports live under `.harness/documents/{mission}/{owning-cxx}/workers/` |
|
|
107
|
+
| 9 | Hierarchical worker ownership — worker reports live under `.harness/documents/{goal-or-child-mission}/{owning-cxx}/workers/` |
|
|
97
108
|
| 10 | Implementation Notes required — `ceo.md`, every `{cxx}.md`, and every worker report must end with an English `## Implementation Notes` section |
|
|
98
109
|
|
|
99
110
|
### Implementation Notes Format
|
|
@@ -169,9 +180,10 @@ npx walwal-harness init --force --project-root /path/to/project
|
|
|
169
180
|
|
|
170
181
|
Features:
|
|
171
182
|
|
|
172
|
-
- **
|
|
173
|
-
- **
|
|
174
|
-
- **Mission
|
|
183
|
+
- **Split Workspace** — full-viewport left/right panes keep Org Tree + Mission Timeline beside the selected mission detail; both panes scroll independently with hidden scrollbars, and the detail pane resets to top on selection changes
|
|
184
|
+
- **Org Tree** — live status of Owner → CEO → CXX → Workers hierarchy, with worker cards grouped under their owning CXX
|
|
185
|
+
- **Mission Timeline** — clickable history of goal/submission/hot-fix missions showing the full dispatch chain
|
|
186
|
+
- **Mission Flow tab** — per-mission flow: Owner prompt → CEO routing → CXX → owner-specific worker evidence → CQO verdict
|
|
175
187
|
- **History tab** — mission-specific Owner request (from CEO summary + closest progress.log match)
|
|
176
188
|
- **Gotchas tab** — searchable `.harness/gotchas/*.md` knowledge base, click to read full markdown
|
|
177
189
|
- **Document tab** — per-CXX markdown doc viewer
|
|
@@ -182,8 +194,10 @@ Features:
|
|
|
182
194
|
|
|
183
195
|
| Path | Role |
|
|
184
196
|
|---|---|
|
|
185
|
-
| `.harness/documents/{
|
|
186
|
-
| `.harness/documents/{
|
|
197
|
+
| `.harness/documents/goal-{index}-{name}/` | Goal-level CXX decisions and worker reports |
|
|
198
|
+
| `.harness/documents/goal-{index}-{name}/submission-{index}-{name}/` | Additional requirement under the active goal |
|
|
199
|
+
| `.harness/documents/goal-{index}-{name}/hotfix-{index}-{name}/` | Emergency fix under the active goal |
|
|
200
|
+
| `.harness/documents/{goal-or-child-mission}/{cxx}/workers/` | Worker reports owned by that CXX |
|
|
187
201
|
| `.harness/conventions/` | Durable rules (CQO writes, survives missions) |
|
|
188
202
|
| `.harness/gotchas/` | Recurrence-prevention records (CQO registers per hot-fix) |
|
|
189
203
|
| `.harness/shared/HR-Resource/` | Hireable worker skill pool |
|
|
@@ -208,6 +222,10 @@ harness-hiring → register and onboard worker
|
|
|
208
222
|
|
|
209
223
|
| Version | Summary |
|
|
210
224
|
|---|---|
|
|
225
|
+
| 7.1.13 | Dashboard: full-viewport split workspace, hidden pane scrollbars, and detail scroll reset on selection |
|
|
226
|
+
| 7.1.12 | Dashboard: split workspace panes now scroll independently |
|
|
227
|
+
| 7.1.11 | Dashboard: persistent 50/50 split workspace and CXX-owned worker grouping in the org tree |
|
|
228
|
+
| 7.1.10 | Adds `/submission` and nested goal/submission/hot-fix mission history |
|
|
211
229
|
| 7.1.9 | Dashboard is packaged with npm and auto-syncs isolated cache by package version; README documents dashboard port/reinstall and update/migrate commands |
|
|
212
230
|
| 7.1.8 | HR-Resource: replace China-platform-specific workers with global equivalents; refine cross-border/video/livestream skills |
|
|
213
231
|
| 7.1.7 | Implementation Notes mandatory in all CXX docs and worker reports; harness-worker-evidence-validate.sh |
|
|
@@ -15,3 +15,12 @@ body {
|
|
|
15
15
|
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
|
|
16
16
|
sans-serif;
|
|
17
17
|
}
|
|
18
|
+
|
|
19
|
+
.scrollbar-hidden {
|
|
20
|
+
scrollbar-width: none;
|
|
21
|
+
-ms-overflow-style: none;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.scrollbar-hidden::-webkit-scrollbar {
|
|
25
|
+
display: none;
|
|
26
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { useEffect } from "react";
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
3
|
|
|
4
4
|
export type DrawerTab =
|
|
5
5
|
| "mission-flow" // Mission document hierarchy (PRIMARY)
|
|
@@ -15,6 +15,8 @@ interface DrawerProps {
|
|
|
15
15
|
onClose: () => void;
|
|
16
16
|
onTabChange: (tab: DrawerTab) => void;
|
|
17
17
|
children: React.ReactNode;
|
|
18
|
+
mode?: "overlay" | "inline";
|
|
19
|
+
scrollResetKey?: string;
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
const TABS: Array<{ id: DrawerTab; label: string }> = [
|
|
@@ -25,15 +27,60 @@ const TABS: Array<{ id: DrawerTab; label: string }> = [
|
|
|
25
27
|
{ id: "logs", label: "Agent Log" },
|
|
26
28
|
];
|
|
27
29
|
|
|
28
|
-
export function Drawer({ open, tab, title, onClose, onTabChange, children }: DrawerProps) {
|
|
30
|
+
export function Drawer({ open, tab, title, onClose, onTabChange, children, mode = "overlay", scrollResetKey }: DrawerProps) {
|
|
31
|
+
const contentRef = useRef<HTMLDivElement | null>(null);
|
|
32
|
+
|
|
29
33
|
useEffect(() => {
|
|
30
|
-
if (!open) return;
|
|
34
|
+
if (!open || mode === "inline") return;
|
|
31
35
|
const onKey = (e: KeyboardEvent) => {
|
|
32
36
|
if (e.key === "Escape") onClose();
|
|
33
37
|
};
|
|
34
38
|
window.addEventListener("keydown", onKey);
|
|
35
39
|
return () => window.removeEventListener("keydown", onKey);
|
|
36
|
-
}, [open, onClose]);
|
|
40
|
+
}, [open, onClose, mode]);
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
contentRef.current?.scrollTo({ top: 0, left: 0 });
|
|
44
|
+
}, [scrollResetKey]);
|
|
45
|
+
|
|
46
|
+
if (mode === "inline") {
|
|
47
|
+
return (
|
|
48
|
+
<aside
|
|
49
|
+
role="complementary"
|
|
50
|
+
aria-label="Detail panel"
|
|
51
|
+
data-testid="drawer"
|
|
52
|
+
data-open="true"
|
|
53
|
+
className="h-full min-h-0 rounded-md border border-brick-wall bg-brick-bg shadow-2xl flex flex-col overflow-hidden"
|
|
54
|
+
>
|
|
55
|
+
<header className="flex items-center justify-between border-b border-brick-wall px-4 py-3">
|
|
56
|
+
<h2 className="text-sm font-mono uppercase tracking-widest text-gray-200 truncate">
|
|
57
|
+
{title}
|
|
58
|
+
</h2>
|
|
59
|
+
</header>
|
|
60
|
+
<nav className="flex flex-wrap border-b border-brick-wall text-[11px] font-mono">
|
|
61
|
+
{TABS.map((t) => (
|
|
62
|
+
<button
|
|
63
|
+
key={t.id}
|
|
64
|
+
type="button"
|
|
65
|
+
data-testid={`drawer-tab-${t.id}`}
|
|
66
|
+
data-active={t.id === tab}
|
|
67
|
+
onClick={() => onTabChange(t.id)}
|
|
68
|
+
className={`min-w-[33%] flex-1 px-2 py-2 transition-colors ${
|
|
69
|
+
t.id === tab
|
|
70
|
+
? "bg-brick-wall/50 text-gray-100 border-b-2 border-aura-typing"
|
|
71
|
+
: "text-gray-500 hover:text-gray-200"
|
|
72
|
+
}`}
|
|
73
|
+
>
|
|
74
|
+
{t.label}
|
|
75
|
+
</button>
|
|
76
|
+
))}
|
|
77
|
+
</nav>
|
|
78
|
+
<div ref={contentRef} className="scrollbar-hidden flex-1 overflow-y-auto px-4 py-3 text-xs text-gray-200 font-mono">
|
|
79
|
+
{children}
|
|
80
|
+
</div>
|
|
81
|
+
</aside>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
37
84
|
|
|
38
85
|
return (
|
|
39
86
|
<>
|
|
@@ -88,7 +135,7 @@ export function Drawer({ open, tab, title, onClose, onTabChange, children }: Dra
|
|
|
88
135
|
</button>
|
|
89
136
|
))}
|
|
90
137
|
</nav>
|
|
91
|
-
<div className="flex-1 overflow-y-auto px-4 py-3 text-xs text-gray-200 font-mono">
|
|
138
|
+
<div ref={contentRef} className="scrollbar-hidden flex-1 overflow-y-auto px-4 py-3 text-xs text-gray-200 font-mono">
|
|
92
139
|
{children}
|
|
93
140
|
</div>
|
|
94
141
|
</aside>
|
|
@@ -153,7 +153,9 @@ export function MissionTimeline({
|
|
|
153
153
|
const typeBadge =
|
|
154
154
|
mission.type === "hotfix"
|
|
155
155
|
? "[🔥 hot-fix]"
|
|
156
|
-
: mission.type === "
|
|
156
|
+
: mission.type === "submission"
|
|
157
|
+
? "[+ submission]"
|
|
158
|
+
: mission.type === "goal" || mission.type === "feature"
|
|
157
159
|
? "[✦ goal]"
|
|
158
160
|
: "[— unknown]";
|
|
159
161
|
const shortId =
|
|
@@ -188,6 +190,8 @@ export function MissionTimeline({
|
|
|
188
190
|
className={`shrink-0 font-mono text-[9px] rounded px-1.5 py-0.5 border ${
|
|
189
191
|
mission.type === "hotfix"
|
|
190
192
|
? "bg-rose-500/15 text-rose-300 border-rose-500/30"
|
|
193
|
+
: mission.type === "submission"
|
|
194
|
+
? "bg-sky-500/15 text-sky-300 border-sky-500/30"
|
|
191
195
|
: "bg-emerald-500/15 text-emerald-300 border-emerald-500/30"
|
|
192
196
|
}`}
|
|
193
197
|
>
|
|
@@ -129,7 +129,7 @@ export function OrgTree({ snapshot, activeNodeId, onNodeClick }: OrgTreeProps) {
|
|
|
129
129
|
id: "owner",
|
|
130
130
|
role: "owner",
|
|
131
131
|
label: "Owner",
|
|
132
|
-
sublabel: "/goal · /hot-fix",
|
|
132
|
+
sublabel: "/goal · /submission · /hot-fix",
|
|
133
133
|
status: "idle",
|
|
134
134
|
activity: snapshot.ownerHistory?.[0]?.content.slice(0, 80) ?? null,
|
|
135
135
|
agentIds: [],
|
|
@@ -225,7 +225,8 @@ export function OrgTree({ snapshot, activeNodeId, onNodeClick }: OrgTreeProps) {
|
|
|
225
225
|
},
|
|
226
226
|
];
|
|
227
227
|
|
|
228
|
-
// Workers
|
|
228
|
+
// Workers are grouped by their owning CXX so evaluator reports sit under CQO,
|
|
229
|
+
// implementation reports under CTO, and legacy flat reports stay visible.
|
|
229
230
|
const workers = currentMission?.workers ?? [];
|
|
230
231
|
|
|
231
232
|
return (
|
|
@@ -271,7 +272,9 @@ export function OrgTree({ snapshot, activeNodeId, onNodeClick }: OrgTreeProps) {
|
|
|
271
272
|
{/* Row 3: CXX nodes with workers below */}
|
|
272
273
|
<div className="flex gap-3 justify-center">
|
|
273
274
|
{cxxNodes.map((node) => {
|
|
274
|
-
const nodeWorkers =
|
|
275
|
+
const nodeWorkers = workers.filter(
|
|
276
|
+
(w) => w.owner === node.role || (w.owner === "unknown" && node.role === "cto")
|
|
277
|
+
);
|
|
275
278
|
|
|
276
279
|
return (
|
|
277
280
|
<div key={node.id} className="flex flex-col items-center gap-2">
|
|
@@ -293,7 +296,7 @@ export function OrgTree({ snapshot, activeNodeId, onNodeClick }: OrgTreeProps) {
|
|
|
293
296
|
onClick={() =>
|
|
294
297
|
onNodeClick({
|
|
295
298
|
id: `worker-${w.name}`,
|
|
296
|
-
role:
|
|
299
|
+
role: node.role,
|
|
297
300
|
label: w.name,
|
|
298
301
|
sublabel: w.status,
|
|
299
302
|
status: w.status === "COMPLETE" ? "idle" : "typing",
|
|
@@ -27,7 +27,6 @@ interface SceneProps {
|
|
|
27
27
|
|
|
28
28
|
export function Scene({ snapshot: initial, lang = "ko" }: SceneProps) {
|
|
29
29
|
const { snapshot, connectionState } = useHarnessStream(initial);
|
|
30
|
-
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
31
30
|
const [drawerTab, setDrawerTab] = useState<DrawerTab>("mission-flow");
|
|
32
31
|
const [selectedAgent, setSelectedAgent] = useState<AgentState | null>(null);
|
|
33
32
|
const [selectedNode, setSelectedNode] = useState<OrgNodeDef | null>(null);
|
|
@@ -38,7 +37,6 @@ export function Scene({ snapshot: initial, lang = "ko" }: SceneProps) {
|
|
|
38
37
|
setSelectedAgent(a);
|
|
39
38
|
setSelectedNode(null);
|
|
40
39
|
setDrawerTab("logs");
|
|
41
|
-
setDrawerOpen(true);
|
|
42
40
|
};
|
|
43
41
|
|
|
44
42
|
const handleNodeClick = (node: OrgNodeDef) => {
|
|
@@ -55,7 +53,6 @@ export function Scene({ snapshot: initial, lang = "ko" }: SceneProps) {
|
|
|
55
53
|
} else {
|
|
56
54
|
setDrawerTab("mission-flow");
|
|
57
55
|
}
|
|
58
|
-
setDrawerOpen(true);
|
|
59
56
|
};
|
|
60
57
|
|
|
61
58
|
const handleMissionClick = (mission: MissionDoc) => {
|
|
@@ -63,7 +60,6 @@ export function Scene({ snapshot: initial, lang = "ko" }: SceneProps) {
|
|
|
63
60
|
setSelectedNode(null);
|
|
64
61
|
setSelectedAgent(null);
|
|
65
62
|
setDrawerTab("mission-flow");
|
|
66
|
-
setDrawerOpen(true);
|
|
67
63
|
};
|
|
68
64
|
|
|
69
65
|
const drawerTitle =
|
|
@@ -74,60 +70,126 @@ export function Scene({ snapshot: initial, lang = "ko" }: SceneProps) {
|
|
|
74
70
|
: selectedNode
|
|
75
71
|
? selectedNode.label
|
|
76
72
|
: "Mission Flow";
|
|
73
|
+
const drawerScrollResetKey = [
|
|
74
|
+
drawerTab,
|
|
75
|
+
selectedMission?.missionId ?? "",
|
|
76
|
+
selectedNode?.id ?? "",
|
|
77
|
+
selectedAgent?.id ?? "",
|
|
78
|
+
].join(":");
|
|
77
79
|
|
|
78
80
|
// Suppress unused lang warning — kept for future i18n
|
|
79
81
|
void lang;
|
|
80
82
|
|
|
81
83
|
return (
|
|
82
|
-
<div className="
|
|
84
|
+
<div className="flex h-[100dvh] w-full flex-col overflow-hidden px-4 py-5">
|
|
83
85
|
{snapshot.escalations.length > 0 && (
|
|
84
86
|
<EscalationStrip escalations={snapshot.escalations} />
|
|
85
87
|
)}
|
|
86
|
-
<div className="min-w-0">
|
|
87
|
-
<
|
|
88
|
-
<
|
|
89
|
-
<div>
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
88
|
+
<div className="grid min-h-0 flex-1 min-w-0 gap-4 lg:grid-cols-2">
|
|
89
|
+
<div className="scrollbar-hidden min-h-0 min-w-0 overflow-y-auto pr-1">
|
|
90
|
+
<section className="min-w-0">
|
|
91
|
+
<div className="mb-3 flex flex-wrap items-end justify-between gap-3">
|
|
92
|
+
<div>
|
|
93
|
+
<p className="text-[11px] font-mono uppercase tracking-[0.24em] text-cyan-300/80">
|
|
94
|
+
Company Structure
|
|
95
|
+
</p>
|
|
96
|
+
<h1 className="mt-1 text-2xl font-semibold text-gray-100">
|
|
97
|
+
{snapshot.projectName || "walwal-harness"} · Organization
|
|
98
|
+
</h1>
|
|
99
|
+
</div>
|
|
100
|
+
<ActivityIndicator snapshot={snapshot} connectionState={connectionState} />
|
|
96
101
|
</div>
|
|
97
|
-
<
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
102
|
+
<div
|
|
103
|
+
data-testid="brick-office-stage"
|
|
104
|
+
className="w-full overflow-x-auto rounded-md border border-gray-700/80 bg-[#12151b] shadow-2xl"
|
|
105
|
+
>
|
|
106
|
+
<div className="pointer-events-none flex items-center justify-between gap-3 border-b border-white/10 bg-black/30 px-4 py-2 font-mono text-[10px] text-gray-300 backdrop-blur">
|
|
107
|
+
<span>Owner → CEO → CXX → Workers</span>
|
|
108
|
+
<span className="truncate">
|
|
109
|
+
{snapshot.missions?.[0]?.missionId ?? "no active mission"}
|
|
110
|
+
{snapshot.missions?.[0]?.cxxPresent.length
|
|
111
|
+
? ` · ${snapshot.missions[0].cxxPresent.join(" / ")}`
|
|
112
|
+
: ""}
|
|
113
|
+
</span>
|
|
114
|
+
</div>
|
|
115
|
+
<OrgTree
|
|
116
|
+
snapshot={snapshot}
|
|
117
|
+
activeNodeId={selectedNode?.id ?? null}
|
|
118
|
+
onNodeClick={handleNodeClick}
|
|
119
|
+
/>
|
|
120
|
+
</div>
|
|
121
|
+
</section>
|
|
122
|
+
|
|
123
|
+
<section className="mt-4">
|
|
124
|
+
<div className="mb-2">
|
|
125
|
+
<p className="font-mono text-[10px] uppercase tracking-[0.2em] text-cyan-300/60">Mission History</p>
|
|
126
|
+
<p className="text-[11px] text-gray-500 mt-0.5">goal · submission · hot-fix 명령별 파생 작업 흐름</p>
|
|
111
127
|
</div>
|
|
112
|
-
<
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
128
|
+
<MissionTimeline
|
|
129
|
+
missions={snapshot.missions ?? []}
|
|
130
|
+
activeMissionId={selectedMission?.missionId ?? null}
|
|
131
|
+
onMissionClick={handleMissionClick}
|
|
116
132
|
/>
|
|
117
|
-
</
|
|
118
|
-
</
|
|
133
|
+
</section>
|
|
134
|
+
</div>
|
|
119
135
|
|
|
120
|
-
<
|
|
121
|
-
<
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
136
|
+
<div className="min-h-0 min-w-0">
|
|
137
|
+
<Drawer
|
|
138
|
+
mode="inline"
|
|
139
|
+
open={true}
|
|
140
|
+
tab={drawerTab}
|
|
141
|
+
title={drawerTitle}
|
|
142
|
+
onClose={() => undefined}
|
|
143
|
+
onTabChange={setDrawerTab}
|
|
144
|
+
scrollResetKey={drawerScrollResetKey}
|
|
145
|
+
>
|
|
146
|
+
{drawerTab === "mission-flow" && (
|
|
147
|
+
<MissionFlowTab
|
|
148
|
+
mission={selectedMission ?? snapshot.missions?.[0] ?? null}
|
|
149
|
+
ownerHistory={snapshot.ownerHistory ?? []}
|
|
150
|
+
/>
|
|
151
|
+
)}
|
|
152
|
+
{drawerTab === "history" && (
|
|
153
|
+
<OwnerHistoryTab
|
|
154
|
+
ownerHistory={snapshot.ownerHistory ?? []}
|
|
155
|
+
mission={selectedMission ?? snapshot.missions?.[0] ?? null}
|
|
156
|
+
/>
|
|
157
|
+
)}
|
|
158
|
+
{drawerTab === "gotchas" && (
|
|
159
|
+
<GotchasTab gotchas={snapshot.gotchas ?? []} />
|
|
160
|
+
)}
|
|
161
|
+
{drawerTab === "mission-doc" && selectedNode && (
|
|
162
|
+
<MissionDocTab
|
|
163
|
+
missions={snapshot.missions ?? []}
|
|
164
|
+
role={
|
|
165
|
+
selectedNode.role === "owner"
|
|
166
|
+
? "ceo"
|
|
167
|
+
: (selectedNode.role as "ceo" | "cto" | "cqo" | "coo" | "cdo" | "ops")
|
|
168
|
+
}
|
|
169
|
+
fromLabel={selectedNode.role === "ceo" ? "Owner" : "CEO"}
|
|
170
|
+
toLabel={
|
|
171
|
+
selectedNode.role === "cto"
|
|
172
|
+
? "Workers"
|
|
173
|
+
: selectedNode.role === "cqo"
|
|
174
|
+
? "Owner Report"
|
|
175
|
+
: "—"
|
|
176
|
+
}
|
|
177
|
+
/>
|
|
178
|
+
)}
|
|
179
|
+
{drawerTab === "logs" && selectedNode && (
|
|
180
|
+
<AgentLogTab agentId={selectedNode.agentIds[0] ?? selectedNode.id} />
|
|
181
|
+
)}
|
|
182
|
+
{drawerTab === "logs" && selectedAgent && !selectedNode && (
|
|
183
|
+
<AgentLogTab agentId={selectedAgent.id} />
|
|
184
|
+
)}
|
|
185
|
+
{drawerTab === "logs" && !selectedNode && !selectedAgent && (
|
|
186
|
+
<div className="text-gray-500 text-xs">Select a node to view logs.</div>
|
|
187
|
+
)}
|
|
188
|
+
{drawerTab === "mission-flow" && !selectedMission && !snapshot.missions?.length && (
|
|
189
|
+
<div className="text-gray-500 text-xs">No missions found in .harness/documents/</div>
|
|
190
|
+
)}
|
|
191
|
+
</Drawer>
|
|
192
|
+
</div>
|
|
131
193
|
</div>
|
|
132
194
|
|
|
133
195
|
{/* Hidden DOM index for E2E + a11y */}
|
|
@@ -153,60 +215,6 @@ export function Scene({ snapshot: initial, lang = "ko" }: SceneProps) {
|
|
|
153
215
|
</li>
|
|
154
216
|
))}
|
|
155
217
|
</ul>
|
|
156
|
-
|
|
157
|
-
<Drawer
|
|
158
|
-
open={drawerOpen}
|
|
159
|
-
tab={drawerTab}
|
|
160
|
-
title={drawerTitle}
|
|
161
|
-
onClose={() => setDrawerOpen(false)}
|
|
162
|
-
onTabChange={setDrawerTab}
|
|
163
|
-
>
|
|
164
|
-
{drawerTab === "mission-flow" && (
|
|
165
|
-
<MissionFlowTab
|
|
166
|
-
mission={selectedMission ?? snapshot.missions?.[0] ?? null}
|
|
167
|
-
ownerHistory={snapshot.ownerHistory ?? []}
|
|
168
|
-
/>
|
|
169
|
-
)}
|
|
170
|
-
{drawerTab === "history" && (
|
|
171
|
-
<OwnerHistoryTab
|
|
172
|
-
ownerHistory={snapshot.ownerHistory ?? []}
|
|
173
|
-
mission={selectedMission ?? snapshot.missions?.[0] ?? null}
|
|
174
|
-
/>
|
|
175
|
-
)}
|
|
176
|
-
{drawerTab === "gotchas" && (
|
|
177
|
-
<GotchasTab gotchas={snapshot.gotchas ?? []} />
|
|
178
|
-
)}
|
|
179
|
-
{drawerTab === "mission-doc" && selectedNode && (
|
|
180
|
-
<MissionDocTab
|
|
181
|
-
missions={snapshot.missions ?? []}
|
|
182
|
-
role={
|
|
183
|
-
selectedNode.role === "owner"
|
|
184
|
-
? "ceo"
|
|
185
|
-
: (selectedNode.role as "ceo" | "cto" | "cqo" | "coo" | "cdo" | "ops")
|
|
186
|
-
}
|
|
187
|
-
fromLabel={selectedNode.role === "ceo" ? "Owner" : "CEO"}
|
|
188
|
-
toLabel={
|
|
189
|
-
selectedNode.role === "cto"
|
|
190
|
-
? "Workers"
|
|
191
|
-
: selectedNode.role === "cqo"
|
|
192
|
-
? "Owner Report"
|
|
193
|
-
: "—"
|
|
194
|
-
}
|
|
195
|
-
/>
|
|
196
|
-
)}
|
|
197
|
-
{drawerTab === "logs" && selectedNode && (
|
|
198
|
-
<AgentLogTab agentId={selectedNode.agentIds[0] ?? selectedNode.id} />
|
|
199
|
-
)}
|
|
200
|
-
{drawerTab === "logs" && selectedAgent && !selectedNode && (
|
|
201
|
-
<AgentLogTab agentId={selectedAgent.id} />
|
|
202
|
-
)}
|
|
203
|
-
{drawerTab === "logs" && !selectedNode && !selectedAgent && (
|
|
204
|
-
<div className="text-gray-500 text-xs">Select a node to view logs.</div>
|
|
205
|
-
)}
|
|
206
|
-
{drawerTab === "mission-flow" && !selectedMission && !snapshot.missions?.length && (
|
|
207
|
-
<div className="text-gray-500 text-xs">No missions found in .harness/documents/</div>
|
|
208
|
-
)}
|
|
209
|
-
</Drawer>
|
|
210
218
|
</div>
|
|
211
219
|
);
|
|
212
220
|
}
|
|
@@ -288,7 +288,8 @@ export function MissionFlowTab({ mission, ownerHistory }: Props) {
|
|
|
288
288
|
|
|
289
289
|
const typeBadge =
|
|
290
290
|
mission.type === "hotfix" ? "[🔥 hot-fix]"
|
|
291
|
-
: mission.type === "
|
|
291
|
+
: mission.type === "submission" ? "[+ submission]"
|
|
292
|
+
: mission.type === "goal" || mission.type === "feature" ? "[✦ goal]"
|
|
292
293
|
: "[— unknown]";
|
|
293
294
|
|
|
294
295
|
let dateStr = "—";
|
|
@@ -341,6 +342,8 @@ export function MissionFlowTab({ mission, ownerHistory }: Props) {
|
|
|
341
342
|
className={`font-mono text-[9px] rounded border px-1.5 py-0.5 ${
|
|
342
343
|
mission.type === "hotfix"
|
|
343
344
|
? "bg-rose-500/15 text-rose-300 border-rose-500/30"
|
|
345
|
+
: mission.type === "submission"
|
|
346
|
+
? "bg-sky-500/15 text-sky-300 border-sky-500/30"
|
|
344
347
|
: "bg-emerald-500/15 text-emerald-300 border-emerald-500/30"
|
|
345
348
|
}`}
|
|
346
349
|
>
|
|
@@ -92,6 +92,8 @@ export function OwnerHistoryTab({ ownerHistory, mission, onEntryClick }: Props)
|
|
|
92
92
|
className={`font-mono text-[9px] px-1.5 py-0.5 rounded ${
|
|
93
93
|
closestEntry.type === "goal"
|
|
94
94
|
? "bg-emerald-500/15 text-emerald-300"
|
|
95
|
+
: closestEntry.type === "submission"
|
|
96
|
+
? "bg-sky-500/15 text-sky-300"
|
|
95
97
|
: closestEntry.type === "hot-fix"
|
|
96
98
|
? "bg-rose-500/15 text-rose-300"
|
|
97
99
|
: "bg-gray-500/15 text-gray-400"
|
|
@@ -140,6 +142,8 @@ export function OwnerHistoryTab({ ownerHistory, mission, onEntryClick }: Props)
|
|
|
140
142
|
className={`font-mono text-[9px] px-1.5 py-0.5 rounded ${
|
|
141
143
|
entry.type === "goal"
|
|
142
144
|
? "bg-emerald-500/15 text-emerald-300"
|
|
145
|
+
: entry.type === "submission"
|
|
146
|
+
? "bg-sky-500/15 text-sky-300"
|
|
143
147
|
: entry.type === "hot-fix"
|
|
144
148
|
? "bg-rose-500/15 text-rose-300"
|
|
145
149
|
: "bg-gray-500/15 text-gray-400"
|