@walwal-harness/cli 7.1.14 → 7.1.16

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/README.md CHANGED
@@ -223,6 +223,8 @@ harness-hiring → register and onboard worker
223
223
 
224
224
  | Version | Summary |
225
225
  |---|---|
226
+ | 7.1.16 | `migrate` now always fully replaces /goal, /submission, /hot-fix commands regardless of current state |
227
+ | 7.1.15 | Dashboard: reset embedded View Doc state when the selected mission or document target changes |
226
228
  | 7.1.14 | Owner acceptance gate: Owner is final reviewer, not tester; dashboard org-node clicks initialize the detail pane |
227
229
  | 7.1.13 | Dashboard: full-viewport split workspace, hidden pane scrollbars, and detail scroll reset on selection |
228
230
  | 7.1.12 | Dashboard: split workspace panes now scroll independently |
@@ -155,6 +155,7 @@ export function Scene({ snapshot: initial, lang = "ko" }: SceneProps) {
155
155
  <MissionFlowTab
156
156
  mission={selectedMission ?? snapshot.missions?.[0] ?? null}
157
157
  ownerHistory={snapshot.ownerHistory ?? []}
158
+ runtime={snapshot.runtime}
158
159
  />
159
160
  )}
160
161
  {drawerTab === "history" && (
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { useState } from "react";
2
+ import { useEffect, useState } from "react";
3
3
  import type { MissionDoc } from "@/lib/types";
4
4
  import { MarkdownView } from "@/lib/markdown";
5
5
 
@@ -15,6 +15,10 @@ export function MissionDocTab({ missions, role, workerName, fromLabel, toLabel }
15
15
  const [selectedIdx, setSelectedIdx] = useState(0);
16
16
  const mission = missions[selectedIdx];
17
17
 
18
+ useEffect(() => {
19
+ setSelectedIdx(0);
20
+ }, [role, workerName]);
21
+
18
22
  if (!missions.length) {
19
23
  return (
20
24
  <div className="text-gray-500">
@@ -1,6 +1,6 @@
1
1
  "use client";
2
- import { useState } from "react";
3
- import type { MissionDoc, OwnerPromptEntry } from "@/lib/types";
2
+ import { useEffect, useState } from "react";
3
+ import type { MissionDoc, OwnerPromptEntry, RuntimeSnapshot } from "@/lib/types";
4
4
  import { MarkdownView } from "@/lib/markdown";
5
5
 
6
6
  type DocView = "overview" | "ceo" | "cto" | "cqo" | "coo" | "cdo" | "ops" | string;
@@ -8,6 +8,7 @@ type DocView = "overview" | "ceo" | "cto" | "cqo" | "coo" | "cdo" | "ops" | stri
8
8
  interface Props {
9
9
  mission: MissionDoc | null;
10
10
  ownerHistory: OwnerPromptEntry[];
11
+ runtime: RuntimeSnapshot;
11
12
  }
12
13
 
13
14
  // ---------------------------------------------------------------------------
@@ -222,13 +223,66 @@ function WorkerCard({
222
223
  // ---------------------------------------------------------------------------
223
224
  // Main component
224
225
  // ---------------------------------------------------------------------------
225
- export function MissionFlowTab({ mission, ownerHistory }: Props) {
226
+ function RuntimeStrip({ runtime }: { runtime: RuntimeSnapshot }) {
227
+ const isActive = runtime.agentStatus === "running";
228
+ const command = runtime.ownerPrompt?.command ?? "input";
229
+ const summary = runtime.ownerPrompt?.summary ?? null;
230
+ return (
231
+ <div
232
+ data-testid="runtime-strip"
233
+ data-current-agent={runtime.currentAgent ?? ""}
234
+ data-agent-status={runtime.agentStatus}
235
+ className={`rounded border px-2.5 py-2 ${
236
+ isActive
237
+ ? "border-cyan-400/30 bg-cyan-400/10"
238
+ : runtime.agentStatus === "blocked" || runtime.agentStatus === "failed"
239
+ ? "border-rose-400/30 bg-rose-400/10"
240
+ : "border-gray-700/60 bg-black/20"
241
+ }`}
242
+ >
243
+ <div className="flex flex-wrap items-center gap-1.5">
244
+ <span className="font-mono text-[9px] uppercase tracking-[0.18em] text-gray-500">
245
+ live runtime
246
+ </span>
247
+ <StepChip role={runtime.currentAgent ?? "worker"} label={runtime.currentAgent ?? "none"} />
248
+ <span
249
+ className={`font-mono text-[9px] ${
250
+ isActive ? "text-cyan-300" : "text-gray-400"
251
+ }`}
252
+ >
253
+ {runtime.agentStatus}
254
+ </span>
255
+ {runtime.nextAgent && (
256
+ <span className="font-mono text-[9px] text-gray-500">
257
+ next: {runtime.nextAgent}
258
+ </span>
259
+ )}
260
+ </div>
261
+ {summary && (
262
+ <p className="mt-1.5 text-[10px] leading-relaxed text-gray-400 line-clamp-2">
263
+ <span className="font-mono text-gray-500">{command}</span>
264
+ <span className="text-gray-600"> · </span>
265
+ {summary}
266
+ </p>
267
+ )}
268
+ </div>
269
+ );
270
+ }
271
+
272
+ export function MissionFlowTab({ mission, ownerHistory, runtime }: Props) {
226
273
  const [docView, setDocView] = useState<DocView>("overview");
227
274
 
275
+ useEffect(() => {
276
+ setDocView("overview");
277
+ }, [mission?.missionId]);
278
+
228
279
  if (!mission) {
229
280
  return (
230
- <div className="text-gray-500 text-xs">
231
- No mission selected. Click a mission from the timeline below.
281
+ <div className="space-y-3">
282
+ <RuntimeStrip runtime={runtime} />
283
+ <div className="text-gray-500 text-xs">
284
+ No mission selected. Click a mission from the timeline below.
285
+ </div>
232
286
  </div>
233
287
  );
234
288
  }
@@ -249,6 +303,7 @@ export function MissionFlowTab({ mission, ownerHistory }: Props) {
249
303
 
250
304
  return (
251
305
  <div className="space-y-3">
306
+ <RuntimeStrip runtime={runtime} />
252
307
  <button
253
308
  type="button"
254
309
  onClick={() => setDocView("overview")}
@@ -335,6 +390,7 @@ export function MissionFlowTab({ mission, ownerHistory }: Props) {
335
390
 
336
391
  return (
337
392
  <div className="space-y-3">
393
+ <RuntimeStrip runtime={runtime} />
338
394
  {/* Mission header */}
339
395
  <div>
340
396
  <div className="flex items-center gap-2 flex-wrap mb-1">
@@ -15,6 +15,30 @@ describe("deriveMinifigState", () => {
15
15
  ).toBe("typing");
16
16
  });
17
17
 
18
+ it("maps v7 CEO alias to dispatcher and brainstormer", () => {
19
+ const progress = {
20
+ current_agent: "ceo",
21
+ agent_status: "running",
22
+ };
23
+ expect(deriveMinifigState("dispatcher", progress)).toBe("typing");
24
+ expect(deriveMinifigState("brainstormer", progress)).toBe("typing");
25
+ });
26
+
27
+ it("maps v7 CXX aliases to their dashboard departments", () => {
28
+ expect(
29
+ deriveMinifigState("generator-designer", {
30
+ current_agent: "cdo",
31
+ agent_status: "running",
32
+ })
33
+ ).toBe("typing");
34
+ expect(
35
+ deriveMinifigState("service-ops", {
36
+ next_agent: "ops",
37
+ agent_status: "pending",
38
+ })
39
+ ).toBe("queued");
40
+ });
41
+
18
42
  it("does not return typing when agent_status is not running", () => {
19
43
  expect(
20
44
  deriveMinifigState("generator-backend", {
@@ -33,6 +33,7 @@ import type {
33
33
  RoomId,
34
34
  RoomMetrics,
35
35
  RoomState,
36
+ RuntimeSnapshot,
36
37
  TrackStatus,
37
38
  WorkerDocEntry,
38
39
  WorkerSnapshot,
@@ -50,6 +51,13 @@ interface RawProgress {
50
51
  current_agent?: AgentId | null;
51
52
  next_agent?: AgentId | null;
52
53
  agent_status?: string;
54
+ updated_at?: string | null;
55
+ owner_prompt?: {
56
+ command?: string;
57
+ summary?: string;
58
+ received_at?: string | null;
59
+ status?: string;
60
+ } | null;
53
61
  pipeline?: Pipeline;
54
62
  failure?: { agent?: AgentId | null; message?: string | null; location?: string | null; retry_target?: AgentId | null } | null;
55
63
  meetings?: {
@@ -205,6 +213,13 @@ function emptySnapshot(banner: ErrorBanner | null = null, rootDir?: string): Har
205
213
  evalScores: null,
206
214
  errorBanner: banner,
207
215
  dashboard: { workers: [], features: [], recentMeetings: [], opsHealth: [], envFiles: [] },
216
+ runtime: {
217
+ currentAgent: null,
218
+ agentStatus: "unknown",
219
+ nextAgent: null,
220
+ updatedAt: null,
221
+ ownerPrompt: null,
222
+ },
208
223
  missions: [],
209
224
  ownerHistory: [],
210
225
  gotchas: [],
@@ -953,6 +968,24 @@ function buildDashboard(rootDir: string, progress: RawProgress | null): Operatio
953
968
  };
954
969
  }
955
970
 
971
+ function buildRuntime(progress: RawProgress | null): RuntimeSnapshot {
972
+ const ownerPrompt = progress?.owner_prompt
973
+ ? {
974
+ command: progress.owner_prompt.command ?? "input",
975
+ summary: progress.owner_prompt.summary ?? "",
976
+ receivedAt: progress.owner_prompt.received_at ?? null,
977
+ status: progress.owner_prompt.status ?? "unknown",
978
+ }
979
+ : null;
980
+ return {
981
+ currentAgent: progress?.current_agent ?? null,
982
+ agentStatus: progress?.agent_status ?? "unknown",
983
+ nextAgent: progress?.next_agent ?? null,
984
+ updatedAt: progress?.updated_at ?? null,
985
+ ownerPrompt,
986
+ };
987
+ }
988
+
956
989
  function readMissions(rootDir: string, limit = 15): MissionDoc[] {
957
990
  const docsDir = path.join(rootDir, ".harness", "documents");
958
991
  if (!existsSync(docsDir)) return [];
@@ -1183,6 +1216,7 @@ export function readHarnessState(rootDir: string): HarnessSnapshot {
1183
1216
  evalScores,
1184
1217
  errorBanner: null,
1185
1218
  dashboard,
1219
+ runtime: buildRuntime(progress),
1186
1220
  missions: readMissions(rootDir),
1187
1221
  ownerHistory: readOwnerHistory(rootDir),
1188
1222
  gotchas: readGotchas(rootDir),
@@ -1,11 +1,24 @@
1
1
  import type { AgentId, Dept, MinifigState } from "./types";
2
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
+
3
16
  export interface ProgressSlice {
4
- current_agent?: AgentId | null;
5
- next_agent?: AgentId | null;
17
+ current_agent?: string | null;
18
+ next_agent?: string | null;
6
19
  agent_status?: string;
7
- failure?: { agent?: AgentId | null; message?: string | null } | null;
8
- meetings?: { active?: AgentId[] };
20
+ failure?: { agent?: string | null; message?: string | null } | null;
21
+ meetings?: { active?: string[] };
9
22
  service_ops?: {
10
23
  incident?: { open?: Array<{ id: string; dept?: string }> };
11
24
  monitor?: { stream_active?: boolean; stream_target?: string | null };
@@ -42,6 +55,13 @@ export function parseSpawnAgent(action: string | null | undefined): string | nul
42
55
  return m ? m[1].toLowerCase() : null;
43
56
  }
44
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
+
45
65
  function isAgentRunningTrack(
46
66
  agentId: string,
47
67
  conductor: ProgressSlice["conductor"]
@@ -49,7 +69,7 @@ function isAgentRunningTrack(
49
69
  const tracks = conductor?.tracks ?? [];
50
70
  return tracks.some(
51
71
  (t) =>
52
- t.owner === agentId &&
72
+ agentMatches(agentId, t.owner) &&
53
73
  (t.status === "running" || t.status === "in_progress" || t.status === "active")
54
74
  );
55
75
  }
@@ -59,7 +79,7 @@ function isAgentQueuedTrack(
59
79
  conductor: ProgressSlice["conductor"]
60
80
  ): boolean {
61
81
  const tracks = conductor?.tracks ?? [];
62
- return tracks.some((t) => t.owner === agentId && t.status === "pending");
82
+ return tracks.some((t) => agentMatches(agentId, t.owner) && t.status === "pending");
63
83
  }
64
84
 
65
85
  // Priority: red-alert > talking > typing > queued > idle.
@@ -76,18 +96,18 @@ export function deriveMinifigState(
76
96
  ctx: DeriveContext = {}
77
97
  ): MinifigState {
78
98
  if (!progress) return "idle";
79
- if (progress.failure?.agent === agentId) return "red-alert";
99
+ if (agentMatches(agentId, progress.failure?.agent)) return "red-alert";
80
100
  if (ctx.dept && ctx.incidentDepts && ctx.incidentDepts.size > 0) {
81
101
  if (ctx.dept === "Operations" || ctx.incidentDepts.has(ctx.dept)) {
82
102
  return "red-alert";
83
103
  }
84
104
  }
85
- const inMeeting = progress.meetings?.active?.includes(agentId) ?? false;
105
+ const inMeeting = progress.meetings?.active?.some((id) => agentMatches(agentId, id)) ?? false;
86
106
  if (inMeeting) return "talking";
87
107
 
88
108
  // Parallel-typing signals (any one of these means the agent is working).
89
109
  if (
90
- progress.current_agent === agentId &&
110
+ agentMatches(agentId, progress.current_agent) &&
91
111
  progress.agent_status === "running"
92
112
  ) {
93
113
  return "typing";
@@ -95,7 +115,7 @@ export function deriveMinifigState(
95
115
  if (isAgentRunningTrack(agentId, progress.conductor)) {
96
116
  return "typing";
97
117
  }
98
- if (parseSpawnAgent(progress.conductor?.current_action) === agentId) {
118
+ if (agentMatches(agentId, parseSpawnAgent(progress.conductor?.current_action))) {
99
119
  return "typing";
100
120
  }
101
121
  // G-006: service-ops co-spawned in monitor mode is actively streaming.
@@ -106,7 +126,7 @@ export function deriveMinifigState(
106
126
  return "typing";
107
127
  }
108
128
  if (
109
- progress.next_agent === agentId &&
129
+ agentMatches(agentId, progress.next_agent) &&
110
130
  progress.agent_status !== "blocked" &&
111
131
  progress.agent_status !== "failed"
112
132
  ) {
@@ -257,6 +257,19 @@ export interface OperationsDashboard {
257
257
  envFiles: EnvFileSummary[];
258
258
  }
259
259
 
260
+ export interface RuntimeSnapshot {
261
+ currentAgent: string | null;
262
+ agentStatus: string;
263
+ nextAgent: string | null;
264
+ updatedAt: string | null;
265
+ ownerPrompt: {
266
+ command: string;
267
+ summary: string;
268
+ receivedAt: string | null;
269
+ status: string;
270
+ } | null;
271
+ }
272
+
260
273
  export interface HarnessSnapshot {
261
274
  version: string;
262
275
  ts: string;
@@ -275,6 +288,7 @@ export interface HarnessSnapshot {
275
288
  evalScores: EvalScores | null;
276
289
  errorBanner: ErrorBanner | null;
277
290
  dashboard: OperationsDashboard;
291
+ runtime: RuntimeSnapshot;
278
292
  missions: MissionDoc[];
279
293
  ownerHistory: OwnerPromptEntry[];
280
294
  gotchas: GotchaEntry[];
package/bin/init.js CHANGED
@@ -1459,6 +1459,14 @@ function showMigrationProposal(flags) {
1459
1459
 
1460
1460
  function runMigrate(opts = {}) {
1461
1461
  const dryRun = opts.dryRun || false;
1462
+
1463
+ // Always refresh owner-facing commands (full replacement regardless of current state).
1464
+ if (dryRun) {
1465
+ log(' (dry-run) commands: /goal, /submission, /hot-fix 교체 예정');
1466
+ } else {
1467
+ installCommands();
1468
+ }
1469
+
1462
1470
  const flags = detectMigrationNeeded();
1463
1471
  const gotchaMissingTotal = Object.values(flags.gotchaMissingEntries || {}).reduce((n, a) => n + a.length, 0);
1464
1472
  const conventionMissingTotal = Object.values(flags.conventionMissingEntries || {}).reduce((n, a) => n + a.length, 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@walwal-harness/cli",
3
- "version": "7.1.14",
3
+ "version": "7.1.16",
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"
@@ -71,6 +71,28 @@ if [ -n "$PROMPT" ] && [ -d "$CWD/.harness" ]; then
71
71
  COMMAND_TYPE="hot-fix"
72
72
  fi
73
73
  echo "$(date +"%Y-%m-%d %H:%M") | user-prompt | ${COMMAND_TYPE} | ${PROMPT_SHORT}" >> "$PROGRESS_LOG"
74
+ if [ "$COMMAND_TYPE" = "goal" ] || [ "$COMMAND_TYPE" = "submission" ] || [ "$COMMAND_TYPE" = "hot-fix" ]; then
75
+ if [ -f "$CWD/.harness/progress.json" ] && command -v jq >/dev/null 2>&1; then
76
+ TMP_PROGRESS="${CWD}/.harness/progress.json.tmp.$$"
77
+ NOW_ISO=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
78
+ jq \
79
+ --arg now "$NOW_ISO" \
80
+ --arg command "$COMMAND_TYPE" \
81
+ --arg prompt "$PROMPT_SHORT" \
82
+ '
83
+ .current_agent = "ceo" |
84
+ .agent_status = "running" |
85
+ .next_agent = "ceo" |
86
+ .updated_at = $now |
87
+ .owner_prompt = {
88
+ command: $command,
89
+ summary: $prompt,
90
+ received_at: $now,
91
+ status: "routing"
92
+ }
93
+ ' "$CWD/.harness/progress.json" > "$TMP_PROGRESS" && mv "$TMP_PROGRESS" "$CWD/.harness/progress.json"
94
+ fi
95
+ fi
74
96
  fi
75
97
  fi
76
98