azclaude-copilot 0.7.2 → 0.7.3
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +2 -2
- package/package.json +1 -1
- package/templates/hooks/stop.js +16 -1
- package/templates/hooks/user-prompt.js +30 -1
- package/templates/visualizer/public/app.js +40 -1
- package/templates/visualizer/public/audio.js +5 -0
- package/templates/visualizer/public/canvas.js +4 -1
- package/templates/visualizer/public/index.html +6 -0
- package/templates/visualizer/public/style.css +21 -0
- package/templates/visualizer/server.js +10 -1
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
{
|
|
10
10
|
"name": "azclaude",
|
|
11
11
|
"description": "AZCLAUDE is a complete AI coding environment for Claude Code. It installs 40 commands, 10 auto-invoked skills, 15 specialized agents, 5 hooks, a real-time pipeline visualizer, and a persistent memory system — in one command.\n\nKey features:\n• Memory across sessions — goals.md + checkpoints injected automatically before every session\n• Self-improving loop — /reflect fixes stale CLAUDE.md rules, /reflexes learns from tool-use patterns, /evolve creates agents from git evidence\n• Autonomous copilot mode — /copilot runs a three-tier team (orchestrator → problem-architect → milestone-builder) across sessions until the product ships\n• Spec-driven workflow — /constitute writes project rules, /spec writes structured ACs, /analyze detects plan drift and ghost milestones, /blueprint traces every milestone to a spec\n• Security layer — 111-rule environment scan (/sentinel), pre-write secret blocking, pre-ship credential audit\n• Progressive levels 0–10 — start with CLAUDE.md, grow into multi-agent pipelines and self-evolving environments\n• Zero dependencies — no npm packages, no external APIs, no vector databases. Plain markdown files and Claude Code's native architecture.\n• Smart install — npx azclaude-copilot@latest auto-detects first install vs upgrade vs verify. Context-aware onboarding shows the right next command for your project state.\n\nExample use cases:\n• /setup — scan an existing project, detect stack + domain + scale, fill CLAUDE.md, generate project-specific skills and agents automatically\n• /copilot \"Build a compliance SaaS with trilingual support\" — walk away, come back to working code across multiple sessions\n• /sentinel — run a scored security audit (0–100, grade A–F) across hooks, permissions, MCP servers, agent configs, and secrets\n• /evolve — detect gaps in the environment, generate new skills and agents from git co-change evidence, report score delta (e.g. 42/100 → 68/100)\n• /constitute — write your project's constitution (non-negotiables, architectural commitments, definition of done) — gates all future AI actions\n• /analyze — cross-artifact consistency check: ghost milestones, spec vs. code drift, unplanned commits\n• /reflect — find stale, missing, or contradicting rules in CLAUDE.md and propose exact fixes\n• /debate \"REST vs GraphQL for this project\" — adversarial evidence-based decision with order-independent scoring, logged to decisions.md",
|
|
12
|
-
"version": "0.7.
|
|
12
|
+
"version": "0.7.3",
|
|
13
13
|
"source": {
|
|
14
14
|
"source": "github",
|
|
15
15
|
"repo": "haytamAroui/AZ-CLAUDE-COPILOT",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "azclaude",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.3",
|
|
4
4
|
"description": "AZCLAUDE is a complete AI coding environment for Claude Code. It installs 40 commands, 10 auto-invoked skills, 15 specialized agents, 5 hooks, a real-time pipeline visualizer, and a persistent memory system — in one command.\n\nKey features:\n• Memory across sessions — goals.md + checkpoints injected automatically before every session\n• Self-improving loop — /reflect fixes stale CLAUDE.md rules, /reflexes learns from tool-use patterns, /evolve creates agents from git evidence\n• Autonomous copilot mode — /copilot runs a three-tier team (orchestrator → problem-architect → milestone-builder) across sessions until the product ships\n• Spec-driven workflow — /constitute writes project rules, /spec writes structured ACs, /analyze detects plan drift and ghost milestones, /blueprint traces every milestone to a spec\n• Security layer — 111-rule environment scan (/sentinel), pre-write secret blocking, pre-ship credential audit\n• Progressive levels 0–10 — start with CLAUDE.md, grow into multi-agent pipelines and self-evolving environments\n• Zero dependencies — no npm packages, no external APIs, no vector databases. Plain markdown files and Claude Code's native architecture.\n• Smart install — npx azclaude-copilot@latest auto-detects first install vs upgrade vs verify. Context-aware onboarding shows the right next command for your project state.\n\nExample use cases:\n• /setup — scan an existing project, detect stack + domain + scale, fill CLAUDE.md, generate project-specific skills and agents automatically\n• /copilot \"Build a compliance SaaS with trilingual support\" — walk away, come back to working code across multiple sessions\n• /sentinel — run a scored security audit (0–100, grade A–F) across hooks, permissions, MCP servers, agent configs, and secrets\n• /evolve — detect gaps in the environment, generate new skills and agents from git co-change evidence, report score delta (e.g. 42/100 → 68/100)\n• /constitute — write your project's constitution (non-negotiables, architectural commitments, definition of done) — gates all future AI actions\n• /analyze — cross-artifact consistency check: ghost milestones, spec vs. code drift, unplanned commits\n• /reflect — find stale, missing, or contradicting rules in CLAUDE.md and propose exact fixes\n• /debate \"REST vs GraphQL for this project\" — adversarial evidence-based decision with order-independent scoring, logged to decisions.md",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "haytamAroui",
|
package/README.md
CHANGED
|
@@ -637,11 +637,11 @@ AZCLAUDE is a lazy-loaded environment of 48 capability modules. It only loads wh
|
|
|
637
637
|
|
|
638
638
|
## Verified
|
|
639
639
|
|
|
640
|
-
|
|
640
|
+
1902 tests. Every template, command, capability, agent, hook, and CLI feature verified.
|
|
641
641
|
|
|
642
642
|
```bash
|
|
643
643
|
bash tests/test-features.sh
|
|
644
|
-
# Results:
|
|
644
|
+
# Results: 1902 passed, 0 failed, 1902 total
|
|
645
645
|
```
|
|
646
646
|
|
|
647
647
|
---
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "azclaude-copilot",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.3",
|
|
4
4
|
"description": "AI coding environment — 40 commands, 10 skills, 15 agents, real-time visualizer, memory, reflexes, evolution. Install: npx azclaude-copilot@latest, then open Claude Code.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"azclaude": "bin/cli.js",
|
package/templates/hooks/stop.js
CHANGED
|
@@ -173,7 +173,22 @@ if (fs.existsSync(seclogPath)) {
|
|
|
173
173
|
} catch (_) {}
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
-
// ── Visualizer session-summary
|
|
176
|
+
// ── Visualizer pipeline-complete + session-summary events (opt-in) ──
|
|
177
|
+
if (process.env.AZCLAUDE_VISUALIZER) {
|
|
178
|
+
// Signal pipeline completion — all stages go green
|
|
179
|
+
try {
|
|
180
|
+
const vPort0 = parseInt(process.env.AZCLAUDE_VISUALIZER, 10) || 8765;
|
|
181
|
+
const pcPayload = JSON.stringify({ type: 'pipeline-complete' });
|
|
182
|
+
const pcReq = require('http').request(
|
|
183
|
+
{ hostname: '127.0.0.1', port: vPort0, path: '/event', method: 'POST',
|
|
184
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(pcPayload) } },
|
|
185
|
+
() => {}
|
|
186
|
+
);
|
|
187
|
+
pcReq.setTimeout(1500, () => pcReq.destroy());
|
|
188
|
+
pcReq.on('error', () => {});
|
|
189
|
+
pcReq.end(pcPayload);
|
|
190
|
+
} catch (_v) {}
|
|
191
|
+
}
|
|
177
192
|
if (process.env.AZCLAUDE_VISUALIZER) {
|
|
178
193
|
try {
|
|
179
194
|
const vPort = parseInt(process.env.AZCLAUDE_VISUALIZER, 10) || 8765;
|
|
@@ -207,8 +207,21 @@ try {
|
|
|
207
207
|
console.log('This pipeline is NON-NEGOTIABLE. Do not skip steps. Do not start coding before Step 1 completes.');
|
|
208
208
|
console.log('--- END PIPELINE ---');
|
|
209
209
|
|
|
210
|
-
// ── Visualizer
|
|
210
|
+
// ── Visualizer events (opt-in) ──
|
|
211
211
|
if (process.env.AZCLAUDE_VISUALIZER) {
|
|
212
|
+
try {
|
|
213
|
+
const vPort = parseInt(process.env.AZCLAUDE_VISUALIZER, 10) || 8765;
|
|
214
|
+
// Send user message first
|
|
215
|
+
const msgPayload = JSON.stringify({ type: 'user-message', message: promptText.slice(0, 200) });
|
|
216
|
+
const msgReq = require('http').request(
|
|
217
|
+
{ hostname: '127.0.0.1', port: vPort, path: '/event', method: 'POST',
|
|
218
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(msgPayload) } },
|
|
219
|
+
() => {}
|
|
220
|
+
);
|
|
221
|
+
msgReq.setTimeout(1500, () => msgReq.destroy());
|
|
222
|
+
msgReq.on('error', () => {});
|
|
223
|
+
msgReq.end(msgPayload);
|
|
224
|
+
} catch (_v) {}
|
|
212
225
|
try {
|
|
213
226
|
const vPort = parseInt(process.env.AZCLAUDE_VISUALIZER, 10) || 8765;
|
|
214
227
|
const payload = JSON.stringify({ type: 'pipeline-start', intents: intents, tier: tier, tierLabel: tierLabel });
|
|
@@ -235,6 +248,22 @@ try {
|
|
|
235
248
|
const ctxSignal = JSON.parse(fs.readFileSync(ctxSignalPath, 'utf8'));
|
|
236
249
|
const pct = ctxSignal.ctxPct || 0;
|
|
237
250
|
|
|
251
|
+
// Send context % to visualizer
|
|
252
|
+
if (process.env.AZCLAUDE_VISUALIZER) {
|
|
253
|
+
try {
|
|
254
|
+
const vPort = parseInt(process.env.AZCLAUDE_VISUALIZER, 10) || 8765;
|
|
255
|
+
const ctxPayload = JSON.stringify({ type: 'context-update', pct: pct });
|
|
256
|
+
const ctxReq = require('http').request(
|
|
257
|
+
{ hostname: '127.0.0.1', port: vPort, path: '/event', method: 'POST',
|
|
258
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(ctxPayload) } },
|
|
259
|
+
() => {}
|
|
260
|
+
);
|
|
261
|
+
ctxReq.setTimeout(1500, () => ctxReq.destroy());
|
|
262
|
+
ctxReq.on('error', () => {});
|
|
263
|
+
ctxReq.end(ctxPayload);
|
|
264
|
+
} catch (_v) {}
|
|
265
|
+
}
|
|
266
|
+
|
|
238
267
|
if (pct >= 85) {
|
|
239
268
|
// AUTO-SAVE: context is critically high — save checkpoint before compaction wipes it
|
|
240
269
|
const checkpointDir = path.join(cfg, 'memory', 'checkpoints');
|
|
@@ -87,6 +87,11 @@
|
|
|
87
87
|
case 'WebFetch': return input.url || '(no URL)';
|
|
88
88
|
case 'WebSearch': return '"' + (input.query || '') + '"';
|
|
89
89
|
case 'Skill': return input.skill || '(unknown skill)';
|
|
90
|
+
case 'MultiEdit': { let s = input.file_path || '(unknown file)'; if (input.edits) s += ' (' + input.edits.length + ' edits)'; return s; }
|
|
91
|
+
case 'NotebookEdit': { let s = input.notebook || input.file_path || '(notebook)'; if (input.cell_id != null) s += ' cell ' + input.cell_id; return s; }
|
|
92
|
+
case 'AskUserQuestion': return input.question || '(question)';
|
|
93
|
+
case 'TaskCreate': return input.subject || '(new task)';
|
|
94
|
+
case 'TaskUpdate': { let s = 'task #' + (input.taskId || '?'); if (input.status) s += ' → ' + input.status; return s; }
|
|
90
95
|
default: {
|
|
91
96
|
const keys = Object.keys(input);
|
|
92
97
|
if (keys.length === 0) return null;
|
|
@@ -197,7 +202,7 @@
|
|
|
197
202
|
pipelineProgress.classList.add('visible');
|
|
198
203
|
const primary = (intents && intents[0]) || 'CODE';
|
|
199
204
|
const cls = primary.toLowerCase();
|
|
200
|
-
brainIntent.innerHTML = '<span class="intent-label ' + (['build','fix','review','test','refactor','plan','devops','frontend'].includes(cls) ? cls : 'default') + '">' + primary + '</span>' +
|
|
205
|
+
brainIntent.innerHTML = '<span class="intent-label ' + (['build','fix','review','test','refactor','plan','devops','frontend','extend','code','security','analyze'].includes(cls) ? cls : 'default') + '">' + primary + '</span>' +
|
|
201
206
|
(tier ? ' <span style="font-size:9px;color:#5A5448">Tier ' + tier + '</span>' : '');
|
|
202
207
|
// Reset pipeline stages
|
|
203
208
|
document.querySelectorAll('.pipeline-stage').forEach(function (el) { el.classList.remove('active', 'complete'); });
|
|
@@ -236,6 +241,9 @@
|
|
|
236
241
|
|
|
237
242
|
if (settings.mcpLabels) { const mcpServer = extractMcpServer(event.tool_name); if (mcpServer) { const label = document.createElement('span'); label.className = 'mcp-label'; label.textContent = mcpServer; header.appendChild(label); } }
|
|
238
243
|
|
|
244
|
+
// Agent label — show which subagent made this call
|
|
245
|
+
if (event.agent_type) { const agentLabel = document.createElement('span'); agentLabel.className = 'mcp-label'; agentLabel.style.background = 'rgba(168, 130, 255, 0.15)'; agentLabel.style.color = '#A882FF'; agentLabel.textContent = event.agent_type; header.appendChild(agentLabel); }
|
|
246
|
+
|
|
239
247
|
// Diff stat badge (AZCLAUDE enriched)
|
|
240
248
|
if (event.diffStat) { const ds = document.createElement('span'); ds.className = 'diff-stat'; ds.textContent = event.diffStat; header.appendChild(ds); }
|
|
241
249
|
|
|
@@ -328,6 +336,37 @@
|
|
|
328
336
|
addSecurityEvent(event.level, event.rule, event.message);
|
|
329
337
|
return;
|
|
330
338
|
}
|
|
339
|
+
if (event.event === 'user-message') {
|
|
340
|
+
var card = document.createElement('div'); card.className = 'user-message-card';
|
|
341
|
+
var hdr = document.createElement('div'); hdr.className = 'user-message-header';
|
|
342
|
+
hdr.innerHTML = '<span class="user-message-icon">▸</span> User';
|
|
343
|
+
var txt = document.createElement('div'); txt.className = 'user-message-text';
|
|
344
|
+
txt.textContent = event.message || '(empty)';
|
|
345
|
+
card.appendChild(hdr); card.appendChild(txt);
|
|
346
|
+
feed.appendChild(card); pruneOldCards(); scrollToBottom();
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (event.event === 'context-update') {
|
|
350
|
+
var pct = event.pct || 0;
|
|
351
|
+
var bar = document.getElementById('context-bar');
|
|
352
|
+
var fill = document.getElementById('context-fill');
|
|
353
|
+
var label = document.getElementById('context-pct');
|
|
354
|
+
if (bar && fill && label) {
|
|
355
|
+
bar.classList.add('visible');
|
|
356
|
+
fill.style.width = pct + '%';
|
|
357
|
+
fill.className = 'context-fill' + (pct >= 85 ? ' critical' : pct >= 70 ? ' warn' : '');
|
|
358
|
+
label.textContent = pct + '%';
|
|
359
|
+
label.style.color = pct >= 85 ? '#C85050' : pct >= 70 ? '#D4A017' : '#5A5448';
|
|
360
|
+
}
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
if (event.event === 'pipeline-complete') {
|
|
364
|
+
document.querySelectorAll('.pipeline-stage').forEach(function (el) {
|
|
365
|
+
el.classList.remove('active', 'complete');
|
|
366
|
+
el.classList.add('done');
|
|
367
|
+
});
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
331
370
|
if (event.event === 'session-summary') {
|
|
332
371
|
const parts = [];
|
|
333
372
|
if (event.duration) parts.push(event.duration);
|
|
@@ -43,6 +43,11 @@
|
|
|
43
43
|
Plan: { register: LOW, style: 'chord', notes: 3, velocity: 0.6 },
|
|
44
44
|
EnterPlanMode: { register: LOW, style: 'chord', notes: 3, velocity: 0.55 },
|
|
45
45
|
ExitPlanMode: { register: MID, style: 'arpUp', notes: 3, velocity: 0.55 },
|
|
46
|
+
MultiEdit: { register: MID, style: 'arpDown', notes: 4, velocity: 0.6 },
|
|
47
|
+
NotebookEdit: { register: LOW, style: 'chord', notes: 3, velocity: 0.55 },
|
|
48
|
+
AskUserQuestion: { register: HIGH, style: 'scatter', notes: 3, velocity: 0.5 },
|
|
49
|
+
TaskCreate: { register: LOW, style: 'arpUp', notes: 2, velocity: 0.45 },
|
|
50
|
+
TaskUpdate: { register: LOW, style: 'pulse', notes: 2, velocity: 0.4 },
|
|
46
51
|
_default: { register: MID, style: 'arpUp', notes: 2, velocity: 0.5 },
|
|
47
52
|
};
|
|
48
53
|
|
|
@@ -8,7 +8,10 @@
|
|
|
8
8
|
Read: '#D4A017', Edit: '#C86840', Write: '#B85535', Bash: '#6B8E5A',
|
|
9
9
|
Glob: '#C8A850', Grep: '#B89040', Agent: '#D4B030', WebFetch: '#4A8A7A',
|
|
10
10
|
WebSearch: '#3A7A6A', TodoWrite: '#A06858', Skill: '#B87040', Plan: '#8A6070',
|
|
11
|
-
EnterPlanMode: '#8A6070', ExitPlanMode: '#8A6070',
|
|
11
|
+
EnterPlanMode: '#8A6070', ExitPlanMode: '#8A6070',
|
|
12
|
+
MultiEdit: '#C86840', NotebookEdit: '#8A6070', AskUserQuestion: '#4A8A7A',
|
|
13
|
+
TaskCreate: '#6B8E5A', TaskUpdate: '#5A7A4A', TaskList: '#5A7A4A',
|
|
14
|
+
_default: '#7A7060',
|
|
12
15
|
};
|
|
13
16
|
|
|
14
17
|
const SECURITY_COLORS = { block: '#ff2222', warn: '#ffaa00' };
|
|
@@ -57,6 +57,12 @@
|
|
|
57
57
|
|
|
58
58
|
<div id="settings-panel"></div>
|
|
59
59
|
|
|
60
|
+
<!-- Context window usage -->
|
|
61
|
+
<div id="context-bar">
|
|
62
|
+
<div class="context-label">Context <span id="context-pct">0%</span></div>
|
|
63
|
+
<div class="context-track"><div class="context-fill" id="context-fill"></div></div>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
60
66
|
<!-- Pipeline progress -->
|
|
61
67
|
<div id="pipeline-progress">
|
|
62
68
|
<div id="brain-intent"></div>
|
|
@@ -89,6 +89,22 @@ body { display: flex; flex-direction: row; }
|
|
|
89
89
|
.toggle-switch.on { background: #2A3528; }
|
|
90
90
|
.toggle-switch.on::after { transform: translateX(16px); background: #6B8E5A; }
|
|
91
91
|
|
|
92
|
+
/* ===== Context Window Bar ===== */
|
|
93
|
+
#context-bar { padding: 8px 16px; border-bottom: 1px solid #1E1C18; flex-shrink: 0; display: none; }
|
|
94
|
+
#context-bar.visible { display: block; }
|
|
95
|
+
.context-label { font-size: 10px; color: #5A5448; display: flex; justify-content: space-between; margin-bottom: 4px; }
|
|
96
|
+
#context-pct { font-weight: 600; }
|
|
97
|
+
.context-track { height: 4px; background: #1E1C18; border-radius: 2px; overflow: hidden; }
|
|
98
|
+
.context-fill { height: 100%; width: 0%; border-radius: 2px; background: #6B8E5A; transition: width 0.5s ease, background 0.5s ease; }
|
|
99
|
+
.context-fill.warn { background: #D4A017; }
|
|
100
|
+
.context-fill.critical { background: #C85050; animation: pulse-dot 1s ease-in-out infinite; }
|
|
101
|
+
|
|
102
|
+
/* ===== User Message Card ===== */
|
|
103
|
+
.user-message-card { background: #1A1816; border-radius: 6px; margin-bottom: 8px; border-left: 3px solid #4A8A7A; padding: 8px 12px; }
|
|
104
|
+
.user-message-header { display: flex; align-items: center; gap: 8px; font-size: 10px; color: #5A5448; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px; }
|
|
105
|
+
.user-message-icon { font-size: 12px; }
|
|
106
|
+
.user-message-text { font-size: 12px; color: #E8DCC8; opacity: 0.85; line-height: 1.4; word-break: break-word; }
|
|
107
|
+
|
|
92
108
|
/* ===== AZCLAUDE Pipeline Progress ===== */
|
|
93
109
|
#pipeline-progress { padding: 10px 16px; border-bottom: 1px solid #1E1C18; flex-shrink: 0; display: none; }
|
|
94
110
|
#pipeline-progress.visible { display: block; }
|
|
@@ -103,12 +119,17 @@ body { display: flex; flex-direction: row; }
|
|
|
103
119
|
.intent-label.plan { background: #241A24; color: #8A6070; }
|
|
104
120
|
.intent-label.devops { background: #1A2418; color: #5A7A4A; }
|
|
105
121
|
.intent-label.frontend { background: #2A1820; color: #B85570; }
|
|
122
|
+
.intent-label.extend { background: #24201A; color: #D4A017; }
|
|
123
|
+
.intent-label.code { background: #1A1E18; color: #8A9E7A; }
|
|
124
|
+
.intent-label.security { background: #2A1818; color: #C85050; }
|
|
125
|
+
.intent-label.analyze { background: #1A1A24; color: #7A80BA; }
|
|
106
126
|
.intent-label.default { background: #1E1C18; color: #8A7E6A; }
|
|
107
127
|
|
|
108
128
|
.pipeline-bar { display: grid; grid-template-columns: repeat(4, 1fr); gap: 3px; }
|
|
109
129
|
.pipeline-stage { font-size: 9px; text-align: center; padding: 4px 2px; border-radius: 3px; background: #1E1C18; color: #3A3630; text-transform: uppercase; letter-spacing: 0.5px; transition: all 0.4s ease; }
|
|
110
130
|
.pipeline-stage.active { background: #1A2A18; color: #6B8E5A; box-shadow: 0 0 8px rgba(107,142,90,0.3); animation: pulse-dot 1.5s ease-in-out infinite; }
|
|
111
131
|
.pipeline-stage.complete { background: #1A2418; color: #4A6A3A; }
|
|
132
|
+
.pipeline-stage.done { background: #1A2A18; color: #6B8E5A; }
|
|
112
133
|
|
|
113
134
|
/* ===== Security Events ===== */
|
|
114
135
|
#security-events { flex-shrink: 0; max-height: 120px; overflow-y: auto; }
|
|
@@ -21,15 +21,20 @@ const LOG_FILE = path.join(os.tmpdir(), 'azclaude-visualizer.jsonl');
|
|
|
21
21
|
const MAX_BODY_SIZE = 1024 * 1024;
|
|
22
22
|
|
|
23
23
|
// ---------------------------------------------------------------------------
|
|
24
|
-
// SSE client management
|
|
24
|
+
// SSE client management + replay buffer
|
|
25
25
|
// ---------------------------------------------------------------------------
|
|
26
26
|
const sseClients = new Set();
|
|
27
|
+
const EVENT_BUFFER_MAX = 200;
|
|
28
|
+
const eventBuffer = [];
|
|
27
29
|
|
|
28
30
|
function broadcast(event) {
|
|
29
31
|
const data = `data: ${JSON.stringify(event)}\n\n`;
|
|
30
32
|
for (const client of sseClients) {
|
|
31
33
|
try { client.write(data); } catch { sseClients.delete(client); }
|
|
32
34
|
}
|
|
35
|
+
// Ring buffer for reconnect replay
|
|
36
|
+
eventBuffer.push(event);
|
|
37
|
+
if (eventBuffer.length > EVENT_BUFFER_MAX) eventBuffer.shift();
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
// SSE heartbeat
|
|
@@ -164,6 +169,10 @@ function handleSSE(req, res) {
|
|
|
164
169
|
'Access-Control-Allow-Origin': '*',
|
|
165
170
|
});
|
|
166
171
|
res.write(': connected\n\n');
|
|
172
|
+
// Replay buffered events so refreshing the browser doesn't lose history
|
|
173
|
+
for (const event of eventBuffer) {
|
|
174
|
+
try { res.write(`data: ${JSON.stringify(event)}\n\n`); } catch { return; }
|
|
175
|
+
}
|
|
167
176
|
sseClients.add(res);
|
|
168
177
|
req.on('close', () => { sseClients.delete(res); });
|
|
169
178
|
}
|