@axiomatic-labs/claudeflow 2.13.34 → 2.13.35
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/lib/panel.js +41 -6
- package/package.json +1 -1
package/lib/panel.js
CHANGED
|
@@ -385,6 +385,7 @@ function listLogEvents(cwd, { limit = 200, before = null, type = 'all' } = {}) {
|
|
|
385
385
|
if (type !== 'all') {
|
|
386
386
|
if (type === 'enforcement' && !r.type.startsWith('enforcement')) continue;
|
|
387
387
|
if (type === 'reminder' && !r.type.startsWith('reminder')) continue;
|
|
388
|
+
if (type === 'blocked' && r.type !== 'enforcement-blocked') continue;
|
|
388
389
|
}
|
|
389
390
|
// Strip large `content` from list view; clients fetch it via /api/logs/:id
|
|
390
391
|
// Old sidecars (pre-token-estimate) lack `contentTokens`. Backfill on
|
|
@@ -408,6 +409,8 @@ function listLogEvents(cwd, { limit = 200, before = null, type = 'all' } = {}) {
|
|
|
408
409
|
capturedFromStdout: r.capturedFromStdout === true,
|
|
409
410
|
stdoutTruncated: r.stdoutTruncated === true,
|
|
410
411
|
parseError: r.parseError || null,
|
|
412
|
+
decision: r.decision || null,
|
|
413
|
+
reason: r.reason || null,
|
|
411
414
|
});
|
|
412
415
|
}
|
|
413
416
|
return { entries, total, thresholds };
|
|
@@ -452,11 +455,16 @@ function getLogsInfo(cwd) {
|
|
|
452
455
|
let mostRecent = null;
|
|
453
456
|
let latestReminderTokens = null;
|
|
454
457
|
let latestReminderStatus = null;
|
|
458
|
+
let recentBlockedCount = 0;
|
|
455
459
|
try {
|
|
456
460
|
const files = fs.readdirSync(dir).filter((f) => f.endsWith('.json')).sort();
|
|
457
461
|
total = files.length;
|
|
458
|
-
// Walk from newest
|
|
459
|
-
|
|
462
|
+
// Walk from newest. Count blocks within the last 50 events for the
|
|
463
|
+
// sidebar severity. Latest-reminder lookup short-circuits when both
|
|
464
|
+
// mostRecent + latestReminderTokens are populated AND we've scanned
|
|
465
|
+
// enough for the block tally.
|
|
466
|
+
const scanCap = Math.min(50, files.length);
|
|
467
|
+
for (let i = files.length - 1; i >= files.length - scanCap; i--) {
|
|
460
468
|
try {
|
|
461
469
|
const r = JSON.parse(fs.readFileSync(path.join(dir, files[i]), 'utf8'));
|
|
462
470
|
if (mostRecent === null) mostRecent = r.timestamp;
|
|
@@ -467,6 +475,7 @@ function getLogsInfo(cwd) {
|
|
|
467
475
|
latestReminderTokens = tokens;
|
|
468
476
|
latestReminderStatus = classifyTokens(tokens, thresholds);
|
|
469
477
|
}
|
|
478
|
+
if (r.type === 'enforcement-blocked') recentBlockedCount++;
|
|
470
479
|
} catch {}
|
|
471
480
|
}
|
|
472
481
|
} catch {}
|
|
@@ -477,6 +486,7 @@ function getLogsInfo(cwd) {
|
|
|
477
486
|
thresholds,
|
|
478
487
|
latestReminderTokens,
|
|
479
488
|
latestReminderStatus,
|
|
489
|
+
recentBlockedCount,
|
|
480
490
|
};
|
|
481
491
|
}
|
|
482
492
|
|
|
@@ -638,6 +648,10 @@ details[open] summary { padding-bottom: 8px; border-bottom: 1px solid var(--bord
|
|
|
638
648
|
.logs-status { font-family: var(--mono); font-size: 12px; white-space: nowrap; }
|
|
639
649
|
.logs-status.injected { color: #58a6ff; }
|
|
640
650
|
.logs-status.silenced { color: var(--err); font-weight: 600; }
|
|
651
|
+
.logs-status.blocked { color: var(--err); font-weight: 700; }
|
|
652
|
+
.logs-table tr.blocked .logs-handler { color: var(--err); }
|
|
653
|
+
.logs-table tr.blocked td:first-child { border-left: 3px solid var(--err) !important; }
|
|
654
|
+
.logs-reason-preview { font-size: 11px; color: var(--err); margin-top: 2px; max-width: 480px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
641
655
|
.token-badge { display: inline-block; padding: 1px 7px; border-radius: 999px; font-size: 10px; font-family: var(--mono); margin-left: 6px; vertical-align: middle; }
|
|
642
656
|
.token-badge.heavy { background: rgba(210,153,34,0.18); color: var(--warn); }
|
|
643
657
|
.token-badge.high { background: rgba(248,81,73,0.20); color: var(--err); }
|
|
@@ -792,6 +806,7 @@ function severityFor(id) {
|
|
|
792
806
|
case 'reminders': return s.reminders && s.reminders.disabledCount > 0 ? 'info' : 'ok';
|
|
793
807
|
case 'logs': {
|
|
794
808
|
if (!s.logs) return 'info';
|
|
809
|
+
if (s.logs.recentBlockedCount > 0) return 'err';
|
|
795
810
|
if (s.logs.latestReminderStatus === 'high') return 'err';
|
|
796
811
|
if (s.logs.latestReminderStatus === 'heavy') return 'warn';
|
|
797
812
|
return s.logs.total > 0 ? 'ok' : 'info';
|
|
@@ -1028,6 +1043,17 @@ async function viewLogDetail(id) {
|
|
|
1028
1043
|
: '<span class="badge warn" title="No content was captured — the hook may have crashed before writing or returned null.">⚠ no stdout</span>');
|
|
1029
1044
|
html += '<h4 style="margin-top:14px">Provenance ' + provBadge + (ev.stdoutTruncated ? ' <span class="badge warn">⚠ truncated</span>' : '') + '</h4>';
|
|
1030
1045
|
}
|
|
1046
|
+
// Enforcement-blocked detail: show decision + full reason verbatim.
|
|
1047
|
+
if (ev.type === 'enforcement-blocked') {
|
|
1048
|
+
html += '<h4 style="margin-top:14px">Decision: <span class="badge err">' + escapeHtml(ev.decision || 'block').toUpperCase() + '</span></h4>';
|
|
1049
|
+
if (ev.reason) {
|
|
1050
|
+
html += '<h4 style="margin-top:14px">Reason</h4>';
|
|
1051
|
+
html += '<pre style="border-color: rgba(248,81,73,0.5)">' + escapeHtml(ev.reason) + '</pre>';
|
|
1052
|
+
}
|
|
1053
|
+
if (ev.capturedStdout) {
|
|
1054
|
+
html += '<details style="margin-top:14px"><summary>Raw stdout (' + fmtBytes(ev.stdoutBytes || 0) + ')</summary><pre>' + escapeHtml(ev.capturedStdout) + '</pre></details>';
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1031
1057
|
if (typeof ev.contentBytes === 'number' && ev.contentBytes > 0) {
|
|
1032
1058
|
const tokens = typeof ev.contentTokens === 'number' ? ev.contentTokens : Math.round(ev.contentBytes / 4);
|
|
1033
1059
|
const th = (state.logs && state.logs.thresholds) || { heavy: 8000, high: 20000 };
|
|
@@ -1092,11 +1118,14 @@ function renderLogs() {
|
|
|
1092
1118
|
const rows = logsState.entries.map((e) => {
|
|
1093
1119
|
const isReminder = e.type.startsWith('reminder');
|
|
1094
1120
|
const isSilenced = e.type === 'enforcement-silenced';
|
|
1121
|
+
const isBlocked = e.type === 'enforcement-blocked';
|
|
1095
1122
|
const badge = isReminder
|
|
1096
1123
|
? '<span class="badge info" style="background:rgba(88,166,255,0.18); color:#58a6ff">REMINDER</span>'
|
|
1097
1124
|
: '<span class="badge err">ENFORCEMENT</span>';
|
|
1098
1125
|
const statusCell = isSilenced
|
|
1099
1126
|
? '<span class="logs-status silenced" title="Master Enforcement OFF silenced this hook — it did NOT run. Claude Code received no output for this event.">✗ SILENCED</span>'
|
|
1127
|
+
: isBlocked
|
|
1128
|
+
? '<span class="logs-status blocked" title="Hook ran and BLOCKED this action. Claude Code rejected the tool call. Click View for the full reason.">⛔ BLOCKED</span>'
|
|
1100
1129
|
: '<span class="logs-status injected" title="Hook ran and emitted additionalContext to Claude Code">✓ injected</span>';
|
|
1101
1130
|
const tokenStatus = e.tokenStatus || 'none';
|
|
1102
1131
|
const tokenBadge = tokenStatus === 'high'
|
|
@@ -1110,13 +1139,18 @@ function renderLogs() {
|
|
|
1110
1139
|
const bytesStr = e.contentBytes ? fmtBytes(e.contentBytes) : '';
|
|
1111
1140
|
const tokensTitle = e.contentBytes
|
|
1112
1141
|
? \`≈\${e.contentTokens} tokens (chars/4 heuristic) · \${fmtBytes(e.contentBytes)} raw · status: \${tokenStatus}\`
|
|
1113
|
-
:
|
|
1114
|
-
|
|
1142
|
+
: isBlocked
|
|
1143
|
+
? 'Hook BLOCKED the action. Click View for full reason.'
|
|
1144
|
+
: 'No content (hook was silenced — never ran)';
|
|
1145
|
+
const rowCls = \`token-\${tokenStatus}\${isSilenced ? ' silenced' : ''}\${isBlocked ? ' blocked' : ''}\`;
|
|
1146
|
+
const reasonPreview = isBlocked && e.reason
|
|
1147
|
+
? \`<div class="logs-reason-preview" title="\${escapeHtml(e.reason)}">\${escapeHtml(e.reason.split('\\n')[0].slice(0, 120))}</div>\`
|
|
1148
|
+
: '';
|
|
1115
1149
|
return \`<tr class="\${rowCls}">
|
|
1116
1150
|
<td class="logs-time"><span title="\${escapeHtml(e.timestamp)}">\${escapeHtml(fmtLocalTime(e.timestamp))}</span><br><span class="muted" style="font-size:11px">\${fmtRelativeTime(e.timestamp)}</span></td>
|
|
1117
1151
|
<td>\${badge}</td>
|
|
1118
1152
|
<td>\${statusCell}</td>
|
|
1119
|
-
<td class="logs-handler"><span class="muted">\${escapeHtml(e.event)}</span><br>\${escapeHtml(e.handler)}\${e.matcher ? \` <span class="muted">(\${escapeHtml(e.matcher)})</span>\` : ''}</td>
|
|
1153
|
+
<td class="logs-handler"><span class="muted">\${escapeHtml(e.event)}</span><br>\${escapeHtml(e.handler)}\${e.matcher ? \` <span class="muted">(\${escapeHtml(e.matcher)})</span>\` : ''}\${reasonPreview}</td>
|
|
1120
1154
|
<td class="logs-size" title="\${escapeHtml(tokensTitle)}">\${tokensStr}\${tokenBadge}\${bytesStr ? \`<br><span class="muted" style="font-size:11px">\${bytesStr}</span>\` : ''}</td>
|
|
1121
1155
|
<td><button class="logs-view" data-log-id="\${escapeHtml(e.id)}">View</button></td>
|
|
1122
1156
|
</tr>\`;
|
|
@@ -1136,7 +1170,8 @@ function renderLogs() {
|
|
|
1136
1170
|
<select id="logs-filter">
|
|
1137
1171
|
<option value="all" \${logsState.type === 'all' ? 'selected' : ''}>All</option>
|
|
1138
1172
|
<option value="reminder" \${logsState.type === 'reminder' ? 'selected' : ''}>Reminders only</option>
|
|
1139
|
-
<option value="enforcement" \${logsState.type === 'enforcement' ? 'selected' : ''}>Enforcement
|
|
1173
|
+
<option value="enforcement" \${logsState.type === 'enforcement' ? 'selected' : ''}>Enforcement (silenced + blocked)</option>
|
|
1174
|
+
<option value="blocked" \${logsState.type === 'blocked' ? 'selected' : ''}>Blocked actions only</option>
|
|
1140
1175
|
</select>
|
|
1141
1176
|
</label>
|
|
1142
1177
|
<button id="logs-reload">Reload</button>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiomatic-labs/claudeflow",
|
|
3
|
-
"version": "2.13.
|
|
3
|
+
"version": "2.13.35",
|
|
4
4
|
"description": "Claudeflow — AI-powered development toolkit for Claude Code. Skills, agents, hooks, and quality gates that ship production apps.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"claudeflow": "./bin/cli.js"
|