@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
|
@@ -1,306 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import React from "react";
|
|
3
|
-
|
|
4
|
-
// ---------------------------------------------------------------------------
|
|
5
|
-
// Inline renderer — handles **bold**, `code`, *italic*
|
|
6
|
-
// ---------------------------------------------------------------------------
|
|
7
|
-
function renderInline(text: string): React.ReactNode[] {
|
|
8
|
-
const parts: React.ReactNode[] = [];
|
|
9
|
-
// Pattern order matters: bold, inline-code, italic
|
|
10
|
-
const re = /(\*\*(.+?)\*\*|`(.+?)`|\*(.+?)\*)/g;
|
|
11
|
-
let last = 0;
|
|
12
|
-
let match: RegExpExecArray | null;
|
|
13
|
-
let key = 0;
|
|
14
|
-
|
|
15
|
-
while ((match = re.exec(text)) !== null) {
|
|
16
|
-
if (match.index > last) {
|
|
17
|
-
parts.push(text.slice(last, match.index));
|
|
18
|
-
}
|
|
19
|
-
if (match[0].startsWith("**")) {
|
|
20
|
-
parts.push(
|
|
21
|
-
<strong key={key++} className="text-white font-semibold">
|
|
22
|
-
{match[2]}
|
|
23
|
-
</strong>
|
|
24
|
-
);
|
|
25
|
-
} else if (match[0].startsWith("`")) {
|
|
26
|
-
parts.push(
|
|
27
|
-
<code
|
|
28
|
-
key={key++}
|
|
29
|
-
className="bg-gray-800 text-amber-300 text-[11px] px-1 rounded"
|
|
30
|
-
>
|
|
31
|
-
{match[3]}
|
|
32
|
-
</code>
|
|
33
|
-
);
|
|
34
|
-
} else {
|
|
35
|
-
parts.push(
|
|
36
|
-
<em key={key++} className="italic text-gray-300">
|
|
37
|
-
{match[4]}
|
|
38
|
-
</em>
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
last = match.index + match[0].length;
|
|
42
|
-
}
|
|
43
|
-
if (last < text.length) parts.push(text.slice(last));
|
|
44
|
-
return parts;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// ---------------------------------------------------------------------------
|
|
48
|
-
// Table renderer
|
|
49
|
-
// ---------------------------------------------------------------------------
|
|
50
|
-
function renderTable(rows: string[]): React.ReactNode {
|
|
51
|
-
// rows[0] = header, rows[1] = separator, rows[2+] = data
|
|
52
|
-
const parseRow = (row: string): string[] =>
|
|
53
|
-
row
|
|
54
|
-
.split("|")
|
|
55
|
-
.map((c) => c.trim())
|
|
56
|
-
.filter((_, i, arr) => i > 0 && i < arr.length - 1);
|
|
57
|
-
|
|
58
|
-
const headers = parseRow(rows[0]);
|
|
59
|
-
const dataRows = rows.slice(2).map(parseRow);
|
|
60
|
-
|
|
61
|
-
return (
|
|
62
|
-
<div key={Math.random()} className="overflow-x-auto">
|
|
63
|
-
<table className="w-full border-collapse font-mono text-[11px]">
|
|
64
|
-
<thead>
|
|
65
|
-
<tr>
|
|
66
|
-
{headers.map((h, i) => (
|
|
67
|
-
<th
|
|
68
|
-
key={i}
|
|
69
|
-
className="border border-gray-700/60 bg-gray-800/50 px-2 py-1 text-left text-cyan-300/90"
|
|
70
|
-
>
|
|
71
|
-
{renderInline(h)}
|
|
72
|
-
</th>
|
|
73
|
-
))}
|
|
74
|
-
</tr>
|
|
75
|
-
</thead>
|
|
76
|
-
<tbody>
|
|
77
|
-
{dataRows.map((row, ri) => (
|
|
78
|
-
<tr key={ri} className="border-b border-gray-700/40">
|
|
79
|
-
{row.map((cell, ci) => (
|
|
80
|
-
<td
|
|
81
|
-
key={ci}
|
|
82
|
-
className="border border-gray-700/40 px-2 py-1 text-gray-300"
|
|
83
|
-
>
|
|
84
|
-
{renderInline(cell)}
|
|
85
|
-
</td>
|
|
86
|
-
))}
|
|
87
|
-
</tr>
|
|
88
|
-
))}
|
|
89
|
-
</tbody>
|
|
90
|
-
</table>
|
|
91
|
-
</div>
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// ---------------------------------------------------------------------------
|
|
96
|
-
// Block renderer state machine
|
|
97
|
-
// ---------------------------------------------------------------------------
|
|
98
|
-
function renderBlocks(content: string): React.ReactNode[] {
|
|
99
|
-
const lines = content.split("\n");
|
|
100
|
-
const nodes: React.ReactNode[] = [];
|
|
101
|
-
let i = 0;
|
|
102
|
-
let keyCounter = 0;
|
|
103
|
-
const k = () => keyCounter++;
|
|
104
|
-
|
|
105
|
-
while (i < lines.length) {
|
|
106
|
-
const line = lines[i];
|
|
107
|
-
|
|
108
|
-
// ---- Fenced code block ----
|
|
109
|
-
if (line.startsWith("```")) {
|
|
110
|
-
const lang = line.slice(3).trim();
|
|
111
|
-
const codeLines: string[] = [];
|
|
112
|
-
i++;
|
|
113
|
-
while (i < lines.length && !lines[i].startsWith("```")) {
|
|
114
|
-
codeLines.push(lines[i]);
|
|
115
|
-
i++;
|
|
116
|
-
}
|
|
117
|
-
i++; // skip closing ```
|
|
118
|
-
nodes.push(
|
|
119
|
-
<pre
|
|
120
|
-
key={k()}
|
|
121
|
-
className="overflow-x-auto rounded border border-gray-700/60 bg-[#0d1117] p-3 my-2"
|
|
122
|
-
>
|
|
123
|
-
<code className="text-emerald-300/90 text-[11px] font-mono whitespace-pre">
|
|
124
|
-
{lang ? (
|
|
125
|
-
<span className="block text-[10px] text-gray-600 mb-1">{lang}</span>
|
|
126
|
-
) : null}
|
|
127
|
-
{codeLines.join("\n")}
|
|
128
|
-
</code>
|
|
129
|
-
</pre>
|
|
130
|
-
);
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// ---- HR ----
|
|
135
|
-
if (/^---+$/.test(line.trim())) {
|
|
136
|
-
nodes.push(<hr key={k()} className="border-gray-700/50 my-3" />);
|
|
137
|
-
i++;
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// ---- H1 ----
|
|
142
|
-
if (line.startsWith("# ")) {
|
|
143
|
-
nodes.push(
|
|
144
|
-
<h1 key={k()} className="text-[16px] font-bold text-white mt-3 mb-1">
|
|
145
|
-
{renderInline(line.slice(2))}
|
|
146
|
-
</h1>
|
|
147
|
-
);
|
|
148
|
-
i++;
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// ---- H2 ----
|
|
153
|
-
if (line.startsWith("## ")) {
|
|
154
|
-
nodes.push(
|
|
155
|
-
<h2
|
|
156
|
-
key={k()}
|
|
157
|
-
className="text-[13px] font-bold text-cyan-300/90 mt-4 mb-0.5 pb-0.5 border-b border-gray-700/40"
|
|
158
|
-
>
|
|
159
|
-
{renderInline(line.slice(3))}
|
|
160
|
-
</h2>
|
|
161
|
-
);
|
|
162
|
-
i++;
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// ---- H3 ----
|
|
167
|
-
if (line.startsWith("### ")) {
|
|
168
|
-
nodes.push(
|
|
169
|
-
<h3 key={k()} className="text-[12px] font-semibold text-gray-200 mt-3 mb-0.5">
|
|
170
|
-
{renderInline(line.slice(4))}
|
|
171
|
-
</h3>
|
|
172
|
-
);
|
|
173
|
-
i++;
|
|
174
|
-
continue;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// ---- H4 ----
|
|
178
|
-
if (line.startsWith("#### ")) {
|
|
179
|
-
nodes.push(
|
|
180
|
-
<h4 key={k()} className="text-[11px] font-semibold text-gray-300 mt-2 mb-0.5">
|
|
181
|
-
{renderInline(line.slice(5))}
|
|
182
|
-
</h4>
|
|
183
|
-
);
|
|
184
|
-
i++;
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// ---- Table ----
|
|
189
|
-
if (line.includes("|") && i + 1 < lines.length && /^\|?[\s\-:|]+\|/.test(lines[i + 1])) {
|
|
190
|
-
const tableRows: string[] = [line];
|
|
191
|
-
i++;
|
|
192
|
-
while (i < lines.length && lines[i].includes("|")) {
|
|
193
|
-
tableRows.push(lines[i]);
|
|
194
|
-
i++;
|
|
195
|
-
}
|
|
196
|
-
if (tableRows.length >= 2) {
|
|
197
|
-
nodes.push(
|
|
198
|
-
<div key={k()} className="my-2">
|
|
199
|
-
{renderTable(tableRows)}
|
|
200
|
-
</div>
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
continue;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// ---- Checkbox list items ----
|
|
207
|
-
if (/^- \[x\]/i.test(line)) {
|
|
208
|
-
nodes.push(
|
|
209
|
-
<div key={k()} className="flex items-start gap-1.5 text-[12px] text-gray-300 pl-2">
|
|
210
|
-
<span className="text-emerald-400 shrink-0">✓</span>
|
|
211
|
-
<span>{renderInline(line.slice(6).trim())}</span>
|
|
212
|
-
</div>
|
|
213
|
-
);
|
|
214
|
-
i++;
|
|
215
|
-
continue;
|
|
216
|
-
}
|
|
217
|
-
if (/^- \[ \]/.test(line)) {
|
|
218
|
-
nodes.push(
|
|
219
|
-
<div key={k()} className="flex items-start gap-1.5 text-[12px] text-gray-400 pl-2">
|
|
220
|
-
<span className="text-gray-600 shrink-0">○</span>
|
|
221
|
-
<span>{renderInline(line.slice(6).trim())}</span>
|
|
222
|
-
</div>
|
|
223
|
-
);
|
|
224
|
-
i++;
|
|
225
|
-
continue;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// ---- Bullet list ----
|
|
229
|
-
if (/^(\s*)([-*])\s/.test(line)) {
|
|
230
|
-
const listItems: React.ReactNode[] = [];
|
|
231
|
-
while (i < lines.length && /^(\s*)([-*])\s/.test(lines[i])) {
|
|
232
|
-
const indent = lines[i].match(/^(\s*)/)?.[1].length ?? 0;
|
|
233
|
-
const text = lines[i].replace(/^\s*[-*]\s/, "");
|
|
234
|
-
listItems.push(
|
|
235
|
-
<li
|
|
236
|
-
key={k()}
|
|
237
|
-
style={{ paddingLeft: indent > 0 ? `${indent * 8}px` : undefined }}
|
|
238
|
-
className="flex items-start gap-1.5"
|
|
239
|
-
>
|
|
240
|
-
<span className="text-gray-600 shrink-0 mt-0.5">•</span>
|
|
241
|
-
<span>{renderInline(text)}</span>
|
|
242
|
-
</li>
|
|
243
|
-
);
|
|
244
|
-
i++;
|
|
245
|
-
}
|
|
246
|
-
nodes.push(
|
|
247
|
-
<ul key={k()} className="space-y-0.5 text-[12px] text-gray-300 my-1">
|
|
248
|
-
{listItems}
|
|
249
|
-
</ul>
|
|
250
|
-
);
|
|
251
|
-
continue;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// ---- Numbered list ----
|
|
255
|
-
if (/^\d+\.\s/.test(line)) {
|
|
256
|
-
const listItems: React.ReactNode[] = [];
|
|
257
|
-
let num = 1;
|
|
258
|
-
while (i < lines.length && /^\d+\.\s/.test(lines[i])) {
|
|
259
|
-
const text = lines[i].replace(/^\d+\.\s/, "");
|
|
260
|
-
listItems.push(
|
|
261
|
-
<li key={k()} className="flex items-start gap-1.5">
|
|
262
|
-
<span className="text-gray-500 shrink-0 tabular-nums">{num}.</span>
|
|
263
|
-
<span>{renderInline(text)}</span>
|
|
264
|
-
</li>
|
|
265
|
-
);
|
|
266
|
-
i++;
|
|
267
|
-
num++;
|
|
268
|
-
}
|
|
269
|
-
nodes.push(
|
|
270
|
-
<ol key={k()} className="space-y-0.5 text-[12px] text-gray-300 my-1">
|
|
271
|
-
{listItems}
|
|
272
|
-
</ol>
|
|
273
|
-
);
|
|
274
|
-
continue;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// ---- Blank line ----
|
|
278
|
-
if (line.trim() === "") {
|
|
279
|
-
i++;
|
|
280
|
-
continue;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// ---- Regular paragraph ----
|
|
284
|
-
nodes.push(
|
|
285
|
-
<p key={k()} className="text-gray-300 text-[12px] leading-relaxed">
|
|
286
|
-
{renderInline(line)}
|
|
287
|
-
</p>
|
|
288
|
-
);
|
|
289
|
-
i++;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
return nodes;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// ---------------------------------------------------------------------------
|
|
296
|
-
// Public component
|
|
297
|
-
// ---------------------------------------------------------------------------
|
|
298
|
-
export function MarkdownView({ source }: { source: string }) {
|
|
299
|
-
// Strip YAML frontmatter (between --- ... ---)
|
|
300
|
-
const content = source.replace(/^---[\s\S]*?---\n+/, "").trim();
|
|
301
|
-
return (
|
|
302
|
-
<div className="space-y-1.5 text-[12px] leading-relaxed">
|
|
303
|
-
{renderBlocks(content)}
|
|
304
|
-
</div>
|
|
305
|
-
);
|
|
306
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { ROOM_RECTS, CORRIDOR_RECTS } from "./iso";
|
|
2
|
-
import { doorsForRoom } from "./furniture";
|
|
3
|
-
import type { RoomId } from "./types";
|
|
4
|
-
|
|
5
|
-
export type Zone = RoomId | "corridor" | "outdoor";
|
|
6
|
-
|
|
7
|
-
export function zoneAt(x: number, z: number): Zone {
|
|
8
|
-
for (const r of ROOM_RECTS) {
|
|
9
|
-
if (x >= r.wx && x <= r.wx + r.ww && z >= r.wy && z <= r.wy + r.wh) {
|
|
10
|
-
return r.id;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
for (const c of CORRIDOR_RECTS) {
|
|
14
|
-
if (x >= c.wx && x <= c.wx + c.ww && z >= c.wy && z <= c.wy + c.wh) {
|
|
15
|
-
return "corridor";
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
return "outdoor";
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Returns the corridor-side foot of the room's primary door — i.e. a point
|
|
22
|
-
// just outside the wall, so a minifig walking to it leaves the room cleanly
|
|
23
|
-
// through the door opening.
|
|
24
|
-
export function exitWaypoint(roomId: RoomId): [number, number] | null {
|
|
25
|
-
const rect = ROOM_RECTS.find((r) => r.id === roomId);
|
|
26
|
-
if (!rect) return null;
|
|
27
|
-
const doors = doorsForRoom(rect);
|
|
28
|
-
if (doors.length === 0) return null;
|
|
29
|
-
// Pick the door that points "down" (toward south corridor) when available;
|
|
30
|
-
// it gives the most natural look because all rooms have a south or north
|
|
31
|
-
// corridor adjacency.
|
|
32
|
-
const preferred =
|
|
33
|
-
doors.find((d) => d.side === "south") ??
|
|
34
|
-
doors.find((d) => d.side === "north") ??
|
|
35
|
-
doors[0];
|
|
36
|
-
const offset = 0.6;
|
|
37
|
-
switch (preferred.side) {
|
|
38
|
-
case "north":
|
|
39
|
-
return [preferred.position[0], preferred.position[1] - offset];
|
|
40
|
-
case "south":
|
|
41
|
-
return [preferred.position[0], preferred.position[1] + offset];
|
|
42
|
-
case "east":
|
|
43
|
-
return [preferred.position[0] + offset, preferred.position[1]];
|
|
44
|
-
case "west":
|
|
45
|
-
return [preferred.position[0] - offset, preferred.position[1]];
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Plans a wall-respecting path from `from` to `to`.
|
|
50
|
-
// Rule: leaving a room or entering a room must pass through that room's door.
|
|
51
|
-
// Movement within the same zone (or between corridor / outdoor) is direct.
|
|
52
|
-
export function planPath(
|
|
53
|
-
from: [number, number],
|
|
54
|
-
to: [number, number]
|
|
55
|
-
): Array<[number, number]> {
|
|
56
|
-
const fromZone = zoneAt(from[0], from[1]);
|
|
57
|
-
const toZone = zoneAt(to[0], to[1]);
|
|
58
|
-
const waypoints: Array<[number, number]> = [];
|
|
59
|
-
|
|
60
|
-
const isRoom = (z: Zone): z is RoomId => z !== "corridor" && z !== "outdoor";
|
|
61
|
-
|
|
62
|
-
if (isRoom(fromZone) && fromZone !== toZone) {
|
|
63
|
-
const exit = exitWaypoint(fromZone);
|
|
64
|
-
if (exit) waypoints.push(exit);
|
|
65
|
-
}
|
|
66
|
-
if (isRoom(toZone) && fromZone !== toZone) {
|
|
67
|
-
const entry = exitWaypoint(toZone);
|
|
68
|
-
if (entry) waypoints.push(entry);
|
|
69
|
-
}
|
|
70
|
-
waypoints.push(to);
|
|
71
|
-
return waypoints;
|
|
72
|
-
}
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import type { AgentId, Dept, MinifigState } from "./types";
|
|
2
|
-
|
|
3
|
-
const AGENT_ALIASES: Record<string, AgentId[]> = {
|
|
4
|
-
ceo: ["dispatcher", "brainstormer"],
|
|
5
|
-
dispatcher: ["dispatcher"],
|
|
6
|
-
brainstormer: ["brainstormer"],
|
|
7
|
-
coo: ["planner", "coo-developer", "documentationer"],
|
|
8
|
-
planner: ["planner"],
|
|
9
|
-
cdo: ["generator-designer"],
|
|
10
|
-
cto: ["cto", "conductor", "generator-backend", "generator-frontend", "generator-designer", "generator-devops"],
|
|
11
|
-
cqo: ["cqo", "evaluator-functional", "evaluator-visual", "evaluator-code-quality", "evaluator-architecture", "evaluator-security"],
|
|
12
|
-
ops: ["service-ops"],
|
|
13
|
-
"service-ops": ["service-ops"],
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export interface ProgressSlice {
|
|
17
|
-
current_agent?: string | null;
|
|
18
|
-
next_agent?: string | null;
|
|
19
|
-
agent_status?: string;
|
|
20
|
-
failure?: { agent?: string | null; message?: string | null } | null;
|
|
21
|
-
meetings?: { active?: string[] };
|
|
22
|
-
service_ops?: {
|
|
23
|
-
incident?: { open?: Array<{ id: string; dept?: string }> };
|
|
24
|
-
monitor?: { stream_active?: boolean; stream_target?: string | null };
|
|
25
|
-
requested_mode?: string | null;
|
|
26
|
-
};
|
|
27
|
-
// v6 NEXUS parallel mode (CLAUDE.md §Parallel Tracks). When tracks have
|
|
28
|
-
// owners with status=running, those agents are concurrently working —
|
|
29
|
-
// not just current_agent. The dashboard treats every running owner as
|
|
30
|
-
// typing so the parallel reality is visible.
|
|
31
|
-
conductor?: {
|
|
32
|
-
state?: string;
|
|
33
|
-
current_action?: string | null;
|
|
34
|
-
tracks?: Array<{ owner?: string; status?: string }>;
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface DeriveContext {
|
|
39
|
-
// Distinct departments with at least one open incident — pre-computed by
|
|
40
|
-
// harness-state.ts so the mapping doesn't iterate the array per agent.
|
|
41
|
-
incidentDepts?: Set<string>;
|
|
42
|
-
// The agent's own department (passed in because we map by department, not
|
|
43
|
-
// by the bare AgentId).
|
|
44
|
-
dept?: Dept | "Operations";
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// `conductor.current_action` carries strings like:
|
|
48
|
-
// "spawn:generator-frontend"
|
|
49
|
-
// "spawn:coo-developer:F12-006"
|
|
50
|
-
// "spawn:generator-backend (foundation F-101)"
|
|
51
|
-
// Pull out the agent id from the head.
|
|
52
|
-
export function parseSpawnAgent(action: string | null | undefined): string | null {
|
|
53
|
-
if (!action) return null;
|
|
54
|
-
const m = /^spawn:([a-z][a-z0-9-]*)/i.exec(action);
|
|
55
|
-
return m ? m[1].toLowerCase() : null;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function agentMatches(agentId: string, raw: string | null | undefined): boolean {
|
|
59
|
-
if (!raw) return false;
|
|
60
|
-
const key = raw.toLowerCase();
|
|
61
|
-
if (key === agentId) return true;
|
|
62
|
-
return AGENT_ALIASES[key]?.includes(agentId as AgentId) ?? false;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function isAgentRunningTrack(
|
|
66
|
-
agentId: string,
|
|
67
|
-
conductor: ProgressSlice["conductor"]
|
|
68
|
-
): boolean {
|
|
69
|
-
const tracks = conductor?.tracks ?? [];
|
|
70
|
-
return tracks.some(
|
|
71
|
-
(t) =>
|
|
72
|
-
agentMatches(agentId, t.owner) &&
|
|
73
|
-
(t.status === "running" || t.status === "in_progress" || t.status === "active")
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function isAgentQueuedTrack(
|
|
78
|
-
agentId: string,
|
|
79
|
-
conductor: ProgressSlice["conductor"]
|
|
80
|
-
): boolean {
|
|
81
|
-
const tracks = conductor?.tracks ?? [];
|
|
82
|
-
return tracks.some((t) => agentMatches(agentId, t.owner) && t.status === "pending");
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Priority: red-alert > talking > typing > queued > idle.
|
|
86
|
-
// red-alert wins because incidents/failures must dominate the visual field.
|
|
87
|
-
// talking wins over typing because meetings teleport overrides desk presence.
|
|
88
|
-
// typing now reflects v6 NEXUS *parallel* mode — multiple agents can be
|
|
89
|
-
// concurrently working, all of them get the typing aura.
|
|
90
|
-
// queued makes autonomous handoff visible: when the company already assigned
|
|
91
|
-
// a next department, that department sits at its desk even before the worker
|
|
92
|
-
// process emits its first log line.
|
|
93
|
-
export function deriveMinifigState(
|
|
94
|
-
agentId: AgentId,
|
|
95
|
-
progress: ProgressSlice | null,
|
|
96
|
-
ctx: DeriveContext = {}
|
|
97
|
-
): MinifigState {
|
|
98
|
-
if (!progress) return "idle";
|
|
99
|
-
if (agentMatches(agentId, progress.failure?.agent)) return "red-alert";
|
|
100
|
-
if (ctx.dept && ctx.incidentDepts && ctx.incidentDepts.size > 0) {
|
|
101
|
-
if (ctx.dept === "Operations" || ctx.incidentDepts.has(ctx.dept)) {
|
|
102
|
-
return "red-alert";
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
const inMeeting = progress.meetings?.active?.some((id) => agentMatches(agentId, id)) ?? false;
|
|
106
|
-
if (inMeeting) return "talking";
|
|
107
|
-
|
|
108
|
-
// Parallel-typing signals (any one of these means the agent is working).
|
|
109
|
-
if (
|
|
110
|
-
agentMatches(agentId, progress.current_agent) &&
|
|
111
|
-
progress.agent_status === "running"
|
|
112
|
-
) {
|
|
113
|
-
return "typing";
|
|
114
|
-
}
|
|
115
|
-
if (isAgentRunningTrack(agentId, progress.conductor)) {
|
|
116
|
-
return "typing";
|
|
117
|
-
}
|
|
118
|
-
if (agentMatches(agentId, parseSpawnAgent(progress.conductor?.current_action))) {
|
|
119
|
-
return "typing";
|
|
120
|
-
}
|
|
121
|
-
// G-006: service-ops co-spawned in monitor mode is actively streaming.
|
|
122
|
-
if (
|
|
123
|
-
agentId === "service-ops" &&
|
|
124
|
-
progress.service_ops?.monitor?.stream_active === true
|
|
125
|
-
) {
|
|
126
|
-
return "typing";
|
|
127
|
-
}
|
|
128
|
-
if (
|
|
129
|
-
agentMatches(agentId, progress.next_agent) &&
|
|
130
|
-
progress.agent_status !== "blocked" &&
|
|
131
|
-
progress.agent_status !== "failed"
|
|
132
|
-
) {
|
|
133
|
-
return "queued";
|
|
134
|
-
}
|
|
135
|
-
if (isAgentQueuedTrack(agentId, progress.conductor)) {
|
|
136
|
-
return "queued";
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return "idle";
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export function pickTalkingPreview(agentId: AgentId): string {
|
|
143
|
-
// Sprint 2 will pull this from progress.log; for Sprint 1 we surface a stub
|
|
144
|
-
// so the speech-bubble component has a sensible default.
|
|
145
|
-
return `${agentId}…`;
|
|
146
|
-
}
|