@walwal-harness/cli 7.1.21 → 7.1.23
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/CHANGELOG.md +23 -0
- package/HR-Resource/cdo/SKILL.md +16 -3
- package/HR-Resource/ceo/SKILL.md +1 -0
- package/README.md +20 -0
- package/apps/harness-dashboard/components/Drawer.tsx +2 -0
- package/apps/harness-dashboard/components/OrgTree.tsx +202 -89
- package/apps/harness-dashboard/components/Scene.tsx +6 -0
- package/apps/harness-dashboard/components/drawer/DesignPreviewTab.tsx +160 -0
- package/apps/harness-dashboard/lib/harness-state.ts +1 -0
- package/apps/harness-dashboard/lib/types.ts +1 -0
- package/assets/templates/HARNESS.md +1 -0
- package/assets/templates/company-pipeline-manifest.json +1 -0
- package/assets/templates/progress.json.template +8 -0
- package/package.json +1 -1
- package/scripts/harness-company-complete.sh +43 -0
- package/scripts/harness-stop.sh +19 -5
- package/scripts/harness-user-prompt-submit.sh +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -31,6 +31,29 @@ docmeta:
|
|
|
31
31
|
|
|
32
32
|
## Unreleased
|
|
33
33
|
|
|
34
|
+
## 7.1.23 — Dashboard CDO preview + radial org tree (2026-05-21)
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
- Dashboard CDO Preview tab: clicking `harness-cdo` opens an iframe preview from `.harness/documents/{mission}/cdo/preview.html`.
|
|
38
|
+
- CDO skill and harness reference now require the preview artifact before CDO completion, with CEO/Owner handoff guidance to view it in the dashboard.
|
|
39
|
+
- Dashboard mission state now reads `cdo/preview.html` alongside CXX markdown docs.
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
- Dashboard Org Tree now uses a compact radial/sunflower layout with Owner/CEO centered and CXX roles arranged around them.
|
|
43
|
+
- Org Tree now has independent zoom controls, ctrl/meta-wheel zoom, and drag panning inside a reduced-height scroll surface.
|
|
44
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
- CDO dashboard navigation now opens the visual Preview tab instead of only the markdown document flow.
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
- v7 `/goal`, `/submission`, and `/hot-fix` now mark both `company_state.state` and `conductor.state` as `running`, so Claude Stop hook auto-chain does not stop after CEO routing or hiring-only summaries.
|
|
50
|
+
- Stop hook now treats v7 CEO routing state (`current_agent=ceo`, `agent_status=running`, `owner_prompt.status=routing`) as an unfinished company loop, including migrated projects that do not yet have running conductor/company state.
|
|
51
|
+
- CEO skill now explicitly forbids ending a mission turn with only hiring/resource-manager output while the Owner goal remains unfinished.
|
|
52
|
+
|
|
53
|
+
### Added
|
|
54
|
+
- `scripts/harness-company-complete.sh` provides the shared running → idle/completed transition for dashboard auto-mode, external runners, hooks, and final CEO handoff paths.
|
|
55
|
+
- New progress templates include `company_state.state` and default `conductor` fields for consistent runner/dashboard state.
|
|
56
|
+
|
|
34
57
|
### Changed
|
|
35
58
|
- Dashboard Org Tree now positions only hired HR-Resource workers from `hr-roster.json`, requiring a backing `.harness/shared/HR-Resource/{worker}/SKILL.md`.
|
|
36
59
|
- Running hired workers are highlighted under their owning CXX instead of showing queue jobs as worker seats.
|
package/HR-Resource/cdo/SKILL.md
CHANGED
|
@@ -18,13 +18,25 @@ Own design strategy for the mission.
|
|
|
18
18
|
5. Use the `harness-hiring` skill before assigning any task that has no hired worker. Do not complete that task yourself.
|
|
19
19
|
6. Delegate all design deliverables and review passes to hired workers in fresh sessions.
|
|
20
20
|
7. Evaluate worker feedback for usefulness, discomfort, novelty, clarity, and differentiation.
|
|
21
|
-
8. Select the final direction and
|
|
21
|
+
8. Select the final direction and write a CDO preview artifact at `.harness/documents/{mission_name}/cdo/preview.html`.
|
|
22
|
+
9. Report to CEO and CTO with a concise preview summary and mention that the visual sample is available in the dashboard by clicking harness-cdo.
|
|
22
23
|
|
|
23
24
|
## Rule
|
|
24
25
|
|
|
25
26
|
Design output must be usable by implementation teams, not just descriptive.
|
|
26
27
|
CDO must not directly produce palettes, typography, layouts, mockups, interaction specs, or design-review findings. CDO may only brief workers, compare their outputs, decide, and document the accepted direction with worker names and report paths.
|
|
27
28
|
|
|
29
|
+
## Preview Artifact Gate
|
|
30
|
+
|
|
31
|
+
CDO completion is invalid without `.harness/documents/{mission_name}/cdo/preview.html`.
|
|
32
|
+
|
|
33
|
+
- The preview must be a self-contained HTML document suitable for dashboard iframe rendering.
|
|
34
|
+
- The preview must summarize the worker-backed final direction visually, not just repeat prose from `cdo.md`.
|
|
35
|
+
- It must include the core screen/state CDO expects CTO to implement, using realistic layout, density, labels, and interaction affordances.
|
|
36
|
+
- It must not load remote scripts or depend on a dev server. Inline CSS is preferred.
|
|
37
|
+
- If the mission has no UI surface, CDO must still write a visual decision board preview explaining the non-UI design constraints.
|
|
38
|
+
- CDO's response to CEO must include a short note: "Dashboard: click harness-cdo to view the Preview."
|
|
39
|
+
|
|
28
40
|
## Owner Handoff Gate
|
|
29
41
|
|
|
30
42
|
Owner is the final acceptance reviewer, not a design QA substitute. CDO must use design/review workers to validate UI/UX readiness, accessibility concerns, responsive behavior assumptions, and visual acceptance risks before handoff. Do not ask the Owner to discover whether the UI is usable or visually broken; ask only for final product or brand acceptance after worker-backed review.
|
|
@@ -34,7 +46,8 @@ Required output sections:
|
|
|
34
46
|
1. Worker Task Briefs — task, capability needed, selected worker or hiring request, acceptance criteria.
|
|
35
47
|
2. Worker Evidence Manifest — worker name, report path, status.
|
|
36
48
|
3. CDO Decision — only decisions accepted from worker evidence.
|
|
37
|
-
4.
|
|
38
|
-
5.
|
|
49
|
+
4. Preview Artifact — path `.harness/documents/{mission_name}/cdo/preview.html`, visual summary, and dashboard viewing note.
|
|
50
|
+
5. Next Handoff — CTO-ready design constraints, inputs, blockers.
|
|
51
|
+
6. Implementation Notes — in English, with `Design Decisions`, `Deviations`, `Tradeoffs`, and `Open Questions`.
|
|
39
52
|
|
|
40
53
|
Every CDO worker brief must require the worker to append the same English `## Implementation Notes` block to the bottom of `.harness/documents/{mission_name}/cdo/workers/{worker-name}.md`, covering risks, self-corrections, chosen direction, and unresolved questions. Use `None` for empty subsections.
|
package/HR-Resource/ceo/SKILL.md
CHANGED
|
@@ -30,6 +30,7 @@ You are the only direct conversation channel with the Owner.
|
|
|
30
30
|
- If a suitable hired worker is absent, invoke the `harness-hiring` skill before the CXX proceeds with that deliverable.
|
|
31
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
32
|
- Every CXX starts from fresh context and records decisions in `.harness/documents/{goal-or-child-mission}/{cxx}.md`.
|
|
33
|
+
- Hiring or resource-manager output is never a stopping point. After missing workers are registered, immediately continue routing to the responsible CXX fresh sessions and require those CXX agents to brief/run the hired workers. Do not end the turn with only a hiring summary while the Owner goal remains unfinished.
|
|
33
34
|
- Preserve DDD boundaries: domain decisions, application wiring, infrastructure, and quality policy are separate responsibilities.
|
|
34
35
|
- 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
36
|
- After writing `.env`, verify with `grep '^HARNESS_BASE_PORT=' .env` before routing service work.
|
package/README.md
CHANGED
|
@@ -158,6 +158,25 @@ Dashboard options:
|
|
|
158
158
|
| `--port <port>` | Run Brick Office on a custom port, e.g. `3002` |
|
|
159
159
|
| `--reinstall` | Delete and rebuild the isolated dashboard cache under `~/.walwal-harness/dashboard/` |
|
|
160
160
|
|
|
161
|
+
### Company Auto-Chain State
|
|
162
|
+
|
|
163
|
+
`/goal`, `/submission`, and `/hot-fix` mark the v7 company loop as running in `.harness/progress.json`:
|
|
164
|
+
|
|
165
|
+
- `company_state.state = "running"`
|
|
166
|
+
- `conductor.state = "running"`
|
|
167
|
+
- `current_agent = "ceo"`
|
|
168
|
+
- `next_agent = "ceo"`
|
|
169
|
+
|
|
170
|
+
Claude's Stop hook uses this state to prevent an unfinished CEO/CXX/worker loop from ending after a partial routing step such as hiring. Hookless executors such as Codex should drive the same state through an external runner or dashboard auto-mode process.
|
|
171
|
+
|
|
172
|
+
When the mission is genuinely complete, the common completion transition is:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
bash scripts/harness-company-complete.sh . mission-complete
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
This sets `company_state.state = "idle"`, `conductor.state = "completed"`, `next_agent = "none"`, and `owner_prompt.status = "completed"` so dashboards, runners, and hooks agree that auto-mode can stop.
|
|
179
|
+
|
|
161
180
|
Existing users should update and migrate from the target project:
|
|
162
181
|
|
|
163
182
|
```bash
|
|
@@ -184,6 +203,7 @@ Features:
|
|
|
184
203
|
- **Org Tree** — live status of Owner → CEO → CXX → Workers hierarchy, with worker cards created only from hired `hr-roster.json` entries backed by `.harness/shared/HR-Resource/{worker}/SKILL.md`; running hired workers are highlighted under their owning CXX
|
|
185
204
|
- **Mission Timeline** — clickable history of goal/submission/hot-fix missions showing the full dispatch chain
|
|
186
205
|
- **Mission Flow tab** — per-mission flow: Owner prompt → CEO routing → CXX → owner-specific worker evidence → CQO verdict
|
|
206
|
+
- **CDO Preview** — clicking `harness-cdo` opens a dashboard iframe preview from `.harness/documents/{mission}/cdo/preview.html`; CDO completion requires this visual summary artifact
|
|
187
207
|
- **History tab** — mission-specific Owner request (from CEO summary + closest progress.log match)
|
|
188
208
|
- **Gotchas tab** — searchable `.harness/gotchas/*.md` knowledge base, click to read full markdown
|
|
189
209
|
- **Document tab** — per-CXX markdown doc viewer
|
|
@@ -5,6 +5,7 @@ export type DrawerTab =
|
|
|
5
5
|
| "mission-flow" // Mission document hierarchy (PRIMARY)
|
|
6
6
|
| "history" // Owner prompt history
|
|
7
7
|
| "gotchas" // Harness gotcha knowledge base
|
|
8
|
+
| "design-preview" // CDO visual sample iframe
|
|
8
9
|
| "mission-doc" // Single doc markdown view
|
|
9
10
|
| "logs"; // Agent log (technical)
|
|
10
11
|
|
|
@@ -23,6 +24,7 @@ const TABS: Array<{ id: DrawerTab; label: string }> = [
|
|
|
23
24
|
{ id: "mission-flow", label: "Mission Flow" },
|
|
24
25
|
{ id: "history", label: "History" },
|
|
25
26
|
{ id: "gotchas", label: "Gotchas" },
|
|
27
|
+
{ id: "design-preview", label: "Preview" },
|
|
26
28
|
{ id: "mission-doc", label: "Document" },
|
|
27
29
|
{ id: "logs", label: "Agent Log" },
|
|
28
30
|
];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { useCallback, useMemo, useRef, useState } from "react";
|
|
2
3
|
import type { AgentId, HarnessSnapshot, MinifigState, OrgNodeDef } from "@/lib/types";
|
|
3
4
|
|
|
4
5
|
interface OrgTreeProps {
|
|
@@ -66,38 +67,12 @@ function OrgCard({
|
|
|
66
67
|
);
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
function BranchConnector({ count }: { count: number }) {
|
|
70
|
-
const h = 44;
|
|
71
|
-
const stepPct = 100 / count;
|
|
72
|
-
const midY = h * 0.5;
|
|
73
|
-
const centers = Array.from({ length: count }, (_, i) => stepPct * i + stepPct / 2);
|
|
74
|
-
return (
|
|
75
|
-
<svg width="100%" height={h} className="overflow-visible pointer-events-none">
|
|
76
|
-
<line x1="50%" y1="0" x2="50%" y2={midY} stroke="#374151" strokeWidth="1.5" />
|
|
77
|
-
<line
|
|
78
|
-
x1={`${centers[0]}%`}
|
|
79
|
-
y1={midY}
|
|
80
|
-
x2={`${centers[centers.length - 1]}%`}
|
|
81
|
-
y2={midY}
|
|
82
|
-
stroke="#374151"
|
|
83
|
-
strokeWidth="1.5"
|
|
84
|
-
/>
|
|
85
|
-
{centers.map((x, i) => (
|
|
86
|
-
<line
|
|
87
|
-
key={i}
|
|
88
|
-
x1={`${x}%`}
|
|
89
|
-
y1={midY}
|
|
90
|
-
x2={`${x}%`}
|
|
91
|
-
y2={h}
|
|
92
|
-
stroke="#374151"
|
|
93
|
-
strokeWidth="1.5"
|
|
94
|
-
/>
|
|
95
|
-
))}
|
|
96
|
-
</svg>
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
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
|
+
|
|
101
76
|
const agentMap = new Map(snapshot.agents.map((a) => [a.id, a]));
|
|
102
77
|
|
|
103
78
|
const getStatus = (ids: AgentId[]): MinifigState => {
|
|
@@ -229,68 +204,205 @@ export function OrgTree({ snapshot, activeNodeId, onNodeClick }: OrgTreeProps) {
|
|
|
229
204
|
// report-only workers remain visible in mission documents, but not as seats.
|
|
230
205
|
const workers = (currentMission?.workers ?? []).filter((w) => w.hired);
|
|
231
206
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
{currentMission && (
|
|
236
|
-
<div className="mb-4 text-center">
|
|
237
|
-
<span className="font-mono text-[10px] uppercase tracking-[0.2em] text-cyan-300/60">
|
|
238
|
-
current mission
|
|
239
|
-
</span>
|
|
240
|
-
<div className="mt-0.5 font-mono text-xs text-gray-400">{currentMission.missionId}</div>
|
|
241
|
-
</div>
|
|
242
|
-
)}
|
|
207
|
+
const setBoundedZoom = useCallback((next: number) => {
|
|
208
|
+
setZoom(Math.min(1.35, Math.max(0.62, Number(next.toFixed(2)))));
|
|
209
|
+
}, []);
|
|
243
210
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
</div>
|
|
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
|
+
);
|
|
253
219
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
+
}, []);
|
|
258
232
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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: 1160, height: 600 };
|
|
247
|
+
const center = { x: 500, y: 240 };
|
|
268
248
|
|
|
269
|
-
|
|
270
|
-
|
|
249
|
+
const layout = useMemo(() => {
|
|
250
|
+
const cxxAngles = [-150, -78, 0, 78, 150];
|
|
251
|
+
const cxxRadius = 280;
|
|
252
|
+
const workerRadius = 150;
|
|
271
253
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
254
|
+
return cxxNodes.map((node, index) => {
|
|
255
|
+
const angle = (cxxAngles[index] * Math.PI) / 180;
|
|
256
|
+
const x = center.x + Math.cos(angle) * cxxRadius;
|
|
257
|
+
const y = center.y + Math.sin(angle) * cxxRadius * 0.58;
|
|
258
|
+
const outwardX = Math.cos(angle) * workerRadius;
|
|
259
|
+
const outwardY = Math.sin(angle) * workerRadius * 0.48;
|
|
260
|
+
return {
|
|
261
|
+
node,
|
|
262
|
+
x,
|
|
263
|
+
y,
|
|
264
|
+
workerX: x + outwardX,
|
|
265
|
+
workerY: y + outwardY,
|
|
266
|
+
};
|
|
267
|
+
});
|
|
268
|
+
}, [cxxNodes]);
|
|
280
269
|
|
|
281
|
-
|
|
282
|
-
|
|
270
|
+
return (
|
|
271
|
+
<div className="select-none">
|
|
272
|
+
<div className="flex items-center justify-between border-b border-white/10 bg-black/20 px-3 py-2">
|
|
273
|
+
<div className="min-w-0">
|
|
274
|
+
{currentMission && (
|
|
275
|
+
<div className="truncate font-mono text-[10px] text-cyan-300/70">
|
|
276
|
+
current mission · {currentMission.missionId}
|
|
277
|
+
</div>
|
|
278
|
+
)}
|
|
279
|
+
</div>
|
|
280
|
+
<div className="flex items-center gap-1">
|
|
281
|
+
<button
|
|
282
|
+
type="button"
|
|
283
|
+
onClick={() => setBoundedZoom(zoom - 0.08)}
|
|
284
|
+
className="h-7 w-7 rounded border border-gray-700 bg-gray-900/80 font-mono text-xs text-gray-200 hover:border-cyan-300/60"
|
|
285
|
+
title="Zoom out"
|
|
286
|
+
>
|
|
287
|
+
-
|
|
288
|
+
</button>
|
|
289
|
+
<button
|
|
290
|
+
type="button"
|
|
291
|
+
onClick={() => setBoundedZoom(defaultZoom)}
|
|
292
|
+
className="h-7 min-w-12 rounded border border-gray-700 bg-gray-900/80 px-2 font-mono text-[10px] text-gray-300 hover:border-cyan-300/60"
|
|
293
|
+
title="Reset zoom"
|
|
294
|
+
>
|
|
295
|
+
{Math.round(zoom * 100)}%
|
|
296
|
+
</button>
|
|
297
|
+
<button
|
|
298
|
+
type="button"
|
|
299
|
+
onClick={() => setBoundedZoom(zoom + 0.08)}
|
|
300
|
+
className="h-7 w-7 rounded border border-gray-700 bg-gray-900/80 font-mono text-xs text-gray-200 hover:border-cyan-300/60"
|
|
301
|
+
title="Zoom in"
|
|
302
|
+
>
|
|
303
|
+
+
|
|
304
|
+
</button>
|
|
305
|
+
</div>
|
|
306
|
+
</div>
|
|
307
|
+
|
|
308
|
+
<div
|
|
309
|
+
ref={scrollRef}
|
|
310
|
+
onWheel={handleWheel}
|
|
311
|
+
onPointerDown={handlePointerDown}
|
|
312
|
+
onPointerMove={handlePointerMove}
|
|
313
|
+
onPointerUp={endDrag}
|
|
314
|
+
onPointerCancel={endDrag}
|
|
315
|
+
className="scrollbar-hidden h-[390px] cursor-grab overflow-auto active:cursor-grabbing"
|
|
316
|
+
>
|
|
317
|
+
<div
|
|
318
|
+
className="relative"
|
|
319
|
+
style={{
|
|
320
|
+
width: canvas.width * zoom,
|
|
321
|
+
height: canvas.height * zoom,
|
|
322
|
+
}}
|
|
323
|
+
>
|
|
324
|
+
<div
|
|
325
|
+
className="absolute left-0 top-0 origin-top-left"
|
|
326
|
+
style={{
|
|
327
|
+
width: canvas.width,
|
|
328
|
+
height: canvas.height,
|
|
329
|
+
transform: `scale(${zoom})`,
|
|
330
|
+
}}
|
|
331
|
+
>
|
|
332
|
+
<svg
|
|
333
|
+
className="pointer-events-none absolute inset-0"
|
|
334
|
+
width={canvas.width}
|
|
335
|
+
height={canvas.height}
|
|
336
|
+
>
|
|
337
|
+
<line x1={center.x} y1={center.y - 26} x2={center.x} y2={center.y + 26} stroke="#374151" strokeWidth="1.5" />
|
|
338
|
+
{layout.map(({ node, x, y }) => (
|
|
339
|
+
<line
|
|
340
|
+
key={node.id}
|
|
341
|
+
x1={center.x}
|
|
342
|
+
y1={center.y}
|
|
343
|
+
x2={x}
|
|
344
|
+
y2={y}
|
|
345
|
+
stroke="#374151"
|
|
346
|
+
strokeDasharray="4 5"
|
|
347
|
+
strokeWidth="1.5"
|
|
348
|
+
/>
|
|
349
|
+
))}
|
|
350
|
+
{layout.map(({ node, x, y, workerX, workerY }) => {
|
|
351
|
+
const nodeWorkers = workers.filter((w) => w.owner === node.role);
|
|
352
|
+
if (nodeWorkers.length === 0) return null;
|
|
353
|
+
return (
|
|
354
|
+
<line
|
|
355
|
+
key={`${node.id}-workers`}
|
|
356
|
+
x1={x}
|
|
357
|
+
y1={y}
|
|
358
|
+
x2={workerX}
|
|
359
|
+
y2={workerY}
|
|
360
|
+
stroke="#374151"
|
|
361
|
+
strokeWidth="1.5"
|
|
362
|
+
/>
|
|
363
|
+
);
|
|
364
|
+
})}
|
|
365
|
+
</svg>
|
|
366
|
+
|
|
367
|
+
<div className="absolute left-[390px] top-[170px] grid w-[220px] gap-2">
|
|
283
368
|
<OrgCard
|
|
284
|
-
node={
|
|
285
|
-
size="
|
|
286
|
-
active={activeNodeId ===
|
|
369
|
+
node={ownerNode}
|
|
370
|
+
size="lg"
|
|
371
|
+
active={activeNodeId === "owner"}
|
|
287
372
|
onClick={onNodeClick}
|
|
288
373
|
/>
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
374
|
+
<OrgCard
|
|
375
|
+
node={ceoNode}
|
|
376
|
+
size="lg"
|
|
377
|
+
active={activeNodeId === "ceo"}
|
|
378
|
+
onClick={onNodeClick}
|
|
379
|
+
/>
|
|
380
|
+
</div>
|
|
381
|
+
|
|
382
|
+
{layout.map(({ node, x, y, workerX, workerY }) => {
|
|
383
|
+
const nodeWorkers = workers.filter((w) => w.owner === node.role);
|
|
384
|
+
const hasActiveWorker = nodeWorkers.some((w) => w.active);
|
|
385
|
+
const displayNode = hasActiveWorker
|
|
386
|
+
? { ...node, status: "typing" as const, activity: "Worker active" }
|
|
387
|
+
: node;
|
|
388
|
+
|
|
389
|
+
return (
|
|
390
|
+
<div
|
|
391
|
+
key={node.id}
|
|
392
|
+
className="absolute"
|
|
393
|
+
style={{ left: x - 82, top: y - 38 }}
|
|
394
|
+
>
|
|
395
|
+
<OrgCard
|
|
396
|
+
node={displayNode}
|
|
397
|
+
size="md"
|
|
398
|
+
active={activeNodeId === node.id}
|
|
399
|
+
onClick={onNodeClick}
|
|
400
|
+
/>
|
|
401
|
+
{nodeWorkers.length > 0 && (
|
|
402
|
+
<div
|
|
403
|
+
className="absolute flex w-[170px] flex-col gap-1.5"
|
|
404
|
+
style={{ left: workerX - x, top: workerY - y }}
|
|
405
|
+
>
|
|
294
406
|
{nodeWorkers.map((w) => {
|
|
295
407
|
const workerNodeId = `worker-${node.role}-${w.name}`;
|
|
296
408
|
const workerActive = activeNodeId === workerNodeId || w.active;
|
|
@@ -339,11 +451,12 @@ export function OrgTree({ snapshot, activeNodeId, onNodeClick }: OrgTreeProps) {
|
|
|
339
451
|
);
|
|
340
452
|
})}
|
|
341
453
|
</div>
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
454
|
+
)}
|
|
455
|
+
</div>
|
|
456
|
+
);
|
|
457
|
+
})}
|
|
458
|
+
</div>
|
|
459
|
+
</div>
|
|
347
460
|
</div>
|
|
348
461
|
</div>
|
|
349
462
|
);
|
|
@@ -17,6 +17,7 @@ import { OwnerHistoryTab } from "./drawer/OwnerHistoryTab";
|
|
|
17
17
|
import { MissionDocTab } from "./drawer/MissionDocTab";
|
|
18
18
|
import { MissionFlowTab } from "./drawer/MissionFlowTab";
|
|
19
19
|
import { GotchasTab } from "./drawer/GotchasTab";
|
|
20
|
+
import { DesignPreviewTab } from "./drawer/DesignPreviewTab";
|
|
20
21
|
import { MissionTimeline } from "./MissionTimeline";
|
|
21
22
|
import { OrgTree } from "./OrgTree";
|
|
22
23
|
|
|
@@ -48,6 +49,8 @@ export function Scene({ snapshot: initial, lang = "ko" }: SceneProps) {
|
|
|
48
49
|
}
|
|
49
50
|
if (node.id === "owner") {
|
|
50
51
|
setDrawerTab("history");
|
|
52
|
+
} else if (node.id === "cdo") {
|
|
53
|
+
setDrawerTab("design-preview");
|
|
51
54
|
} else if (node.id.startsWith("worker-")) {
|
|
52
55
|
setDrawerTab("mission-doc");
|
|
53
56
|
} else {
|
|
@@ -167,6 +170,9 @@ export function Scene({ snapshot: initial, lang = "ko" }: SceneProps) {
|
|
|
167
170
|
{drawerTab === "gotchas" && (
|
|
168
171
|
<GotchasTab gotchas={snapshot.gotchas ?? []} />
|
|
169
172
|
)}
|
|
173
|
+
{drawerTab === "design-preview" && (
|
|
174
|
+
<DesignPreviewTab mission={selectedMission ?? snapshot.missions?.[0] ?? null} />
|
|
175
|
+
)}
|
|
170
176
|
{drawerTab === "mission-doc" && selectedNode && (
|
|
171
177
|
<MissionDocTab
|
|
172
178
|
missions={snapshot.missions ?? []}
|
|
@@ -0,0 +1,160 @@
|
|
|
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
|
+
}
|
|
@@ -1229,6 +1229,7 @@ function readMissions(rootDir: string, progress: RawProgress | null = null, limi
|
|
|
1229
1229
|
cqo: readMd("cqo.md"),
|
|
1230
1230
|
coo: readMd("coo.md"),
|
|
1231
1231
|
cdo: readMd("cdo.md"),
|
|
1232
|
+
cdoPreview: readMd("cdo/preview.html"),
|
|
1232
1233
|
ops: readMd("ops.md"),
|
|
1233
1234
|
workers,
|
|
1234
1235
|
cxxPresent: [...cxxPresent],
|
|
@@ -89,6 +89,7 @@ Flat `.harness/documents/{mission}/workers/` reports are legacy. New worker evid
|
|
|
89
89
|
- CEO must not dispatch, hire, or brief workers directly.
|
|
90
90
|
- CXX agents must not directly execute specialist deliverables.
|
|
91
91
|
- CTO worker dispatch requires `cto.md` first.
|
|
92
|
+
- CDO completion requires `.harness/documents/{mission}/cdo/preview.html`; CEO/Owner can view it from the dashboard by clicking `harness-cdo`.
|
|
92
93
|
- CQO verdicts require evaluator/tester worker evidence.
|
|
93
94
|
- Owner is final acceptance only. Do not report "done, please check" until worker-backed verification proves the goal is complete.
|
|
94
95
|
- Every `ceo.md`, `{cxx}.md`, and worker report must end with:
|
|
@@ -48,11 +48,19 @@
|
|
|
48
48
|
"sprint-contract.md": { "status": "pending", "updated_by": null, "updated_at": null }
|
|
49
49
|
},
|
|
50
50
|
"company_state": {
|
|
51
|
+
"state": "idle",
|
|
51
52
|
"active_workers": 0,
|
|
52
53
|
"concurrency": 3,
|
|
53
54
|
"paused_at": null,
|
|
54
55
|
"resume_from": null
|
|
55
56
|
},
|
|
57
|
+
"conductor": {
|
|
58
|
+
"state": "idle",
|
|
59
|
+
"tracks": [],
|
|
60
|
+
"rendezvous": null,
|
|
61
|
+
"fork_meeting_id": null,
|
|
62
|
+
"stop_chain_count": 0
|
|
63
|
+
},
|
|
56
64
|
"updated_at": "{{DATE}}",
|
|
57
65
|
"history": []
|
|
58
66
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walwal-harness/cli",
|
|
3
|
-
"version": "7.1.
|
|
3
|
+
"version": "7.1.23",
|
|
4
4
|
"description": "Company-style AI agent harness for Claude and Codex. Installs commands, CXX agents, skills, HR-Resource hiring pool, and project-local .harness runtime state.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"walwal-harness": "bin/init.js"
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# harness-company-complete.sh — mark the current v7 company mission complete.
|
|
3
|
+
#
|
|
4
|
+
# Usage:
|
|
5
|
+
# bash scripts/harness-company-complete.sh <project-root> [reason]
|
|
6
|
+
#
|
|
7
|
+
# This is the explicit running -> idle/done transition used by dashboard,
|
|
8
|
+
# runner, hooks, and final CEO handoff paths. It intentionally only updates
|
|
9
|
+
# runtime state; it does not archive or rewrite mission documents.
|
|
10
|
+
|
|
11
|
+
set -uo pipefail
|
|
12
|
+
|
|
13
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
14
|
+
PROJECT_ROOT="${1:-.}"
|
|
15
|
+
REASON="${2:-mission-complete}"
|
|
16
|
+
|
|
17
|
+
PROGRESS="$PROJECT_ROOT/.harness/progress.json"
|
|
18
|
+
[ -f "$PROGRESS" ] || {
|
|
19
|
+
echo "[company-complete] not found: $PROGRESS" >&2
|
|
20
|
+
exit 1
|
|
21
|
+
}
|
|
22
|
+
command -v jq >/dev/null 2>&1 || {
|
|
23
|
+
echo "[company-complete] jq is required" >&2
|
|
24
|
+
exit 1
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
bash "$SCRIPT_DIR/harness-progress-set.sh" "$PROJECT_ROOT" \
|
|
28
|
+
'.company_state.state = "idle" |
|
|
29
|
+
.company_state.active_workers = 0 |
|
|
30
|
+
.company_state.completed_at = (now | todate) |
|
|
31
|
+
.conductor.state = "completed" |
|
|
32
|
+
.conductor.current_action = "complete:'"$REASON"'" |
|
|
33
|
+
.conductor.completed_at = (now | todate) |
|
|
34
|
+
.conductor.tracks = [] |
|
|
35
|
+
.conductor.rendezvous = null |
|
|
36
|
+
.conductor.fork_meeting_id = null |
|
|
37
|
+
.current_agent = null |
|
|
38
|
+
.next_agent = "none" |
|
|
39
|
+
.agent_status = "completed" |
|
|
40
|
+
.owner_prompt.status = "completed" |
|
|
41
|
+
.owner_prompt.completed_at = (now | todate)'
|
|
42
|
+
|
|
43
|
+
echo "[company-complete] marked complete: $REASON"
|
package/scripts/harness-stop.sh
CHANGED
|
@@ -52,8 +52,11 @@ if [ "${STOP_CHAIN_COUNT:-0}" -ge "${STOP_CHAIN_MAX:-200}" ]; then exit 0; fi
|
|
|
52
52
|
|
|
53
53
|
# 회사 루프 상태 확인
|
|
54
54
|
CONDUCTOR_STATE=$(jq -r '.conductor.state // "idle"' "$PROGRESS" 2>/dev/null || echo "idle")
|
|
55
|
+
COMPANY_STATE=$(jq -r '.company_state.state // "idle"' "$PROGRESS" 2>/dev/null || echo "idle")
|
|
56
|
+
CURRENT_AGENT=$(jq -r '.current_agent // "none"' "$PROGRESS" 2>/dev/null || echo "none")
|
|
55
57
|
NEXT_AGENT=$(jq -r '.next_agent // "none"' "$PROGRESS" 2>/dev/null || echo "none")
|
|
56
58
|
AGENT_STATUS=$(jq -r '.agent_status // "pending"' "$PROGRESS" 2>/dev/null || echo "pending")
|
|
59
|
+
OWNER_PROMPT_STATUS=$(jq -r '.owner_prompt.status // "none"' "$PROGRESS" 2>/dev/null || echo "none")
|
|
57
60
|
TASK_STOP_ACTIVE=$(jq -r '.task_stop.active // false' "$PROGRESS" 2>/dev/null || echo "false")
|
|
58
61
|
ESCALATION=$(jq -r '.conductor.escalation // "null"' "$PROGRESS" 2>/dev/null || echo "null")
|
|
59
62
|
SPRINT_STATUS=$(jq -r '.sprint.status // "init"' "$PROGRESS" 2>/dev/null || echo "init")
|
|
@@ -66,19 +69,30 @@ if [ "$HOOK_EVENT" = "Stop" ]; then
|
|
|
66
69
|
fi
|
|
67
70
|
|
|
68
71
|
# 자동 연쇄 조건:
|
|
69
|
-
# conductor 가 running
|
|
70
|
-
# AND task_stop 비활성
|
|
72
|
+
# conductor/company loop 가 running
|
|
71
73
|
# AND task_stop 비활성
|
|
72
74
|
# AND (next_agent 가 명시되어 있거나, agent_status=completed 라서 다음 tick 결정 필요)
|
|
75
|
+
# v7 CEO 루프는 conductor-tick 없이 CEO/CXX 상태로 시작될 수 있다.
|
|
76
|
+
# current_agent=ceo running + owner_prompt.routing 은 기존 설치본이 conductor/company
|
|
77
|
+
# state를 아직 쓰지 않은 stuck 상태에서도 turn 종료 차단 대상으로 본다.
|
|
73
78
|
should_chain="false"
|
|
74
|
-
if [ "$CONDUCTOR_STATE" = "running" ] \
|
|
79
|
+
if { [ "$CONDUCTOR_STATE" = "running" ] || [ "$COMPANY_STATE" = "running" ]; } \
|
|
75
80
|
&& [ "$TASK_STOP_ACTIVE" != "true" ]; then
|
|
76
81
|
if [ "$NEXT_AGENT" != "none" ] && [ "$NEXT_AGENT" != "null" ]; then
|
|
77
82
|
should_chain="true"
|
|
78
83
|
elif [ "$AGENT_STATUS" = "completed" ]; then
|
|
79
84
|
should_chain="true"
|
|
85
|
+
elif [ "$CURRENT_AGENT" = "ceo" ] && [ "$AGENT_STATUS" = "running" ] && [ "$OWNER_PROMPT_STATUS" = "routing" ]; then
|
|
86
|
+
should_chain="true"
|
|
80
87
|
fi
|
|
81
88
|
fi
|
|
89
|
+
if [ "$should_chain" != "true" ] \
|
|
90
|
+
&& [ "$TASK_STOP_ACTIVE" != "true" ] \
|
|
91
|
+
&& [ "$CURRENT_AGENT" = "ceo" ] \
|
|
92
|
+
&& [ "$AGENT_STATUS" = "running" ] \
|
|
93
|
+
&& [ "$OWNER_PROMPT_STATUS" = "routing" ]; then
|
|
94
|
+
should_chain="true"
|
|
95
|
+
fi
|
|
82
96
|
|
|
83
97
|
if [ "$should_chain" != "true" ]; then exit 0; fi
|
|
84
98
|
|
|
@@ -91,9 +105,9 @@ jq --argjson n "$NEW_COUNT" '.conductor.stop_chain_count = $n |
|
|
|
91
105
|
|
|
92
106
|
# Claude 에게 다음 행동 지시
|
|
93
107
|
if [ "$ESCALATION" != "null" ]; then
|
|
94
|
-
REASON="자율 회사 루프 진행 중. escalation=${ESCALATION} 는 정지 사유가 아니라 meeting-manager/service-ops 공유 대상이다. next_agent=${NEXT_AGENT}, agent_status=${AGENT_STATUS}, conductor=${CONDUCTOR_STATE}. SKILL 에 따라 conductor-tick 을 굴려 meeting-manager 회의로
|
|
108
|
+
REASON="자율 회사 루프 진행 중. escalation=${ESCALATION} 는 정지 사유가 아니라 meeting-manager/service-ops 공유 대상이다. current_agent=${CURRENT_AGENT}, next_agent=${NEXT_AGENT}, agent_status=${AGENT_STATUS}, conductor=${CONDUCTOR_STATE}, company=${COMPANY_STATE}. SKILL 에 따라 CEO/CXX/worker 흐름을 계속하거나 conductor-tick 을 굴려 meeting-manager 회의로 라우팅하라. progress.log 에는 절대 미래 시각을 쓰지 말 것. 진짜로 끝났을 때만 turn 을 종료하라."
|
|
95
109
|
else
|
|
96
|
-
REASON="자율 회사 루프 진행 중. next_agent=${NEXT_AGENT}, agent_status=${AGENT_STATUS}, conductor=${CONDUCTOR_STATE}. SKILL 에 따라
|
|
110
|
+
REASON="자율 회사 루프 진행 중. current_agent=${CURRENT_AGENT}, next_agent=${NEXT_AGENT}, agent_status=${AGENT_STATUS}, conductor=${CONDUCTOR_STATE}, company=${COMPANY_STATE}. SKILL 에 따라 CEO/CXX/worker 흐름을 계속하고, 필요한 CXX/worker fresh session 을 실행하라. progress.log 에는 절대 미래 시각을 쓰지 말 것. 진짜로 끝났을 때만 turn 을 종료하라."
|
|
97
111
|
fi
|
|
98
112
|
|
|
99
113
|
jq -nc \
|
|
@@ -83,6 +83,10 @@ if [ -n "$PROMPT" ] && [ -d "$CWD/.harness" ]; then
|
|
|
83
83
|
.current_agent = "ceo" |
|
|
84
84
|
.agent_status = "running" |
|
|
85
85
|
.next_agent = "ceo" |
|
|
86
|
+
.company_state.state = "running" |
|
|
87
|
+
.conductor.state = "running" |
|
|
88
|
+
.conductor.current_action = ("owner-" + $command + "-to-ceo") |
|
|
89
|
+
.conductor.stop_chain_count = 0 |
|
|
86
90
|
.updated_at = $now |
|
|
87
91
|
.owner_prompt = {
|
|
88
92
|
command: $command,
|