@axiomatic-labs/claudeflow 2.13.55 → 2.13.56

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/doctor.js CHANGED
@@ -129,6 +129,54 @@ function checkStaleLockfiles(cwd) {
129
129
  };
130
130
  }
131
131
 
132
+ // Read the browser-error-daemon observer state from its pidfile.
133
+ // Returns { running, pid, lockfile } — running=true only when the pid is
134
+ // alive AND the lockfile exists. Used by both doctor and panel.
135
+ function readObserverState(cwd, cdpPort) {
136
+ const lockfile = path.join(cwd, '.claudeflow', 'tmp', `.browser-cdp-${cdpPort}.pid`);
137
+ let pid = null;
138
+ try {
139
+ pid = parseInt(fs.readFileSync(lockfile, 'utf8').trim(), 10);
140
+ } catch {
141
+ return { running: false, pid: null, lockfile };
142
+ }
143
+ if (!Number.isFinite(pid) || pid <= 0) return { running: false, pid, lockfile };
144
+ let alive = false;
145
+ try { process.kill(pid, 0); alive = true; } catch (e) { alive = e.code === 'EPERM'; }
146
+ return { running: alive, pid, lockfile };
147
+ }
148
+
149
+ // Surface as a doctor/panel issue when the browser-error-daemon observer
150
+ // is stopped while Playwright is configured. The observer collects console
151
+ // errors + network failures during headed browser verification — when it's
152
+ // down, those signals are silently lost, so the agent may verify a flow
153
+ // that has console errors and not know it.
154
+ function checkObserverState(cwd) {
155
+ const mcpPath = path.join(cwd, '.mcp.json');
156
+ let cfg;
157
+ try { cfg = JSON.parse(fs.readFileSync(mcpPath, 'utf8')); }
158
+ catch { return { id: 'observer-state', severity: 'info', message: 'No .mcp.json — observer not applicable.' }; }
159
+ const hasPlaywright = cfg.mcpServers
160
+ && Object.keys(cfg.mcpServers).some((s) => s.toLowerCase().includes('playwright'));
161
+ if (!hasPlaywright) {
162
+ return { id: 'observer-state', severity: 'info', message: 'Playwright not configured — observer not applicable.' };
163
+ }
164
+ const observer = readObserverState(cwd, deriveCdpPort(cwd));
165
+ if (observer.running) {
166
+ return {
167
+ id: 'observer-state',
168
+ severity: 'ok',
169
+ message: `browser-error-daemon running (pid ${observer.pid}).`,
170
+ };
171
+ }
172
+ return {
173
+ id: 'observer-state',
174
+ severity: 'mismatch',
175
+ message: 'browser-error-daemon is STOPPED while Playwright is configured. Console errors / network failures from headed sessions are NOT being collected — the agent may verify a flow with console errors and miss them. The observer should auto-start at SessionStart via `.claude/hooks/SessionStart/browser-error-daemon.js`; if it keeps stopping, inspect that hook for crashes or check the pidfile at `.claudeflow/tmp/.browser-cdp-<port>.pid`.',
176
+ detail: [{ lockfile: observer.lockfile, pid: observer.pid }],
177
+ };
178
+ }
179
+
132
180
  function applyStaleLockfileFix(check) {
133
181
  for (const { file } of check.fix.stale) {
134
182
  try { fs.unlinkSync(file); } catch {}
@@ -163,6 +211,7 @@ async function run(argv = []) {
163
211
  const checks = [
164
212
  checkCdpPortMismatch(cwd),
165
213
  checkStaleLockfiles(cwd),
214
+ checkObserverState(cwd),
166
215
  ];
167
216
 
168
217
  for (const check of checks) printCheck(check);
@@ -202,6 +251,8 @@ module.exports = run;
202
251
  module.exports.deriveCdpPort = deriveCdpPort;
203
252
  module.exports.checkCdpPortMismatch = checkCdpPortMismatch;
204
253
  module.exports.checkStaleLockfiles = checkStaleLockfiles;
254
+ module.exports.checkObserverState = checkObserverState;
255
+ module.exports.readObserverState = readObserverState;
205
256
  module.exports.readPlaywrightCdpEndpoint = readPlaywrightCdpEndpoint;
206
257
  module.exports.applyCdpPortFix = applyCdpPortFix;
207
258
  module.exports.applyStaleLockfileFix = applyStaleLockfileFix;
package/lib/panel.js CHANGED
@@ -19,6 +19,8 @@ const {
19
19
  deriveCdpPort,
20
20
  checkCdpPortMismatch,
21
21
  checkStaleLockfiles,
22
+ checkObserverState,
23
+ readObserverState: readObserverStateFromDoctor,
22
24
  readPlaywrightCdpEndpoint,
23
25
  } = require('./doctor.js');
24
26
  const {
@@ -211,19 +213,10 @@ function getMcpInfo(cwd) {
211
213
  };
212
214
  }
213
215
 
214
- function readObserverState(cwd, cdpPort) {
215
- const lockfile = path.join(cwd, '.claudeflow', 'tmp', `.browser-cdp-${cdpPort}.pid`);
216
- let pid = null;
217
- try {
218
- pid = parseInt(fs.readFileSync(lockfile, 'utf8').trim(), 10);
219
- } catch {
220
- return { running: false, pid: null, lockfile };
221
- }
222
- if (!Number.isFinite(pid) || pid <= 0) return { running: false, pid, lockfile };
223
- let alive = false;
224
- try { process.kill(pid, 0); alive = true; } catch (e) { alive = e.code === 'EPERM'; }
225
- return { running: alive, pid, lockfile };
226
- }
216
+ // Re-export the canonical helper from doctor.js so panel callers keep the
217
+ // same name. The implementation moved to doctor.js so both surfaces (CLI
218
+ // doctor + panel) share a single source of truth.
219
+ const readObserverState = readObserverStateFromDoctor;
227
220
 
228
221
  function getSetupContextInfo(cwd) {
229
222
  const p = path.join(cwd, '.claudeflow', 'config', 'setup-context.json');
@@ -524,7 +517,11 @@ function getLogsInfo(cwd) {
524
517
  }
525
518
 
526
519
  function getDoctorInfo(cwd) {
527
- const checks = [checkCdpPortMismatch(cwd), checkStaleLockfiles(cwd)];
520
+ const checks = [
521
+ checkCdpPortMismatch(cwd),
522
+ checkStaleLockfiles(cwd),
523
+ checkObserverState(cwd),
524
+ ];
528
525
  const issues = checks.filter((c) => c.severity !== 'ok' && c.severity !== 'info');
529
526
  return {
530
527
  issueCount: issues.length,
@@ -872,7 +869,15 @@ function severityFor(id) {
872
869
  if (s.logs.latestReminderStatus === 'heavy') return 'warn';
873
870
  return s.logs.total > 0 ? 'ok' : 'info';
874
871
  }
875
- case 'mcp': return !s.mcp.configFound ? 'info' : (s.mcp.playwright.match && s.mcp.staleLockfiles.length === 0 ? 'ok' : 'warn');
872
+ case 'mcp': {
873
+ if (!s.mcp.configFound) return 'info';
874
+ const playwrightOk = s.mcp.playwright.match && s.mcp.staleLockfiles.length === 0;
875
+ // Observer stopped while Playwright is configured = warn. Console errors
876
+ // and network failures from headed sessions are silently lost when the
877
+ // observer is down.
878
+ const observerOk = !s.mcp.playwright.match || (s.mcp.observer && s.mcp.observer.running);
879
+ return playwrightOk && observerOk ? 'ok' : 'warn';
880
+ }
876
881
  case 'setupContext': return !s.setupContext.exists ? 'err' : (s.setupContext.toolingComplete ? 'ok' : 'warn');
877
882
  case 'activeRun': return s.activeRun.active ? 'info' : 'info';
878
883
  case 'doctor': return s.doctor.issueCount > 0 ? 'warn' : 'ok';
@@ -1294,7 +1299,7 @@ function renderMcp() {
1294
1299
  \${row('Playwright --cdp-endpoint port', String(m.playwright.configured || 'n/a'))}
1295
1300
  \${row('Computed port (from path)', String(m.playwright.computed))}
1296
1301
  \${row('Match', m.playwright.match ? 'YES' : 'NO', { kind: m.playwright.match ? 'ok' : 'err', text: m.playwright.match ? '✓' : '✗' })}
1297
- \${row('Observer', m.observer.running ? 'running (pid '+m.observer.pid+')' : 'stopped', { kind: m.observer.running ? 'ok' : 'info', text: m.observer.running ? '✓' : '·' })}
1302
+ \${row('Observer', m.observer.running ? 'running (pid '+m.observer.pid+')' : 'STOPPED — browser errors NOT being collected', { kind: m.observer.running ? 'ok' : (m.playwright.match ? 'warn' : 'info'), text: m.observer.running ? '✓' : (m.playwright.match ? '⚠' : '·') })}
1298
1303
  </div>
1299
1304
  <h2 style="margin-top:18px">Stale lockfiles</h2>
1300
1305
  \${lockfiles}\`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axiomatic-labs/claudeflow",
3
- "version": "2.13.55",
3
+ "version": "2.13.56",
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"