@axiomatic-labs/claudeflow 2.13.64 → 2.13.66
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/hook-overrides.js +17 -0
- package/lib/panel.js +127 -3
- package/package.json +1 -1
package/lib/hook-overrides.js
CHANGED
|
@@ -57,6 +57,7 @@ function defaultOverrides() {
|
|
|
57
57
|
disabledReminders: [],
|
|
58
58
|
enforcementDisabled: false,
|
|
59
59
|
runToCompletionReminderEnabled: false,
|
|
60
|
+
cwdLockEnabled: false,
|
|
60
61
|
};
|
|
61
62
|
}
|
|
62
63
|
|
|
@@ -69,6 +70,7 @@ function readOverrides(projectRoot) {
|
|
|
69
70
|
disabledReminders: Array.isArray(data.disabledReminders) ? data.disabledReminders : [],
|
|
70
71
|
enforcementDisabled: data.enforcementDisabled === true,
|
|
71
72
|
runToCompletionReminderEnabled: data.runToCompletionReminderEnabled === true,
|
|
73
|
+
cwdLockEnabled: data.cwdLockEnabled === true,
|
|
72
74
|
};
|
|
73
75
|
}
|
|
74
76
|
|
|
@@ -209,6 +211,20 @@ function toggleRunToCompletionReminder(projectRoot, { enable }) {
|
|
|
209
211
|
return { state: enable ? 'on' : 'off' };
|
|
210
212
|
}
|
|
211
213
|
|
|
214
|
+
// Toggle the cwd-lock hook. Independent of the master enforcement switch —
|
|
215
|
+
// the CwdChanged hook is not in the enforcement-catalog and ignores the
|
|
216
|
+
// master switch by design (you can lock cwd even with enforcement off).
|
|
217
|
+
function toggleCwdLock(projectRoot, { enable }) {
|
|
218
|
+
if (typeof enable !== 'boolean') return { state: 'error', reason: 'enable-required' };
|
|
219
|
+
const overrides = readOverrides(projectRoot);
|
|
220
|
+
if (overrides.cwdLockEnabled === enable) {
|
|
221
|
+
return { state: 'noop', reason: enable ? 'already-on' : 'already-off' };
|
|
222
|
+
}
|
|
223
|
+
overrides.cwdLockEnabled = enable;
|
|
224
|
+
writeOverrides(projectRoot, overrides);
|
|
225
|
+
return { state: enable ? 'on' : 'off' };
|
|
226
|
+
}
|
|
227
|
+
|
|
212
228
|
// Toggle a single reminder by id. Same persistence file, same hot-reload
|
|
213
229
|
// semantics — `reload-reminder.js` re-reads overrides on every invocation.
|
|
214
230
|
function toggleReminderOverride(projectRoot, { id, disable }) {
|
|
@@ -240,6 +256,7 @@ module.exports = {
|
|
|
240
256
|
toggleReminderOverride,
|
|
241
257
|
toggleEnforcementMode,
|
|
242
258
|
toggleRunToCompletionReminder,
|
|
259
|
+
toggleCwdLock,
|
|
243
260
|
normalizeHandlerPath,
|
|
244
261
|
matchesOverride,
|
|
245
262
|
};
|
package/lib/panel.js
CHANGED
|
@@ -30,6 +30,7 @@ const {
|
|
|
30
30
|
toggleReminderOverride,
|
|
31
31
|
toggleEnforcementMode,
|
|
32
32
|
toggleRunToCompletionReminder,
|
|
33
|
+
toggleCwdLock,
|
|
33
34
|
normalizeHandlerPath,
|
|
34
35
|
} = require('./hook-overrides.js');
|
|
35
36
|
|
|
@@ -363,6 +364,14 @@ function getRunToCompletionInfo(cwd) {
|
|
|
363
364
|
};
|
|
364
365
|
}
|
|
365
366
|
|
|
367
|
+
function getCwdLockInfo(cwd) {
|
|
368
|
+
const overrides = readOverrides(cwd);
|
|
369
|
+
return {
|
|
370
|
+
on: overrides.cwdLockEnabled === true,
|
|
371
|
+
handler: 'CwdChanged/lock-cwd.js',
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
366
375
|
function getActivePlanInfo(cwd) {
|
|
367
376
|
const p = path.join(cwd, '.claudeflow', 'state', 'active-plan.json');
|
|
368
377
|
let raw;
|
|
@@ -618,6 +627,7 @@ function collectStatus(cwd) {
|
|
|
618
627
|
reminders: getRemindersInfo(cwd),
|
|
619
628
|
enforcement: getEnforcementInfo(cwd),
|
|
620
629
|
runToCompletion: getRunToCompletionInfo(cwd),
|
|
630
|
+
cwdLock: getCwdLockInfo(cwd),
|
|
621
631
|
observer: getObserverInfo(cwd),
|
|
622
632
|
activePlan: getActivePlanInfo(cwd),
|
|
623
633
|
logs: getLogsInfo(cwd),
|
|
@@ -734,6 +744,9 @@ details[open] summary { padding-bottom: 8px; border-bottom: 1px solid var(--bord
|
|
|
734
744
|
.logs-controls { display: flex; gap: 12px; align-items: center; margin-bottom: 14px; }
|
|
735
745
|
.logs-controls select, .logs-controls button { background: var(--panel); border: 1px solid var(--border); color: var(--fg); padding: 6px 10px; border-radius: 6px; font: inherit; }
|
|
736
746
|
.logs-controls button:hover { border-color: var(--accent); cursor: pointer; }
|
|
747
|
+
.observer-filter { background: var(--panel); border: 1px solid var(--border); color: var(--fg); padding: 4px 10px; border-radius: 999px; cursor: pointer; font-size: 12px; font: inherit; }
|
|
748
|
+
.observer-filter:hover { border-color: var(--accent, #6cf); }
|
|
749
|
+
.observer-filter.active { background: rgba(108,170,255,0.12); border-color: var(--accent, #6cf); color: var(--accent, #6cf); }
|
|
737
750
|
.btn-warn { background: var(--panel); border: 1px solid rgba(245,194,52,0.5); color: var(--warn, #f5c234); padding: 6px 12px; border-radius: 6px; cursor: pointer; font: inherit; }
|
|
738
751
|
.btn-warn:hover { border-color: var(--warn, #f5c234); background: rgba(245,194,52,0.08); }
|
|
739
752
|
.btn-warn:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
@@ -797,6 +810,11 @@ details[open] summary { padding-bottom: 8px; border-bottom: 1px solid var(--bord
|
|
|
797
810
|
<label class="enforce-switch"><input type="checkbox" id="run-to-completion-toggle" /><span class="enforce-slider"></span></label>
|
|
798
811
|
<span class="enforce-state" id="run-to-completion-state">…</span>
|
|
799
812
|
</span>
|
|
813
|
+
<span class="enforce-pill" id="cwd-lock-pill" title="Hard block — fires on Claude Code CwdChanged event. When the agent runs cd Subdir + cmd and the cwd would persist away from the project root, the change is blocked with a guidance message. Use (cd Subdir + cmd) subshells or absolute paths instead.">
|
|
814
|
+
<span class="enforce-label">Lock cwd</span>
|
|
815
|
+
<label class="enforce-switch"><input type="checkbox" id="cwd-lock-toggle" /><span class="enforce-slider"></span></label>
|
|
816
|
+
<span class="enforce-state" id="cwd-lock-state">…</span>
|
|
817
|
+
</span>
|
|
800
818
|
<label class="toggle"><input type="checkbox" id="auto" checked /> auto-refresh 5s</label>
|
|
801
819
|
<button id="refresh">Refresh</button>
|
|
802
820
|
</span>
|
|
@@ -829,6 +847,7 @@ const SECTIONS = [
|
|
|
829
847
|
|
|
830
848
|
let state = null;
|
|
831
849
|
let active = 'overview';
|
|
850
|
+
let observerFilter = 'all';
|
|
832
851
|
let timer = null;
|
|
833
852
|
|
|
834
853
|
// Preserve which <details> were open across re-renders (auto-refresh would
|
|
@@ -932,6 +951,20 @@ function renderHeader() {
|
|
|
932
951
|
} else if (rtcPill) {
|
|
933
952
|
rtcPill.style.display = 'none';
|
|
934
953
|
}
|
|
954
|
+
const lock = state.cwdLock;
|
|
955
|
+
const lockPill = document.getElementById('cwd-lock-pill');
|
|
956
|
+
const lockCb = document.getElementById('cwd-lock-toggle');
|
|
957
|
+
const lockLbl = document.getElementById('cwd-lock-state');
|
|
958
|
+
if (lock && lockPill) {
|
|
959
|
+
lockPill.dataset.state = lock.on ? 'on' : 'off';
|
|
960
|
+
lockCb.checked = lock.on;
|
|
961
|
+
lockLbl.textContent = lock.on ? 'ON' : 'OFF';
|
|
962
|
+
lockPill.title = lock.on
|
|
963
|
+
? 'Lock cwd is ON — any persistent change of working directory away from the project root is blocked via the CwdChanged hook. Use (cd Subdir + cmd) subshells or absolute paths.'
|
|
964
|
+
: 'Lock cwd is OFF — the agent can cd into subdirectories and the cwd will persist across tool calls. Click to enforce a single project-root cwd.';
|
|
965
|
+
} else if (lockPill) {
|
|
966
|
+
lockPill.style.display = 'none';
|
|
967
|
+
}
|
|
935
968
|
}
|
|
936
969
|
|
|
937
970
|
function severityFor(id) {
|
|
@@ -1424,11 +1457,54 @@ function renderObserver() {
|
|
|
1424
1457
|
</div>\`
|
|
1425
1458
|
: '<p class="muted">No browser snapshots received yet. (The observer collects when the agent navigates with mcp__claude-in-chrome__*.)</p>';
|
|
1426
1459
|
|
|
1460
|
+
// Build the filter index: for each surface (server label), find which
|
|
1461
|
+
// incidents belong to it. browser incidents are matched by origin URL
|
|
1462
|
+
// against each server's :PORT. Browser-other = browser incidents whose
|
|
1463
|
+
// origin doesn't match any configured server (e.g. external URL).
|
|
1464
|
+
const serverByOrigin = {};
|
|
1465
|
+
o.servers.forEach((srv) => {
|
|
1466
|
+
if (srv.port) serverByOrigin[\`localhost:\${srv.port}\`] = srv.label;
|
|
1467
|
+
if (srv.port) serverByOrigin[\`127.0.0.1:\${srv.port}\`] = srv.label;
|
|
1468
|
+
});
|
|
1469
|
+
function incidentBucket(inc) {
|
|
1470
|
+
if (inc.source === 'server') {
|
|
1471
|
+
return (inc.metadata && inc.metadata.label) || inc.surface || 'server-unknown';
|
|
1472
|
+
}
|
|
1473
|
+
if (inc.source === 'browser') {
|
|
1474
|
+
const url = (inc.metadata && (inc.metadata.url || inc.metadata.origin)) || '';
|
|
1475
|
+
const m = url.match(/^https?:\\/\\/([^/]+)/);
|
|
1476
|
+
if (m && serverByOrigin[m[1]]) return serverByOrigin[m[1]];
|
|
1477
|
+
return 'browser-other';
|
|
1478
|
+
}
|
|
1479
|
+
return inc.source || 'other';
|
|
1480
|
+
}
|
|
1481
|
+
const buckets = {};
|
|
1482
|
+
o.recent_incidents.forEach((inc) => {
|
|
1483
|
+
const b = incidentBucket(inc);
|
|
1484
|
+
if (!buckets[b]) buckets[b] = [];
|
|
1485
|
+
buckets[b].push(inc);
|
|
1486
|
+
});
|
|
1487
|
+
const surfacesInUse = Object.keys(buckets).sort();
|
|
1488
|
+
// Apply observerFilter client-side state. Default 'all' (no filter).
|
|
1489
|
+
const filteredIncidents = (typeof observerFilter === 'undefined' || observerFilter === 'all')
|
|
1490
|
+
? o.recent_incidents
|
|
1491
|
+
: (buckets[observerFilter] || []);
|
|
1492
|
+
|
|
1493
|
+
const filterPill = (label, value, count) => {
|
|
1494
|
+
const active = (typeof observerFilter === 'undefined' && value === 'all') || observerFilter === value;
|
|
1495
|
+
const cls = active ? 'observer-filter active' : 'observer-filter';
|
|
1496
|
+
return \`<button class="\${cls}" data-observer-filter="\${escapeHtml(value)}">\${escapeHtml(label)}<span class="muted" style="margin-left:6px;font-size:11px;">\${count}</span></button>\`;
|
|
1497
|
+
};
|
|
1498
|
+
const filterPills = '<div class="observer-filters" style="display:flex;gap:6px;flex-wrap:wrap;margin:8px 0;">' +
|
|
1499
|
+
filterPill('All', 'all', o.recent_incidents.length) +
|
|
1500
|
+
surfacesInUse.map((surface) => filterPill(surface, surface, buckets[surface].length)).join('') +
|
|
1501
|
+
'</div>';
|
|
1502
|
+
|
|
1427
1503
|
// Incidents feed
|
|
1428
|
-
const incidentsBlock =
|
|
1429
|
-
? '<p class="muted">No incidents
|
|
1504
|
+
const incidentsBlock = filteredIncidents.length === 0
|
|
1505
|
+
? '<p class="muted">No incidents in this filter.</p>'
|
|
1430
1506
|
: '<div class="incidents-feed">' +
|
|
1431
|
-
|
|
1507
|
+
filteredIncidents.map((inc) => {
|
|
1432
1508
|
const sevClass = inc.severity === 'error' || inc.severity === 'critical' ? 'err' : (inc.severity === 'warning' ? 'warn' : 'info');
|
|
1433
1509
|
const resolvedBadge = inc.resolved_at
|
|
1434
1510
|
? '<span class="badge ok" style="margin-left:6px;">resolved</span>'
|
|
@@ -1467,6 +1543,7 @@ function renderObserver() {
|
|
|
1467
1543
|
<h3 style="margin-top:18px;font-size:14px;">Browser channel</h3>
|
|
1468
1544
|
\${browserBlock}
|
|
1469
1545
|
<h3 style="margin-top:18px;font-size:14px;">Incidents (most recent \${o.recent_incidents.length})</h3>
|
|
1546
|
+
\${filterPills}
|
|
1470
1547
|
<div class="card" style="padding:0;">\${incidentsBlock}</div>\`;
|
|
1471
1548
|
}
|
|
1472
1549
|
|
|
@@ -1617,6 +1694,11 @@ document.addEventListener('click', (e) => {
|
|
|
1617
1694
|
if (t.id === 'clear-observer') {
|
|
1618
1695
|
return clearObserverAction(t);
|
|
1619
1696
|
}
|
|
1697
|
+
if (t.dataset && t.dataset.observerFilter) {
|
|
1698
|
+
observerFilter = t.dataset.observerFilter;
|
|
1699
|
+
renderContent();
|
|
1700
|
+
return;
|
|
1701
|
+
}
|
|
1620
1702
|
});
|
|
1621
1703
|
|
|
1622
1704
|
async function clearObserverAction(btn) {
|
|
@@ -1819,6 +1901,34 @@ document.getElementById('run-to-completion-toggle').onchange = (e) => {
|
|
|
1819
1901
|
toggleRunToCompletion(e.target.checked);
|
|
1820
1902
|
};
|
|
1821
1903
|
|
|
1904
|
+
async function toggleCwdLockUI(enable) {
|
|
1905
|
+
try {
|
|
1906
|
+
const r = await fetch('/api/cwd-lock/toggle', {
|
|
1907
|
+
method: 'POST',
|
|
1908
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1909
|
+
body: JSON.stringify({ enable }),
|
|
1910
|
+
});
|
|
1911
|
+
const result = await r.json();
|
|
1912
|
+
if (!r.ok || result.state === 'error') {
|
|
1913
|
+
showToast('Toggle failed: ' + (result.reason || result.error || 'unknown'), 'err');
|
|
1914
|
+
} else if (result.state === 'noop') {
|
|
1915
|
+
showToast('Cwd lock already in target state', 'ok');
|
|
1916
|
+
} else {
|
|
1917
|
+
showToast(enable
|
|
1918
|
+
? 'Cwd lock ON — cd away from project root will be BLOCKED on next CwdChanged.'
|
|
1919
|
+
: 'Cwd lock OFF — agent can freely change directory.',
|
|
1920
|
+
'ok');
|
|
1921
|
+
}
|
|
1922
|
+
} catch (e) {
|
|
1923
|
+
showToast('Network error: ' + e.message, 'err');
|
|
1924
|
+
}
|
|
1925
|
+
await refresh();
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
document.getElementById('cwd-lock-toggle').onchange = (e) => {
|
|
1929
|
+
toggleCwdLockUI(e.target.checked);
|
|
1930
|
+
};
|
|
1931
|
+
|
|
1822
1932
|
document.getElementById('refresh').onclick = refresh;
|
|
1823
1933
|
document.getElementById('auto').onchange = (e) => {
|
|
1824
1934
|
if (timer) clearInterval(timer);
|
|
@@ -1928,6 +2038,20 @@ function handler(cwd) {
|
|
|
1928
2038
|
return send(status, JSON.stringify(result), 'application/json');
|
|
1929
2039
|
}
|
|
1930
2040
|
|
|
2041
|
+
if (req.method === 'POST' && route === '/api/cwd-lock/toggle') {
|
|
2042
|
+
const raw = await readRequestBody(req);
|
|
2043
|
+
let payload;
|
|
2044
|
+
try { payload = JSON.parse(raw); }
|
|
2045
|
+
catch { return send(400, JSON.stringify({ error: 'invalid json' }), 'application/json'); }
|
|
2046
|
+
const { enable } = payload;
|
|
2047
|
+
if (typeof enable !== 'boolean') {
|
|
2048
|
+
return send(400, JSON.stringify({ error: 'enable required' }), 'application/json');
|
|
2049
|
+
}
|
|
2050
|
+
const result = toggleCwdLock(cwd, { enable });
|
|
2051
|
+
const status = result.state === 'error' ? 500 : 200;
|
|
2052
|
+
return send(status, JSON.stringify(result), 'application/json');
|
|
2053
|
+
}
|
|
2054
|
+
|
|
1931
2055
|
if (req.method === 'POST' && route === '/api/run-to-completion/toggle') {
|
|
1932
2056
|
const raw = await readRequestBody(req);
|
|
1933
2057
|
let payload;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiomatic-labs/claudeflow",
|
|
3
|
-
"version": "2.13.
|
|
3
|
+
"version": "2.13.66",
|
|
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"
|