@walwal-harness/cli 7.1.46 → 7.1.47

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.
@@ -172,23 +172,32 @@ export function buildReport(target: DocTarget, state: ContractState): BuiltRepor
172
172
  const agent =
173
173
  state.agents.find((a) => a.id === target.agent) ?? state.agents[0];
174
174
 
175
+ // When opened from a timeline activity block, prefix the report with the
176
+ // moment (time + mission) the clicked block represents.
177
+ const moment =
178
+ target.at != null
179
+ ? `**Activity @ ${new Date(target.at).toLocaleString()}**` +
180
+ (target.mission ? ` · mission \`${target.mission}\`` : "") +
181
+ `\n\n---\n\n`
182
+ : "";
183
+
175
184
  if (target.type === "worker") {
176
185
  const worker = target.worker;
177
- const body = stripFrontmatter(worker.report) || NO_WORKER_SOURCE;
186
+ const body = moment + (stripFrontmatter(worker.report) || NO_WORKER_SOURCE);
178
187
  return {
179
188
  title: worker.name,
180
189
  owner: agent,
181
- kind: "Worker Brief",
190
+ kind: target.at != null ? "Worker · moment" : "Worker Brief",
182
191
  status: worker.status,
183
192
  body,
184
193
  };
185
194
  }
186
195
 
187
- const body = stripFrontmatter(agent.report) || NO_SOURCE;
196
+ const body = moment + (stripFrontmatter(agent.report) || NO_SOURCE);
188
197
  return {
189
198
  title: agent.work,
190
199
  owner: agent,
191
- kind: "CXX Document",
200
+ kind: target.at != null ? "CXX · moment" : "CXX Document",
192
201
  status: agent.status,
193
202
  body,
194
203
  };
@@ -2,11 +2,12 @@
2
2
 
3
3
  /* =============================================================
4
4
  walwal-harness · VIEW 3 — TIMELINE (cadence heatmap + swimlanes)
5
- Ported from harness/views-timeline.jsx (design handoff).
5
+ Ported from harness/views-timeline.jsx (design handoff), wired to REAL
6
+ per-lane activity marks (s.marks) instead of the prototype's random marks.
6
7
  Exports: TimelineView, Cadence
7
8
  ============================================================= */
8
9
 
9
- import React, { useRef } from "react";
10
+ import React from "react";
10
11
  import type { CSSProperties } from "react";
11
12
 
