@walwal-harness/cli 7.1.15 → 7.1.17
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 +2 -0
- package/apps/harness-dashboard/components/Scene.tsx +1 -0
- package/apps/harness-dashboard/components/drawer/MissionFlowTab.tsx +56 -4
- package/apps/harness-dashboard/lib/__tests__/state-mapping.test.ts +24 -0
- package/apps/harness-dashboard/lib/harness-state.ts +50 -3
- package/apps/harness-dashboard/lib/state-mapping.ts +31 -11
- package/apps/harness-dashboard/lib/types.ts +14 -0
- package/bin/init.js +8 -0
- package/package.json +1 -1
- package/scripts/harness-user-prompt-submit.sh +22 -0
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.17 | Dashboard: fix /submission and /hot-fix missions displaying as /goal; runtime strip shows live owner command in mission flow tab |
|
|
227
|
+
| 7.1.16 | `migrate` now always fully replaces /goal, /submission, /hot-fix commands regardless of current state |
|
|
226
228
|
| 7.1.15 | Dashboard: reset embedded View Doc state when the selected mission or document target changes |
|
|
227
229
|
| 7.1.14 | Owner acceptance gate: Owner is final reviewer, not tester; dashboard org-node clicks initialize the detail pane |
|
|
228
230
|
| 7.1.13 | Dashboard: full-viewport split workspace, hidden pane scrollbars, and detail scroll reset on selection |
|
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
|
-
import type { MissionDoc, OwnerPromptEntry } from "@/lib/types";
|
|
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,7 +223,53 @@ function WorkerCard({
|
|
|
222
223
|
// ---------------------------------------------------------------------------
|
|
223
224
|
// Main component
|
|
224
225
|
// ---------------------------------------------------------------------------
|
|
225
|
-
|
|
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
|
|
|
228
275
|
useEffect(() => {
|
|
@@ -231,8 +278,11 @@ export function MissionFlowTab({ mission, ownerHistory }: Props) {
|
|
|
231
278
|
|
|
232
279
|
if (!mission) {
|
|
233
280
|
return (
|
|
234
|
-
<div className="
|
|
235
|
-
|
|
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>
|
|
236
286
|
</div>
|
|
237
287
|
);
|
|
238
288
|
}
|
|
@@ -253,6 +303,7 @@ export function MissionFlowTab({ mission, ownerHistory }: Props) {
|
|
|
253
303
|
|
|
254
304
|
return (
|
|
255
305
|
<div className="space-y-3">
|
|
306
|
+
<RuntimeStrip runtime={runtime} />
|
|
256
307
|
<button
|
|
257
308
|
type="button"
|
|
258
309
|
onClick={() => setDocView("overview")}
|
|
@@ -339,6 +390,7 @@ export function MissionFlowTab({ mission, ownerHistory }: Props) {
|
|
|
339
390
|
|
|
340
391
|
return (
|
|
341
392
|
<div className="space-y-3">
|
|
393
|
+
<RuntimeStrip runtime={runtime} />
|
|
342
394
|
{/* Mission header */}
|
|
343
395
|
<div>
|
|
344
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,7 +968,25 @@ function buildDashboard(rootDir: string, progress: RawProgress | null): Operatio
|
|
|
953
968
|
};
|
|
954
969
|
}
|
|
955
970
|
|
|
956
|
-
function
|
|
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
|
+
|
|
989
|
+
function readMissions(rootDir: string, progress: RawProgress | null = null, limit = 15): MissionDoc[] {
|
|
957
990
|
const docsDir = path.join(rootDir, ".harness", "documents");
|
|
958
991
|
if (!existsSync(docsDir)) return [];
|
|
959
992
|
|
|
@@ -1050,6 +1083,19 @@ function readMissions(rootDir: string, limit = 15): MissionDoc[] {
|
|
|
1050
1083
|
.sort((a, b) => new Date(b.ts).getTime() - new Date(a.ts).getTime())
|
|
1051
1084
|
.slice(0, limit);
|
|
1052
1085
|
|
|
1086
|
+
// When the CEO updates a goal directory in-place for a submission or hot-fix
|
|
1087
|
+
// (instead of creating a submission-* or hotfix-* subdirectory), the most
|
|
1088
|
+
// recently modified mission still has type "goal" from its directory name.
|
|
1089
|
+
// Override it using owner_prompt.command so the display reflects the actual
|
|
1090
|
+
// command the Owner issued, not just the directory prefix.
|
|
1091
|
+
const cmd = progress?.owner_prompt?.command;
|
|
1092
|
+
if (missions.length > 0 && (cmd === "submission" || cmd === "hot-fix")) {
|
|
1093
|
+
const top = missions[0];
|
|
1094
|
+
if (top.type === "goal") {
|
|
1095
|
+
missions[0] = { ...top, type: cmd === "hot-fix" ? "hotfix" : "submission" };
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1053
1099
|
return missions;
|
|
1054
1100
|
}
|
|
1055
1101
|
|
|
@@ -1132,7 +1178,7 @@ export function readHarnessState(rootDir: string): HarnessSnapshot {
|
|
|
1132
1178
|
};
|
|
1133
1179
|
const snapshot = emptySnapshot(banner, rootDir);
|
|
1134
1180
|
snapshot.archive = buildArchive(rootDir);
|
|
1135
|
-
snapshot.missions = readMissions(rootDir);
|
|
1181
|
+
snapshot.missions = readMissions(rootDir, null);
|
|
1136
1182
|
snapshot.ownerHistory = readOwnerHistory(rootDir);
|
|
1137
1183
|
snapshot.gotchas = readGotchas(rootDir);
|
|
1138
1184
|
return snapshot;
|
|
@@ -1183,7 +1229,8 @@ export function readHarnessState(rootDir: string): HarnessSnapshot {
|
|
|
1183
1229
|
evalScores,
|
|
1184
1230
|
errorBanner: null,
|
|
1185
1231
|
dashboard,
|
|
1186
|
-
|
|
1232
|
+
runtime: buildRuntime(progress),
|
|
1233
|
+
missions: readMissions(rootDir, progress),
|
|
1187
1234
|
ownerHistory: readOwnerHistory(rootDir),
|
|
1188
1235
|
gotchas: readGotchas(rootDir),
|
|
1189
1236
|
};
|
|
@@ -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?:
|
|
5
|
-
next_agent?:
|
|
17
|
+
current_agent?: string | null;
|
|
18
|
+
next_agent?: string | null;
|
|
6
19
|
agent_status?: string;
|
|
7
|
-
failure?: { agent?:
|
|
8
|
-
meetings?: { active?:
|
|
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
|
|
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
|
|
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
|
|
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?.
|
|
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
|
|
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)
|
|
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
|
|
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.
|
|
3
|
+
"version": "7.1.17",
|
|
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
|
|