@jaimevalasek/aioson 1.4.0 → 1.5.1
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/CHANGELOG.md +31 -1
- package/LICENSE +661 -21
- package/README.md +3 -1
- package/docs/en/squad-dashboard.md +372 -0
- package/docs/openclaw-bridge.md +308 -0
- package/docs/pt/agentes.md +124 -10
- package/docs/pt/cenarios.md +46 -2
- package/docs/pt/comandos-cli.md +60 -1
- package/docs/pt/inicio-rapido.md +18 -2
- package/docs/pt/squad-dashboard.md +373 -0
- package/docs/testing/genome-2.0-matrix.md +5 -5
- package/docs/testing/genome-2.0-rollout.md +9 -9
- package/package.json +2 -2
- package/src/backup-local.js +74 -0
- package/src/cli.js +98 -0
- package/src/commands/backup-local-cmd.js +25 -0
- package/src/commands/runtime.js +242 -0
- package/src/commands/setup-context.js +7 -2
- package/src/commands/squad-daemon.js +209 -0
- package/src/commands/squad-dashboard.js +39 -0
- package/src/commands/squad-deploy.js +64 -0
- package/src/commands/squad-doctor.js +52 -0
- package/src/commands/squad-mcp.js +270 -0
- package/src/commands/squad-processes.js +56 -0
- package/src/commands/squad-recovery.js +42 -0
- package/src/commands/squad-roi.js +291 -0
- package/src/commands/squad-score.js +250 -0
- package/src/commands/squad-status.js +37 -1
- package/src/commands/squad-validate.js +62 -1
- package/src/commands/squad-webhook.js +160 -0
- package/src/commands/squad-worker.js +191 -0
- package/src/commands/squad-worktrees.js +75 -0
- package/src/commands/web-map.js +70 -0
- package/src/commands/web-scrape.js +71 -0
- package/src/constants.js +8 -0
- package/src/context-writer.js +45 -1
- package/src/i18n/messages/en.js +127 -1
- package/src/i18n/messages/es.js +117 -0
- package/src/i18n/messages/fr.js +117 -0
- package/src/i18n/messages/pt-BR.js +126 -1
- package/src/lib/webhook-server.js +328 -0
- package/src/mcp-connectors/registry.js +602 -0
- package/src/runtime-store.js +259 -2
- package/src/squad/external-session.js +180 -0
- package/src/squad/inter-squad.js +74 -0
- package/src/squad/recovery-context.js +201 -0
- package/src/squad/worktree-manager.js +114 -0
- package/src/squad-daemon.js +490 -0
- package/src/squad-dashboard/api.js +223 -0
- package/src/squad-dashboard/attachment-handler.js +93 -0
- package/src/squad-dashboard/context-monitor.js +157 -0
- package/src/squad-dashboard/execution-logs.js +115 -0
- package/src/squad-dashboard/hunk-review.js +209 -0
- package/src/squad-dashboard/metrics.js +133 -0
- package/src/squad-dashboard/process-monitor.js +125 -0
- package/src/squad-dashboard/renderer.js +858 -0
- package/src/squad-dashboard/server.js +232 -0
- package/src/squad-dashboard/styles.js +525 -0
- package/src/squad-dashboard/token-tracker.js +99 -0
- package/src/web.js +284 -0
- package/src/worker-runner.js +339 -0
- package/template/.aioson/agents/analyst.md +4 -0
- package/template/.aioson/agents/architect.md +4 -0
- package/template/.aioson/agents/dev.md +120 -11
- package/template/.aioson/agents/deyvin.md +8 -0
- package/template/.aioson/agents/neo.md +152 -0
- package/template/.aioson/agents/orache.md +17 -0
- package/template/.aioson/agents/orchestrator.md +26 -0
- package/template/.aioson/agents/product.md +60 -12
- package/template/.aioson/agents/qa.md +1 -0
- package/template/.aioson/agents/setup.md +63 -19
- package/template/.aioson/agents/sheldon.md +603 -0
- package/template/.aioson/agents/squad.md +191 -0
- package/template/.aioson/agents/tester.md +254 -0
- package/template/.aioson/agents/ux-ui.md +12 -0
- package/template/.aioson/config.md +6 -0
- package/template/.aioson/locales/en/agents/analyst.md +8 -0
- package/template/.aioson/locales/en/agents/architect.md +8 -0
- package/template/.aioson/locales/en/agents/dev.md +66 -7
- package/template/.aioson/locales/en/agents/deyvin.md +8 -0
- package/template/.aioson/locales/en/agents/neo.md +8 -0
- package/template/.aioson/locales/en/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/en/agents/qa.md +49 -0
- package/template/.aioson/locales/en/agents/setup.md +2 -1
- package/template/.aioson/locales/en/agents/sheldon.md +340 -0
- package/template/.aioson/locales/en/agents/ux-ui.md +8 -0
- package/template/.aioson/locales/es/agents/analyst.md +8 -0
- package/template/.aioson/locales/es/agents/architect.md +8 -0
- package/template/.aioson/locales/es/agents/dev.md +66 -7
- package/template/.aioson/locales/es/agents/deyvin.md +8 -0
- package/template/.aioson/locales/es/agents/neo.md +48 -0
- package/template/.aioson/locales/es/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/es/agents/qa.md +26 -0
- package/template/.aioson/locales/es/agents/setup.md +2 -1
- package/template/.aioson/locales/es/agents/sheldon.md +192 -0
- package/template/.aioson/locales/es/agents/squad.md +63 -0
- package/template/.aioson/locales/es/agents/ux-ui.md +8 -0
- package/template/.aioson/locales/fr/agents/analyst.md +8 -0
- package/template/.aioson/locales/fr/agents/architect.md +8 -0
- package/template/.aioson/locales/fr/agents/dev.md +66 -7
- package/template/.aioson/locales/fr/agents/deyvin.md +8 -0
- package/template/.aioson/locales/fr/agents/neo.md +48 -0
- package/template/.aioson/locales/fr/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/fr/agents/qa.md +26 -0
- package/template/.aioson/locales/fr/agents/setup.md +2 -1
- package/template/.aioson/locales/fr/agents/sheldon.md +192 -0
- package/template/.aioson/locales/fr/agents/squad.md +63 -0
- package/template/.aioson/locales/fr/agents/ux-ui.md +8 -0
- package/template/.aioson/locales/pt-BR/agents/analyst.md +19 -0
- package/template/.aioson/locales/pt-BR/agents/architect.md +19 -0
- package/template/.aioson/locales/pt-BR/agents/dev.md +75 -12
- package/template/.aioson/locales/pt-BR/agents/deyvin.md +8 -0
- package/template/.aioson/locales/pt-BR/agents/neo.md +147 -0
- package/template/.aioson/locales/pt-BR/agents/orchestrator.md +26 -0
- package/template/.aioson/locales/pt-BR/agents/product.md +8 -3
- package/template/.aioson/locales/pt-BR/agents/qa.md +60 -0
- package/template/.aioson/locales/pt-BR/agents/setup.md +2 -1
- package/template/.aioson/locales/pt-BR/agents/sheldon.md +192 -0
- package/template/.aioson/locales/pt-BR/agents/squad.md +105 -0
- package/template/.aioson/locales/pt-BR/agents/ux-ui.md +8 -0
- package/template/.aioson/schemas/squad-blueprint.schema.json +21 -0
- package/template/.aioson/schemas/squad-manifest.schema.json +178 -1
- package/template/.aioson/skills/design/bold-editorial-ui/SKILL.md +205 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/art-direction.md +338 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/components.md +977 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/dashboards.md +218 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/design-tokens.md +326 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/motion.md +461 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/patterns.md +293 -0
- package/template/.aioson/skills/design/bold-editorial-ui/references/websites.md +352 -0
- package/template/.aioson/skills/design/clean-saas-ui/SKILL.md +210 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/art-direction.md +319 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/components.md +365 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/dashboards.md +196 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/design-tokens.md +244 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/motion.md +235 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/patterns.md +215 -0
- package/template/.aioson/skills/design/clean-saas-ui/references/websites.md +295 -0
- package/template/.aioson/skills/design/cognitive-core-ui/SKILL.md +55 -9
- package/template/.aioson/skills/design/cognitive-core-ui/references/art-direction.md +339 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/components.md +1 -1
- package/template/.aioson/skills/design/cognitive-core-ui/references/dashboards.md +100 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/design-tokens.md +43 -9
- package/template/.aioson/skills/design/cognitive-core-ui/references/motion.md +40 -0
- package/template/.aioson/skills/design/cognitive-core-ui/references/patterns.md +1 -1
- package/template/.aioson/skills/design/cognitive-core-ui/references/websites.md +99 -12
- package/template/.aioson/skills/design/warm-craft-ui/SKILL.md +209 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/art-direction.md +324 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/components.md +508 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/dashboards.md +223 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/design-tokens.md +374 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/motion.md +356 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/patterns.md +288 -0
- package/template/.aioson/skills/design/warm-craft-ui/references/websites.md +289 -0
- package/template/.aioson/skills/premium-visual-design/SKILL.md +83 -0
- package/template/.aioson/skills/premium-visual-design/components/agent-badge.md +92 -0
- package/template/.aioson/skills/premium-visual-design/components/dependency-node.md +102 -0
- package/template/.aioson/skills/premium-visual-design/components/mention-autocomplete.md +136 -0
- package/template/.aioson/skills/premium-visual-design/components/notification-center.md +136 -0
- package/template/.aioson/skills/premium-visual-design/components/review-action-bar.md +188 -0
- package/template/.aioson/skills/premium-visual-design/components/team-switcher.md +131 -0
- package/template/.aioson/skills/premium-visual-design/patterns/agent-message-thread.md +198 -0
- package/template/.aioson/skills/premium-visual-design/patterns/notification-panel.md +275 -0
- package/template/.aioson/skills/premium-visual-design/patterns/review-workflow-ui.md +234 -0
- package/template/.aioson/skills/premium-visual-design/patterns/task-dependency-graph.md +147 -0
- package/template/.aioson/skills/premium-visual-design/tokens/status-extended.md +142 -0
- package/template/.aioson/skills/squad/formats/catalog.json +15 -0
- package/template/.aioson/skills/squad/formats/content/blog-post.md +47 -0
- package/template/.aioson/skills/squad/formats/content/newsletter.md +47 -0
- package/template/.aioson/skills/squad/formats/creative/podcast-script.md +43 -0
- package/template/.aioson/skills/squad/formats/creative/video-script.md +41 -0
- package/template/.aioson/skills/squad/formats/social/instagram-feed.md +42 -0
- package/template/.aioson/skills/squad/formats/social/linkedin-post.md +42 -0
- package/template/.aioson/skills/squad/formats/social/tiktok.md +39 -0
- package/template/.aioson/skills/squad/formats/social/twitter-thread.md +39 -0
- package/template/.aioson/skills/squad/formats/social/youtube-long.md +47 -0
- package/template/.aioson/skills/squad/formats/social/youtube-shorts.md +39 -0
- package/template/.aioson/skills/squad/patterns/multi-platform-pattern.md +108 -0
- package/template/.aioson/skills/squad/patterns/persona-based-pattern.md +98 -0
- package/template/.aioson/skills/squad/patterns/pipeline-pattern.md +106 -0
- package/template/.aioson/skills/squad/patterns/review-loop-pattern.md +81 -0
- package/template/.aioson/skills/squad/references/checklist-templates.md +122 -0
- package/template/.aioson/skills/squad/references/executor-archetypes.md +123 -0
- package/template/.aioson/skills/squad/references/workflow-templates.md +169 -0
- package/template/.aioson/skills/static/debugging-protocol.md +42 -0
- package/template/.aioson/skills/static/git-worktrees.md +36 -0
- package/template/.aioson/tasks/implementation-plan.md +19 -0
- package/template/.aioson/tasks/squad-design.md +28 -0
- package/template/.aioson/tasks/squad-profile.md +48 -0
- package/template/.aioson/tasks/squad-review.md +61 -0
- package/template/.aioson/tasks/squad-task-decompose.md +66 -0
- package/template/.claude/commands/aioson/agent/neo.md +5 -0
- package/template/.claude/commands/aioson/agent/tester.md +5 -0
- package/template/.gemini/GEMINI.md +1 -0
- package/template/.gemini/commands/aios-neo.toml +4 -0
- package/template/.gemini/commands/aios-tester.toml +6 -0
- package/template/AGENTS.md +3 -0
- package/template/CLAUDE.md +5 -2
- package/template/OPENCODE.md +2 -0
|
@@ -0,0 +1,858 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { getInlineCSS, getInlineJS } = require('./styles');
|
|
4
|
+
const { CATEGORIES } = require('./context-monitor');
|
|
5
|
+
const { TOKEN_CATEGORIES } = require('./token-tracker');
|
|
6
|
+
|
|
7
|
+
// CSS color variables per category (must match styles.js :root)
|
|
8
|
+
const CTX_COLORS = {
|
|
9
|
+
system_prompt: 'var(--ctx-system)',
|
|
10
|
+
conversation_history: 'var(--ctx-history)',
|
|
11
|
+
tool_outputs: 'var(--ctx-tools)',
|
|
12
|
+
files_loaded: 'var(--ctx-files)',
|
|
13
|
+
inline_data: 'var(--ctx-inline)',
|
|
14
|
+
other: 'var(--ctx-other)'
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const TOKEN_COLORS = {
|
|
18
|
+
input_tokens: 'var(--ctx-system)',
|
|
19
|
+
output_tokens: 'var(--ctx-history)',
|
|
20
|
+
tool_use_input: 'var(--ctx-tools)',
|
|
21
|
+
tool_outputs: 'var(--ctx-files)',
|
|
22
|
+
cache_write: 'var(--ctx-inline)',
|
|
23
|
+
cache_read: 'var(--ctx-other)'
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function esc(str) {
|
|
27
|
+
return String(str || '')
|
|
28
|
+
.replace(/&/g, '&')
|
|
29
|
+
.replace(/</g, '<')
|
|
30
|
+
.replace(/>/g, '>')
|
|
31
|
+
.replace(/"/g, '"');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function modeBadge(mode) {
|
|
35
|
+
const cls = ({ content: 'badge-content', software: 'badge-software', research: 'badge-research' })[mode] || 'badge-mixed';
|
|
36
|
+
return `<span class="badge ${cls}">${esc(mode)}</span>`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function statusDot(status) {
|
|
40
|
+
const cls = ({
|
|
41
|
+
active: 'status-active', completed: 'status-active',
|
|
42
|
+
stale: 'status-stale', running: 'status-stale',
|
|
43
|
+
error: 'status-error', failed: 'status-error'
|
|
44
|
+
})[status] || 'status-inactive';
|
|
45
|
+
return `<span class="status-dot ${cls}"></span>`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function progressBar(current, total) {
|
|
49
|
+
const pct = total > 0 ? Math.round((current / total) * 100) : 0;
|
|
50
|
+
return `<div class="progress-bar"><div class="progress-fill" style="width:${pct}%"></div></div>
|
|
51
|
+
<div class="sub">${current}/${total} (${pct}%)</div>`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// --- Context & Token widgets ---
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* SVG donut chart for context usage of a single agent.
|
|
58
|
+
* @param {object} agent — enriched agent data from context-monitor
|
|
59
|
+
*/
|
|
60
|
+
function renderContextDonut(agent) {
|
|
61
|
+
if (!agent) return '';
|
|
62
|
+
const cats = agent.categories || {};
|
|
63
|
+
const total = agent.totalUsed || 0;
|
|
64
|
+
const windowSize = agent.windowSize || 0;
|
|
65
|
+
const pct = windowSize > 0 ? Math.round((total / windowSize) * 100) : 0;
|
|
66
|
+
|
|
67
|
+
// SVG donut via stroke-dasharray on a circle (r=30, cx=40, cy=40)
|
|
68
|
+
const r = 30;
|
|
69
|
+
const circumference = 2 * Math.PI * r; // ≈188.5
|
|
70
|
+
let offset = 0;
|
|
71
|
+
const segments = CATEGORIES.map(key => {
|
|
72
|
+
const val = cats[key] || 0;
|
|
73
|
+
const len = total > 0 ? (val / total) * circumference : 0;
|
|
74
|
+
const seg = `<circle
|
|
75
|
+
r="${r}" cx="40" cy="40"
|
|
76
|
+
fill="none"
|
|
77
|
+
stroke="${CTX_COLORS[key]}"
|
|
78
|
+
stroke-width="10"
|
|
79
|
+
stroke-dasharray="${len.toFixed(2)} ${(circumference - len).toFixed(2)}"
|
|
80
|
+
stroke-dashoffset="${(-offset).toFixed(2)}"
|
|
81
|
+
transform="rotate(-90 40 40)"
|
|
82
|
+
/>`;
|
|
83
|
+
offset += len;
|
|
84
|
+
return seg;
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const levelBadge = `<span class="ctx-badge ctx-badge-${agent.warningLevel || 'unknown'}">${agent.warningLevel || 'unknown'}</span>`;
|
|
88
|
+
|
|
89
|
+
const legend = CATEGORIES.map(key => {
|
|
90
|
+
const val = cats[key] || 0;
|
|
91
|
+
if (val === 0) return '';
|
|
92
|
+
const kPct = total > 0 ? Math.round((val / total) * 100) : 0;
|
|
93
|
+
const label = key.replace(/_/g, ' ');
|
|
94
|
+
return `<div class="ctx-legend-item">
|
|
95
|
+
<span class="ctx-legend-dot" style="background:${CTX_COLORS[key]}"></span>
|
|
96
|
+
${esc(label)}: ${kPct}%
|
|
97
|
+
</div>`;
|
|
98
|
+
}).join('');
|
|
99
|
+
|
|
100
|
+
return `<div class="ctx-donut-wrap">
|
|
101
|
+
<svg class="ctx-donut-svg" width="80" height="80" viewBox="0 0 80 80">
|
|
102
|
+
<circle r="${r}" cx="40" cy="40" fill="none" stroke="var(--bg-hover)" stroke-width="10" class="ctx-donut-track"/>
|
|
103
|
+
${segments.join('')}
|
|
104
|
+
<text x="40" y="38" text-anchor="middle" class="ctx-center-text">${pct}%</text>
|
|
105
|
+
<text x="40" y="49" text-anchor="middle" class="ctx-center-sub">of window</text>
|
|
106
|
+
</svg>
|
|
107
|
+
<div>
|
|
108
|
+
<div style="margin-bottom:6px">${levelBadge}</div>
|
|
109
|
+
<div class="ctx-legend">${legend || '<span style="font-size:11px;color:var(--text-muted)">No data</span>'}</div>
|
|
110
|
+
</div>
|
|
111
|
+
</div>`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Stacked bar chart for token usage across sessions.
|
|
116
|
+
* @param {Array} sessions — enriched session objects from token-tracker
|
|
117
|
+
* @param {boolean} wasteFlag
|
|
118
|
+
*/
|
|
119
|
+
function renderTokenStackedBar(sessions, wasteFlag) {
|
|
120
|
+
if (!sessions || sessions.length === 0) {
|
|
121
|
+
return '<div style="font-size:12px;color:var(--text-muted)">No session data</div>';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Aggregate all sessions into a single breakdown for the bar
|
|
125
|
+
const agg = {};
|
|
126
|
+
for (const key of TOKEN_CATEGORIES) agg[key] = 0;
|
|
127
|
+
for (const session of sessions) {
|
|
128
|
+
const bd = session.breakdown || {};
|
|
129
|
+
for (const key of TOKEN_CATEGORIES) agg[key] += bd[key] || 0;
|
|
130
|
+
}
|
|
131
|
+
const grandTotal = TOKEN_CATEGORIES.reduce((s, k) => s + agg[k], 0);
|
|
132
|
+
|
|
133
|
+
const segments = TOKEN_CATEGORIES.map(key => {
|
|
134
|
+
const w = grandTotal > 0 ? ((agg[key] / grandTotal) * 100).toFixed(1) : 0;
|
|
135
|
+
if (w <= 0) return '';
|
|
136
|
+
return `<div class="token-bar-seg" style="width:${w}%;background:${TOKEN_COLORS[key]}" title="${key}: ${agg[key].toLocaleString()} tokens"></div>`;
|
|
137
|
+
}).join('');
|
|
138
|
+
|
|
139
|
+
const legendItems = TOKEN_CATEGORIES.map(key => {
|
|
140
|
+
if (!agg[key]) return '';
|
|
141
|
+
const label = key.replace(/_/g, ' ');
|
|
142
|
+
return `<div class="token-bar-legend-item">
|
|
143
|
+
<span class="token-bar-legend-dot" style="background:${TOKEN_COLORS[key]}"></span>
|
|
144
|
+
${esc(label)}
|
|
145
|
+
</div>`;
|
|
146
|
+
}).join('');
|
|
147
|
+
|
|
148
|
+
const waste = wasteFlag
|
|
149
|
+
? `<div class="waste-hint">⚠ High tool output ratio — consider summarizing tool results to reduce token waste</div>`
|
|
150
|
+
: '';
|
|
151
|
+
|
|
152
|
+
return `<div class="token-bar-wrap">
|
|
153
|
+
<div class="token-stacked-bar">${segments}</div>
|
|
154
|
+
<div class="token-bar-legend">${legendItems}</div>
|
|
155
|
+
${waste}
|
|
156
|
+
</div>`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// --- Autonomy level ---
|
|
160
|
+
|
|
161
|
+
const AUTONOMY_LEVELS = {
|
|
162
|
+
autonomous: { label: 'Autonomous', icon: '🤖', css: 'autonomy-auto' },
|
|
163
|
+
semi: { label: 'Semi', icon: '🤝', css: 'autonomy-semi' },
|
|
164
|
+
'approve-each': { label: 'Approve Each', icon: '👤', css: 'autonomy-approve' }
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
function autonomyBadge(level) {
|
|
168
|
+
const info = AUTONOMY_LEVELS[level] || AUTONOMY_LEVELS['semi'];
|
|
169
|
+
return `<span class="autonomy-badge ${info.css}">${info.icon} ${esc(info.label)}</span>`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function autonomySelector(agentSlug, currentLevel) {
|
|
173
|
+
const opts = Object.entries(AUTONOMY_LEVELS).map(([val, info]) =>
|
|
174
|
+
`<option value="${val}"${currentLevel === val ? ' selected' : ''}>${info.icon} ${esc(info.label)}</option>`
|
|
175
|
+
).join('');
|
|
176
|
+
return `<select class="autonomy-select" data-agent="${esc(agentSlug)}" onchange="setAutonomy('${esc(agentSlug)}',this.value)">${opts}</select>`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// --- Execution logs timeline ---
|
|
180
|
+
|
|
181
|
+
const ENTRY_ICONS = {
|
|
182
|
+
tool_call: '🔧',
|
|
183
|
+
reasoning: '💭',
|
|
184
|
+
milestone: '🏁',
|
|
185
|
+
error: '🔴'
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
function renderEntryToolCall(entry) {
|
|
189
|
+
const inputStr = entry.input != null ? JSON.stringify(entry.input, null, 2) : '';
|
|
190
|
+
const outputStr = entry.output != null ? JSON.stringify(entry.output, null, 2) : '';
|
|
191
|
+
const dur = entry.durationMs != null ? `<span class="log-dur">${entry.durationMs}ms</span>` : '';
|
|
192
|
+
return `<div class="log-entry log-tool-call">
|
|
193
|
+
<div class="log-entry-header">
|
|
194
|
+
<span class="log-icon">${ENTRY_ICONS.tool_call}</span>
|
|
195
|
+
<strong class="log-tool-name">${esc(entry.toolName || 'tool')}</strong>
|
|
196
|
+
${dur}
|
|
197
|
+
<span class="log-ts">${esc(entry.timestamp || '')}</span>
|
|
198
|
+
<button class="log-toggle" onclick="toggleLogDetail(this)">▶ input/output</button>
|
|
199
|
+
</div>
|
|
200
|
+
<div class="log-detail" style="display:none">
|
|
201
|
+
${inputStr ? `<div class="log-section-label">Input</div><pre class="log-pre">${esc(inputStr)}</pre>` : ''}
|
|
202
|
+
${outputStr ? `<div class="log-section-label">Output</div><pre class="log-pre">${esc(outputStr)}</pre>` : ''}
|
|
203
|
+
</div>
|
|
204
|
+
</div>`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function renderEntryReasoning(entry) {
|
|
208
|
+
return `<div class="log-entry log-reasoning">
|
|
209
|
+
<span class="log-icon">${ENTRY_ICONS.reasoning}</span>
|
|
210
|
+
<span class="log-ts">${esc(entry.timestamp || '')}</span>
|
|
211
|
+
<div class="log-reasoning-text">${esc(entry.text || '')}</div>
|
|
212
|
+
</div>`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function renderEntryMilestone(entry) {
|
|
216
|
+
return `<div class="log-entry log-milestone">
|
|
217
|
+
<span class="log-icon">${ENTRY_ICONS.milestone}</span>
|
|
218
|
+
<strong>${esc(entry.label || 'milestone')}</strong>
|
|
219
|
+
<span class="log-ts">${esc(entry.timestamp || '')}</span>
|
|
220
|
+
</div>`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function renderEntryError(entry) {
|
|
224
|
+
return `<div class="log-entry log-error">
|
|
225
|
+
<span class="log-icon">${ENTRY_ICONS.error}</span>
|
|
226
|
+
<strong class="log-error-msg">${esc(entry.message || 'error')}</strong>
|
|
227
|
+
<span class="log-ts">${esc(entry.timestamp || '')}</span>
|
|
228
|
+
${entry.stack ? `<pre class="log-pre log-stack">${esc(entry.stack)}</pre>` : ''}
|
|
229
|
+
</div>`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function renderLogEntry(entry) {
|
|
233
|
+
switch (entry.type) {
|
|
234
|
+
case 'tool_call': return renderEntryToolCall(entry);
|
|
235
|
+
case 'reasoning': return renderEntryReasoning(entry);
|
|
236
|
+
case 'milestone': return renderEntryMilestone(entry);
|
|
237
|
+
case 'error': return renderEntryError(entry);
|
|
238
|
+
default:
|
|
239
|
+
return `<div class="log-entry"><span class="log-icon">•</span><span class="log-ts">${esc(entry.timestamp || '')}</span> ${esc(entry.type || 'unknown')}</div>`;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function renderExecutionLogsPanel(data) {
|
|
244
|
+
const sessions = data.executionLogs;
|
|
245
|
+
if (!sessions || sessions.length === 0) {
|
|
246
|
+
return `<div class="empty"><h3>No execution logs</h3><p>Logs are written by agents as they execute tasks.</p></div>`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
let html = '';
|
|
250
|
+
for (const session of sessions) {
|
|
251
|
+
const entryCount = (session.entries || []).length;
|
|
252
|
+
const sessionLabel = session.agentSlug
|
|
253
|
+
? `${esc(session.agentSlug)} — ${esc(session.startedAt || session.timestamp)}`
|
|
254
|
+
: esc(session.sessionId);
|
|
255
|
+
|
|
256
|
+
const entriesHtml = (session.entries || []).map(renderLogEntry).join('\n');
|
|
257
|
+
|
|
258
|
+
html += `<div class="card log-session" style="margin-bottom:16px">
|
|
259
|
+
<div class="log-session-header">
|
|
260
|
+
<strong>${sessionLabel}</strong>
|
|
261
|
+
<span class="badge badge-mixed">${entryCount} entries</span>
|
|
262
|
+
${session.summary ? `<span class="log-summary">${esc(session.summary)}</span>` : ''}
|
|
263
|
+
</div>
|
|
264
|
+
<div class="log-timeline">${entriesHtml || '<div style="color:var(--text-muted);font-size:12px">No entries</div>'}</div>
|
|
265
|
+
</div>`;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return html;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// --- Page layouts ---
|
|
272
|
+
|
|
273
|
+
function renderLayout(title, sidebarHTML, mainHTML, squadSlug) {
|
|
274
|
+
return `<!DOCTYPE html>
|
|
275
|
+
<html lang="en">
|
|
276
|
+
<head>
|
|
277
|
+
<meta charset="utf-8">
|
|
278
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
279
|
+
<title>${esc(title)} - Squad Dashboard</title>
|
|
280
|
+
<style>${getInlineCSS()}</style>
|
|
281
|
+
</head>
|
|
282
|
+
<body data-squad="${esc(squadSlug || '')}">
|
|
283
|
+
<div class="layout">
|
|
284
|
+
<nav class="sidebar">
|
|
285
|
+
<h1>Squad Dashboard</h1>
|
|
286
|
+
${sidebarHTML}
|
|
287
|
+
</nav>
|
|
288
|
+
<div class="main">
|
|
289
|
+
${mainHTML}
|
|
290
|
+
</div>
|
|
291
|
+
</div>
|
|
292
|
+
<script>${getInlineJS()}</script>
|
|
293
|
+
</body>
|
|
294
|
+
</html>`;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function renderSquadSidebar(squads, activeSlug) {
|
|
298
|
+
if (!squads || squads.length === 0) {
|
|
299
|
+
return '<div style="padding:20px;color:var(--text-muted)">No squads found</div>';
|
|
300
|
+
}
|
|
301
|
+
return squads.map(s => {
|
|
302
|
+
const active = s.slug === activeSlug ? ' active' : '';
|
|
303
|
+
const mode = s.mode ? `<span class="squad-mode">${esc(s.mode)}</span>` : '';
|
|
304
|
+
return `<a href="/squad/${esc(s.slug)}" class="${active}">${esc(s.name || s.slug)}${mode}</a>`;
|
|
305
|
+
}).join('\n');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// --- Home page (list of squads) ---
|
|
309
|
+
|
|
310
|
+
function renderHomePage(squads) {
|
|
311
|
+
const sidebar = renderSquadSidebar(squads, null);
|
|
312
|
+
let main;
|
|
313
|
+
if (!squads || squads.length === 0) {
|
|
314
|
+
main = `<div class="empty"><h3>No squads found</h3><p>Create a squad with <code>@squad design <slug></code></p></div>`;
|
|
315
|
+
} else {
|
|
316
|
+
const rows = squads.map(s => `
|
|
317
|
+
<tr>
|
|
318
|
+
<td><a href="/squad/${esc(s.slug)}">${esc(s.name || s.slug)}</a></td>
|
|
319
|
+
<td>${modeBadge(s.mode)}</td>
|
|
320
|
+
<td>${esc(s.goal || '-')}</td>
|
|
321
|
+
<td>${s.executorCount || 0}</td>
|
|
322
|
+
<td>${esc(s.status || 'active')}</td>
|
|
323
|
+
</tr>
|
|
324
|
+
`).join('');
|
|
325
|
+
main = `
|
|
326
|
+
<div class="page-header"><h2>All Squads</h2><span class="badge badge-mixed">${squads.length} squads</span></div>
|
|
327
|
+
<div class="card">
|
|
328
|
+
<table>
|
|
329
|
+
<thead><tr><th>Squad</th><th>Mode</th><th>Goal</th><th>Executors</th><th>Status</th></tr></thead>
|
|
330
|
+
<tbody>${rows}</tbody>
|
|
331
|
+
</table>
|
|
332
|
+
</div>`;
|
|
333
|
+
}
|
|
334
|
+
return renderLayout('All Squads', sidebar, main, '');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// --- Squad detail page ---
|
|
338
|
+
|
|
339
|
+
function renderSquadPage(squad, panels, data, allSquads) {
|
|
340
|
+
const sidebar = renderSquadSidebar(allSquads, squad.slug);
|
|
341
|
+
|
|
342
|
+
const tabButtons = panels.map((p, i) =>
|
|
343
|
+
`<button class="tab${i === 0 ? ' active' : ''}" data-tab="panel-${p}">${panelLabel(p)}</button>`
|
|
344
|
+
).join('');
|
|
345
|
+
|
|
346
|
+
const tabContents = panels.map((p, i) =>
|
|
347
|
+
`<div id="panel-${p}" class="tab-content${i === 0 ? ' active' : ''}">${renderPanel(p, data, squad)}</div>`
|
|
348
|
+
).join('');
|
|
349
|
+
|
|
350
|
+
const main = `
|
|
351
|
+
<div class="page-header">
|
|
352
|
+
<h2>${esc(squad.name || squad.slug)} ${modeBadge(squad.mode)}</h2>
|
|
353
|
+
</div>
|
|
354
|
+
<div class="tab-group">
|
|
355
|
+
<div class="tabs">${tabButtons}</div>
|
|
356
|
+
${tabContents}
|
|
357
|
+
</div>`;
|
|
358
|
+
|
|
359
|
+
return renderLayout(squad.name || squad.slug, sidebar, main, squad.slug);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function panelLabel(panel) {
|
|
363
|
+
const labels = {
|
|
364
|
+
overview: 'Overview',
|
|
365
|
+
content: 'Content',
|
|
366
|
+
learnings: 'Learnings',
|
|
367
|
+
logs: 'Events',
|
|
368
|
+
'content-preview': 'Preview',
|
|
369
|
+
tasks: 'Tasks',
|
|
370
|
+
'code-output': 'Code',
|
|
371
|
+
integrations: 'Integrations',
|
|
372
|
+
channels: 'Channels',
|
|
373
|
+
pipeline: 'Pipeline',
|
|
374
|
+
metrics: 'Metrics',
|
|
375
|
+
processes: 'Processes',
|
|
376
|
+
review: 'Review',
|
|
377
|
+
'execution-logs': 'Execution Logs'
|
|
378
|
+
};
|
|
379
|
+
return labels[panel] || panel;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function renderPanel(panel, data, squad) {
|
|
383
|
+
switch (panel) {
|
|
384
|
+
case 'overview': return renderOverview(data);
|
|
385
|
+
case 'content': return renderContentPanel(data);
|
|
386
|
+
case 'learnings': return renderLearningsPanel(data);
|
|
387
|
+
case 'logs': return renderEventsPanel(data);
|
|
388
|
+
case 'pipeline': return renderPipelinePanel(data);
|
|
389
|
+
case 'integrations': return renderIntegrationsPanel(squad, data);
|
|
390
|
+
case 'metrics': return renderMetricsPanel(data);
|
|
391
|
+
case 'processes': return renderProcessesPanel(data);
|
|
392
|
+
case 'review': return renderHunkReviewPanel(data);
|
|
393
|
+
case 'tasks': return renderTasksPanel(data, squad);
|
|
394
|
+
case 'execution-logs': return renderExecutionLogsPanel(data);
|
|
395
|
+
default: return `<div class="empty"><h3>${esc(panelLabel(panel))}</h3><p>Coming soon</p></div>`;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// --- Overview panel ---
|
|
400
|
+
|
|
401
|
+
function renderOverview(data) {
|
|
402
|
+
const ov = data.overview || {};
|
|
403
|
+
const cards = [
|
|
404
|
+
{ label: 'Content Items', value: ov.contentItems || 0 },
|
|
405
|
+
{ label: 'Sessions', value: ov.sessions || 0 },
|
|
406
|
+
{ label: 'Learnings', value: ov.learnings || 0 },
|
|
407
|
+
{ label: 'Delivery Rate', value: ov.deliveryRate != null ? `${ov.deliveryRate}%` : 'N/A' }
|
|
408
|
+
];
|
|
409
|
+
|
|
410
|
+
let html = `<div class="grid grid-4">${cards.map(c => `
|
|
411
|
+
<div class="card">
|
|
412
|
+
<h3>${esc(c.label)}</h3>
|
|
413
|
+
<div class="value" id="metric-${c.label.toLowerCase().replace(/\s+/g, '_')}">${esc(String(c.value))}</div>
|
|
414
|
+
</div>`).join('')}</div>`;
|
|
415
|
+
|
|
416
|
+
// Execution plan progress
|
|
417
|
+
const plan = ov.executionPlan;
|
|
418
|
+
if (plan) {
|
|
419
|
+
html += `<div class="card" style="margin-top:16px">
|
|
420
|
+
<h3>Execution Plan</h3>
|
|
421
|
+
<div style="font-size:14px">${statusDot(plan.status)} ${esc(plan.plan_slug)} (${esc(plan.status)})</div>
|
|
422
|
+
${progressBar(plan.rounds_completed, plan.rounds_total)}
|
|
423
|
+
</div>`;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Learning stats
|
|
427
|
+
const ls = ov.learningStats;
|
|
428
|
+
if (ls && (ls.active + ls.stale + ls.archived + ls.promoted) > 0) {
|
|
429
|
+
html += `<div class="card" style="margin-top:16px">
|
|
430
|
+
<h3>Learning Stats</h3>
|
|
431
|
+
<div class="grid grid-4" style="margin-top:8px">
|
|
432
|
+
<div>${statusDot('active')} Active: ${ls.active}</div>
|
|
433
|
+
<div>${statusDot('stale')} Stale: ${ls.stale}</div>
|
|
434
|
+
<div>${statusDot('inactive')} Archived: ${ls.archived}</div>
|
|
435
|
+
<div>${statusDot('completed')} Promoted: ${ls.promoted}</div>
|
|
436
|
+
</div>
|
|
437
|
+
</div>`;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Context & Token observability section
|
|
441
|
+
const contextData = data.contextData;
|
|
442
|
+
const tokenData = data.tokenData;
|
|
443
|
+
const agentSlugs = new Set([
|
|
444
|
+
...Object.keys((contextData && contextData.agents) || {}),
|
|
445
|
+
...Object.keys((tokenData && tokenData.agents) || {})
|
|
446
|
+
]);
|
|
447
|
+
|
|
448
|
+
if (agentSlugs.size > 0) {
|
|
449
|
+
for (const agentSlug of agentSlugs) {
|
|
450
|
+
const agentCtx = contextData && contextData.agents && contextData.agents[agentSlug];
|
|
451
|
+
const agentTok = tokenData && tokenData.agents && tokenData.agents[agentSlug];
|
|
452
|
+
|
|
453
|
+
const donutHTML = agentCtx ? renderContextDonut(agentCtx) : '<div style="font-size:12px;color:var(--text-muted)">No context data</div>';
|
|
454
|
+
const totalTokens = agentTok ? agentTok.totalTokens.toLocaleString() : '—';
|
|
455
|
+
const totalCost = agentTok ? `$${agentTok.totalCostUsd.toFixed(4)}` : '—';
|
|
456
|
+
const barHTML = agentTok ? renderTokenStackedBar(agentTok.sessions, agentTok.wasteFlag) : '';
|
|
457
|
+
|
|
458
|
+
html += `<div class="card" style="margin-top:16px">
|
|
459
|
+
<h3>Observability — ${esc(agentSlug)}</h3>
|
|
460
|
+
<div class="grid grid-2" style="margin-top:12px">
|
|
461
|
+
<div>
|
|
462
|
+
<div style="font-size:11px;text-transform:uppercase;color:var(--text-muted);margin-bottom:8px">Context Window</div>
|
|
463
|
+
${donutHTML}
|
|
464
|
+
</div>
|
|
465
|
+
<div>
|
|
466
|
+
<div style="font-size:11px;text-transform:uppercase;color:var(--text-muted);margin-bottom:8px">
|
|
467
|
+
Token Usage <span style="color:var(--text-muted);font-weight:400">${totalTokens} tokens · ${totalCost}</span>
|
|
468
|
+
</div>
|
|
469
|
+
${barHTML}
|
|
470
|
+
</div>
|
|
471
|
+
</div>
|
|
472
|
+
</div>`;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return html;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// --- Content panel ---
|
|
480
|
+
|
|
481
|
+
function renderContentPanel(data) {
|
|
482
|
+
const items = data.content || [];
|
|
483
|
+
if (items.length === 0) {
|
|
484
|
+
return '<div class="empty"><h3>No content items</h3><p>Content will appear here after squad execution</p></div>';
|
|
485
|
+
}
|
|
486
|
+
const rows = items.map(item => `
|
|
487
|
+
<tr>
|
|
488
|
+
<td>${esc(item.content_key)}</td>
|
|
489
|
+
<td>${esc(item.title || '-')}</td>
|
|
490
|
+
<td>${esc(item.content_type)}</td>
|
|
491
|
+
<td>${esc(item.layout_type || '-')}</td>
|
|
492
|
+
<td>${statusDot(item.status)} ${esc(item.status)}</td>
|
|
493
|
+
<td>${esc(item.updated_at || item.created_at)}</td>
|
|
494
|
+
</tr>
|
|
495
|
+
`).join('');
|
|
496
|
+
return `<div class="card"><table>
|
|
497
|
+
<thead><tr><th>Key</th><th>Title</th><th>Type</th><th>Layout</th><th>Status</th><th>Updated</th></tr></thead>
|
|
498
|
+
<tbody>${rows}</tbody>
|
|
499
|
+
</table></div>`;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// --- Learnings panel ---
|
|
503
|
+
|
|
504
|
+
function renderLearningsPanel(data) {
|
|
505
|
+
const items = data.learnings || [];
|
|
506
|
+
if (items.length === 0) {
|
|
507
|
+
return '<div class="empty"><h3>No learnings yet</h3><p>Learnings are captured during squad execution</p></div>';
|
|
508
|
+
}
|
|
509
|
+
const rows = items.map(item => `
|
|
510
|
+
<tr>
|
|
511
|
+
<td>${statusDot(item.status)} ${esc(item.status)}</td>
|
|
512
|
+
<td>${esc(item.type)}</td>
|
|
513
|
+
<td>${esc(item.title)}</td>
|
|
514
|
+
<td>${esc(item.confidence || '-')}</td>
|
|
515
|
+
<td>${item.frequency || 1}</td>
|
|
516
|
+
<td>${esc(item.updated_at)}</td>
|
|
517
|
+
</tr>
|
|
518
|
+
`).join('');
|
|
519
|
+
return `<div class="card"><table>
|
|
520
|
+
<thead><tr><th>Status</th><th>Type</th><th>Title</th><th>Confidence</th><th>Freq</th><th>Updated</th></tr></thead>
|
|
521
|
+
<tbody>${rows}</tbody>
|
|
522
|
+
</table></div>`;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// --- Events/Logs panel ---
|
|
526
|
+
|
|
527
|
+
function renderEventsPanel(data) {
|
|
528
|
+
const events = data.events || [];
|
|
529
|
+
if (events.length === 0) {
|
|
530
|
+
return '<div class="empty"><h3>No events</h3><p>Events are recorded during execution</p></div>';
|
|
531
|
+
}
|
|
532
|
+
const items = events.map(ev => `
|
|
533
|
+
<div class="timeline-item">
|
|
534
|
+
<div class="time">${esc(ev.created_at)}</div>
|
|
535
|
+
<div class="event">${statusDot(ev.status)} <strong>${esc(ev.phase || ev.event_type || 'event')}</strong> ${esc(ev.message || '')}</div>
|
|
536
|
+
</div>
|
|
537
|
+
`).join('');
|
|
538
|
+
return `<div class="card"><div class="timeline">${items}</div></div>`;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// --- Pipeline panel ---
|
|
542
|
+
|
|
543
|
+
function renderPipelinePanel(data) {
|
|
544
|
+
const info = data.pipelineInfo;
|
|
545
|
+
if (!info || !info.pipeline) {
|
|
546
|
+
return '<div class="empty"><h3>Not in a pipeline</h3><p>This squad is not part of any pipeline</p></div>';
|
|
547
|
+
}
|
|
548
|
+
let html = `<div class="card">
|
|
549
|
+
<h3>Pipeline: ${esc(info.pipeline.pipeline_slug)}</h3>
|
|
550
|
+
<div class="sub">${esc(info.pipeline.description || '')}</div>
|
|
551
|
+
</div>`;
|
|
552
|
+
|
|
553
|
+
if (info.handoffs && info.handoffs.length > 0) {
|
|
554
|
+
const rows = info.handoffs.map(h => `
|
|
555
|
+
<tr>
|
|
556
|
+
<td>${esc(h.from_squad)}:${esc(h.from_port)}</td>
|
|
557
|
+
<td>${esc(h.to_squad)}:${esc(h.to_port)}</td>
|
|
558
|
+
<td>${statusDot(h.status)} ${esc(h.status)}</td>
|
|
559
|
+
<td>${esc(h.created_at)}</td>
|
|
560
|
+
</tr>
|
|
561
|
+
`).join('');
|
|
562
|
+
html += `<div class="card" style="margin-top:16px"><h3>Handoffs</h3><table>
|
|
563
|
+
<thead><tr><th>From</th><th>To</th><th>Status</th><th>Created</th></tr></thead>
|
|
564
|
+
<tbody>${rows}</tbody>
|
|
565
|
+
</table></div>`;
|
|
566
|
+
}
|
|
567
|
+
return html;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// --- Integrations panel ---
|
|
571
|
+
|
|
572
|
+
function renderIntegrationsPanel(squad, data) {
|
|
573
|
+
const mcps = (squad.manifest && squad.manifest.mcps) || [];
|
|
574
|
+
const webhooks = (squad.manifest && squad.manifest.outputStrategy &&
|
|
575
|
+
squad.manifest.outputStrategy.delivery && squad.manifest.outputStrategy.delivery.webhooks) || [];
|
|
576
|
+
|
|
577
|
+
if (mcps.length === 0 && webhooks.length === 0) {
|
|
578
|
+
return '<div class="empty"><h3>No integrations configured</h3><p>Add MCPs or webhooks to the squad manifest</p></div>';
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
let html = '';
|
|
582
|
+
if (mcps.length > 0) {
|
|
583
|
+
html += `<div class="card"><h3>MCPs</h3><table>
|
|
584
|
+
<thead><tr><th>Slug</th><th>Title</th><th>Description</th></tr></thead>
|
|
585
|
+
<tbody>${mcps.map(m => `<tr><td>${esc(m.slug)}</td><td>${esc(m.title || '-')}</td><td>${esc(m.description || '-')}</td></tr>`).join('')}</tbody>
|
|
586
|
+
</table></div>`;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
if (webhooks.length > 0) {
|
|
590
|
+
html += `<div class="card" style="margin-top:16px"><h3>Webhooks</h3><table>
|
|
591
|
+
<thead><tr><th>Slug</th><th>URL</th><th>Trigger</th></tr></thead>
|
|
592
|
+
<tbody>${webhooks.map(w => `<tr><td>${esc(w.slug)}</td><td>${esc(w.url)}</td><td>${esc(w.trigger)}</td></tr>`).join('')}</tbody>
|
|
593
|
+
</table></div>`;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Recent deliveries
|
|
597
|
+
const deliveries = data.deliveries || [];
|
|
598
|
+
if (deliveries.length > 0) {
|
|
599
|
+
const rows = deliveries.map(d => `
|
|
600
|
+
<tr>
|
|
601
|
+
<td>${esc(d.webhook_slug || '-')}</td>
|
|
602
|
+
<td>${esc(d.content_key || '-')}</td>
|
|
603
|
+
<td>${d.status_code || '-'}</td>
|
|
604
|
+
<td>${d.attempt}</td>
|
|
605
|
+
<td>${esc(d.created_at)}</td>
|
|
606
|
+
</tr>
|
|
607
|
+
`).join('');
|
|
608
|
+
html += `<div class="card" style="margin-top:16px"><h3>Recent Deliveries</h3><table>
|
|
609
|
+
<thead><tr><th>Webhook</th><th>Content</th><th>Status</th><th>Attempt</th><th>Date</th></tr></thead>
|
|
610
|
+
<tbody>${rows}</tbody>
|
|
611
|
+
</table></div>`;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
return html;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// --- Metrics panel ---
|
|
618
|
+
|
|
619
|
+
function renderMetricsPanel(data) {
|
|
620
|
+
const metrics = data.customMetrics || [];
|
|
621
|
+
if (metrics.length === 0) {
|
|
622
|
+
return '<div class="empty"><h3>No metrics tracked</h3><p>Use <code>aioson squad:metric</code> to track squad metrics</p></div>';
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const cards = metrics.map(m => {
|
|
626
|
+
const improvement = m.baseline != null ? Math.round(((m.baseline - m.metric_value) / m.baseline) * 100) : null;
|
|
627
|
+
return `<div class="card">
|
|
628
|
+
<h3>${esc(m.metric_key)}</h3>
|
|
629
|
+
<div class="value">${m.metric_value}${esc(m.metric_unit || '')}</div>
|
|
630
|
+
${m.baseline != null ? `<div class="sub">Baseline: ${m.baseline}${esc(m.metric_unit || '')}${improvement != null ? ` (${improvement > 0 ? '+' : ''}${improvement}% change)` : ''}</div>` : ''}
|
|
631
|
+
${m.target != null ? `<div class="sub">Target: ${m.target}${esc(m.metric_unit || '')}</div>` : ''}
|
|
632
|
+
<div class="sub">${esc(m.period || '')} via ${esc(m.source || 'manual')}</div>
|
|
633
|
+
</div>`;
|
|
634
|
+
}).join('');
|
|
635
|
+
|
|
636
|
+
return `<div class="grid grid-3">${cards}</div>`;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// --- Processes panel ---
|
|
640
|
+
|
|
641
|
+
function renderProcessesPanel(data) {
|
|
642
|
+
const processes = data.processes || [];
|
|
643
|
+
|
|
644
|
+
const squadGroups = {};
|
|
645
|
+
for (const proc of processes) {
|
|
646
|
+
const key = proc.squadSlug || 'unknown';
|
|
647
|
+
if (!squadGroups[key]) squadGroups[key] = [];
|
|
648
|
+
squadGroups[key].push(proc);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
if (processes.length === 0) {
|
|
652
|
+
return `<div class="empty">
|
|
653
|
+
<h3>No active processes</h3>
|
|
654
|
+
<p>Processes appear here when squad agents are running.</p>
|
|
655
|
+
</div>`;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
let html = `<div id="processes-root">`;
|
|
659
|
+
|
|
660
|
+
for (const [squadSlug, procs] of Object.entries(squadGroups)) {
|
|
661
|
+
html += `<div class="card" style="margin-bottom:16px">
|
|
662
|
+
<h3>${esc(squadSlug)} <span class="badge badge-mixed">${procs.length}</span></h3>
|
|
663
|
+
<table style="margin-top:8px">
|
|
664
|
+
<thead>
|
|
665
|
+
<tr>
|
|
666
|
+
<th>Status</th><th>Agent</th><th>PID</th><th>Elapsed</th><th>Context</th><th>URL</th><th>Action</th>
|
|
667
|
+
</tr>
|
|
668
|
+
</thead>
|
|
669
|
+
<tbody>`;
|
|
670
|
+
|
|
671
|
+
for (const proc of procs) {
|
|
672
|
+
const aliveClass = proc.alive ? 'status-active' : 'status-inactive';
|
|
673
|
+
const aliveLabel = proc.alive ? 'running' : 'stopped';
|
|
674
|
+
const ctxPct = proc.contextPct != null ? `${proc.contextPct}%` : '—';
|
|
675
|
+
const urlCell = proc.url
|
|
676
|
+
? `<a href="${esc(proc.url)}" target="_blank" rel="noopener">${esc(proc.url)}</a>`
|
|
677
|
+
: '—';
|
|
678
|
+
const stopBtn = proc.alive
|
|
679
|
+
? `<button class="proc-stop-btn" data-pid="${esc(String(proc.pid))}" onclick="procStop(${Number(proc.pid)})">Stop</button>`
|
|
680
|
+
: '';
|
|
681
|
+
|
|
682
|
+
html += `<tr>
|
|
683
|
+
<td><span class="status-dot ${aliveClass}"></span>${esc(aliveLabel)}</td>
|
|
684
|
+
<td>${esc(proc.agentSlug)}</td>
|
|
685
|
+
<td class="proc-pid">${esc(String(proc.pid))}</td>
|
|
686
|
+
<td class="proc-elapsed" data-startedat="${esc(proc.startedAt || '')}">—</td>
|
|
687
|
+
<td>${esc(ctxPct)}</td>
|
|
688
|
+
<td style="max-width:200px;overflow:hidden;text-overflow:ellipsis">${urlCell}</td>
|
|
689
|
+
<td>${stopBtn}</td>
|
|
690
|
+
</tr>`;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
html += `</tbody></table></div>`;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
html += `</div>`;
|
|
697
|
+
return html;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// --- Tasks panel (agents + autonomy config) ---
|
|
701
|
+
|
|
702
|
+
function renderTasksPanel(data, squad) {
|
|
703
|
+
const executors = (squad.manifest && squad.manifest.executors) || [];
|
|
704
|
+
|
|
705
|
+
let html = '';
|
|
706
|
+
|
|
707
|
+
if (executors.length > 0) {
|
|
708
|
+
const agentCards = executors.map(ex => {
|
|
709
|
+
const level = ex.autonomyLevel || 'semi';
|
|
710
|
+
const info = AUTONOMY_LEVELS[level] || AUTONOMY_LEVELS['semi'];
|
|
711
|
+
const modelBadge = ex.modelTier
|
|
712
|
+
? `<span class="badge badge-mixed">${esc(ex.modelTier)}</span>`
|
|
713
|
+
: '';
|
|
714
|
+
const focusTags = (ex.focus || []).map(f => `<span class="tag">${esc(f)}</span>`).join(' ');
|
|
715
|
+
|
|
716
|
+
return `<div class="card agent-card" id="agent-card-${esc(ex.slug)}">
|
|
717
|
+
<div class="agent-card-header">
|
|
718
|
+
<div>
|
|
719
|
+
<strong>${esc(ex.title || ex.slug)}</strong>
|
|
720
|
+
${modelBadge}
|
|
721
|
+
${autonomyBadge(level)}
|
|
722
|
+
</div>
|
|
723
|
+
<div class="agent-card-slug">${esc(ex.slug)}</div>
|
|
724
|
+
</div>
|
|
725
|
+
${ex.role ? `<div class="agent-role">${esc(ex.role)}</div>` : ''}
|
|
726
|
+
${focusTags ? `<div class="agent-focus">${focusTags}</div>` : ''}
|
|
727
|
+
<div class="agent-autonomy-row">
|
|
728
|
+
<label class="agent-autonomy-label">Autonomy:</label>
|
|
729
|
+
${autonomySelector(ex.slug, level)}
|
|
730
|
+
</div>
|
|
731
|
+
</div>`;
|
|
732
|
+
}).join('\n');
|
|
733
|
+
|
|
734
|
+
html += `<div class="card" style="margin-bottom:16px">
|
|
735
|
+
<h3>Agent Configuration</h3>
|
|
736
|
+
<div class="grid grid-3" style="margin-top:12px">${agentCards}</div>
|
|
737
|
+
</div>`;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// Task list (from data.tasks if available)
|
|
741
|
+
const tasks = data.tasks || [];
|
|
742
|
+
if (tasks.length > 0) {
|
|
743
|
+
const rows = tasks.map(t => {
|
|
744
|
+
const agent = executors.find(ex => ex.slug === t.agentSlug);
|
|
745
|
+
const level = (agent && agent.autonomyLevel) || t.autonomyLevel || 'semi';
|
|
746
|
+
return `<tr>
|
|
747
|
+
<td>${esc(t.id || '-')}</td>
|
|
748
|
+
<td>${esc(t.title || '-')}</td>
|
|
749
|
+
<td>${esc(t.agentSlug || '-')}</td>
|
|
750
|
+
<td>${statusDot(t.status)} ${esc(t.status || 'pending')}</td>
|
|
751
|
+
<td>${autonomyBadge(level)}</td>
|
|
752
|
+
</tr>`;
|
|
753
|
+
}).join('');
|
|
754
|
+
html += `<div class="card">
|
|
755
|
+
<h3>Tasks</h3>
|
|
756
|
+
<table style="margin-top:8px">
|
|
757
|
+
<thead><tr><th>ID</th><th>Title</th><th>Agent</th><th>Status</th><th>Autonomy</th></tr></thead>
|
|
758
|
+
<tbody>${rows}</tbody>
|
|
759
|
+
</table>
|
|
760
|
+
</div>`;
|
|
761
|
+
} else if (executors.length === 0) {
|
|
762
|
+
html = `<div class="empty"><h3>No executors configured</h3><p>Add executors to the squad manifest.</p></div>`;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
return html || `<div class="empty"><h3>No tasks yet</h3><p>Tasks appear here during squad execution.</p></div>`;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// --- Hunk Review panel ---
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* Render a single diff hunk with approve/reject/comment controls.
|
|
772
|
+
*/
|
|
773
|
+
function renderHunkCard(hunk, taskId) {
|
|
774
|
+
const statusClass = {
|
|
775
|
+
pending: 'hunk-pending',
|
|
776
|
+
approved: 'hunk-approved',
|
|
777
|
+
rejected: 'hunk-rejected',
|
|
778
|
+
revised: 'hunk-revised'
|
|
779
|
+
}[hunk.status] || 'hunk-pending';
|
|
780
|
+
|
|
781
|
+
const statusIcon = {
|
|
782
|
+
pending: '⏳',
|
|
783
|
+
approved: '✅',
|
|
784
|
+
rejected: '❌',
|
|
785
|
+
revised: '💬'
|
|
786
|
+
}[hunk.status] || '⏳';
|
|
787
|
+
|
|
788
|
+
const diffLines = (hunk.lines || []).map(line => {
|
|
789
|
+
let cls = 'diff-ctx';
|
|
790
|
+
if (line.startsWith('+')) cls = 'diff-add';
|
|
791
|
+
else if (line.startsWith('-')) cls = 'diff-del';
|
|
792
|
+
return `<div class="${cls}">${esc(line)}</div>`;
|
|
793
|
+
}).join('');
|
|
794
|
+
|
|
795
|
+
const commentBlock = hunk.comment
|
|
796
|
+
? `<div class="hunk-comment">💬 ${esc(hunk.comment)}</div>`
|
|
797
|
+
: '';
|
|
798
|
+
|
|
799
|
+
return `<div class="hunk-card ${statusClass}" id="hunk-${esc(hunk.id)}">
|
|
800
|
+
<div class="hunk-header">
|
|
801
|
+
<span class="hunk-file">${esc(hunk.fileHeader || '')}</span>
|
|
802
|
+
<code class="hunk-range">${esc(hunk.header || '')}</code>
|
|
803
|
+
<span class="hunk-delta">+${hunk.additions || 0} / -${hunk.deletions || 0}</span>
|
|
804
|
+
<span class="hunk-status-icon">${statusIcon} ${esc(hunk.status)}</span>
|
|
805
|
+
</div>
|
|
806
|
+
<div class="hunk-diff">${diffLines}</div>
|
|
807
|
+
${commentBlock}
|
|
808
|
+
<div class="hunk-actions">
|
|
809
|
+
<button class="btn-approve" onclick="hunkAction('${esc(taskId)}','${esc(hunk.id)}','approve')">✅ Approve</button>
|
|
810
|
+
<button class="btn-reject" onclick="hunkAction('${esc(taskId)}','${esc(hunk.id)}','reject')">❌ Reject</button>
|
|
811
|
+
<button class="btn-comment" onclick="toggleComment('${esc(hunk.id)}')">💬 Comment</button>
|
|
812
|
+
<div class="hunk-comment-input" id="ci-${esc(hunk.id)}" style="display:none">
|
|
813
|
+
<input type="text" id="ct-${esc(hunk.id)}" placeholder="Add comment…" style="width:100%;margin-top:6px;padding:4px 8px;background:var(--bg-hover);border:1px solid var(--border);border-radius:4px;color:var(--text)">
|
|
814
|
+
<button onclick="hunkAction('${esc(taskId)}','${esc(hunk.id)}','comment')" style="margin-top:4px">Submit</button>
|
|
815
|
+
</div>
|
|
816
|
+
</div>
|
|
817
|
+
</div>`;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* Render the hunk review panel.
|
|
822
|
+
* data.hunkReview = { taskId, hunks: [...], diff }
|
|
823
|
+
*/
|
|
824
|
+
function renderHunkReviewPanel(data) {
|
|
825
|
+
const hr = data.hunkReview;
|
|
826
|
+
if (!hr || !hr.hunks || hr.hunks.length === 0) {
|
|
827
|
+
return `<div class="empty"><h3>No hunks to review</h3><p>Submit a task diff to start hunk-level review.</p></div>`;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
const total = hr.hunks.length;
|
|
831
|
+
const reviewed = hr.hunks.filter(h => h.status !== 'pending').length;
|
|
832
|
+
const approved = hr.hunks.filter(h => h.status === 'approved').length;
|
|
833
|
+
const rejected = hr.hunks.filter(h => h.status === 'rejected').length;
|
|
834
|
+
const pct = total > 0 ? Math.round((reviewed / total) * 100) : 0;
|
|
835
|
+
|
|
836
|
+
const progressBar = `<div class="hunk-progress-wrap">
|
|
837
|
+
<div class="hunk-progress-label">
|
|
838
|
+
${reviewed}/${total} hunks reviewed (${approved} approved, ${rejected} rejected)
|
|
839
|
+
</div>
|
|
840
|
+
<div class="progress-bar"><div class="progress-fill" style="width:${pct}%"></div></div>
|
|
841
|
+
</div>`;
|
|
842
|
+
|
|
843
|
+
const cards = hr.hunks.map(h => renderHunkCard(h, hr.taskId)).join('\n');
|
|
844
|
+
|
|
845
|
+
return `<div class="hunk-review-panel">
|
|
846
|
+
${progressBar}
|
|
847
|
+
<div class="hunk-list">${cards}</div>
|
|
848
|
+
</div>`;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
module.exports = {
|
|
852
|
+
renderHomePage,
|
|
853
|
+
renderSquadPage,
|
|
854
|
+
renderLayout,
|
|
855
|
+
renderSquadSidebar,
|
|
856
|
+
renderHunkReviewPanel,
|
|
857
|
+
esc
|
|
858
|
+
};
|