agmon 0.0.0-stage → 0.1.0
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 +81 -2
- package/bin/cli.js +366 -0
- package/jsconfig.json +8 -0
- package/next.config.mjs +6 -0
- package/package.json +50 -4
- package/postcss.config.mjs +5 -0
- package/public/favicon.svg +48 -0
- package/public/icons/icon.svg +48 -0
- package/public/manifest.json +23 -0
- package/public/sw.js +42 -0
- package/src/app/api/traces/stream/route.js +89 -0
- package/src/app/api/version/route.js +23 -0
- package/src/app/globals.css +15 -0
- package/src/app/layout.js +42 -0
- package/src/app/page.js +174 -0
- package/src/components/PwaRegister.js +31 -0
- package/src/components/UpdateNotification.js +79 -0
- package/src/components/factory/AgentPanel.js +92 -0
- package/src/components/factory/office-canvas.js +95 -0
- package/src/components/factory/office-scene.js +519 -0
- package/src/components/factory/scene/characters.js +335 -0
- package/src/components/factory/scene/office-layout.js +188 -0
- package/src/components/factory/scene/token-streams.js +85 -0
- package/src/components/factory/scene/tool-icons.js +233 -0
- package/src/lib/mockTraces.js +171 -0
- package/src/lib/traceContract.js +46 -0
- package/src/lib/useFactoryTraces.js +142 -0
- package/src/lib/watchers/antigravityWatcher.js +180 -0
- package/src/lib/watchers/claudeWatcher.js +190 -0
- package/src/lib/watchers/watcherManager.js +41 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { Container, Graphics, Text } from "pixi.js";
|
|
2
|
+
|
|
3
|
+
// One glyph per tool type. Each is drawn inside a ±10 box so a badge can swap
|
|
4
|
+
// icons without relayout. Only the tool *type* is shown — never arguments.
|
|
5
|
+
|
|
6
|
+
const S = { cap: "round", join: "round" };
|
|
7
|
+
|
|
8
|
+
export const TOOL_TYPES = {
|
|
9
|
+
bash: {
|
|
10
|
+
label: "Bash",
|
|
11
|
+
color: 0x34d399,
|
|
12
|
+
draw(g, c) {
|
|
13
|
+
g.moveTo(-8, -6).lineTo(-2, 0).lineTo(-8, 6).stroke({ width: 2.4, color: c, ...S });
|
|
14
|
+
g.moveTo(1, 7).lineTo(8, 7).stroke({ width: 2.4, color: c, ...S });
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
read: {
|
|
18
|
+
label: "Read",
|
|
19
|
+
color: 0x60a5fa,
|
|
20
|
+
draw(g, c) {
|
|
21
|
+
g.moveTo(-6, -9).lineTo(3, -9).lineTo(7, -5).lineTo(7, 9).lineTo(-6, 9).closePath()
|
|
22
|
+
.stroke({ width: 2, color: c, ...S });
|
|
23
|
+
g.moveTo(-3, -1).lineTo(4, -1).stroke({ width: 1.8, color: c, ...S });
|
|
24
|
+
g.moveTo(-3, 3).lineTo(4, 3).stroke({ width: 1.8, color: c, ...S });
|
|
25
|
+
g.moveTo(-3, 6.5).lineTo(1, 6.5).stroke({ width: 1.8, color: c, ...S });
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
edit: {
|
|
29
|
+
label: "Edit",
|
|
30
|
+
color: 0xfbbf24,
|
|
31
|
+
draw(g, c) {
|
|
32
|
+
g.moveTo(-7, 8).lineTo(-6, 3).lineTo(4, -7).lineTo(8, -3).lineTo(-2, 7).closePath()
|
|
33
|
+
.stroke({ width: 2, color: c, ...S });
|
|
34
|
+
g.moveTo(1, -4).lineTo(5, 0).stroke({ width: 1.8, color: c, ...S });
|
|
35
|
+
g.moveTo(-8, 9.5).lineTo(-1, 9.5).stroke({ width: 1.8, color: c, ...S });
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
search: {
|
|
39
|
+
label: "Search",
|
|
40
|
+
color: 0xf472b6,
|
|
41
|
+
draw(g, c) {
|
|
42
|
+
g.circle(-2, -2, 6).stroke({ width: 2.2, color: c, ...S });
|
|
43
|
+
g.moveTo(2.5, 2.5).lineTo(8, 8).stroke({ width: 2.6, color: c, ...S });
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
web: {
|
|
47
|
+
label: "Web",
|
|
48
|
+
color: 0x22d3ee,
|
|
49
|
+
draw(g, c) {
|
|
50
|
+
g.circle(0, 0, 8.5).stroke({ width: 2, color: c, ...S });
|
|
51
|
+
g.ellipse(0, 0, 3.6, 8.5).stroke({ width: 1.6, color: c, ...S });
|
|
52
|
+
g.moveTo(-8.5, 0).lineTo(8.5, 0).stroke({ width: 1.6, color: c, ...S });
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
mcp: {
|
|
56
|
+
label: "MCP",
|
|
57
|
+
color: 0xa78bfa,
|
|
58
|
+
draw(g, c) {
|
|
59
|
+
g.moveTo(-4, -9).lineTo(-4, -3).stroke({ width: 2.4, color: c, ...S });
|
|
60
|
+
g.moveTo(4, -9).lineTo(4, -3).stroke({ width: 2.4, color: c, ...S });
|
|
61
|
+
g.roundRect(-8, -3, 16, 9, 3).stroke({ width: 2, color: c, ...S });
|
|
62
|
+
g.moveTo(0, 6).lineTo(0, 10).stroke({ width: 2.4, color: c, ...S });
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
agent: {
|
|
66
|
+
label: "Subagent",
|
|
67
|
+
color: 0xfb923c,
|
|
68
|
+
draw(g, c) {
|
|
69
|
+
g.moveTo(0, -4).lineTo(-6, 6).stroke({ width: 1.8, color: c, ...S });
|
|
70
|
+
g.moveTo(0, -4).lineTo(6, 6).stroke({ width: 1.8, color: c, ...S });
|
|
71
|
+
g.circle(0, -5, 3.6).fill({ color: c });
|
|
72
|
+
g.circle(-6.5, 7, 3).fill({ color: c });
|
|
73
|
+
g.circle(6.5, 7, 3).fill({ color: c });
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
todo: {
|
|
77
|
+
label: "Todo",
|
|
78
|
+
color: 0x94a3b8,
|
|
79
|
+
draw(g, c) {
|
|
80
|
+
g.moveTo(-8, -5).lineTo(-6, -3).lineTo(-3, -7).stroke({ width: 1.8, color: c, ...S });
|
|
81
|
+
g.moveTo(1, -5).lineTo(8, -5).stroke({ width: 1.8, color: c, ...S });
|
|
82
|
+
g.moveTo(-8, 5).lineTo(-6, 7).lineTo(-3, 3).stroke({ width: 1.8, color: c, ...S });
|
|
83
|
+
g.moveTo(1, 5).lineTo(8, 5).stroke({ width: 1.8, color: c, ...S });
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
gitnexus: {
|
|
87
|
+
label: "GitNexus",
|
|
88
|
+
color: 0xc084fc, // purple-400
|
|
89
|
+
draw(g, c) {
|
|
90
|
+
// Knowledge graph: central hub connected to 3 orbiting nodes
|
|
91
|
+
g.moveTo(0, 0).lineTo(-6, -6).stroke({ width: 1.5, color: c, ...S });
|
|
92
|
+
g.moveTo(0, 0).lineTo(6, -5).stroke({ width: 1.5, color: c, ...S });
|
|
93
|
+
g.moveTo(0, 0).lineTo(0, 7).stroke({ width: 1.5, color: c, ...S });
|
|
94
|
+
g.circle(0, 0, 3.2).fill({ color: c });
|
|
95
|
+
g.circle(-6, -6, 2.2).fill({ color: c });
|
|
96
|
+
g.circle(6, -5, 2.2).fill({ color: c });
|
|
97
|
+
g.circle(0, 7, 2.2).fill({ color: c });
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
browser: {
|
|
101
|
+
label: "Browser",
|
|
102
|
+
color: 0x06b6d4, // cyan-500
|
|
103
|
+
draw(g, c) {
|
|
104
|
+
// Browser window with top tab bar
|
|
105
|
+
g.roundRect(-8, -7, 16, 14, 2.5).stroke({ width: 1.8, color: c, ...S });
|
|
106
|
+
g.moveTo(-8, -2).lineTo(8, -2).stroke({ width: 1.4, color: c, ...S });
|
|
107
|
+
g.circle(-5, -4.5, 1).fill({ color: c });
|
|
108
|
+
g.circle(-2, -4.5, 1).fill({ color: c });
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
git: {
|
|
112
|
+
label: "Git",
|
|
113
|
+
color: 0xf97316, // orange-500
|
|
114
|
+
draw(g, c) {
|
|
115
|
+
// Git branching tree
|
|
116
|
+
g.moveTo(-4, -7).lineTo(-4, 7).stroke({ width: 2, color: c, ...S });
|
|
117
|
+
g.moveTo(-4, 2).arc(0, 2, 4, Math.PI, 0).lineTo(4, -3).stroke({ width: 1.8, color: c, ...S });
|
|
118
|
+
g.circle(-4, -6, 2.4).fill({ color: c });
|
|
119
|
+
g.circle(-4, 6, 2.4).fill({ color: c });
|
|
120
|
+
g.circle(4, -3, 2.4).fill({ color: c });
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
docker: {
|
|
124
|
+
label: "Docker",
|
|
125
|
+
color: 0x38bdf8, // light blue
|
|
126
|
+
draw(g, c) {
|
|
127
|
+
// Container blocks stacked
|
|
128
|
+
g.roundRect(-7, 1, 6, 5, 1).fill({ color: c, alpha: 0.8 }).stroke({ width: 1.2, color: c, ...S });
|
|
129
|
+
g.roundRect(1, 1, 6, 5, 1).fill({ color: c, alpha: 0.8 }).stroke({ width: 1.2, color: c, ...S });
|
|
130
|
+
g.roundRect(-3, -6, 6, 5, 1).fill({ color: c, alpha: 0.8 }).stroke({ width: 1.2, color: c, ...S });
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Request-status glyphs share the badge machinery with tools: lifecycle states,
|
|
136
|
+
// the four failure reasons, and fallback.
|
|
137
|
+
export const STATUS_TYPES = {
|
|
138
|
+
pending: {
|
|
139
|
+
label: "Queued",
|
|
140
|
+
color: 0xf59e0b,
|
|
141
|
+
draw(g, c) {
|
|
142
|
+
for (const x of [-7, 0, 7]) g.circle(x, 1, 2.6).fill({ color: c });
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
done: {
|
|
146
|
+
label: "Done",
|
|
147
|
+
color: 0x34d399,
|
|
148
|
+
draw(g, c) {
|
|
149
|
+
g.moveTo(-7, 0).lineTo(-2, 6).lineTo(8, -7).stroke({ width: 3, color: c, ...S });
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
rate_limited: {
|
|
153
|
+
label: "Rate limit",
|
|
154
|
+
color: 0xef4444,
|
|
155
|
+
draw(g, c) {
|
|
156
|
+
g.moveTo(-6, -9).lineTo(6, -9).lineTo(-6, 9).lineTo(6, 9).closePath()
|
|
157
|
+
.stroke({ width: 2, color: c, ...S });
|
|
158
|
+
g.moveTo(-3, 6.5).lineTo(3, 6.5).stroke({ width: 2.2, color: c, ...S });
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
upstream_timeout: {
|
|
162
|
+
label: "Timeout",
|
|
163
|
+
color: 0xef4444,
|
|
164
|
+
draw(g, c) {
|
|
165
|
+
g.circle(0, 0, 8.5).stroke({ width: 2, color: c, ...S });
|
|
166
|
+
g.moveTo(0, -5).lineTo(0, 0).lineTo(4.5, 3).stroke({ width: 2, color: c, ...S });
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
quota_exhausted: {
|
|
170
|
+
label: "Quota",
|
|
171
|
+
color: 0xef4444,
|
|
172
|
+
draw(g, c) {
|
|
173
|
+
g.roundRect(-8, -5, 14, 10, 2).stroke({ width: 2, color: c, ...S });
|
|
174
|
+
g.rect(6, -2, 3, 4).fill({ color: c });
|
|
175
|
+
g.rect(-6, -3, 2.6, 6).fill({ color: c });
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
auth_refresh_failed: {
|
|
179
|
+
label: "Auth",
|
|
180
|
+
color: 0xef4444,
|
|
181
|
+
draw(g, c) {
|
|
182
|
+
g.circle(-4, 0, 4.2).stroke({ width: 2, color: c, ...S });
|
|
183
|
+
g.moveTo(0, 0).lineTo(8, 0).stroke({ width: 2, color: c, ...S });
|
|
184
|
+
g.moveTo(5, 0).lineTo(5, 4).stroke({ width: 2, color: c, ...S });
|
|
185
|
+
g.moveTo(-9, -8).lineTo(9, 8).stroke({ width: 2.2, color: 0xfbbf24, ...S });
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
fallback: {
|
|
189
|
+
label: "Fallback",
|
|
190
|
+
color: 0xfbbf24,
|
|
191
|
+
draw(g, c) {
|
|
192
|
+
g.moveTo(-8, -4).lineTo(7, -4).stroke({ width: 2.2, color: c, ...S });
|
|
193
|
+
g.moveTo(3, -8).lineTo(8, -4).lineTo(3, 0).stroke({ width: 2.2, color: c, ...S });
|
|
194
|
+
g.moveTo(8, 5).lineTo(-7, 5).stroke({ width: 2.2, color: c, ...S });
|
|
195
|
+
g.moveTo(-3, 1).lineTo(-8, 5).lineTo(-3, 9).stroke({ width: 2.2, color: c, ...S });
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
export const TOOL_KEYS = Object.keys(TOOL_TYPES);
|
|
201
|
+
export const STATUS_KEYS = ["pending", "done", "rate_limited", "upstream_timeout", "quota_exhausted", "auth_refresh_failed", "fallback"];
|
|
202
|
+
|
|
203
|
+
/** A round badge: dark disc, coloured ring, the tool's glyph, optional name. */
|
|
204
|
+
export function createToolBadge(tool, { size = 1, showLabel = true, label: labelOverride } = {}) {
|
|
205
|
+
const def = TOOL_TYPES[tool] || STATUS_TYPES[tool] || TOOL_TYPES.bash;
|
|
206
|
+
const root = new Container();
|
|
207
|
+
|
|
208
|
+
const halo = new Graphics();
|
|
209
|
+
halo.circle(0, 0, 21).fill({ color: def.color, alpha: 0.18 });
|
|
210
|
+
|
|
211
|
+
const disc = new Graphics();
|
|
212
|
+
disc.circle(0, 0, 15).fill({ color: 0x0f141c });
|
|
213
|
+
disc.circle(0, 0, 15).stroke({ width: 2, color: def.color, alpha: 0.95 });
|
|
214
|
+
|
|
215
|
+
const glyph = new Graphics();
|
|
216
|
+
def.draw(glyph, def.color);
|
|
217
|
+
glyph.scale.set(0.95);
|
|
218
|
+
|
|
219
|
+
root.addChild(halo, disc, glyph);
|
|
220
|
+
|
|
221
|
+
if (showLabel) {
|
|
222
|
+
const label = new Text({
|
|
223
|
+
text: labelOverride ?? def.label,
|
|
224
|
+
style: { fontFamily: "Inter, system-ui, sans-serif", fontSize: 9, fill: def.color, fontWeight: "800" },
|
|
225
|
+
});
|
|
226
|
+
label.anchor.set(0.5, 0);
|
|
227
|
+
label.y = 18;
|
|
228
|
+
root.addChild(label);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
root.scale.set(size);
|
|
232
|
+
return { root, halo, tool };
|
|
233
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
export const MOCK_PRESETS = {
|
|
2
|
+
idle: { agents: 2, errorRatio: 0 },
|
|
3
|
+
busy: { agents: 8, errorRatio: 0 },
|
|
4
|
+
storm: { agents: 30, errorRatio: 0.15 },
|
|
5
|
+
errors: { agents: 10, errorRatio: 0.6 },
|
|
6
|
+
cases: { agents: 16, errorRatio: 0, cases: true },
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const CLIENTS = [
|
|
10
|
+
{ account: "Claude Code · main", icon: "terminal" },
|
|
11
|
+
{ account: "Claude Code · review", icon: "rate_review" },
|
|
12
|
+
{ account: "Antigravity · agent", icon: "smart_toy" },
|
|
13
|
+
{ account: "Antigravity · dev", icon: "construction" },
|
|
14
|
+
{ account: "Cursor · workspace", icon: "edit_note" },
|
|
15
|
+
{ account: "OpenCode · build", icon: "science" },
|
|
16
|
+
{ account: "Aider", icon: "smart_toy" },
|
|
17
|
+
{ account: "Continue · dev", icon: "play_arrow" },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const PROVIDER_MODELS = [
|
|
21
|
+
{ provider: "claude", model: "claude-3-7-sonnet" },
|
|
22
|
+
{ provider: "gemini", model: "gemini-2.5-pro" },
|
|
23
|
+
{ provider: "openai", model: "gpt-4o" },
|
|
24
|
+
{ provider: "minimax", model: "minimax-m2" },
|
|
25
|
+
{ provider: "deepseek", model: "deepseek-v3" },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const ERROR_REASONS = [
|
|
29
|
+
"rate_limited",
|
|
30
|
+
"upstream_timeout",
|
|
31
|
+
"quota_exhausted",
|
|
32
|
+
"auth_refresh_failed",
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
export const MOCK_TOOL_TYPES = ["bash", "read", "edit", "search", "web", "mcp", "agent", "gitnexus", "browser", "git", "docker"];
|
|
36
|
+
|
|
37
|
+
const CASE_DECK = [
|
|
38
|
+
{ name: "sleeping", state: "done", ageMs: 38000 },
|
|
39
|
+
{ name: "happy", state: "done", ageMs: 9000, cost: 1.42 },
|
|
40
|
+
{ name: "pending", state: "pending", ageMs: 2500, tools: ["todo"] },
|
|
41
|
+
{ name: "gitnexus", state: "streaming", ageMs: 14000, tools: ["gitnexus", "read"] },
|
|
42
|
+
{ name: "docker", state: "streaming", ageMs: 15000, tools: ["docker", "bash"] },
|
|
43
|
+
{ name: "browser", state: "streaming", ageMs: 16000, tools: ["browser", "web"] },
|
|
44
|
+
{ name: "git+agent", state: "streaming", ageMs: 12000, tools: ["git", "agent"], concurrent: 4 },
|
|
45
|
+
{ name: "rate_limited", state: "error", ageMs: 6000, error: "rate_limited" },
|
|
46
|
+
{ name: "upstream_timeout", state: "error", ageMs: 30000, error: "upstream_timeout" },
|
|
47
|
+
{ name: "quota_exhausted", state: "error", ageMs: 8000, error: "quota_exhausted" },
|
|
48
|
+
{ name: "auth_refresh_failed", state: "error", ageMs: 5000, error: "auth_refresh_failed" },
|
|
49
|
+
{ name: "fallback-1", state: "streaming", ageMs: 13000, tools: ["bash"], fallbackShift: 3 },
|
|
50
|
+
{ name: "fallback-2", state: "streaming", ageMs: 14000, tools: ["browser", "mcp"], fallbackShift: 2 },
|
|
51
|
+
{ name: "slow", state: "streaming", ageMs: 36000, tools: ["docker"] },
|
|
52
|
+
{ name: "queue+cached", state: "streaming", ageMs: 10000, tools: ["read", "search"], concurrent: 5, cachedBoost: true },
|
|
53
|
+
{ name: "expensive", state: "done", ageMs: 20000, cost: 7.35 },
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
function createRandom(seed) {
|
|
57
|
+
let s = seed >>> 0 || 1;
|
|
58
|
+
return () => {
|
|
59
|
+
s ^= s << 13;
|
|
60
|
+
s ^= s >>> 17;
|
|
61
|
+
s ^= s << 5;
|
|
62
|
+
s >>>= 0;
|
|
63
|
+
return s / 4294967296;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function pick(rand, list) {
|
|
68
|
+
return list[Math.floor(rand() * list.length) % list.length];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function accountTarget(index) {
|
|
72
|
+
return PROVIDER_MODELS[index % PROVIDER_MODELS.length];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function randomTokens(rand) {
|
|
76
|
+
return {
|
|
77
|
+
input: Math.round(400 + rand() * 14000),
|
|
78
|
+
output: Math.round(80 + rand() * 2900),
|
|
79
|
+
cached: Math.round(rand() * 6000),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function generateMockTraces({ count = 8, errorRatio = 0, seed = 42, now = Date.now(), cases = false } = {}) {
|
|
84
|
+
const rand = createRandom(seed);
|
|
85
|
+
const traces = [];
|
|
86
|
+
|
|
87
|
+
for (let i = 0; i < count; i += 1) {
|
|
88
|
+
const client = CLIENTS[i % CLIENTS.length];
|
|
89
|
+
const cycle = Math.floor(i / CLIENTS.length) + 1;
|
|
90
|
+
const account = cycle > 1 ? `${client.account} ${cycle}` : client.account;
|
|
91
|
+
const connectionId = `mock-conn-${i}`;
|
|
92
|
+
const target = accountTarget(i);
|
|
93
|
+
const deck = cases ? CASE_DECK[i % CASE_DECK.length] : null;
|
|
94
|
+
const randAge = Math.floor(rand() * 40000);
|
|
95
|
+
const randErr = rand() < errorRatio;
|
|
96
|
+
const ageMs = deck ? deck.ageMs : randAge;
|
|
97
|
+
const startedAt = now - ageMs;
|
|
98
|
+
const isError = deck ? deck.state === "error" : randErr;
|
|
99
|
+
|
|
100
|
+
const ageRatio = ageMs / 40000;
|
|
101
|
+
let state;
|
|
102
|
+
if (deck) state = deck.state;
|
|
103
|
+
else if (isError) state = "error";
|
|
104
|
+
else if (ageRatio < 0.25) state = "pending";
|
|
105
|
+
else if (ageRatio < 0.6) state = "streaming";
|
|
106
|
+
else state = "done";
|
|
107
|
+
|
|
108
|
+
const tokens = randomTokens(rand);
|
|
109
|
+
const toolRand = createRandom(seed * 7919 + i * 104729 + 13);
|
|
110
|
+
const toolCount = state === "done" ? 0 : 1 + Math.floor(toolRand() * 3);
|
|
111
|
+
const toolPick = [];
|
|
112
|
+
for (let k = 0; k < toolCount; k += 1) {
|
|
113
|
+
toolPick.push(MOCK_TOOL_TYPES[Math.floor(toolRand() * MOCK_TOOL_TYPES.length)]);
|
|
114
|
+
}
|
|
115
|
+
const toolNames = deck?.tools ?? (state === "error" ? [] : toolPick);
|
|
116
|
+
const toolCalls = [];
|
|
117
|
+
for (const tool of toolNames) {
|
|
118
|
+
if (!toolCalls.some((c) => c.tool === tool)) {
|
|
119
|
+
toolCalls.push({ tool, state: toolCalls.length === 0 ? "running" : "queued" });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const wantsFallback = deck ? !!deck.fallbackShift : toolRand() < 0.28;
|
|
124
|
+
const fallbackProvider = PROVIDER_MODELS[(i + (deck?.fallbackShift ?? 3)) % PROVIDER_MODELS.length].provider;
|
|
125
|
+
const fallback =
|
|
126
|
+
wantsFallback && (state === "pending" || state === "streaming") && fallbackProvider !== target.provider
|
|
127
|
+
? { from: fallbackProvider }
|
|
128
|
+
: null;
|
|
129
|
+
|
|
130
|
+
const concurrent = deck?.concurrent ?? (state === "done" || state === "error" ? 1 : 1 + Math.floor(toolRand() * 3));
|
|
131
|
+
if (deck?.cachedBoost) tokens.cached = 14000;
|
|
132
|
+
|
|
133
|
+
traces.push({
|
|
134
|
+
traceId: `mock-${i}-${Math.floor(rand() * 1e6).toString(36)}`,
|
|
135
|
+
cli: "mock",
|
|
136
|
+
connectionId,
|
|
137
|
+
account,
|
|
138
|
+
model: target.model,
|
|
139
|
+
provider: target.provider,
|
|
140
|
+
state,
|
|
141
|
+
startedAt,
|
|
142
|
+
elapsedMs: ageMs,
|
|
143
|
+
tokens,
|
|
144
|
+
cost: deck?.cost ?? Math.round((tokens.input * 0.000015 + tokens.output * 0.00006) * 100) / 100,
|
|
145
|
+
status: isError ? "error" : "200",
|
|
146
|
+
error: isError ? deck?.error ?? pick(rand, ERROR_REASONS) : null,
|
|
147
|
+
clientIcon: client.icon,
|
|
148
|
+
toolCalls,
|
|
149
|
+
tools: toolNames,
|
|
150
|
+
activeTool: toolNames[0] || null,
|
|
151
|
+
currentCommand: toolNames[0] ? `${toolNames[0]} task running...` : null,
|
|
152
|
+
fallback,
|
|
153
|
+
concurrent,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return traces;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function mockProviderDescriptors(traces) {
|
|
161
|
+
const seen = new Map();
|
|
162
|
+
for (const trace of traces) {
|
|
163
|
+
if (trace.provider && !seen.has(trace.provider)) {
|
|
164
|
+
seen.set(trace.provider, {
|
|
165
|
+
provider: trace.provider,
|
|
166
|
+
name: trace.provider.charAt(0).toUpperCase() + trace.provider.slice(1),
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return [...seen.values()];
|
|
171
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standard Trace Contract for Agent Factory.
|
|
3
|
+
* All watchers normalize their session logs into this uniform schema.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export function normalizeTrace(raw) {
|
|
7
|
+
const tokens = raw.tokens || {};
|
|
8
|
+
return {
|
|
9
|
+
traceId: String(raw.traceId || `trace-${Math.random().toString(36).slice(2, 9)}`),
|
|
10
|
+
cli: raw.cli || "agent", // "antigravity" | "claude" | "cursor" | etc.
|
|
11
|
+
connectionId: String(raw.connectionId || "Agent Desk"),
|
|
12
|
+
account: String(raw.account || "Default Workspace"),
|
|
13
|
+
model: String(raw.model || "Unknown Model"),
|
|
14
|
+
provider: String(raw.provider || "anthropic").toLowerCase(),
|
|
15
|
+
state: ["streaming", "pending", "done", "error"].includes(raw.state) ? raw.state : "streaming",
|
|
16
|
+
startedAt: typeof raw.startedAt === "number" ? raw.startedAt : Date.now(),
|
|
17
|
+
elapsedMs: typeof raw.elapsedMs === "number" ? raw.elapsedMs : 0,
|
|
18
|
+
tokens: {
|
|
19
|
+
input: Number(tokens.input || 0),
|
|
20
|
+
output: Number(tokens.output || 0),
|
|
21
|
+
cached: Number(tokens.cached || 0),
|
|
22
|
+
},
|
|
23
|
+
cost: Number(raw.cost || 0),
|
|
24
|
+
status: String(raw.status || "200"),
|
|
25
|
+
tools: Array.isArray(raw.tools) ? raw.tools : [],
|
|
26
|
+
activeTool: raw.activeTool || null,
|
|
27
|
+
currentCommand: raw.currentCommand || null,
|
|
28
|
+
error: raw.error ? String(raw.error) : null,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function extractProviders(traces) {
|
|
33
|
+
const seen = new Set();
|
|
34
|
+
const list = [];
|
|
35
|
+
for (const t of traces) {
|
|
36
|
+
const p = (t.provider || "other").toLowerCase();
|
|
37
|
+
if (!seen.has(p)) {
|
|
38
|
+
seen.add(p);
|
|
39
|
+
list.push({
|
|
40
|
+
provider: p,
|
|
41
|
+
name: p.charAt(0).toUpperCase() + p.slice(1),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return list;
|
|
46
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
|
+
import { generateMockTraces, mockProviderDescriptors, MOCK_PRESETS } from "./mockTraces";
|
|
5
|
+
|
|
6
|
+
export function useFactoryTraces({ mode = "live", preset = "cases" } = {}) {
|
|
7
|
+
const [traces, setTraces] = useState([]);
|
|
8
|
+
const [providers, setProviders] = useState([]);
|
|
9
|
+
const [connected, setConnected] = useState(false);
|
|
10
|
+
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
11
|
+
const [lastUpdated, setLastUpdated] = useState(null);
|
|
12
|
+
|
|
13
|
+
const eventSourceRef = useRef(null);
|
|
14
|
+
const watchdogTimerRef = useRef(null);
|
|
15
|
+
|
|
16
|
+
// Reconnection trigger
|
|
17
|
+
const [connectionEpoch, setConnectionEpoch] = useState(0);
|
|
18
|
+
|
|
19
|
+
const refresh = useCallback(() => {
|
|
20
|
+
setIsRefreshing(true);
|
|
21
|
+
if (eventSourceRef.current) {
|
|
22
|
+
eventSourceRef.current.close();
|
|
23
|
+
eventSourceRef.current = null;
|
|
24
|
+
}
|
|
25
|
+
setConnectionEpoch((prev) => prev + 1);
|
|
26
|
+
setTimeout(() => setIsRefreshing(false), 500);
|
|
27
|
+
}, []);
|
|
28
|
+
|
|
29
|
+
// 1. Mock Mode
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (mode !== "mock") return undefined;
|
|
32
|
+
|
|
33
|
+
const cfg = MOCK_PRESETS[preset] || MOCK_PRESETS.cases;
|
|
34
|
+
const updateMock = () => {
|
|
35
|
+
const next = generateMockTraces({ count: cfg.agents, cases: !!cfg.cases, errorRatio: cfg.errorRatio });
|
|
36
|
+
setTraces(next);
|
|
37
|
+
setProviders(mockProviderDescriptors(next));
|
|
38
|
+
setConnected(true);
|
|
39
|
+
setLastUpdated(Date.now());
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
updateMock();
|
|
43
|
+
const interval = setInterval(updateMock, 3000);
|
|
44
|
+
return () => clearInterval(interval);
|
|
45
|
+
}, [mode, preset, connectionEpoch]);
|
|
46
|
+
|
|
47
|
+
// 2. Live Mode (SSE + Wake-up Watchdog)
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (mode !== "live") return undefined;
|
|
50
|
+
|
|
51
|
+
let cancelled = false;
|
|
52
|
+
|
|
53
|
+
const resetWatchdog = () => {
|
|
54
|
+
if (watchdogTimerRef.current) clearTimeout(watchdogTimerRef.current);
|
|
55
|
+
// If no packet arrives for 7s, force reconnect
|
|
56
|
+
watchdogTimerRef.current = setTimeout(() => {
|
|
57
|
+
if (!cancelled) {
|
|
58
|
+
console.warn("[SSE] Watchdog timeout, reconnecting...");
|
|
59
|
+
setConnectionEpoch((prev) => prev + 1);
|
|
60
|
+
}
|
|
61
|
+
}, 7000);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const es = new EventSource("/api/traces/stream?source=live");
|
|
66
|
+
eventSourceRef.current = es;
|
|
67
|
+
|
|
68
|
+
es.onopen = () => {
|
|
69
|
+
if (!cancelled) {
|
|
70
|
+
setConnected(true);
|
|
71
|
+
resetWatchdog();
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
es.onmessage = (event) => {
|
|
76
|
+
if (cancelled || !event?.data) return;
|
|
77
|
+
try {
|
|
78
|
+
const payload = JSON.parse(event.data);
|
|
79
|
+
if (Array.isArray(payload.traces)) {
|
|
80
|
+
setTraces(payload.traces);
|
|
81
|
+
}
|
|
82
|
+
if (Array.isArray(payload.providers)) {
|
|
83
|
+
setProviders(payload.providers);
|
|
84
|
+
}
|
|
85
|
+
setLastUpdated(Date.now());
|
|
86
|
+
resetWatchdog();
|
|
87
|
+
} catch {
|
|
88
|
+
// ignore ping
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
es.onerror = () => {
|
|
93
|
+
if (!cancelled) {
|
|
94
|
+
setConnected(false);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
} catch {
|
|
98
|
+
setConnected(false);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return () => {
|
|
102
|
+
cancelled = true;
|
|
103
|
+
if (watchdogTimerRef.current) clearTimeout(watchdogTimerRef.current);
|
|
104
|
+
if (eventSourceRef.current) {
|
|
105
|
+
eventSourceRef.current.close();
|
|
106
|
+
eventSourceRef.current = null;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}, [mode, connectionEpoch]);
|
|
110
|
+
|
|
111
|
+
// 3. Auto-reconnect when laptop wakes up or tab gains focus
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
if (mode !== "live") return undefined;
|
|
114
|
+
|
|
115
|
+
const handleWakeup = () => {
|
|
116
|
+
if (document.visibilityState === "visible") {
|
|
117
|
+
console.log("[AgentFactory] Tab active / wake up, refreshing telemetry...");
|
|
118
|
+
refresh();
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
window.addEventListener("visibilitychange", handleWakeup);
|
|
123
|
+
window.addEventListener("online", handleWakeup);
|
|
124
|
+
window.addEventListener("focus", handleWakeup);
|
|
125
|
+
|
|
126
|
+
return () => {
|
|
127
|
+
window.removeEventListener("visibilitychange", handleWakeup);
|
|
128
|
+
window.removeEventListener("online", handleWakeup);
|
|
129
|
+
window.removeEventListener("focus", handleWakeup);
|
|
130
|
+
};
|
|
131
|
+
}, [mode, refresh]);
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
traces,
|
|
135
|
+
providers,
|
|
136
|
+
connected,
|
|
137
|
+
live: mode === "live",
|
|
138
|
+
isRefreshing,
|
|
139
|
+
refresh,
|
|
140
|
+
lastUpdated,
|
|
141
|
+
};
|
|
142
|
+
}
|