@walwal-harness/cli 7.1.32 → 7.1.34

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.
@@ -25,6 +25,10 @@ Before design work, read `.harness/conventions/shared.md`, `.harness/conventions
25
25
  8. Select the final direction and write a CDO preview artifact at `.harness/documents/{mission_name}/cdo/preview.html`.
26
26
  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.
27
27
 
28
+ ## Worker Activity Telemetry
29
+
30
+ Before launching any fresh worker session, update `.harness/progress.json` with `scripts/harness-progress-set.sh` so dashboards can show the worker as active. Record the worker name, owning CXX, report path, and `status:"running"` under `company_state.workers`, increment `company_state.active_workers`, and set `conductor.current_action` to `spawn:{worker-name}`. After the worker report is accepted, update that worker to `status:"complete"` and decrement `active_workers`. Do not leave `active_workers:0` while a worker session is running.
31
+
28
32
  ## Rule
29
33
 
30
34
  Design output must be usable by implementation teams, not just descriptive.
@@ -35,6 +35,10 @@ During planning, COO must determine whether the active runtime exposes MCP serve
35
35
  7. Review worker reports against the goal.
36
36
  8. Reassign work or report to CEO.
37
37
 
38
+ ## Worker Activity Telemetry
39
+
40
+ Before launching any fresh worker session, update `.harness/progress.json` with `scripts/harness-progress-set.sh` so dashboards can show the worker as active. Record the worker name, owning CXX, report path, and `status:"running"` under `company_state.workers`, increment `company_state.active_workers`, and set `conductor.current_action` to `spawn:{worker-name}`. After the worker report is accepted, update that worker to `status:"complete"` and decrement `active_workers`. Do not leave `active_workers:0` while a worker session is running.
41
+
38
42
  ## Non-Execution Rule
39
43
 
40
44
  COO must not directly produce research findings, sprint plans, feed lists, market conclusions, backtest scripts, or documentation deliverables. COO may only frame the question, select and brief workers, evaluate worker output, and record the accepted decision.
@@ -24,6 +24,10 @@ Before quality work, read `.harness/conventions/shared.md`, `.harness/convention
24
24
  7. Monitor repeated issues and promote verified lessons to `.harness/conventions`, `.harness/gotchas`, `.harness/memories`, or `.harness/shared`.
25
25
  8. Approve or reject archive based solely on worker-provided evidence and OPS runtime/watch evidence when the mission uses a runnable environment.
26
26
 
27
+ ## Worker Activity Telemetry
28
+
29
+ Before launching any fresh worker session, update `.harness/progress.json` with `scripts/harness-progress-set.sh` so dashboards can show the worker as active. Record the worker name, owning CXX, report path, and `status:"running"` under `company_state.workers`, increment `company_state.active_workers`, and set `conductor.current_action` to `spawn:{worker-name}`. After the worker report is accepted, update that worker to `status:"complete"` and decrement `active_workers`. Do not leave `active_workers:0` while a worker session is running.
30
+
27
31
  ## Hard Rules
28
32
 
29
33
  CQO must not directly execute QA, visual review, security review, performance testing, or regression checks. CQO may only define gates, select evaluators, review evidence, decide archive eligibility, and document worker names and report paths.
@@ -26,6 +26,10 @@ Before engineering work, read `.harness/conventions/shared.md`, `.harness/conven
26
26
  9. Delegate all implementation and technical deliverables to hired workers in fresh sessions.
27
27
  10. Collect reports, resolve blockers, and hand completed work to CQO.
28
28
 
29
+ ## Worker Activity Telemetry
30
+
31
+ Before launching any fresh worker session, update `.harness/progress.json` with `scripts/harness-progress-set.sh` so dashboards can show the worker as active. Record the worker name, owning CXX, report path, and `status:"running"` under `company_state.workers`, increment `company_state.active_workers`, and set `conductor.current_action` to `spawn:{worker-name}`. After the worker report is accepted, update that worker to `status:"complete"` and decrement `active_workers`. Do not leave `active_workers:0` while a worker session is running.
32
+
29
33
  ## Hard Rules
30
34
 
31
35
  CTO must not directly write code, create build scripts, choose detailed implementation content, run technical QA as the evaluator, or produce final implementation artifacts. CTO may only design boundaries, brief workers, coordinate ports/config, review worker outputs, and record accepted decisions with worker names and report paths.
@@ -66,6 +66,10 @@ After service launch, OPS continues the same monitoring duty against `runtime.pr
66
66
  10. Classify exceptions as verification, build, backend, frontend, platform, external API, infrastructure, owner-config, production, or unknown.
