@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
|
|
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((
|
|
100
|
-
const a = s.agents.find((x) => x.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:
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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.
|
|
137
|
-
|
|
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 =
|
|
175
|
-
|
|
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.
|
|
210
|
-
width:
|
|
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.
|
|
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.
|
|
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"
|