@songsid/agend 2.3.0-beta.7 → 2.3.0-beta.9
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/dist/agent-cli-instructions.md +2 -1
- package/dist/agent-cli.js +3 -0
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-whats-new.js +2 -0
- package/dist/agent-whats-new.js.map +1 -1
- package/dist/channel/mcp-tools.js +3 -3
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/daemon.d.ts +2 -0
- package/dist/daemon.js +29 -9
- package/dist/daemon.js.map +1 -1
- package/dist/decision-list.d.ts +25 -0
- package/dist/decision-list.js +30 -0
- package/dist/decision-list.js.map +1 -0
- package/dist/fleet-manager.d.ts +9 -2
- package/dist/fleet-manager.js +44 -12
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-traffic.d.ts +36 -0
- package/dist/fleet-traffic.js +100 -0
- package/dist/fleet-traffic.js.map +1 -1
- package/dist/general-knowledge/skills/web-appearance/SKILL.md +17 -10
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +32 -13
- package/dist/instance-config-impact.js +1 -0
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/outbound-handlers.js +4 -1
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +6 -4
- package/dist/outbound-schemas.js +7 -3
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/settings-api.js +4 -0
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +4 -0
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +2 -0
- package/dist/settings-effect.js.map +1 -1
- package/dist/tool-permissions.js +9 -42
- package/dist/tool-permissions.js.map +1 -1
- package/dist/ui/app-effects.js +9 -5
- package/dist/ui/chat-ambient.js +8 -7
- package/dist/ui/chat-effect-posters.js +3 -2
- package/dist/ui/chat-effects.js +22 -15
- package/dist/ui/chat-render.js +5 -2
- package/dist/ui/chat-strings.js +6 -4
- package/dist/ui/chat-thread.js +134 -17
- package/dist/ui/effect-trial-controller.js +13 -8
- package/dist/ui/fleet-canvas.js +54 -18
- package/dist/ui/fleet-org.js +3 -3
- package/dist/ui/fleet-traffic.js +156 -37
- package/dist/ui/fleet-usage.js +401 -0
- package/dist/ui/panel-chat.js +101 -47
- package/dist/ui/panel-fleet.js +6 -5
- package/dist/ui/panel-settings.js +6 -1
- package/dist/ui/settings-owner-effects.js +1 -1
- package/dist/ui/settings-strings.js +2 -0
- package/dist/ui/shared/agend-auth.js +1 -1
- package/dist/ui/shared/app-route.js +1 -1
- package/dist/ui/shared/app-stream.js +3 -2
- package/dist/ui/shared/app.css +89 -9
- package/dist/ui/shared/effect-app-runtime.js +3 -3
- package/dist/ui/shared/effect-chat-runtime.js +1 -1
- package/dist/ui/shared/effect-policy.js +5 -6
- package/dist/ui/shared/effect-profiler.js +31 -9
- package/dist/ui/shared/effect-registry.js +23 -31
- package/dist/ui/shared/effect-stock-snapshot.js +9 -0
- package/dist/ui/shared/effect-stock-trust.js +37 -6
- package/dist/ui/shared/effect-trial-source.js +1 -1
- package/dist/ui/shared/effect-trial-stage.js +27 -21
- package/dist/ui/shared/effect-trial.css +1 -1
- package/dist/ui/shared/fx-motion.js +12 -3
- package/dist/ui/shared/tokens.css +9 -3
- package/dist/ui/shared/usage-dialog.js +8 -0
- package/dist/ui/shared/view-strings.js +4 -0
- package/dist/ui/web-terminal/terminal.css +4 -4
- package/dist/usage-report.d.ts +12 -1
- package/dist/usage-report.js +39 -8
- package/dist/usage-report.js.map +1 -1
- package/dist/web-ambient.js +9 -2
- package/dist/web-ambient.js.map +1 -1
- package/dist/web-api.d.ts +2 -0
- package/dist/web-api.js +8 -3
- package/dist/web-api.js.map +1 -1
- package/dist/web-settings-patch.d.ts +3 -1
- package/dist/web-settings-patch.js +4 -1
- package/dist/web-settings-patch.js.map +1 -1
- package/dist/web-shell-routes.d.ts +1 -1
- package/dist/web-shell-routes.js +1 -1
- package/dist/web-shell-routes.js.map +1 -1
- package/dist/workflow-templates/default.md +1 -1
- package/package.json +1 -1
package/dist/ui/fleet-traffic.js
CHANGED
|
@@ -3,8 +3,14 @@
|
|
|
3
3
|
// stream, every POLL_MS while the page is shown — the user's decision 1). Ids are opaque; there is never any text.
|
|
4
4
|
//
|
|
5
5
|
// - The store keeps each message's latest state: queued (a dotted line), delivering (a dash and a pulse), delivered
|
|
6
|
-
// (the line stays FADE_MS while it fades, then goes
|
|
7
|
-
// failed or
|
|
6
|
+
// or consumed (the line stays FADE_MS while it fades, then goes — long enough to see: an MCP send is delivered and
|
|
7
|
+
// read almost at once); failed, uncertain or cancelled ends it at once (a failed or uncertain delivery is Needs you's
|
|
8
|
+
// to show, #1386). A finished message never comes back.
|
|
9
|
+
// - And the work (the server's Delegations, stream event `delegation`, the snapshot's `delegations`): a task's
|
|
10
|
+
// delegator → worker line stays, "in progress" (a moving dash), until its report comes back; then a short return
|
|
11
|
+
// pulse worker → delegator (RETURN_MS), and it goes. One that waits past DELEGATION_TTL_MS fades as "no report yet".
|
|
12
|
+
// - Every line ends on something drawn: a card, or a folded team's box (a name not on the canvas draws nothing), and
|
|
13
|
+
// runs through lanes that touch the boxes it joins — two cards in one box are joined inside it.
|
|
8
14
|
// - Edges are routed metro-style through the lanes the layout keeps empty (beside the columns and between card rows
|
|
9
15
|
// inside a box; the bus above each row of teams; the trunk at the left), so a line never crosses a card.
|
|
10
16
|
// - Near: one edge per (from, to, kind, phase), ×N when several share it. Far (zoomed out): one edge per team pair.
|
|
@@ -12,14 +18,20 @@
|
|
|
12
18
|
// recipients, or far, it is one edge per recipient team, ×N (the user's decision 3). At most MAX_EDGES are drawn.
|
|
13
19
|
import { OC } from "./fleet-canvas-layout.js";
|
|
14
20
|
|
|
15
|
-
export const FADE_MS =
|
|
21
|
+
export const FADE_MS = 12000;
|
|
22
|
+
export const RETURN_MS = 2400;
|
|
23
|
+
/** As the server's (src/fleet-traffic.ts): how long a delegation waits for its report. */
|
|
24
|
+
export const DELEGATION_TTL_MS = 4 * 3_600_000;
|
|
16
25
|
export const POLL_MS = 5000;
|
|
17
26
|
export const MAX_EDGES = 60;
|
|
18
27
|
export const BUNDLE_AT = 8;
|
|
19
28
|
export const FAR_K = 0.55;
|
|
20
|
-
const MAX_ITEMS = 400, MAX_DONE = 2000;
|
|
29
|
+
const MAX_ITEMS = 400, MAX_DONE = 2000, MAX_WORK = 200;
|
|
30
|
+
const DSTATES = new Set(["open", "done", "expired", "cancelled"]);
|
|
21
31
|
const LIVE = new Set(["queued", "delivering"]);
|
|
22
|
-
const ENDS = new Set(["
|
|
32
|
+
const ENDS = new Set(["failed", "uncertain", "cancelled"]);
|
|
33
|
+
/** Delivered, or read: the line fades out (FADE_MS). */
|
|
34
|
+
const SETTLED = new Set(["delivered", "consumed"]);
|
|
23
35
|
const KINDS = new Set(["task", "report", "query", "update", "send", "broadcast"]);
|
|
24
36
|
const mono = () => (typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now());
|
|
25
37
|
|
|
@@ -28,55 +40,118 @@ export function itemOk(x) {
|
|
|
28
40
|
if (!x || typeof x !== "object") return null;
|
|
29
41
|
const { id, from, to, kind, state, op } = x;
|
|
30
42
|
if (typeof id !== "string" || !/^[0-9a-f]{16}$/.test(id) || typeof from !== "string" || !from || typeof to !== "string" || !to) return null;
|
|
31
|
-
if (!KINDS.has(kind) || !(LIVE.has(state) || state
|
|
43
|
+
if (!KINDS.has(kind) || !(LIVE.has(state) || SETTLED.has(state) || ENDS.has(state))) return null;
|
|
32
44
|
return { id, from, to, kind, state, ...(typeof op === "string" && /^[0-9a-f]{16}$/.test(op) ? { op } : {}) };
|
|
33
45
|
}
|
|
34
46
|
|
|
47
|
+
/** A delegation as the server sends it, checked; null for anything else. */
|
|
48
|
+
export function delegationOk(x) {
|
|
49
|
+
if (!x || typeof x !== "object") return null;
|
|
50
|
+
const { id, from, to, state, age_ms } = x;
|
|
51
|
+
if (typeof id !== "string" || !/^[0-9a-f]{16}$/.test(id) || typeof from !== "string" || !from || typeof to !== "string" || !to || from === to) return null;
|
|
52
|
+
if (!DSTATES.has(state) || typeof age_ms !== "number" || !Number.isFinite(age_ms) || age_ms < 0) return null;
|
|
53
|
+
return { id, from, to, state, age: age_ms };
|
|
54
|
+
}
|
|
55
|
+
|
|
35
56
|
/**
|
|
36
|
-
* The page's traffic: `apply(item, nowMs)` one stream item, `
|
|
37
|
-
*
|
|
57
|
+
* The page's traffic: `apply(item, nowMs)` one stream item, `delegation(d, nowMs)` one delegation event,
|
|
58
|
+
* `load(snapshot, nowMs, since)` a snapshot, `sweep(nowMs)` drops faded lines; each returns whether anything shown
|
|
59
|
+
* changed. `items()` what to draw: { …item, fading }. `streamRev()` counts the stream's events: a snapshot asked for
|
|
60
|
+
* before the latest one (`since` older than it) may be older than what the stream already said, so it adds only what
|
|
61
|
+
* is new and ends or rewinds nothing (#2135 review).
|
|
62
|
+
* Ended work is remembered (bounded): a late snapshot never draws a delegation that is done, expired or taken back.
|
|
38
63
|
*/
|
|
39
64
|
export function createTrafficStore() {
|
|
40
65
|
const items = new Map(); // id → { item, fadeAt }
|
|
41
|
-
const
|
|
66
|
+
const work = new Map(); // delegation id → { d, phase: "work" | "return" | "stale", until }
|
|
67
|
+
const done = new Set(); // message ids that ended: a late item for one is ignored
|
|
68
|
+
const ended = new Set(); // delegation ids that ended: never drawn in progress again
|
|
69
|
+
let rev = 0;
|
|
42
70
|
const finish = (id) => {
|
|
43
71
|
items.delete(id);
|
|
44
72
|
done.add(id);
|
|
45
73
|
if (done.size > MAX_DONE) done.delete(done.values().next().value);
|
|
46
74
|
};
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
75
|
+
const end = (id) => {
|
|
76
|
+
ended.add(id);
|
|
77
|
+
if (ended.size > MAX_DONE) ended.delete(ended.values().next().value);
|
|
78
|
+
};
|
|
79
|
+
const applyItem = (raw, now) => {
|
|
80
|
+
const it = itemOk(raw);
|
|
81
|
+
if (!it || done.has(it.id)) return false;
|
|
82
|
+
const prev = items.get(it.id);
|
|
83
|
+
if (ENDS.has(it.state)) { const had = !!prev; finish(it.id); return had; }
|
|
84
|
+
if (prev && prev.fadeAt !== null) return false; // fading: never goes back, nor starts again
|
|
85
|
+
if (prev && prev.item.state === it.state) return false;
|
|
86
|
+
if (it.state === "consumed" && !prev) { finish(it.id); return false; } // read before this page drew it
|
|
87
|
+
items.delete(it.id);
|
|
88
|
+
// A message keeps what it was (from, to, kind) through its states; only the state moves.
|
|
89
|
+
items.set(it.id, { item: prev ? { ...prev.item, state: it.state } : it, fadeAt: SETTLED.has(it.state) ? now + FADE_MS : null });
|
|
90
|
+
while (items.size > MAX_ITEMS) items.delete(items.keys().next().value);
|
|
91
|
+
return true;
|
|
92
|
+
};
|
|
93
|
+
const applyDelegation = (raw, now) => {
|
|
94
|
+
const d = delegationOk(raw);
|
|
95
|
+
if (!d || ended.has(d.id)) return false;
|
|
96
|
+
const prev = work.get(d.id);
|
|
97
|
+
if (d.state === "open") {
|
|
98
|
+
if (prev) return false;
|
|
99
|
+
work.set(d.id, { d, phase: "work", until: now + Math.max(0, DELEGATION_TTL_MS - d.age) });
|
|
100
|
+
while (work.size > MAX_WORK) { const oldest = work.keys().next().value; work.delete(oldest); end(oldest); }
|
|
58
101
|
return true;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
102
|
+
}
|
|
103
|
+
end(d.id);
|
|
104
|
+
if (d.state === "cancelled") return work.delete(d.id);
|
|
105
|
+
if (d.state === "done") { work.set(d.id, { d, phase: "return", until: now + RETURN_MS }); return true; }
|
|
106
|
+
if (!prev || prev.phase !== "work") return false; // expired: only one this page shows
|
|
107
|
+
work.set(d.id, { d, phase: "stale", until: now + FADE_MS });
|
|
108
|
+
return true;
|
|
109
|
+
};
|
|
110
|
+
const store = {
|
|
111
|
+
apply(raw, now = mono()) { rev++; return applyItem(raw, now); },
|
|
112
|
+
delegation(raw, now = mono()) { rev++; return applyDelegation(raw, now); },
|
|
113
|
+
streamRev: () => rev,
|
|
114
|
+
/**
|
|
115
|
+
* A snapshot is the truth for what is in flight: anything live it no longer lists has ended (missed while away) —
|
|
116
|
+
* unless the stream has spoken since it was asked for (`since`): then it may be older than what is drawn, and only
|
|
117
|
+
* what is new to this page is taken from it.
|
|
118
|
+
*/
|
|
119
|
+
load(snap, now = mono(), since = rev) {
|
|
120
|
+
const fresh = since === rev;
|
|
62
121
|
const list = snap && Array.isArray(snap.items) ? snap.items.map(itemOk).filter(Boolean) : [];
|
|
63
122
|
const listed = new Set(list.map((i) => i.id));
|
|
64
123
|
let changed = false;
|
|
65
|
-
for (const [id, e] of items) if (!listed.has(id) && LIVE.has(e.item.state)) { finish(id); changed = true; }
|
|
66
|
-
// Delivered, for a line drawn here: it fades now (with no stream, the snapshot is the only news of it). Any
|
|
67
|
-
// finished message is not drawn (it finished up to two minutes ago), and an ended one goes.
|
|
124
|
+
if (fresh) for (const [id, e] of items) if (!listed.has(id) && LIVE.has(e.item.state)) { finish(id); changed = true; }
|
|
125
|
+
// Delivered or read, for a line drawn here: it fades now (with no stream, the snapshot is the only news of it). Any
|
|
126
|
+
// other finished message is not drawn (it finished up to two minutes ago), and an ended one goes.
|
|
68
127
|
for (const it of list) {
|
|
69
|
-
if (
|
|
70
|
-
|
|
128
|
+
if (!fresh && items.has(it.id)) continue; // the stream knows it better
|
|
129
|
+
if (LIVE.has(it.state) || (SETTLED.has(it.state) && items.has(it.id))) changed = applyItem(it, now) || changed;
|
|
130
|
+
else if (fresh) { const had = items.has(it.id); finish(it.id); changed = changed || had; }
|
|
71
131
|
}
|
|
132
|
+
// The open delegations: the snapshot's list is the truth (one it no longer lists ended while this page was away).
|
|
133
|
+
const open = snap && Array.isArray(snap.delegations) ? snap.delegations.filter((d) => delegationOk(d)?.state === "open") : [];
|
|
134
|
+
const ids = new Set(open.map((d) => d.id));
|
|
135
|
+
if (fresh) for (const [id, e] of work) if (e.phase === "work" && !ids.has(id)) { work.delete(id); end(id); changed = true; }
|
|
136
|
+
for (const d of open) changed = applyDelegation(d, now) || changed;
|
|
72
137
|
return changed;
|
|
73
138
|
},
|
|
74
139
|
sweep(now = mono()) {
|
|
75
140
|
let changed = false;
|
|
76
141
|
for (const [id, e] of items) if (e.fadeAt !== null && now >= e.fadeAt) { finish(id); changed = true; }
|
|
142
|
+
for (const [id, e] of work) {
|
|
143
|
+
if (now < e.until) continue;
|
|
144
|
+
if (e.phase === "work") { end(id); work.set(id, { ...e, phase: "stale", until: now + FADE_MS }); } // its wait ran out here
|
|
145
|
+
else work.delete(id);
|
|
146
|
+
changed = true;
|
|
147
|
+
}
|
|
77
148
|
return changed;
|
|
78
149
|
},
|
|
79
|
-
|
|
150
|
+
/** What to draw: the messages, then the work (a done one is its return, worker → delegator). */
|
|
151
|
+
items: () => [...[...items.values()].map((e) => ({ ...e.item, fading: e.fadeAt !== null })),
|
|
152
|
+
...[...work.values()].map(({ d, phase }) => phase === "return"
|
|
153
|
+
? { id: `r${d.id}`, from: d.to, to: d.from, kind: "report", state: "delivering", phase, fading: false }
|
|
154
|
+
: { id: `w${d.id}`, from: d.from, to: d.to, kind: "task", state: "delivering", phase, fading: false })],
|
|
80
155
|
fadingCount: () => [...items.values()].filter((e) => e.fadeAt !== null).length,
|
|
81
156
|
};
|
|
82
157
|
return store;
|
|
@@ -103,6 +178,9 @@ function endOf(L, name) {
|
|
|
103
178
|
|
|
104
179
|
// ── Routing: lanes the layout keeps empty ──
|
|
105
180
|
|
|
181
|
+
/** A side lane's offset from the middle of the box's padding: at least 6 px from the box's border and from its cards. */
|
|
182
|
+
const laneOffset = (o) => Math.max(-OC.PAD / 2 + 6, Math.min(OC.PAD / 2 - 6, o * 0.5));
|
|
183
|
+
|
|
106
184
|
const firstBusOf = (L) => Math.min(...L.boxes.map((b) => b.busY));
|
|
107
185
|
/** From an end up to its row's bus lane: the points, and the lane's y. `o` spreads lines sharing a lane. */
|
|
108
186
|
function outward(L, end, o) {
|
|
@@ -118,7 +196,7 @@ function outward(L, end, o) {
|
|
|
118
196
|
}
|
|
119
197
|
const { PAD, GAP, NW, NH } = OC;
|
|
120
198
|
const card = end.c, col = end.i % b.cols, rowI = Math.floor(end.i / b.cols), lines = Math.ceil(b.cells.length / b.cols);
|
|
121
|
-
const io =
|
|
199
|
+
const io = laneOffset(o);
|
|
122
200
|
const leftLane = b.x + PAD / 2 + io, rightLane = b.x + b.w - PAD / 2 + io;
|
|
123
201
|
let pts;
|
|
124
202
|
if (col === 0 || col === b.cols - 1) {
|
|
@@ -134,8 +212,43 @@ function outward(L, end, o) {
|
|
|
134
212
|
pts.push({ x: top.x, y: b.busY + o });
|
|
135
213
|
return { pts, bus: b.busY + o };
|
|
136
214
|
}
|
|
137
|
-
/**
|
|
215
|
+
/**
|
|
216
|
+
* Inside a box, from a card to one of its side lanes (x `lane`): straight out when the card is in that lane's column,
|
|
217
|
+
* else down into the gutter under its row (no card is there) and across. The last point is on the lane.
|
|
218
|
+
*/
|
|
219
|
+
function toLane(L, end, lane, o) {
|
|
220
|
+
const { PAD, GAP, NW, NH } = OC;
|
|
221
|
+
const b = end.b, card = end.c, col = end.i % b.cols, rowI = Math.floor(end.i / b.cols), lines = Math.ceil(b.cells.length / b.cols);
|
|
222
|
+
const io = laneOffset(o);
|
|
223
|
+
const left = lane < b.x + b.w / 2;
|
|
224
|
+
if ((left && col === 0) || (!left && col === b.cols - 1)) {
|
|
225
|
+
const cy = card.y + NH / 2 + io;
|
|
226
|
+
return [{ x: left ? card.x : card.x + NW, y: cy }, { x: lane, y: cy }];
|
|
227
|
+
}
|
|
228
|
+
const gy = card.y + NH + (rowI === lines - 1 ? PAD : GAP) / 2 + io / 2, cx = card.x + NW / 2 + o;
|
|
229
|
+
return [{ x: cx, y: card.y + NH }, { x: cx, y: gy }, { x: lane, y: gy }];
|
|
230
|
+
}
|
|
231
|
+
/** Two cards in one open box: joined inside it, through the side lane nearer to them both (never out to the bus). */
|
|
232
|
+
function inBox(L, a, z, o) {
|
|
233
|
+
const b = a.b, { PAD } = OC;
|
|
234
|
+
const io = laneOffset(o);
|
|
235
|
+
const mid = ((a.i % b.cols) + (z.i % b.cols)) / 2;
|
|
236
|
+
const lane = mid <= (b.cols - 1) / 2 ? b.x + PAD / 2 + io : b.x + b.w - PAD / 2 + io;
|
|
237
|
+
return [...toLane(L, a, lane, o), ...toLane(L, z, lane, o).reverse()];
|
|
238
|
+
}
|
|
239
|
+
/** Two of General's cards: under them, through the gap above the first row (or the canvas's empty space below). */
|
|
240
|
+
function betweenTops(a, z, o) {
|
|
241
|
+
const y = a.c.y + a.c.h + 30 + o * 0.6;
|
|
242
|
+
const ax = a.c.x + a.c.w / 2 + o, zx = z.c.x + z.c.w / 2 + o;
|
|
243
|
+
return [{ x: ax, y: a.c.y + a.c.h }, { x: ax, y }, { x: zx, y }, { x: zx, y: z.c.y + z.c.h }];
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* The points of a line from one end to the other: up to the bus, along it (and the trunk, between rows), down — or,
|
|
247
|
+
* for two cards in one box, inside the box; for two of General's cards, just under them.
|
|
248
|
+
*/
|
|
138
249
|
export function route(L, a, z, o) {
|
|
250
|
+
if (a.kind === "cell" && z.kind === "cell" && a.b === z.b) return inBox(L, a, z, o);
|
|
251
|
+
if (a.kind === "top" && z.kind === "top") return betweenTops(a, z, o);
|
|
139
252
|
const p = outward(L, a, o), q = outward(L, z, o);
|
|
140
253
|
const back = [...q.pts].reverse();
|
|
141
254
|
if (Math.abs(p.bus - q.bus) < 1) return [...p.pts, ...back];
|
|
@@ -169,8 +282,10 @@ export function badgeAt(pts) {
|
|
|
169
282
|
|
|
170
283
|
/** The colour family of a kind: task, report, broadcast, and the rest (send, query, update). */
|
|
171
284
|
export const hue = (kind) => (kind === "task" || kind === "report" || kind === "broadcast" ? kind : "send");
|
|
172
|
-
|
|
173
|
-
const
|
|
285
|
+
// A message: queued, live, fading. The work: in progress (work), its report coming back (return), or no report (stale).
|
|
286
|
+
const phaseOf = (it) => it.phase || (it.fading ? "fading" : it.state === "queued" ? "queued" : "live");
|
|
287
|
+
const RANK = { live: 0, return: 0, work: 1, queued: 2, fading: 3, stale: 3 };
|
|
288
|
+
const settled = (phase) => (phase === "fading" || phase === "stale" ? "fading" : phase);
|
|
174
289
|
|
|
175
290
|
/**
|
|
176
291
|
* The lines to draw from the store's items on layout `L`. `far`: zoomed out (one line per team pair). Each edge:
|
|
@@ -178,6 +293,7 @@ const RANK = { live: 0, queued: 1, fading: 2 };
|
|
|
178
293
|
*/
|
|
179
294
|
export function trafficEdges(items, L, { far = false, max = MAX_EDGES } = {}) {
|
|
180
295
|
if (!L.boxes.length && L.tops.length < 2) return [];
|
|
296
|
+
// A top's way out is the first row's bus: with no box there is none, so only top ↔ top lines (betweenTops) are drawn.
|
|
181
297
|
const groups = new Map();
|
|
182
298
|
const add = (key, g) => { const cur = groups.get(key); if (cur) { cur.n += g.n; cur.names.push(...g.names); } else groups.set(key, g); };
|
|
183
299
|
const teamKey = (name) => { const t = teamOf(L, name); return t === null ? "\u0000general" : t ? t.key : undefined; };
|
|
@@ -195,7 +311,9 @@ export function trafficEdges(items, L, { far = false, max = MAX_EDGES } = {}) {
|
|
|
195
311
|
const k = far ? "team" : "broadcast";
|
|
196
312
|
const a = far ? teamEnd(it.from) : endOf(L, it.from), z = teamEnd(it.to);
|
|
197
313
|
if (!a || !z) continue;
|
|
198
|
-
|
|
314
|
+
// Queued and live messages share a line far away (as before); the work keeps its own.
|
|
315
|
+
const ph = settled(phase), lane = ph === "queued" ? "live" : ph;
|
|
316
|
+
add(`${k}|${a.key}>${z.key}|${lane}`, { a, z, hue: k === "team" ? "team" : "broadcast", phase: ph, n: 1, names });
|
|
199
317
|
continue;
|
|
200
318
|
}
|
|
201
319
|
const a = endOf(L, it.from), z = endOf(L, it.to);
|
|
@@ -214,10 +332,11 @@ export function trafficEdges(items, L, { far = false, max = MAX_EDGES } = {}) {
|
|
|
214
332
|
export function sendingBy(items) {
|
|
215
333
|
const out = new Map();
|
|
216
334
|
for (const it of items) {
|
|
217
|
-
if (it.fading) continue;
|
|
335
|
+
if (it.fading || it.phase === "stale" || it.phase === "return") continue;
|
|
218
336
|
const l = out.get(it.from) || [];
|
|
219
|
-
const
|
|
220
|
-
|
|
337
|
+
const working = it.phase === "work";
|
|
338
|
+
const key = `${it.kind}>${it.to}>${working ? "work" : it.state}`;
|
|
339
|
+
if (!l.some((x) => x.key === key)) l.push({ key, kind: it.kind, to: it.to, queued: it.state === "queued", working });
|
|
221
340
|
out.set(it.from, l);
|
|
222
341
|
}
|
|
223
342
|
return out;
|