@axiomatic-labs/claudeflow 2.13.65 → 2.13.67
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 +107 -11
- 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
|
|
|
@@ -330,11 +331,18 @@ function getObserverInfo(cwd) {
|
|
|
330
331
|
const unresolvedIncidents = summary && Array.isArray(summary.unresolved_incidents)
|
|
331
332
|
? summary.unresolved_incidents
|
|
332
333
|
: [];
|
|
333
|
-
const
|
|
334
|
+
const allInState = state && state.incidents && typeof state.incidents === 'object'
|
|
334
335
|
? Object.values(state.incidents)
|
|
335
336
|
.sort((a, b) => String(b.last_seen_at || '').localeCompare(String(a.last_seen_at || '')))
|
|
336
337
|
.slice(0, 50)
|
|
337
338
|
: [];
|
|
339
|
+
// Default surface to UI: ACTIVE only. "Clear observer state" resolves all
|
|
340
|
+
// active incidents, so after clear the active list is empty (which matches
|
|
341
|
+
// the user's expectation that "Clear" empties the feed). Resolved incidents
|
|
342
|
+
// are kept in state.incidents for audit and can be surfaced via the
|
|
343
|
+
// "Show resolved" filter pill.
|
|
344
|
+
const activeIncidents = allInState.filter((inc) => !inc.resolved_at && inc.active !== false);
|
|
345
|
+
const resolvedIncidents = allInState.filter((inc) => inc.resolved_at || inc.active === false);
|
|
338
346
|
|
|
339
347
|
return {
|
|
340
348
|
daemon,
|
|
@@ -346,7 +354,10 @@ function getObserverInfo(cwd) {
|
|
|
346
354
|
servers,
|
|
347
355
|
browser,
|
|
348
356
|
unresolved_incidents: unresolvedIncidents,
|
|
349
|
-
|
|
357
|
+
active_incidents: activeIncidents,
|
|
358
|
+
resolved_incidents: resolvedIncidents,
|
|
359
|
+
// Kept for backwards compat with older render code — same as active_incidents.
|
|
360
|
+
recent_incidents: activeIncidents,
|
|
350
361
|
totals: summary ? {
|
|
351
362
|
unresolved: summary.unresolved_incidents_total || 0,
|
|
352
363
|
browser: summary.browser_incidents_total || 0,
|
|
@@ -363,6 +374,14 @@ function getRunToCompletionInfo(cwd) {
|
|
|
363
374
|
};
|
|
364
375
|
}
|
|
365
376
|
|
|
377
|
+
function getCwdLockInfo(cwd) {
|
|
378
|
+
const overrides = readOverrides(cwd);
|
|
379
|
+
return {
|
|
380
|
+
on: overrides.cwdLockEnabled === true,
|
|
381
|
+
handler: 'CwdChanged/lock-cwd.js',
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
366
385
|
function getActivePlanInfo(cwd) {
|
|
367
386
|
const p = path.join(cwd, '.claudeflow', 'state', 'active-plan.json');
|
|
368
387
|
let raw;
|
|
@@ -618,6 +637,7 @@ function collectStatus(cwd) {
|
|
|
618
637
|
reminders: getRemindersInfo(cwd),
|
|
619
638
|
enforcement: getEnforcementInfo(cwd),
|
|
620
639
|
runToCompletion: getRunToCompletionInfo(cwd),
|
|
640
|
+
cwdLock: getCwdLockInfo(cwd),
|
|
621
641
|
observer: getObserverInfo(cwd),
|
|
622
642
|
activePlan: getActivePlanInfo(cwd),
|
|
623
643
|
logs: getLogsInfo(cwd),
|
|
@@ -800,6 +820,11 @@ details[open] summary { padding-bottom: 8px; border-bottom: 1px solid var(--bord
|
|
|
800
820
|
<label class="enforce-switch"><input type="checkbox" id="run-to-completion-toggle" /><span class="enforce-slider"></span></label>
|
|
801
821
|
<span class="enforce-state" id="run-to-completion-state">…</span>
|
|
802
822
|
</span>
|
|
823
|
+
<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.">
|
|
824
|
+
<span class="enforce-label">Lock cwd</span>
|
|
825
|
+
<label class="enforce-switch"><input type="checkbox" id="cwd-lock-toggle" /><span class="enforce-slider"></span></label>
|
|
826
|
+
<span class="enforce-state" id="cwd-lock-state">…</span>
|
|
827
|
+
</span>
|
|
803
828
|
<label class="toggle"><input type="checkbox" id="auto" checked /> auto-refresh 5s</label>
|
|
804
829
|
<button id="refresh">Refresh</button>
|
|
805
830
|
</span>
|
|
@@ -833,6 +858,7 @@ const SECTIONS = [
|
|
|
833
858
|
let state = null;
|
|
834
859
|
let active = 'overview';
|
|
835
860
|
let observerFilter = 'all';
|
|
861
|
+
let observerShowResolved = false;
|
|
836
862
|
let timer = null;
|
|
837
863
|
|
|
838
864
|
// Preserve which <details> were open across re-renders (auto-refresh would
|
|
@@ -936,6 +962,20 @@ function renderHeader() {
|
|
|
936
962
|
} else if (rtcPill) {
|
|
937
963
|
rtcPill.style.display = 'none';
|
|
938
964
|
}
|
|
965
|
+
const lock = state.cwdLock;
|
|
966
|
+
const lockPill = document.getElementById('cwd-lock-pill');
|
|
967
|
+
const lockCb = document.getElementById('cwd-lock-toggle');
|
|
968
|
+
const lockLbl = document.getElementById('cwd-lock-state');
|
|
969
|
+
if (lock && lockPill) {
|
|
970
|
+
lockPill.dataset.state = lock.on ? 'on' : 'off';
|
|
971
|
+
lockCb.checked = lock.on;
|
|
972
|
+
lockLbl.textContent = lock.on ? 'ON' : 'OFF';
|
|
973
|
+
lockPill.title = lock.on
|
|
974
|
+
? '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.'
|
|
975
|
+
: '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.';
|
|
976
|
+
} else if (lockPill) {
|
|
977
|
+
lockPill.style.display = 'none';
|
|
978
|
+
}
|
|
939
979
|
}
|
|
940
980
|
|
|
941
981
|
function severityFor(id) {
|
|
@@ -1428,10 +1468,13 @@ function renderObserver() {
|
|
|
1428
1468
|
</div>\`
|
|
1429
1469
|
: '<p class="muted">No browser snapshots received yet. (The observer collects when the agent navigates with mcp__claude-in-chrome__*.)</p>';
|
|
1430
1470
|
|
|
1431
|
-
//
|
|
1432
|
-
//
|
|
1433
|
-
//
|
|
1434
|
-
|
|
1471
|
+
// Active vs resolved toggle (client-side state observerShowResolved).
|
|
1472
|
+
// After "Clear observer state", all active incidents are marked resolved;
|
|
1473
|
+
// by default the UI shows only active. Toggle the pill to see history.
|
|
1474
|
+
const showResolved = (typeof observerShowResolved !== 'undefined') && observerShowResolved;
|
|
1475
|
+
const sourceList = showResolved ? o.resolved_incidents : o.active_incidents;
|
|
1476
|
+
|
|
1477
|
+
// Build the filter index ON THE CURRENT VIEW (active or resolved).
|
|
1435
1478
|
const serverByOrigin = {};
|
|
1436
1479
|
o.servers.forEach((srv) => {
|
|
1437
1480
|
if (srv.port) serverByOrigin[\`localhost:\${srv.port}\`] = srv.label;
|
|
@@ -1450,7 +1493,7 @@ function renderObserver() {
|
|
|
1450
1493
|
return inc.source || 'other';
|
|
1451
1494
|
}
|
|
1452
1495
|
const buckets = {};
|
|
1453
|
-
|
|
1496
|
+
sourceList.forEach((inc) => {
|
|
1454
1497
|
const b = incidentBucket(inc);
|
|
1455
1498
|
if (!buckets[b]) buckets[b] = [];
|
|
1456
1499
|
buckets[b].push(inc);
|
|
@@ -1458,7 +1501,7 @@ function renderObserver() {
|
|
|
1458
1501
|
const surfacesInUse = Object.keys(buckets).sort();
|
|
1459
1502
|
// Apply observerFilter client-side state. Default 'all' (no filter).
|
|
1460
1503
|
const filteredIncidents = (typeof observerFilter === 'undefined' || observerFilter === 'all')
|
|
1461
|
-
?
|
|
1504
|
+
? sourceList
|
|
1462
1505
|
: (buckets[observerFilter] || []);
|
|
1463
1506
|
|
|
1464
1507
|
const filterPill = (label, value, count) => {
|
|
@@ -1466,9 +1509,13 @@ function renderObserver() {
|
|
|
1466
1509
|
const cls = active ? 'observer-filter active' : 'observer-filter';
|
|
1467
1510
|
return \`<button class="\${cls}" data-observer-filter="\${escapeHtml(value)}">\${escapeHtml(label)}<span class="muted" style="margin-left:6px;font-size:11px;">\${count}</span></button>\`;
|
|
1468
1511
|
};
|
|
1469
|
-
|
|
1470
|
-
|
|
1512
|
+
// Show resolved toggle pill — separate from surface filters.
|
|
1513
|
+
const showResolvedClass = showResolved ? 'observer-filter active' : 'observer-filter';
|
|
1514
|
+
const showResolvedPill = \`<button class="\${showResolvedClass}" data-observer-show-resolved="toggle" style="margin-left:auto;">\${showResolved ? 'Showing resolved' : 'Show resolved'}<span class="muted" style="margin-left:6px;font-size:11px;">\${o.resolved_incidents.length}</span></button>\`;
|
|
1515
|
+
const filterPills = '<div class="observer-filters" style="display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin:8px 0;">' +
|
|
1516
|
+
filterPill('All', 'all', sourceList.length) +
|
|
1471
1517
|
surfacesInUse.map((surface) => filterPill(surface, surface, buckets[surface].length)).join('') +
|
|
1518
|
+
showResolvedPill +
|
|
1472
1519
|
'</div>';
|
|
1473
1520
|
|
|
1474
1521
|
// Incidents feed
|
|
@@ -1513,7 +1560,7 @@ function renderObserver() {
|
|
|
1513
1560
|
<div class="card" style="padding:0;">\${serversTable}</div>
|
|
1514
1561
|
<h3 style="margin-top:18px;font-size:14px;">Browser channel</h3>
|
|
1515
1562
|
\${browserBlock}
|
|
1516
|
-
<h3 style="margin-top:18px;font-size:14px;"
|
|
1563
|
+
<h3 style="margin-top:18px;font-size:14px;">\${showResolved ? 'Resolved incidents (' + o.resolved_incidents.length + ')' : 'Active incidents (' + o.active_incidents.length + ')'}</h3>
|
|
1517
1564
|
\${filterPills}
|
|
1518
1565
|
<div class="card" style="padding:0;">\${incidentsBlock}</div>\`;
|
|
1519
1566
|
}
|
|
@@ -1670,6 +1717,13 @@ document.addEventListener('click', (e) => {
|
|
|
1670
1717
|
renderContent();
|
|
1671
1718
|
return;
|
|
1672
1719
|
}
|
|
1720
|
+
if (t.dataset && t.dataset.observerShowResolved) {
|
|
1721
|
+
observerShowResolved = !observerShowResolved;
|
|
1722
|
+
// Reset surface filter when toggling, since counts change.
|
|
1723
|
+
observerFilter = 'all';
|
|
1724
|
+
renderContent();
|
|
1725
|
+
return;
|
|
1726
|
+
}
|
|
1673
1727
|
});
|
|
1674
1728
|
|
|
1675
1729
|
async function clearObserverAction(btn) {
|
|
@@ -1872,6 +1926,34 @@ document.getElementById('run-to-completion-toggle').onchange = (e) => {
|
|
|
1872
1926
|
toggleRunToCompletion(e.target.checked);
|
|
1873
1927
|
};
|
|
1874
1928
|
|
|
1929
|
+
async function toggleCwdLockUI(enable) {
|
|
1930
|
+
try {
|
|
1931
|
+
const r = await fetch('/api/cwd-lock/toggle', {
|
|
1932
|
+
method: 'POST',
|
|
1933
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1934
|
+
body: JSON.stringify({ enable }),
|
|
1935
|
+
});
|
|
1936
|
+
const result = await r.json();
|
|
1937
|
+
if (!r.ok || result.state === 'error') {
|
|
1938
|
+
showToast('Toggle failed: ' + (result.reason || result.error || 'unknown'), 'err');
|
|
1939
|
+
} else if (result.state === 'noop') {
|
|
1940
|
+
showToast('Cwd lock already in target state', 'ok');
|
|
1941
|
+
} else {
|
|
1942
|
+
showToast(enable
|
|
1943
|
+
? 'Cwd lock ON — cd away from project root will be BLOCKED on next CwdChanged.'
|
|
1944
|
+
: 'Cwd lock OFF — agent can freely change directory.',
|
|
1945
|
+
'ok');
|
|
1946
|
+
}
|
|
1947
|
+
} catch (e) {
|
|
1948
|
+
showToast('Network error: ' + e.message, 'err');
|
|
1949
|
+
}
|
|
1950
|
+
await refresh();
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
document.getElementById('cwd-lock-toggle').onchange = (e) => {
|
|
1954
|
+
toggleCwdLockUI(e.target.checked);
|
|
1955
|
+
};
|
|
1956
|
+
|
|
1875
1957
|
document.getElementById('refresh').onclick = refresh;
|
|
1876
1958
|
document.getElementById('auto').onchange = (e) => {
|
|
1877
1959
|
if (timer) clearInterval(timer);
|
|
@@ -1981,6 +2063,20 @@ function handler(cwd) {
|
|
|
1981
2063
|
return send(status, JSON.stringify(result), 'application/json');
|
|
1982
2064
|
}
|
|
1983
2065
|
|
|
2066
|
+
if (req.method === 'POST' && route === '/api/cwd-lock/toggle') {
|
|
2067
|
+
const raw = await readRequestBody(req);
|
|
2068
|
+
let payload;
|
|
2069
|
+
try { payload = JSON.parse(raw); }
|
|
2070
|
+
catch { return send(400, JSON.stringify({ error: 'invalid json' }), 'application/json'); }
|
|
2071
|
+
const { enable } = payload;
|
|
2072
|
+
if (typeof enable !== 'boolean') {
|
|
2073
|
+
return send(400, JSON.stringify({ error: 'enable required' }), 'application/json');
|
|
2074
|
+
}
|
|
2075
|
+
const result = toggleCwdLock(cwd, { enable });
|
|
2076
|
+
const status = result.state === 'error' ? 500 : 200;
|
|
2077
|
+
return send(status, JSON.stringify(result), 'application/json');
|
|
2078
|
+
}
|
|
2079
|
+
|
|
1984
2080
|
if (req.method === 'POST' && route === '/api/run-to-completion/toggle') {
|
|
1985
2081
|
const raw = await readRequestBody(req);
|
|
1986
2082
|
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.67",
|
|
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"
|