12
13
  import type {
@@ -14,6 +15,7 @@ import type {
14
15
  ContractState,
15
16
  ContractWorker,
16
17
  DocTarget,
18
+ TimelineMark,
17
19
  } from "../../lib/brick/contract";
18
20
  import { StatusDot, Label } from "./ui";
19
21
 
@@ -66,104 +68,92 @@ export interface TimelineViewProps {
66
68
  openDoc?: (target: DocTarget) => void;
67
69
  }
68
70
 
69
- type SwimMark = { ago: number; dur: number };
70
-
71
71
  type SwimRow = {
72
72
  kind: "exec" | "worker";
73
73
  id: string;
74
+ /** activity lane id: "cto" for an exec, "cto:worker-name" for a worker. */
75
+ laneId: string;
74
76
  label: string;
75
77
  hue: string;
76
78
  agent: AgentRole;
77
79
  status: string;
78
80
  progress?: number | null;
81
+ worker?: ContractWorker;
79
82
  };
80
83
 
81
84
  export function TimelineView({ s, openDoc }: TimelineViewProps) {
82
- // stable seeded marks per row id
83
- const markCache = useRef<Record<string, SwimMark[]>>({});
84
- function seedMarks(id: string, density: number): SwimMark[] {
85
- const cached = markCache.current[id];
86
- if (cached) return cached;
87
- const arr: SwimMark[] = [];
88
- const n = Math.floor(density * (4 + Math.random() * 7));
89
- for (let i = 0; i < n; i++) {
90
- arr.push({ ago: Math.random() * SPAN_MS, dur: 40000 + Math.random() * 600000 });
91
- }
92
- markCache.current[id] = arr;
93
- return arr;
94
- }
95
-
96
- // build rows: exec + its workers
97
85
  const order: AgentRole[] = ["ceo", "coo", "cto", "cqo", "cdo", "ops"];
86
+
87
+ // Build rows: each exec + the real WORKERS under it. Worker rows come from the
88
+ // activity sub-lanes ("cxx:worker-name") and the active-mission workers — NOT
89
+ // from missions — so a CXX lists the workers that actually ran under it.
98
90
  const rows: SwimRow[] = [];
99
- order.forEach((id) => {
100
- const a = s.agents.find((x) => x.id === id);
91
+ order.forEach((role) => {
92
+ const a = s.agents.find((x) => x.id === role);
101
93
  if (!a) return;
102
- rows.push({ kind: "exec", id: a.id, label: a.name, hue: a.hue, agent: a.id, status: a.status });
103
- s.workers
104
- .filter((w) => w.agent === id)
105
- .forEach((w) => {
106
- rows.push({
107
- kind: "worker",
108
- id: w.id,
109
- label: w.name,
110
- hue: a.hue,
111
- agent: a.id,
112
- status: w.status,
113
- progress: w.progress,
114
- });
115
- });
116
- });
94
+ rows.push({ kind: "exec", id: role, laneId: role, label: a.name, hue: a.hue, agent: role, status: a.status });
117
95
 
118
- // live marks from recent events (last 6 min) keyed to exec rows
119
- const liveByAgent: Record<string, number[]> = {};
120
- s.events.forEach((e) => {
121
- if (e.at == null || e.agent === "") return;
122
- const age = s.now - e.at;
123
- if (age < 6 * 60 * 1000) {
124
- (liveByAgent[e.agent] = liveByAgent[e.agent] || []).push(age);
96
+ const lanes = new Map<string, { label: string; worker?: ContractWorker }>();
97
+ for (const laneId of Object.keys(s.marks)) {
98
+ if (laneId.startsWith(role + ":")) {
99
+ lanes.set(laneId, { label: laneId.slice(role.length + 1) });
100
+ }
101
+ }
102
+ // active-mission workers (carry a report + status); key matches the sub-lane id.
103
+ for (const w of s.workers) {
104
+ if (w.agent === role) lanes.set(w.id, { label: w.name, worker: w });
105
+ }
106
+ for (const [laneId, info] of lanes) {
107
+ rows.push({
108
+ kind: "worker",
109
+ id: laneId,
110
+ laneId,
111
+ label: info.label,
112
+ hue: a.hue,
113
+ agent: role,
114
+ status: info.worker?.status ?? "idle",
115
+ progress: info.worker?.progress ?? null,
116
+ worker: info.worker,
117
+ });
125
118
  }
126
119
  });
127
120
 
128
121
  const xOf = (ago: number): number => Math.max(0, Math.min(100, (ago / SPAN_MS) * 100));
129
-
130
- // axis ticks
131
122
  const ticks: number[] = [];
132
123
  for (let h = 0; h <= 12; h += 2) ticks.push(h);
124
+ const fmtAt = (at: number): string => {
125
+ const d = new Date(at);
126
+ return Number.isFinite(d.getTime()) ? d.toTimeString().slice(0, 8) : "?";
127
+ };
133
128
 
129
+ // Label click → the whole row's report.
134
130
  const openRow = (r: SwimRow) => {
135
131
  if (!openDoc) return;
136
- if (r.kind === "exec") {
137
- openDoc({ type: "agent", agent: r.agent });
138
- return;
139
- }
140
- const worker: ContractWorker | undefined = s.workers.find((w) => w.id === r.id);
141
- if (worker) openDoc({ type: "worker", agent: r.agent, worker });
132
+ if (r.worker) openDoc({ type: "worker", agent: r.agent, worker: r.worker });
133
+ else openDoc({ type: "agent", agent: r.agent });
142
134
  };
135
+ // Block click → detail for that one activity mark (what the lane did at that moment).
136
+ const openMark = (r: SwimRow, m: TimelineMark, e: React.MouseEvent) => {
137
+ e.stopPropagation();
138
+ if (!openDoc) return;
139
+ if (r.worker) openDoc({ type: "worker", agent: r.agent, worker: r.worker, at: m.at, mission: m.mission });
140
+ else openDoc({ type: "agent", agent: r.agent, at: m.at, mission: m.mission });
141
+ };
142
+
143
+ const workerCount = rows.filter((r) => r.kind === "worker").length;
143
144
 
144
145
  return (
145
146
  <div className="timelineview">
146
147
  <Cadence cadence={s.cadence} />
147
148
  <div className="swim">
148
- <Label
149
- right={
150
- rows.filter((r) => r.kind === "worker").length +
151
- " workers · " +
152
- order.length +
153
- " execs"
154
- }
155
- >
149
+ <Label right={workerCount + " workers · " + order.length + " execs"}>
156
150
  ORCHESTRATION TIMELINE · now → −12h
157
151
  </Label>
158
152
  <div className="swim-axis">
159
153
  <div className="swim-axis-pad" />
160
154
  <div className="swim-axis-track">
161
155
  {ticks.map((h) => (
162
- <span
163
- key={h}
164
- className="swim-tick"
165
- style={{ left: (h / 12) * 100 + "%" }}
166
- >
156
+ <span key={h} className="swim-tick" style={{ left: (h / 12) * 100 + "%" }}>
167
157
  {h === 0 ? "now" : "−" + h + "h"}
168
158
  </span>
169
159
  ))}
@@ -171,8 +161,9 @@ export function TimelineView({ s, openDoc }: TimelineViewProps) {
171
161
  </div>
172
162
  <div className="swim-rows">
173
163
  {rows.map((r) => {
174
- const marks = seedMarks(r.id, r.kind === "exec" ? 1.4 : 0.8);
175
- const live = r.kind === "exec" ? liveByAgent[r.agent] || [] : [];
164
+ const marks = (s.marks[r.laneId] ?? []).filter(
165
+ (m) => s.now - m.at >= 0 && s.now - m.at <= SPAN_MS
166
+ );
176
167
  return (
177
168
  <div
178
169
  key={r.id}
@@ -205,21 +196,18 @@ export function TimelineView({ s, openDoc }: TimelineViewProps) {
205
196
  <span
206
197
  key={i}
207
198
  className="swim-block"
199
+ role="button"
200
+ title={fmtAt(m.at) + (m.mission ? " · " + m.mission : "")}
201
+ onClick={(e) => openMark(r, m, e)}
208
202
  style={{
209
- left: xOf(m.ago) + "%",
210
- width: Math.max(0.6, (m.dur / SPAN_MS) * 100) + "%",
203
+ left: xOf(s.now - m.at) + "%",
204
+ width: "9px",
205
+ cursor: "pointer",
211
206
  background: r.hue,
212
- opacity: r.kind === "exec" ? 0.85 : 0.55,
207
+ opacity: r.kind === "exec" ? 0.85 : 0.6,
213
208
  }}
214
209
  />
215
210
  ))}
216
- {live.map((age, i) => (
217
- <span
218
- key={"l" + i}
219
- className="swim-live"
220
- style={{ left: xOf(age) + "%", background: r.hue }}
221
- />
222
- ))}
223
211
  {r.kind === "worker" && r.progress != null && (
224
212
  <span
225
213
  className="swim-now-prog"
@@ -284,6 +284,17 @@ export function toContractState(
284
284
  eventsTotal: snapshot.events.length,
285
285
  };
286
286
 
287
+ // ---- timeline marks: real per-lane activity (epoch + mission) ----------
288
+ const marks: Record<string, Array<{ at: number; mission: string | null }>> = {};
289
+ for (const sample of snapshot.activitySamples) {
290
+ const at = parseTs(sample.ts);
291
+ if (at == null) continue;
292
+ (marks[sample.laneId] = marks[sample.laneId] ?? []).push({
293
+ at,
294
+ mission: sample.missionId ?? null,
295
+ });
296
+ }
297
+
287
298
  return {
288
299
  now: nowMs,
289
300
  agents,
@@ -295,6 +306,7 @@ export function toContractState(
295
306
  metrics,
296
307
  mtrend: [],
297
308
  ctrend: [],
309
+ marks,
298
310
  };
299
311
  }
300
312
 
@@ -39,8 +39,8 @@ export type AlertLevel = "stale" | "attention";
39
39
 
40
40
  /** A reference to a document the DocViewer can render. */
41
41
  export type DocTarget =
42
- | { type: "agent"; agent: AgentRole }
43
- | { type: "worker"; agent: AgentRole; worker: ContractWorker };
42
+ | { type: "agent"; agent: AgentRole; at?: number; mission?: string | null }
43
+ | { type: "worker"; agent: AgentRole; worker: ContractWorker; at?: number; mission?: string | null };
44
44
 
45
45
  /** Static, design-fixed definition of one exec agent. */
46
46
  export interface AgentDef {
@@ -173,6 +173,17 @@ export interface ContractState {
173
173
  mtrend: number[];
174
174
  /** cost sparkline samples; [] when no source. */
175
175
  ctrend: number[];
176
+ /** Real per-lane activity marks (epoch ms + mission) for the timeline
177
+ * swimlanes, keyed by laneId ("cto" exec lane or "cto:worker-name" sub-lane). */
178
+ marks: Record<string, TimelineMark[]>;
179
+ }
180
+
181
+ /** One real activity mark on a timeline lane (from an activity sample). */
182
+ export interface TimelineMark {
183
+ /** epoch ms of the activity. */
184
+ at: number;
185
+ /** mission the activity belonged to, if known. */
186
+ mission: string | null;
176
187
  }
177
188
 
178
189
  /* ---- agent (exec) definitions -----------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@walwal-harness/cli",
3
- "version": "7.1.46",
3
+ "version": "7.1.47",
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"