@walwal-harness/cli 7.1.39 → 7.1.41

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