67
67
  11. Raise emergency events to CEO/CTO/CQO with evidence and the mapped environment record.
68
68
 
69
+ ## Worker Activity Telemetry
70
+
71
+ Before launching any fresh worker session, update `.harness/progress.json` with `scripts/harness-progress-set.sh` so dashboards can show the worker as active. Record the worker name, owning CXX, report path, and `status:"running"` under `company_state.workers`, increment `company_state.active_workers`, and set `conductor.current_action` to `spawn:{worker-name}`. After the worker report is accepted, update that worker to `status:"complete"` and decrement `active_workers`. Do not leave `active_workers:0` while a worker session is running.
72
+
69
73
  ## Required Output Sections
70
74
 
71
75
  1. Worker Task Briefs — monitoring/recovery task, capability needed, selected worker or hiring request, acceptance criteria.
package/README.md CHANGED
@@ -283,6 +283,8 @@ harness-hiring → register and onboard worker
283
283
 
284
284
  | Version | Summary |
285
285
  |---|---|
286
+ | 7.1.34 | Fix dashboard 500 when `company_state.workers` is recorded as a keyed worker map instead of an array |
287
+ | 7.1.33 | Dashboard keeps recently written worker reports active even when docmeta is present, and CXX skills now record worker activity telemetry for live dashboard state |
286
288
  | 7.1.31 | Persist dashboard heatmap activity to 7-day JSONL logs, load persisted samples in the dashboard, and backfill activity during migrate from existing mission docs/progress logs |
287
289
  | 7.1.29 | Idempotent AGENTS/CLAUDE doc merge with marked harness sections, mission lifecycle backfill for init/migrate, completion closes todos and mission-state, and Layer Activity now lists CXX/worker rows sorted by remaining work |
288
290
  | 7.1.28 | Dashboard APM rewrite: top header consolidation, Knowledge Base panel (gotchas/conventions), Layer Activity (CEO/CXX/Worker TODO·DONE·Remain via goal-scope inference), Recent Report list (worker mtime desc), Cadence strip with click-to-prompt tooltip in local timezone, heatmap click-tooltip with empty-cell guard, Document Viewer expand-to-50vw with docmeta hidden, Command Log bubble popover |
@@ -381,4 +381,67 @@ describe("readHarnessState", () => {
381
381
  active: false,
382
382
  });
383
383
  });
384
+
385
+ it("keeps recently written docmeta worker reports active without an explicit COMPLETE marker", () => {
386
+ const harnessDir = path.join(dir, ".harness");
387
+ const missionDir = path.join(harnessDir, "documents", "goal-1-dashboard");
388
+ mkdirSync(path.join(missionDir, "cto", "workers"), { recursive: true });
389
+ writeFileSync(path.join(missionDir, "ceo.md"), "# Dashboard\n");
390
+ writeFileSync(
391
+ path.join(missionDir, "cto", "workers", "frontend-worker.md"),
392
+ "---\ndocmeta:\n id: frontend-worker\n---\n\n# Worker Report\n\nEvidence is still being appended.\n"
393
+ );
394
+ writeFileSync(
395
+ path.join(harnessDir, "progress.json"),
396
+ JSON.stringify({
397
+ current_agent: "ceo",
398
+ agent_status: "running",
399
+ company_state: { active_workers: 0, workers: [] },
400
+ })
401
+ );
402
+
403
+ const workers = readHarnessState(dir).missions[0].workers;
404
+ expect(workers[0]).toMatchObject({
405
+ name: "frontend-worker",
406
+ status: "IN_PROGRESS",
407
+ active: true,
408
+ });
409
+ });
410
+
411
+ it("accepts keyed company_state.workers maps from manual CXX telemetry", () => {
412
+ const harnessDir = path.join(dir, ".harness");
413
+ const missionDir = path.join(harnessDir, "documents", "goal-1-dashboard");
414
+ mkdirSync(path.join(missionDir, "cqo", "workers"), { recursive: true });
415
+ writeFileSync(path.join(missionDir, "ceo.md"), "# Dashboard\n");
416
+ writeFileSync(
417
+ path.join(missionDir, "cqo", "workers", "evaluator.md"),
418
+ "---\ndocmeta:\n id: evaluator\n---\n\n# Worker Report\n"
419
+ );
420
+ writeFileSync(
421
+ path.join(harnessDir, "progress.json"),
422
+ JSON.stringify({
423
+ agent_status: "running",
424
+ company_state: {
425
+ active_workers: 1,
426
+ workers: {
427
+ "engineering-engineering-code-reviewer": {
428
+ owner: "cqo",
429
+ report: ".harness/documents/goal-1-dashboard/cqo/workers/evaluator.md",
430
+ status: "running",
431
+ },
432
+ },
433
+ },
434
+ })
435
+ );
436
+
437
+ const snap = readHarnessState(dir);
438
+ expect(snap.dashboard.workers[0]).toMatchObject({
439
+ agent: "engineering-engineering-code-reviewer",
440
+ status: "running",
441
+ });
442
+ expect(snap.missions[0].workers[0]).toMatchObject({
443
+ owner: "cqo",
444
+ active: true,
445
+ });
446
+ });
384
447
  });
@@ -157,6 +157,18 @@ interface RawProgress {
157
157
  spawn_status?: string;
158
158
  status?: string;
159
159
  pid?: number | null;
160
+ }> | Record<string, {
161
+ team?: number | string;
162
+ feature?: string;
163
+ agent?: string;
164
+ phase?: string;
165
+ prompt?: string | null;
166
+ log?: string | null;
167
+ spawn_status?: string;
168
+ status?: string;
169
+ pid?: number | null;
170
+ owner?: string;
171
+ report?: string | null;
160
172
  }>;
161
173
  last_dispatch?: Array<{
162
174
  team?: number | string;
@@ -784,7 +796,19 @@ function readFeatureTitles(rootDir: string): Map<string, string> {
784
796
  return map;
785
797
  }
786
798
 
787
- type RawWorker = NonNullable<NonNullable<RawProgress["company_state"]>["workers"]>[number];
799
+ interface RawWorker {
800
+ team?: number | string;
801
+ feature?: string;
802
+ agent?: string;
803
+ phase?: string;
804
+ prompt?: string | null;
805
+ log?: string | null;
806
+ spawn_status?: string;
807
+ status?: string;
808
+ pid?: number | null;
809
+ owner?: string;
810
+ report?: string | null;
811
+ }
788
812
  type WorkerOwner = WorkerDocEntry["owner"];
789
813
 
790
814
  interface HiredWorkerEntry {
@@ -839,13 +863,28 @@ function deriveWorkerProgress(worker: RawWorker): number | null {
839
863
  if (status === "blocked" || status === "failed") return 0.35;
840
864
  if (status === "recorded") return 0.15;
841
865
  if (status === "spawned" || status === "running") return 0.55;
842
- if (status === "completed" || status === "done") return 1;
866
+ if (status === "complete" || status === "completed" || status === "done") return 1;
843
867
  return null;
844
868
  }
845
869
 
870
+ function normalizeRawWorkers(value: RawWorker[] | Record<string, RawWorker> | null | undefined): RawWorker[] {
871
+ if (Array.isArray(value)) return value;
872
+ if (!value || typeof value !== "object") return [];
873
+ return Object.entries(value).map(([name, worker], idx) => {
874
+ const w = worker && typeof worker === "object" ? worker : {};
875
+ return {
876
+ ...w,
877
+ team: (w as RawWorker).team ?? idx + 1,
878
+ agent: (w as RawWorker).agent ?? name,
879
+ feature: (w as RawWorker).feature ?? name,
880
+ log: (w as RawWorker).log ?? (w as RawWorker & { report?: string | null }).report ?? null,
881
+ };
882
+ });
883
+ }
884
+
846
885
  function buildWorkers(rootDir: string, progress: RawProgress | null): WorkerSnapshot[] {
847
886
  const titles = readFeatureTitles(rootDir);
848
- let workers = progress?.company_state?.workers ?? [];
887
+ let workers = normalizeRawWorkers(progress?.company_state?.workers);
849
888
  if (workers.length === 0) {
850
889
  const queue = readJsonSafe<{
851
890
  teams?: Record<string, {
@@ -996,12 +1035,12 @@ function activeWorkerNames(progress: RawProgress | null): Set<string> {
996
1035
  const active = new Set<string>();
997
1036
  if (isHarnessRuntimeIdle(progress)) return active;
998
1037
  const workers = [
999
- ...(progress?.company_state?.workers ?? []),
1038
+ ...normalizeRawWorkers(progress?.company_state?.workers),
1000
1039
  ...(progress?.company_state?.last_dispatch ?? []),
1001
1040
  ];
1002
1041
  for (const worker of workers) {
1003
1042
  const rawStatus = (worker.status ?? worker.spawn_status ?? "").toLowerCase();
1004
- if (["completed", "done", "idle"].includes(rawStatus)) continue;
1043
+ if (["complete", "completed", "done", "idle"].includes(rawStatus)) continue;
1005
1044
  for (const value of [worker.agent, worker.feature]) {
1006
1045
  const normalized = normalizeWorkerName(value);
1007
1046
  if (normalized) active.add(normalized);
@@ -1220,16 +1259,6 @@ function readMissions(rootDir: string, progress: RawProgress | null = null, limi
1220
1259
  if (!f.endsWith(".md")) continue;
1221
1260
  const reportAbs = path.join(missionPath, relDir, f);
1222
1261
  const content = readMd(`${relDir}/${f}`) ?? "";
1223
- const statusMatch = content.match(/##\s*Status\s*\n+([A-Z_]+)/);
1224
- // Worker docs sometimes omit the "## Status" block but always carry a
1225
- // docmeta frontmatter when the worker submitted its output. Treat the
1226
- // presence of a docmeta block (or a non-empty body) as COMPLETE.
1227
- const hasDocmeta = /^---[\s\S]*?docmeta:[\s\S]*?---/.test(content);
1228
- const status: WorkerDocEntry["status"] = statusMatch
1229
- ? (statusMatch[1] as WorkerDocEntry["status"])
1230
- : hasDocmeta
1231
- ? "COMPLETE"
1232
- : "unknown";
1233
1262
  const name = normalizeWorkerName(f);
1234
1263
  const hired = hiredForMission(hiredWorkers, name, owner, rel);
1235
1264
  let updatedAt: string | null = null;
@@ -1239,6 +1268,18 @@ function readMissions(rootDir: string, progress: RawProgress | null = null, limi
1239
1268
  updatedAt = mtime.toISOString();
1240
1269
  recentlyTouched = Date.now() - mtime.getTime() < RECENT_WORKER_ACTIVE_MS;
1241
1270
  } catch { /* ignore */ }
1271
+ const statusMatch = content.match(/##\s*Status\s*\n+([A-Z_]+)/);
1272
+ const hasDocmeta = /^---[\s\S]*?docmeta:[\s\S]*?---/.test(content);
1273
+ // Worker docs often write docmeta before appending final evidence.
1274
+ // Without an explicit COMPLETE marker, keep recent writes visible as
1275
+ // active; after the activity window they become historical reports.
1276
+ const status: WorkerDocEntry["status"] = statusMatch
1277
+ ? (statusMatch[1] as WorkerDocEntry["status"])
1278
+ : hasDocmeta && recentlyTouched && !runtimeIdle
1279
+ ? "IN_PROGRESS"
1280
+ : hasDocmeta
1281
+ ? "COMPLETE"
1282
+ : "unknown";
1242
1283
  const entry: WorkerDocEntry = {
1243
1284
  name,
1244
1285
  displayName: workerDisplayName(content, hired, name),
@@ -1246,7 +1287,7 @@ function readMissions(rootDir: string, progress: RawProgress | null = null, limi
1246
1287
  status,
1247
1288
  owner: hired?.owner ?? owner,
1248
1289
  hired: !!hired,
1249
- active: !runtimeIdle && (activeWorkers.has(name) || (recentlyTouched && status !== "COMPLETE")),
1290
+ active: !runtimeIdle && (activeWorkers.has(name) || status === "IN_PROGRESS"),
1250
1291
  sourcePath: hired?.sourcePath ?? null,
1251
1292
  reportPath: path.relative(rootDir, reportAbs),
1252
1293
  updatedAt,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@walwal-harness/cli",
3
- "version": "7.1.32",
3
+ "version": "7.1.34",
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"